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