refactor(recovery): inline header prefix check and strengthen case tests

Drop the small hasHeaderPrefixFold helper and inline the
len+EqualFold check in secureRequestDump for a simpler, single-site
hardcoded replacement.

Also switch the existing lowercase/mixed-case Authorization tests
to write directly to r.Header (bypassing http.Header.Set canonicalization)
so they actually exercise case-insensitive matching on the wire, and
add uppercase Authorization plus lowercase/uppercase Proxy-Authorization
cases.
This commit is contained in:
barry3406 2026-04-19 09:11:11 -07:00
parent 3f1b179fdc
commit 528a95d48d
2 changed files with 40 additions and 9 deletions

View File

@ -100,22 +100,21 @@ func CustomRecoveryWithWriter(out io.Writer, handle RecoveryFunc) HandlerFunc {
func secureRequestDump(r *http.Request) string {
httpRequest, _ := httputil.DumpRequest(r, false)
lines := strings.Split(bytesconv.BytesToString(httpRequest), "\r\n")
const (
authPrefix = "Authorization:"
proxyPrefix = "Proxy-Authorization:"
)
for i, line := range lines {
switch {
case hasHeaderPrefixFold(line, "Authorization:"):
case len(line) >= len(authPrefix) && strings.EqualFold(line[:len(authPrefix)], authPrefix):
lines[i] = "Authorization: *"
case hasHeaderPrefixFold(line, "Proxy-Authorization:"):
case len(line) >= len(proxyPrefix) && strings.EqualFold(line[:len(proxyPrefix)], proxyPrefix):
lines[i] = "Proxy-Authorization: *"
}
}
return strings.Join(lines, "\r\n")
}
// hasHeaderPrefixFold reports whether line begins with prefix, ignoring ASCII case.
func hasHeaderPrefixFold(line, prefix string) bool {
return len(line) >= len(prefix) && strings.EqualFold(line[:len(prefix)], prefix)
}
func defaultHandleRecovery(c *Context, _ any) {
c.AbortWithStatus(http.StatusInternalServerError)
}

View File

@ -274,20 +274,32 @@ func TestSecureRequestDump(t *testing.T) {
wantNotContain: "Bearer secret-token",
},
{
// Bypass http.Header.Set canonicalization to put a lowercase
// header name on the wire and verify case-insensitive matching.
name: "authorization header lowercase",
req: func() *http.Request {
r, _ := http.NewRequest(http.MethodGet, "http://example.com", nil)
r.Header.Set("authorization", "some-secret")
r.Header["authorization"] = []string{"some-secret"}
return r
}(),
wantContains: "Authorization: *",
wantNotContain: "some-secret",
},
{
name: "AUTHORIZATION header uppercase",
req: func() *http.Request {
r, _ := http.NewRequest(http.MethodGet, "http://example.com", nil)
r.Header["AUTHORIZATION"] = []string{"UPPER-SECRET"}
return r
}(),
wantContains: "Authorization: *",
wantNotContain: "UPPER-SECRET",
},
{
name: "Authorization header mixed case",
req: func() *http.Request {
r, _ := http.NewRequest(http.MethodGet, "http://example.com", nil)
r.Header.Set("AuThOrIzAtIoN", "token123")
r.Header["AuThOrIzAtIoN"] = []string{"token123"}
return r
}(),
wantContains: "Authorization: *",
@ -303,6 +315,26 @@ func TestSecureRequestDump(t *testing.T) {
wantContains: "Proxy-Authorization: *",
wantNotContain: "Basic cHJveHk6c2VjcmV0",
},
{
name: "proxy-authorization header lowercase",
req: func() *http.Request {
r, _ := http.NewRequest(http.MethodGet, "http://example.com", nil)
r.Header["proxy-authorization"] = []string{"Basic bG93ZXI="}
return r
}(),
wantContains: "Proxy-Authorization: *",
wantNotContain: "Basic bG93ZXI=",
},
{
name: "PROXY-AUTHORIZATION header uppercase",
req: func() *http.Request {
r, _ := http.NewRequest(http.MethodGet, "http://example.com", nil)
r.Header["PROXY-AUTHORIZATION"] = []string{"Basic VVBQRVI="}
return r
}(),
wantContains: "Proxy-Authorization: *",
wantNotContain: "Basic VVBQRVI=",
},
{
name: "No Authorization header",
req: func() *http.Request {