error_report.lib.php 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353
  1. <?php
  2. /* vim: set expandtab sw=4 ts=4 sts=4: */
  3. /**
  4. * Error reporting functions used to generate and submit error reports
  5. *
  6. * @package PhpMyAdmin
  7. */
  8. /*
  9. * Include for handleContext() and configureCurl in PMA_sendErrorReport()
  10. */
  11. require_once 'libraries/Util.class.php';
  12. if (! defined('PHPMYADMIN')) {
  13. exit;
  14. }
  15. /**
  16. * The generated file that contains the linenumbers for the js files
  17. * If you change any of the js files you can run the scripts/line-counts.sh
  18. */
  19. if (is_readable('js/line_counts.php')) {
  20. include_once 'js/line_counts.php';
  21. }
  22. /**
  23. * the url where to submit reports to
  24. */
  25. define('SUBMISSION_URL', "http://reports.phpmyadmin.net/incidents/create");
  26. /**
  27. * returns the error report data collected from the current configuration or
  28. * from the request parameters sent by the error reporting js code.
  29. *
  30. * @param boolean $pretty_print whether to prettify the report
  31. *
  32. * @return Array/String the report
  33. */
  34. function PMA_getReportData($pretty_print = true)
  35. {
  36. if (empty($_REQUEST['exception'])) {
  37. return '';
  38. }
  39. $exception = $_REQUEST['exception'];
  40. $exception['stack'] = PMA_translateStacktrace($exception['stack']);
  41. list($uri, $script_name) = PMA_sanitizeUrl($exception['url']);
  42. $exception['uri'] = $uri;
  43. unset($exception['url']);
  44. $report = array(
  45. 'exception' => $exception,
  46. 'script_name' => $script_name,
  47. 'pma_version' => PMA_VERSION,
  48. 'browser_name' => PMA_USR_BROWSER_AGENT,
  49. 'browser_version' => PMA_USR_BROWSER_VER,
  50. 'user_os' => PMA_USR_OS,
  51. 'server_software' => $_SERVER['SERVER_SOFTWARE'],
  52. 'user_agent_string' => $_SERVER['HTTP_USER_AGENT'],
  53. 'locale' => $_COOKIE['pma_lang'],
  54. 'configuration_storage' =>
  55. empty($GLOBALS['cfg']['Servers'][1]['pmadb'])
  56. ? 'disabled'
  57. : 'enabled',
  58. 'php_version' => phpversion(),
  59. 'microhistory' => $_REQUEST['microhistory'],
  60. );
  61. if (! empty($_REQUEST['description'])) {
  62. $report['steps'] = $_REQUEST['description'];
  63. }
  64. if (!$pretty_print) {
  65. return $report;
  66. }
  67. /* JSON_PRETTY_PRINT available since PHP 5.4 */
  68. if (defined('JSON_PRETTY_PRINT')) {
  69. return json_encode($report, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
  70. }
  71. return PMA_prettyPrint($report);
  72. }
  73. /**
  74. * Sanitize a url to remove the identifiable host name and extract the
  75. * current scriptname from the url fragment
  76. *
  77. * It returns two things in an array. The first is the uri without the
  78. * hostname and identifying query params. The second is the name of the
  79. * php script in the url
  80. *
  81. * @param String $url the url to sanitize
  82. *
  83. * @return Array the uri and script name
  84. */
  85. function PMA_sanitizeUrl($url)
  86. {
  87. $components = parse_url($url);
  88. if (isset($components['fragment'])
  89. && preg_match('<PMAURL-\d+:>', $components['fragment'], $matches)
  90. ) {
  91. $uri = str_replace($matches[0], '', $components['fragment']);
  92. $url = 'http://dummy_host/' . $uri;
  93. $components = parse_url($url);
  94. }
  95. // get script name
  96. preg_match('<([a-zA-Z\-_\d]*\.php)$>', $components['path'], $matches);
  97. $script_name = $matches[1];
  98. // remove deployment specific details to make uri more generic
  99. parse_str($components['query'], $query_array);
  100. unset($query_array['db']);
  101. unset($query_array['table']);
  102. unset($query_array['token']);
  103. unset($query_array['server']);
  104. $query = http_build_query($query_array);
  105. $uri = $script_name . '?' . $query;
  106. return array($uri, $script_name);
  107. }
  108. /**
  109. * Sends report data to the error reporting server
  110. *
  111. * @param Array $report the report info to be sent
  112. *
  113. * @return String the reply of the server
  114. */
  115. function PMA_sendErrorReport($report)
  116. {
  117. $data_string = json_encode($report);
  118. if (ini_get('allow_url_fopen')) {
  119. $context = array(
  120. 'http' => array(
  121. 'method' => 'POST',
  122. 'content' => $data_string,
  123. 'header' => "Content-Type: multipart/form-data\r\n",
  124. )
  125. );
  126. $context = PMA_Util::handleContext($context);
  127. $response = file_get_contents(
  128. SUBMISSION_URL,
  129. false,
  130. stream_context_create($context)
  131. );
  132. return $response;
  133. }
  134. if (!function_exists('curl_init')) {
  135. return null;
  136. }
  137. $curl_handle = curl_init(SUBMISSION_URL);
  138. $curl_handle = PMA_Util::configureCurl($curl_handle);
  139. curl_setopt($curl_handle, CURLOPT_CUSTOMREQUEST, 'POST');
  140. curl_setopt($curl_handle, CURLOPT_HTTPHEADER, array('Expect:'));
  141. curl_setopt($curl_handle, CURLOPT_POSTFIELDS, $data_string);
  142. curl_setopt($curl_handle, CURLOPT_RETURNTRANSFER, 1);
  143. $response = curl_exec($curl_handle);
  144. curl_close($curl_handle);
  145. return $response;
  146. }
  147. /**
  148. * Returns number of lines in given javascript file.
  149. *
  150. * @param string $filename javascript filename
  151. *
  152. * @return Number of lines
  153. */
  154. function PMA_countLines($filename)
  155. {
  156. global $LINE_COUNT;
  157. if (defined('LINE_COUNTS')) {
  158. return $LINE_COUNT[$filename];
  159. }
  160. $linecount = 0;
  161. $handle = fopen('./js/' . $filename, 'r');
  162. while (!feof($handle)) {
  163. $line = fgets($handle);
  164. if ($line === false) {
  165. break;
  166. }
  167. $linecount++;
  168. }
  169. fclose($handle);
  170. return $linecount;
  171. }
  172. /**
  173. * returns the translated linenumber and the file name from the cumulative line
  174. * number and an array of files
  175. *
  176. * uses the $LINE_COUNT global array of file names and line numbers
  177. *
  178. * @param Array $filenames list of files in order of concatenation
  179. * @param Integer $cumulative_number the cumulative line number in the
  180. * concatenated files
  181. *
  182. * @return Array the filename and linenumber
  183. * Returns two variables in an array:
  184. * - A String $filename the filename where the requested cumulative number
  185. * exists
  186. * - Integer $linenumber the translated line number in the returned file
  187. */
  188. function PMA_getLineNumber($filenames, $cumulative_number)
  189. {
  190. $cumulative_sum = 0;
  191. foreach ($filenames as $filename) {
  192. $filecount = PMA_countLines($filename);
  193. if ($cumulative_number <= $cumulative_sum + $filecount + 2) {
  194. $linenumber = $cumulative_number - $cumulative_sum;
  195. break;
  196. }
  197. $cumulative_sum += $filecount + 2;
  198. }
  199. return array($filename, $linenumber);
  200. }
  201. /**
  202. * translates the cumulative line numbers in the stactrace as well as sanitize
  203. * urls and trim long lines in the context
  204. *
  205. * @param Array $stack the stacktrace
  206. *
  207. * @return Array $stack the modified stacktrace
  208. */
  209. function PMA_translateStacktrace($stack)
  210. {
  211. foreach ($stack as &$level) {
  212. foreach ($level["context"] as &$line) {
  213. if (strlen($line) > 80) {
  214. $line = substr($line, 0, 75) . "//...";
  215. }
  216. }
  217. if (preg_match("<js/get_scripts.js.php\?(.*)>", $level["url"], $matches)) {
  218. parse_str($matches[1], $vars);
  219. List($file_name, $line_number) = PMA_getLineNumber(
  220. $vars["scripts"], $level["line"]
  221. );
  222. $level["filename"] = $file_name;
  223. $level["line"] = $line_number;
  224. } else {
  225. unset($level["context"]);
  226. List($uri, $script_name) = PMA_sanitizeUrl($level["url"]);
  227. $level["uri"] = $uri;
  228. $level["scriptname"] = $script_name;
  229. }
  230. unset($level["url"]);
  231. }
  232. unset($level);
  233. return $stack;
  234. }
  235. /**
  236. * generates the error report form to collect user description and preview the
  237. * report before being sent
  238. *
  239. * @return String the form
  240. */
  241. function PMA_getErrorReportForm()
  242. {
  243. $html = "";
  244. $html .= '<form action="error_report.php" method="post" name="report_frm"'
  245. . ' id="report_frm" class="ajax">'
  246. . '<fieldset style="padding-top:0px">';
  247. $html .= '<p>' . __(
  248. 'phpMyAdmin has encountered an error. We have collected data about'
  249. . ' this error as well as information about relevant configuration'
  250. . ' settings to send to the phpMyAdmin team to help us in'
  251. . ' debugging the problem.'
  252. ) . '</p>';
  253. $html .= '<div class="label"><label><p>'
  254. . __('You may examine the data in the error report:')
  255. . '</p></label></div>'
  256. . '<pre class="report-data">'
  257. . PMA_getReportData()
  258. . '</pre>';
  259. $html .= '<div class="label"><label><p>'
  260. . __('Please explain the steps that lead to the error:')
  261. . '</p></label></div>'
  262. . '<textarea class="report-description" name="description"'
  263. . 'id="report_description"></textarea>';
  264. $html .= '<input type="checkbox" name="always_send"'
  265. . ' id="always_send_checkbox"/>'
  266. . '<label for="always_send_checkbox">'
  267. . __('Automatically send report next time')
  268. . '</label>';
  269. $html .= '</fieldset>';
  270. $html .= PMA_URL_getHiddenInputs();
  271. $reportData = PMA_getReportData(false);
  272. if (! empty($reportData)) {
  273. $html .= PMA_getHiddenFields($reportData);
  274. }
  275. $html .= '</form>';
  276. return $html;
  277. }
  278. /**
  279. * generates the error report form to collect user description and preview the
  280. * report before being sent
  281. *
  282. * @return String the form
  283. */
  284. function PMA_hasLatestLineCounts()
  285. {
  286. $line_counts_time = filemtime("js/line_counts.php");
  287. $js_time = filemtime("js");
  288. return $line_counts_time >= $js_time;
  289. }
  290. /**
  291. * pretty print a variable for the user
  292. *
  293. * @param mixed $object the variable to pretty print
  294. * @param String $namespace the namespace to use for printing values
  295. *
  296. * @return String the human readable form of the variable
  297. */
  298. function PMA_prettyPrint($object, $namespace="")
  299. {
  300. if (! is_array($object)) {
  301. if (empty($namespace)) {
  302. return "$object\n";
  303. } else {
  304. return "$namespace: \"$object\"\n";
  305. }
  306. }
  307. $output = "";
  308. foreach ($object as $key => $value) {
  309. if ($namespace == "") {
  310. $new_namespace = "$key";
  311. } else {
  312. $new_namespace = $namespace . "[$key]";
  313. }
  314. $output .= PMA_prettyPrint($value, $new_namespace);
  315. }
  316. return $output;
  317. }
  318. ?>