Skip to content

Instantly share code, notes, and snippets.

@vatson
Created December 10, 2014 17:19
Show Gist options
  • Save vatson/4c34df06754157e4e57e to your computer and use it in GitHub Desktop.
Save vatson/4c34df06754157e4e57e to your computer and use it in GitHub Desktop.
<?php
/*
* This file is part of the NelmioCorsBundle.
*
* (c) Nelmio <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Nelmio\CorsBundle\EventListener;
use Symfony\Component\HttpKernel\HttpKernelInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Nelmio\CorsBundle\Options\ResolverInterface;
/**
* Adds CORS headers and handles pre-flight requests
*
* @author Jordi Boggiano <[email protected]>
*/
class CorsListener
{
/**
* Simple headers as defined in the spec should always be accepted
*/
protected static $simpleHeaders = array(
'accept',
'accept-language',
'content-language',
'origin',
);
protected $dispatcher;
protected $options;
/** @var ResolverInterface */
protected $configurationResolver;
public function __construct(EventDispatcherInterface $dispatcher, ResolverInterface $configurationResolver)
{
$this->dispatcher = $dispatcher;
$this->configurationResolver = $configurationResolver;
}
public function onKernelRequest(GetResponseEvent $event)
{
if (HttpKernelInterface::MASTER_REQUEST !== $event->getRequestType()) {
return;
}
$request = $event->getRequest();
// skip if not a CORS request
if (!$request->headers->has('Origin') || $request->headers->get('Origin') == $request->getSchemeAndHttpHost()) {
return;
}
$options = $this->configurationResolver->getOptions($request);
if (!$options) {
return;
}
// perform preflight checks
if ('OPTIONS' === $request->getMethod()) {
$event->setResponse($this->getPreflightResponse($request, $options));
return;
}
$this->dispatcher->addListener('kernel.response', array($this, 'onKernelResponse'));
$this->options = $options;
}
public function onKernelResponse(FilterResponseEvent $event)
{
if (HttpKernelInterface::MASTER_REQUEST !== $event->getRequestType()) {
return;
}
$response = $event->getResponse();
$request = $event->getRequest();
// add CORS response headers
//Only provide the Access-Control-Allow-Origin response header matching the client's request
//if the client's Origin has been white-listed
$options = $this->configurationResolver->getOptions($request);
if ( $this->checkOrigin($request, $options) ) {
$response->headers->set('Access-Control-Allow-Origin', $request->headers->get('Origin'));
}
if ($this->options['allow_credentials']) {
$response->headers->set('Access-Control-Allow-Credentials', 'true');
}
if ($this->options['expose_headers']) {
$response->headers->set('Access-Control-Expose-Headers', strtolower(implode(', ', $this->options['expose_headers'])));
}
}
protected function getPreflightResponse(Request $request, array $options)
{
$response = new Response();
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 = $options['allow_headers'] === true
? $request->headers->get('Access-Control-Request-Headers')
: implode(', ', $options['allow_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->set('Access-Control-Allow-Origin', 'null');
return $response;
}
$response->headers->set('Access-Control-Allow-Origin', $request->headers->get('Origin'));
// check request method
if (!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 ($options['allow_headers'] !== true && $headers) {
$headers = trim(strtolower($headers));
foreach (preg_split('{, *}', $headers) as $header) {
if (in_array($header, self::$simpleHeaders, true)) {
continue;
}
if (!in_array($header, $options['allow_headers'], true)) {
$response->setStatusCode(400);
$response->setContent('Unauthorized header '.$header);
break;
}
}
}
return $response;
}
protected function checkOrigin(Request $request, array $options)
{
// check origin
$origin = $request->headers->get('Origin');
if ($options['allow_origin'] === true || in_array($origin, $options['allow_origin'])) {
return true;
}
return false;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment