From 0a9ec3fc423cb3487f4707ac57c23e9aeac2e33d Mon Sep 17 00:00:00 2001 From: Daniel Hensby <dhensby@silverstripe.com> Date: Mon, 30 Jan 2017 15:35:43 +0000 Subject: [PATCH] NEW Director::host() to determine host name of site --- cli-script.php | 15 +++ .../03_Environment_Management.md | 1 + docs/en/04_Changelogs/4.0.0.md | 1 + .../05_Making_A_SilverStripe_Core_Release.md | 3 +- main.php | 3 +- src/Control/Director.php | 110 ++++++++++++------ src/Core/Constants.php | 74 ------------ .../Startup/ParameterConfirmationToken.php | 3 +- src/Dev/DevelopmentAdmin.php | 25 ---- tests/bootstrap/cli.php | 16 +++ 10 files changed, 113 insertions(+), 138 deletions(-) diff --git a/cli-script.php b/cli-script.php index e3b2ac16a73..7880e9084d7 100755 --- a/cli-script.php +++ b/cli-script.php @@ -20,6 +20,21 @@ die(); } +// We update the $_SERVER variable to contain data consistent with the rest of the application. +$_SERVER = array_merge(array( + 'SERVER_PROTOCOL' => 'HTTP/1.1', + 'HTTP_ACCEPT' => 'text/plain;q=0.5', + 'HTTP_ACCEPT_LANGUAGE' => '*;q=0.5', + 'HTTP_ACCEPT_ENCODING' => '', + 'HTTP_ACCEPT_CHARSET' => 'ISO-8859-1;q=0.5', + 'SERVER_SIGNATURE' => 'Command-line PHP/' . phpversion(), + 'SERVER_SOFTWARE' => 'PHP/' . phpversion(), + 'SERVER_ADDR' => '127.0.0.1', + 'REMOTE_ADDR' => '127.0.0.1', + 'REQUEST_METHOD' => 'GET', + 'HTTP_USER_AGENT' => 'CLI', +), $_SERVER); + /** * Identify the cli-script.php file and change to its container directory, so that require_once() works */ diff --git a/docs/en/00_Getting_Started/03_Environment_Management.md b/docs/en/00_Getting_Started/03_Environment_Management.md index 9a817a29305..289e3e20b39 100644 --- a/docs/en/00_Getting_Started/03_Environment_Management.md +++ b/docs/en/00_Getting_Started/03_Environment_Management.md @@ -87,3 +87,4 @@ SilverStripe core environment variables are listed here, though you're free to d | `SS_ALLOWED_HOSTS` | A comma deliminated list of hostnames the site is allowed to respond to | | `SS_MANIFESTCACHE` | The manifest cache to use (defaults to file based caching) | | `SS_IGNORE_DOT_ENV` | If set the .env file will be ignored. This is good for live to mitigate any performance implications of loading the .env file | +| `SS_HOST` | The hostname to use when it isn't determinable by other means (eg: for CLI commands) | diff --git a/docs/en/04_Changelogs/4.0.0.md b/docs/en/04_Changelogs/4.0.0.md index 8db4ea5fea0..00551006459 100644 --- a/docs/en/04_Changelogs/4.0.0.md +++ b/docs/en/04_Changelogs/4.0.0.md @@ -1352,3 +1352,4 @@ logic early in the bootstrap, this is best placed in the `_config.php` files. * Removed support for _ss_environment.php in favour of .env and first class environment variables * Environment variables now can be set in `.env` file placed in webroot or one level above * Environment variables will be read from the environment as well +* `$_FILE_TO_URL_MAPPING` has been removed and replaced with using `Director.alternate_host` or `SS_HOST` env var diff --git a/docs/en/05_Contributing/05_Making_A_SilverStripe_Core_Release.md b/docs/en/05_Contributing/05_Making_A_SilverStripe_Core_Release.md index 98d87a54695..81ed1aaac83 100644 --- a/docs/en/05_Contributing/05_Making_A_SilverStripe_Core_Release.md +++ b/docs/en/05_Contributing/05_Making_A_SilverStripe_Core_Release.md @@ -58,8 +58,7 @@ Example `.env`: SS_DEFAULT_ADMIN_PASSWORD="password" # Basic CLI hostname - global $_FILE_TO_URL_MAPPING; - $_FILE_TO_URL_MAPPING[__DIR__] = "http://localhost"; + SS_HOST="localhost"; You will also need to be assigned the following permissions. Contact one of the SS staff from diff --git a/main.php b/main.php index c1b121b1196..daa214c5984 100644 --- a/main.php +++ b/main.php @@ -188,8 +188,9 @@ header($_SERVER['SERVER_PROTOCOL'] . " 500 Server Error"); die('SilverStripe Framework requires a $databaseConfig defined.'); } + $host = isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : $_SERVER['SERVER_NAME']; $s = (isset($_SERVER['SSL']) || (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] != 'off')) ? 's' : ''; - $installURL = "http$s://" . $_SERVER['HTTP_HOST'] . BASE_URL . '/install.php'; + $installURL = "http$s://" . $host . BASE_URL . '/install.php'; // The above dirname() will equate to "\" on Windows when installing directly from http://localhost (not using // a sub-directory), this really messes things up in some browsers. Let's get rid of the backslashes diff --git a/src/Control/Director.php b/src/Control/Director.php index b9c6e036604..ba8fc2ac503 100644 --- a/src/Control/Director.php +++ b/src/Control/Director.php @@ -101,6 +101,13 @@ class Director implements TemplateGlobalProvider */ private static $alternate_protocol; + /** + * @config + * + * @var string + */ + private static $alternate_host; + /** * @config * @@ -139,6 +146,15 @@ class Director implements TemplateGlobalProvider */ public static function direct($url, DataModel $model) { + // check allowed hosts + if (getenv('SS_ALLOWED_HOSTS') && !Director::is_cli()) { + $all_allowed_hosts = explode(',', getenv('SS_ALLOWED_HOSTS')); + if (!in_array(static::host(), $all_allowed_hosts)) { + throw new HTTPResponse_Exception('Invalid Host', 400); + } + } + + // Validate $_FILES array before merging it with $_POST foreach ($_FILES as $k => $v) { if (is_array($v['tmp_name'])) { @@ -552,38 +568,70 @@ public static function absoluteURL($url, $relativeParent = self::BASE) } /** - * Returns the domain part of the URL 'http://www.mysite.com'. Returns FALSE is this environment - * variable isn't set. + * A helper to determine the current hostname used to access the site. + * The following are used to determine the host (in order) + * - Director.alternate_host + * - Director.alternate_base_url (if it contains a domain name) + * - Trusted proxy headers + * - HTTP Host header + * - SS_HOST env var + * - SERVER_NAME + * - gethostname() * - * @return bool|string + * @return string */ - public static function protocolAndHost() + public static function host() { - $alternate = Config::inst()->get('SilverStripe\\Control\\Director', 'alternate_base_url'); - if ($alternate) { - if (preg_match('/^(http[^:]*:\/\/[^\/]+)(\/|$)/', $alternate, $matches)) { - return $matches[1]; + $headerOverride = false; + if (TRUSTED_PROXY) { + $headers = (getenv('SS_TRUSTED_PROXY_HOST_HEADER')) ? array(getenv('SS_TRUSTED_PROXY_HOST_HEADER')) : null; + if (!$headers) { + // Backwards compatible defaults + $headers = array('HTTP_X_FORWARDED_HOST'); + } + foreach ($headers as $header) { + if (!empty($_SERVER[$header])) { + // Get the first host, in case there's multiple separated through commas + $headerOverride = strtok($_SERVER[$header], ','); + break; + } } } - if (isset($_SERVER['HTTP_HOST'])) { - return Director::protocol() . $_SERVER['HTTP_HOST']; - } else { - global $_FILE_TO_URL_MAPPING; - if (Director::is_cli() && isset($_FILE_TO_URL_MAPPING)) { - $errorSuggestion = ' You probably want to define ' . - 'an entry in $_FILE_TO_URL_MAPPING that covers "' . Director::baseFolder() . '"'; - } elseif (Director::is_cli()) { - $errorSuggestion = ' You probably want to define $_FILE_TO_URL_MAPPING in ' . - 'your _ss_environment.php as instructed on the "sake" page of the doc.silverstripe.com wiki'; - } else { - $errorSuggestion = ""; + if ($host = static::config()->get('alternate_host')) { + return $host; + } + + if ($baseURL = static::config()->get('alternate_base_url')) { + if (preg_match('/^(http[^:]*:\/\/[^\/]+)(\/|$)/', $baseURL, $matches)) { + return parse_url($baseURL, PHP_URL_HOST); } + } - user_error("Director::protocolAndHost() lacks sufficient information - HTTP_HOST not set." - . $errorSuggestion, E_USER_WARNING); - return false; + if ($headerOverride) { + return $headerOverride; + } + + if (isset($_SERVER['HTTP_HOST'])) { + return $_SERVER['HTTP_HOST']; + } + + if ($host = getenv('SS_HOST')) { + return $host; } + + return isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : gethostname(); + } + + /** + * Returns the domain part of the URL 'http://www.mysite.com'. Returns FALSE is this environment + * variable isn't set. + * + * @return bool|string + */ + public static function protocolAndHost() + { + return static::protocol() . static::host(); } /** @@ -950,7 +998,7 @@ public static function absoluteBaseURLWithAuth() $login = "$_SERVER[PHP_AUTH_USER]:$_SERVER[PHP_AUTH_PW]@"; } - return Director::protocol() . $login . $_SERVER['HTTP_HOST'] . Director::baseURL(); + return Director::protocol() . $login . static::host() . Director::baseURL(); } /** @@ -1052,7 +1100,7 @@ public static function forceSSL($patterns = null, $secureDomain = null) */ public static function forceWWW() { - if (!Director::isDev() && !Director::isTest() && strpos($_SERVER['HTTP_HOST'], 'www') !== 0) { + if (!Director::isDev() && !Director::isTest() && strpos(static::host(), 'www') !== 0) { $destURL = str_replace( Director::protocol(), Director::protocol() . 'www.', @@ -1191,11 +1239,7 @@ public static function isDev() // Check if we are running on one of the test servers $devServers = (array)Config::inst()->get('SilverStripe\\Control\\Director', 'dev_servers'); - if (isset($_SERVER['HTTP_HOST']) && in_array($_SERVER['HTTP_HOST'], $devServers)) { - return true; - } - - return false; + return in_array(static::host(), $devServers); } /** @@ -1223,11 +1267,7 @@ public static function isTest() // Check if we are running on one of the test servers $testServers = (array)Config::inst()->get('SilverStripe\\Control\\Director', 'test_servers'); - if (isset($_SERVER['HTTP_HOST']) && in_array($_SERVER['HTTP_HOST'], $testServers)) { - return true; - } - - return false; + return in_array(static::host(), $testServers); } /** diff --git a/src/Core/Constants.php b/src/Core/Constants.php index fb118881577..46a9153b0a6 100644 --- a/src/Core/Constants.php +++ b/src/Core/Constants.php @@ -61,80 +61,6 @@ define('TRUSTED_PROXY', $trusted); } -/** - * A blank HTTP_HOST value is used to detect command-line execution. - * We update the $_SERVER variable to contain data consistent with the rest of the application. - */ -if (!isset($_SERVER['HTTP_HOST'])) { - // HTTP_HOST, REQUEST_PORT, SCRIPT_NAME, and PHP_SELF - global $_FILE_TO_URL_MAPPING; - if (isset($_FILE_TO_URL_MAPPING)) { - $fullPath = $testPath = realpath($_SERVER['SCRIPT_FILENAME']); - while ($testPath && $testPath != '/' && !preg_match('/^[A-Z]:\\\\$/', $testPath)) { - if (isset($_FILE_TO_URL_MAPPING[$testPath])) { - $url = $_FILE_TO_URL_MAPPING[$testPath] - . str_replace(DIRECTORY_SEPARATOR, '/', substr($fullPath, strlen($testPath))); - - $components = parse_url($url); - $_SERVER['HTTP_HOST'] = $components['host']; - if (!empty($components['port'])) { - $_SERVER['HTTP_HOST'] .= ':' . $components['port']; - } - $_SERVER['SCRIPT_NAME'] = $_SERVER['PHP_SELF'] = $components['path']; - if (!empty($components['port'])) { - $_SERVER['REQUEST_PORT'] = $components['port']; - } - break; - } - $testPath = dirname($testPath); - } - } - - // Everything else - $serverDefaults = array( - 'SERVER_PROTOCOL' => 'HTTP/1.1', - 'HTTP_ACCEPT' => 'text/plain;q=0.5', - 'HTTP_ACCEPT_LANGUAGE' => '*;q=0.5', - 'HTTP_ACCEPT_ENCODING' => '', - 'HTTP_ACCEPT_CHARSET' => 'ISO-8859-1;q=0.5', - 'SERVER_SIGNATURE' => 'Command-line PHP/' . phpversion(), - 'SERVER_SOFTWARE' => 'PHP/' . phpversion(), - 'SERVER_ADDR' => '127.0.0.1', - 'REMOTE_ADDR' => '127.0.0.1', - 'REQUEST_METHOD' => 'GET', - 'HTTP_USER_AGENT' => 'CLI', - ); - - $_SERVER = array_merge($serverDefaults, $_SERVER); - - /** - * If we have an HTTP_HOST value, then we're being called from the webserver and there are some things that - * need checking - */ -} else { - - /** - * Fix HTTP_HOST from reverse proxies - */ - $trustedProxyHeader = (defined('SS_TRUSTED_PROXY_HOST_HEADER')) - ? SS_TRUSTED_PROXY_HOST_HEADER - : 'HTTP_X_FORWARDED_HOST'; - - if (TRUSTED_PROXY && !empty($_SERVER[$trustedProxyHeader])) { - // Get the first host, in case there's multiple separated through commas - $_SERVER['HTTP_HOST'] = strtok($_SERVER[$trustedProxyHeader], ','); - } -} - -// Filter by configured allowed hosts -if (defined('SS_ALLOWED_HOSTS') && php_sapi_name() !== "cli") { - $all_allowed_hosts = explode(',', SS_ALLOWED_HOSTS); - if (!isset($_SERVER['HTTP_HOST']) || !in_array($_SERVER['HTTP_HOST'], $all_allowed_hosts)) { - header('HTTP/1.1 400 Invalid Host', true, 400); - die(); - } -} - /** * Define system paths */ diff --git a/src/Core/Startup/ParameterConfirmationToken.php b/src/Core/Startup/ParameterConfirmationToken.php index b9075614a12..862ed4739a5 100644 --- a/src/Core/Startup/ParameterConfirmationToken.php +++ b/src/Core/Startup/ParameterConfirmationToken.php @@ -2,6 +2,7 @@ namespace SilverStripe\Core\Startup; +use SilverStripe\Control\Director; use SilverStripe\Security\RandomGenerator; /** @@ -203,7 +204,7 @@ protected function currentAbsoluteURL() $parts = array_filter(array( // What's our host - $_SERVER['HTTP_HOST'], + Director::host(), // SilverStripe base self::$alternateBaseURL !== null ? self::$alternateBaseURL : BASE_URL, // And URL including base script (eg: if it's index.php/page/url/) diff --git a/src/Dev/DevelopmentAdmin.php b/src/Dev/DevelopmentAdmin.php index e8bc4124e6e..9b8bd28e63f 100644 --- a/src/Dev/DevelopmentAdmin.php +++ b/src/Dev/DevelopmentAdmin.php @@ -64,31 +64,6 @@ protected function init() return; } - // check for valid url mapping - // lacking this information can cause really nasty bugs, - // e.g. when running Director::test() from a FunctionalTest instance - global $_FILE_TO_URL_MAPPING; - if (Director::is_cli()) { - if (isset($_FILE_TO_URL_MAPPING)) { - $testPath = BASE_PATH; - $matched = false; - while ($testPath && $testPath != "/" && !preg_match('/^[A-Z]:\\\\$/', $testPath)) { - if (isset($_FILE_TO_URL_MAPPING[$testPath])) { - $matched = true; - break; - } - $testPath = dirname($testPath); - } - if (!$matched) { - echo 'Warning: You probably want to define '. - 'an entry in $_FILE_TO_URL_MAPPING that covers "' . Director::baseFolder() . '"' . "\n"; - } - } else { - echo 'Warning: You probably want to define $_FILE_TO_URL_MAPPING in '. - 'your _ss_environment.php as instructed on the "sake" page of the doc.silverstripe.org wiki'."\n"; - } - } - // Backwards compat: Default to "draft" stage, which is important // for tasks like dev/build which call DataObject->requireDefaultRecords(), // but also for other administrative tasks which have assumptions about the default stage. diff --git a/tests/bootstrap/cli.php b/tests/bootstrap/cli.php index b3fb74fed79..dbc3d52d1ec 100644 --- a/tests/bootstrap/cli.php +++ b/tests/bootstrap/cli.php @@ -6,6 +6,22 @@ $_SERVER = array(); } +// We update the $_SERVER variable to contain data consistent with the rest of the application. +$_SERVER = array_merge(array( + 'SERVER_PROTOCOL' => 'HTTP/1.1', + 'HTTP_ACCEPT' => 'text/plain;q=0.5', + 'HTTP_ACCEPT_LANGUAGE' => '*;q=0.5', + 'HTTP_ACCEPT_ENCODING' => '', + 'HTTP_ACCEPT_CHARSET' => 'ISO-8859-1;q=0.5', + 'SERVER_SIGNATURE' => 'Command-line PHP/' . phpversion(), + 'SERVER_SOFTWARE' => 'PHP/' . phpversion(), + 'SERVER_NAME' => 'localhost', + 'SERVER_ADDR' => '127.0.0.1', + 'REMOTE_ADDR' => '127.0.0.1', + 'REQUEST_METHOD' => 'GET', + 'HTTP_USER_AGENT' => 'CLI', +), $_SERVER); + $frameworkPath = dirname(dirname(__FILE__)); $frameworkDir = basename($frameworkPath);