From e623dc42cf635cfd8d3d6644b04144cfe02e588c Mon Sep 17 00:00:00 2001 From: Sebastian Mendel Date: Mon, 8 Oct 2007 14:58:18 +0000 Subject: [PATCH] new error handler - needs some more work, but works already: - errors are gathered through all pages till displayed - errors are displayed in main footer - configurable error gathering - configurable error displaying - configurable error logging --- libraries/Error.class.php | 506 ++++++++++++++++++++++++++++++ libraries/Error_Handler.class.php | 390 +++++++++++++++++++++++ libraries/common.inc.php | 5 + libraries/config.default.php | 45 +++ libraries/footer.inc.php | 6 + 5 files changed, 952 insertions(+) create mode 100644 libraries/Error.class.php create mode 100644 libraries/Error_Handler.class.php diff --git a/libraries/Error.class.php b/libraries/Error.class.php new file mode 100644 index 000000000..f957d273a --- /dev/null +++ b/libraries/Error.class.php @@ -0,0 +1,506 @@ + 'Error', + E_WARNING => 'Warning', + E_PARSE => 'Parsing Error', + E_NOTICE => 'Notice', + E_CORE_ERROR => 'Core Error', + E_CORE_WARNING => 'Core Warning', + E_COMPILE_ERROR => 'Compile Error', + E_COMPILE_WARNING => 'Compile Warning', + E_USER_ERROR => 'User Error', + E_USER_WARNING => 'User Warning', + E_USER_NOTICE => 'User Notice', + E_STRICT => 'Runtime Notice', + E_RECOVERABLE_ERROR => 'Catchable Fatal Error', + ); + + /** + * Error levels + * + * @var array + */ + static public $errorlevel = array ( + E_ERROR => 'error', + E_WARNING => 'warning', + E_PARSE => 'error', + E_NOTICE => 'notice', + E_CORE_ERROR => 'error', + E_CORE_WARNING => 'warning', + E_COMPILE_ERROR => 'error', + E_COMPILE_WARNING => 'warning', + E_USER_ERROR => 'error', + E_USER_WARNING => 'warning', + E_USER_NOTICE => 'notice', + E_STRICT => 'notice', + E_RECOVERABLE_ERROR => 'error', + ); + + /** + * The error number + * + * @var integer + */ + protected $_number = 0; + + /** + * The error message/string + * + * @var string + */ + protected $_string = ''; + + /** + * The file in which the error occured + * + * @var string + */ + protected $_file = ''; + + /** + * The line in which the error occured + * + * @var integer + */ + protected $_line = 0; + + /** + * Holds any variables defined in the context where the error occured + * f. e. $this if the error occured in an object method + * + * @var array + */ + protected $_context = array(); + + /** + * Holds the backtrace for this error + * + * @var array + */ + protected $_backtrace = array(); + + /** + * Whether the error was already displayed + * + * @var boolean + */ + protected $_is_displayed = false; + + /** + * Unique id + * + * @var string + */ + protected $_hash = null; + + /** + * Constructor + * + * @uses debug_backtrace() + * @uses PMA_Error::setNumber() + * @uses PMA_Error::setMessage() + * @uses PMA_Error::setFile() + * @uses PMA_Error::setLine() + * @uses PMA_Error::setContext() + * @uses PMA_Error::setBacktrace() + * @param integer $errno + * @param string $errstr + * @param string $errfile + * @param integer $errline + * @param array $errcontext + */ + public function __construct($errno, $errstr, $errfile, $errline, $errcontext) + { + $this->setNumber($errno); + $this->setMessage($errstr); + $this->setFile($errfile); + $this->setLine($errline); + $this->setContext($errcontext); + + $backtrace = debug_backtrace(); + // remove last two calls: debug_backtrace() and handleError() + unset($backtrace[0]); + unset($backtrace[1]); + + $this->setBacktrace($backtrace); + } + + /** + * sets PMA_Error::$_backtrace + * + * @uses PMA_Error::$_backtrace to set it + * @param array $backtrace + */ + public function setBacktrace($backtrace) + { + $this->_backtrace = $backtrace; + } + + /** + * sets PMA_Error::$_context + * + * @uses PMA_Error::$_context to set it + * @param array $context + */ + public function setContext($context) + { + $this->_context = $context; + } + + /** + * sets PMA_Error::$_line + * + * @uses PMA_Error::$_line to set it + * @param integer $line + */ + public function setLine($line) + { + $this->_line = $line; + } + + /** + * sets PMA_Error::$_string + * + * @uses PMA_Error::$_string to set it + * @param string $message + */ + public function setMessage($message) + { + $this->_string = $message; + } + + /** + * sets PMA_Error::$_number + * + * @uses PMA_Error::$_number to set it + * @param integer $number + */ + public function setNumber($number) + { + $this->_number = $number; + } + + /** + * sets PMA_Error::$_file + * + * @uses PMA_Error::$_file to set it + * @uses PMA_Error::relPath() + * @param string $file + */ + public function setFile($file) + { + $this->_file = PMA_Error::relPath($file); + } + + + /** + * returns unique PMA_Error::$_hash, if not exists it will be created + * + * @uses PMA_Error::$_hash as return value and to set it if required + * @uses PMA_Error::getNumber() + * @uses PMA_Error::getMessage() + * @uses PMA_Error::getFile() + * @uses PMA_Error::getLine() + * @uses PMA_Error::getBacktrace() + * @uses md5() + * @param string $file + * @return string PMA_Error::$_hash + */ + public function getHash() + { + if (null === $this->_hash) { + $this->_hash = md5( + $this->getNumber() . + $this->getMessage() . + $this->getFile() . + $this->getLine() . + $this->getBacktrace() + ); + } + + return $this->_hash; + } + + /** + * returns PMA_Error::$_backtrace + * + * @uses PMA_Error::$_backtrace as return value + * @return array PMA_Error::$_backtrace + */ + public function getBacktrace() + { + return $this->_backtrace; + } + + /** + * returns PMA_Error::$_file + * + * @uses PMA_Error::$_file as return value + * @return string PMA_Error::$_file + */ + public function getFile() + { + return $this->_file; + } + + /** + * returns PMA_Error::$_line + * + * @uses PMA_Error::$_line as return value + * @return integer PMA_Error::$_line + */ + public function getLine() + { + return $this->_line; + } + + /** + * returns PMA_Error::$_string + * + * @uses PMA_Error::$_string as return value + * @return string PMA_Error::$_string + */ + public function getMessage() + { + return $this->_string; + } + + /** + * returns PMA_Error::$_number + * + * @uses PMA_Error::$_number as return value + * @return integer PMA_Error::$_number + */ + public function getNumber() + { + return $this->_number; + } + + /** + * returns type of error + * + * @uses PMA_Error::$errortype + * @uses PMA_Error::getNumber() + * @return string type of error + */ + public function getType() + { + return PMA_Error::$errortype[$this->getNumber()]; + } + + /** + * returns level of error + * + * @uses PMA_Error::$$errorlevel + * @uses PMA_Error::getNumber() + * @return string level of error + */ + public function getLevel() + { + return PMA_Error::$errorlevel[$this->getNumber()]; + } + + /** + * returns title prepared for HTML Title-Tag + * + * @uses PMA_Error::getTitle() + * @uses htmlspecialchars() + * @uses substr() + * @return string HTML escaped and truncated title + */ + public function getHtmlTitle() + { + return htmlspecialchars(substr($this->getTitle(), 0, 100)); + } + + /** + * returns title for error + * + * @uses PMA_Error::getType() + * @uses PMA_Error::getMessage() + * @return string + */ + public function getTitle() + { + return $this->getType() . ': ' . $this->getMessage(); + } + + /** + * Display HTML backtrace + * + * @uses PMA_Error::getBacktrace() + * @uses PMA_Error::relPath() + * @uses PMA_Error::displayArg() + * @uses count() + */ + public function displayBacktrace() + { + foreach ($this->getBacktrace() as $step) { + echo PMA_Error::relPath($step['file']) . '#' . $step['line'] . ': '; + if (isset($step['class'])) { + echo $step['class'] . $step['type']; + } + echo $step['function'] . '('; + if (count($step['args']) > 1) { + echo "
\n"; + foreach ($step['args'] as $arg) { + echo "\t"; + $this->displayArg($arg); + echo ',' . "
\n"; + } + } elseif (count($step['args']) > 0) { + foreach ($step['args'] as $arg) { + $this->displayArg($arg, $step['function']); + } + } + echo ')' . "
\n"; + } + } + + /** + * Display a single function argument + * if $function is one of include/require the $arg is converted te relative path + * + * @uses PMA_Error::relPath() + * @uses in_array() + * @uses gettype() + * @param string $arg + * @param string $function + */ + protected function displayArg($arg, $function) + { + $include_functions = array( + 'include', + 'include_once', + 'require', + 'require_once', + ); + + if (in_array($function, $include_functions)) { + echo PMA_Error::relPath($arg); + } else { + echo gettype($arg) . ' ' . $arg; + } + } + + /** + * Displays the error in HTML + * + * @uses PMA_Error::getLevel() + * @uses PMA_Error::getType() + * @uses PMA_Error::getMessage() + * @uses PMA_Error::displayBacktrace() + * @uses PMA_Error::isDisplayed() + */ + public function display() + { + echo '
'; + echo '' . $this->getType() . ''; + echo ' (' . $this->getHash() . ')'; + echo "
\n"; + echo $this->getMessage(); + echo "
\n"; + echo "
\n"; + echo "Backtrace
\n"; + echo "
\n"; + echo $this->displayBacktrace(); + echo '
'; + $this->isDisplayed(true); + } + + /** + * sets and returns PMA_Error::$_is_displayed + * + * @uses PMA_Error::$_is_displayed + * @param boolean $is_displayed + * @return boolean PMA_Error::$_is_displayed + */ + public function isDisplayed($is_displayed = false) + { + if ($is_displayed){ + $this->_is_displayed = $is_displayed; + } + + return $this->_is_displayed; + } + + /** + * whether this error is a user error + * + * @uses E_USER_WARNING + * @uses E_USER_ERROR + * @uses E_USER_NOTICE + * @uses PMA_Error::getNumber() + * @return boolean + */ + public function isUserError() + { + return $this->getNumber() & (E_USER_WARNING | E_USER_ERROR | E_USER_NOTICE); + } + + /** + * return short relative path to phpMyAdmin basedir + * + * prevent path disclusore in error message, + * and make users feel save to submit error reports + * + * @static + * @uses PHP_OS() + * @uses __FILE__() + * @uses realpath() + * @uses substr() + * @uses explode() + * @uses dirname() + * @uses implode() + * @uses count() + * @uses array_pop() + * @uses str_replace() + * @param string $dest path to be shorten + * @return string shortened path + */ + static function relPath($dest) + { + $dest = realpath($dest); + + if (substr(PHP_OS, 0, 3) == 'WIN') { + $path_separator = '\\'; + } else { + $path_separator = '/'; + } + + $Ahere = explode($path_separator, realpath(dirname(__FILE__) . $path_separator . '..')); + $Adest = explode($path_separator, $dest); + + $result = '.'; + // && count ($Adest)>0 && count($Ahere)>0 ) + while (implode($path_separator, $Adest) != implode($path_separator, $Ahere)) { + if (count($Ahere) > count($Adest)) { + array_pop($Ahere); + $result .= $path_separator . '..'; + } else { + array_pop($Adest); + } + } + $path = $result . str_replace(implode($path_separator, $Adest), '', $dest); + return str_replace($path_separator . $path_separator, $path_separator, $path); + } +} +?> diff --git a/libraries/Error_Handler.class.php b/libraries/Error_Handler.class.php new file mode 100644 index 000000000..62f2c2363 --- /dev/null +++ b/libraries/Error_Handler.class.php @@ -0,0 +1,390 @@ +_errors); + } else { + // remember only not displayed errors + foreach ($this->_errors as $key => $error) { + if (! $error->isDisplayed()) { + $_SESSION['errors'][$key] = $error; + } + } + } + } + } + + /** + * returns array with all errors + * + * @uses PMA_Error_Handler::$_errors as return value + * @uses PMA_Error_Handler::_checkSavedErrors() + * @return array PMA_Error_Handler::$_errors + */ + protected function getErrors() + { + $this->_checkSavedErrors(); + return $this->_errors; + } + + /** + * Error handler - called when errors are triggered/occured + * + * The following error types cannot be handled with a user defined function: + * E_ERROR, E_PARSE, E_CORE_ERROR, E_CORE_WARNING, E_COMPILE_ERROR, + * E_COMPILE_WARNING, + * and most of E_STRICT raised in the file where set_error_handler() is called. + * + * @uses E_USER_NOTICE + * @uses E_USER_WARNING + * @uses E_STRICT + * @uses E_NOTICE + * @uses E_WARNING + * @uses E_CORE_WARNING + * @uses E_COMPILE_WARNING + * @uses E_USER_ERROR + * @uses E_ERROR + * @uses E_PARSE + * @uses E_CORE_ERROR + * @uses E_COMPILE_ERROR + * @uses E_RECOVERABLE_ERROR + * @uses PMA_Error + * @uses PMA_Error_Handler::$_errors + * @uses PMA_Error_Handler::_dispFatalError() + * @uses PMA_Error::getHash() + * @uses PMA_Error::getNumber() + * @param integer $errno + * @param string $errstr + * @param string $errfile + * @param integer $errline + * @param array $errcontext + */ + public function handleError($errno, $errstr, $errfile, $errline, $errcontext) + { + // create error object + $error = new PMA_Error($errno, $errstr, $errfile, $errline, $errcontext); + + // do not repeat errors + $this->_errors[$error->getHash()] = $error; + + switch ($error->getNumber()) { + case E_USER_NOTICE: + case E_USER_WARNING: + case E_STRICT: + case E_NOTICE: + case E_WARNING: + case E_CORE_WARNING: + case E_COMPILE_WARNING: + // just collect the error + // display is called from outside + break; + case E_USER_ERROR: + case E_ERROR: + case E_PARSE: + case E_CORE_ERROR: + case E_COMPILE_ERROR: + case E_RECOVERABLE_ERROR: + default: + // FATAL error, dislay it and exit + $this->_dispFatalError($error); + exit; + break; + } + } + + /** + * log error to configured log facility + * + * @todo finish! + * @uses PMA_Error::getMessage() + * @uses error_log() + * @param PMA_Error $error + */ + protected function _logError($error) + { + return error_log($error->getMessage()); + } + + /** + * trigger a custom error + * + * @uses trigger_error() + * @param string $errorInfo + * @param integer $errorNumber + * @param string $file + * @param integer $line + */ + public function triggerError($errorInfo, $errorNumber = null, $file = null, $line = null) + { + // we could also extract file and line from backtrace and call handleError() directly + trigger_error($errorInfo, $errorNumber); + } + + /** + * display fatal error and exit + * + * @uses headers_sent() + * @uses PMA_Error::display() + * @uses PMA_Error_Handler::_dispPageStart() + * @uses PMA_Error_Handler::_dispPageEnd() + * @param PMA_Error $error + */ + protected function _dispFatalError($error) + { + if (! headers_sent()) { + $this->_dispPageStart($error); + } + $error->display(); + $this->_dispPageEnd(); + exit; + } + + /** + * display the whole error page with all errors + * + * @uses headers_sent() + * @uses PMA_Error_Handler::dispAllErrors() + * @uses PMA_Error_Handler::_dispPageStart() + * @uses PMA_Error_Handler::_dispPageEnd() + */ + public function dispErrorPage() + { + if (! headers_sent()) { + $this->_dispPageStart(); + } + $this->dispAllErrors(); + $this->_dispPageEnd(); + } + + /** + * display user errors not displayed + * + * @uses PMA_Error_Handler::getErrors() + * @uses PMA_Error::isDisplayed() + * @uses PMA_Error::isUserError() + * @uses PMA_Error::display() + */ + public function dispUserErrors() + { + foreach ($this->getErrors() as $error) { + if ($error->isUserError() && ! $error->isDisplayed()) { + $error->display(); + } + } + } + + /** + * display HTML header + * + * @uses PMA_Error::getTitle() + * @param PMA_error $error + */ + protected function _dispPageStart($error = null) + { + echo ''; + if ($error) { + echo $error->getTitle(); + } else { + echo 'phpMyAdmin error reporting page'; + } + echo ''; + } + + /** + * display HTML footer + * + */ + protected function _dispPageEnd() + { + echo ''; + } + + /** + * display all errors regardless already displayed or user errors + * + * @uses PMA_Error_Handler::getErrors() + * @uses PMA_Error::display() + */ + public function dispAllErrors() + { + foreach ($this->getErrors() as $error) { + $error->display(); + } + } + + /** + * display errors not displayed + * + * @uses $cfg['Error_Handler']['display'] + * @uses PMA_Error_Handler::getErrors() + * @uses PMA_Error_Handler::dispUserErrors() + * @uses PMA_Error::isDisplayed() + * @uses PMA_Error::display() + */ + public function dispErrors() + { + if ($GLOBALS['cfg']['Error_Handler']['display']) { + foreach ($this->getErrors() as $error) { + if (! $error->isDisplayed()) { + $error->display(); + } + } + } else { + $this->dispUserErrors(); + } + } + + /** + * look in session for saved errors + * + * @uses $_SESSION['errors'] + * @uses PMA_Error_Handler::$_errors + * @uses array_merge() + */ + protected function _checkSavedErrors() + { + if (isset($_SESSION['errors'])) { + // restore saved errors + $this->_errors = array_merge($_SESSION['errors'], $this->_errors); + + // delet stored errors + unset($_SESSION['errors']); + } + } + + /** + * return count of errors + * + * @uses PMA_Error_Handler::getErrors() + * @uses count() + * @return integer number of errors occoured + */ + public function countErrors() + { + return count($this->getErrors()); + } + + /** + * return count of user errors + * + * @uses PMA_Error_Handler::countErrors() + * @uses PMA_Error_Handler::getErrors() + * @uses PMA_Error::isUserError() + * @return integer number of user errors occoured + */ + public function countUserErrors() + { + $count = 0; + if ($this->countErrors()) { + foreach ($this->getErrors() as $error) { + if ($error->isUserError()) { + $count++; + } + } + } + + return $count; + } + + /** + * whether use errors occured or not + * + * @uses PMA_Error_Handler::countUserErrors() + * @return boolean + */ + public function hasUserErrors() + { + return (bool) $this->countUserErrors(); + } + + /** + * whether errors occured or not + * + * @uses PMA_Error_Handler::countErrors() + * @return boolean + */ + public function hasErrors() + { + return (bool) $this->countErrors(); + } + + /** + * number of errors to be displayed + * + * @uses $cfg['Error_Handler']['display'] + * @uses PMA_Error_Handler::countErrors() + * @uses PMA_Error_Handler::countUserErrors() + * @return integer number of errors to be displayed + */ + public function countDisplayErrors() + { + if ($GLOBALS['cfg']['Error_Handler']['display']) { + return $this->countErrors(); + } else { + return $this->countUserErrors(); + } + } + + /** + * whether there are errors to display or not + * + * @uses PMA_Error_Handler::countDisplayErrors() + * @return boolean + */ + public function hasDisplayErrors() + { + return (bool) $this->countDisplayErrors(); + } +} +?> diff --git a/libraries/common.inc.php b/libraries/common.inc.php index 6fb362a1e..5ffe3f596 100644 --- a/libraries/common.inc.php +++ b/libraries/common.inc.php @@ -29,6 +29,10 @@ * @version $Id$ */ +require_once './libraries/Error_Handler.class.php'; + +$GLOBALS['error_handler'] = new PMA_Error_Handler(); + // at this point PMA_PHP_INT_VERSION is not yet defined if (version_compare(phpversion(), '6', 'lt')) { /** @@ -140,6 +144,7 @@ $variables_whitelist = array ( '_ENV', '_COOKIE', '_SESSION', + 'error_handler', ); foreach (get_defined_vars() as $key => $value) { diff --git a/libraries/config.default.php b/libraries/config.default.php index 1d5b39104..ddc2f17fb 100644 --- a/libraries/config.default.php +++ b/libraries/config.default.php @@ -479,6 +479,51 @@ $cfg['VerboseMultiSubmit'] = true; $cfg['AllowArbitraryServer'] = false; +/******************************************************************************* + * Error handler configuration + * + * this configures phpMyAdmins own error handler, it is used to avoid information + * dislcosure, gather errors for logging, reporting and displaying + * + * @global array $cfg['Error_Handler'] + */ +$cfg['Error_Handler'] = array(); + +/** + * whether to display errors or not + * + * this does not affect errors of type E_USER_* + * + * @global boolean $cfg['Error_Handler']['display'] + */ +$cfg['Error_Handler']['display'] = false; + +/** + * where to log errors, false or empty to disable + * + * + * // EXAMPLE log to std PHP error log + * $cfg['Error_Handler']['log'] = array(0); + * // EXAMPLE mail errors + * $cfg['Error_Handler']['log'] = array(1, 'admin@example.org'); + * // EXAMPLE append to specific file + * $cfg['Error_Handler']['log'] = array(3, '/var/log/phpmyadmin_error.log'); + * + * + * @see http://php.net/error_log + * @global string $cfg['Error_Handler']['log'] + */ +$cfg['Error_Handler']['log'] = false; + +/** + * gather all errors in session to be displayed on a error reporting page + * for viewing and/or sending to phpMyAdmin developer team + * + * @global boolean $cfg['Error_Handler']['gather'] + */ +$cfg['Error_Handler']['gather'] = false; + + /******************************************************************************* * Left frame setup */ diff --git a/libraries/footer.inc.php b/libraries/footer.inc.php index 82f20c3f1..3af680ce3 100644 --- a/libraries/footer.inc.php +++ b/libraries/footer.inc.php @@ -58,6 +58,12 @@ if (! PMA_isValid($_REQUEST['no_history']) && empty($GLOBALS['error_message']) $GLOBALS['sql_query']); } +if ($GLOBALS['error_handler']->hasDisplayErrors()) { + echo '
'; + $GLOBALS['error_handler']->dispErrors(); + echo '
'; +} + if (count($GLOBALS['footnotes'])) { echo '
'; foreach ($GLOBALS['footnotes'] as $footnote) {