summaryrefslogtreecommitdiff
path: root/modules/caddyhttp
diff options
context:
space:
mode:
Diffstat (limited to 'modules/caddyhttp')
-rw-r--r--modules/caddyhttp/matchers.go39
-rw-r--r--modules/caddyhttp/matchers_test.go38
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")