Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Add support for handling CORS preflight requests #714

Open
wants to merge 18 commits into
base: v5-development
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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:
Expand Down
26 changes: 26 additions & 0 deletions src/Pecee/SimpleRouter/Route/Route.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
*
Expand Down Expand Up @@ -447,6 +465,10 @@ public function toArray(): array
$values['includeSlash'] = $this->slashParameterEnabled;
}

if ($this->preflightRequestsEnabled === true) {
$values['preflight'] = $this->preflightRequestsEnabled;
}

return $values;
}

Expand Down Expand Up @@ -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;
}

Expand Down
9 changes: 7 additions & 2 deletions src/Pecee/SimpleRouter/Router.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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) {
Expand Down
16 changes: 16 additions & 0 deletions tests/Pecee/SimpleRouter/RouterRouteTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand All @@ -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()
{
Expand Down