Error.php 13 KB

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