22// Use of this source code is governed by a BSD-style
33// license that can be found in the LICENSE file.
44
5- //go:build !plan9
6-
75package securejoin
86
97import (
@@ -15,7 +13,6 @@ import (
1513 "testing"
1614
1715 "github.com/stretchr/testify/assert"
18- "github.com/stretchr/testify/require"
1916)
2017
2118// TODO: These tests won't work on plan9 because it doesn't have symlinks, and
@@ -26,64 +23,9 @@ type input struct {
2623 expected string
2724}
2825
29- func expandedTempDir (t * testing.T ) string {
30- dir := t .TempDir ()
31- dir , err := filepath .EvalSymlinks (dir )
32- require .NoError (t , err )
33- return dir
34- }
35-
36- // Test basic handling of symlink expansion.
37- func TestSymlink (t * testing.T ) {
38- dir := expandedTempDir (t )
39-
40- symlink (t , "somepath" , filepath .Join (dir , "etc" ))
41- symlink (t , "../../../../../../../../../../../../../etc" , filepath .Join (dir , "etclink" ))
42- symlink (t , "/../../../../../../../../../../../../../etc/passwd" , filepath .Join (dir , "passwd" ))
43-
44- rootOrVol := string (filepath .Separator )
45- if vol := filepath .VolumeName (dir ); vol != "" {
46- rootOrVol = vol + rootOrVol
47- }
48-
49- tc := []input {
50- // Make sure that expansion with a root of '/' proceeds in the expected fashion.
51- {rootOrVol , filepath .Join (dir , "passwd" ), filepath .Join (rootOrVol , "etc" , "passwd" )},
52- {rootOrVol , filepath .Join (dir , "etclink" ), filepath .Join (rootOrVol , "etc" )},
53-
54- {rootOrVol , filepath .Join (dir , "etc" ), filepath .Join (dir , "somepath" )},
55- // Now test scoped expansion.
56- {dir , "passwd" , filepath .Join (dir , "somepath" , "passwd" )},
57- {dir , "etclink" , filepath .Join (dir , "somepath" )},
58- {dir , "etc" , filepath .Join (dir , "somepath" )},
59- {dir , "etc/test" , filepath .Join (dir , "somepath" , "test" )},
60- {dir , "etc/test/.." , filepath .Join (dir , "somepath" )},
61- }
62-
63- for _ , test := range tc {
64- got , err := SecureJoin (test .root , test .unsafe )
65- if err != nil {
66- t .Errorf ("securejoin(%q, %q): unexpected error: %v" , test .root , test .unsafe , err )
67- continue
68- }
69- // This is only for OS X, where /etc is a symlink to /private/etc. In
70- // principle, SecureJoin(/, pth) is the same as EvalSymlinks(pth) in
71- // the case where the path exists.
72- if test .root == "/" {
73- if expected , err := filepath .EvalSymlinks (test .expected ); err == nil {
74- test .expected = expected
75- }
76- }
77- if got != test .expected {
78- t .Errorf ("securejoin(%q, %q): expected %q, got %q" , test .root , test .unsafe , test .expected , got )
79- continue
80- }
81- }
82- }
83-
8426// In a path without symlinks, SecureJoin is equivalent to Clean+Join.
8527func TestNoSymlink (t * testing.T ) {
86- dir := expandedTempDir ( t )
28+ dir := t . TempDir ( )
8729
8830 tc := []input {
8931 {dir , "somepath" , filepath .Join (dir , "somepath" )},
@@ -114,92 +56,6 @@ func TestNoSymlink(t *testing.T) {
11456 }
11557}
11658
117- // Make sure that .. is **not** expanded lexically.
118- func TestNonLexical (t * testing.T ) {
119- dir := expandedTempDir (t )
120-
121- mkdirAll (t , filepath .Join (dir , "subdir" ), 0o755 )
122- mkdirAll (t , filepath .Join (dir , "cousinparent" , "cousin" ), 0o755 )
123- symlink (t , "../cousinparent/cousin" , filepath .Join (dir , "subdir" , "link" ))
124- symlink (t , "/../cousinparent/cousin" , filepath .Join (dir , "subdir" , "link2" ))
125- symlink (t , "/../../../../../../../../../../../../../../../../cousinparent/cousin" , filepath .Join (dir , "subdir" , "link3" ))
126-
127- for _ , test := range []input {
128- {dir , "subdir" , filepath .Join (dir , "subdir" )},
129- {dir , "subdir/link/test" , filepath .Join (dir , "cousinparent" , "cousin" , "test" )},
130- {dir , "subdir/link2/test" , filepath .Join (dir , "cousinparent" , "cousin" , "test" )},
131- {dir , "subdir/link3/test" , filepath .Join (dir , "cousinparent" , "cousin" , "test" )},
132- {dir , "subdir/../test" , filepath .Join (dir , "test" )},
133- // This is the divergence from a simple filepath.Clean implementation.
134- {dir , "subdir/link/../test" , filepath .Join (dir , "cousinparent" , "test" )},
135- {dir , "subdir/link2/../test" , filepath .Join (dir , "cousinparent" , "test" )},
136- {dir , "subdir/link3/../test" , filepath .Join (dir , "cousinparent" , "test" )},
137- } {
138- got , err := SecureJoin (test .root , test .unsafe )
139- if err != nil {
140- t .Errorf ("securejoin(%q, %q): unexpected error: %v" , test .root , test .unsafe , err )
141- continue
142- }
143- if got != test .expected {
144- t .Errorf ("securejoin(%q, %q): expected %q, got %q" , test .root , test .unsafe , test .expected , got )
145- continue
146- }
147- }
148- }
149-
150- // Make sure that symlink loops result in errors.
151- func TestSymlinkLoop (t * testing.T ) {
152- dir := expandedTempDir (t )
153-
154- mkdirAll (t , filepath .Join (dir , "subdir" ), 0o755 )
155- symlink (t , "../../../../../../../../../../../../../../../../path" , filepath .Join (dir , "subdir" , "link" ))
156- symlink (t , "/subdir/link" , filepath .Join (dir , "path" ))
157- symlink (t , "/../../../../../../../../../../../../../../../../self" , filepath .Join (dir , "self" ))
158-
159- for _ , test := range []struct {
160- root , unsafe string
161- }{
162- {dir , "subdir/link" },
163- {dir , "path" },
164- {dir , "../../path" },
165- {dir , "subdir/link/../.." },
166- {dir , "../../../../../../../../../../../../../../../../subdir/link/../../../../../../../../../../../../../../../.." },
167- {dir , "self" },
168- {dir , "self/.." },
169- {dir , "/../../../../../../../../../../../../../../../../self/.." },
170- {dir , "/self/././.." },
171- } {
172- got , err := SecureJoin (test .root , test .unsafe )
173- if ! errors .Is (err , syscall .ELOOP ) {
174- t .Errorf ("securejoin(%q, %q): expected ELOOP, got %q & %v" , test .root , test .unsafe , got , err )
175- continue
176- }
177- }
178- }
179-
180- // Make sure that ENOTDIR is correctly handled.
181- func TestEnotdir (t * testing.T ) {
182- dir := expandedTempDir (t )
183-
184- mkdirAll (t , filepath .Join (dir , "subdir" ), 0o755 )
185- writeFile (t , filepath .Join (dir , "notdir" ), []byte ("I am not a directory!" ), 0o755 )
186- symlink (t , "/../../../notdir/somechild" , filepath .Join (dir , "subdir" , "link" ))
187-
188- for _ , test := range []struct {
189- root , unsafe string
190- }{
191- {dir , "subdir/link" },
192- {dir , "notdir" },
193- {dir , "notdir/child" },
194- } {
195- _ , err := SecureJoin (test .root , test .unsafe )
196- if err != nil {
197- t .Errorf ("securejoin(%q, %q): unexpected error: %v" , test .root , test .unsafe , err )
198- continue
199- }
200- }
201- }
202-
20359// Some silly tests to make sure that all error types are correctly handled.
20460func TestIsNotExist (t * testing.T ) {
20561 for _ , test := range []struct {
@@ -224,121 +80,6 @@ func TestIsNotExist(t *testing.T) {
22480 }
22581}
22682
227- type mockVFS struct {
228- lstat func (path string ) (os.FileInfo , error )
229- readlink func (path string ) (string , error )
230- }
231-
232- func (m mockVFS ) Lstat (path string ) (os.FileInfo , error ) { return m .lstat (path ) }
233- func (m mockVFS ) Readlink (path string ) (string , error ) { return m .readlink (path ) }
234-
235- // Make sure that SecureJoinVFS actually does use the given VFS interface.
236- func TestSecureJoinVFS (t * testing.T ) {
237- dir := expandedTempDir (t )
238-
239- mkdirAll (t , filepath .Join (dir , "subdir" ), 0o755 )
240- mkdirAll (t , filepath .Join (dir , "cousinparent" , "cousin" ), 0o755 )
241- symlink (t , "../cousinparent/cousin" , filepath .Join (dir , "subdir" , "link" ))
242- symlink (t , "/../cousinparent/cousin" , filepath .Join (dir , "subdir" , "link2" ))
243- symlink (t , "/../../../../../../../../../../../../../../../../cousinparent/cousin" , filepath .Join (dir , "subdir" , "link3" ))
244-
245- for _ , test := range []input {
246- {dir , "subdir" , filepath .Join (dir , "subdir" )},
247- {dir , "subdir/link/test" , filepath .Join (dir , "cousinparent" , "cousin" , "test" )},
248- {dir , "subdir/link2/test" , filepath .Join (dir , "cousinparent" , "cousin" , "test" )},
249- {dir , "subdir/link3/test" , filepath .Join (dir , "cousinparent" , "cousin" , "test" )},
250- {dir , "subdir/../test" , filepath .Join (dir , "test" )},
251- // This is the divergence from a simple filepath.Clean implementation.
252- {dir , "subdir/link/../test" , filepath .Join (dir , "cousinparent" , "test" )},
253- {dir , "subdir/link2/../test" , filepath .Join (dir , "cousinparent" , "test" )},
254- {dir , "subdir/link3/../test" , filepath .Join (dir , "cousinparent" , "test" )},
255- } {
256- var nLstat , nReadlink int
257- mock := mockVFS {
258- lstat : func (path string ) (os.FileInfo , error ) { nLstat ++ ; return os .Lstat (path ) },
259- readlink : func (path string ) (string , error ) { nReadlink ++ ; return os .Readlink (path ) },
260- }
261-
262- got , err := SecureJoinVFS (test .root , test .unsafe , mock )
263- if err != nil {
264- t .Errorf ("securejoin(%q, %q): unexpected error: %v" , test .root , test .unsafe , err )
265- continue
266- }
267- if got != test .expected {
268- t .Errorf ("securejoin(%q, %q): expected %q, got %q" , test .root , test .unsafe , test .expected , got )
269- continue
270- }
271- if nLstat == 0 && nReadlink == 0 {
272- t .Errorf ("securejoin(%q, %q): expected to use either lstat or readlink, neither were used" , test .root , test .unsafe )
273- }
274- }
275- }
276-
277- // Make sure that SecureJoinVFS actually does use the given VFS interface, and
278- // that errors are correctly propagated.
279- func TestSecureJoinVFSErrors (t * testing.T ) {
280- var (
281- lstatErr = errors .New ("lstat error" )
282- readlinkErr = errors .New ("readlink err" )
283- )
284-
285- dir := expandedTempDir (t )
286-
287- // Make a link.
288- symlink (t , "../../../../../../../../../../../../../../../../path" , filepath .Join (dir , "link" ))
289-
290- // Define some fake mock functions.
291- lstatFailFn := func (string ) (os.FileInfo , error ) { return nil , lstatErr }
292- readlinkFailFn := func (string ) (string , error ) { return "" , readlinkErr }
293-
294- // Make sure that the set of {lstat, readlink} failures do propagate.
295- for idx , test := range []struct {
296- vfs VFS
297- expected []error
298- }{
299- {
300- expected : []error {nil },
301- vfs : mockVFS {
302- lstat : os .Lstat ,
303- readlink : os .Readlink ,
304- },
305- },
306- {
307- expected : []error {lstatErr },
308- vfs : mockVFS {
309- lstat : lstatFailFn ,
310- readlink : os .Readlink ,
311- },
312- },
313- {
314- expected : []error {readlinkErr },
315- vfs : mockVFS {
316- lstat : os .Lstat ,
317- readlink : readlinkFailFn ,
318- },
319- },
320- {
321- expected : []error {lstatErr , readlinkErr },
322- vfs : mockVFS {
323- lstat : lstatFailFn ,
324- readlink : readlinkFailFn ,
325- },
326- },
327- } {
328- _ , err := SecureJoinVFS (dir , "link" , test .vfs )
329-
330- success := false
331- for _ , exp := range test .expected {
332- if errors .Is (err , exp ) {
333- success = true
334- }
335- }
336- if ! success {
337- t .Errorf ("SecureJoinVFS.mock%d: expected to get lstatError, got %v" , idx , err )
338- }
339- }
340- }
341-
34283func TestUncleanRoot (t * testing.T ) {
34384 root := t .TempDir ()
34485
0 commit comments