Skip to content

🐛 fix(ctx): honor Accept weights followed by whitespace before the comma - #4550

Merged
ReneWerner87 merged 2 commits into
gofiber:mainfrom
0xghost42:fix/accept-weight-trailing-whitespace
Jul 23, 2026
Merged

ReneWerner87 merged 2 commits into
gofiber:mainfrom
0xghost42:fix/accept-weight-trailing-whitespace

Conversation

@0xghost42

Copy link
Copy Markdown
Contributor

Bug

The fast path in getOffer (helpers.go:749) parses the weight straight from the bytes after ;q=:

qIndex := i + 3
if bytes.HasPrefix(accept[i:], semicolonQEquals) && bytes.IndexByte(accept[qIndex:], ';') == -1 {
    if q, err := fasthttp.ParseUfloat(accept[qIndex:]); err == nil {
        quality = q
    }
}

forEachMediaRange trims 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, and quality keeps its 1.0 default — 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 defines weight = OWS ";" OWS "q=" qvalue with 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 via VisitHeaderParams parses these headers correctly today:

fast path  text/html;q=0 , text/plain        -> "text/html"   (wrong)
slow path  text/html;a=1;q=0 , text/plain    -> ""            correct
slow path  text/html; q=0 , text/plain       -> ""            correct

Impact

Accepts, AcceptsEncodings, AcceptsCharsets, AcceptsLanguages and Format/AutoFormat all go through getOffer:

Request header Call Before After
Accept: text/html;q=0 , text/plain c.Accepts("text/html", "text/plain") text/html text/plain
Accept-Encoding: gzip;q=0 , deflate c.AcceptsEncodings("gzip", "deflate") gzip deflate
Accept-Language: en;q=0 , fr c.AcceptsLanguages("en", "fr") en fr
Accept: text/html;q=0 , application/json c.AutoFormat("hi") text/html; charset=utf-8 application/json

Weights 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.9 selected text/plain. A tab behaves the same as a space.

Fix

Trim the value before parsing.

Tests

Extended Test_Utils_GetOffer_QualityZeroRejection with the OWS cases, proven FAIL-on-main / PASS-on-fix:

--- FAIL: Test_Utils_GetOffer_QualityZeroRejection
    helpers_test.go:313: Should be empty, but was text/html

The existing suite misses this because helpers_test.go:44 does cover a trailing space (text/plain;q=0.4 ) but passes only one offer, so the mis-parsed q=1.0 and the intended q=0.4 select the same result; and the neighbouring cases add a ; after the spaces, which diverts to the slow path.

gofmt and go vet clean, root package go test . green.

TrimSpace returns a subslice, so Benchmark_Utils_GetOffer is unchanged at 0 allocs/op across every case (simple 34.0ns, 6_offers 112ns, 10_parameters 857ns, count=2).

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.
@0xghost42
0xghost42 requested a review from a team as a code owner July 21, 2026 12:10
@ReneWerner87 ReneWerner87 added this to v3 Jul 21, 2026
@ReneWerner87 ReneWerner87 added this to the v3 milestone Jul 21, 2026
@coderabbitai

coderabbitai Bot commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

getOffer now trims whitespace before parsing media-range q values. Tests verify that whitespace around separators preserves q=0 rejection and positive quality ordering.

Changes

Quality parameter handling

Layer / File(s) Summary
Trim and validate media-range quality values
helpers.go, helpers_test.go
getOffer trims q values before parsing, while tests cover spaces and tabs around separators, including q=0 rejection and positive-weight ordering.

Estimated code review effort: 1 (Trivial) | ~5 minutes

Possibly related PRs

Suggested labels: 📜 RFC Compliance

Poem

I’m a rabbit who trims every space,
So zero stays zero in its proper place.
Tabs may hop and commas may flee,
Quality weights now parse accurately.
Nibble, test, merge—wheee!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title is concise and accurately describes the main fix: handling whitespace before the comma in Accept weights.
Description check ✅ Passed The description clearly explains the bug, fix, impact, and tests, though it doesn't follow every template section.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@codecov

codecov Bot commented Jul 21, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 93.21%. Comparing base (23c4f59) to head (9db7ad1).

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           
Flag Coverage Δ
unittests 93.21% <100.00%> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@gaby gaby changed the title fix(ctx): honor Accept weights followed by whitespace before the comma 🐛 fix(ctx): honor Accept weights followed by whitespace before the comma Jul 23, 2026
@ReneWerner87
ReneWerner87 merged commit cc673de into gofiber:main Jul 23, 2026
18 checks passed
@github-project-automation github-project-automation Bot moved this to Done in v3 Jul 23, 2026
@ReneWerner87 ReneWerner87 modified the milestones: v3, v3.5.0 Aug 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

3 participants