Completed
Pull Request — master (#7057)
by Damian
08:49
created

DirectorTest::testRouteSpecificMiddleware()   B

Complexity

Conditions 1
Paths 1

Size

Total Lines 35
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 18
nc 1
nop 0
dl 0
loc 35
rs 8.8571
c 0
b 0
f 0
1
<?php
2
namespace SilverStripe\Control\Tests;
3
4
use SilverStripe\Control\Cookie_Backend;
5
use SilverStripe\Control\Director;
6
use SilverStripe\Control\HTTPRequest;
7
use SilverStripe\Control\HTTPRequestBuilder;
8
use SilverStripe\Control\HTTPResponse;
9
use SilverStripe\Control\HTTPResponse_Exception;
10
use SilverStripe\Control\Middleware\HTTPMiddleware;
11
use SilverStripe\Control\Middleware\TrustedProxyMiddleware;
12
use SilverStripe\Control\RequestProcessor;
13
use SilverStripe\Control\Tests\DirectorTest\TestController;
14
use SilverStripe\Core\Config\Config;
15
use SilverStripe\Core\Injector\Injector;
16
use SilverStripe\Core\Kernel;
17
use SilverStripe\Dev\SapphireTest;
18
19
/**
20
 * @todo test Director::alternateBaseFolder()
21
 */
22
class DirectorTest extends SapphireTest
23
{
24
    protected static $extra_controllers = [
25
        TestController::class,
26
    ];
27
28
    protected function setUp()
29
    {
30
        parent::setUp();
31
        Director::config()->set('alternate_base_url', 'http://www.mysite.com/');
32
        $this->expectedRedirect = null;
33
    }
34
35
    protected function getExtraRoutes()
36
    {
37
        $rules = parent::getExtraRoutes();
38
39
        $rules['DirectorTestRule/$Action/$ID/$OtherID'] = TestController::class;
40
        $rules['en-nz/$Action/$ID/$OtherID'] = [
41
            'Controller' => TestController::class,
42
            'Locale' => 'en_NZ',
43
        ];
44
        return $rules;
45
    }
46
47
    protected function setUpRoutes()
48
    {
49
        // Don't merge with any existing rules
50
        Director::config()->set('rules', $this->getExtraRoutes());
51
    }
52
53
    public function testFileExists()
54
    {
55
        $tempFileName = 'DirectorTest_testFileExists.tmp';
56
        $tempFilePath = TEMP_FOLDER . '/' . $tempFileName;
57
58
        // create temp file
59
        file_put_contents($tempFilePath, '');
60
61
        $this->assertTrue(
62
            Director::fileExists($tempFilePath),
63
            'File exist check with absolute path'
64
        );
65
66
        $this->assertTrue(
67
            Director::fileExists($tempFilePath . '?queryparams=1&foo[bar]=bar'),
68
            'File exist check with query params ignored'
69
        );
70
71
        unlink($tempFilePath);
72
    }
73
74
    public function testAbsoluteURL()
0 ignored issues
show
Coding Style introduced by
testAbsoluteURL uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
75
    {
76
        Director::config()->set('alternate_base_url', 'http://www.mysite.com/mysite/');
77
        $_SERVER['REQUEST_URI'] = "http://www.mysite.com/mysite/sub-page/";
78
79
        //test empty / local urls
80
        foreach (array('', './', '.') as $url) {
81
            $this->assertEquals("http://www.mysite.com/mysite/", Director::absoluteURL($url, Director::BASE));
82
            $this->assertEquals("http://www.mysite.com/", Director::absoluteURL($url, Director::ROOT));
83
            $this->assertEquals("http://www.mysite.com/mysite/sub-page/", Director::absoluteURL($url, Director::REQUEST));
84
        }
85
86
        // Test site root url
87
        $this->assertEquals("http://www.mysite.com/", Director::absoluteURL('/'));
88
89
        // Test Director::BASE
90
        $this->assertEquals('http://www.mysite.com/', Director::absoluteURL('http://www.mysite.com/', Director::BASE));
91
        $this->assertEquals('http://www.mytest.com', Director::absoluteURL('http://www.mytest.com', Director::BASE));
92
        $this->assertEquals("http://www.mysite.com/test", Director::absoluteURL("http://www.mysite.com/test", Director::BASE));
93
        $this->assertEquals("http://www.mysite.com/root", Director::absoluteURL("/root", Director::BASE));
94
        $this->assertEquals("http://www.mysite.com/root/url", Director::absoluteURL("/root/url", Director::BASE));
95
96
        // Test Director::ROOT
97
        $this->assertEquals('http://www.mysite.com/', Director::absoluteURL('http://www.mysite.com/', Director::ROOT));
98
        $this->assertEquals('http://www.mytest.com', Director::absoluteURL('http://www.mytest.com', Director::ROOT));
99
        $this->assertEquals("http://www.mysite.com/test", Director::absoluteURL("http://www.mysite.com/test", Director::ROOT));
100
        $this->assertEquals("http://www.mysite.com/root", Director::absoluteURL("/root", Director::ROOT));
101
        $this->assertEquals("http://www.mysite.com/root/url", Director::absoluteURL("/root/url", Director::ROOT));
102
103
        // Test Director::REQUEST
104
        $this->assertEquals('http://www.mysite.com/', Director::absoluteURL('http://www.mysite.com/', Director::REQUEST));
105
        $this->assertEquals('http://www.mytest.com', Director::absoluteURL('http://www.mytest.com', Director::REQUEST));
106
        $this->assertEquals("http://www.mysite.com/test", Director::absoluteURL("http://www.mysite.com/test", Director::REQUEST));
107
        $this->assertEquals("http://www.mysite.com/root", Director::absoluteURL("/root", Director::REQUEST));
108
        $this->assertEquals("http://www.mysite.com/root/url", Director::absoluteURL("/root/url", Director::REQUEST));
109
110
        // Test evaluating relative urls relative to base (default)
111
        $this->assertEquals("http://www.mysite.com/mysite/test", Director::absoluteURL("test"));
112
        $this->assertEquals("http://www.mysite.com/mysite/test/url", Director::absoluteURL("test/url"));
113
        $this->assertEquals("http://www.mysite.com/mysite/test", Director::absoluteURL("test", Director::BASE));
114
        $this->assertEquals("http://www.mysite.com/mysite/test/url", Director::absoluteURL("test/url", Director::BASE));
115
116
        // Test evaluting relative urls relative to root
117
        $this->assertEquals("http://www.mysite.com/test", Director::absoluteURL("test", Director::ROOT));
118
        $this->assertEquals("http://www.mysite.com/test/url", Director::absoluteURL("test/url", Director::ROOT));
119
120
        // Test relative to requested page
121
        $this->assertEquals("http://www.mysite.com/mysite/sub-page/test", Director::absoluteURL("test", Director::REQUEST));
122
        $this->assertEquals("http://www.mysite.com/mysite/sub-page/test/url", Director::absoluteURL("test/url", Director::REQUEST));
123
124
        // Test that javascript links are not left intact
125
        $this->assertStringStartsNotWith('javascript', Director::absoluteURL('javascript:alert("attack")'));
126
        $this->assertStringStartsNotWith('alert', Director::absoluteURL('javascript:alert("attack")'));
127
        $this->assertStringStartsNotWith('javascript', Director::absoluteURL('alert("attack")'));
128
        $this->assertStringStartsNotWith('alert', Director::absoluteURL('alert("attack")'));
129
    }
130
131
    public function testAlternativeBaseURL()
0 ignored issues
show
Coding Style introduced by
testAlternativeBaseURL uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
132
    {
133
        // relative base URLs - you should end them in a /
134
        Director::config()->set('alternate_base_url', '/relativebase/');
135
        $_SERVER['HTTP_HOST'] = 'www.somesite.com';
136
        $_SERVER['REQUEST_URI'] = "/relativebase/sub-page/";
137
138
        $this->assertEquals('/relativebase/', Director::baseURL());
139
        $this->assertEquals('http://www.somesite.com/relativebase/', Director::absoluteBaseURL());
140
        $this->assertEquals(
141
            'http://www.somesite.com/relativebase/subfolder/test',
142
            Director::absoluteURL('subfolder/test')
143
        );
144
145
        // absolute base URLS with subdirectory - You should end them in a /
146
        Director::config()->set('alternate_base_url', 'http://www.example.org/relativebase/');
147
        $_SERVER['REQUEST_URI'] = "http://www.example.org/relativebase/sub-page/";
148
        $this->assertEquals('/relativebase/', Director::baseURL()); // Non-absolute url
149
        $this->assertEquals('http://www.example.org/relativebase/', Director::absoluteBaseURL());
150
        $this->assertEquals('http://www.example.org/relativebase/sub-page/', Director::absoluteURL('', Director::REQUEST));
151
        $this->assertEquals('http://www.example.org/relativebase/', Director::absoluteURL('', Director::BASE));
152
        $this->assertEquals('http://www.example.org/', Director::absoluteURL('', Director::ROOT));
153
        $this->assertEquals(
154
            'http://www.example.org/relativebase/sub-page/subfolder/test',
155
            Director::absoluteURL('subfolder/test', Director::REQUEST)
156
        );
157
        $this->assertEquals(
158
            'http://www.example.org/subfolder/test',
159
            Director::absoluteURL('subfolder/test', Director::ROOT)
160
        );
161
        $this->assertEquals(
162
            'http://www.example.org/relativebase/subfolder/test',
163
            Director::absoluteURL('subfolder/test', Director::BASE)
164
        );
165
166
        // absolute base URLs - you should end them in a /
167
        Director::config()->set('alternate_base_url', 'http://www.example.org/');
168
        $_SERVER['REQUEST_URI'] = "http://www.example.org/sub-page/";
169
        $this->assertEquals('/', Director::baseURL()); // Non-absolute url
170
        $this->assertEquals('http://www.example.org/', Director::absoluteBaseURL());
171
        $this->assertEquals('http://www.example.org/sub-page/', Director::absoluteURL('', Director::REQUEST));
172
        $this->assertEquals('http://www.example.org/', Director::absoluteURL('', Director::BASE));
173
        $this->assertEquals('http://www.example.org/', Director::absoluteURL('', Director::ROOT));
174
        $this->assertEquals(
175
            'http://www.example.org/sub-page/subfolder/test',
176
            Director::absoluteURL('subfolder/test', Director::REQUEST)
177
        );
178
        $this->assertEquals(
179
            'http://www.example.org/subfolder/test',
180
            Director::absoluteURL('subfolder/test', Director::ROOT)
181
        );
182
        $this->assertEquals(
183
            'http://www.example.org/subfolder/test',
184
            Director::absoluteURL('subfolder/test', Director::BASE)
185
        );
186
    }
187
188
    /**
189
     * Tests that {@link Director::is_absolute()} works under different environment types
190
     */
191
    public function testIsAbsolute()
192
    {
193
        $expected = array (
194
            'C:/something' => true,
195
            'd:\\'         => true,
196
            'e/'           => false,
197
            's:/directory' => true,
198
            '/var/www'     => true,
199
            '\\Something'  => true,
200
            'something/c:' => false,
201
            'folder'       => false,
202
            'a/c:/'        => false
203
        );
204
205
        foreach ($expected as $path => $result) {
206
            $this->assertEquals(Director::is_absolute($path), $result, "Test result for $path");
207
        }
208
    }
209
210
    public function testIsAbsoluteUrl()
211
    {
212
        $this->assertTrue(Director::is_absolute_url('http://test.com/testpage'));
213
        $this->assertTrue(Director::is_absolute_url('ftp://test.com'));
214
        $this->assertFalse(Director::is_absolute_url('test.com/testpage'));
215
        $this->assertFalse(Director::is_absolute_url('/relative'));
216
        $this->assertFalse(Director::is_absolute_url('relative'));
217
        $this->assertFalse(Director::is_absolute_url("/relative/?url=http://foo.com"));
218
        $this->assertFalse(Director::is_absolute_url("/relative/#http://foo.com"));
219
        $this->assertTrue(Director::is_absolute_url("https://test.com/?url=http://foo.com"));
220
        $this->assertTrue(Director::is_absolute_url("trickparseurl:http://test.com"));
221
        $this->assertTrue(Director::is_absolute_url('//test.com'));
222
        $this->assertTrue(Director::is_absolute_url('/////test.com'));
223
        $this->assertTrue(Director::is_absolute_url('  ///test.com'));
224
        $this->assertTrue(Director::is_absolute_url('http:test.com'));
225
        $this->assertTrue(Director::is_absolute_url('//http://test.com'));
226
    }
227
228
    public function testIsRelativeUrl()
229
    {
230
        $this->assertFalse(Director::is_relative_url('http://test.com'));
231
        $this->assertFalse(Director::is_relative_url('https://test.com'));
232
        $this->assertFalse(Director::is_relative_url('   https://test.com/testpage   '));
233
        $this->assertTrue(Director::is_relative_url('test.com/testpage'));
234
        $this->assertFalse(Director::is_relative_url('ftp://test.com'));
235
        $this->assertTrue(Director::is_relative_url('/relative'));
236
        $this->assertTrue(Director::is_relative_url('relative'));
237
        $this->assertTrue(Director::is_relative_url('/relative/?url=http://test.com'));
238
        $this->assertTrue(Director::is_relative_url('/relative/#=http://test.com'));
239
    }
240
241
    public function testMakeRelative()
242
    {
243
        $siteUrl = Director::absoluteBaseURL();
244
        $siteUrlNoProtocol = preg_replace('/https?:\/\//', '', $siteUrl);
245
246
        $this->assertEquals(Director::makeRelative("$siteUrl"), '');
247
        $this->assertEquals(Director::makeRelative("https://$siteUrlNoProtocol"), '');
248
        $this->assertEquals(Director::makeRelative("http://$siteUrlNoProtocol"), '');
249
250
        $this->assertEquals(Director::makeRelative("   $siteUrl/testpage   "), 'testpage');
251
        $this->assertEquals(Director::makeRelative("$siteUrlNoProtocol/testpage"), 'testpage');
252
253
        $this->assertEquals(Director::makeRelative('ftp://test.com'), 'ftp://test.com');
254
        $this->assertEquals(Director::makeRelative('http://test.com'), 'http://test.com');
255
256
        $this->assertEquals(Director::makeRelative('relative'), 'relative');
257
        $this->assertEquals(Director::makeRelative("$siteUrl/?url=http://test.com"), '?url=http://test.com');
258
259
        $this->assertEquals("test", Director::makeRelative("https://".$siteUrlNoProtocol."/test"));
260
        $this->assertEquals("test", Director::makeRelative("http://".$siteUrlNoProtocol."/test"));
261
    }
262
263
    /**
264
     * Mostly tested by {@link testIsRelativeUrl()},
265
     * just adding the host name matching aspect here.
266
     */
267
    public function testIsSiteUrl()
268
    {
269
        $this->assertFalse(Director::is_site_url("http://test.com"));
270
        $this->assertTrue(Director::is_site_url(Director::absoluteBaseURL()));
271
        $this->assertFalse(Director::is_site_url("http://test.com?url=" . Director::absoluteBaseURL()));
272
        $this->assertFalse(Director::is_site_url("http://test.com?url=" . urlencode(Director::absoluteBaseURL())));
273
        $this->assertFalse(Director::is_site_url("//test.com?url=" . Director::absoluteBaseURL()));
274
    }
275
276
    /**
277
     * Tests isDev, isTest, isLive set from querystring
278
     */
279
    public function testQueryIsEnvironment()
0 ignored issues
show
Coding Style introduced by
testQueryIsEnvironment uses the super-global variable $_SESSION which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
Coding Style introduced by
testQueryIsEnvironment uses the super-global variable $_GET which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
280
    {
281
        if (!isset($_SESSION)) {
282
            $_SESSION = [];
283
        }
284
        // Reset
285
        unset($_SESSION['isDev']);
286
        unset($_SESSION['isLive']);
287
        unset($_GET['isTest']);
288
        unset($_GET['isDev']);
289
290
        /** @var Kernel $kernel */
291
        $kernel = Injector::inst()->get(Kernel::class);
292
        $kernel->setEnvironment(null);
293
294
        // Test isDev=1
295
        $_GET['isDev'] = '1';
296
        $this->assertTrue(Director::isDev());
297
        $this->assertFalse(Director::isTest());
298
        $this->assertFalse(Director::isLive());
299
300
        // Test persistence
301
        unset($_GET['isDev']);
302
        $this->assertTrue(Director::isDev());
303
        $this->assertFalse(Director::isTest());
304
        $this->assertFalse(Director::isLive());
305
306
        // Test change to isTest
307
        $_GET['isTest'] = '1';
308
        $this->assertFalse(Director::isDev());
309
        $this->assertTrue(Director::isTest());
310
        $this->assertFalse(Director::isLive());
311
312
        // Test persistence
313
        unset($_GET['isTest']);
314
        $this->assertFalse(Director::isDev());
315
        $this->assertTrue(Director::isTest());
316
        $this->assertFalse(Director::isLive());
317
    }
318
319
    public function testResetGlobalsAfterTestRequest()
0 ignored issues
show
Coding Style introduced by
testResetGlobalsAfterTestRequest uses the super-global variable $_GET which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
Coding Style introduced by
testResetGlobalsAfterTestRequest uses the super-global variable $_POST which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
Coding Style introduced by
testResetGlobalsAfterTestRequest uses the super-global variable $_COOKIE which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
320
    {
321
        $_GET = array('somekey' => 'getvalue');
322
        $_POST = array('somekey' => 'postvalue');
323
        $_COOKIE = array('somekey' => 'cookievalue');
324
325
        $cookies = Injector::inst()->createWithArgs(
326
            Cookie_Backend::class,
327
            array(array('somekey' => 'sometestcookievalue'))
328
        );
329
330
        Director::test(
331
            'errorpage?somekey=sometestgetvalue',
332
            array('somekey' => 'sometestpostvalue'),
333
            null,
334
            null,
335
            null,
336
            null,
337
            $cookies
338
        );
339
340
        $this->assertEquals(
341
            'getvalue',
342
            $_GET['somekey'],
343
            '$_GET reset to original value after Director::test()'
344
        );
345
        $this->assertEquals(
346
            'postvalue',
347
            $_POST['somekey'],
348
            '$_POST reset to original value after Director::test()'
349
        );
350
        $this->assertEquals(
351
            'cookievalue',
352
            $_COOKIE['somekey'],
353
            '$_COOKIE reset to original value after Director::test()'
354
        );
355
    }
356
357
    public function providerTestTestRequestCarriesGlobals()
358
    {
359
        $tests = [];
360
        $fixture = [ 'somekey' => 'sometestvalue' ];
361
        foreach (array('get', 'post') as $method) {
362
            foreach (array('return%sValue', 'returnRequestValue', 'returnCookieValue') as $testfunction) {
363
                $url = 'TestController/' . sprintf($testfunction, ucfirst($method))
364
                    . '?' . http_build_query($fixture);
365
                $tests[] = [$url, $fixture, $method];
366
            }
367
        }
368
        return $tests;
369
    }
370
371
    /**
372
     * @dataProvider providerTestTestRequestCarriesGlobals
373
     * @param $url
374
     * @param $fixture
375
     * @param $method
376
     */
377
    public function testTestRequestCarriesGlobals($url, $fixture, $method)
378
    {
379
        $getresponse = Director::test(
380
            $url,
381
            $fixture,
382
            null,
383
            strtoupper($method),
384
            null,
385
            null,
386
            Injector::inst()->createWithArgs(Cookie_Backend::class, array($fixture))
387
        );
388
389
        $this->assertInstanceOf(HTTPResponse::class, $getresponse, 'Director::test() returns HTTPResponse');
390
        $this->assertEquals($fixture['somekey'], $getresponse->getBody(), "Director::test({$url}, {$method})");
391
    }
392
393
    /**
394
     * Tests that additional parameters specified in the routing table are
395
     * saved in the request
396
     */
397
    public function testRouteParams()
398
    {
399
        /** @var HTTPRequest $request */
400
        Director::test('en-nz/myaction/myid/myotherid', null, null, null, null, null, null, $request);
401
402
        $this->assertEquals(
403
            array(
404
                'Controller' => TestController::class,
405
                'Action' => 'myaction',
406
                'ID' => 'myid',
407
                'OtherID' => 'myotherid',
408
                'Locale' => 'en_NZ'
409
            ),
410
            $request->params()
411
        );
412
    }
413
414
    public function testForceSSLProtectsEntireSite()
415
    {
416
        $this->expectExceptionRedirect('https://www.mysite.com/some-url');
417
        Director::mockRequest(function () {
418
            Director::forceSSL();
419
        }, '/some-url');
420
    }
421
422
    public function testForceSSLOnTopLevelPagePattern()
423
    {
424
        // Expect admin to trigger redirect
425
        $this->expectExceptionRedirect('https://www.mysite.com/admin');
426
        Director::mockRequest(function () {
427
            Director::forceSSL(array('/^admin/'));
428
        }, '/admin');
429
    }
430
431
    public function testForceSSLOnSubPagesPattern()
432
    {
433
        // Expect to redirect to security login page
434
        $this->expectExceptionRedirect('https://www.mysite.com/Security/login');
435
        Director::mockRequest(function () {
436
            Director::forceSSL(array('/^Security/'));
437
        }, '/Security/login');
438
    }
439
440
    public function testForceSSLWithPatternDoesNotMatchOtherPages()
441
    {
442
        // Not on same url should not trigger redirect
443
        Director::mockRequest(function () {
444
            $this->assertFalse(Director::forceSSL(array('/^admin/')));
445
        }, Director::baseURL() . 'normal-page');
446
447
        // nested url should not triger redirect either
448
        Director::mockRequest(function () {
449
            $this->assertFalse(Director::forceSSL(array('/^admin/', '/^Security/')));
450
        }, Director::baseURL() . 'just-another-page/sub-url');
451
    }
452
453
    public function testForceSSLAlternateDomain()
454
    {
455
        // Ensure that forceSSL throws the appropriate exception
456
        $this->expectExceptionRedirect('https://secure.mysite.com/admin');
457
        Director::mockRequest(function (HTTPRequest $request) {
0 ignored issues
show
Unused Code introduced by
The parameter $request is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
458
            return Director::forceSSL(array('/^admin/'), 'secure.mysite.com');
459
        }, Director::baseURL() . 'admin');
460
    }
461
462
    /**
463
     * Set url to redirect to
464
     *
465
     * @var string
466
     */
467
    protected $expectedRedirect = null;
468
469
    /**
470
     * Expects this test to throw a HTTPResponse_Exception with the given redirect
471
     *
472
     * @param string $url
473
     */
474
    protected function expectExceptionRedirect($url)
475
    {
476
        $this->expectedRedirect = $url;
477
    }
478
479
    protected function runTest()
480
    {
481
        try {
482
            $result = parent::runTest();
483
            if ($this->expectedRedirect) {
484
                $this->fail("Expected to redirect to {$this->expectedRedirect} but no redirect found");
485
            }
486
            return $result;
487
        } catch (HTTPResponse_Exception $exception) {
488
            // Check URL
489
            if ($this->expectedRedirect) {
490
                $url = $exception->getResponse()->getHeader('Location');
491
                $this->assertEquals($this->expectedRedirect, $url, "Expected to redirect to {$this->expectedRedirect}");
492
                return null;
493
            } else {
494
                throw $exception;
495
            }
496
        }
497
    }
498
499
    /**
500
     * @covers \SilverStripe\Control\Director::extract_request_headers()
501
     */
502
    public function testExtractRequestHeaders()
503
    {
504
        $request = array(
505
            'REDIRECT_STATUS'      => '200',
506
            'HTTP_HOST'            => 'host',
507
            'HTTP_USER_AGENT'      => 'User Agent',
508
            'HTTP_ACCEPT'          => 'text/html',
509
            'HTTP_ACCEPT_LANGUAGE' => 'en-us',
510
            'HTTP_COOKIE'          => 'MyCookie=1',
511
            'SERVER_PROTOCOL'      => 'HTTP/1.1',
512
            'REQUEST_METHOD'       => 'GET',
513
            'REQUEST_URI'          => '/',
514
            'SCRIPT_NAME'          => FRAMEWORK_DIR . '/main.php',
515
            'CONTENT_TYPE'         => 'text/xml',
516
            'CONTENT_LENGTH'       => 10
517
        );
518
519
        $headers = array(
520
            'Host'            => 'host',
521
            'User-Agent'      => 'User Agent',
522
            'Accept'          => 'text/html',
523
            'Accept-Language' => 'en-us',
524
            'Cookie'          => 'MyCookie=1',
525
            'Content-Type'    => 'text/xml',
526
            'Content-Length'  => '10'
527
        );
528
529
        $this->assertEquals($headers, HTTPRequestBuilder::extractRequestHeaders($request));
530
    }
531
532
    public function testUnmatchedRequestReturns404()
533
    {
534
        // Remove non-tested rules
535
        $this->assertEquals(404, Director::test('no-route')->getStatusCode());
536
    }
537
538
    public function testIsHttps()
0 ignored issues
show
Coding Style introduced by
testIsHttps uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
539
    {
540
        // Trust all IPs for this test
541
        /** @var TrustedProxyMiddleware $trustedProxyMiddleware */
542
        $trustedProxyMiddleware
543
            = Injector::inst()->get(TrustedProxyMiddleware::class);
544
        $trustedProxyMiddleware->setTrustedProxyIPs('*');
545
546
        // Clear alternate_base_url for this test
547
        Director::config()->remove('alternate_base_url');
548
549
        // nothing available
550
        $headers = array(
551
            'HTTP_X_FORWARDED_PROTOCOL', 'HTTPS', 'SSL'
552
        );
553
        foreach ($headers as $header) {
554
            if (isset($_SERVER[$header])) {
555
                unset($_SERVER['HTTP_X_FORWARDED_PROTOCOL']);
556
            }
557
        }
558
559
        $this->assertEquals(
560
            'no',
561
            Director::test('TestController/returnIsSSL')->getBody()
562
        );
563
564
        $this->assertEquals(
565
            'yes',
566
            Director::test(
567
                'TestController/returnIsSSL',
568
                null,
569
                null,
570
                null,
571
                null,
572
                [ 'X-Forwarded-Protocol' => 'https' ]
573
            )->getBody()
574
        );
575
576
        $this->assertEquals(
577
            'no',
578
            Director::test(
579
                'TestController/returnIsSSL',
580
                null,
581
                null,
582
                null,
583
                null,
584
                [ 'X-Forwarded-Protocol' => 'http' ]
585
            )->getBody()
586
        );
587
588
        $this->assertEquals(
589
            'no',
590
            Director::test(
591
                'TestController/returnIsSSL',
592
                null,
593
                null,
594
                null,
595
                null,
596
                [ 'X-Forwarded-Protocol' => 'ftp' ]
597
            )->getBody()
598
        );
599
600
        // https via HTTPS
601
        $_SERVER['HTTPS'] = 'true';
602
        $this->assertEquals(
603
            'yes',
604
            Director::test('TestController/returnIsSSL')->getBody()
605
        );
606
607
        $_SERVER['HTTPS'] = '1';
608
        $this->assertEquals(
609
            'yes',
610
            Director::test('TestController/returnIsSSL')->getBody()
611
        );
612
613
        $_SERVER['HTTPS'] = 'off';
614
        $this->assertEquals(
615
            'no',
616
            Director::test('TestController/returnIsSSL')->getBody()
617
        );
618
619
        // https via SSL
620
        $_SERVER['SSL'] = '';
621
        $this->assertEquals(
622
            'yes',
623
            Director::test('TestController/returnIsSSL')->getBody()
624
        );
625
    }
626
627
    public function testTestIgnoresHashes()
628
    {
629
        //test that hashes are ignored
630
        $url = "TestController/returnGetValue?somekey=key";
631
        $hash = "#test";
632
        /** @var HTTPRequest $request */
633
        $response = Director::test($url . $hash, null, null, null, null, null, null, $request);
634
        $this->assertFalse($response->isError());
635
        $this->assertEquals('key', $response->getBody());
636
        $this->assertEquals($request->getURL(true), $url);
637
638
        //test encoded hashes are accepted
639
        $url = "TestController/returnGetValue?somekey=test%23key";
640
        $response = Director::test($url, null, null, null, null, null, null, $request);
641
        $this->assertFalse($response->isError());
642
        $this->assertEquals('test#key', $response->getBody());
643
        $this->assertEquals($request->getURL(true), $url);
644
    }
645
646
    public function testRequestFilterInDirectorTest()
647
    {
648
        $filter = new DirectorTest\TestRequestFilter;
649
650
        $processor = new RequestProcessor(array($filter));
0 ignored issues
show
Deprecated Code introduced by
The class SilverStripe\Control\RequestProcessor has been deprecated with message: 4.0..5.0 Use HTTPMiddleware directly instead.

This class, trait or interface has been deprecated. The supplier of the file has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the type will be removed from the class and what other constant to use instead.

Loading history...
651
652
        Injector::inst()->registerService($processor, RequestProcessor::class);
653
        $response = Director::test('some-dummy-url');
654
        $this->assertEquals(404, $response->getStatusCode());
655
656
        $this->assertEquals(1, $filter->preCalls);
657
        $this->assertEquals(1, $filter->postCalls);
658
659
        $filter->failPost = true;
660
661
        $response = Director::test('some-dummy-url');
662
        $this->assertEquals(500, $response->getStatusCode());
663
        $this->assertEquals(_t(Director::class.'.REQUEST_ABORTED', 'Request aborted'), $response->getBody());
664
665
        $this->assertEquals(2, $filter->preCalls);
666
        $this->assertEquals(2, $filter->postCalls);
667
668
        $filter->failPre = true;
669
670
        $response = Director::test('some-dummy-url');
671
        $this->assertEquals(400, $response->getStatusCode());
672
        $this->assertEquals(_t(Director::class.'.INVALID_REQUEST', 'Invalid request'), $response->getBody());
673
674
        $this->assertEquals(3, $filter->preCalls);
675
676
        // preCall 'true' will trigger an exception and prevent post call execution
677
        $this->assertEquals(2, $filter->postCalls);
678
    }
679
680
    public function testGlobalMiddleware()
681
    {
682
        $middleware = new DirectorTest\TestMiddleware;
683
        Injector::inst()->registerService($middleware, HTTPMiddleware::class . '.director');
684
685
        $response = Director::test('some-dummy-url');
686
        $this->assertEquals(404, $response->getStatusCode());
687
688
        // Both triggered
689
        $this->assertEquals(1, $middleware->preCalls);
690
        $this->assertEquals(1, $middleware->postCalls);
691
692
        $middleware->failPost = true;
693
694
        $response = Director::test('some-dummy-url');
695
        $this->assertEquals(500, $response->getStatusCode());
696
697
        // Both triggered
698
        $this->assertEquals(2, $middleware->preCalls);
699
        $this->assertEquals(2, $middleware->postCalls);
700
701
        $middleware->failPre = true;
702
703
        $response = Director::test('some-dummy-url');
704
        $this->assertEquals(400, $response->getStatusCode());
705
706
        // Pre triggered, post not
707
        $this->assertEquals(3, $middleware->preCalls);
708
        $this->assertEquals(2, $middleware->postCalls);
709
    }
710
711
    public function testRouteSpecificMiddleware()
712
    {
713
        $specificMiddleware = new DirectorTest\TestMiddleware;
714
        Injector::inst()->registerService($specificMiddleware, 'Middleware2');
715
716
        // Global middleware
717
        $middleware = new DirectorTest\TestMiddleware;
718
        Injector::inst()->registerService($middleware, HTTPMiddleware::class . '.director');
719
720
        // URL rules, one of which has a specific middleware
721
        Config::modify()->set(
722
            Director::class,
723
            'rules',
724
            [
725
                'url-one' => TestController::class,
726
                'url-two' => [
727
                    'Controller' => TestController::class,
728
                    'Middleware' => 'Middleware2'
729
                ]
730
            ]
731
        );
732
733
        // URL without a route-specific middleware
734
        Director::test('url-one');
735
736
        // Only the global middleware triggered
737
        $this->assertEquals(1, $middleware->preCalls);
738
        $this->assertEquals(0, $specificMiddleware->postCalls);
739
740
        Director::test('url-two');
741
742
        // Both triggered on the url with the specific middleware applied
743
        $this->assertEquals(2, $middleware->preCalls);
744
        $this->assertEquals(1, $specificMiddleware->postCalls);
745
    }
746
}
747