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

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.     use KernelEventForwardCompatibilityTrait;
  18.     private ?int $hstsMaxAge;
  19.     private bool $hstsSubdomains;
  20.     private bool $hstsPreload;
  21.     private ?string $allowList;
  22.     private ?string $hosts;
  23.     private int $redirectStatusCode;
  24.     /**
  25.      * @param list<string> $allowList
  26.      * @param list<string> $hosts
  27.      */
  28.     public function __construct(
  29.         ?int $hstsMaxAge,
  30.         bool $hstsSubdomains,
  31.         bool $hstsPreload false,
  32.         array $allowList = [],
  33.         array $hosts = [],
  34.         int $redirectStatusCode 302
  35.     ) {
  36.         $this->hstsMaxAge $hstsMaxAge;
  37.         $this->hstsSubdomains $hstsSubdomains;
  38.         $this->hstsPreload $hstsPreload;
  39.         $this->allowList = [] !== $allowList '('.implode('|'$allowList).')' null;
  40.         $this->hosts = [] !== $hosts '('.implode('|'$hosts).')' null;
  41.         $this->redirectStatusCode $redirectStatusCode;
  42.     }
  43.     public function onKernelRequest(RequestEvent $e): void
  44.     {
  45.         if (!$this->isMainRequest($e)) {
  46.             return;
  47.         }
  48.         $request $e->getRequest();
  49.         // skip SSL & non-GET/HEAD requests
  50.         if ($request->isSecure() || !$request->isMethodSafe()) {
  51.             return;
  52.         }
  53.         // skip allowed URLs
  54.         if (null !== $this->allowList && === preg_match('{'.$this->allowList.'}i''' === $request->getPathInfo() ? '/' $request->getPathInfo())) {
  55.             return;
  56.         }
  57.         // skip non-listed hosts
  58.         if (null !== $this->hosts && !== preg_match('{'.$this->hosts.'}i''' === $request->getHost() ? '/' $request->getHost())) {
  59.             return;
  60.         }
  61.         // redirect the rest to SSL
  62.         $e->setResponse(new RedirectResponse('https://'.substr($request->getUri(), 7), $this->redirectStatusCode));
  63.     }
  64.     public function onKernelResponse(ResponseEvent $e): void
  65.     {
  66.         if (!$this->isMainRequest($e)) {
  67.             return;
  68.         }
  69.         // skip non-SSL requests as per the RFC
  70.         // "An HSTS Host MUST NOT include the STS header field in HTTP responses conveyed over non-secure transport."
  71.         $request $e->getRequest();
  72.         if (!$request->isSecure()) {
  73.             return;
  74.         }
  75.         $response $e->getResponse();
  76.         if (!$response->headers->has('Strict-Transport-Security')) {
  77.             $header 'max-age='.$this->hstsMaxAge;
  78.             $header .= ($this->hstsSubdomains '; includeSubDomains' '');
  79.             $header .= ($this->hstsPreload '; preload' '');
  80.             $response->headers->set('Strict-Transport-Security'$header);
  81.         }
  82.     }
  83. }