From ad50c7581076bfae882e277a9c9ffa454190b586 Mon Sep 17 00:00:00 2001 From: jurakin Date: Thu, 30 May 2024 20:12:06 +0200 Subject: [PATCH 1/2] preflight requests --- README.md | 24 ++++++++++++++++++++ src/Pecee/SimpleRouter/Router.php | 9 ++++++-- tests/Pecee/SimpleRouter/RouterRouteTest.php | 16 +++++++++++++ 3 files changed, 47 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 3dbbb21a..e29ca980 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,7 @@ You can donate any amount of your choice by [clicking here](https://www.paypal.c - [Required parameters](#required-parameters) - [Optional parameters](#optional-parameters) - [Including slash in parameters](#including-slash-in-parameters) + - [Handling CORS Preflight Requests](#handling-cors-preflight-requests) - [Regular expression constraints](#regular-expression-constraints) - [Regular expression route-match](#regular-expression-route-match) - [Custom regex for matching parameters](#custom-regex-for-matching-parameters) @@ -513,6 +514,29 @@ SimpleRouter::get('/path/{fileOrFolder}', function ($fileOrFolder) { - Requesting `/path/file` will return the `$fileOrFolder` value: `file`. - Requesting `/path/folder/` will return the `$fileOrFolder` value: `folder/`. +### Handling CORS Preflight Requests + +You may enable handling of CORS preflight requests for your routes using the `preflight` setting. This ensures that the router will respond to `OPTIONS` requests with a status code 200 and no content, allowing cross-origin requests to proceed. + +**Example** + +```php +// single route +SimpleRouter::form('foo', function () { + // ... +})->setSettings(['preflight' => true]); + +// group routes +SimpleRouter::group(['preflight' => true], function () { + SimpleRouter::form('foo', function() { + // ... + }); +}); +``` + +- Requesting `OPTIONS /foo` will return `HTTP/1.1 200 OK`. +- Requesting `POST /foo` will proceed normally. + ### Regular expression constraints You may constrain the format of your route parameters using the where method on a route instance. The where method accepts the name of the parameter and a regular expression defining how the parameter should be constrained: diff --git a/src/Pecee/SimpleRouter/Router.php b/src/Pecee/SimpleRouter/Router.php index 3e1964a3..ab3fb900 100644 --- a/src/Pecee/SimpleRouter/Router.php +++ b/src/Pecee/SimpleRouter/Router.php @@ -392,8 +392,9 @@ public function routeRequest(): ?string 'route' => $route, ]); - /* Check if request method matches */ - if (count($route->getRequestMethods()) !== 0 && in_array($this->request->getMethod(), $route->getRequestMethods(), true) === false) { + /* Check if request method does not match */ + if ((count($route->getRequestMethods()) !== 0 && in_array($this->request->getMethod(), $route->getRequestMethods(), true) === false) && + !($route->getPreflightRequestsEnabled() && $this->request->getMethod() === Request::REQUEST_TYPE_OPTIONS)) { $this->debug('Method "%s" not allowed', $this->request->getMethod()); // Only set method not allowed is not already set @@ -424,6 +425,10 @@ public function routeRequest(): ?string 'route' => $route, ]); + if ($route->getPreflightRequestsEnabled() && $this->request->getMethod() === Request::REQUEST_TYPE_OPTIONS) { + return ''; + } + $routeOutput = $route->renderRoute($this->request, $this); if ($this->renderMultipleRoutes === true) { diff --git a/tests/Pecee/SimpleRouter/RouterRouteTest.php b/tests/Pecee/SimpleRouter/RouterRouteTest.php index 18ab4d95..14837eaf 100644 --- a/tests/Pecee/SimpleRouter/RouterRouteTest.php +++ b/tests/Pecee/SimpleRouter/RouterRouteTest.php @@ -81,6 +81,14 @@ public function testDelete() $this->assertTrue(true); } + public function testOptions() + { + TestRouter::options('/my/test/url', 'DummyController@method1'); + TestRouter::debug('/my/test/url', 'options'); + + $this->assertTrue(true); + } + public function testMethodNotAllowed() { TestRouter::get('/my/test/url', 'DummyController@method1'); @@ -91,6 +99,14 @@ public function testMethodNotAllowed() $this->assertEquals(403, $e->getCode()); } } + + public function testPreflight() + { + TestRouter::get('/my/test/url', 'DummyController@method1', ['preflight' => true]); + TestRouter::debug('/my/test/url', 'options'); + + $this->assertTrue(true); + } public function testSimpleParam() { From 0fc7a90a0ab4caf858064f2cb51224b65ee437b7 Mon Sep 17 00:00:00 2001 From: jurakin Date: Sun, 2 Jun 2024 12:24:43 +0200 Subject: [PATCH 2/2] forgot to add --- src/Pecee/SimpleRouter/Route/Route.php | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/Pecee/SimpleRouter/Route/Route.php b/src/Pecee/SimpleRouter/Route/Route.php index 3ddf03f4..dfdb644c 100644 --- a/src/Pecee/SimpleRouter/Route/Route.php +++ b/src/Pecee/SimpleRouter/Route/Route.php @@ -26,6 +26,13 @@ abstract class Route implements IRoute */ protected bool $slashParameterEnabled = false; + /** + * Enables handling of CORS preflight requests. + * When true, the router responds to OPTIONS requests with status 200 and no content. Otherwise, it proceeds normally. + * @var bool + */ + protected bool $preflightRequestsEnabled = false; + /** * Default regular expression used for parsing parameters. * @var string|null @@ -414,6 +421,17 @@ public function getSlashParameterEnabled(): bool return $this->slashParameterEnabled; } + public function setPreflightRequestsEnabled(bool $enabled): self + { + $this->preflightRequestsEnabled = $enabled; + return $this; + } + + public function getPreflightRequestsEnabled(): bool + { + return $this->preflightRequestsEnabled; + } + /** * Export route settings to array so they can be merged with another route. * @@ -447,6 +465,10 @@ public function toArray(): array $values['includeSlash'] = $this->slashParameterEnabled; } + if ($this->preflightRequestsEnabled === true) { + $values['preflight'] = $this->preflightRequestsEnabled; + } + return $values; } @@ -488,6 +510,10 @@ public function setSettings(array $settings, bool $merge = false): IRoute $this->setSlashParameterEnabled($settings['includeSlash']); } + if (isset($settings['preflight']) === true) { + $this->setPreflightRequestsEnabled($settings['preflight']); + } + return $this; }