ErrorReport.php 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260
  1. <?php
  2. /* vim: set expandtab sw=4 ts=4 sts=4: */
  3. /**
  4. * Holds the PhpMyAdmin\ErrorReport class
  5. *
  6. * @package PhpMyAdmin
  7. */
  8. namespace PhpMyAdmin;
  9. use PhpMyAdmin\Relation;
  10. use PhpMyAdmin\Template;
  11. use PhpMyAdmin\Url;
  12. use PhpMyAdmin\Utils\HttpRequest;
  13. /**
  14. * Error reporting functions used to generate and submit error reports
  15. *
  16. * @package PhpMyAdmin
  17. */
  18. class ErrorReport
  19. {
  20. /**
  21. * The URL where to submit reports to
  22. *
  23. * @var string
  24. */
  25. private $submissionUrl;
  26. /**
  27. * @var HttpRequest
  28. */
  29. private $httpRequest;
  30. /**
  31. * @var Relation $relation
  32. */
  33. private $relation;
  34. /**
  35. * Constructor
  36. *
  37. * @param HttpRequest $httpRequest HttpRequest instance
  38. */
  39. public function __construct(HttpRequest $httpRequest)
  40. {
  41. $this->httpRequest = $httpRequest;
  42. $this->submissionUrl = 'https://reports.phpmyadmin.net/incidents/create';
  43. $this->relation = new Relation();
  44. }
  45. /**
  46. * Returns the pretty printed error report data collected from the
  47. * current configuration or from the request parameters sent by the
  48. * error reporting js code.
  49. *
  50. * @return string the report
  51. */
  52. private function getPrettyData()
  53. {
  54. $report = $this->getData();
  55. return json_encode($report, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
  56. }
  57. /**
  58. * Returns the error report data collected from the current configuration or
  59. * from the request parameters sent by the error reporting js code.
  60. *
  61. * @param string $exceptionType whether exception is 'js' or 'php'
  62. *
  63. * @return array error report if success, Empty Array otherwise
  64. */
  65. public function getData($exceptionType = 'js')
  66. {
  67. $relParams = $this->relation->getRelationsParam();
  68. // common params for both, php & js exceptions
  69. $report = [
  70. "pma_version" => PMA_VERSION,
  71. "browser_name" => PMA_USR_BROWSER_AGENT,
  72. "browser_version" => PMA_USR_BROWSER_VER,
  73. "user_os" => PMA_USR_OS,
  74. "server_software" => $_SERVER['SERVER_SOFTWARE'],
  75. "user_agent_string" => $_SERVER['HTTP_USER_AGENT'],
  76. "locale" => $_COOKIE['pma_lang'],
  77. "configuration_storage" =>
  78. is_null($relParams['db']) ? "disabled" : "enabled",
  79. "php_version" => phpversion()
  80. ];
  81. if ($exceptionType == 'js') {
  82. if (empty($_POST['exception'])) {
  83. return [];
  84. }
  85. $exception = $_POST['exception'];
  86. $exception["stack"] = $this->translateStacktrace($exception["stack"]);
  87. list($uri, $scriptName) = $this->sanitizeUrl($exception["url"]);
  88. $exception["uri"] = $uri;
  89. unset($exception["url"]);
  90. $report["exception_type"] = 'js';
  91. $report["exception"] = $exception;
  92. $report["script_name"] = $scriptName;
  93. $report["microhistory"] = $_POST['microhistory'];
  94. if (! empty($_POST['description'])) {
  95. $report['steps'] = $_POST['description'];
  96. }
  97. } elseif ($exceptionType == 'php') {
  98. $errors = [];
  99. // create php error report
  100. $i = 0;
  101. if (!isset($_SESSION['prev_errors'])
  102. || $_SESSION['prev_errors'] == ''
  103. ) {
  104. return [];
  105. }
  106. foreach ($_SESSION['prev_errors'] as $errorObj) {
  107. /* @var $errorObj PhpMyAdmin\Error */
  108. if ($errorObj->getLine()
  109. && $errorObj->getType()
  110. && $errorObj->getNumber() != E_USER_WARNING
  111. ) {
  112. $errors[$i++] = [
  113. "lineNum" => $errorObj->getLine(),
  114. "file" => $errorObj->getFile(),
  115. "type" => $errorObj->getType(),
  116. "msg" => $errorObj->getOnlyMessage(),
  117. "stackTrace" => $errorObj->getBacktrace(5),
  118. "stackhash" => $errorObj->getHash()
  119. ];
  120. }
  121. }
  122. // if there were no 'actual' errors to be submitted.
  123. if ($i==0) {
  124. return []; // then return empty array
  125. }
  126. $report["exception_type"] = 'php';
  127. $report["errors"] = $errors;
  128. } else {
  129. return [];
  130. }
  131. return $report;
  132. }
  133. /**
  134. * Sanitize a url to remove the identifiable host name and extract the
  135. * current script name from the url fragment
  136. *
  137. * It returns two things in an array. The first is the uri without the
  138. * hostname and identifying query params. The second is the name of the
  139. * php script in the url
  140. *
  141. * @param string $url the url to sanitize
  142. *
  143. * @return array the uri and script name
  144. */
  145. private function sanitizeUrl($url)
  146. {
  147. $components = parse_url($url);
  148. if (isset($components["fragment"])
  149. && preg_match("<PMAURL-\d+:>", $components["fragment"], $matches)
  150. ) {
  151. $uri = str_replace($matches[0], "", $components["fragment"]);
  152. $url = "https://example.com/" . $uri;
  153. $components = parse_url($url);
  154. }
  155. // get script name
  156. preg_match("<([a-zA-Z\-_\d]*\.php)$>", $components["path"], $matches);
  157. if (count($matches) < 2) {
  158. $scriptName = 'index.php';
  159. } else {
  160. $scriptName = $matches[1];
  161. }
  162. // remove deployment specific details to make uri more generic
  163. if (isset($components["query"])) {
  164. parse_str($components["query"], $queryArray);
  165. unset($queryArray["db"]);
  166. unset($queryArray["table"]);
  167. unset($queryArray["token"]);
  168. unset($queryArray["server"]);
  169. $query = http_build_query($queryArray);
  170. } else {
  171. $query = '';
  172. }
  173. $uri = $scriptName . "?" . $query;
  174. return [$uri, $scriptName];
  175. }
  176. /**
  177. * Sends report data to the error reporting server
  178. *
  179. * @param array $report the report info to be sent
  180. *
  181. * @return string the reply of the server
  182. */
  183. public function send(array $report)
  184. {
  185. $response = $this->httpRequest->create(
  186. $this->submissionUrl,
  187. "POST",
  188. false,
  189. json_encode($report),
  190. "Content-Type: application/json"
  191. );
  192. return $response;
  193. }
  194. /**
  195. * Translates the cumulative line numbers in the stack trace as well as sanitize
  196. * urls and trim long lines in the context
  197. *
  198. * @param array $stack the stack trace
  199. *
  200. * @return array $stack the modified stack trace
  201. */
  202. private function translateStacktrace(array $stack)
  203. {
  204. foreach ($stack as &$level) {
  205. foreach ($level["context"] as &$line) {
  206. if (mb_strlen($line) > 80) {
  207. $line = mb_substr($line, 0, 75) . "//...";
  208. }
  209. }
  210. unset($level["context"]);
  211. list($uri, $scriptName) = $this->sanitizeUrl($level["url"]);
  212. $level["uri"] = $uri;
  213. $level["scriptname"] = $scriptName;
  214. unset($level["url"]);
  215. }
  216. unset($level);
  217. return $stack;
  218. }
  219. /**
  220. * Generates the error report form to collect user description and preview the
  221. * report before being sent
  222. *
  223. * @return string the form
  224. */
  225. public function getForm()
  226. {
  227. $datas = [
  228. 'report_data' => $this->getPrettyData(),
  229. 'hidden_inputs' => Url::getHiddenInputs(),
  230. 'hidden_fields' => null,
  231. ];
  232. $reportData = $this->getData();
  233. if (!empty($reportData)) {
  234. $datas['hidden_fields'] = Url::getHiddenFields($reportData);
  235. }
  236. return Template::get('error/report_form')->render($datas);
  237. }
  238. }