Skip to content

Commit 62bcfa8

Browse files
authored
Merge pull request #8649 from kenjis/feat-cors
feat: add CORS filter
2 parents ee8e117 + 3a7e880 commit 62bcfa8

File tree

18 files changed

+1671
-1
lines changed

18 files changed

+1671
-1
lines changed

.github/workflows/test-phpcpd.yml

+1
Original file line numberDiff line numberDiff line change
@@ -58,4 +58,5 @@ jobs:
5858
--exclude system/HTTP/SiteURI.php
5959
--exclude system/Validation/Rules.php
6060
--exclude system/Autoloader/Autoloader.php
61+
--exclude system/Config/Filters.php
6162
-- app/ public/ system/

app/Config/Cors.php

+105
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
<?php
2+
3+
namespace Config;
4+
5+
use CodeIgniter\Config\BaseConfig;
6+
7+
/**
8+
* Cross-Origin Resource Sharing (CORS) Configuration
9+
*
10+
* @see https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS
11+
*/
12+
class Cors extends BaseConfig
13+
{
14+
/**
15+
* The default CORS configuration.
16+
*
17+
* @var array{
18+
* allowedOrigins: list<string>,
19+
* allowedOriginsPatterns: list<string>,
20+
* supportsCredentials: bool,
21+
* allowedHeaders: list<string>,
22+
* exposedHeaders: list<string>,
23+
* allowedMethods: list<string>,
24+
* maxAge: int,
25+
* }
26+
*/
27+
public array $default = [
28+
/**
29+
* Origins for the `Access-Control-Allow-Origin` header.
30+
*
31+
* @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin
32+
*
33+
* E.g.:
34+
* - ['http://localhost:8080']
35+
* - ['https://www.example.com']
36+
*/
37+
'allowedOrigins' => [],
38+
39+
/**
40+
* Origin regex patterns for the `Access-Control-Allow-Origin` header.
41+
*
42+
* @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin
43+
*
44+
* NOTE: A pattern specified here is part of a regular expression. It will
45+
* be actually `#\A<pattern>\z#`.
46+
*
47+
* E.g.:
48+
* - ['https://\w+\.example\.com']
49+
*/
50+
'allowedOriginsPatterns' => [],
51+
52+
/**
53+
* Weather to send the `Access-Control-Allow-Credentials` header.
54+
*
55+
* The Access-Control-Allow-Credentials response header tells browsers whether
56+
* the server allows cross-origin HTTP requests to include credentials.
57+
*
58+
* @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Credentials
59+
*/
60+
'supportsCredentials' => false,
61+
62+
/**
63+
* Set headers to allow.
64+
*
65+
* The Access-Control-Allow-Headers response header is used in response to
66+
* a preflight request which includes the Access-Control-Request-Headers to
67+
* indicate which HTTP headers can be used during the actual request.
68+
*
69+
* @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Headers
70+
*/
71+
'allowedHeaders' => [],
72+
73+
/**
74+
* Set headers to expose.
75+
*
76+
* The Access-Control-Expose-Headers response header allows a server to
77+
* indicate which response headers should be made available to scripts running
78+
* in the browser, in response to a cross-origin request.
79+
*
80+
* @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Expose-Headers
81+
*/
82+
'exposedHeaders' => [],
83+
84+
/**
85+
* Set methods to allow.
86+
*
87+
* The Access-Control-Allow-Methods response header specifies one or more
88+
* methods allowed when accessing a resource in response to a preflight
89+
* request.
90+
*
91+
* E.g.:
92+
* - ['GET', 'POST', 'PUT', 'DELETE']
93+
*
94+
* @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Methods
95+
*/
96+
'allowedMethods' => [],
97+
98+
/**
99+
* Set how many seconds the results of a preflight request can be cached.
100+
*
101+
* @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Max-Age
102+
*/
103+
'maxAge' => 7200,
104+
];
105+
}

app/Config/Filters.php

+2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace Config;
44

55
use CodeIgniter\Config\Filters as BaseFilters;
6+
use CodeIgniter\Filters\Cors;
67
use CodeIgniter\Filters\CSRF;
78
use CodeIgniter\Filters\DebugToolbar;
89
use CodeIgniter\Filters\ForceHTTPS;
@@ -29,6 +30,7 @@ class Filters extends BaseFilters
2930
'honeypot' => Honeypot::class,
3031
'invalidchars' => InvalidChars::class,
3132
'secureheaders' => SecureHeaders::class,
33+
'cors' => Cors::class,
3234
'forcehttps' => ForceHTTPS::class,
3335
'pagecache' => PageCache::class,
3436
'performance' => PerformanceMetrics::class,

system/CodeIgniter.php

+2
Original file line numberDiff line numberDiff line change
@@ -460,6 +460,7 @@ protected function handleRequest(?RouteCollectionInterface $routes, Cache $cache
460460
$uri = $this->request->getPath();
461461

462462
if ($this->enableFilters) {
463+
/** @var Filters $filters */
463464
$filters = service('filters');
464465

465466
// If any filters were specified within the routes file,
@@ -517,6 +518,7 @@ protected function handleRequest(?RouteCollectionInterface $routes, Cache $cache
517518
$this->gatherOutput($cacheConfig, $returned);
518519

519520
if ($this->enableFilters) {
521+
/** @var Filters $filters */
520522
$filters = service('filters');
521523
$filters->setResponse($this->response);
522524

system/Config/Filters.php

+3-1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
namespace CodeIgniter\Config;
1515

16+
use CodeIgniter\Filters\Cors;
1617
use CodeIgniter\Filters\CSRF;
1718
use CodeIgniter\Filters\DebugToolbar;
1819
use CodeIgniter\Filters\ForceHTTPS;
@@ -42,6 +43,7 @@ class Filters extends BaseConfig
4243
'honeypot' => Honeypot::class,
4344
'invalidchars' => InvalidChars::class,
4445
'secureheaders' => SecureHeaders::class,
46+
'cors' => Cors::class,
4547
'forcehttps' => ForceHTTPS::class,
4648
'pagecache' => PageCache::class,
4749
'performance' => PerformanceMetrics::class,
@@ -95,7 +97,7 @@ class Filters extends BaseConfig
9597
* particular HTTP method (GET, POST, etc.).
9698
*
9799
* Example:
98-
* 'post' => ['foo', 'bar']
100+
* 'POST' => ['foo', 'bar']
99101
*
100102
* If you use this, you should disable auto-routing because auto-routing
101103
* permits any HTTP method to access a controller. Accessing the controller

system/Filters/Cors.php

+111
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* This file is part of CodeIgniter 4 framework.
7+
*
8+
* (c) CodeIgniter Foundation <[email protected]>
9+
*
10+
* For the full copyright and license information, please view
11+
* the LICENSE file that was distributed with this source code.
12+
*/
13+
14+
namespace CodeIgniter\Filters;
15+
16+
use CodeIgniter\HTTP\Cors as CorsService;
17+
use CodeIgniter\HTTP\IncomingRequest;
18+
use CodeIgniter\HTTP\RequestInterface;
19+
use CodeIgniter\HTTP\ResponseInterface;
20+
21+
/**
22+
* @see \CodeIgniter\Filters\CorsTest
23+
*/
24+
class Cors implements FilterInterface
25+
{
26+
private ?CorsService $cors = null;
27+
28+
/**
29+
* @testTag $config is used for testing purposes only.
30+
*
31+
* @param array{
32+
* allowedOrigins?: list<string>,
33+
* allowedOriginsPatterns?: list<string>,
34+
* supportsCredentials?: bool,
35+
* allowedHeaders?: list<string>,
36+
* exposedHeaders?: list<string>,
37+
* allowedMethods?: list<string>,
38+
* maxAge?: int,
39+
* } $config
40+
*/
41+
public function __construct(array $config = [])
42+
{
43+
if ($config !== []) {
44+
$this->cors = new CorsService($config);
45+
}
46+
}
47+
48+
/**
49+
* @param list<string>|null $arguments
50+
*
51+
* @return ResponseInterface|string|void
52+
*/
53+
public function before(RequestInterface $request, $arguments = null)
54+
{
55+
if (! $request instanceof IncomingRequest) {
56+
return;
57+
}
58+
59+
$this->createCorsService($arguments);
60+
61+
if (! $this->cors->isPreflightRequest($request)) {
62+
return;
63+
}
64+
65+
/** @var ResponseInterface $response */
66+
$response = service('response');
67+
68+
$response = $this->cors->handlePreflightRequest($request, $response);
69+
70+
// Always adds `Vary: Access-Control-Request-Method` header for cacheability.
71+
// If there is an intermediate cache server such as a CDN, if a plain
72+
// OPTIONS request is sent, it may be cached. But valid preflight requests
73+
// have this header, so it will be cached separately.
74+
$response->appendHeader('Vary', 'Access-Control-Request-Method');
75+
76+
return $response;
77+
}
78+
79+
/**
80+
* @param list<string>|null $arguments
81+
*/
82+
private function createCorsService(?array $arguments): void
83+
{
84+
$this->cors ??= ($arguments === null) ? CorsService::factory()
85+
: CorsService::factory($arguments[0]);
86+
}
87+
88+
/**
89+
* @param list<string>|null $arguments
90+
*
91+
* @return ResponseInterface|void
92+
*/
93+
public function after(RequestInterface $request, ResponseInterface $response, $arguments = null)
94+
{
95+
if (! $request instanceof IncomingRequest) {
96+
return;
97+
}
98+
99+
$this->createCorsService($arguments);
100+
101+
// Always adds `Vary: Access-Control-Request-Method` header for cacheability.
102+
// If there is an intermediate cache server such as a CDN, if a plain
103+
// OPTIONS request is sent, it may be cached. But valid preflight requests
104+
// have this header, so it will be cached separately.
105+
if ($request->is('OPTIONS')) {
106+
$response->appendHeader('Vary', 'Access-Control-Request-Method');
107+
}
108+
109+
return $this->cors->addResponseHeaders($request, $response);
110+
}
111+
}

0 commit comments

Comments
 (0)