Passed
Push — master ( 9b9c6c...ef704e )
by Daniel
35:52 queued 24:20
created

tests/php/Control/DirectorTest.php (1 issue)

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\HTTPResponse;
8
use SilverStripe\Control\HTTPResponse_Exception;
9
use SilverStripe\Control\Middleware\CanonicalURLMiddleware;
10
use SilverStripe\Control\Middleware\RequestHandlerMiddlewareAdapter;
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:9090/');
32
33
        // Ensure redirects enabled on all environments
34
        CanonicalURLMiddleware::singleton()->setEnabledEnvs(true);
35
        $this->expectedRedirect = null;
36
    }
37
38
    protected function getExtraRoutes()
39
    {
40
        $rules = parent::getExtraRoutes();
41
42
        $rules['DirectorTestRule/$Action/$ID/$OtherID'] = TestController::class;
43
        $rules['en-nz/$Action/$ID/$OtherID'] = [
44
            'Controller' => TestController::class,
45
            'Locale' => 'en_NZ',
46
        ];
47
        return $rules;
48
    }
49
50
    protected function setUpRoutes()
51
    {
52
        // Don't merge with any existing rules
53
        Director::config()->set('rules', $this->getExtraRoutes());
54
    }
55
56
    public function testFileExists()
57
    {
58
        $tempFileName = 'DirectorTest_testFileExists.tmp';
59
        $tempFilePath = TEMP_PATH . DIRECTORY_SEPARATOR . $tempFileName;
60
61
        // create temp file
62
        file_put_contents($tempFilePath, '');
63
64
        $this->assertTrue(
65
            Director::fileExists($tempFilePath),
66
            'File exist check with absolute path'
67
        );
68
69
        $this->assertTrue(
70
            Director::fileExists($tempFilePath . '?queryparams=1&foo[bar]=bar'),
71
            'File exist check with query params ignored'
72
        );
73
74
        unlink($tempFilePath);
75
    }
76
77
    public function testAbsoluteURL()
78
    {
79
        Director::config()->set('alternate_base_url', 'http://www.mysite.com:9090/mysite/');
80
        $_SERVER['REQUEST_URI'] = "http://www.mysite.com:9090/mysite/sub-page/";
81
82
        //test empty / local urls
83
        foreach (array('', './', '.') as $url) {
84
            $this->assertEquals("http://www.mysite.com:9090/mysite/", Director::absoluteURL($url, Director::BASE));
85
            $this->assertEquals("http://www.mysite.com:9090/", Director::absoluteURL($url, Director::ROOT));
86
            $this->assertEquals("http://www.mysite.com:9090/mysite/sub-page/", Director::absoluteURL($url, Director::REQUEST));
87
        }
88
89
        // Test site root url
90
        $this->assertEquals("http://www.mysite.com:9090/", Director::absoluteURL('/'));
91
92
        // Test Director::BASE
93
        $this->assertEquals('http://www.mysite.com:9090/', Director::absoluteURL('http://www.mysite.com:9090/', Director::BASE));
94
        $this->assertEquals('http://www.mytest.com', Director::absoluteURL('http://www.mytest.com', Director::BASE));
95
        $this->assertEquals("http://www.mysite.com:9090/test", Director::absoluteURL("http://www.mysite.com:9090/test", Director::BASE));
96
        $this->assertEquals("http://www.mysite.com:9090/root", Director::absoluteURL("/root", Director::BASE));
97
        $this->assertEquals("http://www.mysite.com:9090/root/url", Director::absoluteURL("/root/url", Director::BASE));
98
99
        // Test Director::ROOT
100
        $this->assertEquals('http://www.mysite.com:9090/', Director::absoluteURL('http://www.mysite.com:9090/', Director::ROOT));
101
        $this->assertEquals('http://www.mytest.com', Director::absoluteURL('http://www.mytest.com', Director::ROOT));
102
        $this->assertEquals("http://www.mysite.com:9090/test", Director::absoluteURL("http://www.mysite.com:9090/test", Director::ROOT));
103
        $this->assertEquals("http://www.mysite.com:9090/root", Director::absoluteURL("/root", Director::ROOT));
104
        $this->assertEquals("http://www.mysite.com:9090/root/url", Director::absoluteURL("/root/url", Director::ROOT));
105
106
        // Test Director::REQUEST
107
        $this->assertEquals('http://www.mysite.com:9090/', Director::absoluteURL('http://www.mysite.com:9090/', Director::REQUEST));
108
        $this->assertEquals('http://www.mytest.com', Director::absoluteURL('http://www.mytest.com', Director::REQUEST));
109
        $this->assertEquals("http://www.mysite.com:9090/test", Director::absoluteURL("http://www.mysite.com:9090/test", Director::REQUEST));
110
        $this->assertEquals("http://www.mysite.com:9090/root", Director::absoluteURL("/root", Director::REQUEST));
111
        $this->assertEquals("http://www.mysite.com:9090/root/url", Director::absoluteURL("/root/url", Director::REQUEST));
112
113
        // Test evaluating relative urls relative to base (default)
114
        $this->assertEquals("http://www.mysite.com:9090/mysite/test", Director::absoluteURL("test"));
115
        $this->assertEquals("http://www.mysite.com:9090/mysite/test/url", Director::absoluteURL("test/url"));
116
        $this->assertEquals("http://www.mysite.com:9090/mysite/test", Director::absoluteURL("test", Director::BASE));
117
        $this->assertEquals("http://www.mysite.com:9090/mysite/test/url", Director::absoluteURL("test/url", Director::BASE));
118
119
        // Test evaluting relative urls relative to root
120
        $this->assertEquals("http://www.mysite.com:9090/test", Director::absoluteURL("test", Director::ROOT));
121
        $this->assertEquals("http://www.mysite.com:9090/test/url", Director::absoluteURL("test/url", Director::ROOT));
122
123
        // Test relative to requested page
124
        $this->assertEquals("http://www.mysite.com:9090/mysite/sub-page/test", Director::absoluteURL("test", Director::REQUEST));
125
        $this->assertEquals("http://www.mysite.com:9090/mysite/sub-page/test/url", Director::absoluteURL("test/url", Director::REQUEST));
126
127
        // Test that javascript links are not left intact
128
        $this->assertStringStartsNotWith('javascript', Director::absoluteURL('javascript:alert("attack")'));
129
        $this->assertStringStartsNotWith('alert', Director::absoluteURL('javascript:alert("attack")'));
130
        $this->assertStringStartsNotWith('javascript', Director::absoluteURL('alert("attack")'));
131
        $this->assertStringStartsNotWith('alert', Director::absoluteURL('alert("attack")'));
132
    }
