vendor/can/rest/src/Can/RestBundle/EventListener/PreFlightRequestListener.php line 50

Open in your IDE?
  1. <?php
  2. namespace Can\RestBundle\EventListener;
  3. use Can\RestBundle\CanRestBundle;
  4. use Can\RestBundle\Cors\CorsConfiguration;
  5. use Can\RestBundle\Cors\CorsConfigurationFactory;
  6. use Can\RestBundle\Cors\CorsHeader;
  7. use Can\RestBundle\Cors\CorsUtil;
  8. use Can\RestBundle\Cors\Matcher\ChainMatcher;
  9. use Can\RestBundle\Cors\OriginMatcherInterface;
  10. use Symfony\Component\EventDispatcher\EventDispatcherInterface;
  11. use Symfony\Component\HttpFoundation\Request;
  12. use Symfony\Component\HttpFoundation\Response;
  13. use Symfony\Component\HttpKernel\Event\RequestEvent;
  14. /**
  15.  * Handles pre-flighted requests.
  16.  *
  17.  * @group cors
  18.  *
  19.  * @package can/rest-bundle
  20.  * @author lechecacharro <lechecacharro@gmail.com>
  21.  */
  22. class PreFlightRequestListener extends CorsListener
  23. {
  24.     /**
  25.      * @var EventDispatcherInterface
  26.      */
  27.     private $dispatcher;
  28.     /**
  29.      * PreFlightRequestListener constructor.
  30.      *
  31.      * @param CorsConfigurationFactory       $factory
  32.      * @param EventDispatcherInterface $dispatcher
  33.      */
  34.     public function __construct(CorsConfigurationFactory $factoryEventDispatcherInterface $dispatcher)
  35.     {
  36.         parent::__construct($factory);
  37.         $this->dispatcher $dispatcher;
  38.     }
  39.     /**
  40.      * @param RequestEvent $event
  41.      */
  42.     public function onRequest(RequestEvent $event): void
  43.     {
  44.         if (! $event->isMasterRequest()) {
  45.             return;
  46.         }
  47.         $request $event->getRequest();
  48.         $configuration $this->getConfiguration($request);
  49.         if (! $configuration->isEnabled()) {
  50.             return;
  51.         }
  52.         // Skip if not a CORS request -- this includes requests not
  53.         // specifying an "Origin" header
  54.         if (! CorsUtil::isCrossOrigin($request)) {
  55.             return;
  56.         }
  57.         $request->attributes->set(CanRestBundle::ATTR_CORS_ALLOWEDtrue);
  58.         $request->attributes->set(CanRestBundle::ATTR_CORS_XORIGINtrue);
  59.         // If the "force_allow_origin" option is set, then add a listener
  60.         // which will set or override the "Access-Control-Allow-Origin" header
  61.         if (! empty($configuration->getForceAllowOrigin())) {
  62.             $this->dispatcher->addListener('kernel.response', [CorsForceAllowOriginListener::class, 'onResponse'], -1);
  63.         }
  64.         // Pre-flight checks
  65.         if (CorsUtil::isPreFlight($request)) {
  66.             $preflightResponse $this->getPreFlightResponse($request$configuration);
  67.             $event->setResponse($preflightResponse);
  68.             return;
  69.         }
  70.         if (! $this->isAllowedOrigin($request$configuration)) {
  71.             $request->attributes->set(CanRestBundle::ATTR_CORS_ALLOWEDfalse);
  72.             return;
  73.         }
  74.     }
  75.     /**
  76.      * @param CorsConfiguration $configuration
  77.      *
  78.      * @return OriginMatcherInterface
  79.      */
  80.     private function createOriginMatcher(CorsConfiguration $configuration): OriginMatcherInterface
  81.     {
  82.         return ChainMatcher::create($configuration->getAllowOrigins());
  83.     }
  84.     /**
  85.      * @param Request     $request
  86.      * @param CorsConfiguration $configuration
  87.      *
  88.      * @return bool
  89.      */
  90.     private function isAllowedOrigin(Request $requestCorsConfiguration $configuration): bool
  91.     {
  92.         $origin $request->headers->get('Origin');
  93.         return $this->createOriginMatcher($configuration)->matches($origin);
  94.     }
  95.     /**
  96.      * @param Request     $request
  97.      * @param CorsConfiguration $configuration
  98.      *
  99.      * @return Response
  100.      */
  101.     private function getPreFlightResponse(Request $requestCorsConfiguration $configuration): Response
  102.     {
  103.         $response = new Response();
  104.         if ($configuration->isAllowCredentials()) {
  105.             $response->headers->set(CorsHeader::ACCESS_CONTROL_ALLOW_CREDENTIALS'true');
  106.         }
  107.         if ($configuration->getAllowMethods()) {
  108.             $response->headers->set(CorsHeader::ACCESS_CONTROL_ALLOW_METHODSimplode(', '$configuration->getAllowMethods()));
  109.         }
  110.         if (count($configuration->getAllowHeaders())) {
  111.             if ($configuration->isAllHeadersAllowed()) {
  112.                 $headers $request->headers->get(CorsHeader::ACCESS_CONTROL_REQUEST_HEADERS);
  113.             } else {
  114.                 $headers implode(', '$configuration->getAllowHeaders());
  115.             }
  116.             if ($headers) {
  117.                 $response->headers->set(CorsHeader::ACCESS_CONTROL_ALLOW_HEADERS$headers);
  118.             }
  119.         }
  120.         if ($configuration->getMaxAge()) {
  121.             $response->headers->set(CorsHeader::ACCESS_CONTROL_MAX_AGE$configuration->getMaxAge());
  122.         }
  123.         if (! $this->isAllowedOrigin($request$configuration)) {
  124.             $response->headers->set(CorsHeader::ACCESS_CONTROL_ALLOW_ORIGIN'null');
  125.             return $response;
  126.         }
  127.         $response->headers->set(CorsHeader::ACCESS_CONTROL_ALLOW_ORIGIN$request->headers->get('Origin'));
  128.         $method $request->headers->get(CorsHeader::ACCESS_CONTROL_REQUEST_METHOD);
  129.         // If the method is not allowed, then use the HTTP response code
  130.         // 405 ("Method Not Allowed")
  131.         if (! $configuration->isMethodAllowed($method)) {
  132.             $response->setStatusCode(405);
  133.             return $response;
  134.         }
  135.         // We have to allow the header in the case-set as we received it by
  136.         // the client. Firefox e.g. sends the LINK method as "Link", and we
  137.         // have to allow it like this or the browser will deny the request
  138.         if (! in_array($method$configuration->getAllowMethods(), true)) {
  139.             $configuration->allowMethod($method);
  140.             $response->headers->set(CorsHeader::ACCESS_CONTROL_ALLOW_METHODSimplode(', '$configuration->getAllowMethods()));
  141.         }
  142.         // Check request headers
  143.         $headers $request->headers->get(CorsHeader::ACCESS_CONTROL_REQUEST_HEADERS);
  144.         if ($configuration->isAllHeadersAllowed() && $headers) {
  145.             $headers trim(strtolower($headers));
  146.             foreach (preg_split('/, */'$headers) as $header) {
  147.                 if (CorsUtil::isSimpleRequestHeader($header)) {
  148.                     continue;
  149.                 }
  150.                 // If the header is not allowed, then use the HTTP response
  151.                 // code 400 ("Bad Request")
  152.                 if (! $configuration->isHeaderAllowed($header)) {
  153.                     $response->setStatusCode(400);
  154.                     $response->setContent('Unauthorized header '$header);
  155.                     break;
  156.                 }
  157.             }
  158.         }
  159.         return $response;
  160.     }
  161. }