123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161 | package policy
import (
"path/filepath"
"reflect"
"strings"
"testing"
)
// A policy that parses writes back to one that parses to the same rules,
// and writes back the same again: blinker edits policies by rewriting
// them, and what the user reviewed must be what is saved.
func FuzzParseMarshal(f *testing.F) {
f.Add([]byte("project /p\n# note\nfile read dir with spaces/x\nhost ask *.GitHub.com\nenv deny GH_*\nenv set GOTOOLCHAIN local\ntool allow Commit\ninclude agent-safe\n"))
f.Add([]byte("file\thide\thide\t[ ]\n\n# lone comment\n"))
f.Fuzz(func(t *testing.T, data []byte) {
a, err := Parse(data)
if err != nil {
return
}
out := a.Marshal()
b, err := Parse(out)
if err != nil {
t.Fatalf("Marshal wrote what Parse refuses: %v\n%s", err, out)
}
strip := func(f *File) File {
g := *f
g.Files, g.Network, g.Env, g.Tools, g.Includes = nil, nil, nil, nil, nil
for _, inc := range f.Includes {
inc.Line = 0
g.Includes = append(g.Includes, inc)
}
for _, r := range f.Files {
r.Line = 0
g.Files = append(g.Files, r)
}
for _, r := range f.Network {
r.Line = 0
g.Network = append(g.Network, r)
}
for _, r := range f.Env {
r.Line = 0
g.Env = append(g.Env, r)
}
for _, r := range f.Tools {
r.Line = 0
g.Tools = append(g.Tools, r)
}
g.text = nil
return g
}
if sa, sb := strip(a), strip(b); !reflect.DeepEqual(sa, sb) {
t.Fatalf("rules changed in a round trip:\n%+v\n%+v\n%s", sa, sb, out)
}
if again := b.Marshal(); string(again) != string(out) {
t.Fatalf("Marshal is not stable:\n%s\n---\n%s", out, again)
}
})
}
// fuzzPath turns bytes into a path of components from a small alphabet,
// so patterns and paths meet often.
func fuzzPath(b []byte, glob bool) string {
names := []string{"a", "b", ".env", "x.go", "ab"}
if glob {
names = append(names, "*", "**", "?", "[ab]", "*.go", ".e*", "[!a]")
}
var parts []string
for _, c := range b {
parts = append(parts, names[int(c)%len(names)])
}
return strings.Join(parts, "/")
}
// Match is MatchAnyExact of the path or one of its ancestors, and Covers
// never claims more than Match does.
func FuzzMatcher(f *testing.F) {
f.Add([]byte{6, 2}, []byte{5, 1}, []byte{0, 1, 2})
f.Add([]byte{6, 6}, []byte{7, 3}, []byte{0, 4, 3})
f.Fuzz(func(t *testing.T, gb, mb, pb []byte) {
if len(gb) > 6 || len(mb) > 6 || len(pb) > 8 {
return
}
home, project := "/h", "/h/p"
n, err := NewMatcher(fuzzPath(gb, true)+"x", home, project) // never empty, never ".."
if err != nil {
return
}
m, err := NewMatcher(fuzzPath(mb, true)+"x", home, project)
if err != nil {
return
}
p := filepath.Join(project, fuzzPath(pb, false))
anc := false
for cur := p; ; cur = filepath.Dir(cur) {
if MatchAnyExact([]Matcher{n}, cur) {
anc = true
break
}
if cur == "/" {
break
}
}
if n.Match(p) != anc {
t.Fatalf("%q: Match(%s) = %v, but an ancestor-or-self matches exactly: %v", n.Path, p, n.Match(p), anc)
}
if n.Covers(m) && m.Match(p) && !n.Match(p) {
t.Fatalf("%q covers %q, but only the latter matches %s", n.Path, m.Path, p)
}
})
}
// A host pattern matches its own domain's hosts only, whatever their case
// or final dot.
func FuzzHostMatches(f *testing.F) {
f.Add("api.github.com", "*.github.com")
f.Add("github.com", "*.github.com")
f.Add("evilgithub.com", "*.github.com")
f.Add("GitHub.COM.", "github.com")
f.Fuzz(func(t *testing.T, host, pattern string) {
p, err := ValidateHost(pattern)
if err != nil {
return
}
got := HostMatches(host, p)
// Case is ASCII case: host names are ASCII.
upper := strings.Map(func(r rune) rune {
if r >= 'a' && r <= 'z' {
return r - 'a' + 'A'
}
return r
}, host)
if HostMatches(upper, p) != got || HostMatches(lowerASCII(host), p) != got {
t.Fatalf("HostMatches(%q, %q) depends on case", host, p)
}
// One final dot is the root's, so it changes nothing; "a.." is no
// host name, and not one of a's.
norm := lowerASCII(host)
if !strings.HasSuffix(host, ".") {
if HostMatches(host+".", p) != got {
t.Fatalf("HostMatches(%q, %q) depends on a final dot", host, p)
}
} else {
norm = strings.TrimSuffix(norm, ".")
}
switch base, sub := strings.CutPrefix(p, "*."); {
case p == "*":
if !got {
t.Fatalf("* does not match %q", host)
}
case sub:
if want := strings.HasSuffix(norm, "."+base) && len(norm) > len(base)+1; got != want {
t.Fatalf("HostMatches(%q, %q) = %v", host, p, got)
}
default:
if got != (norm == p) {
t.Fatalf("HostMatches(%q, %q) = %v", host, p, got)
}
}
})
}
|