src/Eccube/Session/Storage/Handler/SameSiteNoneCompatSessionHandler.php line 173

Open in your IDE?
  1. <?php
  2. /*
  3. * This file is part of EC-CUBE
  4. *
  5. * Copyright(c) EC-CUBE CO.,LTD. All Rights Reserved.
  6. *
  7. * http://www.ec-cube.co.jp/
  8. *
  9. * For the full copyright and license information, please view the LICENSE
  10. * file that was distributed with this source code.
  11. */
  12. namespace Eccube\Session\Storage\Handler;
  13. use Skorp\Dissua\SameSite;
  14. use Symfony\Component\HttpFoundation\Cookie;
  15. use Symfony\Component\HttpFoundation\Request;
  16. use Symfony\Component\HttpFoundation\Session\Storage\Handler\StrictSessionHandler;
  17. class SameSiteNoneCompatSessionHandler extends StrictSessionHandler
  18. {
  19. /** @var \SessionHandlerInterface */
  20. private $handler;
  21. /** @var bool */
  22. private $doDestroy;
  23. /** @var string */
  24. private $sessionName;
  25. /** @var string|null */
  26. private $prefetchData;
  27. /** @var string */
  28. private $newSessionId;
  29. /**
  30. * {@inheritdoc}
  31. */
  32. public function __construct(\SessionHandlerInterface $handler)
  33. {
  34. $this->handler = $handler;
  35. ini_set('session.cookie_secure', $this->getCookieSecure());
  36. ini_set('session.cookie_samesite', $this->getCookieSameSite());
  37. ini_set('session.cookie_path', $this->getCookiePath());
  38. }
  39. /**
  40. * {@inheritdoc}
  41. */
  42. public function open($savePath, $sessionName)
  43. {
  44. $this->sessionName = $sessionName;
  45. // see https://github.com/symfony/symfony/blob/2adc85d49cbe14e346068fa7e9c2e1f08ab31de6/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/AbstractSessionHandler.php#L35-L37
  46. if (!headers_sent() && !ini_get('session.cache_limiter') && '0' !== ini_get('session.cache_limiter')) {
  47. header(sprintf('Cache-Control: max-age=%d, private, must-revalidate', 60 * (int) ini_get('session.cache_expire')));
  48. }
  49. return $this->handler->open($savePath, $sessionName);
  50. }
  51. /**
  52. * {@inheritdoc}
  53. */
  54. protected function doRead($sessionId)
  55. {
  56. return $this->handler->read($sessionId);
  57. }
  58. /**
  59. * {@inheritdoc}
  60. */
  61. public function updateTimestamp($sessionId, $data)
  62. {
  63. return $this->write($sessionId, $data);
  64. }
  65. /**
  66. * {@inheritdoc}
  67. */
  68. protected function doWrite($sessionId, $data)
  69. {
  70. return $this->handler->write($sessionId, $data);
  71. }
  72. /**
  73. * {@inheritdoc}
  74. *
  75. * @see https://github.com/symfony/symfony/blob/2adc85d49cbe14e346068fa7e9c2e1f08ab31de6/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/AbstractSessionHandler.php#L126-L167
  76. */
  77. public function destroy($sessionId)
  78. {
  79. if (\PHP_VERSION_ID < 70000) {
  80. $this->prefetchData = null;
  81. }
  82. if (!headers_sent() && filter_var(ini_get('session.use_cookies'), FILTER_VALIDATE_BOOLEAN)) {
  83. if (!$this->sessionName) {
  84. throw new \LogicException(sprintf('Session name cannot be empty, did you forget to call "parent::open()" in "%s"?.', \get_class($this)));
  85. }
  86. $sessionCookie = sprintf(' %s=', urlencode($this->sessionName));
  87. $sessionCookieWithId = sprintf('%s%s;', $sessionCookie, urlencode($sessionId));
  88. $sessionCookieFound = false;
  89. $otherCookies = [];
  90. foreach (headers_list() as $h) {
  91. if (0 !== stripos($h, 'Set-Cookie:')) {
  92. continue;
  93. }
  94. if (11 === strpos($h, $sessionCookie, 11)) {
  95. $sessionCookieFound = true;
  96. if (11 !== strpos($h, $sessionCookieWithId, 11)) {
  97. $otherCookies[] = $h;
  98. }
  99. } else {
  100. $otherCookies[] = $h;
  101. }
  102. }
  103. if ($sessionCookieFound) {
  104. header_remove('Set-Cookie');
  105. foreach ($otherCookies as $h) {
  106. header($h, false);
  107. }
  108. } else {
  109. if (\PHP_VERSION_ID < 70300) {
  110. setcookie($this->sessionName, '', 0, ini_get('session.cookie_path'), ini_get('session.cookie_domain'), filter_var(ini_get('session.cookie_secure'), FILTER_VALIDATE_BOOLEAN), filter_var(ini_get('session.cookie_httponly'), FILTER_VALIDATE_BOOLEAN));
  111. } else {
  112. setcookie($this->sessionName, '',
  113. [
  114. 'expires' => 0,
  115. 'path' => $this->getCookiePath(),
  116. 'domain' => ini_get('session.cookie_domain'),
  117. 'secure' => filter_var(ini_get('session.cookie_secure'), FILTER_VALIDATE_BOOLEAN),
  118. 'httponly' => filter_var(ini_get('session.cookie_httponly'), FILTER_VALIDATE_BOOLEAN),
  119. 'samesite' => $this->getCookieSameSite(),
  120. ]
  121. );
  122. }
  123. }
  124. }
  125. return $this->newSessionId === $sessionId || $this->doDestroy($sessionId);
  126. }
  127. /**
  128. * {@inheritdoc}
  129. */
  130. protected function doDestroy($sessionId)
  131. {
  132. $this->doDestroy = false;
  133. return $this->handler->destroy($sessionId);
  134. }
  135. /**
  136. * {@inheritdoc}
  137. */
  138. public function close()
  139. {
  140. return $this->handler->close();
  141. }
  142. /**
  143. * @return bool
  144. */
  145. public function gc($maxlifetime)
  146. {
  147. return $this->handler->gc($maxlifetime);
  148. }
  149. /**
  150. * @return string
  151. */
  152. public function getCookieSameSite()
  153. {
  154. if ($this->shouldSendSameSiteNone() && \PHP_VERSION_ID >= 70300 && $this->getCookieSecure()) {
  155. return Cookie::SAMESITE_NONE;
  156. }
  157. return '';
  158. }
  159. /**
  160. * @return string
  161. */
  162. public function getCookiePath()
  163. {
  164. $cookiePath = env('ECCUBE_COOKIE_PATH', '/');
  165. if ($this->shouldSendSameSiteNone() && \PHP_VERSION_ID < 70300 && $this->getCookieSecure()) {
  166. return $cookiePath.'; SameSite='.Cookie::SAMESITE_NONE;
  167. }
  168. return $cookiePath;
  169. }
  170. /**
  171. * @return string
  172. */
  173. public function getCookieSecure()
  174. {
  175. $request = Request::createFromGlobals();
  176. return $request->isSecure() ? '1' : '0';
  177. }
  178. /**
  179. * @return bool
  180. */
  181. private function shouldSendSameSiteNone()
  182. {
  183. $userAgent = array_key_exists('HTTP_USER_AGENT', $_SERVER) ? $_SERVER['HTTP_USER_AGENT'] : null;
  184. return SameSite::handle($userAgent);
  185. }
  186. }