133
134
    public function testAlternativeBaseURL()
135
    {
136
        // relative base URLs - you should end them in a /
137
        Director::config()->set('alternate_base_url', '/relativebase/');
138
        $_SERVER['HTTP_HOST'] = 'www.somesite.com';
139
        $_SERVER['REQUEST_URI'] = "/relativebase/sub-page/";
140
141
        $this->assertEquals('/relativebase/', Director::baseURL());
142
        $this->assertEquals('http://www.somesite.com/relativebase/', Director::absoluteBaseURL());
143
        $this->assertEquals(
144
            'http://www.somesite.com/relativebase/subfolder/test',
145
            Director::absoluteURL('subfolder/test')
146
        );
147
148
        // absolute base URLS with subdirectory - You should end them in a /
149
        Director::config()->set('alternate_base_url', 'http://www.example.org/relativebase/');
150
        $_SERVER['REQUEST_URI'] = "http://www.example.org/relativebase/sub-page/";
151
        $this->assertEquals('/relativebase/', Director::baseURL()); // Non-absolute url
152
        $this->assertEquals('http://www.example.org/relativebase/', Director::absoluteBaseURL());
153
        $this->assertEquals('http://www.example.org/relativebase/sub-page/', Director::absoluteURL('', Director::REQUEST));
154
        $this->assertEquals('http://www.example.org/relativebase/', Director::absoluteURL('', Director::BASE));
155
        $this->assertEquals('http://www.example.org/', Director::absoluteURL('', Director::ROOT));
156
        $this->assertEquals(
157
            'http://www.example.org/relativebase/sub-page/subfolder/test',
158
            Director::absoluteURL('subfolder/test', Director::REQUEST)
159
        );
160
        $this->assertEquals(
161
            'http://www.example.org/subfolder/test',
162
            Director::absoluteURL('subfolder/test', Director::ROOT)
163
        );
164
        $this->assertEquals(
165
            'http://www.example.org/relativebase/subfolder/test',
166
            Director::absoluteURL('subfolder/test', Director::BASE)
167
        );
168
169
        // absolute base URLs - you should end them in a /
170
        Director::config()->set('alternate_base_url', 'http://www.example.org/');
171
        $_SERVER['REQUEST_URI'] = "http://www.example.org/sub-page/";
172
        $this->assertEquals('/', Director::baseURL()); // Non-absolute url
173
        $this->assertEquals('http://www.example.org/', Director::absoluteBaseURL());
174
        $this->assertEquals('http://www.example.org/sub-page/', Director::absoluteURL('', Director::REQUEST));
175
        $this->assertEquals('http://www.example.org/', Director::absoluteURL('', Director::BASE));
176
        $this->assertEquals('http://www.example.org/', Director::absoluteURL('', Director::ROOT));
177
        $this->assertEquals(
178
            'http://www.example.org/sub-page/subfolder/test',
179
            Director::absoluteURL('subfolder/test', Director::REQUEST)
180
        );
181
        $this->assertEquals(
182
            'http://www.example.org/subfolder/test',
183
            Director::absoluteURL('subfolder/test', Director::ROOT)
184
        );
185
        $this->assertEquals(
186
            'http://www.example.org/subfolder/test',
187
            Director::absoluteURL('subfolder/test', Director::BASE)
188
        );
189
    }
