From 335cc21fd605e02b52d4fe79e2f423e1cf295269 Mon Sep 17 00:00:00 2001 From: yintaisha Date: Sat, 20 Jun 2026 20:24:32 +0800 Subject: [PATCH] http: normalize method names to uppercase in MatchMethod.Provision Signed-off-by: yintaisha --- modules/caddyhttp/matchers.go | 11 +++++++ modules/caddyhttp/matchers_test.go | 53 ++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+) diff --git a/modules/caddyhttp/matchers.go b/modules/caddyhttp/matchers.go index 9f84a90da..2bdd874b7 100644 --- a/modules/caddyhttp/matchers.go +++ b/modules/caddyhttp/matchers.go @@ -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) diff --git a/modules/caddyhttp/matchers_test.go b/modules/caddyhttp/matchers_test.go index c0f02d23c..b129aa4b7 100644 --- a/modules/caddyhttp/matchers_test.go +++ b/modules/caddyhttp/matchers_test.go @@ -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