Skip to content

Commit 96fc135

Browse files
committed
restored methods to handle SameSite cookie header, but without using separate class
1 parent d41c519 commit 96fc135

File tree

2 files changed

+105
-6
lines changed

2 files changed

+105
-6
lines changed

Http/SetCookie.php

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ class SetCookie
2424
private $secure = false;
2525
/** @var bool */
2626
private $httpOnly = false;
27+
/** @var string|null */
28+
private $sameSite = null;
2729

2830
private function __construct(string $name, ?string $value = null)
2931
{
@@ -71,6 +73,11 @@ public function getHttpOnly() : bool
7173
return $this->httpOnly;
7274
}
7375

76+
public function getSameSite() : ?string
77+
{
78+
return $this->sameSite;
79+
}
80+
7481
public function withValue(?string $value = null) : self
7582
{
7683
$clone = clone($this);
@@ -169,6 +176,39 @@ public function withHttpOnly(bool $httpOnly = true) : self
169176
return $clone;
170177
}
171178

179+
/**
180+
* @throws \InvalidArgumentException If the given SameSite string is neither strict nor lax.
181+
*/
182+
public function withSameSite(string $sameSite = 'lax') : self
183+
{
184+
$clone = clone($this);
185+
186+
$lowerCaseSite = \strtolower($sameSite);
187+
188+
if ($lowerCaseSite === 'strict') {
189+
$strict = true;
190+
} elseif ($lowerCaseSite === 'lax') {
191+
$strict = false;
192+
} else {
193+
throw new \InvalidArgumentException(\sprintf(
194+
'Expected modifier value to be either "strict" or "lax", "%s" given', $sameSite
195+
));
196+
}
197+
198+
$clone->sameSite = ($strict) ? 'Strict' : 'Lax';
199+
200+
return $clone;
201+
}
202+
203+
public function withoutSameSite() : self
204+
{
205+
$clone = clone($this);
206+
207+
$clone->sameSite = null;
208+
209+
return $clone;
210+
}
211+
172212
public function __toString() : string
173213
{
174214
$cookieStringParts = [
@@ -181,6 +221,7 @@ public function __toString() : string
181221
$cookieStringParts = $this->appendFormattedMaxAgePartIfSet($cookieStringParts);
182222
$cookieStringParts = $this->appendFormattedSecurePartIfSet($cookieStringParts);
183223
$cookieStringParts = $this->appendFormattedHttpOnlyPartIfSet($cookieStringParts);
224+
$cookieStringParts = $this->appendFormattedSameSitePartIfSet($cookieStringParts);
184225

185226
return \implode('; ', $cookieStringParts);
186227
}
@@ -248,6 +289,9 @@ public static function fromString(string $string) : self
248289
case 'httponly':
249290
$setCookie = $setCookie->withHttpOnly(true);
250291
break;
292+
case 'samesite':
293+
$setCookie = $setCookie->withSameSite($attributeValue);
294+
break;
251295
}
252296
}
253297

@@ -307,4 +351,15 @@ private function appendFormattedHttpOnlyPartIfSet(array $cookieStringParts) : ar
307351

308352
return $cookieStringParts;
309353
}
354+
355+
private function appendFormattedSameSitePartIfSet(array $cookieStringParts) : array
356+
{
357+
if ($this->sameSite === null) {
358+
return $cookieStringParts;
359+
}
360+
361+
$cookieStringParts[] = \sprintf('SameSite=%s', $this->sameSite);
362+
363+
return $cookieStringParts;
364+
}
310365
}

tests/SetCookieTest.php

Lines changed: 50 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ public function testFromString(string $cookieString, SetCookie $expectedSetCooki
1414
{
1515
$setCookie = SetCookie::fromString($cookieString);
1616

17-
self::assertEquals($expectedSetCookie, $setCookie);
18-
self::assertEquals($cookieString, (string) $setCookie);
17+
$this->assertEquals($expectedSetCookie, $setCookie);
18+
$this->assertEquals($cookieString, (string) $setCookie);
1919
}
2020

2121
public function provideParsesFromSetCookieStringData() : array
@@ -110,25 +110,69 @@ public function provideParsesFromSetCookieStringData() : array
110110
->withSecure(true)
111111
->withHttpOnly(true),
112112
],
113+
[
114+
'lu=Rg3vHJZnehYLjVg7qi3bZjzg; Domain=.example.com; Path=/; Expires=Tue, 15 Jan 2013 21:47:38 GMT; Max-Age=500; Secure; HttpOnly; SameSite=Strict',
115+
SetCookie::create('lu')
116+
->withValue('Rg3vHJZnehYLjVg7qi3bZjzg')
117+
->withExpires(new \DateTime('Tue, 15-Jan-2013 21:47:38 GMT'))
118+
->withMaxAge(500)
119+
->withPath('/')
120+
->withDomain('.example.com')
121+
->withSecure(true)
122+
->withHttpOnly(true)
123+
->withSameSite('strict'),
124+
],
125+
[
126+
'lu=Rg3vHJZnehYLjVg7qi3bZjzg; Domain=.example.com; Path=/; Expires=Tue, 15 Jan 2013 21:47:38 GMT; Max-Age=500; Secure; HttpOnly; SameSite=Lax',
127+
SetCookie::create('lu')
128+
->withValue('Rg3vHJZnehYLjVg7qi3bZjzg')
129+
->withExpires(new \DateTime('Tue, 15-Jan-2013 21:47:38 GMT'))
130+
->withMaxAge(500)
131+
->withPath('/')
132+
->withDomain('.example.com')
133+
->withSecure(true)
134+
->withHttpOnly(true)
135+
->withSameSite('lax'),
136+
],
113137
];
114138
}
115139

116140
public function testExpireCookies() : void
117141
{
118142
$setCookie = SetCookie::createExpired('expire_immediately');
119143

120-
self::assertLessThan(\time(), $setCookie->getExpires());
144+
$this->assertLessThan(\time(), $setCookie->getExpires());
121145
}
122146

123-
public function TestLongLivingCookies() : void
147+
public function testLongLivingCookies() : void
124148
{
125149
$setCookie = SetCookie::createRememberedForever('remember_forever');
126150

127151
$fourYearsFromNow = (new \DateTime('+4 years'))->getTimestamp();
128-
self::assertGreaterThan($fourYearsFromNow, $setCookie->getExpires());
152+
$this->assertGreaterThan($fourYearsFromNow, $setCookie->getExpires());
153+
}
154+
155+
public function testSameSite() : void
156+
{
157+
$setCookie = SetCookie::create('foo', 'bar');
158+
159+
$this->assertNull($setCookie->getSameSite());
160+
$this->assertSame('foo=bar', $setCookie->__toString());
161+
162+
$setCookie = $setCookie->withSameSite('strict');
163+
164+
$this->assertEquals('Strict', $setCookie->getSameSite());
165+
$this->assertSame('foo=bar; SameSite=Strict', $setCookie->__toString());
166+
167+
$setCookie = $setCookie->withoutSameSite();
168+
$this->assertNull($setCookie->getSameSite());
169+
$this->assertSame('foo=bar', $setCookie->__toString());
170+
171+
$this->expectException(\InvalidArgumentException::class);
172+
$setCookie = $setCookie->withSameSite('foo');
129173
}
130174

131-
public function TestInvalid() : void
175+
public function testInvalid() : void
132176
{
133177
$setCookie = SetCookie::create('foo', 'bar');
134178

0 commit comments

Comments
 (0)