diff --git a/middleware/compress.go b/middleware/compress.go index 7754d5db8..d01064206 100644 --- a/middleware/compress.go +++ b/middleware/compress.go @@ -11,6 +11,7 @@ import ( "io" "net" "net/http" + "strconv" "strings" "sync" @@ -91,7 +92,7 @@ func (config GzipConfig) ToMiddleware() (echo.MiddlewareFunc, error) { res := c.Response() res.Header().Add(echo.HeaderVary, echo.HeaderAcceptEncoding) - if strings.Contains(c.Request().Header.Get(echo.HeaderAcceptEncoding), gzipScheme) { + if acceptsGzip(c.Request().Header.Get(echo.HeaderAcceptEncoding)) { i := pool.Get() w, ok := i.(*gzip.Writer) if !ok { @@ -225,6 +226,48 @@ func gzipCompressPool(config GzipConfig) sync.Pool { } } +func acceptsGzip(header string) bool { + if header == "" { + return false + } + + accepted := false + for _, part := range strings.Split(header, ",") { + part = strings.TrimSpace(part) + if part == "" { + continue + } + + token, params, _ := strings.Cut(part, ";") + token = strings.TrimSpace(token) + + q := 1.0 + if params != "" { + for _, param := range strings.Split(params, ";") { + param = strings.TrimSpace(param) + if len(param) < 2 || !strings.HasPrefix(strings.ToLower(param), "q=") { + continue + } + parsed, err := strconv.ParseFloat(strings.TrimSpace(param[2:]), 64) + if err != nil { + continue + } + q = parsed + break + } + } + + switch { + case strings.EqualFold(token, gzipScheme): + return q > 0 + case token == "*" && q > 0: + accepted = true + } + } + + return accepted +} + func bufferPool() sync.Pool { return sync.Pool{ New: func() any { diff --git a/middleware/compress_test.go b/middleware/compress_test.go index 084ffc9c7..b97f7ed01 100644 --- a/middleware/compress_test.go +++ b/middleware/compress_test.go @@ -68,6 +68,25 @@ func TestGzip_AcceptEncodingHeader(t *testing.T) { assert.Equal(t, "test", buf.String()) } +func TestGzip_AcceptEncodingHeaderWithZeroQuality(t *testing.T) { + h := Gzip()(func(c *echo.Context) error { + c.Response().Write([]byte("test")) + return nil + }) + + e := echo.New() + req := httptest.NewRequest(http.MethodGet, "/", nil) + req.Header.Set(echo.HeaderAcceptEncoding, "gzip;q=0") + rec := httptest.NewRecorder() + c := e.NewContext(req, rec) + + err := h(c) + assert.NoError(t, err) + + assert.Empty(t, rec.Header().Get(echo.HeaderContentEncoding)) + assert.Equal(t, "test", rec.Body.String()) +} + func TestGzip_chunked(t *testing.T) { e := echo.New() req := httptest.NewRequest(http.MethodGet, "/", nil)