diff --git a/.gitattributes b/.gitattributes index 9709aa5..65a00ff 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,3 +1,4 @@ +# exclude dev files from export to reduce archive download size /.gitattributes export-ignore /.github/workflows/ export-ignore /.gitignore export-ignore @@ -7,3 +8,6 @@ /phpunit.xml.dist export-ignore /phpunit.xml.legacy export-ignore /tests/ export-ignore + +# preserve original EOL with no automatic conversation +* -text diff --git a/src/App.php b/src/App.php index 7566337..a07446e 100644 --- a/src/App.php +++ b/src/App.php @@ -343,13 +343,9 @@ private function routeRequest(ServerRequestInterface $request) $routeInfo = $this->routeDispatcher->dispatch($request->getMethod(), $request->getUri()->getPath()); switch ($routeInfo[0]) { case \FastRoute\Dispatcher::NOT_FOUND: - return $this->errorNotFound($request); + return self::errorNotFound($request); case \FastRoute\Dispatcher::METHOD_NOT_ALLOWED: - $allowedMethods = $routeInfo[1]; - - return $this->errorMethodNotAllowed( - $request->withAttribute('allowed', $allowedMethods) - ); + return $this->errorMethodNotAllowed($routeInfo[1]); case \FastRoute\Dispatcher::FOUND: $handler = $routeInfo[1]; $vars = $routeInfo[2]; @@ -422,60 +418,86 @@ private function log(string $message): void } } - private function error(int $statusCode, ?string $info = null): ResponseInterface + private static function error(int $statusCode, string $title, string ...$info): ResponseInterface { - $response = new Response( + $nonce = \base64_encode(\random_bytes(16)); + $info = \implode('', \array_map(function (string $info) { return "
$info
\n"; }, $info)); + $html = << + + +' . $method . ''; }, $allowedMethods));
+
return $this->error(
405,
- implode(', ', $request->getAttribute('allowed'))
- )->withHeader('Allowed', implode(', ', $request->getAttribute('allowed')));
+ 'Method Not Allowed',
+ 'Please check the URL in the address bar and try again with ' . $methods . ' request.'
+ )->withHeader('Allow', implode(', ', $allowedMethods));
}
private function errorHandlerException(\Throwable $e): ResponseInterface
{
- $where = ' (' . \basename($e->getFile()) . ':' . $e->getLine() . ')';
+ $where = ' in ' . \basename($e->getFile()) . ':' . $e->getLine() . '';
+ $message = '' . $this->escapeHtml($e->getMessage()) . '';
return $this->error(
500,
- 'Expected request handler to return ' . ResponseInterface::class . ' but got uncaught ' . \get_class($e) . '' . $where . ': ' . $e->getMessage()
+ 'Internal Server Error',
+ 'The requested page failed to load, please try again later.',
+ 'Expected request handler to return ' . ResponseInterface::class . ' but got uncaught ' . \get_class($e) . ' with message ' . $message . $where . '.'
);
}
@@ -483,7 +505,9 @@ private function errorHandlerResponse($value): ResponseInterface
{
return $this->error(
500,
- 'Expected request handler to return ' . ResponseInterface::class . ' but got ' . $this->describeType($value) . ''
+ 'Internal Server Error',
+ 'The requested page failed to load, please try again later.',
+ 'Expected request handler to return ' . ResponseInterface::class . ' but got ' . $this->describeType($value) . '.'
);
}
@@ -491,7 +515,9 @@ private function errorHandlerCoroutine($value): ResponseInterface
{
return $this->error(
500,
- 'Expected request handler to yield ' . PromiseInterface::class . ' but got ' . $this->describeType($value) . ''
+ 'Internal Server Error',
+ 'The requested page failed to load, please try again later.',
+ 'Expected request handler to yield ' . PromiseInterface::class . ' but got ' . $this->describeType($value) . '.'
);
}
@@ -504,4 +530,16 @@ private function describeType($value): string
}
return \is_object($value) ? \get_class($value) : \gettype($value);
}
+
+ private function escapeHtml(string $s): string
+ {
+ return \addcslashes(
+ \str_replace(
+ ' ',
+ ' ',
+ \htmlspecialchars($s, \ENT_NOQUOTES | \ENT_SUBSTITUTE | \ENT_DISALLOWED, 'utf-8')
+ ),
+ "\0..\032\\"
+ );
+ }
}
diff --git a/src/FilesystemHandler.php b/src/FilesystemHandler.php
index cf48920..1c1372b 100644
--- a/src/FilesystemHandler.php
+++ b/src/FilesystemHandler.php
@@ -124,13 +124,7 @@ public function __invoke(ServerRequestInterface $request)
\file_get_contents($path)
);
} else {
- return new Response(
- 404,
- [
- 'Content-Type' => 'text/plain; charset=utf-8'
- ],
- "Error 404: Not Found\n"
- );
+ return App::errorNotFound();
}
}
diff --git a/tests/AppMiddlewareTest.php b/tests/AppMiddlewareTest.php
index 8a6227c..7c7b417 100644
--- a/tests/AppMiddlewareTest.php
+++ b/tests/AppMiddlewareTest.php
@@ -388,8 +388,10 @@ public function testMiddlewareCallsNextWhichThrowsExceptionReturnsInternalServer
/** @var ResponseInterface $response */
$this->assertInstanceOf(ResponseInterface::class, $response);
$this->assertEquals(500, $response->getStatusCode());
- $this->assertEquals('text/html', $response->getHeaderLine('Content-Type'));
- $this->assertEquals("500 (Internal Server Error): Expected request handler to return Psr\Http\Message\ResponseInterface but got uncaught RuntimeException (AppMiddlewareTest.php:$line): Foo\n", (string) $response->getBody());
+ $this->assertEquals('text/html; charset=utf-8', $response->getHeaderLine('Content-Type'));
+ $this->assertStringContainsString("The requested page failed to load, please try again later.
\n", (string) $response->getBody()); + $this->assertStringContainsString("Expected request handler to return Psr\Http\Message\ResponseInterface but got uncaught RuntimeException with message Foo in AppMiddlewareTest.php:$line.
Psr\Http\Message\ResponseInterface but got uncaught RuntimeException (AppMiddlewareTest.php:$line): Foo\n", (string) $response->getBody());
+ $this->assertEquals('text/html; charset=utf-8', $response->getHeaderLine('Content-Type'));
+ $this->assertStringContainsString("The requested page failed to load, please try again later.
\n", (string) $response->getBody()); + $this->assertStringContainsString("Expected request handler to return Psr\Http\Message\ResponseInterface but got uncaught RuntimeException with message Foo in AppMiddlewareTest.php:$line.