3
3
* Copyright © Magento, Inc. All rights reserved.
4
4
* See COPYING.txt for license details.
5
5
*/
6
+ declare (strict_types=1 );
6
7
7
8
namespace Magento \Framework \Stdlib \Cookie ;
8
9
21
22
* stores the cookie.
22
23
*
23
24
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
25
+ * @SuppressWarnings(PHPMD.CookieAndSessionMisuse)
24
26
*/
25
27
class PhpCookieManager implements CookieManagerInterface
26
28
{
@@ -63,6 +65,18 @@ class PhpCookieManager implements CookieManagerInterface
63
65
*/
64
66
private $ httpHeader ;
65
67
68
+ /**#@+
69
+ * Constant for SameSite Supported Php Version
70
+ */
71
+ private const SAMESITE_SUPPORTED_PHP_VERSION = '7.3 ' ;
72
+ /**#@-*/
73
+
74
+ /**#@+
75
+ * Constant for Set-Cookie Header
76
+ */
77
+ private const COOKIE_HEADER = 'Set-Cookie: ' ;
78
+ /**#@-*/
79
+
66
80
/**
67
81
* @param CookieScopeInterface $scope
68
82
* @param CookieReaderInterface $reader
@@ -98,7 +112,7 @@ public function __construct(
98
112
public function setSensitiveCookie ($ name , $ value , SensitiveCookieMetadata $ metadata = null )
99
113
{
100
114
$ metadataArray = $ this ->scope ->getSensitiveCookieMetadata ($ metadata )->__toArray ();
101
- $ this ->setCookie ($ name , $ value , $ metadataArray );
115
+ $ this ->setCookie (( string ) $ name , ( string ) $ value , $ metadataArray );
102
116
}
103
117
104
118
/**
@@ -118,7 +132,7 @@ public function setSensitiveCookie($name, $value, SensitiveCookieMetadata $metad
118
132
public function setPublicCookie ($ name , $ value , PublicCookieMetadata $ metadata = null )
119
133
{
120
134
$ metadataArray = $ this ->scope ->getPublicCookieMetadata ($ metadata )->__toArray ();
121
- $ this ->setCookie ($ name , $ value , $ metadataArray );
135
+ $ this ->setCookie (( string ) $ name , ( string ) $ value , $ metadataArray );
122
136
}
123
137
124
138
/**
@@ -138,32 +152,41 @@ protected function setCookie($name, $value, array $metadataArray)
138
152
139
153
$ this ->checkAbilityToSendCookie ($ name , $ value );
140
154
141
- $ phpSetcookieSuccess = setcookie (
142
- $ name ,
143
- $ value ,
144
- $ expire ,
145
- $ this ->extractValue (CookieMetadata::KEY_PATH , $ metadataArray , '' ),
146
- $ this ->extractValue (CookieMetadata::KEY_DOMAIN , $ metadataArray , '' ),
147
- $ this ->extractValue (CookieMetadata::KEY_SECURE , $ metadataArray , false ),
148
- $ this ->extractValue (CookieMetadata::KEY_HTTP_ONLY , $ metadataArray , false )
149
- );
150
-
151
- if (!$ phpSetcookieSuccess ) {
152
- $ params ['name ' ] = $ name ;
153
- if ($ value == '' ) {
154
- throw new FailureToSendException (
155
- new Phrase ('The cookie with "%name" cookieName couldn \'t be deleted. ' , $ params )
156
- );
157
- } else {
158
- throw new FailureToSendException (
159
- new Phrase ('The cookie with "%name" cookieName couldn \'t be sent. Please try again later. ' , $ params )
160
- );
155
+ if (version_compare (phpversion (), self ::SAMESITE_SUPPORTED_PHP_VERSION , '>= ' )) {
156
+
157
+ $ phpSetcookieSuccess = setcookie (
158
+ $ name ,
159
+ $ value ,
160
+ [
161
+ 'expires ' => $ expire ,
162
+ 'path ' => $ this ->extractValue (CookieMetadata::KEY_PATH , $ metadataArray , '' ),
163
+ 'domain ' => $ this ->extractValue (CookieMetadata::KEY_DOMAIN , $ metadataArray , '' ),
164
+ 'secure ' => $ this ->extractValue (CookieMetadata::KEY_SECURE , $ metadataArray , false ),
165
+ 'httponly ' => $ this ->extractValue (CookieMetadata::KEY_HTTP_ONLY , $ metadataArray , false ),
166
+ 'samesite ' => $ this ->extractValue (CookieMetadata::KEY_SAME_SITE , $ metadataArray , 'Lax ' )
167
+ ]
168
+ );
169
+ if (!$ phpSetcookieSuccess ) {
170
+ $ params ['name ' ] = $ name ;
171
+ if ($ value == '' ) {
172
+ throw new FailureToSendException (
173
+ new Phrase ('The cookie with "%name" cookieName couldn \'t be deleted. ' , $ params )
174
+ );
175
+ } else {
176
+ $ exceptionMessage = 'The cookie with "%name" cookieName couldn \'t be sent. Please try again later. ' ;
177
+ throw new FailureToSendException (
178
+ new Phrase ($ exceptionMessage , $ params )
179
+ );
180
+ }
161
181
}
182
+ } else {
183
+ $ this ->setCookieSameSite ($ name , $ value , $ metadataArray );
162
184
}
163
185
}
164
186
165
187
/**
166
188
* Retrieve the size of a cookie.
189
+ *
167
190
* The size of a cookie is determined by the length of 'name=value' portion of the cookie.
168
191
*
169
192
* @param string $name
@@ -177,8 +200,7 @@ private function sizeOfCookie($name, $value)
177
200
}
178
201
179
202
/**
180
- * Determines whether or not it is possible to send the cookie, based on the number of cookies that already
181
- * exist and the size of the cookie.
203
+ * Determines ability to send cookies, based on the number of existing cookies and cookie size
182
204
*
183
205
* @param string $name
184
206
* @param string|null $value
@@ -249,6 +271,7 @@ private function computeExpirationTime(array $metadataArray)
249
271
250
272
/**
251
273
* Determines the value to be used as a $parameter.
274
+ *
252
275
* If $metadataArray[$parameter] is not set, returns the $defaultValue.
253
276
*
254
277
* @param string $parameter
@@ -303,4 +326,78 @@ public function deleteCookie($name, CookieMetadata $metadata = null)
303
326
// Remove the cookie
304
327
unset($ _COOKIE [$ name ]);
305
328
}
329
+
330
+ /**
331
+ * Polyfill for Set-Cookie with support for SameSite attribute
332
+ *
333
+ * Supports Php version 7.2 and lower
334
+ *
335
+ * @param string $name
336
+ * @param string $value
337
+ * @param array $metadataArray
338
+ * @throws FailureToSendException
339
+ * @return void
340
+ */
341
+ private function setCookieSameSite (string $ name , string $ value , array $ metadataArray ): void
342
+ {
343
+
344
+ $ expires = $ this ->computeExpirationTime ($ metadataArray );
345
+ $ path = $ this ->extractValue (CookieMetadata::KEY_PATH , $ metadataArray , '' );
346
+ $ domain = $ this ->extractValue (CookieMetadata::KEY_DOMAIN , $ metadataArray , '' );
347
+ $ secure = $ this ->extractValue (CookieMetadata::KEY_SECURE , $ metadataArray , false );
348
+ $ httpOnly = $ this ->extractValue (CookieMetadata::KEY_HTTP_ONLY , $ metadataArray , false );
349
+ $ sameSite = $ this ->extractValue (CookieMetadata::KEY_SAME_SITE , $ metadataArray , 'Lax ' );
350
+ $ params = [];
351
+ $ setCookieSuccess = false ;
352
+
353
+ if ('' === $ value ) {
354
+ $ params [] = $ name . '= ' . 'deleted ' ;
355
+
356
+ } else {
357
+ $ params [] = $ name . '= ' . rawurlencode ($ value );
358
+ }
359
+
360
+ if (0 !== $ expires ) {
361
+ $ formattedExpirationTime = gmdate ('D, d-M-Y H:i:s T ' , $ expires );
362
+ $ params [] = sprintf ('expires=%s ' , $ formattedExpirationTime );
363
+ }
364
+
365
+ if ($ path ) {
366
+ $ params [] = sprintf ('path=%s ' , $ path );
367
+ }
368
+
369
+ if ($ domain ) {
370
+ $ params [] = sprintf ('domain=%s ' , $ domain );
371
+ }
372
+
373
+ if ($ httpOnly ) {
374
+ $ params [] = 'HttpOnly ' ;
375
+ }
376
+
377
+ if ($ secure ) {
378
+ $ params [] = 'secure ' ;
379
+ }
380
+
381
+ $ params [] = sprintf ('SameSite=%s ' , $ sameSite );
382
+ $ header = sprintf (self ::COOKIE_HEADER . "%s " , implode ('; ' , $ params ));
383
+ header ($ header , false );
384
+
385
+ $ setCookieSuccess = array_filter (headers_list (), function ($ value ) use ($ header ) {
386
+ return strpos ($ value , $ header ) !== false ;
387
+ });
388
+
389
+ if (!$ setCookieSuccess ) {
390
+ $ args ['name ' ] = $ name ;
391
+ if ($ value == '' ) {
392
+ throw new FailureToSendException (
393
+ new Phrase ('The cookie with "%name" cookieName couldn \'t be deleted. ' , $ args )
394
+ );
395
+ } else {
396
+ $ exceptionMessage = 'The cookie with "%name" cookieName couldn \'t be sent. Please try again later. ' ;
397
+ throw new FailureToSendException (
398
+ new Phrase ($ exceptionMessage , $ args )
399
+ );
400
+ }
401
+ }
402
+ }
306
403
}
0 commit comments