NEW Director::host() to determine host name of site

This commit is contained in:
Daniel Hensby 2017-01-30 15:35:43 +00:00 committed by Daniel Hensby
parent 6e096f6172
commit 8c8231c03e
No known key found for this signature in database
GPG Key ID: E38EC566FE29EB66
12 changed files with 127 additions and 156 deletions

View File

@ -20,6 +20,21 @@ if(PHP_SAPI != "cli" && PHP_SAPI != "cgi" && PHP_SAPI != "cgi-fcgi") {
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
*/

View File

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

View File

@ -44,25 +44,21 @@ This currently only works on UNIX like systems, not on Windows.
Sometimes SilverStripe needs to know the URL of your site. For example, when sending an email or generating static
files. When you're visiting the site in a web browser this is easy to work out, but when executing scripts on the
command line, it has no way of knowing. To work this out, add lines to your
[_ss_environment.php](/getting_started/environment_management) file.
command line, it has no way of knowing. To work this out, there are several ways to resolve this. You can set alternate
base URLs, hosts, and protocol.
:::php
global $_FILE_TO_URL_MAPPING;
eg:
$_FILE_TO_URL_MAPPING['/Users/sminnee/Sites'] = 'http://localhost';
```yml
SilverStripe\Control\Director:
alternate_base_url: 'https://example.com/'
```
The above statement tells SilverStripe that anything executed under the `/Users/sminnee/Sites` directory will have the
base URL `http://localhost`. The site `/Users/sminnee/Sites/my_silverstripe_project` will translate to the URL
`http://localhost/my_silverstripe_project`.
Alternatively you can use the `SS_HOST` environment variable to set a fallback hostname:
You can add multiple file to url mapping definitions. The most specific mapping will be used.
:::php
global $_FILE_TO_URL_MAPPING;
$_FILE_TO_URL_MAPPING['/Users/sminnee/Sites'] = 'http://localhost';
$_FILE_TO_URL_MAPPING['/Users/sminnee/Sites/my_silverstripe_project'] = 'http://project.localhost';
```
SS_HOST="localhost"
```
### Usage

View File

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

View File

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

View File

@ -188,8 +188,9 @@ if(!isset($databaseConfig) || !isset($databaseConfig['database']) || !$databaseC
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

View File

@ -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'])) {
@ -551,6 +567,62 @@ class Director implements TemplateGlobalProvider
return Controller::join_links($parent, $url);
}
/**
* 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 string
*/
public static function host()
{
$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 ($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);
}
}
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.
@ -559,31 +631,7 @@ class Director implements TemplateGlobalProvider
*/
public static function protocolAndHost()
{
$alternate = Config::inst()->get('SilverStripe\\Control\\Director', 'alternate_base_url');
if ($alternate) {
if (preg_match('/^(http[^:]*:\/\/[^\/]+)(\/|$)/', $alternate, $matches)) {
return $matches[1];
}
}
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 = "";
}
user_error("Director::protocolAndHost() lacks sufficient information - HTTP_HOST not set."
. $errorSuggestion, E_USER_WARNING);
return false;
}
return static::protocol() . static::host();
}
/**
@ -950,7 +998,7 @@ class Director implements TemplateGlobalProvider
$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 @@ class Director implements TemplateGlobalProvider
*/
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 @@ class Director implements TemplateGlobalProvider
// 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 @@ class Director implements TemplateGlobalProvider
// 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);
}
/**

View File

@ -61,80 +61,6 @@ if (!defined('TRUSTED_PROXY')) {
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
*/

View File

@ -2,6 +2,7 @@
namespace SilverStripe\Core\Startup;
use SilverStripe\Control\Director;
use SilverStripe\Security\RandomGenerator;
/**
@ -203,7 +204,7 @@ class ParameterConfirmationToken
$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/)

View File

@ -64,31 +64,6 @@ class DevelopmentAdmin extends Controller
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.

View File

@ -6,6 +6,22 @@ if (!$_SERVER) {
$_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);

View File

@ -42,7 +42,7 @@ class DevAdminControllerTest extends FunctionalTest
public function testGoodRegisteredControllerOutput()
{
// Check for the controller running from the registered url above
// (we use contains rather than equals because sometimes you get Warning: You probably want to define an entry in $_FILE_TO_URL_MAPPING)
// (we use contains rather than equals because sometimes you get a warning)
$this->assertContains(Controller1::OK_MSG, $this->getCapture('/dev/x1'));
$this->assertContains(Controller1::OK_MSG, $this->getCapture('/dev/x1/y1'));
}