Completed
Push — master ( 0b9e95...0208b2 )
by Damian
42s queued 20s
created

DirectorTest::testIsRelativeUrl()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 11
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 9
nc 1
nop 0
dl 0
loc 11
rs 9.4285
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\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(
85
                "http://www.mysite.com:9090/mysite/",
86
                Director::absoluteURL($url, Director::BASE)
87
            );
88
            $this->assertEquals(
89
                "http://www.mysite.com:9090/",
90
                Director::absoluteURL($url, Director::ROOT)
91
            );
92
            $this->assertEquals(
93
                "http://www.mysite.com:9090/mysite/sub-page/",
94
                Director::absoluteURL($url, Director::REQUEST)
95
            );
96
        }
97
98
        // Test site root url
99
        $this->assertEquals("http://www.mysite.com:9090/", Director::absoluteURL('/'));
100
101
        // Test Director::BASE
102
        $this->assertEquals(
103
            'http://www.mysite.com:9090/',
104
            Director::absoluteURL('http://www.mysite.com:9090/', Director::BASE)
105
        );
106
        $this->assertEquals(
107
            'http://www.mytest.com',
108
            Director::absoluteURL('http://www.mytest.com', Director::BASE)
109
        );
110
        $this->assertEquals(
111
            "http://www.mysite.com:9090/test",
112
            Director::absoluteURL("http://www.mysite.com:9090/test", Director::BASE)
113
        );
114
        $this->assertEquals(
115
            "http://www.mysite.com:9090/root",
116
            Director::absoluteURL("/root", Director::BASE)
117
        );
118
        $this->assertEquals(
119
            "http://www.mysite.com:9090/root/url",
120
            Director::absoluteURL("/root/url", Director::BASE)
121
        );
122
123
        // Test Director::ROOT
124
        $this->assertEquals(
125
            'http://www.mysite.com:9090/',
126
            Director::absoluteURL('http://www.mysite.com:9090/', Director::ROOT)
127
        );
128
        $this->assertEquals(
129
            'http://www.mytest.com',
130
            Director::absoluteURL('http://www.mytest.com', Director::ROOT)
131
        );
132
        $this->assertEquals(
133
            "http://www.mysite.com:9090/test",
134
            Director::absoluteURL("http://www.mysite.com:9090/test", Director::ROOT)
135
        );
136
        $this->assertEquals(
137
            "http://www.mysite.com:9090/root",
138
            Director::absoluteURL("/root", Director::ROOT)
139
        );
140
        $this->assertEquals(
141
            "http://www.mysite.com:9090/root/url",
142
            Director::absoluteURL("/root/url", Director::ROOT)
143
        );
144
145
        // Test Director::REQUEST
146
        $this->assertEquals(
147
            'http://www.mysite.com:9090/',
148
            Director::absoluteURL('http://www.mysite.com:9090/', Director::REQUEST)
149
        );
150
        $this->assertEquals(
151
            'http://www.mytest.com',
152
            Director::absoluteURL('http://www.mytest.com', Director::REQUEST)
153
        );
154
        $this->assertEquals(
155
            "http://www.mysite.com:9090/test",
156
            Director::absoluteURL("http://www.mysite.com:9090/test", Director::REQUEST)
157
        );
158
        $this->assertEquals(
159
            "http://www.mysite.com:9090/root",
160
            Director::absoluteURL("/root", Director::REQUEST)
161
        );
162
        $this->assertEquals(
163
            "http://www.mysite.com:9090/root/url",
164
            Director::absoluteURL("/root/url", Director::REQUEST)
165
        );
166
167
        // Test evaluating relative urls relative to base (default)
168
        $this->assertEquals(
169
            "http://www.mysite.com:9090/mysite/test",
170
            Director::absoluteURL("test")
171
        );
172
        $this->assertEquals(
173
            "http://www.mysite.com:9090/mysite/test/url",
174
            Director::absoluteURL("test/url")
175
        );
176
        $this->assertEquals(
177
            "http://www.mysite.com:9090/mysite/test",
178
            Director::absoluteURL("test", Director::BASE)
179
        );
180
        $this->assertEquals(
181
            "http://www.mysite.com:9090/mysite/test/url",
182
            Director::absoluteURL("test/url", Director::BASE)
183
        );
184
185
        // Test evaluting relative urls relative to root
186
        $this->assertEquals(
187
            "http://www.mysite.com:9090/test",
188
            Director::absoluteURL("test", Director::ROOT)
189
        );
190
        $this->assertEquals(
191
            "http://www.mysite.com:9090/test/url",
192
            Director::absoluteURL("test/url", Director::ROOT)
193
        );
194
195
        // Test relative to requested page
196
        $this->assertEquals(
197
            "http://www.mysite.com:9090/mysite/sub-page/test",
198
            Director::absoluteURL("test", Director::REQUEST)
199
        );
200
        $this->assertEquals(
201
            "http://www.mysite.com:9090/mysite/sub-page/test/url",
202
            Director::absoluteURL("test/url", Director::REQUEST)
203
        );
204
205
        // Test that javascript links are not left intact
206
        $this->assertStringStartsNotWith('javascript', Director::absoluteURL('javascript:alert("attack")'));
207
        $this->assertStringStartsNotWith('alert', Director::absoluteURL('javascript:alert("attack")'));
208
        $this->assertStringStartsNotWith('javascript', Director::absoluteURL('alert("attack")'));
209
        $this->assertStringStartsNotWith('alert', Director::absoluteURL('alert("attack")'));
210
    }
211
212
    public function testAlternativeBaseURL()
213
    {
214
        // relative base URLs - you should end them in a /
215
        Director::config()->set('alternate_base_url', '/relativebase/');
216
        $_SERVER['HTTP_HOST'] = 'www.somesite.com';
217
        $_SERVER['REQUEST_URI'] = "/relativebase/sub-page/";
218
219
        $this->assertEquals('/relativebase/', Director::baseURL());
220
        $this->assertEquals('http://www.somesite.com/relativebase/', Director::absoluteBaseURL());
221
        $this->assertEquals(
222
            'http://www.somesite.com/relativebase/subfolder/test',
223
            Director::absoluteURL('subfolder/test')
224
        );
225
226
        // absolute base URLS with subdirectory - You should end them in a /
227
        Director::config()->set('alternate_base_url', 'http://www.example.org/relativebase/');
228
        $_SERVER['REQUEST_URI'] = "http://www.example.org/relativebase/sub-page/";
229
        $this->assertEquals('/relativebase/', Director::baseURL()); // Non-absolute url
230
        $this->assertEquals('http://www.example.org/relativebase/', Director::absoluteBaseURL());
231
        $this->assertEquals(
232
            'http://www.example.org/relativebase/sub-page/',
233
            Director::absoluteURL('', Director::REQUEST)
234
        );
235
        $this->assertEquals(
236
            'http://www.example.org/relativebase/',
237
            Director::absoluteURL('', Director::BASE)
238
        );
239
        $this->assertEquals('http://www.example.org/', Director::absoluteURL('', Director::ROOT));
240
        $this->assertEquals(
241
            'http://www.example.org/relativebase/sub-page/subfolder/test',
242
            Director::absoluteURL('subfolder/test', Director::REQUEST)
243
        );
244
        $this->assertEquals(
245
            'http://www.example.org/subfolder/test',
246
            Director::absoluteURL('subfolder/test', Director::ROOT)
247
        );
248
        $this->assertEquals(
249
            'http://www.example.org/relativebase/subfolder/test',
250
            Director::absoluteURL('subfolder/test', Director::BASE)
251
        );
252
253
        // absolute base URLs - you should end them in a /
254
        Director::config()->set('alternate_base_url', 'http://www.example.org/');
255
        $_SERVER['REQUEST_URI'] = "http://www.example.org/sub-page/";
256
        $this->assertEquals('/', Director::baseURL()); // Non-absolute url
257
        $this->assertEquals('http://www.example.org/', Director::absoluteBaseURL());
258
        $this->assertEquals('http://www.example.org/sub-page/', Director::absoluteURL('', Director::REQUEST));
259
        $this->assertEquals('http://www.example.org/', Director::absoluteURL('', Director::BASE));
260
        $this->assertEquals('http://www.example.org/', Director::absoluteURL('', Director::ROOT));
261
        $this->assertEquals(
262
            'http://www.example.org/sub-page/subfolder/test',
263
            Director::absoluteURL('subfolder/test', Director::REQUEST)
264
        );
265
        $this->assertEquals(
266
            'http://www.example.org/subfolder/test',
267
            Director::absoluteURL('subfolder/test', Director::ROOT)
268
        );
269
        $this->assertEquals(
270
            'http://www.example.org/subfolder/test',
271
            Director::absoluteURL('subfolder/test', Director::BASE)
272
        );
273
    }
274
275
    /**
276
     * Tests that {@link Director::is_absolute()} works under different environment types
277
     */
278
    public function testIsAbsolute()
279
    {
280
        $expected = array (
281
            'C:/something' => true,
282
            'd:\\'         => true,
283
            'e/'           => false,
284
            's:/directory' => true,
285
            '/var/www'     => true,
286
            '\\Something'  => true,
287
            'something/c:' => false,
288
            'folder'       => false,
289
            'a/c:/'        => false
290
        );
291
292
        foreach ($expected as $path => $result) {
293
            $this->assertEquals(Director::is_absolute($path), $result, "Test result for $path");
294
        }
295
    }
296
297
    public function testIsAbsoluteUrl()
298
    {
299
        $this->assertTrue(Director::is_absolute_url('http://test.com/testpage'));
300
        $this->assertTrue(Director::is_absolute_url('ftp://test.com'));
301
        $this->assertFalse(Director::is_absolute_url('test.com/testpage'));
302
        $this->assertFalse(Director::is_absolute_url('/relative'));
303
        $this->assertFalse(Director::is_absolute_url('relative'));
304
        $this->assertFalse(Director::is_absolute_url("/relative/?url=http://foo.com"));
305
        $this->assertFalse(Director::is_absolute_url("/relative/#http://foo.com"));
306
        $this->assertTrue(Director::is_absolute_url("https://test.com/?url=http://foo.com"));
307
        $this->assertTrue(Director::is_absolute_url("trickparseurl:http://test.com"));
308
        $this->assertTrue(Director::is_absolute_url('//test.com'));
309
        $this->assertTrue(Director::is_absolute_url('/////test.com'));
310
        $this->assertTrue(Director::is_absolute_url('  ///test.com'));
311
        $this->assertTrue(Director::is_absolute_url('http:test.com'));
312
        $this->assertTrue(Director::is_absolute_url('//http://test.com'));
313
    }
314
315
    public function testHostPortParts()
316
    {
317
        $this->assertEquals('www.mysite.com:9090', Director::host());
318
        $this->assertEquals('www.mysite.com', Director::hostName());
319
        $this->assertEquals('9090', Director::port());
320
    }
321
322
    public function testIsRelativeUrl()
323
    {
324
        $this->assertFalse(Director::is_relative_url('http://test.com'));
325
        $this->assertFalse(Director::is_relative_url('https://test.com'));
326
        $this->assertFalse(Director::is_relative_url('   https://test.com/testpage   '));
327
        $this->assertTrue(Director::is_relative_url('test.com/testpage'));
328
        $this->assertFalse(Director::is_relative_url('ftp://test.com'));
329
        $this->assertTrue(Director::is_relative_url('/relative'));
330
        $this->assertTrue(Director::is_relative_url('relative'));
331
        $this->assertTrue(Director::is_relative_url('/relative/?url=http://test.com'));
332
        $this->assertTrue(Director::is_relative_url('/relative/#=http://test.com'));
333
    }
334
335
    /**
336
     * @return array
337
     */
338
    public function providerMakeRelative()
339
    {
340
        return [
341
            // Resilience to slash position
342
            [
343
                'http://www.mysite.com:9090/base/folder',
344
                'http://www.mysite.com:9090/base/folder',
345
                ''
346
            ],
347
            [
348
                'http://www.mysite.com:9090/base/folder',
349
                'http://www.mysite.com:9090/base/folder/',
350
                ''
351
            ],
352
            [
353
                'http://www.mysite.com:9090/base/folder/',
354
                'http://www.mysite.com:9090/base/folder',
355
                ''
356
            ],
357
            [
358
                'http://www.mysite.com:9090/',
359
                'http://www.mysite.com:9090/',
360
                ''
361
            ],
362
            [
363
                'http://www.mysite.com:9090/',
364
                'http://www.mysite.com',
365
                ''
366
            ],
367
            [
368
                'http://www.mysite.com',
369
                'http://www.mysite.com:9090/',
370
                ''
371
            ],
372
            [
373
                'http://www.mysite.com:9090/base/folder',
374
                'http://www.mysite.com:9090/base/folder/page',
375
                'page'
376
            ],
377
            [
378
                'http://www.mysite.com:9090/',
379
                'http://www.mysite.com:9090/page/',
380
                'page/'
381
            ],
382
            // Parsing protocol safely
383
            [
384
                'http://www.mysite.com:9090/base/folder',
385
                'https://www.mysite.com:9090/base/folder',
386
                ''
387
            ],
388
            [
389
                'https://www.mysite.com:9090/base/folder',
390
                'http://www.mysite.com:9090/base/folder/testpage',
391
                'testpage'
392
            ],
393
            [
394
                'http://www.mysite.com:9090/base/folder',
395
                '//www.mysite.com:9090/base/folder/testpage',
396
                'testpage'
397
            ],
398
            // Dirty input
399
            [
400
                'http://www.mysite.com:9090/base/folder',
401
                '    https://www.mysite.com:9090/base/folder/testpage    ',
402
                'testpage'
403
            ],
404
            [
405
                'http://www.mysite.com:9090/base/folder',
406
                '//www.mysite.com:9090/base//folder/testpage//subpage',
407
                'testpage/subpage'
408
            ],
409
            // Non-http protocol isn't modified
410
            [
411
                'http://www.mysite.com:9090/base/folder',
412
                'ftp://test.com',
413
                'ftp://test.com'
414
            ],
415
            // Alternate hostnames are redirected
416
            [
417
                'https://www.mysite.com:9090/base/folder',
418
                'http://mysite.com:9090/base/folder/testpage',
419
                'testpage'
420
            ],
421
            [
422
                'http://www.otherdomain.com/base/folder',
423
                '//www.mysite.com:9090/base/folder/testpage',
424
                'testpage'
425
            ],
426
            // Base folder is found
427
            [
428
                'http://www.mysite.com:9090/base/folder',
429
                BASE_PATH . '/some/file.txt',
430
                'some/file.txt',
431
            ],
432
            // public folder is found
433
            [
434
                'http://www.mysite.com:9090/base/folder',
435
                PUBLIC_PATH . '/some/file.txt',
436
                'some/file.txt',
437
            ],
438
            // querystring is protected
439
            [
440
                'http://www.mysite.com:9090/base/folder',
441
                '//www.mysite.com:9090/base//folder/testpage//subpage?args=hello',
442
                'testpage/subpage?args=hello'
443
            ],
444
            [
445
                'http://www.mysite.com:9090/base/folder',
446
                '//www.mysite.com:9090/base//folder/?args=hello',
447
                '?args=hello'
448
            ],
449
        ];
450
    }
