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