Newer
Older
mattpiwik
a validé
<?php
/**
* Piwik - free/libre analytics platform
*
mattpiwik
a validé
* @link http://piwik.org
*
mattpiwik
a validé
*/
namespace Piwik;
use Exception;
use Piwik\Network\IPUtils;
mattpiwik
a validé
/**
diosmosis
a validé
* Provides URL related helper methods.
diosmosis
a validé
* This class provides simple methods that can be used to parse and modify
* the current URL. It is most useful when plugins need to redirect the current
* request to a URL and when they need to link to other parts of Piwik in
* HTML.
diosmosis
a validé
* ### Examples
diosmosis
a validé
* **Redirect to a different controller action**
* public function myControllerAction()
* {
* $url = Url::getCurrentQueryStringWithParametersModified(array(
* 'module' => 'DevicesDetection',
* 'action' => 'index'
* ));
* Url::redirectToUrl($url);
* }
diosmosis
a validé
* **Link to a different controller action in a template**
* public function myControllerAction()
* {
* $url = Url::getCurrentQueryStringWithParametersModified(array(
* 'module' => 'UserCountryMap',
* 'action' => 'realtimeMap',
* 'changeVisitAlpha' => 0,
* 'removeOldVisits' => 0
* ));
* $view = new View("@MyPlugin/myPopup");
* $view->realtimeMapUrl = $url;
* return $view->render();
* }
mattpiwik
a validé
*/
class Url
mattpiwik
a validé
{
diosmosis
a validé
* Returns the current URL.
diosmosis
a validé
* @return string eg, `"http://example.org/dir1/dir2/index.php?param1=value1¶m2=value2"`
* @api
{
return self::getCurrentScheme() . '://'
diosmosis
a validé
. self::getCurrentHost()
Matthieu Napoli
a validé
. self::getCurrentScriptName(false)
}
/**
diosmosis
a validé
* Returns the current URL without the query string.
* @param bool $checkTrustedHost Whether to do trusted host check. Should ALWAYS be true,
* except in {@link Piwik\Plugin\Controller}.
diosmosis
a validé
* @return string eg, `"http://example.org/dir1/dir2/index.php"` if the current URL is
* `"http://example.org/dir1/dir2/index.php?param1=value1¶m2=value2"`.
* @api
public static function getCurrentUrlWithoutQueryString($checkTrustedHost = true)
return self::getCurrentScheme() . '://'
diosmosis
a validé
. self::getCurrentHost($default = 'unknown', $checkTrustedHost)
. self::getCurrentScriptName(false);
}
/**
diosmosis
a validé
* Returns the current URL without the query string and without the name of the file
* being executed.
diosmosis
a validé
* @return string eg, `"http://example.org/dir1/dir2/"` if the current URL is
* `"http://example.org/dir1/dir2/index.php?param1=value1¶m2=value2"`.
* @api
public static function getCurrentUrlWithoutFileName()
return self::getCurrentScheme() . '://'
diosmosis
a validé
. self::getCurrentHost()
. self::getCurrentScriptPath();
}
/**
diosmosis
a validé
* Returns the path to the script being executed. The script file name is not included.
diosmosis
a validé
* @return string eg, `"/dir1/dir2/"` if the current URL is
* `"http://example.org/dir1/dir2/index.php?param1=value1¶m2=value2"`
* @api
{
$queryString = self::getCurrentScriptName();
//add a fake letter case /test/test2/ returns /test which is not expected
$urlDir = dirname($queryString . 'x');
$urlDir = str_replace('\\', '/', $urlDir);
// if we are in a subpath we add a trailing slash
if (strlen($urlDir) > 1) {
$urlDir .= '/';
}
return $urlDir;
}
/**
diosmosis
a validé
* Returns the path to the script being executed. Includes the script file name.
Matthieu Napoli
a validé
* @param bool $removePathInfo If true (default value) then the PATH_INFO will be stripped.
diosmosis
a validé
* @return string eg, `"/dir1/dir2/index.php"` if the current URL is
* `"http://example.org/dir1/dir2/index.php?param1=value1¶m2=value2"`
* @api
Matthieu Napoli
a validé
public static function getCurrentScriptName($removePathInfo = true)
{
$url = '';
if (!empty($_SERVER['REQUEST_URI'])) {
$url = $_SERVER['REQUEST_URI'];
// strip http://host (Apache+Rails anomaly)
if (preg_match('~^https?://[^/]+($|/.*)~D', $url, $matches)) {
$url = $matches[1];
}
// strip parameters
if (($pos = strpos($url, "?")) !== false) {
$url = substr($url, 0, $pos);
}
// strip path_info
Matthieu Napoli
a validé
if ($removePathInfo && isset($_SERVER['PATH_INFO'])) {
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
$url = substr($url, 0, -strlen($_SERVER['PATH_INFO']));
}
}
/**
* SCRIPT_NAME is our fallback, though it may not be set correctly
*
* @see http://php.net/manual/en/reserved.variables.php
*/
if (empty($url)) {
if (isset($_SERVER['SCRIPT_NAME'])) {
$url = $_SERVER['SCRIPT_NAME'];
} elseif (isset($_SERVER['SCRIPT_FILENAME'])) {
$url = $_SERVER['SCRIPT_FILENAME'];
} elseif (isset($_SERVER['argv'])) {
$url = $_SERVER['argv'][0];
}
}
if (!isset($url[0]) || $url[0] !== '/') {
$url = '/' . $url;
}
return $url;
}
/**
diosmosis
a validé
* Returns the current URL's protocol.
diosmosis
a validé
* @return string `'https'` or `'http'`
* @api
if (self::isPiwikConfiguredToAssumeSecureConnection()) {
return 'https';
}
mattab
a validé
return self::getCurrentSchemeFromRequestHeader();
}
/**
* Validates the **Host** HTTP header (untrusted user input). Used to prevent Host header
* attacks.
* @param string|bool $host Contents of Host: header from the HTTP request. If `false`, gets the
* @return bool `true` if valid; `false` otherwise.
public static function isValidHost($host = false)
{
// only do trusted host check if it's enabled
if (isset(Config::getInstance()->General['enable_trusted_host_check'])
&& Config::getInstance()->General['enable_trusted_host_check'] == 0
) {
return true;
}
if ($host === false) {
$host = @$_SERVER['HTTP_HOST'];
if (empty($host)) {
// if no current host, assume valid
return true;
}
}
// if host is in hardcoded whitelist, assume it's valid
if (in_array($host, self::getAlwaysTrustedHosts())) {
return true;
}
// Only punctuation we allow is '[', ']', ':', '.', '_' and '-'
$hostLength = strlen($host);
if ($hostLength !== strcspn($host, '`~!@#$%^&*()+={}\\|;"\'<>,?/ ')) {
return false;
}
// if no trusted hosts, just assume it's valid
if (empty($trustedHosts)) {
self::saveTrustedHostnameInConfig($host);
return true;
}
// Escape trusted hosts for preg_match call below
$trustedHost = preg_quote($trustedHost);
}
$trustedHosts = str_replace("/", "\\/", $trustedHosts);
$untrustedHost = Common::mb_strtolower($host);
$untrustedHost = rtrim($untrustedHost, '.');
mattab
a validé
$hostRegex = Common::mb_strtolower('/(^|.)' . implode('|', $trustedHosts) . '$/');
mattab
a validé
$result = preg_match($hostRegex, $untrustedHost);
return 0 !== $result;
}
/**
* Records one host, or an array of hosts in the config file,
*
* @static
* @param $host string|array
*/
public static function saveTrustedHostnameInConfig($host)
Marcin Czołnowski
a validé
{
return self::saveHostsnameInConfig($host, 'General', 'trusted_hosts');
}
public static function saveCORSHostnameInConfig($host)
{
Marcin Czołnowski
a validé
return self::saveHostsnameInConfig($host, 'General', 'cors_domains');
Marcin Czołnowski
a validé
}
protected static function saveHostsnameInConfig($host, $domain, $key)
Thomas Steur
a validé
if (Piwik::hasUserSuperUserAccess()
&& file_exists(Config::getLocalConfigPath())
Marcin Czołnowski
a validé
$config = Config::getInstance()->$domain;
if (!is_array($host)) {
$host = array($host);
}
$host = array_filter($host);
if (empty($host)) {
return false;
}
Marcin Czołnowski
a validé
$config[$key] = $host;
Config::getInstance()->$domain = $config;
Config::getInstance()->forceSave();
}
/**
diosmosis
a validé
* Returns the current host.
*
* @param bool $checkIfTrusted Whether to do trusted host check. Should ALWAYS be true,
* except in Controller.
diosmosis
a validé
* @return string|bool eg, `"demo.piwik.org"` or false if no host found.
public static function getHost($checkIfTrusted = true)
{
// HTTP/1.1 request
if (isset($_SERVER['HTTP_HOST'])
&& strlen($host = $_SERVER['HTTP_HOST'])
&& (!$checkIfTrusted
|| self::isValidHost($host))
) {
return $host;
}
// HTTP/1.0 request doesn't include Host: header
if (isset($_SERVER['SERVER_ADDR'])) {
return $_SERVER['SERVER_ADDR'];
}
return false;
}
mattab
a validé
* Sets the host. Useful for CLI scripts, eg. core:archive command
* @param $host string
*/
{
$_SERVER['HTTP_HOST'] = $host;
}
diosmosis
a validé
* Returns the current host.
*
* @param string $default Default value to return if host unknown
* @param bool $checkTrustedHost Whether to do trusted host check. Should ALWAYS be true,
* except in Controller.
diosmosis
a validé
* @return string eg, `"example.org"` if the current URL is
* `"http://example.org/dir1/dir2/index.php?param1=value1¶m2=value2"`
* @api
public static function getCurrentHost($default = 'unknown', $checkTrustedHost = true)
$hostHeaders = array();
$config = Config::getInstance()->General;
Thomas Steur
a validé
if (isset($config['proxy_host_headers'])) {
$hostHeaders = $config['proxy_host_headers'];
}
if (!is_array($hostHeaders)) {
$hostHeaders = array();
}
$host = self::getHost($checkTrustedHost);
$default = Common::sanitizeInputValue($host ? $host : $default);
return IP::getNonProxyIpFromHeader($default, $hostHeaders);
}
/**
diosmosis
a validé
* Returns the query string of the current URL.
diosmosis
a validé
* @return string eg, `"?param1=value1¶m2=value2"` if the current URL is
* `"http://example.org/dir1/dir2/index.php?param1=value1¶m2=value2"`
* @api
{
$url = '';
if (isset($_SERVER['QUERY_STRING'])
&& !empty($_SERVER['QUERY_STRING'])
) {
$url .= "?" . $_SERVER['QUERY_STRING'];
}
return $url;
}
/**
diosmosis
a validé
* Returns an array mapping query paramater names with query parameter values for
* the current URL.
diosmosis
a validé
* @return array If current URL is `"http://example.org/dir1/dir2/index.php?param1=value1¶m2=value2"`
* this will return:
* array(
* 'param1' => string 'value1',
* 'param2' => string 'value2'
* )
diosmosis
a validé
* @api
public static function getArrayFromCurrentQueryString()
{
$queryString = self::getCurrentQueryString();
$urlValues = UrlHelper::getArrayFromQueryString($queryString);
return $urlValues;
}
/**
diosmosis
a validé
* Modifies the current query string with the supplied parameters and returns
* the result. Parameters in the current URL will be overwritten with values
* in `$params` and parameters absent from the current URL but present in `$params`
* will be added to the result.
diosmosis
a validé
* @param array $params set of parameters to modify/add in the current URL
* eg, `array('param3' => 'value3')`
* @return string eg, `"?param2=value2¶m3=value3"`
* @api
public static function getCurrentQueryStringWithParametersModified($params)
{
$urlValues = self::getArrayFromCurrentQueryString();
foreach ($params as $key => $value) {
$urlValues[$key] = $value;
}
$query = self::getQueryStringFromParameters($urlValues);
if (strlen($query) > 0) {
return '?' . $query;
}
return '';
}
/**
* Converts an array of parameters name => value mappings to a query
* string. Values must already be URL encoded before you call this function.
diosmosis
a validé
* @param array $parameters eg. `array('param1' => 10, 'param2' => array(1,2))`
* @return string eg. `"param1=10¶m2[]=1¶m2[]=2"`
* @api
public static function getQueryStringFromParameters($parameters)
{
$query = '';
foreach ($parameters as $name => $value) {
continue;
}
if (is_array($value)) {
foreach ($value as $theValue) {
$query .= $name . "[]=" . $theValue . "&";
}
} else {
$query .= $name . "=" . $value . "&";
}
}
$query = substr($query, 0, -1);
return $query;
}
public static function getQueryStringFromUrl($url)
{
return parse_url($url, PHP_URL_QUERY);
}
diosmosis
a validé
* Redirects the user to the referrer. If no referrer exists, the user is redirected
* to the current URL without query string.
diosmosis
a validé
* @api
$referrer = self::getReferrer();
if ($referrer !== false) {
self::redirectToUrl($referrer);
}
self::redirectToUrl(self::getCurrentUrlWithoutQueryString());
}
private static function redirectToUrlNoExit($url)
{
if (UrlHelper::isLookLikeUrl($url)
|| strpos($url, 'index.php') === 0
) {
Matthieu Napoli
a validé
Common::sendResponseCode(302);
Common::sendHeader("Location: $url");
} else {
echo "Invalid URL to redirect to.";
}
if (Common::isPhpCliMode()) {
throw new Exception("If you were using a browser, Piwik would redirect you to this URL: $url \n\n");
}
}
diosmosis
a validé
* Redirects the user to the specified URL.
*
* @param string $url
Thomas Steur
a validé
* @throws Exception
diosmosis
a validé
* @api
// Close the session manually.
// We should not have to call this because it was registered via register_shutdown_function,
// but it is not always called fast enough
Session::close();
mattab
a validé
exit;
}
/**
* If the page is using HTTP, redirect to the same page over HTTPS
*/
{
Thomas Steur
a validé
if (ProxyHttp::isHttps()) {
return;
}
$url = self::getCurrentUrl();
$url = str_replace("http://", "https://", $url);
self::redirectToUrl($url);
}
* Returns the **HTTP_REFERER** `$_SERVER` variable, or `false` if not found.
diosmosis
a validé
* @return string|false
* @api
{
if (!empty($_SERVER['HTTP_REFERER'])) {
return $_SERVER['HTTP_REFERER'];
}
return false;
}
/**
* Returns `true` if the URL points to something on the same host, `false` if otherwise.
*
* @param string $url
* @return bool True if local; false otherwise.
diosmosis
a validé
* @api
{
if (empty($url)) {
return true;
}
// handle host name mangling
$requestUri = isset($_SERVER['SCRIPT_URI']) ? $_SERVER['SCRIPT_URI'] : '';
$parseRequest = @parse_url($requestUri);
$hosts = array(self::getHost(), self::getCurrentHost());
if (!empty($parseRequest['host'])) {
$hosts[] = $parseRequest['host'];
}
// drop port numbers from hostnames and IP addresses
$hosts = array_map(array('self', 'getHostSanitized'), $hosts);
$disableHostCheck = Config::getInstance()->General['enable_trusted_host_check'] == 0;
// compare scheme and host
$parsedUrl = @parse_url($url);
$host = IPUtils::sanitizeIp(@$parsedUrl['host']);
return !empty($host)
Marcin Czołnowski
a validé
&& ($disableHostCheck || in_array($host, $hosts))
&& !empty($parsedUrl['scheme'])
&& in_array($parsedUrl['scheme'], array('http', 'https'));
mattab
a validé
$hosts = self::getHostsFromConfig('General', 'trusted_hosts');
// Case user wrote in the config, http://example.com/test instead of example.com
foreach ($hosts as &$host) {
if (UrlHelper::isLookLikeUrl($host)) {
$host = parse_url($host, PHP_URL_HOST);
}
}
return $hosts;
Marcin Czołnowski
a validé
public static function getCorsHostsFromConfig()
{
Marcin Czołnowski
a validé
return self::getHostsFromConfig('General', 'cors_domains');
Marcin Czołnowski
a validé
}
/**
* Returns hostname, without port numbers
*
* @param $host
* @return array
*/
public static function getHostSanitized($host)
{
if (!class_exists("Piwik\\Network\\IPUtils")) {
throw new Exception("Piwik\\Network\\IPUtils could not be found, maybe you are using Piwik from git and need to update Composer. $ php composer.phar update");
}
return IPUtils::sanitizeIp($host);
}
Marcin Czołnowski
a validé
mattab
a validé
protected static function getHostsFromConfig($domain, $key)
Marcin Czołnowski
a validé
{
$config = @Config::getInstance()->$domain;
if (!isset($config[$key])) {
return array();
}
$hosts = $config[$key];
if (!is_array($hosts)) {
return array();
}
return $hosts;
}
/**
* Returns the host part of any valid URL.
*
* @param string $url Any fully qualified URL
* @return string|null The actual host in lower case or null if $url is not a valid fully qualified URL.
*/
public static function getHostFromUrl($url)
{
$parsedUrl = parse_url($url);
if (empty($parsedUrl['host'])) {
return;
}
return Common::mb_strtolower($parsedUrl['host']);
}
/**
* Checks whether any of the given URLs has the given host. If not, we will also check whether any URL uses a
* subdomain of the given host. For instance if host is "example.com" and a URL is "http://www.example.com" we
* consider this as valid and return true. The always trusted hosts such as "127.0.0.1" are considered valid as well.
*
* @param $host
* @param $urls
* @return bool
*/
public static function isHostInUrls($host, $urls)
{
if (empty($host)) {
return false;
}
$host = Common::mb_strtolower($host);
if (!empty($urls)) {
foreach ($urls as $url) {
if (Common::mb_strtolower($url) === $host) {
return true;
}
$siteHost = self::getHostFromUrl($url);
if ($siteHost === $host) {
return true;
}
if (Common::stringEndsWith($siteHost, '.' . $host)) {
// allow subdomains
return true;
}
}
}
return in_array($host, self::getAlwaysTrustedHosts());
}
/**
* List of hosts that are never checked for validity.
*
* @return array
*/
private static function getAlwaysTrustedHosts()
{
return self::getLocalHostnames();
}
/**
* @return array
*/
public static function getLocalHostnames()
{
return array('localhost', '127.0.0.1', '::1', '[::1]');
}
mattab
a validé
/**
* @return bool
*/
public static function isSecureConnectionAssumedByPiwikButNotForcedYet()
{
$isSecureConnectionLikelyNotUsed = Url::isSecureConnectionLikelyNotUsed();
$hasSessionCookieSecureFlag = ProxyHttp::isHttps();
$isSecureConnectionAssumedByPiwikButNotForcedYet = Url::isPiwikConfiguredToAssumeSecureConnection() && !SettingsPiwik::isHttpsForced();
return $isSecureConnectionLikelyNotUsed
&& $hasSessionCookieSecureFlag
&& $isSecureConnectionAssumedByPiwikButNotForcedYet;
}
mattab
a validé
/**
* @return string
*/
protected static function getCurrentSchemeFromRequestHeader()
mattab
a validé
{
if ((isset($_SERVER['HTTPS']) && ($_SERVER['HTTPS'] == 'on' || $_SERVER['HTTPS'] === true))
|| (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https')
) {
return 'https';
}
return 'http';
}
protected static function isSecureConnectionLikelyNotUsed()
{
return Url::getCurrentSchemeFromRequestHeader() == 'http';
}
mattab
a validé
/**
* @return bool
*/
protected static function isPiwikConfiguredToAssumeSecureConnection()
mattab
a validé
{
$assume_secure_protocol = @Config::getInstance()->General['assume_secure_protocol'];
return (bool) $assume_secure_protocol;
mattab
a validé
}