Completed
Push — 4.2 ( b98495...ec5640 )
by Daniel
22:42
created

HTTPTest::testDeprecatedCacheControlHandling()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 18
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 11
nc 1
nop 0
dl 0
loc 18
rs 9.9
c 0
b 0
f 0
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()
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 = array(
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(
0 ignored issues
show
Bug introduced by
The method set() does not exist on SilverStripe\Core\Config\Config. ( Ignorable by Annotation )

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

119
        Config::modify()->/** @scrutinizer ignore-call */ set(

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
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
    /**
171
     * @expectedException \LogicException
172
     * @expectedExceptionMessageRegExp /Found unsupported legacy directives in HTTP\.cache_control: unknown/
173
     */
174
    public function testDeprecatedCacheControlHandlingThrowsWithUnknownDirectives()
175
    {
176
        /** @var Config */
177
        Config::modify()->set(
178
            HTTP::class,
179
            'cache_control',
180
            [
181
                'no-store' => true,
182
                'unknown' => true,
183
            ]
184
        );
185
        $response = new HTTPResponse('', 200);
186
        $this->addCacheHeaders($response);
187
    }
188
189
    /**
190
     * Tests {@link HTTP::getLinksIn()}
191
     */
192
    public function testGetLinksIn()
193
    {
194
        $content = '
195
			<h2><a href="/">My Cool Site</a></h2>
196
197
			<p>
198
				A boy went <a href="home/">home</a> to see his <span><a href="mother/">mother</a></span>. This
199
				involved a short <a href="$Journey">journey</a>, as well as some <a href="space travel">space travel</a>
200
				and <a href=unquoted>unquoted</a> events, as well as a <a href=\'single quote\'>single quote</a> from
201
				his <a href="/father">father</a>.
202
			</p>
203
204
			<p>
205
				There were also some elements with extra <a class=attribute href=\'attributes\'>attributes</a> which
206
				played a part in his <a href=journey"extra id="JourneyLink">journey</a>. HE ALSO DISCOVERED THE
207
				<A HREF="CAPS LOCK">KEY</a>. Later he got his <a href="quotes \'mixed\' up">mixed up</a>.
208
			</p>
209
		';
210
211
        $expected = array (
212
            '/', 'home/', 'mother/', '$Journey', 'space travel', 'unquoted', 'single quote', '/father', 'attributes',
213
            'journey', 'CAPS LOCK', 'quotes \'mixed\' up'
214
        );
215
216
        $result = HTTP::getLinksIn($content);
217
218
        // Results don't neccesarily come out in the order they are in the $content param.
219
        sort($result);
220
        sort($expected);
221
222
        $this->assertTrue(is_array($result));
223
        $this->assertEquals($expected, $result, 'Test that all links within the content are found.');
224
    }
225
226
    /**
227
     * Tests {@link HTTP::setGetVar()}
228
     */
229
    public function testSetGetVar()
230
    {
231
        // Hackery to work around volatile URL formats in test invocation,
232
        // and the inability of Director::absoluteBaseURL() to produce consistent URLs.
233
        Director::mockRequest(function (HTTPRequest $request) {
234
            $controller = new Controller();
235
            $controller->setRequest($request);
236
            $controller->pushCurrent();
237
            try {
238
                $this->assertContains(
239
                    'relative/url?foo=bar',
240
                    HTTP::setGetVar('foo', 'bar'),
241
                    'Omitting a URL falls back to current URL'
242
                );
243
            } finally {
244
                $controller->popCurrent();
245
            }
246
        }, 'relative/url/');
247
248
        $this->assertEquals(
249
            'relative/url?foo=bar',
250
            HTTP::setGetVar('foo', 'bar', 'relative/url'),
251
            'Relative URL without existing query params'
252
        );
253
254
        $this->assertEquals(
255
            'relative/url?baz=buz&foo=bar',
256
            HTTP::setGetVar('foo', 'bar', '/relative/url?baz=buz'),
257
            'Relative URL with existing query params, and new added key'
258
        );
259
260
        $this->assertEquals(
261
            'http://test.com/?foo=new&buz=baz',
262
            HTTP::setGetVar('foo', 'new', 'http://test.com/?foo=old&buz=baz'),
263
            'Absolute URL without path and multipe existing query params, overwriting an existing parameter'
264
        );
265
266
        $this->assertContains(
267
            'http://test.com/?foo=new',
268
            HTTP::setGetVar('foo', 'new', 'http://test.com/?foo=&foo=old'),
269
            'Absolute URL and empty query param'
270
        );
271
        // http_build_query() escapes angular brackets, they should be correctly urldecoded by the browser client
272
        $this->assertEquals(
273
            'http://test.com/?foo%5Btest%5D=one&foo%5Btest%5D=two',
274
            HTTP::setGetVar('foo[test]', 'two', 'http://test.com/?foo[test]=one'),
275
            'Absolute URL and PHP array query string notation'
276
        );
277
278
        $urls = array(
279
            'http://www.test.com:8080',
280
            'http://test.com:3000/',
281
            'http://test.com:3030/baz/',
282
            'http://baz:[email protected]',
283
            'http://[email protected]/',
284
            'http://baz:[email protected]:8080',
285
            'http://[email protected]:8080'
286
        );
287
288
        foreach ($urls as $testURL) {
289
            $this->assertEquals(
290
                $testURL . '?foo=bar',
291
                HTTP::setGetVar('foo', 'bar', $testURL),
292
                'Absolute URL and Port Number'
293
            );
294
        }
295
    }
296
297
    /**
298
     * Test that the the get_mime_type() works correctly
299
     */
300
    public function testGetMimeType()
301
    {
302
        $this->assertEquals('text/plain', HTTP::get_mime_type('file.csv'));
303
        $this->assertEquals('image/gif', HTTP::get_mime_type('file.gif'));
304
        $this->assertEquals('text/html', HTTP::get_mime_type('file.html'));
305
        $this->assertEquals('image/jpeg', HTTP::get_mime_type('file.jpg'));
306
        $this->assertEquals('image/jpeg', HTTP::get_mime_type('upperfile.JPG'));
307
        $this->assertEquals('image/png', HTTP::get_mime_type('file.png'));
308
        $this->assertEquals(
309
            'image/vnd.adobe.photoshop',
310
            HTTP::get_mime_type('file.psd')
311
        );
312
        $this->assertEquals('audio/x-wav', HTTP::get_mime_type('file.wav'));
313
    }
314
315
    /**
316
     * Test that absoluteURLs correctly transforms urls within CSS to absolute
317
     */
318
    public function testAbsoluteURLsCSS()
319
    {
320
        $this->withBaseURL(
321
            'http://www.silverstripe.org/',
322
            function () {
323
324
                // background-image
325
                // Note that using /./ in urls is absolutely acceptable
326
                $this->assertEquals(
327
                    '<div style="background-image: url(\'http://www.silverstripe.org/./images/mybackground.gif\');">' . 'Content</div>',
328
                    HTTP::absoluteURLs('<div style="background-image: url(\'./images/mybackground.gif\');">Content</div>')
329
                );
330
331
                // background
332
                $this->assertEquals(
333
                    '<div style="background: url(\'http://www.silverstripe.org/images/mybackground.gif\');">Content</div>',
334
                    HTTP::absoluteURLs('<div style="background: url(\'images/mybackground.gif\');">Content</div>')
335
                );
336
337
                // list-style-image
338
                $this->assertEquals(
339
                    '<div style=\'background: url(http://www.silverstripe.org/list.png);\'>Content</div>',
340
                    HTTP::absoluteURLs('<div style=\'background: url(list.png);\'>Content</div>')
341
                );
342
343
                // list-style
344
                $this->assertEquals(
345
                    '<div style=\'background: url("http://www.silverstripe.org/./assets/list.png");\'>Content</div>',
346
                    HTTP::absoluteURLs('<div style=\'background: url("./assets/list.png");\'>Content</div>')
347
                );
348
            }
349
        );
350
    }
351
352
    /**
353
     * Test that absoluteURLs correctly transforms urls within html attributes to absolute
354
     */
355
    public function testAbsoluteURLsAttributes()
356
    {
357
        $this->withBaseURL(
358
            'http://www.silverstripe.org/',
359
            function () {
360
                //empty links
361
                $this->assertEquals(
362
                    '<a href="http://www.silverstripe.org/">test</a>',
363
                    HTTP::absoluteURLs('<a href="">test</a>')
364
                );
365
366
                $this->assertEquals(
367
                    '<a href="http://www.silverstripe.org/">test</a>',
368
                    HTTP::absoluteURLs('<a href="/">test</a>')
369
                );
370
371
                //relative
372
                $this->assertEquals(
373
                    '<a href="http://www.silverstripe.org/">test</a>',
374
                    HTTP::absoluteURLs('<a href="./">test</a>')
375
                );
376
                $this->assertEquals(
377
                    '<a href="http://www.silverstripe.org/">test</a>',
378
                    HTTP::absoluteURLs('<a href=".">test</a>')
379
                );
380
381
                // links
382
                $this->assertEquals(
383
                    '<a href=\'http://www.silverstripe.org/blog/\'>SS Blog</a>',
384
                    HTTP::absoluteURLs('<a href=\'/blog/\'>SS Blog</a>')
385
                );
386
387
                // background
388
                // Note that using /./ in urls is absolutely acceptable
389
                $this->assertEquals(
390
                    '<div background="http://www.silverstripe.org/./themes/silverstripe/images/nav-bg-repeat-2.png">' . 'SS Blog</div>',
391
                    HTTP::absoluteURLs('<div background="./themes/silverstripe/images/nav-bg-repeat-2.png">SS Blog</div>')
392
                );
393
394
                //check dot segments
395
                // Assumption: dots are not removed
396
                //if they were, the url should be: http://www.silverstripe.org/abc
397
                $this->assertEquals(
398
                    '<a href="http://www.silverstripe.org/test/page/../../abc">Test</a>',
399
                    HTTP::absoluteURLs('<a href="test/page/../../abc">Test</a>')
400
                );
401
402
                // image
403
                $this->assertEquals(
404
                    '<img src=\'http://www.silverstripe.org/themes/silverstripe/images/logo-org.png\' />',
405
                    HTTP::absoluteURLs('<img src=\'themes/silverstripe/images/logo-org.png\' />')
406
                );
407
408
                // link
409
                $this->assertEquals(
410
                    '<link href=http://www.silverstripe.org/base.css />',
411
                    HTTP::absoluteURLs('<link href=base.css />')
412
                );
413
414
                // Test special characters are retained
415
                $this->assertEquals(
416
                    '<a href="http://www.silverstripe.org/Security/changepassword?m=3&amp;t=7214fdfde">password reset link</a>',
417
                    HTTP::absoluteURLs('<a href="/Security/changepassword?m=3&amp;t=7214fdfde">password reset link</a>')
418
                );
419
            }
420
        );
421
    }
422
423
    /**
424
     *  Make sure URI schemes are not rewritten
425
     */
426
    public function testURISchemes()
427
    {
428
        $this->withBaseURL(
429
            'http://www.silverstripe.org/',
430
            function ($test) {
0 ignored issues
show
Unused Code introduced by
The parameter $test 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

430
            function (/** @scrutinizer ignore-unused */ $test) {

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...
431
432
                // mailto
433
                $this->assertEquals(
434
                    '<a href=\'mailto:[email protected]\'>Email Us</a>',
435
                    HTTP::absoluteURLs('<a href=\'mailto:[email protected]\'>Email Us</a>'),
436
                    'Email links are not rewritten'
437
                );
438
439
                // data uri
440
                $this->assertEquals(
441
                    '<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38' . 'GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==" alt="Red dot" />',
442
                    HTTP::absoluteURLs(
443
                        '<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAH' . 'ElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==" alt="Red dot" />'
444
                    ),
445
                    'Data URI links are not rewritten'
446
                );
447
448
                // call
449
                $this->assertEquals(
450
                    '<a href="callto:12345678" />',
451
                    HTTP::absoluteURLs('<a href="callto:12345678" />'),
452
                    'Call to links are not rewritten'
453
                );
454
            }
455
        );
456
    }
457
458
    public function testFilename2url()
459
    {
460
        $this->withBaseURL(
461
            'http://www.silverstripe.org/',
462
            function ($test) {
0 ignored issues
show
Unused Code introduced by
The parameter $test 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

462
            function (/** @scrutinizer ignore-unused */ $test) {

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...
463
                $frameworkTests = ltrim(FRAMEWORK_DIR . '/tests', '/');
464
                $this->assertEquals(
465
                    "http://www.silverstripe.org/$frameworkTests/php/Control/HTTPTest.php",
466
                    HTTP::filename2url(__FILE__)
467
                );
468
            }
469
        );
470
    }
471
472
    /**
473
     * Process cache headers on a response
474
     *
475
     * @param HTTPResponse $response
476
     */
477
    protected function addCacheHeaders(HTTPResponse $response)
478
    {
479
        // Mock request
480
        $session = new Session([]);
481
        $request = new HTTPRequest('GET', '/');
482
        $request->setSession($session);
483
484
        // Run middleware
485
        HTTPCacheControlMiddleware::singleton()
486
            ->process($request, function (HTTPRequest $request) use ($response) {
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

486
            ->process($request, function (/** @scrutinizer ignore-unused */ HTTPRequest $request) use ($response) {

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...
487
                return $response;
488
            });
489
    }
490
}
491