summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAlex Pott2016-03-10 11:14:17 +0000
committerAlex Pott2016-03-10 11:14:17 +0000
commit84879a82b4142c1d792fc26b609df7b072c42580 (patch)
treeb1a42d368b6178c49ca68163b7844d9abcf877a7
parenta120fedff984bc2ea7274eee04987cd0b37bea34 (diff)
Issue #2237231 by clemens.tolboom, kim.pepper, dawehner, Wim Leers, Crell: Support OPTIONS request
-rw-r--r--core/core.services.yml5
-rw-r--r--core/lib/Drupal/Core/EventSubscriber/OptionsRequestSubscriber.php74
-rw-r--r--core/tests/Drupal/Tests/Core/EventSubscriber/OptionsRequestSubscriberTest.php114
3 files changed, 193 insertions, 0 deletions
diff --git a/core/core.services.yml b/core/core.services.yml
index c5a13a4..62a0f3e 100644
--- a/core/core.services.yml
+++ b/core/core.services.yml
@@ -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']
diff --git a/core/lib/Drupal/Core/EventSubscriber/OptionsRequestSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/OptionsRequestSubscriber.php
new file mode 100644
index 0000000..17ef946
--- /dev/null
+++ b/core/lib/Drupal/Core/EventSubscriber/OptionsRequestSubscriber.php
@@ -0,0 +1,74 @@
+<?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;
+ }
+
+}
diff --git a/core/tests/Drupal/Tests/Core/EventSubscriber/OptionsRequestSubscriberTest.php b/core/tests/Drupal/Tests/Core/EventSubscriber/OptionsRequestSubscriberTest.php
new file mode 100644
index 0000000..7c0e454
--- /dev/null
+++ b/core/tests/Drupal/Tests/Core/EventSubscriber/OptionsRequestSubscriberTest.php
@@ -0,0 +1,114 @@
+<?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;
+ }
+
+}