Download
84 lines 2.5 KiBGoModified 2026-10-21 raw
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)
		}
	})
}