1<?php 2/* vim: set expandtab sw=4 ts=4 sts=4: */ 3/** 4 * Holds class PhpMyAdmin\Error 5 * 6 * @package PhpMyAdmin 7 */ 8namespace PhpMyAdmin; 9 10use Exception; 11use PhpMyAdmin\Message; 12 13/** 14 * a single error 15 * 16 * @package PhpMyAdmin 17 */ 18class Error extends Message 19{ 20 /** 21 * Error types 22 * 23 * @var array 24 */ 25 static public $errortype = array ( 26 0 => 'Internal error', 27 E_ERROR => 'Error', 28 E_WARNING => 'Warning', 29 E_PARSE => 'Parsing Error', 30 E_NOTICE => 'Notice', 31 E_CORE_ERROR => 'Core Error', 32 E_CORE_WARNING => 'Core Warning', 33 E_COMPILE_ERROR => 'Compile Error', 34 E_COMPILE_WARNING => 'Compile Warning', 35 E_USER_ERROR => 'User Error', 36 E_USER_WARNING => 'User Warning', 37 E_USER_NOTICE => 'User Notice', 38 E_STRICT => 'Runtime Notice', 39 E_DEPRECATED => 'Deprecation Notice', 40 E_USER_DEPRECATED => 'Deprecation Notice', 41 E_RECOVERABLE_ERROR => 'Catchable Fatal Error', 42 ); 43 44 /** 45 * Error levels 46 * 47 * @var array 48 */ 49 static public $errorlevel = array ( 50 0 => 'error', 51 E_ERROR => 'error', 52 E_WARNING => 'error', 53 E_PARSE => 'error', 54 E_NOTICE => 'notice', 55 E_CORE_ERROR => 'error', 56 E_CORE_WARNING => 'error', 57 E_COMPILE_ERROR => 'error', 58 E_COMPILE_WARNING => 'error', 59 E_USER_ERROR => 'error', 60 E_USER_WARNING => 'error', 61 E_USER_NOTICE => 'notice', 62 E_STRICT => 'notice', 63 E_DEPRECATED => 'notice', 64 E_USER_DEPRECATED => 'notice', 65 E_RECOVERABLE_ERROR => 'error', 66 ); 67 68 /** 69 * The file in which the error occurred 70 * 71 * @var string 72 */ 73 protected $file = ''; 74 75 /** 76 * The line in which the error occurred 77 * 78 * @var integer 79 */ 80 protected $line = 0; 81 82 /** 83 * Holds the backtrace for this error 84 * 85 * @var array 86 */ 87 protected $backtrace = array(); 88 89 /** 90 * Hide location of errors 91 */ 92 protected $hide_location = false; 93 94 /** 95 * Constructor 96 * 97 * @param integer $errno error number 98 * @param string $errstr error message 99 * @param string $errfile file 100 * @param integer $errline line 101 */ 102 public function __construct($errno, $errstr, $errfile, $errline) 103 { 104 $this->setNumber($errno); 105 $this->setMessage($errstr, false); 106 $this->setFile($errfile); 107 $this->setLine($errline); 108 109 // This function can be disabled in php.ini 110 if (function_exists('debug_backtrace')) { 111 $backtrace = @debug_backtrace(); 112 // remove last three calls: 113 // debug_backtrace(), handleError() and addError() 114 $backtrace = array_slice($backtrace, 3); 115 } else { 116 $backtrace = array(); 117 } 118 119 $this->setBacktrace($backtrace); 120 } 121 122 /** 123 * Process backtrace to avoid path disclossures, objects and so on 124 * 125 * @param array $backtrace backtrace 126 * 127 * @return array 128 */ 129 public static function processBacktrace(array $backtrace) 130 { 131 $result = array(); 132 133 $members = array('line', 'function', 'class', 'type'); 134 135 foreach ($backtrace as $idx => $step) { 136 /* Create new backtrace entry */ 137 $result[$idx] = array(); 138 139 /* Make path relative */ 140 if (isset($step['file'])) { 141 $result[$idx]['file'] = self::relPath($step['file']); 142 } 143 144 /* Store members we want */ 145 foreach ($members as $name) { 146 if (isset($step[$name])) { 147 $result[$idx][$name] = $step[$name]; 148 } 149 } 150 151 /* Store simplified args */ 152 if (isset($step['args'])) { 153 foreach ($step['args'] as $key => $arg) { 154 $result[$idx]['args'][$key] = self::getArg($arg, $step['function']); 155 } 156 } 157 } 158 159 return $result; 160 } 161 162 /** 163 * Toggles location hiding 164 * 165 * @param boolean $hide Whether to hide 166 * 167 * @return void 168 */ 169 public function setHideLocation($hide) 170 { 171 $this->hide_location = $hide; 172 } 173 174 /** 175 * sets PhpMyAdmin\Error::$_backtrace 176 * 177 * We don't store full arguments to avoid wakeup or memory problems. 178 * 179 * @param array $backtrace backtrace 180 * 181 * @return void 182 */ 183 public function setBacktrace(array $backtrace) 184 { 185 $this->backtrace = self::processBacktrace($backtrace); 186 } 187 188 /** 189 * sets PhpMyAdmin\Error::$_line 190 * 191 * @param integer $line the line 192 * 193 * @return void 194 */ 195 public function setLine($line) 196 { 197 $this->line = $line; 198 } 199 200 /** 201 * sets PhpMyAdmin\Error::$_file 202 * 203 * @param string $file the file 204 * 205 * @return void 206 */ 207 public function setFile($file) 208 { 209 $this->file = self::relPath($file); 210 } 211 212 213 /** 214 * returns unique PhpMyAdmin\Error::$hash, if not exists it will be created 215 * 216 * @return string PhpMyAdmin\Error::$hash 217 */ 218 public function getHash() 219 { 220 try { 221 $backtrace = serialize($this->getBacktrace()); 222 } catch(Exception $e) { 223 $backtrace = ''; 224 } 225 if ($this->hash === null) { 226 $this->hash = md5( 227 $this->getNumber() . 228 $this->getMessage() . 229 $this->getFile() . 230 $this->getLine() . 231 $backtrace 232 ); 233 } 234 235 return $this->hash; 236 } 237 238 /** 239 * returns PhpMyAdmin\Error::$_backtrace for first $count frames 240 * pass $count = -1 to get full backtrace. 241 * The same can be done by not passing $count at all. 242 * 243 * @param integer $count Number of stack frames. 244 * 245 * @return array PhpMyAdmin\Error::$_backtrace 246 */ 247 public function getBacktrace($count = -1) 248 { 249 if ($count != -1) { 250 return array_slice($this->backtrace, 0, $count); 251 } 252 return $this->backtrace; 253 } 254 255 /** 256 * returns PhpMyAdmin\Error::$file 257 * 258 * @return string PhpMyAdmin\Error::$file 259 */ 260 public function getFile() 261 { 262 return $this->file; 263 } 264 265 /** 266 * returns PhpMyAdmin\Error::$line 267 * 268 * @return integer PhpMyAdmin\Error::$line 269 */ 270 public function getLine() 271 { 272 return $this->line; 273 } 274 275 /** 276 * returns type of error 277 * 278 * @return string type of error 279 */ 280 public function getType() 281 { 282 return self::$errortype[$this->getNumber()]; 283 } 284 285 /** 286 * returns level of error 287 * 288 * @return string level of error 289 */ 290 public function getLevel() 291 { 292 return self::$errorlevel[$this->getNumber()]; 293 } 294 295 /** 296 * returns title prepared for HTML Title-Tag 297 * 298 * @return string HTML escaped and truncated title 299 */ 300 public function getHtmlTitle() 301 { 302 return htmlspecialchars( 303 mb_substr($this->getTitle(), 0, 100) 304 ); 305 } 306 307 /** 308 * returns title for error 309 * 310 * @return string 311 */ 312 public function getTitle() 313 { 314 return $this->getType() . ': ' . $this->getMessage(); 315 } 316 317 /** 318 * Get HTML backtrace 319 * 320 * @return string 321 */ 322 public function getBacktraceDisplay() 323 { 324 return self::formatBacktrace( 325 $this->getBacktrace(), 326 "<br />\n", 327 "<br />\n" 328 ); 329 } 330 331 /** 332 * return formatted backtrace field 333 * 334 * @param array $backtrace Backtrace data 335 * @param string $separator Arguments separator to use 336 * @param string $lines Lines separator to use 337 * 338 * @return string formatted backtrace 339 */ 340 public static function formatBacktrace(array $backtrace, $separator, $lines) 341 { 342 $retval = ''; 343 344 foreach ($backtrace as $step) { 345 if (isset($step['file']) && isset($step['line'])) { 346 $retval .= self::relPath($step['file']) 347 . '#' . $step['line'] . ': '; 348 } 349 if (isset($step['class'])) { 350 $retval .= $step['class'] . $step['type']; 351 } 352 $retval .= self::getFunctionCall($step, $separator); 353 $retval .= $lines; 354 } 355 356 return $retval; 357 } 358 359 /** 360 * Formats function call in a backtrace 361 * 362 * @param array $step backtrace step 363 * @param string $separator Arguments separator to use 364 * 365 * @return string 366 */ 367 public static function getFunctionCall(array $step, $separator) 368 { 369 $retval = $step['function'] . '('; 370 if (isset($step['args'])) { 371 if (count($step['args']) > 1) { 372 $retval .= $separator; 373 foreach ($step['args'] as $arg) { 374 $retval .= "\t"; 375 $retval .= $arg; 376 $retval .= ',' . $separator; 377 } 378 } elseif (count($step['args']) > 0) { 379 foreach ($step['args'] as $arg) { 380 $retval .= $arg; 381 } 382 } 383 } 384 $retval .= ')'; 385 return $retval; 386 } 387 388 /** 389 * Get a single function argument 390 * 391 * if $function is one of include/require 392 * the $arg is converted to a relative path 393 * 394 * @param string $arg argument to process 395 * @param string $function function name 396 * 397 * @return string 398 */ 399 public static function getArg($arg, $function) 400 { 401 $retval = ''; 402 $include_functions = array( 403 'include', 404 'include_once', 405 'require', 406 'require_once', 407 ); 408 $connect_functions = array( 409 'mysql_connect', 410 'mysql_pconnect', 411 'mysqli_connect', 412 'mysqli_real_connect', 413 'connect', 414 '_realConnect' 415 ); 416 417 if (in_array($function, $include_functions)) { 418 $retval .= self::relPath($arg); 419 } elseif (in_array($function, $connect_functions) 420 && getType($arg) === 'string' 421 ) { 422 $retval .= getType($arg) . ' ********'; 423 } elseif (is_scalar($arg)) { 424 $retval .= getType($arg) . ' ' 425 . htmlspecialchars(var_export($arg, true)); 426 } elseif (is_object($arg)) { 427 $retval .= '<Class:' . get_class($arg) . '>'; 428 } else { 429 $retval .= getType($arg); 430 } 431 432 return $retval; 433 } 434 435 /** 436 * Gets the error as string of HTML 437 * 438 * @return string 439 */ 440 public function getDisplay() 441 { 442 $this->isDisplayed(true); 443 $retval = '<div class="' . $this->getLevel() . '">'; 444 if (! $this->isUserError()) { 445 $retval .= '<strong>' . $this->getType() . '</strong>'; 446 $retval .= ' in ' . $this->getFile() . '#' . $this->getLine(); 447 $retval .= "<br />\n"; 448 } 449 $retval .= $this->getMessage(); 450 if (! $this->isUserError()) { 451 $retval .= "<br />\n"; 452 $retval .= "<br />\n"; 453 $retval .= "<strong>Backtrace</strong><br />\n"; 454 $retval .= "<br />\n"; 455 $retval .= $this->getBacktraceDisplay(); 456 } 457 $retval .= '</div>'; 458 459 return $retval; 460 } 461 462 /** 463 * whether this error is a user error 464 * 465 * @return boolean 466 */ 467 public function isUserError() 468 { 469 return $this->hide_location || 470 ($this->getNumber() & (E_USER_WARNING | E_USER_ERROR | E_USER_NOTICE | E_USER_DEPRECATED)); 471 } 472 473 /** 474 * return short relative path to phpMyAdmin basedir 475 * 476 * prevent path disclosure in error message, 477 * and make users feel safe to submit error reports 478 * 479 * @param string $path path to be shorten 480 * 481 * @return string shortened path 482 */ 483 public static function relPath($path) 484 { 485 $dest = @realpath($path); 486 487 /* Probably affected by open_basedir */ 488 if ($dest === false) { 489 return basename($path); 490 } 491 492 $Ahere = explode( 493 DIRECTORY_SEPARATOR, 494 realpath(__DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . '..') 495 ); 496 $Adest = explode(DIRECTORY_SEPARATOR, $dest); 497 498 $result = '.'; 499 // && count ($Adest)>0 && count($Ahere)>0 ) 500 while (implode(DIRECTORY_SEPARATOR, $Adest) != implode(DIRECTORY_SEPARATOR, $Ahere)) { 501 if (count($Ahere) > count($Adest)) { 502 array_pop($Ahere); 503 $result .= DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . '..'; 504 } else { 505 array_pop($Adest); 506 } 507 } 508 $path = $result . str_replace(implode(DIRECTORY_SEPARATOR, $Adest), '', $dest); 509 return str_replace( 510 DIRECTORY_SEPARATOR . PATH_SEPARATOR, 511 DIRECTORY_SEPARATOR, 512 $path 513 ); 514 } 515} 516