Passed
Push — master ( bad1ac...1cdfe7 )
by Robbie
15:25 queued 05:45
created

DirectorTest::testHostPortParts()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 3
nc 1
nop 0
dl 0
loc 5
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace SilverStripe\Control\Tests;
4
5
use SilverStripe\Control\Cookie_Backend;
6
use SilverStripe\Control\Director;
7
use SilverStripe\Control\HTTPRequest;
8
use SilverStripe\Control\HTTPResponse;
9
use SilverStripe\Control\HTTPResponse_Exception;
10
use SilverStripe\Control\Middleware\CanonicalURLMiddleware;
11
use SilverStripe\Control\Middleware\RequestHandlerMiddlewareAdapter;
12
use SilverStripe\Control\Middleware\TrustedProxyMiddleware;
13
use SilverStripe\Control\RequestProcessor;
14
use SilverStripe\Control\Tests\DirectorTest\TestController;
15
use SilverStripe\Core\Config\Config;
16
use SilverStripe\Core\Injector\Injector;
17
use SilverStripe\Core\Kernel;
18
use SilverStripe\Dev\SapphireTest;
19
20
/**
21
 * @todo test Director::alternateBaseFolder()
22
 */
23
class DirectorTest extends SapphireTest
24
{
25
    protected static $extra_controllers = [
26
        TestController::class,
27
    ];
28
29
    protected function setUp()
30
    {
31
        parent::setUp();
32
        Director::config()->set('alternate_base_url', 'http://www.mysite.com:9090/');
33
34
        // Ensure redirects enabled on all environments and global state doesn't affect the tests
35
        CanonicalURLMiddleware::singleton()
36
            ->setForceSSLDomain(null)
37
            ->setForceSSLPatterns([])
38
            ->setEnabledEnvs(true);
39
        $this->expectedRedirect = null;
40
    }
41
42
    protected function getExtraRoutes()
43
    {
44
        $rules = parent::getExtraRoutes();
45
46
        $rules['DirectorTestRule/$Action/$ID/$OtherID'] = TestController::class;
47
        $rules['en-nz/$Action/$ID/$OtherID'] = [
48
            'Controller' => TestController::class,
49
            'Locale' => 'en_NZ',
50
        ];
51
        return $rules;
52
    }
53
54
    protected function setUpRoutes()
55
    {
56
        // Don't merge with any existing rules
57
        Director::config()->set('rules', $this->getExtraRoutes());
58
    }
59
60
    public function testFileExists()
61
    {
62
        $tempFileName = 'DirectorTest_testFileExists.tmp';
63
        $tempFilePath = TEMP_PATH . DIRECTORY_SEPARATOR . $tempFileName;
64
65
        // create temp file
66
        file_put_contents($tempFilePath, '');
67
68
        $this->assertTrue(
69
            Director::fileExists($tempFilePath),
70
            'File exist check with absolute path'
71
        );
72
73
        $this->assertTrue(
74
            Director::fileExists($tempFilePath . '?queryparams=1&foo[bar]=bar'),
75
            'File exist check with query params ignored'
76
        );
77
78
        unlink($tempFilePath);
79
    }
80
81
    public function testAbsoluteURL()
82
    {
83
        Director::config()->set('alternate_base_url', 'http://www.mysite.com:9090/mysite/');
84
        $_SERVER['REQUEST_URI'] = "http://www.mysite.com:9090/mysite/sub-page/";
85
86
        //test empty / local urls
87
        foreach (array('', './', '.') as $url) {
88
            $this->assertEquals(
89
                "http://www.mysite.com:9090/mysite/",
90
                Director::absoluteURL($url, Director::BASE)
91
            );
92
            $this->assertEquals(
93
                "http://www.mysite.com:9090/",
94
                Director::absoluteURL($url, Director::ROOT)
95
            );
96
            $this->assertEquals(
97
                "http://www.mysite.com:9090/mysite/sub-page/",
98
                Director::absoluteURL($url, Director::REQUEST)
99
            );
100
        }
101
102
        // Test site root url
103
        $this->assertEquals("http://www.mysite.com:9090/", Director::absoluteURL('/'));
104
105
        // Test Director::BASE
106
        $this->assertEquals(
107
            'http://www.mysite.com:9090/',
108
            Director::absoluteURL('http://www.mysite.com:9090/', Director::BASE)
109
        );
110
        $this->assertEquals(
111
            'http://www.mytest.com',
112
            Director::absoluteURL('http://www.mytest.com', Director::BASE)
113
        );
114
        $this->assertEquals(
115
            "http://www.mysite.com:9090/test",
116
            Director::absoluteURL("http://www.mysite.com:9090/test", Director::BASE)
117
        );
118
        $this->assertEquals(
119
            "http://www.mysite.com:9090/root",
120
            Director::absoluteURL("/root", Director::BASE)
121
        );
122
        $this->assertEquals(
123
            "http://www.mysite.com:9090/root/url",
124
            Director::absoluteURL("/root/url", Director::BASE)
125
        );
126
127
        // Test Director::ROOT
128
        $this->assertEquals(
129
            'http://www.mysite.com:9090/',
130
            Director::absoluteURL('http://www.mysite.com:9090/', Director::ROOT)
131
        );
132
        $this->assertEquals(
133
            'http://www.mytest.com',
134
            Director::absoluteURL('http://www.mytest.com', Director::ROOT)
135
        );
136
        $this->assertEquals(
137
            "http://www.mysite.com:9090/test",
138
            Director::absoluteURL("http://www.mysite.com:9090/test", Director::ROOT)
139
        );
140
        $this->assertEquals(
141
            "http://www.mysite.com:9090/root",
142
            Director::absoluteURL("/root", Director::ROOT)
143
        );
144
        $this->assertEquals(
145
            "http://www.mysite.com:9090/root/url",
146
            Director::absoluteURL("/root/url", Director::ROOT)
147
        );
148
149
        // Test Director::REQUEST
150
        $this->assertEquals(
151
            'http://www.mysite.com:9090/',
152
            Director::absoluteURL('http://www.mysite.com:9090/', Director::REQUEST)
153
        );
154
        $this->assertEquals(
155
            'http://www.mytest.com',
156
            Director::absoluteURL('http://www.mytest.com', Director::REQUEST)
157
        );
158
        $this->assertEquals(
159
            "http://www.mysite.com:9090/test",
160
            Director::absoluteURL("http://www.mysite.com:9090/test", Director::REQUEST)
161
        );
162
        $this->assertEquals(
163
            "http://www.mysite.com:9090/root",
164
            Director::absoluteURL("/root", Director::REQUEST)
165
        );
166
        $this->assertEquals(
167
            "http://www.mysite.com:9090/root/url",
168
            Director::absoluteURL("/root/url", Director::REQUEST)
169
        );
170
171
        // Test evaluating relative urls relative to base (default)
172
        $this->assertEquals(
173
            "http://www.mysite.com:9090/mysite/test",
174
            Director::absoluteURL("test")
175
        );
176
        $this->assertEquals(
177
            "http://www.mysite.com:9090/mysite/test/url",
178
            Director::absoluteURL("test/url")
179
        );
180
        $this->assertEquals(
181
            "http://www.mysite.com:9090/mysite/test",
182
            Director::absoluteURL("test", Director::BASE)
183
        );
184
        $this->assertEquals(
185
            "http://www.mysite.com:9090/mysite/test/url",
186
            Director::absoluteURL("test/url", Director::BASE)
187
        );
188
189
        // Test evaluting relative urls relative to root
190
        $this->assertEquals(
191
            "http://www.mysite.com:9090/test",
192
            Director::absoluteURL("test", Director::ROOT)
193
        );
194
        $this->assertEquals(
195
            "http://www.mysite.com:9090/test/url",
196
            Director::absoluteURL("test/url", Director::ROOT)
197
        );
198
199
        // Test relative to requested page
200
        $this->assertEquals(
201
            "http://www.mysite.com:9090/mysite/sub-page/test",
202
            Director::absoluteURL("test", Director::REQUEST)
203
        );
204
        $this->assertEquals(
205
            "http://www.mysite.com:9090/mysite/sub-page/test/url",
206
            Director::absoluteURL("test/url", Director::REQUEST)
207
        );
208
209
        // Test that javascript links are not left intact
210
        $this->assertStringStartsNotWith('javascript', Director::absoluteURL('javascript:alert("attack")'));
211
        $this->assertStringStartsNotWith('alert', Director::absoluteURL('javascript:alert("attack")'));
212
        $this->assertStringStartsNotWith('javascript', Director::absoluteURL('alert("attack")'));
213
        $this->assertStringStartsNotWith('alert', Director::absoluteURL('alert("attack")'));
214
    }
215
216
    public function testAlternativeBaseURL()
217
    {
218
        // relative base URLs - you should end them in a /
219
        Director::config()->set('alternate_base_url', '/relativebase/');
220
        $_SERVER['HTTP_HOST'] = 'www.somesite.com';
221
        $_SERVER['REQUEST_URI'] = "/relativebase/sub-page/";
222
223
        $this->assertEquals('/relativebase/', Director::baseURL());
224
        $this->assertEquals('http://www.somesite.com/relativebase/', Director::absoluteBaseURL());
225
        $this->assertEquals(
226
            'http://www.somesite.com/relativebase/subfolder/test',
227
            Director::absoluteURL('subfolder/test')
228
        );
229
230
        // absolute base URLS with subdirectory - You should end them in a /
231
        Director::config()->set('alternate_base_url', 'http://www.example.org/relativebase/');
232
        $_SERVER['REQUEST_URI'] = "http://www.example.org/relativebase/sub-page/";
233
        $this->assertEquals('/relativebase/', Director::baseURL()); // Non-absolute url
234
        $this->assertEquals('http://www.example.org/relativebase/', Director::absoluteBaseURL());
235
        $this->assertEquals(
236
            'http://www.example.org/relativebase/sub-page/',
237
            Director::absoluteURL('', Director::REQUEST)
238
        );
239
        $this->assertEquals(
240
            'http://www.example.org/relativebase/',
241
            Director::absoluteURL('', Director::BASE)
242
        );
243
        $this->assertEquals('http://www.example.org/', Director::absoluteURL('', Director::ROOT));
244
        $this->assertEquals(
245
            'http://www.example.org/relativebase/sub-page/subfolder/test',
246
            Director::absoluteURL('subfolder/test', Director::REQUEST)
247
        );
248
        $this->assertEquals(
249
            'http://www.example.org/subfolder/test',
250
            Director::absoluteURL('subfolder/test', Director::ROOT)
251
        );
252
        $this->assertEquals(
253
            'http://www.example.org/relativebase/subfolder/test',
254
            Director::absoluteURL('subfolder/test', Director::BASE)
255
        );
256
257
        // absolute base URLs - you should end them in a /
258
        Director::config()->set('alternate_base_url', 'http://www.example.org/');
259
        $_SERVER['REQUEST_URI'] = "http://www.example.org/sub-page/";
260
        $this->assertEquals('/', Director::baseURL()); // Non-absolute url
261
        $this->assertEquals('http://www.example.org/', Director::absoluteBaseURL());
262
        $this->assertEquals('http://www.example.org/sub-page/', Director::absoluteURL('', Director::REQUEST));
263
        $this->assertEquals('http://www.example.org/', Director::absoluteURL('', Director::BASE));
264
        $this->assertEquals('http://www.example.org/', Director::absoluteURL('', Director::ROOT));
265
        $this->assertEquals(
266
            'http://www.example.org/sub-page/subfolder/test',
267
            Director::absoluteURL('subfolder/test', Director::REQUEST)
268
        );
269
        $this->assertEquals(
270
            'http://www.example.org/subfolder/test',
271
            Director::absoluteURL('subfolder/test', Director::ROOT)
272
        );
273
        $this->assertEquals(
274
            'http://www.example.org/subfolder/test',
275
            Director::absoluteURL('subfolder/test', Director::BASE)
276
        );
277
    }
278
279
    /**
280
     * Tests that {@link Director::is_absolute()} works under different environment types
281
     * @dataProvider provideAbsolutePaths
282
     */
283
    public function testIsAbsolute($path, $result)
284
    {
285
        $this->assertEquals($result, Director::is_absolute($path));
286
    }
287
288
    public function provideAbsolutePaths()
289
    {
290
        return [
291
            ['C:/something', true],
292
            ['d:\\', true],
293
            ['e/', false],
294
            ['s:/directory', true],
295
            ['/var/www', true],
296
            ['\\Something', true],
297
            ['something/c:', false],
298
            ['folder', false],
299
            ['a/c:/', false],
300
        ];
301
    }
302
303
    public function testIsAbsoluteUrl()
304
    {
305
        $this->assertTrue(Director::is_absolute_url('http://test.com/testpage'));
306
        $this->assertTrue(Director::is_absolute_url('ftp://test.com'));
307
        $this->assertFalse(Director::is_absolute_url('test.com/testpage'));
308
        $this->assertFalse(Director::is_absolute_url('/relative'));
309
        $this->assertFalse(Director::is_absolute_url('relative'));
310
        $this->assertFalse(Director::is_absolute_url("/relative/?url=http://foo.com"));
311
        $this->assertFalse(Director::is_absolute_url("/relative/#http://foo.com"));
312
        $this->assertTrue(Director::is_absolute_url("https://test.com/?url=http://foo.com"));
313
        $this->assertTrue(Director::is_absolute_url("trickparseurl:http://test.com"));
314
        $this->assertTrue(Director::is_absolute_url('//test.com'));
315
        $this->assertTrue(Director::is_absolute_url('/////test.com'));
316
        $this->assertTrue(Director::is_absolute_url('  ///test.com'));
317
        $this->assertTrue(Director::is_absolute_url('http:test.com'));
318
        $this->assertTrue(Director::is_absolute_url('//http://test.com'));
319
    }
320
321
    public function testHostPortParts()
322
    {
323
        $this->assertEquals('www.mysite.com:9090', Director::host());
324
        $this->assertEquals('www.mysite.com', Director::hostName());
325
        $this->assertEquals('9090', Director::port());
326
    }
327
328
    public function testIsRelativeUrl()
329
    {
330
        $this->assertFalse(Director::is_relative_url('http://test.com'));
331
        $this->assertFalse(Director::is_relative_url('https://test.com'));
332
        $this->assertFalse(Director::is_relative_url('   https://test.com/testpage   '));
333
        $this->assertTrue(Director::is_relative_url('test.com/testpage'));
334
        $this->assertFalse(Director::is_relative_url('ftp://test.com'));
335
        $this->assertTrue(Director::is_relative_url('/relative'));
336
        $this->assertTrue(Director::is_relative_url('relative'));
337
        $this->assertTrue(Director::is_relative_url('/relative/?url=http://test.com'));
338
        $this->assertTrue(Director::is_relative_url('/relative/#=http://test.com'));
339
    }
340
341
    /**
342
     * @return array
343
     */
344
    public function providerMakeRelative()
345
    {
346
        return [
347
            // Resilience to slash position
348
            [
349
                'http://www.mysite.com:9090/base/folder',
350
                'http://www.mysite.com:9090/base/folder',
351
                ''
352
            ],
353
            [
354
                'http://www.mysite.com:9090/base/folder',
355
                'http://www.mysite.com:9090/base/folder/',
356
                ''
357
            ],
358
            [
359
                'http://www.mysite.com:9090/base/folder/',
360
                'http://www.mysite.com:9090/base/folder',
361
                ''
362
            ],
363
            [
364
                'http://www.mysite.com:9090/',
365
                'http://www.mysite.com:9090/',
366
                ''
367
            ],
368
            [
369
                'http://www.mysite.com:9090/',
370
                'http://www.mysite.com',
371
                ''
372
            ],
373
            [
374
                'http://www.mysite.com',
375
                'http://www.mysite.com:9090/',
376
                ''
377
            ],
378
            [
379
                'http://www.mysite.com:9090/base/folder',
380
                'http://www.mysite.com:9090/base/folder/page',
381
                'page'
382
            ],
383
            [
384
                'http://www.mysite.com:9090/',
385
                'http://www.mysite.com:9090/page/',
386
                'page/'
387
            ],
388
            // Parsing protocol safely
389
            [
390
                'http://www.mysite.com:9090/base/folder',
391
                'https://www.mysite.com:9090/base/folder',
392
                ''
393
            ],
394
            [
395
                'https://www.mysite.com:9090/base/folder',
396
                'http://www.mysite.com:9090/base/folder/testpage',
397
                'testpage'
398
            ],
399
            [
400
                'http://www.mysite.com:9090/base/folder',
401
                '//www.mysite.com:9090/base/folder/testpage',
402
                'testpage'
403
            ],
404
            // Dirty input
405
            [
406
                'http://www.mysite.com:9090/base/folder',
407
                '    https://www.mysite.com:9090/base/folder/testpage    ',
408
                'testpage'
409
            ],
410
            [
411
                'http://www.mysite.com:9090/base/folder',
412
                '//www.mysite.com:9090/base//folder/testpage//subpage',
413
                'testpage/subpage'
414
            ],
415
            // Non-http protocol isn't modified
416
            [
417
                'http://www.mysite.com:9090/base/folder',
418
                'ftp://test.com',
419
                'ftp://test.com'
420
            ],
421
            // Alternate hostnames are redirected
422
            [
423
                'https://www.mysite.com:9090/base/folder',
424
                'http://mysite.com:9090/base/folder/testpage',
425
                'testpage'
426
            ],
427
            [
428
                'http://www.otherdomain.com/base/folder',
429
                '//www.mysite.com:9090/base/folder/testpage',
430
                'testpage'
431
            ],
432
            // Base folder is found
433
            [
434
                'http://www.mysite.com:9090/base/folder',
435
                BASE_PATH . '/some/file.txt',
436
                'some/file.txt',
437
            ],
438
            // public folder is found
439
            [
440
                'http://www.mysite.com:9090/base/folder',
441
                PUBLIC_PATH . '/some/file.txt',
442
                'some/file.txt',
443
            ],
444
            // querystring is protected
445
            [
446
                'http://www.mysite.com:9090/base/folder',
447
                '//www.mysite.com:9090/base//folder/testpage//subpage?args=hello',
448
                'testpage/subpage?args=hello'
449
            ],
450
            [
451
                'http://www.mysite.com:9090/base/folder',
452
                '//www.mysite.com:9090/base//folder/?args=hello',
453
                '?args=hello'
454
            ],
455
        ];
456
    }
457
458
    /**
459
     * @dataProvider providerMakeRelative
460
     * @param string $baseURL Site base URL
461
     * @param string $requestURL Request URL
462
     * @param string $relativeURL Expected relative URL
463
     */
464
    public function testMakeRelative($baseURL, $requestURL, $relativeURL)
465
    {
466
        Director::config()->set('alternate_base_url', $baseURL);
467
        $actualRelative = Director::makeRelative($requestURL);
468
        $this->assertEquals(
469
            $relativeURL,
470
            $actualRelative,
471
            "Expected relativeURL of {$requestURL} to be {$relativeURL}"
472
        );
473
    }
474
475
    /**
476
     * Mostly tested by {@link testIsRelativeUrl()},
477
     * just adding the host name matching aspect here.
478
     */
479
    public function testIsSiteUrl()
480
    {
481
        $this->assertFalse(Director::is_site_url("http://test.com"));
482
        $this->assertTrue(Director::is_site_url(Director::absoluteBaseURL()));
483
        $this->assertFalse(Director::is_site_url("http://test.com?url=" . Director::absoluteBaseURL()));
484
        $this->assertFalse(Director::is_site_url("http://test.com?url=" . urlencode(Director::absoluteBaseURL())));
485
        $this->assertFalse(Director::is_site_url("//test.com?url=" . Director::absoluteBaseURL()));
486
        $this->assertFalse(Director::is_site_url('http://google.com\@test.com'));
487
        $this->assertFalse(Director::is_site_url('http://google.com/@test.com'));
488
        $this->assertFalse(Director::is_site_url('http://google.com:pass\@test.com'));
489
        $this->assertFalse(Director::is_site_url('http://google.com:pass/@test.com'));
490
    }
491
492
    public function testResetGlobalsAfterTestRequest()
493
    {
494
        $_GET = array('somekey' => 'getvalue');
495
        $_POST = array('somekey' => 'postvalue');
496
        $_COOKIE = array('somekey' => 'cookievalue');
497
498
        $cookies = Injector::inst()->createWithArgs(
499
            Cookie_Backend::class,
500
            array(array('somekey' => 'sometestcookievalue'))
501
        );
502
503
        Director::test(
504
            'errorpage?somekey=sometestgetvalue',
505
            array('somekey' => 'sometestpostvalue'),
506
            null,
507
            null,
508
            null,
509
            null,
510
            $cookies
511
        );
512
513
        $this->assertEquals(
514
            'getvalue',
515
            $_GET['somekey'],
516
            '$_GET reset to original value after Director::test()'
517
        );
518
        $this->assertEquals(
519
            'postvalue',
520
            $_POST['somekey'],
521
            '$_POST reset to original value after Director::test()'
522
        );
523
        $this->assertEquals(
524
            'cookievalue',
525
            $_COOKIE['somekey'],
526
            '$_COOKIE reset to original value after Director::test()'
527
        );
528
    }
529
530
    public function providerTestTestRequestCarriesGlobals()
531
    {
532
        $tests = [];
533
        $fixture = ['somekey' => 'sometestvalue'];
534
        foreach (array('get', 'post') as $method) {
535
            foreach (array('return%sValue', 'returnRequestValue', 'returnCookieValue') as $testfunction) {
536
                $url = 'TestController/' . sprintf($testfunction, ucfirst($method))
537
                    . '?' . http_build_query($fixture);
538
                $tests[] = [$url, $fixture, $method];
539
            }
540
        }
541
        return $tests;
542
    }
543
544
    /**
545
     * @dataProvider providerTestTestRequestCarriesGlobals
546
     * @param $url
547
     * @param $fixture
548
     * @param $method
549
     */
550
    public function testTestRequestCarriesGlobals($url, $fixture, $method)
551
    {
552
        $getresponse = Director::test(
553
            $url,
554
            $fixture,
555
            null,
556
            strtoupper($method),
557
            null,
558
            null,
559
            Injector::inst()->createWithArgs(Cookie_Backend::class, array($fixture))
560
        );
561
562
        $this->assertInstanceOf(HTTPResponse::class, $getresponse, 'Director::test() returns HTTPResponse');
563
        $this->assertEquals($fixture['somekey'], $getresponse->getBody(), "Director::test({$url}, {$method})");
564
    }
565
566
    /**
567
     * Tests that additional parameters specified in the routing table are
568
     * saved in the request
569
     */
570
    public function testRouteParams()
571
    {
572
        /** @var HTTPRequest $request */
573
        Director::test('en-nz/myaction/myid/myotherid', null, null, null, null, null, null, $request);
574
575
        $this->assertEquals(
576
            array(
577
                'Controller' => TestController::class,
578
                'Action' => 'myaction',
579
                'ID' => 'myid',
580
                'OtherID' => 'myotherid',
581
                'Locale' => 'en_NZ'
582
            ),
583
            $request->params()
584
        );
585
    }
586
587
    public function testForceWWW()
588
    {
589
        $this->expectExceptionRedirect('http://www.mysite.com:9090/some-url');
590
        Director::mockRequest(function ($request) {
591
            Injector::inst()->registerService($request, HTTPRequest::class);
592
            Director::forceWWW();
593
        }, 'http://mysite.com:9090/some-url');
594
    }
595
596
    public function testPromisedForceWWW()
597
    {
598
        Director::forceWWW();
599
600
        // Flag is set but not redirected yet
601
        $middleware = CanonicalURLMiddleware::singleton();
602
        $this->assertTrue($middleware->getForceWWW());
603
604
        // Middleware forces the redirection eventually
605
        /** @var HTTPResponse $response */
606
        $response = Director::mockRequest(function ($request) use ($middleware) {
607
            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

607
            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...
608
                return null;
609
            });
610
        }, 'http://mysite.com:9090/some-url');
611
612
        // Middleware returns non-exception redirect
613
        $this->assertEquals('http://www.mysite.com:9090/some-url', $response->getHeader('Location'));
614
        $this->assertEquals(301, $response->getStatusCode());
615
    }
616
617
    public function testForceSSLProtectsEntireSite()
618
    {
619
        $this->expectExceptionRedirect('https://www.mysite.com:9090/some-url');
620
        Director::mockRequest(function ($request) {
621
            Injector::inst()->registerService($request, HTTPRequest::class);
622
            Director::forceSSL();
623
        }, 'http://www.mysite.com:9090/some-url');
624
    }
625
626
    public function testPromisedForceSSL()
627
    {
628
            Director::forceSSL();
629
630
        // Flag is set but not redirected yet
631
        $middleware = CanonicalURLMiddleware::singleton();
632
        $this->assertTrue($middleware->getForceSSL());
633
634
        // Middleware forces the redirection eventually
635
        /** @var HTTPResponse $response */
636
        $response = Director::mockRequest(function ($request) use ($middleware) {
637
            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

637
            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...
638
                return null;
639
            });
640
        }, 'http://www.mysite.com:9090/some-url');
641
642
        // Middleware returns non-exception redirect
643
        $this->assertInstanceOf(HTTPResponse::class, $response);
644
        $this->assertEquals('https://www.mysite.com:9090/some-url', $response->getHeader('Location'));
645
        $this->assertEquals(301, $response->getStatusCode());
646
    }
647
648
    public function testForceSSLOnTopLevelPagePattern()
649
    {
650
        // Expect admin to trigger redirect
651
        $this->expectExceptionRedirect('https://www.mysite.com:9090/admin');
652
        Director::mockRequest(function (HTTPRequest $request) {
653
            Injector::inst()->registerService($request, HTTPRequest::class);
654
            Director::forceSSL(array('/^admin/'));
655
        }, 'http://www.mysite.com:9090/admin');
656
    }
657
658
    public function testForceSSLOnSubPagesPattern()
659
    {
660
        // Expect to redirect to security login page
661
        $this->expectExceptionRedirect('https://www.mysite.com:9090/Security/login');
662
        Director::mockRequest(function (HTTPRequest $request) {
663
            Injector::inst()->registerService($request, HTTPRequest::class);
664
            Director::forceSSL(array('/^Security/'));
665
        }, 'http://www.mysite.com:9090/Security/login');
666
    }
667
668
    public function testForceSSLWithPatternDoesNotMatchOtherPages()
669
    {
670
        // Not on same url should not trigger redirect
671
        $response = Director::mockRequest(function (HTTPRequest $request) {
672
            Injector::inst()->registerService($request, HTTPRequest::class);
673
            Director::forceSSL(array('/^admin/'));
674
        }, 'http://www.mysite.com:9090/normal-page');
675
        $this->assertNull($response, 'Non-matching patterns do not trigger redirect');
676
677
        // nested url should not triger redirect either
678
        $response = Director::mockRequest(function (HTTPRequest $request) {
679
            Injector::inst()->registerService($request, HTTPRequest::class);
680
            Director::forceSSL(array('/^admin/', '/^Security/'));
681
        }, 'http://www.mysite.com:9090/just-another-page/sub-url');
682
        $this->assertNull($response, 'Non-matching patterns do not trigger redirect');
683
    }