451
452
    /**
453
     * @dataProvider providerMakeRelative
454
     * @param string $baseURL Site base URL
455
     * @param string $requestURL Request URL
456
     * @param string $relativeURL Expected relative URL
457
     */
458
    public function testMakeRelative($baseURL, $requestURL, $relativeURL)
459
    {
460
        Director::config()->set('alternate_base_url', $baseURL);
461
        $actualRelative = Director::makeRelative($requestURL);
462
        $this->assertEquals(
463
            $relativeURL,
464
            $actualRelative,
465
            "Expected relativeURL of {$requestURL} to be {$relativeURL}"
466
        );
467
    }
468
469
    /**
470
     * Mostly tested by {@link testIsRelativeUrl()},
471
     * just adding the host name matching aspect here.
472
     */
473
    public function testIsSiteUrl()
474
    {
475
        $this->assertFalse(Director::is_site_url("http://test.com"));
476
        $this->assertTrue(Director::is_site_url(Director::absoluteBaseURL()));
477
        $this->assertFalse(Director::is_site_url("http://test.com?url=" . Director::absoluteBaseURL()));
478
        $this->assertFalse(Director::is_site_url("http://test.com?url=" . urlencode(Director::absoluteBaseURL())));
479
        $this->assertFalse(Director::is_site_url("//test.com?url=" . Director::absoluteBaseURL()));
480
    }
481
482
    /**
483
     * Tests isDev, isTest, isLive set from querystring
484
     */
485
    public function testQueryIsEnvironment()
486
    {
487
        if (!isset($_SESSION)) {
488
            $_SESSION = [];
489
        }
490
        // Reset
491
        unset($_SESSION['isDev']);
492
        unset($_SESSION['isLive']);
493
        unset($_GET['isTest']);
494
        unset($_GET['isDev']);
495
496
        /** @var Kernel $kernel */
497
        $kernel = Injector::inst()->get(Kernel::class);
498
        $kernel->setEnvironment(null);
499
500
        // Test isDev=1
501
        $_GET['isDev'] = '1';
502
        $this->assertTrue(Director::isDev());
503
        $this->assertFalse(Director::isTest());
504
        $this->assertFalse(Director::isLive());
505
506
        // Test persistence
507
        unset($_GET['isDev']);
508
        $this->assertTrue(Director::isDev());
509
        $this->assertFalse(Director::isTest());
510
        $this->assertFalse(Director::isLive());
511
512
        // Test change to isTest
513
        $_GET['isTest'] = '1';
514
        $this->assertFalse(Director::isDev());
515
        $this->assertTrue(Director::isTest());
516
        $this->assertFalse(Director::isLive());
517
518
        // Test persistence
519
        unset($_GET['isTest']);
520
        $this->assertFalse(Director::isDev());
521
        $this->assertTrue(Director::isTest());
522
        $this->assertFalse(Director::isLive());
523
    }
524
525
    public function testResetGlobalsAfterTestRequest()
526
    {
527
        $_GET = array('somekey' => 'getvalue');
528
        $_POST = array('somekey' => 'postvalue');
529
        $_COOKIE = array('somekey' => 'cookievalue');
530
531
        $cookies = Injector::inst()->createWithArgs(
532
            Cookie_Backend::class,
533
            array(array('somekey' => 'sometestcookievalue'))
534
        );
535
536
        Director::test(
537
            'errorpage?somekey=sometestgetvalue',
538
            array('somekey' => 'sometestpostvalue'),
539
            null,
540
            null,
541
            null,
542
            null,
543
            $cookies
544
        );
545
546
        $this->assertEquals(
547
            'getvalue',
548
            $_GET['somekey'],
549
            '$_GET reset to original value after Director::test()'
550
        );
551
        $this->assertEquals(
552
            'postvalue',
553
            $_POST['somekey'],
554
            '$_POST reset to original value after Director::test()'
555
        );
556
        $this->assertEquals(
557
            'cookievalue',
558
            $_COOKIE['somekey'],
559
            '$_COOKIE reset to original value after Director::test()'
560
        );
561
    }
562
563
    public function providerTestTestRequestCarriesGlobals()
564
    {
565
        $tests = [];
566
        $fixture = [ 'somekey' => 'sometestvalue' ];
567
        foreach (array('get', 'post') as $method) {
568
            foreach (array('return%sValue', 'returnRequestValue', 'returnCookieValue') as $testfunction) {
569
                $url = 'TestController/' . sprintf($testfunction, ucfirst($method))
570
                    . '?' . http_build_query($fixture);
571
                $tests[] = [$url, $fixture, $method];
572
            }
573
        }
574
        return $tests;
575
    }
