Skip to content

fix(middleware): respect Accept-Encoding q-values in Gzip#3021

Open
DucMinhNe wants to merge 1 commit into
labstack:masterfrom
DucMinhNe:fix/gzip-accept-encoding-qvalue
Open

fix(middleware): respect Accept-Encoding q-values in Gzip#3021
DucMinhNe wants to merge 1 commit into
labstack:masterfrom
DucMinhNe:fix/gzip-accept-encoding-qvalue

Conversation

@DucMinhNe

Copy link
Copy Markdown

The Gzip middleware decides whether to compress a response with a naive substring check:

if strings.Contains(c.Request().Header.Get(echo.HeaderAcceptEncoding), gzipScheme) {

Per RFC 9110 §12.5.3 this is incorrect in two ways:

  • Accept-Encoding: gzip;q=0 explicitly means the client does not accept gzip ("A qvalue of 0 means not acceptable"), yet strings.Contains still enables compression.
  • A distinct coding token such as x-gzip or supergzip substring-matches "gzip" and wrongly triggers compression.

Repro (before the fix)

A request with Accept-Encoding: gzip;q=0 returns a gzip-encoded body (Content-Encoding: gzip), even though the client asked not to be gzipped.

Fix

Replace the substring check with a small acceptsGzip parser that:

  • splits the header into codings,
  • matches the gzip token (or *) exactly, and
  • honours the q-value, skipping compression on an explicit q=0 rejection.

Tests

Added TestGzip_AcceptEncodingQValue and TestAcceptsGzip covering gzip;q=0, q>0, multi-coding headers, identity, the * wildcard, and the supergzip/x-gzip substring false-positives.

go test ./middleware/..., gofmt -l and go vet ./middleware/ are all clean. No public API change; behaviour only changes for headers that were previously handled incorrectly.

The Gzip middleware decided whether to compress using a naive
strings.Contains check for "gzip" on the Accept-Encoding header.

Per RFC 9110 §12.5.3 this is incorrect in two ways:

  - "Accept-Encoding: gzip;q=0" explicitly means the client does NOT
    accept gzip, yet Contains still enabled compression.
  - codings such as "x-gzip" or "supergzip" substring-matched "gzip"
    even though they are distinct tokens.

Replace the substring check with a small Accept-Encoding parser that
matches the gzip (or "*") coding token and honours its q-value, skipping
compression on an explicit q=0 rejection. Add focused tests covering
q=0, q>0, identity, wildcard and substring false-positives.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes Echo’s Gzip middleware decision logic by replacing a naive strings.Contains check on Accept-Encoding with token-based parsing that aims to follow RFC 9110 §12.5.3 (exact token matching and honoring q values).

Changes:

  • Introduces an acceptsGzip helper to parse Accept-Encoding codings and q parameters instead of substring matching.
  • Updates the Gzip middleware to use acceptsGzip when deciding whether to enable gzip compression.
  • Adds tests covering q=0, wildcard *, multiple codings, and substring false-positives like supergzip/x-gzip.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
middleware/compress.go Adds acceptsGzip/parseCoding and switches middleware gating from substring match to parsed logic.
middleware/compress_test.go Adds regression tests for Accept-Encoding parsing and gzip enablement behavior.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread middleware/compress.go
Comment on lines +35 to +54
func acceptsGzip(acceptEncoding string) bool {
if acceptEncoding == "" {
return false
}
for _, part := range strings.Split(acceptEncoding, ",") {
coding, qvalue := parseCoding(part)
if coding != gzipScheme && coding != "*" {
continue
}
if qvalue == "0" || qvalue == "0.0" || qvalue == "0.00" || qvalue == "0.000" {
// q=0 means "not acceptable"; an explicit gzip rejection wins.
if coding == gzipScheme {
return false
}
continue
}
return true
}
return false
}
Comment on lines +444 to +457
func TestAcceptsGzip(t *testing.T) {
assert.True(t, acceptsGzip("gzip"))
assert.True(t, acceptsGzip(" GZIP "))
assert.True(t, acceptsGzip("br, gzip;q=0.9"))
assert.True(t, acceptsGzip("*"))
assert.True(t, acceptsGzip("*;q=0.1"))
assert.False(t, acceptsGzip(""))
assert.False(t, acceptsGzip("gzip;q=0"))
assert.False(t, acceptsGzip("gzip; q=0.000"))
assert.False(t, acceptsGzip("identity"))
assert.False(t, acceptsGzip("supergzip"))
assert.False(t, acceptsGzip("x-gzip"))
assert.False(t, acceptsGzip("*;q=0"))
}
@aldas

aldas commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

Are there examples how other frameworks, maybe in other programming languages are dealing with this?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants