bug #33646 [HttpFoundation] allow additinal characters in not raw cookies (marie)

This PR was merged into the 3.4 branch.

Discussion
----------

[HttpFoundation] allow additinal characters in not raw cookies

| Q             | A
| ------------- | ---
| Branch?       | 4.3
| Bug fix?      | yes
| Tickets       | Fix #33619
| License     | MIT

> **Description**
> When creating a non-raw Cookie, it is impossible to provide a name that contains characters matching the regex class [=,; \t\r\n\013\014]

**Solution**: Check with this regex only raw cookie

Commits
-------

4db1402770 [HttpFoundation] allow additinal characters in not raw cookies
This commit is contained in:
Nicolas Grekas 2019-09-28 17:10:09 +02:00
commit ca253960d8
6 changed files with 44 additions and 16 deletions

View File

@ -18,6 +18,10 @@ namespace Symfony\Component\HttpFoundation;
*/ */
class Cookie class Cookie
{ {
const SAMESITE_NONE = 'none';
const SAMESITE_LAX = 'lax';
const SAMESITE_STRICT = 'strict';
protected $name; protected $name;
protected $value; protected $value;
protected $domain; protected $domain;
@ -25,12 +29,13 @@ class Cookie
protected $path; protected $path;
protected $secure; protected $secure;
protected $httpOnly; protected $httpOnly;
private $raw; private $raw;
private $sameSite; private $sameSite;
const SAMESITE_NONE = 'none'; private static $reservedCharsList = "=,; \t\r\n\v\f";
const SAMESITE_LAX = 'lax'; private static $reservedCharsFrom = ['=', ',', ';', ' ', "\t", "\r", "\n", "\v", "\f"];
const SAMESITE_STRICT = 'strict'; private static $reservedCharsTo = ['%3D', '%2C', '%3B', '%20', '%09', '%0D', '%0A', '%0B', '%0C'];
/** /**
* Creates cookie from raw header string. * Creates cookie from raw header string.
@ -97,7 +102,7 @@ class Cookie
public function __construct($name, $value = null, $expire = 0, $path = '/', $domain = null, $secure = false, $httpOnly = true, $raw = false, $sameSite = null) public function __construct($name, $value = null, $expire = 0, $path = '/', $domain = null, $secure = false, $httpOnly = true, $raw = false, $sameSite = null)
{ {
// from PHP source code // from PHP source code
if (preg_match("/[=,; \t\r\n\013\014]/", $name)) { if ($raw && false !== strpbrk($name, self::$reservedCharsList)) {
throw new \InvalidArgumentException(sprintf('The cookie name "%s" contains invalid characters.', $name)); throw new \InvalidArgumentException(sprintf('The cookie name "%s" contains invalid characters.', $name));
} }
@ -143,7 +148,13 @@ class Cookie
*/ */
public function __toString() public function __toString()
{ {
$str = ($this->isRaw() ? $this->getName() : urlencode($this->getName())).'='; if ($this->isRaw()) {
$str = $this->getName();
} else {
$str = str_replace(self::$reservedCharsFrom, self::$reservedCharsTo, $this->getName());
}
$str .= '=';
if ('' === (string) $this->getValue()) { if ('' === (string) $this->getValue()) {
$str .= 'deleted; expires='.gmdate('D, d-M-Y H:i:s T', time() - 31536001).'; Max-Age=0'; $str .= 'deleted; expires='.gmdate('D, d-M-Y H:i:s T', time() - 31536001).'; Max-Age=0';

View File

@ -342,7 +342,7 @@ class Response
// cookies // cookies
foreach ($this->headers->getCookies() as $cookie) { foreach ($this->headers->getCookies() as $cookie) {
header('Set-Cookie: '.$cookie->getName().strstr($cookie, '='), false, $this->statusCode); header('Set-Cookie: '.$cookie, false, $this->statusCode);
} }
// status // status

View File

@ -24,10 +24,9 @@ use Symfony\Component\HttpFoundation\Cookie;
*/ */
class CookieTest extends TestCase class CookieTest extends TestCase
{ {
public function invalidNames() public function namesWithSpecialCharacters()
{ {
return [ return [
[''],
[',MyName'], [',MyName'],
[';MyName'], [';MyName'],
[' MyName'], [' MyName'],
@ -40,12 +39,26 @@ class CookieTest extends TestCase
} }
/** /**
* @dataProvider invalidNames * @dataProvider namesWithSpecialCharacters
*/ */
public function testInstantiationThrowsExceptionIfCookieNameContainsInvalidCharacters($name) public function testInstantiationThrowsExceptionIfRawCookieNameContainsSpecialCharacters($name)
{ {
$this->expectException('InvalidArgumentException'); $this->expectException('InvalidArgumentException');
new Cookie($name); new Cookie($name, null, 0, null, null, null, false, true);
}
/**
* @dataProvider namesWithSpecialCharacters
*/
public function testInstantiationSucceedNonRawCookieNameContainsSpecialCharacters($name)
{
$this->assertInstanceOf(Cookie::class, new Cookie($name));
}
public function testInstantiationThrowsExceptionIfCookieNameIsEmpty()
{
$this->expectException('InvalidArgumentException');
new Cookie('');
} }
public function testInvalidExpiration() public function testInvalidExpiration()

View File

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

View File

@ -4,9 +4,12 @@ use Symfony\Component\HttpFoundation\Cookie;
$r = require __DIR__.'/common.inc'; $r = require __DIR__.'/common.inc';
$str = '?*():@&+$/%#[]'; $str1 = "=,; \t\r\n\v\f";
$r->headers->setCookie(new Cookie($str1, $str1, 0, '', null, false, false, false, null));
$r->headers->setCookie(new Cookie($str, $str, 0, '', null, false, false)); $str2 = '?*():@&+$/%#[]';
$r->headers->setCookie(new Cookie($str2, $str2, 0, '', null, false, false, false, null));
$r->sendHeaders(); $r->sendHeaders();
setcookie($str, $str, 0, '/'); setcookie($str2, $str2, 0, '/');

View File

@ -5,7 +5,7 @@ use Symfony\Component\HttpFoundation\Cookie;
$r = require __DIR__.'/common.inc'; $r = require __DIR__.'/common.inc';
try { try {
$r->headers->setCookie(new Cookie('Hello + world', 'hodor')); $r->headers->setCookie(new Cookie('Hello + world', 'hodor', 0, null, null, null, false, true));
} catch (\InvalidArgumentException $e) { } catch (\InvalidArgumentException $e) {
echo $e->getMessage(); echo $e->getMessage();
} }