From 318a3ca785474fa27fbff9b3ee19fd4c04261ecd Mon Sep 17 00:00:00 2001 From: Philipp Holzer Date: Mon, 8 Apr 2019 21:12:10 +0200 Subject: [PATCH 1/7] Create own base URL class which holds the whole base url business logic --- boot.php | 11 - config/defaults.config.php | 5 - mod/admin.php | 14 +- src/App.php | 189 +++------- src/Core/Authentication.php | 3 +- src/Core/Session.php | 3 +- src/Core/System.php | 2 +- src/Core/Update.php | 141 +------ src/Factory/DependencyFactory.php | 4 +- src/Model/Contact.php | 5 +- src/Network/Probe.php | 30 +- src/Protocol/DFRN.php | 7 +- src/Util/BaseURL.php | 344 ++++++++++++++++++ src/Util/Config/ConfigFileSaver.php | 341 ----------------- src/Util/Network.php | 22 ++ tests/src/Util/Config/ConfigFileSaverTest.php | 189 ---------- 16 files changed, 434 insertions(+), 876 deletions(-) create mode 100644 src/Util/BaseURL.php delete mode 100644 src/Util/Config/ConfigFileSaver.php delete mode 100644 tests/src/Util/Config/ConfigFileSaverTest.php diff --git a/boot.php b/boot.php index e75b2abd91..dfedcdbbbe 100644 --- a/boot.php +++ b/boot.php @@ -82,17 +82,6 @@ define('MAX_IMAGE_LENGTH', -1); */ define('DEFAULT_DB_ENGINE', 'InnoDB'); -/** - * @name SSL Policy - * - * SSL redirection policies - * @{ - */ -define('SSL_POLICY_NONE', 0); -define('SSL_POLICY_FULL', 1); -define('SSL_POLICY_SELFSIGN', 2); -/* @}*/ - /** @deprecated since version 2019.03, please use \Friendica\Module\Register::CLOSED instead */ define('REGISTER_CLOSED', \Friendica\Module\Register::CLOSED); /** @deprecated since version 2019.03, please use \Friendica\Module\Register::APPROVE instead */ diff --git a/config/defaults.config.php b/config/defaults.config.php index 0d9e55c7d9..b42bc8c628 100644 --- a/config/defaults.config.php +++ b/config/defaults.config.php @@ -377,11 +377,6 @@ return [ // Maximum number of posts that a user can send per month with the API. 0 to disable monthly throttling. 'throttle_limit_month' => 0, - // urlpath (String) - // If you are using a subdirectory of your domain you will need to put the relative path (from the root of your domain) here. - // For instance if your URL is 'http://example.com/directory/subdirectory', set urlpath to 'directory/subdirectory'. - 'urlpath' => '', - // username_min_length (Integer) // The minimum character length a username can be. // This length is check once the username has been trimmed and multiple spaces have been collapsed into one. diff --git a/mod/admin.php b/mod/admin.php index 0df907cbaa..db8bb403c1 100644 --- a/mod/admin.php +++ b/mod/admin.php @@ -32,6 +32,7 @@ use Friendica\Module\Tos; use Friendica\Protocol\PortableContact; use Friendica\Util\Arrays; use Friendica\Util\BasePath; +use Friendica\Util\BaseURL; use Friendica\Util\DateTimeFormat; use Friendica\Util\Network; use Friendica\Util\Strings; @@ -1041,9 +1042,6 @@ function admin_page_site_post(App $a) update_table($a, "gcontact", ['connect', 'addr'], $old_host, $new_host); // update config - $configFileSaver = new \Friendica\Util\Config\ConfigFileSaver($a->getBasePath()); - $configFileSaver->addConfigValue('config', 'hostname', parse_url($new_url, PHP_URL_HOST)); - $configFileSaver->saveToConfigFile(); Config::set('system', 'url', $new_url); $a->setBaseURL($new_url); @@ -1199,7 +1197,7 @@ function admin_page_site_post(App $a) $diaspora_enabled = false; } if ($ssl_policy != intval(Config::get('system', 'ssl_policy'))) { - if ($ssl_policy == SSL_POLICY_FULL) { + if ($ssl_policy == BaseURL::SSL_POLICY_FULL) { q("UPDATE `contact` SET `url` = REPLACE(`url` , 'http:' , 'https:'), `photo` = REPLACE(`photo` , 'http:' , 'https:'), @@ -1217,7 +1215,7 @@ function admin_page_site_post(App $a) `thumb` = REPLACE(`thumb` , 'http:' , 'https:') WHERE 1 " ); - } elseif ($ssl_policy == SSL_POLICY_SELFSIGN) { + } elseif ($ssl_policy == BaseURL::SSL_POLICY_SELFSIGN) { q("UPDATE `contact` SET `url` = REPLACE(`url` , 'https:' , 'http:'), `photo` = REPLACE(`photo` , 'https:' , 'http:'), @@ -1473,9 +1471,9 @@ function admin_page_site(App $a) ]; $ssl_choices = [ - SSL_POLICY_NONE => L10n::t("No SSL policy, links will track page SSL state"), - SSL_POLICY_FULL => L10n::t("Force all links to use SSL"), - SSL_POLICY_SELFSIGN => L10n::t("Self-signed certificate, use SSL for local links only \x28discouraged\x29") + BaseURL::SSL_POLICY_NONE => L10n::t("No SSL policy, links will track page SSL state"), + BaseURL::SSL_POLICY_FULL => L10n::t("Force all links to use SSL"), + BaseURL::SSL_POLICY_SELFSIGN => L10n::t("Self-signed certificate, use SSL for local links only \x28discouraged\x29") ]; $check_git_version_choices = [ diff --git a/src/App.php b/src/App.php index 2576cc5d8e..3e1dc147f3 100644 --- a/src/App.php +++ b/src/App.php @@ -8,7 +8,6 @@ use Detection\MobileDetect; use DOMDocument; use DOMXPath; use Exception; -use FastRoute\RouteCollector; use Friendica\Core\Config\Cache\IConfigCache; use Friendica\Core\Config\Configuration; use Friendica\Core\Hook; @@ -16,6 +15,7 @@ use Friendica\Core\Theme; use Friendica\Database\DBA; use Friendica\Model\Profile; use Friendica\Network\HTTPException\InternalServerErrorException; +use Friendica\Util\BaseURL; use Friendica\Util\Config\ConfigFileLoader; use Friendica\Util\HTTPSignature; use Friendica\Util\Profiler; @@ -84,9 +84,9 @@ class App private $router; /** - * @var string The App URL path + * @var BaseURL */ - private $urlPath; + private $baseURL; /** * @var bool true, if the call is from the Friendica APP, otherwise false @@ -178,6 +178,11 @@ class App return $this->mode; } + /** + * Returns the router of the Application + * + * @return App\Router + */ public function getRouter() { return $this->router; @@ -220,8 +225,6 @@ class App } public $queue; - private $scheme; - private $hostname; /** * @brief App constructor. @@ -229,19 +232,21 @@ class App * @param Configuration $config The Configuration * @param App\Mode $mode The mode of this Friendica app * @param App\Router $router The router of this Friendica app + * @param BaseURL $baseURL The full base URL of this Friendica app * @param LoggerInterface $logger The current app logger * @param Profiler $profiler The profiler of this application * @param bool $isBackend Whether it is used for backend or frontend (Default true=backend) * * @throws Exception if the Basepath is not usable */ - public function __construct(Configuration $config, App\Mode $mode, App\Router $router, LoggerInterface $logger, Profiler $profiler, $isBackend = true) + public function __construct(Configuration $config, App\Mode $mode, App\Router $router, BaseURL $baseURL, LoggerInterface $logger, Profiler $profiler, $isBackend = true) { BaseObject::setApp($this); $this->config = $config; $this->mode = $mode; $this->router = $router; + $this->baseURL = $baseURL; $this->profiler = $profiler; $this->logger = $logger; @@ -257,26 +262,6 @@ class App // This has to be quite large to deal with embedded private photos ini_set('pcre.backtrack_limit', 500000); - $this->scheme = 'http'; - - if (!empty($_SERVER['HTTPS']) || - !empty($_SERVER['HTTP_FORWARDED']) && preg_match('/proto=https/', $_SERVER['HTTP_FORWARDED']) || - !empty($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https' || - !empty($_SERVER['HTTP_X_FORWARDED_SSL']) && $_SERVER['HTTP_X_FORWARDED_SSL'] == 'on' || - !empty($_SERVER['FRONT_END_HTTPS']) && $_SERVER['FRONT_END_HTTPS'] == 'on' || - !empty($_SERVER['SERVER_PORT']) && (intval($_SERVER['SERVER_PORT']) == 443) // XXX: reasonable assumption, but isn't this hardcoding too much? - ) { - $this->scheme = 'https'; - } - - if (!empty($_SERVER['SERVER_NAME'])) { - $this->hostname = $_SERVER['SERVER_NAME']; - - if (!empty($_SERVER['SERVER_PORT']) && $_SERVER['SERVER_PORT'] != 80 && $_SERVER['SERVER_PORT'] != 443) { - $this->hostname .= ':' . $_SERVER['SERVER_PORT']; - } - } - set_include_path( get_include_path() . PATH_SEPARATOR . $this->getBasePath() . DIRECTORY_SEPARATOR . 'include' . PATH_SEPARATOR @@ -354,8 +339,6 @@ class App */ public function reload() { - $this->determineURLPath(); - $this->getMode()->determine($this->getBasePath()); if ($this->getMode()->has(App\Mode::DBAVAILABLE)) { @@ -398,97 +381,28 @@ class App } /** - * Figure out if we are running at the top of a domain or in a sub-directory and adjust accordingly + * Returns the scheme of the current call + * @return string + * + * @deprecated 2019.06 - use BaseURL->getScheme() instead */ - private function determineURLPath() - { - /* - * The automatic path detection in this function is currently deactivated, - * see issue https://github.com/friendica/friendica/issues/6679 - * - * The problem is that the function seems to be confused with some url. - * These then confuses the detection which changes the url path. - */ - - /* Relative script path to the web server root - * Not all of those $_SERVER properties can be present, so we do by inverse priority order - */ -/* - $relative_script_path = ''; - $relative_script_path = defaults($_SERVER, 'REDIRECT_URL' , $relative_script_path); - $relative_script_path = defaults($_SERVER, 'REDIRECT_URI' , $relative_script_path); - $relative_script_path = defaults($_SERVER, 'REDIRECT_SCRIPT_URL', $relative_script_path); - $relative_script_path = defaults($_SERVER, 'SCRIPT_URL' , $relative_script_path); - $relative_script_path = defaults($_SERVER, 'REQUEST_URI' , $relative_script_path); -*/ - $this->urlPath = $this->config->get('system', 'urlpath'); - - /* $relative_script_path gives /relative/path/to/friendica/module/parameter - * QUERY_STRING gives pagename=module/parameter - * - * To get /relative/path/to/friendica we perform dirname() for as many levels as there are slashes in the QUERY_STRING - */ -/* - if (!empty($relative_script_path)) { - // Module - if (!empty($_SERVER['QUERY_STRING'])) { - $path = trim(rdirname($relative_script_path, substr_count(trim($_SERVER['QUERY_STRING'], '/'), '/') + 1), '/'); - } else { - // Root page - $path = trim($relative_script_path, '/'); - } - - if ($path && $path != $this->urlPath) { - $this->urlPath = $path; - } - } -*/ - } - public function getScheme() { - return $this->scheme; + return $this->baseURL->getScheme(); } /** - * @brief Retrieves the Friendica instance base URL + * Retrieves the Friendica instance base URL * - * This function assembles the base URL from multiple parts: - * - Protocol is determined either by the request or a combination of - * system.ssl_policy and the $ssl parameter. - * - Host name is determined either by system.hostname or inferred from request - * - Path is inferred from SCRIPT_NAME + * @param bool $ssl Whether to append http or https under BaseURL::SSL_POLICY_SELFSIGN * - * Note: $ssl parameter value doesn't directly correlate with the resulting protocol - * - * @param bool $ssl Whether to append http or https under SSL_POLICY_SELFSIGN * @return string Friendica server base URL - * @throws InternalServerErrorException + * + * @deprecated 2019.06 - use BaseURL->get($ssl) instead */ public function getBaseURL($ssl = false) { - $scheme = $this->scheme; - - if ($this->config->get('system', 'ssl_policy') == SSL_POLICY_FULL) { - $scheme = 'https'; - } - - // Basically, we have $ssl = true on any links which can only be seen by a logged in user - // (and also the login link). Anything seen by an outsider will have it turned off. - - if ($this->config->get('system', 'ssl_policy') == SSL_POLICY_SELFSIGN) { - if ($ssl) { - $scheme = 'https'; - } else { - $scheme = 'http'; - } - } - - if ($this->config->get('config', 'hostname') != '') { - $this->hostname = $this->config->get('config', 'hostname'); - } - - return $scheme . '://' . $this->hostname . (!empty($this->getURLPath()) ? '/' . $this->getURLPath() : '' ); + return $this->baseURL->get($ssl); } /** @@ -497,55 +411,36 @@ class App * Clears the baseurl cache to prevent inconsistencies * * @param string $url - * @throws InternalServerErrorException + * + * @deprecated 2019.06 - use BaseURL->saveByURL($url) instead */ public function setBaseURL($url) { - $parsed = @parse_url($url); - $hostname = ''; - - if (!empty($parsed)) { - if (!empty($parsed['scheme'])) { - $this->scheme = $parsed['scheme']; - } - - if (!empty($parsed['host'])) { - $hostname = $parsed['host']; - } - - if (!empty($parsed['port'])) { - $hostname .= ':' . $parsed['port']; - } - if (!empty($parsed['path'])) { - $this->urlPath = trim($parsed['path'], '\\/'); - } - - if (file_exists($this->getBasePath() . '/.htpreconfig.php')) { - include $this->getBasePath() . '/.htpreconfig.php'; - } - - if ($this->config->get('config', 'hostname') != '') { - $this->hostname = $this->config->get('config', 'hostname'); - } - - if (!isset($this->hostname) || ($this->hostname == '')) { - $this->hostname = $hostname; - } - } + $this->baseURL->saveByURL($url); } + /** + * Returns the current hostname + * + * @return string + * + * @deprecated 2019.06 - use BaseURL->getHostname() instead + */ public function getHostName() { - if ($this->config->get('config', 'hostname') != '') { - $this->hostname = $this->config->get('config', 'hostname'); - } - - return $this->hostname; + return $this->baseURL->getHostname(); } + /** + * Returns the sub-path of the full URL + * + * @return string + * + * @deprecated 2019.06 - use BaseURL->getUrlPath() instead + */ public function getURLPath() { - return $this->urlPath; + return $this->baseURL->getUrlPath(); } /** @@ -1120,7 +1015,7 @@ class App if (!$this->getMode()->isInstall()) { // Force SSL redirection if ($this->config->get('system', 'force_ssl') && ($this->getScheme() == "http") - && intval($this->config->get('system', 'ssl_policy')) == SSL_POLICY_FULL + && intval($this->config->get('system', 'ssl_policy')) == BaseURL::SSL_POLICY_FULL && strpos($this->getBaseURL(), 'https://') === 0 && $_SERVER['REQUEST_METHOD'] == 'GET') { header('HTTP/1.1 302 Moved Temporarily'); @@ -1458,7 +1353,7 @@ class App header("X-Friendica-Version: " . FRIENDICA_VERSION); header("Content-type: text/html; charset=utf-8"); - if ($this->config->get('system', 'hsts') && ($this->config->get('system', 'ssl_policy') == SSL_POLICY_FULL)) { + if ($this->config->get('system', 'hsts') && ($this->config->get('system', 'ssl_policy') == BaseUrl::SSL_POLICY_FULL)) { header("Strict-Transport-Security: max-age=31536000"); } diff --git a/src/Core/Authentication.php b/src/Core/Authentication.php index 106ba2a602..1963d34b4e 100644 --- a/src/Core/Authentication.php +++ b/src/Core/Authentication.php @@ -8,6 +8,7 @@ namespace Friendica\Core; use Friendica\BaseObject; use Friendica\Database\DBA; use Friendica\Model\User; +use Friendica\Util\BaseURL; use Friendica\Util\DateTimeFormat; /** @@ -51,7 +52,7 @@ class Authentication extends BaseObject $value = ""; } - setcookie("Friendica", $value, $time, "/", "", (Config::get('system', 'ssl_policy') == SSL_POLICY_FULL), true); + setcookie("Friendica", $value, $time, "/", "", (Config::get('system', 'ssl_policy') == BaseUrl::SSL_POLICY_FULL), true); } /** diff --git a/src/Core/Session.php b/src/Core/Session.php index d9143c8809..f1da864bb1 100644 --- a/src/Core/Session.php +++ b/src/Core/Session.php @@ -7,6 +7,7 @@ namespace Friendica\Core; use Friendica\Core\Session\CacheSessionHandler; use Friendica\Core\Session\DatabaseSessionHandler; +use Friendica\Util\BaseURL; /** * High-level Session service class @@ -24,7 +25,7 @@ class Session ini_set('session.use_only_cookies', 1); ini_set('session.cookie_httponly', 1); - if (Config::get('system', 'ssl_policy') == SSL_POLICY_FULL) { + if (Config::get('system', 'ssl_policy') == BaseUrl::SSL_POLICY_FULL) { ini_set('session.cookie_secure', 1); } diff --git a/src/Core/System.php b/src/Core/System.php index 45a88fe093..83c3dc9081 100644 --- a/src/Core/System.php +++ b/src/Core/System.php @@ -23,7 +23,7 @@ class System extends BaseObject /** * @brief Retrieves the Friendica instance base URL * - * @param bool $ssl Whether to append http or https under SSL_POLICY_SELFSIGN + * @param bool $ssl Whether to append http or https under BaseURL::SSL_POLICY_SELFSIGN * @return string Friendica server base URL * @throws InternalServerErrorException */ diff --git a/src/Core/Update.php b/src/Core/Update.php index d2bbc237c7..328938fe1c 100644 --- a/src/Core/Update.php +++ b/src/Core/Update.php @@ -3,12 +3,8 @@ namespace Friendica\Core; use Friendica\App; -use Friendica\Core\Config\Cache\IConfigCache; use Friendica\Database\DBA; use Friendica\Database\DBStructure; -use Friendica\Util\BasePath; -use Friendica\Util\Config\ConfigFileLoader; -use Friendica\Util\Config\ConfigFileSaver; use Friendica\Util\Strings; class Update @@ -32,7 +28,7 @@ class Update } // Check if the config files are set correctly - self::checkConfigFile($basePath, $mode); + self::checkBaseSettings($_SERVER); // Don't check the status if the last update was failed if (Config::get('system', 'update', Update::SUCCESS, true) == Update::FAILED) { @@ -229,142 +225,9 @@ class Update } } - /** - * Checks the config settings and saves given config values into the config file - * - * @param string $basePath The basepath of Friendica - * @param App\Mode $mode The current App mode - * - * @return bool True, if something has been saved - */ - public static function checkConfigFile($basePath, App\Mode $mode) - { - if (empty($basePath)) { - $basePath = BasePath::create(dirname(__DIR__, 2)); - } - - $config = [ - 'config' => [ - 'hostname' => [ - 'allowEmpty' => false, - 'default' => '', - ], - ], - 'system' => [ - 'basepath' => [ - 'allowEmpty' => false, - 'default' => $basePath, - ], - ] - ]; - - $configFileLoader = new ConfigFileLoader($basePath, $mode); - $configCache = new Config\Cache\ConfigCache(); - $configFileLoader->setupCache($configCache, true); - - // checks if something is to update, otherwise skip this function at all - $missingConfig = $configCache->keyDiff($config); - if (empty($missingConfig)) { - return true; - } - - // We just want one update process - if (Lock::acquire('config_update')) { - $configFileSaver = new ConfigFileSaver($basePath); - - $updated = false; - $toDelete = []; - - foreach ($missingConfig as $category => $keys) { - foreach ($keys as $key => $value) { - if (self::updateConfigEntry($configCache, $configFileSaver, $category, $key, $value['allowEmpty'], $value['default'])) { - $toDelete[] = ['cat' => $category, 'key' => $key]; - $updated = true; - }; - } - } - - // In case there is nothing to do, skip the update - if (!$updated) { - Lock::release('config_update'); - return true; - } - - if (!$configFileSaver->saveToConfigFile()) { - Logger::alert('Config entry update failed - maybe wrong permission?'); - Lock::release('config_update'); - return false; - } - - // After the successful save, remove the db values - foreach ($toDelete as $delete) { - DBA::delete('config', ['cat' => $delete['cat'], 'k' => $delete['key']]); - } - - Lock::release('config_update'); - } - - return true; - } - - /** - * Adds a value to the ConfigFileSave in case it isn't already updated - * - * @param IConfigCache $configCache The cached config file - * @param ConfigFileSaver $configFileSaver The config file saver - * @param string $cat The config category - * @param string $key The config key - * @param bool $allowEmpty If true, empty values are valid (Default there has to be a variable) - * @param string $default A default value, if none of the settings are valid - * - * @return boolean True, if a value was updated - * - * @throws \Exception if DBA or Logger doesn't work - */ - private static function updateConfigEntry( - IConfigCache $configCache, - ConfigFileSaver $configFileSaver, - $cat, - $key, - $allowEmpty = false, - $default = '') + public static function checkBaseSettings(array $server) { - // check if the config file differs from the whole configuration (= The db contains other values) - $fileValue = $configCache->get($cat, $key); - $dbConfig = DBA::selectFirst('config', ['v'], ['cat' => $cat, 'k' => $key]); - - if (DBA::isResult($dbConfig)) { - $dbValue = $dbConfig['v']; - } else { - $dbValue = null; - } - - // If the db contains a config value, check it - if (( - ($allowEmpty && isset($dbValue)) || - (!$allowEmpty && !empty($dbValue)) - ) && - $fileValue !== $dbValue) { - Logger::info('Difference in config found', ['cat' => $cat, 'key' => $key, 'file' => $fileValue, 'db' => $dbValue]); - $configFileSaver->addConfigValue($cat, $key, $dbValue); - return true; - - // If both config values are not set, use the default value - } elseif ( - ($allowEmpty && !isset($fileValue) && !isset($dbValue)) || - (!$allowEmpty && empty($fileValue) && empty($dbValue) && !empty($default))) { - - Logger::info('Using default for config', ['cat' => $cat, 'key' => $key, 'value' => $default]); - $configFileSaver->addConfigValue($cat, $key, $default); - return true; - - // If either the file config value isn't empty or the db value is the same as the - // file config value, skip it - } else { - Logger::debug('No Difference in config found', ['cat' => $cat, 'key' => $key, 'value' => $fileValue, 'db' => $dbValue]); - return false; - } } /** diff --git a/src/Factory/DependencyFactory.php b/src/Factory/DependencyFactory.php index 0f33e095bc..e55a52d14e 100644 --- a/src/Factory/DependencyFactory.php +++ b/src/Factory/DependencyFactory.php @@ -5,6 +5,7 @@ namespace Friendica\Factory; use Friendica\App; use Friendica\Factory; use Friendica\Util\BasePath; +use Friendica\Util\BaseURL; use Friendica\Util\Config; class DependencyFactory @@ -34,7 +35,8 @@ class DependencyFactory Factory\ConfigFactory::createPConfig($configCache); $logger = Factory\LoggerFactory::create($channel, $config, $profiler); Factory\LoggerFactory::createDev($channel, $config, $profiler); + $baseURL = new BaseURL($config, $_SERVER); - return new App($config, $mode, $router, $logger, $profiler, $isBackend); + return new App($config, $mode, $router, $baseURL, $logger, $profiler, $isBackend); } } diff --git a/src/Model/Contact.php b/src/Model/Contact.php index fe373ae94a..b19783427a 100644 --- a/src/Model/Contact.php +++ b/src/Model/Contact.php @@ -22,6 +22,7 @@ use Friendica\Protocol\Diaspora; use Friendica\Protocol\OStatus; use Friendica\Protocol\PortableContact; use Friendica\Protocol\Salmon; +use Friendica\Util\BaseURL; use Friendica\Util\DateTimeFormat; use Friendica\Util\Network; use Friendica\Util\Strings; @@ -1938,7 +1939,7 @@ class Contact extends BaseObject public static function updateSslPolicy(array $contact, $new_policy) { $ssl_changed = false; - if ((intval($new_policy) == SSL_POLICY_SELFSIGN || $new_policy === 'self') && strstr($contact['url'], 'https:')) { + if ((intval($new_policy) == BaseURL::SSL_POLICY_SELFSIGN || $new_policy === 'self') && strstr($contact['url'], 'https:')) { $ssl_changed = true; $contact['url'] = str_replace('https:', 'http:', $contact['url']); $contact['request'] = str_replace('https:', 'http:', $contact['request']); @@ -1948,7 +1949,7 @@ class Contact extends BaseObject $contact['poco'] = str_replace('https:', 'http:', $contact['poco']); } - if ((intval($new_policy) == SSL_POLICY_FULL || $new_policy === 'full') && strstr($contact['url'], 'http:')) { + if ((intval($new_policy) == BaseURL::SSL_POLICY_FULL || $new_policy === 'full') && strstr($contact['url'], 'http:')) { $ssl_changed = true; $contact['url'] = str_replace('http:', 'https:', $contact['url']); $contact['request'] = str_replace('http:', 'https:', $contact['request']); diff --git a/src/Network/Probe.php b/src/Network/Probe.php index 008106ec3f..b0d2630103 100644 --- a/src/Network/Probe.php +++ b/src/Network/Probe.php @@ -10,6 +10,7 @@ namespace Friendica\Network; */ use DOMDocument; +use DomXPath; use Friendica\Core\Cache; use Friendica\Core\Config; use Friendica\Core\Logger; @@ -18,15 +19,14 @@ use Friendica\Core\System; use Friendica\Database\DBA; use Friendica\Model\Contact; use Friendica\Model\Profile; +use Friendica\Protocol\ActivityPub; use Friendica\Protocol\Email; use Friendica\Protocol\Feed; -use Friendica\Protocol\ActivityPub; use Friendica\Util\Crypto; use Friendica\Util\DateTimeFormat; use Friendica\Util\Network; use Friendica\Util\Strings; use Friendica\Util\XML; -use DomXPath; /** * @brief This class contain functions for probing URL @@ -510,30 +510,6 @@ class Probe return $data; } - /** - * @brief Switch the scheme of an url between http and https - * - * @param string $url URL - * - * @return string switched URL - */ - private static function switchScheme($url) - { - $parts = parse_url($url); - - if (!isset($parts['scheme'])) { - return $url; - } - - if ($parts['scheme'] == 'http') { - $url = str_replace('http://', 'https://', $url); - } elseif ($parts['scheme'] == 'https') { - $url = str_replace('https://', 'http://', $url); - } - - return $url; - } - /** * @brief Checks if a profile url should be OStatus but only provides partial information * @@ -566,7 +542,7 @@ class Probe return $webfinger; } - $url = self::switchScheme($webfinger['subject']); + $url = Network::switchScheme($webfinger['subject']); $path = str_replace('{uri}', urlencode($url), $lrdd); $webfinger2 = self::webfinger($path, $type); diff --git a/src/Protocol/DFRN.php b/src/Protocol/DFRN.php index 8433801812..b6252655ac 100644 --- a/src/Protocol/DFRN.php +++ b/src/Protocol/DFRN.php @@ -29,6 +29,7 @@ use Friendica\Model\PermissionSet; use Friendica\Model\Profile; use Friendica\Model\User; use Friendica\Object\Image; +use Friendica\Util\BaseURL; use Friendica\Util\Crypto; use Friendica\Util\DateTimeFormat; use Friendica\Util\Network; @@ -1208,13 +1209,13 @@ class DFRN $ssl_val = intval(Config::get('system', 'ssl_policy')); switch ($ssl_val) { - case SSL_POLICY_FULL: + case BaseURL::SSL_POLICY_FULL: $ssl_policy = 'full'; break; - case SSL_POLICY_SELFSIGN: + case BaseURL::SSL_POLICY_SELFSIGN: $ssl_policy = 'self'; break; - case SSL_POLICY_NONE: + case BaseURL::SSL_POLICY_NONE: default: $ssl_policy = 'none'; break; diff --git a/src/Util/BaseURL.php b/src/Util/BaseURL.php new file mode 100644 index 0000000000..a9bed2b5e8 --- /dev/null +++ b/src/Util/BaseURL.php @@ -0,0 +1,344 @@ +hostname; + } + + /** + * Returns the current scheme of this call + * @return string + */ + public function getScheme() + { + return $this->scheme; + } + + /** + * Returns the SSL policy of this node + * @return int + */ + public function getSSLPolicy() + { + return $this->sslPolicy; + } + + /** + * Returns the sub-path of this URL + * @return string + */ + public function getUrlPath() + { + return $this->urlPath; + } + + /** + * Returns the full URL of this call + * + * Note: $ssl parameter value doesn't directly correlate with the resulting protocol + * + * @param bool $ssl True, if ssl should get used + * + * @return string + */ + public function get($ssl = false) + { + return (!$ssl ? $this->url : $this->returnBaseURL($ssl)); + } + + /** + * Save current parts of the base Url + * + * @param string? $hostname + * @param int? $sslPolicy + * @param string? $urlPath + * + * @return bool true, if successful + */ + public function save($hostname = null, $sslPolicy = null, $urlPath = null) + { + $success = true; + + if (!empty($hostname)) { + $this->hostname = $hostname; + if (!$this->config->set('config', 'hostname', $this->hostname)) { + $success = false; + } + } + + if (isset($sslPolicy)) { + $this->sslPolicy = $sslPolicy; + if (!$this->config->set('system', 'ssl_policy', $this->sslPolicy)) { + $success = false; + } + } + + if (isset($urlPath)) { + $this->urlPath = $urlPath; + if (!$this->config->set('system', 'urlpath', $this->urlPath)) { + $success = false; + } + } + + $this->determineBaseUrl(); + if (!$this->config->set('system', 'url', $this->url)) { + $success = false; + } + + return $success; + } + + /** + * Save the current url as base URL + * + * @param $url + * + * @return bool true, if the save was successful + */ + public function saveByURL($url) + { + $parsed = @parse_url($url); + + if (empty($parsed)) { + return false; + } + + $hostname = $parsed['host']; + if (!empty($hostname) && !empty($parsed['port'])) { + $hostname .= ':' . $parsed['port']; + } + + $urlPath = null; + if (!empty($parsed['path'])) { + $urlPath = trim($parsed['path'], '\\/'); + } + + return $this->save($hostname, null, $urlPath); + } + + /** + * @param Configuration $config The Friendica configuration + * @param array $server The $_SERVER array + */ + public function __construct(Configuration $config, array $server) + { + $this->config = $config; + $this->server = $server; + + $this->checkConfig(); + $this->determineSchema(); + } + + /** + * Check the current config during loading + */ + public function checkConfig() + { + $this->hostname = $this->config->get('config', 'hostname', null); + $this->urlPath = $this->config->get('system', 'urlpath', null); + $this->sslPolicy = $this->config->get('system', 'ssl_policy', null); + $this->url = $this->config->get('system', 'url', null); + + if (empty($this->hostname)) { + $this->determineHostname(); + + if (!empty($this->hostname)) { + $this->config->set('config', 'hostname', $this->hostname); + } + } + + if (!isset($this->urlPath)) { + $this->determineURLPath(); + $this->config->set('system', 'urlpath', $this->urlPath); + } + + if (!isset($this->sslPolicy)) { + $this->sslPolicy = self::SSL_POLICY_NONE; + $this->config->set('system', 'ssl_policy', $this->sslPolicy); + } + + if (empty($this->url)) { + $this->determineBaseUrl(); + + if (!empty($url)) { + $this->config->set('system', 'url', $this->url); + } + } + } + + /** + * Determines the hostname of this node if not set already + */ + private function determineHostname() + { + $this->hostname = ''; + + if (!empty($this->server['SERVER_NAME'])) { + $this->hostname = $this->server['SERVER_NAME']; + + if (!empty($this->server['SERVER_PORT']) && $this->server['SERVER_PORT'] != 80 && $this->server['SERVER_PORT'] != 443) { + $this->hostname .= ':' . $this->server['SERVER_PORT']; + } + } + } + + /** + * Figure out if we are running at the top of a domain or in a sub-directory + */ + private function determineURLPath() + { + $this->urlPath = ''; + + /* + * The automatic path detection in this function is currently deactivated, + * see issue https://github.com/friendica/friendica/issues/6679 + * + * The problem is that the function seems to be confused with some url. + * These then confuses the detection which changes the url path. + */ + + /* Relative script path to the web server root + * Not all of those $_SERVER properties can be present, so we do by inverse priority order + */ + $relative_script_path = ''; + $relative_script_path = defaults($this->server, 'REDIRECT_URL', $relative_script_path); + $relative_script_path = defaults($this->server, 'REDIRECT_URI', $relative_script_path); + $relative_script_path = defaults($this->server, 'REDIRECT_SCRIPT_URL', $relative_script_path); + $relative_script_path = defaults($this->server, 'SCRIPT_URL', $relative_script_path); + $relative_script_path = defaults($this->server, 'REQUEST_URI', $relative_script_path); + + /* $relative_script_path gives /relative/path/to/friendica/module/parameter + * QUERY_STRING gives pagename=module/parameter + * + * To get /relative/path/to/friendica we perform dirname() for as many levels as there are slashes in the QUERY_STRING + */ + if (!empty($relative_script_path)) { + // Module + if (!empty($this->server['QUERY_STRING'])) { + $this->urlPath = trim(rdirname($relative_script_path, substr_count(trim($this->server['QUERY_STRING'], '/'), '/') + 1), '/'); + } else { + // Root page + $this->urlPath = trim($relative_script_path, '/'); + } + } + } + + /** + * Determine the full URL based on all parts + */ + private function determineBaseUrl() + { + $scheme = 'http'; + + if ($this->sslPolicy == self::SSL_POLICY_FULL) { + $scheme = 'https'; + } + + $this->url = $scheme . '://' . $this->hostname . (!empty($this->urlPath) ? '/' . $this->urlPath : '' ); + } + + /** + * Determine the scheme of the current used link + */ + private function determineSchema() + { + $this->scheme = 'http'; + + if (!empty($this->server['HTTPS']) || + !empty($this->server['HTTP_FORWARDED']) && preg_match('/proto=https/', $this->server['HTTP_FORWARDED']) || + !empty($this->server['HTTP_X_FORWARDED_PROTO']) && $this->server['HTTP_X_FORWARDED_PROTO'] == 'https' || + !empty($this->server['HTTP_X_FORWARDED_SSL']) && $this->server['HTTP_X_FORWARDED_SSL'] == 'on' || + !empty($this->server['FRONT_END_HTTPS']) && $this->server['FRONT_END_HTTPS'] == 'on' || + !empty($this->server['SERVER_PORT']) && (intval($this->server['SERVER_PORT']) == 443) // XXX: reasonable assumption, but isn't this hardcoding too much? + ) { + $this->scheme = 'https'; + } + } + + /** + * Returns the URL based on the current used ssl setting + * + * @param bool $ssl true, if ssl should be used + * + * @return string + */ + private function returnBaseURL($ssl) + { + if ($this->sslPolicy == self::SSL_POLICY_SELFSIGN && $ssl) { + return Network::switchScheme($this->url); + } + + return $this->url; + } +} diff --git a/src/Util/Config/ConfigFileSaver.php b/src/Util/Config/ConfigFileSaver.php deleted file mode 100644 index e32e11a4d5..0000000000 --- a/src/Util/Config/ConfigFileSaver.php +++ /dev/null @@ -1,341 +0,0 @@ -settings)); - - for ($i = 0; $i < $settingsCount; $i++) { - // if already set, overwrite the value - if ($this->settings[$i]['cat'] === $cat && - $this->settings[$i]['key'] === $key) { - $this->settings[$i] = ['cat' => $cat, 'key' => $key, 'value' => $value]; - return; - } - } - - $this->settings[] = ['cat' => $cat, 'key' => $key, 'value' => $value]; - } - - /** - * Resetting all added configuration entries so far - */ - public function reset() - { - $this->settings = []; - } - - /** - * Save all added configuration entries to the given config files - * After updating the config entries, all configuration entries will be reseted - * - * @param string $name The name of the configuration file (default is empty, which means the default name each type) - * - * @return bool true, if at least one configuration file was successfully updated or nothing to do - */ - public function saveToConfigFile($name = '') - { - // If no settings et, return true - if (count(array_keys($this->settings)) === 0) { - return true; - } - - $saved = false; - - // Check for the *.config.php file inside the /config/ path - list($reading, $writing) = $this->openFile($this->getConfigFullName($name)); - if (isset($reading) && isset($writing)) { - $this->saveConfigFile($reading, $writing); - // Close the current file handler and rename them - if ($this->closeFile($this->getConfigFullName($name), $reading, $writing)) { - // just return true, if everything went fine - $saved = true; - } - } - - // Check for the *.ini.php file inside the /config/ path - list($reading, $writing) = $this->openFile($this->getIniFullName($name)); - if (isset($reading) && isset($writing)) { - $this->saveINIConfigFile($reading, $writing); - // Close the current file handler and rename them - if ($this->closeFile($this->getIniFullName($name), $reading, $writing)) { - // just return true, if everything went fine - $saved = true; - } - } - - // Check for the *.php file (normally .htconfig.php) inside the / path - list($reading, $writing) = $this->openFile($this->getHtConfigFullName($name)); - if (isset($reading) && isset($writing)) { - $this->saveToLegacyConfig($reading, $writing); - // Close the current file handler and rename them - if ($this->closeFile($this->getHtConfigFullName($name), $reading, $writing)) { - // just return true, if everything went fine - $saved = true; - } - } - - $this->reset(); - - return $saved; - } - - /** - * Opens a config file and returns two handler for reading and writing - * - * @param string $fullName The full name of the current config - * - * @return array An array containing the two reading and writing handler - */ - private function openFile($fullName) - { - if (empty($fullName)) { - return [null, null]; - } - - try { - $reading = fopen($fullName, 'r'); - } catch (\Exception $exception) { - return [null, null]; - } - - if (!$reading) { - return [null, null]; - } - - try { - $writing = fopen($fullName . '.tmp', 'w'); - } catch (\Exception $exception) { - fclose($reading); - return [null, null]; - } - - if (!$writing) { - fclose($reading); - return [null, null]; - } - - return [$reading, $writing]; - } - - /** - * Close and rename the config file - * - * @param string $fullName The full name of the current config - * @param resource $reading The reading resource handler - * @param resource $writing The writing resource handler - * - * @return bool True, if the close was successful - */ - private function closeFile($fullName, $reading, $writing) - { - fclose($reading); - fclose($writing); - - try { - $renamed = rename($fullName, $fullName . '.old'); - } catch (\Exception $exception) { - return false; - } - - if (!$renamed) { - return false; - } - - try { - $renamed = rename($fullName . '.tmp', $fullName); - } catch (\Exception $exception) { - // revert the move of the current config file to have at least the old config - rename($fullName . '.old', $fullName); - return false; - } - - if (!$renamed) { - // revert the move of the current config file to have at least the old config - rename($fullName . '.old', $fullName); - return false; - } - - return true; - } - - /** - * Saves all configuration values to a config file - * - * @param resource $reading The reading handler - * @param resource $writing The writing handler - */ - private function saveConfigFile($reading, $writing) - { - $settingsCount = count(array_keys($this->settings)); - $categoryFound = array_fill(0, $settingsCount, false); - $categoryBracketFound = array_fill(0, $settingsCount, false);; - $lineFound = array_fill(0, $settingsCount, false);; - $lineArrowFound = array_fill(0, $settingsCount, false);; - - while (!feof($reading)) { - - $line = fgets($reading); - - // check for each added setting if we have to replace a config line - for ($i = 0; $i < $settingsCount; $i++) { - - // find the first line like "'system' =>" - if (!$categoryFound[$i] && stristr($line, sprintf('\'%s\'', $this->settings[$i]['cat']))) { - $categoryFound[$i] = true; - } - - // find the first line with a starting bracket ( "[" ) - if ($categoryFound[$i] && !$categoryBracketFound[$i] && stristr($line, '[')) { - $categoryBracketFound[$i] = true; - } - - // find the first line with the key like "'value'" - if ($categoryBracketFound[$i] && !$lineFound[$i] && stristr($line, sprintf('\'%s\'', $this->settings[$i]['key']))) { - $lineFound[$i] = true; - } - - // find the first line with an arrow ("=>") after finding the key - if ($lineFound[$i] && !$lineArrowFound[$i] && stristr($line, '=>')) { - $lineArrowFound[$i] = true; - } - - // find the current value and replace it - if ($lineArrowFound[$i] && preg_match_all('/\'(.*?)\'/', $line, $matches, PREG_SET_ORDER)) { - $lineVal = end($matches)[0]; - $line = str_replace($lineVal, '\'' . $this->settings[$i]['value'] . '\'', $line); - $categoryFound[$i] = false; - $categoryBracketFound[$i] = false; - $lineFound[$i] = false; - $lineArrowFound[$i] = false; - // if a line contains a closing bracket for the category ( "]" ) and we didn't find the key/value pair, - // add it as a new line before the closing bracket - } elseif ($categoryBracketFound[$i] && !$lineArrowFound[$i] && stristr($line, ']')) { - $categoryFound[$i] = false; - $categoryBracketFound[$i] = false; - $lineFound[$i] = false; - $lineArrowFound[$i] = false; - $newLine = sprintf(self::INDENT . self::INDENT . '\'%s\' => \'%s\',' . PHP_EOL, $this->settings[$i]['key'], $this->settings[$i]['value']); - $line = $newLine . $line; - } - } - - fputs($writing, $line); - } - } - - /** - * Saves a value to a ini file - * - * @param resource $reading The reading handler - * @param resource $writing The writing handler - */ - private function saveINIConfigFile($reading, $writing) - { - $settingsCount = count(array_keys($this->settings)); - $categoryFound = array_fill(0, $settingsCount, false); - - while (!feof($reading)) { - - $line = fgets($reading); - - // check for each added setting if we have to replace a config line - for ($i = 0; $i < $settingsCount; $i++) { - - // find the category of the current setting - if (!$categoryFound[$i] && stristr($line, sprintf('[%s]', $this->settings[$i]['cat']))) { - $categoryFound[$i] = true; - - // check the current value - } elseif ($categoryFound[$i] && preg_match_all('/^' . $this->settings[$i]['key'] . '\s*=\s*(.*?)$/', $line, $matches, PREG_SET_ORDER)) { - $line = $this->settings[$i]['key'] . ' = ' . $this->settings[$i]['value'] . PHP_EOL; - $categoryFound[$i] = false; - - // If end of INI file, add the line before the INI end - } elseif ($categoryFound[$i] && (preg_match_all('/^\[.*?\]$/', $line) || preg_match_all('/^INI;.*$/', $line))) { - $categoryFound[$i] = false; - $newLine = $this->settings[$i]['key'] . ' = ' . $this->settings[$i]['value'] . PHP_EOL; - $line = $newLine . $line; - } - } - - fputs($writing, $line); - } - } - - /** - * Saves a value to a .php file (normally .htconfig.php) - * - * @param resource $reading The reading handler - * @param resource $writing The writing handler - */ - private function saveToLegacyConfig($reading, $writing) - { - $settingsCount = count(array_keys($this->settings)); - $found = array_fill(0, $settingsCount, false); - while (!feof($reading)) { - - $line = fgets($reading); - - // check for each added setting if we have to replace a config line - for ($i = 0; $i < $settingsCount; $i++) { - - // check for a non plain config setting (use category too) - if ($this->settings[$i]['cat'] !== 'config' && preg_match_all('/^\$a\-\>config\[\'' . $this->settings[$i]['cat'] . '\'\]\[\'' . $this->settings[$i]['key'] . '\'\]\s*=\s\'*(.*?)\';$/', $line, $matches, PREG_SET_ORDER)) { - $line = '$a->config[\'' . $this->settings[$i]['cat'] . '\'][\'' . $this->settings[$i]['key'] . '\'] = \'' . $this->settings[$i]['value'] . '\';' . PHP_EOL; - $found[$i] = true; - - // check for a plain config setting (don't use a category) - } elseif ($this->settings[$i]['cat'] === 'config' && preg_match_all('/^\$a\-\>config\[\'' . $this->settings[$i]['key'] . '\'\]\s*=\s\'*(.*?)\';$/', $line, $matches, PREG_SET_ORDER)) { - $line = '$a->config[\'' . $this->settings[$i]['key'] . '\'] = \'' . $this->settings[$i]['value'] . '\';' . PHP_EOL; - $found[$i] = true; - } - } - - fputs($writing, $line); - } - - for ($i = 0; $i < $settingsCount; $i++) { - if (!$found[$i]) { - if ($this->settings[$i]['cat'] !== 'config') { - $line = '$a->config[\'' . $this->settings[$i]['cat'] . '\'][\'' . $this->settings[$i]['key'] . '\'] = \'' . $this->settings[$i]['value'] . '\';' . PHP_EOL; - } else { - $line = '$a->config[\'' . $this->settings[$i]['key'] . '\'] = \'' . $this->settings[$i]['value'] . '\';' . PHP_EOL; - } - - fputs($writing, $line); - } - } - } -} diff --git a/src/Util/Network.php b/src/Util/Network.php index 35744647e3..b534a5505b 100644 --- a/src/Util/Network.php +++ b/src/Util/Network.php @@ -835,4 +835,26 @@ class Network (strlen($query) ? "?".$query : '') . (strlen($fragment) ? "#".$fragment : ''); } + + + /** + * @brief Switch the scheme of an url between http and https + * + * @param string $url URL + * + * @return string switched URL + */ + public static function switchScheme($url) + { + $parts = parse_url($url, PHP_URL_SCHEME); + if (!isset($parts['scheme'])) { + return $url; + } + if ($parts['scheme'] == 'http') { + $url = str_replace('http://', 'https://', $url); + } elseif ($parts['scheme'] == 'https') { + $url = str_replace('https://', 'http://', $url); + } + return $url; + } } diff --git a/tests/src/Util/Config/ConfigFileSaverTest.php b/tests/src/Util/Config/ConfigFileSaverTest.php deleted file mode 100644 index 04adf6014a..0000000000 --- a/tests/src/Util/Config/ConfigFileSaverTest.php +++ /dev/null @@ -1,189 +0,0 @@ -setUpVfsDir(); - $this->mode = \Mockery::mock(App\Mode::class); - $this->mode->shouldReceive('isInstall')->andReturn(true); - } - - public function dataConfigFiles() - { - return [ - 'config' => [ - 'fileName' => 'local.config.php', - 'filePath' => dirname(__DIR__) . DIRECTORY_SEPARATOR . - '..' . DIRECTORY_SEPARATOR . - '..' . DIRECTORY_SEPARATOR . - 'datasets' . DIRECTORY_SEPARATOR . - 'config', - 'relativePath' => 'config', - ], - 'ini' => [ - 'fileName' => 'local.ini.php', - 'filePath' => dirname(__DIR__) . DIRECTORY_SEPARATOR . - '..' . DIRECTORY_SEPARATOR . - '..' . DIRECTORY_SEPARATOR . - 'datasets' . DIRECTORY_SEPARATOR . - 'config', - 'relativePath' => 'config', - ], - 'htconfig' => [ - 'fileName' => '.htconfig.php', - 'filePath' => dirname(__DIR__) . DIRECTORY_SEPARATOR . - '..' . DIRECTORY_SEPARATOR . - '..' . DIRECTORY_SEPARATOR . - 'datasets' . DIRECTORY_SEPARATOR . - 'config', - 'relativePath' => '', - ], - ]; - } - - /** - * Test the saveToConfigFile() method - * @dataProvider dataConfigFiles - * - * @todo 20190324 [nupplaphil] for ini-configs, it isn't possible to use $ or ! inside values - */ - public function testSaveToConfig($fileName, $filePath, $relativePath) - { - $this->delConfigFile('local.config.php'); - - if (empty($relativePath)) { - $root = $this->root; - $relativeFullName = $fileName; - } else { - $root = $this->root->getChild($relativePath); - $relativeFullName = $relativePath . DIRECTORY_SEPARATOR . $fileName; - } - - vfsStream::newFile($fileName) - ->at($root) - ->setContent(file_get_contents($filePath . DIRECTORY_SEPARATOR . $fileName)); - - $configFileSaver = new ConfigFileSaver($this->root->url()); - $configFileLoader = new ConfigFileLoader($this->root->url(), $this->mode); - $configCache = new ConfigCache(); - $configFileLoader->setupCache($configCache); - - $this->assertEquals('admin@test.it', $configCache->get('config', 'admin_email')); - $this->assertEquals('frio', $configCache->get('system', 'theme')); - $this->assertNull($configCache->get('config', 'test_val')); - $this->assertNull($configCache->get('system', 'test_val2')); - - // update values (system and config value) - $configFileSaver->addConfigValue('config', 'admin_email', 'new@mail.it'); - $configFileSaver->addConfigValue('system', 'theme', 'vier'); - - // insert values (system and config value) - $configFileSaver->addConfigValue('config', 'test_val', 'Testingwith@all.we can'); - $configFileSaver->addConfigValue('system', 'test_val2', 'TestIt First'); - - // overwrite value - $configFileSaver->addConfigValue('system', 'test_val2', 'TestIt Now'); - - // save it - $this->assertTrue($configFileSaver->saveToConfigFile()); - - $newConfigCache = new ConfigCache(); - $configFileLoader->setupCache($newConfigCache); - - $this->assertEquals('new@mail.it', $newConfigCache->get('config', 'admin_email')); - $this->assertEquals('Testingwith@all.we can', $newConfigCache->get('config', 'test_val')); - $this->assertEquals('vier', $newConfigCache->get('system', 'theme')); - $this->assertEquals('TestIt Now', $newConfigCache->get('system', 'test_val2')); - - $this->assertTrue($this->root->hasChild($relativeFullName)); - $this->assertTrue($this->root->hasChild($relativeFullName . '.old')); - $this->assertFalse($this->root->hasChild($relativeFullName . '.tmp')); - - $this->assertEquals(file_get_contents($filePath . DIRECTORY_SEPARATOR . $fileName), file_get_contents($this->root->getChild($relativeFullName . '.old')->url())); - } - - /** - * Test the saveToConfigFile() method without permissions - * @dataProvider dataConfigFiles - */ - public function testNoPermission($fileName, $filePath, $relativePath) - { - $this->delConfigFile('local.config.php'); - - if (empty($relativePath)) { - $root = $this->root; - $relativeFullName = $fileName; - } else { - $root = $this->root->getChild($relativePath); - $relativeFullName = $relativePath . DIRECTORY_SEPARATOR . $fileName; - } - - $root->chmod(000); - - vfsStream::newFile($fileName) - ->at($root) - ->setContent(file_get_contents($filePath . DIRECTORY_SEPARATOR . $fileName)); - - $configFileSaver = new ConfigFileSaver($this->root->url()); - - $configFileSaver->addConfigValue('system', 'test_val2', 'TestIt Now'); - - // wrong mod, so return false if nothing to write - $this->assertFalse($configFileSaver->saveToConfigFile()); - } - - /** - * Test the saveToConfigFile() method with nothing to do - * @dataProvider dataConfigFiles - */ - public function testNothingToDo($fileName, $filePath, $relativePath) - { - $this->delConfigFile('local.config.php'); - - if (empty($relativePath)) { - $root = $this->root; - $relativeFullName = $fileName; - } else { - $root = $this->root->getChild($relativePath); - $relativeFullName = $relativePath . DIRECTORY_SEPARATOR . $fileName; - } - - vfsStream::newFile($fileName) - ->at($root) - ->setContent(file_get_contents($filePath . DIRECTORY_SEPARATOR . $fileName)); - - $configFileSaver = new ConfigFileSaver($this->root->url()); - $configFileLoader = new ConfigFileLoader($this->root->url(), $this->mode); - $configCache = new ConfigCache(); - $configFileLoader->setupCache($configCache); - - // save nothing - $this->assertTrue($configFileSaver->saveToConfigFile()); - - $this->assertTrue($this->root->hasChild($relativeFullName)); - $this->assertFalse($this->root->hasChild($relativeFullName . '.old')); - $this->assertFalse($this->root->hasChild($relativeFullName . '.tmp')); - - $this->assertEquals(file_get_contents($filePath . DIRECTORY_SEPARATOR . $fileName), file_get_contents($this->root->getChild($relativeFullName)->url())); - } -} From edd4f06ad0084353ba81ba917464bb26e1368a34 Mon Sep 17 00:00:00 2001 From: Philipp Holzer Date: Mon, 8 Apr 2019 23:12:34 +0200 Subject: [PATCH 2/7] Adding tests --- src/App.php | 13 +- src/Util/BasePath.php | 2 +- src/Util/BaseURL.php | 69 ++-- src/Util/Network.php | 12 +- tests/include/ApiTest.php | 4 +- tests/src/Database/DBATest.php | 4 +- tests/src/Database/DBStructureTest.php | 4 +- tests/src/Util/BaseURLTest.php | 455 +++++++++++++++++++++++++ 8 files changed, 520 insertions(+), 43 deletions(-) create mode 100644 tests/src/Util/BaseURLTest.php diff --git a/src/App.php b/src/App.php index 3e1dc147f3..21675b9dc3 100644 --- a/src/App.php +++ b/src/App.php @@ -196,7 +196,6 @@ class App * @see initHead() * * @param string $path - * @throws InternalServerErrorException */ public function registerStylesheet($path) { @@ -215,7 +214,6 @@ class App * @see initFooter() * * @param string $path - * @throws InternalServerErrorException */ public function registerFooterScript($path) { @@ -835,7 +833,7 @@ class App { $sender_email = $this->config->get('config', 'sender_email'); if (empty($sender_email)) { - $hostname = $this->getHostName(); + $hostname = $this->baseURL->getHostname(); if (strpos($hostname, ':')) { $hostname = substr($hostname, 0, strpos($hostname, ':')); } @@ -980,7 +978,7 @@ class App // and www.example.com vs example.com. // We will only change the url to an ip address if there is no existing setting - if (empty($url) || (!Util\Strings::compareLink($url, $this->getBaseURL())) && (!preg_match("/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/", $this->getHostName()))) { + if (empty($url) || (!Util\Strings::compareLink($url, $this->getBaseURL())) && (!preg_match("/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/", $this->baseURL->getHostname()))) { $this->config->set('system', 'url', $this->getBaseURL()); } } @@ -1014,10 +1012,7 @@ class App if (!$this->getMode()->isInstall()) { // Force SSL redirection - if ($this->config->get('system', 'force_ssl') && ($this->getScheme() == "http") - && intval($this->config->get('system', 'ssl_policy')) == BaseURL::SSL_POLICY_FULL - && strpos($this->getBaseURL(), 'https://') === 0 - && $_SERVER['REQUEST_METHOD'] == 'GET') { + if ($this->baseURL->checkRedirectHttps()) { header('HTTP/1.1 302 Moved Temporarily'); header('Location: ' . $this->getBaseURL() . '/' . $this->query_string); exit(); @@ -1353,7 +1348,7 @@ class App header("X-Friendica-Version: " . FRIENDICA_VERSION); header("Content-type: text/html; charset=utf-8"); - if ($this->config->get('system', 'hsts') && ($this->config->get('system', 'ssl_policy') == BaseUrl::SSL_POLICY_FULL)) { + if ($this->config->get('system', 'hsts') && ($this->baseURL->getSSLPolicy() == BaseUrl::SSL_POLICY_FULL)) { header("Strict-Transport-Security: max-age=31536000"); } diff --git a/src/Util/BasePath.php b/src/Util/BasePath.php index a2849831eb..fc9c3b5939 100644 --- a/src/Util/BasePath.php +++ b/src/Util/BasePath.php @@ -17,7 +17,7 @@ class BasePath * * @throws \Exception if directory isn't usable */ - public static function create($basePath, $server = []) + public static function create($basePath, array $server = []) { if (!$basePath && !empty($server['DOCUMENT_ROOT'])) { $basePath = $server['DOCUMENT_ROOT']; diff --git a/src/Util/BaseURL.php b/src/Util/BaseURL.php index a9bed2b5e8..c4f6e991b4 100644 --- a/src/Util/BaseURL.php +++ b/src/Util/BaseURL.php @@ -6,7 +6,7 @@ use Friendica\Core\Config\Configuration; /** * A class which checks and contains the basic - * environment for the BaseURL (url, urlpath, ssl_policy, hostname) + * environment for the BaseURL (url, urlpath, ssl_policy, hostname, scheme) */ class BaseURL { @@ -25,6 +25,11 @@ class BaseURL */ const SSL_POLICY_SELFSIGN = 2; + /** + * Define the Default SSL scheme + */ + const DEFAULT_SSL_SCHEME = self::SSL_POLICY_SELFSIGN; + /** * The Friendica Config * @var Configuration @@ -114,7 +119,11 @@ class BaseURL */ public function get($ssl = false) { - return (!$ssl ? $this->url : $this->returnBaseURL($ssl)); + if ($this->sslPolicy === self::SSL_POLICY_SELFSIGN && $ssl) { + return Network::switchScheme($this->url); + } + + return $this->url; } /** @@ -184,7 +193,29 @@ class BaseURL $urlPath = trim($parsed['path'], '\\/'); } - return $this->save($hostname, null, $urlPath); + $sslPolicy = null; + if (!empty($parsed['scheme'])) { + if ($parsed['scheme'] == 'https') { + $sslPolicy = BaseURL::SSL_POLICY_FULL; + } + } + + return $this->save($hostname, $sslPolicy, $urlPath); + } + + /** + * Checks, if a redirect to the HTTPS site would be necessary + * + * @return bool + */ + public function checkRedirectHttps() + { + return $this->config->get('system', 'force_ssl') + && ($this->getScheme() == "http") + && intval($this->getSSLPolicy()) == BaseURL::SSL_POLICY_FULL + && strpos($this->get(), 'https://') === 0 + && !empty($this->server['REQUEST_METHOD']) + && $this->server['REQUEST_METHOD'] === 'GET'; } /** @@ -196,8 +227,8 @@ class BaseURL $this->config = $config; $this->server = $server; - $this->checkConfig(); $this->determineSchema(); + $this->checkConfig(); } /** @@ -205,10 +236,10 @@ class BaseURL */ public function checkConfig() { - $this->hostname = $this->config->get('config', 'hostname', null); - $this->urlPath = $this->config->get('system', 'urlpath', null); - $this->sslPolicy = $this->config->get('system', 'ssl_policy', null); - $this->url = $this->config->get('system', 'url', null); + $this->hostname = $this->config->get('config', 'hostname'); + $this->urlPath = $this->config->get('system', 'urlpath'); + $this->sslPolicy = $this->config->get('system', 'ssl_policy'); + $this->url = $this->config->get('system', 'url'); if (empty($this->hostname)) { $this->determineHostname(); @@ -224,7 +255,11 @@ class BaseURL } if (!isset($this->sslPolicy)) { - $this->sslPolicy = self::SSL_POLICY_NONE; + if ($this->scheme == 'https') { + $this->sslPolicy = self::SSL_POLICY_FULL; + } else { + $this->sslPolicy = self::DEFAULT_SSL_SCHEME; + } $this->config->set('system', 'ssl_policy', $this->sslPolicy); } @@ -325,20 +360,4 @@ class BaseURL $this->scheme = 'https'; } } - - /** - * Returns the URL based on the current used ssl setting - * - * @param bool $ssl true, if ssl should be used - * - * @return string - */ - private function returnBaseURL($ssl) - { - if ($this->sslPolicy == self::SSL_POLICY_SELFSIGN && $ssl) { - return Network::switchScheme($this->url); - } - - return $this->url; - } } diff --git a/src/Util/Network.php b/src/Util/Network.php index b534a5505b..e4e9c5af5b 100644 --- a/src/Util/Network.php +++ b/src/Util/Network.php @@ -838,7 +838,7 @@ class Network /** - * @brief Switch the scheme of an url between http and https + * Switch the scheme of an url between http and https * * @param string $url URL * @@ -846,15 +846,17 @@ class Network */ public static function switchScheme($url) { - $parts = parse_url($url, PHP_URL_SCHEME); - if (!isset($parts['scheme'])) { + $scheme = parse_url($url, PHP_URL_SCHEME); + if (empty($scheme)) { return $url; } - if ($parts['scheme'] == 'http') { + + if ($scheme === 'http') { $url = str_replace('http://', 'https://', $url); - } elseif ($parts['scheme'] == 'https') { + } elseif ($scheme === 'https') { $url = str_replace('https://', 'http://', $url); } + return $url; } } diff --git a/tests/include/ApiTest.php b/tests/include/ApiTest.php index ecfe3e9621..933b9a45ae 100644 --- a/tests/include/ApiTest.php +++ b/tests/include/ApiTest.php @@ -13,6 +13,7 @@ use Friendica\Core\System; use Friendica\Factory; use Friendica\Network\HTTPException; use Friendica\Util\BasePath; +use Friendica\Util\BaseURL; use Friendica\Util\Config\ConfigFileLoader; use Monolog\Handler\TestHandler; @@ -58,7 +59,8 @@ class ApiTest extends DatabaseTest $config = Factory\ConfigFactory::createConfig($configCache); Factory\ConfigFactory::createPConfig($configCache); $logger = Factory\LoggerFactory::create('test', $config, $profiler); - $this->app = new App($config, $mode, $router, $logger, $profiler, false); + $baseUrl = new BaseURL($config, $_SERVER); + $this->app = new App($config, $mode, $router, $baseUrl, $logger, $profiler, false); parent::setUp(); diff --git a/tests/src/Database/DBATest.php b/tests/src/Database/DBATest.php index 889ae6af0d..21ccd1df1f 100644 --- a/tests/src/Database/DBATest.php +++ b/tests/src/Database/DBATest.php @@ -7,6 +7,7 @@ use Friendica\Database\DBA; use Friendica\Factory; use Friendica\Test\DatabaseTest; use Friendica\Util\BasePath; +use Friendica\Util\BaseURL; use Friendica\Util\Config\ConfigFileLoader; class DBATest extends DatabaseTest @@ -23,7 +24,8 @@ class DBATest extends DatabaseTest $config = Factory\ConfigFactory::createConfig($configCache); Factory\ConfigFactory::createPConfig($configCache); $logger = Factory\LoggerFactory::create('test', $config, $profiler); - $this->app = new App($config, $mode, $router, $logger, $profiler, false); + $baseUrl = new BaseURL($config, $_SERVER); + $this->app = new App($config, $mode, $router, $baseUrl, $logger, $profiler, false); parent::setUp(); diff --git a/tests/src/Database/DBStructureTest.php b/tests/src/Database/DBStructureTest.php index ec1531783e..4bf4ed51c4 100644 --- a/tests/src/Database/DBStructureTest.php +++ b/tests/src/Database/DBStructureTest.php @@ -7,6 +7,7 @@ use Friendica\Database\DBStructure; use Friendica\Factory; use Friendica\Test\DatabaseTest; use Friendica\Util\BasePath; +use Friendica\Util\BaseURL; use Friendica\Util\Config\ConfigFileLoader; class DBStructureTest extends DatabaseTest @@ -23,7 +24,8 @@ class DBStructureTest extends DatabaseTest $config = Factory\ConfigFactory::createConfig($configCache); Factory\ConfigFactory::createPConfig($configCache); $logger = Factory\LoggerFactory::create('test', $config, $profiler); - $this->app = new App($config, $mode, $router, $logger, $profiler, false); + $baseUrl = new BaseURL($config, $_SERVER); + $this->app = new App($config, $mode, $router, $baseUrl, $logger, $profiler, false); parent::setUp(); } diff --git a/tests/src/Util/BaseURLTest.php b/tests/src/Util/BaseURLTest.php new file mode 100644 index 0000000000..330a4eebe7 --- /dev/null +++ b/tests/src/Util/BaseURLTest.php @@ -0,0 +1,455 @@ + [ + 'server' => [], + 'input' => [ + 'hostname' => null, + 'urlPath' => null, + 'sslPolicy' => null, + 'url' => null, + ], + 'assert' => [ + 'hostname' => '', + 'urlPath' => '', + 'sslPolicy' => BaseURL::DEFAULT_SSL_SCHEME, + 'url' => 'http://', + 'scheme' => 'http', + ], + ], + 'WithSubDirectory' => [ + 'server' => [ + 'SERVER_NAME' => 'friendica.local', + 'REDIRECT_URI' => 'test/module/more', + 'QUERY_STRING' => 'module/more', + ], + 'input' => [ + 'hostname' => null, + 'urlPath' => null, + 'sslPolicy' => null, + 'url' => null, + ], + 'assert' => [ + 'hostname' => 'friendica.local', + 'urlPath' => 'test', + 'sslPolicy' => BaseURL::DEFAULT_SSL_SCHEME, + 'url' => 'http://friendica.local/test', + 'scheme' => 'http', + ], + ], + 'input' => [ + 'server' => [], + 'input' => [ + 'hostname' => 'friendica.local', + 'urlPath' => 'test', + 'sslPolicy' => BaseURL::SSL_POLICY_FULL, + 'url' => 'http://friendica.local/test', + ], + 'assert' => [ + 'hostname' => 'friendica.local', + 'urlPath' => 'test', + 'sslPolicy' => BaseURL::SSL_POLICY_FULL, + 'url' => 'http://friendica.local/test', + 'scheme' => 'http', + ], + ], + 'WithHttpsScheme' => [ + 'server' => [ + 'SERVER_NAME' => 'friendica.local', + 'REDIRECT_URI' => 'test/module/more', + 'QUERY_STRING' => 'module/more', + 'HTTPS' => true, + ], + 'input' => [ + 'hostname' => null, + 'urlPath' => null, + 'sslPolicy' => null, + 'url' => null, + ], + 'assert' => [ + 'hostname' => 'friendica.local', + 'urlPath' => 'test', + 'sslPolicy' => BaseURL::SSL_POLICY_FULL, + 'url' => 'https://friendica.local/test', + 'scheme' => 'https', + ], + ], + 'WithoutQueryString' => [ + 'server' => [ + 'SERVER_NAME' => 'friendica.local', + 'REDIRECT_URI' => 'test/more', + 'HTTPS' => true, + ], + 'input' => [ + 'hostname' => null, + 'urlPath' => null, + 'sslPolicy' => null, + 'url' => null, + ], + 'assert' => [ + 'hostname' => 'friendica.local', + 'urlPath' => 'test/more', + 'sslPolicy' => BaseURL::SSL_POLICY_FULL, + 'url' => 'https://friendica.local/test/more', + 'scheme' => 'https', + ], + ], + 'WithPort' => [ + 'server' => [ + 'SERVER_NAME' => 'friendica.local', + 'SERVER_PORT' => '1234', + 'REDIRECT_URI' => 'test/more', + 'HTTPS' => true, + ], + 'input' => [ + 'hostname' => null, + 'urlPath' => null, + 'sslPolicy' => null, + 'url' => null, + ], + 'assert' => [ + 'hostname' => 'friendica.local:1234', + 'urlPath' => 'test/more', + 'sslPolicy' => BaseURL::SSL_POLICY_FULL, + 'url' => 'https://friendica.local:1234/test/more', + 'scheme' => 'https', + ], + ], + 'With443Port' => [ + 'server' => [ + 'SERVER_NAME' => 'friendica.local', + 'SERVER_PORT' => '443', + 'REDIRECT_URI' => 'test/more', + ], + 'input' => [ + 'hostname' => null, + 'urlPath' => null, + 'sslPolicy' => null, + 'url' => null, + ], + 'assert' => [ + 'hostname' => 'friendica.local', + 'urlPath' => 'test/more', + 'sslPolicy' => BaseURL::SSL_POLICY_FULL, + 'url' => 'https://friendica.local/test/more', + 'scheme' => 'https', + ], + ], + 'With80Port' => [ + 'server' => [ + 'SERVER_NAME' => 'friendica.local', + 'SERVER_PORT' => '80', + 'REDIRECT_URI' => 'test/more', + ], + 'input' => [ + 'hostname' => null, + 'urlPath' => null, + 'sslPolicy' => null, + 'url' => null, + ], + 'assert' => [ + 'hostname' => 'friendica.local', + 'urlPath' => 'test/more', + 'sslPolicy' => BaseURL::DEFAULT_SSL_SCHEME, + 'url' => 'http://friendica.local/test/more', + 'scheme' => 'http', + ], + ], + ]; + } + + /** + * Test the default config determination + * @dataProvider dataDefault + */ + public function testCheck($server, $input, $assert) + { + $configMock = \Mockery::mock(Configuration::class); + $configMock->shouldReceive('get')->with('config', 'hostname')->andReturn($input['hostname']); + $configMock->shouldReceive('get')->with('system', 'urlpath')->andReturn($input['urlPath']); + $configMock->shouldReceive('get')->with('system', 'ssl_policy')->andReturn($input['sslPolicy']); + $configMock->shouldReceive('get')->with('system', 'url')->andReturn($input['url']); + + if (!isset($input['urlPath']) && isset($assert['urlPath'])) { + $configMock->shouldReceive('set')->with('system', 'urlpath', $assert['urlPath'])->once(); + } + + if (!isset($input['sslPolicy']) && isset($assert['sslPolicy'])) { + $configMock->shouldReceive('set')->with('system', 'ssl_policy', $assert['sslPolicy'])->once(); + } + + if (!isset($input['hostname']) && !empty($assert['hostname'])) { + $configMock->shouldReceive('set')->with('config', 'hostname', $assert['hostname'])->once(); + } + + $baseUrl = new BaseURL($configMock, $server); + + $this->assertEquals($assert['hostname'], $baseUrl->getHostname()); + $this->assertEquals($assert['urlPath'], $baseUrl->getUrlPath()); + $this->assertEquals($assert['sslPolicy'], $baseUrl->getSSLPolicy()); + $this->assertEquals($assert['scheme'], $baseUrl->getScheme()); + $this->assertEquals($assert['url'], $baseUrl->get()); + } + + public function dataSave() + { + return [ + 'default' => [ + 'input' => [ + 'hostname' => 'friendica.old', + 'urlPath' => 'is/old/path', + 'sslPolicy' => BaseURL::DEFAULT_SSL_SCHEME, + 'url' => 'http://friendica.old/is/old/path', + 'force_ssl' => true, + ], + 'save' => [ + 'hostname' => 'friendica.local', + 'urlPath' => 'new/path', + 'sslPolicy' => BaseURL::SSL_POLICY_FULL, + ], + 'url' => 'https://friendica.local/new/path', + ], + 'null' => [ + 'input' => [ + 'hostname' => 'friendica.old', + 'urlPath' => 'is/old/path', + 'sslPolicy' => BaseURL::DEFAULT_SSL_SCHEME, + 'url' => 'http://friendica.old/is/old/path', + 'force_ssl' => true, + ], + 'save' => [ + 'hostname' => null, + 'urlPath' => null, + 'sslPolicy' => null, + ], + 'url' => 'http://friendica.old/is/old/path', + ], + 'changeHostname' => [ + 'input' => [ + 'hostname' => 'friendica.old', + 'urlPath' => 'is/old/path', + 'sslPolicy' => BaseURL::DEFAULT_SSL_SCHEME, + 'url' => 'http://friendica.old/is/old/path', + 'force_ssl' => true, + ], + 'save' => [ + 'hostname' => 'friendica.local', + 'urlPath' => null, + 'sslPolicy' => null, + ], + 'url' => 'http://friendica.local/is/old/path', + ], + 'changeUrlPath' => [ + 'input' => [ + 'hostname' => 'friendica.old', + 'urlPath' => 'is/old/path', + 'sslPolicy' => BaseURL::DEFAULT_SSL_SCHEME, + 'url' => 'http://friendica.old/is/old/path', + 'force_ssl' => true, + ], + 'save' => [ + 'hostname' => null, + 'urlPath' => 'new/path', + 'sslPolicy' => null, + ], + 'url' => 'http://friendica.old/new/path', + ], + 'changeSSLPolicy' => [ + 'input' => [ + 'hostname' => 'friendica.old', + 'urlPath' => 'is/old/path', + 'sslPolicy' => BaseURL::DEFAULT_SSL_SCHEME, + 'url' => 'http://friendica.old/is/old/path', + 'force_ssl' => true, + ], + 'save' => [ + 'hostname' => null, + 'urlPath' => null, + 'sslPolicy' => BaseURL::SSL_POLICY_FULL, + ], + 'url' => 'https://friendica.old/is/old/path', + ], + ]; + } + + /** + * Test the save() method + * @dataProvider dataSave + */ + public function testSave($input, $save, $url) + { + $configMock = \Mockery::mock(Configuration::class); + $configMock->shouldReceive('get')->with('config', 'hostname')->andReturn($input['hostname']); + $configMock->shouldReceive('get')->with('system', 'urlpath')->andReturn($input['urlPath']); + $configMock->shouldReceive('get')->with('system', 'ssl_policy')->andReturn($input['sslPolicy']); + $configMock->shouldReceive('get')->with('system', 'url')->andReturn($input['url']); + $configMock->shouldReceive('get')->with('system', 'force_ssl')->andReturn($input['force_ssl']); + + $baseUrl = new BaseURL($configMock, []); + + $configMock->shouldReceive('set')->with('config', 'hostname', $save['hostname'])->andReturn(true)->once(); + $configMock->shouldReceive('set')->with('system', 'urlpath', $save['urlPath'])->andReturn(true)->once(); + $configMock->shouldReceive('set')->with('system', 'ssl_policy', $save['sslPolicy'])->andReturn(true)->once(); + $configMock->shouldReceive('set')->with('system', 'url', $url)->andReturn(true)->once(); + + $baseUrl->save($save['hostname'], $save['sslPolicy'], $save['urlPath']); + + $this->assertEquals($url, $baseUrl->get()); + } + + /** + * Test the saveByUrl() method + * @dataProvider dataSave + * + * @param $input + * @param $save + * @param $url + */ + public function testSaveByUrl($input, $save, $url) + { + $configMock = \Mockery::mock(Configuration::class); + $configMock->shouldReceive('get')->with('config', 'hostname')->andReturn($input['hostname']); + $configMock->shouldReceive('get')->with('system', 'urlpath')->andReturn($input['urlPath']); + $configMock->shouldReceive('get')->with('system', 'ssl_policy')->andReturn($input['sslPolicy']); + $configMock->shouldReceive('get')->with('system', 'url')->andReturn($input['url']); + $configMock->shouldReceive('get')->with('system', 'force_ssl')->andReturn($input['force_ssl']); + + $baseUrl = new BaseURL($configMock, []); + + $configMock->shouldReceive('set')->with('config', 'hostname', (!empty($save['hostname']) ? $save['hostname'] : $input['hostname']))->andReturn(true)->once(); + $configMock->shouldReceive('set')->with('system', 'urlpath', (!empty($save['urlPath']) ? $save['urlPath'] : $input['urlPath']))->andReturn(true)->once(); + $configMock->shouldReceive('set')->with('system', 'ssl_policy', (!empty($save['sslPolicy']) ? $save['sslPolicy'] : $input['sslPolicy']))->andReturn(true)->once(); + $configMock->shouldReceive('set')->with('system', 'url', $url)->andReturn(true)->once(); + + $baseUrl->saveByURL($url); + + $this->assertEquals($url, $baseUrl->get()); + } + + public function dataGetBaseUrl() + { + return [ + 'default' => [ + 'sslPolicy' => BaseURL::DEFAULT_SSL_SCHEME, + 'ssl' => false, + 'url' => 'http://friendica.local/new/test', + 'assert' => 'http://friendica.local/new/test', + ], + 'DefaultWithSSL' => [ + 'sslPolicy' => BaseURL::DEFAULT_SSL_SCHEME, + 'ssl' => true, + 'url' => 'http://friendica.local/new/test', + 'assert' => 'https://friendica.local/new/test', + ], + 'SSLFullWithSSL' => [ + 'sslPolicy' => BaseURL::SSL_POLICY_FULL, + 'ssl' => true, + 'url' => 'http://friendica.local/new/test', + 'assert' => 'http://friendica.local/new/test', + ], + 'SSLFullWithoutSSL' => [ + 'sslPolicy' => BaseURL::SSL_POLICY_FULL, + 'ssl' => false, + 'url' => 'https://friendica.local/new/test', + 'assert' => 'https://friendica.local/new/test', + ], + 'NoSSLWithSSL' => [ + 'sslPolicy' => BaseURL::SSL_POLICY_NONE, + 'ssl' => true, + 'url' => 'http://friendica.local/new/test', + 'assert' => 'http://friendica.local/new/test', + ], + 'NoSSLWithoutSSL' => [ + 'sslPolicy' => BaseURL::SSL_POLICY_NONE, + 'ssl' => false, + 'url' => 'http://friendica.local/new/test', + 'assert' => 'http://friendica.local/new/test', + ], + ]; + } + + /** + * Test the get() method + * @dataProvider dataGetBaseUrl + */ + public function testGetURL($sslPolicy, $ssl, $url, $assert) + { + $configMock = \Mockery::mock(Configuration::class); + $configMock->shouldReceive('get')->with('config', 'hostname')->andReturn('friendica.local'); + $configMock->shouldReceive('get')->with('system', 'urlpath')->andReturn('new/test'); + $configMock->shouldReceive('get')->with('system', 'ssl_policy')->andReturn($sslPolicy); + $configMock->shouldReceive('get')->with('system', 'url')->andReturn($url); + + $baseUrl = new BaseURL($configMock, []); + + $this->assertEquals($assert, $baseUrl->get($ssl)); + } + + public function dataCheckRedirectHTTPS() + { + return [ + 'default' => [ + 'server' => [ + 'REQUEST_METHOD' => 'GET', + 'HTTPS' => true, + ], + 'forceSSL' => false, + 'sslPolicy' => BaseURL::DEFAULT_SSL_SCHEME, + 'url' => 'https://friendica.local', + 'redirect' => false, + ], + 'forceSSL' => [ + 'server' => [ + 'REQUEST_METHOD' => 'GET', + ], + 'forceSSL' => true, + 'sslPolicy' => BaseURL::DEFAULT_SSL_SCHEME, + 'url' => 'https://friendica.local', + 'redirect' => false, + ], + 'forceSSLWithSSLPolicy' => [ + 'server' => [], + 'forceSSL' => true, + 'sslPolicy' => BaseURL::SSL_POLICY_FULL, + 'url' => 'https://friendica.local', + 'redirect' => false, + ], + 'forceSSLWithSSLPolicyAndGet' => [ + 'server' => [ + 'REQUEST_METHOD' => 'GET', + ], + 'forceSSL' => true, + 'sslPolicy' => BaseURL::SSL_POLICY_FULL, + 'url' => 'https://friendica.local', + 'redirect' => true, + ], + ]; + } + + /** + * Test the checkRedirectHTTPS() method + * @dataProvider dataCheckRedirectHTTPS + */ + public function testCheckRedirectHTTPS($server, $forceSSL, $sslPolicy, $url, $redirect) + { + $configMock = \Mockery::mock(Configuration::class); + $configMock->shouldReceive('get')->with('config', 'hostname')->andReturn('friendica.local'); + $configMock->shouldReceive('get')->with('system', 'urlpath')->andReturn('new/test'); + $configMock->shouldReceive('get')->with('system', 'ssl_policy')->andReturn($sslPolicy); + $configMock->shouldReceive('get')->with('system', 'url')->andReturn($url); + $configMock->shouldReceive('get')->with('system', 'force_ssl')->andReturn($forceSSL); + + $baseUrl = new BaseURL($configMock, $server); + + $this->assertEquals($redirect, $baseUrl->checkRedirectHttps()); + } +} From 63660b6fc0231913ebcc5433e9aae4ec80b81aa1 Mon Sep 17 00:00:00 2001 From: Philipp Holzer Date: Mon, 8 Apr 2019 23:29:37 +0200 Subject: [PATCH 3/7] Removed unnecessary class --- src/Core/Update.php | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/Core/Update.php b/src/Core/Update.php index 328938fe1c..3a356d1624 100644 --- a/src/Core/Update.php +++ b/src/Core/Update.php @@ -27,9 +27,6 @@ class Update return; } - // Check if the config files are set correctly - self::checkBaseSettings($_SERVER); - // Don't check the status if the last update was failed if (Config::get('system', 'update', Update::SUCCESS, true) == Update::FAILED) { return; @@ -225,11 +222,6 @@ class Update } } - public static function checkBaseSettings(array $server) - { - - } - /** * send the email and do what is needed to do on update fails * From e69138039a4492be0ac0c921bded5fdb9e23bff4 Mon Sep 17 00:00:00 2001 From: Philipp Holzer Date: Tue, 9 Apr 2019 08:41:26 +0200 Subject: [PATCH 4/7] fix api dataset for test --- tests/datasets/api.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/datasets/api.yml b/tests/datasets/api.yml index e4ef31219f..a28ae21ee9 100644 --- a/tests/datasets/api.yml +++ b/tests/datasets/api.yml @@ -15,7 +15,7 @@ config: k: url v: http://localhost - - cat: system + cat: config k: hostname v: localhost - From 40c075cf47fdffd2dcf6c0b66c14c2cb13954f54 Mon Sep 17 00:00:00 2001 From: Philipp Holzer Date: Tue, 9 Apr 2019 08:48:04 +0200 Subject: [PATCH 5/7] BaseURL hardening --- src/Util/BaseURL.php | 6 +++--- tests/src/Util/BaseURLTest.php | 34 ++++++++++++++++++++++++++-------- 2 files changed, 29 insertions(+), 11 deletions(-) diff --git a/src/Util/BaseURL.php b/src/Util/BaseURL.php index c4f6e991b4..66526ff2db 100644 --- a/src/Util/BaseURL.php +++ b/src/Util/BaseURL.php @@ -139,21 +139,21 @@ class BaseURL { $success = true; - if (!empty($hostname)) { + if (!empty($hostname) && $hostname !== $this->hostname) { $this->hostname = $hostname; if (!$this->config->set('config', 'hostname', $this->hostname)) { $success = false; } } - if (isset($sslPolicy)) { + if (isset($sslPolicy) && $sslPolicy !== $this->sslPolicy) { $this->sslPolicy = $sslPolicy; if (!$this->config->set('system', 'ssl_policy', $this->sslPolicy)) { $success = false; } } - if (isset($urlPath)) { + if (isset($urlPath) && $urlPath !== $this->urlPath) { $this->urlPath = $urlPath; if (!$this->config->set('system', 'urlpath', $this->urlPath)) { $success = false; diff --git a/tests/src/Util/BaseURLTest.php b/tests/src/Util/BaseURLTest.php index 330a4eebe7..c819a22e5f 100644 --- a/tests/src/Util/BaseURLTest.php +++ b/tests/src/Util/BaseURLTest.php @@ -2,10 +2,10 @@ namespace Friendica\Test\src\Util; use Friendica\Core\Config\Configuration; +use Friendica\Test\MockedTest; use Friendica\Util\BaseURL; -use PHPUnit\Framework\TestCase; -class BaseURLTest extends TestCase +class BaseURLTest extends MockedTest { public function dataDefault() { @@ -296,9 +296,18 @@ class BaseURLTest extends TestCase $baseUrl = new BaseURL($configMock, []); - $configMock->shouldReceive('set')->with('config', 'hostname', $save['hostname'])->andReturn(true)->once(); - $configMock->shouldReceive('set')->with('system', 'urlpath', $save['urlPath'])->andReturn(true)->once(); - $configMock->shouldReceive('set')->with('system', 'ssl_policy', $save['sslPolicy'])->andReturn(true)->once(); + if (isset($save['hostname'])) { + $configMock->shouldReceive('set')->with('config', 'hostname', $save['hostname'])->andReturn(true)->once(); + } + + if (isset($save['urlPath'])) { + $configMock->shouldReceive('set')->with('system', 'urlpath', $save['urlPath'])->andReturn(true)->once(); + } + + if (isset($save['sslPolicy'])) { + $configMock->shouldReceive('set')->with('system', 'ssl_policy', $save['sslPolicy'])->andReturn(true)->once(); + } + $configMock->shouldReceive('set')->with('system', 'url', $url)->andReturn(true)->once(); $baseUrl->save($save['hostname'], $save['sslPolicy'], $save['urlPath']); @@ -325,9 +334,18 @@ class BaseURLTest extends TestCase $baseUrl = new BaseURL($configMock, []); - $configMock->shouldReceive('set')->with('config', 'hostname', (!empty($save['hostname']) ? $save['hostname'] : $input['hostname']))->andReturn(true)->once(); - $configMock->shouldReceive('set')->with('system', 'urlpath', (!empty($save['urlPath']) ? $save['urlPath'] : $input['urlPath']))->andReturn(true)->once(); - $configMock->shouldReceive('set')->with('system', 'ssl_policy', (!empty($save['sslPolicy']) ? $save['sslPolicy'] : $input['sslPolicy']))->andReturn(true)->once(); + if (isset($save['hostname'])) { + $configMock->shouldReceive('set')->with('config', 'hostname', $save['hostname'])->andReturn(true)->once(); + } + + if (isset($save['urlPath'])) { + $configMock->shouldReceive('set')->with('system', 'urlpath', $save['urlPath'])->andReturn(true)->once(); + } + + if (isset($save['sslPolicy'])) { + $configMock->shouldReceive('set')->with('system', 'ssl_policy', $save['sslPolicy'])->andReturn(true)->once(); + } + $configMock->shouldReceive('set')->with('system', 'url', $url)->andReturn(true)->once(); $baseUrl->saveByURL($url); From 1dd168488554def9db33a2abbc3697332bb4bb65 Mon Sep 17 00:00:00 2001 From: Philipp Holzer Date: Wed, 10 Apr 2019 20:38:39 +0200 Subject: [PATCH 6/7] Hardening save method in BaseURL --- src/Util/BaseURL.php | 43 +++++++++++++++++------- tests/src/Util/BaseURLTest.php | 61 ++++++++++++++++++++++++++++++++++ 2 files changed, 92 insertions(+), 12 deletions(-) diff --git a/src/Util/BaseURL.php b/src/Util/BaseURL.php index 66526ff2db..cd703a9177 100644 --- a/src/Util/BaseURL.php +++ b/src/Util/BaseURL.php @@ -137,35 +137,54 @@ class BaseURL */ public function save($hostname = null, $sslPolicy = null, $urlPath = null) { - $success = true; + $currHostname = $this->hostname; + $currSSLPolicy = $this->sslPolicy; + $currURLPath = $this->urlPath; if (!empty($hostname) && $hostname !== $this->hostname) { - $this->hostname = $hostname; - if (!$this->config->set('config', 'hostname', $this->hostname)) { - $success = false; + if ($this->config->set('config', 'hostname', $hostname)) { + $this->hostname = $hostname; + } else { + return false; } } if (isset($sslPolicy) && $sslPolicy !== $this->sslPolicy) { - $this->sslPolicy = $sslPolicy; - if (!$this->config->set('system', 'ssl_policy', $this->sslPolicy)) { - $success = false; + if ($this->config->set('system', 'ssl_policy', $sslPolicy)) { + $this->sslPolicy = $sslPolicy; + } else { + $this->hostname = $currHostname; + $this->config->set('config', 'hostname', $this->hostname); + return false; } } if (isset($urlPath) && $urlPath !== $this->urlPath) { - $this->urlPath = $urlPath; - if (!$this->config->set('system', 'urlpath', $this->urlPath)) { - $success = false; + if ($this->config->set('system', 'urlpath', $urlPath)) { + $this->urlPath = $urlPath; + } else { + $this->hostname = $currHostname; + $this->sslPolicy = $currSSLPolicy; + $this->config->set('config', 'hostname', $this->hostname); + $this->config->set('system', 'ssl_policy', $this->sslPolicy); + return false; } } $this->determineBaseUrl(); if (!$this->config->set('system', 'url', $this->url)) { - $success = false; + $this->hostname = $currHostname; + $this->sslPolicy = $currSSLPolicy; + $this->urlPath = $currURLPath; + $this->determineBaseUrl(); + + $this->config->set('config', 'hostname', $this->hostname); + $this->config->set('system', 'ssl_policy', $this->sslPolicy); + $this->config->set('system', 'urlpath', $this->urlPath); + return false; } - return $success; + return true; } /** diff --git a/tests/src/Util/BaseURLTest.php b/tests/src/Util/BaseURLTest.php index c819a22e5f..ee88bd9804 100644 --- a/tests/src/Util/BaseURLTest.php +++ b/tests/src/Util/BaseURLTest.php @@ -470,4 +470,65 @@ class BaseURLTest extends MockedTest $this->assertEquals($redirect, $baseUrl->checkRedirectHttps()); } + + public function dataWrongSave() + { + return [ + 'wrongHostname' => [ + 'fail' => 'hostname', + ], + 'wrongSSLPolicy' => [ + 'fail' => 'sslPolicy', + ], + 'wrongURLPath' => [ + 'fail' => 'urlPath', + ], + 'wrongURL' => [ + 'fail' => 'url', + ], + ]; + } + + /** + * Test the save() method with wrong parameters + * @dataProvider dataWrongSave + */ + public function testWrongSave($fail) + { + $configMock = \Mockery::mock(Configuration::class); + $configMock->shouldReceive('get')->with('config', 'hostname')->andReturn('friendica.local'); + $configMock->shouldReceive('get')->with('system', 'urlpath')->andReturn('new/test'); + $configMock->shouldReceive('get')->with('system', 'ssl_policy')->andReturn(BaseURL::DEFAULT_SSL_SCHEME); + $configMock->shouldReceive('get')->with('system', 'url')->andReturn('http://friendica.local/new/test'); + + switch ($fail) { + case 'hostname': + $configMock->shouldReceive('set')->with('config', 'hostname', \Mockery::any())->andReturn(false)->once(); + break; + case 'sslPolicy': + $configMock->shouldReceive('set')->with('config', 'hostname', \Mockery::any())->andReturn(true)->twice(); + $configMock->shouldReceive('set')->with('system', 'ssl_policy', \Mockery::any())->andReturn(false)->once(); + break; + case 'urlPath': + $configMock->shouldReceive('set')->with('config', 'hostname', \Mockery::any())->andReturn(true)->twice(); + $configMock->shouldReceive('set')->with('system', 'ssl_policy', \Mockery::any())->andReturn(true)->twice(); + $configMock->shouldReceive('set')->with('system', 'urlpath', \Mockery::any())->andReturn(false)->once(); + break; + case 'url': + $configMock->shouldReceive('set')->with('config', 'hostname', \Mockery::any())->andReturn(true)->twice(); + $configMock->shouldReceive('set')->with('system', 'ssl_policy', \Mockery::any())->andReturn(true)->twice(); + $configMock->shouldReceive('set')->with('system', 'urlpath', \Mockery::any())->andReturn(true)->twice(); + $configMock->shouldReceive('set')->with('system', 'url', \Mockery::any())->andReturn(false)->once(); + break; + } + + $baseUrl = new BaseURL($configMock, []); + $this->assertFalse($baseUrl->save('test', 10, 'nope')); + + // nothing should have changed because we never successfully saved anything + $this->assertEquals($baseUrl->getHostname(), 'friendica.local'); + $this->assertEquals($baseUrl->getUrlPath(), 'new/test'); + $this->assertEquals($baseUrl->getSSLPolicy(), BaseURL::DEFAULT_SSL_SCHEME); + $this->assertEquals($baseUrl->get(), 'http://friendica.local/new/test'); + } } From dc48690ed592c0fb641989d970bb60628555e70a Mon Sep 17 00:00:00 2001 From: Philipp Holzer Date: Thu, 11 Apr 2019 00:09:59 +0200 Subject: [PATCH 7/7] Remove hostname blacklisting --- src/Core/Config/Configuration.php | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/src/Core/Config/Configuration.php b/src/Core/Config/Configuration.php index c6fe626d91..532ed982a9 100644 --- a/src/Core/Config/Configuration.php +++ b/src/Core/Config/Configuration.php @@ -10,16 +10,6 @@ namespace Friendica\Core\Config; */ class Configuration { - /** - * The blacklist of configuration settings, which should not get saved to the backend - * @var array - */ - private $configSaveBlacklist = [ - 'config' => [ - 'hostname' => true, - ] - ]; - /** * @var Cache\IConfigCache */ @@ -127,7 +117,7 @@ class Configuration $cached = $this->configCache->set($cat, $key, $value); // If there is no connected adapter, we're finished - if (!$this->configAdapter->isConnected() || !empty($this->configSaveBlacklist[$cat][$key])) { + if (!$this->configAdapter->isConnected()) { return $cached; }