190
191
    /**
192
     * Tests that {@link Director::is_absolute()} works under different environment types
193
     */
194
    public function testIsAbsolute()
195
    {
196
        $expected = array (
197
            'C:/something' => true,
198
            'd:\\'         => true,
199
            'e/'           => false,
200
            's:/directory' => true,
201
            '/var/www'     => true,
202
            '\\Something'  => true,
203
            'something/c:' => false,
204
            'folder'       => false,
205
            'a/c:/'        => false
206
        );
207
208
        foreach ($expected as $path => $result) {
209
            $this->assertEquals(Director::is_absolute($path), $result, "Test result for $path");
210
        }
211
    }
212
213
    public function testIsAbsoluteUrl()
214
    {
215
        $this->assertTrue(Director::is_absolute_url('http://test.com/testpage'));
216
        $this->assertTrue(Director::is_absolute_url('ftp://test.com'));
217
        $this->assertFalse(Director::is_absolute_url('test.com/testpage'));
218
        $this->assertFalse(Director::is_absolute_url('/relative'));
219
        $this->assertFalse(Director::is_absolute_url('relative'));
220
        $this->assertFalse(Director::is_absolute_url("/relative/?url=http://foo.com"));
221
        $this->assertFalse(Director::is_absolute_url("/relative/#http://foo.com"));
222
        $this->assertTrue(Director::is_absolute_url("https://test.com/?url=http://foo.com"));
223
        $this->assertTrue(Director::is_absolute_url("trickparseurl:http://test.com"));
224
        $this->assertTrue(Director::is_absolute_url('//test.com'));
225
        $this->assertTrue(Director::is_absolute_url('/////test.com'));
226
        $this->assertTrue(Director::is_absolute_url('  ///test.com'));
227
        $this->assertTrue(Director::is_absolute_url('http:test.com'));
228
        $this->assertTrue(Director::is_absolute_url('//http://test.com'));
229
    }
230
231
    public function testHostPortParts()
232
    {
233
        $this->assertEquals('www.mysite.com:9090', Director::host());
234
        $this->assertEquals('www.mysite.com', Director::hostName());
235
        $this->assertEquals('9090', Director::port());
236
    }
237
238
    public function testIsRelativeUrl()
239
    {
240
        $this->assertFalse(Director::is_relative_url('http://test.com'));
241
        $this->assertFalse(Director::is_relative_url('https://test.com'));
242
        $this->assertFalse(Director::is_relative_url('   https://test.com/testpage   '));
243
        $this->assertTrue(Director::is_relative_url('test.com/testpage'));
244
        $this->assertFalse(Director::is_relative_url('ftp://test.com'));
245
        $this->assertTrue(Director::is_relative_url('/relative'));
246
        $this->assertTrue(Director::is_relative_url('relative'));
247
        $this->assertTrue(Director::is_relative_url('/relative/?url=http://test.com'));
248
        $this->assertTrue(Director::is_relative_url('/relative/#=http://test.com'));
249
    }
250
251
    /**
252
     * @return array
253
     */
254
    public function providerMakeRelative()
255
    {
256
        return [
257
            // Resilience to slash position
258
            [
259
                'http://www.mysite.com:9090/base/folder',
260
                'http://www.mysite.com:9090/base/folder',
261
                ''
262
            ],
263
            [
264
                'http://www.mysite.com:9090/base/folder',
265
                'http://www.mysite.com:9090/base/folder/',
266
                ''
267
            ],
268
            [
269
                'http://www.mysite.com:9090/base/folder/',
270
                'http://www.mysite.com:9090/base/folder',
271
                ''
272
            ],
273
            [
274
                'http://www.mysite.com:9090/',
275
                'http://www.mysite.com:9090/',
276
                ''
277
            ],
278
            [
279
                'http://www.mysite.com:9090/',
280
                'http://www.mysite.com',
281
                ''
282
            ],
283
            [
284
                'http://www.mysite.com',
285
                'http://www.mysite.com:9090/',
286
                ''
287
            ],
288
            [
289
                'http://www.mysite.com:9090/base/folder',
290
                'http://www.mysite.com:9090/base/folder/page',
291
                'page'
292
            ],
293
            [
294
                'http://www.mysite.com:9090/',
295
                'http://www.mysite.com:9090/page/',
296
                'page/'
297
            ],
298
            // Parsing protocol safely
299
            [
300
                'http://www.mysite.com:9090/base/folder',
301
                'https://www.mysite.com:9090/base/folder',
302
                ''
303
            ],
304
            [
305
                'https://www.mysite.com:9090/base/folder',
306
                'http://www.mysite.com:9090/base/folder/testpage',
307
                'testpage'
308
            ],
309
            [
310
                'http://www.mysite.com:9090/base/folder',
311
                '//www.mysite.com:9090/base/folder/testpage',
312
                'testpage'
313
            ],
314
            // Dirty input
315
            [
316
                'http://www.mysite.com:9090/base/folder',
317
                '    https://www.mysite.com:9090/base/folder/testpage    ',
318
                'testpage'
319
            ],
320
            [
321
                'http://www.mysite.com:9090/base/folder',
322
                '//www.mysite.com:9090/base//folder/testpage//subpage',
323
                'testpage/subpage'
324
            ],
325
            // Non-http protocol isn't modified
326
            [
327
                'http://www.mysite.com:9090/base/folder',
328
                'ftp://test.com',
329
                'ftp://test.com'
330
            ],
331
            // Alternate hostnames are redirected
332
            [
333
                'https://www.mysite.com:9090/base/folder',
334
                'http://mysite.com:9090/base/folder/testpage',
335
                'testpage'
336
            ],
337
            [
338
                'http://www.otherdomain.com/base/folder',
339
                '//www.mysite.com:9090/base/folder/testpage',
340
                'testpage'
341
            ],
342
            // Base folder is found
343
            [
344
                'http://www.mysite.com:9090/base/folder',
345
                BASE_PATH . '/some/file.txt',
346
                'some/file.txt',
347
            ],
348
            // public folder is found
349
            [
350
                'http://www.mysite.com:9090/base/folder',
351
                PUBLIC_PATH . '/some/file.txt',
352
                'some/file.txt',
353
            ],
354
            // querystring is protected
355
            [
356
                'http://www.mysite.com:9090/base/folder',
357
                '//www.mysite.com:9090/base//folder/testpage//subpage?args=hello',
358
                'testpage/subpage?args=hello'
359
            ],
360
            [
361
                'http://www.mysite.com:9090/base/folder',
362
                '//www.mysite.com:9090/base//folder/?args=hello',
363
                '?args=hello'
364
            ],
365
        ];
366
    }
