diff --git a/src/Header.php b/src/Header.php index 0e79a71a..b219b87b 100644 --- a/src/Header.php +++ b/src/Header.php @@ -47,19 +47,21 @@ public static function parse($header): array */ public static function normalize($header): array { - if (!is_array($header)) { - return array_map('trim', explode(',', $header)); - } - $result = []; - foreach ($header as $value) { + foreach ((array) $header as $value) { foreach ((array) $value as $v) { if (strpos($v, ',') === false) { - $result[] = $v; + $trimmed = trim($v); + if ($trimmed !== '') { + $result[] = $trimmed; + } continue; } - foreach (preg_split('/,(?=([^"]*"[^"]*")*[^"]*$)/', $v) as $vv) { - $result[] = trim($vv); + foreach (preg_split('/,(?=([^"]*"([^"]|\\\\.)*")*[^"]*$)/', $v) as $vv) { + $trimmed = trim($vv); + if ($trimmed !== '') { + $result[] = $trimmed; + } } } } diff --git a/tests/HeaderTest.php b/tests/HeaderTest.php index 953498c7..5ff5411c 100644 --- a/tests/HeaderTest.php +++ b/tests/HeaderTest.php @@ -63,9 +63,90 @@ public function testParseParams($header, $result): void self::assertSame($result, Psr7\Header::parse($header)); } - public function testParsesArrayHeaders(): void + public function normalizeProvider(): array { - $header = ['a, b', 'c', 'd, e']; - self::assertSame(['a', 'b', 'c', 'd', 'e'], Psr7\Header::normalize($header)); + return [ + [ + '', + [], + ], + [ + ['a, b', 'c', 'd, e'], + ['a', 'b', 'c', 'd', 'e'], + ], + // Example 'accept-encoding' + [ + 'gzip, br', + ['gzip', 'br'], + ], + // https://httpwg.org/specs/rfc7231.html#rfc.section.5.3.2 + [ + 'text/plain; q=0.5, text/html, text/x-dvi; q=0.8, text/x-c', + ['text/plain; q=0.5', 'text/html', 'text/x-dvi; q=0.8', 'text/x-c'], + ], + // Example 'If-None-Match' with comma within an ETag + [ + '"foo", "foo,bar", "bar"', + ['"foo"', '"foo,bar"', '"bar"'], + ], + // https://httpwg.org/specs/rfc7234.html#cache.control.extensions + [ + 'private, community="UCI"', + ['private', 'community="UCI"'], + ], + // The Cache-Control example with a comma within a community + [ + 'private, community="Guzzle,Psr7"', + ['private', 'community="Guzzle,Psr7"'], + ], + // The Cache-Control example with an escaped space (quoted-pair) within a community + [ + 'private, community="Guzzle\\ Psr7"', + ['private', 'community="Guzzle\\ Psr7"'], + ], + // The Cache-Control example with an escaped quote (quoted-pair) within a community + [ + 'private, community="Guzzle\\"Psr7"', + ['private', 'community="Guzzle\\"Psr7"'], + ], + // The Cache-Control example with an escaped quote (quoted-pair) and a comma within a community + [ + 'private, community="Guzzle\\",Psr7"', + ['private', 'community="Guzzle\\",Psr7"'], + ], + // The Cache-Control example with an escaped backslash (quoted-pair) within a community + [ + 'private, community="Guzzle\\\\Psr7"', + ['private', 'community="Guzzle\\\\Psr7"'], + ], + // The Cache-Control example with an escaped backslash (quoted-pair) within a community + [ + 'private, community="Guzzle\\\\", Psr7', + ['private', 'community="Guzzle\\\\"', 'Psr7'], + ], + // https://httpwg.org/specs/rfc7230.html#rfc.section.7 + [ + 'foo ,bar,', + ['foo', 'bar'], + ], + // https://httpwg.org/specs/rfc7230.html#rfc.section.7 + [ + 'foo , ,bar,charlie ', + ['foo', 'bar', 'charlie'], + ], + [ + "; rel=\"first\",\n; rel=\"next\",\n; rel=\"prev\",\n; rel=\"last\",", + ['; rel="first"', '; rel="next"', '; rel="prev"', '; rel="last"'], + ], + ]; + } + + /** + * @dataProvider normalizeProvider + */ + public function testNormalize($header, $result): void + { + self::assertSame($result, Psr7\Header::normalize([$header])); + self::assertSame($result, Psr7\Header::normalize($header)); } }