Skip to content

Commit 35ffbbf

Browse files
marienicolas-grekas
authored andcommitted
[HttpFoundation] allow additinal characters in not raw cookies
1 parent ec2a74a commit 35ffbbf

File tree

6 files changed

+44
-16
lines changed

6 files changed

+44
-16
lines changed

Cookie.php

+16-5
Original file line numberDiff line numberDiff line change
@@ -18,19 +18,24 @@
1818
*/
1919
class Cookie
2020
{
21+
const SAMESITE_NONE = 'none';
22+
const SAMESITE_LAX = 'lax';
23+
const SAMESITE_STRICT = 'strict';
24+
2125
protected $name;
2226
protected $value;
2327
protected $domain;
2428
protected $expire;
2529
protected $path;
2630
protected $secure;
2731
protected $httpOnly;
32+
2833
private $raw;
2934
private $sameSite;
3035

31-
const SAMESITE_NONE = 'none';
32-
const SAMESITE_LAX = 'lax';
33-
const SAMESITE_STRICT = 'strict';
36+
private static $reservedCharsList = "=,; \t\r\n\v\f";
37+
private static $reservedCharsFrom = ['=', ',', ';', ' ', "\t", "\r", "\n", "\v", "\f"];
38+
private static $reservedCharsTo = ['%3D', '%2C', '%3B', '%20', '%09', '%0D', '%0A', '%0B', '%0C'];
3439

3540
/**
3641
* Creates cookie from raw header string.
@@ -97,7 +102,7 @@ public static function fromString($cookie, $decode = false)
97102
public function __construct($name, $value = null, $expire = 0, $path = '/', $domain = null, $secure = false, $httpOnly = true, $raw = false, $sameSite = null)
98103
{
99104
// from PHP source code
100-
if (preg_match("/[=,; \t\r\n\013\014]/", $name)) {
105+
if ($raw && false !== strpbrk($name, self::$reservedCharsList)) {
101106
throw new \InvalidArgumentException(sprintf('The cookie name "%s" contains invalid characters.', $name));
102107
}
103108

@@ -143,7 +148,13 @@ public function __construct($name, $value = null, $expire = 0, $path = '/', $dom
143148
*/
144149
public function __toString()
145150
{
146-
$str = ($this->isRaw() ? $this->getName() : urlencode($this->getName())).'=';
151+
if ($this->isRaw()) {
152+
$str = $this->getName();
153+
} else {
154+
$str = str_replace(self::$reservedCharsFrom, self::$reservedCharsTo, $this->getName());
155+
}
156+
157+
$str .= '=';
147158

148159
if ('' === (string) $this->getValue()) {
149160
$str .= 'deleted; expires='.gmdate('D, d-M-Y H:i:s T', time() - 31536001).'; Max-Age=0';

Response.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -342,7 +342,7 @@ public function sendHeaders()
342342

343343
// cookies
344344
foreach ($this->headers->getCookies() as $cookie) {
345-
header('Set-Cookie: '.$cookie->getName().strstr($cookie, '='), false, $this->statusCode);
345+
header('Set-Cookie: '.$cookie, false, $this->statusCode);
346346
}
347347

348348
// status

Tests/CookieTest.php

+18-5
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,9 @@
2424
*/
2525
class CookieTest extends TestCase
2626
{
27-
public function invalidNames()
27+
public function namesWithSpecialCharacters()
2828
{
2929
return [
30-
[''],
3130
[',MyName'],
3231
[';MyName'],
3332
[' MyName'],
@@ -40,12 +39,26 @@ public function invalidNames()
4039
}
4140

4241
/**
43-
* @dataProvider invalidNames
42+
* @dataProvider namesWithSpecialCharacters
4443
*/
45-
public function testInstantiationThrowsExceptionIfCookieNameContainsInvalidCharacters($name)
44+
public function testInstantiationThrowsExceptionIfRawCookieNameContainsSpecialCharacters($name)
4645
{
4746
$this->expectException('InvalidArgumentException');
48-
new Cookie($name);
47+
new Cookie($name, null, 0, null, null, null, false, true);
48+
}
49+
50+
/**
51+
* @dataProvider namesWithSpecialCharacters
52+
*/
53+
public function testInstantiationSucceedNonRawCookieNameContainsSpecialCharacters($name)
54+
{
55+
$this->assertInstanceOf(Cookie::class, new Cookie($name));
56+
}
57+
58+
public function testInstantiationThrowsExceptionIfCookieNameIsEmpty()
59+
{
60+
$this->expectException('InvalidArgumentException');
61+
new Cookie('');
4962
}
5063

5164
public function testInvalidExpiration()

Tests/Fixtures/response-functional/cookie_urlencode.expected

+2-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ Array
44
[0] => Content-Type: text/plain; charset=utf-8
55
[1] => Cache-Control: no-cache, private
66
[2] => Date: Sat, 12 Nov 1955 20:04:00 GMT
7-
[3] => Set-Cookie: ?*():@&+$/%#[]=%3F%2A%28%29%3A%40%26%2B%24%2F%25%23%5B%5D; path=/
7+
[3] => Set-Cookie: %3D%2C%3B%20%09%0D%0A%0B%0C=%3D%2C%3B%20%09%0D%0A%0B%0C; path=/
88
[4] => Set-Cookie: ?*():@&+$/%#[]=%3F%2A%28%29%3A%40%26%2B%24%2F%25%23%5B%5D; path=/
9+
[5] => Set-Cookie: ?*():@&+$/%#[]=%3F%2A%28%29%3A%40%26%2B%24%2F%25%23%5B%5D; path=/
910
)
1011
shutdown

Tests/Fixtures/response-functional/cookie_urlencode.php

+6-3
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,12 @@
44

55
$r = require __DIR__.'/common.inc';
66

7-
$str = '?*():@&+$/%#[]';
7+
$str1 = "=,; \t\r\n\v\f";
8+
$r->headers->setCookie(new Cookie($str1, $str1, 0, '', null, false, false, false, null));
89

9-
$r->headers->setCookie(new Cookie($str, $str, 0, '', null, false, false));
10+
$str2 = '?*():@&+$/%#[]';
11+
12+
$r->headers->setCookie(new Cookie($str2, $str2, 0, '', null, false, false, false, null));
1013
$r->sendHeaders();
1114

12-
setcookie($str, $str, 0, '/');
15+
setcookie($str2, $str2, 0, '/');

Tests/Fixtures/response-functional/invalid_cookie_name.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
$r = require __DIR__.'/common.inc';
66

77
try {
8-
$r->headers->setCookie(new Cookie('Hello + world', 'hodor'));
8+
$r->headers->setCookie(new Cookie('Hello + world', 'hodor', 0, null, null, null, false, true));
99
} catch (\InvalidArgumentException $e) {
1010
echo $e->getMessage();
1111
}

0 commit comments

Comments
 (0)