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
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
Gilbert Gilb's committed
Feb 20, 2020 at 18:55 UTC
30c14084abda8565215972fdffc5bbc41708b32b
2 files changed
+86
-10
modules/caddyhttp/matchers.go
+42
-7
@@ -363,11 +363,24 @@ func (m *MatchHeader) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
363
return nil
364
}
365
366
+// Like req.Header.Get(), but that works with Host header.
367
+// go's http module swallows "Host" header.
368
+func getHeader(r *http.Request, field string) []string {
369
+ field = textproto.CanonicalMIMEHeaderKey(field)
370
+
371
+ if field == "Host" {
372
+ return []string{r.Host}
373
+ }
374
+
375
+ return r.Header[field]
376
+}
377
+
378
// Match returns true if r matches m.
379
func (m MatchHeader) Match(r *http.Request) bool {
380
for field, allowedFieldVals := range m {
369
- actualFieldVals, fieldExists := r.Header[textproto.CanonicalMIMEHeaderKey(field)]
370
- if allowedFieldVals != nil && len(allowedFieldVals) == 0 && fieldExists {
381
+ actualFieldVals := getHeader(r, field)
382
+
383
+ if allowedFieldVals != nil && len(allowedFieldVals) == 0 && actualFieldVals != nil {
384
// a non-nil but empty list of allowed values means
385
// match if the header field exists at all
386
continue
@@ -377,6 +390,8 @@ func (m MatchHeader) Match(r *http.Request) bool {
390
for _, actualFieldVal := range actualFieldVals {
391
for _, allowedFieldVal := range allowedFieldVals {
392
switch {
393
+ case allowedFieldVal == "*":
394
+ match = true
395
case strings.HasPrefix(allowedFieldVal, "*") && strings.HasSuffix(allowedFieldVal, "*"):
396
match = strings.Contains(actualFieldVal, allowedFieldVal[1:len(allowedFieldVal)-1])
397
case strings.HasPrefix(allowedFieldVal, "*"):
@@ -412,11 +427,22 @@ func (m *MatchHeaderRE) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
427
*m = make(map[string]*MatchRegexp)
428
}
429
for d.Next() {
415
- var field, val string
416
- if !d.Args(&field, &val) {
430
+ var first, second, third string
431
+ if !d.Args(&first, &second) {
432
return d.ArgErr()
433
}
419
- (*m)[field] = &MatchRegexp{Pattern: val}
434
+
435
+ var name, field, val string
436
+ if d.Args(&third) {
437
+ name = first
438
+ field = second
439
+ val = third
440
+ } else {
441
+ field = first
442
+ val = second
443
+ }
444
+
445
+ (*m)[field] = &MatchRegexp{Pattern: val, Name: name}
446
}
447
return nil
448
}
@@ -424,8 +450,17 @@ func (m *MatchHeaderRE) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
450
// Match returns true if r matches m.
451
func (m MatchHeaderRE) Match(r *http.Request) bool {
452
for field, rm := range m {
427
- repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer)
428
- match := rm.Match(r.Header.Get(field), repl)
453
+ actualFieldVals := getHeader(r, field)
454
+
455
+ match := false
456
+ fieldVal:
457
+ for _, actualFieldVal := range actualFieldVals {
458
+ repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer)
459
+ if rm.Match(actualFieldVal, repl) {
460
+ match = true
461
+ break fieldVal
462
+ }
463
+ }
464
if !match {
465
return false
466
}
modules/caddyhttp/matchers_test.go
+44
-3
@@ -375,6 +375,7 @@ func TestHeaderMatcher(t *testing.T) {
375
for i, tc := range []struct {
376
match MatchHeader
377
input http.Header // make sure these are canonical cased (std lib will do that in a real request)
378
+ host string
379
expect bool
380
}{
381
{
@@ -417,8 +418,30 @@ func TestHeaderMatcher(t *testing.T) {
418
input: http.Header{"Field1": []string{"foo"}, "Field2": []string{"kapow"}},
419
expect: false,
420
},
421
+ {
422
+ match: MatchHeader{"field1": []string{"*"}},
423
+ input: http.Header{"Field1": []string{"foo"}},
424
+ expect: true,
425
+ },
426
+ {
427
+ match: MatchHeader{"field1": []string{"*"}},
428
+ input: http.Header{"Field2": []string{"foo"}},
429
+ expect: false,
430
+ },
431
+ {
432
+ match: MatchHeader{"host": []string{"localhost"}},
433
+ input: http.Header{},
434
+ host: "localhost",
435
+ expect: true,
436
+ },
437
+ {
438
+ match: MatchHeader{"host": []string{"localhost"}},
439
+ input: http.Header{},
440
+ host: "caddyserver.com",
441
+ expect: false,
442
+ },
443
} {
421
- req := &http.Request{Header: tc.input}
444
+ req := &http.Request{Header: tc.input, Host: tc.host}
445
actual := tc.match.Match(req)
446
if actual != tc.expect {
447
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) {
510
for i, tc := range []struct {
511
match MatchHeaderRE
512
input http.Header // make sure these are canonical cased (std lib will do that in a real request)
513
+ host string
514
expect bool
515
expectRepl map[string]string
516
}{
@@ -506,6 +530,23 @@ func TestHeaderREMatcher(t *testing.T) {
530
expect: true,
531
expectRepl: map[string]string{"name.1": "bar"},
532
},
533
+ {
534
+ match: MatchHeaderRE{"Field": &MatchRegexp{Pattern: "^foo.*$", Name: "name"}},
535
+ input: http.Header{"Field": []string{"barfoo", "foobar"}},
536
+ expect: true,
537
+ },
538
+ {
539
+ match: MatchHeaderRE{"host": &MatchRegexp{Pattern: "^localhost$", Name: "name"}},
540
+ input: http.Header{},
541
+ host: "localhost",
542
+ expect: true,
543
+ },
544
+ {
545
+ match: MatchHeaderRE{"host": &MatchRegexp{Pattern: "^local$", Name: "name"}},
546
+ input: http.Header{},
547
+ host: "localhost",
548
+ expect: false,
549
+ },
550
} {
551
// compile the regexp and validate its name
552
err := tc.match.Provision(caddy.Context{})
@@ -520,7 +561,7 @@ func TestHeaderREMatcher(t *testing.T) {
561
}
562
563
// set up the fake request and its Replacer
523
- req := &http.Request{Header: tc.input, URL: new(url.URL)}
564
+ req := &http.Request{Header: tc.input, URL: new(url.URL), Host: tc.host}
565
repl := caddy.NewReplacer()
566
ctx := context.WithValue(req.Context(), caddy.ReplacerCtxKey, repl)
567
req = req.WithContext(ctx)
@@ -579,7 +620,7 @@ func TestVarREMatcher(t *testing.T) {
620
expect: true,
621
},
622
{
582
- desc: "matching agaist value of var set by the VarsMiddleware and referenced by its placeholder succeeds",
623
+ desc: "matching against value of var set by the VarsMiddleware and referenced by its placeholder succeeds",
624
match: MatchVarsRE{"{http.vars.Var1}": &MatchRegexp{Pattern: "[vV]ar[0-9]"}},
625
input: VarsMiddleware{"Var1": "var1Value"},
626
expect: true,