Merge pull request #4218 from tractorcow/pulls/4.0/fix-directortest

API New default behaviour for Director::getAbsoluteUrl
This commit is contained in:
Ingo Schommer 2015-06-18 21:42:23 +12:00
commit 7b47501296
2 changed files with 126 additions and 57 deletions

View File

@ -16,6 +16,21 @@
*/ */
class Director implements TemplateGlobalProvider { class Director implements TemplateGlobalProvider {
/**
* Specifies this url is relative to the base
*/
const BASE = 'BASE';
/**
* Specifies this url is relative to the site root
*/
const ROOT = 'ROOT';
/**
* specifies this url is relative to the current request
*/
const REQUEST = 'REQUEST';
static private $urlParams; static private $urlParams;
static private $rules = array(); static private $rules = array();
@ -428,36 +443,56 @@ class Director implements TemplateGlobalProvider {
/** /**
* Turns the given URL into an absolute URL. * Turns the given URL into an absolute URL.
* By default non-site root relative urls will be evaluated relative to the current request. * By default non-site root relative urls will be evaluated relative to the current base_url
* *
* @param string $url URL To transform to absolute * @param string $url URL To transform to absolute
* @param bool $relativeToSiteBase Flag indicating if non-site root relative urls should be * @param string $relativeParent Method to use for evaluating relative urls.
* evaluated relative to the site BaseURL instead of the current url. * Either one of BASE (baseurl), ROOT (site root), or REQUEST (requested page).
* Defaults to BASE, which is the same behaviour as template url resolution.
* Igored if the url is absolute or site root.
* @return string The fully qualified URL * @return string The fully qualified URL
*/ */
public static function absoluteURL($url, $relativeToSiteBase = false) { public static function absoluteURL($url, $relativeParent = self::BASE) {
if(!isset($_SERVER['REQUEST_URI'])) return false; if(is_bool($relativeParent)) {
// Deprecate old boolean second parameter
//a url of . or ./ is the same as an empty url Deprecation::notice('5.0', 'Director::absoluteURL takes an explicit parent for relative url');
if ($url == '.' || $url == './') { $relativeParent = $relativeParent ? self::BASE : self::REQUEST;
$url = '';
} }
if(strpos($url,'/') === false && !$relativeToSiteBase) { // Check if there is already a protocol given
//if there's no URL we want to force a trailing slash on the link if(preg_match('/^http(s?):\/\//', $url)) {
if (!$url) { return $url;
$url = '/'; }
// Absolute urls without protocol are added
// E.g. //google.com -> http://google.com
if(strpos($url, '//') === 0) {
return self::protocol() . substr($url, 2);
}
// Determine method for mapping the parent to this relative url
if($relativeParent === self::ROOT || self::is_root_relative_url($url)) {
// Root relative urls always should be evaluated relative to the root
$parent = self::protocolAndHost();
} elseif($relativeParent === self::REQUEST) {
// Request relative urls rely on the REQUEST_URI param (old default behaviour)
if(!isset($_SERVER['REQUEST_URI'])) {
return false;
} }
$url = Controller::join_links(dirname($_SERVER['REQUEST_URI'] . 'x'), $url); $parent = dirname($_SERVER['REQUEST_URI'] . 'x');
} else {
// Default to respecting site base_url
$parent = self::absoluteBaseURL();
} }
if(substr($url,0,4) != "http") { // Map empty urls to relative slash and join to base
if(strpos($url, '/') !== 0) $url = Director::baseURL() . $url; if(empty($url) || $url === '.' || $url === './') {
// Sometimes baseURL() can return a full URL instead of just a path $url = '/';
if(substr($url,0,4) != "http") $url = self::protocolAndHost() . $url;
} }
return Controller::join_links($parent, $url);
return $url;
} }
/** /**
@ -615,7 +650,7 @@ class Director implements TemplateGlobalProvider {
// Allow for the accidental inclusion whitespace and // in the URL // Allow for the accidental inclusion whitespace and // in the URL
$url = trim(preg_replace('#([^:])//#', '\\1/', $url)); $url = trim(preg_replace('#([^:])//#', '\\1/', $url));
$base1 = self::absoluteBaseURL(); $base1 = self::absoluteBaseURL();
$baseDomain = substr($base1, strlen(self::protocol())); $baseDomain = substr($base1, strlen(self::protocol()));
// Only bother comparing the URL to the absolute version if $url looks like a URL. // Only bother comparing the URL to the absolute version if $url looks like a URL.
@ -667,10 +702,22 @@ class Director implements TemplateGlobalProvider {
* @return bool * @return bool
*/ */
public static function is_absolute($path) { public static function is_absolute($path) {
if(empty($path)) return false;
if($path[0] == '/' || $path[0] == '\\') return true; if($path[0] == '/' || $path[0] == '\\') return true;
return preg_match('/^[a-zA-Z]:[\\\\\/]/', $path) == 1; return preg_match('/^[a-zA-Z]:[\\\\\/]/', $path) == 1;
} }
/**
* Determine if the url is root relative (i.e. starts with /, but not with //)
* SilverStripe considers root relative urls as a subset of relative urls
*
* @param string $url
* @return bool True if this url is root relative
*/
public static function is_root_relative_url($url) {
return strpos($url, '/') === 0 && strpos($url, '//') !== 0;
}
/** /**
* Checks if a given URL is absolute (e.g. starts with 'http://' etc.). * Checks if a given URL is absolute (e.g. starts with 'http://' etc.).
* URLs beginning with "//" are treated as absolute, as browsers take this to mean * URLs beginning with "//" are treated as absolute, as browsers take this to mean
@ -700,8 +747,8 @@ class Director implements TemplateGlobalProvider {
// Base check for existence of a host on a compliant URL // Base check for existence of a host on a compliant URL
parse_url($url, PHP_URL_HOST) parse_url($url, PHP_URL_HOST)
// Check for more than one leading slash without a protocol. // Check for more than one leading slash without a protocol.
// While not a RFC compliant absolute URL, it is completed to a valid URL by some browsers, // While not a RFC compliant absolute URL, it is completed to a valid URL by some browsers,
// and hence a potential security risk. Single leading slashes are not an issue though. // and hence a potential security risk. Single leading slashes are not an issue though.
|| preg_match('%^\s*/{2,}%', $url) || preg_match('%^\s*/{2,}%', $url)
|| ( || (
// If a colon is found, check if it's part of a valid scheme definition // If a colon is found, check if it's part of a valid scheme definition
@ -713,13 +760,13 @@ class Director implements TemplateGlobalProvider {
} }
/** /**
* Checks if a given URL is relative by checking {@link is_absolute_url()}. * Checks if a given URL is relative (or root relative) by checking {@link is_absolute_url()}
* *
* @param string $url * @param string $url
* @return boolean * @return boolean
*/ */
public static function is_relative_url($url) { public static function is_relative_url($url) {
return (!Director::is_absolute_url($url)); return !static::is_absolute_url($url);
} }
/** /**
@ -747,7 +794,7 @@ class Director implements TemplateGlobalProvider {
/** /**
* Takes a $_SERVER data array and extracts HTTP request headers. * Takes a $_SERVER data array and extracts HTTP request headers.
* *
* @param array $data * @param array $server
* @return array * @return array
*/ */
public static function extract_request_headers(array $server) { public static function extract_request_headers(array $server) {
@ -790,9 +837,14 @@ class Director implements TemplateGlobalProvider {
/** /**
* Returns the Absolute URL of the site root. * Returns the Absolute URL of the site root.
*
* @return string
*/ */
public static function absoluteBaseURL() { public static function absoluteBaseURL() {
return Director::absoluteURL(Director::baseURL()); return self::absoluteURL(
self::baseURL(),
self::ROOT
);
} }
/** /**

View File

@ -99,36 +99,50 @@ class DirectorTest extends SapphireTest {
$_SERVER['REQUEST_URI'] = "$rootURL/mysite/sub-page/"; $_SERVER['REQUEST_URI'] = "$rootURL/mysite/sub-page/";
Config::inst()->update('Director', 'alternate_base_url', '/mysite/'); Config::inst()->update('Director', 'alternate_base_url', '/mysite/');
//test empty URL //test empty / local urls
$this->assertEquals("$rootURL/mysite/sub-page/", Director::absoluteURL('')); foreach(array('', './', '.') as $url) {
$this->assertEquals("$rootURL/mysite/", Director::absoluteURL($url, Director::BASE));
$this->assertEquals("$rootURL/", Director::absoluteURL($url, Director::ROOT));
$this->assertEquals("$rootURL/mysite/sub-page/", Director::absoluteURL($url, Director::REQUEST));
}
//test absolute - / // Test site root url
$this->assertEquals("$rootURL/", Director::absoluteURL('/')); $this->assertEquals("$rootURL/", Director::absoluteURL('/'));
//test relative // Test Director::BASE
$this->assertEquals("$rootURL/mysite/sub-page/", Director::absoluteURL('./')); $this->assertEquals($rootURL, Director::absoluteURL($rootURL, Director::BASE));
$this->assertEquals("$rootURL/mysite/sub-page/", Director::absoluteURL('.')); $this->assertEquals('http://www.mytest.com', Director::absoluteURL('http://www.mytest.com', Director::BASE));
$this->assertEquals("$rootURL/test", Director::absoluteURL("$rootURL/test", Director::BASE));
$this->assertEquals("$rootURL/root", Director::absoluteURL("/root", Director::BASE));
$this->assertEquals("$rootURL/root/url", Director::absoluteURL("/root/url", Director::BASE));
// Test already absolute url // Test Director::ROOT
$this->assertEquals($rootURL, Director::absoluteURL($rootURL)); $this->assertEquals($rootURL, Director::absoluteURL($rootURL, Director::ROOT));
$this->assertEquals($rootURL, Director::absoluteURL($rootURL, true)); $this->assertEquals('http://www.mytest.com', Director::absoluteURL('http://www.mytest.com', Director::ROOT));
$this->assertEquals('http://www.mytest.com', Director::absoluteURL('http://www.mytest.com')); $this->assertEquals("$rootURL/test", Director::absoluteURL("$rootURL/test", Director::ROOT));
$this->assertEquals('http://www.mytest.com', Director::absoluteURL('http://www.mytest.com', true)); $this->assertEquals("$rootURL/root", Director::absoluteURL("/root", Director::ROOT));
$this->assertEquals("$rootURL/test", Director::absoluteURL("$rootURL/test")); $this->assertEquals("$rootURL/root/url", Director::absoluteURL("/root/url", Director::ROOT));
$this->assertEquals("$rootURL/test", Director::absoluteURL("$rootURL/test", true));
// Test relative to base // Test Director::REQUEST
$this->assertEquals("$rootURL/mysite/test", Director::absoluteURL("test", true)); $this->assertEquals($rootURL, Director::absoluteURL($rootURL, Director::REQUEST));
$this->assertEquals("$rootURL/mysite/test/url", Director::absoluteURL("test/url", true)); $this->assertEquals('http://www.mytest.com', Director::absoluteURL('http://www.mytest.com', Director::REQUEST));
$this->assertEquals("$rootURL/root", Director::absoluteURL("/root", true)); $this->assertEquals("$rootURL/test", Director::absoluteURL("$rootURL/test", Director::REQUEST));
$this->assertEquals("$rootURL/root/url", Director::absoluteURL("/root/url", true)); $this->assertEquals("$rootURL/root", Director::absoluteURL("/root", Director::REQUEST));
$this->assertEquals("$rootURL/root/url", Director::absoluteURL("/root/url", Director::REQUEST));
// Test evaluating relative urls relative to base (default)
$this->assertEquals("$rootURL/mysite/test", Director::absoluteURL("test"));
$this->assertEquals("$rootURL/mysite/test/url", Director::absoluteURL("test/url"));
$this->assertEquals("$rootURL/mysite/test", Director::absoluteURL("test", Director::BASE));
$this->assertEquals("$rootURL/mysite/test/url", Director::absoluteURL("test/url", Director::BASE));
// Test evaluting relative urls relative to root
$this->assertEquals("$rootURL/test", Director::absoluteURL("test", Director::ROOT));
$this->assertEquals("$rootURL/test/url", Director::absoluteURL("test/url", Director::ROOT));
// Test relative to requested page // Test relative to requested page
$this->assertEquals("$rootURL/mysite/sub-page/test", Director::absoluteURL("test")); $this->assertEquals("$rootURL/mysite/sub-page/test", Director::absoluteURL("test", Director::REQUEST));
// Legacy behaviour resolves this to $rootURL/mysite/test/url $this->assertEquals("$rootURL/mysite/sub-page/test/url", Director::absoluteURL("test/url", Director::REQUEST));
//$this->assertEquals("$rootURL/mysite/sub-page/test/url", Director::absoluteURL("test/url"));
$this->assertEquals("$rootURL/root", Director::absoluteURL("/root"));
$this->assertEquals("$rootURL/root/url", Director::absoluteURL("/root/url"));
// Test that javascript links are not left intact // Test that javascript links are not left intact
$this->assertStringStartsNotWith('javascript', Director::absoluteURL('javascript:alert("attack")')); $this->assertStringStartsNotWith('javascript', Director::absoluteURL('javascript:alert("attack")'));
@ -157,17 +171,20 @@ class DirectorTest extends SapphireTest {
$_SERVER['REQUEST_URI'] = "http://www.example.org/sub-page/"; $_SERVER['REQUEST_URI'] = "http://www.example.org/sub-page/";
$this->assertEquals('http://www.example.org/', Director::baseURL()); $this->assertEquals('http://www.example.org/', Director::baseURL());
$this->assertEquals('http://www.example.org/', Director::absoluteBaseURL()); $this->assertEquals('http://www.example.org/', Director::absoluteBaseURL());
$this->assertEquals('http://www.example.org/sub-page/', Director::absoluteURL('')); $this->assertEquals('http://www.example.org/sub-page/', Director::absoluteURL('', Director::REQUEST));
$this->assertEquals('http://www.example.org/', Director::absoluteURL('', true)); $this->assertEquals('http://www.example.org/', Director::absoluteURL('', Director::BASE));
/* $this->assertEquals('http://www.example.org/', Director::absoluteURL('', Director::ROOT));
* See Legacy behaviour in testAbsoluteURL - sub-pages with '/' in the string are not correctly evaluated
$this->assertEquals( $this->assertEquals(
'http://www.example.org/sub-page/subfolder/test', 'http://www.example.org/sub-page/subfolder/test',
Director::absoluteURL('subfolder/test') Director::absoluteURL('subfolder/test', Director::REQUEST)
);*/ );
$this->assertEquals( $this->assertEquals(
'http://www.example.org/subfolder/test', 'http://www.example.org/subfolder/test',
Director::absoluteURL('subfolder/test', true) Director::absoluteURL('subfolder/test', Director::ROOT)
);
$this->assertEquals(
'http://www.example.org/subfolder/test',
Director::absoluteURL('subfolder/test', Director::BASE)
); );
// Setting it to false restores functionality // Setting it to false restores functionality