From 7d5108d132d3bccfd8d83bc3fc2dbc46afde20ae Mon Sep 17 00:00:00 2001 From: Francis Lavoie Date: Thu, 1 Sep 2022 23:12:37 -0400 Subject: httpcaddyfile: Add shortcut for expression matchers (#4976) --- caddyconfig/caddyfile/lexer.go | 4 ++ caddyconfig/httpcaddyfile/httptype.go | 47 +++++++++++++++++----- .../integration/caddyfile_adapt/matcher_syntax.txt | 32 +++++++++++---- 3 files changed, 65 insertions(+), 18 deletions(-) diff --git a/caddyconfig/caddyfile/lexer.go b/caddyconfig/caddyfile/lexer.go index 4a23524..5605a6a 100644 --- a/caddyconfig/caddyfile/lexer.go +++ b/caddyconfig/caddyfile/lexer.go @@ -191,3 +191,7 @@ func Tokenize(input []byte, filename string) ([]Token, error) { } return tokens, nil } + +func (t Token) Quoted() bool { + return t.wasQuoted > 0 +} diff --git a/caddyconfig/httpcaddyfile/httptype.go b/caddyconfig/httpcaddyfile/httptype.go index 8fef131..2182a5b 100644 --- a/caddyconfig/httpcaddyfile/httptype.go +++ b/caddyconfig/httpcaddyfile/httptype.go @@ -1201,6 +1201,7 @@ func (st *ServerType) compileEncodedMatcherSets(sblock serverBlock) ([]caddy.Mod func parseMatcherDefinitions(d *caddyfile.Dispenser, matchers map[string]caddy.ModuleMap) error { for d.Next() { + // this is the "name" for "named matchers" definitionName := d.Val() if _, ok := matchers[definitionName]; ok { @@ -1208,16 +1209,9 @@ func parseMatcherDefinitions(d *caddyfile.Dispenser, matchers map[string]caddy.M } matchers[definitionName] = make(caddy.ModuleMap) - // in case there are multiple instances of the same matcher, concatenate - // their tokens (we expect that UnmarshalCaddyfile should be able to - // handle more than one segment); otherwise, we'd overwrite other - // instances of the matcher in this set - tokensByMatcherName := make(map[string][]caddyfile.Token) - for nesting := d.Nesting(); d.NextArg() || d.NextBlock(nesting); { - matcherName := d.Val() - tokensByMatcherName[matcherName] = append(tokensByMatcherName[matcherName], d.NextSegment()...) - } - for matcherName, tokens := range tokensByMatcherName { + // given a matcher name and the tokens following it, parse + // the tokens as a matcher module and record it + makeMatcher := func(matcherName string, tokens []caddyfile.Token) error { mod, err := caddy.GetModule("http.matchers." + matcherName) if err != nil { return fmt.Errorf("getting matcher module '%s': %v", matcherName, err) @@ -1235,6 +1229,39 @@ func parseMatcherDefinitions(d *caddyfile.Dispenser, matchers map[string]caddy.M return fmt.Errorf("matcher module '%s' is not a request matcher", matcherName) } matchers[definitionName][matcherName] = caddyconfig.JSON(rm, nil) + return nil + } + + // if the next token is quoted, we can assume it's not a matcher name + // and that it's probably an 'expression' matcher + if d.NextArg() { + if d.Token().Quoted() { + err := makeMatcher("expression", []caddyfile.Token{d.Token()}) + if err != nil { + return err + } + continue + } + + // if it wasn't quoted, then we need to rewind after calling + // d.NextArg() so the below properly grabs the matcher name + d.Prev() + } + + // in case there are multiple instances of the same matcher, concatenate + // their tokens (we expect that UnmarshalCaddyfile should be able to + // handle more than one segment); otherwise, we'd overwrite other + // instances of the matcher in this set + tokensByMatcherName := make(map[string][]caddyfile.Token) + for nesting := d.Nesting(); d.NextArg() || d.NextBlock(nesting); { + matcherName := d.Val() + tokensByMatcherName[matcherName] = append(tokensByMatcherName[matcherName], d.NextSegment()...) + } + for matcherName, tokens := range tokensByMatcherName { + err := makeMatcher(matcherName, tokens) + if err != nil { + return err + } } } return nil diff --git a/caddytest/integration/caddyfile_adapt/matcher_syntax.txt b/caddytest/integration/caddyfile_adapt/matcher_syntax.txt index a3e0a50..fb3dfb6 100644 --- a/caddytest/integration/caddyfile_adapt/matcher_syntax.txt +++ b/caddytest/integration/caddyfile_adapt/matcher_syntax.txt @@ -19,27 +19,30 @@ @matcher6 vars_regexp "{http.request.uri}" `\.([a-f0-9]{6})\.(css|js)$` respond @matcher6 "from vars_regexp matcher without name" - @matcher7 { + @matcher7 `path('/foo*') && method('GET')` + respond @matcher7 "inline expression matcher shortcut" + + @matcher8 { header Foo bar header Foo foobar header Bar foo } - respond @matcher7 "header matcher merging values of the same field" + respond @matcher8 "header matcher merging values of the same field" - @matcher8 { + @matcher9 { query foo=bar foo=baz bar=foo query bar=baz } - respond @matcher8 "query matcher merging pairs with the same keys" + respond @matcher9 "query matcher merging pairs with the same keys" - @matcher9 { + @matcher10 { header !Foo header Bar foo } - respond @matcher9 "header matcher with null field matcher" + respond @matcher10 "header matcher with null field matcher" - @matcher10 remote_ip private_ranges - respond @matcher10 "remote_ip matcher with private ranges" + @matcher11 remote_ip private_ranges + respond @matcher11 "remote_ip matcher with private ranges" } ---------- { @@ -152,6 +155,19 @@ } ] }, + { + "match": [ + { + "expression": "path('/foo*') \u0026\u0026 method('GET')" + } + ], + "handle": [ + { + "body": "inline expression matcher shortcut", + "handler": "static_response" + } + ] + }, { "match": [ { -- cgit v1.2.3