367
368
    /**
369
     * @dataProvider providerMakeRelative
370
     * @param string $baseURL Site base URL
371
     * @param string $requestURL Request URL
372
     * @param string $relativeURL Expected relative URL
373
     */
374
    public function testMakeRelative($baseURL, $requestURL, $relativeURL)
375
    {
376
        Director::config()->set('alternate_base_url', $baseURL);
377
        $actualRelative = Director::makeRelative($requestURL);
378
        $this->assertEquals(
379
            $relativeURL,
380
            $actualRelative,
381
            "Expected relativeURL of {$requestURL} to be {$relativeURL}"
382
        );
383
    }
384
385
    /**
386
     * Mostly tested by {@link testIsRelativeUrl()},
387
     * just adding the host name matching aspect here.
388
     */
389
    public function testIsSiteUrl()
390
    {
391
        $this->assertFalse(Director::is_site_url("http://test.com"));
392
        $this->assertTrue(Director::is_site_url(Director::absoluteBaseURL()));
393
        $this->assertFalse(Director::is_site_url("http://test.com?url=" . Director::absoluteBaseURL()));
394
        $this->assertFalse(Director::is_site_url("http://test.com?url=" . urlencode(Director::absoluteBaseURL())));
395
        $this->assertFalse(Director::is_site_url("//test.com?url=" . Director::absoluteBaseURL()));
396
    }
397
398
    /**
399
     * Tests isDev, isTest, isLive set from querystring
400
     */
401
    public function testQueryIsEnvironment()
402
    {
403
        if (!isset($_SESSION)) {
404
            $_SESSION = [];
405
        }
406
        // Reset
407
        unset($_SESSION['isDev']);
408
        unset($_SESSION['isLive']);
409
        unset($_GET['isTest']);
410
        unset($_GET['isDev']);
411
412
        /** @var Kernel $kernel */
413
        $kernel = Injector::inst()->get(Kernel::class);
414
        $kernel->setEnvironment(null);
415
416
        // Test isDev=1
417
        $_GET['isDev'] = '1';
418
        $this->assertTrue(Director::isDev());
419
        $this->assertFalse(Director::isTest());
420
        $this->assertFalse(Director::isLive());
421
422
        // Test persistence
423
        unset($_GET['isDev']);
424
        $this->assertTrue(Director::isDev());
425
        $this->assertFalse(Director::isTest());
426
        $this->assertFalse(Director::isLive());
427
428
        // Test change to isTest
429
        $_GET['isTest'] = '1';
430
        $this->assertFalse(Director::isDev());
431
        $this->assertTrue(Director::isTest());
432
        $this->assertFalse(Director::isLive());
433
434
        // Test persistence
435
        unset($_GET['isTest']);
436
        $this->assertFalse(Director::isDev());
437
        $this->assertTrue(Director::isTest());
438
        $this->assertFalse(Director::isLive());
439
    }
440
441
    public function testResetGlobalsAfterTestRequest()
