[BUGFIX] Check if a valid logger is available in error handlers
[Packages/TYPO3.CMS.git] / typo3 / sysext / core / Classes / Error / AbstractExceptionHandler.php
1 <?php
2 namespace TYPO3\CMS\Core\Error;
3
4 /*
5 * This file is part of the TYPO3 CMS project.
6 *
7 * It is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU General Public License, either version 2
9 * of the License, or any later version.
10 *
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
13 *
14 * The TYPO3 project - inspiring people to share!
15 */
16
17 use Psr\Log\LoggerAwareInterface;
18 use Psr\Log\LoggerAwareTrait;
19 use TYPO3\CMS\Core\Database\ConnectionPool;
20 use TYPO3\CMS\Core\SingletonInterface;
21 use TYPO3\CMS\Core\Utility\GeneralUtility;
22 use TYPO3\CMS\Core\Utility\HttpUtility;
23
24 /**
25 * An abstract exception handler
26 *
27 * This file is a backport from TYPO3 Flow
28 */
29 abstract class AbstractExceptionHandler implements ExceptionHandlerInterface, SingletonInterface, LoggerAwareInterface
30 {
31 use LoggerAwareTrait;
32
33 const CONTEXT_WEB = 'WEB';
34 const CONTEXT_CLI = 'CLI';
35
36 /**
37 * Displays the given exception
38 *
39 * @param \Throwable $exception The throwable object.
40 *
41 * @throws \Exception
42 */
43 public function handleException(\Throwable $exception)
44 {
45 switch (PHP_SAPI) {
46 case 'cli':
47 $this->echoExceptionCLI($exception);
48 break;
49 default:
50 $this->echoExceptionWeb($exception);
51 }
52 }
53
54 /**
55 * Writes exception to different logs
56 *
57 * @param \Throwable $exception The throwable object.
58 * @param string $context The context where the exception was thrown, WEB or CLI
59 */
60 protected function writeLogEntries(\Throwable $exception, $context)
61 {
62 // Do not write any logs for this message to avoid filling up tables or files with illegal requests
63 if ($exception->getCode() === 1396795884) {
64 return;
65 }
66 $filePathAndName = $exception->getFile();
67 $exceptionCodeNumber = $exception->getCode() > 0 ? '#' . $exception->getCode() . ': ' : '';
68 $logTitle = 'Core: Exception handler (' . $context . ')';
69 $logMessage = 'Uncaught TYPO3 Exception: ' . $exceptionCodeNumber . $exception->getMessage() . ' | '
70 . get_class($exception) . ' thrown in file ' . $filePathAndName . ' in line ' . $exception->getLine();
71 if ($context === 'WEB') {
72 $logMessage .= '. Requested URL: ' . $this->anonymizeToken(GeneralUtility::getIndpEnv('TYPO3_REQUEST_URL'));
73 }
74 // When database credentials are wrong, the exception is probably
75 // caused by this. Therefor we cannot do any database operation,
76 // otherwise this will lead into recurring exceptions.
77 try {
78 if ($this->logger) {
79 $this->logger->critical($logTitle . ': ' . $logMessage, [
80 'TYPO3_MODE' => TYPO3_MODE,
81 'exception' => $exception
82 ]);
83 }
84 // Write error message to sys_log table
85 $this->writeLog($logTitle . ': ' . $logMessage);
86 } catch (\Exception $exception) {
87 }
88 }
89
90 /**
91 * Writes an exception in the sys_log table
92 *
93 * @param string $logMessage Default text that follows the message.
94 */
95 protected function writeLog($logMessage)
96 {
97 $connection = GeneralUtility::makeInstance(ConnectionPool::class)
98 ->getConnectionForTable('sys_log');
99
100 if (!$connection->isConnected()) {
101 return;
102 }
103 $userId = 0;
104 $workspace = 0;
105 $data = [];
106 $backendUser = $this->getBackendUser();
107 if (is_object($backendUser)) {
108 if (isset($backendUser->user['uid'])) {
109 $userId = $backendUser->user['uid'];
110 }
111 if (isset($backendUser->workspace)) {
112 $workspace = $backendUser->workspace;
113 }
114 if (!empty($backendUser->user['ses_backuserid'])) {
115 $data['originalUser'] = $backendUser->user['ses_backuserid'];
116 }
117 }
118
119 $connection->insert(
120 'sys_log',
121 [
122 'userid' => $userId,
123 'type' => 5,
124 'action' => 0,
125 'error' => 2,
126 'details_nr' => 0,
127 'details' => str_replace('%', '%%', $logMessage),
128 'log_data' => empty($data) ? '' : serialize($data),
129 'IP' => (string)GeneralUtility::getIndpEnv('REMOTE_ADDR'),
130 'tstamp' => $GLOBALS['EXEC_TIME'],
131 'workspace' => $workspace
132 ]
133 );
134 }
135
136 /**
137 * Sends the HTTP Status 500 code, if $exception is *not* a
138 * TYPO3\CMS\Core\Error\Http\StatusException and headers are not sent, yet.
139 *
140 * @param \Throwable $exception The throwable object.
141 */
142 protected function sendStatusHeaders(\Throwable $exception)
143 {
144 if (method_exists($exception, 'getStatusHeaders')) {
145 $headers = $exception->getStatusHeaders();
146 } else {
147 $headers = [HttpUtility::HTTP_STATUS_500];
148 }
149 if (!headers_sent()) {
150 foreach ($headers as $header) {
151 header($header);
152 }
153 }
154 }
155
156 /**
157 * @return \TYPO3\CMS\Core\Authentication\BackendUserAuthentication
158 */
159 protected function getBackendUser()
160 {
161 return $GLOBALS['BE_USER'];
162 }
163
164 /**
165 * Replaces the generated token with a generic equivalent
166 *
167 * @param string $requestedUrl
168 * @return string
169 */
170 protected function anonymizeToken(string $requestedUrl): string
171 {
172 $pattern = '/(?<=[tT]oken=)[0-9a-fA-F]{40}/';
173 return preg_replace($pattern, '--AnonymizedToken--', $requestedUrl);
174 }
175 }