Skip to content
Snippets Groups Projects
Commit 84879a82 authored by Alex Pott's avatar Alex Pott
Browse files

Issue #2237231 by clemens.tolboom, kim.pepper, dawehner, Wim Leers, Crell: Support OPTIONS request

parent a120fedf
Branches
Tags
2 merge requests!7452Issue #1797438. HTML5 validation is preventing form submit and not fully...,!789Issue #3210310: Adjust Database API to remove deprecated Drupal 9 code in Drupal 10
......@@ -1022,6 +1022,11 @@ services:
tags:
- { name: event_subscriber }
arguments: ['@router', '@request_stack', '@router.request_context', NULL]
options_request_listener:
class: Drupal\Core\EventSubscriber\OptionsRequestSubscriber
arguments: ['@router.route_provider']
tags:
- { name: event_subscriber }
bare_html_page_renderer:
class: Drupal\Core\Render\BareHtmlPageRenderer
arguments: ['@renderer', '@html_response.attachments_processor']
......
<?php
/**
* @file
* Contains \Drupal\Core\EventSubscriber\OptionsRequestSubscriber.
*/
namespace Drupal\Core\EventSubscriber;
use Symfony\Cmf\Component\Routing\RouteProviderInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\Routing\Route;
/**
* Handles options requests.
*
* Therefore it sends a options response using all methods on all possible
* routes.
*/
class OptionsRequestSubscriber implements EventSubscriberInterface {
/**
* The route provider.
*
* @var \Symfony\Cmf\Component\Routing\RouteProviderInterface
*/
protected $routeProvider;
/**
* Creates a new OptionsRequestSubscriber instance.
*
* @param \Symfony\Cmf\Component\Routing\RouteProviderInterface $route_provider
* The route provider.
*/
public function __construct(RouteProviderInterface $route_provider) {
$this->routeProvider = $route_provider;
}
/**
* Tries to handle the options request.
*
* @param \Symfony\Component\HttpKernel\Event\GetResponseEvent $event
* The request event.
*/
public function onRequest(GetResponseEvent $event) {
if ($event->getRequest()->isMethod('OPTIONS')) {
$routes = $this->routeProvider->getRouteCollectionForRequest($event->getRequest());
// In case we don't have any routes, a 403 should be thrown by the normal
// request handling.
if (count($routes) > 0) {
$methods = array_map(function (Route $route) {
return $route->getMethods();
}, $routes->all());
// Flatten and unique the available methods.
$methods = array_unique(call_user_func_array('array_merge', $methods));
$response = new Response('', 200, ['Allow' => implode(', ', $methods)]);
$event->setResponse($response);
}
}
}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents() {
// Set a high priority so it is executed before routing.
$events[KernelEvents::REQUEST][] = ['onRequest', 1000];
return $events;
}
}
<?php
/**
* @file
* Contains \Drupal\Tests\Core\EventSubscriber\OptionsRequestSubscriberTest.
*/
namespace Drupal\Tests\Core\EventSubscriber;
use Drupal\Core\EventSubscriber\OptionsRequestSubscriber;
use Symfony\Cmf\Component\Routing\RouteProviderInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\HttpKernelInterface;
use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\RouteCollection;
/**
* @coversDefaultClass \Drupal\Core\EventSubscriber\OptionsRequestSubscriber
* @group EventSubscriber
*/
class OptionsRequestSubscriberTest extends \PHPUnit_Framework_TestCase {
/**
* @covers ::onRequest
*/
public function testWithNonOptionRequest() {
$kernel = $this->prophesize(HttpKernelInterface::class);
$request = Request::create('/example', 'GET');
$route_provider = $this->prophesize(RouteProviderInterface::class);
$route_provider->getRouteCollectionForRequest($request)->shouldNotBeCalled();
$subscriber = new OptionsRequestSubscriber($route_provider->reveal());
$event = new GetResponseEvent($kernel->reveal(), $request, HttpKernelInterface::MASTER_REQUEST);
$subscriber->onRequest($event);
$this->assertFalse($event->hasResponse());
}
/**
* @covers ::onRequest
*/
public function testWithoutMatchingRoutes() {
$kernel = $this->prophesize(HttpKernelInterface::class);
$request = Request::create('/example', 'OPTIONS');
$route_provider = $this->prophesize(RouteProviderInterface::class);
$route_provider->getRouteCollectionForRequest($request)->willReturn(new RouteCollection())->shouldBeCalled();
$subscriber = new OptionsRequestSubscriber($route_provider->reveal());
$event = new GetResponseEvent($kernel->reveal(), $request, HttpKernelInterface::MASTER_REQUEST);
$subscriber->onRequest($event);
$this->assertFalse($event->hasResponse());
}
/**
* @covers ::onRequest
* @dataProvider providerTestOnRequestWithOptionsRequest
*/
public function testWithOptionsRequest(RouteCollection $collection, $expected_header) {
$kernel = $this->prophesize(HttpKernelInterface::class);
$request = Request::create('/example', 'OPTIONS');
$route_provider = $this->prophesize(RouteProviderInterface::class);
$route_provider->getRouteCollectionForRequest($request)->willReturn($collection)->shouldBeCalled();
$subscriber = new OptionsRequestSubscriber($route_provider->reveal());
$event = new GetResponseEvent($kernel->reveal(), $request, HttpKernelInterface::MASTER_REQUEST);
$subscriber->onRequest($event);
$this->assertTrue($event->hasResponse());
$response = $event->getResponse();
$this->assertEquals(200, $response->getStatusCode());
$this->assertEquals($expected_header, $response->headers->get('Allow'));
}
public function providerTestOnRequestWithOptionsRequest() {
$data = [];
foreach (['GET', 'POST', 'PATCH', 'PUT', 'DELETE'] as $method) {
$collection = new RouteCollection();
$collection->add('example.1', new Route('/example', [], [], [], '', [], [$method]));
$data['one_route_' . $method] = [$collection, $method];
}
foreach (['GET', 'POST', 'PATCH', 'PUT', 'DELETE'] as $method_a) {
foreach (['GET', 'POST', 'PATCH', 'PUT', 'DELETE'] as $method_b) {
if ($method_a != $method_b) {
$collection = new RouteCollection();
$collection->add('example.1', new Route('/example', [], [], [], '', [], [$method_a, $method_b]));
$data['one_route_' . $method_a . '_' . $method_b] = [$collection, $method_a . ', ' . $method_b];
}
}
}
foreach (['GET', 'POST', 'PATCH', 'PUT', 'DELETE'] as $method_a) {
foreach (['GET', 'POST', 'PATCH', 'PUT', 'DELETE'] as $method_b) {
foreach (['GET', 'POST', 'PATCH', 'PUT', 'DELETE'] as $method_c) {
$collection = new RouteCollection();
$collection->add('example.1', new Route('/example', [], [], [], '', [], [$method_a]));
$collection->add('example.2', new Route('/example', [], [], [], '', [], [$method_a, $method_b]));
$collection->add('example.3', new Route('/example', [], [], [], '', [], [$method_b, $method_c]));
$methods = array_unique([$method_a, $method_b, $method_c]);
$data['multiple_routes_' . $method_a . '_' . $method_b . '_' . $method_c] = [$collection, implode(', ', $methods)];
}
}
}
return $data;
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment