Hide boot error (#2633)

Completely redact boot error unless debug mode or display_errors is enabled. Attempt to use Flarum log file when possible. Fixes #2290
This commit is contained in:
Clark Winkelmann 2021-03-02 15:57:06 +01:00 committed by GitHub
parent a4ba1f890c
commit 185abf05b7

View File

@ -9,6 +9,7 @@
namespace Flarum\Http;
use Flarum\Foundation\ErrorHandling\LogReporter;
use Flarum\Foundation\SiteInterface;
use Laminas\Diactoros\Response;
use Laminas\Diactoros\ServerRequest;
@ -16,6 +17,7 @@ use Laminas\Diactoros\ServerRequestFactory;
use Laminas\HttpHandlerRunner\Emitter\SapiEmitter;
use Laminas\HttpHandlerRunner\RequestHandlerRunner;
use Laminas\Stratigility\Middleware\ErrorResponseGenerator;
use Psr\Log\LoggerInterface;
use Throwable;
class Server
@ -55,26 +57,70 @@ class Server
try {
return $this->site->bootApp()->getRequestHandler();
} catch (Throwable $e) {
exit($this->formatBootException($e));
// Apply response code first so whatever happens, it's set before anything is printed
http_response_code(500);
try {
$this->cleanBootExceptionLog($e);
} catch (Throwable $e) {
// Ignore errors in logger. The important goal is to log the original error
}
$this->fallbackBootExceptionLog($e);
}
}
/**
* Display the most relevant information about an early exception.
* Attempt to log the boot exception in a clean way and stop the script execution.
* This means looking for debug mode and/or our normal error logger.
* There is always a risk for this to fail,
* for example if the container bindings aren't present
* or if there is a filesystem error.
* @param Throwable $error
*/
private function formatBootException(Throwable $error): string
private function cleanBootExceptionLog(Throwable $error)
{
$message = $error->getMessage();
$file = $error->getFile();
$line = $error->getLine();
$type = get_class($error);
if (app()->has('flarum.config') && app('flarum.config')->inDebugMode()) {
// If the application booted far enough for the config to be available, we will check for debug mode
// Since the config is loaded very early, it is very likely to be available from the container
$message = $error->getMessage();
$file = $error->getFile();
$line = $error->getLine();
$type = get_class($error);
return <<<ERROR
echo <<<ERROR
Flarum encountered a boot error ($type)<br />
<b>$message</b><br />
thrown in <b>$file</b> on line <b>$line</b>
<pre>$error</pre>
ERROR;
exit(1);
} elseif (app()->has(LoggerInterface::class)) {
// If the application booted far enough for the logger to be available, we will log the error there
// Considering most boot errors are related to database or extensions, the logger should already be loaded
// We check for LoggerInterface binding because it's a constructor dependency of LogReporter,
// then instantiate LogReporter through the container for automatic dependency injection
app(LogReporter::class)->report($error);
echo 'Flarum encountered a boot error. Details have been logged to the Flarum log file.';
exit(1);
}
}
/**
* If the clean logging doesn't work, then we have a last opportunity.
* Here we need to be extra careful not to include anything that might be sensitive on the page.
* @param Throwable $error
* @throws Throwable
*/
private function fallbackBootExceptionLog(Throwable $error)
{
echo 'Flarum encountered a boot error. Details have been logged to the system PHP log file.<br />';
// Throwing the exception ensures it will be visible with PHP display_errors=On
// but invisible if that feature is turned off
// PHP will also automatically choose a valid place to log it based on the system settings
throw $error;
}
}