Implemented access control handling.
This commit is contained in:
parent
c481a9eb3a
commit
d54ca02b38
10 changed files with 1289 additions and 106 deletions
2
VERSION
2
VERSION
|
@ -1 +1 @@
|
|||
0.2503.150248
|
||||
0.2503.192123
|
||||
|
|
42
composer.lock
generated
42
composer.lock
generated
|
@ -1034,16 +1034,16 @@
|
|||
},
|
||||
{
|
||||
"name": "phpunit/php-code-coverage",
|
||||
"version": "12.0.4",
|
||||
"version": "12.1.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/sebastianbergmann/php-code-coverage.git",
|
||||
"reference": "79e5ef5897068689c7c325554d6df905480ce942"
|
||||
"reference": "d331a5ced3d9a2b917baa9841b2211e72f9e780d"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/79e5ef5897068689c7c325554d6df905480ce942",
|
||||
"reference": "79e5ef5897068689c7c325554d6df905480ce942",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/d331a5ced3d9a2b917baa9841b2211e72f9e780d",
|
||||
"reference": "d331a5ced3d9a2b917baa9841b2211e72f9e780d",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -1070,7 +1070,7 @@
|
|||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-main": "12.0.x-dev"
|
||||
"dev-main": "12.1.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
|
@ -1099,7 +1099,7 @@
|
|||
"support": {
|
||||
"issues": "https://github.com/sebastianbergmann/php-code-coverage/issues",
|
||||
"security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy",
|
||||
"source": "https://github.com/sebastianbergmann/php-code-coverage/tree/12.0.4"
|
||||
"source": "https://github.com/sebastianbergmann/php-code-coverage/tree/12.1.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
|
@ -1107,7 +1107,7 @@
|
|||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2025-02-25T13:27:48+00:00"
|
||||
"time": "2025-03-17T13:56:07+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpunit/php-file-iterator",
|
||||
|
@ -1356,16 +1356,16 @@
|
|||
},
|
||||
{
|
||||
"name": "phpunit/phpunit",
|
||||
"version": "12.0.7",
|
||||
"version": "12.0.9",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/sebastianbergmann/phpunit.git",
|
||||
"reference": "2845e49082ef7acc4a71a2ef71bbf32f31da22c9"
|
||||
"reference": "7835bb4276780e0bbb385ce0a777b839e03096db"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/2845e49082ef7acc4a71a2ef71bbf32f31da22c9",
|
||||
"reference": "2845e49082ef7acc4a71a2ef71bbf32f31da22c9",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/7835bb4276780e0bbb385ce0a777b839e03096db",
|
||||
"reference": "7835bb4276780e0bbb385ce0a777b839e03096db",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -1379,7 +1379,7 @@
|
|||
"phar-io/manifest": "^2.0.4",
|
||||
"phar-io/version": "^3.2.1",
|
||||
"php": ">=8.3",
|
||||
"phpunit/php-code-coverage": "^12.0.4",
|
||||
"phpunit/php-code-coverage": "^12.1.0",
|
||||
"phpunit/php-file-iterator": "^6.0.0",
|
||||
"phpunit/php-invoker": "^6.0.0",
|
||||
"phpunit/php-text-template": "^5.0.0",
|
||||
|
@ -1391,7 +1391,7 @@
|
|||
"sebastian/exporter": "^7.0.0",
|
||||
"sebastian/global-state": "^8.0.0",
|
||||
"sebastian/object-enumerator": "^7.0.0",
|
||||
"sebastian/type": "^6.0.0",
|
||||
"sebastian/type": "^6.0.2",
|
||||
"sebastian/version": "^6.0.0",
|
||||
"staabm/side-effects-detector": "^1.0.5"
|
||||
},
|
||||
|
@ -1433,7 +1433,7 @@
|
|||
"support": {
|
||||
"issues": "https://github.com/sebastianbergmann/phpunit/issues",
|
||||
"security": "https://github.com/sebastianbergmann/phpunit/security/policy",
|
||||
"source": "https://github.com/sebastianbergmann/phpunit/tree/12.0.7"
|
||||
"source": "https://github.com/sebastianbergmann/phpunit/tree/12.0.9"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
|
@ -1449,7 +1449,7 @@
|
|||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2025-03-07T07:32:22+00:00"
|
||||
"time": "2025-03-19T13:47:33+00:00"
|
||||
},
|
||||
{
|
||||
"name": "sebastian/cli-parser",
|
||||
|
@ -2155,16 +2155,16 @@
|
|||
},
|
||||
{
|
||||
"name": "sebastian/type",
|
||||
"version": "6.0.0",
|
||||
"version": "6.0.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/sebastianbergmann/type.git",
|
||||
"reference": "533fe082889a616f330bcba6f50965135f4f2fab"
|
||||
"reference": "1d7cd6e514384c36d7a390347f57c385d4be6069"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/type/zipball/533fe082889a616f330bcba6f50965135f4f2fab",
|
||||
"reference": "533fe082889a616f330bcba6f50965135f4f2fab",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/type/zipball/1d7cd6e514384c36d7a390347f57c385d4be6069",
|
||||
"reference": "1d7cd6e514384c36d7a390347f57c385d4be6069",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -2200,7 +2200,7 @@
|
|||
"support": {
|
||||
"issues": "https://github.com/sebastianbergmann/type/issues",
|
||||
"security": "https://github.com/sebastianbergmann/type/security/policy",
|
||||
"source": "https://github.com/sebastianbergmann/type/tree/6.0.0"
|
||||
"source": "https://github.com/sebastianbergmann/type/tree/6.0.2"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
|
@ -2208,7 +2208,7 @@
|
|||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2025-02-07T05:00:19+00:00"
|
||||
"time": "2025-03-18T13:37:31+00:00"
|
||||
},
|
||||
{
|
||||
"name": "sebastian/version",
|
||||
|
|
170
src/Http/Routing/AccessControl/AccessControl.php
Normal file
170
src/Http/Routing/AccessControl/AccessControl.php
Normal file
|
@ -0,0 +1,170 @@
|
|||
<?php
|
||||
// AccessControl.php
|
||||
// Created: 2025-03-19
|
||||
// Updated: 2025-03-19
|
||||
|
||||
namespace Index\Http\Routing\AccessControl;
|
||||
|
||||
use Attribute;
|
||||
use Closure;
|
||||
use ReflectionAttribute;
|
||||
use ReflectionFunction;
|
||||
use RuntimeException;
|
||||
use Index\XArray;
|
||||
use Index\Http\Routing\Routes\RouteInfo;
|
||||
|
||||
/**
|
||||
* Contains access control info for cross origin requests.
|
||||
*/
|
||||
#[Attribute(Attribute::TARGET_FUNCTION | Attribute::TARGET_METHOD)]
|
||||
final class AccessControl {
|
||||
/**
|
||||
* Headers that are always allowed regardless of Access-Control-Allow-Headers.
|
||||
* Also included the CORS ones cus they basically are anyway.
|
||||
*
|
||||
* @see https://developer.mozilla.org/en-US/docs/Glossary/CORS-safelisted_response_header
|
||||
* @var string[]
|
||||
*/
|
||||
public const array HEADER_SAFELIST = [
|
||||
'Access-Control-Allow-Headers',
|
||||
'Access-Control-Allow-Methods',
|
||||
'Access-Control-Allow-Origin',
|
||||
'Access-Control-Expose-Headers',
|
||||
'Access-Control-Request-Headers',
|
||||
'Access-Control-Request-Method',
|
||||
'Cache-Control',
|
||||
'Content-Language',
|
||||
'Content-Length',
|
||||
'Content-Type',
|
||||
'Expires',
|
||||
'Last-Modified',
|
||||
'Pragma',
|
||||
];
|
||||
|
||||
/**
|
||||
* @param bool $allow Whether to allow cross origin requests for this route.
|
||||
* If false, everything else will be ignored since there's no need to specify it.
|
||||
* Provided if necessary to explicitly deny.
|
||||
* @param bool $credentials Whether to permit the client to include the credentials for our domain in requests using withCredentials = true.
|
||||
* @param string[]|true $allowMethods What additional methods to allow for this route. true function as wildcard.
|
||||
* Setting $allow to true will already include the $method provided in the RouteInfo constructor.
|
||||
* @param string[]|true $allowHeaders What headers to allow for this route. true functions as wildcard.
|
||||
* @param string[]|true $exposeHeaders What headers to expose to the remote origin. true function as wildcard, false as an explicit empty list.
|
||||
* In addition to Cache-Control, Content-Language, Content-Length, Content-Type, Expires, Last-Modified and Pragma.
|
||||
* false and empty array are seemingly swapped in terms of functionality at first glance
|
||||
* but i think having false represent an explicit Nothing will be nicer in the long run since it changes the type entirely
|
||||
*/
|
||||
public function __construct(
|
||||
public private(set) bool $allow = true,
|
||||
public private(set) bool $credentials = false,
|
||||
public private(set) array|true $allowMethods = [],
|
||||
public private(set) array|true $allowHeaders = [],
|
||||
public private(set) array|true $exposeHeaders = [],
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Verifies if provided headers are allowed.
|
||||
*
|
||||
* @param string[] $headers Request headers.
|
||||
* @return bool
|
||||
*/
|
||||
public function verifyHeadersAllowed(array $headers): bool {
|
||||
return $this->allowHeaders === true || XArray::all($headers, fn($name) => (
|
||||
XArray::any(self::HEADER_SAFELIST, fn($safeName) => strcasecmp($safeName, $name) === 0)
|
||||
|| XArray::any($this->allowHeaders, fn($allowName) => strcasecmp($allowName, $name) === 0)
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads methods on a provided Closure and creates or amends an AccessControl object.
|
||||
*
|
||||
* @param Closure $closure Closure to read attributes from.
|
||||
* @param ?AccessControl $info Base access control info object.
|
||||
* @return ?AccessControl
|
||||
*/
|
||||
public static function read(Closure $closure, ?AccessControl $info): ?AccessControl {
|
||||
$attrs = (new ReflectionFunction($closure))->getAttributes(self::class, ReflectionAttribute::IS_INSTANCEOF);
|
||||
$attr = array_shift($attrs);
|
||||
if($attr === null)
|
||||
return $info;
|
||||
|
||||
$attr = $attr->newInstance();
|
||||
|
||||
// explicit allow === false always takes precedence over everything else
|
||||
if(!$attr->allow)
|
||||
return $attr;
|
||||
|
||||
if($info !== null) {
|
||||
// see the above
|
||||
if(!$info->allow)
|
||||
return $info;
|
||||
|
||||
// false is default so true takes precedence
|
||||
// maybe not the best strat but its the one you're getting
|
||||
if($info->credentials)
|
||||
$attr->credentials = true;
|
||||
|
||||
if($attr->allowMethods !== true)
|
||||
$attr->allowMethods = $info->allowMethods === true
|
||||
? true : array_values(array_unique(array_merge($attr->allowMethods, $info->allowMethods)));
|
||||
|
||||
if($attr->allowHeaders !== true)
|
||||
$attr->allowHeaders = $info->allowHeaders === true
|
||||
? true : array_values(array_unique(array_merge($attr->allowHeaders, $info->allowHeaders)));
|
||||
|
||||
if($attr->exposeHeaders !== true)
|
||||
$attr->exposeHeaders = $info->exposeHeaders === true
|
||||
? true : array_values(array_unique(array_merge($attr->exposeHeaders, $info->exposeHeaders)));
|
||||
}
|
||||
|
||||
return $attr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Aggregrates access control declarations for multiple
|
||||
*
|
||||
* @param array<object{info: RouteInfo}|RouteInfo> $routes Routes that were caught for this preflight request.
|
||||
* @param string $requestMethod Intended request method for this preflight.
|
||||
* @return AccessControl Aggregated access control info.
|
||||
*/
|
||||
public static function aggregate(array $routes, string $requestMethod): AccessControl {
|
||||
$allow = false;
|
||||
$credentials = false;
|
||||
$allowMethods = [];
|
||||
$allowHeaders = [];
|
||||
$exposeHeaders = [];
|
||||
|
||||
foreach($routes as $routeInfo) {
|
||||
if(!($routeInfo instanceof RouteInfo)) {
|
||||
if(property_exists($routeInfo, 'info') && $routeInfo->info instanceof RouteInfo)
|
||||
$routeInfo = $routeInfo->info;
|
||||
else continue;
|
||||
}
|
||||
|
||||
$accessControl = $routeInfo->accessControl;
|
||||
if($accessControl?->allow !== true)
|
||||
continue;
|
||||
|
||||
$allow = true;
|
||||
|
||||
if($allowMethods !== true)
|
||||
if($accessControl->allowMethods === true) {
|
||||
$allowMethods = true;
|
||||
} else {
|
||||
$allowMethods[] = $routeInfo->method;
|
||||
if(!empty($accessControl->allowMethods))
|
||||
$allowMethods = array_merge($allowMethods, $accessControl->allowMethods);
|
||||
}
|
||||
|
||||
if($routeInfo->method === $requestMethod) {
|
||||
if($accessControl->credentials)
|
||||
$credentials = true;
|
||||
|
||||
$allowHeaders = $accessControl->allowHeaders;
|
||||
$exposeHeaders = $accessControl->exposeHeaders;
|
||||
}
|
||||
}
|
||||
|
||||
return new AccessControl($allow, $credentials, $allowMethods, $allowHeaders, $exposeHeaders);
|
||||
}
|
||||
}
|
52
src/Http/Routing/AccessControl/AccessControlHandler.php
Normal file
52
src/Http/Routing/AccessControl/AccessControlHandler.php
Normal file
|
@ -0,0 +1,52 @@
|
|||
<?php
|
||||
// AccessControlHandler.php
|
||||
// Created: 2025-03-19
|
||||
// Updated: 2025-03-19
|
||||
|
||||
namespace Index\Http\Routing\AccessControl;
|
||||
|
||||
use Index\XArray;
|
||||
use Index\Http\HttpUri;
|
||||
use Index\Http\Routing\HandlerContext;
|
||||
use Index\Http\Routing\Routes\RouteInfo;
|
||||
|
||||
/**
|
||||
* CORS handler interface.
|
||||
*/
|
||||
interface AccessControlHandler {
|
||||
/**
|
||||
* Aggregates info needed for built-in HTTP OPTIONS method handler.
|
||||
*
|
||||
* @param HandlerContext $context Route handler context.
|
||||
* @param AccessControl $accessControl Aggregated access control rules.
|
||||
* @param RouteInfo[] $routes Routes that this OPTIONS request concern.
|
||||
* @param HttpUri $requestOrigin HTTP request Origin header value.
|
||||
* @param string $requestMethod Intended HTTP request method.
|
||||
* @param string[]|null $requestHeaders Intended HTTP request headers.
|
||||
* @return AccessControlPreflight
|
||||
*/
|
||||
public function handlePreflight(
|
||||
HandlerContext $context,
|
||||
AccessControl $accessControl,
|
||||
array $routes,
|
||||
HttpUri $requestOrigin,
|
||||
string $requestMethod,
|
||||
array|null $requestHeaders,
|
||||
): AccessControlPreflight;
|
||||
|
||||
/**
|
||||
* Adds CORS headers to a request if applicable.
|
||||
*
|
||||
* @param HandlerContext $context Route handler context.
|
||||
* @param RouteInfo $routeInfo Route info.
|
||||
* @param AccessControl $accessControl Access control for the given route info.
|
||||
* @param HttpUri $requestOrigin HTTP request Origin header value.
|
||||
* @return AccessControlResult
|
||||
*/
|
||||
public function handleRequest(
|
||||
HandlerContext $context,
|
||||
RouteInfo $routeInfo,
|
||||
AccessControl $accessControl,
|
||||
HttpUri $requestOrigin,
|
||||
): AccessControlResult;
|
||||
}
|
69
src/Http/Routing/AccessControl/AccessControlPreflight.php
Normal file
69
src/Http/Routing/AccessControl/AccessControlPreflight.php
Normal file
|
@ -0,0 +1,69 @@
|
|||
<?php
|
||||
// AccessControlPreflight.php
|
||||
// Created: 2025-03-19
|
||||
// Updated: 2025-03-19
|
||||
|
||||
namespace Index\Http\Routing\AccessControl;
|
||||
|
||||
use Index\Http\HttpResponseBuilder;
|
||||
|
||||
/**
|
||||
* Contains the result for a preflight request.
|
||||
*/
|
||||
class AccessControlPreflight {
|
||||
/**
|
||||
* @param string|bool $origin Origin to use in the Access-Control-Allow-Origin header, true for *, false for deny.
|
||||
* @param string[]|true $methods Methods to use in the Access-Control-Allow-Methods header, true for *.
|
||||
* @param string[]|true|null $headers Headers to use in the Access-Control-Allow-Headers header, true for *, null for none.
|
||||
* @param bool $credentials Value for the Access-Control-Allow-Credentials header.
|
||||
* @param ?int $ttl Override value for Access-Control-Max-Age header, null for default supplied in AccessControlHandler constructor.
|
||||
*/
|
||||
public function __construct(
|
||||
public private(set) string|bool $origin = false,
|
||||
public private(set) array|true $methods = [],
|
||||
public private(set) array|true|null $headers = null,
|
||||
public private(set) bool $credentials = false,
|
||||
public private(set) ?int $ttl = null,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Applies this collection of headers to a HttpResponseBuilder.
|
||||
*
|
||||
* @param HttpResponseBuilder $response Response to apply to.
|
||||
* @param int $ttl Default TTL value, if $ttl is specified in the constructor it should be overridden.
|
||||
*/
|
||||
public function applyPreflight(HttpResponseBuilder $response, int $ttl): void {
|
||||
if($this->origin === false)
|
||||
return;
|
||||
|
||||
if($this->credentials)
|
||||
$response->setHeader('Access-Control-Allow-Credentials', 'true');
|
||||
|
||||
if($this->headers !== null) {
|
||||
if($this->headers === true)
|
||||
$response->setHeader('Access-Control-Allow-Headers', '*');
|
||||
else {
|
||||
$headers = array_unique($this->headers);
|
||||
sort($headers);
|
||||
$response->setHeader('Access-Control-Allow-Headers', implode(', ', $headers));
|
||||
}
|
||||
}
|
||||
|
||||
if($this->methods === true) {
|
||||
$response->setHeader('Access-Control-Allow-Methods', '*');
|
||||
} elseif(!empty($this->methods)) {
|
||||
$methods = array_unique($this->methods);
|
||||
sort($methods);
|
||||
$response->setHeader('Access-Control-Allow-Methods', implode(', ', $methods));
|
||||
}
|
||||
|
||||
if($this->origin === true) {
|
||||
$response->setHeader('Access-Control-Allow-Origin', '*');
|
||||
} else {
|
||||
$response->addVary('Origin');
|
||||
$response->setHeader('Access-Control-Allow-Origin', $this->origin);
|
||||
}
|
||||
|
||||
$response->setHeader('Access-Control-Max-Age', (string)($this->ttl ?? $ttl));
|
||||
}
|
||||
}
|
54
src/Http/Routing/AccessControl/AccessControlResult.php
Normal file
54
src/Http/Routing/AccessControl/AccessControlResult.php
Normal file
|
@ -0,0 +1,54 @@
|
|||
<?php
|
||||
// AccessControlResult.php
|
||||
// Created: 2025-03-19
|
||||
// Updated: 2025-03-19
|
||||
|
||||
namespace Index\Http\Routing\AccessControl;
|
||||
|
||||
use Index\Http\HttpResponseBuilder;
|
||||
|
||||
/**
|
||||
* Contains the result for a normal request.
|
||||
*/
|
||||
class AccessControlResult {
|
||||
/**
|
||||
* @param string|bool $origin Origin to use in the Access-Control-Allow-Origin header, true for *, false for deny.
|
||||
* @param string[]|bool $headers Headers to use in the Access-Control-Expose-Headers header, true for *, false for none.
|
||||
* @param bool $credentials Value for the Access-Control-Allow-Credentials header.
|
||||
*/
|
||||
public function __construct(
|
||||
public private(set) string|bool $origin = false,
|
||||
public private(set) array|bool $headers = [],
|
||||
public private(set) bool $credentials = false,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Applies this collection of headers to a HttpResponseBuilder.
|
||||
*
|
||||
* @param HttpResponseBuilder $response Response to apply to.
|
||||
*/
|
||||
public function applyResult(HttpResponseBuilder $response): void {
|
||||
if($this->origin === false)
|
||||
return;
|
||||
|
||||
if($this->credentials)
|
||||
$response->setHeader('Access-Control-Allow-Credentials', 'true');
|
||||
|
||||
if($this->origin === true) {
|
||||
$response->setHeader('Access-Control-Allow-Origin', '*');
|
||||
} else {
|
||||
$response->addVary('Origin');
|
||||
$response->setHeader('Access-Control-Allow-Origin', $this->origin);
|
||||
}
|
||||
|
||||
if($this->headers === true)
|
||||
$response->setHeader('Access-Control-Expose-Headers', '*');
|
||||
elseif($this->headers === false)
|
||||
$response->setHeader('Access-Control-Expose-Headers', '');
|
||||
elseif(!empty($this->headers)) {
|
||||
$headers = array_unique($this->headers);
|
||||
sort($headers);
|
||||
$response->setHeader('Access-Control-Expose-Headers', implode(', ', $headers));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
<?php
|
||||
// SimpleAccessControlHandler.php
|
||||
// Created: 2025-03-19
|
||||
// Updated: 2025-03-19
|
||||
|
||||
namespace Index\Http\Routing\AccessControl;
|
||||
|
||||
use Index\XArray;
|
||||
use Index\Http\HttpUri;
|
||||
use Index\Http\Routing\HandlerContext;
|
||||
use Index\Http\Routing\Routes\RouteInfo;
|
||||
|
||||
/**
|
||||
* Base CORS handler.
|
||||
*
|
||||
* Override checkAccess to make this not function as a wildcard.
|
||||
*/
|
||||
class SimpleAccessControlHandler implements AccessControlHandler {
|
||||
public function checkAccess(
|
||||
HandlerContext $context,
|
||||
AccessControl $accessControl,
|
||||
HttpUri $origin,
|
||||
?RouteInfo $routeInfo = null,
|
||||
): string|bool {
|
||||
return $accessControl->credentials ? (string)$origin : true;
|
||||
}
|
||||
|
||||
public function handlePreflight(
|
||||
HandlerContext $context,
|
||||
AccessControl $accessControl,
|
||||
array $routes,
|
||||
HttpUri $requestOrigin,
|
||||
string $requestMethod,
|
||||
array|null $requestHeaders,
|
||||
): AccessControlPreflight {
|
||||
$requestOrigin = $this->checkAccess($context, $accessControl, $requestOrigin);
|
||||
if($requestOrigin === false)
|
||||
return new AccessControlPreflight;
|
||||
|
||||
return new AccessControlPreflight(
|
||||
$requestOrigin,
|
||||
$accessControl->allowMethods,
|
||||
$requestHeaders === null ? null : ($accessControl->allowHeaders === true ? true : $requestHeaders),
|
||||
$accessControl->credentials,
|
||||
);
|
||||
}
|
||||
|
||||
public function handleRequest(
|
||||
HandlerContext $context,
|
||||
RouteInfo $routeInfo,
|
||||
AccessControl $accessControl,
|
||||
HttpUri $requestOrigin,
|
||||
): AccessControlResult {
|
||||
$requestOrigin = $this->checkAccess($context, $accessControl, $requestOrigin, $routeInfo);
|
||||
if($requestOrigin === false)
|
||||
return new AccessControlResult;
|
||||
|
||||
return new AccessControlResult(
|
||||
$requestOrigin,
|
||||
$accessControl->exposeHeaders,
|
||||
$accessControl->credentials,
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
<?php
|
||||
// Router.php
|
||||
// Created: 2024-03-28
|
||||
// Updated: 2025-03-15
|
||||
// Updated: 2025-03-19
|
||||
|
||||
namespace Index\Http\Routing;
|
||||
|
||||
|
@ -9,8 +9,10 @@ use InvalidArgumentException;
|
|||
use JsonSerializable;
|
||||
use RuntimeException;
|
||||
use Stringable;
|
||||
use Index\XArray;
|
||||
use Index\Bencode\{Bencode,BencodeSerializable};
|
||||
use Index\Http\HttpRequest;
|
||||
use Index\Http\{HttpRequest,HttpUri};
|
||||
use Index\Http\Routing\AccessControl\{AccessControl,AccessControlHandler,SimpleAccessControlHandler};
|
||||
use Index\Http\Routing\ErrorHandling\{ErrorHandler,HtmlErrorHandler,PlainErrorHandler};
|
||||
use Index\Http\Routing\Filters\FilterInfo;
|
||||
use Index\Http\Routing\Processors\{ProcessorInfo,ProcessorTarget};
|
||||
|
@ -46,22 +48,32 @@ class Router implements RequestHandlerInterface {
|
|||
private array $postprocs = [];
|
||||
|
||||
/**
|
||||
* @param ErrorHandler|'html'|'plain' $errorHandler Error handling to use for error responses with an empty body. 'html' for the default HTML implementation, 'plain' for the plaintext implementation.
|
||||
* @param bool $registerDefaultProcessors Whether the default processors should be registered.
|
||||
* Access control handler instance.
|
||||
*/
|
||||
public function __construct(
|
||||
ErrorHandler|string $errorHandler = 'html',
|
||||
bool $registerDefaultProcessors = true
|
||||
) {
|
||||
$this->setErrorHandler($errorHandler);
|
||||
if($registerDefaultProcessors)
|
||||
$this->register(new RouterProcessors);
|
||||
}
|
||||
public private(set) AccessControlHandler $accessControlHandler;
|
||||
|
||||
/**
|
||||
* Error handler instance.
|
||||
*/
|
||||
public ErrorHandler $errorHandler;
|
||||
public private(set) ErrorHandler $errorHandler;
|
||||
|
||||
/**
|
||||
* @param ErrorHandler|'html'|'plain' $errorHandler Error handling to use for error responses with an empty body. 'html' for the default HTML implementation, 'plain' for the plaintext implementation.
|
||||
* @param ?AccessControlHandler $accessControlHandler Handler for CORS requests.
|
||||
* @param int $accessControlTtl Default Access-Control-Max-Age value.
|
||||
* @param bool $registerDefaultProcessors Whether the default processors should be registered.
|
||||
*/
|
||||
public function __construct(
|
||||
ErrorHandler|string $errorHandler = 'html',
|
||||
?AccessControlHandler $accessControlHandler = null,
|
||||
public private(set) int $accessControlTtl = 300,
|
||||
bool $registerDefaultProcessors = true
|
||||
) {
|
||||
$this->setErrorHandler($errorHandler);
|
||||
$this->accessControlHandler = $accessControlHandler ?? new SimpleAccessControlHandler;
|
||||
if($registerDefaultProcessors)
|
||||
$this->register(new RouterProcessors);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets an error handler.
|
||||
|
@ -250,14 +262,17 @@ class Router implements RequestHandlerInterface {
|
|||
}
|
||||
}
|
||||
|
||||
/** @var array<string, object{info: RouteInfo, matches: ?array<int|string, mixed>}> */
|
||||
$methods = [];
|
||||
foreach($this->routes as $routeInfo) {
|
||||
$match = $routeInfo->matcher->match($context->request->uri);
|
||||
if($match !== false)
|
||||
if($match !== false) {
|
||||
$routeInfo->readAccessControl();
|
||||
$methods[$routeInfo->method] = (object)[
|
||||
'info' => $routeInfo,
|
||||
'matches' => is_array($match) ? $match : null,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
if(empty($methods)) {
|
||||
|
@ -276,14 +291,39 @@ class Router implements RequestHandlerInterface {
|
|||
$allow[] = 'OPTIONS';
|
||||
if(in_array('GET', $allow))
|
||||
$allow[] = 'HEAD';
|
||||
|
||||
$context->response->setAllow($allow);
|
||||
$context->response->reasonPhrase = '';
|
||||
|
||||
if($context->request->method === 'OPTIONS') {
|
||||
// this should include CORS stuff
|
||||
// should have an entry in RouteInfo which get aggregated here, but also attributes
|
||||
$context->response->statusCode = 204;
|
||||
$context->response->statusCode = 200;
|
||||
$context->response->setHeader('Content-Length', '0');
|
||||
|
||||
if($context->request->hasHeader('Origin')) {
|
||||
$requestMethod = strtoupper($context->request->getHeaderLine('Access-Control-Request-Method'));
|
||||
$routeInfos = array_map(fn($item) => $item->info, $methods);
|
||||
$accessControl = AccessControl::aggregate($routeInfos, $requestMethod);
|
||||
|
||||
if($accessControl->allow) {
|
||||
if($context->request->hasHeader('Access-Control-Request-Headers')) {
|
||||
$requestHeaders = trim($context->request->getHeaderLine('Access-Control-Request-Headers'));
|
||||
$requestHeaders = $requestHeaders === '' ? [] : XArray::select(
|
||||
explode(',', $requestHeaders),
|
||||
fn($part) => trim($part)
|
||||
);
|
||||
} else
|
||||
$requestHeaders = null;
|
||||
|
||||
if($requestHeaders === null || $accessControl->verifyHeadersAllowed($requestHeaders))
|
||||
$this->accessControlHandler->handlePreflight(
|
||||
$context,
|
||||
$accessControl,
|
||||
$routeInfos,
|
||||
HttpUri::createUri($context->request->getHeaderLine('Origin')),
|
||||
$requestMethod,
|
||||
$requestHeaders,
|
||||
)->applyPreflight($context->response, $this->accessControlTtl);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$context->response->statusCode = 405;
|
||||
$this->errorHandler->handle($context);
|
||||
|
@ -295,6 +335,18 @@ class Router implements RequestHandlerInterface {
|
|||
}
|
||||
|
||||
$route = $methods[$context->request->method];
|
||||
|
||||
// if you're overriding OPTIONS you're on your own
|
||||
if($context->request->method !== 'OPTIONS'
|
||||
&& $context->request->hasHeader('Origin')
|
||||
&& $route->info->accessControl?->allow === true)
|
||||
$this->accessControlHandler->handleRequest(
|
||||
$context,
|
||||
$route->info,
|
||||
$route->info->accessControl,
|
||||
HttpUri::createUri($context->request->getHeaderLine('Origin')),
|
||||
)->applyResult($context->response);
|
||||
|
||||
$route->info->readProcessors();
|
||||
|
||||
foreach($route->info->preprocs as $name => $args) {
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
<?php
|
||||
// RouteInfo.php
|
||||
// Created: 2025-03-02
|
||||
// Updated: 2025-03-07
|
||||
// Updated: 2025-03-19
|
||||
|
||||
namespace Index\Http\Routing\Routes;
|
||||
|
||||
use Closure;
|
||||
use InvalidArgumentException;
|
||||
use Index\Http\Routing\AccessControl\AccessControl;
|
||||
use Index\Http\Routing\Processors\ProcessAttribute;
|
||||
use Index\Http\Routing\UriMatchers\{ExactPathUriMatcher,PatternPathUriMatcher,UriMatcher};
|
||||
|
||||
|
@ -33,6 +34,12 @@ class RouteInfo {
|
|||
*/
|
||||
public private(set) array $postprocs = [];
|
||||
|
||||
/**
|
||||
* CORS info for this route.
|
||||
*/
|
||||
public ?AccessControl $accessControl = null;
|
||||
|
||||
private bool $aclRead = false;
|
||||
private bool $processorsRead = false;
|
||||
|
||||
/**
|
||||
|
@ -97,6 +104,17 @@ class RouteInfo {
|
|||
$this->before($name, $args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads access control attributes and merges them with the existing declarations.
|
||||
*/
|
||||
public function readAccessControl(): void {
|
||||
if($this->aclRead)
|
||||
return;
|
||||
$this->aclRead = true;
|
||||
|
||||
$this->accessControl = AccessControl::read($this->handler, $this->accessControl);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates RouteInfo instance using ExactPathUriMatcher.
|
||||
*
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<?php
|
||||
// RouterTest.php
|
||||
// Created: 2022-01-20
|
||||
// Updated: 2025-03-15
|
||||
// Updated: 2025-03-19
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
|
@ -10,6 +10,7 @@ use PHPUnit\Framework\Attributes\CoversClass;
|
|||
use Index\Http\{HttpHeaders,HttpResponseBuilder,HttpRequest,HttpUri};
|
||||
use Index\Http\Content\{FormContent,MultipartFormContent,UrlEncodedFormContent};
|
||||
use Index\Http\Routing\{HandlerContext,RouteHandler,RouteHandlerCommon,Router};
|
||||
use Index\Http\Routing\AccessControl\{AccessControl};
|
||||
use Index\Http\Routing\Filters\{FilterInfo,PrefixFilter};
|
||||
use Index\Http\Routing\Processors\{After,Before,Postprocessor,Preprocessor,ProcessorInfo};
|
||||
use Index\Http\Routing\Routes\{ExactRoute,PatternRoute,RouteInfo};
|
||||
|
@ -27,6 +28,7 @@ use Index\Http\Streams\Stream;
|
|||
#[CoversClass(RouteHandlerCommon::class)]
|
||||
#[CoversClass(PatternRoute::class)]
|
||||
#[CoversClass(FilterInfo::class)]
|
||||
#[CoversClass(AccessControl::class)]
|
||||
final class RouterTest extends TestCase {
|
||||
public function testRouter(): void {
|
||||
$router1 = new Router;
|
||||
|
@ -38,8 +40,8 @@ final class RouterTest extends TestCase {
|
|||
$router1->route(RouteInfo::exact('PUT', '/', fn() => 'put'));
|
||||
$router1->route(RouteInfo::exact('CUSTOM', '/', fn() => 'wacky'));
|
||||
|
||||
$this->assertEquals('get', (string)$router1->handle(HttpRequest::createRequestWithoutBody('GET', '/'))->getBody());
|
||||
$this->assertEquals('wacky', (string)$router1->handle(HttpRequest::createRequestWithoutBody('CUSTOM', '/'))->getBody());
|
||||
$this->assertSame('get', (string)$router1->handle(HttpRequest::createRequestWithoutBody('GET', '/'))->getBody());
|
||||
$this->assertSame('wacky', (string)$router1->handle(HttpRequest::createRequestWithoutBody('CUSTOM', '/'))->getBody());
|
||||
|
||||
$router1->filter(FilterInfo::prefix('/', function() { /* this one intentionally does nothing */ }));
|
||||
|
||||
|
@ -53,7 +55,7 @@ final class RouterTest extends TestCase {
|
|||
$router1->route(RouteInfo::pattern('GET', '#^/user/([A-Za-z0-9]+)$#uD', fn(string $user) => $user));
|
||||
$router1->route(RouteInfo::pattern('GET', '#^/user/([A-Za-z0-9]+)/below$#uD', fn(string $user) => 'below ' . $user));
|
||||
|
||||
$this->assertEquals('warioware below static', (string)$router1->handle(HttpRequest::createRequestWithoutBody('GET', '/user/static/below'))->getBody());
|
||||
$this->assertSame('warioware below static', (string)$router1->handle(HttpRequest::createRequestWithoutBody('GET', '/user/static/below'))->getBody());
|
||||
|
||||
$router2 = new Router;
|
||||
$router2->filter(FilterInfo::prefix('/', fn() => 'meow'));
|
||||
|
@ -61,7 +63,7 @@ final class RouterTest extends TestCase {
|
|||
$router2->route(RouteInfo::exact('GET', '/contact', fn() => 'contact page'));
|
||||
$router2->route(RouteInfo::exact('GET', '/25252', fn() => 'numeric test'));
|
||||
|
||||
$this->assertEquals('meow', (string)$router2->handle(HttpRequest::createRequestWithoutBody('GET', '/rules'))->getBody());
|
||||
$this->assertSame('meow', (string)$router2->handle(HttpRequest::createRequestWithoutBody('GET', '/rules'))->getBody());
|
||||
}
|
||||
|
||||
public function testAttribute(): void {
|
||||
|
@ -117,13 +119,13 @@ final class RouterTest extends TestCase {
|
|||
|
||||
$router->register($handler);
|
||||
|
||||
$this->assertEquals('index', (string)$router->handle(HttpRequest::createRequestWithoutBody('GET', '/'))->getBody());
|
||||
$this->assertEquals('avatar', (string)$router->handle(HttpRequest::createRequestWithoutBody('POST', '/avatar'))->getBody());
|
||||
$this->assertEquals('static', (string)$router->handle(HttpRequest::createRequestWithoutBody('PUT', '/static'))->getBody());
|
||||
$this->assertEquals('meow', (string)$router->handle(HttpRequest::createRequestWithoutBody('GET', '/meow'))->getBody());
|
||||
$this->assertEquals('meow', (string)$router->handle(HttpRequest::createRequestWithoutBody('POST', '/meow'))->getBody());
|
||||
$this->assertEquals('profile of Cool134', (string)$router->handle(HttpRequest::createRequestWithoutBody('GET', '/profile/Cool134'))->getBody());
|
||||
$this->assertEquals('still the profile of Cool134', (string)$router->handle(HttpRequest::createRequestWithoutBody('GET', '/profile-but-raw/Cool134'))->getBody());
|
||||
$this->assertSame('index', (string)$router->handle(HttpRequest::createRequestWithoutBody('GET', '/'))->getBody());
|
||||
$this->assertSame('avatar', (string)$router->handle(HttpRequest::createRequestWithoutBody('POST', '/avatar'))->getBody());
|
||||
$this->assertSame('static', (string)$router->handle(HttpRequest::createRequestWithoutBody('PUT', '/static'))->getBody());
|
||||
$this->assertSame('meow', (string)$router->handle(HttpRequest::createRequestWithoutBody('GET', '/meow'))->getBody());
|
||||
$this->assertSame('meow', (string)$router->handle(HttpRequest::createRequestWithoutBody('POST', '/meow'))->getBody());
|
||||
$this->assertSame('profile of Cool134', (string)$router->handle(HttpRequest::createRequestWithoutBody('GET', '/profile/Cool134'))->getBody());
|
||||
$this->assertSame('still the profile of Cool134', (string)$router->handle(HttpRequest::createRequestWithoutBody('GET', '/profile-but-raw/Cool134'))->getBody());
|
||||
}
|
||||
|
||||
public function testEEPROMSituation(): void {
|
||||
|
@ -132,8 +134,8 @@ final class RouterTest extends TestCase {
|
|||
$router->route(RouteInfo::pattern('DELETE', '#^/uploads/([A-Za-z0-9\-_]+)$#uD', fn(string $id) => "Delete {$id}"));
|
||||
|
||||
// make sure both GET and DELETE are able to execute with a different pattern
|
||||
$this->assertEquals('Get AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', (string)$router->handle(HttpRequest::createRequestWithoutBody('GET', '/uploads/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'))->getBody());
|
||||
$this->assertEquals('Delete BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB', (string)$router->handle(HttpRequest::createRequestWithoutBody('DELETE', '/uploads/BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB'))->getBody());
|
||||
$this->assertSame('Get AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', (string)$router->handle(HttpRequest::createRequestWithoutBody('GET', '/uploads/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'))->getBody());
|
||||
$this->assertSame('Delete BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB', (string)$router->handle(HttpRequest::createRequestWithoutBody('DELETE', '/uploads/BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB'))->getBody());
|
||||
}
|
||||
|
||||
public function testFilterInterceptionOnRoot(): void {
|
||||
|
@ -142,9 +144,9 @@ final class RouterTest extends TestCase {
|
|||
$router->route(RouteInfo::exact('GET', '/', fn() => 'unexpected'));
|
||||
$router->route(RouteInfo::exact('GET', '/test', fn() => 'also unexpected'));
|
||||
|
||||
$this->assertEquals('expected', (string)$router->handle(HttpRequest::createRequestWithoutBody('GET', '/'))->getBody());
|
||||
$this->assertEquals('expected', (string)$router->handle(HttpRequest::createRequestWithoutBody('GET', '/test'))->getBody());
|
||||
$this->assertEquals('expected', (string)$router->handle(HttpRequest::createRequestWithoutBody('GET', '/error'))->getBody());
|
||||
$this->assertSame('expected', (string)$router->handle(HttpRequest::createRequestWithoutBody('GET', '/'))->getBody());
|
||||
$this->assertSame('expected', (string)$router->handle(HttpRequest::createRequestWithoutBody('GET', '/test'))->getBody());
|
||||
$this->assertSame('expected', (string)$router->handle(HttpRequest::createRequestWithoutBody('GET', '/error'))->getBody());
|
||||
}
|
||||
|
||||
public function testDefaultOptionsImplementation(): void {
|
||||
|
@ -154,9 +156,10 @@ final class RouterTest extends TestCase {
|
|||
$router->route(RouteInfo::exact('PATCH', '/test', fn() => 'patch'));
|
||||
|
||||
$response = $router->handle(HttpRequest::createRequestWithoutBody('OPTIONS', '/test'));
|
||||
$this->assertEquals(204, $response->getStatusCode());
|
||||
$this->assertEquals('No Content', $response->getReasonPhrase());
|
||||
$this->assertEquals('GET, HEAD, OPTIONS, PATCH, POST', $response->getHeaderLine('Allow'));
|
||||
$this->assertSame(200, $response->getStatusCode());
|
||||
$this->assertSame('OK', $response->getReasonPhrase());
|
||||
$this->assertSame('0', $response->getHeaderLine('Content-Length'));
|
||||
$this->assertSame('GET, HEAD, OPTIONS, PATCH, POST', $response->getHeaderLine('Allow'));
|
||||
}
|
||||
|
||||
public function testDefaultProcessorsNoRegister(): void {
|
||||
|
@ -238,69 +241,69 @@ final class RouterTest extends TestCase {
|
|||
return '<!doctype html><h1>ensuring autodetect gets skipped</h1>';
|
||||
}
|
||||
|
||||
/** @return array{wow: string} */
|
||||
#[After('output:json', flags: 0)]
|
||||
#[ExactRoute('GET', '/output/json')]
|
||||
/** @return array{wow: string} */
|
||||
public function testOutputJson(): array {
|
||||
return ['wow' => 'objects?? / epic!!'];
|
||||
}
|
||||
|
||||
/** @return array{benben: int} */
|
||||
#[After('output:bencode')]
|
||||
#[ExactRoute('GET', '/output/bencode')]
|
||||
/** @return array{benben: int} */
|
||||
public function testOutputBencode(): array {
|
||||
return ['benben' => 12345];
|
||||
}
|
||||
});
|
||||
|
||||
$response = $router->handle(HttpRequest::createRequestWithoutBody('GET', '/output/stream'));
|
||||
$this->assertEquals('application/octet-stream', $response->getHeaderLine('Content-Type'));
|
||||
$this->assertEquals('<!doctype html><h1>ensuring autodetect gets skipped</h1>', (string)$response->getBody());
|
||||
$this->assertSame('application/octet-stream', $response->getHeaderLine('Content-Type'));
|
||||
$this->assertSame('<!doctype html><h1>ensuring autodetect gets skipped</h1>', (string)$response->getBody());
|
||||
|
||||
$response = $router->handle(HttpRequest::createRequestWithoutBody('GET', '/output/plain'));
|
||||
$this->assertEquals('text/plain;charset=utf-8', $response->getHeaderLine('Content-Type'));
|
||||
$this->assertEquals('<!doctype html><h1>ensuring autodetect gets skipped</h1>', (string)$response->getBody());
|
||||
$this->assertSame('text/plain;charset=utf-8', $response->getHeaderLine('Content-Type'));
|
||||
$this->assertSame('<!doctype html><h1>ensuring autodetect gets skipped</h1>', (string)$response->getBody());
|
||||
|
||||
$response = $router->handle(HttpRequest::createRequestWithoutBody('GET', '/output/html'));
|
||||
$this->assertEquals('text/html;charset=us-ascii', $response->getHeaderLine('Content-Type'));
|
||||
$this->assertEquals('<?xml idk how xml opens the prefix is enough><beans></beans>', (string)$response->getBody());
|
||||
$this->assertSame('text/html;charset=us-ascii', $response->getHeaderLine('Content-Type'));
|
||||
$this->assertSame('<?xml idk how xml opens the prefix is enough><beans></beans>', (string)$response->getBody());
|
||||
|
||||
$response = $router->handle(HttpRequest::createRequestWithoutBody('GET', '/output/xml'));
|
||||
$this->assertEquals('application/xml', $response->getHeaderLine('Content-Type'));
|
||||
$this->assertEquals('soup', (string)$response->getBody());
|
||||
$this->assertSame('application/xml', $response->getHeaderLine('Content-Type'));
|
||||
$this->assertSame('soup', (string)$response->getBody());
|
||||
|
||||
$response = $router->handle(HttpRequest::createRequestWithoutBody('GET', '/output/css'));
|
||||
$this->assertEquals('text/css', $response->getHeaderLine('Content-Type'));
|
||||
$this->assertEquals('<!doctype html><h1>ensuring autodetect gets skipped</h1>', (string)$response->getBody());
|
||||
$this->assertSame('text/css', $response->getHeaderLine('Content-Type'));
|
||||
$this->assertSame('<!doctype html><h1>ensuring autodetect gets skipped</h1>', (string)$response->getBody());
|
||||
|
||||
$response = $router->handle(HttpRequest::createRequestWithoutBody('GET', '/output/js'));
|
||||
$this->assertEquals('application/javascript', $response->getHeaderLine('Content-Type'));
|
||||
$this->assertEquals('<!doctype html><h1>ensuring autodetect gets skipped</h1>', (string)$response->getBody());
|
||||
$this->assertSame('application/javascript', $response->getHeaderLine('Content-Type'));
|
||||
$this->assertSame('<!doctype html><h1>ensuring autodetect gets skipped</h1>', (string)$response->getBody());
|
||||
|
||||
$response = $router->handle(HttpRequest::createRequestWithoutBody('GET', '/output/json'));
|
||||
$this->assertEquals('application/json', $response->getHeaderLine('Content-Type'));
|
||||
$this->assertEquals('{"wow":"objects?? \/ epic!!"}', (string)$response->getBody());
|
||||
$this->assertSame('application/json', $response->getHeaderLine('Content-Type'));
|
||||
$this->assertSame('{"wow":"objects?? \/ epic!!"}', (string)$response->getBody());
|
||||
|
||||
$response = $router->handle(HttpRequest::createRequestWithoutBody('GET', '/output/bencode'));
|
||||
$this->assertEquals('application/x-bittorrent', $response->getHeaderLine('Content-Type'));
|
||||
$this->assertEquals('d6:benbeni12345ee', (string)$response->getBody());
|
||||
$this->assertSame('application/x-bittorrent', $response->getHeaderLine('Content-Type'));
|
||||
$this->assertSame('d6:benbeni12345ee', (string)$response->getBody());
|
||||
|
||||
$response = $router->handle(HttpRequest::createRequestWithoutBody('POST', '/optional-form'));
|
||||
$this->assertEquals(200, $response->getStatusCode());
|
||||
$this->assertEquals('none', (string)$response->getBody());
|
||||
$this->assertSame(200, $response->getStatusCode());
|
||||
$this->assertSame('none', (string)$response->getBody());
|
||||
|
||||
$response = $router->handle(HttpRequest::createRequestWithBody('POST', '/optional-form', [], Stream::createStream('test=mewow')));
|
||||
$this->assertEquals(200, $response->getStatusCode());
|
||||
$this->assertEquals('none', (string)$response->getBody());
|
||||
$this->assertSame(200, $response->getStatusCode());
|
||||
$this->assertSame('none', (string)$response->getBody());
|
||||
|
||||
$response = $router->handle(HttpRequest::createRequestWithBody('POST', '/optional-form', ['Content-Type' => ['application/x-www-form-urlencoded']], Stream::createStream('test=mewow')));
|
||||
$this->assertEquals(200, $response->getStatusCode());
|
||||
$this->assertEquals('urlencoded:mewow', (string)$response->getBody());
|
||||
$this->assertSame(200, $response->getStatusCode());
|
||||
$this->assertSame('urlencoded:mewow', (string)$response->getBody());
|
||||
|
||||
// an empty string is valid too
|
||||
$response = $router->handle(HttpRequest::createRequestWithBody('POST', '/optional-form', ['Content-Type' => ['application/x-www-form-urlencoded']], Stream::createStream('')));
|
||||
$this->assertEquals(200, $response->getStatusCode());
|
||||
$this->assertEquals('urlencoded:', (string)$response->getBody());
|
||||
$this->assertSame(200, $response->getStatusCode());
|
||||
$this->assertSame('urlencoded:', (string)$response->getBody());
|
||||
|
||||
$response = $router->handle(HttpRequest::createRequestWithBody('POST', '/optional-form', ['Content-Type' => ['multipart/form-data; boundary="--soap12345"']], Stream::createStream(implode("\r\n", [
|
||||
'----soap12345',
|
||||
|
@ -310,25 +313,25 @@ final class RouterTest extends TestCase {
|
|||
'----soap12345--',
|
||||
'',
|
||||
]))));
|
||||
$this->assertEquals(200, $response->getStatusCode());
|
||||
$this->assertEquals('multipart:wowof', (string)$response->getBody());
|
||||
$this->assertSame(200, $response->getStatusCode());
|
||||
$this->assertSame('multipart:wowof', (string)$response->getBody());
|
||||
|
||||
$response = $router->handle(HttpRequest::createRequestWithBody('POST', '/optional-form', ['Content-Type' => ['multipart/form-data; boundary="--soap12345"']], Stream::createStream(implode("\r\n", [
|
||||
'----soap12345--',
|
||||
'',
|
||||
]))));
|
||||
$this->assertEquals(200, $response->getStatusCode());
|
||||
$this->assertEquals('multipart:', (string)$response->getBody());
|
||||
$this->assertSame(200, $response->getStatusCode());
|
||||
$this->assertSame('multipart:', (string)$response->getBody());
|
||||
|
||||
$response = $router->handle(HttpRequest::createRequestWithoutBody('POST', '/required-urlencoded'));
|
||||
$this->assertEquals(400, $response->getStatusCode());
|
||||
$this->assertSame(400, $response->getStatusCode());
|
||||
|
||||
$response = $router->handle(HttpRequest::createRequestWithoutBody('POST', '/required-multipart'));
|
||||
$this->assertEquals(400, $response->getStatusCode());
|
||||
$this->assertSame(400, $response->getStatusCode());
|
||||
|
||||
$response = $router->handle(HttpRequest::createRequestWithBody('POST', '/required-urlencoded', ['Content-Type' => ['application/x-www-form-urlencoded']], Stream::createStream('test=meow')));
|
||||
$this->assertEquals(200, $response->getStatusCode());
|
||||
$this->assertEquals('meow', (string)$response->getBody());
|
||||
$this->assertSame(200, $response->getStatusCode());
|
||||
$this->assertSame('meow', (string)$response->getBody());
|
||||
|
||||
$response = $router->handle(HttpRequest::createRequestWithBody('POST', '/required-multipart', ['Content-Type' => ['multipart/form-data; boundary="--soap56789"']], Stream::createStream(implode("\r\n", [
|
||||
'----soap56789',
|
||||
|
@ -338,11 +341,11 @@ final class RouterTest extends TestCase {
|
|||
'----soap56789--',
|
||||
'',
|
||||
]))));
|
||||
$this->assertEquals(200, $response->getStatusCode());
|
||||
$this->assertEquals('woof', (string)$response->getBody());
|
||||
$this->assertSame(200, $response->getStatusCode());
|
||||
$this->assertSame('woof', (string)$response->getBody());
|
||||
|
||||
$response = $router->handle(HttpRequest::createRequestWithBody('POST', '/required-urlencoded', ['Content-Type' => ['multipart/form-data; boundary="--soap56789"']], Stream::createStream('test=meow')));
|
||||
$this->assertEquals(400, $response->getStatusCode());
|
||||
$this->assertSame(400, $response->getStatusCode());
|
||||
|
||||
$response = $router->handle(HttpRequest::createRequestWithBody('POST', '/required-multipart', ['Content-Type' => ['application/x-www-form-urlencoded']], Stream::createStream(implode("\r\n", [
|
||||
'----soap56789',
|
||||
|
@ -352,7 +355,7 @@ final class RouterTest extends TestCase {
|
|||
'----soap56789--',
|
||||
'',
|
||||
]))));
|
||||
$this->assertEquals(400, $response->getStatusCode());
|
||||
$this->assertSame(400, $response->getStatusCode());
|
||||
|
||||
$response = $router->handle(HttpRequest::createRequestWithBody('POST', '/required-multipart', ['Content-Type' => ['multipart/form-data']], Stream::createStream(implode("\r\n", [
|
||||
'----aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
|
||||
|
@ -362,7 +365,7 @@ final class RouterTest extends TestCase {
|
|||
'----aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa--',
|
||||
'',
|
||||
]))));
|
||||
$this->assertEquals(400, $response->getStatusCode());
|
||||
$this->assertSame(400, $response->getStatusCode());
|
||||
}
|
||||
|
||||
public function testProcessors(): void {
|
||||
|
@ -424,15 +427,716 @@ final class RouterTest extends TestCase {
|
|||
));
|
||||
|
||||
$response = $router->handle(HttpRequest::createRequestWithBody('POST', '/test', [], Stream::createStream(base64_encode('mewow'))));
|
||||
$this->assertEquals(200, $response->getStatusCode());
|
||||
$this->assertEquals("{\n \"data\": \"mewow\"\n}", (string)$response->getBody());
|
||||
$this->assertSame(200, $response->getStatusCode());
|
||||
$this->assertSame("{\n \"data\": \"mewow\"\n}", (string)$response->getBody());
|
||||
|
||||
$response = $router->handle(HttpRequest::createRequestWithBody('PUT', '/alternate', [], Stream::createStream(base64_encode('soap'))));
|
||||
$this->assertEquals(200, $response->getStatusCode());
|
||||
$this->assertEquals("{\n \"path\": \"/alternate\",\n \"data\": \"soap\"\n}", (string)$response->getBody());
|
||||
$this->assertSame(200, $response->getStatusCode());
|
||||
$this->assertSame("{\n \"path\": \"/alternate\",\n \"data\": \"soap\"\n}", (string)$response->getBody());
|
||||
|
||||
$response = $router->handle(HttpRequest::createRequestWithoutBody('GET', '/filtered'));
|
||||
$this->assertEquals(403, $response->getStatusCode());
|
||||
$this->assertEquals('it can work like a filter too', (string)$response->getBody());
|
||||
$this->assertSame(403, $response->getStatusCode());
|
||||
$this->assertSame('it can work like a filter too', (string)$response->getBody());
|
||||
}
|
||||
|
||||
public function testAccessControl(): void {
|
||||
$router = new Router;
|
||||
|
||||
$router->register(new class implements RouteHandler {
|
||||
use RouteHandlerCommon;
|
||||
|
||||
|
||||
#[ExactRoute('GET', '/nothing')]
|
||||
public function nothing(): void {}
|
||||
|
||||
|
||||
#[AccessControl]
|
||||
#[ExactRoute('GET', '/acld')]
|
||||
public function getAccessControlDifferent(): void {}
|
||||
|
||||
#[ExactRoute('POST', '/acld')]
|
||||
public function postAccessControlDifferent(): void {}
|
||||
|
||||
|
||||
#[AccessControl]
|
||||
#[ExactRoute('GET', '/copdodnop')]
|
||||
public function getCredentialsOnPostDenyOnDeleteNothingOnPut(): void {}
|
||||
|
||||
#[AccessControl(credentials: true)]
|
||||
#[ExactRoute('POST', '/copdodnop')]
|
||||
public function postCredentialsOnPostDenyOnDeleteNothingOnPut(): void {}
|
||||
|
||||
#[AccessControl(allow: false)]
|
||||
#[ExactRoute('DELETE', '/copdodnop')]
|
||||
public function deleteCredentialsOnPostDenyOnDeleteNothingOnPut(): void {}
|
||||
|
||||
#[ExactRoute('PUT', '/copdodnop')]
|
||||
public function putCredentialsOnPostDenyOnDeleteNothingOnPut(): void {}
|
||||
|
||||
|
||||
#[AccessControl(allowMethods: ['HEAD'])]
|
||||
#[ExactRoute('GET', '/methods/extra')]
|
||||
public function getMethodsExtra(): void {}
|
||||
|
||||
|
||||
#[AccessControl(allowHeaders: true)]
|
||||
#[ExactRoute('GET', '/headers/allow-all')]
|
||||
public function getHeadersAllowAll(): void {}
|
||||
|
||||
#[AccessControl(allowHeaders: ['Authorization', 'X-Content-Index', 'Content-Type'])]
|
||||
#[ExactRoute('GET', '/headers/allow-specific')]
|
||||
public function getHeadersAllowSpecific(): void {}
|
||||
|
||||
|
||||
#[AccessControl]
|
||||
#[ExactRoute('GET', '/headers/expose-safe')]
|
||||
public function getHeadersExposeSafe(): void {}
|
||||
|
||||
#[AccessControl(exposeHeaders: true)]
|
||||
#[ExactRoute('GET', '/headers/expose-all')]
|
||||
public function getHeadersExposeAll(): void {}
|
||||
|
||||
#[AccessControl(exposeHeaders: ['X-EEPROM-Max-Size', 'Vary'])]
|
||||
#[ExactRoute('GET', '/headers/expose-specific')]
|
||||
public function getHeadersExposeSpecific(): void {}
|
||||
|
||||
// including a little curveball! i'm sure this won't lead to confusion
|
||||
#[AccessControl(allow: true, credentials: true, allowMethods: ['POST', 'HEAD'], allowHeaders: ['Content-Length', 'X-Content-Index'], exposeHeaders: ['X-Satori-Time', 'X-Satori-Hash'])]
|
||||
#[ExactRoute('GET', '/all-together-now')]
|
||||
public function getAllTogetherNow(): void {}
|
||||
|
||||
#[ExactRoute('POST', '/all-together-now')]
|
||||
public function postAllTogetherNow(): void {}
|
||||
});
|
||||
|
||||
$routeWithNo = RouteInfo::exact(
|
||||
'PUT', '/no-override',
|
||||
#[AccessControl(allow: false)]
|
||||
function(): void {}
|
||||
);
|
||||
$routeWithNo->accessControl = new AccessControl; // allow: true
|
||||
$router->route($routeWithNo);
|
||||
|
||||
$response = $router->handle(HttpRequest::createRequestWithoutBody('OPTIONS', '/nothing'));
|
||||
$this->assertSame(200, $response->getStatusCode());
|
||||
$this->assertSame('0', $response->getHeaderLine('Content-Length'));
|
||||
$this->assertSame('GET, HEAD, OPTIONS', $response->getHeaderLine('Allow'));
|
||||
|
||||
$response = $router->handle(HttpRequest::createRequestWithoutBody(
|
||||
'OPTIONS', '/nothing',
|
||||
[
|
||||
'Origin' => ['https://railgun.sh'],
|
||||
'Access-Control-Request-Method' => ['GET'],
|
||||
],
|
||||
));
|
||||
$this->assertSame(200, $response->getStatusCode());
|
||||
$this->assertSame('0', $response->getHeaderLine('Content-Length'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Allow-Credentials'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Allow-Headers'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Allow-Methods'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Allow-Origin'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Expose-Headers'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Max-Age'));
|
||||
|
||||
$response = $router->handle(HttpRequest::createRequestWithoutBody(
|
||||
'GET', '/nothing',
|
||||
[
|
||||
'Origin' => ['https://railgun.sh'],
|
||||
],
|
||||
));
|
||||
$this->assertSame(200, $response->getStatusCode());
|
||||
$this->assertFalse($response->hasHeader('Content-Length'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Allow-Credentials'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Allow-Headers'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Allow-Methods'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Allow-Origin'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Expose-Headers'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Max-Age'));
|
||||
|
||||
$response = $router->handle(HttpRequest::createRequestWithoutBody(
|
||||
'OPTIONS', '/no-override',
|
||||
[
|
||||
'Origin' => ['https://railgun.sh'],
|
||||
'Access-Control-Request-Method' => ['PUT'],
|
||||
],
|
||||
));
|
||||
$this->assertSame(200, $response->getStatusCode());
|
||||
$this->assertSame('0', $response->getHeaderLine('Content-Length'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Allow-Credentials'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Allow-Headers'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Allow-Methods'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Allow-Origin'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Expose-Headers'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Max-Age'));
|
||||
|
||||
$response = $router->handle(HttpRequest::createRequestWithoutBody(
|
||||
'PUT', '/no-override',
|
||||
[
|
||||
'Origin' => ['https://railgun.sh'],
|
||||
],
|
||||
));
|
||||
$this->assertSame(200, $response->getStatusCode());
|
||||
$this->assertFalse($response->hasHeader('Content-Length'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Allow-Credentials'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Allow-Headers'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Allow-Methods'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Allow-Origin'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Expose-Headers'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Max-Age'));
|
||||
|
||||
$response = $router->handle(HttpRequest::createRequestWithoutBody(
|
||||
'OPTIONS', '/acld',
|
||||
[
|
||||
'Origin' => ['https://railgun.sh'],
|
||||
'Access-Control-Request-Method' => ['GET'],
|
||||
],
|
||||
));
|
||||
$this->assertSame(200, $response->getStatusCode());
|
||||
$this->assertSame('0', $response->getHeaderLine('Content-Length'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Allow-Credentials'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Allow-Headers'));
|
||||
$this->assertSame('GET', $response->getHeaderLine('Access-Control-Allow-Methods'));
|
||||
$this->assertSame('*', $response->getHeaderLine('Access-Control-Allow-Origin'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Expose-Headers'));
|
||||
$this->assertSame('300', $response->getHeaderLine('Access-Control-Max-Age'));
|
||||
|
||||
$response = $router->handle(HttpRequest::createRequestWithoutBody(
|
||||
'GET', '/acld',
|
||||
[
|
||||
'Origin' => ['https://railgun.sh'],
|
||||
],
|
||||
));
|
||||
$this->assertSame(200, $response->getStatusCode());
|
||||
$this->assertFalse($response->hasHeader('Content-Length'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Allow-Credentials'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Allow-Headers'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Allow-Methods'));
|
||||
$this->assertSame('*', $response->getHeaderLine('Access-Control-Allow-Origin'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Expose-Headers'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Max-Age'));
|
||||
|
||||
$response = $router->handle(HttpRequest::createRequestWithoutBody(
|
||||
'POST', '/acld',
|
||||
[
|
||||
'Origin' => ['https://railgun.sh'],
|
||||
],
|
||||
));
|
||||
$this->assertSame(200, $response->getStatusCode());
|
||||
$this->assertFalse($response->hasHeader('Content-Length'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Allow-Credentials'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Allow-Headers'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Allow-Methods'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Allow-Origin'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Expose-Headers'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Max-Age'));
|
||||
|
||||
$response = $router->handle(HttpRequest::createRequestWithoutBody(
|
||||
'OPTIONS', '/copdodnop',
|
||||
[
|
||||
'Origin' => ['https://railgun.sh'],
|
||||
'Access-Control-Request-Method' => ['GET'],
|
||||
],
|
||||
));
|
||||
$this->assertSame(200, $response->getStatusCode());
|
||||
$this->assertSame('0', $response->getHeaderLine('Content-Length'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Allow-Credentials'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Allow-Headers'));
|
||||
$this->assertSame('GET, POST', $response->getHeaderLine('Access-Control-Allow-Methods'));
|
||||
$this->assertSame('*', $response->getHeaderLine('Access-Control-Allow-Origin'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Expose-Headers'));
|
||||
$this->assertSame('300', $response->getHeaderLine('Access-Control-Max-Age'));
|
||||
|
||||
$response = $router->handle(HttpRequest::createRequestWithoutBody(
|
||||
'GET', '/copdodnop',
|
||||
[
|
||||
'Origin' => ['https://railgun.sh'],
|
||||
],
|
||||
));
|
||||
$this->assertSame(200, $response->getStatusCode());
|
||||
$this->assertFalse($response->hasHeader('Content-Length'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Allow-Credentials'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Allow-Headers'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Allow-Methods'));
|
||||
$this->assertSame('*', $response->getHeaderLine('Access-Control-Allow-Origin'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Expose-Headers'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Max-Age'));
|
||||
|
||||
$response = $router->handle(HttpRequest::createRequestWithoutBody(
|
||||
'OPTIONS', '/copdodnop',
|
||||
[
|
||||
'Origin' => ['https://railgun.sh'],
|
||||
'Access-Control-Request-Method' => ['POST'],
|
||||
],
|
||||
));
|
||||
$this->assertSame(200, $response->getStatusCode());
|
||||
$this->assertSame('0', $response->getHeaderLine('Content-Length'));
|
||||
$this->assertSame('true', $response->getHeaderLine('Access-Control-Allow-Credentials'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Allow-Headers'));
|
||||
$this->assertSame('GET, POST', $response->getHeaderLine('Access-Control-Allow-Methods'));
|
||||
$this->assertSame('https://railgun.sh', $response->getHeaderLine('Access-Control-Allow-Origin'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Expose-Headers'));
|
||||
$this->assertSame('300', $response->getHeaderLine('Access-Control-Max-Age'));
|
||||
|
||||
$response = $router->handle(HttpRequest::createRequestWithoutBody(
|
||||
'POST', '/copdodnop',
|
||||
[
|
||||
'Origin' => ['https://railgun.sh'],
|
||||
],
|
||||
));
|
||||
$this->assertSame(200, $response->getStatusCode());
|
||||
$this->assertFalse($response->hasHeader('Content-Length'));
|
||||
$this->assertSame('true', $response->getHeaderLine('Access-Control-Allow-Credentials'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Allow-Headers'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Allow-Methods'));
|
||||
$this->assertSame('https://railgun.sh', $response->getHeaderLine('Access-Control-Allow-Origin'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Expose-Headers'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Max-Age'));
|
||||
|
||||
$response = $router->handle(HttpRequest::createRequestWithoutBody(
|
||||
'OPTIONS', '/copdodnop',
|
||||
[
|
||||
'Origin' => ['https://railgun.sh'],
|
||||
'Access-Control-Request-Method' => ['DELETE'],
|
||||
],
|
||||
));
|
||||
$this->assertSame(200, $response->getStatusCode());
|
||||
$this->assertSame('0', $response->getHeaderLine('Content-Length'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Allow-Credentials'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Allow-Headers'));
|
||||
$this->assertSame('GET, POST', $response->getHeaderLine('Access-Control-Allow-Methods'));
|
||||
$this->assertSame('*', $response->getHeaderLine('Access-Control-Allow-Origin'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Expose-Headers'));
|
||||
$this->assertSame('300', $response->getHeaderLine('Access-Control-Max-Age'));
|
||||
|
||||
$response = $router->handle(HttpRequest::createRequestWithoutBody(
|
||||
'DELETE', '/copdodnop',
|
||||
[
|
||||
'Origin' => ['https://railgun.sh'],
|
||||
],
|
||||
));
|
||||
$this->assertSame(200, $response->getStatusCode());
|
||||
$this->assertFalse($response->hasHeader('Content-Length'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Allow-Credentials'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Allow-Headers'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Allow-Methods'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Allow-Origin'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Expose-Headers'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Max-Age'));
|
||||
|
||||
$response = $router->handle(HttpRequest::createRequestWithoutBody(
|
||||
'OPTIONS', '/copdodnop',
|
||||
[
|
||||
'Origin' => ['https://railgun.sh'],
|
||||
'Access-Control-Request-Method' => ['PUT'],
|
||||
],
|
||||
));
|
||||
$this->assertSame(200, $response->getStatusCode());
|
||||
$this->assertSame('0', $response->getHeaderLine('Content-Length'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Allow-Credentials'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Allow-Headers'));
|
||||
$this->assertSame('GET, POST', $response->getHeaderLine('Access-Control-Allow-Methods'));
|
||||
$this->assertSame('*', $response->getHeaderLine('Access-Control-Allow-Origin'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Expose-Headers'));
|
||||
$this->assertSame('300', $response->getHeaderLine('Access-Control-Max-Age'));
|
||||
|
||||
$response = $router->handle(HttpRequest::createRequestWithoutBody(
|
||||
'PUT', '/copdodnop',
|
||||
[
|
||||
'Origin' => ['https://railgun.sh'],
|
||||
],
|
||||
));
|
||||
$this->assertSame(200, $response->getStatusCode());
|
||||
$this->assertFalse($response->hasHeader('Content-Length'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Allow-Credentials'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Allow-Headers'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Allow-Methods'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Allow-Origin'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Expose-Headers'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Max-Age'));
|
||||
|
||||
$response = $router->handle(HttpRequest::createRequestWithoutBody(
|
||||
'OPTIONS', '/methods/extra',
|
||||
[
|
||||
'Origin' => ['https://railgun.sh'],
|
||||
'Access-Control-Request-Method' => ['HEAD'],
|
||||
],
|
||||
));
|
||||
$this->assertSame(200, $response->getStatusCode());
|
||||
$this->assertSame('0', $response->getHeaderLine('Content-Length'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Allow-Credentials'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Allow-Headers'));
|
||||
$this->assertSame('GET, HEAD', $response->getHeaderLine('Access-Control-Allow-Methods'));
|
||||
$this->assertSame('*', $response->getHeaderLine('Access-Control-Allow-Origin'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Expose-Headers'));
|
||||
$this->assertSame('300', $response->getHeaderLine('Access-Control-Max-Age'));
|
||||
|
||||
$response = $router->handle(HttpRequest::createRequestWithoutBody(
|
||||
'GET', '/methods/extra',
|
||||
[
|
||||
'Origin' => ['https://railgun.sh'],
|
||||
],
|
||||
));
|
||||
$this->assertSame(200, $response->getStatusCode());
|
||||
$this->assertFalse($response->hasHeader('Content-Length'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Allow-Credentials'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Allow-Headers'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Allow-Methods'));
|
||||
$this->assertSame('*', $response->getHeaderLine('Access-Control-Allow-Origin'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Expose-Headers'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Max-Age'));
|
||||
|
||||
$response = $router->handle(HttpRequest::createRequestWithoutBody(
|
||||
'HEAD', '/methods/extra',
|
||||
[
|
||||
'Origin' => ['https://railgun.sh'],
|
||||
],
|
||||
));
|
||||
$this->assertSame(200, $response->getStatusCode());
|
||||
$this->assertFalse($response->hasHeader('Content-Length'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Allow-Credentials'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Allow-Headers'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Allow-Methods'));
|
||||
$this->assertSame('*', $response->getHeaderLine('Access-Control-Allow-Origin'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Expose-Headers'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Max-Age'));
|
||||
|
||||
$response = $router->handle(HttpRequest::createRequestWithoutBody(
|
||||
'OPTIONS', '/headers/allow-all',
|
||||
[
|
||||
'Origin' => ['https://railgun.sh'],
|
||||
'Access-Control-Request-Method' => ['GET'],
|
||||
],
|
||||
));
|
||||
$this->assertSame(200, $response->getStatusCode());
|
||||
$this->assertSame('0', $response->getHeaderLine('Content-Length'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Allow-Credentials'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Allow-Headers'));
|
||||
$this->assertSame('GET', $response->getHeaderLine('Access-Control-Allow-Methods'));
|
||||
$this->assertSame('*', $response->getHeaderLine('Access-Control-Allow-Origin'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Expose-Headers'));
|
||||
$this->assertSame('300', $response->getHeaderLine('Access-Control-Max-Age'));
|
||||
|
||||
$response = $router->handle(HttpRequest::createRequestWithoutBody(
|
||||
'OPTIONS', '/headers/allow-all',
|
||||
[
|
||||
'Origin' => ['https://railgun.sh'],
|
||||
'Access-Control-Request-Method' => ['GET'],
|
||||
'Access-Control-Request-Headers' => ['X-Soap'],
|
||||
],
|
||||
));
|
||||
$this->assertSame(200, $response->getStatusCode());
|
||||
$this->assertSame('0', $response->getHeaderLine('Content-Length'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Allow-Credentials'));
|
||||
$this->assertSame('*', $response->getHeaderLine('Access-Control-Allow-Headers'));
|
||||
$this->assertSame('GET', $response->getHeaderLine('Access-Control-Allow-Methods'));
|
||||
$this->assertSame('*', $response->getHeaderLine('Access-Control-Allow-Origin'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Expose-Headers'));
|
||||
$this->assertSame('300', $response->getHeaderLine('Access-Control-Max-Age'));
|
||||
|
||||
$response = $router->handle(HttpRequest::createRequestWithoutBody(
|
||||
'GET', '/headers/allow-all',
|
||||
[
|
||||
'Origin' => ['https://railgun.sh'],
|
||||
],
|
||||
));
|
||||
$this->assertSame(200, $response->getStatusCode());
|
||||
$this->assertFalse($response->hasHeader('Content-Length'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Allow-Credentials'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Allow-Headers'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Allow-Methods'));
|
||||
$this->assertSame('*', $response->getHeaderLine('Access-Control-Allow-Origin'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Expose-Headers'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Max-Age'));
|
||||
|
||||
$response = $router->handle(HttpRequest::createRequestWithoutBody(
|
||||
'OPTIONS', '/headers/allow-all',
|
||||
[
|
||||
'Origin' => ['https://railgun.sh'],
|
||||
'Access-Control-Request-Method' => ['GET'],
|
||||
'Access-Control-Request-Headers' => [''],
|
||||
],
|
||||
));
|
||||
$this->assertSame(200, $response->getStatusCode());
|
||||
$this->assertSame('0', $response->getHeaderLine('Content-Length'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Allow-Credentials'));
|
||||
$this->assertSame('*', $response->getHeaderLine('Access-Control-Allow-Headers'));
|
||||
$this->assertSame('GET', $response->getHeaderLine('Access-Control-Allow-Methods'));
|
||||
$this->assertSame('*', $response->getHeaderLine('Access-Control-Allow-Origin'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Expose-Headers'));
|
||||
$this->assertSame('300', $response->getHeaderLine('Access-Control-Max-Age'));
|
||||
|
||||
$response = $router->handle(HttpRequest::createRequestWithoutBody(
|
||||
'GET', '/headers/allow-all',
|
||||
[
|
||||
'Origin' => ['https://railgun.sh'],
|
||||
],
|
||||
));
|
||||
$this->assertSame(200, $response->getStatusCode());
|
||||
$this->assertFalse($response->hasHeader('Content-Length'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Allow-Credentials'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Allow-Headers'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Allow-Methods'));
|
||||
$this->assertSame('*', $response->getHeaderLine('Access-Control-Allow-Origin'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Expose-Headers'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Max-Age'));
|
||||
|
||||
$response = $router->handle(HttpRequest::createRequestWithoutBody(
|
||||
'OPTIONS', '/headers/allow-specific',
|
||||
[
|
||||
'Origin' => ['https://railgun.sh'],
|
||||
'Access-Control-Request-Method' => ['GET'],
|
||||
],
|
||||
));
|
||||
$this->assertSame(200, $response->getStatusCode());
|
||||
$this->assertSame('0', $response->getHeaderLine('Content-Length'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Allow-Credentials'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Allow-Headers'));
|
||||
$this->assertSame('GET', $response->getHeaderLine('Access-Control-Allow-Methods'));
|
||||
$this->assertSame('*', $response->getHeaderLine('Access-Control-Allow-Origin'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Expose-Headers'));
|
||||
$this->assertSame('300', $response->getHeaderLine('Access-Control-Max-Age'));
|
||||
|
||||
$response = $router->handle(HttpRequest::createRequestWithoutBody(
|
||||
'OPTIONS', '/headers/allow-specific',
|
||||
[
|
||||
'Origin' => ['https://railgun.sh'],
|
||||
'Access-Control-Request-Method' => ['GET'],
|
||||
'Access-Control-Request-Headers' => ['X-Content-Index', 'Authorization'],
|
||||
],
|
||||
));
|
||||
$this->assertSame(200, $response->getStatusCode());
|
||||
$this->assertSame('0', $response->getHeaderLine('Content-Length'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Allow-Credentials'));
|
||||
$this->assertSame('Authorization, X-Content-Index', $response->getHeaderLine('Access-Control-Allow-Headers'));
|
||||
$this->assertSame('GET', $response->getHeaderLine('Access-Control-Allow-Methods'));
|
||||
$this->assertSame('*', $response->getHeaderLine('Access-Control-Allow-Origin'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Expose-Headers'));
|
||||
$this->assertSame('300', $response->getHeaderLine('Access-Control-Max-Age'));
|
||||
|
||||
$response = $router->handle(HttpRequest::createRequestWithoutBody(
|
||||
'OPTIONS', '/headers/allow-specific',
|
||||
[
|
||||
'Origin' => ['https://railgun.sh'],
|
||||
'Access-Control-Request-Method' => ['GET'],
|
||||
'Access-Control-Request-Headers' => ['X-Beans'],
|
||||
],
|
||||
));
|
||||
$this->assertSame(200, $response->getStatusCode());
|
||||
$this->assertSame('0', $response->getHeaderLine('Content-Length'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Allow-Credentials'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Allow-Headers'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Allow-Methods'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Allow-Origin'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Expose-Headers'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Max-Age'));
|
||||
|
||||
$response = $router->handle(HttpRequest::createRequestWithoutBody(
|
||||
'GET', '/headers/allow-specific',
|
||||
[
|
||||
'Origin' => ['https://railgun.sh'],
|
||||
],
|
||||
));
|
||||
$this->assertSame(200, $response->getStatusCode());
|
||||
$this->assertFalse($response->hasHeader('Content-Length'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Allow-Credentials'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Allow-Headers'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Allow-Methods'));
|
||||
$this->assertSame('*', $response->getHeaderLine('Access-Control-Allow-Origin'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Expose-Headers'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Max-Age'));
|
||||
|
||||
$response = $router->handle(HttpRequest::createRequestWithoutBody(
|
||||
'OPTIONS', '/headers/expose-safe',
|
||||
[
|
||||
'Origin' => ['https://railgun.sh'],
|
||||
'Access-Control-Request-Method' => ['GET'],
|
||||
],
|
||||
));
|
||||
$this->assertSame(200, $response->getStatusCode());
|
||||
$this->assertSame('0', $response->getHeaderLine('Content-Length'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Allow-Credentials'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Allow-Headers'));
|
||||
$this->assertSame('GET', $response->getHeaderLine('Access-Control-Allow-Methods'));
|
||||
$this->assertSame('*', $response->getHeaderLine('Access-Control-Allow-Origin'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Expose-Headers'));
|
||||
$this->assertSame('300', $response->getHeaderLine('Access-Control-Max-Age'));
|
||||
|
||||
$response = $router->handle(HttpRequest::createRequestWithoutBody(
|
||||
'GET', '/headers/expose-safe',
|
||||
[
|
||||
'Origin' => ['https://railgun.sh'],
|
||||
],
|
||||
));
|
||||
$this->assertSame(200, $response->getStatusCode());
|
||||
$this->assertFalse($response->hasHeader('Content-Length'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Allow-Credentials'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Allow-Headers'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Allow-Methods'));
|
||||
$this->assertSame('*', $response->getHeaderLine('Access-Control-Allow-Origin'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Expose-Headers'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Max-Age'));
|
||||
|
||||
$response = $router->handle(HttpRequest::createRequestWithoutBody(
|
||||
'OPTIONS', '/headers/expose-all',
|
||||
[
|
||||
'Origin' => ['https://railgun.sh'],
|
||||
'Access-Control-Request-Method' => ['GET'],
|
||||
],
|
||||
));
|
||||
$this->assertSame(200, $response->getStatusCode());
|
||||
$this->assertSame('0', $response->getHeaderLine('Content-Length'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Allow-Credentials'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Allow-Headers'));
|
||||
$this->assertSame('GET', $response->getHeaderLine('Access-Control-Allow-Methods'));
|
||||
$this->assertSame('*', $response->getHeaderLine('Access-Control-Allow-Origin'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Expose-Headers'));
|
||||
$this->assertSame('300', $response->getHeaderLine('Access-Control-Max-Age'));
|
||||
|
||||
$response = $router->handle(HttpRequest::createRequestWithoutBody(
|
||||
'GET', '/headers/expose-all',
|
||||
[
|
||||
'Origin' => ['https://railgun.sh'],
|
||||
],
|
||||
));
|
||||
$this->assertSame(200, $response->getStatusCode());
|
||||
$this->assertFalse($response->hasHeader('Content-Length'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Allow-Credentials'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Allow-Headers'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Allow-Methods'));
|
||||
$this->assertSame('*', $response->getHeaderLine('Access-Control-Allow-Origin'));
|
||||
$this->assertTrue($response->hasHeader('Access-Control-Expose-Headers'));
|
||||
$this->assertSame('*', $response->getHeaderLine('Access-Control-Expose-Headers'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Max-Age'));
|
||||
|
||||
$response = $router->handle(HttpRequest::createRequestWithoutBody(
|
||||
'OPTIONS', '/headers/expose-specific',
|
||||
[
|
||||
'Origin' => ['https://railgun.sh'],
|
||||
'Access-Control-Request-Method' => ['GET'],
|
||||
],
|
||||
));
|
||||
$this->assertSame(200, $response->getStatusCode());
|
||||
$this->assertSame('0', $response->getHeaderLine('Content-Length'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Allow-Credentials'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Allow-Headers'));
|
||||
$this->assertSame('GET', $response->getHeaderLine('Access-Control-Allow-Methods'));
|
||||
$this->assertSame('*', $response->getHeaderLine('Access-Control-Allow-Origin'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Expose-Headers'));
|
||||
$this->assertSame('300', $response->getHeaderLine('Access-Control-Max-Age'));
|
||||
|
||||
$response = $router->handle(HttpRequest::createRequestWithoutBody(
|
||||
'GET', '/headers/expose-specific',
|
||||
[
|
||||
'Origin' => ['https://railgun.sh'],
|
||||
],
|
||||
));
|
||||
$this->assertSame(200, $response->getStatusCode());
|
||||
$this->assertFalse($response->hasHeader('Content-Length'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Allow-Credentials'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Allow-Headers'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Allow-Methods'));
|
||||
$this->assertSame('*', $response->getHeaderLine('Access-Control-Allow-Origin'));
|
||||
$this->assertTrue($response->hasHeader('Access-Control-Expose-Headers'));
|
||||
$this->assertSame('Vary, X-EEPROM-Max-Size', $response->getHeaderLine('Access-Control-Expose-Headers'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Max-Age'));
|
||||
|
||||
$response = $router->handle(HttpRequest::createRequestWithoutBody(
|
||||
'OPTIONS', '/all-together-now',
|
||||
[
|
||||
'Origin' => ['https://flash.moe:8443'],
|
||||
'Access-Control-Request-Method' => ['GET'],
|
||||
],
|
||||
));
|
||||
$this->assertSame(200, $response->getStatusCode());
|
||||
$this->assertSame('0', $response->getHeaderLine('Content-Length'));
|
||||
$this->assertSame('true', $response->getHeaderLine('Access-Control-Allow-Credentials'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Allow-Headers'));
|
||||
$this->assertSame('GET, HEAD, POST', $response->getHeaderLine('Access-Control-Allow-Methods'));
|
||||
$this->assertSame('https://flash.moe:8443', $response->getHeaderLine('Access-Control-Allow-Origin'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Expose-Headers'));
|
||||
$this->assertSame('300', $response->getHeaderLine('Access-Control-Max-Age'));
|
||||
|
||||
$response = $router->handle(HttpRequest::createRequestWithoutBody(
|
||||
'OPTIONS', '/all-together-now',
|
||||
[
|
||||
'Origin' => ['https://flash.moe:8443'],
|
||||
'Access-Control-Request-Method' => ['GET'],
|
||||
'Access-Control-Request-Headers' => [''],
|
||||
],
|
||||
));
|
||||
$this->assertSame(200, $response->getStatusCode());
|
||||
$this->assertSame('0', $response->getHeaderLine('Content-Length'));
|
||||
$this->assertSame('true', $response->getHeaderLine('Access-Control-Allow-Credentials'));
|
||||
$this->assertTrue($response->hasHeader('Access-Control-Allow-Headers'));
|
||||
$this->assertSame('', $response->getHeaderLine('Access-Control-Allow-Headers'));
|
||||
$this->assertSame('GET, HEAD, POST', $response->getHeaderLine('Access-Control-Allow-Methods'));
|
||||
$this->assertSame('https://flash.moe:8443', $response->getHeaderLine('Access-Control-Allow-Origin'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Expose-Headers'));
|
||||
$this->assertSame('300', $response->getHeaderLine('Access-Control-Max-Age'));
|
||||
|
||||
$response = $router->handle(HttpRequest::createRequestWithoutBody(
|
||||
'OPTIONS', '/all-together-now',
|
||||
[
|
||||
'Origin' => ['https://flash.moe:8443'],
|
||||
'Access-Control-Request-Method' => ['GET'],
|
||||
'Access-Control-Request-Headers' => ['x-content-index'],
|
||||
],
|
||||
));
|
||||
$this->assertSame(200, $response->getStatusCode());
|
||||
$this->assertSame('0', $response->getHeaderLine('Content-Length'));
|
||||
$this->assertSame('true', $response->getHeaderLine('Access-Control-Allow-Credentials'));
|
||||
$this->assertSame('x-content-index', $response->getHeaderLine('Access-Control-Allow-Headers'));
|
||||
$this->assertSame('GET, HEAD, POST', $response->getHeaderLine('Access-Control-Allow-Methods'));
|
||||
$this->assertSame('https://flash.moe:8443', $response->getHeaderLine('Access-Control-Allow-Origin'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Expose-Headers'));
|
||||
$this->assertSame('300', $response->getHeaderLine('Access-Control-Max-Age'));
|
||||
|
||||
$response = $router->handle(HttpRequest::createRequestWithoutBody(
|
||||
'GET', '/all-together-now',
|
||||
[
|
||||
'Origin' => ['https://flash.moe:8443'],
|
||||
],
|
||||
));
|
||||
$this->assertSame(200, $response->getStatusCode());
|
||||
$this->assertFalse($response->hasHeader('Content-Length'));
|
||||
$this->assertSame('true', $response->getHeaderLine('Access-Control-Allow-Credentials'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Allow-Headers'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Allow-Methods'));
|
||||
$this->assertSame('https://flash.moe:8443', $response->getHeaderLine('Access-Control-Allow-Origin'));
|
||||
$this->assertSame('X-Satori-Hash, X-Satori-Time', $response->getHeaderLine('Access-Control-Expose-Headers'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Max-Age'));
|
||||
|
||||
// although routes can declare other methods than their own as CORS supported,
|
||||
// allow credentials and headers ONLY apply to their own method
|
||||
$response = $router->handle(HttpRequest::createRequestWithoutBody(
|
||||
'OPTIONS', '/all-together-now',
|
||||
[
|
||||
'Origin' => ['https://flash.moe:8443'],
|
||||
'Access-Control-Request-Method' => ['POST'],
|
||||
],
|
||||
));
|
||||
$this->assertSame(200, $response->getStatusCode());
|
||||
$this->assertSame('0', $response->getHeaderLine('Content-Length'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Allow-Credentials'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Allow-Headers'));
|
||||
$this->assertSame('GET, HEAD, POST', $response->getHeaderLine('Access-Control-Allow-Methods'));
|
||||
$this->assertSame('*', $response->getHeaderLine('Access-Control-Allow-Origin'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Expose-Headers'));
|
||||
$this->assertSame('300', $response->getHeaderLine('Access-Control-Max-Age'));
|
||||
|
||||
$response = $router->handle(HttpRequest::createRequestWithoutBody(
|
||||
'POST', '/all-together-now',
|
||||
[
|
||||
'Origin' => ['https://flash.moe:8443'],
|
||||
],
|
||||
));
|
||||
$this->assertSame(200, $response->getStatusCode());
|
||||
$this->assertFalse($response->hasHeader('Content-Length'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Allow-Credentials'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Allow-Headers'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Allow-Methods'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Allow-Origin'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Expose-Headers'));
|
||||
$this->assertFalse($response->hasHeader('Access-Control-Max-Age'));
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue