Merge pull request #6989 from nupplaphil/task/basePath_baseUrl_fix

Automatic BaseURL determination fix
This commit is contained in:
Hypolite Petovan 2019-04-11 04:07:20 -04:00 committed by GitHub
commit 6a2c0b3cc7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 1020 additions and 905 deletions

View File

@ -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 */

View File

@ -381,11 +381,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.

View File

@ -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 = [

View File

@ -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;
@ -191,7 +196,6 @@ class App
* @see initHead()
*
* @param string $path
* @throws InternalServerErrorException
*/
public function registerStylesheet($path)
{
@ -210,7 +214,6 @@ class App
* @see initFooter()
*
* @param string $path
* @throws InternalServerErrorException
*/
public function registerFooterScript($path)
{
@ -220,8 +223,6 @@ class App
}
public $queue;
private $scheme;
private $hostname;
/**
* @brief App constructor.
@ -229,19 +230,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 +260,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 +337,6 @@ class App
*/
public function reload()
{
$this->determineURLPath();
$this->getMode()->determine($this->getBasePath());
if ($this->getMode()->has(App\Mode::DBAVAILABLE)) {
@ -398,97 +379,28 @@ class App
}
/**
* Figure out if we are running at the top of a domain or in a sub-directory and adjust accordingly
*/
private function determineURLPath()
{
/*
* The automatic path detection in this function is currently deactivated,
* see issue https://github.com/friendica/friendica/issues/6679
* Returns the scheme of the current call
* @return string
*
* The problem is that the function seems to be confused with some url.
* These then confuses the detection which changes the url path.
* @deprecated 2019.06 - use BaseURL->getScheme() instead
*/
/* 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 +409,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();
}
/**
@ -940,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, ':'));
}
@ -1085,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());
}
}
@ -1119,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')) == 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();
@ -1458,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') == SSL_POLICY_FULL)) {
if ($this->config->get('system', 'hsts') && ($this->baseURL->getSSLPolicy() == BaseUrl::SSL_POLICY_FULL)) {
header("Strict-Transport-Security: max-age=31536000");
}

View File

@ -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);
}
/**

View File

@ -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;
}

View File

@ -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);
}

View File

@ -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
*/

View File

@ -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
@ -31,9 +27,6 @@ class Update
return;
}
// Check if the config files are set correctly
self::checkConfigFile($basePath, $mode);
// Don't check the status if the last update was failed
if (Config::get('system', 'update', Update::SUCCESS, true) == Update::FAILED) {
return;
@ -229,144 +222,6 @@ 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 = '')
{
// 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;
}
}
/**
* send the email and do what is needed to do on update fails
*

View File

@ -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);
}
}

View File

@ -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;
@ -2009,7 +2010,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']);
@ -2019,7 +2020,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']);

View File

@ -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);

View File

@ -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;

View File

@ -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'];

382
src/Util/BaseURL.php Normal file
View File

@ -0,0 +1,382 @@
<?php
namespace Friendica\Util;
use Friendica\Core\Config\Configuration;
/**
* A class which checks and contains the basic
* environment for the BaseURL (url, urlpath, ssl_policy, hostname, scheme)
*/
class BaseURL
{
/**
* No SSL necessary
*/
const SSL_POLICY_NONE = 0;
/**
* SSL is necessary
*/
const SSL_POLICY_FULL = 1;
/**
* SSL is optional, but preferred
*/
const SSL_POLICY_SELFSIGN = 2;
/**
* Define the Default SSL scheme
*/
const DEFAULT_SSL_SCHEME = self::SSL_POLICY_SELFSIGN;
/**
* The Friendica Config
* @var Configuration
*/
private $config;
/**
* The server side variables
* @var array
*/
private $server;
/**
* The hostname of the Base URL
* @var string
*/
private $hostname;
/**
* The SSL_POLICY of the Base URL
* @var int
*/
private $sslPolicy;
/**
* The URL sub-path of the Base URL
* @var string
*/
private $urlPath;
/**
* The full URL
* @var string
*/
private $url;
/**
* The current scheme of this call
* @var string
*/
private $scheme;
/**
* Returns the hostname of this node
* @return string
*/
public function getHostname()
{
return $this->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)
{
if ($this->sslPolicy === self::SSL_POLICY_SELFSIGN && $ssl) {
return Network::switchScheme($this->url);
}
return $this->url;
}
/**
* 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)
{
$currHostname = $this->hostname;
$currSSLPolicy = $this->sslPolicy;
$currURLPath = $this->urlPath;
if (!empty($hostname) && $hostname !== $this->hostname) {
if ($this->config->set('config', 'hostname', $hostname)) {
$this->hostname = $hostname;
} else {
return false;
}
}
if (isset($sslPolicy) && $sslPolicy !== $this->sslPolicy) {
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) {
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)) {
$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 true;
}
/**
* 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'], '\\/');
}
$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';
}
/**
* @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->determineSchema();
$this->checkConfig();
}
/**
* Check the current config during loading
*/
public function checkConfig()
{
$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();
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)) {
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);
}
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';
}
}
}

