summaryrefslogtreecommitdiff
path: root/modules
diff options
context:
space:
mode:
authorMohammed Al Sahaf <msaa1990@gmail.com>2020-02-08 22:26:31 +0300
committerGitHub <noreply@github.com>2020-02-08 12:26:31 -0700
commit9bdd6caa0bcced5caf30872548700277f2db1877 (patch)
tree445d01c413bed501415d7dd098457c6fdbf9fcf3 /modules
parentf7f6e371efcc6ddbbef56cd02c9e02fbcede033f (diff)
v2: Implement RegExp Vars Matcher (#2997)
* implement regexp var matcher * use subtests pattern for tests * be more consistent with naming: MatchVarRE -> MatchVarsRE, var_regexp -> vars_regexp
Diffstat (limited to 'modules')
-rw-r--r--modules/caddyhttp/matchers_test.go86
-rw-r--r--modules/caddyhttp/vars.go69
2 files changed, 155 insertions, 0 deletions
diff --git a/modules/caddyhttp/matchers_test.go b/modules/caddyhttp/matchers_test.go
index 06f137e..58df171 100644
--- a/modules/caddyhttp/matchers_test.go
+++ b/modules/caddyhttp/matchers_test.go
@@ -545,6 +545,92 @@ func TestHeaderREMatcher(t *testing.T) {
}
}
+func TestVarREMatcher(t *testing.T) {
+ for i, tc := range []struct {
+ desc string
+ match MatchVarsRE
+ input VarsMiddleware
+ expect bool
+ expectRepl map[string]string
+ }{
+ {
+ desc: "match static value within var set by the VarsMiddleware succeeds",
+ match: MatchVarsRE{"Var1": &MatchRegexp{Pattern: "foo"}},
+ input: VarsMiddleware{"Var1": "here is foo val"},
+ expect: true,
+ },
+ {
+ desc: "value set by VarsMiddleware not satisfying regexp matcher fails to match",
+ match: MatchVarsRE{"Var1": &MatchRegexp{Pattern: "$foo^"}},
+ input: VarsMiddleware{"Var1": "foobar"},
+ expect: false,
+ },
+ {
+ desc: "successfully matched value is captured and its placeholder is added to replacer",
+ match: MatchVarsRE{"Var1": &MatchRegexp{Pattern: "^foo(.*)$", Name: "name"}},
+ input: VarsMiddleware{"Var1": "foobar"},
+ expect: true,
+ expectRepl: map[string]string{"name.1": "bar"},
+ },
+ {
+ desc: "matching against a value of standard variables succeeds",
+ match: MatchVarsRE{"{http.request.method}": &MatchRegexp{Pattern: "^G.[tT]$"}},
+ input: VarsMiddleware{},
+ expect: true,
+ },
+ {
+ desc: "matching agaist value of var set by the VarsMiddleware and referenced by its placeholder succeeds",
+ match: MatchVarsRE{"{http.vars.Var1}": &MatchRegexp{Pattern: "[vV]ar[0-9]"}},
+ input: VarsMiddleware{"Var1": "var1Value"},
+ expect: true,
+ },
+ } {
+ tc := tc // capture range value
+ t.Run(tc.desc, func(t *testing.T) {
+ t.Parallel()
+ // compile the regexp and validate its name
+ err := tc.match.Provision(caddy.Context{})
+ if err != nil {
+ t.Errorf("Test %d %v: Provisioning: %v", i, tc.match, err)
+ return
+ }
+ err = tc.match.Validate()
+ if err != nil {
+ t.Errorf("Test %d %v: Validating: %v", i, tc.match, err)
+ return
+ }
+
+ // set up the fake request and its Replacer
+ req := &http.Request{URL: new(url.URL), Method: http.MethodGet}
+ repl := caddy.NewReplacer()
+ ctx := context.WithValue(req.Context(), caddy.ReplacerCtxKey, repl)
+ ctx = context.WithValue(ctx, VarsCtxKey, make(map[string]interface{}))
+ req = req.WithContext(ctx)
+
+ addHTTPVarsToReplacer(repl, req, httptest.NewRecorder())
+
+ tc.input.ServeHTTP(httptest.NewRecorder(), req, emptyHandler)
+
+ actual := tc.match.Match(req)
+ if actual != tc.expect {
+ t.Errorf("Test %d [%v]: Expected %t, got %t for input '%s'",
+ i, tc.match, tc.expect, actual, tc.input)
+ return
+ }
+
+ for key, expectVal := range tc.expectRepl {
+ placeholder := fmt.Sprintf("{http.regexp.%s}", key)
+ actualVal := repl.ReplaceAll(placeholder, "<empty>")
+ if actualVal != expectVal {
+ t.Errorf("Test %d [%v]: Expected placeholder {http.regexp.%s} to be '%s' but got '%s'",
+ i, tc.match, key, expectVal, actualVal)
+ return
+ }
+ }
+ })
+ }
+}
+
func TestResponseMatcher(t *testing.T) {
for i, tc := range []struct {
require ResponseMatcher
diff --git a/modules/caddyhttp/vars.go b/modules/caddyhttp/vars.go
index 1208d9c..9561d46 100644
--- a/modules/caddyhttp/vars.go
+++ b/modules/caddyhttp/vars.go
@@ -25,6 +25,7 @@ import (
func init() {
caddy.RegisterModule(VarsMiddleware{})
caddy.RegisterModule(VarsMatcher{})
+ caddy.RegisterModule(MatchVarsRE{})
}
// VarsMiddleware is an HTTP middleware which sets variables
@@ -88,6 +89,74 @@ func (m VarsMatcher) Match(r *http.Request) bool {
return true
}
+// MatchVarsRE matches the value of the context variables by a given regular expression.
+//
+// Upon a match, it adds placeholders to the request: `{http.regexp.name.capture_group}`
+// where `name` is the regular expression's name, and `capture_group` is either
+// the named or positional capture group from the expression itself. If no name
+// is given, then the placeholder omits the name: `{http.regexp.capture_group}`
+// (potentially leading to collisions).
+type MatchVarsRE map[string]*MatchRegexp
+
+// CaddyModule returns the Caddy module information.
+func (MatchVarsRE) CaddyModule() caddy.ModuleInfo {
+ return caddy.ModuleInfo{
+ ID: "http.matchers.vars_regexp",
+ New: func() caddy.Module { return new(MatchVarsRE) },
+ }
+}
+
+// Provision compiles m's regular expressions.
+func (m MatchVarsRE) Provision(ctx caddy.Context) error {
+ for _, rm := range m {
+ err := rm.Provision(ctx)
+ if err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+// Match returns true if r matches m.
+func (m MatchVarsRE) Match(r *http.Request) bool {
+ vars := r.Context().Value(VarsCtxKey).(map[string]interface{})
+ repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer)
+ for k, rm := range m {
+ var varStr string
+ switch vv := vars[k].(type) {
+ case string:
+ varStr = vv
+ case fmt.Stringer:
+ varStr = vv.String()
+ case error:
+ varStr = vv.Error()
+ default:
+ varStr = fmt.Sprintf("%v", vv)
+ }
+ valExpanded := repl.ReplaceAll(varStr, "")
+ if match := rm.Match(valExpanded, repl); match {
+ return match
+ }
+
+ replacedVal := repl.ReplaceAll(k, "")
+ if match := rm.Match(replacedVal, repl); match {
+ return match
+ }
+ }
+ return false
+}
+
+// Validate validates m's regular expressions.
+func (m MatchVarsRE) Validate() error {
+ for _, rm := range m {
+ err := rm.Validate()
+ if err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
// GetVar gets a value out of the context's variable table by key.
// If the key does not exist, the return value will be nil.
func GetVar(ctx context.Context, key string) interface{} {