1212namespace Symfony \Component \HttpFoundation \Session \Storage ;
1313
1414use Symfony \Component \HttpFoundation \Session \SessionBagInterface ;
15+ use Symfony \Component \HttpFoundation \Session \SessionUtils ;
1516use Symfony \Component \HttpFoundation \Session \Storage \Handler \StrictSessionHandler ;
1617use Symfony \Component \HttpFoundation \Session \Storage \Proxy \AbstractProxy ;
1718use Symfony \Component \HttpFoundation \Session \Storage \Proxy \SessionHandlerProxy ;
@@ -48,6 +49,11 @@ class NativeSessionStorage implements SessionStorageInterface
4849 */
4950 protected $ metadataBag ;
5051
52+ /**
53+ * @var string|null
54+ */
55+ private $ emulateSameSite ;
56+
5157 /**
5258 * Depending on how you want the storage driver to behave you probably
5359 * want to override this constructor entirely.
@@ -67,6 +73,7 @@ class NativeSessionStorage implements SessionStorageInterface
6773 * cookie_lifetime, "0"
6874 * cookie_path, "/"
6975 * cookie_secure, ""
76+ * cookie_samesite, null
7077 * entropy_file, ""
7178 * entropy_length, "0"
7279 * gc_divisor, "100"
@@ -94,9 +101,7 @@ class NativeSessionStorage implements SessionStorageInterface
94101 * trans_sid_hosts, $_SERVER['HTTP_HOST']
95102 * trans_sid_tags, "a=href,area=href,frame=src,form="
96103 *
97- * @param array $options Session configuration options
98- * @param \SessionHandlerInterface|null $handler
99- * @param MetadataBag $metaBag MetadataBag
104+ * @param AbstractProxy|\SessionHandlerInterface|null $handler
100105 */
101106 public function __construct (array $ options = [], $ handler = null , MetadataBag $ metaBag = null )
102107 {
@@ -150,6 +155,13 @@ public function start()
150155 throw new \RuntimeException ('Failed to start the session ' );
151156 }
152157
158+ if (null !== $ this ->emulateSameSite ) {
159+ $ originalCookie = SessionUtils::popSessionCookie (session_name (), session_id ());
160+ if (null !== $ originalCookie ) {
161+ header (sprintf ('%s; SameSite=%s ' , $ originalCookie , $ this ->emulateSameSite ), false );
162+ }
163+ }
164+
153165 $ this ->loadSession ();
154166
155167 return true ;
@@ -215,6 +227,13 @@ public function regenerate($destroy = false, $lifetime = null)
215227 // @see https://bugs.php.net/70013
216228 $ this ->loadSession ();
217229
230+ if (null !== $ this ->emulateSameSite ) {
231+ $ originalCookie = SessionUtils::popSessionCookie (session_name (), session_id ());
232+ if (null !== $ originalCookie ) {
233+ header (sprintf ('%s; SameSite=%s ' , $ originalCookie , $ this ->emulateSameSite ), false );
234+ }
235+ }
236+
218237 return $ isRegenerated ;
219238 }
220239
@@ -223,6 +242,7 @@ public function regenerate($destroy = false, $lifetime = null)
223242 */
224243 public function save ()
225244 {
245+ // Store a copy so we can restore the bags in case the session was not left empty
226246 $ session = $ _SESSION ;
227247
228248 foreach ($ this ->bags as $ bag ) {
@@ -248,7 +268,11 @@ public function save()
248268 session_write_close ();
249269 } finally {
250270 restore_error_handler ();
251- $ _SESSION = $ session ;
271+
272+ // Restore only if not empty
273+ if ($ _SESSION ) {
274+ $ _SESSION = $ session ;
275+ }
252276 }
253277
254278 $ this ->closed = true ;
@@ -347,7 +371,7 @@ public function setOptions(array $options)
347371
348372 $ validOptions = array_flip ([
349373 'cache_expire ' , 'cache_limiter ' , 'cookie_domain ' , 'cookie_httponly ' ,
350- 'cookie_lifetime ' , 'cookie_path ' , 'cookie_secure ' ,
374+ 'cookie_lifetime ' , 'cookie_path ' , 'cookie_secure ' , ' cookie_samesite ' ,
351375 'entropy_file ' , 'entropy_length ' , 'gc_divisor ' ,
352376 'gc_maxlifetime ' , 'gc_probability ' , 'hash_bits_per_character ' ,
353377 'hash_function ' , 'lazy_write ' , 'name ' , 'referer_check ' ,
@@ -360,6 +384,12 @@ public function setOptions(array $options)
360384
361385 foreach ($ options as $ key => $ value ) {
362386 if (isset ($ validOptions [$ key ])) {
387+ if ('cookie_samesite ' === $ key && \PHP_VERSION_ID < 70300 ) {
388+ // PHP < 7.3 does not support same_site cookies. We will emulate it in
389+ // the start() method instead.
390+ $ this ->emulateSameSite = $ value ;
391+ continue ;
392+ }
363393 ini_set ('url_rewriter.tags ' !== $ key ? 'session. ' .$ key : $ key , $ value );
364394 }
365395 }
@@ -381,7 +411,7 @@ public function setOptions(array $options)
381411 * @see https://php.net/sessionhandlerinterface
382412 * @see https://php.net/sessionhandler
383413 *
384- * @param \SessionHandlerInterface|null $saveHandler
414+ * @param AbstractProxy| \SessionHandlerInterface|null $saveHandler
385415 *
386416 * @throws \InvalidArgumentException
387417 */
0 commit comments