1717 */
1818class 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 *
0 commit comments