diff --git a/src/MessageTrait.php b/src/MessageTrait.php index 81f59f1..7a1192e 100644 --- a/src/MessageTrait.php +++ b/src/MessageTrait.php @@ -377,7 +377,7 @@ private function registerProtocolVersion(string $protocol): void */ private function normalizeHeaderName($name): string { - if (!is_string($name) || !preg_match('/^[a-zA-Z0-9\'`#$%&*+.^_|~!-]+$/', $name)) { + if (!is_string($name) || !preg_match('/^[a-zA-Z0-9\'`#$%&*+.^_|~!-]+$/D', $name)) { throw new InvalidArgumentException(sprintf( '`%s` is not valid header name.', (is_object($name) ? get_class($name) : (is_string($name) ? $name : gettype($name))) @@ -403,7 +403,7 @@ private function normalizeHeaderValue($value): array } foreach ($value as $v) { - if ((!is_string($v) && !is_numeric($v)) || !preg_match('/^[ \t\x21-\x7E\x80-\xFF]*$/', (string) $v)) { + if ((!is_string($v) && !is_numeric($v)) || !preg_match('/^[ \t\x21-\x7E\x80-\xFF]*$/D', (string) $v)) { throw new InvalidArgumentException(sprintf( '"%s" is not valid header value.', (is_object($v) ? get_class($v) : (is_string($v) ? $v : gettype($v))) diff --git a/src/UploadedFile.php b/src/UploadedFile.php index 0a1ab4d..1b9e3d4 100644 --- a/src/UploadedFile.php +++ b/src/UploadedFile.php @@ -170,7 +170,7 @@ public function getStream(): StreamInterface /** * {@inheritdoc} * - * @psalm-suppress DocblockTypeContradiction + * @psalm-suppress NoValue, RedundantCondition, TypeDoesNotContainType */ public function moveTo($targetPath): void { diff --git a/tests/RequestTest.php b/tests/RequestTest.php index ef212bc..60b6434 100644 --- a/tests/RequestTest.php +++ b/tests/RequestTest.php @@ -12,6 +12,7 @@ use stdClass; use function array_merge; +use function chr; final class RequestTest extends TestCase { @@ -175,4 +176,49 @@ private function getInvalidValues(array $values = []): array return $common; } + + /** + * @dataProvider provideHeaderValuesContainingNotAllowedChars + */ + public function testCannotHaveHeaderWithInvalidValue(string $name): void + { + $this->expectException(InvalidArgumentException::class); + $request = new Request('GET', 'https://example.com/'); + $request->withHeader($name, 'Bar'); + } + + public static function provideHeaderValuesContainingNotAllowedChars(): array + { + // Explicit tests for newlines as the most common exploit vector. + $tests = [ + ["new\nline"], + ["new\r\nline"], + ["new\rline"], + ["new\r\n line"], + ["newline\n"], + ["\nnewline"], + ["newline\r\n"], + ["\n\rnewline"], + ]; + + for ($i = 0; $i <= 0xff; $i++) { + if (chr($i) === "\t") { + continue; + } + if (chr($i) === " ") { + continue; + } + if ($i >= 0x21 && $i <= 0x7e) { + continue; + } + if ($i >= 0x80) { + continue; + } + + $tests[] = ["foo" . chr($i) . "bar"]; + $tests[] = ["foo" . chr($i)]; + } + + return $tests; + } } diff --git a/tests/ResponseTest.php b/tests/ResponseTest.php index 47599f3..dd573dc 100644 --- a/tests/ResponseTest.php +++ b/tests/ResponseTest.php @@ -148,4 +148,34 @@ private function getInvalidValues(array $values = []): array return $common; } + + /** + * @dataProvider invalidWithHeaderProvider + */ + public function testWithInvalidHeader($header, $headerValue, $expectedMessage): void + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage($expectedMessage); + $this->response->withHeader($header, $headerValue); + } + + public static function invalidWithHeaderProvider(): array + { + return [ + ['foo', [], 'Header value must be a string or an array of strings, empty array given.'], + ['foo', new stdClass(), '"stdClass" is not valid header value.'], + [[], 'foo', '`array` is not valid header name.'], + [false, 'foo', '`boolean` is not valid header name.'], + [new stdClass(), 'foo', '`stdClass` is not valid header name.'], + ['', 'foo', '`` is not valid header name.'], + ["Content-Type\r\n\r\n", 'foo', "`Content-Type\r\n\r\n` is not valid header name."], + ["Content-Type\r\n", 'foo', "`Content-Type\r\n` is not valid header name."], + ["Content-Type\n", 'foo', "`Content-Type\n` is not valid header name."], + ["\r\nContent-Type", 'foo',"`\r\nContent-Type` is not valid header name."], + ["\nContent-Type", 'foo', "`\nContent-Type` is not valid header name."], + ["\n", 'foo', "`\n` is not valid header name."], + ["\r\n", 'foo', "`\r\n` is not valid header name."], + ["\t", 'foo', "`\t` is not valid header name."], + ]; + } }