mirror of
https://github.com/caddyserver/caddy.git
synced 2025-03-09 23:49:02 -04:00
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
This commit is contained in:
parent
f7f6e371ef
commit
9bdd6caa0b
@ -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) {
|
func TestResponseMatcher(t *testing.T) {
|
||||||
for i, tc := range []struct {
|
for i, tc := range []struct {
|
||||||
require ResponseMatcher
|
require ResponseMatcher
|
||||||
|
@ -25,6 +25,7 @@ import (
|
|||||||
func init() {
|
func init() {
|
||||||
caddy.RegisterModule(VarsMiddleware{})
|
caddy.RegisterModule(VarsMiddleware{})
|
||||||
caddy.RegisterModule(VarsMatcher{})
|
caddy.RegisterModule(VarsMatcher{})
|
||||||
|
caddy.RegisterModule(MatchVarsRE{})
|
||||||
}
|
}
|
||||||
|
|
||||||
// VarsMiddleware is an HTTP middleware which sets variables
|
// VarsMiddleware is an HTTP middleware which sets variables
|
||||||
@ -88,6 +89,74 @@ func (m VarsMatcher) Match(r *http.Request) bool {
|
|||||||
return true
|
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.
|
// 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.
|
// If the key does not exist, the return value will be nil.
|
||||||
func GetVar(ctx context.Context, key string) interface{} {
|
func GetVar(ctx context.Context, key string) interface{} {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user