From 9e6be65899e1caa182303bdea82315cf53482242 Mon Sep 17 00:00:00 2001 From: kenjis Date: Tue, 21 Nov 2023 13:58:43 +0900 Subject: [PATCH 1/7] test: add test for multiple headers --- .../HTTP/CURLRequestDoNotShareOptionsTest.php | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/tests/system/HTTP/CURLRequestDoNotShareOptionsTest.php b/tests/system/HTTP/CURLRequestDoNotShareOptionsTest.php index c9710616e2d2..a7f3268a2b72 100644 --- a/tests/system/HTTP/CURLRequestDoNotShareOptionsTest.php +++ b/tests/system/HTTP/CURLRequestDoNotShareOptionsTest.php @@ -852,6 +852,38 @@ public function testResponseHeadersWithMultipleRequests(): void $this->assertSame(200, $response->getStatusCode()); } + public function testResponseHeadersWithMultipleSetCookies(): void + { + $request = $this->getRequest([ + 'base_uri' => 'https://github.com/', + ]); + + $output = "HTTP/2 200 +server: GitHub.com +date: Sat, 11 Nov 2023 02:26:55 GMT +content-type: text/html; charset=utf-8 +set-cookie: _gh_sess=PlRlha1YumlLhLuo5MuNbIWJRO9RRuR%2FHfYsWRh5B0mkalFIZstlAbTmSstl8q%2FAC57IsWMVuFHWQc6L4qDHQJrwhuYVO5ZaigPCUjAStnhh%2FieZQVqIf92Al7vusuzx2o8XH%2Fv6nd9qzMTAWc2%2FkRsl8jxPQYGNaWeuUBY2w3%2FDORSikN4c0vHOyedhU7Xcv3Ryz5xD3DNxK9R8xKNZ6OSXLJ6bjX8iIT6LxvroVIf2HjvowW9cQsq0kN08mS6KtTnH0mD3ANWqsVVWeMzFNA%3D%3D--Jx830Q9Nmkfz9OGA--kEcPtNphvjNMopYqFDxUbw%3D%3D; Path=/; HttpOnly; Secure; SameSite=Lax +set-cookie: _octo=GH1.1.599292127.1699669625; Path=/; Domain=github.com; Expires=Mon, 11 Nov 2024 02:27:05 GMT; Secure; SameSite=Lax +set-cookie: logged_in=no; Path=/; Domain=github.com; Expires=Mon, 11 Nov 2024 02:27:05 GMT; HttpOnly; Secure; SameSite=Lax +accept-ranges: bytes\x0d\x0a\x0d\x0a"; + $request->setOutput($output); + + $response = $request->get('/'); + + $setCookieHeaders = $response->header('set-cookie'); + + $this->assertCount(3, $setCookieHeaders); + $this->assertSame( + 'logged_in=no; Path=/; Domain=github.com; Expires=Mon, 11 Nov 2024 02:27:05 GMT; HttpOnly; Secure; SameSite=Lax', + $setCookieHeaders[2]->getValue() + ); + + $this->assertSame( + '_octo=GH1.1.599292127.1699669625; Path=/; Domain=github.com; Expires=Mon, 11 Nov 2024 02:27:05 GMT; Secure; SameSite=Lax', + $setCookieHeaders[1]->getValueLine() + ); + } + public function testSplitResponse(): void { $request = $this->getRequest([ From 4986bc27cc83a430c7c695786c80e8951577e2ce Mon Sep 17 00:00:00 2001 From: kenjis Date: Tue, 21 Nov 2023 14:01:19 +0900 Subject: [PATCH 2/7] fix: CURLRequest losts headers with same name --- system/HTTP/CURLRequest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/HTTP/CURLRequest.php b/system/HTTP/CURLRequest.php index 9f5cd61ab275..6d1690bc7614 100644 --- a/system/HTTP/CURLRequest.php +++ b/system/HTTP/CURLRequest.php @@ -482,7 +482,7 @@ protected function setResponseHeaders(array $headers = []) $title = substr($header, 0, $pos); $value = substr($header, $pos + 1); - $this->response->setHeader($title, $value); + $this->response->addHeader($title, $value); } elseif (strpos($header, 'HTTP') === 0) { preg_match('#^HTTP\/([12](?:\.[01])?) (\d+) (.+)#', $header, $matches); From 7852e3fb77452400229299bb07eeaed69c676dff Mon Sep 17 00:00:00 2001 From: kenjis Date: Tue, 21 Nov 2023 14:18:45 +0900 Subject: [PATCH 3/7] fix: trim header name and value --- system/HTTP/CURLRequest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/system/HTTP/CURLRequest.php b/system/HTTP/CURLRequest.php index 6d1690bc7614..4c011c985f1d 100644 --- a/system/HTTP/CURLRequest.php +++ b/system/HTTP/CURLRequest.php @@ -479,8 +479,8 @@ protected function setResponseHeaders(array $headers = []) { foreach ($headers as $header) { if (($pos = strpos($header, ':')) !== false) { - $title = substr($header, 0, $pos); - $value = substr($header, $pos + 1); + $title = trim(substr($header, 0, $pos)); + $value = trim(substr($header, $pos + 1)); $this->response->addHeader($title, $value); } elseif (strpos($header, 'HTTP') === 0) { From a769e53ad500e2badb124edf990041bd0c58d6e1 Mon Sep 17 00:00:00 2001 From: kenjis Date: Tue, 21 Nov 2023 17:43:09 +0900 Subject: [PATCH 4/7] feat: add check if headers() returns array --- app/Views/errors/html/error_exception.php | 33 ++++++++++++++++++----- system/Cache/ResponseCache.php | 11 ++++++-- system/Debug/Toolbar.php | 25 ++++++++++++++--- system/HTTP/RedirectResponse.php | 10 +++++-- system/HTTP/ResponseTrait.php | 18 +++++++++++-- 5 files changed, 80 insertions(+), 17 deletions(-) diff --git a/app/Views/errors/html/error_exception.php b/app/Views/errors/html/error_exception.php index d27d52fdc576..ac09f33f5fec 100644 --- a/app/Views/errors/html/error_exception.php +++ b/app/Views/errors/html/error_exception.php @@ -1,4 +1,5 @@ - + $value) : ?> - getName(), 'html') ?> - getValueLine(), 'html') ?> + + + getValueLine(), 'html'); + } else { + foreach ($value as $i => $header) { + echo ' ('. $i+1 . ') ' . esc($header->getValueLine(), 'html'); + } + } + ?> + @@ -316,8 +327,6 @@ headers(); ?> - -

Headers

@@ -328,10 +337,20 @@ - + $value) : ?> - + diff --git a/system/Cache/ResponseCache.php b/system/Cache/ResponseCache.php index 79948af10e54..6e8e8b4887b1 100644 --- a/system/Cache/ResponseCache.php +++ b/system/Cache/ResponseCache.php @@ -12,6 +12,7 @@ namespace CodeIgniter\Cache; use CodeIgniter\HTTP\CLIRequest; +use CodeIgniter\HTTP\Header; use CodeIgniter\HTTP\IncomingRequest; use CodeIgniter\HTTP\ResponseInterface; use Config\Cache as CacheConfig; @@ -99,8 +100,14 @@ public function make($request, ResponseInterface $response): bool $headers = []; - foreach ($response->headers() as $header) { - $headers[$header->getName()] = $header->getValueLine(); + foreach ($response->headers() as $name => $value) { + if ($value instanceof Header) { + $headers[$name] = $value->getValueLine(); + } else { + foreach ($value as $header) { + $headers[$name][] = $header->getValueLine(); + } + } } return $this->cache->save( diff --git a/system/Debug/Toolbar.php b/system/Debug/Toolbar.php index b2d54c5d52cc..f20822cf2e7b 100644 --- a/system/Debug/Toolbar.php +++ b/system/Debug/Toolbar.php @@ -18,6 +18,7 @@ use CodeIgniter\Format\JSONFormatter; use CodeIgniter\Format\XMLFormatter; use CodeIgniter\HTTP\DownloadResponse; +use CodeIgniter\HTTP\Header; use CodeIgniter\HTTP\IncomingRequest; use CodeIgniter\HTTP\RequestInterface; use CodeIgniter\HTTP\ResponseInterface; @@ -140,8 +141,16 @@ public function run(float $startTime, float $totalTime, RequestInterface $reques $data['vars']['post'][esc($name)] = is_array($value) ? '
' . esc(print_r($value, true)) . '
' : esc($value); } - foreach ($request->headers() as $header) { - $data['vars']['headers'][esc($header->getName())] = esc($header->getValueLine()); + foreach ($request->headers() as $name => $value) { + if ($value instanceof Header) { + $data['vars']['headers'][esc($name)] = esc($value->getValueLine()); + } else { + foreach ($value as $i => $header) { + $data['vars']['headers'][esc($name)] ??= ''; + $data['vars']['headers'][esc($name)] .= ' (' . $i + 1 . ') ' + . esc($header->getValueLine()); + } + } } foreach ($request->getCookie() as $name => $value) { @@ -157,8 +166,16 @@ public function run(float $startTime, float $totalTime, RequestInterface $reques 'headers' => [], ]; - foreach ($response->headers() as $header) { - $data['vars']['response']['headers'][esc($header->getName())] = esc($header->getValueLine()); + foreach ($response->headers() as $name => $value) { + if ($value instanceof Header) { + $data['vars']['response']['headers'][esc($name)] = esc($value->getValueLine()); + } else { + foreach ($value as $i => $header) { + $data['vars']['response']['headers'][esc($name)] ??= ''; + $data['vars']['response']['headers'][esc($name)] .= ' (' . $i + 1 . ') ' + . esc($header->getValueLine()); + } + } } $data['config'] = Config::display(); diff --git a/system/HTTP/RedirectResponse.php b/system/HTTP/RedirectResponse.php index d6f234ca9f68..8efd4c2c51ea 100644 --- a/system/HTTP/RedirectResponse.php +++ b/system/HTTP/RedirectResponse.php @@ -161,8 +161,14 @@ public function withCookies() */ public function withHeaders() { - foreach (Services::response()->headers() as $name => $header) { - $this->setHeader($name, $header->getValue()); + foreach (Services::response()->headers() as $name => $value) { + if ($value instanceof Header) { + $this->setHeader($name, $value->getValue()); + } else { + foreach ($value as $header) { + $this->addHeader($name, $header->getValue()); + } + } } return $this; diff --git a/system/HTTP/ResponseTrait.php b/system/HTTP/ResponseTrait.php index 8d8cad504866..e5e006c776aa 100644 --- a/system/HTTP/ResponseTrait.php +++ b/system/HTTP/ResponseTrait.php @@ -398,8 +398,22 @@ public function sendHeaders() header(sprintf('HTTP/%s %s %s', $this->getProtocolVersion(), $this->getStatusCode(), $this->getReasonPhrase()), true, $this->getStatusCode()); // Send all of our headers - foreach (array_keys($this->headers()) as $name) { - header($name . ': ' . $this->getHeaderLine($name), false, $this->getStatusCode()); + foreach ($this->headers() as $name => $value) { + if ($value instanceof Header) { + header( + $name . ': ' . $value->getValueLine(), + false, + $this->getStatusCode() + ); + } else { + foreach ($value as $header) { + header( + $name . ': ' . $header->getValueLine(), + false, + $this->getStatusCode() + ); + } + } } return $this; From 3c38acbf6ddbd5c72c7dd960f04bb27e41393c57 Mon Sep 17 00:00:00 2001 From: kenjis Date: Wed, 22 Nov 2023 09:18:03 +0900 Subject: [PATCH 5/7] fix: Call to an undefined method ------ -------------------------------------------------- Line system/HTTP/CURLRequest.php ------ -------------------------------------------------- 485 Call to an undefined method CodeIgniter\HTTP\ResponseInterface::addHeader(). ------ -------------------------------------------------- --- system/HTTP/CURLRequest.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/system/HTTP/CURLRequest.php b/system/HTTP/CURLRequest.php index 4c011c985f1d..35b62ec3486f 100644 --- a/system/HTTP/CURLRequest.php +++ b/system/HTTP/CURLRequest.php @@ -482,7 +482,11 @@ protected function setResponseHeaders(array $headers = []) $title = trim(substr($header, 0, $pos)); $value = trim(substr($header, $pos + 1)); - $this->response->addHeader($title, $value); + if ($this->response instanceof Response) { + $this->response->addHeader($title, $value); + } else { + $this->response->setHeader($title, $value); + } } elseif (strpos($header, 'HTTP') === 0) { preg_match('#^HTTP\/([12](?:\.[01])?) (\d+) (.+)#', $header, $matches); From 568538e113587454b0f7faa1ec29e56766071f54 Mon Sep 17 00:00:00 2001 From: kenjis Date: Wed, 22 Nov 2023 09:19:59 +0900 Subject: [PATCH 6/7] refactor: suppress PHPStan errors ------ ------------------------------------------------------------------- Line system/Debug/Toolbar.php ------ ------------------------------------------------------------------- 150 Binary operation "+" between non-falsy-string and 1 results in an error. 175 Binary operation "+" between non-falsy-string and 1 results in an error. ------ ------------------------------------------------------------------- --- system/Debug/Toolbar.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/system/Debug/Toolbar.php b/system/Debug/Toolbar.php index f20822cf2e7b..3f846a36d2e7 100644 --- a/system/Debug/Toolbar.php +++ b/system/Debug/Toolbar.php @@ -146,8 +146,9 @@ public function run(float $startTime, float $totalTime, RequestInterface $reques $data['vars']['headers'][esc($name)] = esc($value->getValueLine()); } else { foreach ($value as $i => $header) { + $index = $i + 1; $data['vars']['headers'][esc($name)] ??= ''; - $data['vars']['headers'][esc($name)] .= ' (' . $i + 1 . ') ' + $data['vars']['headers'][esc($name)] .= ' (' . $index . ') ' . esc($header->getValueLine()); } } @@ -171,8 +172,9 @@ public function run(float $startTime, float $totalTime, RequestInterface $reques $data['vars']['response']['headers'][esc($name)] = esc($value->getValueLine()); } else { foreach ($value as $i => $header) { + $index = $i + 1; $data['vars']['response']['headers'][esc($name)] ??= ''; - $data['vars']['response']['headers'][esc($name)] .= ' (' . $i + 1 . ') ' + $data['vars']['response']['headers'][esc($name)] .= ' (' . $index . ') ' . esc($header->getValueLine()); } } From 8b0e1febee996da6d789dcb9fe4babecabc5ff0a Mon Sep 17 00:00:00 2001 From: kenjis Date: Wed, 22 Nov 2023 09:48:48 +0900 Subject: [PATCH 7/7] chore: add skip_violations --- deptrac.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/deptrac.yaml b/deptrac.yaml index 271378bfde55..4d6cb8912820 100644 --- a/deptrac.yaml +++ b/deptrac.yaml @@ -225,6 +225,7 @@ parameters: # Individual class exemptions CodeIgniter\Cache\ResponseCache: - CodeIgniter\HTTP\CLIRequest + - CodeIgniter\HTTP\Header - CodeIgniter\HTTP\IncomingRequest - CodeIgniter\HTTP\ResponseInterface CodeIgniter\Entity\Cast\URICast:
getHeaderLine($name), 'html') ?> + getHeaderLine($name), 'html'); + } else { + foreach ($value as $i => $header) { + echo ' ('. $i+1 . ') ' . esc($header->getValueLine(), 'html'); + } + } + ?> +