From 2ce4218a3062101869b57546962fd58c5b3c8364 Mon Sep 17 00:00:00 2001 From: David Stotijn Date: Thu, 31 Mar 2022 14:53:40 +0200 Subject: [PATCH] Add filter support for HTTP headers --- .../src/features/reqlog/components/Search.tsx | 3 + pkg/proxy/intercept/filter.go | 18 ++++ pkg/reqlog/search.go | 18 ++++ pkg/search/http.go | 82 +++++++++++++++++++ pkg/sender/search.go | 18 ++++ 5 files changed, 139 insertions(+) create mode 100644 pkg/search/http.go diff --git a/admin/src/features/reqlog/components/Search.tsx b/admin/src/features/reqlog/components/Search.tsx index 99e0760..073ad49 100644 --- a/admin/src/features/reqlog/components/Search.tsx +++ b/admin/src/features/reqlog/components/Search.tsx @@ -76,6 +76,7 @@ function Search(): JSX.Element { setSearchExpr(e.target.value)} onFocus={() => setFilterOpen(true)} + autoCorrect="false" + spellCheck="false" /> diff --git a/pkg/proxy/intercept/filter.go b/pkg/proxy/intercept/filter.go index 5be81c3..983ff8a 100644 --- a/pkg/proxy/intercept/filter.go +++ b/pkg/proxy/intercept/filter.go @@ -133,6 +133,15 @@ func matchReqInfixExpr(req *http.Request, expr search.InfixExpression) (bool, er return false, fmt.Errorf("failed to get string literal from request for left operand: %w", err) } + if leftVal == "headers" { + match, err := search.MatchHTTPHeaders(expr.Operator, expr.Right, req.Header) + if err != nil { + return false, fmt.Errorf("failed to match request HTTP headers: %w", err) + } + + return match, nil + } + if expr.Operator == search.TokOpRe || expr.Operator == search.TokOpNotRe { right, ok := expr.Right.(search.RegexpLiteral) if !ok { @@ -324,6 +333,15 @@ func matchResInfixExpr(res *http.Response, expr search.InfixExpression) (bool, e return false, fmt.Errorf("failed to get string literal from response for left operand: %w", err) } + if leftVal == "headers" { + match, err := search.MatchHTTPHeaders(expr.Operator, expr.Right, res.Header) + if err != nil { + return false, fmt.Errorf("failed to match request HTTP headers: %w", err) + } + + return match, nil + } + if expr.Operator == search.TokOpRe || expr.Operator == search.TokOpNotRe { right, ok := expr.Right.(search.RegexpLiteral) if !ok { diff --git a/pkg/reqlog/search.go b/pkg/reqlog/search.go index 9fa6053..673909f 100644 --- a/pkg/reqlog/search.go +++ b/pkg/reqlog/search.go @@ -98,6 +98,24 @@ func (reqLog RequestLog) matchInfixExpr(expr search.InfixExpression) (bool, erro leftVal := reqLog.getMappedStringLiteral(left.Value) + if leftVal == "req.headers" { + match, err := search.MatchHTTPHeaders(expr.Operator, expr.Right, reqLog.Header) + if err != nil { + return false, fmt.Errorf("failed to match request HTTP headers: %w", err) + } + + return match, nil + } + + if leftVal == "res.headers" && reqLog.Response != nil { + match, err := search.MatchHTTPHeaders(expr.Operator, expr.Right, reqLog.Response.Header) + if err != nil { + return false, fmt.Errorf("failed to match response HTTP headers: %w", err) + } + + return match, nil + } + if expr.Operator == search.TokOpRe || expr.Operator == search.TokOpNotRe { right, ok := expr.Right.(search.RegexpLiteral) if !ok { diff --git a/pkg/search/http.go b/pkg/search/http.go new file mode 100644 index 0000000..9f7e85a --- /dev/null +++ b/pkg/search/http.go @@ -0,0 +1,82 @@ +package search + +import ( + "errors" + "fmt" + "net/http" +) + +func MatchHTTPHeaders(op TokenType, expr Expression, headers http.Header) (bool, error) { + if headers == nil { + return false, nil + } + + switch op { + case TokOpEq: + strLiteral, ok := expr.(StringLiteral) + if !ok { + return false, errors.New("search: expression must be a string literal") + } + + // Return `true` if at least one header (: ) is equal to the string literal. + for key, values := range headers { + for _, value := range values { + if strLiteral.Value == fmt.Sprintf("%v: %v", key, value) { + return true, nil + } + } + } + + return false, nil + case TokOpNotEq: + strLiteral, ok := expr.(StringLiteral) + if !ok { + return false, errors.New("search: expression must be a string literal") + } + + // Return `true` if none of the headers (: ) are equal to the string literal. + for key, values := range headers { + for _, value := range values { + if strLiteral.Value == fmt.Sprintf("%v: %v", key, value) { + return false, nil + } + } + } + + return true, nil + case TokOpRe: + re, ok := expr.(RegexpLiteral) + if !ok { + return false, errors.New("search: expression must be a regular expression") + } + + // Return `true` if at least one header (: ) matches the regular expression. + for key, values := range headers { + for _, value := range values { + if re.MatchString(fmt.Sprintf("%v: %v", key, value)) { + return true, nil + } + } + } + + return false, nil + case TokOpNotRe: + re, ok := expr.(RegexpLiteral) + if !ok { + return false, errors.New("search: expression must be a regular expression") + } + + // Return `true` if none of the headers (: ) match the regular expression. + for key, values := range headers { + for _, value := range values { + if re.MatchString(fmt.Sprintf("%v: %v", key, value)) { + return false, nil + } + } + } + + return true, nil + default: + return false, fmt.Errorf("search: unsupported operator %q", op.String()) + } +} diff --git a/pkg/sender/search.go b/pkg/sender/search.go index 950ae80..ac79f9d 100644 --- a/pkg/sender/search.go +++ b/pkg/sender/search.go @@ -91,6 +91,24 @@ func (req Request) matchInfixExpr(expr search.InfixExpression) (bool, error) { leftVal := req.getMappedStringLiteral(left.Value) + if leftVal == "req.headers" { + match, err := search.MatchHTTPHeaders(expr.Operator, expr.Right, req.Header) + if err != nil { + return false, fmt.Errorf("failed to match request HTTP headers: %w", err) + } + + return match, nil + } + + if leftVal == "res.headers" && req.Response != nil { + match, err := search.MatchHTTPHeaders(expr.Operator, expr.Right, req.Response.Header) + if err != nil { + return false, fmt.Errorf("failed to match response HTTP headers: %w", err) + } + + return match, nil + } + if expr.Operator == search.TokOpRe || expr.Operator == search.TokOpNotRe { right, ok := expr.Right.(search.RegexpLiteral) if !ok {