<?php declare(strict_types=1);namespace Shopware\Storefront\Framework\Csrf;use Shopware\Core\Framework\Feature;use Shopware\Core\Framework\Log\Package;use Symfony\Component\HttpFoundation\Cookie;use Symfony\Component\HttpFoundation\Request;use Symfony\Component\HttpFoundation\RequestStack;use Symfony\Component\HttpFoundation\Response;use Symfony\Component\HttpFoundation\Session\Session;use Symfony\Component\HttpFoundation\Session\SessionInterface;use Symfony\Component\HttpFoundation\Session\Storage\SessionStorageFactoryInterface;use Symfony\Component\HttpFoundation\StreamedResponse;use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;/** * @deprecated tag:v6.5.0 - class will be removed as the csrf system will be removed in favor for the samesite approach */#[Package('storefront')]class CsrfPlaceholderHandler{ public const CSRF_PLACEHOLDER = 'a3c732590a21e881d362504ad9856ce.PrquT36Hqq9w7Y0sHnhBdXwwB8yuE7u_GKA8-4XvN_w.C-WZGErg9dwX2cR8My11OhtVaon5eMKOS8RZvfy-f51U9Os-CfTaxD2PvQ007700">; private CsrfTokenManagerInterface $csrfTokenManager; private bool $csrfEnabled; private string $csrfMode; private RequestStack $requestStack; private SessionStorageFactoryInterface $sessionFactory; /** * @internal */ public function __construct(CsrfTokenManagerInterface $csrfTokenManager, bool $csrfEnabled, string $csrfMode, RequestStack $requestStack, SessionStorageFactoryInterface $sessionFactory) { $this->csrfTokenManager = $csrfTokenManager; $this->csrfEnabled = $csrfEnabled; $this->csrfMode = $csrfMode; $this->requestStack = $requestStack; $this->sessionFactory = $sessionFactory; } public function replaceCsrfToken(Response $response, Request $request): Response { Feature::triggerDeprecationOrThrow( 'v6.5.0.0', Feature::deprecatedClassMessage(__CLASS__, 'v6.5.0.0') ); if ($response instanceof StreamedResponse) { return $response; } if (!$this->csrfEnabled || $this->csrfMode !== CsrfModes::MODE_TWIG) { return $response; } if ($response->getStatusCode() !== Response::HTTP_OK && $response->getStatusCode() !== Response::HTTP_NOT_FOUND) { return $response; } $content = $response->getContent(); if ($content === false) { return $response; } // Early return if the placeholder is not present in body to save cpu cycles with the regex if (!\str_contains($content, self::CSRF_PLACEHOLDER)) { return $response; } // Get session from session provider if not provided in session. This happens when the page is fully cached $session = $request->hasSession() ? $request->getSession() : $this->createSession($request); $request->setSession($session); if ($session !== null) { // StorefrontSubscriber did not run and set the session name. This can happen when the page is fully cached in the http cache if (!$session->isStarted()) { $session->setName('session-'); } // The SessionTokenStorage gets the session from the RequestStack. This is at this moment empty as the Symfony request cycle did run already $this->requestStack->push($request); } $processedIntents = []; // https://regex101.com/r/fefx3V/1 $content = preg_replace_callback( '/' . self::CSRF_PLACEHOLDER . '(?<intent>[^#]*)#/', function ($matches) use ($response, $request, &$processedIntents) { $intent = $matches['intent']; $token = $processedIntents[$intent] ?? null; // Don't generate the token and set the cookie again if ($token === null) { $token = $this->getToken($intent); $cookie = Cookie::create('csrf[' . $intent . ']', $token); $cookie->setSecureDefault($request->isSecure()); $response->headers->setCookie($cookie); $processedIntents[$intent] = $token; } return $token; }, $content ); $response->setContent($content); if ($session !== null) { // Pop out the request injected some lines above. This is important for long running applications with roadrunner or swoole $this->requestStack->pop(); } return $response; } private function getToken(string $intent): string { return $this->csrfTokenManager->getToken($intent)->getValue(); } private function createSession(Request $request): SessionInterface { $session = new Session($this->sessionFactory->createStorage($request)); $session->setName('session-'); $request->setSession($session); return $session; }}