Error.php 13 KB

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