From c593766be8827ead774f48fff8d0fd47f967cf6d Mon Sep 17 00:00:00 2001 From: willitscale Date: Sat, 2 Aug 2025 20:55:14 +0100 Subject: [PATCH] Aligning header keys with RFC-9110 section 5.1 --- src/Requests/ServerRequest.php | 11 +- tests/Controllers/HeadersControllerTest.php | 115 ++++++++++++++++++ .../TestApp/Controllers/HeadersController.php | 61 ++++++++++ 3 files changed, 183 insertions(+), 4 deletions(-) create mode 100644 tests/Controllers/HeadersControllerTest.php create mode 100644 tests/TestApp/Controllers/HeadersController.php diff --git a/src/Requests/ServerRequest.php b/src/Requests/ServerRequest.php index bfcbc6b..c11d07e 100644 --- a/src/Requests/ServerRequest.php +++ b/src/Requests/ServerRequest.php @@ -47,6 +47,10 @@ public function __construct( $this->uploadedFiles = $uploadedFiles ?? $_FILES; $this->parsedBody = $parsedBody ?? $_POST; $this->attributes = $attributes; + + foreach ($this->headers as $name => $values) { + $this->headers[strtolower($name)] = $values; + } } public function extractHeadersFromServer(): array @@ -54,10 +58,7 @@ public function extractHeadersFromServer(): array $headers = []; foreach ($_SERVER as $name => $value) { if (0 === stripos($name, 'HTTP_')) { - // The only issue with this is that if a header contains underscores, they will be - // replaced with dashes and lowercase first letter will be replaced with uppercase. - $key = ucwords(str_replace('_', ' ', strtolower(substr($name, 5)))); - $key = str_replace(' ', '-', $key); + $key = substr($name, 5); $headers[$key] = [$value]; } } @@ -149,6 +150,8 @@ public function getHeader($name): array public function getHeaderLine($name): string { + $name = strtolower($name); + if (empty($this->headers[$name])) { return ''; } diff --git a/tests/Controllers/HeadersControllerTest.php b/tests/Controllers/HeadersControllerTest.php new file mode 100644 index 0000000..501ee92 --- /dev/null +++ b/tests/Controllers/HeadersControllerTest.php @@ -0,0 +1,115 @@ +setupRouter( + 'GET', + $path, + $this->getTestRoot(), + $this->getComposerTestFile(), + null, + [$key => $value] + ); + + $response = $router->route()->getBody()->getContents(); + $this->assertEquals($value, $response); + } + + public static function headersDataProvider(): array + { + return [ + 'it should pass a lower case header with a lower case key' => [ + 'value' => 'test', + 'key' => 'lower-case', + 'path' => '/headers/lower-case' + ], + 'it should pass a lower case header with an upper case key' => [ + 'value' => 'test', + 'key' => 'LOWER-CASE', + 'path' => '/headers/lower-case' + ], + 'it should pass a lower case header with a mixed case key' => [ + 'value' => 'test', + 'key' => 'LoWeR-cAsE', + 'path' => '/headers/lower-case' + ], + 'it should pass a lower case header with a camel case key' => [ + 'value' => 'test', + 'key' => 'Lower-Case', + 'path' => '/headers/lower-case' + ], + 'it should pass a upper case header with an upper case key' => [ + 'value' => 'test', + 'key' => 'UPPER-CASE', + 'path' => '/headers/upper-case' + ], + 'it should pass a upper case header with a lower case key' => [ + 'value' => 'test', + 'key' => 'upper-case', + 'path' => '/headers/upper-case' + ], + 'it should pass a upper case header with a mixed case key' => [ + 'value' => 'test', + 'key' => 'UpPeR-cAsE', + 'path' => '/headers/upper-case' + ], + 'it should pass a upper case header with a camel case key' => [ + 'value' => 'test', + 'key' => 'Upper-Case', + 'path' => '/headers/upper-case' + ], + 'it should pass a mixed case header with an upper case key' => [ + 'value' => 'test', + 'key' => 'MIXED-CASE', + 'path' => '/headers/mixed-case' + ], + 'it should pass a mixed case header with a lower case key' => [ + 'value' => 'test', + 'key' => 'mixed-case', + 'path' => '/headers/mixed-case' + ], + 'it should pass a mixed case header with a mixed case key' => [ + 'value' => 'test', + 'key' => 'MiXeD-cAsE', + 'path' => '/headers/mixed-case' + ], + 'it should pass a mixed case header with a camel case key' => [ + 'value' => 'test', + 'key' => 'Mixed-Case', + 'path' => '/headers/mixed-case' + ], + 'it should pass a camel case header with an upper case key' => [ + 'value' => 'test', + 'key' => 'CAMEL-CASE', + 'path' => '/headers/camel-case' + ], + 'it should pass a camel case header with a lower case key' => [ + 'value' => 'test', + 'key' => 'camel-case', + 'path' => '/headers/camel-case' + ], + 'it should pass a camel case header with a mixed case key' => [ + 'value' => 'test', + 'key' => 'CaMeL-cAsE', + 'path' => '/headers/camel-case' + ], + 'it should pass a camel case header with a camel case key' => [ + 'value' => 'test', + 'key' => 'Camel-Case', + 'path' => '/headers/camel-case' + ], + ]; + } +} diff --git a/tests/TestApp/Controllers/HeadersController.php b/tests/TestApp/Controllers/HeadersController.php new file mode 100644 index 0000000..232a5dd --- /dev/null +++ b/tests/TestApp/Controllers/HeadersController.php @@ -0,0 +1,61 @@ +setData($header) + ->setHttpStatusCode(HttpStatusCode::HTTP_OK) + ->build(); + } + + #[Path('/upper-case')] + #[Method(HttpMethod::GET)] + public function headersUpperCase( + #[HeaderParameter('UPPER-CASE', true)] string $header, + ): ResponseInterface { + return new ResponseBuilder() + ->setData($header) + ->setHttpStatusCode(HttpStatusCode::HTTP_OK) + ->build(); + } + + #[Path('/mixed-case')] + #[Method(HttpMethod::GET)] + public function headersMixedCase( + #[HeaderParameter('mIxEd-CaSe', true)] string $header, + ): ResponseInterface { + return new ResponseBuilder() + ->setData($header) + ->setHttpStatusCode(HttpStatusCode::HTTP_OK) + ->build(); + } + + #[Path('/camel-case')] + #[Method(HttpMethod::GET)] + public function headersCamelCase( + #[HeaderParameter('Camel-Case', true)] string $header, + ): ResponseInterface { + return new ResponseBuilder() + ->setData($header) + ->setHttpStatusCode(HttpStatusCode::HTTP_OK) + ->build(); + } +}