From 4f38bea337108cfcf98dc17cb9e3e4c093ad67e5 Mon Sep 17 00:00:00 2001 From: sonnemusk Date: Wed, 22 Jul 2026 13:42:24 +0800 Subject: [PATCH] fix: parse comma-separated X-Forwarded-Proto in Scheme Proxies may send multi-value X-Forwarded-Proto (e.g. "https, http"). The whole string failed isValidProto, so Scheme fell through incorrectly. Use the left-most valid token for X-Forwarded-Proto/Protocol and X-Url-Scheme. --- context.go | 18 +++++++++++++++--- context_test.go | 16 ++++++++++++++++ 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/context.go b/context.go index 9a7429f39..9146ae306 100644 --- a/context.go +++ b/context.go @@ -208,6 +208,18 @@ func isValidProto(proto string) bool { return false } +// firstValidProto returns the left-most valid scheme token from a possibly +// comma-separated header value (e.g. "https, http" from multi-proxy hops). +func firstValidProto(header string) string { + for _, part := range strings.Split(header, ",") { + p := strings.TrimSpace(part) + if isValidProto(p) { + return p + } + } + return "" +} + // Scheme returns the HTTP protocol scheme, `http` or `https`. func (c *Context) Scheme() string { // Can't use `r.Request.URL.Scheme` @@ -215,16 +227,16 @@ func (c *Context) Scheme() string { if c.IsTLS() { return "https" } - if scheme := c.request.Header.Get(HeaderXForwardedProto); isValidProto(scheme) { + if scheme := firstValidProto(c.request.Header.Get(HeaderXForwardedProto)); scheme != "" { return scheme } - if scheme := c.request.Header.Get(HeaderXForwardedProtocol); isValidProto(scheme) { + if scheme := firstValidProto(c.request.Header.Get(HeaderXForwardedProtocol)); scheme != "" { return scheme } if ssl := c.request.Header.Get(HeaderXForwardedSsl); ssl == "on" { return "https" } - if scheme := c.request.Header.Get(HeaderXUrlScheme); isValidProto(scheme) { + if scheme := firstValidProto(c.request.Header.Get(HeaderXUrlScheme)); scheme != "" { return scheme } return "http" diff --git a/context_test.go b/context_test.go index 9b820c6e3..5666e01f7 100644 --- a/context_test.go +++ b/context_test.go @@ -1140,6 +1140,22 @@ func TestContext_Scheme(t *testing.T) { }, expect: "HTTPS", }, + { + name: "uses left-most token from comma-separated X-Forwarded-Proto", + givenIsTLS: false, + givenHeaders: http.Header{ + HeaderXForwardedProto: []string{"https, http"}, + }, + expect: "https", + }, + { + name: "skips invalid tokens in comma-separated X-Forwarded-Proto", + givenIsTLS: false, + givenHeaders: http.Header{ + HeaderXForwardedProto: []string{"ftp, https"}, + }, + expect: "https", + }, { name: "uses X-Forwarded-Proto ws", givenIsTLS: false,