Skip to content
This repository was archived by the owner on Jan 30, 2020. It is now read-only.

Commit c7e40d7

Browse files
committed
Merge pull request #186 from MadCat34/fix/174-SameSite
Add SameSite directive #174
2 parents b8c93fa + cc1d12f commit c7e40d7

File tree

2 files changed

+128
-2
lines changed

2 files changed

+128
-2
lines changed

src/Header/SetCookie.php

Lines changed: 70 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,33 @@
1717
*/
1818
class SetCookie implements MultipleHeaderInterface
1919
{
20+
/**
21+
* Cookie will not be sent for any cross-domain requests whatsoever.
22+
* Even if the user simply navigates to the target site with a regular link, the cookie will not be sent.
23+
*/
24+
const SAME_SITE_STRICT = 'Strict';
25+
26+
/**
27+
* Cookie will not be passed for any cross-domain requests unless it's a regular link that navigates user
28+
* to the target site.
29+
* Other requests methods (such as POST and PUT) and XHR requests will not contain this cookie.
30+
*/
31+
const SAME_SITE_LAX = 'Lax';
32+
33+
/**
34+
* Cookie will be sent with same-site and cross-site requests.
35+
*/
36+
const SAME_SITE_NONE = 'None';
37+
38+
/**
39+
* @internal
40+
*/
41+
const SAME_SITE_ALLOWED_VALUES = [
42+
self::SAME_SITE_STRICT,
43+
self::SAME_SITE_LAX,
44+
self::SAME_SITE_NONE,
45+
];
46+
2047
/**
2148
* Cookie name
2249
*
@@ -85,6 +112,11 @@ class SetCookie implements MultipleHeaderInterface
85112
*/
86113
protected $httponly;
87114

115+
/**
116+
* @var string|null
117+
*/
118+
protected $sameSite;
119+
88120
/**
89121
* @var bool
90122
*/
@@ -152,6 +184,9 @@ public static function fromString($headerLine, $bypassHeaderFieldName = false)
152184
case 'maxage':
153185
$header->setMaxAge($headerValue);
154186
break;
187+
case 'samesite':
188+
$header->setSameSite($headerValue);
189+
break;
155190
default:
156191
// Intentionally omitted
157192
}
@@ -199,6 +234,7 @@ public static function fromString($headerLine, $bypassHeaderFieldName = false)
199234
* @param bool $httponly
200235
* @param int|null $maxAge
201236
* @param int|null $version
237+
* @param string|null $sameSite
202238
*/
203239
public function __construct(
204240
$name = null,
@@ -209,7 +245,8 @@ public function __construct(
209245
$secure = false,
210246
$httponly = false,
211247
$maxAge = null,
212-
$version = null
248+
$version = null,
249+
$sameSite = null
213250
) {
214251
$this->type = 'Cookie';
215252

@@ -221,7 +258,8 @@ public function __construct(
221258
->setExpires($expires)
222259
->setPath($path)
223260
->setSecure($secure)
224-
->setHttpOnly($httponly);
261+
->setHttpOnly($httponly)
262+
->setSameSite($sameSite);
225263
}
226264

227265
/**
@@ -298,6 +336,11 @@ public function getFieldValue()
298336
$fieldValue .= '; HttpOnly';
299337
}
300338

339+
$sameSite = $this->getSameSite();
340+
if ($sameSite !== null && in_array($sameSite, self::SAME_SITE_ALLOWED_VALUES, true)) {
341+
$fieldValue .= '; SameSite=' . $sameSite;
342+
}
343+
301344
return $fieldValue;
302345
}
303346

@@ -560,6 +603,31 @@ public function isSessionCookie()
560603
return ($this->expires === null);
561604
}
562605

606+
/**
607+
* @return string|null
608+
*/
609+
public function getSameSite()
610+
{
611+
return $this->sameSite;
612+
}
613+
614+
/**
615+
* @param string|null $sameSite
616+
* @return $this
617+
* @throws Exception\InvalidArgumentException
618+
*/
619+
public function setSameSite($sameSite)
620+
{
621+
if ($sameSite !== null && ! in_array($sameSite, self::SAME_SITE_ALLOWED_VALUES, true)) {
622+
throw new Exception\InvalidArgumentException(sprintf(
623+
'Invalid value provided for SameSite directive: "%s"; expected one of: Strict, Lax or None',
624+
is_scalar($sameSite) ? $sameSite : gettype($sameSite)
625+
));
626+
}
627+
$this->sameSite = $sameSite;
628+
return $this;
629+
}
630+
563631
/**
564632
* Check whether the value for this cookie should be quoted
565633
*

test/Header/SetCookieTest.php

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,50 @@ public function testSetCookieConstructor()
4343
$this->assertEquals(9, $setCookieHeader->getVersion());
4444
}
4545

46+
public function testSetCookieConstructorWithSameSite()
47+
{
48+
$setCookieHeader = new SetCookie(
49+
'myname',
50+
'myvalue',
51+
'Wed, 13-Jan-2021 22:23:01 GMT',
52+
'/accounts',
53+
'docs.foo.com',
54+
true,
55+
true,
56+
99,
57+
9,
58+
SetCookie::SAME_SITE_STRICT
59+
);
60+
$this->assertEquals('myname', $setCookieHeader->getName());
61+
$this->assertEquals('myvalue', $setCookieHeader->getValue());
62+
$this->assertEquals('Wed, 13-Jan-2021 22:23:01 GMT', $setCookieHeader->getExpires());
63+
$this->assertEquals('/accounts', $setCookieHeader->getPath());
64+
$this->assertEquals('docs.foo.com', $setCookieHeader->getDomain());
65+
$this->assertTrue($setCookieHeader->isSecure());
66+
$this->assertTrue($setCookieHeader->isHttpOnly());
67+
$this->assertEquals(99, $setCookieHeader->getMaxAge());
68+
$this->assertEquals(9, $setCookieHeader->getVersion());
69+
$this->assertEquals('Strict', $setCookieHeader->getSameSite());
70+
}
71+
72+
public function testSetCookieWithInvalidSameSiteValueThrowException()
73+
{
74+
$this->expectException(InvalidArgumentException::class);
75+
76+
$setCookieHeader = new SetCookie(
77+
'myname',
78+
'myvalue',
79+
'Wed, 13-Jan-2021 22:23:01 GMT',
80+
'/accounts',
81+
'docs.foo.com',
82+
true,
83+
true,
84+
99,
85+
9,
86+
'InvalidValue'
87+
);
88+
}
89+
4690
public function testSetCookieFromStringWithQuotedValue()
4791
{
4892
$setCookieHeader = SetCookie::fromString('Set-Cookie: myname="quotedValue"');
@@ -85,6 +129,20 @@ public function testSetCookieFromStringCanCreateSingleHeader()
85129
$this->assertEquals('Wed, 13-Jan-2021 22:23:01 GMT', $setCookieHeader->getExpires());
86130
$this->assertTrue($setCookieHeader->isSecure());
87131
$this->assertTrue($setCookieHeader->isHttponly());
132+
133+
$setCookieHeader = SetCookie::fromString(
134+
'set-cookie: myname=myvalue; Domain=docs.foo.com; Path=/accounts;'
135+
. 'Expires=Wed, 13-Jan-2021 22:23:01 GMT; Secure; HttpOnly; SameSite=Strict'
136+
);
137+
$this->assertInstanceOf(MultipleHeaderInterface::class, $setCookieHeader);
138+
$this->assertEquals('myname', $setCookieHeader->getName());
139+
$this->assertEquals('myvalue', $setCookieHeader->getValue());
140+
$this->assertEquals('docs.foo.com', $setCookieHeader->getDomain());
141+
$this->assertEquals('/accounts', $setCookieHeader->getPath());
142+
$this->assertEquals('Wed, 13-Jan-2021 22:23:01 GMT', $setCookieHeader->getExpires());
143+
$this->assertTrue($setCookieHeader->isSecure());
144+
$this->assertTrue($setCookieHeader->isHttponly());
145+
$this->assertEquals(setCookie::SAME_SITE_STRICT, $setCookieHeader->getSameSite());
88146
}
89147

90148
public function testSetCookieFromStringCanCreateMultipleHeaders()

0 commit comments

Comments
 (0)