http: normalize method names to uppercase in MatchMethod.Provision

Signed-off-by: yintaisha <yintaishan@outlook.com>
This commit is contained in:
yintaisha 2026-06-20 20:24:32 +08:00
parent 69d6ace32e
commit 335cc21fd6
2 changed files with 64 additions and 0 deletions

View file

@ -785,6 +785,15 @@ func (MatchMethod) CaddyModule() caddy.ModuleInfo {
}
}
// Provision uppercases all configured methods to ensure case-insensitive
// matching against incoming requests, whose methods are always uppercase.
func (m MatchMethod) Provision(_ caddy.Context) error {
for i := range m {
m[i] = strings.ToUpper(m[i])
}
return nil
}
// UnmarshalCaddyfile implements caddyfile.Unmarshaler.
func (m *MatchMethod) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
// iterate to merge multiple matchers into one
@ -1645,9 +1654,11 @@ var (
_ RequestMatcherWithError = (*MatchHost)(nil)
_ caddy.Provisioner = (*MatchHost)(nil)
_ RequestMatcherWithError = (*MatchPath)(nil)
_ caddy.Provisioner = (*MatchPath)(nil)
_ RequestMatcherWithError = (*MatchPathRE)(nil)
_ caddy.Provisioner = (*MatchPathRE)(nil)
_ RequestMatcherWithError = (*MatchMethod)(nil)
_ caddy.Provisioner = (*MatchMethod)(nil)
_ RequestMatcherWithError = (*MatchQuery)(nil)
_ RequestMatcherWithError = (*MatchHeader)(nil)
_ RequestMatcherWithError = (*MatchHeaderRE)(nil)

View file

@ -1218,6 +1218,59 @@ func TestNotMatcher(t *testing.T) {
}
}
func TestMethodMatcher(t *testing.T) {
for i, tc := range []struct {
scenario string
match MatchMethod
input string
expect bool
}{
{
scenario: "match uppercase GET",
match: MatchMethod{"GET"},
input: http.MethodGet,
expect: true,
},
{
scenario: "match lowercase get after Provision uppercases it",
match: MatchMethod{"get"},
input: http.MethodGet,
expect: true,
},
{
scenario: "match mixed case Post after Provision uppercases it",
match: MatchMethod{"Post"},
input: http.MethodPost,
expect: true,
},
{
scenario: "no match for wrong method",
match: MatchMethod{"GET"},
input: http.MethodPost,
expect: false,
},
{
scenario: "match one of multiple methods",
match: MatchMethod{"get", "post"},
input: http.MethodPost,
expect: true,
},
} {
req := &http.Request{Method: tc.input}
if err := tc.match.Provision(caddy.Context{}); err != nil {
t.Errorf("Test %d %v: provisioning failed: %v", i, tc.scenario, err)
}
actual, err := tc.match.MatchWithError(req)
if err != nil {
t.Errorf("Test %d %v: matching failed: %v", i, tc.scenario, err)
}
if actual != tc.expect {
t.Errorf("Test %d %v: Expected %t, got %t for method=%s", i, tc.scenario, tc.expect, actual, tc.input)
continue
}
}
}
func BenchmarkLargeHostMatcher(b *testing.B) {
// this benchmark simulates a large host matcher (thousands of entries) where each
// value is an exact hostname (not a placeholder or wildcard) - compare the results