silverstripe-framework/tests/php/Control/DirectorTest.php
Damian Mooyman 0c41a97a8b API Refactor Form request handling into FormRequestHandler
API Add HasRequestHandler interface
API Refactor Link() and url handling behaviour from Controller into RequestHandler
API RequestHandler classes now must define url_segment to have a default Link()
API Clean up redirectBack()
2017-03-10 15:04:33 +13:00

619 lines
23 KiB
PHP

<?php
namespace SilverStripe\Control\Tests;
use SilverStripe\Control\Cookie_Backend;
use SilverStripe\Control\HTTPRequest;
use SilverStripe\Control\HTTPResponse;
use SilverStripe\Control\HTTPResponse_Exception;
use SilverStripe\Control\Tests\DirectorTest\TestController;
use SilverStripe\Core\Config\Config;
use SilverStripe\Core\Injector\Injector;
use SilverStripe\Dev\SapphireTest;
use SilverStripe\Control\Director;
use SilverStripe\Control\RequestProcessor;
use SilverStripe\Security\Security;
/**
* @todo test Director::alternateBaseFolder()
*/
class DirectorTest extends SapphireTest
{
protected static $originalRequestURI;
protected $originalProtocolHeaders = array();
protected $originalGet = array();
protected $originalSession = array();
protected $extraControllers = [
TestController::class
];
public function setUp()
{
parent::setUp();
// Hold the original request URI once so it doesn't get overwritten
if (!self::$originalRequestURI) {
self::$originalRequestURI = $_SERVER['REQUEST_URI'];
}
$_SERVER['REQUEST_URI'] = 'http://www.mysite.com';
$this->originalGet = $_GET;
$this->originalSession = $_SESSION;
$_SESSION = array();
$headers = array(
'HTTP_X_FORWARDED_PROTOCOL', 'HTTPS', 'SSL'
);
foreach ($headers as $header) {
if (isset($_SERVER[$header])) {
$this->originalProtocolHeaders[$header] = $_SERVER[$header];
}
}
Config::modify()->set(Director::class, 'alternate_base_url', '/');
}
protected function getExtraRoutes()
{
$rules = parent::getExtraRoutes();
$rules['DirectorTestRule/$Action/$ID/$OtherID'] = TestController::class;
$rules['en-nz/$Action/$ID/$OtherID'] = [
'Controller' => TestController::class,
'Locale' => 'en_NZ',
];
return $rules;
}
protected function setUpRoutes()
{
// Don't merge with any existing rules
Director::config()->set('rules', $this->getExtraRoutes());
}
public function tearDown()
{
$_GET = $this->originalGet;
$_SESSION = $this->originalSession;
// Reinstate the original REQUEST_URI after it was modified by some tests
$_SERVER['REQUEST_URI'] = self::$originalRequestURI;
if ($this->originalProtocolHeaders) {
foreach ($this->originalProtocolHeaders as $header => $value) {
$_SERVER[$header] = $value;
}
}
parent::tearDown();
}
public function testFileExists()
{
$tempFileName = 'DirectorTest_testFileExists.tmp';
$tempFilePath = TEMP_FOLDER . '/' . $tempFileName;
// create temp file
file_put_contents($tempFilePath, '');
$this->assertTrue(
Director::fileExists($tempFilePath),
'File exist check with absolute path'
);
$this->assertTrue(
Director::fileExists($tempFilePath . '?queryparams=1&foo[bar]=bar'),
'File exist check with query params ignored'
);
unlink($tempFilePath);
}
public function testAbsoluteURL()
{
$rootURL = Director::protocolAndHost();
$_SERVER['REQUEST_URI'] = "$rootURL/mysite/sub-page/";
Director::config()->set('alternate_base_url', '/mysite/');
//test empty / local urls
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 site root url
$this->assertEquals("$rootURL/", Director::absoluteURL('/'));
// Test Director::BASE
$this->assertEquals($rootURL, Director::absoluteURL($rootURL, Director::BASE));
$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 Director::ROOT
$this->assertEquals($rootURL, Director::absoluteURL($rootURL, Director::ROOT));
$this->assertEquals('http://www.mytest.com', Director::absoluteURL('http://www.mytest.com', Director::ROOT));
$this->assertEquals("$rootURL/test", Director::absoluteURL("$rootURL/test", Director::ROOT));
$this->assertEquals("$rootURL/root", Director::absoluteURL("/root", Director::ROOT));
$this->assertEquals("$rootURL/root/url", Director::absoluteURL("/root/url", Director::ROOT));
// Test Director::REQUEST
$this->assertEquals($rootURL, Director::absoluteURL($rootURL, Director::REQUEST));
$this->assertEquals('http://www.mytest.com', Director::absoluteURL('http://www.mytest.com', Director::REQUEST));
$this->assertEquals("$rootURL/test", Director::absoluteURL("$rootURL/test", Director::REQUEST));
$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
$this->assertEquals("$rootURL/mysite/sub-page/test", Director::absoluteURL("test", Director::REQUEST));
$this->assertEquals("$rootURL/mysite/sub-page/test/url", Director::absoluteURL("test/url", Director::REQUEST));
// Test that javascript links are not left intact
$this->assertStringStartsNotWith('javascript', Director::absoluteURL('javascript:alert("attack")'));
$this->assertStringStartsNotWith('alert', Director::absoluteURL('javascript:alert("attack")'));
$this->assertStringStartsNotWith('javascript', Director::absoluteURL('alert("attack")'));
$this->assertStringStartsNotWith('alert', Director::absoluteURL('alert("attack")'));
}
public function testAlternativeBaseURL()
{
// Get original protocol and hostname
$rootURL = Director::protocolAndHost();
// relative base URLs - you should end them in a /
Director::config()->set('alternate_base_url', '/relativebase/');
$_SERVER['REQUEST_URI'] = "$rootURL/relativebase/sub-page/";
$this->assertEquals('/relativebase/', Director::baseURL());
$this->assertEquals($rootURL . '/relativebase/', Director::absoluteBaseURL());
$this->assertEquals(
$rootURL . '/relativebase/subfolder/test',
Director::absoluteURL('subfolder/test')
);
// absolute base URLs - you should end them in a /
Director::config()->set('alternate_base_url', 'http://www.example.org/');
$_SERVER['REQUEST_URI'] = "http://www.example.org/sub-page/";
$this->assertEquals('http://www.example.org/', Director::baseURL());
$this->assertEquals('http://www.example.org/', Director::absoluteBaseURL());
$this->assertEquals('http://www.example.org/sub-page/', Director::absoluteURL('', Director::REQUEST));
$this->assertEquals('http://www.example.org/', Director::absoluteURL('', Director::BASE));
$this->assertEquals('http://www.example.org/', Director::absoluteURL('', Director::ROOT));
$this->assertEquals(
'http://www.example.org/sub-page/subfolder/test',
Director::absoluteURL('subfolder/test', Director::REQUEST)
);
$this->assertEquals(
'http://www.example.org/subfolder/test',
Director::absoluteURL('subfolder/test', Director::ROOT)
);
$this->assertEquals(
'http://www.example.org/subfolder/test',
Director::absoluteURL('subfolder/test', Director::BASE)
);
}
/**
* Tests that {@link Director::is_absolute()} works under different environment types
*/
public function testIsAbsolute()
{
$expected = array (
'C:/something' => true,
'd:\\' => true,
'e/' => false,
's:/directory' => true,
'/var/www' => true,
'\\Something' => true,
'something/c:' => false,
'folder' => false,
'a/c:/' => false
);
foreach ($expected as $path => $result) {
$this->assertEquals(Director::is_absolute($path), $result, "Test result for $path");
}
}
public function testIsAbsoluteUrl()
{
$this->assertTrue(Director::is_absolute_url('http://test.com/testpage'));
$this->assertTrue(Director::is_absolute_url('ftp://test.com'));
$this->assertFalse(Director::is_absolute_url('test.com/testpage'));
$this->assertFalse(Director::is_absolute_url('/relative'));
$this->assertFalse(Director::is_absolute_url('relative'));
$this->assertFalse(Director::is_absolute_url("/relative/?url=http://foo.com"));
$this->assertFalse(Director::is_absolute_url("/relative/#http://foo.com"));
$this->assertTrue(Director::is_absolute_url("https://test.com/?url=http://foo.com"));
$this->assertTrue(Director::is_absolute_url("trickparseurl:http://test.com"));
$this->assertTrue(Director::is_absolute_url('//test.com'));
$this->assertTrue(Director::is_absolute_url('/////test.com'));
$this->assertTrue(Director::is_absolute_url(' ///test.com'));
$this->assertTrue(Director::is_absolute_url('http:test.com'));
$this->assertTrue(Director::is_absolute_url('//http://test.com'));
}
public function testIsRelativeUrl()
{
$this->assertFalse(Director::is_relative_url('http://test.com'));
$this->assertFalse(Director::is_relative_url('https://test.com'));
$this->assertFalse(Director::is_relative_url(' https://test.com/testpage '));
$this->assertTrue(Director::is_relative_url('test.com/testpage'));
$this->assertFalse(Director::is_relative_url('ftp://test.com'));
$this->assertTrue(Director::is_relative_url('/relative'));
$this->assertTrue(Director::is_relative_url('relative'));
$this->assertTrue(Director::is_relative_url('/relative/?url=http://test.com'));
$this->assertTrue(Director::is_relative_url('/relative/#=http://test.com'));
}
public function testMakeRelative()
{
$siteUrl = Director::absoluteBaseURL();
$siteUrlNoProtocol = preg_replace('/https?:\/\//', '', $siteUrl);
$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"));
}
/**
* Mostly tested by {@link testIsRelativeUrl()},
* just adding the host name matching aspect here.
*/
public function testIsSiteUrl()
{
$this->assertFalse(Director::is_site_url("http://test.com"));
$this->assertTrue(Director::is_site_url(Director::absoluteBaseURL()));
$this->assertFalse(Director::is_site_url("http://test.com?url=" . Director::absoluteBaseURL()));
$this->assertFalse(Director::is_site_url("http://test.com?url=" . urlencode(Director::absoluteBaseURL())));
$this->assertFalse(Director::is_site_url("//test.com?url=" . Director::absoluteBaseURL()));
}
/**
* Tests isDev, isTest, isLive set from querystring
*/
public function testQueryIsEnvironment()
{
// Reset
unset($_SESSION['isDev']);
unset($_SESSION['isLive']);
unset($_GET['isTest']);
unset($_GET['isDev']);
$_SESSION = $_SESSION ?: array();
// Test isDev=1
$_GET['isDev'] = '1';
$this->assertTrue(Director::isDev());
$this->assertFalse(Director::isTest());
$this->assertFalse(Director::isLive());
// Test persistence
unset($_GET['isDev']);
$this->assertTrue(Director::isDev());
$this->assertFalse(Director::isTest());
$this->assertFalse(Director::isLive());
// Test change to isTest
$_GET['isTest'] = '1';
$this->assertFalse(Director::isDev());
$this->assertTrue(Director::isTest());
$this->assertFalse(Director::isLive());
// Test persistence
unset($_GET['isTest']);
$this->assertFalse(Director::isDev());
$this->assertTrue(Director::isTest());
$this->assertFalse(Director::isLive());
}
public function testResetGlobalsAfterTestRequest()
{
$_GET = array('somekey' => 'getvalue');
$_POST = array('somekey' => 'postvalue');
$_COOKIE = array('somekey' => 'cookievalue');
$cookies = Injector::inst()->createWithArgs(
Cookie_Backend::class,
array(array('somekey' => 'sometestcookievalue'))
);
Director::test(
'errorpage?somekey=sometestgetvalue',
array('somekey' => 'sometestpostvalue'),
null,
null,
null,
null,
$cookies
);
$this->assertEquals(
'getvalue',
$_GET['somekey'],
'$_GET reset to original value after Director::test()'
);
$this->assertEquals(
'postvalue',
$_POST['somekey'],
'$_POST reset to original value after Director::test()'
);
$this->assertEquals(
'cookievalue',
$_COOKIE['somekey'],
'$_COOKIE reset to original value after Director::test()'
);
}
public function testTestRequestCarriesGlobals()
{
$fixture = array('somekey' => 'sometestvalue');
foreach (array('get', 'post') as $method) {
foreach (array('return%sValue', 'returnRequestValue', 'returnCookieValue') as $testfunction) {
$url = 'TestController/' . sprintf($testfunction, ucfirst($method))
. '?' . http_build_query($fixture);
$getresponse = Director::test(
$url,
$fixture,
null,
strtoupper($method),
null,
null,
Injector::inst()->createWithArgs(Cookie_Backend::class, array($fixture))
);
$this->assertInstanceOf(HTTPResponse::class, $getresponse, 'Director::test() returns HTTPResponse');
$this->assertEquals($fixture['somekey'], $getresponse->getBody(), 'Director::test() ' . $testfunction);
}
}
}
/**
* Tests that additional parameters specified in the routing table are
* saved in the request
*/
public function testRouteParams()
{
/** @var HTTPRequest $request */
Director::test('en-nz/myaction/myid/myotherid', null, null, null, null, null, null, $request);
$this->assertEquals(
array(
'Controller' => TestController::class,
'Action' => 'myaction',
'ID' => 'myid',
'OtherID' => 'myotherid',
'Locale' => 'en_NZ'
),
$request->params()
);
}
public function testForceSSLProtectsEntireSite()
{
$_SERVER['REQUEST_URI'] = '/admin';
$output = Director::forceSSL();
$this->assertEquals($output, 'https://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']);
$_SERVER['REQUEST_URI'] = Director::baseURL() . 'some-url';
$output = Director::forceSSL();
$this->assertEquals($output, 'https://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']);
}
public function testForceSSLOnTopLevelPagePattern()
{
$_SERVER['REQUEST_URI'] = Director::baseURL() . 'admin';
$output = Director::forceSSL(array('/^admin/'));
$this->assertEquals($output, 'https://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']);
}
public function testForceSSLOnSubPagesPattern()
{
$_SERVER['REQUEST_URI'] = Director::baseURL() . Config::inst()->get(Security::class, 'login_url');
$output = Director::forceSSL(array('/^Security/'));
$this->assertEquals($output, 'https://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']);
}
public function testForceSSLWithPatternDoesNotMatchOtherPages()
{
$_SERVER['REQUEST_URI'] = Director::baseURL() . 'normal-page';
$output = Director::forceSSL(array('/^admin/'));
$this->assertFalse($output);
$_SERVER['REQUEST_URI'] = Director::baseURL() . 'just-another-page/sub-url';
$output = Director::forceSSL(array('/^admin/', '/^Security/'));
$this->assertFalse($output);
}
public function testForceSSLAlternateDomain()
{
Director::config()->set('alternate_base_url', '/');
$_SERVER['REQUEST_URI'] = Director::baseURL() . 'admin';
$output = Director::forceSSL(array('/^admin/'), 'secure.mysite.com');
$this->assertEquals($output, 'https://secure.mysite.com/admin');
}
/**
* @covers \SilverStripe\Control\Director::extract_request_headers()
*/
public function testExtractRequestHeaders()
{
$request = array(
'REDIRECT_STATUS' => '200',
'HTTP_HOST' => 'host',
'HTTP_USER_AGENT' => 'User Agent',
'HTTP_ACCEPT' => 'text/html',
'HTTP_ACCEPT_LANGUAGE' => 'en-us',
'HTTP_COOKIE' => 'MyCookie=1',
'SERVER_PROTOCOL' => 'HTTP/1.1',
'REQUEST_METHOD' => 'GET',
'REQUEST_URI' => '/',
'SCRIPT_NAME' => FRAMEWORK_DIR . '/main.php',
'CONTENT_TYPE' => 'text/xml',
'CONTENT_LENGTH' => 10
);
$headers = array(
'Host' => 'host',
'User-Agent' => 'User Agent',
'Accept' => 'text/html',
'Accept-Language' => 'en-us',
'Cookie' => 'MyCookie=1',
'Content-Type' => 'text/xml',
'Content-Length' => '10'
);
$this->assertEquals($headers, Director::extract_request_headers($request));
}
public function testUnmatchedRequestReturns404()
{
// Remove non-tested rules
$this->assertEquals(404, Director::test('no-route')->getStatusCode());
}
public function testIsHttps()
{
if (!TRUSTED_PROXY) {
$this->markTestSkipped('Test cannot be run without trusted proxy');
}
// nothing available
$headers = array(
'HTTP_X_FORWARDED_PROTOCOL', 'HTTPS', 'SSL'
);
$origServer = $_SERVER;
foreach ($headers as $header) {
if (isset($_SERVER[$header])) {
unset($_SERVER['HTTP_X_FORWARDED_PROTOCOL']);
}
}
$this->assertFalse(Director::is_https());
$_SERVER['HTTP_X_FORWARDED_PROTOCOL'] = 'https';
$this->assertTrue(Director::is_https());
$_SERVER['HTTP_X_FORWARDED_PROTOCOL'] = 'http';
$this->assertFalse(Director::is_https());
$_SERVER['HTTP_X_FORWARDED_PROTOCOL'] = 'ftp';
$this->assertFalse(Director::is_https());
$_SERVER['HTTP_X_FORWARDED_PROTO'] = 'https';
$this->assertTrue(Director::is_https());
$_SERVER['HTTP_X_FORWARDED_PROTO'] = 'http';
$this->assertFalse(Director::is_https());
$_SERVER['HTTP_X_FORWARDED_PROTO'] = 'ftp';
$this->assertFalse(Director::is_https());
$_SERVER['HTTP_FRONT_END_HTTPS'] = 'On';
$this->assertTrue(Director::is_https());
$_SERVER['HTTP_FRONT_END_HTTPS'] = 'Off';
$this->assertFalse(Director::is_https());
// https via HTTPS
$_SERVER['HTTPS'] = 'true';
$this->assertTrue(Director::is_https());
$_SERVER['HTTPS'] = '1';
$this->assertTrue(Director::is_https());
$_SERVER['HTTPS'] = 'off';
$this->assertFalse(Director::is_https());
// https via SSL
$_SERVER['SSL'] = '';
$this->assertTrue(Director::is_https());
$_SERVER = $origServer;
}
public function testTestIgnoresHashes()
{
//test that hashes are ignored
$url = "TestController/returnGetValue?somekey=key";
$hash = "#test";
/** @var HTTPRequest $request */
$response = Director::test($url . $hash, null, null, null, null, null, null, $request);
$this->assertFalse($response->isError());
$this->assertEquals('key', $response->getBody());
$this->assertEquals($request->getURL(true), $url);
//test encoded hashes are accepted
$url = "TestController/returnGetValue?somekey=test%23key";
$response = Director::test($url, null, null, null, null, null, null, $request);
$this->assertFalse($response->isError());
$this->assertEquals('test#key', $response->getBody());
$this->assertEquals($request->getURL(true), $url);
}
public function testRequestFilterInDirectorTest()
{
$filter = new DirectorTest\TestRequestFilter;
$processor = new RequestProcessor(array($filter));
Injector::inst()->registerService($processor, RequestProcessor::class);
Director::test('some-dummy-url');
$this->assertEquals(1, $filter->preCalls);
$this->assertEquals(1, $filter->postCalls);
$filter->failPost = true;
$this->setExpectedException(HTTPResponse_Exception::class);
Director::test('some-dummy-url');
$this->assertEquals(2, $filter->preCalls);
$this->assertEquals(2, $filter->postCalls);
$filter->failPre = true;
Director::test('some-dummy-url');
$this->assertEquals(3, $filter->preCalls);
// preCall 'false' will trigger an exception and prevent post call execution
$this->assertEquals(2, $filter->postCalls);
}
}