1
0
mirror of https://github.com/BookStackApp/BookStack.git synced 2026-10-23 06:49:01 +03:00
Files
bookstack/app/Exceptions/Handler.php
Dan Brown 2183fc7fc8 API: Limited exception details shown
Updated exception handler to reduce the amount of detail shown to
prevent potentially sensitive details (like internal paths) being shown
in the error message.

Added an interface for specifically marking exceptions whos messages we
may want to show.

Thanks to Tanner Marks for reporting.
2026-07-28 13:27:42 +01:00

183 lines
5.0 KiB
PHP

<?php
namespace BookStack\Exceptions;
use Illuminate\Auth\AuthenticationException;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use Illuminate\Http\Exceptions\PostTooLargeException;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Validation\ValidationException;
use Symfony\Component\ErrorHandler\Error\FatalError;
use Symfony\Component\HttpFoundation\Response as SymfonyResponse;
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
use Throwable;
class Handler extends ExceptionHandler
{
/**
* A list of the exception types that are not reported.
*
* @var array<int, class-string<Throwable>>
*/
protected $dontReport = [
NotFoundException::class,
StoppedAuthenticationException::class,
NotifyException::class,
];
/**
* A list of the inputs that are never flashed to the session on validation exceptions.
*
* @var array<int, string>
*/
protected $dontFlash = [
'current_password',
'password',
'password_confirmation',
];
/**
* A function to run upon out of memory.
* If it returns a response, that will be provided back to the request
* upon an out of memory event.
*
* @var ?callable(): ?Response
*/
protected $onOutOfMemory = null;
/**
* Report or log an exception.
*
* @param Throwable $exception
*
* @return void
*@throws Throwable
*
*/
public function report(Throwable $exception)
{
parent::report($exception);
}
/**
* Render an exception into an HTTP response.
*
* @param Request $request
*/
public function render($request, Throwable $e): SymfonyResponse
{
if ($e instanceof FatalError && str_contains($e->getMessage(), 'bytes exhausted (tried to allocate') && $this->onOutOfMemory) {
$response = call_user_func($this->onOutOfMemory);
if ($response) {
return $response;
}
}
if ($e instanceof PostTooLargeException) {
$e = new NotifyException(trans('errors.server_post_limit'), '/', 413);
}
if ($this->isApiRequest($request)) {
return $this->renderApiException($e);
}
return parent::render($request, $e);
}
/**
* Provide a function to be called when an out of memory event occurs.
* If the callable returns a response, this response will be returned
* to the request upon error.
*/
public function prepareForOutOfMemory(callable $onOutOfMemory): void
{
$this->onOutOfMemory = $onOutOfMemory;
}
/**
* Forget the current out of memory handler, if existing.
*/
public function forgetOutOfMemoryHandler(): void
{
$this->onOutOfMemory = null;
}
/**
* Check if the given request is an API request.
*/
protected function isApiRequest(Request $request): bool
{
return str_starts_with($request->path(), 'api/');
}
/**
* Render an exception when the API is in use.
*/
protected function renderApiException(Throwable $e): JsonResponse
{
$code = 500;
$headers = [];
if ($e instanceof HttpExceptionInterface) {
$code = $e->getStatusCode();
$headers = $e->getHeaders();
}
$responseData = [
'error' => [
'message' => 'An error occurred',
],
];
if ($e instanceof ModelNotFoundException) {
$responseData['error']['message'] = 'The requested resource could not be found.';
$code = 404;
}
if ($e instanceof ShowsApiExceptionMessage) {
$responseData['error']['message'] = $e->getMessageForApi();
}
if (app()->hasDebugModeEnabled()) {
$responseData['error']['message'] = $e->getMessage();
}
if ($e instanceof ValidationException) {
$responseData['error']['message'] = 'The given data was invalid.';
$responseData['error']['validation'] = $e->errors();
$code = $e->status;
}
$responseData['error']['code'] = $code;
return new JsonResponse($responseData, $code, $headers);
}
/**
* Convert an authentication exception into an unauthenticated response.
*
* @param Request $request
*/
protected function unauthenticated($request, AuthenticationException $exception): SymfonyResponse
{
if ($request->expectsJson()) {
return response()->json(['error' => 'Unauthenticated.'], 401);
}
return redirect()->guest('login');
}
/**
* Convert a validation exception into a JSON response.
*
* @param Request $request
*/
protected function invalidJson($request, ValidationException $exception): JsonResponse
{
return response()->json($exception->errors(), $exception->status);
}
}