diff --git a/README.md b/README.md index 3dbbb21..e29ca98 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/Route/Route.php b/src/Pecee/SimpleRouter/Route/Route.php index 3ddf03f..dfdb644 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; } diff --git a/src/Pecee/SimpleRouter/Router.php b/src/Pecee/SimpleRouter/Router.php index 3e1964a..ab3fb90 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 18ab4d9..14837ea 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() {