Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 7314456

Browse filesBrowse files
committed
[HttpFoundation] Create cookie from string + synchronize response cookies
1 parent d614193 commit 7314456
Copy full SHA for 7314456

File tree

8 files changed

+173
-53
lines changed
Filter options

8 files changed

+173
-53
lines changed

‎src/Symfony/Component/HttpFoundation/Cookie.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/HttpFoundation/Cookie.php
+51-2Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,14 +31,63 @@ class Cookie
3131
const SAMESITE_LAX = 'lax';
3232
const SAMESITE_STRICT = 'strict';
3333

34+
/**
35+
* Creates cookie from raw header string.
36+
*
37+
* @param string $cookie
38+
* @param bool $decode
39+
*
40+
* @return static
41+
*/
42+
public static function fromString($cookie, $decode = false)
43+
{
44+
$data = array(
45+
'expires' => 0,
46+
'path' => '/',
47+
'domain' => null,
48+
'secure' => false,
49+
'httponly' => true,
50+
'raw' => !$decode,
51+
'samesite' => null,
52+
);
53+
foreach (explode(';', $cookie) as $part) {
54+
if (false === strpos($part, '=')) {
55+
$key = trim($part);
56+
$value = true;
57+
} else {
58+
list($key, $value) = explode('=', trim($part), 2);
59+
$key = trim($key);
60+
$value = trim($value);
61+
}
62+
if (!isset($data['name'])) {
63+
$data['name'] = $decode ? urldecode($key) : $key;
64+
$data['value'] = true === $value ? null : ($decode ? urldecode($value) : $value);
65+
continue;
66+
}
67+
switch ($key = strtolower($key)) {
68+
case 'name':
69+
case 'value':
70+
break;
71+
case 'max-age':
72+
$data['expires'] = time() + (int) $value;
73+
break;
74+
default:
75+
$data[$key] = $value;
76+
break;
77+
}
78+
}
79+
80+
return new static($data['name'], $data['value'], $data['expires'], $data['path'], $data['domain'], $data['secure'], $data['httponly'], $data['raw'], $data['samesite']);
81+
}
82+
3483
/**
3584
* Constructor.
3685
*
3786
* @param string $name The name of the cookie
38-
* @param string $value The value of the cookie
87+
* @param string|null $value The value of the cookie
3988
* @param int|string|\DateTimeInterface $expire The time the cookie expires
4089
* @param string $path The path on the server in which the cookie will be available on
41-
* @param string $domain The domain that the cookie is available to
90+
* @param string|null $domain The domain that the cookie is available to
4291
* @param bool $secure Whether the cookie should only be transmitted over a secure HTTPS connection from the client
4392
* @param bool $httpOnly Whether the cookie will be made accessible only through the HTTP protocol
4493
* @param bool $raw Whether the cookie value should be sent with no url encoding

‎src/Symfony/Component/HttpFoundation/HeaderBag.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/HttpFoundation/HeaderBag.php
+10-9Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -40,14 +40,14 @@ public function __construct(array $headers = array())
4040
*/
4141
public function __toString()
4242
{
43-
if (!$this->headers) {
43+
if (!$headers = $this->all()) {
4444
return '';
4545
}
4646

47-
$max = max(array_map('strlen', array_keys($this->headers))) + 1;
47+
ksort($headers);
48+
$max = max(array_map('strlen', array_keys($headers))) + 1;
4849
$content = '';
49-
ksort($this->headers);
50-
foreach ($this->headers as $name => $values) {
50+
foreach ($headers as $name => $values) {
5151
$name = implode('-', array_map('ucfirst', explode('-', $name)));
5252
foreach ($values as $value) {
5353
$content .= sprintf("%-{$max}s %s\r\n", $name.':', $value);
@@ -74,7 +74,7 @@ public function all()
7474
*/
7575
public function keys()
7676
{
77-
return array_keys($this->headers);
77+
return array_keys($this->all());
7878
}
7979

8080
/**
@@ -112,8 +112,9 @@ public function add(array $headers)
112112
public function get($key, $default = null, $first = true)
113113
{
114114
$key = str_replace('_', '-', strtolower($key));
115+
$headers = $this->all();
115116

116-
if (!array_key_exists($key, $this->headers)) {
117+
if (!array_key_exists($key, $headers)) {
117118
if (null === $default) {
118119
return $first ? null : array();
119120
}
@@ -122,10 +123,10 @@ public function get($key, $default = null, $first = true)
122123
}
123124

124125
if ($first) {
125-
return count($this->headers[$key]) ? $this->headers[$key][0] : $default;
126+
return count($headers[$key]) ? $headers[$key][0] : $default;
126127
}
127128

128-
return $this->headers[$key];
129+
return $headers[$key];
129130
}
130131

131132
/**
@@ -161,7 +162,7 @@ public function set($key, $values, $replace = true)
161162
*/
162163
public function has($key)
163164
{
164-
return array_key_exists(str_replace('_', '-', strtolower($key)), $this->headers);
165+
return array_key_exists(str_replace('_', '-', strtolower($key)), $this->all());
165166
}
166167

167168
/**

‎src/Symfony/Component/HttpFoundation/Response.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/HttpFoundation/Response.php
+1-1Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -375,7 +375,7 @@ public function sendHeaders()
375375
}
376376

377377
// headers
378-
foreach ($this->headers->allPreserveCase() as $name => $values) {
378+
foreach ($this->headers->allPreserveCaseWithoutCookies() as $name => $values) {
379379
foreach ($values as $value) {
380380
header($name.': '.$value, false, $this->statusCode);
381381
}

‎src/Symfony/Component/HttpFoundation/ResponseHeaderBag.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/HttpFoundation/ResponseHeaderBag.php
+56-19Lines changed: 56 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -54,28 +54,28 @@ public function __construct(array $headers = array())
5454
}
5555

5656
/**
57-
* {@inheritdoc}
57+
* Returns the headers, with original capitalizations.
58+
*
59+
* @return array An array of headers
5860
*/
59-
public function __toString()
61+
public function allPreserveCase()
6062
{
61-
$cookies = '';
62-
foreach ($this->getCookies() as $cookie) {
63-
$cookies .= 'Set-Cookie: '.$cookie."\r\n";
63+
$headers = array();
64+
foreach ($this->all() as $name => $value) {
65+
$headers[isset($this->headerNames[$name]) ? $this->headerNames[$name] : $name] = $value;
6466
}
6567

66-
ksort($this->headerNames);
67-
68-
return parent::__toString().$cookies;
68+
return $headers;
6969
}
7070

71-
/**
72-
* Returns the headers, with original capitalizations.
73-
*
74-
* @return array An array of headers
75-
*/
76-
public function allPreserveCase()
71+
public function allPreserveCaseWithoutCookies()
7772
{
78-
return array_combine($this->headerNames, $this->headers);
73+
$headers = $this->allPreserveCase();
74+
if (isset($this->headerNames['set-cookie'])) {
75+
unset($headers[$this->headerNames['set-cookie']]);
76+
}
77+
78+
return $headers;
7979
}
8080

8181
/**
@@ -95,13 +95,39 @@ public function replace(array $headers = array())
9595
/**
9696
* {@inheritdoc}
9797
*/
98-
public function set($key, $values, $replace = true)
98+
public function all()
9999
{
100-
parent::set($key, $values, $replace);
100+
$headers = parent::all();
101+
foreach ($this->getCookies() as $cookie) {
102+
$headers['set-cookie'][] = (string) $cookie;
103+
}
101104

105+
return $headers;
106+
}
107+
108+
/**
109+
* {@inheritdoc}
110+
*/
111+
public function set($key, $values, $replace = true)
112+
{
102113
$uniqueKey = str_replace('_', '-', strtolower($key));
114+
115+
if ('set-cookie' === $uniqueKey) {
116+
if ($replace) {
117+
$this->cookies = array();
118+
}
119+
foreach ((array) $values as $cookie) {
120+
$this->setCookie(Cookie::fromString($cookie));
121+
}
122+
$this->headerNames[$uniqueKey] = $key;
123+
124+
return;
125+
}
126+
103127
$this->headerNames[$uniqueKey] = $key;
104128

129+
parent::set($key, $values, $replace);
130+
105131
// ensure the cache-control header has sensible defaults
106132
if (in_array($uniqueKey, array('cache-control', 'etag', 'last-modified', 'expires'))) {
107133
$computed = $this->computeCacheControlValue();
@@ -116,11 +142,17 @@ public function set($key, $values, $replace = true)
116142
*/
117143
public function remove($key)
118144
{
119-
parent::remove($key);
120-
121145
$uniqueKey = str_replace('_', '-', strtolower($key));
122146
unset($this->headerNames[$uniqueKey]);
123147

148+
if ('set-cookie' === $uniqueKey) {
149+
$this->cookies = array();
150+
151+
return;
152+
}
153+
154+
parent::remove($key);
155+
124156
if ('cache-control' === $uniqueKey) {
125157
$this->computedCacheControl = array();
126158
}
@@ -150,6 +182,7 @@ public function getCacheControlDirective($key)
150182
public function setCookie(Cookie $cookie)
151183
{
152184
$this->cookies[$cookie->getDomain()][$cookie->getPath()][$cookie->getName()] = $cookie;
185+
$this->headerNames['set-cookie'] = 'Set-Cookie';
153186
}
154187

155188
/**
@@ -174,6 +207,10 @@ public function removeCookie($name, $path = '/', $domain = null)
174207
unset($this->cookies[$domain]);
175208
}
176209
}
210+
211+
if (empty($this->cookies)) {
212+
unset($this->headerNames['set-cookie']);
213+
}
177214
}
178215

179216
/**

‎src/Symfony/Component/HttpFoundation/Tests/CookieTest.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/HttpFoundation/Tests/CookieTest.php
+9Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,4 +178,13 @@ public function testGetMaxAge()
178178
$cookie = new Cookie('foo', 'bar', $expire = time() - 100);
179179
$this->assertEquals($expire - time(), $cookie->getMaxAge());
180180
}
181+
182+
public function testFromString()
183+
{
184+
$cookie = Cookie::fromString('foo=bar; expires=Fri, 20-May-2011 15:25:52 GMT; path=/; domain=.myfoodomain.com; secure; httponly');
185+
$this->assertEquals(new Cookie('foo', 'bar', strtotime('Fri, 20-May-2011 15:25:52 GMT'), '/', '.myfoodomain.com', true, true, true), $cookie);
186+
187+
$cookie = Cookie::fromString('foo=bar', true);
188+
$this->assertEquals(new Cookie('foo', 'bar'), $cookie);
189+
}
181190
}

‎src/Symfony/Component/HttpFoundation/Tests/ResponseHeaderBagTest.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/HttpFoundation/Tests/ResponseHeaderBagTest.php
+43-10Lines changed: 43 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -124,11 +124,11 @@ public function testToStringIncludesCookieHeaders()
124124
$bag = new ResponseHeaderBag(array());
125125
$bag->setCookie(new Cookie('foo', 'bar'));
126126

127-
$this->assertContains('Set-Cookie: foo=bar; path=/; httponly', explode("\r\n", $bag->__toString()));
127+
$this->assertSetCookieHeader('foo=bar; path=/; httponly', $bag);
128128

129129
$bag->clearCookie('foo');
130130

131-
$this->assertRegExp('#^Set-Cookie: foo=deleted; expires='.gmdate('D, d-M-Y H:i:s T', time() - 31536001).'; max-age=-31536001; path=/; httponly#m', $bag->__toString());
131+
$this->assertSetCookieHeader('foo=deleted; expires='.gmdate('D, d-M-Y H:i:s T', time() - 31536001).'; max-age=-31536001; path=/; httponly', $bag);
132132
}
133133

134134
public function testClearCookieSecureNotHttpOnly()
@@ -137,7 +137,7 @@ public function testClearCookieSecureNotHttpOnly()
137137

138138
$bag->clearCookie('foo', '/', null, true, false);
139139

140-
$this->assertRegExp('#^Set-Cookie: foo=deleted; expires='.gmdate('D, d-M-Y H:i:s T', time() - 31536001).'; max-age=-31536001; path=/; secure#m', $bag->__toString());
140+
$this->assertSetCookieHeader('foo=deleted; expires='.gmdate('D, d-M-Y H:i:s T', time() - 31536001).'; max-age=-31536001; path=/; secure', $bag);
141141
}
142142

143143
public function testReplace()
@@ -172,14 +172,21 @@ public function testCookiesWithSameNames()
172172
$bag->setCookie(new Cookie('foo', 'bar'));
173173

174174
$this->assertCount(4, $bag->getCookies());
175-
176-
$headers = explode("\r\n", $bag->__toString());
177-
$this->assertContains('Set-Cookie: foo=bar; path=/path/foo; domain=foo.bar; httponly', $headers);
178-
$this->assertContains('Set-Cookie: foo=bar; path=/path/foo; domain=foo.bar; httponly', $headers);
179-
$this->assertContains('Set-Cookie: foo=bar; path=/path/bar; domain=bar.foo; httponly', $headers);
180-
$this->assertContains('Set-Cookie: foo=bar; path=/; httponly', $headers);
175+
$this->assertEquals('foo=bar; path=/path/foo; domain=foo.bar; httponly', $bag->get('set-cookie'));
176+
$this->assertEquals(array(
177+
'foo=bar; path=/path/foo; domain=foo.bar; httponly',
178+
'foo=bar; path=/path/bar; domain=foo.bar; httponly',
179+
'foo=bar; path=/path/bar; domain=bar.foo; httponly',
180+
'foo=bar; path=/; httponly',
181+
), $bag->get('set-cookie', null, false));
182+
183+
$this->assertSetCookieHeader('foo=bar; path=/path/foo; domain=foo.bar; httponly', $bag);
184+
$this->assertSetCookieHeader('foo=bar; path=/path/bar; domain=foo.bar; httponly', $bag);
185+
$this->assertSetCookieHeader('foo=bar; path=/path/bar; domain=bar.foo; httponly', $bag);
186+
$this->assertSetCookieHeader('foo=bar; path=/; httponly', $bag);
181187

182188
$cookies = $bag->getCookies(ResponseHeaderBag::COOKIES_ARRAY);
189+
183190
$this->assertTrue(isset($cookies['foo.bar']['/path/foo']['foo']));
184191
$this->assertTrue(isset($cookies['foo.bar']['/path/bar']['foo']));
185192
$this->assertTrue(isset($cookies['bar.foo']['/path/bar']['foo']));
@@ -189,18 +196,23 @@ public function testCookiesWithSameNames()
189196
public function testRemoveCookie()
190197
{
191198
$bag = new ResponseHeaderBag();
199+
$this->assertFalse($bag->has('set-cookie'));
200+
192201
$bag->setCookie(new Cookie('foo', 'bar', 0, '/path/foo', 'foo.bar'));
193202
$bag->setCookie(new Cookie('bar', 'foo', 0, '/path/bar', 'foo.bar'));
203+
$this->assertTrue($bag->has('set-cookie'));
194204

195205
$cookies = $bag->getCookies(ResponseHeaderBag::COOKIES_ARRAY);
196206
$this->assertTrue(isset($cookies['foo.bar']['/path/foo']));
197207

198208
$bag->removeCookie('foo', '/path/foo', 'foo.bar');
209+
$this->assertTrue($bag->has('set-cookie'));
199210

200211
$cookies = $bag->getCookies(ResponseHeaderBag::COOKIES_ARRAY);
201212
$this->assertFalse(isset($cookies['foo.bar']['/path/foo']));
202213

203214
$bag->removeCookie('bar', '/path/bar', 'foo.bar');
215+
$this->assertFalse($bag->has('set-cookie'));
204216

205217
$cookies = $bag->getCookies(ResponseHeaderBag::COOKIES_ARRAY);
206218
$this->assertFalse(isset($cookies['foo.bar']));
@@ -224,14 +236,30 @@ public function testRemoveCookieWithNullRemove()
224236
$this->assertFalse(isset($cookies['']['/']['bar']));
225237
}
226238

239+
public function testSetCookieHeader()
240+
{
241+
$bag = new ResponseHeaderBag();
242+
$bag->set('set-cookie', 'foo=bar');
243+
$this->assertEquals(array(new Cookie('foo', 'bar', 0, '/', null, false, true, true)), $bag->getCookies());
244+
245+
$bag->set('set-cookie', 'foo2=bar2', false);
246+
$this->assertEquals(array(
247+
new Cookie('foo', 'bar', 0, '/', null, false, true, true),
248+
new Cookie('foo2', 'bar2', 0, '/', null, false, true, true),
249+
), $bag->getCookies());
250+
251+
$bag->remove('set-cookie');
252+
$this->assertEquals(array(), $bag->getCookies());
253+
}
254+
227255
/**
228256
* @expectedException \InvalidArgumentException
229257
*/
230258
public function testGetCookiesWithInvalidArgument()
231259
{
232260
$bag = new ResponseHeaderBag();
233261

234-
$cookies = $bag->getCookies('invalid_argument');
262+
$bag->getCookies('invalid_argument');
235263
}
236264

237265
/**
@@ -302,4 +330,9 @@ public function provideMakeDispositionFail()
302330
array('attachment', 'föö.html'),
303331
);
304332
}
333+
334+
protected function assertSetCookieHeader($expected, ResponseHeaderBag $actual)
335+
{
336+
$this->assertRegExp('#^Set-Cookie:\s+'.preg_quote($expected, '#').'$#m', str_replace("\r\n", "\n", (string) $actual));
337+
}
305338
}

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.