feat(modules): add case-insensitive word matcher option (#319)

This commit is contained in:
Tigah
2026-07-22 12:51:39 -07:00
committed by GitHub
parent 356b16fc63
commit 5ebeb89f93
3 changed files with 31 additions and 15 deletions
+13 -4
View File
@@ -383,7 +383,7 @@ func checkMatcher(m *Matcher, resp *http.Response, body string) bool {
return false
case "word":
return checkWords(getPart(m.Part, resp, body), m.Words, m.Condition)
return checkWords(getPart(m.Part, resp, body), m.Words, m.Condition, m.CaseInsensitive)
case "regex":
return checkRegex(getPart(m.Part, resp, body), m.Regex, m.Condition)
@@ -436,10 +436,19 @@ func getPart(part string, resp *http.Response, body string) string {
}
// checkWords checks if any/all words are found.
func checkWords(content string, words []string, condition string) bool {
func checkWords(content string, words []string, condition string, caseInsensitive bool) bool {
if caseInsensitive {
content = strings.ToLower(content)
}
fold := func(w string) string {
if caseInsensitive {
return strings.ToLower(w)
}
return w
}
if condition == "or" {
for _, word := range words {
if strings.Contains(content, word) {
if strings.Contains(content, fold(word)) {
return true
}
}
@@ -447,7 +456,7 @@ func checkWords(content string, words []string, condition string) bool {
}
// Default to AND
for _, word := range words {
if !strings.Contains(content, word) {
if !strings.Contains(content, fold(word)) {
return false
}
}
+16 -11
View File
@@ -197,22 +197,27 @@ func TestCheckWords(t *testing.T) {
const content = "alpha beta gamma"
tests := []struct {
name string
words []string
condition string
expect bool
name string
content string
words []string
condition string
caseInsens bool
expect bool
}{
{name: "and all present", words: []string{"alpha", "gamma"}, condition: "and", expect: true},
{name: "and missing", words: []string{"alpha", "delta"}, condition: "and", expect: false},
{name: "or present", words: []string{"delta", "beta"}, condition: "or", expect: true},
{name: "or absent", words: []string{"delta", "epsilon"}, condition: "or", expect: false},
{name: "empty under and matches vacuously", words: nil, condition: "and", expect: true},
{name: "empty under or matches nothing", words: nil, condition: "or", expect: false},
{name: "and all present", content: content, words: []string{"alpha", "gamma"}, condition: "and", expect: true},
{name: "and missing", content: content, words: []string{"alpha", "delta"}, condition: "and", expect: false},
{name: "or present", content: content, words: []string{"delta", "beta"}, condition: "or", expect: true},
{name: "or absent", content: content, words: []string{"delta", "epsilon"}, condition: "or", expect: false},
{name: "empty under and matches vacuously", content: content, words: nil, condition: "and", expect: true},
{name: "empty under or matches nothing", content: content, words: nil, condition: "or", expect: false},
{name: "case mismatch fails by default", content: "AdMiN panel", words: []string{"admin"}, condition: "or", expect: false},
{name: "case-insensitive folds both sides", content: "AdMiN panel", words: []string{"admin"}, condition: "or", caseInsens: true, expect: true},
{name: "case-insensitive folds mixed needle", content: "admin panel", words: []string{"AdMiN"}, condition: "or", caseInsens: true, expect: true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := checkWords(content, tt.words, tt.condition); got != tt.expect {
if got := checkWords(tt.content, tt.words, tt.condition, tt.caseInsens); got != tt.expect {
t.Errorf("checkWords = %v, want %v", got, tt.expect)
}
})
+2
View File
@@ -95,6 +95,8 @@ type Matcher struct {
Hash []int64 `yaml:"hash,omitempty"` // favicon: shodan mmh3 hashes (signed or unsigned)
Condition string `yaml:"condition"` // and, or
Negative bool `yaml:"negative"`
// CaseInsensitive folds word matching to lower-case when set (word matcher only).
CaseInsensitive bool `yaml:"case-insensitive,omitempty"`
}
// Extractor defines data extraction from responses.