🐛 fix(ctx): honor Accept weights followed by whitespace before the comma - #4550
Conversation
The fast path in getOffer parsed the weight straight from the bytes after ";q=", so any optional whitespace sitting before the list separator was handed to ParseUfloat as part of the number. Parsing "0 " fails, the error was discarded, and quality kept its 1.0 default -- turning an explicit refusal into the client's strongest preference. RFC 9110 section 5.6.1.2 allows OWS on both sides of the comma in every list-based field, and forEachMediaRange only trims the leading side. The same function already trims the trailing side off the media range itself a few lines below, and the slow path parses these headers correctly, so only this branch was affected. Accept: text/html;q=0 , text/plain selected text/html, the one offer the client had refused. Accepts, AcceptsEncodings, AcceptsCharsets, AcceptsLanguages and Format all share this path. Weights above zero were skewed the same way, so an element with a trailing space also outranked a genuinely higher-weighted one. Trim the value before parsing. TrimSpace returns a subslice, so the benchmarks are unchanged at 0 allocs/op.
Walkthrough
ChangesQuality parameter handling
Estimated code review effort: 1 (Trivial) | ~5 minutes Possibly related PRs
Suggested labels: Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #4550 +/- ##
=======================================
Coverage 93.21% 93.21%
=======================================
Files 140 140
Lines 14645 14645
=======================================
Hits 13651 13651
Misses 620 620
Partials 374 374
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Bug
The fast path in
getOffer(helpers.go:749) parses the weight straight from the bytes after;q=:forEachMediaRangetrims only the leading whitespace of each list element, so whitespace sitting before the comma is still attached to the weight.ParseUfloat("0 ")returns an error, the error is discarded, andqualitykeeps its1.0default — an explicit refusal becomes the client's strongest preference.RFC 9110 §5.6.1.2 allows OWS on both sides of the comma in every list-based field (
#element => [ element ] *( OWS "," OWS [ element ] )), and §12.4.2 definesweight = OWS ";" OWS "q=" qvaluewith no trailing OWS — so that whitespace belongs to the list separator, not to the number.Only this branch is affected. The same function already trims the trailing side off the media range itself at
helpers.go:779, and the slow path viaVisitHeaderParamsparses these headers correctly today:Impact
Accepts,AcceptsEncodings,AcceptsCharsets,AcceptsLanguagesandFormat/AutoFormatall go throughgetOffer:Accept: text/html;q=0 , text/plainc.Accepts("text/html", "text/plain")text/htmltext/plainAccept-Encoding: gzip;q=0 , deflatec.AcceptsEncodings("gzip", "deflate")gzipdeflateAccept-Language: en;q=0 , frc.AcceptsLanguages("en", "fr")enfrAccept: text/html;q=0 , application/jsonc.AutoFormat("hi")text/html; charset=utf-8application/jsonWeights above zero are skewed the same way, so an element with a trailing space also outranks a genuinely higher-weighted one:
text/plain;q=0.1 , application/json;q=0.9selectedtext/plain. A tab behaves the same as a space.Fix
Trim the value before parsing.
Tests
Extended
Test_Utils_GetOffer_QualityZeroRejectionwith the OWS cases, proven FAIL-on-main / PASS-on-fix:The existing suite misses this because
helpers_test.go:44does cover a trailing space (text/plain;q=0.4) but passes only one offer, so the mis-parsedq=1.0and the intendedq=0.4select the same result; and the neighbouring cases add a;after the spaces, which diverts to the slow path.gofmtandgo vetclean, root packagego test .green.TrimSpacereturns a subslice, soBenchmark_Utils_GetOfferis unchanged at 0 allocs/op across every case (simple 34.0ns, 6_offers 112ns, 10_parameters 857ns, count=2).