vendor/shopware/storefront/Framework/Csrf/CsrfPlaceholderHandler.php line 51

Open in your IDE?
  1. <?php declare(strict_types=1);
  2. namespace Shopware\Storefront\Framework\Csrf;
  3. use Shopware\Core\Framework\Feature;
  4. use Shopware\Core\Framework\Log\Package;
  5. use Symfony\Component\HttpFoundation\Cookie;
  6. use Symfony\Component\HttpFoundation\Request;
  7. use Symfony\Component\HttpFoundation\RequestStack;
  8. use Symfony\Component\HttpFoundation\Response;
  9. use Symfony\Component\HttpFoundation\Session\Session;
  10. use Symfony\Component\HttpFoundation\Session\SessionInterface;
  11. use Symfony\Component\HttpFoundation\Session\Storage\SessionStorageFactoryInterface;
  12. use Symfony\Component\HttpFoundation\StreamedResponse;
  13. use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;
  14. /**
  15.  * @deprecated tag:v6.5.0 - class will be removed as the csrf system will be removed in favor for the samesite approach
  16.  */
  17. #[Package('storefront')]
  18. class CsrfPlaceholderHandler
  19. {
  20.     public const CSRF_PLACEHOLDER '754c481992411ac100.sfiHfZ4bk1QxK4-vtZPLmnAl0_NULsLiDseqqoBe83w.2bH3Ja029hJ5dMXa-KGb7AFflZYLarCbXb3v-vRuiQ_9z8Mw9U-mLVhcuw007700">;
  21.     private CsrfTokenManagerInterface $csrfTokenManager;
  22.     private bool $csrfEnabled;
  23.     private string $csrfMode;
  24.     private RequestStack $requestStack;
  25.     private SessionStorageFactoryInterface $sessionFactory;
  26.     /**
  27.      * @internal
  28.      */
  29.     public function __construct(CsrfTokenManagerInterface $csrfTokenManagerbool $csrfEnabledstring $csrfModeRequestStack $requestStackSessionStorageFactoryInterface $sessionFactory)
  30.     {
  31.         $this->csrfTokenManager $csrfTokenManager;
  32.         $this->csrfEnabled $csrfEnabled;
  33.         $this->csrfMode $csrfMode;
  34.         $this->requestStack $requestStack;
  35.         $this->sessionFactory $sessionFactory;
  36.     }
  37.     public function replaceCsrfToken(Response $responseRequest $request): Response
  38.     {
  39.         Feature::triggerDeprecationOrThrow(
  40.             'v6.5.0.0',
  41.             Feature::deprecatedClassMessage(__CLASS__'v6.5.0.0')
  42.         );
  43.         if ($response instanceof StreamedResponse) {
  44.             return $response;
  45.         }
  46.         if (!$this->csrfEnabled || $this->csrfMode !== CsrfModes::MODE_TWIG) {
  47.             return $response;
  48.         }
  49.         if ($response->getStatusCode() !== Response::HTTP_OK && $response->getStatusCode() !== Response::HTTP_NOT_FOUND) {
  50.             return $response;
  51.         }
  52.         $content $response->getContent();
  53.         if ($content === false) {
  54.             return $response;
  55.         }
  56.         // Early return if the placeholder is not present in body to save cpu cycles with the regex
  57.         if (!\str_contains($contentself::CSRF_PLACEHOLDER)) {
  58.             return $response;
  59.         }
  60.         // Get session from session provider if not provided in session. This happens when the page is fully cached
  61.         $session $request->hasSession() ? $request->getSession() : $this->createSession($request);
  62.         $request->setSession($session);
  63.         if ($session !== null) {
  64.             // StorefrontSubscriber did not run and set the session name. This can happen when the page is fully cached in the http cache
  65.             if (!$session->isStarted()) {
  66.                 $session->setName('session-');
  67.             }
  68.             // The SessionTokenStorage gets the session from the RequestStack. This is at this moment empty as the Symfony request cycle did run already
  69.             $this->requestStack->push($request);
  70.         }
  71.         $processedIntents = [];
  72.         // https://regex101.com/r/fefx3V/1
  73.         $content preg_replace_callback(
  74.             '/' self::CSRF_PLACEHOLDER '(?<intent>[^#]*)#/',
  75.             function ($matches) use ($response$request, &$processedIntents) {
  76.                 $intent $matches['intent'];
  77.                 $token $processedIntents[$intent] ?? null;
  78.                 // Don't generate the token and set the cookie again
  79.                 if ($token === null) {
  80.                     $token $this->getToken($intent);
  81.                     $cookie Cookie::create('csrf[' $intent ']'$token);
  82.                     $cookie->setSecureDefault($request->isSecure());
  83.                     $response->headers->setCookie($cookie);
  84.                     $processedIntents[$intent] = $token;
  85.                 }
  86.                 return $token;
  87.             },
  88.             $content
  89.         );
  90.         $response->setContent($content);
  91.         if ($session !== null) {
  92.             // Pop out the request injected some lines above. This is important for long running applications with roadrunner or swoole
  93.             $this->requestStack->pop();
  94.         }
  95.         return $response;
  96.     }
  97.     private function getToken(string $intent): string
  98.     {
  99.         return $this->csrfTokenManager->getToken($intent)->getValue();
  100.     }
  101.     private function createSession(Request $request): SessionInterface
  102.     {
  103.         $session = new Session($this->sessionFactory->createStorage($request));
  104.         $session->setName('session-');
  105.         $request->setSession($session);
  106.         return $session;
  107.     }
  108. }