Commit | Line | Data |
---|---|---|
158bd3ca TD |
1 | <?php |
2 | /** | |
3 | * @author Marcel Werk | |
f406809a | 4 | * @copyright 2001-2016 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 MS |
19 | |
20 | /** | |
21 | * Escapes a string for use in sql query. | |
22 | * | |
23 | * @see \wcf\system\database\Database::escapeString() | |
24 | * @param string $string | |
25 | * @return string | |
26 | */ | |
5bc5a7e6 TD |
27 | function escapeString($string) { |
28 | return WCF::getDB()->escapeString($string); | |
29 | } | |
f406809a AE |
30 | |
31 | /** | |
32 | * Helper method to output debug data for all passed variables, | |
33 | * uses `print_r()` for arrays and objects, `var_dump()` otherwise. | |
34 | */ | |
35 | function wcfDebug() { | |
36 | echo "<pre>"; | |
37 | ||
38 | $args = func_get_args(); | |
39 | $length = count($args); | |
40 | if ($length === 0) { | |
a5cf4a28 | 41 | echo "ERROR: No arguments provided.<hr>"; |
f406809a AE |
42 | } |
43 | else { | |
44 | for ($i = 0; $i < $length; $i++) { | |
45 | $arg = $args[$i]; | |
46 | ||
47 | echo "<h2>Argument {$i} (" . gettype($arg) . ")</h2>"; | |
48 | ||
49 | if (is_array($arg) || is_object($arg)) { | |
50 | print_r($arg); | |
51 | } | |
52 | else { | |
53 | var_dump($arg); | |
54 | } | |
55 | ||
56 | echo "<hr>"; | |
57 | } | |
58 | } | |
59 | ||
60 | $backtrace = debug_backtrace(); | |
61 | ||
62 | // output call location to help finding these debug outputs again | |
63 | echo "wcfDebug() called in {$backtrace[0]['file']} on line {$backtrace[0]['line']}"; | |
64 | ||
65 | echo "</pre>"; | |
66 | ||
67 | exit; | |
68 | } | |
2b6fbe94 | 69 | |
5bc5a7e6 TD |
70 | // define DOCUMENT_ROOT on IIS if not set |
71 | if (PHP_EOL == "\r\n") { | |
72 | if (!isset($_SERVER['DOCUMENT_ROOT']) && isset($_SERVER['SCRIPT_FILENAME'])) { | |
73 | $_SERVER['DOCUMENT_ROOT'] = str_replace( '\\', '/', substr($_SERVER['SCRIPT_FILENAME'], 0, 0 - strlen($_SERVER['PHP_SELF']))); | |
74 | } | |
75 | if (!isset($_SERVER['DOCUMENT_ROOT']) && isset($_SERVER['PATH_TRANSLATED'])) { | |
76 | $_SERVER['DOCUMENT_ROOT'] = str_replace( '\\', '/', substr(str_replace('\\\\', '\\', $_SERVER['PATH_TRANSLATED']), 0, 0 - strlen($_SERVER['PHP_SELF']))); | |
77 | } | |
2b6fbe94 | 78 | |
5bc5a7e6 TD |
79 | if (!isset($_SERVER['REQUEST_URI'])) { |
80 | $_SERVER['REQUEST_URI'] = substr($_SERVER['PHP_SELF'], 1); | |
81 | if (isset($_SERVER['QUERY_STRING'])) { | |
82 | $_SERVER['REQUEST_URI'] .= '?' . $_SERVER['QUERY_STRING']; | |
83 | } | |
84 | } | |
85 | } | |
dcb3a44c | 86 | } |
322ced57 | 87 | |
fc23fa20 | 88 | // @codingStandardsIgnoreStart |
5bc5a7e6 | 89 | namespace wcf\functions\exception { |
fc23fa20 | 90 | use wcf\system\WCF; |
b8e0376c | 91 | use wcf\system\exception\IExtraInformationException; |
5bc5a7e6 | 92 | use wcf\system\exception\SystemException; |
5bc5a7e6 TD |
93 | use wcf\util\FileUtil; |
94 | use wcf\util\StringUtil; | |
15de71ce TD |
95 | |
96 | /** | |
97 | * Logs the given Throwable. | |
98 | * | |
99 | * @param \Throwable|\Exception $e | |
100 | * @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. | |
101 | * @return string The ID of the log entry. | |
102 | */ | |
76e90abc | 103 | function logThrowable($e, &$logFile = null) { |
8891d4b9 | 104 | if ($logFile === null) $logFile = WCF_DIR . 'log/' . gmdate('Y-m-d', TIME_NOW) . '.txt'; |
b8149369 | 105 | touch($logFile); |
e4499881 | 106 | |
b8149369 TD |
107 | // don't forget to update ExceptionLogViewPage, when changing the log file format |
108 | $message = gmdate('r', TIME_NOW)."\n". | |
109 | 'Message: '.str_replace("\n", ' ', $e->getMessage())."\n". | |
110 | 'PHP version: '.phpversion()."\n". | |
111 | 'WCF version: '.WCF_VERSION."\n". | |
112 | 'Request URI: '.(isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : '')."\n". | |
113 | 'Referrer: '.(isset($_SERVER['HTTP_REFERER']) ? str_replace("\n", ' ', $_SERVER['HTTP_REFERER']) : '')."\n". | |
114 | 'User Agent: '.(isset($_SERVER['HTTP_USER_AGENT']) ? str_replace("\n", ' ', $_SERVER['HTTP_USER_AGENT']) : '')."\n". | |
115 | 'Peak Memory Usage: '.memory_get_peak_usage().'/'.FileUtil::getMemoryLimit()."\n"; | |
116 | do { | |
117 | $message .= "======\n". | |
118 | 'Error Class: '.get_class($e)."\n". | |
119 | 'Error Message: '.str_replace("\n", ' ', $e->getMessage())."\n". | |
120 | 'Error Code: '.intval($e->getCode())."\n". | |
121 | 'File: '.str_replace("\n", ' ', $e->getFile()).' ('.$e->getLine().')'."\n". | |
122 | 'Extra Information: '.($e instanceof IExtraInformationException ? base64_encode(serialize($e->getExtraInformation())) : '-')."\n". | |
72f128c3 | 123 | 'Stack Trace: '.json_encode(array_map(function ($item) { |
b8149369 TD |
124 | $item['args'] = array_map(function ($item) { |
125 | switch (gettype($item)) { | |
126 | case 'object': | |
127 | return get_class($item); | |
128 | case 'array': | |
129 | return array_map(function () { | |
130 | return '[redacted]'; | |
131 | }, $item); | |
132 | default: | |
133 | return $item; | |
134 | } | |
135 | }, $item['args']); | |
e4499881 | 136 | |
b8149369 | 137 | return $item; |
72f128c3 | 138 | }, sanitizeStacktrace($e, true)))."\n"; |
b8149369 | 139 | } |
63b9817b | 140 | while ($e = $e->getPrevious()); |
e4499881 | 141 | |
b8149369 TD |
142 | // calculate Exception-ID |
143 | $exceptionID = sha1($message); | |
2b6fbe94 | 144 | $entry = "<<<<<<<<".$exceptionID."<<<<\n".$message."<<<<\n\n"; |
e4499881 | 145 | |
b8149369 TD |
146 | file_put_contents($logFile, $entry, FILE_APPEND); |
147 | return $exceptionID; | |
148 | } | |
2b6fbe94 | 149 | |
15de71ce TD |
150 | /** |
151 | * Pretty prints the given Throwable. It is recommended to `exit;` | |
152 | * the request after calling this function. | |
153 | * | |
154 | * @param \Throwable|\Exception $e | |
155 | * @throws \Exception | |
156 | */ | |
5bc5a7e6 | 157 | function printThrowable($e) { |
76e90abc | 158 | $exceptionID = logThrowable($e, $logFile); |
614e2466 AE |
159 | |
160 | $exceptionTitle = $exceptionSubtitle = $exceptionExplanation = ''; | |
161 | $logFile = sanitizePath($logFile); | |
162 | try { | |
04edc2ff AE |
163 | if (WCF::getLanguage() !== null) { |
164 | $exceptionTitle = WCF::getLanguage()->get('wcf.global.exception.title', true); | |
165 | $exceptionSubtitle = str_replace('{$exceptionID}', $exceptionID, WCF::getLanguage()->get('wcf.global.exception.subtitle', true)); | |
166 | $exceptionExplanation = str_replace('{$logFile}', $logFile, WCF::getLanguage()->get('wcf.global.exception.explanation', true)); | |
167 | } | |
614e2466 AE |
168 | } |
169 | catch (\Exception $e) { | |
170 | // ignore | |
171 | } | |
172 | catch (\Throwable $e) { | |
173 | // ignore | |
174 | } | |
175 | ||
176 | if (!$exceptionTitle || !$exceptionSubtitle || !$exceptionExplanation) { | |
177 | // one or more failed, fallback to english | |
178 | $exceptionTitle = 'An error has occured'; | |
a7eb5dda | 179 | $exceptionSubtitle = 'Internal error code: <span class="exceptionInlineCodeWrapper"><span class="exceptionInlineCode">'.$exceptionID.'</span></span>'; |
614e2466 AE |
180 | $exceptionExplanation = <<<EXPLANATION |
181 | <p class="exceptionSubtitle">What happened?</p> | |
182 | <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> | |
183 | <p class="exceptionText"> </p> <!-- required to ensure spacing after copy & paste --> | |
184 | <p class="exceptionText"> | |
185 | The error code can be used by an administrator to lookup the full error message in the Administration Control Panel via “Logs » Errors”. | |
819d7822 | 186 | 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 |
187 | </p> |
188 | <p class="exceptionText"> </p> <!-- required to ensure spacing after copy & paste --> | |
189 | <p class="exceptionText">Notice: The error code was randomly generated and has no use beyond looking up the full message.</p> | |
190 | EXPLANATION; | |
191 | ||
192 | } | |
193 | ||
194 | /* | |
195 | * A notice on the HTML used below: | |
196 | * | |
197 | * It might appear a bit weird to use <p> all over the place where semantically | |
198 | * other elements would fit in way better. The reason behind this is that we avoid | |
199 | * inheriting unwanted styles (e.g. exception displayed in an overlay) and that | |
200 | * the output needs to be properly readable when copied & pasted somewhere. | |
201 | * | |
202 | * Besides the visual appearance, the output was built to provide a maximum of | |
203 | * compatibility and readability when pasted somewhere else, e.g. a WYSIWYG editor | |
204 | * without the potential of messing up the formatting and thus harming the readability. | |
205 | */ | |
5bc5a7e6 TD |
206 | ?><!DOCTYPE html> |
207 | <html> | |
208 | <head> | |
962c9241 | 209 | <?php if (!defined('EXCEPTION_PRIVACY') || EXCEPTION_PRIVACY !== 'private') { ?> |
5bc5a7e6 TD |
210 | <title>Fatal Error: <?php echo StringUtil::encodeHTML($e->getMessage()); ?></title> |
211 | <?php } else { ?> | |
212 | <title>Fatal Error</title> | |
213 | <?php } ?> | |
614e2466 AE |
214 | <meta charset="utf-8"> |
215 | <meta name="viewport" content="width=device-width, initial-scale=1"> | |
5bc5a7e6 | 216 | <style> |
614e2466 | 217 | .exceptionBody { |
8972cde9 MW |
218 | background-color: rgb(250, 250, 250); |
219 | color: rgb(44, 62, 80); | |
614e2466 AE |
220 | margin: 0; |
221 | padding: 0; | |
5bc5a7e6 | 222 | } |
614e2466 AE |
223 | |
224 | .exceptionContainer { | |
225 | box-sizing: border-box; | |
226 | font-family: 'Segoe UI', 'Lucida Grande', 'Helvetica Neue', Helvetica, Arial, sans-serif; | |
227 | font-size: 14px; | |
228 | padding-bottom: 20px; | |
5bc5a7e6 | 229 | } |
614e2466 AE |
230 | |
231 | .exceptionContainer * { | |
232 | box-sizing: inherit; | |
614e2466 AE |
233 | line-height: 1.5em; |
234 | margin: 0; | |
235 | padding: 0; | |
5bc5a7e6 | 236 | } |
614e2466 AE |
237 | |
238 | .exceptionHeader { | |
8972cde9 | 239 | background-color: rgb(58, 109, 156); |
89130385 | 240 | padding: 30px 0; |
5bc5a7e6 | 241 | } |
614e2466 AE |
242 | |
243 | .exceptionTitle { | |
244 | color: #fff; | |
245 | font-size: 28px; | |
246 | font-weight: 300; | |
5bc5a7e6 | 247 | } |
614e2466 AE |
248 | |
249 | .exceptionErrorCode { | |
250 | color: #fff; | |
251 | margin-top: .5em; | |
5bc5a7e6 | 252 | } |
614e2466 AE |
253 | |
254 | .exceptionErrorCode .exceptionInlineCode { | |
255 | background-color: rgb(52, 73, 94); | |
256 | border-radius: 3px; | |
257 | color: #fff; | |
258 | font-family: monospace; | |
259 | padding: 3px 10px; | |
260 | white-space: nowrap; | |
5bc5a7e6 | 261 | } |
614e2466 AE |
262 | |
263 | .exceptionSubtitle { | |
264 | border-bottom: 1px solid rgb(238, 238, 238); | |
614e2466 AE |
265 | font-size: 24px; |
266 | font-weight: 300; | |
267 | margin-bottom: 15px; | |
268 | padding-bottom: 10px; | |
5bc5a7e6 | 269 | } |
614e2466 AE |
270 | |
271 | .exceptionContainer > .exceptionBoundary { | |
272 | margin-top: 30px; | |
5bc5a7e6 | 273 | } |
614e2466 AE |
274 | |
275 | .exceptionText .exceptionInlineCodeWrapper { | |
276 | border: 1px solid rgb(169, 169, 169); | |
277 | border-radius: 3px; | |
278 | padding: 2px 5px; | |
5bc5a7e6 | 279 | } |
614e2466 AE |
280 | |
281 | .exceptionText .exceptionInlineCode { | |
282 | font-family: monospace; | |
283 | white-space: nowrap; | |
5bc5a7e6 | 284 | } |
614e2466 AE |
285 | |
286 | .exceptionFieldTitle { | |
287 | color: rgb(59, 109, 169); | |
5bc5a7e6 | 288 | } |
614e2466 AE |
289 | |
290 | .exceptionFieldTitle .exceptionColon { | |
291 | /* hide colon in browser, but will be visible after copy & paste */ | |
292 | opacity: 0; | |
5bc5a7e6 | 293 | } |
614e2466 AE |
294 | |
295 | .exceptionFieldValue { | |
296 | font-size: 18px; | |
3d8a295e | 297 | min-height: 1.5em; |
614e2466 AE |
298 | } |
299 | ||
300 | .exceptionSystemInformation, | |
301 | .exceptionErrorDetails, | |
302 | .exceptionStacktrace { | |
303 | list-style-type: none; | |
304 | } | |
305 | ||
306 | .exceptionSystemInformation > li:not(:first-child), | |
307 | .exceptionErrorDetails > li:not(:first-child) { | |
308 | margin-top: 10px; | |
309 | } | |
310 | ||
311 | .exceptionStacktrace { | |
312 | display: block; | |
313 | margin-top: 5px; | |
314 | overflow: auto; | |
315 | padding-bottom: 20px; | |
316 | } | |
317 | ||
318 | .exceptionStacktraceFile, | |
319 | .exceptionStacktraceFile span, | |
320 | .exceptionStacktraceCall, | |
321 | .exceptionStacktraceCall span { | |
322 | font-family: monospace !important; | |
323 | white-space: nowrap !important; | |
324 | } | |
325 | ||
326 | .exceptionStacktraceCall + .exceptionStacktraceFile { | |
327 | margin-top: 5px; | |
328 | } | |
329 | ||
330 | .exceptionStacktraceCall { | |
331 | padding-left: 40px; | |
332 | } | |
333 | ||
334 | .exceptionStacktraceCall, | |
335 | .exceptionStacktraceCall span { | |
336 | color: rgb(102, 102, 102) !important; | |
337 | font-size: 13px !important; | |
338 | } | |
339 | ||
340 | /* mobile */ | |
341 | @media (max-width: 767px) { | |
342 | .exceptionBoundary { | |
343 | min-width: 320px; | |
344 | padding: 0 10px; | |
dd6a2623 | 345 | } |
614e2466 AE |
346 | |
347 | .exceptionText .exceptionInlineCodeWrapper { | |
348 | display: inline-block; | |
349 | overflow: auto; | |
dd6a2623 | 350 | } |
614e2466 AE |
351 | |
352 | .exceptionErrorCode .exceptionInlineCode { | |
353 | font-size: 13px; | |
354 | padding: 2px 5px; | |
dd6a2623 | 355 | } |
614e2466 AE |
356 | } |
357 | ||
358 | /* desktop */ | |
359 | @media (min-width: 768px) { | |
360 | .exceptionBoundary { | |
361 | margin: 0 auto; | |
362 | max-width: 1400px; | |
363 | min-width: 1200px; | |
364 | padding: 0 10px; | |
365 | } | |
366 | ||
367 | .exceptionSystemInformation { | |
368 | display: flex; | |
369 | flex-wrap: wrap; | |
370 | } | |
371 | ||
372 | .exceptionSystemInformation1, | |
373 | .exceptionSystemInformation3, | |
374 | .exceptionSystemInformation5 { | |
375 | flex: 0 0 200px; | |
376 | margin: 0 0 10px 0 !important; | |
377 | } | |
378 | ||
379 | .exceptionSystemInformation2, | |
380 | .exceptionSystemInformation4, | |
381 | .exceptionSystemInformation6 { | |
382 | flex: 0 0 calc(100% - 210px); | |
383 | margin: 0 0 10px 10px !important; | |
384 | max-width: calc(100% - 210px); | |
385 | } | |
386 | ||
387 | .exceptionSystemInformation1 { order: 1; } | |
388 | .exceptionSystemInformation2 { order: 2; } | |
389 | .exceptionSystemInformation3 { order: 3; } | |
390 | .exceptionSystemInformation4 { order: 4; } | |
391 | .exceptionSystemInformation5 { order: 5; } | |
392 | .exceptionSystemInformation6 { order: 6; } | |
393 | ||
394 | .exceptionSystemInformation .exceptionFieldValue { | |
395 | overflow: hidden; | |
396 | text-overflow: ellipsis; | |
397 | white-space: nowrap; | |
398 | } | |
399 | } | |
400 | </style> | |
401 | </head> | |
402 | <body class="exceptionBody"> | |
403 | <div class="exceptionContainer"> | |
404 | <div class="exceptionHeader"> | |
405 | <div class="exceptionBoundary"> | |
406 | <p class="exceptionTitle"><?php echo $exceptionTitle; ?></p> | |
407 | <p class="exceptionErrorCode"><?php echo str_replace('{$exceptionID}', $exceptionID, $exceptionSubtitle); ?></p> | |
408 | </div> | |
409 | </div> | |
410 | ||
411 | <div class="exceptionBoundary"> | |
412 | <?php echo $exceptionExplanation; ?> | |
5bc5a7e6 | 413 | </div> |
962c9241 | 414 | <?php if (!defined('EXCEPTION_PRIVACY') || EXCEPTION_PRIVACY !== 'private') { ?> |
614e2466 AE |
415 | <div class="exceptionBoundary"> |
416 | <p class="exceptionSubtitle">System Information</p> | |
417 | <ul class="exceptionSystemInformation"> | |
418 | <li class="exceptionSystemInformation1"> | |
419 | <p class="exceptionFieldTitle">PHP Version<span class="exceptionColon">:</span></p> | |
420 | <p class="exceptionFieldValue"><?php echo StringUtil::encodeHTML(phpversion()); ?></p> | |
421 | </li> | |
422 | <li class="exceptionSystemInformation3"> | |
bb2a04c7 | 423 | <p class="exceptionFieldTitle">WoltLab Suite Core<span class="exceptionColon">:</span></p> |
614e2466 AE |
424 | <p class="exceptionFieldValue"><?php echo StringUtil::encodeHTML(WCF_VERSION); ?></p> |
425 | </li> | |
426 | <li class="exceptionSystemInformation5"> | |
427 | <p class="exceptionFieldTitle">Peak Memory Usage<span class="exceptionColon">:</span></p> | |
428 | <p class="exceptionFieldValue"><?php echo round(memory_get_peak_usage() / 1024 / 1024, 3); ?>/<?php echo round(FileUtil::getMemoryLimit() / 1024 / 1024, 3); ?> MiB</p> | |
429 | </li> | |
430 | <li class="exceptionSystemInformation2"> | |
431 | <p class="exceptionFieldTitle">Request URI<span class="exceptionColon">:</span></p> | |
432 | <p class="exceptionFieldValue"><?php if (isset($_SERVER['REQUEST_URI'])) echo StringUtil::encodeHTML($_SERVER['REQUEST_URI']); ?></p> | |
433 | </li> | |
434 | <li class="exceptionSystemInformation4"> | |
435 | <p class="exceptionFieldTitle">Referrer<span class="exceptionColon">:</span></p> | |
436 | <p class="exceptionFieldValue"><?php if (isset($_SERVER['HTTP_REFERER'])) echo StringUtil::encodeHTML($_SERVER['HTTP_REFERER']); ?></p> | |
437 | </li> | |
438 | <li class="exceptionSystemInformation6"> | |
439 | <p class="exceptionFieldTitle">User Agent<span class="exceptionColon">:</span></p> | |
440 | <p class="exceptionFieldValue"><?php if (isset($_SERVER['HTTP_USER_AGENT'])) echo StringUtil::encodeHTML($_SERVER['HTTP_USER_AGENT']); ?></p> | |
441 | </li> | |
442 | </ul> | |
5bc5a7e6 | 443 | </div> |
614e2466 | 444 | |
5bc5a7e6 TD |
445 | <?php |
446 | $first = true; | |
447 | do { | |
448 | ?> | |
614e2466 AE |
449 | <div class="exceptionBoundary"> |
450 | <p class="exceptionSubtitle"><?php if (!$e->getPrevious() && !$first) { echo "Original "; } else if ($e->getPrevious() && $first) { echo "Final "; } ?>Error</p> | |
5bc5a7e6 | 451 | <?php if ($e instanceof SystemException && $e->getDescription()) { ?> |
614e2466 | 452 | <p class="exceptionText"><?php echo $e->getDescription(); ?></p> |
5bc5a7e6 | 453 | <?php } ?> |
614e2466 | 454 | <ul class="exceptionErrorDetails"> |
0b46efcf TD |
455 | <li> |
456 | <p class="exceptionFieldTitle">Error Type<span class="exceptionColon">:</span></p> | |
457 | <p class="exceptionFieldValue"><?php echo StringUtil::encodeHTML(get_class($e)); ?></p> | |
458 | </li> | |
614e2466 AE |
459 | <li> |
460 | <p class="exceptionFieldTitle">Error Message<span class="exceptionColon">:</span></p> | |
461 | <p class="exceptionFieldValue"><?php echo StringUtil::encodeHTML($e->getMessage()); ?></p> | |
462 | </li> | |
463 | <?php if ($e->getCode()) { ?> | |
464 | <li> | |
465 | <p class="exceptionFieldTitle">Error Code<span class="exceptionColon">:</span></p> | |
466 | <p class="exceptionFieldValue"><?php echo intval($e->getCode()); ?></p> | |
467 | </li> | |
468 | <?php } ?> | |
469 | <li> | |
470 | <p class="exceptionFieldTitle">File<span class="exceptionColon">:</span></p> | |
471 | <p class="exceptionFieldValue" style="word-break: break-all"><?php echo StringUtil::encodeHTML(sanitizePath($e->getFile())); ?> (<?php echo $e->getLine(); ?>)</p> | |
472 | </li> | |
473 | ||
5bc5a7e6 TD |
474 | <?php |
475 | if ($e instanceof SystemException) { | |
476 | ob_start(); | |
477 | $e->show(); | |
478 | ob_end_clean(); | |
2b6fbe94 | 479 | |
5bc5a7e6 TD |
480 | $reflection = new \ReflectionClass($e); |
481 | $property = $reflection->getProperty('information'); | |
482 | $property->setAccessible(true); | |
483 | if ($property->getValue($e)) { | |
484 | throw new \Exception("Using the 'information' property of SystemException is not supported any more."); | |
485 | } | |
486 | } | |
b8e0376c TD |
487 | if ($e instanceof IExtraInformationException) { |
488 | foreach ($e->getExtraInformation() as list($key, $value)) { | |
614e2466 AE |
489 | ?> |
490 | <li> | |
491 | <p class="exceptionFieldTitle"><?php echo StringUtil::encodeHTML($key); ?><span class="exceptionColon">:</span></p> | |
492 | <p class="exceptionFieldValue"><?php echo StringUtil::encodeHTML($value); ?></p> | |
493 | </li> | |
494 | <?php | |
b8e0376c TD |
495 | } |
496 | } | |
5bc5a7e6 | 497 | ?> |
614e2466 AE |
498 | <li> |
499 | <p class="exceptionFieldTitle">Stack Trace<span class="exceptionColon">:</span></p> | |
500 | <ul class="exceptionStacktrace"> | |
501 | <?php | |
502 | $trace = sanitizeStacktrace($e); | |
614e2466 AE |
503 | for ($i = 0, $max = count($trace); $i < $max; $i++) { |
504 | ?> | |
505 | <li class="exceptionStacktraceFile"><?php echo '#'.$i.' '.StringUtil::encodeHTML($trace[$i]['file']).' ('.$trace[$i]['line'].')'.':'; ?></li> | |
506 | <li class="exceptionStacktraceCall"> | |
507 | <?php | |
508 | echo $trace[$i]['class'].$trace[$i]['type'].$trace[$i]['function'].'('; | |
509 | echo implode(', ', array_map(function ($item) { | |
510 | switch (gettype($item)) { | |
511 | case 'integer': | |
512 | case 'double': | |
513 | return $item; | |
514 | case 'NULL': | |
515 | return 'null'; | |
516 | case 'string': | |
aac60824 | 517 | return "'".addcslashes(StringUtil::encodeHTML($item), "\\'")."'"; |
614e2466 AE |
518 | case 'boolean': |
519 | return $item ? 'true' : 'false'; | |
520 | case 'array': | |
521 | $keys = array_keys($item); | |
522 | if (count($keys) > 5) return "[ ".count($keys)." items ]"; | |
523 | return '[ '.implode(', ', array_map(function ($item) { | |
524 | return $item.' => '; | |
525 | }, $keys)).']'; | |
526 | case 'object': | |
527 | return get_class($item); | |
528 | } | |
ef47717c MS |
529 | |
530 | throw new \LogicException('Unreachable'); | |
614e2466 AE |
531 | }, $trace[$i]['args'])); |
532 | echo ')</li>'; | |
533 | } | |
534 | ?> | |
535 | </ul> | |
536 | </li> | |
537 | </ul> | |
5bc5a7e6 TD |
538 | </div> |
539 | <?php | |
540 | $first = false; | |
63b9817b | 541 | } while ($e = $e->getPrevious()); |
5bc5a7e6 TD |
542 | ?> |
543 | <?php } ?> | |
544 | </div> | |
545 | </body> | |
546 | </html> | |
547 | <?php | |
322ced57 | 548 | } |
5bc5a7e6 | 549 | |
15de71ce TD |
550 | /** |
551 | * Returns the stack trace of the given Throwable with sensitive | |
552 | * information removed. | |
553 | * | |
554 | * @param \Throwable|\Exception $e | |
555 | * @param boolean $ignorePaths If set to `true`: Don't call `sanitizePath`. | |
556 | * @return mixed[] | |
557 | */ | |
5bc5a7e6 TD |
558 | function sanitizeStacktrace($e, $ignorePaths = false) { |
559 | $trace = $e->getTrace(); | |
2b6fbe94 | 560 | |
5bc5a7e6 | 561 | return array_map(function ($item) use ($ignorePaths) { |
2b6fbe94 TD |
562 | if (!isset($item['file'])) $item['file'] = '[internal function]'; |
563 | if (!isset($item['line'])) $item['line'] = '?'; | |
564 | if (!isset($item['class'])) $item['class'] = ''; | |
565 | if (!isset($item['type'])) $item['type'] = ''; | |
335aae2b | 566 | if (!isset($item['args'])) $item['args'] = []; |
2b6fbe94 | 567 | |
5bc5a7e6 | 568 | // strip database credentials |
2b6fbe94 TD |
569 | if (preg_match('~\\\\?wcf\\\\system\\\\database\\\\[a-zA-Z]*Database~', $item['class']) || $item['class'] === 'PDO') { |
570 | if ($item['function'] === '__construct') { | |
571 | $item['args'] = array_map(function () { | |
572 | return '[redacted]'; | |
573 | }, $item['args']); | |
5bc5a7e6 TD |
574 | } |
575 | } | |
390ec505 | 576 | |
5bc5a7e6 TD |
577 | if (!$ignorePaths) { |
578 | $item['args'] = array_map(function ($item) { | |
b8e0376c | 579 | if (!is_string($item)) return $item; |
390ec505 TD |
580 | |
581 | if (preg_match('~^('.preg_quote($_SERVER['DOCUMENT_ROOT'], '~').'|'.preg_quote(WCF_DIR, '~').')~', $item)) { | |
5bc5a7e6 TD |
582 | $item = sanitizePath($item); |
583 | } | |
2b6fbe94 | 584 | |
390ec505 | 585 | return $item; |
5bc5a7e6 | 586 | }, $item['args']); |
390ec505 | 587 | |
2b6fbe94 | 588 | $item['file'] = sanitizePath($item['file']); |
5bc5a7e6 | 589 | } |
390ec505 | 590 | |
5bc5a7e6 TD |
591 | return $item; |
592 | }, $trace); | |
322ced57 | 593 | } |
390ec505 | 594 | |
15de71ce TD |
595 | /** |
596 | * Returns the given path relative to `WCF_DIR`, unless both, | |
597 | * `EXCEPTION_PRIVACY` is `public` and the debug mode is enabled. | |
598 | * | |
ef47717c | 599 | * @param string $path |
15de71ce TD |
600 | * @return string |
601 | */ | |
5bc5a7e6 | 602 | function sanitizePath($path) { |
962c9241 TD |
603 | if (WCF::debugModeIsEnabled() && defined('EXCEPTION_PRIVACY') && EXCEPTION_PRIVACY === 'public') { |
604 | return $path; | |
605 | } | |
390ec505 | 606 | |
5bc5a7e6 | 607 | return '*/'.FileUtil::removeTrailingSlash(FileUtil::getRelativePath(WCF_DIR, $path)); |
322ced57 AE |
608 | } |
609 | } | |
fc23fa20 | 610 | // @codingStandardsIgnoreEnd |