package matchers import ( "testing" "github.com/Knetic/govaluate" "github.com/projectdiscovery/nuclei/v3/pkg/operators/common/dsl" "github.com/stretchr/testify/require" ) func TestWordANDCondition(t *testing.T) { m := &Matcher{condition: ANDCondition, Words: []string{"a", "b"}} isMatched, matched := m.MatchWords("a b", nil) require.True(t, isMatched, "Could not match words with valid AND condition") require.Equal(t, m.Words, matched) isMatched, matched = m.MatchWords("b", nil) require.False(t, isMatched, "Could match words with invalid AND condition") require.Equal(t, []string{}, matched) } func TestRegexANDCondition(t *testing.T) { m := &Matcher{Type: MatcherTypeHolder{MatcherType: RegexMatcher}, Condition: "and", Regex: []string{"[a-z]{3}", "\\d{2}"}} err := m.CompileMatchers() require.Nil(t, err) isMatched, matched := m.MatchRegex("abc abcd 123") require.True(t, isMatched, "Could not match regex with valid AND condition") require.Equal(t, []string{"abc", "abc", "12"}, matched) isMatched, matched = m.MatchRegex("bc 1") require.False(t, isMatched, "Could match regex with invalid AND condition") require.Equal(t, []string{}, matched) } func TestORCondition(t *testing.T) { m := &Matcher{condition: ORCondition, Words: []string{"a", "b"}} isMatched, matched := m.MatchWords("a b", nil) require.True(t, isMatched, "Could not match valid word OR condition") require.Equal(t, []string{"a"}, matched) isMatched, matched = m.MatchWords("b", nil) require.True(t, isMatched, "Could not match valid word OR condition") require.Equal(t, []string{"b"}, matched) isMatched, matched = m.MatchWords("c", nil) require.False(t, isMatched, "Could match invalid word OR condition") require.Equal(t, []string{}, matched) } func TestRegexOrCondition(t *testing.T) { m := &Matcher{Type: MatcherTypeHolder{MatcherType: RegexMatcher}, Condition: "or", Regex: []string{"[a-z]{3}", "\\d{2}"}} err := m.CompileMatchers() require.Nil(t, err) isMatched, matched := m.MatchRegex("ab 123") require.True(t, isMatched, "Could not match valid regex OR condition") require.Equal(t, []string{"12"}, matched) isMatched, matched = m.MatchRegex("bc 1") require.False(t, isMatched, "Could match invalid regex OR condition") require.Equal(t, []string{}, matched) } func TestHexEncoding(t *testing.T) { m := &Matcher{Encoding: "hex", Type: MatcherTypeHolder{MatcherType: WordsMatcher}, Part: "body", Words: []string{"50494e47"}} err := m.CompileMatchers() require.Nil(t, err, "could not compile matcher") isMatched, matched := m.MatchWords("PING", nil) require.True(t, isMatched, "Could not match valid Hex condition") require.Equal(t, m.Words, matched) } func TestMatcher_MatchDSL(t *testing.T) { compiled, err := govaluate.NewEvaluableExpressionWithFunctions("contains(body, \"{{VARIABLE}}\")", dsl.HelperFunctions) require.Nil(t, err, "couldn't compile expression") m := &Matcher{Type: MatcherTypeHolder{MatcherType: DSLMatcher}, dslCompiled: []*govaluate.EvaluableExpression{compiled}} err = m.CompileMatchers() require.Nil(t, err, "could not compile matcher") values := []string{"PING", "pong"} for _, value := range values { isMatched := m.MatchDSL(map[string]interface{}{"body": value, "VARIABLE": value}) require.True(t, isMatched) } } func TestMatcher_MatchXPath_HTML(t *testing.T) { body := `
This domain is for use in illustrative examples in documents. You may use this domain in literature without prior coordination or asking for permission.
notvalid") require.False(t, isMatched, "Invalid xpath did not return false") } func TestMatchRegex_CaseInsensitivePrefixSkip(t *testing.T) { m := &Matcher{Type: MatcherTypeHolder{MatcherType: RegexMatcher}, Condition: "or", Regex: []string{"(?i)abc"}} err := m.CompileMatchers() require.NoError(t, err) ok, got := m.MatchRegex("zzz AbC yyy") require.True(t, ok) require.NotEmpty(t, got) } func TestMatchStatusCodeAndSize(t *testing.T) { mStatus := &Matcher{Status: []int{200, 302}} require.True(t, mStatus.MatchStatusCode(200)) require.True(t, mStatus.MatchStatusCode(302)) require.False(t, mStatus.MatchStatusCode(404)) mSize := &Matcher{Size: []int{5, 10}} require.True(t, mSize.MatchSize(5)) require.False(t, mSize.MatchSize(7)) } func TestMatchBinary_AND_OR(t *testing.T) { // AND should fail if any binary not present mAnd := &Matcher{Type: MatcherTypeHolder{MatcherType: BinaryMatcher}, Condition: "and", Binary: []string{"50494e47", "414141"}} // "PING", "AAA" require.NoError(t, mAnd.CompileMatchers()) ok, _ := mAnd.MatchBinary("PING") require.False(t, ok) // OR should succeed if any present mOr := &Matcher{Type: MatcherTypeHolder{MatcherType: BinaryMatcher}, Condition: "or", Binary: []string{"414141", "50494e47"}} // "AAA", "PING" require.NoError(t, mOr.CompileMatchers()) ok, got := mOr.MatchBinary("xxPINGyy") require.True(t, ok) require.NotEmpty(t, got) } func TestMatchRegex_LiteralPrefixShortCircuit(t *testing.T) { // AND: first regex has literal prefix "abc"; corpus lacks it => early false mAnd := &Matcher{Type: MatcherTypeHolder{MatcherType: RegexMatcher}, Condition: "and", Regex: []string{"abc[0-9]*", "[0-9]{2}"}} require.NoError(t, mAnd.CompileMatchers()) ok, matches := mAnd.MatchRegex("zzz 12 yyy") require.False(t, ok) require.Empty(t, matches) // OR: first regex skipped due to missing prefix, second matches => true mOr := &Matcher{Type: MatcherTypeHolder{MatcherType: RegexMatcher}, Condition: "or", Regex: []string{"abc[0-9]*", "[0-9]{2}"}} require.NoError(t, mOr.CompileMatchers()) ok, matches = mOr.MatchRegex("zzz 12 yyy") require.True(t, ok) require.Equal(t, []string{"12"}, matches) } func TestMatcher_MatchDSL_ErrorHandling(t *testing.T) { // First expression errors (division by zero), second is true bad, err := govaluate.NewEvaluableExpression("1 / 0") require.NoError(t, err) good, err := govaluate.NewEvaluableExpression("1 == 1") require.NoError(t, err) m := &Matcher{Type: MatcherTypeHolder{MatcherType: DSLMatcher}, Condition: "or", dslCompiled: []*govaluate.EvaluableExpression{bad, good}} require.NoError(t, m.CompileMatchers()) ok := m.MatchDSL(map[string]interface{}{}) require.True(t, ok) }