diff options
author | Gilbert Gilb's <gilbsgilbs@users.noreply.github.com> | 2020-02-20 18:55:47 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-02-20 10:55:47 -0700 |
commit | 30c14084abda8565215972fdffc5bbc41708b32b (patch) | |
tree | 0e54c2d8817b574fbbe6fc7f13687a62c68c94f0 /modules | |
parent | 99f91c4c6f812ebfae505a8c29a750965af0cfcb (diff) |
caddyhttp: Fixes for header and header_regexp directives (#3061)
* Fix crash when specifying "*" to header directive.
Fixes #3060
* Look Host header in header and header_regexp.
Also, if more than one header is provided, header_regexp now looks for
extra headers values to reflect the behavior from header.
Fixes #3059
* Fix parsing of named header_regexp in Caddyfile.
See #3059
Diffstat (limited to 'modules')
-rw-r--r-- | modules/caddyhttp/matchers.go | 49 | ||||
-rw-r--r-- | modules/caddyhttp/matchers_test.go | 47 |
2 files changed, 86 insertions, 10 deletions
diff --git a/modules/caddyhttp/matchers.go b/modules/caddyhttp/matchers.go index 3b890d9..3c357c6 100644 --- a/modules/caddyhttp/matchers.go +++ b/modules/caddyhttp/matchers.go @@ -363,11 +363,24 @@ func (m *MatchHeader) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { return nil } +// Like req.Header.Get(), but that works with Host header. +// go's http module swallows "Host" header. +func getHeader(r *http.Request, field string) []string { + field = textproto.CanonicalMIMEHeaderKey(field) + + if field == "Host" { + return []string{r.Host} + } + + return r.Header[field] +} + // Match returns true if r matches m. func (m MatchHeader) Match(r *http.Request) bool { for field, allowedFieldVals := range m { - actualFieldVals, fieldExists := r.Header[textproto.CanonicalMIMEHeaderKey(field)] - if allowedFieldVals != nil && len(allowedFieldVals) == 0 && fieldExists { + actualFieldVals := getHeader(r, field) + + if allowedFieldVals != nil && len(allowedFieldVals) == 0 && actualFieldVals != nil { // a non-nil but empty list of allowed values means // match if the header field exists at all continue @@ -377,6 +390,8 @@ func (m MatchHeader) Match(r *http.Request) bool { for _, actualFieldVal := range actualFieldVals { for _, allowedFieldVal := range allowedFieldVals { switch { + case allowedFieldVal == "*": + match = true case strings.HasPrefix(allowedFieldVal, "*") && strings.HasSuffix(allowedFieldVal, "*"): match = strings.Contains(actualFieldVal, allowedFieldVal[1:len(allowedFieldVal)-1]) case strings.HasPrefix(allowedFieldVal, "*"): @@ -412,11 +427,22 @@ func (m *MatchHeaderRE) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { *m = make(map[string]*MatchRegexp) } for d.Next() { - var field, val string - if !d.Args(&field, &val) { + var first, second, third string + if !d.Args(&first, &second) { return d.ArgErr() } - (*m)[field] = &MatchRegexp{Pattern: val} + + var name, field, val string + if d.Args(&third) { + name = first + field = second + val = third + } else { + field = first + val = second + } + + (*m)[field] = &MatchRegexp{Pattern: val, Name: name} } return nil } @@ -424,8 +450,17 @@ func (m *MatchHeaderRE) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { // Match returns true if r matches m. func (m MatchHeaderRE) Match(r *http.Request) bool { for field, rm := range m { - repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer) - match := rm.Match(r.Header.Get(field), repl) + actualFieldVals := getHeader(r, field) + + match := false + fieldVal: + for _, actualFieldVal := range actualFieldVals { + repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer) + if rm.Match(actualFieldVal, repl) { + match = true + break fieldVal + } + } if !match { return false } diff --git a/modules/caddyhttp/matchers_test.go b/modules/caddyhttp/matchers_test.go index 58df171..3b5afd2 100644 --- a/modules/caddyhttp/matchers_test.go +++ b/modules/caddyhttp/matchers_test.go @@ -375,6 +375,7 @@ func TestHeaderMatcher(t *testing.T) { for i, tc := range []struct { match MatchHeader input http.Header // make sure these are canonical cased (std lib will do that in a real request) + host string expect bool }{ { @@ -417,8 +418,30 @@ func TestHeaderMatcher(t *testing.T) { input: http.Header{"Field1": []string{"foo"}, "Field2": []string{"kapow"}}, expect: false, }, + { + match: MatchHeader{"field1": []string{"*"}}, + input: http.Header{"Field1": []string{"foo"}}, + expect: true, + }, + { + match: MatchHeader{"field1": []string{"*"}}, + input: http.Header{"Field2": []string{"foo"}}, + expect: false, + }, + { + match: MatchHeader{"host": []string{"localhost"}}, + input: http.Header{}, + host: "localhost", + expect: true, + }, + { + match: MatchHeader{"host": []string{"localhost"}}, + input: http.Header{}, + host: "caddyserver.com", + expect: false, + }, } { - req := &http.Request{Header: tc.input} + req := &http.Request{Header: tc.input, Host: tc.host} actual := tc.match.Match(req) if actual != tc.expect { t.Errorf("Test %d %v: Expected %t, got %t for '%s'", i, tc.match, tc.expect, actual, tc.input) @@ -487,6 +510,7 @@ func TestHeaderREMatcher(t *testing.T) { for i, tc := range []struct { match MatchHeaderRE input http.Header // make sure these are canonical cased (std lib will do that in a real request) + host string expect bool expectRepl map[string]string }{ @@ -506,6 +530,23 @@ func TestHeaderREMatcher(t *testing.T) { expect: true, expectRepl: map[string]string{"name.1": "bar"}, }, + { + match: MatchHeaderRE{"Field": &MatchRegexp{Pattern: "^foo.*$", Name: "name"}}, + input: http.Header{"Field": []string{"barfoo", "foobar"}}, + expect: true, + }, + { + match: MatchHeaderRE{"host": &MatchRegexp{Pattern: "^localhost$", Name: "name"}}, + input: http.Header{}, + host: "localhost", + expect: true, + }, + { + match: MatchHeaderRE{"host": &MatchRegexp{Pattern: "^local$", Name: "name"}}, + input: http.Header{}, + host: "localhost", + expect: false, + }, } { // compile the regexp and validate its name err := tc.match.Provision(caddy.Context{}) @@ -520,7 +561,7 @@ func TestHeaderREMatcher(t *testing.T) { } // set up the fake request and its Replacer - req := &http.Request{Header: tc.input, URL: new(url.URL)} + req := &http.Request{Header: tc.input, URL: new(url.URL), Host: tc.host} repl := caddy.NewReplacer() ctx := context.WithValue(req.Context(), caddy.ReplacerCtxKey, repl) req = req.WithContext(ctx) @@ -579,7 +620,7 @@ func TestVarREMatcher(t *testing.T) { expect: true, }, { - desc: "matching agaist value of var set by the VarsMiddleware and referenced by its placeholder succeeds", + desc: "matching against 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, |