Duplicate `Set-Cookie` headers on Laravel
adrianfalleiro opened this issue · comments
When using this HttpKernel adapter with the Laravel bootstrap I am seeing duplicate Set-Cookie headers being sent.
Is this supposed to happen? It doesn't seem to break anything in my testing so far though.
From what I can see, this is occurring because of line 276 of PHPPM\Bridges\HttpKernel in which an array of cookies is being merged with existing Set-Cookie headers on the SymfonyResponse object:
$headers['Set-Cookie'] = array_merge((array)$headers['Set-Cookie'], $cookies);
Does this need to happen? You're already merging $nativeHeaders with all of the SymfonyResponse object headers on line 247:
$headers = array_merge($nativeHeaders, $syResponse->headers->allPreserveCase());
I've created a demo project to show the issue: https://github.com/adrianfalleiro/laravel-5.6-ppm-example
All you need to do is composer install and then ./vendor/bin/ppm start. If you inspect the headers on the home page you should be able to see duplicate Set-Cookie headers sent.
Also for context I am running this on macOS 10.12.6 with PHP 7.2.4 installed (via Homebrew).
And, there must be array_merge_recursive function. So I really don't get why we should merge cookies twice.
Index: Bridges/HttpKernel.php
<+>UTF-8
===================================================================
--- Bridges/HttpKernel.php (revision 086def550ccf72aee48bd62107cb2aa343cfebf6)
+++ Bridges/HttpKernel.php (revision 0329324c219a8d892bed2a09fc2e481382e38e6c)
@@ -104,7 +104,7 @@
if ($this->application instanceof TerminableInterface) {
$this->application->terminate($syRequest, $syResponse);
}
-
+
if ($this->application instanceof Kernel) {
$this->application->terminate($syRequest, $syResponse);
}
@@ -244,34 +244,10 @@
// operates on a clean header.
header_remove();
- $headers = array_merge($nativeHeaders, $syResponse->headers->allPreserveCase());
+ $syHeaders = $syResponse->headers->allPreserveCase();
+ $headers = array_merge_recursive($nativeHeaders, $syHeaders);
$cookies = [];
- /** @var Cookie $cookie */
- foreach ($syResponse->headers->getCookies() as $cookie) {
- $cookieHeader = sprintf('%s=%s', $cookie->getName(), $cookie->getValue());
-
- if ($cookie->getPath()) {
- $cookieHeader .= '; Path=' . $cookie->getPath();
- }
- if ($cookie->getDomain()) {
- $cookieHeader .= '; Domain=' . $cookie->getDomain();
- }
-
- if ($cookie->getExpiresTime()) {
- $cookieHeader .= '; Expires=' . gmdate('D, d-M-Y H:i:s', $cookie->getExpiresTime()). ' GMT';
- }
-
- if ($cookie->isSecure()) {
- $cookieHeader .= '; Secure';
- }
- if ($cookie->isHttpOnly()) {
- $cookieHeader .= '; HttpOnly';
- }
-
- $cookies[] = $cookieHeader;
- }
-
if (isset($headers['Set-Cookie'])) {
$headers['Set-Cookie'] = array_merge((array)$headers['Set-Cookie'], $cookies);
} else {
After this everything works as expected.
@dzubchik because you completely got rid of all cookies set by Symfony Response instance. AFAIK allPreserveCase() does not build cookie header entries of header->cookies.
oh, well, I just saw, Symfony\Component\HttpFoundation\Cookie has __toString which does it already. 👍
Before this session cookie was not installed.
@dzubchik When I tested your code block above on a Laravel project authentication fails. I cannot figure out why, but it looks to be failing because the XSRF tokens in the POST body and session do not match.
Instead the below seemed to work for me
protected function mapResponse(SymfonyResponse $syResponse, $stdout='')
{
...
- $headers = array_merge($nativeHeaders, $syResponse->headers->allPreserveCase());
+ $headers = array_merge($nativeHeaders, $syResponse->headers->allPreserveCaseWithoutCookies());
...
}And then iterating over $syResponse->headers->getCookies() and adding them to the PSR-7 response as it currently does.
I'm not really sure why this works...as allPreserveCase() does exactly the same thing.
See also #112 for fixing the session cookie. That PR also got rid of duplicate cookies for me.
This issue should be retested now that #112 has been merged.
Thanks, I’ll retest my original code
Please reopen if issue persists.
