1<?php
2
3/*
4 * This file is part of the Symfony package.
5 *
6 * (c) Fabien Potencier <fabien@symfony.com>
7 *
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
10 */
11
12namespace Symfony\Component\HttpFoundation\Tests;
13
14use Symfony\Component\HttpFoundation\ResponseHeaderBag;
15use Symfony\Component\HttpFoundation\Cookie;
16
17/**
18 * @group time-sensitive
19 */
20class ResponseHeaderBagTest extends \PHPUnit_Framework_TestCase
21{
22    /**
23     * @dataProvider provideAllPreserveCase
24     */
25    public function testAllPreserveCase($headers, $expected)
26    {
27        $bag = new ResponseHeaderBag($headers);
28
29        $this->assertEquals($expected, $bag->allPreserveCase(), '->allPreserveCase() gets all input keys in original case');
30    }
31
32    public function provideAllPreserveCase()
33    {
34        return array(
35            array(
36                array('fOo' => 'BAR'),
37                array('fOo' => array('BAR'), 'Cache-Control' => array('no-cache')),
38            ),
39            array(
40                array('ETag' => 'xyzzy'),
41                array('ETag' => array('xyzzy'), 'Cache-Control' => array('private, must-revalidate')),
42            ),
43            array(
44                array('Content-MD5' => 'Q2hlY2sgSW50ZWdyaXR5IQ=='),
45                array('Content-MD5' => array('Q2hlY2sgSW50ZWdyaXR5IQ=='), 'Cache-Control' => array('no-cache')),
46            ),
47            array(
48                array('P3P' => 'CP="CAO PSA OUR"'),
49                array('P3P' => array('CP="CAO PSA OUR"'), 'Cache-Control' => array('no-cache')),
50            ),
51            array(
52                array('WWW-Authenticate' => 'Basic realm="WallyWorld"'),
53                array('WWW-Authenticate' => array('Basic realm="WallyWorld"'), 'Cache-Control' => array('no-cache')),
54            ),
55            array(
56                array('X-UA-Compatible' => 'IE=edge,chrome=1'),
57                array('X-UA-Compatible' => array('IE=edge,chrome=1'), 'Cache-Control' => array('no-cache')),
58            ),
59            array(
60                array('X-XSS-Protection' => '1; mode=block'),
61                array('X-XSS-Protection' => array('1; mode=block'), 'Cache-Control' => array('no-cache')),
62            ),
63        );
64    }
65
66    public function testCacheControlHeader()
67    {
68        $bag = new ResponseHeaderBag(array());
69        $this->assertEquals('no-cache', $bag->get('Cache-Control'));
70        $this->assertTrue($bag->hasCacheControlDirective('no-cache'));
71
72        $bag = new ResponseHeaderBag(array('Cache-Control' => 'public'));
73        $this->assertEquals('public', $bag->get('Cache-Control'));
74        $this->assertTrue($bag->hasCacheControlDirective('public'));
75
76        $bag = new ResponseHeaderBag(array('ETag' => 'abcde'));
77        $this->assertEquals('private, must-revalidate', $bag->get('Cache-Control'));
78        $this->assertTrue($bag->hasCacheControlDirective('private'));
79        $this->assertTrue($bag->hasCacheControlDirective('must-revalidate'));
80        $this->assertFalse($bag->hasCacheControlDirective('max-age'));
81
82        $bag = new ResponseHeaderBag(array('Expires' => 'Wed, 16 Feb 2011 14:17:43 GMT'));
83        $this->assertEquals('private, must-revalidate', $bag->get('Cache-Control'));
84
85        $bag = new ResponseHeaderBag(array(
86            'Expires' => 'Wed, 16 Feb 2011 14:17:43 GMT',
87            'Cache-Control' => 'max-age=3600',
88        ));
89        $this->assertEquals('max-age=3600, private', $bag->get('Cache-Control'));
90
91        $bag = new ResponseHeaderBag(array('Last-Modified' => 'abcde'));
92        $this->assertEquals('private, must-revalidate', $bag->get('Cache-Control'));
93
94        $bag = new ResponseHeaderBag(array('Etag' => 'abcde', 'Last-Modified' => 'abcde'));
95        $this->assertEquals('private, must-revalidate', $bag->get('Cache-Control'));
96
97        $bag = new ResponseHeaderBag(array('cache-control' => 'max-age=100'));
98        $this->assertEquals('max-age=100, private', $bag->get('Cache-Control'));
99
100        $bag = new ResponseHeaderBag(array('cache-control' => 's-maxage=100'));
101        $this->assertEquals('s-maxage=100', $bag->get('Cache-Control'));
102
103        $bag = new ResponseHeaderBag(array('cache-control' => 'private, max-age=100'));
104        $this->assertEquals('max-age=100, private', $bag->get('Cache-Control'));
105
106        $bag = new ResponseHeaderBag(array('cache-control' => 'public, max-age=100'));
107        $this->assertEquals('max-age=100, public', $bag->get('Cache-Control'));
108
109        $bag = new ResponseHeaderBag();
110        $bag->set('Last-Modified', 'abcde');
111        $this->assertEquals('private, must-revalidate', $bag->get('Cache-Control'));
112    }
113
114    public function testToStringIncludesCookieHeaders()
115    {
116        $bag = new ResponseHeaderBag(array());
117        $bag->setCookie(new Cookie('foo', 'bar'));
118
119        $this->assertContains('Set-Cookie: foo=bar; path=/; httponly', explode("\r\n", $bag->__toString()));
120
121        $bag->clearCookie('foo');
122
123        $this->assertRegExp('#^Set-Cookie: foo=deleted; expires='.gmdate('D, d-M-Y H:i:s T', time() - 31536001).'; path=/; httponly#m', $bag->__toString());
124    }
125
126    public function testClearCookieSecureNotHttpOnly()
127    {
128        $bag = new ResponseHeaderBag(array());
129
130        $bag->clearCookie('foo', '/', null, true, false);
131
132        $this->assertRegExp('#^Set-Cookie: foo=deleted; expires='.gmdate('D, d-M-Y H:i:s T', time() - 31536001).'; path=/; secure#m', $bag->__toString());
133    }
134
135    public function testReplace()
136    {
137        $bag = new ResponseHeaderBag(array());
138        $this->assertEquals('no-cache', $bag->get('Cache-Control'));
139        $this->assertTrue($bag->hasCacheControlDirective('no-cache'));
140
141        $bag->replace(array('Cache-Control' => 'public'));
142        $this->assertEquals('public', $bag->get('Cache-Control'));
143        $this->assertTrue($bag->hasCacheControlDirective('public'));
144    }
145
146    public function testReplaceWithRemove()
147    {
148        $bag = new ResponseHeaderBag(array());
149        $this->assertEquals('no-cache', $bag->get('Cache-Control'));
150        $this->assertTrue($bag->hasCacheControlDirective('no-cache'));
151
152        $bag->remove('Cache-Control');
153        $bag->replace(array());
154        $this->assertEquals('no-cache', $bag->get('Cache-Control'));
155        $this->assertTrue($bag->hasCacheControlDirective('no-cache'));
156    }
157
158    public function testCookiesWithSameNames()
159    {
160        $bag = new ResponseHeaderBag();
161        $bag->setCookie(new Cookie('foo', 'bar', 0, '/path/foo', 'foo.bar'));
162        $bag->setCookie(new Cookie('foo', 'bar', 0, '/path/bar', 'foo.bar'));
163        $bag->setCookie(new Cookie('foo', 'bar', 0, '/path/bar', 'bar.foo'));
164        $bag->setCookie(new Cookie('foo', 'bar'));
165
166        $this->assertCount(4, $bag->getCookies());
167
168        $headers = explode("\r\n", $bag->__toString());
169        $this->assertContains('Set-Cookie: foo=bar; path=/path/foo; domain=foo.bar; httponly', $headers);
170        $this->assertContains('Set-Cookie: foo=bar; path=/path/foo; domain=foo.bar; httponly', $headers);
171        $this->assertContains('Set-Cookie: foo=bar; path=/path/bar; domain=bar.foo; httponly', $headers);
172        $this->assertContains('Set-Cookie: foo=bar; path=/; httponly', $headers);
173
174        $cookies = $bag->getCookies(ResponseHeaderBag::COOKIES_ARRAY);
175        $this->assertTrue(isset($cookies['foo.bar']['/path/foo']['foo']));
176        $this->assertTrue(isset($cookies['foo.bar']['/path/bar']['foo']));
177        $this->assertTrue(isset($cookies['bar.foo']['/path/bar']['foo']));
178        $this->assertTrue(isset($cookies['']['/']['foo']));
179    }
180
181    public function testRemoveCookie()
182    {
183        $bag = new ResponseHeaderBag();
184        $bag->setCookie(new Cookie('foo', 'bar', 0, '/path/foo', 'foo.bar'));
185        $bag->setCookie(new Cookie('bar', 'foo', 0, '/path/bar', 'foo.bar'));
186
187        $cookies = $bag->getCookies(ResponseHeaderBag::COOKIES_ARRAY);
188        $this->assertTrue(isset($cookies['foo.bar']['/path/foo']));
189
190        $bag->removeCookie('foo', '/path/foo', 'foo.bar');
191
192        $cookies = $bag->getCookies(ResponseHeaderBag::COOKIES_ARRAY);
193        $this->assertFalse(isset($cookies['foo.bar']['/path/foo']));
194
195        $bag->removeCookie('bar', '/path/bar', 'foo.bar');
196
197        $cookies = $bag->getCookies(ResponseHeaderBag::COOKIES_ARRAY);
198        $this->assertFalse(isset($cookies['foo.bar']));
199    }
200
201    public function testRemoveCookieWithNullRemove()
202    {
203        $bag = new ResponseHeaderBag();
204        $bag->setCookie(new Cookie('foo', 'bar', 0));
205        $bag->setCookie(new Cookie('bar', 'foo', 0));
206
207        $cookies = $bag->getCookies(ResponseHeaderBag::COOKIES_ARRAY);
208        $this->assertTrue(isset($cookies['']['/']));
209
210        $bag->removeCookie('foo', null);
211        $cookies = $bag->getCookies(ResponseHeaderBag::COOKIES_ARRAY);
212        $this->assertFalse(isset($cookies['']['/']['foo']));
213
214        $bag->removeCookie('bar', null);
215        $cookies = $bag->getCookies(ResponseHeaderBag::COOKIES_ARRAY);
216        $this->assertFalse(isset($cookies['']['/']['bar']));
217    }
218
219    /**
220     * @expectedException \InvalidArgumentException
221     */
222    public function testGetCookiesWithInvalidArgument()
223    {
224        $bag = new ResponseHeaderBag();
225
226        $cookies = $bag->getCookies('invalid_argument');
227    }
228
229    /**
230     * @expectedException \InvalidArgumentException
231     */
232    public function testMakeDispositionInvalidDisposition()
233    {
234        $headers = new ResponseHeaderBag();
235
236        $headers->makeDisposition('invalid', 'foo.html');
237    }
238
239    /**
240     * @dataProvider provideMakeDisposition
241     */
242    public function testMakeDisposition($disposition, $filename, $filenameFallback, $expected)
243    {
244        $headers = new ResponseHeaderBag();
245
246        $this->assertEquals($expected, $headers->makeDisposition($disposition, $filename, $filenameFallback));
247    }
248
249    public function testToStringDoesntMessUpHeaders()
250    {
251        $headers = new ResponseHeaderBag();
252
253        $headers->set('Location', 'http://www.symfony.com');
254        $headers->set('Content-type', 'text/html');
255
256        (string) $headers;
257
258        $allHeaders = $headers->allPreserveCase();
259        $this->assertEquals(array('http://www.symfony.com'), $allHeaders['Location']);
260        $this->assertEquals(array('text/html'), $allHeaders['Content-type']);
261    }
262
263    public function provideMakeDisposition()
264    {
265        return array(
266            array('attachment', 'foo.html', 'foo.html', 'attachment; filename="foo.html"'),
267            array('attachment', 'foo.html', '', 'attachment; filename="foo.html"'),
268            array('attachment', 'foo bar.html', '', 'attachment; filename="foo bar.html"'),
269            array('attachment', 'foo "bar".html', '', 'attachment; filename="foo \\"bar\\".html"'),
270            array('attachment', 'foo%20bar.html', 'foo bar.html', 'attachment; filename="foo bar.html"; filename*=utf-8\'\'foo%2520bar.html'),
271            array('attachment', 'föö.html', 'foo.html', 'attachment; filename="foo.html"; filename*=utf-8\'\'f%C3%B6%C3%B6.html'),
272        );
273    }
274
275    /**
276     * @dataProvider provideMakeDispositionFail
277     * @expectedException \InvalidArgumentException
278     */
279    public function testMakeDispositionFail($disposition, $filename)
280    {
281        $headers = new ResponseHeaderBag();
282
283        $headers->makeDisposition($disposition, $filename);
284    }
285
286    public function provideMakeDispositionFail()
287    {
288        return array(
289            array('attachment', 'foo%20bar.html'),
290            array('attachment', 'foo/bar.html'),
291            array('attachment', '/foo.html'),
292            array('attachment', 'foo\bar.html'),
293            array('attachment', '\foo.html'),
294            array('attachment', 'föö.html'),
295        );
296    }
297}
298