From 4b7870fb8b43286f4e51edf09d1ff9dc8e9707b4 Mon Sep 17 00:00:00 2001 From: sonnemusk Date: Wed, 22 Jul 2026 13:43:34 +0800 Subject: [PATCH 1/2] fix(middleware): match gzip Content-Encoding case-insensitively Proxies and clients may send Content-Encoding: Gzip or GZIP. Exact string compare skipped decompression. Trim and EqualFold against "gzip". --- middleware/decompress.go | 10 +++++++++- middleware/decompress_test.go | 26 ++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/middleware/decompress.go b/middleware/decompress.go index 501ee6c5b..61fac721e 100644 --- a/middleware/decompress.go +++ b/middleware/decompress.go @@ -7,6 +7,7 @@ import ( "compress/gzip" "io" "net/http" + "strings" "sync" "github.com/labstack/echo/v5" @@ -82,7 +83,7 @@ func (config DecompressConfig) ToMiddleware() (echo.MiddlewareFunc, error) { return next(c) } - if c.Request().Header.Get(echo.HeaderContentEncoding) != GZIPEncoding { + if !isGzipContentEncoding(c.Request().Header.Get(echo.HeaderContentEncoding)) { return next(c) } @@ -127,6 +128,13 @@ func (config DecompressConfig) ToMiddleware() (echo.MiddlewareFunc, error) { }, nil } + +// isGzipContentEncoding reports whether Content-Encoding is gzip (case-insensitive). +// Surrounding whitespace is ignored so common proxy variations still match. +func isGzipContentEncoding(v string) bool { + return strings.EqualFold(strings.TrimSpace(v), GZIPEncoding) +} + // limitedGzipReader wraps a gzip reader with size limiting to prevent zip bombs type limitedGzipReader struct { *gzip.Reader diff --git a/middleware/decompress_test.go b/middleware/decompress_test.go index 8dc3057ba..b6be65d78 100644 --- a/middleware/decompress_test.go +++ b/middleware/decompress_test.go @@ -506,3 +506,29 @@ func BenchmarkDecompress_WithLimit(b *testing.B) { })(c) } } + +func TestDecompressContentEncodingCaseInsensitive(t *testing.T) { + e := echo.New() + body := `{"name":"echo"}` + gz, err := gzipString(body) + assert.NoError(t, err) + + for _, encoding := range []string{"GZIP", "Gzip", " gzip "} { + t.Run(encoding, func(t *testing.T) { + req := httptest.NewRequest(http.MethodPost, "/", bytes.NewReader(gz)) + req.Header.Set(echo.HeaderContentEncoding, encoding) + rec := httptest.NewRecorder() + c := e.NewContext(req, rec) + + h := Decompress()(func(c *echo.Context) error { + b, err := io.ReadAll(c.Request().Body) + if err != nil { + return err + } + return c.String(http.StatusOK, string(b)) + }) + assert.NoError(t, h(c)) + assert.Equal(t, body, rec.Body.String()) + }) + } +} From c7d2c83ea27e3fd34ecbe56f71827115e7b33fa5 Mon Sep 17 00:00:00 2001 From: sonnemusk Date: Wed, 22 Jul 2026 15:35:49 +0800 Subject: [PATCH 2/2] fix(middleware): keep gzip match to EqualFold only Drop TrimSpace so the change is strictly RFC 9110 case-insensitivity for content codings, per review discussion. --- middleware/decompress.go | 6 +++--- middleware/decompress_test.go | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/middleware/decompress.go b/middleware/decompress.go index 61fac721e..52febc680 100644 --- a/middleware/decompress.go +++ b/middleware/decompress.go @@ -129,10 +129,10 @@ func (config DecompressConfig) ToMiddleware() (echo.MiddlewareFunc, error) { } -// isGzipContentEncoding reports whether Content-Encoding is gzip (case-insensitive). -// Surrounding whitespace is ignored so common proxy variations still match. +// isGzipContentEncoding reports whether Content-Encoding is gzip. +// Content codings are case-insensitive per RFC 9110 ยง8.4.1. func isGzipContentEncoding(v string) bool { - return strings.EqualFold(strings.TrimSpace(v), GZIPEncoding) + return strings.EqualFold(v, GZIPEncoding) } // limitedGzipReader wraps a gzip reader with size limiting to prevent zip bombs diff --git a/middleware/decompress_test.go b/middleware/decompress_test.go index b6be65d78..fadfa7fac 100644 --- a/middleware/decompress_test.go +++ b/middleware/decompress_test.go @@ -513,7 +513,7 @@ func TestDecompressContentEncodingCaseInsensitive(t *testing.T) { gz, err := gzipString(body) assert.NoError(t, err) - for _, encoding := range []string{"GZIP", "Gzip", " gzip "} { + for _, encoding := range []string{"GZIP", "Gzip"} { t.Run(encoding, func(t *testing.T) { req := httptest.NewRequest(http.MethodPost, "/", bytes.NewReader(gz)) req.Header.Set(echo.HeaderContentEncoding, encoding)