684
685
    public function testForceSSLAlternateDomain()
686
    {
687
        // Ensure that forceSSL throws the appropriate exception
688
        $this->expectExceptionRedirect('https://secure.mysite.com/admin');
689
        Director::mockRequest(function (HTTPRequest $request) {
690
            Injector::inst()->registerService($request, HTTPRequest::class);
691
            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...
692
        }, 'http://www.mysite.com:9090/admin');
693
    }
694
695
    public function testForceSSLAlternateDomainWithPort()
696
    {
697
        // Ensure that forceSSL throws the appropriate exception
698
        $this->expectExceptionRedirect('https://secure.mysite.com:81/admin');
699
        Director::mockRequest(function (HTTPRequest $request) {
700
            Injector::inst()->registerService($request, HTTPRequest::class);
701
            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...
702
        }, 'http://www.mysite.com:9090/admin');
703
    }
704
705
    /**
706
     * Test that combined forceWWW and forceSSL combine safely
707
     */
708
    public function testForceSSLandForceWWW()
709
    {
710
        Director::forceWWW();
711
        Director::forceSSL();
712
713
        // Flag is set but not redirected yet
714
        $middleware = CanonicalURLMiddleware::singleton();
715
        $this->assertTrue($middleware->getForceWWW());
716
        $this->assertTrue($middleware->getForceSSL());
717
718
        // Middleware forces the redirection eventually
719
        /** @var HTTPResponse $response */
720
        $response = Director::mockRequest(function ($request) use ($middleware) {
721
            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

721
            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...
722
                return null;
723
            });
724
        }, 'http://mysite.com:9090/some-url');
725
726
        // Middleware returns non-exception redirect
727
        $this->assertEquals('https://www.mysite.com:9090/some-url', $response->getHeader('Location'));
728
        $this->assertEquals(301, $response->getStatusCode());
729
    }
730
731
    /**
732
     * Set url to redirect to
733
     *
734
     * @var string
735
     */
736
    protected $expectedRedirect = null;
737
738
    /**
739
     * Expects this test to throw a HTTPResponse_Exception with the given redirect
740
     *
741
     * @param string $url
742
     */
743
    protected function expectExceptionRedirect($url)
744
    {
745
        $this->expectedRedirect = $url;
746
    }
747
748
    protected function runTest()
749
    {
750
        try {
751
            $result = parent::runTest();
752
            if ($this->expectedRedirect) {
753
                $this->fail("Expected to redirect to {$this->expectedRedirect} but no redirect found");
754
            }
755
            return $result;
756
        } catch (HTTPResponse_Exception $exception) {
757
            // Check URL
758
            if ($this->expectedRedirect) {
759
                $url = $exception->getResponse()->getHeader('Location');
760
                $this->assertEquals($this->expectedRedirect, $url, "Expected to redirect to {$this->expectedRedirect}");
761
                return null;
762
            } else {
763
                throw $exception;
764
            }
765
        }
766
    }
767
768
    public function testUnmatchedRequestReturns404()
769
    {
770
        // Remove non-tested rules
771
        $this->assertEquals(404, Director::test('no-route')->getStatusCode());
772
    }
773
774
    public function testIsHttps()
775
    {
776
        // Trust all IPs for this test
777
        /** @var TrustedProxyMiddleware $trustedProxyMiddleware */
778
        $trustedProxyMiddleware
779
            = Injector::inst()->get(TrustedProxyMiddleware::class);
780
        $trustedProxyMiddleware->setTrustedProxyIPs('*');
781
782
        // Clear alternate_base_url for this test
783
        Director::config()->remove('alternate_base_url');
784
785
        // nothing available
786
        $headers = array(
787
            'HTTP_X_FORWARDED_PROTOCOL', 'HTTPS', 'SSL'
788
        );
789
        foreach ($headers as $header) {
790
            if (isset($_SERVER[$header])) {
791
                unset($_SERVER['HTTP_X_FORWARDED_PROTOCOL']);
792
            }
793
        }
794
795
        $this->assertEquals(
796
            'no',
797
            Director::test('TestController/returnIsSSL')->getBody()
798
        );
799
800
        $this->assertEquals(
801
            'yes',
802
            Director::test(
803
                'TestController/returnIsSSL',
804
                null,
805
                null,
806
                null,
807
                null,
808
                ['X-Forwarded-Protocol' => 'https']
809
            )->getBody()
810
        );
811
812
        $this->assertEquals(
813
            'no',
814
            Director::test(
815
                'TestController/returnIsSSL',
816
                null,
817
                null,
818
                null,
819
                null,
820
                ['X-Forwarded-Protocol' => 'http']
821
            )->getBody()
822
        );
823
824
        $this->assertEquals(
825
            'no',
826
            Director::test(
827
                'TestController/returnIsSSL',
828
                null,
829
                null,
830
                null,
831
                null,
832
                ['X-Forwarded-Protocol' => 'ftp']
833
            )->getBody()
834
        );
835
836
        // https via HTTPS
837
        $_SERVER['HTTPS'] = 'true';
838
        $this->assertEquals(
839
            'yes',
840
            Director::test('TestController/returnIsSSL')->getBody()
841
        );
842
843
        $_SERVER['HTTPS'] = '1';
844
        $this->assertEquals(
845
            'yes',
846
            Director::test('TestController/returnIsSSL')->getBody()
847
        );
848
849
        $_SERVER['HTTPS'] = 'off';
850
        $this->assertEquals(
851
            'no',
852
            Director::test('TestController/returnIsSSL')->getBody()
853
        );
854
855
        // https via SSL
856
        $_SERVER['SSL'] = '';
857
        $this->assertEquals(
858
            'yes',
859
            Director::test('TestController/returnIsSSL')->getBody()
860
        );
861
    }
862
863
    public function testTestIgnoresHashes()
864
    {
865
        //test that hashes are ignored
866
        $url = "TestController/returnGetValue?somekey=key";
867
        $hash = "#test";
868
        /** @var HTTPRequest $request */
869
        $response = Director::test($url . $hash, null, null, null, null, null, null, $request);
870
        $this->assertFalse($response->isError());
871
        $this->assertEquals('key', $response->getBody());
872
        $this->assertEquals($request->getURL(true), $url);
873
874
        //test encoded hashes are accepted
875
        $url = "TestController/returnGetValue?somekey=test%23key";
876
        $response = Director::test($url, null, null, null, null, null, null, $request);
877
        $this->assertFalse($response->isError());
878
        $this->assertEquals('test#key', $response->getBody());
879
        $this->assertEquals($request->getURL(true), $url);
880
    }
881
882
    public function testRequestFilterInDirectorTest()
883
    {
884
        $filter = new DirectorTest\TestRequestFilter;
885
886
        $processor = new RequestProcessor(array($filter));
0 ignored issues
show
Deprecated Code introduced by
The class SilverStripe\Control\RequestProcessor has been deprecated: 4.0.0:5.0.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

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