vendor/nelmio/security-bundle/EventListener/ForcedSslListener.php line 81

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