xref: /OpenGrok/opengrok-indexer/src/test/java/org/opengrok/indexer/util/PathUtilsTest.java (revision 52d10766ed1db3b0fd2c59a0da7292a32f244b50)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * See LICENSE.txt included in this distribution for the specific
9  * language governing permissions and limitations under the License.
10  *
11  * When distributing Covered Code, include this CDDL HEADER in each
12  * file and include the License file at LICENSE.txt.
13  * If applicable, add the following below this CDDL HEADER, with the
14  * fields enclosed by brackets "[]" replaced with your own identifying
15  * information: Portions Copyright [yyyy] [name of copyright owner]
16  *
17  * CDDL HEADER END
18  */
19 
20 /*
21  * Copyright (c) 2017, 2019, Chris Fraire <cfraire@me.com>.
22  * Copyright (c) 2008, 2021, Oracle and/or its affiliates. All rights reserved.
23  */
24 package org.opengrok.indexer.util;
25 
26 import org.junit.jupiter.api.AfterEach;
27 import org.junit.jupiter.api.Disabled;
28 import org.junit.jupiter.api.Test;
29 import org.junit.jupiter.api.condition.EnabledOnOs;
30 import org.junit.jupiter.api.condition.OS;
31 
32 import java.io.File;
33 import java.io.IOException;
34 import java.nio.file.Files;
35 import java.nio.file.Path;
36 import java.nio.file.Paths;
37 import java.util.ArrayList;
38 import java.util.HashSet;
39 import java.util.List;
40 import java.util.Set;
41 
42 import static org.junit.jupiter.api.Assertions.assertEquals;
43 import static org.junit.jupiter.api.Assertions.assertNotNull;
44 import static org.junit.jupiter.api.Assertions.assertTrue;
45 
46 /**
47  * Represents a container for tests of {@link PathUtils}.
48  */
49 public class PathUtilsTest {
50 
51     private final List<File> tempDirs = new ArrayList<>();
52 
relativeToCanonical(String path, String canonical)53     private String relativeToCanonical(String path, String canonical) throws IOException {
54         return PathUtils.getRelativeToCanonical(Paths.get(path), Paths.get(canonical));
55     }
56 
relativeToCanonical(String path, String canonical, Set<String> allowedSymlinks, Set<String> canonicalRoots)57     private String relativeToCanonical(String path, String canonical, Set<String> allowedSymlinks,
58         Set<String> canonicalRoots)
59         throws IOException, ForbiddenSymlinkException {
60         return PathUtils.getRelativeToCanonical(Paths.get(path), Paths.get(canonical), allowedSymlinks, canonicalRoots);
61     }
62 
63     @AfterEach
tearDown()64     public void tearDown() {
65         try {
66             tempDirs.forEach((tempDir) -> {
67                 try {
68                     IOUtils.removeRecursive(tempDir.toPath());
69                 } catch (IOException e) {
70                     // ignore
71                 }
72             });
73         } finally {
74             tempDirs.clear();
75         }
76     }
77 
78     @Test
shouldHandleSameInputs()79     public void shouldHandleSameInputs() throws IOException {
80         final String USR_BIN = Paths.get("/usr/bin").toString();
81         String rel = relativeToCanonical(USR_BIN, USR_BIN);
82         assertEquals("", rel, USR_BIN + " rel to itself");
83     }
84 
85     @Test
shouldHandleEffectivelySameInputs()86     public void shouldHandleEffectivelySameInputs() throws IOException {
87         String USR_BIN = Paths.get(Paths.get("/usr/bin").toUri()).toString();
88         String rel = relativeToCanonical(USR_BIN + File.separator, USR_BIN);
89         assertEquals("", rel, USR_BIN + " rel to ~itself");
90     }
91 
92     @Test
93     @EnabledOnOs({OS.LINUX, OS.MAC, OS.SOLARIS, OS.AIX, OS.OTHER})
shouldHandleLinksOfArbitraryDepthWithValidation()94     public void shouldHandleLinksOfArbitraryDepthWithValidation()
95         throws IOException, ForbiddenSymlinkException {
96         // Create real directories
97         File sourceRoot = createTemporaryDirectory("srcroot");
98         assertTrue(sourceRoot.isDirectory(), "sourceRoot.isDirectory()");
99 
100         File realDir1 = createTemporaryDirectory("realdir1");
101         assertTrue(realDir1.isDirectory(), "realDir1.isDirectory()");
102         File realDir1b = new File(realDir1, "b");
103         realDir1b.mkdir();
104         assertTrue(realDir1b.isDirectory(), "realDir1b.isDirectory()");
105 
106         File realDir2 = createTemporaryDirectory("realdir2");
107         assertTrue(realDir2.isDirectory(), "realDir2.isDirectory()");
108 
109         // Create symlink #1 underneath source root (srcroot/symlink1 -> realdir1)
110         final String SYMLINK1 = "symlink1";
111         File symlink1 = new File(sourceRoot, SYMLINK1);
112         Files.createSymbolicLink(Paths.get(symlink1.getPath()),
113             Paths.get(realDir1.getPath()));
114         assertTrue(symlink1.exists(), "symlink1.exists()");
115 
116         // Create symlink #2 underneath realdir1/b (realdir1/b/symlink2 -> realdir2)
117         final String SYMLINK2 = "symlink2";
118         File symlink2 = new File(realDir1b, SYMLINK2);
119         Files.createSymbolicLink(Paths.get(symlink2.getPath()), Paths.get(realDir2.getPath()));
120         assertTrue(symlink2.exists(), "symlink2.exists()");
121 
122         // Assert symbolic path srcroot/symlink1/b/symlink2
123         Path sympath = Paths.get(sourceRoot.getPath(), SYMLINK1, "b", SYMLINK2);
124         assertTrue(Files.exists(sympath), "2-link path exists");
125 
126         // Test v. realDir1 canonical
127         String realDir1Canon = realDir1.getCanonicalPath();
128         String rel = relativeToCanonical(sympath.toString(), realDir1Canon);
129         assertEquals("b/" + SYMLINK2, rel, "because links aren't validated");
130 
131         // Test v. realDir1 canonical with validation and no allowed links
132         Set<String> allowedSymLinks = new HashSet<>();
133         ForbiddenSymlinkException expex = null;
134         try {
135             relativeToCanonical(sympath.toString(), realDir1Canon,
136                     allowedSymLinks, null);
137         } catch (ForbiddenSymlinkException e) {
138             expex = e;
139         }
140         assertNotNull(expex, "because no links allowed, arg1 is returned fully");
141 
142         // Test v. realDir1 canonical with validation and an allowed link
143         allowedSymLinks.add(symlink2.getPath());
144         rel = relativeToCanonical(sympath.toString(),
145                 realDir1Canon, allowedSymLinks, null);
146         assertEquals("b/" + SYMLINK2, rel, "because link is OKed");
147     }
148 
149     @Test
150     @EnabledOnOs({OS.LINUX, OS.MAC, OS.SOLARIS, OS.AIX, OS.OTHER})
shouldHandleLinksToCanonicalChildrenOfAllowedLinks()151     public void shouldHandleLinksToCanonicalChildrenOfAllowedLinks()
152             throws IOException, ForbiddenSymlinkException {
153         // Create real directories
154         File sourceRoot = createTemporaryDirectory("srcroot");
155         assertTrue(sourceRoot.isDirectory(), sourceRoot + " should be a dir");
156 
157         File realDir1 = createTemporaryDirectory("realdir1");
158         assertTrue(realDir1.isDirectory(), realDir1 + " should be a dir");
159 
160         File realDir1b = new File(realDir1, "b");
161         assertTrue(realDir1b.mkdir(), realDir1b + " should be created");
162 
163         // Create symlink #1 to realdir1/ in source root.
164         final String SYMLINK1 = "symlink1";
165         File symlink1 = new File(sourceRoot, SYMLINK1);
166         Files.createSymbolicLink(symlink1.toPath(), realDir1.toPath());
167         assertTrue(symlink1.exists(), symlink1 + " should exist");
168 
169         // Create symlink #2 to realdir1/b in source root.
170         final String SYMLINK2 = "symlink2";
171         File symlink2 = new File(realDir1b, SYMLINK2);
172         Files.createSymbolicLink(symlink2.toPath(), realDir1b.toPath());
173         assertTrue(symlink2.exists(), symlink2 + " should exist");
174 
175         // Test symlink2 v. realDir1 canonical with validation and an allowed symlink1
176         Set<String> allowedSymLinks = new HashSet<>();
177         allowedSymLinks.add(symlink1.getPath());
178 
179         String realDir1Canon = realDir1.getCanonicalPath();
180         String rel = relativeToCanonical(symlink2.toString(), realDir1Canon, allowedSymLinks, null);
181         assertEquals("b", rel, "symlink2 should be allowed implicitly as a canonical child of symlink1");
182     }
183 
184     @Disabled("macOS has /var symlink, and I also made a second link, `myhome'.")
185     @Test
shouldResolvePrivateVarOnMacOS()186     public void shouldResolvePrivateVarOnMacOS() throws IOException {
187         final String MY_VAR_FOLDERS =
188             "/var/folders/58/546k9lk08xl56t0059bln0_h0000gp/T/tilde/Documents";
189         final String EXPECTED_REL = MY_VAR_FOLDERS.substring("/var/".length());
190         String rel = relativeToCanonical(MY_VAR_FOLDERS,
191             "/private/var");
192         assertEquals(EXPECTED_REL, rel, "/var/run rel to /private/var");
193     }
194 
195     @Disabled("For ad-hoc testing with \\ in paths.")
196     @Test
mightResolveBackslashesToo()197     public void mightResolveBackslashesToo() throws IOException {
198         final String MY_VAR_FOLDERS =
199             "\\var\\folders\\58\\546k9lk08xl56t0059bln0_h0000gp\\T";
200         final String EXPECTED_REL = MY_VAR_FOLDERS.substring("/var/".length()).
201             replace('\\', '/');
202         String rel = relativeToCanonical(MY_VAR_FOLDERS, "/private/var");
203         assertEquals(EXPECTED_REL, rel, "/var/run rel to /private/var");
204     }
205 
createTemporaryDirectory(String name)206     private File createTemporaryDirectory(String name) throws IOException {
207         File f = Files.createTempDirectory(name).toFile();
208         tempDirs.add(f);
209         return f;
210     }
211 }
212