diff options
Diffstat (limited to 'modules')
-rw-r--r-- | modules/caddyhttp/matchers.go | 39 | ||||
-rw-r--r-- | modules/caddyhttp/matchers_test.go | 38 |
2 files changed, 61 insertions, 16 deletions
diff --git a/modules/caddyhttp/matchers.go b/modules/caddyhttp/matchers.go index 40a767f..f63e48e 100644 --- a/modules/caddyhttp/matchers.go +++ b/modules/caddyhttp/matchers.go @@ -46,7 +46,17 @@ type ( // [customized or disabled](/docs/json/apps/http/servers/automatic_https/). MatchHost []string - // MatchPath matches requests by the URI's path (case-insensitive). + // MatchPath matches requests by the URI's path (case-insensitive). Path + // matches are exact, but wildcards may be used: + // + // - At the end, for a prefix match (`/prefix/*`) + // - At the beginning, for a suffix match (`*.suffix`) + // - On both sides, for a substring match (`*/contains/*`) + // - In the middle, for a globular match (`/accounts/*/info`) + // + // This matcher is fast, so it does not support regular expressions or + // capture groups. For slower but more capable matching, use the path_regexp + // matcher. MatchPath []string // MatchPathRE matches requests by a regular expression on the URI's path. @@ -197,11 +207,15 @@ func (m MatchPath) Match(r *http.Request) bool { // being matched by *.php to be treated as PHP scripts lowerPath = strings.TrimRight(lowerPath, ". ") + repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer) + for _, matchPath := range m { - // special case: first character is equals sign, - // treat it as an exact match - if strings.HasPrefix(matchPath, "=") { - if lowerPath == matchPath[1:] { + matchPath = repl.ReplaceAll(matchPath, "") + + // special case: first and last characters are wildcard, + // treat it as a fast substring match + if strings.HasPrefix(matchPath, "*") && strings.HasSuffix(matchPath, "*") { + if strings.Contains(lowerPath, matchPath[1:len(matchPath)-1]) { return true } continue @@ -216,15 +230,22 @@ func (m MatchPath) Match(r *http.Request) bool { continue } + // special case: last character is a wildcard, + // treat it as a fast prefix match + if strings.HasSuffix(matchPath, "*") { + if strings.HasPrefix(lowerPath, matchPath[:len(matchPath)-1]) { + return true + } + continue + } + + // for everything else, try globular matching, which also + // is exact matching if there are no glob/wildcard chars; // can ignore error here because we can't handle it anyway matches, _ := filepath.Match(matchPath, lowerPath) if matches { return true } - - if strings.HasPrefix(lowerPath, matchPath) { - return true - } } return false } diff --git a/modules/caddyhttp/matchers_test.go b/modules/caddyhttp/matchers_test.go index 34a1647..8e06546 100644 --- a/modules/caddyhttp/matchers_test.go +++ b/modules/caddyhttp/matchers_test.go @@ -183,8 +183,18 @@ func TestPathMatcher(t *testing.T) { expect: false, }, { + match: MatchPath{"/foo/bar/"}, + input: "/foo/bar/", + expect: true, + }, + { match: MatchPath{"/foo/bar/", "/other"}, input: "/other/", + expect: false, + }, + { + match: MatchPath{"/foo/bar/", "/other"}, + input: "/other", expect: true, }, { @@ -213,19 +223,19 @@ func TestPathMatcher(t *testing.T) { expect: false, }, { - match: MatchPath{"=/foo"}, - input: "/foo", + match: MatchPath{"*substring*"}, + input: "/foo/substring/bar.txt", expect: true, }, { - match: MatchPath{"=/foo"}, + match: MatchPath{"/foo"}, input: "/foo/bar", expect: false, }, { - match: MatchPath{"=/foo"}, - input: "/FOO", - expect: true, + match: MatchPath{"/foo"}, + input: "/foo/bar", + expect: false, }, { match: MatchPath{"/foo"}, @@ -233,12 +243,21 @@ func TestPathMatcher(t *testing.T) { expect: true, }, { + match: MatchPath{"/foo*"}, + input: "/FOOOO", + expect: true, + }, + { match: MatchPath{"/foo/bar.txt"}, input: "/foo/BAR.txt", expect: true, }, } { req := &http.Request{URL: &url.URL{Path: tc.input}} + repl := caddy.NewReplacer() + ctx := context.WithValue(req.Context(), caddy.ReplacerCtxKey, repl) + req = req.WithContext(ctx) + 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) @@ -251,8 +270,13 @@ func TestPathMatcherWindows(t *testing.T) { // only Windows has this bug where it will ignore // trailing dots and spaces in a filename, but we // test for it on all platforms to be more consistent - match := MatchPath{"*.php"} + req := &http.Request{URL: &url.URL{Path: "/index.php . . .."}} + repl := caddy.NewReplacer() + ctx := context.WithValue(req.Context(), caddy.ReplacerCtxKey, repl) + req = req.WithContext(ctx) + + match := MatchPath{"*.php"} matched := match.Match(req) if !matched { t.Errorf("Expected to match; should ignore trailing dots and spaces") |