return fsys.Glob(pattern)
}
+ // Check pattern is well-formed.
+ if _, err := path.Match(pattern, ""); err != nil {
+ return nil, err
+ }
if !hasMeta(pattern) {
if _, err = Stat(fsys, pattern); err != nil {
return nil, nil
import (
. "io/fs"
"os"
+ "path"
"testing"
)
}
func TestGlobError(t *testing.T) {
- _, err := Glob(os.DirFS("."), "[]")
- if err == nil {
- t.Error("expected error for bad pattern; got none")
+ bad := []string{`[]`, `nonexist/[]`}
+ for _, pattern := range bad {
+ _, err := Glob(os.DirFS("."), pattern)
+ if err != path.ErrBadPattern {
+ t.Errorf("Glob(fs, %#q) returned err=%v, want path.ErrBadPattern", pattern, err)
+ }
}
}
// If so, it returns the remainder of s (after the match).
// Chunk is all single-character operators: literals, char classes, and ?.
func matchChunk(chunk, s string) (rest string, ok bool, err error) {
+ // failed records whether the match has failed.
+ // After the match fails, the loop continues on processing chunk,
+ // checking that the pattern is well-formed but no longer reading s.
+ failed := false
for len(chunk) > 0 {
- if len(s) == 0 {
- return
+ if !failed && len(s) == 0 {
+ failed = true
}
switch chunk[0] {
case '[':
// character class
- r, n := utf8.DecodeRuneInString(s)
- s = s[n:]
- chunk = chunk[1:]
- // We can't end right after '[', we're expecting at least
- // a closing bracket and possibly a caret.
- if len(chunk) == 0 {
- err = ErrBadPattern
- return
+ var r rune
+ if !failed {
+ var n int
+ r, n = utf8.DecodeRuneInString(s)
+ s = s[n:]
}
+ chunk = chunk[1:]
// possibly negated
- negated := chunk[0] == '^'
- if negated {
+ negated := false
+ if len(chunk) > 0 && chunk[0] == '^' {
+ negated = true
chunk = chunk[1:]
}
// parse all ranges
}
var lo, hi rune
if lo, chunk, err = getEsc(chunk); err != nil {
- return
+ return "", false, err
}
hi = lo
if chunk[0] == '-' {
if hi, chunk, err = getEsc(chunk[1:]); err != nil {
- return
+ return "", false, err
}
}
if lo <= r && r <= hi {
nrange++
}
if match == negated {
- return
+ failed = true
}
case '?':
- if s[0] == Separator {
- return
+ if !failed {
+ if s[0] == Separator {
+ failed = true
+ }
+ _, n := utf8.DecodeRuneInString(s)
+ s = s[n:]
}
- _, n := utf8.DecodeRuneInString(s)
- s = s[n:]
chunk = chunk[1:]
case '\\':
if runtime.GOOS != "windows" {
chunk = chunk[1:]
if len(chunk) == 0 {
- err = ErrBadPattern
- return
+ return "", false, ErrBadPattern
}
}
fallthrough
default:
- if chunk[0] != s[0] {
- return
+ if !failed {
+ if chunk[0] != s[0] {
+ failed = true
+ }
+ s = s[1:]
}
- s = s[1:]
chunk = chunk[1:]
}
}
+ if failed {
+ return "", false, nil
+ }
return s, true, nil
}
// The only possible returned error is ErrBadPattern, when pattern
// is malformed.
func Glob(pattern string) (matches []string, err error) {
+ // Check pattern is well-formed.
+ if _, err := Match(pattern, ""); err != nil {
+ return nil, err
+ }
if !hasMeta(pattern) {
if _, err = os.Lstat(pattern); err != nil {
return nil, nil
{"[", "a", false, ErrBadPattern},
{"[^", "a", false, ErrBadPattern},
{"[^bc", "a", false, ErrBadPattern},
- {"a[", "a", false, nil},
+ {"a[", "a", false, ErrBadPattern},
{"a[", "ab", false, ErrBadPattern},
+ {"a[", "x", false, ErrBadPattern},
+ {"a/b[", "x", false, ErrBadPattern},
{"*x", "xxx", true, nil},
}
}
func TestGlobError(t *testing.T) {
- _, err := Glob("[]")
- if err == nil {
- t.Error("expected error for bad pattern; got none")
+ bad := []string{`[]`, `nonexist/[]`}
+ for _, pattern := range bad {
+ if _, err := Glob(pattern); err != ErrBadPattern {
+ t.Errorf("Glob(%#q) returned err=%v, want ErrBadPattern", pattern, err)
+ }
}
}
}
}
}
+ // Before returning false with no error,
+ // check that the remainder of the pattern is syntactically valid.
+ for len(pattern) > 0 {
+ _, chunk, pattern = scanChunk(pattern)
+ if _, _, err := matchChunk(chunk, ""); err != nil {
+ return false, err
+ }
+ }
return false, nil
}
return len(name) == 0, nil
// If so, it returns the remainder of s (after the match).
// Chunk is all single-character operators: literals, char classes, and ?.
func matchChunk(chunk, s string) (rest string, ok bool, err error) {
+ // failed records whether the match has failed.
+ // After the match fails, the loop continues on processing chunk,
+ // checking that the pattern is well-formed but no longer reading s.
+ failed := false
for len(chunk) > 0 {
- if len(s) == 0 {
- return
+ if !failed && len(s) == 0 {
+ failed = true
}
switch chunk[0] {
case '[':
// character class
- r, n := utf8.DecodeRuneInString(s)
- s = s[n:]
+ var r rune
+ if !failed {
+ var n int
+ r, n = utf8.DecodeRuneInString(s)
+ s = s[n:]
+ }
chunk = chunk[1:]
// possibly negated
- notNegated := true
+ negated := false
if len(chunk) > 0 && chunk[0] == '^' {
- notNegated = false
+ negated = true
chunk = chunk[1:]
}
// parse all ranges
}
var lo, hi rune
if lo, chunk, err = getEsc(chunk); err != nil {
- return
+ return "", false, err
}
hi = lo
if chunk[0] == '-' {
if hi, chunk, err = getEsc(chunk[1:]); err != nil {
- return
+ return "", false, err
}
}
if lo <= r && r <= hi {
}
nrange++
}
- if match != notNegated {
- return
+ if match == negated {
+ failed = true
}
case '?':
- if s[0] == '/' {
- return
+ if !failed {
+ if s[0] == '/' {
+ failed = true
+ }
+ _, n := utf8.DecodeRuneInString(s)
+ s = s[n:]
}
- _, n := utf8.DecodeRuneInString(s)
- s = s[n:]
chunk = chunk[1:]
case '\\':
chunk = chunk[1:]
if len(chunk) == 0 {
- err = ErrBadPattern
- return
+ return "", false, ErrBadPattern
}
fallthrough
default:
- if chunk[0] != s[0] {
- return
+ if !failed {
+ if chunk[0] != s[0] {
+ failed = true
+ }
+ s = s[1:]
}
- s = s[1:]
chunk = chunk[1:]
}
}
+ if failed {
+ return "", false, nil
+ }
return s, true, nil
}
{"[", "a", false, ErrBadPattern},
{"[^", "a", false, ErrBadPattern},
{"[^bc", "a", false, ErrBadPattern},
- {"a[", "a", false, nil},
+ {"a[", "a", false, ErrBadPattern},
{"a[", "ab", false, ErrBadPattern},
+ {"a[", "x", false, ErrBadPattern},
+ {"a/b[", "x", false, ErrBadPattern},
{"*x", "xxx", true, nil},
}
glob = strings.Join(elem, "/") + "/"
}
+ // Test that malformed patterns are detected.
+ // The error is likely path.ErrBadPattern but need not be.
+ if _, err := t.fsys.(fs.GlobFS).Glob(glob + "nonexist/[]"); err == nil {
+ t.errorf("%s: Glob(%#q): bad pattern not detected", dir, glob+"nonexist/[]")
+ }
+
// Try to find a letter that appears in only some of the final names.
c := rune('a')
for ; c <= 'z'; c++ {