View File

@ -1,341 +0,0 @@
<?php
namespace Friendica\Util\Config;
/**
* The ConfigFileSaver saves specific variables into the config-files
*
* It is capable of loading the following config files:
* - *.config.php (current)
* - *.ini.php (deprecated)
* - *.htconfig.php (deprecated)
*/
class ConfigFileSaver extends ConfigFileManager
{
/**
* The standard indentation for config files
* @var string
*/
const INDENT = "\t";
/**
* The settings array to save to
* @var array
*/
private $settings = [];
/**
* Adds a given value to the config file
* Either it replaces the current value or it will get added
*
* @param string $cat The configuration category
* @param string $key The configuration key
* @param string $value The new value
*/
public function addConfigValue($cat, $key, $value)
{
$settingsCount = count(array_keys($this->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);
}
}
}
}

View File

@ -835,4 +835,28 @@ class Network
(strlen($query) ? "?".$query : '') .
(strlen($fragment) ? "#".$fragment : '');
}
/**
* Switch the scheme of an url between http and https
*
* @param string $url URL
*
* @return string switched URL
*/
public static function switchScheme($url)
{
$scheme = parse_url($url, PHP_URL_SCHEME);
if (empty($scheme)) {
return $url;
}
if ($scheme === 'http') {
$url = str_replace('http://', 'https://', $url);
} elseif ($scheme === 'https') {
$url = str_replace('https://', 'http://', $url);
}
return $url;
}
}

View File

@ -15,7 +15,7 @@ config:
k: url
v: http://localhost
-
cat: system
cat: config
k: hostname
v: localhost
-

View File

@ -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();

View File

@ -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();

View File

@ -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();
}

View File

@ -0,0 +1,534 @@
<?php
namespace Friendica\Test\src\Util;
use Friendica\Core\Config\Configuration;
use Friendica\Test\MockedTest;
use Friendica\Util\BaseURL;
class BaseURLTest extends MockedTest
{
public function dataDefault()
{
return [
'null' => [
'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, []);
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']);
$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, []);
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);
$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());
}
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');
}
}

View File

@ -1,189 +0,0 @@
<?php
namespace Friendica\Test\src\Util\Config;
use Friendica\App;
use Friendica\Core\Config\Cache\ConfigCache;
use Friendica\Test\MockedTest;
use Friendica\Test\Util\VFSTrait;
use Friendica\Util\Config\ConfigFileLoader;
use Friendica\Util\Config\ConfigFileSaver;
use Mockery\MockInterface;
use org\bovigo\vfs\vfsStream;
class ConfigFileSaverTest extends MockedTest
{
use VFSTrait;
/**
* @var App\Mode|MockInterface
*/
private $mode;
protected function setUp()
{
parent::setUp();
$this->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()));
}
}