Loading core/core.services.yml +5 −0 Original line number Diff line number Diff line Loading @@ -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'] Loading core/lib/Drupal/Core/EventSubscriber/OptionsRequestSubscriber.php 0 → 100644 +74 −0 Original line number Diff line number Diff line <?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; } } core/tests/Drupal/Tests/Core/EventSubscriber/OptionsRequestSubscriberTest.php 0 → 100644 +114 −0 Original line number Diff line number Diff line <?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; } } Loading
core/core.services.yml +5 −0 Original line number Diff line number Diff line Loading @@ -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'] Loading
core/lib/Drupal/Core/EventSubscriber/OptionsRequestSubscriber.php 0 → 100644 +74 −0 Original line number Diff line number Diff line <?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; } }
core/tests/Drupal/Tests/Core/EventSubscriber/OptionsRequestSubscriberTest.php 0 → 100644 +114 −0 Original line number Diff line number Diff line <?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; } }