@marcel91200 / caddy Mirror · read-only / commits / 9bdd6caa
Fork 0

Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS

Mirrored from https://github.com/Slazee/caddy.git

Synced Nov 14, 05:15 UTC

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

Mohammed Al Sahaf committed Feb 8, 2020 at 22:26 UTC 9bdd6caa0bcced5caf30872548700277f2db1877
2 files changed +155
modules/caddyhttp/matchers_test.go
+86
@@ -545,6 +545,92 @@ func TestHeaderREMatcher(t *testing.T) {
545 }
546 }
547
548 +func TestVarREMatcher(t *testing.T) {
549 + for i, tc := range []struct {
550 + desc string
551 + match MatchVarsRE
552 + input VarsMiddleware
553 + expect bool
554 + expectRepl map[string]string
555 + }{
556 + {
557 + desc: "match static value within var set by the VarsMiddleware succeeds",
558 + match: MatchVarsRE{"Var1": &MatchRegexp{Pattern: "foo"}},
559 + input: VarsMiddleware{"Var1": "here is foo val"},
560 + expect: true,
561 + },
562 + {
563 + desc: "value set by VarsMiddleware not satisfying regexp matcher fails to match",
564 + match: MatchVarsRE{"Var1": &MatchRegexp{Pattern: "$foo^"}},
565 + input: VarsMiddleware{"Var1": "foobar"},
566 + expect: false,
567 + },
568 + {
569 + desc: "successfully matched value is captured and its placeholder is added to replacer",
570 + match: MatchVarsRE{"Var1": &MatchRegexp{Pattern: "^foo(.*)$", Name: "name"}},
571 + input: VarsMiddleware{"Var1": "foobar"},
572 + expect: true,
573 + expectRepl: map[string]string{"name.1": "bar"},
574 + },
575 + {
576 + desc: "matching against a value of standard variables succeeds",
577 + match: MatchVarsRE{"{http.request.method}": &MatchRegexp{Pattern: "^G.[tT]$"}},
578 + input: VarsMiddleware{},
579 + expect: true,
580 + },
581 + {
582 + desc: "matching agaist value of var set by the VarsMiddleware and referenced by its placeholder succeeds",
583 + match: MatchVarsRE{"{http.vars.Var1}": &MatchRegexp{Pattern: "[vV]ar[0-9]"}},
584 + input: VarsMiddleware{"Var1": "var1Value"},
585 + expect: true,
586 + },
587 + } {
588 + tc := tc // capture range value
589 + t.Run(tc.desc, func(t *testing.T) {
590 + t.Parallel()
591 + // compile the regexp and validate its name
592 + err := tc.match.Provision(caddy.Context{})
593 + if err != nil {
594 + t.Errorf("Test %d %v: Provisioning: %v", i, tc.match, err)
595 + return
596 + }
597 + err = tc.match.Validate()
598 + if err != nil {
599 + t.Errorf("Test %d %v: Validating: %v", i, tc.match, err)
600 + return
601 + }
602 +
603 + // set up the fake request and its Replacer
604 + req := &http.Request{URL: new(url.URL), Method: http.MethodGet}
605 + repl := caddy.NewReplacer()
606 + ctx := context.WithValue(req.Context(), caddy.ReplacerCtxKey, repl)
607 + ctx = context.WithValue(ctx, VarsCtxKey, make(map[string]interface{}))
608 + req = req.WithContext(ctx)
609 +
610 + addHTTPVarsToReplacer(repl, req, httptest.NewRecorder())
611 +
612 + tc.input.ServeHTTP(httptest.NewRecorder(), req, emptyHandler)
613 +
614 + actual := tc.match.Match(req)
615 + if actual != tc.expect {
616 + t.Errorf("Test %d [%v]: Expected %t, got %t for input '%s'",
617 + i, tc.match, tc.expect, actual, tc.input)
618 + return
619 + }
620 +
621 + for key, expectVal := range tc.expectRepl {
622 + placeholder := fmt.Sprintf("{http.regexp.%s}", key)
623 + actualVal := repl.ReplaceAll(placeholder, "<empty>")
624 + if actualVal != expectVal {
625 + t.Errorf("Test %d [%v]: Expected placeholder {http.regexp.%s} to be '%s' but got '%s'",
626 + i, tc.match, key, expectVal, actualVal)
627 + return
628 + }
629 + }
630 + })
631 + }
632 +}
633 +
634 func TestResponseMatcher(t *testing.T) {
635 for i, tc := range []struct {
636 require ResponseMatcher
modules/caddyhttp/vars.go
+69
@@ -25,6 +25,7 @@ import (
25 func init() {
26 caddy.RegisterModule(VarsMiddleware{})
27 caddy.RegisterModule(VarsMatcher{})
28 + caddy.RegisterModule(MatchVarsRE{})
29 }
30
31 // VarsMiddleware is an HTTP middleware which sets variables
@@ -88,6 +89,74 @@ func (m VarsMatcher) Match(r *http.Request) bool {
89 return true
90 }
91
92 +// MatchVarsRE matches the value of the context variables by a given regular expression.
93 +//
94 +// Upon a match, it adds placeholders to the request: `{http.regexp.name.capture_group}`
95 +// where `name` is the regular expression's name, and `capture_group` is either
96 +// the named or positional capture group from the expression itself. If no name
97 +// is given, then the placeholder omits the name: `{http.regexp.capture_group}`
98 +// (potentially leading to collisions).
99 +type MatchVarsRE map[string]*MatchRegexp
100 +
101 +// CaddyModule returns the Caddy module information.
102 +func (MatchVarsRE) CaddyModule() caddy.ModuleInfo {
103 + return caddy.ModuleInfo{
104 + ID: "http.matchers.vars_regexp",
105 + New: func() caddy.Module { return new(MatchVarsRE) },
106 + }
107 +}
108 +
109 +// Provision compiles m's regular expressions.
110 +func (m MatchVarsRE) Provision(ctx caddy.Context) error {
111 + for _, rm := range m {
112 + err := rm.Provision(ctx)
113 + if err != nil {
114 + return err
115 + }
116 + }
117 + return nil
118 +}
119 +
120 +// Match returns true if r matches m.
121 +func (m MatchVarsRE) Match(r *http.Request) bool {
122 + vars := r.Context().Value(VarsCtxKey).(map[string]interface{})
123 + repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer)
124 + for k, rm := range m {
125 + var varStr string
126 + switch vv := vars[k].(type) {
127 + case string:
128 + varStr = vv
129 + case fmt.Stringer:
130 + varStr = vv.String()
131 + case error:
132 + varStr = vv.Error()
133 + default:
134 + varStr = fmt.Sprintf("%v", vv)
135 + }
136 + valExpanded := repl.ReplaceAll(varStr, "")
137 + if match := rm.Match(valExpanded, repl); match {
138 + return match
139 + }
140 +
141 + replacedVal := repl.ReplaceAll(k, "")
142 + if match := rm.Match(replacedVal, repl); match {
143 + return match
144 + }
145 + }
146 + return false
147 +}
148 +
149 +// Validate validates m's regular expressions.
150 +func (m MatchVarsRE) Validate() error {
151 + for _, rm := range m {
152 + err := rm.Validate()
153 + if err != nil {
154 + return err
155 + }
156 + }
157 + return nil
158 +}
159 +
160 // GetVar gets a value out of the context's variable table by key.
161 // If the key does not exist, the return value will be nil.
162 func GetVar(ctx context.Context, key string) interface{} {