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, 2018, Oracle and/or its affiliates. All rights reserved.
23  */
24 package org.opengrok.indexer.util;
25 
26 import static org.junit.Assert.assertEquals;
27 import static org.junit.Assert.assertTrue;
28 
29 import java.io.File;
30 import java.io.IOException;
31 import java.nio.file.Files;
32 import java.nio.file.Path;
33 import java.nio.file.Paths;
34 import java.util.ArrayList;
35 import java.util.HashSet;
36 import java.util.List;
37 import java.util.Set;
38 import org.junit.After;
39 import org.junit.Assert;
40 import org.junit.Ignore;
41 import org.junit.Rule;
42 import org.junit.Test;
43 import org.opengrok.indexer.condition.ConditionalRun;
44 import org.opengrok.indexer.condition.ConditionalRunRule;
45 import org.opengrok.indexer.condition.UnixPresent;
46 
47 /**
48  * Represents a container for tests of {@link PathUtils}.
49  */
50 public class PathUtilsTest {
51 
52     private final List<File> tempDirs = new ArrayList<>();
53 
54     @Rule
55     public ConditionalRunRule rule = new ConditionalRunRule();
56 
57     @After
tearDown()58     public void tearDown() {
59         try {
60             tempDirs.forEach((tempDir) -> {
61                 try {
62                     IOUtils.removeRecursive(tempDir.toPath());
63                 } catch (IOException e) {
64                     // ignore
65                 }
66             });
67         } finally {
68             tempDirs.clear();
69         }
70     }
71 
72     @Test
shouldHandleSameInputs()73     public void shouldHandleSameInputs() throws IOException {
74         final String USR_BIN = Paths.get("/usr/bin").toString();
75         String rel = PathUtils.getRelativeToCanonical(USR_BIN, USR_BIN);
76         Assert.assertEquals(USR_BIN + " rel to itself", "", rel);
77     }
78 
79     @Test
shouldHandleEffectivelySameInputs()80     public void shouldHandleEffectivelySameInputs() throws IOException {
81         String USR_BIN = Paths.get(Paths.get("/usr/bin").toUri()).toString();
82         String rel = PathUtils.getRelativeToCanonical(USR_BIN + File.separator, USR_BIN);
83         Assert.assertEquals(USR_BIN + " rel to ~itself", "", rel);
84     }
85 
86     @Test
87     @ConditionalRun(UnixPresent.class)
shouldHandleLinksOfArbitraryDepthWithValidation()88     public void shouldHandleLinksOfArbitraryDepthWithValidation()
89             throws IOException, ForbiddenSymlinkException {
90         // Create real directories
91         File sourceRoot = createTemporaryDirectory("srcroot");
92         assertTrue("sourceRoot.isDirectory()", sourceRoot.isDirectory());
93 
94         File realDir1 = createTemporaryDirectory("realdir1");
95         assertTrue("realDir1.isDirectory()", realDir1.isDirectory());
96         File realDir1b = new File(realDir1, "b");
97         realDir1b.mkdir();
98         assertTrue("realDir1b.isDirectory()", realDir1b.isDirectory());
99 
100         File realDir2 = createTemporaryDirectory("realdir2");
101         assertTrue("realDir2.isDirectory()", realDir2.isDirectory());
102 
103         // Create symlink #1 underneath source root.
104         final String SYMLINK1 = "symlink1";
105         File symlink1 = new File(sourceRoot, SYMLINK1);
106         Files.createSymbolicLink(Paths.get(symlink1.getPath()),
107             Paths.get(realDir1.getPath()));
108         assertTrue("symlink1.exists()", symlink1.exists());
109 
110         // Create symlink #2 underneath realdir1/b.
111         final String SYMLINK2 = "symlink2";
112         File symlink2 = new File(realDir1b, SYMLINK2);
113         Files.createSymbolicLink(Paths.get(symlink2.getPath()),
114             Paths.get(realDir2.getPath()));
115         assertTrue("symlink2.exists()", symlink2.exists());
116 
117         // Assert symbolic path
118         Path sympath = Paths.get(sourceRoot.getPath(), SYMLINK1, "b",
119             SYMLINK2);
120         assertTrue("2-link path exists", Files.exists(sympath));
121 
122         // Test v. realDir1 canonical
123         String realDir1Canon = realDir1.getCanonicalPath();
124         String rel = PathUtils.getRelativeToCanonical(sympath.toString(),
125             realDir1Canon);
126         assertEquals("because links aren't validated", "b/" + SYMLINK2, rel);
127 
128         // Test v. realDir1 canonical with validation and no allowed links
129         Set<String> allowedSymLinks = new HashSet<>();
130         ForbiddenSymlinkException expex = null;
131         try {
132             PathUtils.getRelativeToCanonical(sympath.toString(), realDir1Canon,
133                     allowedSymLinks, null);
134         } catch (ForbiddenSymlinkException e) {
135             expex = e;
136         }
137         Assert.assertNotNull("because no links allowed, arg1 is returned fully",
138             expex);
139 
140         // Test v. realDir1 canonical with validation and an allowed link
141         allowedSymLinks.add(symlink2.getPath());
142         rel = PathUtils.getRelativeToCanonical(sympath.toString(),
143                 realDir1Canon, allowedSymLinks, null);
144         assertEquals("because link is OKed", "b/" + SYMLINK2, rel);
145     }
146 
147     @Test
148     @ConditionalRun(UnixPresent.class)
shouldHandleLinksToCanonicalChildrenOfAllowedLinks()149     public void shouldHandleLinksToCanonicalChildrenOfAllowedLinks()
150             throws IOException, ForbiddenSymlinkException {
151         // Create real directories
152         File sourceRoot = createTemporaryDirectory("srcroot");
153         assertTrue(sourceRoot + " should be a dir", sourceRoot.isDirectory());
154 
155         File realDir1 = createTemporaryDirectory("realdir1");
156         assertTrue(realDir1 + " should be a dir", realDir1.isDirectory());
157 
158         File realDir1b = new File(realDir1, "b");
159         assertTrue(realDir1b + " should be created", realDir1b.mkdir());
160 
161         // Create symlink #1 to realdir1/ in source root.
162         final String SYMLINK1 = "symlink1";
163         File symlink1 = new File(sourceRoot, SYMLINK1);
164         Files.createSymbolicLink(symlink1.toPath(), realDir1.toPath());
165         assertTrue(symlink1 + " should exist", symlink1.exists());
166 
167         // Create symlink #2 to realdir1/b in source root.
168         final String SYMLINK2 = "symlink2";
169         File symlink2 = new File(realDir1b, SYMLINK2);
170         Files.createSymbolicLink(symlink2.toPath(), realDir1b.toPath());
171         assertTrue(symlink2 + " should exist", symlink2.exists());
172 
173         // Test symlink2 v. realDir1 canonical with validation and an allowed symlink1
174         Set<String> allowedSymLinks = new HashSet<>();
175         allowedSymLinks.add(symlink1.getPath());
176 
177         String realDir1Canon = realDir1.getCanonicalPath();
178         String rel = PathUtils.getRelativeToCanonical(symlink2.toString(),
179                 realDir1Canon, allowedSymLinks, null);
180         assertEquals("symlink2 should be allowed implicitly as a canonical child of symlink1",
181                 "b", rel);
182     }
183 
184     @Ignore("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 = PathUtils.getRelativeToCanonical(MY_VAR_FOLDERS,
191             "/private/var");
192         Assert.assertEquals("/var/run rel to /private/var", EXPECTED_REL, rel);
193     }
194 
195     @Ignore("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 = PathUtils.getRelativeToCanonical(MY_VAR_FOLDERS,
203             "/private/var");
204         Assert.assertEquals("/var/run rel to /private/var", EXPECTED_REL, rel);
205     }
206 
createTemporaryDirectory(String name)207     private File createTemporaryDirectory(String name) throws IOException {
208         File f = Files.createTempDirectory(name).toFile();
209         tempDirs.add(f);
210         return f;
211     }
212 }
213