Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 21 additions & 2 deletions internal/modules/favicon.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,20 @@ func faviconEvidence(matchers []Matcher, body string) (string, bool) {
return fmt.Sprintf("favicon mmh3=%d", hash), true
}

// hasNonEmpty reports whether at least one entry in vals is non-empty.
func hasNonEmpty(vals []string) bool {
for _, v := range vals {
if v != "" {
return true
}
}
return false
}

// validateMatchers fails favicon matchers that would silently never fire (no
// hash, or one out of 32-bit range) and malformed range matchers at load
// rather than at match time.
// hash, or one out of 32-bit range) and malformed range matchers at load rather
// than at match time. an empty word or regex list matches every response under
// the default AND condition, so it is rejected here too, for every transport.
func validateMatchers(matchers []Matcher) error {
for i := range matchers {
if matchers[i].Type == "favicon" {
Expand All @@ -83,6 +94,14 @@ func validateMatchers(matchers []Matcher) error {
}
}

if matchers[i].Type == "word" && !hasNonEmpty(matchers[i].Words) {
return fmt.Errorf("word matcher requires at least one non-empty word")
}

if matchers[i].Type == "regex" && !hasNonEmpty(matchers[i].Regex) {
return fmt.Errorf("regex matcher requires at least one non-empty pattern")
}

if matchers[i].Type == "range" {
if matchers[i].Min == nil && matchers[i].Max == nil {
return fmt.Errorf("range matcher requires min or max")
Expand Down
10 changes: 10 additions & 0 deletions internal/modules/favicon_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,16 @@ func TestValidateMatchers(t *testing.T) {
{name: "favicon with no hash", matchers: []Matcher{{Type: "favicon"}}, wantErr: true},
{name: "out-of-range hash", matchers: []Matcher{{Type: "favicon", Hash: []int64{99999999999}}}, wantErr: true},
{name: "non-favicon ignored", matchers: []Matcher{{Type: "word", Words: []string{"x"}}}, wantErr: false},
{name: "word with no words rejected", matchers: []Matcher{{Type: "word"}}, wantErr: true},
{name: "word with nil words rejected", matchers: []Matcher{{Type: "word", Words: nil}}, wantErr: true},
{name: "word with only empty string rejected", matchers: []Matcher{{Type: "word", Words: []string{""}}}, wantErr: true},
{name: "word with only empty strings rejected", matchers: []Matcher{{Type: "word", Words: []string{"", ""}}}, wantErr: true},
{name: "word with one real word allowed", matchers: []Matcher{{Type: "word", Words: []string{"real"}}}, wantErr: false},
{name: "word with empty and real word allowed", matchers: []Matcher{{Type: "word", Words: []string{"", "real"}}}, wantErr: false},
{name: "regex with no patterns rejected", matchers: []Matcher{{Type: "regex"}}, wantErr: true},
{name: "regex with nil patterns rejected", matchers: []Matcher{{Type: "regex", Regex: nil}}, wantErr: true},
{name: "regex with only empty pattern rejected", matchers: []Matcher{{Type: "regex", Regex: []string{""}}}, wantErr: true},
{name: "regex with one real pattern allowed", matchers: []Matcher{{Type: "regex", Regex: []string{"real"}}}, wantErr: false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
Expand Down
Loading