Passed
Pull Request — 4 (#10028)
by Steve
09:01
created

HTTPTest   A

Complexity

Total Complexity 18

Size/Duplication

Total Lines 468
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 243
dl 0
loc 468
rs 10
c 0
b 0
f 0
wmc 18
1
<?php
2
3
namespace SilverStripe\Control\Tests;
4
5
use SilverStripe\Control\Controller;
6
use SilverStripe\Control\Director;
7
use SilverStripe\Control\HTTP;
8
use SilverStripe\Control\HTTPRequest;
9
use SilverStripe\Control\HTTPResponse;
10
use SilverStripe\Control\Middleware\HTTPCacheControlMiddleware;
11
use SilverStripe\Control\Session;
12
use SilverStripe\Core\Config\Config;
13
use SilverStripe\Dev\FunctionalTest;
14
15
/**
16
 * Tests the {@link HTTP} class
17
 *
18
 * @skipUpgrade
19
 */
20
class HTTPTest extends FunctionalTest
21
{
22
    protected function setUp(): void
23
    {
24
        parent::setUp();
25
        // Set to disabled at null forcing level
26
        HTTPCacheControlMiddleware::config()
27
            ->set('defaultState', 'disabled')
28
            ->set('defaultForcingLevel', 0);
29
        HTTPCacheControlMiddleware::reset();
30
    }
31
32
    public function testAddCacheHeaders()
33
    {
34
        $body = "<html><head></head><body><h1>Mysite</h1></body></html>";
35
        $response = new HTTPResponse($body, 200);
36
        HTTPCacheControlMiddleware::singleton()->publicCache();
37
        HTTPCacheControlMiddleware::singleton()->setMaxAge(30);
38
39
        $this->addCacheHeaders($response);
40
        $this->assertNotEmpty($response->getHeader('Cache-Control'));
41
42
        // Ensure cache headers are set correctly when disabled via config (e.g. when dev)
43
        HTTPCacheControlMiddleware::config()
44
            ->set('defaultState', 'disabled')
45
            ->set('defaultForcingLevel', HTTPCacheControlMiddleware::LEVEL_DISABLED);
46
        HTTPCacheControlMiddleware::reset();
47
        HTTPCacheControlMiddleware::singleton()->publicCache();
48
        HTTPCacheControlMiddleware::singleton()->setMaxAge(30);
49
        $response = new HTTPResponse($body, 200);
50
        $this->addCacheHeaders($response);
51
        $this->assertContains('no-cache', $response->getHeader('Cache-Control'));
52
        $this->assertContains('no-store', $response->getHeader('Cache-Control'));
53
        $this->assertContains('must-revalidate', $response->getHeader('Cache-Control'));
54
55
        // Ensure max-age setting is respected in production.
56
        HTTPCacheControlMiddleware::config()
57
            ->set('defaultState', 'disabled')
58
            ->set('defaultForcingLevel', 0);
59
        HTTPCacheControlMiddleware::reset();
60
        HTTPCacheControlMiddleware::singleton()->publicCache();
61
        HTTPCacheControlMiddleware::singleton()->setMaxAge(30);
62
        $response = new HTTPResponse($body, 200);
63
        $this->addCacheHeaders($response);
64
        $this->assertContains('max-age=30', $response->getHeader('Cache-Control'));
65
        $this->assertNotContains('max-age=0', $response->getHeader('Cache-Control'));
66
67
        // Still "live": Ensure header's aren't overridden if already set (using purposefully different values).
68
        $headers = [
69
            'Vary' => '*',
70
            'Pragma' => 'no-cache',
71
            'Cache-Control' => 'max-age=0, no-cache, no-store',
72
        ];
73
        foreach ($headers as $header => $value) {
74
            $response->addHeader($header, $value);
75
        }
76
        HTTPCacheControlMiddleware::reset();
77
        HTTPCacheControlMiddleware::singleton()->publicCache();
78
        HTTPCacheControlMiddleware::singleton()->setMaxAge(30);
79
        $this->addCacheHeaders($response);
80
        foreach ($headers as $header => $value) {
81
            $this->assertEquals($value, $response->getHeader($header));
82
        }
83
    }
84
85
    public function testConfigVary()
86
    {
87
        $body = "<html><head></head><body><h1>Mysite</h1></body></html>";
88
        $response = new HTTPResponse($body, 200);
89
        HTTPCacheControlMiddleware::singleton()
90
            ->setMaxAge(30)
91
            ->setVary('X-Requested-With, X-Forwarded-Protocol');
92
        $this->addCacheHeaders($response);
93
94
        // Vary set properly
95
        $v = $response->getHeader('Vary');
96
        $this->assertContains("X-Forwarded-Protocol", $v);
97
        $this->assertContains("X-Requested-With", $v);
98
        $this->assertNotContains("Cookie", $v);
99
        $this->assertNotContains("User-Agent", $v);
100
        $this->assertNotContains("Accept", $v);
101
102
        // No vary
103
        HTTPCacheControlMiddleware::singleton()
104
            ->setMaxAge(30)
105
            ->setVary(null);
106
        HTTPCacheControlMiddleware::reset();
107
        HTTPCacheControlMiddleware::config()
108
            ->set('defaultVary', []);
109
110
        $response = new HTTPResponse($body, 200);
111
        $this->addCacheHeaders($response);
112
        $v = $response->getHeader('Vary');
113
        $this->assertEmpty($v);
114
    }
115
116
    public function testDeprecatedVaryHandling()
117
    {
118
        /** @var Config */
119
        Config::modify()->set(
120
            HTTP::class,
121
            'vary',
122
            'X-Foo'
123
        );
124
        $response = new HTTPResponse('', 200);
125
        $this->addCacheHeaders($response);
126
        $header = $response->getHeader('Vary');
127
        $this->assertContains('X-Foo', $header);
128
    }
129
130
    public function testDeprecatedCacheControlHandling()
131
    {
132
        HTTPCacheControlMiddleware::singleton()->publicCache();
133
134
        /** @var Config */
135
        Config::modify()->set(
136
            HTTP::class,
137
            'cache_control',
138
            [
139
                'no-store' => true,
140
                'no-cache' => true,
141
            ]
142
        );
143
        $response = new HTTPResponse('', 200);
144
        $this->addCacheHeaders($response);
145
        $header = $response->getHeader('Cache-Control');
146
        $this->assertContains('no-store', $header);
147
        $this->assertContains('no-cache', $header);
148
    }
149
150
    public function testDeprecatedCacheControlHandlingOnMaxAge()
151
    {
152
        HTTPCacheControlMiddleware::singleton()->publicCache();
153
154
        /** @var Config */
155
        Config::modify()->set(
156
            HTTP::class,
157
            'cache_control',
158
            [
159
                // Needs to be separate from no-cache and no-store,
160
                // since that would unset max-age
161
                'max-age' => 99,
162
            ]
163
        );
164
        $response = new HTTPResponse('', 200);
165
        $this->addCacheHeaders($response);
166
        $header = $response->getHeader('Cache-Control');
167
        $this->assertContains('max-age=99', $header);
168
    }
169
170
    public function testDeprecatedCacheControlHandlingThrowsWithUnknownDirectives()
171
    {
172
        $this->expectException(\LogicException::class);
173
        $this->expectExceptionMessageMatches('/Found unsupported legacy directives in HTTP\.cache_control: unknown/');
174
        /** @var Config */
175
        Config::modify()->set(
176
            HTTP::class,
177
            'cache_control',
178
            [
179
                'no-store' => true,
180
                'unknown' => true,
181
            ]
182
        );
183
        $response = new HTTPResponse('', 200);
184
        $this->addCacheHeaders($response);
185
    }
186
187
    /**
188
     * Tests {@link HTTP::getLinksIn()}
189
     */
190
    public function testGetLinksIn()
191
    {
192
        $content = '
193
			<h2><a href="/">My Cool Site</a></h2>
194
195
			<p>
196
				A boy went <a href="home/">home</a> to see his <span><a href="mother/">mother</a></span>. This
197
				involved a short <a href="$Journey">journey</a>, as well as some <a href="space travel">space travel</a>
198
				and <a href=unquoted>unquoted</a> events, as well as a <a href=\'single quote\'>single quote</a> from
199
				his <a href="/father">father</a>.
200
			</p>
201
202
			<p>
203
				There were also some elements with extra <a class=attribute href=\'attributes\'>attributes</a> which
204
				played a part in his <a href=journey"extra id="JourneyLink">journey</a>. HE ALSO DISCOVERED THE
205
				<A HREF="CAPS LOCK">KEY</a>. Later he got his <a href="quotes \'mixed\' up">mixed up</a>.
206
			</p>
207
		';
208
209
        $expected =  [
210
            '/', 'home/', 'mother/', '$Journey', 'space travel', 'unquoted', 'single quote', '/father', 'attributes',
211
            'journey', 'CAPS LOCK', 'quotes \'mixed\' up'
212
        ];
213
214
        $result = HTTP::getLinksIn($content);
215
216
        // Results don't neccesarily come out in the order they are in the $content param.
217
        sort($result);
218
        sort($expected);
219
220
        $this->assertInternalType('array', ($result);
0 ignored issues
show
Bug introduced by
A parse error occurred: Syntax error, unexpected ';', expecting ',' or ')' on line 220 at column 52
Loading history...
221
        $this->assertEquals($expected, $result, 'Test that all links within the content are found.');
222
    }
223
224
    /**
225
     * Tests {@link HTTP::setGetVar()}
226
     */
227
    public function testSetGetVar()
228
    {
229
        // Hackery to work around volatile URL formats in test invocation,
230
        // and the inability of Director::absoluteBaseURL() to produce consistent URLs.
231
        Director::mockRequest(function (HTTPRequest $request) {
232
            $controller = new Controller();
233
            $controller->setRequest($request);
234
            $controller->pushCurrent();
235
            try {
236
                $this->assertContains(
237
                    'relative/url?foo=bar',
238
                    HTTP::setGetVar('foo', 'bar'),
239
                    'Omitting a URL falls back to current URL'
240
                );
241
            } finally {
242
                $controller->popCurrent();
243
            }
244
        }, 'relative/url/');
245
246
        $this->assertEquals(
247
            '/relative/url?foo=bar',
248
            HTTP::setGetVar('foo', 'bar', 'relative/url'),
249
            'Relative URL without existing query params'
250
        );
251
252
        $this->assertEquals(
253
            '/relative/url?baz=buz&foo=bar',
254
            HTTP::setGetVar('foo', 'bar', '/relative/url?baz=buz'),
255
            'Relative URL with existing query params, and new added key'
256
        );
257
258
        $this->assertEquals(
259
            'http://test.com/?foo=new&buz=baz',
260
            HTTP::setGetVar('foo', 'new', 'http://test.com/?foo=old&buz=baz'),
261
            'Absolute URL without path and multipe existing query params, overwriting an existing parameter'
262
        );
263
264
        $this->assertContains(
265
            'http://test.com/?foo=new',
266
            HTTP::setGetVar('foo', 'new', 'http://test.com/?foo=&foo=old'),
267
            'Absolute URL and empty query param'
268
        );
269
        // http_build_query() escapes angular brackets, they should be correctly urldecoded by the browser client
270
        $this->assertEquals(
271
            'http://test.com/?foo%5Btest%5D=one&foo%5Btest%5D=two',
272
            HTTP::setGetVar('foo[test]', 'two', 'http://test.com/?foo[test]=one'),
273
            'Absolute URL and PHP array query string notation'
274
        );
275
276
        $urls = [
277
            'http://www.test.com:8080',
278
            'http://test.com:3000/',
279
            'http://test.com:3030/baz/',
280
            'http://baz:[email protected]',
281
            'http://[email protected]/',
282
            'http://baz:[email protected]:8080',
283
            'http://[email protected]:8080'
284
        ];
285
286
        foreach ($urls as $testURL) {
287
            $this->assertEquals(
288
                $testURL . '?foo=bar',
289
                HTTP::setGetVar('foo', 'bar', $testURL),
290
                'Absolute URL and Port Number'
291
            );
292
        }
293
    }
294
295
    /**
296
     * Test that the the get_mime_type() works correctly
297
     */
298
    public function testGetMimeType()
299
    {
300
        $this->assertEquals('text/plain', HTTP::get_mime_type('file.csv'));
301
        $this->assertEquals('image/gif', HTTP::get_mime_type('file.gif'));
302
        $this->assertEquals('text/html', HTTP::get_mime_type('file.html'));
303
        $this->assertEquals('image/jpeg', HTTP::get_mime_type('file.jpg'));
304
        $this->assertEquals('image/jpeg', HTTP::get_mime_type('upperfile.JPG'));
305
        $this->assertEquals('image/png', HTTP::get_mime_type('file.png'));
306
        $this->assertEquals(
307
            'image/vnd.adobe.photoshop',
308
            HTTP::get_mime_type('file.psd')
309
        );
310
        $this->assertEquals('audio/x-wav', HTTP::get_mime_type('file.wav'));
311
    }
312
313
    /**
314
     * Test that absoluteURLs correctly transforms urls within CSS to absolute
315
     */
316
    public function testAbsoluteURLsCSS()
317
    {
318
        $this->withBaseURL(
319
            'http://www.silverstripe.org/',
320
            function () {
321
322
                // background-image
323
                // Note that using /./ in urls is absolutely acceptable
324
                $this->assertEquals(
325
                    '<div style="background-image: url(\'http://www.silverstripe.org/./images/mybackground.gif\');">' . 'Content</div>',
326
                    HTTP::absoluteURLs('<div style="background-image: url(\'./images/mybackground.gif\');">Content</div>')
327
                );
328
329
                // background
330
                $this->assertEquals(
331
                    '<div style="background: url(\'http://www.silverstripe.org/images/mybackground.gif\');">Content</div>',
332
                    HTTP::absoluteURLs('<div style="background: url(\'images/mybackground.gif\');">Content</div>')
333
                );
334
335
                // list-style-image
336
                $this->assertEquals(
337
                    '<div style=\'background: url(http://www.silverstripe.org/list.png);\'>Content</div>',
338
                    HTTP::absoluteURLs('<div style=\'background: url(list.png);\'>Content</div>')
339
                );
340
341
                // list-style
342
                $this->assertEquals(
343
                    '<div style=\'background: url("http://www.silverstripe.org/./assets/list.png");\'>Content</div>',
344
                    HTTP::absoluteURLs('<div style=\'background: url("./assets/list.png");\'>Content</div>')
345
                );
346
            }
347
        );
348
    }
349
350
    /**
351
     * Test that absoluteURLs correctly transforms urls within html attributes to absolute
352
     */
353
    public function testAbsoluteURLsAttributes()
354
    {
355
        $this->withBaseURL(
356
            'http://www.silverstripe.org/',
357
            function () {
358
                //empty links
359
                $this->assertEquals(
360
                    '<a href="http://www.silverstripe.org/">test</a>',
361
                    HTTP::absoluteURLs('<a href="">test</a>')
362
                );
363
364
                $this->assertEquals(
365
                    '<a href="http://www.silverstripe.org/">test</a>',
366
                    HTTP::absoluteURLs('<a href="/">test</a>')
367
                );
368
369
                //relative
370
                $this->assertEquals(
371
                    '<a href="http://www.silverstripe.org/">test</a>',
372
                    HTTP::absoluteURLs('<a href="./">test</a>')
373
                );
374
                $this->assertEquals(
375
                    '<a href="http://www.silverstripe.org/">test</a>',
376
                    HTTP::absoluteURLs('<a href=".">test</a>')
377
                );
378
379
                // links
380
                $this->assertEquals(
381
                    '<a href=\'http://www.silverstripe.org/blog/\'>SS Blog</a>',
382
                    HTTP::absoluteURLs('<a href=\'/blog/\'>SS Blog</a>')
383
                );
384
385
                // background
386
                // Note that using /./ in urls is absolutely acceptable
387
                $this->assertEquals(
388
                    '<div background="http://www.silverstripe.org/./themes/silverstripe/images/nav-bg-repeat-2.png">' . 'SS Blog</div>',
389
                    HTTP::absoluteURLs('<div background="./themes/silverstripe/images/nav-bg-repeat-2.png">SS Blog</div>')
390
                );
391
392
                //check dot segments
393
                // Assumption: dots are not removed
394
                //if they were, the url should be: http://www.silverstripe.org/abc
395
                $this->assertEquals(
396
                    '<a href="http://www.silverstripe.org/test/page/../../abc">Test</a>',
397
                    HTTP::absoluteURLs('<a href="test/page/../../abc">Test</a>')
398
                );
399
400
                // image
401
                $this->assertEquals(
402
                    '<img src=\'http://www.silverstripe.org/themes/silverstripe/images/logo-org.png\' />',
403
                    HTTP::absoluteURLs('<img src=\'themes/silverstripe/images/logo-org.png\' />')
404
                );
405
406
                // link
407
                $this->assertEquals(
408
                    '<link href=http://www.silverstripe.org/base.css />',
409
                    HTTP::absoluteURLs('<link href=base.css />')
410
                );
411
412
                // Test special characters are retained
413
                $this->assertEquals(
414
                    '<a href="http://www.silverstripe.org/Security/changepassword?m=3&amp;t=7214fdfde">password reset link</a>',
415
                    HTTP::absoluteURLs('<a href="/Security/changepassword?m=3&amp;t=7214fdfde">password reset link</a>')
416
                );
417
            }
418
        );
419
    }
420
421
    /**
422
     *  Make sure URI schemes are not rewritten
423
     */
424
    public function testURISchemes()
425
    {
426
        $this->withBaseURL(
427
            'http://www.silverstripe.org/',
428
            function ($test) {
429
430
                // mailto
431
                $this->assertEquals(
432
                    '<a href=\'mailto:[email protected]\'>Email Us</a>',
433
                    HTTP::absoluteURLs('<a href=\'mailto:[email protected]\'>Email Us</a>'),
434
                    'Email links are not rewritten'
435
                );
436
437
                // data uri
438
                $this->assertEquals(
439
                    '<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38' . 'GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==" alt="Red dot" />',
440
                    HTTP::absoluteURLs(
441
                        '<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAH' . 'ElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==" alt="Red dot" />'
442
                    ),
443
                    'Data URI links are not rewritten'
444
                );
445
446
                // call
447
                $this->assertEquals(
448
                    '<a href="callto:12345678" />',
449
                    HTTP::absoluteURLs('<a href="callto:12345678" />'),
450
                    'Call to links are not rewritten'
451
                );
452
            }
453
        );
454
    }
455
456
    public function testFilename2url()
457
    {
458
        $this->withBaseURL(
459
            'http://www.silverstripe.org/',
460
            function () {
461
                $frameworkTests = ltrim(FRAMEWORK_DIR . '/tests', '/');
462
                $this->assertEquals(
463
                    "http://www.silverstripe.org/$frameworkTests/php/Control/HTTPTest.php",
464
                    HTTP::filename2url(__FILE__)
465
                );
466
            }
467
        );
468
    }
469
470
    /**
471
     * Process cache headers on a response
472
     *
473
     * @param HTTPResponse $response
474
     */
475
    protected function addCacheHeaders(HTTPResponse $response)
476
    {
477
        // Mock request
478
        $session = new Session([]);
479
        $request = new HTTPRequest('GET', '/');
480
        $request->setSession($session);
481
482
        // Run middleware
483
        HTTPCacheControlMiddleware::singleton()
484
            ->process($request, function () use ($response) {
485
                return $response;
486
            });
487
    }
488
}
489