vendor/nelmio/security-bundle/src/EventListener/ForcedSslListener.php line 49

Open in your IDE?
  1. <?php
  2. declare(strict_types=1);
  3. /*
  4.  * This file is part of the Nelmio SecurityBundle.
  5.  *
  6.  * (c) Nelmio <hello@nelm.io>
  7.  *
  8.  * For the full copyright and license information, please view the LICENSE
  9.  * file that was distributed with this source code.
  10.  */
  11. namespace Nelmio\SecurityBundle\EventListener;
  12. use Symfony\Component\HttpFoundation\RedirectResponse;
  13. use Symfony\Component\HttpKernel\Event\RequestEvent;
  14. use Symfony\Component\HttpKernel\Event\ResponseEvent;
  15. final class ForcedSslListener
  16. {
  17.     private ?int $hstsMaxAge;
  18.     private bool $hstsSubdomains;
  19.     private bool $hstsPreload;
  20.     private ?string $allowList;
  21.     private ?string $hosts;
  22.     private int $redirectStatusCode;
  23.     /**
  24.      * @param list<string> $allowList
  25.      * @param list<string> $hosts
  26.      */
  27.     public function __construct(
  28.         ?int $hstsMaxAge,
  29.         bool $hstsSubdomains,
  30.         bool $hstsPreload false,
  31.         array $allowList = [],
  32.         array $hosts = [],
  33.         int $redirectStatusCode 302
  34.     ) {
  35.         $this->hstsMaxAge $hstsMaxAge;
  36.         $this->hstsSubdomains $hstsSubdomains;
  37.         $this->hstsPreload $hstsPreload;
  38.         $this->allowList = [] !== $allowList '('.implode('|'$allowList).')' null;
  39.         $this->hosts = [] !== $hosts '('.implode('|'$hosts).')' null;
  40.         $this->redirectStatusCode $redirectStatusCode;
  41.     }
  42.     public function onKernelRequest(RequestEvent $e): void
  43.     {
  44.         if (!$e->isMainRequest()) {
  45.             return;
  46.         }
  47.         $request $e->getRequest();
  48.         // skip SSL & non-GET/HEAD requests
  49.         if ($request->isSecure() || !$request->isMethodSafe()) {
  50.             return;
  51.         }
  52.         // skip allowed URLs
  53.         if (null !== $this->allowList && === preg_match('{'.$this->allowList.'}i''' === $request->getPathInfo() ? '/' $request->getPathInfo())) {
  54.             return;
  55.         }
  56.         // skip non-listed hosts
  57.         if (null !== $this->hosts && !== preg_match('{'.$this->hosts.'}i''' === $request->getHost() ? '/' $request->getHost())) {
  58.             return;
  59.         }
  60.         // redirect the rest to SSL
  61.         $e->setResponse(new RedirectResponse('https://'.substr($request->getUri(), 7), $this->redirectStatusCode));
  62.     }
  63.     public function onKernelResponse(ResponseEvent $e): void
  64.     {
  65.         if (!$e->isMainRequest()) {
  66.             return;
  67.         }
  68.         // skip non-SSL requests as per the RFC
  69.         // "An HSTS Host MUST NOT include the STS header field in HTTP responses conveyed over non-secure transport."
  70.         $request $e->getRequest();
  71.         if (!$request->isSecure()) {
  72.             return;
  73.         }
  74.         $response $e->getResponse();
  75.         if (!$response->headers->has('Strict-Transport-Security')) {
  76.             $header 'max-age='.$this->hstsMaxAge;
  77.             $header .= ($this->hstsSubdomains '; includeSubDomains' '');
  78.             $header .= ($this->hstsPreload '; preload' '');
  79.             $response->headers->set('Strict-Transport-Security'$header);
  80.         }
  81.     }
  82. }