1// Copyright 2009 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package filepath_test
6
7import (
8	"fmt"
9	"internal/testenv"
10	"os"
11	. "path/filepath"
12	"reflect"
13	"runtime"
14	"slices"
15	"strings"
16	"testing"
17)
18
19type MatchTest struct {
20	pattern, s string
21	match      bool
22	err        error
23}
24
25var matchTests = []MatchTest{
26	{"abc", "abc", true, nil},
27	{"*", "abc", true, nil},
28	{"*c", "abc", true, nil},
29	{"a*", "a", true, nil},
30	{"a*", "abc", true, nil},
31	{"a*", "ab/c", false, nil},
32	{"a*/b", "abc/b", true, nil},
33	{"a*/b", "a/c/b", false, nil},
34	{"a*b*c*d*e*/f", "axbxcxdxe/f", true, nil},
35	{"a*b*c*d*e*/f", "axbxcxdxexxx/f", true, nil},
36	{"a*b*c*d*e*/f", "axbxcxdxe/xxx/f", false, nil},
37	{"a*b*c*d*e*/f", "axbxcxdxexxx/fff", false, nil},
38	{"a*b?c*x", "abxbbxdbxebxczzx", true, nil},
39	{"a*b?c*x", "abxbbxdbxebxczzy", false, nil},
40	{"ab[c]", "abc", true, nil},
41	{"ab[b-d]", "abc", true, nil},
42	{"ab[e-g]", "abc", false, nil},
43	{"ab[^c]", "abc", false, nil},
44	{"ab[^b-d]", "abc", false, nil},
45	{"ab[^e-g]", "abc", true, nil},
46	{"a\\*b", "a*b", true, nil},
47	{"a\\*b", "ab", false, nil},
48	{"a?b", "a☺b", true, nil},
49	{"a[^a]b", "a☺b", true, nil},
50	{"a???b", "a☺b", false, nil},
51	{"a[^a][^a][^a]b", "a☺b", false, nil},
52	{"[a-ζ]*", "α", true, nil},
53	{"*[a-ζ]", "A", false, nil},
54	{"a?b", "a/b", false, nil},
55	{"a*b", "a/b", false, nil},
56	{"[\\]a]", "]", true, nil},
57	{"[\\-]", "-", true, nil},
58	{"[x\\-]", "x", true, nil},
59	{"[x\\-]", "-", true, nil},
60	{"[x\\-]", "z", false, nil},
61	{"[\\-x]", "x", true, nil},
62	{"[\\-x]", "-", true, nil},
63	{"[\\-x]", "a", false, nil},
64	{"[]a]", "]", false, ErrBadPattern},
65	{"[-]", "-", false, ErrBadPattern},
66	{"[x-]", "x", false, ErrBadPattern},
67	{"[x-]", "-", false, ErrBadPattern},
68	{"[x-]", "z", false, ErrBadPattern},
69	{"[-x]", "x", false, ErrBadPattern},
70	{"[-x]", "-", false, ErrBadPattern},
71	{"[-x]", "a", false, ErrBadPattern},
72	{"\\", "a", false, ErrBadPattern},
73	{"[a-b-c]", "a", false, ErrBadPattern},
74	{"[", "a", false, ErrBadPattern},
75	{"[^", "a", false, ErrBadPattern},
76	{"[^bc", "a", false, ErrBadPattern},
77	{"a[", "a", false, ErrBadPattern},
78	{"a[", "ab", false, ErrBadPattern},
79	{"a[", "x", false, ErrBadPattern},
80	{"a/b[", "x", false, ErrBadPattern},
81	{"*x", "xxx", true, nil},
82}
83
84func errp(e error) string {
85	if e == nil {
86		return "<nil>"
87	}
88	return e.Error()
89}
90
91func TestMatch(t *testing.T) {
92	for _, tt := range matchTests {
93		pattern := tt.pattern
94		s := tt.s
95		if runtime.GOOS == "windows" {
96			if strings.Contains(pattern, "\\") {
97				// no escape allowed on windows.
98				continue
99			}
100			pattern = Clean(pattern)
101			s = Clean(s)
102		}
103		ok, err := Match(pattern, s)
104		if ok != tt.match || err != tt.err {
105			t.Errorf("Match(%#q, %#q) = %v, %q want %v, %q", pattern, s, ok, errp(err), tt.match, errp(tt.err))
106		}
107	}
108}
109
110var globTests = []struct {
111	pattern, result string
112}{
113	{"match.go", "match.go"},
114	{"mat?h.go", "match.go"},
115	{"*", "match.go"},
116	{"../*/match.go", "../filepath/match.go"},
117}
118
119func TestGlob(t *testing.T) {
120	for _, tt := range globTests {
121		pattern := tt.pattern
122		result := tt.result
123		if runtime.GOOS == "windows" {
124			pattern = Clean(pattern)
125			result = Clean(result)
126		}
127		matches, err := Glob(pattern)
128		if err != nil {
129			t.Errorf("Glob error for %q: %s", pattern, err)
130			continue
131		}
132		if !slices.Contains(matches, result) {
133			t.Errorf("Glob(%#q) = %#v want %v", pattern, matches, result)
134		}
135	}
136	for _, pattern := range []string{"no_match", "../*/no_match"} {
137		matches, err := Glob(pattern)
138		if err != nil {
139			t.Errorf("Glob error for %q: %s", pattern, err)
140			continue
141		}
142		if len(matches) != 0 {
143			t.Errorf("Glob(%#q) = %#v want []", pattern, matches)
144		}
145	}
146}
147
148func TestCVE202230632(t *testing.T) {
149	// Prior to CVE-2022-30632, this would cause a stack exhaustion given a
150	// large number of separators (more than 4,000,000). There is now a limit
151	// of 10,000.
152	_, err := Glob("/*" + strings.Repeat("/", 10001))
153	if err != ErrBadPattern {
154		t.Fatalf("Glob returned err=%v, want ErrBadPattern", err)
155	}
156}
157
158func TestGlobError(t *testing.T) {
159	bad := []string{`[]`, `nonexist/[]`}
160	for _, pattern := range bad {
161		if _, err := Glob(pattern); err != ErrBadPattern {
162			t.Errorf("Glob(%#q) returned err=%v, want ErrBadPattern", pattern, err)
163		}
164	}
165}
166
167func TestGlobUNC(t *testing.T) {
168	// Just make sure this runs without crashing for now.
169	// See issue 15879.
170	Glob(`\\?\C:\*`)
171}
172
173var globSymlinkTests = []struct {
174	path, dest string
175	brokenLink bool
176}{
177	{"test1", "link1", false},
178	{"test2", "link2", true},
179}
180
181func TestGlobSymlink(t *testing.T) {
182	testenv.MustHaveSymlink(t)
183
184	tmpDir := t.TempDir()
185	for _, tt := range globSymlinkTests {
186		path := Join(tmpDir, tt.path)
187		dest := Join(tmpDir, tt.dest)
188		f, err := os.Create(path)
189		if err != nil {
190			t.Fatal(err)
191		}
192		if err := f.Close(); err != nil {
193			t.Fatal(err)
194		}
195		err = os.Symlink(path, dest)
196		if err != nil {
197			t.Fatal(err)
198		}
199		if tt.brokenLink {
200			// Break the symlink.
201			os.Remove(path)
202		}
203		matches, err := Glob(dest)
204		if err != nil {
205			t.Errorf("GlobSymlink error for %q: %s", dest, err)
206		}
207		if !slices.Contains(matches, dest) {
208			t.Errorf("Glob(%#q) = %#v want %v", dest, matches, dest)
209		}
210	}
211}
212
213type globTest struct {
214	pattern string
215	matches []string
216}
217
218func (test *globTest) buildWant(root string) []string {
219	want := make([]string, 0)
220	for _, m := range test.matches {
221		want = append(want, root+FromSlash(m))
222	}
223	slices.Sort(want)
224	return want
225}
226
227func (test *globTest) globAbs(root, rootPattern string) error {
228	p := FromSlash(rootPattern + `\` + test.pattern)
229	have, err := Glob(p)
230	if err != nil {
231		return err
232	}
233	slices.Sort(have)
234	want := test.buildWant(root + `\`)
235	if strings.Join(want, "_") == strings.Join(have, "_") {
236		return nil
237	}
238	return fmt.Errorf("Glob(%q) returns %q, but %q expected", p, have, want)
239}
240
241func (test *globTest) globRel(root string) error {
242	p := root + FromSlash(test.pattern)
243	have, err := Glob(p)
244	if err != nil {
245		return err
246	}
247	slices.Sort(have)
248	want := test.buildWant(root)
249	if strings.Join(want, "_") == strings.Join(have, "_") {
250		return nil
251	}
252	// try also matching version without root prefix
253	wantWithNoRoot := test.buildWant("")
254	if strings.Join(wantWithNoRoot, "_") == strings.Join(have, "_") {
255		return nil
256	}
257	return fmt.Errorf("Glob(%q) returns %q, but %q expected", p, have, want)
258}
259
260func TestWindowsGlob(t *testing.T) {
261	if runtime.GOOS != "windows" {
262		t.Skipf("skipping windows specific test")
263	}
264
265	tmpDir := tempDirCanonical(t)
266	if len(tmpDir) < 3 {
267		t.Fatalf("tmpDir path %q is too short", tmpDir)
268	}
269	if tmpDir[1] != ':' {
270		t.Fatalf("tmpDir path %q must have drive letter in it", tmpDir)
271	}
272
273	dirs := []string{
274		"a",
275		"b",
276		"dir/d/bin",
277	}
278	files := []string{
279		"dir/d/bin/git.exe",
280	}
281	for _, dir := range dirs {
282		err := os.MkdirAll(Join(tmpDir, dir), 0777)
283		if err != nil {
284			t.Fatal(err)
285		}
286	}
287	for _, file := range files {
288		err := os.WriteFile(Join(tmpDir, file), nil, 0666)
289		if err != nil {
290			t.Fatal(err)
291		}
292	}
293
294	tests := []globTest{
295		{"a", []string{"a"}},
296		{"b", []string{"b"}},
297		{"c", []string{}},
298		{"*", []string{"a", "b", "dir"}},
299		{"d*", []string{"dir"}},
300		{"*i*", []string{"dir"}},
301		{"*r", []string{"dir"}},
302		{"?ir", []string{"dir"}},
303		{"?r", []string{}},
304		{"d*/*/bin/git.exe", []string{"dir/d/bin/git.exe"}},
305	}
306
307	// test absolute paths
308	for _, test := range tests {
309		var p string
310		if err := test.globAbs(tmpDir, tmpDir); err != nil {
311			t.Error(err)
312		}
313		// test C:\*Documents and Settings\...
314		p = tmpDir
315		p = strings.Replace(p, `:\`, `:\*`, 1)
316		if err := test.globAbs(tmpDir, p); err != nil {
317			t.Error(err)
318		}
319		// test C:\Documents and Settings*\...
320		p = tmpDir
321		p = strings.Replace(p, `:\`, `:`, 1)
322		p = strings.Replace(p, `\`, `*\`, 1)
323		p = strings.Replace(p, `:`, `:\`, 1)
324		if err := test.globAbs(tmpDir, p); err != nil {
325			t.Error(err)
326		}
327	}
328
329	// test relative paths
330	wd, err := os.Getwd()
331	if err != nil {
332		t.Fatal(err)
333	}
334	err = os.Chdir(tmpDir)
335	if err != nil {
336		t.Fatal(err)
337	}
338	defer func() {
339		err := os.Chdir(wd)
340		if err != nil {
341			t.Fatal(err)
342		}
343	}()
344	for _, test := range tests {
345		err := test.globRel("")
346		if err != nil {
347			t.Error(err)
348		}
349		err = test.globRel(`.\`)
350		if err != nil {
351			t.Error(err)
352		}
353		err = test.globRel(tmpDir[:2]) // C:
354		if err != nil {
355			t.Error(err)
356		}
357	}
358}
359
360func TestNonWindowsGlobEscape(t *testing.T) {
361	if runtime.GOOS == "windows" {
362		t.Skipf("skipping non-windows specific test")
363	}
364	pattern := `\match.go`
365	want := []string{"match.go"}
366	matches, err := Glob(pattern)
367	if err != nil {
368		t.Fatalf("Glob error for %q: %s", pattern, err)
369	}
370	if !reflect.DeepEqual(matches, want) {
371		t.Fatalf("Glob(%#q) = %v want %v", pattern, matches, want)
372	}
373}
374