@@ 11,6 11,7 @@ package fnmatch
// a period must be matched explicitly, but a range will match it too
import (
+ "errors"
"unicode"
"unicode/utf8"
)
@@ 33,10 34,23 @@ func unpackRune(str *string) rune {
return rune
}
+func Match(pattern, s string, flags int) bool {
+ found, err := match(pattern, s, flags, 25)
+ if err != nil || !found {
+ return false
+ } else {
+ return true
+ }
+}
+
// Matches the pattern against the string, with the given flags,
// and returns true if the match is successful.
// This function should match fnmatch.3 as closely as possible.
-func Match(pattern, s string, flags int) bool {
+func match(pattern, s string, flags int, limit uint) (bool, error) {
+ if limit == 0 {
+ return false, errors.New("recursion limit exceeded")
+ }
+
// The implementation for this function was patterned after the BSD fnmatch.c
// source found at http://src.gnu-darwin.org/src/contrib/csup/fnmatch.c.html
noescape := (flags&FNM_NOESCAPE != 0)
@@ 63,14 77,14 @@ func Match(pattern, s string, flags int) bool {
switch c {
case '?':
if len(s) == 0 {
- return false
+ return false, nil
}
sc := unpackS()
if pathname && sc == '/' {
- return false
+ return false, nil
}
if period && sc == '.' && (sLastAtStart || (pathname && sLastSlash)) {
- return false
+ return false, nil
}
case '*':
// collapse multiple *'s
@@ 79,20 93,19 @@ func Match(pattern, s string, flags int) bool {
pattern = pattern[1:]
}
if period && s[0] == '.' && (sAtStart || (pathname && sLastUnpacked == '/')) {
- return false
+ return false, nil
}
// optimize for patterns with * at end or before /
if len(pattern) == 0 {
if pathname {
- return leadingdir || (strchr(s, '/') == -1)
+ return leadingdir || (strchr(s, '/') == -1), nil
} else {
- return true
+ return true, nil
}
- return !(pathname && strchr(s, '/') >= 0)
} else if pathname && pattern[0] == '/' {
offset := strchr(s, '/')
if offset == -1 {
- return false
+ return false, nil
} else {
// we already know our pattern and string have a /, skip past it
s = s[offset:] // use unpackS here to maintain our bookkeeping state
@@ 105,23 118,27 @@ func Match(pattern, s string, flags int) bool {
for test := s; len(test) > 0; unpackRune(&test) {
// I believe the (flags &^ FNM_PERIOD) is a bug when FNM_PATHNAME is specified
// but this follows exactly from how fnmatch.c implements it
- if Match(pattern, test, (flags &^ FNM_PERIOD)) {
- return true
+ found, err := match(pattern, test, (flags &^ FNM_PERIOD), limit-1)
+ if err != nil {
+ return false, err
+ }
+ if found {
+ return true, nil
} else if pathname && test[0] == '/' {
break
}
}
- return false
+ return false, nil
case '[':
if len(s) == 0 {
- return false
+ return false, nil
}
if pathname && s[0] == '/' {
- return false
+ return false, nil
}
sc := unpackS()
if !rangematch(&pattern, sc, flags) {
- return false
+ return false, nil
}
case '\\':
if !noescape {
@@ 132,18 149,18 @@ func Match(pattern, s string, flags int) bool {
fallthrough
default:
if len(s) == 0 {
- return false
+ return false, nil
}
sc := unpackS()
switch {
case sc == c:
case casefold && unicode.ToLower(sc) == unicode.ToLower(c):
default:
- return false
+ return false, nil
}
}
}
- return len(s) == 0 || (leadingdir && s[0] == '/')
+ return len(s) == 0 || (leadingdir && s[0] == '/'), nil
}
func rangematch(pattern *string, test rune, flags int) bool {
@@ 2,6 2,7 @@ package fnmatch
import (
"testing"
+ "time"
)
// This is a set of tests ported from a set of tests for C fnmatch
@@ 347,3 348,33 @@ func TestFNMLeadingDir(t *testing.T) {
}
}
}
+
+func TestRecursionLimit(t *testing.T) {
+ timeout := time.After(10 * time.Second)
+ success := make(chan bool)
+ go func() {
+ success <- Match("x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*", "xxxxxxxxxxxxxxxxxxxxxxxx", 0)
+ }()
+
+ select {
+ case <-timeout:
+ t.Errorf("Timed out\n");
+ case found := <-success:
+ if found {
+ t.Errorf("Failed\n");
+ }
+ }
+
+ go func() {
+ success <- Match("x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*x*", "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", 0)
+ }()
+
+ select {
+ case <-timeout:
+ t.Errorf("Timed out\n");
+ case found := <-success:
+ if found {
+ t.Errorf("Failed\n");
+ }
+ }
+}