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 PHPUnit\Framework\TestCase; 15use Symfony\Component\HttpFoundation\Cookie; 16use Symfony\Component\HttpFoundation\ResponseHeaderBag; 17 18/** 19 * @group time-sensitive 20 */ 21class ResponseHeaderBagTest extends TestCase 22{ 23 public function testAllPreserveCase() 24 { 25 $headers = [ 26 'fOo' => 'BAR', 27 'ETag' => 'xyzzy', 28 'Content-MD5' => 'Q2hlY2sgSW50ZWdyaXR5IQ==', 29 'P3P' => 'CP="CAO PSA OUR"', 30 'WWW-Authenticate' => 'Basic realm="WallyWorld"', 31 'X-UA-Compatible' => 'IE=edge,chrome=1', 32 'X-XSS-Protection' => '1; mode=block', 33 ]; 34 35 $bag = new ResponseHeaderBag($headers); 36 $allPreservedCase = $bag->allPreserveCase(); 37 38 foreach (array_keys($headers) as $headerName) { 39 $this->assertArrayHasKey($headerName, $allPreservedCase, '->allPreserveCase() gets all input keys in original case'); 40 } 41 } 42 43 public function testCacheControlHeader() 44 { 45 $bag = new ResponseHeaderBag([]); 46 $this->assertEquals('no-cache, private', $bag->get('Cache-Control')); 47 $this->assertTrue($bag->hasCacheControlDirective('no-cache')); 48 49 $bag = new ResponseHeaderBag(['Cache-Control' => 'public']); 50 $this->assertEquals('public', $bag->get('Cache-Control')); 51 $this->assertTrue($bag->hasCacheControlDirective('public')); 52 53 $bag = new ResponseHeaderBag(['ETag' => 'abcde']); 54 $this->assertEquals('no-cache, private', $bag->get('Cache-Control')); 55 $this->assertTrue($bag->hasCacheControlDirective('private')); 56 $this->assertTrue($bag->hasCacheControlDirective('no-cache')); 57 $this->assertFalse($bag->hasCacheControlDirective('max-age')); 58 59 $bag = new ResponseHeaderBag(['Expires' => 'Wed, 16 Feb 2011 14:17:43 GMT']); 60 $this->assertEquals('private, must-revalidate', $bag->get('Cache-Control')); 61 62 $bag = new ResponseHeaderBag([ 63 'Expires' => 'Wed, 16 Feb 2011 14:17:43 GMT', 64 'Cache-Control' => 'max-age=3600', 65 ]); 66 $this->assertEquals('max-age=3600, private', $bag->get('Cache-Control')); 67 68 $bag = new ResponseHeaderBag(['Last-Modified' => 'abcde']); 69 $this->assertEquals('private, must-revalidate', $bag->get('Cache-Control')); 70 71 $bag = new ResponseHeaderBag(['Etag' => 'abcde', 'Last-Modified' => 'abcde']); 72 $this->assertEquals('private, must-revalidate', $bag->get('Cache-Control')); 73 74 $bag = new ResponseHeaderBag(['cache-control' => 'max-age=100']); 75 $this->assertEquals('max-age=100, private', $bag->get('Cache-Control')); 76 77 $bag = new ResponseHeaderBag(['cache-control' => 's-maxage=100']); 78 $this->assertEquals('s-maxage=100', $bag->get('Cache-Control')); 79 80 $bag = new ResponseHeaderBag(['cache-control' => 'private, max-age=100']); 81 $this->assertEquals('max-age=100, private', $bag->get('Cache-Control')); 82 83 $bag = new ResponseHeaderBag(['cache-control' => 'public, max-age=100']); 84 $this->assertEquals('max-age=100, public', $bag->get('Cache-Control')); 85 86 $bag = new ResponseHeaderBag(); 87 $bag->set('Last-Modified', 'abcde'); 88 $this->assertEquals('private, must-revalidate', $bag->get('Cache-Control')); 89 90 $bag = new ResponseHeaderBag(); 91 $bag->set('Cache-Control', ['public', 'must-revalidate']); 92 $this->assertCount(1, $bag->all('Cache-Control')); 93 $this->assertEquals('must-revalidate, public', $bag->get('Cache-Control')); 94 95 $bag = new ResponseHeaderBag(); 96 $bag->set('Cache-Control', 'public'); 97 $bag->set('Cache-Control', 'must-revalidate', false); 98 $this->assertCount(1, $bag->all('Cache-Control')); 99 $this->assertEquals('must-revalidate, public', $bag->get('Cache-Control')); 100 } 101 102 public function testCacheControlClone() 103 { 104 $headers = ['foo' => 'bar']; 105 $bag1 = new ResponseHeaderBag($headers); 106 $bag2 = new ResponseHeaderBag($bag1->allPreserveCase()); 107 $this->assertEquals($bag1->allPreserveCase(), $bag2->allPreserveCase()); 108 } 109 110 public function testToStringIncludesCookieHeaders() 111 { 112 $bag = new ResponseHeaderBag([]); 113 $bag->setCookie(Cookie::create('foo', 'bar')); 114 115 $this->assertSetCookieHeader('foo=bar; path=/; httponly; samesite=lax', $bag); 116 117 $bag->clearCookie('foo'); 118 119 $this->assertSetCookieHeader('foo=deleted; expires='.gmdate('D, d-M-Y H:i:s T', time() - 31536001).'; Max-Age=0; path=/; httponly', $bag); 120 } 121 122 public function testClearCookieSecureNotHttpOnly() 123 { 124 $bag = new ResponseHeaderBag([]); 125 126 $bag->clearCookie('foo', '/', null, true, false); 127 128 $this->assertSetCookieHeader('foo=deleted; expires='.gmdate('D, d-M-Y H:i:s T', time() - 31536001).'; Max-Age=0; path=/; secure', $bag); 129 } 130 131 public function testClearCookieSamesite() 132 { 133 $bag = new ResponseHeaderBag([]); 134 135 $bag->clearCookie('foo', '/', null, true, false, 'none'); 136 $this->assertSetCookieHeader('foo=deleted; expires='.gmdate('D, d-M-Y H:i:s T', time() - 31536001).'; Max-Age=0; path=/; secure; samesite=none', $bag); 137 } 138 139 public function testReplace() 140 { 141 $bag = new ResponseHeaderBag([]); 142 $this->assertEquals('no-cache, private', $bag->get('Cache-Control')); 143 $this->assertTrue($bag->hasCacheControlDirective('no-cache')); 144 145 $bag->replace(['Cache-Control' => 'public']); 146 $this->assertEquals('public', $bag->get('Cache-Control')); 147 $this->assertTrue($bag->hasCacheControlDirective('public')); 148 } 149 150 public function testReplaceWithRemove() 151 { 152 $bag = new ResponseHeaderBag([]); 153 $this->assertEquals('no-cache, private', $bag->get('Cache-Control')); 154 $this->assertTrue($bag->hasCacheControlDirective('no-cache')); 155 156 $bag->remove('Cache-Control'); 157 $bag->replace([]); 158 $this->assertEquals('no-cache, private', $bag->get('Cache-Control')); 159 $this->assertTrue($bag->hasCacheControlDirective('no-cache')); 160 } 161 162 public function testCookiesWithSameNames() 163 { 164 $bag = new ResponseHeaderBag(); 165 $bag->setCookie(Cookie::create('foo', 'bar', 0, '/path/foo', 'foo.bar')); 166 $bag->setCookie(Cookie::create('foo', 'bar', 0, '/path/bar', 'foo.bar')); 167 $bag->setCookie(Cookie::create('foo', 'bar', 0, '/path/bar', 'bar.foo')); 168 $bag->setCookie(Cookie::create('foo', 'bar')); 169 170 $this->assertCount(4, $bag->getCookies()); 171 $this->assertEquals('foo=bar; path=/path/foo; domain=foo.bar; httponly; samesite=lax', $bag->get('set-cookie')); 172 $this->assertEquals([ 173 'foo=bar; path=/path/foo; domain=foo.bar; httponly; samesite=lax', 174 'foo=bar; path=/path/bar; domain=foo.bar; httponly; samesite=lax', 175 'foo=bar; path=/path/bar; domain=bar.foo; httponly; samesite=lax', 176 'foo=bar; path=/; httponly; samesite=lax', 177 ], $bag->all('set-cookie')); 178 179 $this->assertSetCookieHeader('foo=bar; path=/path/foo; domain=foo.bar; httponly; samesite=lax', $bag); 180 $this->assertSetCookieHeader('foo=bar; path=/path/bar; domain=foo.bar; httponly; samesite=lax', $bag); 181 $this->assertSetCookieHeader('foo=bar; path=/path/bar; domain=bar.foo; httponly; samesite=lax', $bag); 182 $this->assertSetCookieHeader('foo=bar; path=/; httponly; samesite=lax', $bag); 183 184 $cookies = $bag->getCookies(ResponseHeaderBag::COOKIES_ARRAY); 185 186 $this->assertArrayHasKey('foo', $cookies['foo.bar']['/path/foo']); 187 $this->assertArrayHasKey('foo', $cookies['foo.bar']['/path/bar']); 188 $this->assertArrayHasKey('foo', $cookies['bar.foo']['/path/bar']); 189 $this->assertArrayHasKey('foo', $cookies['']['/']); 190 } 191 192 public function testRemoveCookie() 193 { 194 $bag = new ResponseHeaderBag(); 195 $this->assertFalse($bag->has('set-cookie')); 196 197 $bag->setCookie(Cookie::create('foo', 'bar', 0, '/path/foo', 'foo.bar')); 198 $bag->setCookie(Cookie::create('bar', 'foo', 0, '/path/bar', 'foo.bar')); 199 $this->assertTrue($bag->has('set-cookie')); 200 201 $cookies = $bag->getCookies(ResponseHeaderBag::COOKIES_ARRAY); 202 $this->assertArrayHasKey('/path/foo', $cookies['foo.bar']); 203 204 $bag->removeCookie('foo', '/path/foo', 'foo.bar'); 205 $this->assertTrue($bag->has('set-cookie')); 206 207 $cookies = $bag->getCookies(ResponseHeaderBag::COOKIES_ARRAY); 208 $this->assertArrayNotHasKey('/path/foo', $cookies['foo.bar']); 209 210 $bag->removeCookie('bar', '/path/bar', 'foo.bar'); 211 $this->assertFalse($bag->has('set-cookie')); 212 213 $cookies = $bag->getCookies(ResponseHeaderBag::COOKIES_ARRAY); 214 $this->assertArrayNotHasKey('foo.bar', $cookies); 215 } 216 217 public function testRemoveCookieWithNullRemove() 218 { 219 $bag = new ResponseHeaderBag(); 220 $bag->setCookie(Cookie::create('foo', 'bar')); 221 $bag->setCookie(Cookie::create('bar', 'foo')); 222 223 $cookies = $bag->getCookies(ResponseHeaderBag::COOKIES_ARRAY); 224 $this->assertArrayHasKey('/', $cookies['']); 225 226 $bag->removeCookie('foo', null); 227 $cookies = $bag->getCookies(ResponseHeaderBag::COOKIES_ARRAY); 228 $this->assertArrayNotHasKey('foo', $cookies['']['/']); 229 230 $bag->removeCookie('bar', null); 231 $cookies = $bag->getCookies(ResponseHeaderBag::COOKIES_ARRAY); 232 $this->assertFalse(isset($cookies['']['/']['bar'])); 233 } 234 235 public function testSetCookieHeader() 236 { 237 $bag = new ResponseHeaderBag(); 238 $bag->set('set-cookie', 'foo=bar'); 239 $this->assertEquals([Cookie::create('foo', 'bar', 0, '/', null, false, false, true, null)], $bag->getCookies()); 240 241 $bag->set('set-cookie', 'foo2=bar2', false); 242 $this->assertEquals([ 243 Cookie::create('foo', 'bar', 0, '/', null, false, false, true, null), 244 Cookie::create('foo2', 'bar2', 0, '/', null, false, false, true, null), 245 ], $bag->getCookies()); 246 247 $bag->remove('set-cookie'); 248 $this->assertEquals([], $bag->getCookies()); 249 } 250 251 public function testGetCookiesWithInvalidArgument() 252 { 253 $this->expectException('InvalidArgumentException'); 254 $bag = new ResponseHeaderBag(); 255 256 $bag->getCookies('invalid_argument'); 257 } 258 259 public function testToStringDoesntMessUpHeaders() 260 { 261 $headers = new ResponseHeaderBag(); 262 263 $headers->set('Location', 'http://www.symfony.com'); 264 $headers->set('Content-type', 'text/html'); 265 266 (string) $headers; 267 268 $allHeaders = $headers->allPreserveCase(); 269 $this->assertEquals(['http://www.symfony.com'], $allHeaders['Location']); 270 $this->assertEquals(['text/html'], $allHeaders['Content-type']); 271 } 272 273 public function testDateHeaderAddedOnCreation() 274 { 275 $now = time(); 276 277 $bag = new ResponseHeaderBag(); 278 $this->assertTrue($bag->has('Date')); 279 280 $this->assertEquals($now, $bag->getDate('Date')->getTimestamp()); 281 } 282 283 public function testDateHeaderCanBeSetOnCreation() 284 { 285 $someDate = 'Thu, 23 Mar 2017 09:15:12 GMT'; 286 $bag = new ResponseHeaderBag(['Date' => $someDate]); 287 288 $this->assertEquals($someDate, $bag->get('Date')); 289 } 290 291 public function testDateHeaderWillBeRecreatedWhenRemoved() 292 { 293 $someDate = 'Thu, 23 Mar 2017 09:15:12 GMT'; 294 $bag = new ResponseHeaderBag(['Date' => $someDate]); 295 $bag->remove('Date'); 296 297 // a (new) Date header is still present 298 $this->assertTrue($bag->has('Date')); 299 $this->assertNotEquals($someDate, $bag->get('Date')); 300 } 301 302 public function testDateHeaderWillBeRecreatedWhenHeadersAreReplaced() 303 { 304 $bag = new ResponseHeaderBag(); 305 $bag->replace([]); 306 307 $this->assertTrue($bag->has('Date')); 308 } 309 310 private function assertSetCookieHeader(string $expected, ResponseHeaderBag $actual) 311 { 312 $this->assertRegExp('#^Set-Cookie:\s+'.preg_quote($expected, '#').'$#m', str_replace("\r\n", "\n", (string) $actual)); 313 } 314} 315