vendor/nelmio/cors-bundle/EventListener/CorsListener.php line 85
<?php/** This file is part of the NelmioCorsBundle.** (c) Nelmio <hello@nelm.io>** For the full copyright and license information, please view the LICENSE* file that was distributed with this source code.*/namespace Nelmio\CorsBundle\EventListener;use Nelmio\CorsBundle\Options\ResolverInterface;use Symfony\Component\HttpFoundation\Request;use Symfony\Component\HttpFoundation\Response;use Symfony\Component\HttpKernel\Event\RequestEvent;use Symfony\Component\HttpKernel\Event\ResponseEvent;use Symfony\Component\HttpKernel\HttpKernelInterface;/*** Adds CORS headers and handles pre-flight requests** @author Jordi Boggiano <j.boggiano@seld.be>*/class CorsListener{const SHOULD_ALLOW_ORIGIN_ATTR = '_nelmio_cors_should_allow_origin';const SHOULD_FORCE_ORIGIN_ATTR = '_nelmio_cors_should_force_origin';/*** Simple headers as defined in the spec should always be accepted*/protected static $simpleHeaders = ['accept','accept-language','content-language','origin',];/** @var ResolverInterface */protected $configurationResolver;public function __construct(ResolverInterface $configurationResolver){$this->configurationResolver = $configurationResolver;}public function onKernelRequest(RequestEvent $event): void{if (HttpKernelInterface::MASTER_REQUEST !== $event->getRequestType()) {return;}$request = $event->getRequest();if (!$options = $this->configurationResolver->getOptions($request)) {return;}// if the "forced_allow_origin_value" option is set, add a listener which will set or override the "Access-Control-Allow-Origin" headerif (!empty($options['forced_allow_origin_value'])) {$request->attributes->set(self::SHOULD_FORCE_ORIGIN_ATTR, true);}// skip if not a CORS requestif (!$request->headers->has('Origin') || $request->headers->get('Origin') === $request->getSchemeAndHttpHost()) {return;}// perform preflight checksif ('OPTIONS' === $request->getMethod() && $request->headers->has('Access-Control-Request-Method')) {$event->setResponse($this->getPreflightResponse($request, $options));return;}if (!$this->checkOrigin($request, $options)) {return;}$request->attributes->set(self::SHOULD_ALLOW_ORIGIN_ATTR, true);}public function onKernelResponse(ResponseEvent $event): void{if (HttpKernelInterface::MASTER_REQUEST !== $event->getRequestType()) {return;}$request = $event->getRequest();$shouldAllowOrigin = $request->attributes->getBoolean(self::SHOULD_ALLOW_ORIGIN_ATTR);$shouldForceOrigin = $request->attributes->getBoolean(self::SHOULD_FORCE_ORIGIN_ATTR);if (!$shouldAllowOrigin && !$shouldForceOrigin) {return;}if (!$options = $this->configurationResolver->getOptions($request)) {return;}if ($shouldAllowOrigin) {$response = $event->getResponse();// add CORS response headers$response->headers->set('Access-Control-Allow-Origin', $request->headers->get('Origin'));if ($options['allow_credentials']) {$response->headers->set('Access-Control-Allow-Credentials', 'true');}if ($options['expose_headers']) {$response->headers->set('Access-Control-Expose-Headers', strtolower(implode(', ', $options['expose_headers'])));}}if ($shouldForceOrigin) {$event->getResponse()->headers->set('Access-Control-Allow-Origin', $options['forced_allow_origin_value']);}}protected function getPreflightResponse(Request $request, array $options): Response{$response = new Response();$response->setVary(['Origin']);if ($options['allow_credentials']) {$response->headers->set('Access-Control-Allow-Credentials', 'true');}if ($options['allow_methods']) {$response->headers->set('Access-Control-Allow-Methods', implode(', ', $options['allow_methods']));}if ($options['allow_headers']) {$headers = $this->isWildcard($options, 'allow_headers')? $request->headers->get('Access-Control-Request-Headers'): implode(', ', $options['allow_headers']);if ($headers) {$response->headers->set('Access-Control-Allow-Headers', $headers);}}if ($options['max_age']) {$response->headers->set('Access-Control-Max-Age', $options['max_age']);}if (!$this->checkOrigin($request, $options)) {$response->headers->remove('Access-Control-Allow-Origin');return $response;}$response->headers->set('Access-Control-Allow-Origin', $request->headers->get('Origin'));// check request methodif (!in_array(strtoupper($request->headers->get('Access-Control-Request-Method')), $options['allow_methods'], true)) {$response->setStatusCode(405);return $response;}/*** We have to allow the header in the case-set as we received it by the client.* Firefox f.e. sends the LINK method as "Link", and we have to allow it like this or the browser will deny the* request.*/if (!in_array($request->headers->get('Access-Control-Request-Method'), $options['allow_methods'], true)) {$options['allow_methods'][] = $request->headers->get('Access-Control-Request-Method');$response->headers->set('Access-Control-Allow-Methods', implode(', ', $options['allow_methods']));}// check request headers$headers = $request->headers->get('Access-Control-Request-Headers');if ($headers && !$this->isWildcard($options, 'allow_headers')) {$headers = strtolower(trim($headers));foreach (preg_split('{, *}', $headers) as $header) {if (in_array($header, self::$simpleHeaders, true)) {continue;}if (!in_array($header, $options['allow_headers'], true)) {$sanitizedMessage = htmlentities('Unauthorized header '.$header, ENT_QUOTES, 'UTF-8');$response->setStatusCode(400);$response->setContent($sanitizedMessage);break;}}}return $response;}protected function checkOrigin(Request $request, array $options): bool{// check origin$origin = $request->headers->get('Origin');if ($this->isWildcard($options, 'allow_origin')) {return true;}if ($options['origin_regex'] === true) {// origin regex matchingforeach ($options['allow_origin'] as $originRegexp) {if (preg_match('{'.$originRegexp.'}i', $origin)) {return true;}}} else {// old origin matchingif (in_array($origin, $options['allow_origin'])) {return true;}}return false;}private function isWildcard(array $options, string $option): bool{return $options[$option] === true || (is_array($options[$option]) && in_array('*', $options[$option]));}}