ErrorReport.php 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275
  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. /** @var Config $PMA_Config */
  68. global $PMA_Config;
  69. $relParams = $this->relation->getRelationsParam();
  70. // common params for both, php & js exceptions
  71. $report = [
  72. "pma_version" => PMA_VERSION,
  73. "browser_name" => PMA_USR_BROWSER_AGENT,
  74. "browser_version" => PMA_USR_BROWSER_VER,
  75. "user_os" => PMA_USR_OS,
  76. "server_software" => $_SERVER['SERVER_SOFTWARE'],
  77. "user_agent_string" => $_SERVER['HTTP_USER_AGENT'],
  78. "locale" => $PMA_Config->getCookie('pma_lang'),
  79. "configuration_storage" =>
  80. is_null($relParams['db']) ? "disabled" : "enabled",
  81. "php_version" => phpversion()
  82. ];
  83. if ($exceptionType == 'js') {
  84. if (empty($_POST['exception'])) {
  85. return [];
  86. }
  87. $exception = $_POST['exception'];
  88. $exception["stack"] = $this->translateStacktrace($exception["stack"]);
  89. if (isset($exception["url"])) {
  90. list($uri, $scriptName) = $this->sanitizeUrl($exception["url"]);
  91. $exception["uri"] = $uri;
  92. $report["script_name"] = $scriptName;
  93. unset($exception["url"]);
  94. } else if (isset($_POST["url"])) {
  95. list($uri, $scriptName) = $this->sanitizeUrl($_POST["url"]);
  96. $exception["uri"] = $uri;
  97. $report["script_name"] = $scriptName;
  98. unset($_POST["url"]);
  99. } else {
  100. $report["script_name"] = null;
  101. }
  102. $report["exception_type"] = 'js';
  103. $report["exception"] = $exception;
  104. if (isset($_POST['microhistory'])) {
  105. $report["microhistory"] = $_POST['microhistory'];
  106. }
  107. if (! empty($_POST['description'])) {
  108. $report['steps'] = $_POST['description'];
  109. }
  110. } elseif ($exceptionType == 'php') {
  111. $errors = [];
  112. // create php error report
  113. $i = 0;
  114. if (!isset($_SESSION['prev_errors'])
  115. || $_SESSION['prev_errors'] == ''
  116. ) {
  117. return [];
  118. }
  119. foreach ($_SESSION['prev_errors'] as $errorObj) {
  120. /* @var $errorObj PhpMyAdmin\Error */
  121. if ($errorObj->getLine()
  122. && $errorObj->getType()
  123. && $errorObj->getNumber() != E_USER_WARNING
  124. ) {
  125. $errors[$i++] = [
  126. "lineNum" => $errorObj->getLine(),
  127. "file" => $errorObj->getFile(),
  128. "type" => $errorObj->getType(),
  129. "msg" => $errorObj->getOnlyMessage(),
  130. "stackTrace" => $errorObj->getBacktrace(5),
  131. "stackhash" => $errorObj->getHash()
  132. ];
  133. }
  134. }
  135. // if there were no 'actual' errors to be submitted.
  136. if ($i==0) {
  137. return []; // then return empty array
  138. }
  139. $report["exception_type"] = 'php';
  140. $report["errors"] = $errors;
  141. } else {
  142. return [];
  143. }
  144. return $report;
  145. }
  146. /**
  147. * Sanitize a url to remove the identifiable host name and extract the
  148. * current script name from the url fragment
  149. *
  150. * It returns two things in an array. The first is the uri without the
  151. * hostname and identifying query params. The second is the name of the
  152. * php script in the url
  153. *
  154. * @param string $url the url to sanitize
  155. *
  156. * @return array the uri and script name
  157. */
  158. private function sanitizeUrl($url)
  159. {
  160. $components = parse_url($url);
  161. if (isset($components["fragment"])
  162. && preg_match("<PMAURL-\d+:>", $components["fragment"], $matches)
  163. ) {
  164. $uri = str_replace($matches[0], "", $components["fragment"]);
  165. $url = "https://example.com/" . $uri;
  166. $components = parse_url($url);
  167. }
  168. // get script name
  169. preg_match("<([a-zA-Z\-_\d\.]*\.php|js\/[a-zA-Z\-_\d\/\.]*\.js)$>", $components["path"], $matches);
  170. if (count($matches) < 2) {
  171. $scriptName = 'index.php';
  172. } else {
  173. $scriptName = $matches[1];
  174. }
  175. // remove deployment specific details to make uri more generic
  176. if (isset($components["query"])) {
  177. parse_str($components["query"], $queryArray);
  178. unset($queryArray["db"]);
  179. unset($queryArray["table"]);
  180. unset($queryArray["token"]);
  181. unset($queryArray["server"]);
  182. unset($queryArray["eq"]);
  183. $query = http_build_query($queryArray);
  184. } else {
  185. $query = '';
  186. }
  187. $uri = $scriptName . "?" . $query;
  188. return [$uri, $scriptName];
  189. }
  190. /**
  191. * Sends report data to the error reporting server
  192. *
  193. * @param array $report the report info to be sent
  194. *
  195. * @return string|null|bool the reply of the server
  196. */
  197. public function send(array $report)
  198. {
  199. $response = $this->httpRequest->create(
  200. $this->submissionUrl,
  201. "POST",
  202. false,
  203. json_encode($report),
  204. "Content-Type: application/json"
  205. );
  206. return $response;
  207. }
  208. /**
  209. * Translates the cumulative line numbers in the stack trace as well as sanitize
  210. * urls and trim long lines in the context
  211. *
  212. * @param array $stack the stack trace
  213. *
  214. * @return array $stack the modified stack trace
  215. */
  216. private function translateStacktrace(array $stack)
  217. {
  218. foreach ($stack as &$level) {
  219. foreach ($level["context"] as &$line) {
  220. if (mb_strlen($line) > 80) {
  221. $line = mb_substr($line, 0, 75) . "//...";
  222. }
  223. }
  224. list($uri, $scriptName) = $this->sanitizeUrl($level["url"]);
  225. $level["uri"] = $uri;
  226. $level["scriptname"] = $scriptName;
  227. unset($level["url"]);
  228. }
  229. unset($level);
  230. return $stack;
  231. }
  232. /**
  233. * Generates the error report form to collect user description and preview the
  234. * report before being sent
  235. *
  236. * @return string the form
  237. */
  238. public function getForm()
  239. {
  240. $datas = [
  241. 'report_data' => $this->getPrettyData(),
  242. 'hidden_inputs' => Url::getHiddenInputs(),
  243. 'hidden_fields' => null,
  244. ];
  245. $reportData = $this->getData();
  246. if (!empty($reportData)) {
  247. $datas['hidden_fields'] = Url::getHiddenFields($reportData);
  248. }
  249. return Template::get('error/report_form')->render($datas);
  250. }
  251. }