123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384 | package httpd
import (
"strings"
"testing"
)
// Run one with `make fuzz`; plain `go test` runs only the seeds.
// quote is a pattern that matches s literally: each byte that is not a
// letter or digit escaped with %, which patterns(7) allows for any
// non-alphanumeric character.
func quote(s string) string {
var b strings.Builder
for _, c := range []byte(s) {
if !class(c, 'w') {
b.WriteByte('%')
}
b.WriteByte(c)
}
return b.String()
}
// A pattern Check passes must match without panicking, give %0 and one
// capture for each that checkPattern counted, and find %0 in the path,
// at its start when anchored. A pattern Check refuses matches nothing.
// Any path, quoted, must find itself in a path just where strings.Index
// does. Inputs are kept small: like patterns.c, the matcher backtracks
// in time exponential in its quantifiers, and MAXCCALLS bounds only its
// depth, so ^a?a?...a?$ on 40 a and a b runs for hours.
func FuzzPattern(f *testing.F) {
for _, s := range [][2]string{
{"^/code/(..-)/?$", "/code/r/src/"},
{"^/git/[^/]+%.git/HEAD$", "/git/a.git/HEAD"},
{"pack%-%x+%.idx$", "/p/pack-0fA9.idx"},
{"^%a[]]$", "a]"},
{"[^]]-%]", "ab]"},
{"^(a?)a$", "a"},
{"((a*)(.-)%w(%s*))$", "xaaa yz"},
{"[%a-z]+", "a-z"},
{"[a-%d]", "d"},
{"%b()", "(a)"},
{"[a%]", "a"},
{"()", ""},
} {
f.Add(s[0], s[1])
}
f.Fuzz(func(t *testing.T, pat, s string) {
if len(s) > 64 || strings.Count(pat, "?")+strings.Count(pat, "*")+
strings.Count(pat, "+")+strings.Count(pat, "-") > 4 {
return
}
loc := PatternLocation(pat)
n, _ := checkPattern(pat)
caps, ok := loc.Match(s)
if loc.Check() != nil {
if ok {
t.Fatalf("%q matched %q despite failing Check", pat, s)
}
} else if ok {
if len(caps) != 1+n {
t.Fatalf("%q on %q: %d captures, checkPattern counted %d", pat, s, len(caps)-1, n)
}
if !strings.Contains(s, caps[0]) || (strings.HasPrefix(pat, "^") && !strings.HasPrefix(s, caps[0])) {
t.Fatalf("%q on %q matched %q", pat, s, caps[0])
}
}
q := quote(pat)
if _, err := checkPattern(q); err != nil {
t.Fatalf("quote(%q) = %q: %v", pat, q, err)
}
i := strings.Index(s, pat)
got, ok := find(s, q)
if ok != (i >= 0) || ok && got[0] != pat {
t.Fatalf("%q on %q = %q, %v; Index is %d", q, s, got, ok, i)
}
if _, ok := find(s, "^"+q); ok != strings.HasPrefix(s, pat) {
t.Fatalf("^%q on %q = %v", q, s, ok)
}
if _, ok := find(s, q+"$"); ok != strings.HasSuffix(s, pat) {
t.Fatalf("%q$ on %q = %v", q, s, ok)
}
})
}
|