576
577
    /**
578
     * @dataProvider providerTestTestRequestCarriesGlobals
579
     * @param $url
580
     * @param $fixture
581
     * @param $method
582
     */
583
    public function testTestRequestCarriesGlobals($url, $fixture, $method)
584
    {
585
        $getresponse = Director::test(
586
            $url,
587
            $fixture,
588
            null,
589
            strtoupper($method),
590
            null,
591
            null,
592
            Injector::inst()->createWithArgs(Cookie_Backend::class, array($fixture))
593
        );
594
595
        $this->assertInstanceOf(HTTPResponse::class, $getresponse, 'Director::test() returns HTTPResponse');
596
        $this->assertEquals($fixture['somekey'], $getresponse->getBody(), "Director::test({$url}, {$method})");
597
    }
598
599
    /**
600
     * Tests that additional parameters specified in the routing table are
601
     * saved in the request
602
     */
603
    public function testRouteParams()
604
    {
605
        /** @var HTTPRequest $request */
606
        Director::test('en-nz/myaction/myid/myotherid', null, null, null, null, null, null, $request);
607
608
        $this->assertEquals(
609
            array(
610
                'Controller' => TestController::class,
611
                'Action' => 'myaction',
612
                'ID' => 'myid',
613
                'OtherID' => 'myotherid',
614
                'Locale' => 'en_NZ'
615
            ),
616
            $request->params()
617
        );
618
    }
619
620
    public function testForceWWW()
621
    {
622
        $this->expectExceptionRedirect('http://www.mysite.com:9090/some-url');
623
        Director::mockRequest(function ($request) {
624
            Injector::inst()->registerService($request, HTTPRequest::class);
625
            Director::forceWWW();
626
        }, 'http://mysite.com:9090/some-url');
627
    }
628
629
    public function testPromisedForceWWW()
