TYPO3 v15 dev-main snapshot ()
This commit is contained in:
@@ -0,0 +1,246 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Core\Error;
|
||||
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Log\LoggerAwareInterface;
|
||||
use Psr\Log\LoggerAwareTrait;
|
||||
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
|
||||
use TYPO3\CMS\Core\Database\ConnectionPool;
|
||||
use TYPO3\CMS\Core\Error\Http\StatusException;
|
||||
use TYPO3\CMS\Core\Http\ApplicationType;
|
||||
use TYPO3\CMS\Core\Http\NormalizedParams;
|
||||
use TYPO3\CMS\Core\SingletonInterface;
|
||||
use TYPO3\CMS\Core\SysLog\Action as SystemLogGenericAction;
|
||||
use TYPO3\CMS\Core\SysLog\Error as SystemLogErrorClassification;
|
||||
use TYPO3\CMS\Core\SysLog\Type as SystemLogType;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Core\Utility\HttpUtility;
|
||||
|
||||
/**
|
||||
* An abstract exception handler
|
||||
*
|
||||
* This file is a backport from TYPO3 Flow
|
||||
*/
|
||||
abstract class AbstractExceptionHandler implements ExceptionHandlerInterface, SingletonInterface, LoggerAwareInterface
|
||||
{
|
||||
use LoggerAwareTrait;
|
||||
|
||||
public const CONTEXT_WEB = 'WEB';
|
||||
public const CONTEXT_CLI = 'CLI';
|
||||
|
||||
protected const IGNORED_EXCEPTION_CODES = [
|
||||
1396795884, // Current host header value does not match the configured trusted hosts pattern
|
||||
1616175867, // Backend login request is rate limited
|
||||
1616175847, // Frontend login request is rate limited
|
||||
1436717275, // Request with unsupported HTTP method
|
||||
1699604555, // Outdated __trustedProperties format in extbase property mapping
|
||||
1517949792, // The IP address of your client does not match the list of allowed IP addresses
|
||||
1517949793, // Backend access by browser is locked for maintenance
|
||||
1517949794, // Backend and Install Tool are locked for maintenance
|
||||
1436717270, // Client sends a header with an invalid name
|
||||
1436717269, // Client sends a header with an invalid value
|
||||
];
|
||||
|
||||
public const IGNORED_HMAC_EXCEPTION_CODES = [
|
||||
1581862822, // Failed HMAC validation due to modified __trustedProperties in extbase property mapping
|
||||
1581862823, // Failed HMAC validation due to modified form state in ext:forms
|
||||
1320830018, // Failed HMAC validation due to modified HMAC string in Extbase HashService
|
||||
1320830276, // Failed HMAC validation due to too short HMAC string in Extbase HashService
|
||||
1704454157, // Failed HMAC validation due to modified HMAC string in Core HashService
|
||||
1704454152, // Failed HMAC validation due to too short HMAC string in Core HashService
|
||||
];
|
||||
|
||||
protected bool $logExceptionStackTrace = false;
|
||||
|
||||
/**
|
||||
* Displays the given exception
|
||||
*
|
||||
* @param \Throwable $exception The throwable object.
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function handleException(\Throwable $exception)
|
||||
{
|
||||
switch (PHP_SAPI) {
|
||||
case 'cli':
|
||||
$this->echoExceptionCLI($exception);
|
||||
break;
|
||||
default:
|
||||
$this->echoExceptionWeb($exception);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes exception to different logs
|
||||
*
|
||||
* @param \Throwable $exception The throwable object.
|
||||
* @param string $mode The context where the exception was thrown.
|
||||
* Either self::CONTEXT_WEB or self::CONTEXT_CLI.
|
||||
*/
|
||||
protected function writeLogEntries(\Throwable $exception, string $mode): void
|
||||
{
|
||||
// Do not write any logs for some messages to avoid filling up tables or files with illegal requests
|
||||
$ignoredCodes = array_merge(self::IGNORED_EXCEPTION_CODES, self::IGNORED_HMAC_EXCEPTION_CODES);
|
||||
if (in_array($exception->getCode(), $ignoredCodes, true)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// PSR-3 logging framework.
|
||||
try {
|
||||
if ($this->logger) {
|
||||
// 'FE' if in FrontendApplication, else 'BE' (also in CLI without request object)
|
||||
// @todo: We could reconsider this construct with PHP 8.5: It might be possible to register
|
||||
// the exception handler early during bootstrap. Then, later, when a request is available,
|
||||
// get it, and reconfigure exception handler to its final state. This would avoid the runtime
|
||||
// dependency to request including the funny PHP_SAPI fork in handleException().
|
||||
$applicationMode = ($GLOBALS['TYPO3_REQUEST'] ?? null) instanceof ServerRequestInterface
|
||||
&& ApplicationType::fromRequest($GLOBALS['TYPO3_REQUEST'])->isFrontend()
|
||||
? 'FE'
|
||||
: 'BE';
|
||||
$requestUrl = $this->anonymizeToken(NormalizedParams::createFromServerParams($_SERVER)->getRequestUrl());
|
||||
$this->logger->critical('Core: Exception handler ({mode}: {application_mode}): {exception_class}, code #{exception_code}, file {file}, line {line}: {message}', [
|
||||
'mode' => $mode,
|
||||
'application_mode' => $applicationMode,
|
||||
'exception_class' => get_class($exception),
|
||||
'exception_code' => $exception->getCode(),
|
||||
'file' => $exception->getFile(),
|
||||
'line' => $exception->getLine(),
|
||||
'message' => $exception->getMessage(),
|
||||
'request_url' => $requestUrl,
|
||||
'exception' => $this->logExceptionStackTrace ? $exception : null,
|
||||
]);
|
||||
}
|
||||
} catch (\Exception $exception) {
|
||||
// A nested exception here was probably caused by a database failure, which means there's little
|
||||
// else that can be done other than moving on and letting the system hard-fail.
|
||||
}
|
||||
|
||||
// Legacy logger. Remove this section eventually.
|
||||
$filePathAndName = $exception->getFile();
|
||||
$exceptionCodeNumber = $exception->getCode() > 0 ? '#' . $exception->getCode() . ': ' : '';
|
||||
$logTitle = 'Core: Exception handler (' . $mode . ')';
|
||||
$logMessage = 'Uncaught TYPO3 Exception: ' . $exceptionCodeNumber . $exception->getMessage() . ' | '
|
||||
. get_class($exception) . ' thrown in file ' . $filePathAndName . ' in line ' . $exception->getLine();
|
||||
if ($mode === self::CONTEXT_WEB) {
|
||||
$logMessage .= '. Requested URL: ' . $this->anonymizeToken(NormalizedParams::createFromServerParams($_SERVER)->getRequestUrl());
|
||||
}
|
||||
// When database credentials are wrong, the exception is probably
|
||||
// caused by this. Therefore we cannot do any database operation,
|
||||
// otherwise this will lead into recurring exceptions.
|
||||
try {
|
||||
// Write error message to sys_log table
|
||||
$this->writeLog($logTitle . ': ' . $logMessage);
|
||||
} catch (\Throwable $exception) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes an exception in the sys_log table
|
||||
*
|
||||
* @param string $logMessage Default text that follows the message.
|
||||
*/
|
||||
protected function writeLog(string $logMessage)
|
||||
{
|
||||
$connection = GeneralUtility::makeInstance(ConnectionPool::class)
|
||||
->getConnectionForTable('sys_log');
|
||||
|
||||
if (!$connection->isConnected()) {
|
||||
return;
|
||||
}
|
||||
$userId = 0;
|
||||
$workspace = 0;
|
||||
$data = [];
|
||||
$backendUser = $this->getBackendUser();
|
||||
if ($backendUser !== null) {
|
||||
if (isset($backendUser->user['uid'])) {
|
||||
$userId = $backendUser->user['uid'];
|
||||
}
|
||||
$workspace = $backendUser->workspace;
|
||||
if ($backUserId = $backendUser->getOriginalUserIdWhenInSwitchUserMode()) {
|
||||
$data['originalUser'] = $backUserId;
|
||||
}
|
||||
}
|
||||
|
||||
$connection->insert(
|
||||
'sys_log',
|
||||
[
|
||||
'userid' => $userId,
|
||||
'type' => SystemLogType::ERROR,
|
||||
'channel' => SystemLogType::toChannel(SystemLogType::ERROR),
|
||||
'action' => SystemLogGenericAction::UNDEFINED,
|
||||
'error' => SystemLogErrorClassification::SYSTEM_ERROR,
|
||||
'level' => SystemLogType::toLevel(SystemLogType::ERROR),
|
||||
'details' => str_replace('%', '%%', $logMessage),
|
||||
'log_data' => empty($data) ? '' : json_encode($data),
|
||||
'IP' => NormalizedParams::createFromServerParams($_SERVER)->getRemoteAddress(),
|
||||
'tstamp' => $GLOBALS['EXEC_TIME'],
|
||||
'workspace' => $workspace,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends the HTTP Status 500 code, if $exception is *not* a
|
||||
* TYPO3\CMS\Core\Error\Http\StatusException and headers are not sent, yet.
|
||||
*
|
||||
* @param \Throwable $exception The throwable object.
|
||||
*/
|
||||
protected function sendStatusHeaders(\Throwable $exception)
|
||||
{
|
||||
$headers = $exception instanceof StatusException
|
||||
? $exception->getStatusHeaders()
|
||||
: [HttpUtility::HTTP_STATUS_500];
|
||||
if (!headers_sent()) {
|
||||
foreach ($headers as $header) {
|
||||
header($header);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Derives the numeric HTTP status code from the exception.
|
||||
*
|
||||
* Mirrors the logic of {@see sendStatusHeaders()}: returns the status code
|
||||
* from the HTTP status line of a StatusException, or 500 for any other exception.
|
||||
*/
|
||||
protected function getHttpStatusCodeFromException(\Throwable $exception): int
|
||||
{
|
||||
if (!($exception instanceof StatusException)) {
|
||||
return 500;
|
||||
}
|
||||
foreach ($exception->getStatusHeaders() ?? [] as $header) {
|
||||
if (preg_match('/^HTTP\/[\d.]+\s+(\d{3})/', $header, $matches)) {
|
||||
return (int)$matches[1];
|
||||
}
|
||||
}
|
||||
return 500;
|
||||
}
|
||||
|
||||
protected function getBackendUser(): ?BackendUserAuthentication
|
||||
{
|
||||
return $GLOBALS['BE_USER'] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces the generated token with a generic equivalent
|
||||
*/
|
||||
protected function anonymizeToken(string $requestedUrl): string
|
||||
{
|
||||
$pattern = '/(?:(?<=[tT]oken=)|(?<=[tT]oken%3D))[0-9a-fA-F]{40}/';
|
||||
return preg_replace($pattern, '--AnonymizedToken--', $requestedUrl);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,668 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Core\Error;
|
||||
|
||||
use TYPO3\CMS\Core\Core\Environment;
|
||||
use TYPO3\CMS\Core\Information\Typo3Information;
|
||||
|
||||
/**
|
||||
* A basic but solid exception handler which catches everything which
|
||||
* falls through the other exception handlers and provides useful debugging
|
||||
* information.
|
||||
*/
|
||||
class DebugExceptionHandler extends AbstractExceptionHandler
|
||||
{
|
||||
protected bool $logExceptionStackTrace = true;
|
||||
|
||||
/**
|
||||
* Constructs this exception handler - registers itself as the default exception handler.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
set_exception_handler($this->handleException(...));
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats and echoes the exception as XHTML.
|
||||
*
|
||||
* @param \Throwable $exception The throwable object.
|
||||
*/
|
||||
public function echoExceptionWeb(\Throwable $exception)
|
||||
{
|
||||
$this->sendStatusHeaders($exception);
|
||||
$this->writeLogEntries($exception, self::CONTEXT_WEB);
|
||||
|
||||
$content = $this->getContent($exception);
|
||||
$css = $this->getStylesheet();
|
||||
$js = $this->getJavascript();
|
||||
|
||||
echo <<<HTML
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>TYPO3 Exception</title>
|
||||
<meta name="robots" content="noindex,nofollow" />
|
||||
<style>$css</style>
|
||||
<script>$js</script>
|
||||
</head>
|
||||
<body>
|
||||
$content
|
||||
</body>
|
||||
</html>
|
||||
HTML;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats and echoes the exception for the command line
|
||||
*
|
||||
* @param \Throwable $exception The throwable object.
|
||||
*/
|
||||
public function echoExceptionCLI(\Throwable $exception)
|
||||
{
|
||||
$filePathAndName = $exception->getFile();
|
||||
$exceptionCodeNumber = $exception->getCode() > 0 ? '#' . $exception->getCode() . ': ' : '';
|
||||
$this->writeLogEntries($exception, self::CONTEXT_CLI);
|
||||
echo LF . 'Uncaught TYPO3 Exception ' . $exceptionCodeNumber . $exception->getMessage() . LF;
|
||||
echo 'thrown in file ' . $filePathAndName . LF;
|
||||
echo 'in line ' . $exception->getLine() . LF . LF;
|
||||
die(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the HTML for the error output.
|
||||
*/
|
||||
protected function getContent(\Throwable $throwable): string
|
||||
{
|
||||
$content = '';
|
||||
|
||||
// exceptions can be chained
|
||||
// for easier debugging, all exceptions are displayed to the developer
|
||||
$throwables = $this->getAllThrowables($throwable);
|
||||
$count = count($throwables);
|
||||
foreach ($throwables as $position => $e) {
|
||||
$content .= $this->getSingleThrowableContent($e, $position + 1, $count);
|
||||
}
|
||||
|
||||
$exceptionInfo = '';
|
||||
if ($throwable->getCode() > 0) {
|
||||
$documentationLink = Typo3Information::URL_EXCEPTION . 'debug/' . $throwable->getCode();
|
||||
$exceptionInfo = <<<INFO
|
||||
<div class="container">
|
||||
<div class="callout">
|
||||
<div class="callout-content">
|
||||
<div class="callout-title">Get help in the TYPO3 Documentation</div>
|
||||
<div class="callout-body">
|
||||
<p>
|
||||
If you need help solving this exception, you can have a look at the TYPO3 Documentation.
|
||||
There you can find solutions provided by the TYPO3 community.
|
||||
Once you have found a solution to the problem, help others by contributing to the
|
||||
documentation page.
|
||||
</p>
|
||||
<p>
|
||||
<a href="$documentationLink" target="_blank" rel="noreferrer">Find a solution for this exception in the TYPO3 Documentation.</a>
|
||||
<span id="stacktrace-action-buttons"></span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
INFO;
|
||||
}
|
||||
|
||||
$typo3Logo = $this->getTypo3LogoAsSvg();
|
||||
|
||||
try {
|
||||
// This outside dependency class is always loaded before the exception handler is setup.
|
||||
// So it is safe to access without affecting the output of this handler.
|
||||
$projectPath = Environment::getProjectPath() . DIRECTORY_SEPARATOR;
|
||||
} catch (\Throwable) {
|
||||
// just in case something goes wrong.
|
||||
$projectPath = '';
|
||||
}
|
||||
|
||||
$projectPathEscaped = $this->escapeHtml($projectPath);
|
||||
|
||||
return <<<HTML
|
||||
<div class="exception-page" data-project-path="$projectPathEscaped">
|
||||
<div class="exception-summary">
|
||||
<div class="container">
|
||||
<div class="exception-message-wrapper">
|
||||
<div class="exception-illustration hidden-xs-down">$typo3Logo</div>
|
||||
<h1 class="exception-message break-long-words">
|
||||
Whoops, looks like something went wrong.
|
||||
<span id="stacktrace-action-buttons"></span>
|
||||
</h1>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
$exceptionInfo
|
||||
|
||||
<div class="container">
|
||||
$content
|
||||
</div>
|
||||
</div>
|
||||
HTML;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the HTML for a single throwable.
|
||||
*/
|
||||
protected function getSingleThrowableContent(\Throwable $throwable, int $index, int $total): string
|
||||
{
|
||||
$exceptionTitle = get_class($throwable);
|
||||
$exceptionCode = $throwable->getCode() ? '#' . $throwable->getCode() . ' ' : '';
|
||||
$exceptionMessage = $this->escapeHtml($throwable->getMessage());
|
||||
|
||||
// The trace does not contain the step where the exception is thrown.
|
||||
// To display it as well it is added manually to the trace.
|
||||
$trace = $throwable->getTrace();
|
||||
array_unshift($trace, [
|
||||
'file' => $throwable->getFile(),
|
||||
'line' => $throwable->getLine(),
|
||||
'args' => [],
|
||||
]);
|
||||
|
||||
$backtraceCode = $this->getBacktraceCode($trace);
|
||||
|
||||
return <<<HTML
|
||||
<div class="trace">
|
||||
<div class="trace-head">
|
||||
<h3 class="trace-class">
|
||||
<span class="text-variant">({$index}/{$total})</span>
|
||||
<span class="exception-title">{$exceptionCode}{$exceptionTitle}</span>
|
||||
</h3>
|
||||
<p class="trace-message break-long-words">{$exceptionMessage}</p>
|
||||
</div>
|
||||
<div class="trace-body">
|
||||
{$backtraceCode}
|
||||
</div>
|
||||
</div>
|
||||
HTML;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the stylesheet needed to display the error page.
|
||||
*/
|
||||
protected function getStylesheet(): string
|
||||
{
|
||||
return <<<STYLESHEET
|
||||
html {
|
||||
-webkit-text-size-adjust: 100%;
|
||||
-ms-text-size-adjust: 100%;
|
||||
-ms-overflow-style: scrollbar;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.exception-page {
|
||||
background-color: #eaeaea;
|
||||
color: #212121;
|
||||
font-family: -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";
|
||||
font-weight: 400;
|
||||
height: 100dvh;
|
||||
line-height: 1.5;
|
||||
overflow-x: hidden;
|
||||
overflow-y: scroll;
|
||||
text-align: left;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.panel-collapse .exception-page {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.exception-page a {
|
||||
color: #ff8700;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.exception-page a:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.exception-page abbr[title] {
|
||||
border-bottom: none;
|
||||
cursor: help;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.exception-page code,
|
||||
.exception-page kbd,
|
||||
.exception-page pre,
|
||||
.exception-page samp {
|
||||
font-family: SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
.exception-page pre {
|
||||
background-color: #ffffff;
|
||||
overflow-x: auto;
|
||||
border: 1px solid rgba(0,0,0,0.125);
|
||||
}
|
||||
|
||||
.exception-page pre span {
|
||||
display: block;
|
||||
line-height: 1.3em;
|
||||
}
|
||||
|
||||
.exception-page pre span:before {
|
||||
display: inline-block;
|
||||
content: attr(data-line);
|
||||
border-right: 1px solid #b9b9b9;
|
||||
margin-right: 0.5em;
|
||||
padding-right: 0.5em;
|
||||
background-color: #f4f4f4;
|
||||
width: 4em;
|
||||
text-align: right;
|
||||
color: #515151;
|
||||
}
|
||||
|
||||
.exception-page pre span.highlight {
|
||||
background-color: #cce5ff;
|
||||
}
|
||||
|
||||
.exception-page .break-long-words {
|
||||
-ms-word-break: break-all;
|
||||
word-break: break-all;
|
||||
word-break: break-word;
|
||||
-webkit-hyphens: auto;
|
||||
-moz-hyphens: auto;
|
||||
hyphens: auto;
|
||||
}
|
||||
|
||||
.exception-page .callout {
|
||||
padding: 1.5rem;
|
||||
background-color: #fff;
|
||||
margin-bottom: 2em;
|
||||
box-shadow: 0 2px 1px rgba(0,0,0,.15);
|
||||
border-left: 3px solid #8c8c8c;
|
||||
}
|
||||
|
||||
.exception-page .callout-title {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.exception-page .callout-body p:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.exception-page .container {
|
||||
max-width: 1140px;
|
||||
margin: 0 auto;
|
||||
padding: 0 30px;
|
||||
}
|
||||
|
||||
.panel-collapse .exception-page .container {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.exception-page .exception-illustration {
|
||||
width: 3em;
|
||||
height: 3em;
|
||||
float: left;
|
||||
margin-right: 1rem;
|
||||
}
|
||||
|
||||
.exception-page .exception-illustration svg {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.exception-page .exception-illustration svg path {
|
||||
fill: #ff8700;
|
||||
}
|
||||
|
||||
.exception-page .exception-summary {
|
||||
background: #000000;
|
||||
color: #fff;
|
||||
padding: 1.5rem 0;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.exception-page .exception-summary h1 {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.exception-page .text-variant {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.exception-page .trace {
|
||||
background-color: #fff;
|
||||
margin-bottom: 2rem;
|
||||
box-shadow: 0 2px 1px rgba(0,0,0,.15);
|
||||
}
|
||||
|
||||
.exception-page .trace-arguments {
|
||||
color: #8c8c8c;
|
||||
}
|
||||
|
||||
.exception-page .trace-hint {
|
||||
margin: 0 0 0.5rem 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.exception-page .trace-body {
|
||||
}
|
||||
|
||||
.exception-page .trace-call {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.exception-page .trace-class {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.exception-page .trace-file pre {
|
||||
margin-top: 1.5rem;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.exception-page .trace-head {
|
||||
color: #721c24;
|
||||
background-color: #f8d7da;
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
.exception-page .trace-file-path {
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.exception-page .trace-message {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.exception-page .trace-step {
|
||||
padding: 1.5rem;
|
||||
border-bottom: 1px solid #b9b9b9;
|
||||
}
|
||||
|
||||
.exception-page .trace-step > *:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.exception-page .trace-step > *:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.exception-page .trace-step:nth-child(even)
|
||||
{
|
||||
background-color: #fafafa;
|
||||
}
|
||||
|
||||
.exception-page .trace-step:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.exception-page .copy-button {
|
||||
cursor: pointer;
|
||||
border: 0.1rem solid transparent;
|
||||
background-color: transparent;
|
||||
padding: 0;
|
||||
margin-left: 1rem;
|
||||
}
|
||||
|
||||
.exception-page .copy-button:hover {
|
||||
border: 0.1rem solid #b9b9b9;
|
||||
}
|
||||
|
||||
.exception-page #stacktrace-action-buttons {
|
||||
display: inline-flex;
|
||||
justify-content: center;
|
||||
gap: 0.5rem;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.exception-page .stacktrace-action-button {
|
||||
cursor: pointer;
|
||||
padding: 0.5rem;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
-webkit-tap-highlight-color: rgba(0,0,0,0);
|
||||
box-sizing: border-box;
|
||||
background-color: color(srgb 0.97 0.97 0.97);
|
||||
border: 1px solid color(srgb 0.75 0.75 0.75);
|
||||
border-radius: .75em;
|
||||
color: color(srgb 0.1 0.1 0.1);
|
||||
display: inline-flex;
|
||||
font-weight: 400;
|
||||
gap: .35em;
|
||||
justify-content: center;
|
||||
outline-offset: 0;
|
||||
text-decoration: none;
|
||||
--typo3-transition-color: color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out,opacity .15s ease-in-out;
|
||||
transition: var(--typo3-transition-color);
|
||||
user-select: none;
|
||||
vertical-align: middle;
|
||||
white-space: nowrap;
|
||||
margin-bottom: 0;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
pre.plaintextFallback {
|
||||
margin: 2rem auto;
|
||||
border: 1px solid black;
|
||||
max-height: 250px;
|
||||
font-size: 0.8em;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
STYLESHEET;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns JavaScript functionality. Loaded from a TypeScript build.
|
||||
* It is loaded inline, to not need to load additional URIs or build routes to assets.
|
||||
* Also, it does not use ES6 module loading to be light-weight and dependency free.
|
||||
*/
|
||||
protected function getJavascript(): string
|
||||
{
|
||||
return file_get_contents(__DIR__ . '/../../Resources/Public/JavaScript/utility/debug-exception-handler-service.js');
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the backtrace as HTML.
|
||||
*/
|
||||
protected function getBacktraceCode(array $trace): string
|
||||
{
|
||||
$content = '';
|
||||
|
||||
foreach ($trace as $step) {
|
||||
$content .= '<div class="trace-step">';
|
||||
$args = $this->flattenArgs($step['args'] ?? []);
|
||||
|
||||
if (isset($step['function'])) {
|
||||
$content .= '<div class="trace-call">' . sprintf(
|
||||
'at <span class="trace-class">%s</span><span class="trace-type">%s</span><span class="trace-method">%s</span>(<span class="trace-arguments">%s</span>)',
|
||||
$step['class'] ?? '',
|
||||
$step['type'] ?? '',
|
||||
$step['function'],
|
||||
$this->formatArgs($args)
|
||||
) . '</div>';
|
||||
}
|
||||
|
||||
if (isset($step['file']) && isset($step['line'])) {
|
||||
$content .= $this->getCodeSnippet($step['file'], $step['line']);
|
||||
}
|
||||
|
||||
$content .= '</div>';
|
||||
}
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a code snippet from the specified file.
|
||||
*
|
||||
* @param string $filePathAndName Absolute path and file name of the PHP file
|
||||
* @param int $lineNumber Line number defining the center of the code snippet
|
||||
* @return string The code snippet
|
||||
*/
|
||||
protected function getCodeSnippet(string $filePathAndName, int $lineNumber): string
|
||||
{
|
||||
$showLinesAround = 4;
|
||||
|
||||
$content = '<div class="trace-file">';
|
||||
$content .= '<div class="trace-file-head">' . $this->formatPath($filePathAndName, $lineNumber) . '</div>';
|
||||
|
||||
if (@file_exists($filePathAndName)) {
|
||||
$phpFile = @file($filePathAndName);
|
||||
if (is_array($phpFile)) {
|
||||
$startLine = $lineNumber > $showLinesAround ? $lineNumber - $showLinesAround : 1;
|
||||
$phpFileCount = count($phpFile);
|
||||
$endLine = $lineNumber < $phpFileCount - $showLinesAround ? $lineNumber + $showLinesAround + 1 : $phpFileCount + 1;
|
||||
if ($endLine > $startLine) {
|
||||
$content .= '<div class="trace-file-content">';
|
||||
$content .= '<pre>';
|
||||
|
||||
for ($line = $startLine; $line < $endLine; $line++) {
|
||||
$codeLine = str_replace("\t", ' ', $phpFile[$line - 1]);
|
||||
$spanClass = '';
|
||||
if ($line === $lineNumber) {
|
||||
$spanClass = 'highlight';
|
||||
}
|
||||
|
||||
$content .= '<span class="' . $spanClass . '" data-line="' . $line . '">' . $this->escapeHtml($codeLine) . '</span>';
|
||||
}
|
||||
|
||||
$content .= '</pre>';
|
||||
$content .= '</div>';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$content .= '</div>';
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a path adding a line number.
|
||||
*
|
||||
* @param string $path The full path of the file.
|
||||
* @param int $line The line number.
|
||||
*/
|
||||
protected function formatPath(string $path, int $line): string
|
||||
{
|
||||
// "data-lineno" is evaluated by debug-exception-handler-service.js
|
||||
return sprintf(
|
||||
'<span class="block trace-file-path">in <strong data-lineno="%s">%s</strong>%s</span>',
|
||||
$line > 0 ? $line : 1,
|
||||
$this->escapeHtml($path),
|
||||
$line > 0 ? ' line ' . $line : ''
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats the arguments of a method call.
|
||||
*
|
||||
* @param array $args The flattened args of method/function call
|
||||
*/
|
||||
protected function formatArgs(array $args): string
|
||||
{
|
||||
$result = [];
|
||||
foreach ($args as $key => $item) {
|
||||
if ($item[0] === 'object') {
|
||||
$formattedValue = sprintf('<em>object</em>(%s)', $item[1]);
|
||||
} elseif ($item[0] === 'array') {
|
||||
$formattedValue = sprintf('<em>array</em>(%s)', is_array($item[1]) ? $this->formatArgs($item[1]) : $item[1]);
|
||||
} elseif ($item[0] === 'null') {
|
||||
$formattedValue = '<em>null</em>';
|
||||
} elseif ($item[0] === 'boolean') {
|
||||
$formattedValue = '<em>' . strtolower(var_export($item[1], true)) . '</em>';
|
||||
} elseif ($item[0] === 'resource') {
|
||||
$formattedValue = '<em>resource</em>';
|
||||
} else {
|
||||
$formattedValue = str_replace("\n", '', $this->escapeHtml(var_export($item[1], true)));
|
||||
}
|
||||
|
||||
$result[] = is_int($key) ? $formattedValue : sprintf("'%s' => %s", $this->escapeHtml($key), $formattedValue);
|
||||
}
|
||||
|
||||
return implode(', ', $result);
|
||||
}
|
||||
|
||||
protected function flattenArgs(array $args, int $level = 0, int &$count = 0): array
|
||||
{
|
||||
$result = [];
|
||||
foreach ($args as $key => $value) {
|
||||
if (++$count > 1e4) {
|
||||
return ['array', '*SKIPPED over 10000 entries*'];
|
||||
}
|
||||
if ($value instanceof \__PHP_Incomplete_Class) {
|
||||
// is_object() returns false on PHP<=7.1
|
||||
$result[$key] = ['incomplete-object', $this->getClassNameFromIncomplete($value)];
|
||||
} elseif (is_object($value)) {
|
||||
$result[$key] = ['object', get_class($value)];
|
||||
} elseif (is_array($value)) {
|
||||
if ($level > 10) {
|
||||
$result[$key] = ['array', '*DEEP NESTED ARRAY*'];
|
||||
} else {
|
||||
$result[$key] = ['array', $this->flattenArgs($value, $level + 1, $count)];
|
||||
}
|
||||
} elseif ($value === null) {
|
||||
$result[$key] = ['null', null];
|
||||
} elseif (is_bool($value)) {
|
||||
$result[$key] = ['boolean', $value];
|
||||
} elseif (is_int($value)) {
|
||||
$result[$key] = ['integer', $value];
|
||||
} elseif (is_float($value)) {
|
||||
$result[$key] = ['float', $value];
|
||||
} elseif (is_resource($value)) {
|
||||
$result[$key] = ['resource', get_resource_type($value)];
|
||||
} else {
|
||||
$result[$key] = ['string', (string)$value];
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
protected function getClassNameFromIncomplete(\__PHP_Incomplete_Class $value): string
|
||||
{
|
||||
$array = new \ArrayObject($value);
|
||||
|
||||
return $array['__PHP_Incomplete_Class_Name'];
|
||||
}
|
||||
|
||||
protected function escapeHtml(string $str): string
|
||||
{
|
||||
return htmlspecialchars($str, ENT_COMPAT | ENT_SUBSTITUTE);
|
||||
}
|
||||
|
||||
protected function getTypo3LogoAsSvg(): string
|
||||
{
|
||||
return <<<SVG
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path d="M11.1 10.3c-.2 0-.3.1-.5.1C9 10.4 6.8 5 6.8 3.2c0-.7.2-.9.4-1.1-2 .2-4.2.9-4.9 1.8-.2.2-.3.6-.3 1 0 2.8 3 9.2 5.1 9.2 1 0 2.6-1.6 4-3.8m-1-8.4c1.9 0 3.9.3 3.9 1.4 0 2.2-1.4 4.9-2.1 4.9C10.6 8.3 9 4.7 9 2.9c0-.8.3-1 1.1-1"></path></svg>
|
||||
SVG;
|
||||
}
|
||||
|
||||
protected function getAllThrowables(\Throwable $throwable): array
|
||||
{
|
||||
$all = [$throwable];
|
||||
|
||||
while ($throwable = $throwable->getPrevious()) {
|
||||
$all[] = $throwable;
|
||||
}
|
||||
|
||||
return $all;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,290 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Core\Error;
|
||||
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Log\LoggerAwareInterface;
|
||||
use Psr\Log\LoggerAwareTrait;
|
||||
use Psr\Log\LogLevel;
|
||||
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
|
||||
use TYPO3\CMS\Core\Database\ConnectionPool;
|
||||
use TYPO3\CMS\Core\Http\ApplicationType;
|
||||
use TYPO3\CMS\Core\Http\NormalizedParams;
|
||||
use TYPO3\CMS\Core\Log\LogManager;
|
||||
use TYPO3\CMS\Core\Messaging\FlashMessage;
|
||||
use TYPO3\CMS\Core\Messaging\FlashMessageService;
|
||||
use TYPO3\CMS\Core\SysLog\Action as SystemLogGenericAction;
|
||||
use TYPO3\CMS\Core\SysLog\Error as SystemLogErrorClassification;
|
||||
use TYPO3\CMS\Core\SysLog\Type as SystemLogType;
|
||||
use TYPO3\CMS\Core\TimeTracker\TimeTracker;
|
||||
use TYPO3\CMS\Core\Type\ContextualFeedbackSeverity;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Global error handler for TYPO3
|
||||
*
|
||||
* This file is a backport from TYPO3 Flow
|
||||
*/
|
||||
class ErrorHandler implements ErrorHandlerInterface, LoggerAwareInterface
|
||||
{
|
||||
use LoggerAwareTrait;
|
||||
|
||||
protected const ERROR_LEVEL_LABELS = [
|
||||
E_WARNING => 'PHP Warning',
|
||||
E_NOTICE => 'PHP Notice',
|
||||
E_USER_ERROR => 'PHP User Error',
|
||||
E_USER_WARNING => 'PHP User Warning',
|
||||
E_USER_NOTICE => 'PHP User Notice',
|
||||
E_RECOVERABLE_ERROR => 'PHP Catchable Fatal Error',
|
||||
E_USER_DEPRECATED => 'TYPO3 Deprecation Notice',
|
||||
E_DEPRECATED => 'PHP Runtime Deprecation Notice',
|
||||
// @todo: Remove 2048 (deprecated E_STRICT) in v14, as this value is no longer used by PHP itself
|
||||
// and only kept here here because possible custom PHP extensions may still use it.
|
||||
// See https://wiki.php.net/rfc/deprecations_php_8_4#remove_e_strict_error_level_and_deprecate_e_strict_constant
|
||||
2048 /* deprecated E_STRICT */ => 'PHP Runtime Notice',
|
||||
];
|
||||
|
||||
/**
|
||||
* Error levels which should result in an exception thrown.
|
||||
*/
|
||||
protected int $exceptionalErrors = 0;
|
||||
|
||||
/**
|
||||
* Error levels which should be handled.
|
||||
*/
|
||||
protected int $errorHandlerErrors = 0;
|
||||
|
||||
/**
|
||||
* Whether to write a flash message in case of an error
|
||||
*/
|
||||
protected bool $debugMode = false;
|
||||
|
||||
/**
|
||||
* Registers this class as default error handler
|
||||
*
|
||||
* @param int $errorHandlerErrors The integer representing the E_* error level which should be
|
||||
*/
|
||||
public function __construct($errorHandlerErrors)
|
||||
{
|
||||
$excludedErrors = E_COMPILE_WARNING | E_COMPILE_ERROR | E_CORE_WARNING | E_CORE_ERROR | E_PARSE | E_ERROR;
|
||||
// reduces error types to those a custom error handler can process
|
||||
$this->errorHandlerErrors = (int)$errorHandlerErrors & ~$excludedErrors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines which error levels should result in an exception thrown.
|
||||
*
|
||||
* @param int $exceptionalErrors The integer representing the E_* error level to handle as exceptions
|
||||
*/
|
||||
public function setExceptionalErrors($exceptionalErrors)
|
||||
{
|
||||
$exceptionalErrors = (int)$exceptionalErrors;
|
||||
// We always disallow E_USER_DEPRECATED to generate exceptions as this may cause
|
||||
// bad user experience specifically during upgrades.
|
||||
$this->exceptionalErrors = $exceptionalErrors & ~E_USER_DEPRECATED;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $debugMode
|
||||
*/
|
||||
public function setDebugMode($debugMode)
|
||||
{
|
||||
$this->debugMode = (bool)$debugMode;
|
||||
}
|
||||
|
||||
public function registerErrorHandler()
|
||||
{
|
||||
set_error_handler([$this, 'handleError']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles an error.
|
||||
* If the error is registered as exceptionalError it will by converted into an exception, to be handled
|
||||
* by the configured exceptionhandler. Additionally the error message is written to the configured logs.
|
||||
* If application is backend, the error message is also added to the flashMessageQueue, in frontend the
|
||||
* error message is displayed in the admin panel (as TsLog message).
|
||||
*
|
||||
* @param int $errorLevel The error level - one of the E_* constants
|
||||
* @param string $errorMessage The error message
|
||||
* @param string $errorFile Name of the file the error occurred in
|
||||
* @param int $errorLine Line number where the error occurred
|
||||
* @return bool
|
||||
* @throws Exception with the data passed to this method if the error is registered as exceptionalError
|
||||
*/
|
||||
public function handleError($errorLevel, $errorMessage, $errorFile, $errorLine)
|
||||
{
|
||||
// Filter all errors, that should not be reported/ handled from current error reporting
|
||||
$reportingLevel = $this->errorHandlerErrors & error_reporting();
|
||||
// Since symfony does this:
|
||||
// @trigger_error('...', E_USER_DEPRECATED), and we DO want to log these,
|
||||
// we always enforce deprecation messages to be handled, even when they are silenced
|
||||
$reportingLevel |= E_USER_DEPRECATED;
|
||||
$shouldHandleError = (bool)($reportingLevel & $errorLevel);
|
||||
if (!$shouldHandleError) {
|
||||
return self::ERROR_HANDLED;
|
||||
}
|
||||
|
||||
$message = self::ERROR_LEVEL_LABELS[$errorLevel] . ': ' . $errorMessage . ' in ' . $errorFile . ' line ' . $errorLine;
|
||||
if ($errorLevel & $this->exceptionalErrors) {
|
||||
throw new Exception($message, 1476107295);
|
||||
}
|
||||
|
||||
$message = $this->getFormattedLogMessage($message);
|
||||
|
||||
if ($errorLevel === E_USER_DEPRECATED || $errorLevel === E_DEPRECATED) {
|
||||
$logger = GeneralUtility::makeInstance(LogManager::class)->getLogger('TYPO3.CMS.deprecations');
|
||||
$logger->notice($message);
|
||||
return self::ERROR_HANDLED;
|
||||
}
|
||||
|
||||
switch ($errorLevel) {
|
||||
case E_USER_ERROR:
|
||||
case E_RECOVERABLE_ERROR:
|
||||
$logLevel = LogLevel::ERROR;
|
||||
break;
|
||||
case E_USER_WARNING:
|
||||
case E_WARNING:
|
||||
$logLevel = LogLevel::WARNING;
|
||||
break;
|
||||
default:
|
||||
$logLevel = LogLevel::NOTICE;
|
||||
}
|
||||
|
||||
if ($this->logger) {
|
||||
$this->logger->log($logLevel, $message);
|
||||
}
|
||||
|
||||
try {
|
||||
// Write error message to TSlog (admin panel)
|
||||
$this->getTimeTracker()->setTSlogMessage($message, $logLevel);
|
||||
} catch (\Throwable $e) {
|
||||
// Silently catch in case an error occurs before the DI container is in place
|
||||
}
|
||||
// Write error message to sys_log table (ext: belog, Tools->Log)
|
||||
if ($errorLevel & ($GLOBALS['TYPO3_CONF_VARS']['SYS']['belogErrorReporting'] ?? 0)) {
|
||||
// Silently catch in case an error occurs before a database connection exists.
|
||||
try {
|
||||
$this->writeLog($message, $logLevel);
|
||||
} catch (\Exception $e) {
|
||||
}
|
||||
}
|
||||
if ($logLevel === LogLevel::ERROR) {
|
||||
// Let the internal handler continue. This will stop the script
|
||||
return self::PROPAGATE_ERROR;
|
||||
}
|
||||
if ($this->debugMode) {
|
||||
$this->createAndEnqueueFlashMessage($message, $errorLevel);
|
||||
}
|
||||
// Don't execute PHP internal error handler
|
||||
return self::ERROR_HANDLED;
|
||||
}
|
||||
|
||||
protected function createAndEnqueueFlashMessage(string $message, int $errorLevel): void
|
||||
{
|
||||
switch ($errorLevel) {
|
||||
case E_USER_WARNING:
|
||||
case E_WARNING:
|
||||
$flashMessageSeverity = ContextualFeedbackSeverity::WARNING;
|
||||
break;
|
||||
default:
|
||||
$flashMessageSeverity = ContextualFeedbackSeverity::NOTICE;
|
||||
}
|
||||
$flashMessage = new FlashMessage(
|
||||
$message,
|
||||
self::ERROR_LEVEL_LABELS[$errorLevel],
|
||||
$flashMessageSeverity
|
||||
);
|
||||
$flashMessageService = GeneralUtility::makeInstance(FlashMessageService::class);
|
||||
$defaultFlashMessageQueue = $flashMessageService->getMessageQueueByIdentifier();
|
||||
$defaultFlashMessageQueue->enqueue($flashMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes an error in the sys_log table
|
||||
*
|
||||
* @param string $logMessage Default text that follows the message (in english!).
|
||||
* @param string $logLevel The error level, see LogLevel::* constants
|
||||
*/
|
||||
protected function writeLog($logMessage, string $logLevel)
|
||||
{
|
||||
// Avoid ConnectionPool usage prior boot completion (see #96291).
|
||||
if (!GeneralUtility::getContainer()->get('boot.state')->complete) {
|
||||
if ($this->logger) {
|
||||
// Log via debug(), the original message has already been logged with the original serverity in handleError().
|
||||
// This log entry is targeted for users that try to debug why a log record is missing in sys_log
|
||||
// while it has been logged to the logging framework.
|
||||
$this->logger->debug(
|
||||
'An error could not be logged to database as it appeared during early bootstrap (TCA or ext_localconf.php loading).',
|
||||
['original_message' => $logMessage, 'original_loglevel' => $logLevel]
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
$connection = GeneralUtility::makeInstance(ConnectionPool::class)
|
||||
->getConnectionForTable('sys_log');
|
||||
if ($connection->isConnected()) {
|
||||
$userId = 0;
|
||||
$workspace = 0;
|
||||
$data = [];
|
||||
$backendUser = $this->getBackendUser();
|
||||
if (is_object($backendUser)) {
|
||||
if (isset($backendUser->user['uid'])) {
|
||||
$userId = $backendUser->user['uid'];
|
||||
}
|
||||
$workspace = $backendUser->workspace;
|
||||
if ($backUserId = $backendUser->getOriginalUserIdWhenInSwitchUserMode()) {
|
||||
$data['originalUser'] = $backUserId;
|
||||
}
|
||||
}
|
||||
|
||||
$connection->insert(
|
||||
'sys_log',
|
||||
[
|
||||
'userid' => $userId,
|
||||
'type' => SystemLogType::ERROR,
|
||||
'channel' => SystemLogType::toChannel(SystemLogType::ERROR),
|
||||
'action' => SystemLogGenericAction::UNDEFINED,
|
||||
'error' => SystemLogErrorClassification::SYSTEM_ERROR,
|
||||
'level' => $logLevel,
|
||||
'details' => str_replace('%', '%%', $logMessage),
|
||||
'log_data' => empty($data) ? '' : json_encode($data),
|
||||
'IP' => NormalizedParams::createFromServerParams($_SERVER)->getRemoteAddress(),
|
||||
'tstamp' => $GLOBALS['EXEC_TIME'],
|
||||
'workspace' => $workspace,
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
protected function getFormattedLogMessage(string $message): string
|
||||
{
|
||||
// String 'FE' if in FrontendApplication, else 'BE' (also in CLI without request object)
|
||||
$applicationType = ($GLOBALS['TYPO3_REQUEST'] ?? null) instanceof ServerRequestInterface
|
||||
&& ApplicationType::fromRequest($GLOBALS['TYPO3_REQUEST'])->isFrontend() ? 'FE' : 'BE';
|
||||
$logPrefix = 'Core: Error handler (' . $applicationType . ')';
|
||||
return $logPrefix . ': ' . $message;
|
||||
}
|
||||
|
||||
protected function getTimeTracker(): TimeTracker
|
||||
{
|
||||
return GeneralUtility::makeInstance(TimeTracker::class);
|
||||
}
|
||||
|
||||
protected function getBackendUser(): ?BackendUserAuthentication
|
||||
{
|
||||
return $GLOBALS['BE_USER'] ?? null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Core\Error;
|
||||
|
||||
/**
|
||||
* Error handler interface for TYPO3
|
||||
*
|
||||
* This file is a backport from TYPO3 Flow
|
||||
*/
|
||||
interface ErrorHandlerInterface
|
||||
{
|
||||
// Constants to make the implications of the PHP error handling API a bit more obvious.
|
||||
public const ERROR_HANDLED = true;
|
||||
public const PROPAGATE_ERROR = false;
|
||||
|
||||
/**
|
||||
* Registers this class as default error handler
|
||||
*
|
||||
* If dependencies need to be added using injector methods, the error handler may
|
||||
* also be registered later on, within the optional registerErrorHandler() method.
|
||||
*
|
||||
* @param int $errorHandlerErrors The integer representing the E_* error level which should be
|
||||
*/
|
||||
public function __construct($errorHandlerErrors);
|
||||
|
||||
/**
|
||||
* Defines which error levels should result in an exception thrown.
|
||||
*
|
||||
* @param int $exceptionalErrors The integer representing the E_* error level to handle as exceptions
|
||||
*/
|
||||
public function setExceptionalErrors($exceptionalErrors);
|
||||
|
||||
/**
|
||||
* Handles an error.
|
||||
* If the error is registered as exceptionalError it will by converted into an exception, to be handled
|
||||
* by the configured exceptionhandler. Additionally the error message is written to the configured logs.
|
||||
* If application is backend, the error message is also added to the flashMessageQueue, in frontend the
|
||||
* error message is displayed in the admin panel (as TsLog message).
|
||||
*
|
||||
* @param int $errorLevel The error level - one of the E_* constants
|
||||
* @param string $errorMessage The error message
|
||||
* @param string $errorFile Name of the file the error occurred in
|
||||
* @param int $errorLine Line number where the error occurred
|
||||
* @return bool
|
||||
* @throws \TYPO3\CMS\Core\Error\Exception with the data passed to this method if the error is registered as exceptionalError
|
||||
*/
|
||||
public function handleError($errorLevel, $errorMessage, $errorFile, $errorLine);
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Core\Error;
|
||||
|
||||
/**
|
||||
* An exception which represents a PHP error.
|
||||
*/
|
||||
class Exception extends \TYPO3\CMS\Core\Exception {}
|
||||
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Core\Error;
|
||||
|
||||
/**
|
||||
* Contract for an exception handler
|
||||
*
|
||||
* This file is a backport from TYPO3 Flow
|
||||
*/
|
||||
interface ExceptionHandlerInterface
|
||||
{
|
||||
/**
|
||||
* Constructs this exception handler - registers itself as the default exception handler.
|
||||
*/
|
||||
public function __construct();
|
||||
|
||||
/**
|
||||
* Handles the given exception
|
||||
*
|
||||
* @param \Throwable $exception The throwable object.
|
||||
*/
|
||||
public function handleException(\Throwable $exception);
|
||||
|
||||
/**
|
||||
* Formats and echoes the exception as XHTML.
|
||||
*
|
||||
* @param \Throwable $exception The throwable object.
|
||||
*/
|
||||
public function echoExceptionWeb(\Throwable $exception);
|
||||
|
||||
/**
|
||||
* Formats and echoes the exception for the command line
|
||||
*
|
||||
* @param \Throwable $exception The throwable object.
|
||||
*/
|
||||
public function echoExceptionCLI(\Throwable $exception);
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Core\Error\Http;
|
||||
|
||||
/**
|
||||
* HTTP Client Error Exception (Error 4xx)
|
||||
*/
|
||||
abstract class AbstractClientErrorException extends StatusException {}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Core\Error\Http;
|
||||
|
||||
/**
|
||||
* HTTP Server Error Exception (Error 5xx)
|
||||
*/
|
||||
abstract class AbstractServerErrorException extends StatusException {}
|
||||
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Core\Error\Http;
|
||||
|
||||
use TYPO3\CMS\Core\Utility\HttpUtility;
|
||||
|
||||
/**
|
||||
* Exception for Error 400 - Bad Request
|
||||
*/
|
||||
class BadRequestException extends AbstractClientErrorException
|
||||
{
|
||||
/**
|
||||
* @var array HTTP Status Header lines
|
||||
*/
|
||||
protected $statusHeaders = [HttpUtility::HTTP_STATUS_400];
|
||||
|
||||
/**
|
||||
* @var string Title of the message
|
||||
*/
|
||||
protected $title = 'Bad Request (400)';
|
||||
|
||||
/**
|
||||
* @var string Error Message
|
||||
*/
|
||||
protected $message = 'The request cannot be fulfilled due to bad syntax.';
|
||||
|
||||
/**
|
||||
* Constructor for this Status Exception
|
||||
*
|
||||
* @param string $message Error Message
|
||||
* @param int $code Exception Code
|
||||
*/
|
||||
public function __construct($message = null, $code = 0)
|
||||
{
|
||||
if (!empty($message)) {
|
||||
$this->message = $message;
|
||||
}
|
||||
parent::__construct($this->statusHeaders, $this->message, $this->title, $code);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Core\Error\Http;
|
||||
|
||||
use TYPO3\CMS\Core\Utility\HttpUtility;
|
||||
|
||||
/**
|
||||
* Exception for Error 403 - Forbidden
|
||||
*/
|
||||
class ForbiddenException extends AbstractClientErrorException
|
||||
{
|
||||
/**
|
||||
* @var array HTTP Status Header lines
|
||||
*/
|
||||
protected $statusHeaders = [HttpUtility::HTTP_STATUS_403];
|
||||
|
||||
/**
|
||||
* @var string Title of the message
|
||||
*/
|
||||
protected $title = 'Forbidden (403)';
|
||||
|
||||
/**
|
||||
* @var string Error Message
|
||||
*/
|
||||
protected $message = 'You are not allowed to access this page.';
|
||||
|
||||
/**
|
||||
* Constructor for this Status Exception
|
||||
*
|
||||
* @param string $message Error Message
|
||||
* @param int $code Exception Code
|
||||
*/
|
||||
public function __construct($message = null, $code = 0)
|
||||
{
|
||||
if (!empty($message)) {
|
||||
$this->message = $message;
|
||||
}
|
||||
parent::__construct($this->statusHeaders, $this->message, $this->title, $code);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Core\Error\Http;
|
||||
|
||||
use TYPO3\CMS\Core\Utility\HttpUtility;
|
||||
|
||||
/**
|
||||
* Exception for Error 500 - Internal Server Error
|
||||
*/
|
||||
class InternalServerErrorException extends AbstractServerErrorException
|
||||
{
|
||||
/**
|
||||
* @var array HTTP Status Header lines
|
||||
*/
|
||||
protected $statusHeaders = [HttpUtility::HTTP_STATUS_500];
|
||||
|
||||
/**
|
||||
* @var string Title of the message
|
||||
*/
|
||||
protected $title = 'Internal Server Error (500)';
|
||||
|
||||
/**
|
||||
* @var string Error Message
|
||||
*/
|
||||
protected $message = 'This page is currently not available due to server errors.';
|
||||
|
||||
/**
|
||||
* Constructor for this Status Exception
|
||||
*
|
||||
* @param string $message Error Message
|
||||
* @param int $code Exception Code
|
||||
*/
|
||||
public function __construct($message = null, $code = 0)
|
||||
{
|
||||
if (!empty($message)) {
|
||||
$this->message = $message;
|
||||
}
|
||||
parent::__construct($this->statusHeaders, $this->message, $this->title, $code);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Core\Error\Http;
|
||||
|
||||
/**
|
||||
* Excepts if typolink could not be resolved for link pages.
|
||||
*/
|
||||
class LinkedPageNotResolvableException extends PageNotFoundException {}
|
||||
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Core\Error\Http;
|
||||
|
||||
use TYPO3\CMS\Core\Utility\HttpUtility;
|
||||
|
||||
/**
|
||||
* Exception for Error 404 - Page Not Found
|
||||
*/
|
||||
class PageNotFoundException extends AbstractClientErrorException
|
||||
{
|
||||
/**
|
||||
* @var array HTTP Status Header lines
|
||||
*/
|
||||
protected $statusHeaders = [HttpUtility::HTTP_STATUS_404];
|
||||
|
||||
/**
|
||||
* @var string Title of the message
|
||||
*/
|
||||
protected $title = 'Page Not Found (404)';
|
||||
|
||||
/**
|
||||
* @var string Error Message
|
||||
*/
|
||||
protected $message = 'The page you tried to access was not found.';
|
||||
|
||||
/**
|
||||
* Constructor for this Status Exception
|
||||
*
|
||||
* @param string $message Error Message
|
||||
* @param int $code Exception Code
|
||||
*/
|
||||
public function __construct($message = null, $code = 0)
|
||||
{
|
||||
if (!empty($message)) {
|
||||
$this->message = $message;
|
||||
}
|
||||
parent::__construct($this->statusHeaders, $this->message, $this->title, $code);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Core\Error\Http;
|
||||
|
||||
use TYPO3\CMS\Core\Utility\HttpUtility;
|
||||
|
||||
/**
|
||||
* Exception for Error 503 - Service Unavailable
|
||||
*/
|
||||
class ServiceUnavailableException extends AbstractServerErrorException
|
||||
{
|
||||
/**
|
||||
* @var array HTTP Status Header lines
|
||||
*/
|
||||
protected $statusHeaders = [HttpUtility::HTTP_STATUS_503];
|
||||
|
||||
/**
|
||||
* @var string Title of the message
|
||||
*/
|
||||
protected $title = 'Service Unavailable (503)';
|
||||
|
||||
/**
|
||||
* @var string Error Message
|
||||
*/
|
||||
protected $message = 'This page is currently not available.';
|
||||
|
||||
/**
|
||||
* Constructor for this Status Exception
|
||||
*
|
||||
* @param string $message Error Message
|
||||
* @param int $code Exception Code
|
||||
*/
|
||||
public function __construct($message = null, $code = 0)
|
||||
{
|
||||
if (!empty($message)) {
|
||||
$this->message = $message;
|
||||
}
|
||||
parent::__construct($this->statusHeaders, $this->message, $this->title, $code);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Core\Error\Http;
|
||||
|
||||
/**
|
||||
* Exception when a shortcut target page could not be resolved
|
||||
*/
|
||||
class ShortcutTargetPageNotFoundException extends PageNotFoundException {}
|
||||
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Core\Error\Http;
|
||||
|
||||
use TYPO3\CMS\Core\Error\Exception;
|
||||
|
||||
/**
|
||||
* HTTP Status Exception
|
||||
*
|
||||
* @todo: Rename to AbstractStatusException and declare abstract
|
||||
*/
|
||||
class StatusException extends Exception
|
||||
{
|
||||
/**
|
||||
* @var array HTTP Status Header lines
|
||||
*/
|
||||
protected $statusHeaders;
|
||||
|
||||
/**
|
||||
* @var string Title of the message
|
||||
*/
|
||||
protected $title = 'Oops, an error occurred!';
|
||||
|
||||
/**
|
||||
* Constructor for this Status Exception
|
||||
*
|
||||
* @param string|array $statusHeaders HTTP Status header line(s)
|
||||
* @param string $title Title of the error message
|
||||
* @param string $message Error Message
|
||||
* @param int $code Exception Code
|
||||
*/
|
||||
public function __construct($statusHeaders, $message, $title = '', $code = 0)
|
||||
{
|
||||
if (is_array($statusHeaders)) {
|
||||
$this->statusHeaders = $statusHeaders;
|
||||
} else {
|
||||
$this->statusHeaders = [$statusHeaders];
|
||||
}
|
||||
$this->title = $title ?: $this->title;
|
||||
parent::__construct($message, $code);
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter for the title.
|
||||
*
|
||||
* @param string $title
|
||||
*/
|
||||
public function setTitle($title)
|
||||
{
|
||||
$this->title = $title;
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter for the title.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getTitle()
|
||||
{
|
||||
return $this->title;
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter for the Status Header.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getStatusHeaders()
|
||||
{
|
||||
return $this->statusHeaders;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Core\Error\Http;
|
||||
|
||||
use TYPO3\CMS\Core\Utility\HttpUtility;
|
||||
|
||||
/**
|
||||
* Exception for Error 401 - Unauthorized
|
||||
*/
|
||||
class UnauthorizedException extends AbstractClientErrorException
|
||||
{
|
||||
/**
|
||||
* @var array HTTP Status Header lines
|
||||
*/
|
||||
protected $statusHeaders = [HttpUtility::HTTP_STATUS_401];
|
||||
|
||||
/**
|
||||
* @var string Title of the message
|
||||
*/
|
||||
protected $title = 'Unauthorized (401)';
|
||||
|
||||
/**
|
||||
* @var string Error Message
|
||||
*/
|
||||
protected $message = 'Accessing this page requires authorization.';
|
||||
|
||||
/**
|
||||
* Constructor for this Status Exception
|
||||
*
|
||||
* @param string $message Error Message
|
||||
* @param int $code Exception Code
|
||||
*/
|
||||
public function __construct($message = null, $code = 0)
|
||||
{
|
||||
if (!empty($message)) {
|
||||
$this->message = $message;
|
||||
}
|
||||
parent::__construct($this->statusHeaders, $this->message, $this->title, $code);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Core\Error\PageErrorHandler;
|
||||
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use TYPO3\CMS\Core\Http\HtmlResponse;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Core\View\ViewFactoryData;
|
||||
use TYPO3\CMS\Core\View\ViewFactoryInterface;
|
||||
|
||||
/**
|
||||
* An error handler that renders a fluid template.
|
||||
* This is typically configured via the "Sites configuration" module in the backend.
|
||||
*/
|
||||
class FluidPageErrorHandler implements PageErrorHandlerInterface
|
||||
{
|
||||
/**
|
||||
* @todo: Change this "API" to not pollute __construct() anymore
|
||||
*/
|
||||
public function __construct(
|
||||
protected int $statusCode,
|
||||
protected array $configuration
|
||||
) {}
|
||||
|
||||
public function handlePageError(ServerRequestInterface $request, string $message, array $reasons = []): ResponseInterface
|
||||
{
|
||||
$configuration = $this->configuration;
|
||||
$templateRootPaths = null;
|
||||
if (is_string($configuration['errorFluidTemplatesRootPath'] ?? false) && $configuration['errorFluidTemplatesRootPath'] !== '') {
|
||||
$templateRootPaths = [$configuration['errorFluidTemplatesRootPath']];
|
||||
}
|
||||
$layoutRootPaths = null;
|
||||
if (is_string($configuration['errorFluidLayoutsRootPath'] ?? false) && $configuration['errorFluidLayoutsRootPath'] !== '') {
|
||||
$layoutRootPaths = [$configuration['errorFluidLayoutsRootPath']];
|
||||
}
|
||||
$partialRootPaths = null;
|
||||
if (is_string($configuration['errorFluidPartialsRootPath'] ?? false) && $configuration['errorFluidPartialsRootPath'] !== '') {
|
||||
$partialRootPaths = [$configuration['errorFluidPartialsRootPath']];
|
||||
}
|
||||
$templatePathAndFilename = null;
|
||||
if (is_string($configuration['errorFluidTemplate'] ?? false) && $configuration['errorFluidTemplate'] !== '') {
|
||||
$templatePathAndFilename = GeneralUtility::getFileAbsFileName($configuration['errorFluidTemplate']);
|
||||
}
|
||||
if ($templatePathAndFilename === null || !is_file($templatePathAndFilename)) {
|
||||
throw new \RuntimeException('FluidPageErrorHandler: Configured Fluid template file not found.', 1764510148);
|
||||
}
|
||||
|
||||
$viewFactoryDate = new ViewFactoryData(
|
||||
templateRootPaths: $templateRootPaths,
|
||||
partialRootPaths: $partialRootPaths,
|
||||
layoutRootPaths: $layoutRootPaths,
|
||||
templatePathAndFilename: $templatePathAndFilename,
|
||||
request: $request,
|
||||
);
|
||||
$viewFactory = GeneralUtility::makeInstance(ViewFactoryInterface::class);
|
||||
$view = $viewFactory->create($viewFactoryDate);
|
||||
$view->assignMultiple([
|
||||
'request' => $request,
|
||||
'message' => $message,
|
||||
'reasons' => $reasons,
|
||||
]);
|
||||
return new HtmlResponse($view->render(), $this->statusCode);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Core\Error\PageErrorHandler;
|
||||
|
||||
use TYPO3\CMS\Core\Error\Exception;
|
||||
|
||||
/**
|
||||
* Is typically used, when a site configuration has a page-error handler configured but this does not implement
|
||||
* the PageErrorHandlerInterface
|
||||
*/
|
||||
class InvalidPageErrorHandlerException extends Exception {}
|
||||
@@ -0,0 +1,241 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Core\Error\PageErrorHandler;
|
||||
|
||||
use GuzzleHttp\Exception\GuzzleException;
|
||||
use Psr\Http\Message\RequestFactoryInterface;
|
||||
use Psr\Http\Message\ResponseFactoryInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use TYPO3\CMS\Core\Exception\SiteNotFoundException;
|
||||
use TYPO3\CMS\Core\Http\Client\GuzzleClientFactory;
|
||||
use TYPO3\CMS\Core\Http\HtmlResponse;
|
||||
use TYPO3\CMS\Core\Http\Uri;
|
||||
use TYPO3\CMS\Core\LinkHandling\LinkService;
|
||||
use TYPO3\CMS\Core\Routing\InvalidRouteArgumentsException;
|
||||
use TYPO3\CMS\Core\Site\Entity\Site;
|
||||
use TYPO3\CMS\Core\Site\Entity\SiteLanguage;
|
||||
use TYPO3\CMS\Core\Site\SiteFinder;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Frontend\Http\Application;
|
||||
|
||||
/**
|
||||
* Renders the content of a page to be displayed (also in relation to language etc)
|
||||
* This is typically configured via the "Sites configuration" module in the backend.
|
||||
*/
|
||||
class PageContentErrorHandler implements PageErrorHandlerInterface
|
||||
{
|
||||
protected int $statusCode;
|
||||
protected array $errorHandlerConfiguration;
|
||||
protected int $pageUid = 0;
|
||||
protected Application $application;
|
||||
protected ResponseFactoryInterface $responseFactory;
|
||||
protected SiteFinder $siteFinder;
|
||||
protected LinkService $link;
|
||||
protected RequestFactoryInterface $requestFactory;
|
||||
protected GuzzleClientFactory $guzzleClientFactory;
|
||||
|
||||
/**
|
||||
* PageContentErrorHandler constructor.
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function __construct(int $statusCode, array $configuration)
|
||||
{
|
||||
$this->statusCode = $statusCode;
|
||||
if (empty($configuration['errorContentSource'])) {
|
||||
throw new \InvalidArgumentException('PageContentErrorHandler needs to have a proper link set.', 1522826413);
|
||||
}
|
||||
$this->errorHandlerConfiguration = $configuration;
|
||||
|
||||
// @todo Convert this to DI once this class can be injected properly.
|
||||
$container = GeneralUtility::getContainer();
|
||||
$this->application = $container->get(Application::class);
|
||||
$this->responseFactory = $container->get(ResponseFactoryInterface::class);
|
||||
$this->siteFinder = GeneralUtility::makeInstance(SiteFinder::class);
|
||||
$this->link = $container->get(LinkService::class);
|
||||
$this->requestFactory = $container->get(RequestFactoryInterface::class);
|
||||
$this->guzzleClientFactory = $container->get(GuzzleClientFactory::class);
|
||||
}
|
||||
|
||||
public function handlePageError(ServerRequestInterface $request, string $message, array $reasons = []): ResponseInterface
|
||||
{
|
||||
try {
|
||||
$urlParams = $this->link->resolve($this->errorHandlerConfiguration['errorContentSource']);
|
||||
$urlParams['pageuid'] = (int)($urlParams['pageuid'] ?? 0);
|
||||
$urlType = $urlParams['type'] ?? LinkService::TYPE_UNKNOWN;
|
||||
$resolvedUrl = $this->resolveUrl($request, $urlParams);
|
||||
|
||||
// avoid denial-of-service amplification scenario
|
||||
if ($resolvedUrl === (string)$request->getUri()) {
|
||||
return new HtmlResponse(
|
||||
'The error page could not be resolved, as the error page itself is not accessible',
|
||||
$this->statusCode
|
||||
);
|
||||
}
|
||||
// External URL most likely pointing to additional hosts or pages not contained in the current instance,
|
||||
// and using internal sub requests would never receive a valid page. Send an external request instead.
|
||||
if ($urlType === LinkService::TYPE_URL) {
|
||||
return $this->sendExternalRequest($resolvedUrl, $request);
|
||||
}
|
||||
// Create a sub-request and do not take any special query parameters into account
|
||||
$subRequest = $request->withQueryParams([])->withUri(new Uri($resolvedUrl))->withMethod('GET');
|
||||
$subResponse = $this->sendSubRequest($subRequest, $urlParams['pageuid'], $request);
|
||||
|
||||
if ($subResponse->getStatusCode() >= 300) {
|
||||
throw new \RuntimeException(sprintf('Error handler could not fetch error page "%s", status code: %s', $resolvedUrl, $subResponse->getStatusCode()), 1544172839);
|
||||
}
|
||||
|
||||
$response = $this->responseFactory->createResponse($this->statusCode)
|
||||
->withHeader('content-type', $subResponse->getHeader('content-type'))
|
||||
->withBody($subResponse->getBody());
|
||||
|
||||
foreach (['Content-Security-Policy', 'Content-Security-Policy-Report-Only'] as $header) {
|
||||
if ($subResponse->hasHeader($header)) {
|
||||
$response = $response->withHeader($header, $subResponse->getHeader($header));
|
||||
}
|
||||
}
|
||||
return $response;
|
||||
} catch (InvalidRouteArgumentsException|SiteNotFoundException $e) {
|
||||
return new HtmlResponse('Invalid error handler configuration: ' . $this->errorHandlerConfiguration['errorContentSource']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends an in-process subrequest.
|
||||
*
|
||||
* The $pageId is used to ensure the correct site is accessed.
|
||||
*/
|
||||
protected function sendSubRequest(ServerRequestInterface $request, int $pageId, ServerRequestInterface $originalRequest): ResponseInterface
|
||||
{
|
||||
$site = $request->getAttribute('site');
|
||||
if (!$site instanceof Site) {
|
||||
$site = $this->siteFinder->getSiteByPageId($pageId);
|
||||
$request = $request->withAttribute('site', $site);
|
||||
}
|
||||
|
||||
$request = $request->withAttribute('originalRequest', $originalRequest);
|
||||
|
||||
return $this->application->handle($request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends an external request to fetch the error page from a remote resource.
|
||||
*
|
||||
* A custom header is added and checked to mitigate request loops, which
|
||||
* indicates additional configuration error in the error handler config.
|
||||
*/
|
||||
protected function sendExternalRequest(string $url, ServerRequestInterface $originalRequest): ResponseInterface
|
||||
{
|
||||
if ($originalRequest->hasHeader('Requested-By')
|
||||
&& in_array('TYPO3 Error Handler', $originalRequest->getHeader('Requested-By'), true)
|
||||
) {
|
||||
// If the header is set here, it is a recursive call within the same instance where an
|
||||
// outer error handler called a page that results in another error handler call. To break
|
||||
// the loop, we except here.
|
||||
return new HtmlResponse(
|
||||
'The error page could not be resolved, the error page itself is not accessible',
|
||||
$this->statusCode
|
||||
);
|
||||
}
|
||||
try {
|
||||
$request = $this->requestFactory->createRequest('GET', $url)
|
||||
->withHeader('Content-Type', 'text/html')
|
||||
->withHeader('Requested-By', 'TYPO3 Error Handler');
|
||||
$response = $this->guzzleClientFactory->getClient()->send($request);
|
||||
// In case global guzzle configuration has been changed to not throw an exception
|
||||
// for error status codes, the response status code is checked here.
|
||||
if ($response->getStatusCode() >= 300) {
|
||||
return new HtmlResponse(
|
||||
'The error page could not be resolved, as the error page itself is not accessible',
|
||||
$this->statusCode
|
||||
);
|
||||
}
|
||||
return $this->responseFactory
|
||||
->createResponse($this->statusCode)
|
||||
->withHeader('Content-Type', $response->getHeader('Content-Type'))
|
||||
->withBody($response->getBody());
|
||||
} catch (GuzzleException) {
|
||||
return new HtmlResponse(
|
||||
'The error page could not be resolved, the error page itself is not accessible',
|
||||
$this->statusCode
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the URL (currently only page and external URL are supported)
|
||||
*/
|
||||
protected function resolveUrl(ServerRequestInterface $request, array $urlParams): string
|
||||
{
|
||||
if (!in_array($urlParams['type'], ['page', 'url'])) {
|
||||
throw new \InvalidArgumentException('PageContentErrorHandler can only handle TYPO3 URLs of types "page" or "url"', 1522826609);
|
||||
}
|
||||
if ($urlParams['type'] === 'url') {
|
||||
return $urlParams['url'];
|
||||
}
|
||||
|
||||
// Get the site related to the configured error page
|
||||
$site = $this->siteFinder->getSiteByPageId($urlParams['pageuid']);
|
||||
$requestLanguage = $request->getAttribute('language');
|
||||
// Try to get the current request language from the site that was found above
|
||||
if ($requestLanguage instanceof SiteLanguage && $requestLanguage->isEnabled()) {
|
||||
try {
|
||||
$language = $site->getLanguageById($requestLanguage->getLanguageId());
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
$language = $site->getDefaultLanguage();
|
||||
}
|
||||
} else {
|
||||
$language = $site->getDefaultLanguage();
|
||||
}
|
||||
|
||||
// Requested language or default language is disabled in current site => Fetch first "enabled" language
|
||||
if (!$language->isEnabled()) {
|
||||
$enabledLanguages = $site->getLanguages();
|
||||
if ($enabledLanguages === []) {
|
||||
throw new \RuntimeException(
|
||||
'Site ' . $site->getIdentifier() . ' does not define any enabled language.',
|
||||
1674487171
|
||||
);
|
||||
}
|
||||
$language = reset($enabledLanguages);
|
||||
}
|
||||
|
||||
// Build Url
|
||||
$uri = $site->getRouter()->generateUri(
|
||||
(int)$urlParams['pageuid'],
|
||||
['_language' => $language]
|
||||
);
|
||||
|
||||
// Fallback to the current URL if the site is not having a proper scheme and host
|
||||
$currentUri = $request->getUri();
|
||||
if (empty($uri->getScheme())) {
|
||||
$uri = $uri->withScheme($currentUri->getScheme());
|
||||
}
|
||||
if (empty($uri->getUserInfo())) {
|
||||
$uri = $uri->withUserInfo($currentUri->getUserInfo());
|
||||
}
|
||||
if (empty($uri->getHost())) {
|
||||
$uri = $uri->withHost($currentUri->getHost());
|
||||
}
|
||||
if ($uri->getPort() === null) {
|
||||
$uri = $uri->withPort($currentUri->getPort());
|
||||
}
|
||||
|
||||
return (string)$uri;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Core\Error\PageErrorHandler;
|
||||
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
|
||||
/**
|
||||
* Page error handler interface, used to jump in for Frontend-related calls
|
||||
*
|
||||
* Needs to be implemented by all custom PHP-related Page Error Handlers.
|
||||
*/
|
||||
interface PageErrorHandlerInterface
|
||||
{
|
||||
/**
|
||||
* @param array<string, mixed> $reasons
|
||||
*/
|
||||
public function handlePageError(ServerRequestInterface $request, string $message, array $reasons = []): ResponseInterface;
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Core\Error\PageErrorHandler;
|
||||
|
||||
use TYPO3\CMS\Core\Error\Exception;
|
||||
|
||||
/**
|
||||
* Is typically used, when a site configuration has no page-error handler configured
|
||||
* for a specific HTTP Status type that is requested.
|
||||
*/
|
||||
class PageErrorHandlerNotConfiguredException extends Exception {}
|
||||
@@ -0,0 +1,140 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Core\Error\PageErrorHandler;
|
||||
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use TYPO3\CMS\Core\Context\Context;
|
||||
use TYPO3\CMS\Core\Controller\ErrorPageController;
|
||||
use TYPO3\CMS\Core\Http\HtmlResponse;
|
||||
use TYPO3\CMS\Core\Http\RedirectResponse;
|
||||
use TYPO3\CMS\Core\LinkHandling\LinkService;
|
||||
use TYPO3\CMS\Core\Site\Entity\Site;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
use TYPO3\CMS\Frontend\Page\PageAccessFailureReasons;
|
||||
|
||||
/**
|
||||
* An error handler that redirects to a configured page, where the login process is handled. Passes a configurable
|
||||
* url parameter (`return_url` or `redirect_url`) to the target page.
|
||||
*/
|
||||
class RedirectLoginErrorHandler implements PageErrorHandlerInterface
|
||||
{
|
||||
protected int $loginRedirectPid = 0;
|
||||
protected int $statusCode = 0;
|
||||
protected string $loginRedirectParameter = '';
|
||||
protected Context $context;
|
||||
protected LinkService $linkService;
|
||||
|
||||
public function __construct(int $statusCode, array $configuration)
|
||||
{
|
||||
$this->statusCode = $statusCode;
|
||||
$this->context = GeneralUtility::makeInstance(Context::class);
|
||||
$this->linkService = GeneralUtility::makeInstance(LinkService::class);
|
||||
|
||||
$urlParams = $this->linkService->resolve($configuration['loginRedirectTarget'] ?? '');
|
||||
$this->loginRedirectPid = (int)($urlParams['pageuid'] ?? 0);
|
||||
$this->loginRedirectParameter = $configuration['loginRedirectParameter'] ?? 'return_url';
|
||||
}
|
||||
|
||||
public function handlePageError(
|
||||
ServerRequestInterface $request,
|
||||
string $message,
|
||||
array $reasons = []
|
||||
): ResponseInterface {
|
||||
$this->checkHandlerConfiguration();
|
||||
|
||||
if ($this->shouldHandleRequest($reasons)) {
|
||||
return $this->handleLoginRedirect($request);
|
||||
}
|
||||
|
||||
// Show general error message with a 403 HTTP statuscode
|
||||
return $this->getGenericAccessDeniedResponse($message);
|
||||
}
|
||||
|
||||
private function getGenericAccessDeniedResponse(string $reason): ResponseInterface
|
||||
{
|
||||
$content = GeneralUtility::makeInstance(ErrorPageController::class)->errorAction(
|
||||
'Page Not Found',
|
||||
'The page did not exist or was inaccessible.' . ($reason ? ' Reason: ' . $reason : ''),
|
||||
0,
|
||||
$this->statusCode,
|
||||
);
|
||||
return new HtmlResponse($content, $this->statusCode);
|
||||
}
|
||||
|
||||
private function handleLoginRedirect(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
if ($this->isLoggedIn()) {
|
||||
return $this->getGenericAccessDeniedResponse(
|
||||
'The requested page was not accessible with the provided credentials'
|
||||
);
|
||||
}
|
||||
|
||||
/** @var Site $site */
|
||||
$site = $request->getAttribute('site');
|
||||
$language = $request->getAttribute('language');
|
||||
|
||||
$loginUrl = $site->getRouter()->generateUri(
|
||||
$this->loginRedirectPid,
|
||||
[
|
||||
'_language' => $language,
|
||||
$this->loginRedirectParameter => (string)$request->getUri(),
|
||||
]
|
||||
);
|
||||
|
||||
return new RedirectResponse($loginUrl);
|
||||
}
|
||||
|
||||
private function shouldHandleRequest(array $reasons): bool
|
||||
{
|
||||
if (!isset($reasons['code'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$accessDeniedReasons = [
|
||||
PageAccessFailureReasons::ACCESS_DENIED_PAGE_NOT_RESOLVED,
|
||||
PageAccessFailureReasons::ACCESS_DENIED_SUBSECTION_NOT_RESOLVED,
|
||||
];
|
||||
$isAccessDenied = in_array($reasons['code'], $accessDeniedReasons, true);
|
||||
|
||||
return $isAccessDenied || $this->isSimulatedBackendGroup();
|
||||
}
|
||||
|
||||
private function isLoggedIn(): bool
|
||||
{
|
||||
return $this->context->getPropertyFromAspect('frontend.user', 'isLoggedIn') || $this->isSimulatedBackendGroup();
|
||||
}
|
||||
|
||||
protected function isSimulatedBackendGroup(): bool
|
||||
{
|
||||
// look for special "any group"
|
||||
return $this->context->getPropertyFromAspect('backend.user', 'isLoggedIn')
|
||||
&& $this->context->getPropertyFromAspect('frontend.user', 'groupIds')[1] === -2;
|
||||
}
|
||||
|
||||
private function checkHandlerConfiguration(): void
|
||||
{
|
||||
if ($this->loginRedirectPid === 0) {
|
||||
throw new \RuntimeException('No loginRedirectTarget configured for LoginRedirect errorhandler', 1700813537);
|
||||
}
|
||||
|
||||
if ($this->statusCode !== 403) {
|
||||
throw new \RuntimeException('Invalid HTTP statuscode ' . $this->statusCode . ' for LoginRedirect errorhandler', 1700813545);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Core\Error;
|
||||
|
||||
use TYPO3\CMS\Core\Controller\ErrorPageController;
|
||||
use TYPO3\CMS\Core\Error\Http\AbstractClientErrorException;
|
||||
use TYPO3\CMS\Core\Error\Http\StatusException;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* An exception handler which catches any exception and
|
||||
* renders an error page without backtrace (Web) or a slim
|
||||
* message on CLI.
|
||||
*/
|
||||
class ProductionExceptionHandler extends AbstractExceptionHandler
|
||||
{
|
||||
/**
|
||||
* Default title for error messages
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $defaultTitle = 'Oops, an error occurred!';
|
||||
|
||||
/**
|
||||
* Default message for error messages
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $defaultMessage = '';
|
||||
|
||||
/**
|
||||
* Constructs this exception handler - registers itself as the default exception handler.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
set_exception_handler($this->handleException(...));
|
||||
}
|
||||
|
||||
/**
|
||||
* Echoes an exception for the web.
|
||||
*
|
||||
* @param \Throwable $exception The throwable object.
|
||||
*/
|
||||
public function echoExceptionWeb(\Throwable $exception)
|
||||
{
|
||||
$this->sendStatusHeaders($exception);
|
||||
$this->writeLogEntries($exception, self::CONTEXT_WEB);
|
||||
echo GeneralUtility::makeInstance(ErrorPageController::class)->errorAction(
|
||||
$this->getTitle($exception),
|
||||
$this->getMessage($exception),
|
||||
$this->discloseExceptionInformation($exception) ? $exception->getCode() : 0,
|
||||
$this->getHttpStatusCodeFromException($exception)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Echoes an exception for the command line.
|
||||
*
|
||||
* @param \Throwable $exception The throwable object.
|
||||
*/
|
||||
public function echoExceptionCLI(\Throwable $exception)
|
||||
{
|
||||
$filePathAndName = $exception->getFile();
|
||||
$exceptionCodeNumber = $exception->getCode() > 0 ? '#' . $exception->getCode() . ': ' : '';
|
||||
$this->writeLogEntries($exception, self::CONTEXT_CLI);
|
||||
echo LF . 'Uncaught TYPO3 Exception ' . $exceptionCodeNumber . $exception->getMessage() . LF;
|
||||
echo 'thrown in file ' . $filePathAndName . LF;
|
||||
echo 'in line ' . $exception->getLine() . LF . LF;
|
||||
die(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines, whether Exception details should be outputted
|
||||
*
|
||||
* @param \Throwable $exception The throwable object.
|
||||
* @return bool
|
||||
*/
|
||||
protected function discloseExceptionInformation(\Throwable $exception)
|
||||
{
|
||||
// Allow message to be shown in production mode if the exception is about
|
||||
// trusted host configuration. By doing so we do not disclose
|
||||
// any valuable information to an attacker but avoid confusions among TYPO3 admins
|
||||
// in production context.
|
||||
if ($exception->getCode() === 1396795884) {
|
||||
return true;
|
||||
}
|
||||
// Show client error messages 40x in every case
|
||||
if ($exception instanceof AbstractClientErrorException) {
|
||||
return true;
|
||||
}
|
||||
// Only show errors if a BE user is authenticated
|
||||
$backendUser = $this->getBackendUser();
|
||||
if ($backendUser === null) {
|
||||
return false;
|
||||
}
|
||||
return ($backendUser->user['uid'] ?? 0) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the title for the error message
|
||||
*
|
||||
* @param \Throwable $exception The throwable object.
|
||||
* @return string
|
||||
*/
|
||||
protected function getTitle(\Throwable $exception)
|
||||
{
|
||||
if ($this->discloseExceptionInformation($exception) && $exception instanceof StatusException && $exception->getTitle() !== '') {
|
||||
return $exception->getTitle();
|
||||
}
|
||||
return $this->defaultTitle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the message for the error message
|
||||
*
|
||||
* @param \Throwable $exception The throwable object.
|
||||
* @return string
|
||||
*/
|
||||
protected function getMessage(\Throwable $exception)
|
||||
{
|
||||
if ($this->discloseExceptionInformation($exception)) {
|
||||
return $exception->getMessage();
|
||||
}
|
||||
return $this->defaultMessage;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user