Skip to content

Commit 26df07b

Browse files
committed
[HttpFoundation] Cookies Having Independent Partitioned State (CHIPS)
1 parent 95fa158 commit 26df07b

File tree

3 files changed

+77
-6
lines changed

3 files changed

+77
-6
lines changed

src/Symfony/Component/HttpFoundation/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ CHANGELOG
77
* Make `HeaderBag::getDate()`, `Response::getDate()`, `getExpires()` and `getLastModified()` return a `DateTimeImmutable`
88
* Support root-level `Generator` in `StreamedJsonResponse`
99
* Add `UriSigner` from the HttpKernel component
10+
* Add `partitioned` flag to `Cookie` (CHIPS Cookie)
1011

1112
6.3
1213
---

src/Symfony/Component/HttpFoundation/Cookie.php

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ class Cookie
3232

3333
private bool $raw;
3434
private ?string $sameSite = null;
35+
private bool $partitioned = false;
3536
private bool $secureDefault = false;
3637

3738
private const RESERVED_CHARS_LIST = "=,; \t\r\n\v\f";
@@ -51,6 +52,7 @@ public static function fromString(string $cookie, bool $decode = false): static
5152
'httponly' => false,
5253
'raw' => !$decode,
5354
'samesite' => null,
55+
'partitioned' => false,
5456
];
5557

5658
$parts = HeaderUtils::split($cookie, ';=');
@@ -66,17 +68,20 @@ public static function fromString(string $cookie, bool $decode = false): static
6668
$data['expires'] = time() + (int) $data['max-age'];
6769
}
6870

69-
return new static($name, $value, $data['expires'], $data['path'], $data['domain'], $data['secure'], $data['httponly'], $data['raw'], $data['samesite']);
71+
return new static($name, $value, $data['expires'], $data['path'], $data['domain'], $data['secure'], $data['httponly'], $data['raw'], $data['samesite'], $data['partitioned']);
7072
}
7173

7274
/**
7375
* @see self::__construct
7476
*
7577
* @param self::SAMESITE_*|''|null $sameSite
78+
* @param bool $partitioned
7679
*/
77-
public static function create(string $name, string $value = null, int|string|\DateTimeInterface $expire = 0, ?string $path = '/', string $domain = null, bool $secure = null, bool $httpOnly = true, bool $raw = false, ?string $sameSite = self::SAMESITE_LAX): self
80+
public static function create(string $name, string $value = null, int|string|\DateTimeInterface $expire = 0, ?string $path = '/', string $domain = null, bool $secure = null, bool $httpOnly = true, bool $raw = false, ?string $sameSite = self::SAMESITE_LAX /* , bool $partitioned = false */): self
7881
{
79-
return new self($name, $value, $expire, $path, $domain, $secure, $httpOnly, $raw, $sameSite);
82+
$partitioned = 9 < \func_num_args() ? func_get_arg(9) : false;
83+
84+
return new self($name, $value, $expire, $path, $domain, $secure, $httpOnly, $raw, $sameSite, $partitioned);
8085
}
8186

8287
/**
@@ -92,7 +97,7 @@ public static function create(string $name, string $value = null, int|string|\Da
9297
*
9398
* @throws \InvalidArgumentException
9499
*/
95-
public function __construct(string $name, string $value = null, int|string|\DateTimeInterface $expire = 0, ?string $path = '/', string $domain = null, bool $secure = null, bool $httpOnly = true, bool $raw = false, ?string $sameSite = self::SAMESITE_LAX)
100+
public function __construct(string $name, string $value = null, int|string|\DateTimeInterface $expire = 0, ?string $path = '/', string $domain = null, bool $secure = null, bool $httpOnly = true, bool $raw = false, ?string $sameSite = self::SAMESITE_LAX, bool $partitioned = false)
96101
{
97102
// from PHP source code
98103
if ($raw && false !== strpbrk($name, self::RESERVED_CHARS_LIST)) {
@@ -112,6 +117,7 @@ public function __construct(string $name, string $value = null, int|string|\Date
112117
$this->httpOnly = $httpOnly;
113118
$this->raw = $raw;
114119
$this->sameSite = $this->withSameSite($sameSite)->sameSite;
120+
$this->partitioned = $partitioned;
115121
}
116122

117123
/**
@@ -237,6 +243,17 @@ public function withSameSite(?string $sameSite): static
237243
return $cookie;
238244
}
239245

246+
/**
247+
* Creates a cookie copy that is tied to the top-level site in cross-site context.
248+
*/
249+
public function withPartitioned(bool $partitioned = true): static
250+
{
251+
$cookie = clone $this;
252+
$cookie->partitioned = $partitioned;
253+
254+
return $cookie;
255+
}
256+
240257
/**
241258
* Returns the cookie as a string.
242259
*/
@@ -268,18 +285,22 @@ public function __toString(): string
268285
$str .= '; domain='.$this->getDomain();
269286
}
270287

271-
if (true === $this->isSecure()) {
288+
if ($this->isSecure()) {
272289
$str .= '; secure';
273290
}
274291

275-
if (true === $this->isHttpOnly()) {
292+
if ($this->isHttpOnly()) {
276293
$str .= '; httponly';
277294
}
278295

279296
if (null !== $this->getSameSite()) {
280297
$str .= '; samesite='.$this->getSameSite();
281298
}
282299

300+
if ($this->isPartitioned()) {
301+
$str .= '; partitioned';
302+
}
303+
283304
return $str;
284305
}
285306

@@ -365,6 +386,14 @@ public function isRaw(): bool
365386
return $this->raw;
366387
}
367388

389+
/**
390+
* Checks whether the cookie should be tied to the top-level site in cross-site context.
391+
*/
392+
public function isPartitioned(): bool
393+
{
394+
return $this->partitioned;
395+
}
396+
368397
/**
369398
* @return self::SAMESITE_*|null
370399
*/

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

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,19 @@ public function testNegativeExpirationIsNotPossible()
8787
$this->assertSame(0, $cookie->getExpiresTime());
8888
}
8989

