Error.php 13 KB

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