Create own base URL class which holds the whole base url business logic

This commit is contained in:
Philipp Holzer 2019-04-08 21:12:10 +02:00
parent 6ea531d2f8
commit 318a3ca785
No known key found for this signature in database
GPG key ID: 517BE60E2CE5C8A5
16 changed files with 434 additions and 876 deletions

344
src/Util/BaseURL.php Normal file
View file

@ -0,0 +1,344 @@
<?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)
*/
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;
/**
* 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)
{
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;
}
}

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