‪TYPO3CMS  ‪main
AbstractExceptionHandler.php
Go to the documentation of this file.
1 <?php
2 
3 /*
4  * This file is part of the TYPO3 CMS project.
5  *
6  * It is free software; you can redistribute it and/or modify it under
7  * the terms of the GNU General Public License, either version 2
8  * of the License, or any later version.
9  *
10  * For the full copyright and license information, please read the
11  * LICENSE.txt file that was distributed with this source code.
12  *
13  * The TYPO3 project - inspiring people to share!
14  */
15 
17 
18 use Psr\Http\Message\ServerRequestInterface;
19 use Psr\Log\LoggerAwareInterface;
20 use Psr\Log\LoggerAwareTrait;
26 use ‪TYPO3\CMS\Core\SysLog\Error as SystemLogErrorClassification;
27 use ‪TYPO3\CMS\Core\SysLog\Type as SystemLogType;
30 
36 abstract class ‪AbstractExceptionHandler implements ‪ExceptionHandlerInterface, ‪SingletonInterface, LoggerAwareInterface
37 {
38  use LoggerAwareTrait;
39 
40  public const ‪CONTEXT_WEB = 'WEB';
41  public const ‪CONTEXT_CLI = 'CLI';
42 
43  protected bool ‪$logExceptionStackTrace = false;
44 
45  protected const ‪IGNORED_EXCEPTION_CODES = [
46  1396795884, // Current host header value does not match the configured trusted hosts pattern
47  1616175867, // Backend login request is rate limited
48  1616175847, // Frontend login request is rate limited
49  1436717275, // Request with unsupported HTTP method
50  1699604555, // Outdated __trustedProperties format in extbase property mapping
51  ];
52 
54  1581862822, // Failed HMAC validation due to modified __trustedProperties in extbase property mapping
55  1581862823, // Failed HMAC validation due to modified form state in ext:forms
56  ];
57 
65  public function ‪handleException(\Throwable $exception)
66  {
67  switch (PHP_SAPI) {
68  case 'cli':
69  $this->‪echoExceptionCLI($exception);
70  break;
71  default:
72  $this->‪echoExceptionWeb($exception);
73  }
74  }
75 
83  protected function ‪writeLogEntries(\Throwable $exception, string $mode): void
84  {
85  // Do not write any logs for some messages to avoid filling up tables or files with illegal requests
86  $ignoredCodes = array_merge(self::IGNORED_EXCEPTION_CODES, self::IGNORED_HMAC_EXCEPTION_CODES);
87  if (in_array($exception->getCode(), $ignoredCodes, true)) {
88  return;
89  }
90 
91  // PSR-3 logging framework.
92  try {
93  if ($this->logger) {
94  // 'FE' if in FrontendApplication, else 'BE' (also in CLI without request object)
95  $applicationMode = (‪$GLOBALS['TYPO3_REQUEST'] ?? null) instanceof ServerRequestInterface
96  && ‪ApplicationType::fromRequest(‪$GLOBALS['TYPO3_REQUEST'])->isFrontend()
97  ? 'FE'
98  : 'BE';
99  $requestUrl = $this->‪anonymizeToken(GeneralUtility::getIndpEnv('TYPO3_REQUEST_URL'));
100  $this->logger->critical('Core: Exception handler ({mode}: {application_mode}): {exception_class}, code #{exception_code}, file {file}, line {line}: {message}', [
101  'mode' => $mode,
102  'application_mode' => $applicationMode,
103  'exception_class' => get_class($exception),
104  'exception_code' => $exception->getCode(),
105  'file' => $exception->getFile(),
106  'line' => $exception->getLine(),
107  'message' => $exception->getMessage(),
108  'request_url' => $requestUrl,
109  'exception' => $this->logExceptionStackTrace ? $exception : null,
110  ]);
111  }
112  } catch (\‪Exception $exception) {
113  // A nested exception here was probably caused by a database failure, which means there's little
114  // else that can be done other than moving on and letting the system hard-fail.
115  }
116 
117  // Legacy logger. Remove this section eventually.
118  $filePathAndName = $exception->getFile();
119  $exceptionCodeNumber = $exception->getCode() > 0 ? '#' . $exception->getCode() . ': ' : '';
120  $logTitle = 'Core: Exception handler (' . $mode . ')';
121  $logMessage = 'Uncaught TYPO3 Exception: ' . $exceptionCodeNumber . $exception->getMessage() . ' | '
122  . get_class($exception) . ' thrown in file ' . $filePathAndName . ' in line ' . $exception->getLine();
123  if ($mode === self::CONTEXT_WEB) {
124  $logMessage .= '. Requested URL: ' . $this->‪anonymizeToken(GeneralUtility::getIndpEnv('TYPO3_REQUEST_URL'));
125  }
126  // When database credentials are wrong, the exception is probably
127  // caused by this. Therefore we cannot do any database operation,
128  // otherwise this will lead into recurring exceptions.
129  try {
130  // Write error message to sys_log table
131  $this->‪writeLog($logTitle . ': ' . $logMessage);
132  } catch (\Throwable $exception) {
133  }
134  }
135 
141  protected function ‪writeLog(string $logMessage)
142  {
143  $connection = GeneralUtility::makeInstance(ConnectionPool::class)
144  ->getConnectionForTable('sys_log');
145 
146  if (!$connection->isConnected()) {
147  return;
148  }
149  $userId = 0;
150  $workspace = 0;
151  $data = [];
152  $backendUser = $this->‪getBackendUser();
153  if ($backendUser !== null) {
154  if (isset($backendUser->user['uid'])) {
155  $userId = $backendUser->user['uid'];
156  }
157  $workspace = $backendUser->workspace;
158  if ($backUserId = $backendUser->getOriginalUserIdWhenInSwitchUserMode()) {
159  $data['originalUser'] = $backUserId;
160  }
161  }
162 
163  $connection->insert(
164  'sys_log',
165  [
166  'userid' => $userId,
167  'type' => SystemLogType::ERROR,
168  'channel' => SystemLogType::toChannel(SystemLogType::ERROR),
169  'action' => SystemLogGenericAction::UNDEFINED,
170  'error' => SystemLogErrorClassification::SYSTEM_ERROR,
171  'level' => SystemLogType::toLevel(SystemLogType::ERROR),
172  'details_nr' => 0,
173  'details' => str_replace('%', '%%', $logMessage),
174  'log_data' => empty($data) ? '' : json_encode($data),
175  'IP' => (string)GeneralUtility::getIndpEnv('REMOTE_ADDR'),
176  'tstamp' => ‪$GLOBALS['EXEC_TIME'],
177  'workspace' => $workspace,
178  ]
179  );
180  }
181 
188  protected function ‪sendStatusHeaders(\Throwable $exception)
189  {
190  if (method_exists($exception, 'getStatusHeaders')) {
191  $headers = $exception->getStatusHeaders();
192  } else {
193  $headers = [‪HttpUtility::HTTP_STATUS_500];
194  }
195  if (!headers_sent()) {
196  foreach ($headers as $header) {
197  header($header);
198  }
199  }
200  }
201 
203  {
204  return ‪$GLOBALS['BE_USER'] ?? null;
205  }
206 
210  protected function ‪anonymizeToken(string $requestedUrl): string
211  {
212  $pattern = '/(?:(?<=[tT]oken=)|(?<=[tT]oken%3D))[0-9a-fA-F]{40}/';
213  return preg_replace($pattern, '--AnonymizedToken--', $requestedUrl);
214  }
215 }
‪TYPO3\CMS\Core\SysLog\Action
Definition: Cache.php:18
‪TYPO3\CMS\Core\Error\AbstractExceptionHandler\writeLogEntries
‪writeLogEntries(\Throwable $exception, string $mode)
Definition: AbstractExceptionHandler.php:83
‪TYPO3\CMS\Core\Error\AbstractExceptionHandler\CONTEXT_WEB
‪const CONTEXT_WEB
Definition: AbstractExceptionHandler.php:40
‪TYPO3\CMS\Core\Error\AbstractExceptionHandler\IGNORED_HMAC_EXCEPTION_CODES
‪const IGNORED_HMAC_EXCEPTION_CODES
Definition: AbstractExceptionHandler.php:53
‪TYPO3\CMS\Core\Error\AbstractExceptionHandler
Definition: AbstractExceptionHandler.php:37
‪TYPO3\CMS\Core\Error\AbstractExceptionHandler\$logExceptionStackTrace
‪bool $logExceptionStackTrace
Definition: AbstractExceptionHandler.php:43
‪TYPO3\CMS\Core\Error\AbstractExceptionHandler\sendStatusHeaders
‪sendStatusHeaders(\Throwable $exception)
Definition: AbstractExceptionHandler.php:188
‪TYPO3\CMS\Core\SysLog\Error
Definition: Error.php:24
‪TYPO3\CMS\Core\Authentication\BackendUserAuthentication
Definition: BackendUserAuthentication.php:62
‪TYPO3\CMS\Core\Error\AbstractExceptionHandler\writeLog
‪writeLog(string $logMessage)
Definition: AbstractExceptionHandler.php:141
‪TYPO3\CMS\Core\Error\Exception
Definition: Exception.php:21
‪TYPO3\CMS\Core\Error\AbstractExceptionHandler\CONTEXT_CLI
‪const CONTEXT_CLI
Definition: AbstractExceptionHandler.php:41
‪TYPO3\CMS\Core\Error\AbstractExceptionHandler\handleException
‪handleException(\Throwable $exception)
Definition: AbstractExceptionHandler.php:65
‪TYPO3\CMS\Core\Error\ExceptionHandlerInterface
Definition: ExceptionHandlerInterface.php:24
‪TYPO3\CMS\Core\SingletonInterface
Definition: SingletonInterface.php:22
‪$GLOBALS
‪$GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['adminpanel']['modules']
Definition: ext_localconf.php:25
‪TYPO3\CMS\Core\Error\AbstractExceptionHandler\IGNORED_EXCEPTION_CODES
‪const IGNORED_EXCEPTION_CODES
Definition: AbstractExceptionHandler.php:45
‪TYPO3\CMS\Core\Error
Definition: AbstractExceptionHandler.php:16
‪TYPO3\CMS\Core\Error\ExceptionHandlerInterface\echoExceptionWeb
‪echoExceptionWeb(\Throwable $exception)
‪TYPO3\CMS\Core\Utility\HttpUtility
Definition: HttpUtility.php:24
‪TYPO3\CMS\Core\Http\fromRequest
‪@ fromRequest
Definition: ApplicationType.php:66
‪TYPO3\CMS\Core\Error\AbstractExceptionHandler\anonymizeToken
‪anonymizeToken(string $requestedUrl)
Definition: AbstractExceptionHandler.php:210
‪TYPO3\CMS\Core\Database\ConnectionPool
Definition: ConnectionPool.php:46
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:52
‪TYPO3\CMS\Core\Utility\HttpUtility\HTTP_STATUS_500
‪const HTTP_STATUS_500
Definition: HttpUtility.php:82
‪TYPO3\CMS\Core\SysLog\Type
Definition: Type.php:28
‪TYPO3\CMS\Core\Error\ExceptionHandlerInterface\echoExceptionCLI
‪echoExceptionCLI(\Throwable $exception)
‪TYPO3\CMS\Core\Http\ApplicationType
‪ApplicationType
Definition: ApplicationType.php:55
‪TYPO3\CMS\Core\Error\AbstractExceptionHandler\getBackendUser
‪getBackendUser()
Definition: AbstractExceptionHandler.php:202