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/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: 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..3f846a36d2e7 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,17 @@ 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) { + $index = $i + 1; + $data['vars']['headers'][esc($name)] ??= ''; + $data['vars']['headers'][esc($name)] .= ' (' . $index . ') ' + . esc($header->getValueLine()); + } + } } foreach ($request->getCookie() as $name => $value) { @@ -157,8 +167,17 @@ 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) { + $index = $i + 1; + $data['vars']['response']['headers'][esc($name)] ??= ''; + $data['vars']['response']['headers'][esc($name)] .= ' (' . $index . ') ' + . esc($header->getValueLine()); + } + } } $data['config'] = Config::display(); diff --git a/system/HTTP/CURLRequest.php b/system/HTTP/CURLRequest.php index 9f5cd61ab275..35b62ec3486f 100644 --- a/system/HTTP/CURLRequest.php +++ b/system/HTTP/CURLRequest.php @@ -479,10 +479,14 @@ 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->setHeader($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); 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; 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([
getHeaderLine($name), 'html') ?> + getHeaderLine($name), 'html'); + } else { + foreach ($value as $i => $header) { + echo ' ('. $i+1 . ') ' . esc($header->getValueLine(), 'html'); + } + } + ?> +