Merge pull request #7520 from open-sausages/pulls/4.0/config-redirect-works

API / BUG - Introduce new request resolver middleware and fix broken forceWWW / forceSSL
This commit is contained in:
Chris Joe 2017-11-02 10:01:58 +13:00 committed by GitHub
commit cd55a039e7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 610 additions and 122 deletions

View File

@ -11,6 +11,7 @@ SilverStripe\Core\Injector\Injector:
SessionMiddleware: '%$SilverStripe\Control\Middleware\SessionMiddleware'
RequestProcessorMiddleware: '%$SilverStripe\Control\RequestProcessor'
FlushMiddleware: '%$SilverStripe\Control\Middleware\FlushMiddleware'
CanonicalURLMiddleware: '%$SilverStripe\Control\Middleware\CanonicalURLMiddleware'
SilverStripe\Control\Middleware\AllowedHostsMiddleware:
properties:
AllowedHosts: '`SS_ALLOWED_HOSTS`'
@ -37,3 +38,12 @@ After:
SilverStripe\Core\Injector\Injector:
# Note: If Director config changes, take note it will affect this config too
SilverStripe\Core\Startup\ErrorDirector: '%$SilverStripe\Control\Director'
---
Name: canonicalurls
---
SilverStripe\Core\Injector\Injector:
SilverStripe\Control\Middleware\CanonicalURLMiddleware:
properties:
ForceSSL: false
ForceWWW: false

View File

