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