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