summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGilbert Gilb's <gilbsgilbs@users.noreply.github.com>2020-02-20 18:55:47 +0100
committerGitHub <noreply@github.com>2020-02-20 10:55:47 -0700
commit30c14084abda8565215972fdffc5bbc41708b32b (patch)
tree0e54c2d8817b574fbbe6fc7f13687a62c68c94f0
parent99f91c4c6f812ebfae505a8c29a750965af0cfcb (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
-rw-r--r--modules/caddyhttp/matchers.go49
-rw-r--r--modules/caddyhttp/matchers_test.go47
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,