442
    {
443
        $_GET = array('somekey' => 'getvalue');
444
        $_POST = array('somekey' => 'postvalue');
445
        $_COOKIE = array('somekey' => 'cookievalue');
446
447
        $cookies = Injector::inst()->createWithArgs(
448
            Cookie_Backend::class,
449
            array(array('somekey' => 'sometestcookievalue'))
450
        );
451
452
        Director::test(
453
            'errorpage?somekey=sometestgetvalue',
454
            array('somekey' => 'sometestpostvalue'),
455
            null,
456
            null,
457
            null,
458
            null,
459
            $cookies
460
        );
461
462
        $this->assertEquals(
463
            'getvalue',
464
            $_GET['somekey'],
465
            '$_GET reset to original value after Director::test()'
466
        );
467
        $this->assertEquals(
468
            'postvalue',
469
            $_POST['somekey'],
470
            '$_POST reset to original value after Director::test()'
471
        );
472
        $this->assertEquals(
473
            'cookievalue',
474
            $_COOKIE['somekey'],
475
            '$_COOKIE reset to original value after Director::test()'
476
        );
477
    }
478
479
    public function providerTestTestRequestCarriesGlobals()
480
    {
481
        $tests = [];
482
        $fixture = [ 'somekey' => 'sometestvalue' ];
483
        foreach (array('get', 'post') as $method) {
484
            foreach (array('return%sValue', 'returnRequestValue', 'returnCookieValue') as $testfunction) {
485
                $url = 'TestController/' . sprintf($testfunction, ucfirst($method))
486
                    . '?' . http_build_query($fixture);
487
                $tests[] = [$url, $fixture, $method];
488
            }
489
        }
490
        return $tests;
491
    }
492
493
    /**
494
     * @dataProvider providerTestTestRequestCarriesGlobals
495
     * @param $url
496
     * @param $fixture
497
     * @param $method
498
     */
499
    public function testTestRequestCarriesGlobals($url, $fixture, $method)
500
    {
501
        $getresponse = Director::test(
502
            $url,
503
            $fixture,
504
            null,
505
            strtoupper($method),
506
            null,
507
            null,
508
            Injector::inst()->createWithArgs(Cookie_Backend::class, array($fixture))
509
        );
510
511
        $this->assertInstanceOf(HTTPResponse::class, $getresponse, 'Director::test() returns HTTPResponse');
512
        $this->assertEquals($fixture['somekey'], $getresponse->getBody(), "Director::test({$url}, {$method})");
513
    }
514
515
    /**
516
     * Tests that additional parameters specified in the routing table are
517
     * saved in the request
518
     */
519
    public function testRouteParams()
520
    {
521
        /** @var HTTPRequest $request */
522
        Director::test('en-nz/myaction/myid/myotherid', null, null, null, null, null, null, $request);
523
524
        $this->assertEquals(
525
            array(
526
                'Controller' => TestController::class,
527
                'Action' => 'myaction',
528
                'ID' => 'myid',
529
                'OtherID' => 'myotherid',
530
                'Locale' => 'en_NZ'
531
            ),
532
            $request->params()
533
        );
534
    }
535
536
    public function testForceWWW()
537
    {
538
        $this->expectExceptionRedirect('http://www.mysite.com:9090/some-url');
539
        Director::mockRequest(function ($request) {
540
            Injector::inst()->registerService($request, HTTPRequest::class);
541
            Director::forceWWW();
542
        }, 'http://mysite.com:9090/some-url');
543
    }
544
545
    public function testPromisedForceWWW()
546
    {
547
        Director::forceWWW();
548
549
        // Flag is set but not redirected yet
550
        $middleware = CanonicalURLMiddleware::singleton();
551
        $this->assertTrue($middleware->getForceWWW());
552
553
        // Middleware forces the redirection eventually
554
        /** @var HTTPResponse $response */
555
        $response = Director::mockRequest(function ($request) use ($middleware) {
556
            return $middleware->process($request, function ($request) {
557
                return null;
558
            });
559
        }, 'http://mysite.com:9090/some-url');
560
561
        // Middleware returns non-exception redirect
562
        $this->assertEquals('http://www.mysite.com:9090/some-url', $response->getHeader('Location'));
563
        $this->assertEquals(301, $response->getStatusCode());
564
    }
565
566
    public function testForceSSLProtectsEntireSite()
567
    {
568
        $this->expectExceptionRedirect('https://www.mysite.com:9090/some-url');
569
        Director::mockRequest(function ($request) {
570
            Injector::inst()->registerService($request, HTTPRequest::class);
571
            Director::forceSSL();
572
        }, 'http://www.mysite.com:9090/some-url');
573
    }
574
575
    public function testPromisedForceSSL()
576
    {
577
            Director::forceSSL();
578
579
        // Flag is set but not redirected yet
580
        $middleware = CanonicalURLMiddleware::singleton();
581
        $this->assertTrue($middleware->getForceSSL());
582
583
        // Middleware forces the redirection eventually
584
        /** @var HTTPResponse $response */
585
        $response = Director::mockRequest(function ($request) use ($middleware) {
586
            return $middleware->process($request, function ($request) {
587
                return null;
588
            });
589
        }, 'http://www.mysite.com:9090/some-url');
590
591
        // Middleware returns non-exception redirect
592
        $this->assertEquals('https://www.mysite.com:9090/some-url', $response->getHeader('Location'));
593
        $this->assertEquals(301, $response->getStatusCode());
594
    }