@ -3,6 +3,7 @@
namespace SilverStripe\Control;
use SilverStripe\CMS\Model\SiteTree;
use SilverStripe\Control\Middleware\CanonicalURLMiddleware;
use SilverStripe\Control\Middleware\HTTPMiddlewareAware;
use SilverStripe\Core\Config\Configurable;
use SilverStripe\Core\Environment;
@ -242,7 +243,7 @@ class Director implements TemplateGlobalProvider
// If a port is mentioned in the absolute URL, be sure to add that into the HTTP host
$newVars['_SERVER']['HTTP_HOST'] = isset($bits['port'])
? $bits['host'].':'.$bits['port']
? $bits['host'] . ':' . $bits['port']
: $bits['host'];
}
@ -595,53 +596,34 @@ class Director implements TemplateGlobalProvider
* Turns an absolute URL or folder into one that's relative to the root of the site. This is useful
* when turning a URL into a filesystem reference, or vice versa.
*
* @param string $url Accepts both a URL or a filesystem path.
* Note: You should check {@link Director::is_site_url()} if making an untrusted url relative prior
* to calling this function.
*
* @param string $url Accepts both a URL or a filesystem path.
* @return string
*/
public static function makeRelative($url)
{
// Allow for the accidental inclusion whitespace and // in the URL
$url = trim(preg_replace('#([^:])//#', '\\1/', $url));
$url = preg_replace('#([^:])//#', '\\1/', trim($url));
$base1 = self::absoluteBaseURL();
$baseDomain = substr($base1, strlen(self::protocol()));
// If using a real url, remove protocol / hostname / auth / port
if (preg_match('#^(?<protocol>https?:)?//(?<hostpart>[^/]*)(?<url>(/.*)?)$#i', $url, $matches)) {
$url = $matches['url'];
}
// Only bother comparing the URL to the absolute version if $url looks like a URL.
if (preg_match('/^https?[^:]*:\/\//', $url, $matches)) {
$urlProtocol = $matches[0];
$urlWithoutProtocol = substr($url, strlen($urlProtocol));
// Empty case
if (trim($url, '\\/') === '') {
return '';
}
// If we are already looking at baseURL, return '' (substr will return false)
if ($url == $base1) {
return '';
} elseif (substr($url, 0, strlen($base1)) == $base1) {
return substr($url, strlen($base1));
} elseif (substr($base1, -1) == "/" && $url == substr($base1, 0, -1)) {
// Convert http://www.mydomain.com/mysitedir to ''
return "";
// Remove base folder or url
foreach ([self::baseFolder(), self::baseURL()] as $base) {
// Ensure single / doesn't break comparison (unless it would make base empty)
$base = rtrim($base, '\\/') ?: $base;
if (stripos($url, $base) === 0) {
return ltrim(substr($url, strlen($base)), '\\/');
}
if (substr($urlWithoutProtocol, 0, strlen($baseDomain)) == $baseDomain) {
return substr($urlWithoutProtocol, strlen($baseDomain));
}
}
// test for base folder, e.g. /var/www
$base2 = self::baseFolder();
if (substr($url, 0, strlen($base2)) == $base2) {
return substr($url, strlen($base2));
}
// Test for relative base url, e.g. mywebsite/ if the full URL is http://localhost/mywebsite/
$base3 = self::baseURL();
if (substr($url, 0, strlen($base3)) == $base3) {
return substr($url, strlen($base3));
}
// Test for relative base url, e.g mywebsite/ if the full url is localhost/myswebsite
if (substr($url, 0, strlen($baseDomain)) == $baseDomain) {
return substr($url, strlen($baseDomain));
}
// Nothing matched, fall back to returning the original URL
@ -697,10 +679,10 @@ class Director implements TemplateGlobalProvider
{
// Strip off the query and fragment parts of the URL before checking
if (($queryPosition = strpos($url, '?')) !== false) {
$url = substr($url, 0, $queryPosition-1);
$url = substr($url, 0, $queryPosition - 1);
}
if (($hashPosition = strpos($url, '#')) !== false) {
$url = substr($url, 0, $hashPosition-1);
$url = substr($url, 0, $hashPosition - 1);
}
$colonPosition = strpos($url, ':');
$slashPosition = strpos($url, '/');
@ -809,7 +791,7 @@ class Director implements TemplateGlobalProvider
$login = "$_SERVER[PHP_AUTH_USER]:$_SERVER[PHP_AUTH_PW]@";
}
return Director::protocol($request) . $login . static::host($request) . Director::baseURL();
return Director::protocol($request) . $login . static::host($request) . Director::baseURL();
}
/**
@ -855,62 +837,29 @@ class Director implements TemplateGlobalProvider
*
* @param array $patterns Array of regex patterns to match URLs that should be HTTPS.
* @param string $secureDomain Secure domain to redirect to. Defaults to the current domain.
* @return bool true if already on SSL, false if doesn't match patterns (or cannot redirect)
* @throws HTTPResponse_Exception Throws exception with redirect, if successful
* @param HTTPRequest|null $request Request object to check
*/
public static function forceSSL($patterns = null, $secureDomain = null)
public static function forceSSL($patterns = null, $secureDomain = null, HTTPRequest $request = null)
{
// Already on SSL
if (static::is_https()) {
return true;
}
// Can't redirect without a url
if (!isset($_SERVER['REQUEST_URI'])) {
return false;
}
$handler = CanonicalURLMiddleware::singleton()->setForceSSL(true);
if ($patterns) {
$matched = false;
$relativeURL = self::makeRelative(Director::absoluteURL($_SERVER['REQUEST_URI']));
// protect portions of the site based on the pattern
foreach ($patterns as $pattern) {
if (preg_match($pattern, $relativeURL)) {
$matched = true;
break;
}
}
if (!$matched) {
return false;
}
$handler->setForceSSLPatterns($patterns);
}
// if an domain is specified, redirect to that instead of the current domain
if (!$secureDomain) {
$secureDomain = static::host();
if ($secureDomain) {
$handler->setForceSSLDomain($secureDomain);
}
$url = 'https://' . $secureDomain . $_SERVER['REQUEST_URI'];
// Force redirect
self::force_redirect($url);
return true;
$handler->throwRedirectIfNeeded($request);
}
/**
* Force a redirect to a domain starting with "www."
*
* @param HTTPRequest $request
*/
public static function forceWWW()
public static function forceWWW(HTTPRequest $request = null)
{
if (!Director::isDev() && !Director::isTest() && strpos(static::host(), 'www') !== 0) {
$destURL = str_replace(
Director::protocol(),
Director::protocol() . 'www.',
Director::absoluteURL($_SERVER['REQUEST_URI'])
);
self::force_redirect($destURL);
}
$handler = CanonicalURLMiddleware::singleton()->setForceWWW(true);
$handler->throwRedirectIfNeeded($request);
}
/**
@ -947,7 +896,7 @@ class Director implements TemplateGlobalProvider
* Can also be checked with {@link Director::isDev()}, {@link Director::isTest()}, and
* {@link Director::isLive()}.
*
* @return bool
* @return string
*/
public static function get_environment_type()
{

View File

@ -0,0 +1,331 @@
<?php
namespace SilverStripe\Control\Middleware;
use SilverStripe\Control\Controller;
use SilverStripe\Control\Director;
use SilverStripe\Control\HTTP;
use SilverStripe\Control\HTTPRequest;
use SilverStripe\Control\HTTPResponse;
use SilverStripe\Control\HTTPResponse_Exception;
use SilverStripe\Core\CoreKernel;
use SilverStripe\Core\Injector\Injectable;
use SilverStripe\Core\Injector\Injector;
/**
* Allows events to be registered and passed through middleware.
* Useful for event registered prior to the beginning of a middleware chain.
*/
class CanonicalURLMiddleware implements HTTPMiddleware
{
use Injectable;
/**
* Set if we should redirect to WWW
*
* @var bool
*/
protected $forceWWW = false;
/**
* Set if we should force SSL
*
* @var bool
*/
protected $forceSSL = false;
/**
* Redirect type
*
* @var int
*/
protected $redirectType = 301;
/**
* Environment variables this middleware is enabled in, or a fixed boolean flag to
* apply to all environments
*
* @var array|bool
*/
protected $enabledEnvs = [
CoreKernel::LIVE
];
/**
* If forceSSL is enabled, this is the list of patterns that the url must match (at least one)
*
* @var array Array of regexps to match against relative url
*/
protected $forceSSLPatterns = [];
/**
* SSL Domain to use
*
* @var string
*/
protected $forceSSLDomain = null;
/**
* @return array
*/
public function getForceSSLPatterns()
{
return $this->forceSSLPatterns ?: [];
}
/**
* @param array $forceSSLPatterns
* @return $this
*/
public function setForceSSLPatterns($forceSSLPatterns)
{
$this->forceSSLPatterns = $forceSSLPatterns;
return $this;
}
/**
* @return string
*/
public function getForceSSLDomain()
{
return $this->forceSSLDomain;
}
/**
* @param string $forceSSLDomain
* @return $this
*/
public function setForceSSLDomain($forceSSLDomain)
{
$this->forceSSLDomain = $forceSSLDomain;
return $this;
}
/**
* @return bool
*/
public function getForceWWW()
{
return $this->forceWWW;
}
/**
* @param bool $forceWWW
* @return $this
*/
public function setForceWWW($forceWWW)
{
$this->forceWWW = $forceWWW;
return $this;
}
/**
* @return bool
*/
public function getForceSSL()
{
return $this->forceSSL;
}
/**
* @param bool $forceSSL
* @return $this
*/
public function setForceSSL($forceSSL)
{
$this->forceSSL = $forceSSL;
return $this;
}
/**
* Generate response for the given request
*
* @param HTTPRequest $request
* @param callable $delegate
* @return HTTPResponse
*/
public function process(HTTPRequest $request, callable $delegate)
{
// Handle any redirects
$redirect = $this->getRedirect($request);
if ($redirect) {
return $redirect;
}
return $delegate($request);
}
/**
* Given request object determine if we should redirect.
*
* @param HTTPRequest $request Pre-validated request object
* @return HTTPResponse|null If a redirect is needed return the response
*/
protected function getRedirect(HTTPRequest $request)
{
// Check global disable
if (!$this->isEnabled()) {
return null;
}
// Get properties of current request
$host = $request->getHost();
$scheme = $request->getScheme();
// Check https
if ($this->requiresSSL($request)) {
$scheme = 'https';
// Promote ssl domain if configured
$host = $this->getForceSSLDomain() ?: $host;
}
// Check www.
if ($this->getForceWWW() && strpos($host, 'www.') !== 0) {
$host = "www.{$host}";
}
// No-op if no changes
if ($request->getScheme() === $scheme && $request->getHost() === $host) {
return null;
}
// Rebuild url for request
$url = Controller::join_links("{$scheme}://{$host}", Director::baseURL(), $request->getURL(true));
// Force redirect
$response = new HTTPResponse();
$response->redirect($url, $this->getRedirectType());
HTTP::add_cache_headers($response);
return $response;
}
/**
* Handles redirection to canonical urls outside of the main middleware chain
* using HTTPResponseException.
* Will not do anything if a current HTTPRequest isn't available
*
* @param HTTPRequest|null $request Allow HTTPRequest to be used for the base comparison
* @throws HTTPResponse_Exception
*/
public function throwRedirectIfNeeded(HTTPRequest $request = null)
{
$request = $this->getOrValidateRequest($request);
if (!$request) {
return;
}
$response = $this->getRedirect($request);
if ($response) {
throw new HTTPResponse_Exception($response);
}
}
/**
* Return a valid request, if one is available, or null if none is available
*
* @param HTTPRequest $request
* @return mixed|null
*/
protected function getOrValidateRequest(HTTPRequest $request = null)
{
if ($request instanceof HTTPRequest) {
return $request;
}
if (Injector::inst()->has(HTTPRequest::class)) {
return Injector::inst()->get(HTTPRequest::class);
}
return null;
}
/**
* Check if a redirect for SSL is necessary
*
* @param HTTPRequest $request
* @return bool
*/
protected function requiresSSL(HTTPRequest $request)
{
// Check if force SSL is enabled
if (!$this->getForceSSL()) {
return false;
}
// Already on SSL
if ($request->getScheme() === 'https') {
return false;
}
// Veto if any existing patterns fail
$patterns = $this->getForceSSLPatterns();
if (!$patterns) {
return true;
}
// Filter redirect based on url
$relativeURL = $request->getURL(true);
foreach ($patterns as $pattern) {
if (preg_match($pattern, $relativeURL)) {
return true;
}
}
// No patterns match
return false;
}
/**
* @return int
*/
public function getRedirectType()
{
return $this->redirectType;
}
/**
* @param int $redirectType
* @return $this
*/
public function setRedirectType($redirectType)
{
$this->redirectType = $redirectType;
return $this;
}
/**
* Get enabled flag, or list of environments to enable in
*
* @return array|bool
*/
public function getEnabledEnvs()
{
return $this->enabledEnvs;
}
/**
* @param array|bool $enabledEnvs
* @return $this
*/
public function setEnabledEnvs($enabledEnvs)
{
$this->enabledEnvs = $enabledEnvs;
return $this;
}
/**
* Ensure this middleware is enabled
*/
protected function isEnabled()
{
// At least one redirect must be enabled
if (!$this->getForceWWW() && !$this->getForceSSL()) {
return false;
}
// Filter by env vars
$enabledEnvs = $this->getEnabledEnvs();
if (is_bool($enabledEnvs)) {
return $enabledEnvs;
}
return empty($enabledEnvs) || in_array(Director::get_environment_type(), $enabledEnvs);
}
}

View File

@ -92,10 +92,12 @@ class TinyMCECombinedGenerator implements TinyMCEScriptGenerator, Flushable
foreach ($config->getPlugins() as $plugin => $path) {
// Add external plugin
if ($path) {
// Skip external urls
if (is_string($path) && !Director::is_site_url($path)) {
continue;
}
// Convert URLS to relative paths
if (is_string($path)
&& (Director::is_absolute_url($path) || strpos($path, '/') === 0)
) {
if (is_string($path)) {
$path = Director::makeRelative($path);
}
// Ensure file exists

View File

@ -14,6 +14,8 @@ use DOMDocument;
*
* It's designed to allow dependancy injection to replace the standard HTML4 version with one that
* handles XHTML or HTML5 instead
*
* @mixin DOMDocument
*/
abstract class HTMLValue extends ViewableData
{

View File

@ -7,7 +7,7 @@ use SilverStripe\Control\HTTPRequest;
use SilverStripe\Control\HTTPRequestBuilder;
use SilverStripe\Control\HTTPResponse;
use SilverStripe\Control\HTTPResponse_Exception;
use SilverStripe\Control\Middleware\HTTPMiddleware;
use SilverStripe\Control\Middleware\CanonicalURLMiddleware;
use SilverStripe\Control\Middleware\RequestHandlerMiddlewareAdapter;
use SilverStripe\Control\Middleware\TrustedProxyMiddleware;
use SilverStripe\Control\RequestProcessor;
@ -30,6 +30,9 @@ class DirectorTest extends SapphireTest
{
parent::setUp();
Director::config()->set('alternate_base_url', 'http://www.mysite.com/');
// Ensure redirects enabled on all environments
CanonicalURLMiddleware::singleton()->setEnabledEnvs(true);
$this->expectedRedirect = null;
}
@ -239,26 +242,132 @@ class DirectorTest extends SapphireTest
$this->assertTrue(Director::is_relative_url('/relative/#=http://test.com'));
}
public function testMakeRelative()
/**
* @return array
*/
public function providerMakeRelative()
{
$siteUrl = Director::absoluteBaseURL();
$siteUrlNoProtocol = preg_replace('/https?:\/\//', '', $siteUrl);
return [
// Resilience to slash position
[
'http://www.mysite.com/base/folder',
'http://www.mysite.com/base/folder',
''
],
[
'http://www.mysite.com/base/folder',
'http://www.mysite.com/base/folder/',
''
],
[
'http://www.mysite.com/base/folder/',
'http://www.mysite.com/base/folder',
''
],
[
'http://www.mysite.com/',
'http://www.mysite.com/',
''
],
[
'http://www.mysite.com/',
'http://www.mysite.com',
''
],
[
'http://www.mysite.com',
'http://www.mysite.com/',
''
],
[
'http://www.mysite.com/base/folder',
'http://www.mysite.com/base/folder/page',
'page'
],
[
'http://www.mysite.com/',
'http://www.mysite.com/page/',
'page/'
],
// Parsing protocol safely
[
'http://www.mysite.com/base/folder',
'https://www.mysite.com/base/folder',
''
],
[
'https://www.mysite.com/base/folder',
'http://www.mysite.com/base/folder/testpage',
'testpage'
],
[
'http://www.mysite.com/base/folder',
'//www.mysite.com/base/folder/testpage',
'testpage'
],
// Dirty input
[
'http://www.mysite.com/base/folder',
' https://www.mysite.com/base/folder/testpage ',
'testpage'
],
[
'http://www.mysite.com/base/folder',
'//www.mysite.com/base//folder/testpage//subpage',
'testpage/subpage'
],
// Non-http protocol isn't modified
[
'http://www.mysite.com/base/folder',
'ftp://test.com',
'ftp://test.com'
],
// Alternate hostnames are redirected
[
'https://www.mysite.com/base/folder',
'http://mysite.com/base/folder/testpage',
'testpage'
],
[
'http://www.otherdomain.com/base/folder',
'//www.mysite.com/base/folder/testpage',
'testpage'
],
// Base folder is found
[
'http://www.mysite.com/base/folder',
BASE_PATH . '/some/file.txt',
'some/file.txt',
],
// querystring is protected
[
'http://www.mysite.com/base/folder',
'//www.mysite.com/base//folder/testpage//subpage?args=hello',
'testpage/subpage?args=hello'
],
[
'http://www.mysite.com/base/folder',
'//www.mysite.com/base//folder/?args=hello',
'?args=hello'
],
];
}
$this->assertEquals(Director::makeRelative("$siteUrl"), '');
$this->assertEquals(Director::makeRelative("https://$siteUrlNoProtocol"), '');
$this->assertEquals(Director::makeRelative("http://$siteUrlNoProtocol"), '');
$this->assertEquals(Director::makeRelative(" $siteUrl/testpage "), 'testpage');
$this->assertEquals(Director::makeRelative("$siteUrlNoProtocol/testpage"), 'testpage');
$this->assertEquals(Director::makeRelative('ftp://test.com'), 'ftp://test.com');
$this->assertEquals(Director::makeRelative('http://test.com'), 'http://test.com');
$this->assertEquals(Director::makeRelative('relative'), 'relative');
$this->assertEquals(Director::makeRelative("$siteUrl/?url=http://test.com"), '?url=http://test.com');
$this->assertEquals("test", Director::makeRelative("https://".$siteUrlNoProtocol."/test"));
$this->assertEquals("test", Director::makeRelative("http://".$siteUrlNoProtocol."/test"));
/**
* @dataProvider providerMakeRelative
* @param string $baseURL Site base URL
* @param string $requestURL Request URL
* @param string $relativeURL Expected relative URL
*/
public function testMakeRelative($baseURL, $requestURL, $relativeURL)
{
Director::config()->set('alternate_base_url', $baseURL);
$actualRelative = Director::makeRelative($requestURL);
$this->assertEquals(
$relativeURL,
$actualRelative,
"Expected relativeURL of {$requestURL} to be {$relativeURL}"
);
}
/**
@ -412,43 +521,101 @@ class DirectorTest extends SapphireTest
);
}
public function testForceWWW()
{
$this->expectExceptionRedirect('http://www.mysite.com/some-url');
Director::mockRequest(function ($request) {
Injector::inst()->registerService($request, HTTPRequest::class);
Director::forceWWW();
}, 'http://mysite.com/some-url');
}
public function testPromisedForceWWW()
{
Director::forceWWW();
// Flag is set but not redirected yet
$middleware = CanonicalURLMiddleware::singleton();
$this->assertTrue($middleware->getForceWWW());
// Middleware forces the redirection eventually
/** @var HTTPResponse $response */
$response = Director::mockRequest(function ($request) use ($middleware) {
return $middleware->process($request, function ($request) {
return null;
});
}, 'http://mysite.com/some-url');
// Middleware returns non-exception redirect
$this->assertEquals('http://www.mysite.com/some-url', $response->getHeader('Location'));
$this->assertEquals(301, $response->getStatusCode());
}
public function testForceSSLProtectsEntireSite()
{
$this->expectExceptionRedirect('https://www.mysite.com/some-url');
Director::mockRequest(function () {
Director::mockRequest(function ($request) {
Injector::inst()->registerService($request, HTTPRequest::class);
Director::forceSSL();
}, '/some-url');
}, 'http://www.mysite.com/some-url');
}
public function testPromisedForceSSL()
{
Director::forceSSL();
// Flag is set but not redirected yet
$middleware = CanonicalURLMiddleware::singleton();
$this->assertTrue($middleware->getForceSSL());
// Middleware forces the redirection eventually
/** @var HTTPResponse $response */
$response = Director::mockRequest(function ($request) use ($middleware) {
return $middleware->process($request, function ($request) {
return null;
});
}, 'http://www.mysite.com/some-url');
// Middleware returns non-exception redirect
$this->assertEquals('https://www.mysite.com/some-url', $response->getHeader('Location'));
$this->assertEquals(301, $response->getStatusCode());
}
public function testForceSSLOnTopLevelPagePattern()
{
// Expect admin to trigger redirect
$this->expectExceptionRedirect('https://www.mysite.com/admin');
Director::mockRequest(function () {
Director::mockRequest(function (HTTPRequest $request) {
Injector::inst()->registerService($request, HTTPRequest::class);
Director::forceSSL(array('/^admin/'));
}, '/admin');
}, 'http://www.mysite.com/admin');
}
public function testForceSSLOnSubPagesPattern()
{
// Expect to redirect to security login page
$this->expectExceptionRedirect('https://www.mysite.com/Security/login');
Director::mockRequest(function () {
Director::mockRequest(function (HTTPRequest $request) {
Injector::inst()->registerService($request, HTTPRequest::class);
Director::forceSSL(array('/^Security/'));
}, '/Security/login');
}, 'http://www.mysite.com/Security/login');
}
public function testForceSSLWithPatternDoesNotMatchOtherPages()
{
// Not on same url should not trigger redirect
Director::mockRequest(function () {
$this->assertFalse(Director::forceSSL(array('/^admin/')));
}, Director::baseURL() . 'normal-page');
$response = Director::mockRequest(function (HTTPRequest $request) {
Injector::inst()->registerService($request, HTTPRequest::class);
Director::forceSSL(array('/^admin/'));
}, 'http://www.mysite.com/normal-page');
$this->assertNull($response, 'Non-matching patterns do not trigger redirect');
// nested url should not triger redirect either
Director::mockRequest(function () {
$this->assertFalse(Director::forceSSL(array('/^admin/', '/^Security/')));
}, Director::baseURL() . 'just-another-page/sub-url');
$response = Director::mockRequest(function (HTTPRequest $request) {
Injector::inst()->registerService($request, HTTPRequest::class);
Director::forceSSL(array('/^admin/', '/^Security/'));
}, 'http://www.mysite.com/just-another-page/sub-url');
$this->assertNull($response, 'Non-matching patterns do not trigger redirect');
}
public function testForceSSLAlternateDomain()
@ -456,8 +623,35 @@ class DirectorTest extends SapphireTest
// Ensure that forceSSL throws the appropriate exception
$this->expectExceptionRedirect('https://secure.mysite.com/admin');
Director::mockRequest(function (HTTPRequest $request) {
Injector::inst()->registerService($request, HTTPRequest::class);
return Director::forceSSL(array('/^admin/'), 'secure.mysite.com');
}, Director::baseURL() . 'admin');
}, 'http://www.mysite.com/admin');
}
/**
* Test that combined forceWWW and forceSSL combine safely
*/
public function testForceSSLandForceWWW()
{
Director::forceWWW();
Director::forceSSL();
// Flag is set but not redirected yet
$middleware = CanonicalURLMiddleware::singleton();
$this->assertTrue($middleware->getForceWWW());
$this->assertTrue($middleware->getForceSSL());
// Middleware forces the redirection eventually
/** @var HTTPResponse $response */
$response = Director::mockRequest(function ($request) use ($middleware) {
return $middleware->process($request, function ($request) {
return null;
});
}, 'http://mysite.com/some-url');
// Middleware returns non-exception redirect
$this->assertEquals('https://www.mysite.com/some-url', $response->getHeader('Location'));
$this->assertEquals(301, $response->getStatusCode());
}
/**