90+
public function testMinimalParameters()
91+
{
92+
$constructedCookie = new Cookie('foo');
93+
94+
$createdCookie = Cookie::create('foo');
95+
96+
$cookie = new Cookie('foo', null, 0, '/', null, null, true, false, 'lax');
97+
98+
$this->assertEquals($constructedCookie, $cookie);
99+
100+
$this->assertEquals($createdCookie, $cookie);
101+
}
102+
90103
public function testGetValue()
91104
{
92105
$value = 'MyValue';
@@ -187,6 +200,17 @@ public function testIsHttpOnly()
187200
$this->assertTrue($cookie->isHttpOnly(), '->isHttpOnly() returns whether the cookie is only transmitted over HTTP');
188201
}
189202

203+
public function testIsPartitioned()
204+
{
205+
$cookie = new Cookie('foo', 'bar', 0, '/', '.myfoodomain.com', true, true, false, 'Lax', true);
206+
207+
$this->assertTrue($cookie->isPartitioned());
208+
209+
$cookie = Cookie::create('foo')->withPartitioned(true);
210+
211+
$this->assertTrue($cookie->isPartitioned());
212+
}
213+
190214
public function testCookieIsNotCleared()
191215
{
192216
$cookie = Cookie::create('foo', 'bar', time() + 3600 * 24);
@@ -262,6 +286,20 @@ public function testToString()
262286
->withSameSite(null);
263287
$this->assertEquals($expected, (string) $cookie, '->__toString() returns string representation of a cleared cookie if value is NULL');
264288

289+
$expected = 'foo=deleted; expires='.gmdate('D, d M Y H:i:s T', $expire = time() - 31536001).'; Max-Age=0; path=/admin/; domain=.myfoodomain.com; secure; httponly; samesite=none; partitioned';
290+
$cookie = new Cookie('foo', null, 1, '/admin/', '.myfoodomain.com', true, true, false, 'none', true);
291+
$this->assertEquals($expected, (string) $cookie, '->__toString() returns string representation of a cleared cookie if value is NULL');
292+
293+
$cookie = Cookie::create('foo')
294+
->withExpires(1)
295+
->withPath('/admin/')
296+
->withDomain('.myfoodomain.com')
297+
->withSecure(true)
298+
->withHttpOnly(true)
299+
->withSameSite('none')
300+
->withPartitioned(true);
301+
$this->assertEquals($expected, (string) $cookie, '->__toString() returns string representation of a cleared cookie if value is NULL');
302+
265303
$expected = 'foo=bar; path=/; httponly; samesite=lax';
266304
$cookie = Cookie::create('foo', 'bar');
267305
$this->assertEquals($expected, (string) $cookie);
@@ -321,6 +359,9 @@ public function testFromString()
321359

322360
$cookie = Cookie::fromString('foo_cookie=foo==; expires=Tue, 22 Sep 2020 06:27:09 GMT; path=/');
323361
$this->assertEquals(Cookie::create('foo_cookie', 'foo==', strtotime('Tue, 22 Sep 2020 06:27:09 GMT'), '/', null, false, false, true, null), $cookie);
362+
363+
$cookie = Cookie::fromString('foo_cookie=foo==; expires=Tue, 22 Sep 2020 06:27:09 GMT; path=/; secure; httponly; samesite=none; partitioned');
364+
$this->assertEquals(new Cookie('foo_cookie', 'foo==', strtotime('Tue, 22 Sep 2020 06:27:09 GMT'), '/', null, true, true, true, 'none', true), $cookie);
324365
}
325366

326367
public function testFromStringWithHttpOnly()

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy