Parse Accept-Encoding q-values in withGzip - #130
Merged
Conversation
withGzip decided whether to compress with strings.Contains(header, "gzip"), but Accept-Encoding is a q-value list (RFC 9110 12.5.3), not an opaque string. A client sending "gzip;q=0" is explicitly refusing gzip, yet the substring match compressed anyway and handed it a body it said it could not decode. The deprecated "x-gzip" token matched for the same reason, as would any future coding whose name contains "gzip". Parse the header instead: split on commas, compare the coding token exactly (case-insensitively), and skip entries carrying q=0. A bare "*" now counts as accepting gzip, which is what the header means and what most servers do; previously "Accept-Encoding: *" got an identity response. An explicit gzip entry outranks a wildcard, so "gzip;q=0, *" stays a refusal. A qvalue that fails to parse is ignored rather than read as a refusal, keeping the prior behaviour for malformed headers. The JSON response shape is unchanged, so this is not an /api/v1 breaking change.
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



Pull Request
📖 Description
withGzipdecided whether to compress with a substring search:But
Accept-Encodingis a q-value list (RFC 9110 §12.5.3), not an opaque string. Two bugs followed from that, both verified to still reproduce onmain(f5ceab5) before this fix:gzip;q=0is an explicit refusal, but the substring match compressed anyway — the client received a body it had just told the server it could not decode. Same foridentity, gzip;q=0andgzip;q=0.000.x-gzipmatched as a false positive. A deprecated alias most clients happen to handle, so mostly cosmetic, but the same bug would fire for any future coding token containinggzip.This is a fix, not a breaking change: the JSON response shape is untouched, so no
/api/v2is needed.Behaviour change worth calling out
Accept-Encoding: *now yields a compressed response; previously the substring match didn't seegzipin*and returned identity. This is what the header means (a client sending*accepts any coding) and what most servers do, so it is compatible — but it is a behaviour addition rather than a pure bug fix.*;q=0correctly stays a refusal.One deviation from the issue's suggested patch
The issue's sketch iterates and
continues past a refusedgzipentry, which meansAccept-Encoding: gzip;q=0, *would fall through to the wildcard and compress — still wrong. RFC 9110 §12.5.3 says the most specific match wins, so this implementation lets an explicitgzipentry decide and only falls back to the wildcard when nogzipentry is present. Covered by tests in both directions (gzip;q=0, *→ no;gzip, *;q=0→ yes).A qvalue that fails to parse is ignored rather than read as a refusal, so a malformed header keeps the pre-existing compressing behaviour instead of silently losing compression.
🎫 Issues
Closes #101
👩💻 Reviewer Notes
Two small helpers added to
internal/httpapi/middleware.go(acceptsGzip,qualityIsZero) and a one-line change at thewithGzipcall site;strconvadded to the imports. Nothing else in the middleware chain is touched.Worth a look at the wildcard-precedence logic in
acceptsGzip(the deviation described above) and at the decision to treat an unparseable qvalue as "not a refusal".Smoke test against a running instance:
No Pi hardware is required to verify this change — it is pure HTTP header handling, fully covered by the tests below.
📑 Test Plan
Both test styles the issue suggested, added to
internal/httpapi/middleware_test.go:TestWithGzip_AcceptEncodingNegotiation— table-driven through the full handler chain (s.Handler().ServeHTTP), asserting on theContent-Encodingresponse header, matching the style of the existingTestHandleHistory_GzipWhenAccepted. Covers every row from the issue's table plus uppercase codings and both wildcard-precedence cases.TestAcceptsGzip_HeaderForms— direct unit test of the parser, covering whitespace-heavy forms (" gzip ; q = 0.5 "),GZip;Q=1,q=0.,gzipped,identity;q=0, and a bare,.Both tests were confirmed to fail on the pre-fix code (6 of the 14 handler-chain cases failed) and pass after it.
Full suite:
go build ./...,go vet ./...,go test ./... -race -cover, andgolangci-lint run(0 issues) all clean locally.internal/httpapicoverage is 95.7%.✅ Checklist
General
go test ./... -race -coverpasses locally).go vet ./...andgolangci-lint runare clean.ARCHITECTURE.mdif this changes a documented design decision. — n/a, no documented design decision changes.REST API / configuration / packaging
docs/API.mdto reflect a REST API change. — added a note under "Compression" that q-values are honoured,*counts as accepting gzip, andx-gzipdoes not./api/v1/...response shapes, or a new API version (/api/v2/...) was introduced instead.README.md/packaging/pimonitor.example.yaml— n/a, no configuration change.packaging/install.shor the systemd units — n/a, no packaging change.⏭ Next Steps
None.
Accept-Encodingis the only q-value header PiMonitor negotiates on; there is noAcceptorAccept-Languagehandling with the same shape of bug.