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