diff --git a/system/HTTP/Negotiate.php b/system/HTTP/Negotiate.php index 82123558a273..a4f55f3549ad 100644 --- a/system/HTTP/Negotiate.php +++ b/system/HTTP/Negotiate.php @@ -409,6 +409,11 @@ protected function matchParameters(array $acceptable, array $supported): bool */ public function matchTypes(array $acceptable, array $supported): bool { + // A value without a subtype, such as a malformed "json", is not a media type. + if (! str_contains($acceptable['value'], '/') || ! str_contains($supported['value'], '/')) { + return false; + } + // PHPDocumentor v2 cannot parse yet the shorter list syntax, // causing no API generation for the file. [$aType, $aSubType] = explode('/', $acceptable['value']); diff --git a/tests/system/HTTP/NegotiateTest.php b/tests/system/HTTP/NegotiateTest.php index 083846c8a1a8..d4b0a9de5a1d 100644 --- a/tests/system/HTTP/NegotiateTest.php +++ b/tests/system/HTTP/NegotiateTest.php @@ -197,6 +197,15 @@ public function testBestMatchOnlyLowQuality(): void $this->assertSame('popcorn/cheddar', $this->negotiate->media(['popcorn/cheddar', 'apple/mac'], false)); } + public function testBestMatchIgnoresMediaRangesWithoutSubtype(): void + { + $this->request->setHeader('Accept', 'json, application/json'); + $this->assertSame('application/json', $this->negotiate->media(['text/html', 'application/json'], true)); + + $this->request->setHeader('Accept', ', text/html'); + $this->assertSame('text/html', $this->negotiate->media(['application/json', 'text/html'], true)); + } + public function testParameterMatching(): void { $this->request->setHeader('Accept', 'popcorn/cheddar;a=0;b=1'); diff --git a/user_guide_src/source/changelogs/v4.7.6.rst b/user_guide_src/source/changelogs/v4.7.6.rst index 75baa337d7b0..732968c4510d 100644 --- a/user_guide_src/source/changelogs/v4.7.6.rst +++ b/user_guide_src/source/changelogs/v4.7.6.rst @@ -40,6 +40,7 @@ Bugs Fixed - **Helpers:** Fixed a regression in v4.7.5 where ``set_value()`` threw a ``TypeError`` when the default value was not a string or an array, such as an integer, a float, ``null``, or a ``Stringable`` object. Such values are now converted to a string. - **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 ``Negotiate::media()`` threw an ``ErrorException`` when the ``Accept`` header contained a value without a subtype, such as ``json`` or an empty list element. Such values are now ignored. - **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. - **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.