| Current Path : /var/www/surf/TYPO3/vendor/typo3/cms-core/Classes/Error/ |
| Current File : /var/www/surf/TYPO3/vendor/typo3/cms-core/Classes/Error/AbstractExceptionHandler.php |
<?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\Http\ApplicationType;
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 bool $logExceptionStackTrace = false;
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
];
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
];
/**
* 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)
$applicationMode = ($GLOBALS['TYPO3_REQUEST'] ?? null) instanceof ServerRequestInterface
&& ApplicationType::fromRequest($GLOBALS['TYPO3_REQUEST'])->isFrontend()
? 'FE'
: 'BE';
$requestUrl = $this->anonymizeToken(GeneralUtility::getIndpEnv('TYPO3_REQUEST_URL'));
$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(GeneralUtility::getIndpEnv('TYPO3_REQUEST_URL'));
}
// 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 (\Exception $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_nr' => 0,
'details' => str_replace('%', '%%', $logMessage),
'log_data' => empty($data) ? '' : json_encode($data),
'IP' => (string)GeneralUtility::getIndpEnv('REMOTE_ADDR'),
'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)
{
if (method_exists($exception, 'getStatusHeaders')) {
$headers = $exception->getStatusHeaders();
} else {
$headers = [HttpUtility::HTTP_STATUS_500];
}
if (!headers_sent()) {
foreach ($headers as $header) {
header($header);
}
}
}
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);
}
}