Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 37 additions & 4 deletions system/HTTP/Negotiate.php
Original file line number Diff line number Diff line change
Expand Up @@ -184,13 +184,23 @@ protected function getBestMatch(
}

// if acceptable value is "anything", return the first available
// that the header does not exclude
if ($accept['value'] === '*' || $accept['value'] === '*/*') {
return $supported[0];
foreach ($supported as $available) {
if (! $this->isExcluded($acceptable, $available)) {
return $available;
}
}

continue;
}

// If an acceptable value is supported, return it
// If an acceptable value is supported, return it, unless it was
// matched by a wildcard such as "text/*" and the header excludes it
foreach ($supported as $available) {
if ($this->match($accept, $available, $enforceTypes, $matchLocales)) {
if ($this->match($accept, $available, $enforceTypes, $matchLocales)
&& (! str_contains($accept['value'], '*') || ! $this->isExcluded($acceptable, $available))
) {
return $available;
}
}
Expand Down Expand Up @@ -233,8 +243,15 @@ protected function getBestLocaleMatch(array $supportedLocales, ?string $header):
}

// if acceptable value is "anything", return the first available
// that the header does not exclude
if ($accept['value'] === '*') {
return $supportedLocales[0];
foreach ($supportedLocales as $locale) {
if (! $this->isExcluded($acceptable, $locale)) {
return $locale;
}
}

continue;
}

// look for exact match
Expand Down Expand Up @@ -263,6 +280,22 @@ protected function getBestLocaleMatch(array $supportedLocales, ?string $header):
return $supportedLocales[0];
}

/**
* Checks whether the header excludes a supported value with q=0.
*
* @param list<array{value: string, q: float, params: array<string, string>}> $acceptable
*/
private function isExcluded(array $acceptable, string $supported): bool
{
foreach ($acceptable as $accept) {
if ($accept['q'] === 0.0 && $this->match($accept, $supported)) {
return true;
}
}

return false;
}

/**
* Parses an Accept* header into it's multiple values.
*
Expand Down
25 changes: 25 additions & 0 deletions tests/system/HTTP/NegotiateTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,31 @@ public function testBestMatchLowQuality(): void
$this->assertSame('apple/mac', $this->negotiate->media(['popcorn/cheddar', 'apple/mac'], false));
}

public function testWildcardsSkipExcludedValues(): void
{
$this->request->setHeader('Accept', 'application/json;q=0, */*');
$this->assertSame('text/html', $this->negotiate->media(['application/json', 'text/html'], true));

$this->request->setHeader('Accept', 'text/*, text/html;q=0');
$this->assertSame('text/plain', $this->negotiate->media(['text/html', 'text/plain'], true));

$this->request->setHeader('Accept', '*/*, text/html;q=0');
$this->assertSame('', $this->negotiate->media(['text/html'], true));

$this->request->setHeader('Accept-Charset', 'utf-8;q=0, *');
$this->assertSame('iso-8859-1', $this->negotiate->charset(['utf-8', 'iso-8859-1']));

$this->request->setHeader('Accept-Encoding', 'gzip;q=0, *');
$this->assertSame('br', $this->negotiate->encoding(['gzip', 'br']));

$this->request->setHeader('Accept-Language', 'en;q=0, *');
$this->assertSame('fr', $this->negotiate->language(['en', 'fr']));

config(Feature::class)->strictLocaleNegotiation = true;

$this->assertSame('de', $this->negotiate->language(['en', 'de']));
}

public function testBestMatchOnlyLowQuality(): void
{
$this->request->setHeader('Accept', 'popcorn/cheddar;q=0');
Expand Down
1 change: 1 addition & 0 deletions user_guide_src/source/changelogs/v4.7.6.rst
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ Bugs Fixed
- **HTTP:** Fixed a bug in ``ContentSecurityPolicy::setDefaultSrc()`` where passing an array of URIs resulted in an invalid ``'Array'`` directive.
- **HTTP:** Fixed stale ``$_REQUEST`` values after ``SiteURIFactory`` normalizes GET parameters, so ``Validation::withRequest()`` reads the normalized values while unrelated REQUEST entries are preserved.
- **HTTP:** Fixed a bug where ``IncomingRequest::isSecure()`` did not recognize ``https`` in the ``X-Forwarded-Proto`` header from a trusted proxy when the value contained uppercase letters or surrounding whitespace, or was represented as a single-element array.
- **HTTP:** Fixed a bug where ``Negotiate`` returned a value that the request header excluded with ``q=0`` when the header also contained a wildcard such as ``*``, ``*/*`` or ``text/*``. For example, ``Accept-Encoding: gzip;q=0, *`` no longer selects ``gzip``.
- **Security:** Fixed a bug where a valid JSON request body that is not an object (for example a JSON array) was corrupted after CSRF verification when the token was sent via the ``X-CSRF-TOKEN`` header.

See the repo's
Expand Down
Loading