mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 14:05:37 +02:00
BUG Fix forceWWW and forceSSL not working in _config.php
API Introduce CanonicalURLMiddleware BUG Fix Director::makeRelative() failing on multi-domain sites
This commit is contained in:
parent
921bf7df1e
commit
9d3277f3d3
@ -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
|
||||
|
||||
|
@ -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()
|
||||
{
|
||||
|
331
src/Control/Middleware/CanonicalURLMiddleware.php
Normal file
331
src/Control/Middleware/CanonicalURLMiddleware.php
Normal 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);
|
||||
}
|
||||
}
|
@ -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
|
||||
|
@ -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
|
||||
{
|
||||
|
@ -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());
|
||||
}
|
||||
|
||||
/**
|
||||
|
Loading…
Reference in New Issue
Block a user