Commit | Line | Data |
---|---|---|
a9229942 | 1 | <?php // @codingStandardsIgnoreFile |
158bd3ca TD |
2 | /** |
3 | * @author Marcel Werk | |
7b7b9764 | 4 | * @copyright 2001-2019 WoltLab GmbH |
158bd3ca | 5 | * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> |
e71525e4 | 6 | * @package WoltLabSuite\Core |
158bd3ca | 7 | */ |
5bc5a7e6 TD |
8 | namespace { |
9 | use wcf\system\WCF; | |
2b6fbe94 | 10 | |
5bc5a7e6 | 11 | // set exception handler |
d82507dc | 12 | set_exception_handler([WCF::class, 'handleException']); |
5bc5a7e6 | 13 | // set php error handler |
d82507dc | 14 | set_error_handler([WCF::class, 'handleError'], E_ALL); |
5bc5a7e6 | 15 | // set shutdown function |
d82507dc | 16 | register_shutdown_function([WCF::class, 'destruct']); |
5bc5a7e6 | 17 | // set autoload function |
d82507dc | 18 | spl_autoload_register([WCF::class, 'autoload']); |
ef47717c | 19 | |
9bfa7303 TD |
20 | spl_autoload_register(function ($className) { |
21 | /** | |
22 | * @deprecated 5.3 This file is a compatibility layer mapping from Leafo\\ to ScssPhp\\ | |
23 | */ | |
24 | $leafo = 'Leafo\\'; | |
25 | if (substr($className, 0, strlen($leafo)) === $leafo) { | |
26 | class_alias('ScssPhp\\'.substr($className, strlen($leafo)), $className, true); | |
27 | } | |
28 | }); | |
29 | ||
ef47717c MS |
30 | /** |
31 | * Escapes a string for use in sql query. | |
32 | * | |
33 | * @see \wcf\system\database\Database::escapeString() | |
34 | * @param string $string | |
35 | * @return string | |
36 | */ | |
5bc5a7e6 TD |
37 | function escapeString($string) { |
38 | return WCF::getDB()->escapeString($string); | |
39 | } | |
f406809a AE |
40 | |
41 | /** | |
42 | * Helper method to output debug data for all passed variables, | |
43 | * uses `print_r()` for arrays and objects, `var_dump()` otherwise. | |
44 | */ | |
45 | function wcfDebug() { | |
46 | echo "<pre>"; | |
47 | ||
48 | $args = func_get_args(); | |
49 | $length = count($args); | |
50 | if ($length === 0) { | |
a5cf4a28 | 51 | echo "ERROR: No arguments provided.<hr>"; |
f406809a AE |
52 | } |
53 | else { | |
54 | for ($i = 0; $i < $length; $i++) { | |
55 | $arg = $args[$i]; | |
56 | ||
57 | echo "<h2>Argument {$i} (" . gettype($arg) . ")</h2>"; | |
58 | ||
59 | if (is_array($arg) || is_object($arg)) { | |
60 | print_r($arg); | |
61 | } | |
62 | else { | |
63 | var_dump($arg); | |
64 | } | |
65 | ||
66 | echo "<hr>"; | |
67 | } | |
68 | } | |
69 | ||
70 | $backtrace = debug_backtrace(); | |
71 | ||
72 | // output call location to help finding these debug outputs again | |
73 | echo "wcfDebug() called in {$backtrace[0]['file']} on line {$backtrace[0]['line']}"; | |
74 | ||
75 | echo "</pre>"; | |
76 | ||
77 | exit; | |
78 | } | |
2b6fbe94 | 79 | |
5bc5a7e6 TD |
80 | // define DOCUMENT_ROOT on IIS if not set |
81 | if (PHP_EOL == "\r\n") { | |
82 | if (!isset($_SERVER['DOCUMENT_ROOT']) && isset($_SERVER['SCRIPT_FILENAME'])) { | |
82d72850 | 83 | $_SERVER['DOCUMENT_ROOT'] = str_replace('\\', '/', substr($_SERVER['SCRIPT_FILENAME'], 0, 0 - strlen($_SERVER['PHP_SELF']))); |
5bc5a7e6 TD |
84 | } |
85 | if (!isset($_SERVER['DOCUMENT_ROOT']) && isset($_SERVER['PATH_TRANSLATED'])) { | |
82d72850 | 86 | $_SERVER['DOCUMENT_ROOT'] = str_replace('\\', '/', substr(str_replace('\\\\', '\\', $_SERVER['PATH_TRANSLATED']), 0, 0 - strlen($_SERVER['PHP_SELF']))); |
5bc5a7e6 | 87 | } |
2b6fbe94 | 88 | |
5bc5a7e6 TD |
89 | if (!isset($_SERVER['REQUEST_URI'])) { |
90 | $_SERVER['REQUEST_URI'] = substr($_SERVER['PHP_SELF'], 1); | |
91 | if (isset($_SERVER['QUERY_STRING'])) { | |
92 | $_SERVER['REQUEST_URI'] .= '?' . $_SERVER['QUERY_STRING']; | |
93 | } | |
94 | } | |
95 | } | |
416c2b51 AE |
96 | |
97 | // setting global gzip compression breaks output buffering | |
98 | if (@ini_get('zlib.output_compression')) { | |
ac81e92d | 99 | @ini_set('zlib.output_compression', '0'); |
416c2b51 | 100 | } |
745a1974 AE |
101 | |
102 | if (!function_exists('is_countable')) { | |
a5525bc9 MS |
103 | function is_countable($var) { |
104 | return is_array($var) || $var instanceof Countable || $var instanceof ResourceBundle || $var instanceof SimpleXmlElement; | |
105 | } | |
745a1974 | 106 | } |
dcb3a44c | 107 | } |
322ced57 | 108 | |
2c1520ae | 109 | namespace wcf { |
badbbc6d | 110 | function getRequestId(): string { |
2c1520ae TD |
111 | if (!defined('WCF_REQUEST_ID_HEADER') || !WCF_REQUEST_ID_HEADER) return ''; |
112 | ||
113 | return $_SERVER[WCF_REQUEST_ID_HEADER] ?? ''; | |
114 | } | |
3a046f18 | 115 | |
badbbc6d | 116 | function getMinorVersion(): string { |
3a046f18 TD |
117 | return preg_replace('/^(\d+\.\d+)\..*$/', '\\1', WCF_VERSION); |
118 | } | |
22eabfcf TD |
119 | |
120 | #[Attribute(\Attribute::TARGET_PARAMETER)] | |
121 | class SensitiveArgument | |
122 | { | |
123 | } | |
2c1520ae TD |
124 | } |
125 | ||
5bc5a7e6 | 126 | namespace wcf\functions\exception { |
fc23fa20 | 127 | use wcf\system\WCF; |
b8e0376c | 128 | use wcf\system\exception\IExtraInformationException; |
c4d491ab | 129 | use wcf\system\exception\ILoggingAwareException; |
5bc5a7e6 | 130 | use wcf\system\exception\SystemException; |
5bc5a7e6 TD |
131 | use wcf\util\FileUtil; |
132 | use wcf\util\StringUtil; | |
5f9ea37a MS |
133 | |
134 | /** | |
135 | * If the stacktrace contains a compiled template, the context of the relevant template line | |
136 | * is returned, otherwise an empty array is returned. | |
137 | */ | |
138 | function getTemplateContextLines(\Throwable $e): array { | |
139 | try { | |
140 | $contextLineCount = 5; | |
141 | foreach ($e->getTrace() as $traceEntry) { | |
142 | if (isset($traceEntry['file']) && \preg_match('~/templates/compiled/.+\.php$~', | |
143 | $traceEntry['file'])) { | |
144 | $startLine = $traceEntry['line'] - $contextLineCount; | |
145 | $relativeErrorLine = $contextLineCount; | |
146 | if ($startLine < 0) { | |
147 | $startLine = 0; | |
148 | $relativeErrorLine = $traceEntry['line'] - 1; | |
149 | } | |
150 | ||
151 | $file = \fopen($traceEntry['file'], 'r'); | |
152 | if (!$file) { | |
153 | return []; | |
154 | } | |
155 | ||
156 | for ($line = 0; $line < $startLine; $line++) { | |
157 | if (\substr(\fgets($file, 1024), -1) !== "\n") { | |
158 | // We don't want to handle a file where lines exceed 1024 Bytes. | |
159 | return []; | |
160 | } | |
161 | } | |
162 | ||
163 | $maxLineCount = 2 * $contextLineCount + 1; | |
164 | $lines = []; | |
165 | while (!\feof($file) && \count($lines) < $maxLineCount) { | |
166 | $line = \fgets($file, 1024); | |
167 | if (\substr($line, -1) !== "\n" && !\feof($file)) { | |
168 | // We don't want to handle a file where lines exceed 1024 Bytes. | |
169 | return []; | |
170 | } | |
171 | ||
172 | if (count($lines) === $relativeErrorLine - 1) { | |
173 | $line = "====> {$line}"; | |
174 | } | |
175 | ||
176 | $lines[] = $line; | |
177 | } | |
178 | ||
179 | return $lines; | |
180 | } | |
181 | } | |
182 | } | |
183 | catch (\Throwable $e) { | |
184 | // Ignore errors while extracting the template context to be saved in the exception log. | |
185 | } | |
186 | ||
187 | return []; | |
188 | } | |
15de71ce TD |
189 | |
190 | /** | |
191 | * Logs the given Throwable. | |
192 | * | |
15de71ce TD |
193 | * @param string $logFile The log file to use. If set to `null` the default log file will be used and the variable contents will be replaced by the actual path. |
194 | * @return string The ID of the log entry. | |
195 | */ | |
badbbc6d | 196 | function logThrowable(\Throwable $e, &$logFile = null): string { |
8891d4b9 | 197 | if ($logFile === null) $logFile = WCF_DIR . 'log/' . gmdate('Y-m-d', TIME_NOW) . '.txt'; |
b8149369 | 198 | touch($logFile); |
e4499881 | 199 | |
45774969 TD |
200 | $stripNewlines = function ($item) { |
201 | return str_replace("\n", ' ', $item); | |
202 | }; | |
203 | ||
5f9ea37a MS |
204 | $getExtraInformation = function (\Throwable $e) { |
205 | $extraInformation = []; | |
206 | ||
207 | if ($e instanceof IExtraInformationException) { | |
208 | $extraInformation = $e->getExtraInformation(); | |
209 | } | |
210 | ||
211 | $templateContextLines = getTemplateContextLines($e); | |
212 | if (!empty($templateContextLines)) { | |
213 | $extraInformation[] = [ | |
214 | 'Template Context', | |
215 | \implode("", $templateContextLines), | |
216 | ]; | |
217 | } | |
218 | ||
219 | return !empty($extraInformation) ? base64_encode(serialize($extraInformation)) : "-"; | |
220 | }; | |
221 | ||
9b83d173 | 222 | // don't forget to update ExceptionLogUtil / ExceptionLogViewPage, when changing the log file format |
b8149369 | 223 | $message = gmdate('r', TIME_NOW)."\n". |
45774969 | 224 | 'Message: '.$stripNewlines($e->getMessage())."\n". |
b8149369 | 225 | 'PHP version: '.phpversion()."\n". |
67d7afc7 | 226 | 'WoltLab Suite version: '.WCF_VERSION."\n". |
9b83d173 | 227 | 'Request URI: '.$stripNewlines(($_SERVER['REQUEST_METHOD'] ?? '').' '.($_SERVER['REQUEST_URI'] ?? '')).(\wcf\getRequestId() ? ' ('.\wcf\getRequestId().')' : '')."\n". |
45774969 TD |
228 | 'Referrer: '.$stripNewlines($_SERVER['HTTP_REFERER'] ?? '')."\n". |
229 | 'User Agent: '.$stripNewlines($_SERVER['HTTP_USER_AGENT'] ?? '')."\n". | |
b8149369 | 230 | 'Peak Memory Usage: '.memory_get_peak_usage().'/'.FileUtil::getMemoryLimit()."\n"; |
33b728a6 | 231 | $prev = $e; |
b8149369 TD |
232 | do { |
233 | $message .= "======\n". | |
33b728a6 | 234 | 'Error Class: '.get_class($prev)."\n". |
45774969 | 235 | 'Error Message: '.$stripNewlines($prev->getMessage())."\n". |
cdb994ab | 236 | 'Error Code: '.$stripNewlines($prev->getCode())."\n". |
45774969 | 237 | 'File: '.$stripNewlines($prev->getFile()).' ('.$prev->getLine().')'."\n". |
5f9ea37a | 238 | 'Extra Information: ' . $getExtraInformation($prev) . "\n". |
72f128c3 | 239 | 'Stack Trace: '.json_encode(array_map(function ($item) { |
b8149369 TD |
240 | $item['args'] = array_map(function ($item) { |
241 | switch (gettype($item)) { | |
242 | case 'object': | |
243 | return get_class($item); | |
244 | case 'array': | |
245 | return array_map(function () { | |
246 | return '[redacted]'; | |
247 | }, $item); | |
403684df TD |
248 | case 'resource': |
249 | return 'resource('.get_resource_type($item).')'; | |
b8149369 TD |
250 | default: |
251 | return $item; | |
252 | } | |
253 | }, $item['args']); | |
e4499881 | 254 | |
b8149369 | 255 | return $item; |
33b728a6 | 256 | }, sanitizeStacktrace($prev, true)))."\n"; |
b8149369 | 257 | } |
33b728a6 | 258 | while ($prev = $prev->getPrevious()); |
e4499881 | 259 | |
b8149369 TD |
260 | // calculate Exception-ID |
261 | $exceptionID = sha1($message); | |
2b6fbe94 | 262 | $entry = "<<<<<<<<".$exceptionID."<<<<\n".$message."<<<<\n\n"; |
e4499881 | 263 | |
b8149369 | 264 | file_put_contents($logFile, $entry, FILE_APPEND); |
33b728a6 TD |
265 | |
266 | // let the Exception know it has been logged | |
c4d491ab TD |
267 | if ( |
268 | $e instanceof ILoggingAwareException | |
269 | || (method_exists($e, 'finalizeLog') && is_callable([$e, 'finalizeLog'])) | |
270 | ) { | |
271 | /** @var ILoggingAwareException $e */ | |
272 | $e->finalizeLog($exceptionID, $logFile); | |
273 | } | |
33b728a6 | 274 | |
b8149369 TD |
275 | return $exceptionID; |
276 | } | |
2b6fbe94 | 277 | |
15de71ce TD |
278 | /** |
279 | * Pretty prints the given Throwable. It is recommended to `exit;` | |
280 | * the request after calling this function. | |
281 | * | |
15de71ce TD |
282 | * @throws \Exception |
283 | */ | |
badbbc6d | 284 | function printThrowable(\Throwable $e) { |
76e90abc | 285 | $exceptionID = logThrowable($e, $logFile); |
2c1520ae | 286 | if (\wcf\getRequestId()) $exceptionID .= '/'.\wcf\getRequestId(); |
614e2466 AE |
287 | |
288 | $exceptionTitle = $exceptionSubtitle = $exceptionExplanation = ''; | |
289 | $logFile = sanitizePath($logFile); | |
290 | try { | |
04edc2ff AE |
291 | if (WCF::getLanguage() !== null) { |
292 | $exceptionTitle = WCF::getLanguage()->get('wcf.global.exception.title', true); | |
293 | $exceptionSubtitle = str_replace('{$exceptionID}', $exceptionID, WCF::getLanguage()->get('wcf.global.exception.subtitle', true)); | |
294 | $exceptionExplanation = str_replace('{$logFile}', $logFile, WCF::getLanguage()->get('wcf.global.exception.explanation', true)); | |
295 | } | |
614e2466 | 296 | } |
614e2466 AE |
297 | catch (\Throwable $e) { |
298 | // ignore | |
299 | } | |
300 | ||
301 | if (!$exceptionTitle || !$exceptionSubtitle || !$exceptionExplanation) { | |
302 | // one or more failed, fallback to english | |
e8d7ee4d | 303 | $exceptionTitle = 'An error has occurred'; |
a7eb5dda | 304 | $exceptionSubtitle = 'Internal error code: <span class="exceptionInlineCodeWrapper"><span class="exceptionInlineCode">'.$exceptionID.'</span></span>'; |
614e2466 AE |
305 | $exceptionExplanation = <<<EXPLANATION |
306 | <p class="exceptionSubtitle">What happened?</p> | |
307 | <p class="exceptionText">An error has occured while trying to handle your request and execution has been terminated. Please forward the above error code to the site administrator.</p> | |
308 | <p class="exceptionText"> </p> <!-- required to ensure spacing after copy & paste --> | |
309 | <p class="exceptionText"> | |
310 | The error code can be used by an administrator to lookup the full error message in the Administration Control Panel via “Logs » Errors”. | |
819d7822 | 311 | In addition the error has been written to the log file located at <span class="exceptionInlineCodeWrapper"><span class="exceptionInlineCode">{$logFile}</span></span> and can be accessed with a FTP program or similar. |
614e2466 AE |
312 | </p> |
313 | <p class="exceptionText"> </p> <!-- required to ensure spacing after copy & paste --> | |
314 | <p class="exceptionText">Notice: The error code was randomly generated and has no use beyond looking up the full message.</p> | |
315 | EXPLANATION; | |
316 | ||
317 | } | |
318 | ||
319 | /* | |
320 | * A notice on the HTML used below: | |
321 | * | |
322 | * It might appear a bit weird to use <p> all over the place where semantically | |
323 | * other elements would fit in way better. The reason behind this is that we avoid | |
324 | * inheriting unwanted styles (e.g. exception displayed in an overlay) and that | |
325 | * the output needs to be properly readable when copied & pasted somewhere. | |
326 | * | |
327 | * Besides the visual appearance, the output was built to provide a maximum of | |
328 | * compatibility and readability when pasted somewhere else, e.g. a WYSIWYG editor | |
329 | * without the potential of messing up the formatting and thus harming the readability. | |
330 | */ | |
5bc5a7e6 TD |
331 | ?><!DOCTYPE html> |
332 | <html> | |
333 | <head> | |
6ba86ea0 | 334 | <meta charset="utf-8"> |
962c9241 | 335 | <?php if (!defined('EXCEPTION_PRIVACY') || EXCEPTION_PRIVACY !== 'private') { ?> |
5bc5a7e6 TD |
336 | <title>Fatal Error: <?php echo StringUtil::encodeHTML($e->getMessage()); ?></title> |
337 | <?php } else { ?> | |
338 | <title>Fatal Error</title> | |
339 | <?php } ?> | |
614e2466 | 340 | <meta name="viewport" content="width=device-width, initial-scale=1"> |
5bc5a7e6 | 341 | <style> |
614e2466 | 342 | .exceptionBody { |
8972cde9 MW |
343 | background-color: rgb(250, 250, 250); |
344 | color: rgb(44, 62, 80); | |
614e2466 AE |
345 | margin: 0; |
346 | padding: 0; | |
5bc5a7e6 | 347 | } |
614e2466 AE |
348 | |
349 | .exceptionContainer { | |
350 | box-sizing: border-box; | |
351 | font-family: 'Segoe UI', 'Lucida Grande', 'Helvetica Neue', Helvetica, Arial, sans-serif; | |
352 | font-size: 14px; | |
353 | padding-bottom: 20px; | |
5bc5a7e6 | 354 | } |
614e2466 AE |
355 | |
356 | .exceptionContainer * { | |
357 | box-sizing: inherit; | |
614e2466 AE |
358 | line-height: 1.5em; |
359 | margin: 0; | |
360 | padding: 0; | |
5bc5a7e6 | 361 | } |
614e2466 AE |
362 | |
363 | .exceptionHeader { | |
8972cde9 | 364 | background-color: rgb(58, 109, 156); |
89130385 | 365 | padding: 30px 0; |
5bc5a7e6 | 366 | } |
614e2466 AE |
367 | |
368 | .exceptionTitle { | |
369 | color: #fff; | |
370 | font-size: 28px; | |
371 | font-weight: 300; | |
5bc5a7e6 | 372 | } |
614e2466 AE |
373 | |
374 | .exceptionErrorCode { | |
375 | color: #fff; | |
376 | margin-top: .5em; | |
5bc5a7e6 | 377 | } |
614e2466 AE |
378 | |
379 | .exceptionErrorCode .exceptionInlineCode { | |
3bfbe63f | 380 | background-color: rgb(43, 79, 113); |
614e2466 AE |
381 | border-radius: 3px; |
382 | color: #fff; | |
383 | font-family: monospace; | |
384 | padding: 3px 10px; | |
385 | white-space: nowrap; | |
5bc5a7e6 | 386 | } |
614e2466 AE |
387 | |
388 | .exceptionSubtitle { | |
389 | border-bottom: 1px solid rgb(238, 238, 238); | |
614e2466 AE |
390 | font-size: 24px; |
391 | font-weight: 300; | |
392 | margin-bottom: 15px; | |
393 | padding-bottom: 10px; | |
5bc5a7e6 | 394 | } |
614e2466 AE |
395 | |
396 | .exceptionContainer > .exceptionBoundary { | |
397 | margin-top: 30px; | |
5bc5a7e6 | 398 | } |
614e2466 AE |
399 | |
400 | .exceptionText .exceptionInlineCodeWrapper { | |
401 | border: 1px solid rgb(169, 169, 169); | |
402 | border-radius: 3px; | |
403 | padding: 2px 5px; | |
5bc5a7e6 | 404 | } |
614e2466 AE |
405 | |
406 | .exceptionText .exceptionInlineCode { | |
407 | font-family: monospace; | |
408 | white-space: nowrap; | |
5bc5a7e6 | 409 | } |
614e2466 AE |
410 | |
411 | .exceptionFieldTitle { | |
412 | color: rgb(59, 109, 169); | |
5bc5a7e6 | 413 | } |
614e2466 AE |
414 | |
415 | .exceptionFieldTitle .exceptionColon { | |
416 | /* hide colon in browser, but will be visible after copy & paste */ | |
417 | opacity: 0; | |
5bc5a7e6 | 418 | } |
614e2466 AE |
419 | |
420 | .exceptionFieldValue { | |
421 | font-size: 18px; | |
3d8a295e | 422 | min-height: 1.5em; |
614e2466 AE |
423 | } |
424 | ||
5f9ea37a MS |
425 | pre.exceptionFieldValue { |
426 | font-size: 14px; | |
427 | white-space: pre-wrap; | |
428 | } | |
429 | ||
614e2466 AE |
430 | .exceptionSystemInformation, |
431 | .exceptionErrorDetails, | |
432 | .exceptionStacktrace { | |
433 | list-style-type: none; | |
434 | } | |
435 | ||
436 | .exceptionSystemInformation > li:not(:first-child), | |
437 | .exceptionErrorDetails > li:not(:first-child) { | |
438 | margin-top: 10px; | |
439 | } | |
440 | ||
441 | .exceptionStacktrace { | |
442 | display: block; | |
443 | margin-top: 5px; | |
444 | overflow: auto; | |
445 | padding-bottom: 20px; | |
446 | } | |
447 | ||
448 | .exceptionStacktraceFile, | |
449 | .exceptionStacktraceFile span, | |
450 | .exceptionStacktraceCall, | |
451 | .exceptionStacktraceCall span { | |
452 | font-family: monospace !important; | |
453 | white-space: nowrap !important; | |
454 | } | |
455 | ||
456 | .exceptionStacktraceCall + .exceptionStacktraceFile { | |
457 | margin-top: 5px; | |
458 | } | |
459 | ||
460 | .exceptionStacktraceCall { | |
461 | padding-left: 40px; | |
462 | } | |
463 | ||
464 | .exceptionStacktraceCall, | |
465 | .exceptionStacktraceCall span { | |
466 | color: rgb(102, 102, 102) !important; | |
467 | font-size: 13px !important; | |
468 | } | |
469 | ||
470 | /* mobile */ | |
471 | @media (max-width: 767px) { | |
472 | .exceptionBoundary { | |
473 | min-width: 320px; | |
474 | padding: 0 10px; | |
dd6a2623 | 475 | } |
614e2466 AE |
476 | |
477 | .exceptionText .exceptionInlineCodeWrapper { | |
478 | display: inline-block; | |
479 | overflow: auto; | |
dd6a2623 | 480 | } |
614e2466 AE |
481 | |
482 | .exceptionErrorCode .exceptionInlineCode { | |
483 | font-size: 13px; | |
484 | padding: 2px 5px; | |
dd6a2623 | 485 | } |
614e2466 AE |
486 | } |
487 | ||
488 | /* desktop */ | |
489 | @media (min-width: 768px) { | |
490 | .exceptionBoundary { | |
491 | margin: 0 auto; | |
492 | max-width: 1400px; | |
493 | min-width: 1200px; | |
494 | padding: 0 10px; | |
495 | } | |
496 | ||
497 | .exceptionSystemInformation { | |
498 | display: flex; | |
499 | flex-wrap: wrap; | |
500 | } | |
501 | ||
502 | .exceptionSystemInformation1, | |
503 | .exceptionSystemInformation3, | |
504 | .exceptionSystemInformation5 { | |
505 | flex: 0 0 200px; | |
506 | margin: 0 0 10px 0 !important; | |
507 | } | |
508 | ||
509 | .exceptionSystemInformation2, | |
510 | .exceptionSystemInformation4, | |
511 | .exceptionSystemInformation6 { | |
512 | flex: 0 0 calc(100% - 210px); | |
513 | margin: 0 0 10px 10px !important; | |
514 | max-width: calc(100% - 210px); | |
515 | } | |
516 | ||
517 | .exceptionSystemInformation1 { order: 1; } | |
518 | .exceptionSystemInformation2 { order: 2; } | |
519 | .exceptionSystemInformation3 { order: 3; } | |
520 | .exceptionSystemInformation4 { order: 4; } | |
521 | .exceptionSystemInformation5 { order: 5; } | |
522 | .exceptionSystemInformation6 { order: 6; } | |
523 | ||
524 | .exceptionSystemInformation .exceptionFieldValue { | |
525 | overflow: hidden; | |
526 | text-overflow: ellipsis; | |
527 | white-space: nowrap; | |
528 | } | |
529 | } | |
530 | </style> | |
531 | </head> | |
532 | <body class="exceptionBody"> | |
533 | <div class="exceptionContainer"> | |
534 | <div class="exceptionHeader"> | |
535 | <div class="exceptionBoundary"> | |
536 | <p class="exceptionTitle"><?php echo $exceptionTitle; ?></p> | |
537 | <p class="exceptionErrorCode"><?php echo str_replace('{$exceptionID}', $exceptionID, $exceptionSubtitle); ?></p> | |
538 | </div> | |
539 | </div> | |
540 | ||
541 | <div class="exceptionBoundary"> | |
542 | <?php echo $exceptionExplanation; ?> | |
5bc5a7e6 | 543 | </div> |
962c9241 | 544 | <?php if (!defined('EXCEPTION_PRIVACY') || EXCEPTION_PRIVACY !== 'private') { ?> |
614e2466 AE |
545 | <div class="exceptionBoundary"> |
546 | <p class="exceptionSubtitle">System Information</p> | |
547 | <ul class="exceptionSystemInformation"> | |
548 | <li class="exceptionSystemInformation1"> | |
549 | <p class="exceptionFieldTitle">PHP Version<span class="exceptionColon">:</span></p> | |
550 | <p class="exceptionFieldValue"><?php echo StringUtil::encodeHTML(phpversion()); ?></p> | |
551 | </li> | |
552 | <li class="exceptionSystemInformation3"> | |
bb2a04c7 | 553 | <p class="exceptionFieldTitle">WoltLab Suite Core<span class="exceptionColon">:</span></p> |
614e2466 AE |
554 | <p class="exceptionFieldValue"><?php echo StringUtil::encodeHTML(WCF_VERSION); ?></p> |
555 | </li> | |
556 | <li class="exceptionSystemInformation5"> | |
557 | <p class="exceptionFieldTitle">Peak Memory Usage<span class="exceptionColon">:</span></p> | |
558 | <p class="exceptionFieldValue"><?php echo round(memory_get_peak_usage() / 1024 / 1024, 3); ?>/<?php echo round(FileUtil::getMemoryLimit() / 1024 / 1024, 3); ?> MiB</p> | |
559 | </li> | |
560 | <li class="exceptionSystemInformation2"> | |
561 | <p class="exceptionFieldTitle">Request URI<span class="exceptionColon">:</span></p> | |
9b83d173 | 562 | <p class="exceptionFieldValue"><?php if (isset($_SERVER['REQUEST_METHOD'])) echo StringUtil::encodeHTML($_SERVER['REQUEST_METHOD']); ?> <?php if (isset($_SERVER['REQUEST_URI'])) echo StringUtil::encodeHTML($_SERVER['REQUEST_URI']); ?></p> |
614e2466 AE |
563 | </li> |
564 | <li class="exceptionSystemInformation4"> | |
565 | <p class="exceptionFieldTitle">Referrer<span class="exceptionColon">:</span></p> | |
566 | <p class="exceptionFieldValue"><?php if (isset($_SERVER['HTTP_REFERER'])) echo StringUtil::encodeHTML($_SERVER['HTTP_REFERER']); ?></p> | |
567 | </li> | |
568 | <li class="exceptionSystemInformation6"> | |
569 | <p class="exceptionFieldTitle">User Agent<span class="exceptionColon">:</span></p> | |
570 | <p class="exceptionFieldValue"><?php if (isset($_SERVER['HTTP_USER_AGENT'])) echo StringUtil::encodeHTML($_SERVER['HTTP_USER_AGENT']); ?></p> | |
571 | </li> | |
572 | </ul> | |
5bc5a7e6 | 573 | </div> |
614e2466 | 574 | |
5bc5a7e6 TD |
575 | <?php |
576 | $first = true; | |
ef68e2f2 TD |
577 | $exceptions = []; |
578 | $current = $e; | |
579 | do { | |
580 | $exceptions[] = $current; | |
581 | } | |
582 | while ($current = $current->getPrevious()); | |
583 | ||
584 | $e = array_pop($exceptions); | |
5bc5a7e6 TD |
585 | do { |
586 | ?> | |
614e2466 | 587 | <div class="exceptionBoundary"> |
ef68e2f2 | 588 | <p class="exceptionSubtitle"><?php if (!empty($exceptions) && $first) { echo "Original "; } else if (empty($exceptions) && !$first) { echo "Final "; } ?>Error</p> |
5bc5a7e6 | 589 | <?php if ($e instanceof SystemException && $e->getDescription()) { ?> |
614e2466 | 590 | <p class="exceptionText"><?php echo $e->getDescription(); ?></p> |
5bc5a7e6 | 591 | <?php } ?> |
614e2466 | 592 | <ul class="exceptionErrorDetails"> |
0b46efcf TD |
593 | <li> |
594 | <p class="exceptionFieldTitle">Error Type<span class="exceptionColon">:</span></p> | |
595 | <p class="exceptionFieldValue"><?php echo StringUtil::encodeHTML(get_class($e)); ?></p> | |
596 | </li> | |
614e2466 AE |
597 | <li> |
598 | <p class="exceptionFieldTitle">Error Message<span class="exceptionColon">:</span></p> | |
599 | <p class="exceptionFieldValue"><?php echo StringUtil::encodeHTML($e->getMessage()); ?></p> | |
600 | </li> | |
601 | <?php if ($e->getCode()) { ?> | |
602 | <li> | |
603 | <p class="exceptionFieldTitle">Error Code<span class="exceptionColon">:</span></p> | |
cdb994ab | 604 | <p class="exceptionFieldValue"><?php echo StringUtil::encodeHTML($e->getCode()); ?></p> |
614e2466 AE |
605 | </li> |
606 | <?php } ?> | |
607 | <li> | |
608 | <p class="exceptionFieldTitle">File<span class="exceptionColon">:</span></p> | |
609 | <p class="exceptionFieldValue" style="word-break: break-all"><?php echo StringUtil::encodeHTML(sanitizePath($e->getFile())); ?> (<?php echo $e->getLine(); ?>)</p> | |
610 | </li> | |
611 | ||
5bc5a7e6 TD |
612 | <?php |
613 | if ($e instanceof SystemException) { | |
614 | ob_start(); | |
615 | $e->show(); | |
616 | ob_end_clean(); | |
2b6fbe94 | 617 | |
5bc5a7e6 TD |
618 | $reflection = new \ReflectionClass($e); |
619 | $property = $reflection->getProperty('information'); | |
620 | $property->setAccessible(true); | |
621 | if ($property->getValue($e)) { | |
622 | throw new \Exception("Using the 'information' property of SystemException is not supported any more."); | |
623 | } | |
624 | } | |
b8e0376c TD |
625 | if ($e instanceof IExtraInformationException) { |
626 | foreach ($e->getExtraInformation() as list($key, $value)) { | |
614e2466 AE |
627 | ?> |
628 | <li> | |
629 | <p class="exceptionFieldTitle"><?php echo StringUtil::encodeHTML($key); ?><span class="exceptionColon">:</span></p> | |
630 | <p class="exceptionFieldValue"><?php echo StringUtil::encodeHTML($value); ?></p> | |
631 | </li> | |
632 | <?php | |
b8e0376c TD |
633 | } |
634 | } | |
5f9ea37a MS |
635 | |
636 | $templateContextLines = getTemplateContextLines($e); | |
637 | if (!empty($templateContextLines)) { | |
638 | ?> | |
639 | <li> | |
640 | <p class="exceptionFieldTitle">Template Context<span class="exceptionColon">:</span></p> | |
641 | <pre class="exceptionFieldValue"><?php echo StringUtil::encodeHTML(implode("", $templateContextLines));?></pre> | |
642 | </li> | |
643 | <?php | |
644 | } | |
5bc5a7e6 | 645 | ?> |
614e2466 AE |
646 | <li> |
647 | <p class="exceptionFieldTitle">Stack Trace<span class="exceptionColon">:</span></p> | |
648 | <ul class="exceptionStacktrace"> | |
649 | <?php | |
650 | $trace = sanitizeStacktrace($e); | |
614e2466 AE |
651 | for ($i = 0, $max = count($trace); $i < $max; $i++) { |
652 | ?> | |
653 | <li class="exceptionStacktraceFile"><?php echo '#'.$i.' '.StringUtil::encodeHTML($trace[$i]['file']).' ('.$trace[$i]['line'].')'.':'; ?></li> | |
654 | <li class="exceptionStacktraceCall"> | |
655 | <?php | |
656 | echo $trace[$i]['class'].$trace[$i]['type'].$trace[$i]['function'].'('; | |
657 | echo implode(', ', array_map(function ($item) { | |
658 | switch (gettype($item)) { | |
659 | case 'integer': | |
660 | case 'double': | |
661 | return $item; | |
662 | case 'NULL': | |
663 | return 'null'; | |
664 | case 'string': | |
aac60824 | 665 | return "'".addcslashes(StringUtil::encodeHTML($item), "\\'")."'"; |
614e2466 AE |
666 | case 'boolean': |
667 | return $item ? 'true' : 'false'; | |
668 | case 'array': | |
669 | $keys = array_keys($item); | |
670 | if (count($keys) > 5) return "[ ".count($keys)." items ]"; | |
671 | return '[ '.implode(', ', array_map(function ($item) { | |
672 | return $item.' => '; | |
673 | }, $keys)).']'; | |
674 | case 'object': | |
675 | return get_class($item); | |
403684df TD |
676 | case 'resource': |
677 | return 'resource('.get_resource_type($item).')'; | |
476ee399 MS |
678 | case 'resource (closed)': |
679 | return 'resource (closed)'; | |
614e2466 | 680 | } |
ef47717c MS |
681 | |
682 | throw new \LogicException('Unreachable'); | |
614e2466 AE |
683 | }, $trace[$i]['args'])); |
684 | echo ')</li>'; | |
685 | } | |
686 | ?> | |
687 | </ul> | |
688 | </li> | |
689 | </ul> | |
5bc5a7e6 TD |
690 | </div> |
691 | <?php | |
692 | $first = false; | |
ef68e2f2 | 693 | } while ($e = array_pop($exceptions)); |
5bc5a7e6 TD |
694 | ?> |
695 | <?php } ?> | |
696 | </div> | |
697 | </body> | |
698 | </html> | |
699 | <?php | |
322ced57 | 700 | } |
5bc5a7e6 | 701 | |
15de71ce TD |
702 | /** |
703 | * Returns the stack trace of the given Throwable with sensitive | |
704 | * information removed. | |
705 | * | |
1524f8c0 | 706 | * @param bool $ignorePaths If set to `true`: Don't call `sanitizePath`. |
15de71ce TD |
707 | * @return mixed[] |
708 | */ | |
badbbc6d | 709 | function sanitizeStacktrace(\Throwable $e, bool $ignorePaths = false) { |
5bc5a7e6 | 710 | $trace = $e->getTrace(); |
2b6fbe94 | 711 | |
5bc5a7e6 | 712 | return array_map(function ($item) use ($ignorePaths) { |
2b6fbe94 TD |
713 | if (!isset($item['file'])) $item['file'] = '[internal function]'; |
714 | if (!isset($item['line'])) $item['line'] = '?'; | |
715 | if (!isset($item['class'])) $item['class'] = ''; | |
716 | if (!isset($item['type'])) $item['type'] = ''; | |
335aae2b | 717 | if (!isset($item['args'])) $item['args'] = []; |
22eabfcf | 718 | |
7d000370 TD |
719 | if (!empty($item['args'])) { |
720 | if ($item['class']) { | |
721 | $function = new \ReflectionMethod($item['class'], $item['function']); | |
22eabfcf | 722 | } |
7d000370 TD |
723 | else { |
724 | $function = new \ReflectionFunction($item['function']); | |
80b38d09 | 725 | } |
22eabfcf | 726 | |
7d000370 TD |
727 | $parameters = $function->getParameters(); |
728 | $i = 0; | |
729 | foreach ($parameters as $parameter) { | |
730 | $isSensitive = false; | |
731 | if ( | |
732 | \method_exists($parameter, 'getAttributes') | |
733 | && !empty($parameter->getAttributes(\wcf\SensitiveArgument::class)) | |
734 | ) { | |
735 | $isSensitive = true; | |
736 | } | |
737 | if (\preg_match( | |
738 | '/(?:^(?:password|passphrase|secret)|(?:Password|Passphrase|Secret))/', | |
739 | $parameter->getName() | |
740 | )) { | |
741 | $isSensitive = true; | |
742 | } | |
743 | ||
744 | if ($isSensitive && isset($item['args'][$i])) { | |
745 | $item['args'][$i] = '[redacted]'; | |
746 | } | |
747 | $i++; | |
22eabfcf | 748 | } |
7d000370 TD |
749 | |
750 | // strip database credentials | |
9c63885b TD |
751 | if ( |
752 | preg_match('~\\\\?wcf\\\\system\\\\database\\\\[a-zA-Z]*Database~', $item['class']) | |
753 | || $item['class'] === 'PDO' | |
754 | ) { | |
7d000370 TD |
755 | if ($item['function'] === '__construct') { |
756 | $item['args'] = array_map(function () { | |
757 | return '[redacted]'; | |
758 | }, $item['args']); | |
759 | } | |
5bc5a7e6 TD |
760 | } |
761 | } | |
390ec505 | 762 | |
5bc5a7e6 TD |
763 | if (!$ignorePaths) { |
764 | $item['args'] = array_map(function ($item) { | |
b8e0376c | 765 | if (!is_string($item)) return $item; |
390ec505 TD |
766 | |
767 | if (preg_match('~^('.preg_quote($_SERVER['DOCUMENT_ROOT'], '~').'|'.preg_quote(WCF_DIR, '~').')~', $item)) { | |
5bc5a7e6 TD |
768 | $item = sanitizePath($item); |
769 | } | |
2b6fbe94 | 770 | |
390ec505 | 771 | return $item; |
5bc5a7e6 | 772 | }, $item['args']); |
390ec505 | 773 | |
2b6fbe94 | 774 | $item['file'] = sanitizePath($item['file']); |
5bc5a7e6 | 775 | } |
390ec505 | 776 | |
5bc5a7e6 TD |
777 | return $item; |
778 | }, $trace); | |
322ced57 | 779 | } |
390ec505 | 780 | |
15de71ce TD |
781 | /** |
782 | * Returns the given path relative to `WCF_DIR`, unless both, | |
783 | * `EXCEPTION_PRIVACY` is `public` and the debug mode is enabled. | |
784 | * | |
ef47717c | 785 | * @param string $path |
15de71ce TD |
786 | * @return string |
787 | */ | |
badbbc6d | 788 | function sanitizePath(string $path): string { |
962c9241 TD |
789 | if (WCF::debugModeIsEnabled() && defined('EXCEPTION_PRIVACY') && EXCEPTION_PRIVACY === 'public') { |
790 | return $path; | |
791 | } | |
390ec505 | 792 | |
5bc5a7e6 | 793 | return '*/'.FileUtil::removeTrailingSlash(FileUtil::getRelativePath(WCF_DIR, $path)); |
322ced57 AE |
794 | } |
795 | } |