From: Michael M Slusarz Date: Fri, 12 Nov 2010 20:22:14 +0000 (-0700) Subject: Abstract storage portion of sessionhandler into a separate class X-Git-Url: https://git.internetallee.de/?a=commitdiff_plain;h=d36abc878b38c33eacd718cfd86a173800f9f3c0;p=horde.git Abstract storage portion of sessionhandler into a separate class --- diff --git a/framework/Core/lib/Horde/Core/Factory/SessionHandler.php b/framework/Core/lib/Horde/Core/Factory/SessionHandler.php index c65d48b1a..927e82f22 100644 --- a/framework/Core/lib/Horde/Core/Factory/SessionHandler.php +++ b/framework/Core/lib/Horde/Core/Factory/SessionHandler.php @@ -35,53 +35,57 @@ class Horde_Core_Factory_SessionHandler } $params = Horde::getDriverConfig('sessionhandler', $driver); - if (strcasecmp($driver, 'Sql') === 0) { - $params['db'] = $injector->getInstance('Horde_Db_Adapter'); - } elseif (strcasecmp($driver, 'Memcache') === 0) { - $params['memcache'] = $injector->getInstance('Horde_Memcache'); - } elseif (strcasecmp($driver, 'Ldap') === 0) { + $driver = basename(Horde_String::lower($driver)); + $noset = false; + + switch ($driver) { + case 'builtin': + $noset = true; + break; + + case 'ldap': $params['ldap'] = $injector->getInstances('Horde_Core_Factory_Ldap')->getLdap('horde', 'sessionhandler'); + break; + + case 'memcache': + $params['memcache'] = $injector->getInstance('Horde_Memcache'); + break; + + case 'sql': + $params['db'] = $injector->getInstance('Horde_Db_Adapter'); + break; } - $logger = $injector->getInstance('Horde_Log_Logger'); + $class = 'Horde_SessionHandler_Storage_' . Horde_String::ucfirst($driver); + if (!class_exists($class)) { + throw new Horde_SessionHandler_Exception('Driver not found: ' . $class); + } + $storage = new $class($params); if (!empty($conf['sessionhandler']['memcache']) && - (strcasecmp($driver, 'Builtin') != 0) && - (strcasecmp($driver, 'Memcache') != 0)) { - $params = array( + !in_array($driver, array('builtin', 'memcache'))) { + $storage = new Horde_SessionHandler_Storage_Stack(array( 'stack' => array( - array( - 'driver' => 'Memcache', - 'params' => array( - 'memcache' => $injector->getInstance('Horde_Memcache'), - 'logger' => $logger - ) - ), - array( - 'driver' => $driver, - 'params' => array_merge($params, array( - 'logger' => $logger - )) - ) + new Horde_SessionHandler_Storage_Memcache(array( + 'memcache' => $injector->getInstance('Horde_Memcache') + )), + $storage ) - ); - $driver = 'Stack'; + )); } - $params['logger'] = $logger; - // TODO: Uncomment once all session data is saved through - // Horde_Session. - //$params['no_md5'] = true - $params['parse'] = array($this, 'readSessionData'); - - $driver = basename(strtolower($driver)); - $class = 'Horde_SessionHandler_' . ucfirst($driver); - - if (class_exists($class)) { - return new $class($params); - } - throw new Horde_SessionHandler_Exception('Driver not found: ' . $driver); + return new Horde_SessionHandler( + $storage, + array( + 'logger' => $injector->getInstance('Horde_Log_Logger'), + // TODO: Uncomment once all session data is saved through + // Horde_Session. + //'no_md5' => true, + 'noset' => $noset, + 'parse' => array($this, 'readSessionData') + ) + ); } /** diff --git a/framework/SessionHandler/lib/Horde/SessionHandler.php b/framework/SessionHandler/lib/Horde/SessionHandler.php index a943715ef..696e23191 100644 --- a/framework/SessionHandler/lib/Horde/SessionHandler.php +++ b/framework/SessionHandler/lib/Horde/SessionHandler.php @@ -1,6 +1,6 @@ * logger - (Horde_Log_Logger) A logger instance. * DEFAULT: No logging @@ -70,16 +78,20 @@ abstract class Horde_SessionHandler * DEFAULT: No * */ - public function __construct(array $params = array()) + public function __construct(Horde_SessionHandler_Storage $storage, + array $params = array()) { $params = array_merge($this->_params, $params); if (isset($params['logger'])) { $this->_logger = $params['logger']; unset($params['logger']); + + $storage->setLogger($this->_logger); } $this->_params = $params; + $this->_storage = $storage; if (empty($this->_params['noset'])) { ini_set('session.save_handler', 'user'); @@ -117,7 +129,7 @@ abstract class Horde_SessionHandler { if (!$this->_connected) { try { - $this->_open($save_path, $session_name); + $this->_storage->open($save_path, $session_name); } catch (Horde_SessionHandler_Exception $e) { if ($this->_logger) { $this->_logger->log($e, 'ERR'); @@ -132,16 +144,6 @@ abstract class Horde_SessionHandler } /** - * Open the backend. - * - * @param string $save_path The path to the session object. - * @param string $session_name The name of the session. - * - * @throws Horde_SessionHandler_Exception - */ - abstract protected function _open($save_path = null, $session_name = null); - - /** * Close the backend. * * @return boolean True on success, false otherwise. @@ -149,7 +151,7 @@ abstract class Horde_SessionHandler public function close() { try { - $this->_close(); + $this->_storage->close(); } catch (Horde_SessionHandler_Exception $e) { if ($this->_logger) { $this->_logger->log($e, 'ERR'); @@ -162,13 +164,6 @@ abstract class Horde_SessionHandler } /** - * Close the backend. - * - * @throws Horde_SessionHandler_Exception - */ - abstract protected function _close(); - - /** * Read the data for a particular session identifier from the backend. * This method should only be called internally by PHP via * session_set_save_handler(). @@ -179,7 +174,7 @@ abstract class Horde_SessionHandler */ public function read($id) { - $result = $this->_read($id); + $result = $this->_storage->read($id); if (empty($this->_params['no_md5'])) { $this->_sig = md5($result); } @@ -187,15 +182,6 @@ abstract class Horde_SessionHandler } /** - * Read the data for a particular session identifier from the backend. - * - * @param string $id The session identifier. - * - * @return string The session data. - */ - abstract protected function _read($id); - - /** * Write session data to the backend. * This method should only be called internally by PHP via * session_set_save_handler(). @@ -210,7 +196,7 @@ abstract class Horde_SessionHandler if ($this->changed || (empty($this->_params['no_md5']) && ($this->_sig != md5($session_data)))) { - return $this->_write($id, $session_data); + return $this->_storage->write($id, $session_data); } if ($this->_logger) { @@ -221,16 +207,6 @@ abstract class Horde_SessionHandler } /** - * Write session data to the backend. - * - * @param string $id The session identifier. - * @param string $session_data The session data. - * - * @return boolean True on success, false otherwise. - */ - abstract protected function _write($id, $session_data); - - /** * Destroy the data for a particular session identifier in the backend. * This method should only be called internally by PHP via * session_set_save_handler(). @@ -239,7 +215,10 @@ abstract class Horde_SessionHandler * * @return boolean True on success, false otherwise. */ - abstract public function destroy($id); + public function destroy($id) + { + return $this->_storage->destroy($id); + } /** * Garbage collect stale sessions from the backend. @@ -250,18 +229,9 @@ abstract class Horde_SessionHandler * * @return boolean True on success, false otherwise. */ - abstract public function gc($maxlifetime = 300); - - /** - * Get session data read-only. - * - * @param string $id The session identifier. - * - * @return string The session data. - */ - protected function _readOnly($id) + public function gc($maxlifetime = 300) { - return $this->read($id); + return $this->_storage->gc($id); } /** @@ -270,7 +240,10 @@ abstract class Horde_SessionHandler * @return array A list of valid session identifiers. * @throws Horde_SessionHandler_Exception */ - abstract public function getSessionIDs(); + public function getSessionIDs() + { + return $this->_storage->getSessionIDs(); + } /** * Returns a list of authenticated users and data about their session. @@ -291,9 +264,11 @@ abstract class Horde_SessionHandler $sessions = $this->getSessionIDs(); + $this->_storage->readonly = true; + foreach ($sessions as $id) { try { - $data = $this->_readOnly($id); + $data = $this->read($id); } catch (Horde_SessionHandler_Exception $e) { continue; } @@ -304,6 +279,8 @@ abstract class Horde_SessionHandler } } + $this->_storage->readonly = false; + return $info; } diff --git a/framework/SessionHandler/lib/Horde/SessionHandler/Builtin.php b/framework/SessionHandler/lib/Horde/SessionHandler/Builtin.php deleted file mode 100644 index 72a77d093..000000000 --- a/framework/SessionHandler/lib/Horde/SessionHandler/Builtin.php +++ /dev/null @@ -1,146 +0,0 @@ - - * @category Horde - * @package SessionHandler - */ -class Horde_SessionHandler_Builtin extends Horde_SessionHandler -{ - /** - * Directory with session files. - * - * @var string - */ - protected $_path; - - /** - * Constructor. - * - * @param array $params Parameters. - */ - public function __construct(array $params = array()) - { - parent::__construct(array_merge($params, array( - 'noset' => true - ))); - - $this->_path = session_save_path(); - if (!$this->_path) { - $this->_path = Horde_Util::getTempDir(); - } - } - - /** - * Open the backend. - * - * @param string $save_path The path to the session object. - * @param string $session_name The name of the session. - */ - protected function _open($save_path = null, $session_name = null) - { - } - - /** - * Close the backend. - * - * @throws Horde_Exception - */ - protected function _close() - { - } - - /** - * Read the data for a particular session identifier from the backend. - * - * @param string $id The session identifier. - * - * @return string The session data. - */ - protected function _read($id) - { - $file = $this->_path . '/sess_' . $id; - $session_data = @file_get_contents($file); - if (($session_data === false) && $this->_logger) { - $this->_logger->log('Unable to read file: ' . $file, 'ERR'); - } - - return strval($session_data); - } - - /** - * Write session data to the backend. - * - * @param string $id The session identifier. - * @param string $session_data The session data. - * - * @return boolean True on success, false otherwise. - */ - protected function _write($id, $session_data) - { - return false; - } - - /** - * Destroy the data for a particular session identifier in the backend. - * This method should only be called internally by PHP via - * session_set_save_handler(). - * - * @param string $id The session identifier. - * - * @return boolean True on success, false otherwise. - */ - public function destroy($id) - { - return false; - } - - /** - * Garbage collect stale sessions from the backend. - * This method should only be called internally by PHP via - * session_set_save_handler(). - * - * @param integer $maxlifetime The maximum age of a session. - * - * @return boolean True on success, false otherwise. - */ - public function gc($maxlifetime = 300) - { - return false; - } - - /** - * Get a list of the valid session identifiers. - * - * @return array A list of valid session identifiers. - */ - public function getSessionIDs() - { - $sessions = array(); - - try { - $di = new DirectoryIterator($this->_path); - } catch (UnexpectedValueException $e) { - return $sessions; - } - - foreach ($di as $val) { - /* Make sure we're dealing with files that start with sess_. */ - if ($val->isFile() && - (strpos($val->getFilename(), 'sess_') === 0)) { - $sessions[] = substr($val->getFilename(), strlen('sess_')); - } - } - - return $sessions; - } - -} diff --git a/framework/SessionHandler/lib/Horde/SessionHandler/External.php b/framework/SessionHandler/lib/Horde/SessionHandler/External.php deleted file mode 100644 index 414e31071..000000000 --- a/framework/SessionHandler/lib/Horde/SessionHandler/External.php +++ /dev/null @@ -1,125 +0,0 @@ - - * @category Horde - * @package SessionHandler - */ -class Horde_SessionHandler_External extends Horde_SessionHandler -{ - /** - * Constructor. - * - * @param array $params Required parameters: - *
-     * 'close' - (callback) See session_set_save_handler().
-     * 'destroy' - (callback) See session_set_save_handler().
-     * 'gc' - (callback) See session_set_save_handler().
-     * 'open' - (callback) See session_set_save_handler().
-     * 'read' - (callback) See session_set_save_handler().
-     * 'write' - (callback) See session_set_save_handler().
-     * 
- * - * @throws InvalidArgumentException - */ - public function __construct(array $params = array()) - { - foreach (array('open', 'close', 'read', 'write', 'destroy', 'gc') as $val) { - if (!isset($params[$val])) { - throw new InvalidArgumentException('Missing parameter: ' . $val); - } - } - - parent::__construct($params); - } - - /** - * Open the backend. - * - * @param string $save_path The path to the session object. - * @param string $session_name The name of the session. - */ - protected function _open($save_path = null, $session_name = null) - { - call_user_func($this->_params['open'], $save_path, $session_name); - } - - /** - * Close the backend. - */ - protected function _close() - { - call_user_func($this->_params['close']); - } - - /** - * Read the data for a particular session identifier from the backend. - * - * @param string $id The session identifier. - * - * @return string The session data. - */ - protected function _read($id) - { - return call_user_func($this->_params['read']); - } - - /** - * Write session data to the backend. - * - * @param string $id The session identifier. - * @param string $session_data The session data. - * - * @return boolean True on success, false otherwise. - */ - protected function _write($id, $session_data) - { - return call_user_func($this->_params['write'], $id, $session_data); - } - - /** - * Destroy the data for a particular session identifier in the backend. - * This method should only be called internally by PHP via - * session_set_save_handler(). - * - * @param string $id The session identifier. - * - * @return boolean True on success, false otherwise. - */ - public function destroy($id) - { - return call_user_func($this->_params['destroy'], $id); - } - - /** - * Garbage collect stale sessions from the backend. - * This method should only be called internally by PHP via - * session_set_save_handler(). - * - * @param integer $maxlifetime The maximum age of a session. - * - * @return boolean True on success, false otherwise. - */ - public function gc($maxlifetime = 300) - { - return call_user_func($this->_params['gc'], $maxlifetime); - } - - /** - * Get a list of the valid session identifiers. - * - * @throws Horde_SessionHandler_Exception - */ - public function getSessionIDs() - { - throw new Horde_SessionHandler_Exception('Driver does not support listing session IDs.'); - } - -} diff --git a/framework/SessionHandler/lib/Horde/SessionHandler/Memcache.php b/framework/SessionHandler/lib/Horde/SessionHandler/Memcache.php deleted file mode 100644 index 34344b7d7..000000000 --- a/framework/SessionHandler/lib/Horde/SessionHandler/Memcache.php +++ /dev/null @@ -1,300 +0,0 @@ - - * @author Michael Slusarz - * @category Horde - * @package SessionHandler - */ -class Horde_SessionHandler_Memcache extends Horde_SessionHandler -{ - /** - * Memcache object. - * - * @var Horde_Memcache - */ - protected $_memcache; - - /** - * Current session ID. - * - * @var string - */ - protected $_id; - - /** - * Do read-only get? - * - * @var boolean - */ - protected $_readonly = false; - - /** - * The ID used for session tracking. - * - * @var string - */ - protected $_trackID = 'horde_memcache_sessions_track'; - - /** - * Constructor. - * - * @param array $params Parameters: - *
-     * 'memcache' - (Horde_Memcache) [REQUIRED] A memcache object.
-     * 'track' - (boolean) Track active sessions?
-     * 'track_lifetime' - (integer) The number of seconds after which tracked
-     *                    sessions will be treated as expired.
-     * 
- * - * @throws InvalidArgumentException - */ - public function __construct(array $params = array()) - { - if (empty($params['memcache'])) { - throw new InvalidArgumentException('Missing memcache argument.'); - } - - $this->_memcache = $params['memcache']; - unset($params['memcache']); - - parent::__construct($params); - - if (empty($this->_params['track_lt'])) { - $this->_params['track_lt'] = ini_get('session.gc_maxlifetime'); - } - - if (!empty($this->_params['track']) && (rand(0, 999) == 0)) { - register_shutdown_function(array($this, 'trackGC')); - } - } - - /** - * Open the backend. - * - * @param string $save_path The path to the session object. - * @param string $session_name The name of the session. - * - * @throws Horde_SessionHandler_Exception - */ - protected function _open($save_path = null, $session_name = null) - { - } - - /** - * Close the backend. - */ - protected function _close() - { - if (isset($this->_id)) { - $this->_memcache->unlock($this->_id); - } - } - - /** - * Read the data for a particular session identifier. - * - * @param string $id The session identifier. - * - * @return string The session data. - */ - protected function _read($id) - { - if (!$this->_readonly) { - $this->_memcache->lock($id); - } - $result = $this->_memcache->get($id); - - if ($result === false) { - if (!$this->_readonly) { - $this->_memcache->unlock($id); - } - - if ($result === false) { - if ($this->_logger) { - $this->_logger->log('Error retrieving session data (id = ' . $id . ')', 'DEBUG'); - } - return false; - } - } - - if (!$this->_readonly) { - $this->_id = $id; - } - - if ($this->_logger) { - $this->_logger->log('Read session data (id = ' . $id . ')', 'DEBUG'); - } - - return $result; - } - - /** - * Write session data to the backend. - * - * @param string $id The session identifier. - * @param string $session_data The session data. - * - * @return boolean True on success, false otherwise. - */ - protected function _write($id, $session_data) - { - if (!empty($this->_params['track'])) { - // Do a replace - the only time it should fail is if we are - // writing a session for the first time. If that is the case, - // update the session tracker. - $res = $this->_memcache->replace($id, $session_data); - $track = !$res; - } else { - $res = $track = false; - } - - if (!$res && - !$this->_memcache->set($id, $session_data)) { - if ($this->_logger) { - $this->_logger->log('Error writing session data (id = ' . $id . ')', 'ERR'); - } - return false; - } - - if ($track) { - $this->_memcache->lock($this->_trackID); - $ids = $this->_memcache->get($this->_trackID); - if ($ids === false) { - $ids = array(); - } - - $ids[$id] = time(); - $this->_memcache->set($this->_trackID, $ids); - $this->_memcache->unlock($this->_trackID); - } - - if ($this->_logger) { - $this->_logger->log('Wrote session data (id = ' . $id . ')', 'DEBUG'); - } - - return true; - } - - /** - * Destroy the data for a particular session identifier. - * - * @param string $id The session identifier. - * - * @return boolean True on success, false otherwise. - */ - public function destroy($id) - { - $result = $this->_memcache->delete($id); - $this->_memcache->unlock($id); - - if ($result === false) { - if ($this->_logger) { - $this->_logger->log('Failed to delete session (id = ' . $id . ')', 'DEBUG'); - } - return false; - } - - if (!empty($this->_params['track'])) { - $this->_memcache->lock($this->_trackID); - $ids = $this->_memcache->get($this->_trackID); - if ($ids !== false) { - unset($ids[$id]); - $this->_memcache->set($this->_trackID, $ids); - } - $this->_memcache->unlock($this->_trackID); - } - - if ($this->_logger) { - $this->_logger->log('Deleted session data (id = ' . $id . ')', 'DEBUG'); - } - - return true; - } - - /** - * Garbage collect stale sessions from the backend. - * - * @param integer $maxlifetime The maximum age of a session. - * - * @return boolean True on success, false otherwise. - */ - public function gc($maxlifetime = 300) - { - // Memcache does its own garbage collection. - return true; - } - - /** - * Get a list of (possibly) valid session identifiers. - * - * @return array A list of session identifiers. - * @throws Horde_SessionHandler_Exception - */ - public function getSessionIDs() - { - if (empty($this->_params['track'])) { - throw new Horde_SessionHandler_Exception('Memcache session tracking not enabled.'); - } - - $this->trackGC(); - - $ids = $this->_memcache->get($this->_trackID); - - return ($ids === false) - ? array() - : array_keys($ids); - } - - /** - * Get session data read-only. - * - * @param string $id The session identifier. - * - * @return string The session data. - */ - protected function _readOnly($id) - { - $this->_readonly = true; - $result = $this->_memcache->get($id); - $this->_readonly = false; - - return $result; - } - - /** - * Do garbage collection for session tracking information. - */ - public function trackGC() - { - $this->_memcache->lock($this->_trackID); - $ids = $this->_memcache->get($this->_trackID); - if (empty($ids)) { - $this->_memcache->unlock($this->_trackID); - return; - } - - $tstamp = time() - $this->_params['track_lt']; - $alter = false; - - foreach ($ids as $key => $val) { - if ($tstamp > $val) { - unset($ids[$key]); - $alter = true; - } - } - - if ($alter) { - $this->_memcache->set($this->_trackID, $ids); - } - - $this->_memcache->unlock($this->_trackID); - } - -} diff --git a/framework/SessionHandler/lib/Horde/SessionHandler/Sql.php b/framework/SessionHandler/lib/Horde/SessionHandler/Sql.php deleted file mode 100644 index bf8839b99..000000000 --- a/framework/SessionHandler/lib/Horde/SessionHandler/Sql.php +++ /dev/null @@ -1,209 +0,0 @@ - - * @category Horde - * @package SessionHandler - */ -class Horde_SessionHandler_Sql extends Horde_SessionHandler -{ - /** - * Handle for the current database connection. - * - * @var Horde_Db_Adapter - */ - protected $_db; - - /** - * Constructor. - * - * @param array $params Parameters: - *
-     * 'db' - (Horde_Db_Adapter) [REQUIRED] The DB instance.
-     * 'table' - (string) The name of the sessions table.
-     *           DEFAULT: 'horde_sessionhandler'
-     * 
- * - * @throws InvalidArgumentException - */ - public function __construct(array $params = array()) - { - if (!isset($params['db'])) { - throw new InvalidArgumentException('Missing db parameter.'); - } - $this->_db = $params['db']; - unset($params['db']); - - parent::__construct(array_merge(array( - 'table' => 'horde_sessionhandler' - ), $params)); - } - - /** - * Close the backend. - * - * @throws Horde_SessionHandler_Exception - */ - protected function _close() - { - /* Close any open transactions. */ - try { - $this->_db->commitDbTransaction(); - } catch (Horde_Db_Exception $e) { - throw new Horde_SessionHandler_Exception($e); - } - } - - /** - * Read the data for a particular session identifier from the backend. - * - * @param string $id The session identifier. - * - * @return string The session data. - */ - protected function _read($id) - { - /* Begin a transaction. */ - // TODO: Rowlocking in Mysql - $this->_db->beginDbTransaction(); - - /* Build query. */ - $query = sprintf('SELECT session_data FROM %s WHERE session_id = ?', - $this->_params['table']); - $values = array($id); - - /* Execute the query. */ - try { - return $this->_db->selectValue($query, $values); - } catch (Horde_Db_Exception $e) { - return false; - } - } - - /** - * Write session data to the backend. - * - * @param string $id The session identifier. - * @param string $session_data The session data. - * - * @return boolean True on success, false otherwise. - */ - protected function _write($id, $session_data) - { - /* Build the SQL query. */ - $query = sprintf('SELECT session_id FROM %s WHERE session_id = ?', - $this->_params['table']); - $values = array($id); - - /* Execute the query. */ - try { - $result = $this->_db->selectValue($query, $values); - } catch (Horde_Db_Exception $e) { - return false; - } - - /* Build the replace SQL query. */ - $query = sprintf('REPLACE INTO %s ' . - '(session_id, session_data, session_lastmodified) ' . - 'VALUES (?, ?, ?)', - $this->_params['table']); - $values = array( - $id, - $session_data, - time() - ); - - /* Execute the replace query. */ - try { - $this->_db->update($query, $values); - $this->_db->commitDbTransaction(); - } catch (Horde_Db_Exception $e) { - $this->_db->rollbackDbTransaction(); - return false; - } - - return true; - } - - /** - * Destroy the data for a particular session identifier in the backend. - * - * @param string $id The session identifier. - * - * @return boolean True on success, false otherwise. - */ - public function destroy($id) - { - /* Build the SQL query. */ - $query = sprintf('DELETE FROM %s WHERE session_id = ?', - $this->_params['table']); - $values = array($id); - - /* Execute the query. */ - try { - $this->_db->delete($query, $values); - $this->_db->commitDbTransaction(); - } catch (Horde_Db_Exception $e) { - return false; - } - - return true; - } - - /** - * Garbage collect stale sessions from the backend. - * - * @param integer $maxlifetime The maximum age of a session. - * - * @return boolean True on success, false otherwise. - */ - public function gc($maxlifetime = 300) - { - /* Build the SQL query. */ - $query = sprintf('DELETE FROM %s WHERE session_lastmodified < ?', - $this->_params['table']); - $values = array(time() - $maxlifetime); - - /* Execute the query. */ - try { - $this->_db->delete($query, $values); - } catch (Horde_Db_Exception $e) { - return false; - } - - return true; - } - - /** - * Get a list of the valid session identifiers. - * - * @return array A list of valid session identifiers. - */ - public function getSessionIDs() - { - $this->open(); - - /* Build the SQL query. */ - $query = sprintf('SELECT session_id FROM %s' . - ' WHERE session_lastmodified >= ?', - $this->_params['table']); - $values = array(time() - ini_get('session.gc_maxlifetime')); - - /* Execute the query. */ - try { - return $this->_db->selectValues($query, $values); - } catch (Horde_Db_Exception $e) { - return array(); - } - } - -} diff --git a/framework/SessionHandler/lib/Horde/SessionHandler/Stack.php b/framework/SessionHandler/lib/Horde/SessionHandler/Stack.php deleted file mode 100644 index 0050257e0..000000000 --- a/framework/SessionHandler/lib/Horde/SessionHandler/Stack.php +++ /dev/null @@ -1,188 +0,0 @@ - - * @category Horde - * @package SessionHandler - */ -class Horde_SessionHandler_Stack extends Horde_SessionHandler -{ - /** - * Stack of sessionhandlers. - * - * @var string - */ - protected $_stack = array(); - - /** - * Constructor. - * - * @param array $params Parameters: - *
-     * 'stack' - (array) [REQUIRED] A list of sessionhandlers to loop
-     *           through, in order of priority. The last entry is considered
-     *           the "master" server.
-     *           Each value should contain an array with two keys: 'driver', a
-     *           string value with the SessionHandler driver to use, and
-     *           'params', containing any parameters needed by this driver.
-     * 
- * - * @throws InvalidArgumentException - */ - public function __construct(array $params = array()) - { - if (!isset($params['stack'])) { - throw new InvalidArgumentException('Missing stack parameter.'); - } - - foreach ($params['stack'] as $val) { - $this->_stack[] = Horde_SessionHandler::factory($val['driver'], $val['params']); - } - - unset($params['stack']); - - parent::__construct($params); - } - - /** - * Open the backend. - * - * @param string $save_path The path to the session object. - * @param string $session_name The name of the session. - * - * @throws Horde_SessionHandler_Exception - */ - protected function _open($save_path = null, $session_name = null) - { - foreach ($this->_stack as $val) { - $val->open($save_path, $session_name); - } - } - - /** - * Close the backend. - * - * @throws Horde_SessionHandler_Exception - */ - protected function _close() - { - foreach ($this->_stack as $val) { - $val->close(); - } - } - - /** - * Read the data for a particular session identifier from the backend. - * - * @param string $id The session identifier. - * - * @return string The session data. - */ - protected function _read($id) - { - foreach ($this->_stack as $val) { - $result = $val->read($id); - if ($result === false) { - break; - } - } - - return $result; - } - - /** - * Write session data to the backend. - * - * @param string $id The session identifier. - * @param string $session_data The session data. - * - * @return boolean True on success, false otherwise. - */ - protected function _write($id, $session_data) - { - /* Do writes in *reverse* order - it is OK if a write to one of the - * non-master backends fails. */ - $master = true; - - foreach (array_reverse($this->_stack) as $val) { - $result = $val->write($id, $session_data); - if ($result === false) { - if ($master) { - return false; - } - /* Attempt to invalidate cache if write failed. */ - $val->destroy($id); - } - $master = false; - } - - return true; - } - - /** - * Destroy the data for a particular session identifier in the backend. - * This method should only be called internally by PHP via - * session_set_save_handler(). - * - * @param string $id The session identifier. - * - * @return boolean True on success, false otherwise. - */ - public function destroy($id) - { - /* Only report success from master. */ - $master = $success = true; - - foreach (array_reverse($this->_stack) as $val) { - $result = $val->destroy($id); - if ($master && ($result === false)) { - $success = false; - } - $master = false; - } - - return $success; - } - - /** - * Garbage collect stale sessions from the backend. - * This method should only be called internally by PHP via - * session_set_save_handler(). - * - * @param integer $maxlifetime The maximum age of a session. - * - * @return boolean True on success, false otherwise. - */ - public function gc($maxlifetime = 300) - { - /* Only report GC results from master. */ - foreach ($this->_stack as $val) { - $result = $val->gc($maxlifetime); - } - - return $result; - } - - /** - * Get a list of the valid session identifiers. - * - * @return array A list of valid session identifiers. - * @throws Horde_SessionHandler_Exception - */ - public function getSessionIDs() - { - /* Grab session ID list from the master. */ - $ob = end($this->_stack); - return $ob->getSessionIDs(); - } - -} diff --git a/framework/SessionHandler/lib/Horde/SessionHandler/Storage.php b/framework/SessionHandler/lib/Horde/SessionHandler/Storage.php new file mode 100644 index 000000000..78fe885b1 --- /dev/null +++ b/framework/SessionHandler/lib/Horde/SessionHandler/Storage.php @@ -0,0 +1,124 @@ + + * @category Horde + * @license http://www.fsf.org/copyleft/lgpl.html LGPL + * @package SessionHandler + */ +abstract class Horde_SessionHandler_Storage +{ + /** + * Access session read-only? + * + * @var boolean + */ + public $readonly = false; + + /** + * A logger instance. + * + * @var Horde_Log_Logger + */ + protected $_logger; + + /** + * Hash containing connection parameters. + * + * @var array + */ + protected $_params = array(); + + /** + * Constructor. + * + * @param array $params Configuration parameters. + */ + public function __construct(array $params = array()) + { + $params = array_merge($this->_params, $params); + } + + /** + * Set the logger object. + * + * @param Horde_Log_Logger $log The logger instance. + */ + public function setLogger(Horde_Log_Logger $log) + { + $this->_logger = $log; + } + + /** + * Open the backend. + * + * @param string $save_path The path to the session object. + * @param string $session_name The name of the session. + * + * @throws Horde_SessionHandler_Exception + */ + abstract public function open($save_path = null, $session_name = null); + + /** + * Close the backend. + * + * @throws Horde_SessionHandler_Exception + */ + abstract public function close(); + + /** + * Read the data for a particular session identifier from the backend. + * + * @param string $id The session identifier. + * + * @return string The session data. + */ + abstract public function read($id); + + /** + * Write session data to the backend. + * + * @param string $id The session identifier. + * @param string $session_data The session data. + * + * @return boolean True on success, false otherwise. + */ + abstract public function write($id, $session_data); + + /** + * Destroy the data for a particular session identifier in the backend. + * This method should only be called internally by PHP via + * session_set_save_handler(). + * + * @param string $id The session identifier. + * + * @return boolean True on success, false otherwise. + */ + abstract public function destroy($id); + + /** + * Garbage collect stale sessions from the backend. + * This method should only be called internally by PHP via + * session_set_save_handler(). + * + * @param integer $maxlifetime The maximum age of a session. + * + * @return boolean True on success, false otherwise. + */ + abstract public function gc($maxlifetime = 300); + + /** + * Get a list of the valid session identifiers. + * + * @return array A list of valid session identifiers. + * @throws Horde_SessionHandler_Exception + */ + abstract public function getSessionIDs(); + +} diff --git a/framework/SessionHandler/lib/Horde/SessionHandler/Storage/Builtin.php b/framework/SessionHandler/lib/Horde/SessionHandler/Storage/Builtin.php new file mode 100644 index 000000000..5bf7477d5 --- /dev/null +++ b/framework/SessionHandler/lib/Horde/SessionHandler/Storage/Builtin.php @@ -0,0 +1,107 @@ + + * @category Horde + * @license http://www.fsf.org/copyleft/lgpl.html LGPL + * @package SessionHandler + */ +class Horde_SessionHandler_Storage_Builtin extends Horde_SessionHandler_Storage +{ + /** + * Directory with session files. + * + * @var string + */ + protected $_path; + + /** + */ + public function __construct(array $params = array()) + { + parent::__construct($params); + + $this->_path = session_save_path(); + if (!$this->_path) { + $this->_path = Horde_Util::getTempDir(); + } + } + + /** + */ + public function open($save_path = null, $session_name = null) + { + } + + /** + */ + public function close() + { + } + + /** + */ + public function read($id) + { + $file = $this->_path . '/sess_' . $id; + $session_data = @file_get_contents($file); + if (($session_data === false) && $this->_logger) { + $this->_logger->log('Unable to read file: ' . $file, 'ERR'); + } + + return strval($session_data); + } + + /** + */ + public function write($id, $session_data) + { + return false; + } + + /** + */ + public function destroy($id) + { + return false; + } + + /** + */ + public function gc($maxlifetime = 300) + { + return false; + } + + /** + */ + public function getSessionIDs() + { + $sessions = array(); + + try { + $di = new DirectoryIterator($this->_path); + } catch (UnexpectedValueException $e) { + return $sessions; + } + + foreach ($di as $val) { + /* Make sure we're dealing with files that start with sess_. */ + if ($val->isFile() && + (strpos($val->getFilename(), 'sess_') === 0)) { + $sessions[] = substr($val->getFilename(), strlen('sess_')); + } + } + + return $sessions; + } + +} diff --git a/framework/SessionHandler/lib/Horde/SessionHandler/Storage/External.php b/framework/SessionHandler/lib/Horde/SessionHandler/Storage/External.php new file mode 100644 index 000000000..7731921f8 --- /dev/null +++ b/framework/SessionHandler/lib/Horde/SessionHandler/Storage/External.php @@ -0,0 +1,96 @@ + + * @category Horde + * @license http://www.fsf.org/copyleft/lgpl.html LGPL + * @package SessionHandler + */ +class Horde_SessionHandler_Storage_External extends Horde_SessionHandler_Storage +{ + /** + * Constructor. + * + * @param array $params Required parameters: + *
+     * close - (callback) See session_set_save_handler().
+     * destroy - (callback) See session_set_save_handler().
+     * gc - (callback) See session_set_save_handler().
+     * open - (callback) See session_set_save_handler().
+     * read - (callback) See session_set_save_handler().
+     * write - (callback) See session_set_save_handler().
+     * 
+ * + * @throws InvalidArgumentException + */ + public function __construct(array $params = array()) + { + foreach (array('open', 'close', 'read', 'write', 'destroy', 'gc') as $val) { + if (!isset($params[$val])) { + throw new InvalidArgumentException('Missing parameter: ' . $val); + } + } + + parent::__construct($params); + } + + /** + */ + public function open($save_path = null, $session_name = null) + { + call_user_func($this->_params['open'], $save_path, $session_name); + } + + /** + */ + public function close() + { + call_user_func($this->_params['close']); + } + + /** + */ + public function read($id) + { + return call_user_func($this->_params['read']); + } + + /** + */ + public function write($id, $session_data) + { + return call_user_func($this->_params['write'], $id, $session_data); + } + + /** + */ + public function destroy($id) + { + return call_user_func($this->_params['destroy'], $id); + } + + /** + */ + public function gc($maxlifetime = 300) + { + return call_user_func($this->_params['gc'], $maxlifetime); + } + + /** + * Get a list of the valid session identifiers. + * + * @throws Horde_SessionHandler_Exception + */ + public function getSessionIDs() + { + throw new Horde_SessionHandler_Exception('Driver does not support listing session IDs.'); + } + +} diff --git a/framework/SessionHandler/lib/Horde/SessionHandler/Storage/Memcache.php b/framework/SessionHandler/lib/Horde/SessionHandler/Storage/Memcache.php new file mode 100644 index 000000000..9dd1c28c4 --- /dev/null +++ b/framework/SessionHandler/lib/Horde/SessionHandler/Storage/Memcache.php @@ -0,0 +1,245 @@ + + * @author Michael Slusarz + * @category Horde + * @package SessionHandler + */ +class Horde_SessionHandler_Storage_Memcache extends Horde_SessionHandler_Storage +{ + /** + * Memcache object. + * + * @var Horde_Memcache + */ + protected $_memcache; + + /** + * Current session ID. + * + * @var string + */ + protected $_id; + + /** + * The ID used for session tracking. + * + * @var string + */ + protected $_trackID = 'horde_memcache_sessions_track'; + + /** + * Constructor. + * + * @param array $params Parameters: + *
+     * 'memcache' - (Horde_Memcache) [REQUIRED] A memcache object.
+     * 'track' - (boolean) Track active sessions?
+     * 'track_lifetime' - (integer) The number of seconds after which tracked
+     *                    sessions will be treated as expired.
+     * 
+ * + * @throws InvalidArgumentException + */ + public function __construct(array $params = array()) + { + if (empty($params['memcache'])) { + throw new InvalidArgumentException('Missing memcache argument.'); + } + + $this->_memcache = $params['memcache']; + unset($params['memcache']); + + parent::__construct($params); + + if (empty($this->_params['track_lt'])) { + $this->_params['track_lt'] = ini_get('session.gc_maxlifetime'); + } + + if (!empty($this->_params['track']) && (rand(0, 999) == 0)) { + register_shutdown_function(array($this, 'trackGC')); + } + } + + /** + */ + public function open($save_path = null, $session_name = null) + { + } + + /** + */ + public function close() + { + if (isset($this->_id)) { + $this->_memcache->unlock($this->_id); + } + } + + /** + */ + public function read($id) + { + if (!$this->readonly) { + $this->_memcache->lock($id); + } + $result = $this->_memcache->get($id); + + if ($result === false) { + if (!$this->readonly) { + $this->_memcache->unlock($id); + } + + if ($result === false) { + if ($this->_logger) { + $this->_logger->log('Error retrieving session data (id = ' . $id . ')', 'DEBUG'); + } + return false; + } + } + + if (!$this->readonly) { + $this->_id = $id; + } + + if ($this->_logger) { + $this->_logger->log('Read session data (id = ' . $id . ')', 'DEBUG'); + } + + return $result; + } + + /** + */ + public function write($id, $session_data) + { + if (!empty($this->_params['track'])) { + // Do a replace - the only time it should fail is if we are + // writing a session for the first time. If that is the case, + // update the session tracker. + $res = $this->_memcache->replace($id, $session_data); + $track = !$res; + } else { + $res = $track = false; + } + + if (!$res && + !$this->_memcache->set($id, $session_data)) { + if ($this->_logger) { + $this->_logger->log('Error writing session data (id = ' . $id . ')', 'ERR'); + } + return false; + } + + if ($track) { + $this->_memcache->lock($this->_trackID); + $ids = $this->_memcache->get($this->_trackID); + if ($ids === false) { + $ids = array(); + } + + $ids[$id] = time(); + $this->_memcache->set($this->_trackID, $ids); + $this->_memcache->unlock($this->_trackID); + } + + if ($this->_logger) { + $this->_logger->log('Wrote session data (id = ' . $id . ')', 'DEBUG'); + } + + return true; + } + + /** + */ + public function destroy($id) + { + $result = $this->_memcache->delete($id); + $this->_memcache->unlock($id); + + if ($result === false) { + if ($this->_logger) { + $this->_logger->log('Failed to delete session (id = ' . $id . ')', 'DEBUG'); + } + return false; + } + + if (!empty($this->_params['track'])) { + $this->_memcache->lock($this->_trackID); + $ids = $this->_memcache->get($this->_trackID); + if ($ids !== false) { + unset($ids[$id]); + $this->_memcache->set($this->_trackID, $ids); + } + $this->_memcache->unlock($this->_trackID); + } + + if ($this->_logger) { + $this->_logger->log('Deleted session data (id = ' . $id . ')', 'DEBUG'); + } + + return true; + } + + /** + */ + public function gc($maxlifetime = 300) + { + // Memcache does its own garbage collection. + return true; + } + + /** + */ + public function getSessionIDs() + { + if (empty($this->_params['track'])) { + throw new Horde_SessionHandler_Exception('Memcache session tracking not enabled.'); + } + + $this->trackGC(); + + $ids = $this->_memcache->get($this->_trackID); + + return ($ids === false) + ? array() + : array_keys($ids); + } + + /** + * Do garbage collection for session tracking information. + */ + public function trackGC() + { + $this->_memcache->lock($this->_trackID); + $ids = $this->_memcache->get($this->_trackID); + if (empty($ids)) { + $this->_memcache->unlock($this->_trackID); + return; + } + + $tstamp = time() - $this->_params['track_lt']; + $alter = false; + + foreach ($ids as $key => $val) { + if ($tstamp > $val) { + unset($ids[$key]); + $alter = true; + } + } + + if ($alter) { + $this->_memcache->set($this->_trackID, $ids); + } + + $this->_memcache->unlock($this->_trackID); + } + +} diff --git a/framework/SessionHandler/lib/Horde/SessionHandler/Storage/Sql.php b/framework/SessionHandler/lib/Horde/SessionHandler/Storage/Sql.php new file mode 100644 index 000000000..39cda3c69 --- /dev/null +++ b/framework/SessionHandler/lib/Horde/SessionHandler/Storage/Sql.php @@ -0,0 +1,201 @@ + + * CREATE TABLE horde_sessionhandler ( + * VARCHAR(32) NOT NULL, + * session_lastmodified INT NOT NULL, + * session_data LONGBLOB, + * -- Or, on some DBMS systems: + * -- session_data IMAGE, + * + * PRIMARY KEY (session_id) + * ); + * + * CREATE INDEX session_lastmodified_idx ON horde_sessionhandler (session_lastmodified); + * + * + * Copyright 2002-2010 The Horde Project (http://www.horde.org/) + * + * See the enclosed file COPYING for license information (LGPL). If you + * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html. + * + * @author Mike Cochrane + * @category Horde + * @license http://www.fsf.org/copyleft/lgpl.html LGPL + * @package SessionHandler + */ +class Horde_SessionHandler_Storage_Sql extends Horde_SessionHandler_Storage +{ + /** + * Handle for the current database connection. + * + * @var Horde_Db_Adapter + */ + protected $_db; + + /** + * Constructor. + * + * @param array $params Parameters: + *
+     * 'db' - (Horde_Db_Adapter) [REQUIRED] The DB instance.
+     * 'table' - (string) The name of the sessions table.
+     *           DEFAULT: 'horde_sessionhandler'
+     * 
+ * + * @throws InvalidArgumentException + */ + public function __construct(array $params = array()) + { + if (!isset($params['db'])) { + throw new InvalidArgumentException('Missing db parameter.'); + } + $this->_db = $params['db']; + unset($params['db']); + + parent::__construct(array_merge(array( + 'table' => 'horde_sessionhandler' + ), $params)); + } + + /** + */ + public function open($save_path = null, $session_name = null) + { + } + + /** + */ + public function close() + { + /* Close any open transactions. */ + try { + $this->_db->commitDbTransaction(); + } catch (Horde_Db_Exception $e) { + throw new Horde_SessionHandler_Exception($e); + } + } + + /** + */ + public function read($id) + { + /* Begin a transaction. */ + // TODO: Rowlocking in Mysql + $this->_db->beginDbTransaction(); + + /* Build query. */ + $query = sprintf('SELECT session_data FROM %s WHERE session_id = ?', + $this->_params['table']); + $values = array($id); + + /* Execute the query. */ + try { + return $this->_db->selectValue($query, $values); + } catch (Horde_Db_Exception $e) { + return false; + } + } + + /** + */ + public function write($id, $session_data) + { + /* Build the SQL query. */ + $query = sprintf('SELECT session_id FROM %s WHERE session_id = ?', + $this->_params['table']); + $values = array($id); + + /* Execute the query. */ + try { + $result = $this->_db->selectValue($query, $values); + } catch (Horde_Db_Exception $e) { + return false; + } + + /* Build the replace SQL query. */ + $query = sprintf('REPLACE INTO %s ' . + '(session_id, session_data, session_lastmodified) ' . + 'VALUES (?, ?, ?)', + $this->_params['table']); + $values = array( + $id, + $session_data, + time() + ); + + /* Execute the replace query. */ + try { + $this->_db->update($query, $values); + $this->_db->commitDbTransaction(); + } catch (Horde_Db_Exception $e) { + $this->_db->rollbackDbTransaction(); + return false; + } + + return true; + } + + /** + */ + public function destroy($id) + { + /* Build the SQL query. */ + $query = sprintf('DELETE FROM %s WHERE session_id = ?', + $this->_params['table']); + $values = array($id); + + /* Execute the query. */ + try { + $this->_db->delete($query, $values); + $this->_db->commitDbTransaction(); + } catch (Horde_Db_Exception $e) { + return false; + } + + return true; + } + + /** + */ + public function gc($maxlifetime = 300) + { + /* Build the SQL query. */ + $query = sprintf('DELETE FROM %s WHERE session_lastmodified < ?', + $this->_params['table']); + $values = array(time() - $maxlifetime); + + /* Execute the query. */ + try { + $this->_db->delete($query, $values); + } catch (Horde_Db_Exception $e) { + return false; + } + + return true; + } + + /** + */ + public function getSessionIDs() + { + $this->open(); + + /* Build the SQL query. */ + $query = sprintf('SELECT session_id FROM %s' . + ' WHERE session_lastmodified >= ?', + $this->_params['table']); + $values = array(time() - ini_get('session.gc_maxlifetime')); + + /* Execute the query. */ + try { + return $this->_db->selectValues($query, $values); + } catch (Horde_Db_Exception $e) { + return array(); + } + } + +} diff --git a/framework/SessionHandler/lib/Horde/SessionHandler/Storage/Stack.php b/framework/SessionHandler/lib/Horde/SessionHandler/Storage/Stack.php new file mode 100644 index 000000000..b3f0e568b --- /dev/null +++ b/framework/SessionHandler/lib/Horde/SessionHandler/Storage/Stack.php @@ -0,0 +1,159 @@ + + * @category Horde + * @license http://www.fsf.org/copyleft/lgpl.html LGPL + * @package SessionHandler + */ +class Horde_SessionHandler_Storage_Stack extends Horde_SessionHandler_Storage +{ + /** + * Stack of storage objects. + * + * @var array + */ + protected $_stack = array(); + + /** + * Constructor. + * + * @param array $params Parameters: + *
+     * stack - (array) [REQUIRED] A list of storage objects to loop
+     *         through, in order of priority. The last entry is considered
+     *         the "master" server.
+     * 
+ * + * @throws InvalidArgumentException + */ + public function __construct(array $params = array()) + { + if (!isset($params['stack'])) { + throw new InvalidArgumentException('Missing stack parameter.'); + } + + $this->_stack = $params['stack']; + unset($params['stack']); + + parent::__construct($params); + } + + /** + * Set the logger object. + * + * @param Horde_Log_Logger $log The logger instance. + */ + public function setLogger(Horde_Log_Logger $log) + { + parent::setLogger($log); + + foreach ($this->_stack as $ob) { + $ob->setLogger($log); + } + } + + /** + */ + public function open($save_path = null, $session_name = null) + { + foreach ($this->_stack as $val) { + $val->open($save_path, $session_name); + } + } + + /** + */ + public function close() + { + foreach ($this->_stack as $val) { + $val->close(); + } + } + + /** + */ + public function read($id) + { + foreach ($this->_stack as $val) { + $result = $val->read($id); + if ($result === false) { + break; + } + } + + return $result; + } + + /** + */ + public function write($id, $session_data) + { + /* Do writes in *reverse* order - it is OK if a write to one of the + * non-master backends fails. */ + $master = true; + + foreach (array_reverse($this->_stack) as $val) { + $result = $val->write($id, $session_data); + if ($result === false) { + if ($master) { + return false; + } + /* Attempt to invalidate cache if write failed. */ + $val->destroy($id); + } + $master = false; + } + + return true; + } + + /** + */ + public function destroy($id) + { + /* Only report success from master. */ + $master = $success = true; + + foreach (array_reverse($this->_stack) as $val) { + $result = $val->destroy($id); + if ($master && ($result === false)) { + $success = false; + } + $master = false; + } + + return $success; + } + + /** + */ + public function gc($maxlifetime = 300) + { + /* Only report GC results from master. */ + foreach ($this->_stack as $val) { + $result = $val->gc($maxlifetime); + } + + return $result; + } + + /** + */ + public function getSessionIDs() + { + /* Grab session ID list from the master. */ + $ob = end($this->_stack); + return $ob->getSessionIDs(); + } + +} diff --git a/framework/SessionHandler/package.xml b/framework/SessionHandler/package.xml index 4a0295b91..e86b7ff6c 100644 --- a/framework/SessionHandler/package.xml +++ b/framework/SessionHandler/package.xml @@ -34,7 +34,7 @@ beta LGPL - + * Abstracted storage-specific code into 'Storage' drivers. * Removed LDAP driver * Abstracted memcache persistent-backend code into 'Stack' driver. * Renamed 'none' driver to 'Builtin'. @@ -50,12 +50,15 @@ - + + + + + + + - - - - + @@ -97,12 +100,13 @@ - + + + + + - - - - +