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
This commit is contained in:
Sebastian Mendel
2007-10-08 14:58:18 +00:00
parent bed8a4cc50
commit e623dc42cf
5 changed files with 952 additions and 0 deletions

506
libraries/Error.class.php Normal file
View File

@@ -0,0 +1,506 @@
<?php
/* vim: set expandtab sw=4 ts=4 sts=4: */
/**
* Holds class PMA_Error
*
* @version $Id$
*/
/**
* a single error
*
*/
class PMA_Error
{
/**
* Error types
*
* @var array
*/
static public $errortype = array (
E_ERROR => '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 "<br />\n";
foreach ($step['args'] as $arg) {
echo "\t";
$this->displayArg($arg);
echo ',' . "<br />\n";
}
} elseif (count($step['args']) > 0) {
foreach ($step['args'] as $arg) {
$this->displayArg($arg, $step['function']);
}
}
echo ')' . "<br />\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 '<div class="' . $this->getLevel() . '">';
echo '<strong>' . $this->getType() . '</strong>';
echo ' (' . $this->getHash() . ')';
echo "<br />\n";
echo $this->getMessage();
echo "<br />\n";
echo "<br />\n";
echo "<strong>Backtrace</strong><br />\n";
echo "<br />\n";
echo $this->displayBacktrace();
echo '</div>';
$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);
}
}
?>

View File

@@ -0,0 +1,390 @@
<?php
/* vim: set expandtab sw=4 ts=4 sts=4: */
/**
* Holds class PMA_Error_Handler
*
* @version $Id$
*/
/**
*
*/
require_once './libraries/Error.class.php';
/**
* handling errors
*
*/
class PMA_Error_Handler
{
/**
* holds errors to be displayed or reported later ...
*
* @var array of PMA_Error
*/
protected $_errors = array();
/**
* Constructor - set PHP error handler
*
* @uses set_error_handler()
*/
public function __construct()
{
set_error_handler(array($this, 'handleError'));
}
/**
* Destructor
*
* stores errors in session
*
* @uses $_SESSION['errors']
* @uses array_merge()
* @uses PMA_Error_Handler::$_errors
* @uses PMA_Error::isDisplayed()
*/
public function __destruct()
{
if (isset($_SESSION)) {
if (! isset($_SESSION['errors'])) {
$_SESSION['errors'] = array();
}
if ($GLOBALS['cfg']['Error_Handler']['gather']) {
// remember all errors
$_SESSION['errors'] = array_merge($_SESSION['errors'], $this->_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 '<html><head><title>';
if ($error) {
echo $error->getTitle();
} else {
echo 'phpMyAdmin error reporting page';
}
echo '</title></head>';
}
/**
* display HTML footer
*
*/
protected function _dispPageEnd()
{
echo '</body></html>';
}
/**
* 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();
}
}
?>

View File

@@ -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) {

View File

@@ -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
*
* <code>
* // 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');
* </code>
*
* @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
*/

View File

@@ -58,6 +58,12 @@ if (! PMA_isValid($_REQUEST['no_history']) && empty($GLOBALS['error_message'])
$GLOBALS['sql_query']);
}
if ($GLOBALS['error_handler']->hasDisplayErrors()) {
echo '<div>';
$GLOBALS['error_handler']->dispErrors();
echo '</div>';
}
if (count($GLOBALS['footnotes'])) {
echo '<div class="notice">';
foreach ($GLOBALS['footnotes'] as $footnote) {