caddyhttp: Enhance vars matcher (#4433)

* caddyhttp: Enhance vars matcher

Enable "or" logic for multiple values.
Fall back to checking placeholders if not a var name.

* Fix tests (thanks @mohammed90 !)
This commit is contained in:
Matt Holt 2021-12-13 13:59:58 -07:00 committed by GitHub
parent c04d24cafa
commit ecac03cdcb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 55 additions and 25 deletions

View file

@ -101,7 +101,9 @@
"match": [ "match": [
{ {
"vars": { "vars": {
"{http.request.uri}": "/vars-matcher" "{http.request.uri}": [
"/vars-matcher"
]
} }
} }
], ],

View file

@ -59,8 +59,15 @@ func (t VarsMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Request, next H
} }
// VarsMatcher is an HTTP request matcher which can match // VarsMatcher is an HTTP request matcher which can match
// requests based on variables in the context. // requests based on variables in the context. The key is
type VarsMatcher map[string]string // the name of the variable, and the values are possible
// values the variable can be in order to match (OR'ed).
//
// As a special case, this matcher can also match on
// placeholders generally. If the key is not an HTTP chain
// variable, it will be checked to see if it is a
// placeholder name, and if so, will compare its value.
type VarsMatcher map[string][]string
// CaddyModule returns the Caddy module information. // CaddyModule returns the Caddy module information.
func (VarsMatcher) CaddyModule() caddy.ModuleInfo { func (VarsMatcher) CaddyModule() caddy.ModuleInfo {
@ -73,14 +80,18 @@ func (VarsMatcher) CaddyModule() caddy.ModuleInfo {
// UnmarshalCaddyfile implements caddyfile.Unmarshaler. // UnmarshalCaddyfile implements caddyfile.Unmarshaler.
func (m *VarsMatcher) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { func (m *VarsMatcher) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
if *m == nil { if *m == nil {
*m = make(map[string]string) *m = make(map[string][]string)
} }
for d.Next() { for d.Next() {
var field, val string var field string
if !d.Args(&field, &val) { if !d.Args(&field) {
return d.Errf("malformed vars matcher: expected both field and value") return d.Errf("malformed vars matcher: expected field name")
} }
(*m)[field] = val vals := d.RemainingArgs()
if len(vals) == 0 {
return d.Errf("malformed vars matcher: expected at least one value to match against")
}
(*m)[field] = vals
if d.NextBlock(0) { if d.NextBlock(0) {
return d.Err("malformed vars matcher: blocks are not supported") return d.Err("malformed vars matcher: blocks are not supported")
} }
@ -88,29 +99,46 @@ func (m *VarsMatcher) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
return nil return nil
} }
// Match matches a request based on variables in the context. // Match matches a request based on variables in the context,
// or placeholders if the key is not a variable.
func (m VarsMatcher) Match(r *http.Request) bool { func (m VarsMatcher) Match(r *http.Request) bool {
if len(m) == 0 {
return true
}
vars := r.Context().Value(VarsCtxKey).(map[string]interface{}) vars := r.Context().Value(VarsCtxKey).(map[string]interface{})
repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer) repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer)
for k, v := range m {
keyExpanded := repl.ReplaceAll(k, "") for key, vals := range m {
valExpanded := repl.ReplaceAll(v, "") // look up the comparison value we will check against with this key
var varStr string matcherVarNameExpanded := repl.ReplaceAll(key, "")
switch vv := vars[keyExpanded].(type) { varValue, ok := vars[matcherVarNameExpanded]
case string: if !ok {
varStr = vv // as a special case, if it's not an HTTP variable,
case fmt.Stringer: // see if it's a placeholder name
varStr = vv.String() varValue, _ = repl.Get(matcherVarNameExpanded)
case error:
varStr = vv.Error()
default:
varStr = fmt.Sprintf("%v", vv)
} }
if varStr != valExpanded {
return false // see if any of the values given in the matcher match the actual value
for _, v := range vals {
matcherValExpanded := repl.ReplaceAll(v, "")
var varStr string
switch vv := varValue.(type) {
case string:
varStr = vv
case fmt.Stringer:
varStr = vv.String()
case error:
varStr = vv.Error()
default:
varStr = fmt.Sprintf("%v", vv)
}
if varStr == matcherValExpanded {
return true
}
} }
} }
return true return false
} }
// MatchVarsRE matches the value of the context variables by a given regular expression. // MatchVarsRE matches the value of the context variables by a given regular expression.