diff --git a/v2/pkg/operators/matchers/compile.go b/v2/pkg/operators/matchers/compile.go index 03d65fd4..73d17f9e 100644 --- a/v2/pkg/operators/matchers/compile.go +++ b/v2/pkg/operators/matchers/compile.go @@ -47,16 +47,5 @@ func (m *Matcher) CompileMatchers() error { } else { m.condition = ORCondition } - - // Setup the part of the request to match, if any. - if m.Part != "" { - m.part, ok = PartTypes[m.Part] - if !ok { - return fmt.Errorf("unknown matcher part specified: %s", m.Part) - } - } else { - m.part = BodyPart - } - return nil } diff --git a/v2/pkg/operators/matchers/match.go b/v2/pkg/operators/matchers/match.go index 3584d8d9..f39f0d0e 100644 --- a/v2/pkg/operators/matchers/match.go +++ b/v2/pkg/operators/matchers/match.go @@ -19,27 +19,27 @@ func (m *Matcher) Match(resp *http.Response, body, headers string, duration time return m.isNegative(m.matchSizeCode(len(body))) case WordsMatcher: // Match the parts as required for word check - if m.part == BodyPart { + if m.Part == "body" { return m.isNegative(m.matchWords(body)) - } else if m.part == HeaderPart { + } else if m.Part == "header" { return m.isNegative(m.matchWords(headers)) } else { return m.isNegative(m.matchWords(headers) || m.matchWords(body)) } case RegexMatcher: // Match the parts as required for regex check - if m.part == BodyPart { + if m.Part == "body" { return m.isNegative(m.matchRegex(body)) - } else if m.part == HeaderPart { + } else if m.Part == "header" { return m.isNegative(m.matchRegex(headers)) } else { return m.isNegative(m.matchRegex(headers) || m.matchRegex(body)) } case BinaryMatcher: // Match the parts as required for binary characters check - if m.part == BodyPart { + if m.Part == "body" { return m.isNegative(m.matchBinary(body)) - } else if m.part == HeaderPart { + } else if m.Part == "header" { return m.isNegative(m.matchBinary(headers)) } else { return m.isNegative(m.matchBinary(headers) || m.matchBinary(body)) @@ -55,7 +55,8 @@ func (m *Matcher) Match(resp *http.Response, body, headers string, duration time // MatchDNS matches a dns response against a given matcher func (m *Matcher) MatchDNS(msg *dns.Msg) bool { switch m.matcherType { - // [WIP] add dns status code matcher + case StatusMatcher: + return m.isNegative(m.matchStatusCode(msg.Rcode)) case SizeMatcher: return m.matchSizeCode(msg.Len()) case WordsMatcher: @@ -71,7 +72,6 @@ func (m *Matcher) MatchDNS(msg *dns.Msg) bool { // Match complex query return m.matchDSL(DNSToMap(msg, "")) } - return false } diff --git a/v2/pkg/operators/matchers/matchers.go b/v2/pkg/operators/matchers/matchers.go index be4b4af3..c825e5e1 100644 --- a/v2/pkg/operators/matchers/matchers.go +++ b/v2/pkg/operators/matchers/matchers.go @@ -6,12 +6,21 @@ import ( "github.com/Knetic/govaluate" ) -// Matcher is used to identify whether a template was successful. +// Matcher is used to match a part in the output from a protocol. type Matcher struct { // Type is the type of the matcher Type string `yaml:"type"` - // matcherType is the internal type of the matcher - matcherType MatcherType + // Condition is the optional condition between two matcher variables + // + // By default, the condition is assumed to be OR. + Condition string `yaml:"condition,omitempty"` + + // Part is the part of the data to match + Part string `yaml:"part,omitempty"` + + // Negative specifies if the match should be reversed + // It will only match if the condition is not true. + Negative bool `yaml:"negative,omitempty"` // Name is matcher Name Name string `yaml:"name,omitempty"` @@ -23,32 +32,16 @@ type Matcher struct { Words []string `yaml:"words,omitempty"` // Regex are the regex pattern required to be present in the response Regex []string `yaml:"regex,omitempty"` - // regexCompiled is the compiled variant - regexCompiled []*regexp.Regexp // Binary are the binary characters required to be present in the response Binary []string `yaml:"binary,omitempty"` // DSL are the dsl queries DSL []string `yaml:"dsl,omitempty"` - // dslCompiled is the compiled variant - dslCompiled []*govaluate.EvaluableExpression - // Condition is the optional condition between two matcher variables - // - // By default, the condition is assumed to be OR. - Condition string `yaml:"condition,omitempty"` - // condition is the condition of the matcher - condition ConditionType - - // Part is the part of the request to match - // - // By default, matching is performed in request body. - Part string `yaml:"part,omitempty"` - // part is the part of the request to match - part Part - - // Negative specifies if the match should be reversed - // It will only match if the condition is not true. - Negative bool `yaml:"negative,omitempty"` + // cached data for the compiled matcher + condition ConditionType + matcherType MatcherType + regexCompiled []*regexp.Regexp + dslCompiled []*govaluate.EvaluableExpression } // MatcherType is the type of the matcher specified @@ -95,30 +88,6 @@ var ConditionTypes = map[string]ConditionType{ "or": ORCondition, } -// Part is the part of the request to match -type Part int - -const ( - // BodyPart matches body of the response. - BodyPart Part = iota + 1 - // HeaderPart matches headers of the response. - HeaderPart - // AllPart matches both response body and headers of the response. - AllPart -) - -// PartTypes is an table for conversion of part type from string. -var PartTypes = map[string]Part{ - "body": BodyPart, - "header": HeaderPart, - "all": AllPart, -} - -// GetPart returns the part of the matcher -func (m *Matcher) GetPart() Part { - return m.part -} - // isNegative reverts the results of the match if the matcher // is of type negative. func (m *Matcher) isNegative(data bool) bool { diff --git a/v2/pkg/operators/matchers/util.go b/v2/pkg/operators/matchers/util.go deleted file mode 100644 index 22146891..00000000 --- a/v2/pkg/operators/matchers/util.go +++ /dev/null @@ -1,85 +0,0 @@ -package matchers - -import ( - "fmt" - "net/http" - "net/http/httputil" - "strings" - "time" - - "github.com/miekg/dns" -) - -const defaultFormat = "%s" - -// HTTPToMap Converts HTTP to Matcher Map -func HTTPToMap(resp *http.Response, body, headers string, duration time.Duration, format string) (m map[string]interface{}) { - m = make(map[string]interface{}) - - if format == "" { - format = defaultFormat - } - - m[fmt.Sprintf(format, "content_length")] = resp.ContentLength - m[fmt.Sprintf(format, "status_code")] = resp.StatusCode - - for k, v := range resp.Header { - k = strings.ToLower(strings.TrimSpace(strings.ReplaceAll(k, "-", "_"))) - m[fmt.Sprintf(format, k)] = strings.Join(v, " ") - } - - m[fmt.Sprintf(format, "all_headers")] = headers - m[fmt.Sprintf(format, "body")] = body - - if r, err := httputil.DumpResponse(resp, true); err == nil { - m[fmt.Sprintf(format, "raw")] = string(r) - } - - // Converts duration to seconds (floating point) for DSL syntax - m[fmt.Sprintf(format, "duration")] = duration.Seconds() - - return m -} - -// DNSToMap Converts DNS to Matcher Map -func DNSToMap(msg *dns.Msg, format string) (m map[string]interface{}) { - m = make(map[string]interface{}) - - if format == "" { - format = defaultFormat - } - - m[fmt.Sprintf(format, "rcode")] = msg.Rcode - - var qs string - - for _, question := range msg.Question { - qs += fmt.Sprintln(question.String()) - } - - m[fmt.Sprintf(format, "question")] = qs - - var exs string - for _, extra := range msg.Extra { - exs += fmt.Sprintln(extra.String()) - } - - m[fmt.Sprintf(format, "extra")] = exs - - var ans string - for _, answer := range msg.Answer { - ans += fmt.Sprintln(answer.String()) - } - - m[fmt.Sprintf(format, "answer")] = ans - - var nss string - for _, ns := range msg.Ns { - nss += fmt.Sprintln(ns.String()) - } - - m[fmt.Sprintf(format, "ns")] = nss - m[fmt.Sprintf(format, "raw")] = msg.String() - - return m -} diff --git a/v2/pkg/protocols/dns/matchers.go b/v2/pkg/protocols/dns/matchers.go new file mode 100644 index 00000000..371f3576 --- /dev/null +++ b/v2/pkg/protocols/dns/matchers.go @@ -0,0 +1,42 @@ +package dns + +import ( + "bytes" + + "github.com/miekg/dns" +) + +// responseToDSLMap converts a DNS response to a map for use in DSL matching +func responseToDSLMap(msg *dns.Msg) map[string]interface{} { + data := make(map[string]interface{}, 6) + + data["rcode"] = msg.Rcode + + buffer := &bytes.Buffer{} + for _, question := range msg.Question { + buffer.WriteString(question.String()) + } + data["question"] = buffer.String() + buffer.Reset() + + for _, extra := range msg.Extra { + buffer.WriteString(extra.String()) + } + data["extra"] = buffer.String() + buffer.Reset() + + for _, answer := range msg.Answer { + buffer.WriteString(answer.String()) + } + data["answer"] = buffer.String() + buffer.Reset() + + for _, ns := range msg.Ns { + buffer.WriteString(ns.String()) + } + data["ns"] = buffer.String() + buffer.Reset() + + data["raw"] = msg.String() + return data +} diff --git a/v2/pkg/protocols/http/matchers.go b/v2/pkg/protocols/http/matchers.go new file mode 100644 index 00000000..8b2a0a35 --- /dev/null +++ b/v2/pkg/protocols/http/matchers.go @@ -0,0 +1,32 @@ +package http + +import ( + "net/http" + "net/http/httputil" + "strings" + "time" +) + +// responseToDSLMap converts a HTTP response to a map for use in DSL matching +func responseToDSLMap(resp *http.Response, body, headers string, duration time.Duration, extra map[string]interface{}) map[string]interface{} { + data := make(map[string]interface{}, len(extra)+6+len(resp.Header)) + for k, v := range extra { + data[k] = v + } + + data["content_length"] = resp.ContentLength + data["status_code"] = resp.StatusCode + + for k, v := range resp.Header { + k = strings.ToLower(strings.TrimSpace(strings.ReplaceAll(k, "-", "_"))) + data[k] = strings.Join(v, " ") + } + data["all_headers"] = headers + data["body"] = body + + if r, err := httputil.DumpResponse(resp, true); err == nil { + data["raw"] = string(r) + } + data["duration"] = duration.Seconds() + return data +}