From dc929ad237ee718a1a47b142376631e1e951cf43 Mon Sep 17 00:00:00 2001 From: Geoffrey Hoffman Date: Wed, 7 Aug 2024 13:07:11 -0700 Subject: [PATCH 1/3] Issue #35 Add 'Partitioned' value for PHP Cookies --- src/Cookie.php | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/src/Cookie.php b/src/Cookie.php index 52d3256..5958576 100644 --- a/src/Cookie.php +++ b/src/Cookie.php @@ -31,6 +31,7 @@ final class Cookie { const SAME_SITE_RESTRICTION_NONE = 'None'; const SAME_SITE_RESTRICTION_LAX = 'Lax'; const SAME_SITE_RESTRICTION_STRICT = 'Strict'; + const PARTITIONED = 'Partitioned'; /** @var string the name of the cookie which is also the key for future accesses via `$_COOKIE[...]` */ private $name; @@ -48,6 +49,8 @@ final class Cookie { private $secureOnly; /** @var string|null indicates that the cookie should not be sent along with cross-site requests (either `null`, `None`, `Lax` or `Strict`) */ private $sameSiteRestriction; + /** @var string|null indicates that the cookie should be partitioned to comply with CHIPS see https://developer.mozilla.org/en-US/docs/Web/Privacy/Privacy_sandbox/Partitioned_cookies */ + private $partitioned; /** * Prepares a new cookie @@ -63,6 +66,7 @@ public function __construct($name) { $this->httpOnly = true; $this->secureOnly = false; $this->sameSiteRestriction = self::SAME_SITE_RESTRICTION_LAX; + $this->partitioned = false; } /** @@ -242,6 +246,22 @@ public function setSameSiteRestriction($sameSiteRestriction) { return $this; } + /** + * Anything truthy passed in will set 'Partitioned' on the cookie + * @param false|string|null $partitioned + */ + public function setPartitioned($partitioned) + { + if ($partitioned) { + $partitioned = 'Partitioned'; + } + else { + $partitioned = null; + } + $this->partitioned = $partitioned; + } + + /** * Saves the cookie * @@ -326,9 +346,10 @@ public static function setcookie($name, $value = null, $expiryTime = 0, $path = * @param bool $secureOnly indicates that the cookie should be sent back by the client over secure HTTPS connections only * @param bool $httpOnly indicates that the cookie should be accessible through the HTTP protocol only and not through scripting languages * @param string|null $sameSiteRestriction indicates that the cookie should not be sent along with cross-site requests (either `null`, `None`, `Lax` or `Strict`) + * @param string|null $partitioned indicates that the cookie should be partitioned for CHIPS compatibility. See https://developer.mozilla.org/en-US/docs/Web/Privacy/Privacy_sandbox/Partitioned_cookies`) * @return string the HTTP header */ - public static function buildCookieHeader($name, $value = null, $expiryTime = 0, $path = null, $domain = null, $secureOnly = false, $httpOnly = false, $sameSiteRestriction = null) { + public static function buildCookieHeader($name, $value = null, $expiryTime = 0, $path = null, $domain = null, $secureOnly = false, $httpOnly = false, $sameSiteRestriction = null, $partitioned = null) { if (self::isNameValid($name)) { $name = (string) $name; } @@ -398,6 +419,10 @@ public static function buildCookieHeader($name, $value = null, $expiryTime = 0, $headerStr .= '; SameSite=Strict'; } + if ($partitioned) { + $headerStr .= '; ' . self::PARTITIONED; + } + return $headerStr; } @@ -443,6 +468,9 @@ public static function parse($cookieHeader) { elseif (\stripos($attribute, 'SameSite=') === 0) { $cookie->setSameSiteRestriction(\substr($attribute, 9)); } + elseif (\stripos($attribute, 'Partitioned') === 0) { + $cookie->setPartitioned(true); + } } } From 54f7d474bd2760e0ab20863b1f41b681cdb850cc Mon Sep 17 00:00:00 2001 From: Geoffrey Hoffman Date: Wed, 7 Aug 2024 13:25:01 -0700 Subject: [PATCH 2/3] Issue #35 Added test for Partitioned cookies. I found the tests not working that well (PHP 8.3) however. --- tests/index.php | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/tests/index.php b/tests/index.php index fe672d7..8d95357 100644 --- a/tests/index.php +++ b/tests/index.php @@ -19,14 +19,18 @@ // start output buffering \ob_start(); -\testCookie(null); -\testCookie(false); -\testCookie(''); -\testCookie(0); +try { + \testCookie(null); + \testCookie(false); + \testCookie(''); + \testCookie(0); + \testCookie('hello', null); +} catch (\Throwable $e) { + echo $e->getMessage(); +} \testCookie('hello'); \testCookie('hello', false); \testCookie('hello', true); -\testCookie('hello', null); \testCookie('hello', ''); \testCookie('hello', 0); \testCookie('hello', 1); @@ -118,6 +122,7 @@ \testEqual((new \Delight\Cookie\Cookie('key'))->setValue('value')->setDomain('.www.example.com'), 'Set-Cookie: key=value; path=/; domain=.www.example.com; httponly; SameSite=Lax'); \testEqual((new \Delight\Cookie\Cookie('key'))->setValue('value')->setDomain('blog.example.com'), 'Set-Cookie: key=value; path=/; domain=.blog.example.com; httponly; SameSite=Lax'); \testEqual((new \Delight\Cookie\Cookie('key'))->setValue('value')->setDomain('.blog.example.com'), 'Set-Cookie: key=value; path=/; domain=.blog.example.com; httponly; SameSite=Lax'); +\testEqual((new \Delight\Cookie\Cookie('SID'))->setValue('31d4d96e407aad42')->setSameSiteRestriction('Lax')->setSecureOnly(true)->setPartitioned(true), 'Set-Cookie: SID=31d4d96e407aad42; path=/; secure; httponly; SameSite=Lax; Partitioned'); \testEqual(\Delight\Cookie\Cookie::parse('Set-Cookie: SID'), ''); \testEqual(\Delight\Cookie\Cookie::parse('Set-Cookie: SID=31d4d96e407aad42'), 'Set-Cookie: SID=31d4d96e407aad42'); From 6a89ae87336e056b093836be6b2aa2ac3fe5c4a9 Mon Sep 17 00:00:00 2001 From: Geoffrey Hoffman Date: Wed, 7 Aug 2024 13:29:14 -0700 Subject: [PATCH 3/3] Issue #35 Reference the self const instead of duplicating the string --- src/Cookie.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Cookie.php b/src/Cookie.php index 5958576..27fe513 100644 --- a/src/Cookie.php +++ b/src/Cookie.php @@ -253,7 +253,7 @@ public function setSameSiteRestriction($sameSiteRestriction) { public function setPartitioned($partitioned) { if ($partitioned) { - $partitioned = 'Partitioned'; + $partitioned = self::PARTITIONED; } else { $partitioned = null;