1
2
3
4
5 package filepath_test
6
7 import (
8 "fmt"
9 "internal/testenv"
10 "os"
11 . "path/filepath"
12 "reflect"
13 "runtime"
14 "slices"
15 "strings"
16 "testing"
17 )
18
19 type MatchTest struct {
20 pattern, s string
21 match bool
22 err error
23 }
24
25 var 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
84 func errp(e error) string {
85 if e == nil {
86 return "<nil>"
87 }
88 return e.Error()
89 }
90
91 func 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
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
110 var 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
119 func 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
148 func TestCVE202230632(t *testing.T) {
149
150
151
152 _, err := Glob("/*" + strings.Repeat("/", 10001))
153 if err != ErrBadPattern {
154 t.Fatalf("Glob returned err=%v, want ErrBadPattern", err)
155 }
156 }
157
158 func 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
167 func TestGlobUNC(t *testing.T) {
168
169
170 Glob(`\\?\C:\*`)
171 }
172
173 var globSymlinkTests = []struct {
174 path, dest string
175 brokenLink bool
176 }{
177 {"test1", "link1", false},
178 {"test2", "link2", true},
179 }
180
181 func 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
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
213 type globTest struct {
214 pattern string
215 matches []string
216 }
217
218 func (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
227 func (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
241 func (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
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
260 func 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
308 for _, test := range tests {
309 var p string
310 if err := test.globAbs(tmpDir, tmpDir); err != nil {
311 t.Error(err)
312 }
313
314 p = tmpDir
315 p = strings.Replace(p, `:\`, `:\*`, 1)
316 if err := test.globAbs(tmpDir, p); err != nil {
317 t.Error(err)
318 }
319
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
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])
354 if err != nil {
355 t.Error(err)
356 }
357 }
358 }
359
360 func 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
View as plain text