Repository navigation
fix: do not let wildcards select values excluded with q=0 in Negotiate - #10661
Merged
michalsn merged 1 commit intoOct 28, 2026
Merged
Conversation
michalsn
reviewed
Oct 26, 2026
wleeaf
force-pushed
the
negotiate-wildcard-excluded-values
branch
2 times, most recently
from
October 26, 2026 11:48
04f1211 to
78e6fb1
Compare
Contributor
Author
|
Rebased onto develop. The description is updated for the wider scope. |
michalsn
reviewed
Oct 26, 2026
wleeaf
force-pushed
the
negotiate-wildcard-excluded-values
branch
2 times, most recently
from
October 26, 2026 23:35
d2dba59 to
ab58f75
Compare
Contributor
Author
|
Rebased onto develop again. |
wleeaf
force-pushed
the
negotiate-wildcard-excluded-values
branch
from
October 27, 2026 09:00
ab58f75 to
045a0d4
Compare
A wildcard or a broader range could select a supported value even when the header excluded that value with q=0. For example, `Accept-Encoding: gzip;q=0, *` selected gzip, `Accept: text/*;q=0, */*` selected text/html, `Accept: text/html;q=0, */*` selected text/html;charset=utf-8, and `Accept-Language: en;q=0, *` selected en-US, with and without strict locale negotiation. A supported value is now excluded when the most specific value in the header that matches it has q=0 (RFC 9110, Section 12.5.1), so `text/html, text/*;q=0` still selects text/html. A media range matches the media types of its type and subtype that have its parameters, and a language range matches the tags that equal it or start with it followed by "-" (RFC 4647, Section 3.3.1). A type/subtype range is more specific than a type/* range, which is more specific than */*, and a range with more parameters is more specific than one of the same kind with fewer, so `text/*;charset=utf-8;q=0, text/*` excludes text/html;charset=utf-8. These comparisons ignore case, except for parameter values other than charset (RFC 9110, Section 8.3). Wildcards, broader matches and the strict locale fallbacks skip excluded values. Without a match, the fallback to the first supported value when `$strictMatch` is false is unchanged.
wleeaf
force-pushed
the
negotiate-wildcard-excluded-values
branch
from
October 27, 2026 09:00
045a0d4 to
3f468d2
Compare
Contributor
Author
|
Rebased onto |
paulbalandan
approved these changes
Oct 27, 2026
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.
Description
When the header holds a wildcard,
Negotiatereturns the first supported value, even if the same header excludes that value withq=0. In RFC 9110, a weight of 0 means "not acceptable" (section 12.4.2), and a more specific entry overrides a wildcard (sections 12.5.1 and 12.5.3). Examples:Accept-Encoding: gzip;q=0, *withencoding(['gzip', 'br'])returnsgzip.Accept: application/json;q=0, */*withmedia(['application/json', 'text/html'], true)returnsapplication/json.Accept: text/*, text/html;q=0withmedia(['text/html', 'text/plain'], true)returnstext/html.charset()behaves the same way, and so doeslanguage(), with and without$strictLocaleNegotiation. For example,Accept-Language: en;q=0, *givesen.getBestMatch()andgetBestLocaleMatch()now skip values that the header excludes. A privateisExcluded()applies the most specific value in the header that matches a supported value (RFC 9110, section 12.5.1): media ranges match by type, subtype and their own parameters, sotext/*;q=0excludestext/htmlandtext/html;q=0excludestext/html;charset=utf-8, and language ranges use RFC 4647 basic filtering, soen;q=0excludesen-US. A more specific range with a positive weight still wins:text/html, text/*;q=0selectstext/html, anden;q=0, en-GB;q=0.5, *selectsen-GB. Wildcards, broader matches and the strict locale fallbacks all skip excluded values. When nothing matches,$strictMatch = falsestill falls back to the first supported value, as documented and astestBestMatchOnlyLowQualityexpects.testWildcardsSkipExcludedValues,testMediaRangesExcludeMatchingValues,testMoreSpecificRangesTakePrecedenceandtestLanguageRangesExcludeRegionalLocalesintests/system/HTTP/NegotiateTest.phpcovermedia(),charset(),encoding()andlanguage()in both locale modes, with positive controls. They fail without the change.tests/system/HTTP/passes. php-cs-fixer, PHPStan and Rector (dry run) report nothing for the changed files.Checklist: