1919
2020import static org .junit .jupiter .api .Assertions .assertEquals ;
2121import static org .junit .jupiter .api .Assertions .assertNotNull ;
22+ import static org .junit .jupiter .api .Assertions .assertNull ;
2223import static org .junit .jupiter .api .Assertions .assertSame ;
2324import static org .junit .jupiter .api .Assertions .assertThrows ;
2425import static org .junit .jupiter .api .Assertions .assertTrue ;
2829import java .nio .file .Path ;
2930import java .nio .file .Paths ;
3031
32+ import org .apache .commons .lang3 .StringUtils ;
3133import org .junit .jupiter .api .Test ;
3234import org .junit .jupiter .api .io .TempDir ;
3335import org .junit .jupiter .params .ParameterizedTest ;
@@ -42,16 +44,29 @@ private Path createDirectory(final Path tempDir, final String other) throws IOEx
4244 return Files .createDirectory (tempDir .resolve (other ));
4345 }
4446
47+ private Path getRelPathToTop () {
48+ final Path startPath = PathUtils .current ().toAbsolutePath ();
49+ final Path parent = startPath ;
50+ final int nameCount = parent .getNameCount ();
51+ final String relName = StringUtils .repeat ("../" , nameCount );
52+ final Path relPath = Paths .get (relName );
53+ // sanity checks
54+ final Path rootPath = relPath .toAbsolutePath ().normalize ();
55+ assertNull (rootPath .getFileName ());
56+ assertEquals (startPath .getRoot (), rootPath );
57+ return relPath ;
58+ }
59+
4560 @ Test
4661 void testAbsolutePath (@ TempDir final Path fenceRootPath ) throws Exception {
4762 // tempDir is the fence
48- final Path childTest = fenceRootPath .resolve ("child/file.txt" );
63+ final Path resolved = fenceRootPath .resolve ("child/file.txt" );
4964 final PathFence fence = PathFence .builder ().setRoots (fenceRootPath ).get ();
5065 // getPath with an absolute string should be allowed
51- final Path childOk = fence .apply (childTest .toString ());
52- assertEquals (childTest .toAbsolutePath ().normalize (), childOk .toAbsolutePath ().normalize ());
66+ final Path childOk = fence .apply (resolved .toString ());
67+ assertEquals (resolved .toAbsolutePath ().normalize (), childOk .toAbsolutePath ().normalize ());
5368 // check with a Path instance should return the same instance when allowed
54- assertSame (childTest , fence .apply (childTest ));
69+ assertSame (resolved , fence .apply (resolved ));
5570 }
5671
5772 @ ParameterizedTest
@@ -64,6 +79,16 @@ void testEmpty(final String test) {
6479 assertSame (path , fence .apply (path ));
6580 }
6681
82+ private void testEscapeAttempt (final Path fenceRootPath ) {
83+ final Path resolved = fenceRootPath .resolve ("../../etc/passwd" );
84+ final Path relative = Paths .get ("../../etc/passwd" );
85+ final PathFence fence = PathFence .builder ().setRoots (fenceRootPath ).get ();
86+ assertThrows (IllegalArgumentException .class , () -> fence .apply (resolved ));
87+ assertThrows (IllegalArgumentException .class , () -> fence .apply (relative ));
88+ assertThrows (IllegalArgumentException .class , () -> fence .apply (resolved .toString ()));
89+ assertThrows (IllegalArgumentException .class , () -> fence .apply (relative .toString ()));
90+ }
91+
6792 @ Test
6893 void testMultipleFencePaths (@ TempDir final Path tempDir ) throws Exception {
6994 // The fence is inside tempDir
@@ -81,10 +106,9 @@ void testMultipleFencePaths(@TempDir final Path tempDir) throws Exception {
81106 @ Test
82107 void testNormalization (@ TempDir final Path tempDir ) throws Exception {
83108 final Path fenceRootPath = createDirectory (tempDir , "root-one" );
84- final Path weird = fenceRootPath .resolve ("subdir/../other.txt" );
109+ final Path resolved = fenceRootPath .resolve ("subdir/../other.txt" );
85110 final PathFence fence = PathFence .builder ().setRoots (fenceRootPath ).get ();
86- // Path contains '..' but after normalization it's still inside the base
87- assertSame (weird , fence .apply (weird ));
111+ assertSame (resolved , fence .apply (resolved ));
88112 }
89113
90114 @ Test
@@ -98,4 +122,27 @@ void testOutsideFenceThrows(@TempDir final Path tempDir) throws Exception {
98122 assertTrue (msg .contains ("not in the fence" ), () -> "Expected message to mention fence: " + msg );
99123 assertTrue (msg .contains (other .toAbsolutePath ().toString ()), () -> "Expected message to contain the path: " + msg );
100124 }
125+
126+ @ Test
127+ void testResolveRelative () throws Exception {
128+ final PathFence fence = PathFence .builder ().setRoots (Paths .get ("/foo/bar" )).get ();
129+ final Path relPathTop = getRelPathToTop ();
130+ final Path relPath = relPathTop .resolve ("foo/bar" );
131+ assertSame (relPath , fence .apply (relPath ));
132+ }
133+
134+ @ Test
135+ void testResolveRelativeRoot () throws Exception {
136+ final Path relPathTop = getRelPathToTop ();
137+ final PathFence fence = PathFence .builder ().setRoots (relPathTop .resolve ("foo/bar" )).get ();
138+ final Path relPath = relPathTop .resolve ("foo/bar" );
139+ assertSame (relPath , fence .apply (relPath ));
140+ }
141+
142+ @ ParameterizedTest
143+ @ ValueSource (strings = { "/a" , "/a/b" , "/a/b/c" , "/a/b/c/d" , "a" , "a/b" , "a/b/c" , "a/b/c/d" })
144+ void testUserHomeDirEscapeAttempt (final String path ) throws Exception {
145+ testEscapeAttempt (Paths .get (path ));
146+ }
147+
101148}
0 commit comments