Fix #15621 - Support CloudFront-Forwarded-Proto header
[phpmyadmin.git] / libraries / classes / ErrorReport.php
blob6a3e65a2303861c209bbcc88bb8a893ed76db4ff
1 <?php
2 /* vim: set expandtab sw=4 ts=4 sts=4: */
3 /**
4 * Holds the PhpMyAdmin\ErrorReport class
6 * @package PhpMyAdmin
7 */
8 namespace PhpMyAdmin;
10 use PhpMyAdmin\Relation;
11 use PhpMyAdmin\Template;
12 use PhpMyAdmin\Url;
13 use PhpMyAdmin\Utils\HttpRequest;
15 /**
16 * Error reporting functions used to generate and submit error reports
18 * @package PhpMyAdmin
20 class ErrorReport
22 /**
23 * The URL where to submit reports to
25 * @var string
27 private $submissionUrl;
29 /**
30 * @var HttpRequest
32 private $httpRequest;
34 /**
35 * @var Relation $relation
37 private $relation;
39 /**
40 * Constructor
42 * @param HttpRequest $httpRequest HttpRequest instance
44 public function __construct(HttpRequest $httpRequest)
46 $this->httpRequest = $httpRequest;
47 $this->submissionUrl = 'https://reports.phpmyadmin.net/incidents/create';
48 $this->relation = new Relation();
51 /**
52 * Returns the pretty printed error report data collected from the
53 * current configuration or from the request parameters sent by the
54 * error reporting js code.
56 * @return string the report
58 private function getPrettyData()
60 $report = $this->getData();
62 return json_encode($report, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
65 /**
66 * Returns the error report data collected from the current configuration or
67 * from the request parameters sent by the error reporting js code.
69 * @param string $exceptionType whether exception is 'js' or 'php'
71 * @return array error report if success, Empty Array otherwise
73 public function getData($exceptionType = 'js')
75 /** @var Config $PMA_Config */
76 global $PMA_Config;
78 $relParams = $this->relation->getRelationsParam();
79 // common params for both, php & js exceptions
80 $report = [
81 "pma_version" => PMA_VERSION,
82 "browser_name" => PMA_USR_BROWSER_AGENT,
83 "browser_version" => PMA_USR_BROWSER_VER,
84 "user_os" => PMA_USR_OS,
85 "server_software" => $_SERVER['SERVER_SOFTWARE'],
86 "user_agent_string" => $_SERVER['HTTP_USER_AGENT'],
87 "locale" => $PMA_Config->getCookie('pma_lang'),
88 "configuration_storage" =>
89 is_null($relParams['db']) ? "disabled" : "enabled",
90 "php_version" => phpversion()
93 if ($exceptionType == 'js') {
94 if (empty($_POST['exception'])) {
95 return [];
97 $exception = $_POST['exception'];
98 $exception["stack"] = $this->translateStacktrace($exception["stack"]);
100 if (isset($exception["url"])) {
101 list($uri, $scriptName) = $this->sanitizeUrl($exception["url"]);
102 $exception["uri"] = $uri;
103 $report["script_name"] = $scriptName;
104 unset($exception["url"]);
105 } else if (isset($_POST["url"])) {
106 list($uri, $scriptName) = $this->sanitizeUrl($_POST["url"]);
107 $exception["uri"] = $uri;
108 $report["script_name"] = $scriptName;
109 unset($_POST["url"]);
110 } else {
111 $report["script_name"] = null;
114 $report["exception_type"] = 'js';
115 $report["exception"] = $exception;
116 if (isset($_POST['microhistory'])) {
117 $report["microhistory"] = $_POST['microhistory'];
120 if (! empty($_POST['description'])) {
121 $report['steps'] = $_POST['description'];
123 } elseif ($exceptionType == 'php') {
124 $errors = [];
125 // create php error report
126 $i = 0;
127 if (!isset($_SESSION['prev_errors'])
128 || $_SESSION['prev_errors'] == ''
130 return [];
132 foreach ($_SESSION['prev_errors'] as $errorObj) {
133 /* @var $errorObj PhpMyAdmin\Error */
134 if ($errorObj->getLine()
135 && $errorObj->getType()
136 && $errorObj->getNumber() != E_USER_WARNING
138 $errors[$i++] = [
139 "lineNum" => $errorObj->getLine(),
140 "file" => $errorObj->getFile(),
141 "type" => $errorObj->getType(),
142 "msg" => $errorObj->getOnlyMessage(),
143 "stackTrace" => $errorObj->getBacktrace(5),
144 "stackhash" => $errorObj->getHash()
149 // if there were no 'actual' errors to be submitted.
150 if ($i==0) {
151 return []; // then return empty array
153 $report["exception_type"] = 'php';
154 $report["errors"] = $errors;
155 } else {
156 return [];
159 return $report;
163 * Sanitize a url to remove the identifiable host name and extract the
164 * current script name from the url fragment
166 * It returns two things in an array. The first is the uri without the
167 * hostname and identifying query params. The second is the name of the
168 * php script in the url
170 * @param string $url the url to sanitize
172 * @return array the uri and script name
174 private function sanitizeUrl($url)
176 $components = parse_url($url);
177 if (isset($components["fragment"])
178 && preg_match("<PMAURL-\d+:>", $components["fragment"], $matches)
180 $uri = str_replace($matches[0], "", $components["fragment"]);
181 $url = "https://example.com/" . $uri;
182 $components = parse_url($url);
185 // get script name
186 preg_match("<([a-zA-Z\-_\d\.]*\.php|js\/[a-zA-Z\-_\d\/\.]*\.js)$>", $components["path"], $matches);
187 if (count($matches) < 2) {
188 $scriptName = 'index.php';
189 } else {
190 $scriptName = $matches[1];
193 // remove deployment specific details to make uri more generic
194 if (isset($components["query"])) {
195 parse_str($components["query"], $queryArray);
196 unset($queryArray["db"]);
197 unset($queryArray["table"]);
198 unset($queryArray["token"]);
199 unset($queryArray["server"]);
200 $query = http_build_query($queryArray);
201 } else {
202 $query = '';
205 $uri = $scriptName . "?" . $query;
206 return [$uri, $scriptName];
210 * Sends report data to the error reporting server
212 * @param array $report the report info to be sent
214 * @return string|null|bool the reply of the server
216 public function send(array $report)
218 $response = $this->httpRequest->create(
219 $this->submissionUrl,
220 "POST",
221 false,
222 json_encode($report),
223 "Content-Type: application/json"
225 return $response;
229 * Translates the cumulative line numbers in the stack trace as well as sanitize
230 * urls and trim long lines in the context
232 * @param array $stack the stack trace
234 * @return array $stack the modified stack trace
236 private function translateStacktrace(array $stack)
238 foreach ($stack as &$level) {
239 foreach ($level["context"] as &$line) {
240 if (mb_strlen($line) > 80) {
241 $line = mb_substr($line, 0, 75) . "//...";
244 list($uri, $scriptName) = $this->sanitizeUrl($level["url"]);
245 $level["uri"] = $uri;
246 $level["scriptname"] = $scriptName;
247 unset($level["url"]);
249 unset($level);
250 return $stack;
254 * Generates the error report form to collect user description and preview the
255 * report before being sent
257 * @return string the form
259 public function getForm()
261 $datas = [
262 'report_data' => $this->getPrettyData(),
263 'hidden_inputs' => Url::getHiddenInputs(),
264 'hidden_fields' => null,
267 $reportData = $this->getData();
268 if (!empty($reportData)) {
269 $datas['hidden_fields'] = Url::getHiddenFields($reportData);
272 return Template::get('error/report_form')->render($datas);