595
596
    public function testForceSSLOnTopLevelPagePattern()
597
    {
598
        // Expect admin to trigger redirect
599
        $this->expectExceptionRedirect('https://www.mysite.com:9090/admin');
600
        Director::mockRequest(function (HTTPRequest $request) {
601
            Injector::inst()->registerService($request, HTTPRequest::class);
602
            Director::forceSSL(array('/^admin/'));
603
        }, 'http://www.mysite.com:9090/admin');
604
    }
605
606
    public function testForceSSLOnSubPagesPattern()
607
    {
608
        // Expect to redirect to security login page
609
        $this->expectExceptionRedirect('https://www.mysite.com:9090/Security/login');
610
        Director::mockRequest(function (HTTPRequest $request) {
611
            Injector::inst()->registerService($request, HTTPRequest::class);
612
            Director::forceSSL(array('/^Security/'));
613
        }, 'http://www.mysite.com:9090/Security/login');
614
    }
615
616
    public function testForceSSLWithPatternDoesNotMatchOtherPages()
617
    {
618
        // Not on same url should not trigger redirect
619
        $response = Director::mockRequest(function (HTTPRequest $request) {
620
            Injector::inst()->registerService($request, HTTPRequest::class);
621
            Director::forceSSL(array('/^admin/'));
622
        }, 'http://www.mysite.com:9090/normal-page');
623
        $this->assertNull($response, 'Non-matching patterns do not trigger redirect');
624
625
        // nested url should not triger redirect either
626
        $response = Director::mockRequest(function (HTTPRequest $request) {
627
            Injector::inst()->registerService($request, HTTPRequest::class);
628
            Director::forceSSL(array('/^admin/', '/^Security/'));
629
        }, 'http://www.mysite.com:9090/just-another-page/sub-url');
630
        $this->assertNull($response, 'Non-matching patterns do not trigger redirect');
631
    }
632
633
    public function testForceSSLAlternateDomain()
634
    {
635
        // Ensure that forceSSL throws the appropriate exception
636
        $this->expectExceptionRedirect('https://secure.mysite.com/admin');
637
        Director::mockRequest(function (HTTPRequest $request) {
638
            Injector::inst()->registerService($request, HTTPRequest::class);
639
            return Director::forceSSL(array('/^admin/'), 'secure.mysite.com');
640
        }, 'http://www.mysite.com:9090/admin');
641
    }
642
643
    public function testForceSSLAlternateDomainWithPort()
644
    {
645
        // Ensure that forceSSL throws the appropriate exception
646
        $this->expectExceptionRedirect('https://secure.mysite.com:81/admin');
647
        Director::mockRequest(function (HTTPRequest $request) {
648
            Injector::inst()->registerService($request, HTTPRequest::class);
649
            return Director::forceSSL(array('/^admin/'), 'secure.mysite.com:81');
650
        }, 'http://www.mysite.com:9090/admin');
651
    }
652
653
    /**
654
     * Test that combined forceWWW and forceSSL combine safely
655
     */
656
    public function testForceSSLandForceWWW()
657
    {
658
        Director::forceWWW();
659
        Director::forceSSL();
660
661
        // Flag is set but not redirected yet
662
        $middleware = CanonicalURLMiddleware::singleton();
663
        $this->assertTrue($middleware->getForceWWW());
664
        $this->assertTrue($middleware->getForceSSL());
665
666
        // Middleware forces the redirection eventually
667
        /** @var HTTPResponse $response */
668
        $response = Director::mockRequest(function ($request) use ($middleware) {
669
            return $middleware->process($request, function ($request) {
670
                return null;
671
            });
672
        }, 'http://mysite.com:9090/some-url');
673
674
        // Middleware returns non-exception redirect
675
        $this->assertEquals('https://www.mysite.com:9090/some-url', $response->getHeader('Location'));
676
        $this->assertEquals(301, $response->getStatusCode());
677
    }
678
679
    /**
680
     * Set url to redirect to
681
     *
682
     * @var string
683
     */
684
    protected $expectedRedirect = null;
685
686
    /**
687
     * Expects this test to throw a HTTPResponse_Exception with the given redirect
688
     *
689
     * @param string $url
690
     */
691
    protected function expectExceptionRedirect($url)
692
    {
693
        $this->expectedRedirect = $url;
694
    }
695
696
    protected function runTest()
697
    {
698
        try {
699
            $result = parent::runTest();
700
            if ($this->expectedRedirect) {
701
                $this->fail("Expected to redirect to {$this->expectedRedirect} but no redirect found");
702
            }
703
            return $result;
704
        } catch (HTTPResponse_Exception $exception) {
705
            // Check URL
706
            if ($this->expectedRedirect) {
707
                $url = $exception->getResponse()->getHeader('Location');
708
                $this->assertEquals($this->expectedRedirect, $url, "Expected to redirect to {$this->expectedRedirect}");
709
                return null;
710
            } else {
711
                throw $exception;
712
            }
713
        }
714
    }
