CVE-2026-49214
Description
CRLF injection in guzzlehttp/psr7 prior to 2.10.2 allows attackers to inject arbitrary HTTP headers via malformed host components in user-supplied URLs.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
CRLF injection in guzzlehttp/psr7 prior to 2.10.2 allows attackers to inject arbitrary HTTP headers via malformed host components in user-supplied URLs.
Vulnerability
In guzzlehttp/psr7 versions prior to 2.10.2, the library does not reject ASCII control characters (including CRLF), whitespace, or DEL in first-party URI host components. This affects PSR-7 Uri, Request, and other objects that store host values. The 1.x branch is end-of-life and will not receive a patch [1][2][3].
Exploitation
An attacker must supply a user-controlled URL containing CRLF or other header-unsafe characters in the host component. The application then uses this URL to construct a PSR-7 Uri or Request, and subsequently serializes the request (e.g., via Message::toString()) without setting an explicit Host header. During serialization, the malformed host is copied into the Host header, allowing the attacker to inject additional HTTP header lines. For example, a host of "example.com\r\nX-Injected: yes" results in a Host header spanning multiple lines [2][3].
Impact
Successful exploitation enables an attacker to inject arbitrary HTTP headers into a serialized request. This can lead to request smuggling, cache poisoning, or other header-based attacks depending on how downstream components (proxies, load balancers, etc.) parse the malformed request. The attacker gains the ability to influence the behavior of intermediate infrastructure, potentially compromising confidentiality and integrity [2][3].
Mitigation
The vulnerability is patched in version 2.10.2 and later; all users should upgrade immediately. For 1.x branches, no fix will be provided as they are end-of-life. As a workaround, validate and reject any untrusted URI strings that contain ASCII control characters, whitespace, or DEL before constructing PSR-7 objects. Ensure that HTTP clients or serializers independently reject invalid host data before writing requests to the network [2][3].
AI Insight generated on Jun 11, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected products
1Patches
3c68fe44ea6b5Reject malformed Host authorities (#717)
8 files changed · +289 −30
CHANGELOG.md+1 −0 modified@@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Security - Reject control and whitespace characters in URI host components (GHSA-hq7v-mx3g-29hw) +- Reject malformed Host values when constructing request URIs (GHSA-34xg-wgjx-8xph) ### Fixed
src/Message.php+22 −4 modified@@ -233,6 +233,23 @@ public static function parseMessage(string $message): array * @param array $headers Array of headers (each value an array). */ public static function parseRequestUri(string $path, array $headers): string + { + $host = self::getHostFromHeaders($headers); + + // If no host is found, then a full URI cannot be constructed. + if ($host === null) { + return $path; + } + + $scheme = substr($host, -4) === ':443' ? 'https' : 'http'; + + return $scheme.'://'.$host.'/'.ltrim($path, '/'); + } + + /** + * @param array $headers Array of headers (each value an array). + */ + private static function getHostFromHeaders(array $headers): ?string { $hostKey = array_filter(array_keys($headers), function ($k) { // Numeric array keys are converted to int by PHP. @@ -241,15 +258,16 @@ public static function parseRequestUri(string $path, array $headers): string return strtolower($k) === 'host'; }); - // If no host is found, then a full URI cannot be constructed. if (!$hostKey) { - return $path; + return null; } $host = $headers[reset($hostKey)][0]; - $scheme = substr($host, -4) === ':443' ? 'https' : 'http'; + if (!is_string($host) || Rfc7230::parseHostHeader($host) === null) { + throw new \InvalidArgumentException('Invalid request string'); + } - return $scheme.'://'.$host.'/'.ltrim($path, '/'); + return $host; } /**
src/Rfc3986.php+25 −0 added@@ -0,0 +1,25 @@ +<?php + +declare(strict_types=1); + +namespace GuzzleHttp\Psr7; + +/** + * @internal + */ +final class Rfc3986 +{ + /** + * Sub-delims for use in a regex. + * + * @see https://datatracker.ietf.org/doc/html/rfc3986#section-2.2 + */ + public const CHAR_SUB_DELIMS = '!\$&\'\(\)\*\+,;='; + + /** + * Unreserved characters for use in a regex. + * + * @see https://datatracker.ietf.org/doc/html/rfc3986#section-2.3 + */ + public const CHAR_UNRESERVED = 'a-zA-Z0-9_\-\.~'; +}
src/Rfc7230.php+83 −0 modified@@ -20,4 +20,87 @@ final class Rfc7230 */ public const HEADER_REGEX = "(^([^()<>@,;:\\\"/[\]?={}\x01-\x20\x7F]++):[ \t]*+((?:[ \t]*+[\x21-\x7E\x80-\xFF]++)*+)[ \t]*+\r?\n)m"; public const HEADER_FOLD_REGEX = "(\r?\n[ \t]++)"; + + /** + * @return array{0: string, 1: int|null}|null + */ + public static function parseHostHeader(string $authority): ?array + { + if ($authority === '') { + return null; + } + + $host = $authority; + $port = null; + + if ($authority[0] === '[') { + $closingBracket = strpos($authority, ']'); + if ($closingBracket === false) { + return null; + } + + $host = substr($authority, 0, $closingBracket + 1); + $remainder = substr($authority, $closingBracket + 1); + if ($remainder !== '') { + if ($remainder[0] !== ':') { + return null; + } + + $port = self::parseAuthorityPort(substr($remainder, 1)); + if ($port === null) { + return null; + } + } + } elseif (false !== ($colon = strpos($authority, ':'))) { + $host = substr($authority, 0, $colon); + $port = self::parseAuthorityPort(substr($authority, $colon + 1)); + if ($port === null) { + return null; + } + } + + if ($host === '' || !self::isValidHostHeaderHost($host)) { + return null; + } + + return [$host, $port]; + } + + private static function isValidHostHeaderHost(string $host): bool + { + if (preg_match('/[\x00-\x20\x7F\/\?#@\\\\]/', $host)) { + return false; + } + + if (strpos($host, '[') !== false || strpos($host, ']') !== false) { + if ($host[0] !== '[' || substr($host, -1) !== ']') { + return false; + } + + $address = substr($host, 1, -1); + + return filter_var($address, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV6) !== false + || preg_match('/^v[0-9a-f]+\.['.Rfc3986::CHAR_UNRESERVED.Rfc3986::CHAR_SUB_DELIMS.':]+$/iD', $address) === 1; + } + + return strpos($host, ':') === false; + } + + private static function parseAuthorityPort(string $port): ?int + { + if ($port === '' || !ctype_digit($port)) { + return null; + } + + $normalized = ltrim($port, '0'); + if ($normalized === '') { + return 0; + } + + if (strlen($normalized) > 5 || (int) $normalized > 0xFFFF) { + return null; + } + + return (int) $normalized; + } }
src/ServerRequest.php+21 −10 modified@@ -166,7 +166,7 @@ private static function normalizeNestedFileSpec(array $files = []): array public static function fromGlobals(): ServerRequestInterface { $method = self::getServerParam('REQUEST_METHOD') ?? 'GET'; - $headers = self::getAllHeaders(); + $headers = self::removeInvalidHostHeader(self::getAllHeaders()); $uri = self::getUriFromGlobals(); $body = new CachingStream(new LazyOpenStream('php://input', 'r+')); $serverProtocol = self::getServerParam('SERVER_PROTOCOL'); @@ -213,20 +213,31 @@ private static function getServerParam(string $key): ?string } /** - * @return array{0: string|null, 1: int|null} + * @param array<array-key, string> $headers + * + * @return array<array-key, string> */ - private static function extractHostAndPortFromAuthority(string $authority): array + private static function removeInvalidHostHeader(array $headers): array { - $uri = 'http://'.$authority; - $parts = parse_url($uri); - if (!is_array($parts)) { - return [null, null]; + foreach ($headers as $name => $value) { + if (strtolower((string) $name) !== 'host') { + continue; + } + + if (Rfc7230::parseHostHeader($value) === null) { + unset($headers[$name]); + } } - $host = $parts['host'] ?? null; - $port = $parts['port'] ?? null; + return $headers; + } - return [$host, $port]; + /** + * @return array{0: string|null, 1: int|null} + */ + private static function extractHostAndPortFromAuthority(string $authority): array + { + return Rfc7230::parseHostHeader($authority) ?? [null, null]; } /**
src/Uri.php+3 −16 modified@@ -38,19 +38,6 @@ class Uri implements UriInterface, \JsonSerializable 'ldap' => 389, ]; - /** - * Unreserved characters for use in a regex. - * - * @see https://datatracker.ietf.org/doc/html/rfc3986#section-2.3 - */ - private const CHAR_UNRESERVED = 'a-zA-Z0-9_\-\.~'; - - /** - * Sub-delims for use in a regex. - * - * @see https://datatracker.ietf.org/doc/html/rfc3986#section-2.2 - */ - private const CHAR_SUB_DELIMS = '!\$&\'\(\)\*\+,;='; private const QUERY_SEPARATORS_REPLACEMENT = ['=' => '%3D', '&' => '%26', '+' => '%2B']; /** @var string Uri scheme. */ @@ -647,7 +634,7 @@ private function filterUserInfoComponent($component): string } return preg_replace_callback( - '/(?:[^%'.self::CHAR_UNRESERVED.self::CHAR_SUB_DELIMS.']+|%(?![A-Fa-f0-9]{2}))/', + '/(?:[^%'.Rfc3986::CHAR_UNRESERVED.Rfc3986::CHAR_SUB_DELIMS.']+|%(?![A-Fa-f0-9]{2}))/', [$this, 'rawurlencodeMatchZero'], $component ); @@ -749,7 +736,7 @@ private function filterPath($path): string } return preg_replace_callback( - '/(?:[^'.self::CHAR_UNRESERVED.self::CHAR_SUB_DELIMS.'%:@\/]++|%(?![A-Fa-f0-9]{2}))/', + '/(?:[^'.Rfc3986::CHAR_UNRESERVED.Rfc3986::CHAR_SUB_DELIMS.'%:@\/]++|%(?![A-Fa-f0-9]{2}))/', [$this, 'rawurlencodeMatchZero'], $path ); @@ -769,7 +756,7 @@ private function filterQueryAndFragment($str): string } return preg_replace_callback( - '/(?:[^'.self::CHAR_UNRESERVED.self::CHAR_SUB_DELIMS.'%:@\/\?]++|%(?![A-Fa-f0-9]{2}))/', + '/(?:[^'.Rfc3986::CHAR_UNRESERVED.Rfc3986::CHAR_SUB_DELIMS.'%:@\/\?]++|%(?![A-Fa-f0-9]{2}))/', [$this, 'rawurlencodeMatchZero'], $str );
tests/MessageTest.php+56 −0 modified@@ -107,6 +107,62 @@ public function testParsesRequestMessagesWithUriWhenHostIsNotFirst(): void self::assertSame('http://foo.com/', (string) $request->getUri()); } + /** + * @dataProvider invalidHostHeaderProvider + */ + public function testParseRequestRejectsInvalidHostHeader(string $host): void + { + $this->expectException(\InvalidArgumentException::class); + + Psr7\Message::parseRequest("GET / HTTP/1.1\r\nHost: {$host}\r\n\r\n"); + } + + public static function invalidHostHeaderProvider(): iterable + { + yield 'userinfo delimiter' => ['trusted.example@evil.example']; + yield 'path delimiter' => ['example.com/path']; + yield 'query delimiter' => ['example.com?query']; + yield 'fragment delimiter' => ['example.com#fragment']; + yield 'backslash delimiter' => ['example.com\\evil']; + yield 'space' => ['bad host']; + yield 'tab' => ["bad\thost"]; + yield 'control character' => ['example'.chr(1).'com']; + yield 'delete' => ['example'.chr(0x7F).'com']; + yield 'multiple ports' => ['example.com:443:8443']; + yield 'missing closing bracket' => ['[::1']; + yield 'unexpected bracket suffix' => ['[::1]x']; + yield 'invalid ip literal' => ['[bad]']; + yield 'unexpected opening bracket' => ['foo[bar']; + yield 'unexpected closing bracket' => ['foo]bar']; + } + + /** + * @dataProvider validHostHeaderProvider + */ + public function testParseRequestAcceptsValidHostHeader(string $host, string $expectedUri): void + { + $request = Psr7\Message::parseRequest("GET / HTTP/1.1\r\nHost: {$host}\r\n\r\n"); + + self::assertSame($host, $request->getHeaderLine('Host')); + self::assertSame($expectedUri, (string) $request->getUri()); + } + + public static function validHostHeaderProvider(): iterable + { + yield 'host' => ['foo.com', 'http://foo.com/']; + yield 'https default port' => ['foo.com:443', 'https://foo.com/']; + yield 'non-default port' => ['foo.com:8080', 'http://foo.com:8080/']; + yield 'ipv6' => ['[::1]', 'http://[::1]/']; + yield 'ipv6 port' => ['[::1]:443', 'https://[::1]/']; + } + + public function testParseRequestAcceptsMissingHostHeader(): void + { + $request = Psr7\Message::parseRequest("GET /abc HTTP/1.1\r\nFoo: bar\r\n\r\n"); + + self::assertSame('/abc', (string) $request->getUri()); + } + public function testParsesRequestMessagesWithFullUri(): void { $req = "GET https://www.google.com:443/search?q=foobar HTTP/1.1\r\nHost: www.google.com\r\n\r\n";
tests/ServerRequestTest.php+78 −0 modified@@ -354,6 +354,46 @@ public static function dataGetUriFromGlobals(): iterable 'https://localhost/blog/article.php?id=10&user=foo', array_merge($server, ['HTTP_HOST' => 'a:b']), ], + 'Host header with userinfo delimiter' => [ + 'https://localhost/blog/article.php?id=10&user=foo', + array_merge($server, ['HTTP_HOST' => 'trusted.example@evil.example']), + ], + 'Host header with path delimiter' => [ + 'https://localhost/blog/article.php?id=10&user=foo', + array_merge($server, ['HTTP_HOST' => 'example.com/path']), + ], + 'Host header with query delimiter' => [ + 'https://localhost/blog/article.php?id=10&user=foo', + array_merge($server, ['HTTP_HOST' => 'example.com?x=1']), + ], + 'Host header with fragment delimiter' => [ + 'https://localhost/blog/article.php?id=10&user=foo', + array_merge($server, ['HTTP_HOST' => 'example.com#frag']), + ], + 'Host header with backslash delimiter' => [ + 'https://localhost/blog/article.php?id=10&user=foo', + array_merge($server, ['HTTP_HOST' => 'example.com\\evil']), + ], + 'Host header with space' => [ + 'https://localhost/blog/article.php?id=10&user=foo', + array_merge($server, ['HTTP_HOST' => 'bad host']), + ], + 'Host header with multiple ports' => [ + 'https://localhost/blog/article.php?id=10&user=foo', + array_merge($server, ['HTTP_HOST' => 'example.com:80:90']), + ], + 'Host header with invalid ip literal' => [ + 'https://localhost/blog/article.php?id=10&user=foo', + array_merge($server, ['HTTP_HOST' => '[bad]']), + ], + 'Host header with unexpected opening bracket' => [ + 'https://localhost/blog/article.php?id=10&user=foo', + array_merge($server, ['HTTP_HOST' => 'foo[bar']), + ], + 'Host header with unexpected closing bracket' => [ + 'https://localhost/blog/article.php?id=10&user=foo', + array_merge($server, ['HTTP_HOST' => 'foo]bar']), + ], 'Different port with SERVER_PORT' => [ 'https://www.example.org:8324/blog/article.php?id=10&user=foo', array_merge($server, ['SERVER_PORT' => '8324']), @@ -536,6 +576,44 @@ public function testFromGlobalsDefaultsNonStringMethodAndProtocol(): void self::assertSame('1.1', $server->getProtocolVersion()); } + /** + * @dataProvider invalidHostHeaderFromGlobalsProvider + */ + public function testFromGlobalsDropsInvalidHostHeaderWhenUriFallsBack(string $host): void + { + if (!\function_exists('getallheaders')) { + self::markTestSkipped('getallheaders() is not available.'); + } + + $_SERVER = [ + 'REQUEST_URI' => '/', + 'HTTP_HOST' => $host, + 'SERVER_PORT' => '443', + 'HTTPS' => 'on', + ]; + + $_COOKIE = $_POST = $_GET = $_FILES = []; + + $request = ServerRequest::fromGlobals(); + + self::assertSame('localhost', $request->getUri()->getHost()); + self::assertSame('localhost', $request->getHeaderLine('Host')); + } + + public static function invalidHostHeaderFromGlobalsProvider(): iterable + { + yield 'userinfo delimiter' => ['trusted.example@evil.example']; + yield 'path delimiter' => ['example.com/path']; + yield 'query delimiter' => ['example.com?x=1']; + yield 'fragment delimiter' => ['example.com#frag']; + yield 'backslash delimiter' => ['example.com\\evil']; + yield 'space' => ['bad host']; + yield 'multiple ports' => ['example.com:80:90']; + yield 'invalid ip literal' => ['[bad]']; + yield 'unexpected opening bracket' => ['foo[bar']; + yield 'unexpected closing bracket' => ['foo]bar']; + } + public function testUploadedFiles(): void { $request1 = new ServerRequest('GET', '/');
12caca7f2302Reject control characters in URI hosts (#715)
5 files changed · +94 −4
CHANGELOG.md+6 −0 modified@@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## 2.10.2 - TBD + +### Security + +- Reject control and whitespace characters in URI host components (GHSA-hq7v-mx3g-29hw) + ## 2.10.1 - 2026-05-20 ### Fixed
src/Request.php+4 −0 modified@@ -132,10 +132,14 @@ private function updateHostFromUri(): void return; } + Uri::assertValidHost($host); + if (($port = $this->uri->getPort()) !== null) { $host .= ':'.$port; } + $this->assertValue($host); + if (isset($this->headerNames['host'])) { $header = $this->headerNames['host']; } else {
src/Uri.php+35 −4 modified@@ -81,7 +81,13 @@ public function __construct(string $uri = '') if ($parts === false) { throw new MalformedUriException("Unable to parse URI: $uri"); } - $this->applyParts($parts); + try { + $this->applyParts($parts); + } catch (MalformedUriException $e) { + throw $e; + } catch (\InvalidArgumentException $e) { + throw new MalformedUriException($e->getMessage(), 0, $e); + } } } @@ -390,12 +396,34 @@ public static function withQueryValues(UriInterface $uri, array $keyValueArray): public static function fromParts(array $parts): UriInterface { $uri = new self(); - $uri->applyParts($parts); - $uri->validateState(); + try { + $uri->applyParts($parts); + $uri->validateState(); + } catch (MalformedUriException $e) { + throw $e; + } catch (\InvalidArgumentException $e) { + throw new MalformedUriException($e->getMessage(), 0, $e); + } return $uri; } + /** + * @throws \InvalidArgumentException If the host is invalid. + * + * @internal + */ + public static function assertValidHost(string $host): void + { + if ($host === '') { + return; + } + + if (preg_match('/[\x00-\x20\x7F]/', $host)) { + throw new \InvalidArgumentException(sprintf('Invalid host: "%s"', $host)); + } + } + public function getScheme(): string { return $this->scheme; @@ -636,7 +664,10 @@ private function filterHost($host): string throw new \InvalidArgumentException('Host must be a string'); } - return \strtr($host, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'); + $host = \strtr($host, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'); + self::assertValidHost($host); + + return $host; } /**
tests/RequestTest.php+23 −0 modified@@ -9,6 +9,7 @@ use GuzzleHttp\Psr7\Uri; use PHPUnit\Framework\TestCase; use Psr\Http\Message\StreamInterface; +use Psr\Http\Message\UriInterface; /** * @covers \GuzzleHttp\Psr7\MessageTrait @@ -314,6 +315,28 @@ public function testAddsPortToHeaderAndReplacePreviousPort(): void self::assertSame('foo.com:8125', $r->getHeaderLine('host')); } + public function testGeneratedHostHeaderRejectsInvalidUriHostFromCustomUri(): void + { + $uri = $this->createMock(UriInterface::class); + $uri->method('getHost')->willReturn("foo\nbar"); + $uri->method('getPort')->willReturn(null); + + $this->expectException(\InvalidArgumentException::class); + + new Request('GET', $uri); + } + + public function testGeneratedHostHeaderValidatesAssembledHostWithPort(): void + { + $uri = $this->createMock(UriInterface::class); + $uri->method('getHost')->willReturn('example.com'); + $uri->method('getPort')->willReturn(8080); + + $request = new Request('GET', $uri); + + self::assertSame('example.com:8080', $request->getHeaderLine('Host')); + } + /** * @dataProvider provideHeaderValuesContainingNotAllowedChars */
tests/UriTest.php+26 −0 modified@@ -214,6 +214,32 @@ public function testHostMustHaveCorrectType(): void (new Uri())->withHost([]); } + /** + * @dataProvider getInvalidHostsWithControlCharacters + */ + public function testHostMustRejectControlCharacters(string $host): void + { + $this->expectException(\InvalidArgumentException::class); + + (new Uri())->withHost($host); + } + + public static function getInvalidHostsWithControlCharacters(): iterable + { + for ($i = 0; $i <= 0x20; ++$i) { + yield 'ascii 0x'.strtoupper(dechex($i)) => ['example'.chr($i).'com']; + } + + yield 'ascii 0x7F' => ['example'.chr(0x7F).'com']; + } + + public function testParseUriRejectsHostWithControlCharacter(): void + { + $this->expectException(MalformedUriException::class); + + new Uri("http://example.com\r\nX-Injected:%20yes/"); + } + public function testPathMustHaveCorrectType(): void { $this->expectException(\InvalidArgumentException::class);
a0fda818b0f7Normalize global header values (#718)
3 files changed · +65 −1
CHANGELOG.md+4 −0 modified@@ -11,6 +11,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Reject control and whitespace characters in URI host components (GHSA-hq7v-mx3g-29hw) +### Fixed + +- Make `ServerRequest::fromGlobals()` robust against unexpected HTTP header value types in `$_SERVER` + ## 2.10.1 - 2026-05-20 ### Fixed
src/ServerRequest.php+27 −1 modified@@ -166,7 +166,7 @@ private static function normalizeNestedFileSpec(array $files = []): array public static function fromGlobals(): ServerRequestInterface { $method = self::getServerParam('REQUEST_METHOD') ?? 'GET'; - $headers = getallheaders(); + $headers = self::getAllHeaders(); $uri = self::getUriFromGlobals(); $body = new CachingStream(new LazyOpenStream('php://input', 'r+')); $serverProtocol = self::getServerParam('SERVER_PROTOCOL'); @@ -181,6 +181,32 @@ public static function fromGlobals(): ServerRequestInterface ->withUploadedFiles(self::normalizeFiles($_FILES)); } + /** + * @return array<array-key, string> + */ + private static function getAllHeaders(): array + { + return self::normalizeHeaderValues(getallheaders()); + } + + /** + * @param array<array-key, mixed> $headers + * + * @return array<array-key, string> + */ + private static function normalizeHeaderValues(array $headers): array + { + $normalized = []; + + foreach ($headers as $name => $value) { + if (is_scalar($value) || (is_object($value) && method_exists($value, '__toString'))) { + $normalized[$name] = (string) $value; + } + } + + return $normalized; + } + private static function getServerParam(string $key): ?string { return isset($_SERVER[$key]) && is_string($_SERVER[$key]) ? $_SERVER[$key] : null;
tests/ServerRequestTest.php+34 −0 modified@@ -485,6 +485,40 @@ public function testFromGlobals(): void self::assertEquals($expectedFiles, $server->getUploadedFiles()); } + public function testFromGlobalsNormalizesUnexpectedHeaderValueTypes(): void + { + $_SERVER = [ + 'REQUEST_URI' => '/', + 'HTTP_HOST' => 'www.example.org', + 'HTTP_X_INT' => 123, + 'HTTP_X_FLOAT' => 1.5, + 'HTTP_X_FALSE' => false, + 'HTTP_X_TRUE' => true, + 'HTTP_X_STRINGABLE' => new class { + public function __toString(): string + { + return 'stringable'; + } + }, + 'HTTP_X_ARRAY' => ['bad'], + 'HTTP_X_OBJECT' => new \stdClass(), + 'HTTP_123' => 'numeric header', + ]; + + $_COOKIE = $_POST = $_GET = $_FILES = []; + + $server = ServerRequest::fromGlobals(); + + self::assertSame('123', $server->getHeaderLine('X-Int')); + self::assertSame('1.5', $server->getHeaderLine('X-Float')); + self::assertSame([''], $server->getHeader('X-False')); + self::assertSame('1', $server->getHeaderLine('X-True')); + self::assertSame('stringable', $server->getHeaderLine('X-Stringable')); + self::assertSame('numeric header', $server->getHeaderLine('123')); + self::assertFalse($server->hasHeader('X-Array')); + self::assertFalse($server->hasHeader('X-Object')); + } + public function testFromGlobalsDefaultsNonStringMethodAndProtocol(): void { $_SERVER = [
Vulnerability mechanics
No source-code context for this CVE — mechanics is only generated when we can read the actual fix diff. Without that, the four sections (root cause, attack vector, affected code, fix) would be speculation rather than analysis.
References
2News mentions
0No linked articles in our index yet.