Chris@14: Chris@14: * Chris@14: * For the full copyright and license information, please view the LICENSE Chris@14: * file that was distributed with this source code. Chris@14: */ Chris@14: Chris@14: namespace Symfony\Component\HttpFoundation\Session\Storage\Handler; Chris@14: Chris@14: /** Chris@14: * This abstract session handler provides a generic implementation Chris@14: * of the PHP 7.0 SessionUpdateTimestampHandlerInterface, Chris@14: * enabling strict and lazy session handling. Chris@14: * Chris@14: * @author Nicolas Grekas
Chris@14: */ Chris@14: abstract class AbstractSessionHandler implements \SessionHandlerInterface, \SessionUpdateTimestampHandlerInterface Chris@14: { Chris@14: private $sessionName; Chris@14: private $prefetchId; Chris@14: private $prefetchData; Chris@14: private $newSessionId; Chris@14: private $igbinaryEmptyData; Chris@14: Chris@14: /** Chris@14: * {@inheritdoc} Chris@14: */ Chris@14: public function open($savePath, $sessionName) Chris@14: { Chris@14: $this->sessionName = $sessionName; Chris@14: if (!headers_sent() && !ini_get('session.cache_limiter') && '0' !== ini_get('session.cache_limiter')) { Chris@14: header(sprintf('Cache-Control: max-age=%d, private, must-revalidate', 60 * (int) ini_get('session.cache_expire'))); Chris@14: } Chris@14: Chris@14: return true; Chris@14: } Chris@14: Chris@14: /** Chris@14: * @param string $sessionId Chris@14: * Chris@14: * @return string Chris@14: */ Chris@14: abstract protected function doRead($sessionId); Chris@14: Chris@14: /** Chris@14: * @param string $sessionId Chris@14: * @param string $data Chris@14: * Chris@14: * @return bool Chris@14: */ Chris@14: abstract protected function doWrite($sessionId, $data); Chris@14: Chris@14: /** Chris@14: * @param string $sessionId Chris@14: * Chris@14: * @return bool Chris@14: */ Chris@14: abstract protected function doDestroy($sessionId); Chris@14: Chris@14: /** Chris@14: * {@inheritdoc} Chris@14: */ Chris@14: public function validateId($sessionId) Chris@14: { Chris@14: $this->prefetchData = $this->read($sessionId); Chris@14: $this->prefetchId = $sessionId; Chris@14: Chris@14: return '' !== $this->prefetchData; Chris@14: } Chris@14: Chris@14: /** Chris@14: * {@inheritdoc} Chris@14: */ Chris@14: public function read($sessionId) Chris@14: { Chris@14: if (null !== $this->prefetchId) { Chris@14: $prefetchId = $this->prefetchId; Chris@14: $prefetchData = $this->prefetchData; Chris@14: $this->prefetchId = $this->prefetchData = null; Chris@14: Chris@14: if ($prefetchId === $sessionId || '' === $prefetchData) { Chris@14: $this->newSessionId = '' === $prefetchData ? $sessionId : null; Chris@14: Chris@14: return $prefetchData; Chris@14: } Chris@14: } Chris@14: Chris@14: $data = $this->doRead($sessionId); Chris@14: $this->newSessionId = '' === $data ? $sessionId : null; Chris@14: if (\PHP_VERSION_ID < 70000) { Chris@14: $this->prefetchData = $data; Chris@14: } Chris@14: Chris@14: return $data; Chris@14: } Chris@14: Chris@14: /** Chris@14: * {@inheritdoc} Chris@14: */ Chris@14: public function write($sessionId, $data) Chris@14: { Chris@14: if (\PHP_VERSION_ID < 70000 && $this->prefetchData) { Chris@14: $readData = $this->prefetchData; Chris@14: $this->prefetchData = null; Chris@14: Chris@14: if ($readData === $data) { Chris@14: return $this->updateTimestamp($sessionId, $data); Chris@14: } Chris@14: } Chris@14: if (null === $this->igbinaryEmptyData) { Chris@14: // see https://github.com/igbinary/igbinary/issues/146 Chris@17: $this->igbinaryEmptyData = \function_exists('igbinary_serialize') ? igbinary_serialize([]) : ''; Chris@14: } Chris@14: if ('' === $data || $this->igbinaryEmptyData === $data) { Chris@14: return $this->destroy($sessionId); Chris@14: } Chris@14: $this->newSessionId = null; Chris@14: Chris@14: return $this->doWrite($sessionId, $data); Chris@14: } Chris@14: Chris@14: /** Chris@14: * {@inheritdoc} Chris@14: */ Chris@14: public function destroy($sessionId) Chris@14: { Chris@14: if (\PHP_VERSION_ID < 70000) { Chris@14: $this->prefetchData = null; Chris@14: } Chris@17: if (!headers_sent() && filter_var(ini_get('session.use_cookies'), FILTER_VALIDATE_BOOLEAN)) { Chris@14: if (!$this->sessionName) { Chris@17: throw new \LogicException(sprintf('Session name cannot be empty, did you forget to call "parent::open()" in "%s"?.', \get_class($this))); Chris@14: } Chris@14: $sessionCookie = sprintf(' %s=', urlencode($this->sessionName)); Chris@14: $sessionCookieWithId = sprintf('%s%s;', $sessionCookie, urlencode($sessionId)); Chris@14: $sessionCookieFound = false; Chris@17: $otherCookies = []; Chris@14: foreach (headers_list() as $h) { Chris@14: if (0 !== stripos($h, 'Set-Cookie:')) { Chris@14: continue; Chris@14: } Chris@14: if (11 === strpos($h, $sessionCookie, 11)) { Chris@14: $sessionCookieFound = true; Chris@14: Chris@14: if (11 !== strpos($h, $sessionCookieWithId, 11)) { Chris@14: $otherCookies[] = $h; Chris@14: } Chris@14: } else { Chris@14: $otherCookies[] = $h; Chris@14: } Chris@14: } Chris@14: if ($sessionCookieFound) { Chris@14: header_remove('Set-Cookie'); Chris@14: foreach ($otherCookies as $h) { Chris@14: header($h, false); Chris@14: } Chris@14: } else { Chris@17: 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)); Chris@14: } Chris@14: } Chris@14: Chris@14: return $this->newSessionId === $sessionId || $this->doDestroy($sessionId); Chris@14: } Chris@14: }