715
716
    public function testUnmatchedRequestReturns404()
717
    {
718
        // Remove non-tested rules
719
        $this->assertEquals(404, Director::test('no-route')->getStatusCode());
720
    }
721
722
    public function testIsHttps()
723
    {
724
        // Trust all IPs for this test
725
        /** @var TrustedProxyMiddleware $trustedProxyMiddleware */
726
        $trustedProxyMiddleware
727
            = Injector::inst()->get(TrustedProxyMiddleware::class);
728
        $trustedProxyMiddleware->setTrustedProxyIPs('*');
729
730
        // Clear alternate_base_url for this test
731
        Director::config()->remove('alternate_base_url');
732
733
        // nothing available
734
        $headers = array(
735
            'HTTP_X_FORWARDED_PROTOCOL', 'HTTPS', 'SSL'
736
        );
737
        foreach ($headers as $header) {
738
            if (isset($_SERVER[$header])) {
739
                unset($_SERVER['HTTP_X_FORWARDED_PROTOCOL']);
740
            }
741
        }
742
743
        $this->assertEquals(
744
            'no',
745
            Director::test('TestController/returnIsSSL')->getBody()
746
        );
747
748
        $this->assertEquals(
749
            'yes',
750
            Director::test(
751
                'TestController/returnIsSSL',
752
                null,
753
                null,
754
                null,
755
                null,
756
                [ 'X-Forwarded-Protocol' => 'https' ]
757
            )->getBody()
758
        );
759
760
        $this->assertEquals(
761
            'no',
762
            Director::test(
763
                'TestController/returnIsSSL',
764
                null,
765
                null,
766
                null,
767
                null,
768
                [ 'X-Forwarded-Protocol' => 'http' ]
769
            )->getBody()
770
        );
771
772
        $this->assertEquals(
773
            'no',
774
            Director::test(
775
                'TestController/returnIsSSL',
776
                null,
777
                null,
778
                null,
779
                null,
780
                [ 'X-Forwarded-Protocol' => 'ftp' ]
781
            )->getBody()
782
        );
783
784
        // https via HTTPS
785
        $_SERVER['HTTPS'] = 'true';
786
        $this->assertEquals(
787
            'yes',
788
            Director::test('TestController/returnIsSSL')->getBody()
789
        );
790
791
        $_SERVER['HTTPS'] = '1';
792
        $this->assertEquals(
793
            'yes',
794
            Director::test('TestController/returnIsSSL')->getBody()
795
        );
796
797
        $_SERVER['HTTPS'] = 'off';
798
        $this->assertEquals(
799
            'no',
800
            Director::test('TestController/returnIsSSL')->getBody()
801
        );
802
803
        // https via SSL
804
        $_SERVER['SSL'] = '';
805
        $this->assertEquals(
806
            'yes',
807
            Director::test('TestController/returnIsSSL')->getBody()
808
        );
809
    }
810
811
    public function testTestIgnoresHashes()
812
    {
813
        //test that hashes are ignored
814
        $url = "TestController/returnGetValue?somekey=key";
815
        $hash = "#test";
816
        /** @var HTTPRequest $request */
817
        $response = Director::test($url . $hash, null, null, null, null, null, null, $request);
818
        $this->assertFalse($response->isError());
819
        $this->assertEquals('key', $response->getBody());
820
        $this->assertEquals($request->getURL(true), $url);
821
822
        //test encoded hashes are accepted
823
        $url = "TestController/returnGetValue?somekey=test%23key";
824
        $response = Director::test($url, null, null, null, null, null, null, $request);
825
        $this->assertFalse($response->isError());
826
        $this->assertEquals('test#key', $response->getBody());
827
        $this->assertEquals($request->getURL(true), $url);
828
    }
829
830
    public function testRequestFilterInDirectorTest()
831
    {
832
        $filter = new DirectorTest\TestRequestFilter;
833
834
        $processor = new RequestProcessor(array($filter));
0 ignored issues
show
Deprecated Code introduced by
The class SilverStripe\Control\RequestProcessor has been deprecated: 4.0..5.0 Use HTTPMiddleware directly instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

834
        $processor = /** @scrutinizer ignore-deprecated */ new RequestProcessor(array($filter));
Loading history...
835
836
        Injector::inst()->registerService($processor, RequestProcessor::class);
837
        $response = Director::test('some-dummy-url');
838
        $this->assertEquals(404, $response->getStatusCode());
839
840
        $this->assertEquals(1, $filter->preCalls);
841
        $this->assertEquals(1, $filter->postCalls);
842
843
        $filter->failPost = true;
844
845
        $response = Director::test('some-dummy-url');
846
        $this->assertEquals(500, $response->getStatusCode());
847
        $this->assertEquals(_t(Director::class . '.REQUEST_ABORTED', 'Request aborted'), $response->getBody());
848
849
        $this->assertEquals(2, $filter->preCalls);
850
        $this->assertEquals(2, $filter->postCalls);
851
852
        $filter->failPre = true;
853
854
        $response = Director::test('some-dummy-url');
855
        $this->assertEquals(400, $response->getStatusCode());
856
        $this->assertEquals(_t(Director::class . '.INVALID_REQUEST', 'Invalid request'), $response->getBody());
857
858
        $this->assertEquals(3, $filter->preCalls);
859
860
        // preCall 'true' will trigger an exception and prevent post call execution
861
        $this->assertEquals(2, $filter->postCalls);
862
    }
863
864
    public function testGlobalMiddleware()
865
    {
866
        $middleware = new DirectorTest\TestMiddleware;
867
        Director::singleton()->setMiddlewares([$middleware]);
868
869
        $response = Director::test('some-dummy-url');
870
        $this->assertEquals(404, $response->getStatusCode());
871
872
        // Both triggered
873
        $this->assertEquals(1, $middleware->preCalls);
874
        $this->assertEquals(1, $middleware->postCalls);
875
876
        $middleware->failPost = true;
877
878
        $response = Director::test('some-dummy-url');
879
        $this->assertEquals(500, $response->getStatusCode());
880
881
        // Both triggered
882
        $this->assertEquals(2, $middleware->preCalls);
883
        $this->assertEquals(2, $middleware->postCalls);
884
885
        $middleware->failPre = true;
886
887
        $response = Director::test('some-dummy-url');
888
        $this->assertEquals(400, $response->getStatusCode());
889
890
        // Pre triggered, post not
891
        $this->assertEquals(3, $middleware->preCalls);
892
        $this->assertEquals(2, $middleware->postCalls);
893
    }
894
895
    public function testRouteSpecificMiddleware()
896
    {
897
        // Inject adapter in place of controller
898
        $specificMiddleware = new DirectorTest\TestMiddleware;
899
        Injector::inst()->registerService($specificMiddleware, 'SpecificMiddleware');
900
901
        // Register adapter as factory for creating this controller
902
        Config::modify()->merge(
903
            Injector::class,
904
            'ControllerWithMiddleware',
905
            [
906
                'class' => RequestHandlerMiddlewareAdapter::class,
907
                'constructor' => [
908
                    '%$' . TestController::class
909
                ],
910
                'properties' => [
911
                    'Middlewares' => [
912
                        '%$SpecificMiddleware',
913
                    ],
914
                ],
915
            ]
916
        );
917
918
        // Global middleware
919
        $middleware = new DirectorTest\TestMiddleware;
920
        Director::singleton()->setMiddlewares([ $middleware ]);
921
922
        // URL rules, one of which has a specific middleware
923
        Config::modify()->set(
924
            Director::class,
925
            'rules',
926
            [
927
                'url-one' => TestController::class,
928
                'url-two' => [
929
                    'Controller' => 'ControllerWithMiddleware',
930
                ],
931
            ]
932
        );
933
934
        // URL without a route-specific middleware
935
        Director::test('url-one');
936
937
        // Only the global middleware triggered
938
        $this->assertEquals(1, $middleware->preCalls);
939
        $this->assertEquals(0, $specificMiddleware->postCalls);
940
941
        Director::test('url-two');
942
943
        // Both triggered on the url with the specific middleware applied
944
        $this->assertEquals(2, $middleware->preCalls);
945
        $this->assertEquals(1, $specificMiddleware->postCalls);
946
    }
947
948
    /**
949
     * If using phpdbg it returns itself instead of "cli" from php_sapi_name()
950
     */
951
    public function testIsCli()
952
    {
953
        $this->assertTrue(Director::is_cli(), 'is_cli should be true for PHP CLI and phpdbg');
954
    }
955
956
    public function testMockRequest()
957
    {
958
        Director::config()->set('alternate_base_url', 'http://www.mysite.com:9090/some-subdir/');
959
960
        // Can handle root-relative $url
961
        Director::mockRequest(function (HTTPRequest $request) {
962
            $this->assertEquals('some-page/nested', $request->getURL());
963
            $this->assertEquals(1, $request->getVar('query'));
964
            $this->assertEquals('/some-subdir/some-page/nested', $_SERVER['REQUEST_URI']);
965
        }, '/some-subdir/some-page/nested?query=1');
966
967
        // Can handle absolute $url
968
        Director::mockRequest(function (HTTPRequest $request) {
969
            $this->assertEquals('some-page/nested', $request->getURL());
970
            $this->assertEquals(1, $request->getVar('query'));
971
            $this->assertEquals('/some-subdir/some-page/nested', $_SERVER['REQUEST_URI']);
972
        }, 'http://www.mysite.com:9090/some-subdir/some-page/nested?query=1');
973
974
        // Can handle relative $url
975
        Director::mockRequest(function (HTTPRequest $request) {
976
            $this->assertEquals('some-page/nested', $request->getURL());
977
            $this->assertEquals(1, $request->getVar('query'));
978
            $this->assertEquals('/some-subdir/some-page/nested', $_SERVER['REQUEST_URI']);
979
        }, 'some-page/nested?query=1');
980
    }
981
}
982