diff --git a/UPGRADE-3.4.md b/UPGRADE-3.4.md index 5ef2448bf27a7..1b9a40c452dde 100644 --- a/UPGRADE-3.4.md +++ b/UPGRADE-3.4.md @@ -126,6 +126,8 @@ Form FrameworkBundle --------------- + * The `session.use_strict_mode` option has been deprecated and is enabled by default. + * The `cache:clear` command doesn't clear "app" PSR-6 cache pools anymore, but still clears "system" ones. Use the `cache:pool:clear` command to clear "app" pools instead. @@ -235,18 +237,13 @@ HttpFoundation * The `Symfony\Component\HttpFoundation\Session\Storage\Handler\NativeSessionHandler` class has been deprecated and will be removed in 4.0. Use the `\SessionHandler` class instead. - * The `Symfony\Component\HttpFoundation\Session\Storage\Proxy\AbstractProxy` class has been - deprecated and will be removed in 4.0. Use your `\SessionHandlerInterface` implementation directly. + * The `Symfony\Component\HttpFoundation\Session\Storage\Handler\WriteCheckSessionHandler` class has been + deprecated and will be removed in 4.0. Implement `SessionUpdateTimestampHandlerInterface` or + extend `AbstractSessionHandler` instead. * The `Symfony\Component\HttpFoundation\Session\Storage\Proxy\NativeProxy` class has been deprecated and will be removed in 4.0. Use your `\SessionHandlerInterface` implementation directly. - * The `Symfony\Component\HttpFoundation\Session\Storage\Proxy\SessionHandlerProxy` class has been - deprecated and will be removed in 4.0. Use your `\SessionHandlerInterface` implementation directly. - - * `NativeSessionStorage::setSaveHandler()` now takes an instance of `\SessionHandlerInterface` as argument. - Not passing it is deprecated and will throw a `TypeError` in 4.0. - * Using `Symfony\Component\HttpFoundation\Session\Storage\Handler\MongoDbSessionHandler` with the legacy mongo extension has been deprecated and will be removed in 4.0. Use it with the mongodb/mongodb package and ext-mongodb instead. diff --git a/UPGRADE-4.0.md b/UPGRADE-4.0.md index b2854c5925494..77959fa48bab1 100644 --- a/UPGRADE-4.0.md +++ b/UPGRADE-4.0.md @@ -329,6 +329,8 @@ Form FrameworkBundle --------------- + * The `session.use_strict_mode` option has been removed and strict mode is always enabled. + * The `validator.mapping.cache.doctrine.apc` service has been removed. * The "framework.trusted_proxies" configuration option and the corresponding "kernel.trusted_proxies" parameter have been removed. Use the `Request::setTrustedProxies()` method in your front controller instead. @@ -542,12 +544,11 @@ HttpFoundation * The ability to check only for cacheable HTTP methods using `Request::isMethodSafe()` is not supported anymore, use `Request::isMethodCacheable()` instead. - * The `Symfony\Component\HttpFoundation\Session\Storage\Handler\NativeSessionHandler`, - `Symfony\Component\HttpFoundation\Session\Storage\Proxy\AbstractProxy`, - `Symfony\Component\HttpFoundation\Session\Storage\Proxy\NativeProxy` and - `Symfony\Component\HttpFoundation\Session\Storage\Proxy\SessionHandlerProxy` classes have been removed. + * The `Symfony\Component\HttpFoundation\Session\Storage\Handler\WriteCheckSessionHandler` class has been + removed. Implement `SessionUpdateTimestampHandlerInterface` or extend `AbstractSessionHandler` instead. - * `NativeSessionStorage::setSaveHandler()` now requires an instance of `\SessionHandlerInterface` as argument. + * The `Symfony\Component\HttpFoundation\Session\Storage\Handler\NativeSessionHandler` and + `Symfony\Component\HttpFoundation\Session\Storage\Proxy\NativeProxy` classes have been removed. * The `Symfony\Component\HttpFoundation\Session\Storage\Handler\MongoDbSessionHandler` does not work with the legacy mongo extension anymore. It requires mongodb/mongodb package and ext-mongodb. diff --git a/composer.json b/composer.json index 3d96b012ce0b9..5b8e7985aae34 100644 --- a/composer.json +++ b/composer.json @@ -30,7 +30,7 @@ "symfony/polyfill-intl-icu": "~1.0", "symfony/polyfill-mbstring": "~1.0", "symfony/polyfill-php56": "~1.0", - "symfony/polyfill-php70": "~1.0", + "symfony/polyfill-php70": "~1.6", "symfony/polyfill-util": "~1.0" }, "replace": { diff --git a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md index 49fd03165a5b7..db3d480b3e6c1 100644 --- a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md +++ b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md @@ -4,6 +4,7 @@ CHANGELOG 3.4.0 ----- + * Session `use_strict_mode` is now enabled by default and the corresponding option has been deprecated * Made the `cache:clear` command to *not* clear "app" PSR-6 cache pools anymore, but to still clear "system" ones; use the `cache:pool:clear` command to clear "app" pools instead * Always register a minimalist logger that writes in `stderr` diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php index 4d5af046cbec2..14c61e1395323 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php @@ -462,11 +462,14 @@ private function addSessionSection(ArrayNodeDefinition $rootNode) ->scalarNode('gc_divisor')->end() ->scalarNode('gc_probability')->defaultValue(1)->end() ->scalarNode('gc_maxlifetime')->end() - ->booleanNode('use_strict_mode')->end() + ->booleanNode('use_strict_mode') + ->defaultTrue() + ->setDeprecated('The "%path%.%node%" option is enabled by default and deprecated since Symfony 3.4. It will be always enabled in 4.0.') + ->end() ->scalarNode('save_path')->defaultValue('%kernel.cache_dir%/sessions')->end() ->integerNode('metadata_update_threshold') ->defaultValue('0') - ->info('seconds to wait between 2 session metadata updates, it will also prevent the session handler to write if the session has not changed') + ->info('seconds to wait between 2 session metadata updates') ->end() ->end() ->end() diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index 568bb0913ebc7..5eb3c24dff08f 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -916,14 +916,7 @@ private function registerSessionConfiguration(array $config, ContainerBuilder $c $container->getDefinition('session.storage.native')->replaceArgument(1, null); $container->getDefinition('session.storage.php_bridge')->replaceArgument(0, null); } else { - $handlerId = $config['handler_id']; - - if ($config['metadata_update_threshold'] > 0) { - $container->getDefinition('session.handler.write_check')->addArgument(new Reference($handlerId)); - $handlerId = 'session.handler.write_check'; - } - - $container->setAlias('session.handler', $handlerId)->setPrivate(true); + $container->setAlias('session.handler', $config['handler_id'])->setPrivate(true); } $container->setParameter('session.save_path', $config['save_path']); diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/session.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/session.xml index 335c14a8a6611..ce7a3dd7eee7d 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/session.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/session.xml @@ -48,11 +48,17 @@ - - %session.save_path% + + + + %session.save_path% + + - + + The "%service_id%" service is deprecated since Symfony 3.4 and will be removed in 4.0. Use the `session.lazy_write` ini setting instead. + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php index e6e83d40b538d..80f54afd20cad 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php @@ -301,6 +301,7 @@ protected static function getBundleDefaultConfig() 'gc_probability' => 1, 'save_path' => '%kernel.cache_dir%/sessions', 'metadata_update_threshold' => '0', + 'use_strict_mode' => true, ), 'request' => array( 'enabled' => false, diff --git a/src/Symfony/Component/HttpFoundation/CHANGELOG.md b/src/Symfony/Component/HttpFoundation/CHANGELOG.md index 83a3c8e32eaf6..ee5b6cecf2e85 100644 --- a/src/Symfony/Component/HttpFoundation/CHANGELOG.md +++ b/src/Symfony/Component/HttpFoundation/CHANGELOG.md @@ -4,8 +4,9 @@ CHANGELOG 3.4.0 ----- - * deprecated the `NativeSessionHandler` class, - * deprecated the `AbstractProxy`, `NativeProxy` and `SessionHandlerProxy` classes, + * implemented PHP 7.0's `SessionUpdateTimestampHandlerInterface` with a new + `AbstractSessionHandler` base class and a new `StrictSessionHandler` wrapper + * deprecated the `WriteCheckSessionHandler`, `NativeSessionHandler` and `NativeProxy` classes * deprecated setting session save handlers that do not implement `\SessionHandlerInterface` in `NativeSessionStorage::setSaveHandler()` * deprecated using `MongoDbSessionHandler` with the legacy mongo extension; use it with the mongodb/mongodb package and ext-mongodb instead * deprecated `MemcacheSessionHandler`; use `MemcachedSessionHandler` instead diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/AbstractSessionHandler.php b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/AbstractSessionHandler.php new file mode 100644 index 0000000000000..c20a23b20e5d9 --- /dev/null +++ b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/AbstractSessionHandler.php @@ -0,0 +1,165 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage\Handler; + +/** + * This abstract session handler provides a generic implementation + * of the PHP 7.0 SessionUpdateTimestampHandlerInterface, + * enabling strict and lazy session handling. + * + * @author Nicolas Grekas + */ +abstract class AbstractSessionHandler implements \SessionHandlerInterface, \SessionUpdateTimestampHandlerInterface +{ + private $sessionName; + private $prefetchId; + private $prefetchData; + private $newSessionId; + private $igbinaryEmptyData; + + /** + * {@inheritdoc} + */ + public function open($savePath, $sessionName) + { + $this->sessionName = $sessionName; + + return true; + } + + /** + * @param string $sessionId + * + * @return string + */ + abstract protected function doRead($sessionId); + + /** + * @param string $sessionId + * @param string $data + * + * @return bool + */ + abstract protected function doWrite($sessionId, $data); + + /** + * @param string $sessionId + * + * @return bool + */ + abstract protected function doDestroy($sessionId); + + /** + * {@inheritdoc} + */ + public function validateId($sessionId) + { + $this->prefetchData = $this->read($sessionId); + $this->prefetchId = $sessionId; + + return '' !== $this->prefetchData; + } + + /** + * {@inheritdoc} + */ + public function read($sessionId) + { + if (null !== $this->prefetchId) { + $prefetchId = $this->prefetchId; + $prefetchData = $this->prefetchData; + $this->prefetchId = $this->prefetchData = null; + + if ($prefetchId === $sessionId || '' === $prefetchData) { + $this->newSessionId = '' === $prefetchData ? $sessionId : null; + + return $prefetchData; + } + } + + $data = $this->doRead($sessionId); + $this->newSessionId = '' === $data ? $sessionId : null; + if (\PHP_VERSION_ID < 70000) { + $this->prefetchData = $data; + } + + return $data; + } + + /** + * {@inheritdoc} + */ + public function write($sessionId, $data) + { + if (\PHP_VERSION_ID < 70000 && $this->prefetchData) { + $readData = $this->prefetchData; + $this->prefetchData = null; + + if ($readData === $data) { + return $this->updateTimestamp($sessionId, $data); + } + } + if (null === $this->igbinaryEmptyData) { + // see https://github.com/igbinary/igbinary/issues/146 + $this->igbinaryEmptyData = \function_exists('igbinary_serialize') ? igbinary_serialize(array()) : ''; + } + if ('' === $data || $this->igbinaryEmptyData === $data) { + return $this->destroy($sessionId); + } + $this->newSessionId = null; + + return $this->doWrite($sessionId, $data); + } + + /** + * {@inheritdoc} + */ + public function destroy($sessionId) + { + if (\PHP_VERSION_ID < 70000) { + $this->prefetchData = null; + } + if (!headers_sent() && ini_get('session.use_cookies')) { + if (!$this->sessionName) { + throw new \LogicException(sprintf('Session name cannot be empty, did you forget to call "parent::open()" in "%s"?.', get_class($this))); + } + $sessionCookie = sprintf(' %s=', urlencode($this->sessionName)); + $sessionCookieWithId = sprintf('%s%s;', $sessionCookie, urlencode($sessionId)); + $sessionCookieFound = false; + $otherCookies = array(); + foreach (headers_list() as $h) { + if (0 !== stripos($h, 'Set-Cookie:')) { + continue; + } + if (11 === strpos($h, $sessionCookie, 11)) { + $sessionCookieFound = true; + + if (11 !== strpos($h, $sessionCookieWithId, 11)) { + $otherCookies[] = $h; + } + } else { + $otherCookies[] = $h; + } + } + if ($sessionCookieFound) { + header_remove('Set-Cookie'); + foreach ($otherCookies as $h) { + header('Set-Cookie:'.$h, false); + } + } else { + setcookie($this->sessionName, '', 0, ini_get('session.cookie_path'), ini_get('session.cookie_domain'), ini_get('session.cookie_secure'), ini_get('session.cookie_httponly')); + } + } + + return $this->newSessionId === $sessionId || $this->doDestroy($sessionId); + } +} diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/MemcachedSessionHandler.php b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/MemcachedSessionHandler.php index 3bbde5420d16a..a31642cc83524 100644 --- a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/MemcachedSessionHandler.php +++ b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/MemcachedSessionHandler.php @@ -19,7 +19,7 @@ * * @author Drak */ -class MemcachedSessionHandler implements \SessionHandlerInterface +class MemcachedSessionHandler extends AbstractSessionHandler { /** * @var \Memcached Memcached driver @@ -39,7 +39,7 @@ class MemcachedSessionHandler implements \SessionHandlerInterface /** * List of available options: * * prefix: The prefix to use for the memcached keys in order to avoid collision - * * expiretime: The time to live in seconds + * * expiretime: The time to live in seconds. * * @param \Memcached $memcached A \Memcached instance * @param array $options An associative array of Memcached options @@ -63,7 +63,7 @@ public function __construct(\Memcached $memcached, array $options = array()) /** * {@inheritdoc} */ - public function open($savePath, $sessionName) + public function close() { return true; } @@ -71,23 +71,23 @@ public function open($savePath, $sessionName) /** * {@inheritdoc} */ - public function close() + protected function doRead($sessionId) { - return true; + return $this->memcached->get($this->prefix.$sessionId) ?: ''; } /** * {@inheritdoc} */ - public function read($sessionId) + public function updateTimestamp($sessionId, $data) { - return $this->memcached->get($this->prefix.$sessionId) ?: ''; + return $this->memcached->touch($this->prefix.$sessionId, time() + $this->ttl); } /** * {@inheritdoc} */ - public function write($sessionId, $data) + protected function doWrite($sessionId, $data) { return $this->memcached->set($this->prefix.$sessionId, $data, time() + $this->ttl); } @@ -95,7 +95,7 @@ public function write($sessionId, $data) /** * {@inheritdoc} */ - public function destroy($sessionId) + protected function doDestroy($sessionId) { $result = $this->memcached->delete($this->prefix.$sessionId); diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/MongoDbSessionHandler.php b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/MongoDbSessionHandler.php index d532fb92c6c26..7d770421c55ef 100644 --- a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/MongoDbSessionHandler.php +++ b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/MongoDbSessionHandler.php @@ -19,7 +19,7 @@ * @see https://packagist.org/packages/mongodb/mongodb * @see http://php.net/manual/en/set.mongodb.php */ -class MongoDbSessionHandler implements \SessionHandlerInterface +class MongoDbSessionHandler extends AbstractSessionHandler { /** * @var \Mongo|\MongoClient|\MongoDB\Client @@ -43,7 +43,7 @@ class MongoDbSessionHandler implements \SessionHandlerInterface * * id_field: The field name for storing the session id [default: _id] * * data_field: The field name for storing the session data [default: data] * * time_field: The field name for storing the timestamp [default: time] - * * expiry_field: The field name for storing the expiry-timestamp [default: expires_at] + * * expiry_field: The field name for storing the expiry-timestamp [default: expires_at]. * * It is strongly recommended to put an index on the `expiry_field` for * garbage-collection. Alternatively it's possible to automatically expire @@ -92,14 +92,6 @@ public function __construct($mongo, array $options) ), $options); } - /** - * {@inheritdoc} - */ - public function open($savePath, $sessionName) - { - return true; - } - /** * {@inheritdoc} */ @@ -111,7 +103,7 @@ public function close() /** * {@inheritdoc} */ - public function destroy($sessionId) + protected function doDestroy($sessionId) { $methodName = $this->mongo instanceof \MongoDB\Client ? 'deleteOne' : 'remove'; @@ -139,7 +131,7 @@ public function gc($maxlifetime) /** * {@inheritdoc} */ - public function write($sessionId, $data) + protected function doWrite($sessionId, $data) { $expiry = $this->createDateTime(time() + (int) ini_get('session.gc_maxlifetime')); @@ -171,7 +163,34 @@ public function write($sessionId, $data) /** * {@inheritdoc} */ - public function read($sessionId) + public function updateTimestamp($sessionId, $data) + { + $expiry = $this->createDateTime(time() + (int) ini_get('session.gc_maxlifetime')); + + if ($this->mongo instanceof \MongoDB\Client) { + $methodName = 'updateOne'; + $options = array(); + } else { + $methodName = 'update'; + $options = array('multiple' => false); + } + + $this->getCollection()->$methodName( + array($this->options['id_field'] => $sessionId), + array('$set' => array( + $this->options['time_field'] => $this->createDateTime(), + $this->options['expiry_field'] => $expiry, + )), + $options + ); + + return true; + } + + /** + * {@inheritdoc} + */ + protected function doRead($sessionId) { $dbData = $this->getCollection()->findOne(array( $this->options['id_field'] => $sessionId, diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/NativeSessionHandler.php b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/NativeSessionHandler.php index daa7dbd15b7c8..9ea4629ca16b3 100644 --- a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/NativeSessionHandler.php +++ b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/NativeSessionHandler.php @@ -11,12 +11,14 @@ namespace Symfony\Component\HttpFoundation\Session\Storage\Handler; -@trigger_error('The '.__NAMESPACE__.'\NativeSessionHandler class is deprecated since version 3.4 and will be removed in 4.0. Use the \SessionHandler class instead.', E_USER_DEPRECATED); - /** * @deprecated since version 3.4, to be removed in 4.0. Use \SessionHandler instead. * @see http://php.net/sessionhandler */ class NativeSessionHandler extends \SessionHandler { + public function __construct() + { + @trigger_error('The '.__NAMESPACE__.'\NativeSessionHandler class is deprecated since version 3.4 and will be removed in 4.0. Use the \SessionHandler class instead.', E_USER_DEPRECATED); + } } diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/NullSessionHandler.php b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/NullSessionHandler.php index 981d96d93a978..8d193155b090f 100644 --- a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/NullSessionHandler.php +++ b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/NullSessionHandler.php @@ -16,12 +16,12 @@ * * @author Drak */ -class NullSessionHandler implements \SessionHandlerInterface +class NullSessionHandler extends AbstractSessionHandler { /** * {@inheritdoc} */ - public function open($savePath, $sessionName) + public function close() { return true; } @@ -29,7 +29,7 @@ public function open($savePath, $sessionName) /** * {@inheritdoc} */ - public function close() + public function validateId($sessionId) { return true; } @@ -37,7 +37,7 @@ public function close() /** * {@inheritdoc} */ - public function read($sessionId) + protected function doRead($sessionId) { return ''; } @@ -45,7 +45,15 @@ public function read($sessionId) /** * {@inheritdoc} */ - public function write($sessionId, $data) + public function updateTimestamp($sessionId, $data) + { + return true; + } + + /** + * {@inheritdoc} + */ + protected function doWrite($sessionId, $data) { return true; } @@ -53,7 +61,7 @@ public function write($sessionId, $data) /** * {@inheritdoc} */ - public function destroy($sessionId) + protected function doDestroy($sessionId) { return true; } diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/PdoSessionHandler.php b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/PdoSessionHandler.php index 5cdac639399f0..19bf6e9bcaeef 100644 --- a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/PdoSessionHandler.php +++ b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/PdoSessionHandler.php @@ -38,7 +38,7 @@ * @author Michael Williams * @author Tobias Schultze */ -class PdoSessionHandler implements \SessionHandlerInterface +class PdoSessionHandler extends AbstractSessionHandler { /** * No locking is done. This means sessions are prone to loss of data due to @@ -260,11 +260,13 @@ public function isSessionExpired() */ public function open($savePath, $sessionName) { + $this->sessionExpired = false; + if (null === $this->pdo) { $this->connect($this->dsn ?: $savePath); } - return true; + return parent::open($savePath, $sessionName); } /** @@ -273,7 +275,7 @@ public function open($savePath, $sessionName) public function read($sessionId) { try { - return $this->doRead($sessionId); + return parent::read($sessionId); } catch (\PDOException $e) { $this->rollback(); @@ -296,7 +298,7 @@ public function gc($maxlifetime) /** * {@inheritdoc} */ - public function destroy($sessionId) + protected function doDestroy($sessionId) { // delete the record associated with this id $sql = "DELETE FROM $this->table WHERE $this->idCol = :id"; @@ -317,7 +319,7 @@ public function destroy($sessionId) /** * {@inheritdoc} */ - public function write($sessionId, $data) + protected function doWrite($sessionId, $data) { $maxlifetime = (int) ini_get('session.gc_maxlifetime'); @@ -372,6 +374,30 @@ public function write($sessionId, $data) return true; } + /** + * {@inheritdoc} + */ + public function updateTimestamp($sessionId, $data) + { + $maxlifetime = (int) ini_get('session.gc_maxlifetime'); + + try { + $updateStmt = $this->pdo->prepare( + "UPDATE $this->table SET $this->lifetimeCol = :lifetime, $this->timeCol = :time WHERE $this->idCol = :id" + ); + $updateStmt->bindParam(':id', $sessionId, \PDO::PARAM_STR); + $updateStmt->bindParam(':lifetime', $maxlifetime, \PDO::PARAM_INT); + $updateStmt->bindValue(':time', time(), \PDO::PARAM_INT); + $updateStmt->execute(); + } catch (\PDOException $e) { + $this->rollback(); + + throw $e; + } + + return true; + } + /** * {@inheritdoc} */ @@ -491,10 +517,8 @@ private function rollback() * * @return string The session data */ - private function doRead($sessionId) + protected function doRead($sessionId) { - $this->sessionExpired = false; - if (self::LOCK_ADVISORY === $this->lockMode) { $this->unlockStatements[] = $this->doAdvisoryLock($sessionId); } @@ -517,7 +541,9 @@ private function doRead($sessionId) return is_resource($sessionRows[0][0]) ? stream_get_contents($sessionRows[0][0]) : $sessionRows[0][0]; } - if (self::LOCK_TRANSACTIONAL === $this->lockMode && 'sqlite' !== $this->driver) { + if (!ini_get('session.use_strict_mode') && self::LOCK_TRANSACTIONAL === $this->lockMode && 'sqlite' !== $this->driver) { + // In strict mode, session fixation is not possible: new sessions always start with a unique + // random id, so that concurrency is not possible and this code path can be skipped. // Exclusive-reading of non-existent rows does not block, so we need to do an insert to block // until other connections to the session are committed. try { diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/StrictSessionHandler.php b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/StrictSessionHandler.php new file mode 100644 index 0000000000000..1bad0641e81b1 --- /dev/null +++ b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/StrictSessionHandler.php @@ -0,0 +1,89 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session\Storage\Handler; + +/** + * Adds basic `SessionUpdateTimestampHandlerInterface` behaviors to another `SessionHandlerInterface`. + * + * @author Nicolas Grekas + */ +class StrictSessionHandler extends AbstractSessionHandler +{ + private $handler; + + public function __construct(\SessionHandlerInterface $handler) + { + if ($handler instanceof \SessionUpdateTimestampHandlerInterface) { + throw new \LogicException(sprintf('"%s" is already an instance of "SessionUpdateTimestampHandlerInterface", you cannot wrap it with "%s".', get_class($handler), self::class)); + } + + $this->handler = $handler; + } + + /** + * {@inheritdoc} + */ + public function open($savePath, $sessionName) + { + parent::open($savePath, $sessionName); + + return $this->handler->open($savePath, $sessionName); + } + + /** + * {@inheritdoc} + */ + protected function doRead($sessionId) + { + return $this->handler->read($sessionId); + } + + /** + * {@inheritdoc} + */ + public function updateTimestamp($sessionId, $data) + { + return $this->write($sessionId, $data); + } + + /** + * {@inheritdoc} + */ + protected function doWrite($sessionId, $data) + { + return $this->handler->write($sessionId, $data); + } + + /** + * {@inheritdoc} + */ + protected function doDestroy($sessionId) + { + return $this->handler->destroy($sessionId); + } + + /** + * {@inheritdoc} + */ + public function close() + { + return $this->handler->close(); + } + + /** + * {@inheritdoc} + */ + public function gc($maxlifetime) + { + return $this->handler->gc($maxlifetime); + } +} diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/WriteCheckSessionHandler.php b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/WriteCheckSessionHandler.php index d49c36cae5876..638a633076e38 100644 --- a/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/WriteCheckSessionHandler.php +++ b/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/WriteCheckSessionHandler.php @@ -11,10 +11,14 @@ namespace Symfony\Component\HttpFoundation\Session\Storage\Handler; +@trigger_error(sprintf('The %s class is deprecated since version 3.4 and will be removed in 4.0. Implement `SessionUpdateTimestampHandlerInterface` or extend `AbstractSessionHandler` instead.', WriteCheckSessionHandler::class), E_USER_DEPRECATED); + /** * Wraps another SessionHandlerInterface to only write the session when it has been modified. * * @author Adrien Brault + * + * @deprecated since version 3.4, to be removed in 4.0. Implement `SessionUpdateTimestampHandlerInterface` or extend `AbstractSessionHandler` instead. */ class WriteCheckSessionHandler implements \SessionHandlerInterface { diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/NativeSessionStorage.php b/src/Symfony/Component/HttpFoundation/Session/Storage/NativeSessionStorage.php index 623d38721bcf9..092d21830d732 100644 --- a/src/Symfony/Component/HttpFoundation/Session/Storage/NativeSessionStorage.php +++ b/src/Symfony/Component/HttpFoundation/Session/Storage/NativeSessionStorage.php @@ -11,8 +11,8 @@ namespace Symfony\Component\HttpFoundation\Session\Storage; -use Symfony\Component\Debug\Exception\ContextErrorException; use Symfony\Component\HttpFoundation\Session\SessionBagInterface; +use Symfony\Component\HttpFoundation\Session\Storage\Handler\StrictSessionHandler; use Symfony\Component\HttpFoundation\Session\Storage\Proxy\AbstractProxy; use Symfony\Component\HttpFoundation\Session\Storage\Proxy\SessionHandlerProxy; @@ -26,7 +26,7 @@ class NativeSessionStorage implements SessionStorageInterface /** * @var SessionBagInterface[] */ - protected $bags; + protected $bags = array(); /** * @var bool @@ -100,9 +100,9 @@ class NativeSessionStorage implements SessionStorageInterface public function __construct(array $options = array(), $handler = null, MetadataBag $metaBag = null) { $options += array( - // disable by default because it's managed by HeaderBag (if used) - 'cache_limiter' => '', + 'cache_limiter' => 'private_no_expire', 'use_cookies' => 1, + 'lazy_write' => 1, ); session_register_shutdown(); @@ -217,15 +217,31 @@ public function regenerate($destroy = false, $lifetime = null) */ public function save() { + $session = $_SESSION; + + foreach ($this->bags as $bag) { + if (empty($_SESSION[$key = $bag->getStorageKey()])) { + unset($_SESSION[$key]); + } + } + if (array($key = $this->metadataBag->getStorageKey()) === array_keys($_SESSION)) { + unset($_SESSION[$key]); + } + // Register custom error handler to catch a possible failure warning during session write - set_error_handler(function ($errno, $errstr, $errfile, $errline, $errcontext) { - throw new ContextErrorException($errstr, $errno, E_WARNING, $errfile, $errline, $errcontext); + set_error_handler(function ($errno, $errstr, $errfile, $errline) { + throw new \ErrorException($errstr, $errno, E_WARNING, $errfile, $errline); }, E_WARNING); try { + $e = null; session_write_close(); + } catch (\ErrorException $e) { + } finally { restore_error_handler(); - } catch (ContextErrorException $e) { + $_SESSION = $session; + } + if (null !== $e) { // The default PHP error message is not very helpful, as it does not give any information on the current save handler. // Therefore, we catch this error and trigger a warning with a better error message $handler = $this->getSaveHandler(); @@ -233,7 +249,6 @@ public function save() $handler = $handler->getHandler(); } - restore_error_handler(); trigger_error(sprintf('session_write_close(): Failed to write session data with %s handler', get_class($handler)), E_USER_WARNING); } @@ -386,13 +401,6 @@ public function setSaveHandler($saveHandler = null) throw new \InvalidArgumentException('Must be instance of AbstractProxy; implement \SessionHandlerInterface; or be null.'); } - if ($saveHandler instanceof AbstractProxy) { - @trigger_error( - 'Using session save handlers that are instances of AbstractProxy is deprecated since version 3.4 and will be removed in 4.0.', - E_USER_DEPRECATED - ); - } - if (headers_sent($file, $line)) { throw new \RuntimeException(sprintf('Failed to set the session handler because headers have already been sent by "%s" at line %d.', $file, $line)); } @@ -401,11 +409,13 @@ public function setSaveHandler($saveHandler = null) if (!$saveHandler instanceof AbstractProxy && $saveHandler instanceof \SessionHandlerInterface) { $saveHandler = new SessionHandlerProxy($saveHandler); } elseif (!$saveHandler instanceof AbstractProxy) { - $saveHandler = new SessionHandlerProxy(new \SessionHandler()); + $saveHandler = new SessionHandlerProxy(new StrictSessionHandler(new \SessionHandler())); } $this->saveHandler = $saveHandler; - if ($this->saveHandler instanceof \SessionHandlerInterface) { + if ($this->saveHandler instanceof SessionHandlerProxy) { + session_set_save_handler($this->saveHandler->getHandler(), false); + } elseif ($this->saveHandler instanceof \SessionHandlerInterface) { session_set_save_handler($this->saveHandler, false); } } diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/Proxy/AbstractProxy.php b/src/Symfony/Component/HttpFoundation/Session/Storage/Proxy/AbstractProxy.php index c1c8b9b1f7dd2..09c92483c7575 100644 --- a/src/Symfony/Component/HttpFoundation/Session/Storage/Proxy/AbstractProxy.php +++ b/src/Symfony/Component/HttpFoundation/Session/Storage/Proxy/AbstractProxy.php @@ -11,11 +11,7 @@ namespace Symfony\Component\HttpFoundation\Session\Storage\Proxy; -@trigger_error('The '.__NAMESPACE__.'\AbstractProxy class is deprecated since version 3.4 and will be removed in 4.0. Use your session handler implementation directly.', E_USER_DEPRECATED); - /** - * @deprecated since version 3.4, to be removed in 4.0. Use your session handler implementation directly. - * * @author Drak */ abstract class AbstractProxy diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/Proxy/SessionHandlerProxy.php b/src/Symfony/Component/HttpFoundation/Session/Storage/Proxy/SessionHandlerProxy.php index d6adef82dbf65..359bb877b5e30 100644 --- a/src/Symfony/Component/HttpFoundation/Session/Storage/Proxy/SessionHandlerProxy.php +++ b/src/Symfony/Component/HttpFoundation/Session/Storage/Proxy/SessionHandlerProxy.php @@ -11,11 +11,7 @@ namespace Symfony\Component\HttpFoundation\Session\Storage\Proxy; -@trigger_error('The '.__NAMESPACE__.'\SessionHandlerProxy class is deprecated since version 3.4 and will be removed in 4.0. Use your session handler implementation directly.', E_USER_DEPRECATED); - /** - * @deprecated since version 3.4, to be removed in 4.0. Use your session handler implementation directly. - * * @author Drak */ class SessionHandlerProxy extends AbstractProxy implements \SessionHandlerInterface diff --git a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/AbstractSessionHandlerTest.php b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/AbstractSessionHandlerTest.php new file mode 100644 index 0000000000000..3ac081e3884c1 --- /dev/null +++ b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/AbstractSessionHandlerTest.php @@ -0,0 +1,61 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Tests\Session\Storage\Handler; + +use PHPUnit\Framework\TestCase; + +/** + * @requires PHP 7.0 + */ +class AbstractSessionHandlerTest extends TestCase +{ + private static $server; + + public static function setUpBeforeClass() + { + $spec = array( + 1 => array('file', '/dev/null', 'w'), + 2 => array('file', '/dev/null', 'w'), + ); + if (!self::$server = @proc_open('exec php -S localhost:8053', $spec, $pipes, __DIR__.'/Fixtures')) { + self::markTestSkipped('PHP server unable to start.'); + } + sleep(1); + } + + public static function tearDownAfterClass() + { + if (self::$server) { + proc_terminate(self::$server); + proc_close(self::$server); + } + } + + /** + * @dataProvider provideSession + */ + public function testSession($fixture) + { + $context = array('http' => array('header' => "Cookie: sid=123abc\r\n")); + $context = stream_context_create($context); + $result = file_get_contents(sprintf('http://localhost:8053/%s.php', $fixture), false, $context); + + $this->assertStringEqualsFile(__DIR__.sprintf('/Fixtures/%s.expected', $fixture), $result); + } + + public function provideSession() + { + foreach (glob(__DIR__.'/Fixtures/*.php') as $file) { + yield array(pathinfo($file, PATHINFO_FILENAME)); + } + } +} diff --git a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/common.inc b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/common.inc new file mode 100644 index 0000000000000..5c183acfff324 --- /dev/null +++ b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/common.inc @@ -0,0 +1,152 @@ +data = $data; + } + + public function open($path, $name) + { + echo __FUNCTION__, "\n"; + + return parent::open($path, $name); + } + + public function validateId($sessionId) + { + echo __FUNCTION__, "\n"; + + return parent::validateId($sessionId); + } + + /** + * {@inheritdoc} + */ + public function read($sessionId) + { + echo __FUNCTION__, "\n"; + + return parent::read($sessionId); + } + + /** + * {@inheritdoc} + */ + public function updateTimestamp($sessionId, $data) + { + echo __FUNCTION__, "\n"; + + return true; + } + + /** + * {@inheritdoc} + */ + public function write($sessionId, $data) + { + echo __FUNCTION__, "\n"; + + return parent::write($sessionId, $data); + } + + /** + * {@inheritdoc} + */ + public function destroy($sessionId) + { + echo __FUNCTION__, "\n"; + + return parent::destroy($sessionId); + } + + public function close() + { + echo __FUNCTION__, "\n"; + + return true; + } + + public function gc($maxLifetime) + { + echo __FUNCTION__, "\n"; + + return true; + } + + protected function doRead($sessionId) + { + echo __FUNCTION__.': ', $this->data, "\n"; + + return $this->data; + } + + protected function doWrite($sessionId, $data) + { + echo __FUNCTION__.': ', $data, "\n"; + + return true; + } + + protected function doDestroy($sessionId) + { + echo __FUNCTION__, "\n"; + + return true; + } +} diff --git a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/empty_destroys.expected b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/empty_destroys.expected new file mode 100644 index 0000000000000..1720bf0558386 --- /dev/null +++ b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/empty_destroys.expected @@ -0,0 +1,17 @@ +open +validateId +read +doRead: abc|i:123; +read + +write +destroy +doDestroy +close +Array +( + [0] => Content-Type: text/plain; charset=utf-8 + [1] => Cache-Control: private, max-age=10800 + [2] => Set-Cookie: sid=deleted; expires=Thu, 01-Jan-1970 00:00:01 GMT; Max-Age=0; path=/; secure; HttpOnly +) +shutdown diff --git a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/empty_destroys.php b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/empty_destroys.php new file mode 100644 index 0000000000000..3cfc1250adad1 --- /dev/null +++ b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/empty_destroys.php @@ -0,0 +1,8 @@ + Content-Type: text/plain; charset=utf-8 + [1] => Cache-Control: private, max-age=10800 +) +shutdown diff --git a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/read_only.php b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/read_only.php new file mode 100644 index 0000000000000..3e62fb9ecbbdd --- /dev/null +++ b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/read_only.php @@ -0,0 +1,8 @@ + Content-Type: text/plain; charset=utf-8 + [1] => Cache-Control: private, max-age=10800 + [2] => Set-Cookie: sid=random_session_id; path=/; secure; HttpOnly +) +shutdown diff --git a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/regenerate.php b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/regenerate.php new file mode 100644 index 0000000000000..a0f635c8712ec --- /dev/null +++ b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/regenerate.php @@ -0,0 +1,10 @@ + bar +) +$_SESSION is not empty +write +destroy +close +$_SESSION is not empty +Array +( + [0] => Content-Type: text/plain; charset=utf-8 + [1] => Cache-Control: private, max-age=10800 +) +shutdown diff --git a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/storage.php b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/storage.php new file mode 100644 index 0000000000000..96dca3c2c0006 --- /dev/null +++ b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/storage.php @@ -0,0 +1,24 @@ +setSaveHandler(new TestSessionHandler()); +$flash = new FlashBag(); +$storage->registerBag($flash); +$storage->start(); + +$flash->add('foo', 'bar'); + +print_r($flash->get('foo')); +echo empty($_SESSION) ? '$_SESSION is empty' : '$_SESSION is not empty'; +echo "\n"; + +$storage->save(); + +echo empty($_SESSION) ? '$_SESSION is empty' : '$_SESSION is not empty'; + +ob_start(function ($buffer) { return str_replace(session_id(), 'random_session_id', $buffer); }); diff --git a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/with_cookie.expected b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/with_cookie.expected new file mode 100644 index 0000000000000..47ae4da82449d --- /dev/null +++ b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/with_cookie.expected @@ -0,0 +1,15 @@ +open +validateId +read +doRead: abc|i:123; +read + +updateTimestamp +close +Array +( + [0] => Content-Type: text/plain; charset=utf-8 + [1] => Cache-Control: private, max-age=10800 + [2] => Set-Cookie: abc=def +) +shutdown diff --git a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/with_cookie.php b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/with_cookie.php new file mode 100644 index 0000000000000..ffb5b20a3774e --- /dev/null +++ b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/Fixtures/with_cookie.php @@ -0,0 +1,8 @@ +markTestSkipped('PHPUnit_MockObject cannot mock the PDOStatement class on HHVM. See https://github.com/sebastianbergmann/phpunit-mock-objects/pull/289'); } + if (ini_get('session.use_strict_mode')) { + $this->markTestSkipped('Strict mode needs no locking for new sessions.'); + } $pdo = new MockPdo('pgsql'); $selectStmt = $this->getMockBuilder('PDOStatement')->getMock(); diff --git a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/StrictSessionHandlerTest.php b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/StrictSessionHandlerTest.php new file mode 100644 index 0000000000000..9d2c1949f3d72 --- /dev/null +++ b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/StrictSessionHandlerTest.php @@ -0,0 +1,189 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Tests\Session\Storage\Handler; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpFoundation\Session\Storage\Handler\AbstractSessionHandler; +use Symfony\Component\HttpFoundation\Session\Storage\Handler\StrictSessionHandler; + +class StrictSessionHandlerTest extends TestCase +{ + public function testOpen() + { + $handler = $this->getMockBuilder('SessionHandlerInterface')->getMock(); + $handler->expects($this->once())->method('open') + ->with('path', 'name')->willReturn(true); + $proxy = new StrictSessionHandler($handler); + + $this->assertInstanceof('SessionUpdateTimestampHandlerInterface', $proxy); + $this->assertInstanceof(AbstractSessionHandler::class, $proxy); + $this->assertTrue($proxy->open('path', 'name')); + } + + public function testCloseSession() + { + $handler = $this->getMockBuilder('SessionHandlerInterface')->getMock(); + $handler->expects($this->once())->method('close') + ->willReturn(true); + $proxy = new StrictSessionHandler($handler); + + $this->assertTrue($proxy->close()); + } + + public function testValidateIdOK() + { + $handler = $this->getMockBuilder('SessionHandlerInterface')->getMock(); + $handler->expects($this->once())->method('read') + ->with('id')->willReturn('data'); + $proxy = new StrictSessionHandler($handler); + + $this->assertTrue($proxy->validateId('id')); + } + + public function testValidateIdKO() + { + $handler = $this->getMockBuilder('SessionHandlerInterface')->getMock(); + $handler->expects($this->once())->method('read') + ->with('id')->willReturn(''); + $proxy = new StrictSessionHandler($handler); + + $this->assertFalse($proxy->validateId('id')); + } + + public function testRead() + { + $handler = $this->getMockBuilder('SessionHandlerInterface')->getMock(); + $handler->expects($this->once())->method('read') + ->with('id')->willReturn('data'); + $proxy = new StrictSessionHandler($handler); + + $this->assertSame('data', $proxy->read('id')); + } + + public function testReadWithValidateIdOK() + { + $handler = $this->getMockBuilder('SessionHandlerInterface')->getMock(); + $handler->expects($this->once())->method('read') + ->with('id')->willReturn('data'); + $proxy = new StrictSessionHandler($handler); + + $this->assertTrue($proxy->validateId('id')); + $this->assertSame('data', $proxy->read('id')); + } + + public function testReadWithValidateIdMismatch() + { + $handler = $this->getMockBuilder('SessionHandlerInterface')->getMock(); + $handler->expects($this->exactly(2))->method('read') + ->withConsecutive(array('id1'), array('id2')) + ->will($this->onConsecutiveCalls('data1', 'data2')); + $proxy = new StrictSessionHandler($handler); + + $this->assertTrue($proxy->validateId('id1')); + $this->assertSame('data2', $proxy->read('id2')); + } + + public function testUpdateTimestamp() + { + $handler = $this->getMockBuilder('SessionHandlerInterface')->getMock(); + $handler->expects($this->once())->method('write') + ->with('id', 'data')->willReturn(true); + $proxy = new StrictSessionHandler($handler); + + $this->assertTrue($proxy->updateTimestamp('id', 'data')); + } + + public function testWrite() + { + $handler = $this->getMockBuilder('SessionHandlerInterface')->getMock(); + $handler->expects($this->once())->method('write') + ->with('id', 'data')->willReturn(true); + $proxy = new StrictSessionHandler($handler); + + $this->assertTrue($proxy->write('id', 'data')); + } + + public function testWriteEmptyNewSession() + { + $handler = $this->getMockBuilder('SessionHandlerInterface')->getMock(); + $handler->expects($this->once())->method('read') + ->with('id')->willReturn(''); + $handler->expects($this->never())->method('write'); + $handler->expects($this->never())->method('destroy'); + $proxy = new StrictSessionHandler($handler); + + $this->assertFalse($proxy->validateId('id')); + $this->assertSame('', $proxy->read('id')); + $this->assertTrue($proxy->write('id', '')); + } + + public function testWriteEmptyExistingSession() + { + $handler = $this->getMockBuilder('SessionHandlerInterface')->getMock(); + $handler->expects($this->once())->method('read') + ->with('id')->willReturn('data'); + $handler->expects($this->never())->method('write'); + $handler->expects($this->once())->method('destroy')->willReturn(true); + $proxy = new StrictSessionHandler($handler); + + $this->assertSame('data', $proxy->read('id')); + $this->assertTrue($proxy->write('id', '')); + } + + public function testDestroy() + { + $handler = $this->getMockBuilder('SessionHandlerInterface')->getMock(); + $handler->expects($this->once())->method('destroy') + ->with('id')->willReturn(true); + $proxy = new StrictSessionHandler($handler); + + $this->assertTrue($proxy->destroy('id')); + } + + public function testDestroyNewSession() + { + $handler = $this->getMockBuilder('SessionHandlerInterface')->getMock(); + $handler->expects($this->once())->method('read') + ->with('id')->willReturn(''); + $handler->expects($this->never())->method('destroy'); + $proxy = new StrictSessionHandler($handler); + + $this->assertSame('', $proxy->read('id')); + $this->assertTrue($proxy->destroy('id')); + } + + public function testDestroyNonEmptyNewSession() + { + $handler = $this->getMockBuilder('SessionHandlerInterface')->getMock(); + $handler->expects($this->once())->method('read') + ->with('id')->willReturn(''); + $handler->expects($this->once())->method('write') + ->with('id', 'data')->willReturn(true); + $handler->expects($this->once())->method('destroy') + ->with('id')->willReturn(true); + $proxy = new StrictSessionHandler($handler); + + $this->assertSame('', $proxy->read('id')); + $this->assertTrue($proxy->write('id', 'data')); + $this->assertTrue($proxy->destroy('id')); + } + + public function testGc() + { + $handler = $this->getMockBuilder('SessionHandlerInterface')->getMock(); + $handler->expects($this->once())->method('gc') + ->with(123)->willReturn(true); + $proxy = new StrictSessionHandler($handler); + + $this->assertTrue($proxy->gc(123)); + } +} diff --git a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/WriteCheckSessionHandlerTest.php b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/WriteCheckSessionHandlerTest.php index 5e41a4743edd5..898a7d11a5ae2 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/WriteCheckSessionHandlerTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Handler/WriteCheckSessionHandlerTest.php @@ -16,6 +16,8 @@ /** * @author Adrien Brault + * + * @group legacy */ class WriteCheckSessionHandlerTest extends TestCase { diff --git a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/NativeSessionStorageTest.php b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/NativeSessionStorageTest.php index 818c63a9d2ae2..93de175fd3c67 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/NativeSessionStorageTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/NativeSessionStorageTest.php @@ -14,7 +14,7 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\HttpFoundation\Session\Attribute\AttributeBag; use Symfony\Component\HttpFoundation\Session\Flash\FlashBag; -use Symfony\Component\HttpFoundation\Session\Storage\Handler\NativeSessionHandler; +use Symfony\Component\HttpFoundation\Session\Storage\Handler\NativeFileSessionHandler; use Symfony\Component\HttpFoundation\Session\Storage\Handler\NullSessionHandler; use Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage; use Symfony\Component\HttpFoundation\Session\Storage\Proxy\SessionHandlerProxy; @@ -152,7 +152,7 @@ public function testDefaultSessionCacheLimiter() $this->iniSet('session.cache_limiter', 'nocache'); $storage = new NativeSessionStorage(); - $this->assertEquals('', ini_get('session.cache_limiter')); + $this->assertEquals('private_no_expire', ini_get('session.cache_limiter')); } public function testExplicitSessionCacheLimiter() @@ -201,9 +201,9 @@ public function testSetSaveHandler() $this->assertInstanceOf('Symfony\Component\HttpFoundation\Session\Storage\Proxy\SessionHandlerProxy', $storage->getSaveHandler()); $storage->setSaveHandler(null); $this->assertInstanceOf('Symfony\Component\HttpFoundation\Session\Storage\Proxy\SessionHandlerProxy', $storage->getSaveHandler()); - $storage->setSaveHandler(new SessionHandlerProxy(new NativeSessionHandler())); + $storage->setSaveHandler(new SessionHandlerProxy(new NativeFileSessionHandler())); $this->assertInstanceOf('Symfony\Component\HttpFoundation\Session\Storage\Proxy\SessionHandlerProxy', $storage->getSaveHandler()); - $storage->setSaveHandler(new NativeSessionHandler()); + $storage->setSaveHandler(new NativeFileSessionHandler()); $this->assertInstanceOf('Symfony\Component\HttpFoundation\Session\Storage\Proxy\SessionHandlerProxy', $storage->getSaveHandler()); $storage->setSaveHandler(new SessionHandlerProxy(new NullSessionHandler())); $this->assertInstanceOf('Symfony\Component\HttpFoundation\Session\Storage\Proxy\SessionHandlerProxy', $storage->getSaveHandler()); diff --git a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Proxy/AbstractProxyTest.php b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Proxy/AbstractProxyTest.php index f2be722c8f522..cbb291f19fc3f 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Proxy/AbstractProxyTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Proxy/AbstractProxyTest.php @@ -18,8 +18,6 @@ /** * Test class for AbstractProxy. * - * @group legacy - * * @author Drak */ class AbstractProxyTest extends TestCase diff --git a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Proxy/SessionHandlerProxyTest.php b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Proxy/SessionHandlerProxyTest.php index fdd1dae254198..682825356a724 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Proxy/SessionHandlerProxyTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/Session/Storage/Proxy/SessionHandlerProxyTest.php @@ -21,7 +21,6 @@ * * @runTestsInSeparateProcesses * @preserveGlobalState disabled - * @group legacy */ class SessionHandlerProxyTest extends TestCase { diff --git a/src/Symfony/Component/HttpFoundation/composer.json b/src/Symfony/Component/HttpFoundation/composer.json index 4f13511e9ac7a..f6c6f2e623fe6 100644 --- a/src/Symfony/Component/HttpFoundation/composer.json +++ b/src/Symfony/Component/HttpFoundation/composer.json @@ -17,7 +17,8 @@ ], "require": { "php": "^5.5.9|>=7.0.8", - "symfony/polyfill-mbstring": "~1.1" + "symfony/polyfill-mbstring": "~1.1", + "symfony/polyfill-php70": "~1.6" }, "require-dev": { "symfony/expression-language": "~2.8|~3.0|~4.0" diff --git a/src/Symfony/Component/Security/Csrf/TokenStorage/NativeSessionTokenStorage.php b/src/Symfony/Component/Security/Csrf/TokenStorage/NativeSessionTokenStorage.php index 5ce2774114737..d441ba6ed3d02 100644 --- a/src/Symfony/Component/Security/Csrf/TokenStorage/NativeSessionTokenStorage.php +++ b/src/Symfony/Component/Security/Csrf/TokenStorage/NativeSessionTokenStorage.php @@ -97,12 +97,18 @@ public function removeToken($tokenId) $this->startSession(); } - $token = isset($_SESSION[$this->namespace][$tokenId]) - ? (string) $_SESSION[$this->namespace][$tokenId] - : null; + if (!isset($_SESSION[$this->namespace][$tokenId])) { + return; + } + + $token = (string) $_SESSION[$this->namespace][$tokenId]; unset($_SESSION[$this->namespace][$tokenId]); + if (!$_SESSION[$this->namespace]) { + unset($_SESSION[$this->namespace]); + } + return $token; } pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy