Skip to content

Commit d93baba

Browse files
authored
Ugrade neomerx/cors-psr7 to 2.0 (#67)
* Ugrade neomerx/cors-psr7 to 2.0 This upgrades the internal dependency `neomerx/cors-psr7` to its latest major version 2. This includes the following changes: - Use `Settings::init()` to populate the Settings properties with sane defaults. This avoids type errors in unset options (like the host check) - Add `CorsMiddleware::determineServerOrigin()` for usage in the settings initialization - Adapt setter calls in `CorsMiddleware::buildSettings()` to the updated settings api - Adapt `Settings` to the updated settings api And some minor changes: - Rename `CorsTest` to `CorseMiddlewareTest` - Simplify `CorsMiddlewareTest::wildcardOriginDataProvider()` Fixes #33
1 parent 0efa707 commit d93baba

File tree

5 files changed

+96
-74
lines changed

5 files changed

+96
-74
lines changed

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
},
2323
"require": {
2424
"php": "^7.2|^8.0",
25-
"neomerx/cors-psr7": "^1.0.4",
25+
"neomerx/cors-psr7": "^2",
2626
"psr/http-message": "^1.0.1",
2727
"psr/http-server-middleware": "^1.0",
2828
"tuupola/callable-handler": "^1.0",

src/CorsMiddleware.php

Lines changed: 65 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,10 @@
3939
use Neomerx\Cors\Analyzer as CorsAnalyzer;
4040
use Neomerx\Cors\Contracts\AnalysisResultInterface as CorsAnalysisResultInterface;
4141
use Neomerx\Cors\Contracts\Constants\CorsResponseHeaders;
42-
use Psr\Http\Server\MiddlewareInterface;
43-
use Psr\Http\Server\RequestHandlerInterface;
4442
use Psr\Http\Message\ResponseInterface;
4543
use Psr\Http\Message\ServerRequestInterface;
44+
use Psr\Http\Server\MiddlewareInterface;
45+
use Psr\Http\Server\RequestHandlerInterface;
4646
use Psr\Log\LoggerInterface;
4747
use Tuupola\Http\Factory\ResponseFactory;
4848
use Tuupola\Middleware\Settings as CorsSettings;
@@ -56,16 +56,20 @@
5656
* "origin.server"?: null|string|array<string>,
5757
* cache?: int,
5858
* error?: null|callable,
59-
* logger?: null|LoggerInterface
59+
* logger?: null|LoggerInterface,
6060
* }
6161
*/
6262
final class CorsMiddleware implements MiddlewareInterface
6363
{
6464
use DoublePassTrait;
6565

66-
/**
67-
* @var LoggerInterface|null
68-
*/
66+
/** @var int */
67+
private const PORT_HTTP = 80;
68+
69+
/** @var int */
70+
private const PORT_HTTPS = 443;
71+
72+
/** @var LoggerInterface|null */
6973
private $logger;
7074

7175
/**
@@ -78,7 +82,7 @@ final class CorsMiddleware implements MiddlewareInterface
7882
* "origin.server": null|string|array<string>,
7983
* cache: int,
8084
* error: null|callable,
81-
* logger: null|LoggerInterface
85+
* logger: null|LoggerInterface,
8286
* }
8387
*/
8488
private $options = [
@@ -102,7 +106,7 @@ final class CorsMiddleware implements MiddlewareInterface
102106
* "origin.server"?: null|string|array<string>,
103107
* cache?: int,
104108
* error?: null|callable,
105-
* logger?: null|LoggerInterface
109+
* logger?: null|LoggerInterface,
106110
* } $options
107111
*/
108112
public function __construct(array $options = [])
@@ -216,32 +220,33 @@ private function buildSettings(ServerRequestInterface $request, ResponseInterfac
216220
{
217221
$settings = new CorsSettings();
218222

219-
$origin = array_fill_keys($this->options["origin"], true);
220-
$settings->setRequestAllowedOrigins($origin);
223+
$serverOrigin = $this->determineServerOrigin();
224+
225+
$settings->init(
226+
$serverOrigin['scheme'],
227+
$serverOrigin['host'],
228+
$serverOrigin['port']
229+
);
230+
231+
$settings->setAllowedOrigins($this->options["origin"]);
221232

222233
if (is_callable($this->options["methods"])) {
223234
$methods = (array) $this->options["methods"]($request, $response);
224235
} else {
225236
$methods = (array) $this->options["methods"];
226237
}
227238

228-
$methods = array_fill_keys($methods, true);
229-
$settings->setRequestAllowedMethods($methods);
230-
231-
$headers = array_fill_keys($this->options["headers.allow"], true);
239+
$settings->setAllowedMethods($methods);
232240

233241
/* transform all headers to lowercase */
234-
$headers = array_change_key_case($headers);
235-
236-
$settings->setRequestAllowedHeaders($headers);
242+
$headers = array_change_key_case($this->options["headers.allow"]);
237243

238-
$headers = array_fill_keys($this->options["headers.expose"], true);
239-
$settings->setResponseExposedHeaders($headers);
244+
$settings->setAllowedHeaders($headers);
240245

241-
$settings->setRequestCredentialsSupported($this->options["credentials"]);
246+
$settings->setExposedHeaders($this->options["headers.expose"]);
242247

243-
if (is_string($this->options["origin.server"])) {
244-
$settings->setServerOrigin($this->options["origin.server"]);
248+
if ($this->options["credentials"]) {
249+
$settings->setCredentialsSupported();
245250
}
246251

247252
$settings->setPreFlightCacheMaxAge($this->options["cache"]);
@@ -250,13 +255,48 @@ private function buildSettings(ServerRequestInterface $request, ResponseInterfac
250255
}
251256

252257
/**
253-
* Edge cannot handle multiple Access-Control-Expose-Headers headers
258+
* Try to determine the server origin uri fragments
259+
*
260+
* @return array{scheme: string, host: string, port: int}
261+
*/
262+
private function determineServerOrigin(): array
263+
{
264+
// set some default
265+
$url = [
266+
'scheme' => 'https',
267+
'host' => '',
268+
'port' => self::PORT_HTTPS,
269+
];
270+
271+
// load details from server origin
272+
if (is_string($this->options["origin.server"])) {
273+
/** @var false|array{scheme: string, host: string, port?: int} $url_chunks */
274+
$url_chunks = parse_url($this->options["origin.server"]);
275+
if ($url_chunks !== false) {
276+
$url = $url_chunks;
277+
}
278+
279+
if (!array_key_exists('port', $url)) {
280+
$url['port'] = $url['scheme'] === 'https' ? self::PORT_HTTPS : self::PORT_HTTP;
281+
}
282+
}
283+
284+
return $url;
285+
}
286+
287+
/**
288+
* Edge cannot handle Access-Control-Expose-Headers having a trailing whitespace after the comma
289+
*
290+
* @see https://github.com/tuupola/cors-middleware/issues/40
254291
*/
255292
private function fixHeaders(array $headers): array
256293
{
257294
if (isset($headers[CorsResponseHeaders::EXPOSE_HEADERS])) {
258-
$headers[CorsResponseHeaders::EXPOSE_HEADERS] =
259-
implode(",", $headers[CorsResponseHeaders::EXPOSE_HEADERS]);
295+
$headers[CorsResponseHeaders::EXPOSE_HEADERS] = str_replace(
296+
' ',
297+
'',
298+
$headers[CorsResponseHeaders::EXPOSE_HEADERS]
299+
);
260300
}
261301

262302
return $headers;

src/Settings.php

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,25 +28,34 @@
2828

2929
namespace Tuupola\Middleware;
3030

31-
use Neomerx\Cors\Contracts\Http\ParsedUrlInterface;
3231
use Neomerx\Cors\Strategies\Settings as BaseSettings;
3332

3433
class Settings extends BaseSettings
3534
{
36-
public function isRequestOriginAllowed(ParsedUrlInterface $requestOrigin): bool
35+
/** @var array<string> */
36+
private $allowedOrigins = [];
37+
38+
public function setAllowedOrigins(array $origins): BaseSettings
39+
{
40+
$this->allowedOrigins = $origins;
41+
42+
return parent::setAllowedOrigins($origins);
43+
}
44+
45+
public function isRequestOriginAllowed(string $requestOrigin): bool
3746
{
3847
$isAllowed = parent::isRequestOriginAllowed($requestOrigin);
3948

4049
if (!$isAllowed) {
41-
$isAllowed = $this->wildcardOriginAllowed($requestOrigin->getOrigin());
50+
$isAllowed = $this->wildcardOriginAllowed($requestOrigin);
4251
}
4352

4453
return $isAllowed;
4554
}
4655

4756
private function wildcardOriginAllowed(string $origin): bool
4857
{
49-
foreach ($this->settings[self::KEY_ALLOWED_ORIGINS] as $allowedOrigin => $value) {
58+
foreach ($this->allowedOrigins as $allowedOrigin) {
5059
if (fnmatch($allowedOrigin, $origin)) {
5160
return true;
5261
}

tests/CorsTest.php renamed to tests/CorsMiddlewareTest.php

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@
4141
use Tuupola\Http\Factory\ResponseFactory;
4242
use Tuupola\Http\Factory\ServerRequestFactory;
4343

44-
class CorsTest extends TestCase
44+
class CorsMiddlewareTest extends TestCase
4545
{
4646
public function testShouldBeTrue(): void
4747
{
@@ -63,7 +63,6 @@ public function testShouldReturn200ByDefault(): void
6363

6464
$response = $cors($request, $response, $next);
6565
$this->assertEquals(200, $response->getStatusCode());
66-
//$this->assertEquals("", $response->getBody());
6766
}
6867

6968
public function testShouldAcceptWildcardSettings(): void

tests/SettingsTest.php

Lines changed: 16 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828

2929
namespace Tuupola\Middleware;
3030

31-
use Neomerx\Cors\Http\ParsedUrl;
31+
use Generator;
3232
use PHPUnit\Framework\TestCase;
3333

3434
class SettingsTest extends TestCase
@@ -47,73 +47,47 @@ protected function setUp(): void
4747
/**
4848
* @dataProvider wildcardOriginDataProvider
4949
*/
50-
public function testIsRequestOriginAllowed(string $origin, array $allowedOrigins, bool $expected): void
50+
public function testIsRequestOriginAllowed(string $origin, string $allowedOrigins, bool $expected): void
5151
{
52-
$requestOriginMock = $this->createMock(ParsedUrl::class);
53-
$requestOriginMock->method("getOrigin")
54-
->willReturn($origin);
55-
56-
$this->testObject->setRequestAllowedOrigins($allowedOrigins);
57-
$result = $this->testObject->isRequestOriginAllowed($requestOriginMock);
52+
$this->testObject->setAllowedOrigins([$allowedOrigins]);
53+
$result = $this->testObject->isRequestOriginAllowed($origin);
5854

5955
$this->assertSame($expected, $result);
6056
}
6157

62-
public function wildcardOriginDataProvider(): iterable
58+
public function wildcardOriginDataProvider(): Generator
6359
{
6460
// Allow subdomain without wildcard
65-
$origin = "https://www.example.com";
66-
$allowedOrigins = ["https://www.example.com" => true];
67-
yield [$origin, $allowedOrigins, true];
61+
yield ["https://www.example.com", "https://www.example.com", true];
6862

6963
// Disallow wrong subdomain
70-
$origin = "https://ws.example.com";
71-
$allowedOrigins = ["https://www.example.com" => true];
72-
yield [$origin, $allowedOrigins, false];
64+
yield ["https://ws.example.com", "https://www.example.com", false];
7365

7466
// Allow all
75-
$origin = "https://ws.example.com";
76-
$allowedOrigins = ["*" => true];
77-
yield [$origin, $allowedOrigins, true];
67+
yield ["https://ws.example.com", "*", true];
7868

7969
// Allow subdomain wildcard
80-
$origin = "https://ws.example.com";
81-
$allowedOrigins = ["https://*.example.com" => true];
82-
yield [$origin, $allowedOrigins, true];
70+
yield ["https://ws.example.com", "https://*.example.com", true];
8371

8472
// Allow without specifying protocol
85-
$origin = "https://ws.example.com";
86-
$allowedOrigins = ["*.example.com" => true];
87-
yield [$origin, $allowedOrigins, true];
73+
yield ["https://ws.example.com", "*.example.com", true];
8874

8975
// Allow double subdomain for wildcard
90-
$origin = "https://a.b.example.com";
91-
$allowedOrigins = ["*.example.com" => true];
92-
yield [$origin, $allowedOrigins, true];
76+
yield ["https://a.b.example.com", "*.example.com", true];
9377

9478
// Disallow for incorrect domain wildcard
95-
$origin = "https://a.example.com.evil.com";
96-
$allowedOrigins = ["*.example.com" => true];
97-
yield [$origin, $allowedOrigins, false];
79+
yield ["https://a.example.com.evil.com", "*.example.com", false];
9880

9981
// Allow subdomain in the middle
100-
$origin = "a.b.example.com";
101-
$allowedOrigins = ["a.*.example.com" => true];
102-
yield [$origin, $allowedOrigins, true];
82+
yield ["a.b.example.com", "a.*.example.com", true];
10383

10484
// Disallow wrong subdomain
105-
$origin = "b.bc.example.com";
106-
$allowedOrigins = ["a.*.example.com" => true];
107-
yield [$origin, $allowedOrigins, false];
85+
yield ["b.bc.example.com", "a.*.example.com", false];
10886

10987
// Correctly handle dots
110-
$origin = "exampleXcom";
111-
$allowedOrigins = ["example.com" => true];
112-
yield [$origin, $allowedOrigins, false];
88+
yield ["exampleXcom", "example.com", false];
11389

11490
// Allow subdomain and domain with one rule
115-
$origin = "test.example.com";
116-
$allowedOrigins = ["*example*" => true];
117-
yield [$origin, $allowedOrigins, true];
91+
yield ["test.example.com", "*example*", true];
11892
}
11993
}

0 commit comments

Comments
 (0)