2828import java .nio .file .Path ;
2929import java .nio .file .Paths ;
3030
31+ import org .apache .commons .lang3 .StringUtils ;
3132import org .junit .jupiter .api .Test ;
3233import org .junit .jupiter .api .io .TempDir ;
3334import org .junit .jupiter .params .ParameterizedTest ;
@@ -42,16 +43,29 @@ private Path createDirectory(final Path tempDir, final String other) throws IOEx
4243 return Files .createDirectory (tempDir .resolve (other ));
4344 }
4445
46+ private Path getRelPathToTop () {
47+ final Path startPath = PathUtils .current ().toAbsolutePath ();
48+ final Path parent = startPath ;
49+ final int nameCount = parent .getNameCount ();
50+ final String relName = StringUtils .repeat ("../" , nameCount );
51+ final Path relPath = Paths .get (relName );
52+ // sanity checks
53+ final Path rootPath = relPath .toAbsolutePath ().normalize ();
54+ assertEquals ("/" , rootPath .toString ());
55+ assertEquals (startPath .getRoot (), rootPath );
56+ return relPath ;
57+ }
58+
4559 @ Test
4660 void testAbsolutePath (@ TempDir final Path fenceRootPath ) throws Exception {
4761 // tempDir is the fence
48- final Path childTest = fenceRootPath .resolve ("child/file.txt" );
62+ final Path resolved = fenceRootPath .resolve ("child/file.txt" );
4963 final PathFence fence = PathFence .builder ().setRoots (fenceRootPath ).get ();
5064 // getPath with an absolute string should be allowed
51- final Path childOk = fence .apply (childTest .toString ());
52- assertEquals (childTest .toAbsolutePath ().normalize (), childOk .toAbsolutePath ().normalize ());
65+ final Path childOk = fence .apply (resolved .toString ());
66+ assertEquals (resolved .toAbsolutePath ().normalize (), childOk .toAbsolutePath ().normalize ());
5367 // check with a Path instance should return the same instance when allowed
54- assertSame (childTest , fence .apply (childTest ));
68+ assertSame (resolved , fence .apply (resolved ));
5569 }
5670
5771 @ ParameterizedTest
@@ -64,6 +78,16 @@ void testEmpty(final String test) {
6478 assertSame (path , fence .apply (path ));
6579 }
6680
81+ private void testEscapeAttempt (final Path fenceRootPath ) {
82+ final Path resolved = fenceRootPath .resolve ("../../etc/passwd" );
83+ final Path relative = Paths .get ("../../etc/passwd" );
84+ final PathFence fence = PathFence .builder ().setRoots (fenceRootPath ).get ();
85+ assertThrows (IllegalArgumentException .class , () -> fence .apply (resolved ));
86+ assertThrows (IllegalArgumentException .class , () -> fence .apply (relative ));
87+ assertThrows (IllegalArgumentException .class , () -> fence .apply (resolved .toString ()));
88+ assertThrows (IllegalArgumentException .class , () -> fence .apply (relative .toString ()));
89+ }
90+
6791 @ Test
6892 void testMultipleFencePaths (@ TempDir final Path tempDir ) throws Exception {
6993 // The fence is inside tempDir
@@ -81,10 +105,9 @@ void testMultipleFencePaths(@TempDir final Path tempDir) throws Exception {
81105 @ Test
82106 void testNormalization (@ TempDir final Path tempDir ) throws Exception {
83107 final Path fenceRootPath = createDirectory (tempDir , "root-one" );
84- final Path weird = fenceRootPath .resolve ("subdir/../other.txt" );
108+ final Path resolved = fenceRootPath .resolve ("subdir/../other.txt" );
85109 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 ));
110+ assertSame (resolved , fence .apply (resolved ));
88111 }
89112
90113 @ Test
@@ -98,4 +121,27 @@ void testOutsideFenceThrows(@TempDir final Path tempDir) throws Exception {
98121 assertTrue (msg .contains ("not in the fence" ), () -> "Expected message to mention fence: " + msg );
99122 assertTrue (msg .contains (other .toAbsolutePath ().toString ()), () -> "Expected message to contain the path: " + msg );
100123 }
124+
125+ @ Test
126+ void testResolveRelative () throws Exception {
127+ final PathFence fence = PathFence .builder ().setRoots (Paths .get ("/foo/bar" )).get ();
128+ final Path relPathTop = getRelPathToTop ();
129+ final Path relPath = relPathTop .resolve ("foo/bar" );
130+ assertSame (relPath , fence .apply (relPath ));
131+ }
132+
133+ @ Test
134+ void testResolveRelativeRoot () throws Exception {
135+ final Path relPathTop = getRelPathToTop ();
136+ final PathFence fence = PathFence .builder ().setRoots (relPathTop .resolve ("foo/bar" )).get ();
137+ final Path relPath = relPathTop .resolve ("foo/bar" );
138+ assertSame (relPath , fence .apply (relPath ));
139+ }
140+
141+ @ ParameterizedTest
142+ @ ValueSource (strings = { "/a" , "/a/b" , "/a/b/c" , "/a/b/c/d" , "a" , "a/b" , "a/b/c" , "a/b/c/d" })
143+ void testUserHomeDirEscapeAttempt (final String path ) throws Exception {
144+ testEscapeAttempt (Paths .get (path ));
145+ }
146+
101147}
0 commit comments