diff --git a/README.md b/README.md index af92fa7..3d16db7 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,4 @@ -FIG Cookies -=========== +# FIG Cookies Managing Cookies for PSR-7 Requests and Responses. @@ -15,20 +14,16 @@ Managing Cookies for PSR-7 Requests and Responses.
[![Join the chat at https://gitter.im/dflydev/dflydev-fig-cookies](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/dflydev/dflydev-fig-cookies?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) - -Installation ------------- +## Installation ```bash $> composer require dflydev/fig-cookies ``` +## Concepts -Concepts --------- - -FIG Cookies tackles two problems, managing **Cookie** *Request* headers and -managing **Set-Cookie** *Response* headers. It does this by way of introducing +FIG Cookies tackles two problems, managing **Cookie** _Request_ headers and +managing **Set-Cookie** _Response_ headers. It does this by way of introducing a `Cookies` class to manage collections of `Cookie` instances and a `SetCookies` class to manage collections of `SetCookie` instances. @@ -66,9 +61,7 @@ verbose very quickly. In order to get around that, FIG Cookies provides two facades in an attempt to help simplify things and make the whole process less verbose. - -Basic Usage ------------ +## Basic Usage The easiest way to start working with FIG Cookies is by using the `FigRequestCookies` and `FigResponseCookies` classes. They are facades to the @@ -81,7 +74,6 @@ process so be wary of using too many of these calls in the same section of code. In some cases it may be better to work with the primitive FIG Cookies classes directly rather than using the facades. - ### Request Cookies Requests include cookie information in the **Cookie** request header. The @@ -180,6 +172,7 @@ $setCookie = SetCookie::create('lu') ->withSecure(true) ->withHttpOnly(true) ->withSameSite(SameSite::lax()) + ->withPartitioned() ; ``` @@ -279,9 +272,7 @@ $setCookie = SetCookie::create('ba') FigResponseCookies::set($response, $setCookie->expire()); ``` - -FAQ ---- +## FAQ ### Do you call `setcookies`? @@ -290,7 +281,6 @@ No. Delivery of the rendered `SetCookie` instances is the responsibility of the PSR-7 client implementation. - ### Do you do anything with sessions? No. @@ -298,7 +288,6 @@ No. It would be possible to build session handling using cookies on top of FIG Cookies but it is out of scope for this package. - ### Do you read from `$_COOKIES`? No. @@ -310,18 +299,14 @@ implementations should be including `$_COOKIES` values in the headers so in that case FIG Cookies may be interacting with `$_COOKIES` indirectly. - -License -------- +## License MIT, see LICENSE. - -Community ---------- +## Community Want to get involved? Here are a few ways: - * Find us in the #dflydev IRC channel on irc.freenode.org. - * Mention [@dflydev](https://twitter.com/dflydev) on Twitter. - * [![Join the chat at https://gitter.im/dflydev/dflydev-fig-cookies](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/dflydev/dflydev-fig-cookies?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) +- Find us in the #dflydev IRC channel on irc.freenode.org. +- Mention [@dflydev](https://twitter.com/dflydev) on Twitter. +- [![Join the chat at https://gitter.im/dflydev/dflydev-fig-cookies](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/dflydev/dflydev-fig-cookies?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) diff --git a/src/Dflydev/FigCookies/SetCookie.php b/src/Dflydev/FigCookies/SetCookie.php index 23283a7..2a13816 100644 --- a/src/Dflydev/FigCookies/SetCookie.php +++ b/src/Dflydev/FigCookies/SetCookie.php @@ -42,6 +42,8 @@ class SetCookie private $httpOnly = false; /** @var SameSite|null */ private $sameSite; + /** @var bool */ + private $partitioned = false; private function __construct(string $name, ?string $value = null) { @@ -94,6 +96,11 @@ public function getSameSite(): ?SameSite return $this->sameSite; } + public function getPartitioned(): bool + { + return $this->partitioned; + } + public function withValue(?string $value = null): self { $clone = clone $this; @@ -212,6 +219,18 @@ public function withoutSameSite(): self return $clone; } + public function withPartitioned(bool $partitioned = true): self + { + $clone = clone $this; + + $clone->partitioned = $partitioned; + if ($partitioned) { + $clone->secure = true; + } + + return $clone; + } + public function __toString(): string { $cookieStringParts = [ @@ -225,6 +244,7 @@ public function __toString(): string $cookieStringParts = $this->appendFormattedSecurePartIfSet($cookieStringParts); $cookieStringParts = $this->appendFormattedHttpOnlyPartIfSet($cookieStringParts); $cookieStringParts = $this->appendFormattedSameSitePartIfSet($cookieStringParts); + $cookieStringParts = $this->appendFormattedPartitionedPartIfSet($cookieStringParts); return implode('; ', $cookieStringParts); } @@ -301,6 +321,9 @@ public static function fromSetCookieString(string $string): self case 'samesite': $setCookie = $setCookie->withSameSite(SameSite::fromString((string) $attributeValue)); break; + case 'partitioned': + $setCookie = $setCookie->withPartitioned(); + break; } } @@ -406,4 +429,18 @@ private function appendFormattedSameSitePartIfSet(array $cookieStringParts): arr return $cookieStringParts; } + + /** + * @param string[] $cookieStringParts + * + * @return string[] + */ + private function appendFormattedPartitionedPartIfSet(array $cookieStringParts): array + { + if ($this->partitioned) { + $cookieStringParts[] = 'Partitioned'; + } + + return $cookieStringParts; + } } diff --git a/tests/Dflydev/FigCookies/SetCookieTest.php b/tests/Dflydev/FigCookies/SetCookieTest.php index aa0f482..e19e1ea 100644 --- a/tests/Dflydev/FigCookies/SetCookieTest.php +++ b/tests/Dflydev/FigCookies/SetCookieTest.php @@ -110,37 +110,50 @@ public function provideParsesFromSetCookieStringData(): array [ 'lu=Rg3vHJZnehYLjVg7qi3bZjzg; Domain=.example.com; Path=/; Expires=Tue, 15 Jan 2013 21:47:38 GMT; Max-Age=500; Secure; HttpOnly', SetCookie::create('lu') - ->withValue('Rg3vHJZnehYLjVg7qi3bZjzg') - ->withExpires(new DateTime('Tue, 15-Jan-2013 21:47:38 GMT')) - ->withMaxAge(500) - ->withPath('/') - ->withDomain('.example.com') - ->withSecure(true) - ->withHttpOnly(true), + ->withValue('Rg3vHJZnehYLjVg7qi3bZjzg') + ->withExpires(new DateTime('Tue, 15-Jan-2013 21:47:38 GMT')) + ->withMaxAge(500) + ->withPath('/') + ->withDomain('.example.com') + ->withSecure(true) + ->withHttpOnly(true), ], [ 'lu=Rg3vHJZnehYLjVg7qi3bZjzg; Domain=.example.com; Path=/; Expires=Tue, 15 Jan 2013 21:47:38 GMT; Max-Age=500; Secure; HttpOnly; SameSite=Strict', SetCookie::create('lu') - ->withValue('Rg3vHJZnehYLjVg7qi3bZjzg') - ->withExpires(new DateTime('Tue, 15-Jan-2013 21:47:38 GMT')) - ->withMaxAge(500) - ->withPath('/') - ->withDomain('.example.com') - ->withSecure(true) - ->withHttpOnly(true) - ->withSameSite(SameSite::strict()), + ->withValue('Rg3vHJZnehYLjVg7qi3bZjzg') + ->withExpires(new DateTime('Tue, 15-Jan-2013 21:47:38 GMT')) + ->withMaxAge(500) + ->withPath('/') + ->withDomain('.example.com') + ->withSecure(true) + ->withHttpOnly(true) + ->withSameSite(SameSite::strict()), ], [ 'lu=Rg3vHJZnehYLjVg7qi3bZjzg; Domain=.example.com; Path=/; Expires=Tue, 15 Jan 2013 21:47:38 GMT; Max-Age=500; Secure; HttpOnly; SameSite=Lax', SetCookie::create('lu') - ->withValue('Rg3vHJZnehYLjVg7qi3bZjzg') - ->withExpires(new DateTime('Tue, 15-Jan-2013 21:47:38 GMT')) - ->withMaxAge(500) - ->withPath('/') - ->withDomain('.example.com') - ->withSecure(true) - ->withHttpOnly(true) - ->withSameSite(SameSite::lax()), + ->withValue('Rg3vHJZnehYLjVg7qi3bZjzg') + ->withExpires(new DateTime('Tue, 15-Jan-2013 21:47:38 GMT')) + ->withMaxAge(500) + ->withPath('/') + ->withDomain('.example.com') + ->withSecure(true) + ->withHttpOnly(true) + ->withSameSite(SameSite::lax()), + ], + [ + 'lu=d2ioU4.4KDUjjnCGNut4; Domain=.example.com; Path=/; Expires=Tue, 15 Jan 2013 21:47:38 GMT; Max-Age=500; Secure; HttpOnly; SameSite=Lax; Partitioned', + SetCookie::create('lu') + ->withValue('d2ioU4.4KDUjjnCGNut4') + ->withExpires(new DateTime('Tue, 15-Jan-2013 21:47:38 GMT')) + ->withMaxAge(500) + ->withPath('/') + ->withDomain('.example.com') + ->withSecure(true) + ->withHttpOnly(true) + ->withSameSite(SameSite::lax()) + ->withPartitioned(), ], ]; } @@ -171,6 +184,30 @@ public function it_creates_long_living_cookies(): void self::assertGreaterThan($fourYearsFromNow, $setCookie->getExpires()); } + /** + * @test + */ + public function it_creates_partitioned_cookies(): void + { + $setCookie = SetCookie::create('chips_cookie')->withPartitioned(); + self::assertEquals(true, $setCookie->getPartitioned()); + } + + /** + * @test + */ + public function it_does_not_set_partitioned_cookies_by_default(): void + { + $setCookie = SetCookie::create('non_chips_cookie'); + self::assertEquals(false, $setCookie->getPartitioned()); + } + + public function partitioned_cookies_are_secure(): void + { + $setCookie = SetCookie::create('maybe_secure_partitioned_cookie')->withPartitioned(); + self::assertEquals(true, $setCookie->getSecure()); + } + /** @test */ public function SameSite_modifier_can_be_added_and_removed(): void { diff --git a/tests/Dflydev/FigCookies/SetCookiesTest.php b/tests/Dflydev/FigCookies/SetCookiesTest.php index eb16ec7..01d802e 100644 --- a/tests/Dflydev/FigCookies/SetCookiesTest.php +++ b/tests/Dflydev/FigCookies/SetCookiesTest.php @@ -181,6 +181,20 @@ public function provideSetCookieStringsAndExpectedSetCookiesData(): array SetCookie::create('c', 'CCC'), ], ], + [ + [ + 'd=DDD', + 'e=EEE; Partitioned', + 'f=FFF', + 'g=GGG; Partitioned', + ], + [ + SetCookie::create('d', 'DDD'), + SetCookie::create('e', 'EEE')->withPartitioned(), + SetCookie::create('f', 'FFF'), + SetCookie::create('g', 'GGG')->withPartitioned(), + ], + ], ]; }