630
    {
631
        Director::forceWWW();
632
633
        // Flag is set but not redirected yet
634
        $middleware = CanonicalURLMiddleware::singleton();
635
        $this->assertTrue($middleware->getForceWWW());
636
637
        // Middleware forces the redirection eventually
638
        /** @var HTTPResponse $response */
639
        $response = Director::mockRequest(function ($request) use ($middleware) {
640
            return $middleware->process($request, function ($request) {
0 ignored issues
show
Unused Code introduced by
The parameter $request is not used and could be removed. ( Ignorable by Annotation )

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

640
            return $middleware->process($request, function (/** @scrutinizer ignore-unused */ $request) {

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

Loading history...
641
                return null;
642
            });
643
        }, 'http://mysite.com:9090/some-url');
644
645
        // Middleware returns non-exception redirect
646
        $this->assertEquals('http://www.mysite.com:9090/some-url', $response->getHeader('Location'));
647
        $this->assertEquals(301, $response->getStatusCode());
648
    }
649
650
    public function testForceSSLProtectsEntireSite()
651
    {
652
        $this->expectExceptionRedirect('https://www.mysite.com:9090/some-url');
653
        Director::mockRequest(function ($request) {
654
            Injector::inst()->registerService($request, HTTPRequest::class);
655
            Director::forceSSL();
656
        }, 'http://www.mysite.com:9090/some-url');
657
    }
658
659
    public function testPromisedForceSSL()
660
    {
661
            Director::forceSSL();
662
663
        // Flag is set but not redirected yet
664
        $middleware = CanonicalURLMiddleware::singleton();
665
        $this->assertTrue($middleware->getForceSSL());
666
667
        // Middleware forces the redirection eventually
668
        /** @var HTTPResponse $response */
669
        $response = Director::mockRequest(function ($request) use ($middleware) {
670
            return $middleware->process($request, function ($request) {
0 ignored issues
show
Unused Code introduced by
The parameter $request is not used and could be removed. ( Ignorable by Annotation )

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

670
            return $middleware->process($request, function (/** @scrutinizer ignore-unused */ $request) {

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

Loading history...
671
                return null;
672
            });
673
        }, 'http://www.mysite.com:9090/some-url');
674
675
        // Middleware returns non-exception redirect
676
        $this->assertEquals('https://www.mysite.com:9090/some-url', $response->getHeader('Location'));
677
        $this->assertEquals(301, $response->getStatusCode());
678
    }
679
680
    public function testForceSSLOnTopLevelPagePattern()
681
    {
682
        // Expect admin to trigger redirect
683
        $this->expectExceptionRedirect('https://www.mysite.com:9090/admin');
684
        Director::mockRequest(function (HTTPRequest $request) {
685
            Injector::inst()->registerService($request, HTTPRequest::class);
686
            Director::forceSSL(array('/^admin/'));
687
        }, 'http://www.mysite.com:9090/admin');
688
    }
689
690
    public function testForceSSLOnSubPagesPattern()
691
    {
692
        // Expect to redirect to security login page
693
        $this->expectExceptionRedirect('https://www.mysite.com:9090/Security/login');
694
        Director::mockRequest(function (HTTPRequest $request) {
695
            Injector::inst()->registerService($request, HTTPRequest::class);
696
            Director::forceSSL(array('/^Security/'));
697
        }, 'http://www.mysite.com:9090/Security/login');
698
    }
699
700
    public function testForceSSLWithPatternDoesNotMatchOtherPages()
701
    {
702
        // Not on same url should not trigger redirect
703
        $response = Director::mockRequest(function (HTTPRequest $request) {
704
            Injector::inst()->registerService($request, HTTPRequest::class);
705
            Director::forceSSL(array('/^admin/'));
706
        }, 'http://www.mysite.com:9090/normal-page');
707
        $this->assertNull($response, 'Non-matching patterns do not trigger redirect');
708
709
        // nested url should not triger redirect either
710
        $response = Director::mockRequest(function (HTTPRequest $request) {
711
            Injector::inst()->registerService($request, HTTPRequest::class);
712
            Director::forceSSL(array('/^admin/', '/^Security/'));
713
        }, 'http://www.mysite.com:9090/just-another-page/sub-url');
714
        $this->assertNull($response, 'Non-matching patterns do not trigger redirect');
715
    }
716
717
    public function testForceSSLAlternateDomain()
718
    {
719
        // Ensure that forceSSL throws the appropriate exception
720
        $this->expectExceptionRedirect('https://secure.mysite.com/admin');
721
        Director::mockRequest(function (HTTPRequest $request) {
722
            Injector::inst()->registerService($request, HTTPRequest::class);
723
            return Director::forceSSL(array('/^admin/'), 'secure.mysite.com');
0 ignored issues
show
Bug introduced by
Are you sure the usage of SilverStripe\Control\Dir...), 'secure.mysite.com') targeting SilverStripe\Control\Director::forceSSL() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
724
        }, 'http://www.mysite.com:9090/admin');
725
    }
726
727
    public function testForceSSLAlternateDomainWithPort()
728
    {
729
        // Ensure that forceSSL throws the appropriate exception
730
        $this->expectExceptionRedirect('https://secure.mysite.com:81/admin');
731
        Director::mockRequest(function (HTTPRequest $request) {
732
            Injector::inst()->registerService($request, HTTPRequest::class);
733
            return Director::forceSSL(array('/^admin/'), 'secure.mysite.com:81');
0 ignored issues
show
Bug introduced by
Are you sure the usage of SilverStripe\Control\Dir...'secure.mysite.com:81') targeting SilverStripe\Control\Director::forceSSL() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
734
        }, 'http://www.mysite.com:9090/admin');
735
    }
736
737
    /**
738
     * Test that combined forceWWW and forceSSL combine safely
739
     */
740
    public function testForceSSLandForceWWW()
741
    {
742
        Director::forceWWW();
743
        Director::forceSSL();
744
745
        // Flag is set but not redirected yet
746
        $middleware = CanonicalURLMiddleware::singleton();
747
        $this->assertTrue($middleware->getForceWWW());
748
        $this->assertTrue($middleware->getForceSSL());
749
750
        // Middleware forces the redirection eventually
751
        /** @var HTTPResponse $response */
752
        $response = Director::mockRequest(function ($request) use ($middleware) {
753
            return $middleware->process($request, function ($request) {
0 ignored issues
show
Unused Code introduced by
The parameter $request is not used and could be removed. ( Ignorable by Annotation )

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

753
            return $middleware->process($request, function (/** @scrutinizer ignore-unused */ $request) {

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

Loading history...
754
                return null;
755
            });
756
        }, 'http://mysite.com:9090/some-url');
757
758
        // Middleware returns non-exception redirect
759
        $this->assertEquals('https://www.mysite.com:9090/some-url', $response->getHeader('Location'));
760
        $this->assertEquals(301, $response->getStatusCode());
761
    }
762
763
    /**
764
     * Set url to redirect to
765
     *
766
     * @var string
767
     */
768
    protected $expectedRedirect = null;
769
770
    /**
771
     * Expects this test to throw a HTTPResponse_Exception with the given redirect
772
     *
773
     * @param string $url
774
     */
775
    protected function expectExceptionRedirect($url)
776
    {
777
        $this->expectedRedirect = $url;
778
    }
779
780
    protected function runTest()
781
    {
782
        try {
783
            $result = parent::runTest();
784
            if ($this->expectedRedirect) {
785
                $this->fail("Expected to redirect to {$this->expectedRedirect} but no redirect found");
786
            }
787
            return $result;
788
        } catch (HTTPResponse_Exception $exception) {
789
            // Check URL
790
            if ($this->expectedRedirect) {
791
                $url = $exception->getResponse()->getHeader('Location');
792
                $this->assertEquals($this->expectedRedirect, $url, "Expected to redirect to {$this->expectedRedirect}");
793
                return null;
794
            } else {
795
                throw $exception;
796
            }
797
        }
798
    }
799
800
    public function testUnmatchedRequestReturns404()
801
    {
802
        // Remove non-tested rules
803
        $this->assertEquals(404, Director::test('no-route')->getStatusCode());
804
    }
805
806
    public function testIsHttps()
807
    {
808
        // Trust all IPs for this test
809
        /** @var TrustedProxyMiddleware $trustedProxyMiddleware */
810
        $trustedProxyMiddleware
811
            = Injector::inst()->get(TrustedProxyMiddleware::class);
812
        $trustedProxyMiddleware->setTrustedProxyIPs('*');
813
814
        // Clear alternate_base_url for this test
815
        Director::config()->remove('alternate_base_url');
816
817
        // nothing available
818
        $headers = array(
819
            'HTTP_X_FORWARDED_PROTOCOL', 'HTTPS', 'SSL'
820
        );
821
        foreach ($headers as $header) {
822
            if (isset($_SERVER[$header])) {
823
                unset($_SERVER['HTTP_X_FORWARDED_PROTOCOL']);
824
            }
825
        }
826
827
        $this->assertEquals(
828
            'no',
829
            Director::test('TestController/returnIsSSL')->getBody()
830
        );
831
832
        $this->assertEquals(
833
            'yes',
834
            Director::test(
835
                'TestController/returnIsSSL',
836
                null,
837
                null,
838
                null,
839
                null,
840
                [ 'X-Forwarded-Protocol' => 'https' ]
841
            )->getBody()
842
        );
843
844
        $this->assertEquals(
845
            'no',
846
            Director::test(
847
                'TestController/returnIsSSL',
848
                null,
849
                null,
850
                null,
851
                null,
852
                [ 'X-Forwarded-Protocol' => 'http' ]
853
            )->getBody()
854
        );
855
856
        $this->assertEquals(
857
            'no',
858
            Director::test(
859
                'TestController/returnIsSSL',
860
                null,
861
                null,
862
                null,
863
                null,
864
                [ 'X-Forwarded-Protocol' => 'ftp' ]
865
            )->getBody()
866
        );
867
868
        // https via HTTPS
869
        $_SERVER['HTTPS'] = 'true';
870
        $this->assertEquals(
871
            'yes',
872
            Director::test('TestController/returnIsSSL')->getBody()
873
        );
874
875
        $_SERVER['HTTPS'] = '1';
876
        $this->assertEquals(
877
            'yes',
878
            Director::test('TestController/returnIsSSL')->getBody()
879
        );
880
881
        $_SERVER['HTTPS'] = 'off';
882
        $this->assertEquals(
883
            'no',
884
            Director::test('TestController/returnIsSSL')->getBody()
885
        );
886
887
        // https via SSL
888
        $_SERVER['SSL'] = '';
889
        $this->assertEquals(
890
            'yes',
891
            Director::test('TestController/returnIsSSL')->getBody()
892
        );
893
    }
894
895
    public function testTestIgnoresHashes()
896
    {
897
        //test that hashes are ignored
898
        $url = "TestController/returnGetValue?somekey=key";
899
        $hash = "#test";
900
        /** @var HTTPRequest $request */
901
        $response = Director::test($url . $hash, null, null, null, null, null, null, $request);
902
        $this->assertFalse($response->isError());
903
        $this->assertEquals('key', $response->getBody());
904
        $this->assertEquals($request->getURL(true), $url);
905
906
        //test encoded hashes are accepted
907
        $url = "TestController/returnGetValue?somekey=test%23key";
908
        $response = Director::test($url, null, null, null, null, null, null, $request);
909
        $this->assertFalse($response->isError());
910
        $this->assertEquals('test#key', $response->getBody());
911
        $this->assertEquals($request->getURL(true), $url);
912
    }
913
914
    public function testRequestFilterInDirectorTest()
915
    {
916
        $filter = new DirectorTest\TestRequestFilter;
917
918
        $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

918
        $processor = /** @scrutinizer ignore-deprecated */ new RequestProcessor(array($filter));
Loading history...
919
920
        Injector::inst()->registerService($processor, RequestProcessor::class);
921
        $response = Director::test('some-dummy-url');
922
        $this->assertEquals(404, $response->getStatusCode());
923
924
        $this->assertEquals(1, $filter->preCalls);
925
        $this->assertEquals(1, $filter->postCalls);
926
927
        $filter->failPost = true;
928
929
        $response = Director::test('some-dummy-url');
930
        $this->assertEquals(500, $response->getStatusCode());
931
        $this->assertEquals(_t(Director::class . '.REQUEST_ABORTED', 'Request aborted'), $response->getBody());
932
933
        $this->assertEquals(2, $filter->preCalls);
934
        $this->assertEquals(2, $filter->postCalls);
935
936
        $filter->failPre = true;
937
938
        $response = Director::test('some-dummy-url');
939
        $this->assertEquals(400, $response->getStatusCode());
940
        $this->assertEquals(_t(Director::class . '.INVALID_REQUEST', 'Invalid request'), $response->getBody());
941
942
        $this->assertEquals(3, $filter->preCalls);
943
944
        // preCall 'true' will trigger an exception and prevent post call execution
945
        $this->assertEquals(2, $filter->postCalls);
946
    }
947
948
    public function testGlobalMiddleware()
949
    {
950
        $middleware = new DirectorTest\TestMiddleware;
951
        Director::singleton()->setMiddlewares([$middleware]);
952
953
        $response = Director::test('some-dummy-url');
954
        $this->assertEquals(404, $response->getStatusCode());
955
956
        // Both triggered
957
        $this->assertEquals(1, $middleware->preCalls);
958
        $this->assertEquals(1, $middleware->postCalls);
959
960
        $middleware->failPost = true;
961
962
        $response = Director::test('some-dummy-url');
963
        $this->assertEquals(500, $response->getStatusCode());
964
965
        // Both triggered
966
        $this->assertEquals(2, $middleware->preCalls);
967
        $this->assertEquals(2, $middleware->postCalls);
968
969
        $middleware->failPre = true;
970
971
        $response = Director::test('some-dummy-url');
972
        $this->assertEquals(400, $response->getStatusCode());
973
974
        // Pre triggered, post not
975
        $this->assertEquals(3, $middleware->preCalls);
976
        $this->assertEquals(2, $middleware->postCalls);
977
    }
978
979
    public function testRouteSpecificMiddleware()
980
    {
981
        // Inject adapter in place of controller
982
        $specificMiddleware = new DirectorTest\TestMiddleware;
983
        Injector::inst()->registerService($specificMiddleware, 'SpecificMiddleware');
984
985
        // Register adapter as factory for creating this controller
986
        Config::modify()->merge(
987
            Injector::class,
988
            'ControllerWithMiddleware',
989
            [
990
                'class' => RequestHandlerMiddlewareAdapter::class,
991
                'constructor' => [
992
                    '%$' . TestController::class
993
                ],
994
                'properties' => [
995
                    'Middlewares' => [
996
                        '%$SpecificMiddleware',
997
                    ],
998
                ],
999
            ]
1000
        );
1001
1002
        // Global middleware
1003
        $middleware = new DirectorTest\TestMiddleware;
1004
        Director::singleton()->setMiddlewares([ $middleware ]);
1005
1006
        // URL rules, one of which has a specific middleware
1007
        Config::modify()->set(
1008
            Director::class,
1009
            'rules',
1010
            [
1011
                'url-one' => TestController::class,
1012
                'url-two' => [
1013
                    'Controller' => 'ControllerWithMiddleware',
1014
                ],
1015
            ]
1016
        );
1017
1018
        // URL without a route-specific middleware
1019
        Director::test('url-one');
1020
1021
        // Only the global middleware triggered
1022
        $this->assertEquals(1, $middleware->preCalls);
1023
        $this->assertEquals(0, $specificMiddleware->postCalls);
1024
1025
        Director::test('url-two');
1026
1027
        // Both triggered on the url with the specific middleware applied
1028
        $this->assertEquals(2, $middleware->preCalls);
1029
        $this->assertEquals(1, $specificMiddleware->postCalls);
1030
    }
1031
1032
    /**
1033
     * If using phpdbg it returns itself instead of "cli" from php_sapi_name()
1034
     */
1035
    public function testIsCli()
1036
    {
1037
        $this->assertTrue(Director::is_cli(), 'is_cli should be true for PHP CLI and phpdbg');
1038
    }
1039
1040
    public function testMockRequest()
1041
    {
1042
        Director::config()->set('alternate_base_url', 'http://www.mysite.com:9090/some-subdir/');
1043
1044
        // Can handle root-relative $url
1045
        Director::mockRequest(function (HTTPRequest $request) {
1046
            $this->assertEquals('some-page/nested', $request->getURL());
1047
            $this->assertEquals(1, $request->getVar('query'));
1048
            $this->assertEquals('/some-subdir/some-page/nested', $_SERVER['REQUEST_URI']);
1049
        }, '/some-subdir/some-page/nested?query=1');
1050
1051
        // Can handle absolute $url
1052
        Director::mockRequest(function (HTTPRequest $request) {
1053
            $this->assertEquals('some-page/nested', $request->getURL());
1054
            $this->assertEquals(1, $request->getVar('query'));
1055
            $this->assertEquals('/some-subdir/some-page/nested', $_SERVER['REQUEST_URI']);
1056
        }, 'http://www.mysite.com:9090/some-subdir/some-page/nested?query=1');
1057
1058
        // Can handle relative $url
1059
        Director::mockRequest(function (HTTPRequest $request) {
1060
            $this->assertEquals('some-page/nested', $request->getURL());
1061
            $this->assertEquals(1, $request->getVar('query'));
1062
            $this->assertEquals('/some-subdir/some-page/nested', $_SERVER['REQUEST_URI']);
1063
        }, 'some-page/nested?query=1');
1064
    }
1065
}
1066