diff --git a/src/Symfony/Component/HttpFoundation/Tests/Fixtures/response-functional/deleted_cookie.expected b/src/Symfony/Component/HttpFoundation/Tests/Fixtures/response-functional/deleted_cookie.expected new file mode 100644 index 0000000000000..e8b845e674a84 --- /dev/null +++ b/src/Symfony/Component/HttpFoundation/Tests/Fixtures/response-functional/deleted_cookie.expected @@ -0,0 +1,11 @@ + +Array +( + [0] => Content-Type: text/plain; charset=utf-8 + [1] => Cache-Control: max-age=0, private, must-revalidate + [2] => Cache-Control: max-age=0, must-revalidate, private + [3] => Date: Sat, 12 Nov 1955 20:04:00 GMT + [4] => Expires: %s, %d %s %d %d:%d:%d GMT + [5] => Set-Cookie: PHPSESSID=deleted; expires=%s, %d-%s-%d %d:%d:%d GMT; Max-Age=%d; %s +) +shutdown diff --git a/src/Symfony/Component/HttpFoundation/Tests/Fixtures/response-functional/deleted_cookie.php b/src/Symfony/Component/HttpFoundation/Tests/Fixtures/response-functional/deleted_cookie.php new file mode 100644 index 0000000000000..003b0c121f888 --- /dev/null +++ b/src/Symfony/Component/HttpFoundation/Tests/Fixtures/response-functional/deleted_cookie.php @@ -0,0 +1,60 @@ +cookies->set($sessionName, $sessionId); + +$requestStack = new RequestStack(); +$requestStack->push($request); + +$sessionFactory = new SessionFactory($requestStack, new NativeSessionStorageFactory()); + +$container = new Container(); +$container->set('request_stack', $requestStack); +$container->set('session_factory', $sessionFactory); + +$listener = new SessionListener($container); + +$kernel = new class($r) implements HttpKernelInterface { + /** + * @var Response + */ + private $response; + + public function __construct(Response $response) + { + $this->response = $response; + } + + public function handle(Request $request, int $type = self::MAIN_REQUEST, bool $catch = true): Response + { + return $this->response; + } +}; + +$listener->onKernelRequest(new RequestEvent($kernel, $request, HttpKernelInterface::MAIN_REQUEST)); +$session = $request->getSession(); +$session->set('foo', 'bar'); +$session->invalidate(); + +$listener->onKernelResponse(new ResponseEvent($kernel, $request, HttpKernelInterface::MAIN_REQUEST, $r)); + +$r->sendHeaders(); diff --git a/src/Symfony/Component/HttpFoundation/composer.json b/src/Symfony/Component/HttpFoundation/composer.json index d54bbfd16006b..358e14d166757 100644 --- a/src/Symfony/Component/HttpFoundation/composer.json +++ b/src/Symfony/Component/HttpFoundation/composer.json @@ -24,6 +24,8 @@ "require-dev": { "predis/predis": "~1.0", "symfony/cache": "^4.4|^5.0|^6.0", + "symfony/dependency-injection": "^5.4|^6.0", + "symfony/http-kernel": "^5.4.12|^6.1.4", "symfony/mime": "^4.4|^5.0|^6.0", "symfony/expression-language": "^4.4|^5.0|^6.0" }, diff --git a/src/Symfony/Component/HttpKernel/EventListener/AbstractSessionListener.php b/src/Symfony/Component/HttpKernel/EventListener/AbstractSessionListener.php index d0e8f45d038b6..4603052e933df 100644 --- a/src/Symfony/Component/HttpKernel/EventListener/AbstractSessionListener.php +++ b/src/Symfony/Component/HttpKernel/EventListener/AbstractSessionListener.php @@ -158,6 +158,11 @@ public function onKernelResponse(ResponseEvent $event) $isSessionEmpty = $session->isEmpty() && empty($_SESSION); // checking $_SESSION to keep compatibility with native sessions if ($requestSessionCookieId && $isSessionEmpty) { + // PHP internally sets the session cookie value to "deleted" when setcookie() is called with empty string $value argument + // which happens in \Symfony\Component\HttpFoundation\Session\Storage\Handler\AbstractSessionHandler::destroy + // when the session gets invalidated (for example on logout) so we must handle this case here too + // otherwise we would send two Set-Cookie headers back with the response + SessionUtils::popSessionCookie($sessionName, 'deleted'); $response->headers->clearCookie( $sessionName, $sessionCookiePath,