‪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  1320830018, // Failed HMAC validation due to modified HMAC string in Extbase HashService
57  1320830276, // Failed HMAC validation due to too short HMAC string in Extbase HashService
58  1704454157, // Failed HMAC validation due to modified HMAC string in Core HashService
59  1704454152, // Failed HMAC validation due to too short HMAC string in Core HashService
60  ];
61 
69  public function ‪handleException(\Throwable $exception)
70  {
71  switch (PHP_SAPI) {
72  case 'cli':
73  $this->‪echoExceptionCLI($exception);
74  break;
75  default:
76  $this->‪echoExceptionWeb($exception);
77  }
78  }
79 
87  protected function ‪writeLogEntries(\Throwable $exception, string $mode): void
88  {
89  // Do not write any logs for some messages to avoid filling up tables or files with illegal requests
90  $ignoredCodes = array_merge(self::IGNORED_EXCEPTION_CODES, self::IGNORED_HMAC_EXCEPTION_CODES);
91  if (in_array($exception->getCode(), $ignoredCodes, true)) {
92  return;
93  }
94 
95  // PSR-3 logging framework.
96  try {
97  if ($this->logger) {
98  // 'FE' if in FrontendApplication, else 'BE' (also in CLI without request object)
99  $applicationMode = (‪$GLOBALS['TYPO3_REQUEST'] ?? null) instanceof ServerRequestInterface
100  && ‪ApplicationType::fromRequest(‪$GLOBALS['TYPO3_REQUEST'])->isFrontend()
101  ? 'FE'
102  : 'BE';
103  $requestUrl = $this->‪anonymizeToken(GeneralUtility::getIndpEnv('TYPO3_REQUEST_URL'));
104  $this->logger->critical('Core: Exception handler ({mode}: {application_mode}): {exception_class}, code #{exception_code}, file {file}, line {line}: {message}', [
105  'mode' => $mode,
106  'application_mode' => $applicationMode,
107  'exception_class' => get_class($exception),
108  'exception_code' => $exception->getCode(),
109  'file' => $exception->getFile(),
110  'line' => $exception->getLine(),
111  'message' => $exception->getMessage(),
112  'request_url' => $requestUrl,
113  'exception' => $this->logExceptionStackTrace ? $exception : null,
114  ]);
115  }
116  } catch (\‪Exception $exception) {
117  // A nested exception here was probably caused by a database failure, which means there's little
118  // else that can be done other than moving on and letting the system hard-fail.
119  }
120 
121  // Legacy logger. Remove this section eventually.
122  $filePathAndName = $exception->getFile();
123  $exceptionCodeNumber = $exception->getCode() > 0 ? '#' . $exception->getCode() . ': ' : '';
124  $logTitle = 'Core: Exception handler (' . $mode . ')';
125  $logMessage = 'Uncaught TYPO3 Exception: ' . $exceptionCodeNumber . $exception->getMessage() . ' | '
126  . get_class($exception) . ' thrown in file ' . $filePathAndName . ' in line ' . $exception->getLine();
127  if ($mode === self::CONTEXT_WEB) {
128  $logMessage .= '. Requested URL: ' . $this->‪anonymizeToken(GeneralUtility::getIndpEnv('TYPO3_REQUEST_URL'));
129  }
130  // When database credentials are wrong, the exception is probably
131  // caused by this. Therefore we cannot do any database operation,
132  // otherwise this will lead into recurring exceptions.
133  try {
134  // Write error message to sys_log table
135  $this->‪writeLog($logTitle . ': ' . $logMessage);
136  } catch (\Throwable $exception) {
137  }
138  }
139 
145  protected function ‪writeLog(string $logMessage)
146  {
147  $connection = GeneralUtility::makeInstance(ConnectionPool::class)
148  ->getConnectionForTable('sys_log');
149 
150  if (!$connection->isConnected()) {
151  return;
152  }
153  $userId = 0;
154  $workspace = 0;
155  $data = [];
156  $backendUser = $this->‪getBackendUser();
157  if ($backendUser !== null) {
158  if (isset($backendUser->user['uid'])) {
159  $userId = $backendUser->user['uid'];
160  }
161  $workspace = $backendUser->workspace;
162  if ($backUserId = $backendUser->getOriginalUserIdWhenInSwitchUserMode()) {
163  $data['originalUser'] = $backUserId;
164  }
165  }
166 
167  $connection->insert(
168  'sys_log',
169  [
170  'userid' => $userId,
171  'type' => SystemLogType::ERROR,
172  'channel' => SystemLogType::toChannel(SystemLogType::ERROR),
173  'action' => SystemLogGenericAction::UNDEFINED,
174  'error' => SystemLogErrorClassification::SYSTEM_ERROR,
175  'level' => SystemLogType::toLevel(SystemLogType::ERROR),
176  'details_nr' => 0,
177  'details' => str_replace('%', '%%', $logMessage),
178  'log_data' => empty($data) ? '' : json_encode($data),
179  'IP' => (string)GeneralUtility::getIndpEnv('REMOTE_ADDR'),
180  'tstamp' => ‪$GLOBALS['EXEC_TIME'],
181  'workspace' => $workspace,
182  ]
183  );
184  }
185 
192  protected function ‪sendStatusHeaders(\Throwable $exception)
193  {
194  if (method_exists($exception, 'getStatusHeaders')) {
195  $headers = $exception->getStatusHeaders();
196  } else {
197  $headers = [‪HttpUtility::HTTP_STATUS_500];
198  }
199  if (!headers_sent()) {
200  foreach ($headers as $header) {
201  header($header);
202  }
203  }
204  }
205 
207  {
208  return ‪$GLOBALS['BE_USER'] ?? null;
209  }
210 
214  protected function ‪anonymizeToken(string $requestedUrl): string
215  {
216  $pattern = '/(?:(?<=[tT]oken=)|(?<=[tT]oken%3D))[0-9a-fA-F]{40}/';
217  return preg_replace($pattern, '--AnonymizedToken--', $requestedUrl);
218  }
219 }
‪TYPO3\CMS\Core\SysLog\Action
Definition: Cache.php:18
‪TYPO3\CMS\Core\Error\AbstractExceptionHandler\writeLogEntries
‪writeLogEntries(\Throwable $exception, string $mode)
Definition: AbstractExceptionHandler.php:87
‪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:192
‪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:145
‪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:69
‪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:214
‪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:206