1204 lines
64 KiB
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'));
|
|
}
|
|
}
|