index/tests/RouterTest.php

1204 lines
64 KiB
PHP

<?php
// RouterTest.php
// Created: 2022-01-20
// Updated: 2025-03-23
declare(strict_types=1);
use PHPUnit\Framework\TestCase;
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};
use Index\Http\Streams\Stream;
/**
* This test isn't super representative of the current functionality
* it mostly just does the same tests that were done against the previous implementation
*/
#[CoversClass(Router::class)]
#[CoversClass(RouteInfo::class)]
#[CoversClass(PrefixFilter::class)]
#[CoversClass(ExactRoute::class)]
#[CoversClass(RouteHandler::class)]
#[CoversClass(RouteHandlerCommon::class)]
#[CoversClass(PatternRoute::class)]
#[CoversClass(FilterInfo::class)]
#[CoversClass(AccessControl::class)]
final class RouterTest extends TestCase {
public function testRouter(): void {
$router1 = new Router;
$router1->route(RouteInfo::exact('GET', '/', fn() => 'get'));
$router1->route(RouteInfo::exact('POST', '/', fn() => 'post'));
$router1->route(RouteInfo::exact('DELETE', '/', fn() => 'delete'));
$router1->route(RouteInfo::exact('PATCH', '/', fn() => 'patch'));
$router1->route(RouteInfo::exact('PUT', '/', fn() => 'put'));
$router1->route(RouteInfo::exact('CUSTOM', '/', fn() => 'wacky'));
$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 */ }));
// registration order should matter
$router1->filter(FilterInfo::prefix('/deep', fn() => 'deep'));
$router1->filter(FilterInfo::pattern('#^/user/([A-Za-z0-9]+)/below#u', fn(string $user) => 'warioware below ' . $user));
$router1->route(RouteInfo::exact('GET', '/user/static', fn() => 'the static one'));
$router1->route(RouteInfo::exact('GET', '/user/static/below', fn() => 'below the static one'));
$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->assertSame('warioware below static', (string)$router1->handle(HttpRequest::createRequestWithoutBody('GET', '/user/static/below'))->getBody());
$router2 = new Router;
$router2->filter(FilterInfo::prefix('/', fn() => 'meow'));
$router2->route(RouteInfo::exact('GET', '/rules', fn() => 'rules page'));
$router2->route(RouteInfo::exact('GET', '/contact', fn() => 'contact page'));
$router2->route(RouteInfo::exact('GET', '/25252', fn() => 'numeric test'));
$this->assertSame('meow', (string)$router2->handle(HttpRequest::createRequestWithoutBody('GET', '/rules'))->getBody());
}
public function testAttribute(): void {
$router = new Router;
$handler = new class implements RouteHandler {
use RouteHandlerCommon;
#[ExactRoute('GET', '/')]
public function getIndex(): string {
return 'index';
}
#[ExactRoute('POST', '/avatar')]
public function postAvatar(): string {
return 'avatar';
}
#[ExactRoute('PUT', '/static')]
public static function putStatic(): string {
return 'static';
}
#[ExactRoute('GET', '/meow')]
#[ExactRoute('POST', '/meow')]
public function multiple(): string {
return 'meow';
}
#[PrefixFilter('/filter')]
public function useFilter(): string {
return 'this intercepts';
}
#[ExactRoute('GET', '/filter')]
public function getFilter(): string {
return 'this is intercepted';
}
#[PatternRoute('GET', '/profile/([A-Za-z0-9]+)')]
public function getPattern(string $beans): string {
return sprintf('profile of %s', $beans);
}
#[PatternRoute('GET', '#^/profile-but-raw/([A-Za-z0-9]+)$#uD', raw: true)]
public function getPatternRaw(string $beans): string {
return sprintf('still the profile of %s', $beans);
}
#[ExactRoute('GET', '/announce')]
#[ExactRoute('GET', '/announce.php')]
#[PatternRoute('GET', '/announce/([A-Za-z0-9]+)')]
#[PatternRoute('GET', '/announce.php/([A-Za-z0-9]+)')]
public function getMultipleTestWithDefault(string $key = 'empty'): string {
return $key;
}
#[PatternRoute('GET', '/uploads/([A-Za-z0-9]+|[A-Za-z0-9\-_]{32})(?:-([a-z0-9]+))?(?:\.([A-Za-z0-9\-_]+))?')]
public function getEepromEdgeCase(
string $uploadId,
string $variant = '',
string $extension = ''
): string {
return sprintf('%s//%s//%s', $uploadId, $variant, $extension);
}
#[ExactRoute('GET', '/messages/stats')]
public function getStats(): string {
return 'hit exact';
}
#[PatternRoute('GET', '/messages/([A-Za-z0-9]+)')]
public function getView(): string {
return 'hit pattern';
}
#[ExactRoute('GET', '/assets/avatar')]
#[PatternRoute('GET', '/assets/avatar/([0-9]+)(?:\.[a-z]+)?')]
public function getTrailingSlash(): string {
return 'hit';
}
public function hasNoAttr(): string {
return 'not a route';
}
};
$router->register($handler);
$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());
$this->assertSame('empty', (string)$router->handle(HttpRequest::createRequestWithoutBody('GET', '/announce'))->getBody());
$this->assertSame('empty', (string)$router->handle(HttpRequest::createRequestWithoutBody('GET', '/announce.php'))->getBody());
$this->assertSame('Z643QANLgGNkF4D4h4qvFpyeXjx4TcDE', (string)$router->handle(HttpRequest::createRequestWithoutBody('GET', '/announce/Z643QANLgGNkF4D4h4qvFpyeXjx4TcDE'))->getBody());
$this->assertSame('1aKq8VaGyHohNUUR7RzU1W57Z3hQ6m0YMazAkr2IoiSPsvQJ6QoQutywwiOBlNka', (string)$router->handle(HttpRequest::createRequestWithoutBody('GET', '/announce.php/1aKq8VaGyHohNUUR7RzU1W57Z3hQ6m0YMazAkr2IoiSPsvQJ6QoQutywwiOBlNka'))->getBody());
$this->assertSame('1RJNSRYmxrvXUr////', (string)$router->handle(HttpRequest::createRequestWithoutBody('GET', '/uploads/1RJNSRYmxrvXUr'))->getBody());
$this->assertSame('1RJNSRYmxrvXUr//thumb//', (string)$router->handle(HttpRequest::createRequestWithoutBody('GET', '/uploads/1RJNSRYmxrvXUr-thumb'))->getBody());
$this->assertSame('1RJNSRYmxrvXUr//thumb//jpg', (string)$router->handle(HttpRequest::createRequestWithoutBody('GET', '/uploads/1RJNSRYmxrvXUr-thumb.jpg'))->getBody());
$this->assertSame('1RJNSRYmxrvXUr////jpg', (string)$router->handle(HttpRequest::createRequestWithoutBody('GET', '/uploads/1RJNSRYmxrvXUr.jpg'))->getBody());
$this->assertSame('hit exact', (string)$router->handle(HttpRequest::createRequestWithoutBody('GET', '/messages/stats'))->getBody());
$this->assertSame('hit pattern', (string)$router->handle(HttpRequest::createRequestWithoutBody('GET', '/messages/soaps'))->getBody());
$this->assertSame('hit', (string)$router->handle(HttpRequest::createRequestWithoutBody('GET', '/assets/avatar/'))->getBody());
$this->assertSame('hit', (string)$router->handle(HttpRequest::createRequestWithoutBody('GET', '/assets/avatar/123/'))->getBody());
}
public function testEEPROMSituation(): void {
$router = new Router;
$router->route(RouteInfo::pattern('GET', '#^/uploads/([A-Za-z0-9\-_]+)(?:\.(t|json))?$#uD', fn(string $id) => "Get {$id}"));
$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->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 {
$router = new Router;
$router->filter(FilterInfo::prefix('/', fn() => 'expected'));
$router->route(RouteInfo::exact('GET', '/', fn() => 'unexpected'));
$router->route(RouteInfo::exact('GET', '/test', fn() => 'also unexpected'));
$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 {
$router = new Router;
$router->route(RouteInfo::exact('GET', '/test', fn() => 'get'));
$router->route(RouteInfo::exact('POST', '/test', fn() => 'post'));
$router->route(RouteInfo::exact('PATCH', '/test', fn() => 'patch'));
$response = $router->handle(HttpRequest::createRequestWithoutBody('OPTIONS', '/test'));
$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 {
$this->expectException(RuntimeException::class);
$this->expectExceptionMessage(sprintf('preprocessor "%s" was not found', 'input:urlencoded:required'));
$router = new Router(registerDefaultProcessors: false);
$router->route(RouteInfo::exact(
'GET', '/soap',
#[Before('input:urlencoded:required')]
function() {
return 'test';
},
));
$router->handle(HttpRequest::createRequestWithoutBody('GET', '/soap'));
}
public function testDefaultProcessors(): void {
$router = new Router;
$router->register(new class implements RouteHandler {
use RouteHandlerCommon;
#[Before('input:urlencoded', required: false)]
#[Before('input:multipart', required: false)]
#[ExactRoute('POST', '/optional-form')]
public function formOptional(?FormContent $content = null): string {
if($content instanceof MultipartFormContent)
return 'multipart:' . (string)$content->getParam('test');
if($content instanceof UrlEncodedFormContent)
return 'urlencoded:' . (string)$content->getParam('test');
return 'none';
}
#[Before('input:multipart')]
#[ExactRoute('POST', '/required-multipart')]
public function multipartRequired(MultipartFormContent $content): string {
return (string)$content->getParam('test');
}
#[Before('input:urlencoded')]
#[ExactRoute('POST', '/required-urlencoded')]
public function urlencodedRequired(UrlEncodedFormContent $content): string {
return (string)$content->getParam('test');
}
#[After('output:stream')]
#[ExactRoute('GET', '/output/stream')]
public function testOutputStream(): string {
return '<!doctype html><h1>ensuring autodetect gets skipped</h1>';
}
#[After('output:plain', charset: 'utf-8')]
#[ExactRoute('GET', '/output/plain')]
public function testOutputPlain(): string {
return '<!doctype html><h1>ensuring autodetect gets skipped</h1>';
}
#[After('output:html', charset: 'us-ascii')]
#[ExactRoute('GET', '/output/html')]
public function testOutputHtml(): string {
return '<?xml idk how xml opens the prefix is enough><beans></beans>';
}
#[After('output:xml')]
#[ExactRoute('GET', '/output/xml')]
public function testOutputXml(): string {
return 'soup';
}
#[After('output:css')]
#[ExactRoute('GET', '/output/css')]
public function testOutputCss(): string {
return '<!doctype html><h1>ensuring autodetect gets skipped</h1>';
}
#[After('output:js')]
#[ExactRoute('GET', '/output/js')]
public function testOutputJs(): string {
return '<!doctype html><h1>ensuring autodetect gets skipped</h1>';
}
/** @return array{wow: string} */
#[After('output:json', flags: 0)]
#[ExactRoute('GET', '/output/json')]
public function testOutputJson(): array {
return ['wow' => 'objects?? / epic!!'];
}
/** @return array{benben: int} */
#[After('output:bencode')]
#[ExactRoute('GET', '/output/bencode')]
public function testOutputBencode(): array {
return ['benben' => 12345];
}
#[After('output:bencode')]
#[ExactRoute('GET', '/output/bencode/object')]
public function testOutputBencodeObject(): object {
return new class implements \Index\Bencode\BencodeSerializable {
use \Index\Bencode\BencodeSerializableCommon;
public function __construct(
#[\Index\Bencode\BencodeProperty('failure reason')]
public private(set) string $reason = 'Invalid info hash.'
) {}
};
}
});
$response = $router->handle(HttpRequest::createRequestWithoutBody('GET', '/output/stream'));
$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->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->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->assertSame('application/xml', $response->getHeaderLine('Content-Type'));
$this->assertSame('soup', (string)$response->getBody());
$response = $router->handle(HttpRequest::createRequestWithoutBody('GET', '/output/css'));
$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->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->assertSame('application/json', $response->getHeaderLine('Content-Type'));
$this->assertSame('{"wow":"objects?? \/ epic!!"}', (string)$response->getBody());
$response = $router->handle(HttpRequest::createRequestWithoutBody('GET', '/output/bencode'));
$this->assertSame('application/x-bittorrent', $response->getHeaderLine('Content-Type'));
$this->assertSame('d6:benbeni12345ee', (string)$response->getBody());
$response = $router->handle(HttpRequest::createRequestWithoutBody('GET', '/output/bencode/object'));
$this->assertSame('application/x-bittorrent', $response->getHeaderLine('Content-Type'));
$this->assertSame('d14:failure reason18:Invalid info hash.e', (string)$response->getBody());
$response = $router->handle(HttpRequest::createRequestWithoutBody('POST', '/optional-form'));
$this->assertSame(200, $response->getStatusCode());
$this->assertSame('none', (string)$response->getBody());
$response = $router->handle(HttpRequest::createRequestWithBody('POST', '/optional-form', [], Stream::createStream('test=mewow')));
$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->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->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',
'Content-Disposition: form-data; name="test"',
'',
'wowof',
'----soap12345--',
'',
]))));
$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->assertSame(200, $response->getStatusCode());
$this->assertSame('multipart:', (string)$response->getBody());
$response = $router->handle(HttpRequest::createRequestWithoutBody('POST', '/required-urlencoded'));
$this->assertSame(400, $response->getStatusCode());
$response = $router->handle(HttpRequest::createRequestWithoutBody('POST', '/required-multipart'));
$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->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',
'Content-Disposition: form-data; name="test"',
'',
'woof',
'----soap56789--',
'',
]))));
$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->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',
'Content-Disposition: form-data; name="test"',
'',
'woof',
'----soap56789--',
'',
]))));
$this->assertSame(400, $response->getStatusCode());
$response = $router->handle(HttpRequest::createRequestWithBody('POST', '/required-multipart', ['Content-Type' => ['multipart/form-data']], Stream::createStream(implode("\r\n", [
'----aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
'Content-Disposition: form-data; name="test"',
'',
'the',
'----aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa--',
'',
]))));
$this->assertSame(400, $response->getStatusCode());
}
public function testProcessors(): void {
$router = new Router;
$router->register(new class implements RouteHandler {
use RouteHandlerCommon;
#[Preprocessor('base64-decode-body')]
public function decodePre(HandlerContext $context, HttpRequest $request): void {
$context->addArgument(base64_decode((string)$request->getBody()));
}
#[Postprocessor('crash')]
public function crashPost(): void {
throw new RuntimeException('this shouldnt run!');
}
#[Postprocessor('json-encode')]
public function encodePost(HandlerContext $context, int $flags): void {
$context->halt();
$context->response->setTypeJson();
$context->response->body = Stream::createStream(
json_encode($context->result, JSON_UNESCAPED_SLASHES | JSON_THROW_ON_ERROR | $flags)
);
}
/** @return array{data: string} */
#[ExactRoute('POST', '/test')]
#[Before('base64-decode-body')]
#[After('json-encode', flags: JSON_PRETTY_PRINT)]
#[After('crash')]
public function route(string $decoded): array {
return ['data' => $decoded];
}
});
$router->preprocessor(ProcessorInfo::pre('intercept', function(HttpResponseBuilder $response) {
$response->statusCode = 403;
return 'it can work like a filter too';
}));
$router->route(RouteInfo::exact(
'PUT', '/alternate',
#[Before('base64-decode-body')]
#[After('json-encode', flags: JSON_PRETTY_PRINT)]
function(HttpRequest $request, string $decoded): array {
return ['path' => $request->uri->path, 'data' => $decoded];
}
));
$router->route(RouteInfo::exact(
'GET', '/filtered',
#[Before('intercept')]
#[Before('base64-decode-body')]
function(): string {
return 'should not get here';
}
));
$response = $router->handle(HttpRequest::createRequestWithBody('POST', '/test', [], Stream::createStream(base64_encode('mewow'))));
$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->assertSame(200, $response->getStatusCode());
$this->assertSame("{\n \"path\": \"/alternate\",\n \"data\": \"soap\"\n}", (string)$response->getBody());
$response = $router->handle(HttpRequest::createRequestWithoutBody('GET', '/filtered'));
$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'));
}
}