Improve Exception / Error printing
authorTim Düsterhus <duesterhus@woltlab.com>
Mon, 28 Sep 2015 15:13:06 +0000 (17:13 +0200)
committerTim Düsterhus <duesterhus@woltlab.com>
Tue, 1 Dec 2015 21:15:48 +0000 (22:15 +0100)
wcfsetup/install/files/lib/core.functions.php
wcfsetup/install/files/lib/system/WCF.class.php
wcfsetup/install/files/lib/system/exception/SystemException.class.php

index 8bd22db7ac417c49fa198c0b1c34f83ac5dc7b24..35b2121c3cb6a6325c9d9329160330bceeef122e 100644 (file)
  * @package    com.woltlab.wcf
  * @category   Community Framework
  */
-use wcf\system\WCF;
-
-// set exception handler
-set_exception_handler([ WCF::class, 'handleException' ]);
-// set php error handler
-set_error_handler([ WCF::class, 'handleError' ], E_ALL);
-// set shutdown function
-register_shutdown_function([ WCF::class, 'destruct' ]);
-// set autoload function
-spl_autoload_register([ WCF::class, 'autoload' ]);
-
-// define escape string shortcut
-function escapeString($string) {
-       return WCF::getDB()->escapeString($string);
+namespace {
+       use wcf\system\WCF;
+       
+       // set exception handler
+       set_exception_handler([ WCF::class, 'handleException' ]);
+       // set php error handler
+       set_error_handler([ WCF::class, 'handleError' ], E_ALL);
+       // set shutdown function
+       register_shutdown_function([ WCF::class, 'destruct' ]);
+       // set autoload function
+       spl_autoload_register([ WCF::class, 'autoload' ]);
+       
+       // define escape string shortcut
+       function escapeString($string) {
+               return WCF::getDB()->escapeString($string);
+       }
+       
+       // define DOCUMENT_ROOT on IIS if not set
+       if (PHP_EOL == "\r\n") {
+               if (!isset($_SERVER['DOCUMENT_ROOT']) && isset($_SERVER['SCRIPT_FILENAME'])) {
+                       $_SERVER['DOCUMENT_ROOT'] = str_replace( '\\', '/', substr($_SERVER['SCRIPT_FILENAME'], 0, 0 - strlen($_SERVER['PHP_SELF'])));
+               }
+               if (!isset($_SERVER['DOCUMENT_ROOT']) && isset($_SERVER['PATH_TRANSLATED'])) {
+                       $_SERVER['DOCUMENT_ROOT'] = str_replace( '\\', '/', substr(str_replace('\\\\', '\\', $_SERVER['PATH_TRANSLATED']), 0, 0 - strlen($_SERVER['PHP_SELF'])));
+               }
+               
+               if (!isset($_SERVER['REQUEST_URI'])) {
+                       $_SERVER['REQUEST_URI'] = substr($_SERVER['PHP_SELF'], 1);
+                       if (isset($_SERVER['QUERY_STRING'])) {
+                               $_SERVER['REQUEST_URI'] .= '?' . $_SERVER['QUERY_STRING'];
+                       }
+               }
+       }
 }
 
-// define DOCUMENT_ROOT on IIS if not set
-if (PHP_EOL == "\r\n") {
-       if (!isset($_SERVER['DOCUMENT_ROOT']) && isset($_SERVER['SCRIPT_FILENAME'])) {
-               $_SERVER['DOCUMENT_ROOT'] = str_replace( '\\', '/', substr($_SERVER['SCRIPT_FILENAME'], 0, 0 - strlen($_SERVER['PHP_SELF'])));
+namespace wcf\functions\exception {
+       use wcf\system\exception\SystemException;
+       use wcf\system\WCF;
+       use wcf\util\FileUtil;
+       use wcf\util\StringUtil;
+       
+       function printThrowable($e) {
+               $exceptionID = '123456'; // TODO
+       ?><!DOCTYPE html>
+       <html>
+               <head>
+                       <?php if (WCF::debugModeIsEnabled()) { ?>
+                       <title>Fatal Error: <?php echo StringUtil::encodeHTML($e->getMessage()); ?></title>
+                       <?php } else { ?>
+                       <title>Fatal Error</title>
+                       <?php } ?>
+                       <meta charset="utf-8" />
+                       <style>
+                               .exception {
+                                       font-size: 13px !important;
+                                       font-family: 'Trebuchet MS', Arial, sans-serif !important;
+                                       color: #444 !important;
+                                       text-align: left !important;
+                                       border: 1px solid #036 !important;
+                                       border-radius: 7px !important;
+                                       background-color: #eee !important;
+                                       overflow: auto !important;
+                               }
+                               .exception h1 {
+                                       font-size: 130% !important;
+                                       font-weight: bold !important;
+                                       line-height: 1.1 !important;
+                                       text-shadow: 0 -1px 0 #003 !important;
+                                       color: #fff !important;
+                                       word-wrap: break-word !important;
+                                       border-bottom: 1px solid #036;
+                                       border-radius: 6px 6px 0 0 !important;
+                                       background-color: #369 !important;
+                                       margin: 0 !important;
+                                       padding: 5px 10px !important;
+                               }
+                               .exception div {
+                                       border-top: 1px solid #fff !important;
+                                       border-bottom-right-radius: 6px !important;
+                                       border-bottom-left-radius: 6px !important;
+                                       padding: 0 10px 10px !important;
+                               }
+                               .exception p {
+                                       margin: 0 !important;
+                               }
+                               .exception h2 {
+                                       font-size: 130% !important;
+                                       font-weight: bold !important;
+                                       color: #369 !important;
+                                       text-shadow: 0 1px 0 #fff !important;
+                                       margin: 5px 0 !important;
+                               }
+                               .exception code {
+                                       padding: 0px 3px !important;
+                                       border-radius: 3px !important;
+                                       background-color: white !important;
+                                       border: 1px solid #036 !important;
+                               }
+                               .exception .pre {
+                                       white-space: pre !important;
+                                       font-family: monospace; !important;
+                                       text-overflow: ellipsis !important;
+                                       overflow: hidden !important;
+                               }
+                               .exception .pre:hover {
+                                       text-overflow: clip !important;
+                                       overflow: auto !important;
+                               }
+                               .exception dt {
+                                       float: left !important;
+                                       width: 200px !important;
+                                       text-align: right !important;
+                                       font-weight: bold !important;
+                               }
+                               .exception dd {
+                                       margin-left: 210px !important;
+                               }
+                               .exception dd::before {
+                                       content: "\FEFF" !important;
+                               }
+                               .exception dl {
+                                       margin: 0 !important;
+                               }
+                               .exception dl::after {
+                                       clear: both !important;
+                               }
+                       </style>
+               </head>
+               <body>
+                       <div class="exception">
+                               <?php if (WCF::debugModeIsEnabled()) { ?>
+                               <h1>Fatal Error: <?php echo StringUtil::encodeHTML($e->getMessage()); ?></h1>
+                               <?php } else { ?>
+                               <h1>Fatal Error <!-- :( --></h1>
+                               <?php } ?>
+                               <div>
+                                       <h2>What happened?</h2>
+                                       <p>An unrecoverable error occured while trying to handle your request. The internal error code is as follows: <code><?php echo $exceptionID; ?></code></p>
+                                       <p>Please send this code to the administrator to help him fix the issue.</p>
+                               </div>
+                               <?php if (WCF::debugModeIsEnabled()) { ?>
+                                       <div>
+                                               <h2>System Information:</h2>
+                                               <dl>
+                                                       <dt>PHP Version:</dt> <dd><?php echo StringUtil::encodeHTML(phpversion()); ?></dd>
+                                                       <dt>WCF Version:</dt> <dd><?php echo StringUtil::encodeHTML(WCF_VERSION); ?></dd>
+                                                       <dt>Date:</dt> <dd><?php echo gmdate('r'); ?></dd>
+                                                       <dt>Request URI:</dt> <dd><?php if (isset($_SERVER['REQUEST_URI'])) echo StringUtil::encodeHTML($_SERVER['REQUEST_URI']); ?></dd>
+                                                       <dt>Referrer:</dt> <dd><?php if (isset($_SERVER['HTTP_REFERER'])) echo StringUtil::encodeHTML($_SERVER['HTTP_REFERER']); ?></dd>
+                                                       <dt>User Agent:</dt> <dd><?php if (isset($_SERVER['HTTP_USER_AGENT'])) echo StringUtil::encodeHTML($_SERVER['HTTP_USER_AGENT']); ?></dd>
+                                                       <dt>Peak Memory Usage:</dt> <dd><?php echo $peakMemory = memory_get_peak_usage(); ?>/<?php echo $memoryLimit = FileUtil::getMemoryLimit(); ?> Byte (<?php echo round($peakMemory / 1024 / 1024, 3); ?>/<?php echo round($memoryLimit / 1024 / 1024, 3); ?> MiB)</dd>
+                                               </dl>
+                                       </div>
+                                       <?php
+                                       $first = true;
+                                       do {
+                                       ?>
+                                       <div>
+                                               <h2><?php if (!$e->getPrevious() && !$first) { echo "Original "; } else if ($e->getPrevious() && $first) { echo "Final "; } ?>Error:</h2>
+                                               <?php if ($e instanceof SystemException && $e->getDescription()) { ?>
+                                                       <p><?php echo $e->getDescription(); ?></p>
+                                               <?php } ?>
+                                               <dl>
+                                                       <dt>Error Class:</dt> <dd><?php echo get_class($e); ?></dd>
+                                                       <dt>Error Message:</dt> <dd><?php echo StringUtil::encodeHTML($e->getMessage()); ?></dd>
+                                                       <dt>Error Code:</dt> <dd><?php echo intval($e->getCode()); ?></dd>
+                                                       <dt>File:</dt> <dd><?php echo StringUtil::encodeHTML(sanitizePath($e->getFile())); ?> (<?php echo $e->getLine(); ?>)</dd>
+                                                       <?php
+                                                       if ($e instanceof SystemException) {
+                                                               ob_start();
+                                                               $e->show();
+                                                               ob_end_clean();
+                                                               
+                                                               $reflection = new \ReflectionClass($e);
+                                                               $property = $reflection->getProperty('information');
+                                                               $property->setAccessible(true);
+                                                               if ($property->getValue($e)) {
+                                                                       throw new \Exception("Using the 'information' property of SystemException is not supported any more.");
+                                                               }
+                                                       }
+                                                       ?>
+                                                       <dt>Stack Trace:</dt>
+                                                       <dd class="pre"><?php
+                                                               $trace = array_map(function ($item) {
+                                                                       if (!isset($item['file'])) $item['file'] = '[internal function]';
+                                                                       if (!isset($item['line'])) $item['line'] = '?';
+                                                                       if (!isset($item['class'])) $item['class'] = '';
+                                                                       if (!isset($item['type'])) $item['type'] = '';
+                                                                       
+                                                                       return $item;
+                                                               }, sanitizeStacktrace($e));
+                                                               $pathLength = array_reduce($trace, function ($carry, $item) {
+                                                                       return max($carry, mb_strlen($item['file'].$item['line']));
+                                                               }, 0) + 3;
+                                                               for ($i = 0, $max = count($trace); $i < $max; $i++) {
+                                                                       echo '#'.$i.' '.str_pad(StringUtil::encodeHTML($trace[$i]['file']).' ('.$trace[$i]['line'].')', $pathLength, ' ', STR_PAD_RIGHT).':';
+                                                                       echo ' '.$trace[$i]['class'].$trace[$i]['type'].$trace[$i]['function'].'(';
+                                                                       echo implode(', ', array_map(function ($item) {
+                                                                               switch (gettype($item)) {
+                                                                                       case 'integer':
+                                                                                       case 'double':
+                                                                                               return $item;
+                                                                                       case 'NULL':
+                                                                                               return 'null';
+                                                                                       case 'string':
+                                                                                               return '<span title="'.StringUtil::encodeHTML($item).'">"'.StringUtil::encodeHTML(addcslashes(StringUtil::truncate($item, 25, StringUtil::HELLIP, true), "\n")).'"</span>';
+                                                                                       case 'boolean':
+                                                                                               return $item ? 'true' : 'false';
+                                                                                       case 'array':
+                                                                                               $keys = array_keys($item);
+                                                                                               if (count($keys) > 5) return "[ ".StringUtil::HELLIP." ]";
+                                                                                               return '[ '.implode(', ', array_map(function ($item) {
+                                                                                                       return $item.' => ';
+                                                                                               }, $keys)).']';
+                                                                                       case 'object':
+                                                                                               return get_class($item);
+                                                                               }
+                                                                       }, $trace[$i]['args']));
+                                                                       echo ")\n";
+                                                               }
+                                                               ?></dd>
+                                               </dl>
+                                       </div>
+                                       <?php
+                                       $first = false;
+                                       } while ($e = $e->getPrevious());
+                                       ?>
+                               <?php } ?>
+                       </div>
+               </body>
+       </html>
+       <?php
        }
-       if (!isset($_SERVER['DOCUMENT_ROOT']) && isset($_SERVER['PATH_TRANSLATED'])) {
-               $_SERVER['DOCUMENT_ROOT'] = str_replace( '\\', '/', substr(str_replace('\\\\', '\\', $_SERVER['PATH_TRANSLATED']), 0, 0 - strlen($_SERVER['PHP_SELF'])));
+
+       function sanitizeStacktrace($e, $ignorePaths = false) {
+               $trace = $e->getTrace();
+               
+               return array_map(function ($item) use ($ignorePaths) {
+                       // strip database credentials
+                       if (isset($item['class'])) {
+                               if (preg_match('~\\\\?wcf\\\\system\\\\database\\\\[a-zA-Z]*Database~', $item['class']) || $item['class'] === 'PDO') {
+                                       if ($item['function'] === '__construct') {
+                                               $item['args'] = array_map(function () {
+                                                       return '[redacted]';
+                                               }, $item['args']);
+                                       }
+                               }
+                       }
+                       
+                       if (!$ignorePaths) {
+                               $item['args'] = array_map(function ($item) {
+                                       if (preg_match('~^'.preg_quote($_SERVER['DOCUMENT_ROOT'], '~').'~', $item)) {
+                                               $item = sanitizePath($item);
+                                       }
+                                       
+                                       return preg_replace('~^'.preg_quote(WCF_DIR, '~').'~', '*/', $item);
+                               }, $item['args']);
+                               
+                               if (isset($item['file'])) $item['file'] = sanitizePath($item['file']);
+                       }
+                       
+                       return $item;
+               }, $trace);
        }
-       
-       if (!isset($_SERVER['REQUEST_URI'])) {
-               $_SERVER['REQUEST_URI'] = substr($_SERVER['PHP_SELF'], 1);
-               if (isset($_SERVER['QUERY_STRING'])) {
-                       $_SERVER['REQUEST_URI'] .= '?' . $_SERVER['QUERY_STRING'];
-               }
+
+       function sanitizePath($path) {
+               return '*/'.FileUtil::removeTrailingSlash(FileUtil::getRelativePath(WCF_DIR, $path));
        }
 }
index 92be155773367fcbb280e9793d4bfdd40d535d36..863a0e91b03db7c8bef2a2d6d6cbd5a2e7089fe9 100644 (file)
@@ -233,22 +233,31 @@ class WCF {
         * @param       \Exception      $e
         */
        public static final function handleException($e) {
-               try {
-                       if (!($e instanceof \Exception)) throw $e;
-                       
-                       if ($e instanceof IPrintableException) {
-                               $e->show();
-                               exit;
-                       }
-                       
-                       // repack Exception
-                       self::handleException(new SystemException($e->getMessage(), $e->getCode(), '', $e));
+               // backwards compatibility
+               if ($e instanceof IPrintableException) {
+                       $e->show();
+                       exit;
                }
-               catch (\Throwable $exception) {
-                       die("<pre>WCF::handleException() Unhandled exception: ".$exception->getMessage()."\n\n".$exception->getTraceAsString());
-               }
-               catch (\Exception $exception) {
-                       die("<pre>WCF::handleException() Unhandled exception: ".$exception->getMessage()."\n\n".$exception->getTraceAsString());
+               
+               @header('HTTP/1.1 503 Service Unavailable');
+               try {
+                       \wcf\functions\exception\printThrowable($e);
+               }
+               catch (\Throwable $e2) {
+                       echo "<pre>An Exception was thrown while handling an Exception:\n\n";
+                       echo $e2;
+                       echo "\n\nwas thrown while:\n\n";
+                       echo $e;
+                       echo "\n\nwas handled.</pre>";
+                       exit;
+               }
+               catch (\Exception $e2) {
+                       echo "<pre>An Exception was thrown while handling an Exception:\n\n";
+                       echo $e2;
+                       echo "\n\nwas thrown while:\n\n";
+                       echo $e;
+                       echo "\n\nwas handled.</pre>";
+                       exit;
                }
        }
        
index e4b664fab478fb6099a74daee094b78d6e67f91c..ef1e4458b9d4b68b3c5d43ef37bcfd6870d870a9 100644 (file)
@@ -14,8 +14,7 @@ use wcf\util\StringUtil;
  * @subpackage system.exception
  * @category   Community Framework
  */
-// @codingStandardsIgnoreFile
-class SystemException extends LoggedException implements IPrintableException {
+class SystemException extends LoggedException {
        /**
         * error description
         * @var string
@@ -60,134 +59,6 @@ class SystemException extends LoggedException implements IPrintableException {
         * @see \wcf\system\exception\IPrintableException::show()
         */
        public function show() {
-               // send status code
-               @header('HTTP/1.1 503 Service Unavailable');
                
-               // show user-defined system-exception
-               if (defined('SYSTEMEXCEPTION_FILE') && file_exists(SYSTEMEXCEPTION_FILE)) {
-                       require(SYSTEMEXCEPTION_FILE);
-                       return;
-               }
-               
-               $innerMessage = '';
-               try {
-                       if (is_object(WCF::getLanguage())) {
-                               $innerMessage = WCF::getLanguage()->get('wcf.global.error.exception', true);
-                       }
-               }
-               catch (\Exception $e) { }
-               
-               if (empty($innerMessage)) {
-                       $innerMessage = 'Please send the ID above to the site administrator.<br />The error message can be looked up at &ldquo;ACP &raquo; Logs &raquo; Errors&rdquo;.';
-               }
-               
-               // print report
-               $e = ($this->getPrevious() ?: $this);
-               ?><!DOCTYPE html>
-               <html>
-                       <head>
-                               <title>Fatal error: <?php echo StringUtil::encodeHTML($this->_getMessage()); ?></title>
-                               <meta charset="utf-8" />
-                               <style>
-                                       .systemException {
-                                               font-family: 'Trebuchet MS', Arial, sans-serif !important;
-                                               font-size: 80% !important;
-                                               text-align: left !important;
-                                               border: 1px solid #036;
-                                               border-radius: 7px;
-                                               background-color: #eee !important;
-                                               overflow: auto !important;
-                                       }
-                                       .systemException h1 {
-                                               font-size: 130% !important;
-                                               font-weight: bold !important;
-                                               line-height: 1.1 !important;
-                                               text-decoration: none !important;
-                                               text-shadow: 0 -1px 0 #003 !important;
-                                               color: #fff !important;
-                                               word-wrap: break-word !important;
-                                               border-bottom: 1px solid #036;
-                                               border-top-right-radius: 6px;
-                                               border-top-left-radius: 6px;
-                                               background-color: #369 !important;
-                                               margin: 0 !important;
-                                               padding: 5px 10px !important;
-                                       }
-                                       .systemException div {
-                                               border-top: 1px solid #fff;
-                                               border-bottom-right-radius: 6px;
-                                               border-bottom-left-radius: 6px;
-                                               padding: 0 10px !important;
-                                       }
-                                       .systemException h2 {
-                                               font-size: 130% !important;
-                                               font-weight: bold !important;
-                                               color: #369 !important;
-                                               text-shadow: 0 1px 0 #fff !important;
-                                               margin: 5px 0 !important;
-                                       }
-                                       .systemException pre, .systemException p {
-                                               text-shadow: none !important;
-                                               color: #555 !important;
-                                               margin: 0 !important;
-                                       }
-                                       .systemException pre {
-                                               font-size: .85em !important;
-                                               font-family: "Courier New" !important;
-                                               text-overflow: ellipsis;
-                                               padding-bottom: 1px;
-                                               overflow: hidden !important;
-                                       }
-                                       .systemException pre:hover{
-                                               text-overflow: clip;
-                                               overflow: auto !important;
-                                       }
-                               </style>
-                       </head>
-                       <body>
-                               <div class="systemException">
-                                       <h1>Fatal error: <?php if(!$this->getExceptionID()) { ?>Unable to write log file, please make &quot;<?php echo FileUtil::unifyDirSeparator(WCF_DIR); ?>log/&quot; writable!<?php } else { echo StringUtil::encodeHTML($this->_getMessage()); } ?></h1>
-                                       
-                                       <?php if (WCF::debugModeIsEnabled()) { ?>
-                                               <div>
-                                                       <?php if ($this->getDescription()) { ?><p><br /><?php echo $this->getDescription(); ?></p><?php } ?>
-                                                       
-                                                       <h2>Information:</h2>
-                                                       <p>
-                                                               <b>id:</b> <code><?php echo $this->getExceptionID(); ?></code><br>
-                                                               <b>error message:</b> <?php echo StringUtil::encodeHTML($this->_getMessage()); ?><br>
-                                                               <b>error code:</b> <?php echo intval($e->getCode()); ?><br>
-                                                               <?php echo $this->information; ?>
-                                                               <b>file:</b> <?php echo StringUtil::encodeHTML($e->getFile()); ?> (<?php echo $e->getLine(); ?>)<br>
-                                                               <b>php version:</b> <?php echo StringUtil::encodeHTML(phpversion()); ?><br>
-                                                               <b>wcf version:</b> <?php echo WCF_VERSION; ?><br>
-                                                               <b>date:</b> <?php echo gmdate('r'); ?><br>
-                                                               <b>request:</b> <?php if (isset($_SERVER['REQUEST_URI'])) echo StringUtil::encodeHTML($_SERVER['REQUEST_URI']); ?><br>
-                                                               <b>referer:</b> <?php if (isset($_SERVER['HTTP_REFERER'])) echo StringUtil::encodeHTML($_SERVER['HTTP_REFERER']); ?><br>
-                                                       </p>
-                                                       
-                                                       <h2>Stacktrace:</h2>
-                                                       <pre><?php echo StringUtil::encodeHTML($this->__getTraceAsString()); ?></pre>
-                                               </div>
-                                       <?php } else { ?>
-                                               <div>
-                                                       <h2>Information:</h2>
-                                                       <p>
-                                                               <?php if (!$this->getExceptionID()) { ?>
-                                                                       Unable to write log file, please make &quot;<?php echo FileUtil::unifyDirSeparator(WCF_DIR); ?>log/&quot; writable!
-                                                               <?php } else { ?>
-                                                                       <b>ID:</b> <code><?php echo $this->getExceptionID(); ?></code><br>
-                                                                       <?php echo $innerMessage; ?>
-                                                               <?php } ?>
-                                                       </p>
-                                               </div>
-                                       <?php } ?>
-                                       
-                                       <?php echo $this->functions; ?>
-                               </div>
-                       </body>
-               </html>
-               
-               <?php
        }
 }