vendor/nelmio/cors-bundle/EventListener/CorsListener.php line 85

Open in your IDE?
  1. <?php
  2. /*
  3.  * This file is part of the NelmioCorsBundle.
  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\CorsBundle\EventListener;
  11. use Nelmio\CorsBundle\Options\ResolverInterface;
  12. use Symfony\Component\HttpFoundation\Request;
  13. use Symfony\Component\HttpFoundation\Response;
  14. use Symfony\Component\HttpKernel\Event\RequestEvent;
  15. use Symfony\Component\HttpKernel\Event\ResponseEvent;
  16. use Symfony\Component\HttpKernel\HttpKernelInterface;
  17. /**
  18.  * Adds CORS headers and handles pre-flight requests
  19.  *
  20.  * @author Jordi Boggiano <j.boggiano@seld.be>
  21.  */
  22. class CorsListener
  23. {
  24.     const SHOULD_ALLOW_ORIGIN_ATTR '_nelmio_cors_should_allow_origin';
  25.     const SHOULD_FORCE_ORIGIN_ATTR '_nelmio_cors_should_force_origin';
  26.     /**
  27.      * Simple headers as defined in the spec should always be accepted
  28.      */
  29.     protected static $simpleHeaders = [
  30.         'accept',
  31.         'accept-language',
  32.         'content-language',
  33.         'origin',
  34.     ];
  35.     /** @var ResolverInterface */
  36.     protected $configurationResolver;
  37.     public function __construct(ResolverInterface $configurationResolver)
  38.     {
  39.         $this->configurationResolver $configurationResolver;
  40.     }
  41.     public function onKernelRequest(RequestEvent $event): void
  42.     {
  43.         if (HttpKernelInterface::MASTER_REQUEST !== $event->getRequestType()) {
  44.             return;
  45.         }
  46.         $request $event->getRequest();
  47.         if (!$options $this->configurationResolver->getOptions($request)) {
  48.             return;
  49.         }
  50.         // if the "forced_allow_origin_value" option is set, add a listener which will set or override the "Access-Control-Allow-Origin" header
  51.         if (!empty($options['forced_allow_origin_value'])) {
  52.             $request->attributes->set(self::SHOULD_FORCE_ORIGIN_ATTRtrue);
  53.         }
  54.         // skip if not a CORS request
  55.         if (!$request->headers->has('Origin') || $request->headers->get('Origin') === $request->getSchemeAndHttpHost()) {
  56.             return;
  57.         }
  58.         // perform preflight checks
  59.         if ('OPTIONS' === $request->getMethod() && $request->headers->has('Access-Control-Request-Method')) {
  60.             $event->setResponse($this->getPreflightResponse($request$options));
  61.             return;
  62.         }
  63.         if (!$this->checkOrigin($request$options)) {
  64.             return;
  65.         }
  66.         $request->attributes->set(self::SHOULD_ALLOW_ORIGIN_ATTRtrue);
  67.     }
  68.     public function onKernelResponse(ResponseEvent $event): void
  69.     {
  70.         if (HttpKernelInterface::MASTER_REQUEST !== $event->getRequestType()) {
  71.             return;
  72.         }
  73.         $request $event->getRequest();
  74.         $shouldAllowOrigin $request->attributes->getBoolean(self::SHOULD_ALLOW_ORIGIN_ATTR);
  75.         $shouldForceOrigin $request->attributes->getBoolean(self::SHOULD_FORCE_ORIGIN_ATTR);
  76.         if (!$shouldAllowOrigin && !$shouldForceOrigin) {
  77.             return;
  78.         }
  79.         if (!$options $this->configurationResolver->getOptions($request)) {
  80.             return;
  81.         }
  82.         if ($shouldAllowOrigin) {
  83.             $response $event->getResponse();
  84.             // add CORS response headers
  85.             $response->headers->set('Access-Control-Allow-Origin'$request->headers->get('Origin'));
  86.             if ($options['allow_credentials']) {
  87.                 $response->headers->set('Access-Control-Allow-Credentials''true');
  88.             }
  89.             if ($options['expose_headers']) {
  90.                 $response->headers->set('Access-Control-Expose-Headers'strtolower(implode(', '$options['expose_headers'])));
  91.             }
  92.         }
  93.         if ($shouldForceOrigin) {
  94.             $event->getResponse()->headers->set('Access-Control-Allow-Origin'$options['forced_allow_origin_value']);
  95.         }
  96.     }
  97.     protected function getPreflightResponse(Request $request, array $options): Response
  98.     {
  99.         $response = new Response();
  100.         $response->setVary(['Origin']);
  101.         if ($options['allow_credentials']) {
  102.             $response->headers->set('Access-Control-Allow-Credentials''true');
  103.         }
  104.         if ($options['allow_methods']) {
  105.             $response->headers->set('Access-Control-Allow-Methods'implode(', '$options['allow_methods']));
  106.         }
  107.         if ($options['allow_headers']) {
  108.             $headers $this->isWildcard($options'allow_headers')
  109.                 ? $request->headers->get('Access-Control-Request-Headers')
  110.                 : implode(', '$options['allow_headers']);
  111.             if ($headers) {
  112.                 $response->headers->set('Access-Control-Allow-Headers'$headers);
  113.             }
  114.         }
  115.         if ($options['max_age']) {
  116.             $response->headers->set('Access-Control-Max-Age'$options['max_age']);
  117.         }
  118.         if (!$this->checkOrigin($request$options)) {
  119.             $response->headers->remove('Access-Control-Allow-Origin');
  120.             return $response;
  121.         }
  122.         $response->headers->set('Access-Control-Allow-Origin'$request->headers->get('Origin'));
  123.         // check request method
  124.         if (!in_array(strtoupper($request->headers->get('Access-Control-Request-Method')), $options['allow_methods'], true)) {
  125.             $response->setStatusCode(405);
  126.             return $response;
  127.         }
  128.         /**
  129.          * We have to allow the header in the case-set as we received it by the client.
  130.          * Firefox f.e. sends the LINK method as "Link", and we have to allow it like this or the browser will deny the
  131.          * request.
  132.          */
  133.         if (!in_array($request->headers->get('Access-Control-Request-Method'), $options['allow_methods'], true)) {
  134.             $options['allow_methods'][] = $request->headers->get('Access-Control-Request-Method');
  135.             $response->headers->set('Access-Control-Allow-Methods'implode(', '$options['allow_methods']));
  136.         }
  137.         // check request headers
  138.         $headers $request->headers->get('Access-Control-Request-Headers');
  139.         if ($headers && !$this->isWildcard($options'allow_headers')) {
  140.             $headers strtolower(trim($headers));
  141.             foreach (preg_split('{, *}'$headers) as $header) {
  142.                 if (in_array($headerself::$simpleHeaderstrue)) {
  143.                     continue;
  144.                 }
  145.                 if (!in_array($header$options['allow_headers'], true)) {
  146.                     $sanitizedMessage htmlentities('Unauthorized header '.$headerENT_QUOTES'UTF-8');
  147.                     $response->setStatusCode(400);
  148.                     $response->setContent($sanitizedMessage);
  149.                     break;
  150.                 }
  151.             }
  152.         }
  153.         return $response;
  154.     }
  155.     protected function checkOrigin(Request $request, array $options): bool
  156.     {
  157.         // check origin
  158.         $origin $request->headers->get('Origin');
  159.         if ($this->isWildcard($options'allow_origin')) {
  160.             return true;
  161.         }
  162.         if ($options['origin_regex'] === true) {
  163.             // origin regex matching
  164.             foreach ($options['allow_origin'] as $originRegexp) {
  165.                 if (preg_match('{'.$originRegexp.'}i'$origin)) {
  166.                     return true;
  167.                 }
  168.             }
  169.         } else {
  170.             // old origin matching
  171.             if (in_array($origin$options['allow_origin'])) {
  172.                 return true;
  173.             }
  174.         }
  175.         return false;
  176.     }
  177.     private function isWildcard(array $optionsstring $option): bool
  178.     {
  179.         return $options[$option] === true || (is_array($options[$option]) && in_array('*'$options[$option]));
  180.     }
  181. }