PluginProbe ʕ •ᴥ•ʔ
Matomo Analytics – Powerful, Privacy-First Insights for WordPress / 4.15.0
Matomo Analytics – Powerful, Privacy-First Insights for WordPress v4.15.0
5.11.1 5.11.0 5.10.2 5.10.1 trunk 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 1.1.0 1.1.1 1.1.2 1.1.3 1.2.0 1.3.0 1.3.1 1.3.2 4.0.0 4.0.1 4.0.2 4.0.3 4.0.4 4.1.0 4.1.1 4.1.2 4.1.3 4.10.0 4.11.0 4.12.0 4.13.0 4.13.2 4.13.3 4.13.4 4.13.5 4.14.0 4.14.1 4.14.2 4.15.0 4.15.1 4.15.2 4.15.3 4.2.0 4.3.0 4.3.1 4.4.1 4.4.2 4.5.0 4.6.0 5.0.1 5.0.2 5.0.3 5.0.4 5.0.5 5.0.6 5.0.7 5.0.8 5.1.0 5.1.1 5.1.2 5.1.3 5.1.4 5.1.5 5.1.6 5.1.7 5.10.0 5.2.0 5.2.1 5.2.2 5.3.0 5.3.1 5.3.2 5.3.3 5.6.0 5.6.1 5.7.0 5.7.1 5.8.0 5.8.1 5.8.2
matomo / app / core / Url.php
matomo / app / core Last commit date
API 2 years ago Access 3 years ago Application 4 years ago Archive 3 years ago ArchiveProcessor 2 years ago Archiver 5 years ago AssetManager 3 years ago Auth 3 years ago Category 5 years ago Changes 3 years ago CliMulti 4 years ago Columns 3 years ago Concurrency 3 years ago Config 4 years ago Container 4 years ago CronArchive 3 years ago DataAccess 2 years ago DataFiles 5 years ago DataTable 2 years ago Db 3 years ago DeviceDetector 3 years ago Email 5 years ago Exception 3 years ago Http 4 years ago Intl 4 years ago Mail 3 years ago Measurable 5 years ago Menu 3 years ago Metrics 4 years ago Notification 4 years ago Period 4 years ago Plugin 2 years ago ProfessionalServices 4 years ago Report 5 years ago ReportRenderer 2 years ago Scheduler 4 years ago Segment 3 years ago Session 4 years ago Settings 3 years ago Tracker 3 years ago Translation 4 years ago UpdateCheck 5 years ago Updater 3 years ago Updates 3 years ago Validators 4 years ago View 4 years ago ViewDataTable 3 years ago Visualization 4 years ago Widget 5 years ago Access.php 4 years ago Archive.php 3 years ago ArchiveProcessor.php 2 years ago AssetManager.php 4 years ago Auth.php 5 years ago AuthResult.php 5 years ago BaseFactory.php 5 years ago Cache.php 5 years ago CacheId.php 5 years ago CliMulti.php 4 years ago Common.php 3 years ago Config.php 3 years ago Console.php 3 years ago Context.php 5 years ago Cookie.php 3 years ago CronArchive.php 3 years ago DataArray.php 4 years ago DataTable.php 2 years ago Date.php 3 years ago Db.php 4 years ago DbHelper.php 3 years ago Development.php 5 years ago ErrorHandler.php 5 years ago EventDispatcher.php 3 years ago ExceptionHandler.php 3 years ago FileIntegrity.php 3 years ago Filechecks.php 4 years ago Filesystem.php 3 years ago FrontController.php 3 years ago Http.php 3 years ago IP.php 4 years ago Log.php 4 years ago LogDeleter.php 4 years ago Mail.php 3 years ago Metrics.php 3 years ago NoAccessException.php 5 years ago Nonce.php 3 years ago Notification.php 5 years ago NumberFormatter.php 4 years ago Option.php 4 years ago Period.php 5 years ago Piwik.php 3 years ago Plugin.php 4 years ago Profiler.php 4 years ago ProxyHeaders.php 5 years ago ProxyHttp.php 3 years ago QuickForm2.php 5 years ago RankingQuery.php 3 years ago ReportRenderer.php 4 years ago Segment.php 3 years ago Sequence.php 5 years ago Session.php 3 years ago SettingsPiwik.php 3 years ago SettingsServer.php 5 years ago Singleton.php 5 years ago Site.php 3 years ago SiteContentDetector.php 2 years ago SupportedBrowser.php 3 years ago TCPDF.php 5 years ago Theme.php 5 years ago Timer.php 5 years ago Tracker.php 4 years ago Twig.php 4 years ago Unzip.php 5 years ago UpdateCheck.php 5 years ago Updater.php 4 years ago UpdaterErrorException.php 5 years ago Updates.php 5 years ago Url.php 2 years ago UrlHelper.php 3 years ago Version.php 2 years ago View.php 3 years ago bootstrap.php 3 years ago dispatch.php 5 years ago testMinimumPhpVersion.php 3 years ago
Url.php
833 lines
1 <?php
2
3 /**
4 * Matomo - free/libre analytics platform
5 *
6 * @link https://matomo.org
7 * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
8 *
9 */
10
11 namespace Piwik;
12
13 use Exception;
14 use Matomo\Network\IPUtils;
15
16 /**
17 * Provides URL related helper methods.
18 *
19 * This class provides simple methods that can be used to parse and modify
20 * the current URL. It is most useful when plugins need to redirect the current
21 * request to a URL and when they need to link to other parts of Piwik in
22 * HTML.
23 *
24 * ### Examples
25 *
26 * **Redirect to a different controller action**
27 *
28 * public function myControllerAction()
29 * {
30 * $url = Url::getCurrentQueryStringWithParametersModified(array(
31 * 'module' => 'DevicesDetection',
32 * 'action' => 'index'
33 * ));
34 * Url::redirectToUrl($url);
35 * }
36 *
37 * **Link to a different controller action in a template**
38 *
39 * public function myControllerAction()
40 * {
41 * $url = Url::getCurrentQueryStringWithParametersModified(array(
42 * 'module' => 'UserCountryMap',
43 * 'action' => 'realtimeMap',
44 * 'changeVisitAlpha' => 0,
45 * 'removeOldVisits' => 0
46 * ));
47 * $view = new View("@MyPlugin/myPopup");
48 * $view->realtimeMapUrl = $url;
49 * return $view->render();
50 * }
51 *
52 */
53 class Url
54 {
55 /**
56 * Returns the current URL.
57 *
58 * @return string eg, `"http://example.org/dir1/dir2/index.php?param1=value1&param2=value2"`
59 * @api
60 */
61 public static function getCurrentUrl()
62 {
63 return self::getCurrentScheme() . '://'
64 . self::getCurrentHost()
65 . self::getCurrentScriptName(false)
66 . self::getCurrentQueryString();
67 }
68
69 /**
70 * Returns the current URL without the query string.
71 *
72 * @param bool $checkTrustedHost Whether to do trusted host check. Should ALWAYS be true,
73 * except in {@link Piwik\Plugin\Controller}.
74 * @return string eg, `"http://example.org/dir1/dir2/index.php"` if the current URL is
75 * `"http://example.org/dir1/dir2/index.php?param1=value1&param2=value2"`.
76 * @api
77 */
78 public static function getCurrentUrlWithoutQueryString($checkTrustedHost = true)
79 {
80 return self::getCurrentScheme() . '://'
81 . self::getCurrentHost($default = 'unknown', $checkTrustedHost)
82 . self::getCurrentScriptName(false);
83 }
84
85 /**
86 * Returns the current URL without the query string and without the name of the file
87 * being executed.
88 *
89 * @return string eg, `"http://example.org/dir1/dir2/"` if the current URL is
90 * `"http://example.org/dir1/dir2/index.php?param1=value1&param2=value2"`.
91 * @api
92 */
93 public static function getCurrentUrlWithoutFileName()
94 {
95 return self::getCurrentScheme() . '://'
96 . self::getCurrentHost()
97 . self::getCurrentScriptPath();
98 }
99
100 /**
101 * Returns the path to the script being executed. The script file name is not included.
102 *
103 * @return string eg, `"/dir1/dir2/"` if the current URL is
104 * `"http://example.org/dir1/dir2/index.php?param1=value1&param2=value2"`
105 * @api
106 */
107 public static function getCurrentScriptPath()
108 {
109 $queryString = self::getCurrentScriptName();
110
111 //add a fake letter case /test/test2/ returns /test which is not expected
112 $urlDir = dirname($queryString . 'x');
113 $urlDir = str_replace('\\', '/', $urlDir);
114 // if we are in a subpath we add a trailing slash
115 if (strlen($urlDir) > 1) {
116 $urlDir .= '/';
117 }
118 return $urlDir;
119 }
120
121 /**
122 * Returns the path to the script being executed. Includes the script file name.
123 *
124 * @param bool $removePathInfo If true (default value) then the PATH_INFO will be stripped.
125 * @return string eg, `"/dir1/dir2/index.php"` if the current URL is
126 * `"http://example.org/dir1/dir2/index.php?param1=value1&param2=value2"`
127 * @api
128 */
129 public static function getCurrentScriptName($removePathInfo = true)
130 {
131 $url = '';
132
133 // insert extra path info if proxy_uri_header is set and enabled
134 if (
135 isset(Config::getInstance()->General['proxy_uri_header'])
136 && Config::getInstance()->General['proxy_uri_header'] == 1
137 && !empty($_SERVER['HTTP_X_FORWARDED_URI'])
138 ) {
139 $url .= $_SERVER['HTTP_X_FORWARDED_URI'];
140 }
141
142 if (!empty($_SERVER['REQUEST_URI'])) {
143 $url .= $_SERVER['REQUEST_URI'];
144
145 // strip http://host (Apache+Rails anomaly)
146 if (preg_match('~^https?://[^/]+($|/.*)~D', $url, $matches)) {
147 $url = $matches[1];
148 }
149
150 // strip parameters
151 if (($pos = mb_strpos($url, "?")) !== false) {
152 $url = mb_substr($url, 0, $pos);
153 }
154
155 // strip path_info
156 if ($removePathInfo && !empty($_SERVER['PATH_INFO'])) {
157 $url = mb_substr($url, 0, -mb_strlen($_SERVER['PATH_INFO']));
158 }
159 }
160
161 /**
162 * SCRIPT_NAME is our fallback, though it may not be set correctly
163 *
164 * @see http://php.net/manual/en/reserved.variables.php
165 */
166 if (empty($url)) {
167 if (isset($_SERVER['SCRIPT_NAME'])) {
168 $url = $_SERVER['SCRIPT_NAME'];
169 } elseif (isset($_SERVER['SCRIPT_FILENAME'])) {
170 $url = $_SERVER['SCRIPT_FILENAME'];
171 } elseif (isset($_SERVER['argv'])) {
172 $url = $_SERVER['argv'][0];
173 }
174 }
175
176 if (!isset($url[0]) || $url[0] !== '/') {
177 $url = '/' . $url;
178 }
179
180 // A hash part should actually be never send to the server, as browsers automatically remove them from the request
181 // The same happens for tools like cUrl. While Apache won't answer requests that contain them, Nginx would handle them
182 // and the hash part would be included in REQUEST_URI. Therefor we always remove any hash parts here.
183 if (mb_strpos($url, '#')) {
184 $url = mb_substr($url, 0, mb_strpos($url, '#'));
185 }
186
187 return $url;
188 }
189
190 /**
191 * Returns the current URL's protocol.
192 *
193 * @return string `'https'` or `'http'`
194 * @api
195 */
196 public static function getCurrentScheme()
197 {
198 if (self::isPiwikConfiguredToAssumeSecureConnection()) {
199 return 'https';
200 }
201 return self::getCurrentSchemeFromRequestHeader();
202 }
203
204 /**
205 * Validates the **Host** HTTP header (untrusted user input). Used to prevent Host header
206 * attacks.
207 *
208 * @param string|bool $host Contents of Host: header from the HTTP request. If `false`, gets the
209 * value from the request.
210 * @return bool `true` if valid; `false` otherwise.
211 */
212 public static function isValidHost($host = false): bool
213 {
214 // only do trusted host check if it's enabled
215 if (
216 isset(Config::getInstance()->General['enable_trusted_host_check'])
217 && Config::getInstance()->General['enable_trusted_host_check'] == 0
218 ) {
219 return true;
220 }
221
222 if (false === $host || null === $host) {
223 $host = self::getHostFromServerVariable();
224 if (empty($host)) {
225 // if no current host, assume valid
226 return true;
227 }
228 }
229
230 // if host is in hardcoded allowlist, assume it's valid
231 if (in_array($host, self::getAlwaysTrustedHosts())) {
232 return true;
233 }
234
235 $trustedHosts = self::getTrustedHosts();
236
237 // Only punctuation we allow is '[', ']', ':', '.', '_' and '-'
238 $hostLength = strlen($host);
239 if ($hostLength !== strcspn($host, '`~!@#$%^&*()+={}\\|;"\'<>,?/ ')) {
240 return false;
241 }
242
243 // if no trusted hosts, just assume it's valid
244 if (empty($trustedHosts)) {
245 self::saveTrustedHostnameInConfig($host);
246 return true;
247 }
248
249 // Escape trusted hosts for preg_match call below
250 foreach ($trustedHosts as &$trustedHost) {
251 $trustedHost = preg_quote($trustedHost);
252 }
253 $trustedHosts = str_replace("/", "\\/", $trustedHosts);
254
255 $untrustedHost = mb_strtolower($host);
256 $untrustedHost = rtrim($untrustedHost, '.');
257
258 $hostRegex = mb_strtolower('/(^|.)' . implode('$|', $trustedHosts) . '$/');
259
260 $result = preg_match($hostRegex, $untrustedHost);
261 return 0 !== $result;
262 }
263
264 /**
265 * Records one host, or an array of hosts in the config file,
266 * if user is Super User
267 *
268 * @static
269 * @param $host string|array
270 * @return bool
271 */
272 public static function saveTrustedHostnameInConfig($host)
273 {
274 return self::saveHostsnameInConfig($host, 'General', 'trusted_hosts');
275 }
276
277 public static function saveCORSHostnameInConfig($host)
278 {
279 return self::saveHostsnameInConfig($host, 'General', 'cors_domains');
280 }
281
282 protected static function saveHostsnameInConfig($host, $domain, $key)
283 {
284 if (
285 Piwik::hasUserSuperUserAccess()
286 && file_exists(Config::getLocalConfigPath())
287 ) {
288 $config = Config::getInstance()->$domain;
289 if (!is_array($host)) {
290 $host = [$host];
291 }
292 $host = array_filter($host);
293 if (empty($host)) {
294 return false;
295 }
296 $config[$key] = $host;
297 Config::getInstance()->$domain = $config;
298 Config::getInstance()->forceSave();
299 return true;
300 }
301 return false;
302 }
303
304 /**
305 * Returns the current host.
306 *
307 * @param bool $checkIfTrusted Whether to do trusted host check. Should ALWAYS be true,
308 * except in Controller.
309 * @return string|bool eg, `"demo.piwik.org"` or false if no host found.
310 */
311 public static function getHost($checkIfTrusted = true)
312 {
313 $host = self::getHostFromServerVariable();
314
315 if (strlen($host) && (!$checkIfTrusted || self::isValidHost($host))) {
316 return $host;
317 }
318
319 // HTTP/1.0 request doesn't include Host: header
320 if (isset($_SERVER['SERVER_ADDR'])) {
321 return $_SERVER['SERVER_ADDR'];
322 }
323
324 return false;
325 }
326
327 protected static function getHostFromServerVariable()
328 {
329 try {
330 // this fails when trying to get the hostname before the config was initialized
331 // e.g. for loading the domain specific configuration file
332 // in such a case we always use HTTP_HOST
333 $preferServerName = Config::getInstance()->General['host_validation_use_server_name'];
334 } catch (\Exception $e) {
335 $preferServerName = false;
336 }
337
338 if ($preferServerName && strlen($host = self::getHostFromServerNameVar())) {
339 return $host;
340 } elseif (isset($_SERVER['HTTP_HOST']) && strlen($host = $_SERVER['HTTP_HOST'])) {
341 return $host;
342 }
343
344 return false;
345 }
346
347 /**
348 * Returns the valid hostname (according to RFC standards) as a string; else it will return false if it isn't valid.
349 * If the hostname isn't supplied it will default to using Url::getHost
350 * Note: this will not verify if the hostname is trusted.
351 * @param $hostname
352 * @return false|string
353 */
354 public static function getRFCValidHostname($hostname = null)
355 {
356 if (empty($hostname)) {
357 $hostname = self::getHost(false);
358 }
359 return filter_var($hostname, FILTER_VALIDATE_DOMAIN, FILTER_FLAG_HOSTNAME);
360 }
361
362 /**
363 * Sets the host. Useful for CLI scripts, eg. core:archive command
364 *
365 * @param $host string
366 */
367 public static function setHost($host)
368 {
369 $_SERVER['SERVER_NAME'] = $host;
370 $_SERVER['HTTP_HOST'] = $host;
371 unset($_SERVER['SERVER_PORT']);
372 }
373
374 /**
375 * Returns the current host.
376 *
377 * @param string $default Default value to return if host unknown
378 * @param bool $checkTrustedHost Whether to do trusted host check. Should ALWAYS be true,
379 * except in Controller.
380 * @return string eg, `"example.org"` if the current URL is
381 * `"http://example.org/dir1/dir2/index.php?param1=value1&param2=value2"`
382 * @api
383 */
384 public static function getCurrentHost($default = 'unknown', $checkTrustedHost = true)
385 {
386 $hostHeaders = [];
387
388 $config = Config::getInstance()->General;
389 if (isset($config['proxy_host_headers'])) {
390 $hostHeaders = $config['proxy_host_headers'];
391 }
392
393 if (!is_array($hostHeaders)) {
394 $hostHeaders = [];
395 }
396
397 $host = self::getHost($checkTrustedHost);
398 $default = Common::sanitizeInputValue($host ? $host : $default);
399
400 return IP::getNonProxyIpFromHeader($default, $hostHeaders);
401 }
402
403 /**
404 * Returns the query string of the current URL.
405 *
406 * @return string eg, `"?param1=value1&param2=value2"` if the current URL is
407 * `"http://example.org/dir1/dir2/index.php?param1=value1&param2=value2"`
408 * @api
409 */
410 public static function getCurrentQueryString()
411 {
412 $url = '';
413 if (
414 isset($_SERVER['QUERY_STRING'])
415 && !empty($_SERVER['QUERY_STRING'])
416 ) {
417 $url .= "?" . $_SERVER['QUERY_STRING'];
418 }
419 return $url;
420 }
421
422 /**
423 * Returns an array mapping query parameter names with query parameter values for
424 * the current URL.
425 *
426 * @return array If current URL is `"http://example.org/dir1/dir2/index.php?param1=value1&param2=value2"`
427 * this will return:
428 *
429 * array(
430 * 'param1' => string 'value1',
431 * 'param2' => string 'value2'
432 * )
433 * @api
434 */
435 public static function getArrayFromCurrentQueryString()
436 {
437 $queryString = self::getCurrentQueryString();
438 $urlValues = UrlHelper::getArrayFromQueryString($queryString);
439 return $urlValues;
440 }
441
442 /**
443 * Modifies the current query string with the supplied parameters and returns
444 * the result. Parameters in the current URL will be overwritten with values
445 * in `$params` and parameters absent from the current URL but present in `$params`
446 * will be added to the result.
447 *
448 * @param array $params set of parameters to modify/add in the current URL
449 * eg, `array('param3' => 'value3')`
450 * @return string eg, `"?param2=value2&param3=value3"`
451 * @api
452 */
453 public static function getCurrentQueryStringWithParametersModified($params)
454 {
455 $urlValues = self::getArrayFromCurrentQueryString();
456 foreach ($params as $key => $value) {
457 $urlValues[$key] = $value;
458 }
459 $query = self::getQueryStringFromParameters($urlValues);
460 if (strlen($query) > 0) {
461 return '?' . $query;
462 }
463 return '';
464 }
465
466 /**
467 * Converts an array of parameters name => value mappings to a query
468 * string. Values must already be URL encoded before you call this function.
469 *
470 * @param array $parameters eg. `array('param1' => 10, 'param2' => array(1,2))`
471 * @return string eg. `"param1=10&param2[]=1&param2[]=2"`
472 * @api
473 */
474 public static function getQueryStringFromParameters($parameters)
475 {
476 $query = '';
477 foreach ($parameters as $name => $value) {
478 if (is_null($value) || $value === false) {
479 continue;
480 }
481 if (is_array($value)) {
482 foreach ($value as $theValue) {
483 $query .= $name . "[]=" . $theValue . "&";
484 }
485 } else {
486 $query .= $name . "=" . $value . "&";
487 }
488 }
489 $query = substr($query, 0, -1);
490 return $query;
491 }
492
493 public static function getQueryStringFromUrl($url)
494 {
495 return parse_url($url, PHP_URL_QUERY);
496 }
497
498 /**
499 * Redirects the user to the referrer. If no referrer exists, the user is redirected
500 * to the current URL without query string.
501 *
502 * @api
503 */
504 public static function redirectToReferrer()
505 {
506 $referrer = self::getReferrer();
507 if ($referrer !== false) {
508 self::redirectToUrl($referrer);
509 }
510 self::redirectToUrl(self::getCurrentUrlWithoutQueryString());
511 }
512
513 private static function redirectToUrlNoExit($url)
514 {
515 if (
516 UrlHelper::isLookLikeUrl($url)
517 || strpos($url, 'index.php') === 0
518 ) {
519 Common::sendResponseCode(302);
520 Common::sendHeader("X-Robots-Tag: noindex");
521 Common::sendHeader("Location: $url");
522 } else {
523 echo "Invalid URL to redirect to.";
524 }
525
526 if (Common::isPhpCliMode()) {
527 throw new Exception("If you were using a browser, Matomo would redirect you to this URL: $url \n\n");
528 }
529 }
530
531 /**
532 * Redirects the user to the specified URL.
533 *
534 * @param string $url
535 * @throws Exception
536 * @api
537 */
538 public static function redirectToUrl($url)
539 {
540 // Close the session manually.
541 // We should not have to call this because it was registered via register_shutdown_function,
542 // but it is not always called fast enough
543 Session::close();
544
545 self::redirectToUrlNoExit($url);
546
547 exit;
548 }
549
550 /**
551 * If the page is using HTTP, redirect to the same page over HTTPS
552 */
553 public static function redirectToHttps()
554 {
555 if (ProxyHttp::isHttps()) {
556 return;
557 }
558 $url = self::getCurrentUrl();
559 $url = str_replace("http://", "https://", $url);
560 self::redirectToUrl($url);
561 }
562
563 /**
564 * Returns the **HTTP_REFERER** `$_SERVER` variable, or `false` if not found.
565 *
566 * @return string|false
567 * @api
568 */
569 public static function getReferrer()
570 {
571 if (!empty($_SERVER['HTTP_REFERER'])) {
572 return $_SERVER['HTTP_REFERER'];
573 }
574 return false;
575 }
576
577 /**
578 * Returns `true` if the URL points to something on the same host, `false` if otherwise.
579 *
580 * @param string $url
581 * @return bool True if local; false otherwise.
582 * @api
583 */
584 public static function isLocalUrl($url)
585 {
586 if (empty($url)) {
587 return true;
588 }
589
590 // handle host name mangling
591 $requestUri = isset($_SERVER['SCRIPT_URI']) ? $_SERVER['SCRIPT_URI'] : '';
592 $parseRequest = @parse_url($requestUri);
593 $hosts = [self::getHost(), self::getCurrentHost()];
594 if (!empty($parseRequest['host'])) {
595 $hosts[] = $parseRequest['host'];
596 }
597
598 // drop port numbers from hostnames and IP addresses
599 $hosts = array_map(self::class . '::getHostSanitized', $hosts);
600
601 $disableHostCheck = Config::getInstance()->General['enable_trusted_host_check'] == 0;
602 // compare scheme and host
603 $parsedUrl = @parse_url($url);
604 $host = IPUtils::sanitizeIp($parsedUrl['host'] ?? '');
605 return !empty($host)
606 && ($disableHostCheck || in_array($host, $hosts))
607 && !empty($parsedUrl['scheme'])
608 && in_array($parsedUrl['scheme'], ['http', 'https']);
609 }
610
611 /**
612 * Checks whether the given host is a local host like `127.0.0.1` or `localhost`.
613 *
614 * @param string $host
615 * @return bool
616 */
617 public static function isLocalHost($host)
618 {
619 if (empty($host)) {
620 return false;
621 }
622
623 // remove port
624 $hostWithoutPort = explode(':', $host);
625 array_pop($hostWithoutPort);
626 $hostWithoutPort = implode(':', $hostWithoutPort);
627
628 $localHostnames = Url::getLocalHostnames();
629 return in_array($host, $localHostnames, true)
630 || in_array($hostWithoutPort, $localHostnames, true);
631 }
632
633 public static function getTrustedHostsFromConfig()
634 {
635 $hosts = self::getHostsFromConfig('General', 'trusted_hosts');
636
637 // Case user wrote in the config, http://example.com/test instead of example.com
638 foreach ($hosts as &$host) {
639 if (UrlHelper::isLookLikeUrl($host)) {
640 $host = parse_url($host, PHP_URL_HOST);
641 }
642 }
643 return $hosts;
644 }
645
646 public static function getTrustedHosts()
647 {
648 return self::getTrustedHostsFromConfig();
649 }
650
651 public static function getCorsHostsFromConfig()
652 {
653 return self::getHostsFromConfig('General', 'cors_domains');
654 }
655
656 /**
657 * Returns hostname, without port numbers
658 *
659 * @param $host
660 * @return string
661 */
662 public static function getHostSanitized($host)
663 {
664 if (!class_exists("Matomo\\Network\\IPUtils")) {
665 throw new Exception("Matomo\\Network\\IPUtils could not be found, maybe you are using Matomo from git and need to update Composer. $ php composer.phar update");
666 }
667 return IPUtils::sanitizeIp($host);
668 }
669
670 protected static function getHostsFromConfig($domain, $key)
671 {
672 $config = @Config::getInstance()->$domain;
673
674 if (!isset($config[$key])) {
675 return [];
676 }
677
678 $hosts = $config[$key];
679 if (!is_array($hosts)) {
680 return [];
681 }
682 return $hosts;
683 }
684
685 /**
686 * Returns the host part of any valid URL.
687 *
688 * @param string $url Any fully qualified URL
689 * @return string|null The actual host in lower case or null if $url is not a valid fully qualified URL.
690 */
691 public static function getHostFromUrl($url)
692 {
693 if (!is_string($url)) {
694 return null;
695 }
696
697 $urlHost = parse_url($url, PHP_URL_HOST);
698
699 if (empty($urlHost)) {
700 return null;
701 }
702
703 return mb_strtolower($urlHost);
704 }
705
706 /**
707 * Checks whether any of the given URLs has the given host. If not, we will also check whether any URL uses a
708 * subdomain of the given host. For instance if host is "example.com" and a URL is "http://www.example.com" we
709 * consider this as valid and return true. The always trusted hosts such as "127.0.0.1" are considered valid as well.
710 *
711 * @param $host
712 * @param $urls
713 * @return bool
714 */
715 public static function isHostInUrls($host, $urls)
716 {
717 if (empty($host)) {
718 return false;
719 }
720
721 $host = mb_strtolower($host);
722
723 if (!empty($urls)) {
724 foreach ($urls as $url) {
725 if (mb_strtolower($url) === $host) {
726 return true;
727 }
728
729 $siteHost = self::getHostFromUrl($url);
730
731 if ($siteHost === $host) {
732 return true;
733 }
734
735 if (Common::stringEndsWith($siteHost, '.' . $host)) {
736 // allow subdomains
737 return true;
738 }
739 }
740 }
741
742 return in_array($host, self::getAlwaysTrustedHosts());
743 }
744
745 /**
746 * List of hosts that are never checked for validity.
747 *
748 * @return array
749 */
750 private static function getAlwaysTrustedHosts()
751 {
752 return self::getLocalHostnames();
753 }
754
755 /**
756 * @return array
757 */
758 public static function getLocalHostnames()
759 {
760 return ['localhost', '127.0.0.1', '::1', '[::1]', '[::]', '0000::1', '0177.0.0.1', '2130706433', '[0:0:0:0:0:ffff:127.0.0.1]'];
761 }
762
763 /**
764 * @return bool
765 */
766 public static function isSecureConnectionAssumedByPiwikButNotForcedYet()
767 {
768 $isSecureConnectionLikelyNotUsed = Url::isSecureConnectionLikelyNotUsed();
769 $hasSessionCookieSecureFlag = ProxyHttp::isHttps();
770 $isSecureConnectionAssumedByPiwikButNotForcedYet = Url::isPiwikConfiguredToAssumeSecureConnection() && !SettingsPiwik::isHttpsForced();
771
772 return $isSecureConnectionLikelyNotUsed
773 && $hasSessionCookieSecureFlag
774 && $isSecureConnectionAssumedByPiwikButNotForcedYet;
775 }
776
777 /**
778 * @return string
779 */
780 protected static function getCurrentSchemeFromRequestHeader()
781 {
782 if (isset($_SERVER['HTTP_X_FORWARDED_SCHEME']) && strtolower($_SERVER['HTTP_X_FORWARDED_SCHEME']) === 'https') {
783 return 'https';
784 }
785
786 if (isset($_SERVER['HTTP_X_URL_SCHEME']) && strtolower($_SERVER['HTTP_X_URL_SCHEME']) === 'https') {
787 return 'https';
788 }
789
790 if (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] == 'http') {
791 return 'http';
792 }
793
794 if (
795 (isset($_SERVER['HTTPS']) && ($_SERVER['HTTPS'] == 'on' || $_SERVER['HTTPS'] === true))
796 || (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https')
797 ) {
798 return 'https';
799 }
800 return 'http';
801 }
802
803 protected static function isSecureConnectionLikelyNotUsed()
804 {
805 return Url::getCurrentSchemeFromRequestHeader() == 'http';
806 }
807
808 /**
809 * @return bool
810 */
811 protected static function isPiwikConfiguredToAssumeSecureConnection()
812 {
813 $assume_secure_protocol = @Config::getInstance()->General['assume_secure_protocol'];
814 return (bool) $assume_secure_protocol;
815 }
816
817 public static function getHostFromServerNameVar()
818 {
819 $host = @$_SERVER['SERVER_NAME'];
820 if (!empty($host)) {
821 if (
822 strpos($host, ':') === false
823 && !empty($_SERVER['SERVER_PORT'])
824 && $_SERVER['SERVER_PORT'] != 80
825 && $_SERVER['SERVER_PORT'] != 443
826 ) {
827 $host .= ':' . $_SERVER['SERVER_PORT'];
828 }
829 }
830 return $host;
831 }
832 }
833