diff options
Diffstat (limited to 'program/lib/Roundcube/rcube_session.php')
-rw-r--r-- | program/lib/Roundcube/rcube_session.php | 202 |
1 files changed, 152 insertions, 50 deletions
diff --git a/program/lib/Roundcube/rcube_session.php b/program/lib/Roundcube/rcube_session.php index 1aa5d5856..615ec6f0e 100644 --- a/program/lib/Roundcube/rcube_session.php +++ b/program/lib/Roundcube/rcube_session.php @@ -32,6 +32,8 @@ class rcube_session private $ip; private $start; private $changed; + private $time_diff = 0; + private $reloaded = false; private $unsets = array(); private $gc_handlers = array(); private $cookiename = 'roundcube_sessauth'; @@ -41,6 +43,7 @@ class rcube_session private $secret = ''; private $ip_check = false; private $logging = false; + private $storage; private $memcache; @@ -58,11 +61,14 @@ class rcube_session $this->set_lifetime($lifetime); // use memcache backend - if ($config->get('session_storage', 'db') == 'memcache') { + $this->storage = $config->get('session_storage', 'db'); + if ($this->storage == 'memcache') { $this->memcache = rcube::get_instance()->get_memcache(); // set custom functions for PHP session management if memcache is available if ($this->memcache) { + ini_set('session.serialize_handler', 'php'); + session_set_save_handler( array($this, 'open'), array($this, 'close'), @@ -78,7 +84,9 @@ class rcube_session true, true); } } - else { + else if ($this->storage != 'php') { + ini_set('session.serialize_handler', 'php'); + // set custom functions for PHP session management session_set_save_handler( array($this, 'open'), @@ -86,7 +94,23 @@ class rcube_session array($this, 'db_read'), array($this, 'db_write'), array($this, 'db_destroy'), - array($this, 'db_gc')); + array($this, 'gc')); + } + } + + + /** + * Wrapper for session_start() + */ + public function start() + { + session_start(); + + // copy some session properties to object vars + if ($this->storage == 'php') { + $this->key = session_id(); + $this->ip = $_SESSION['__IP']; + $this->changed = $_SESSION['__MTIME']; } } @@ -115,6 +139,25 @@ class rcube_session /** + * Wrapper for session_write_close() + */ + public function write_close() + { + if ($this->storage == 'php') { + $_SESSION['__IP'] = $this->ip; + $_SESSION['__MTIME'] = time(); + } + + session_write_close(); + + // write_close() is called on script shutdown, see rcube::shutdown() + // execute cleanup functionality if enabled by session gc handler + // we do this after closing the session for better performance + $this->gc_shutdown(); + } + + + /** * Read session data from database * * @param string Session ID @@ -124,14 +167,16 @@ class rcube_session public function db_read($key) { $sql_result = $this->db->query( - "SELECT vars, ip, changed FROM ".$this->db->table_name('session') - ." WHERE sess_id = ?", $key); + "SELECT vars, ip, changed, " . $this->db->now() . " AS ts" + . " FROM " . $this->db->table_name('session') + . " WHERE sess_id = ?", $key); if ($sql_result && ($sql_arr = $this->db->fetch_assoc($sql_result))) { - $this->changed = strtotime($sql_arr['changed']); - $this->ip = $sql_arr['ip']; - $this->vars = base64_decode($sql_arr['vars']); - $this->key = $key; + $this->time_diff = time() - strtotime($sql_arr['ts']); + $this->changed = strtotime($sql_arr['changed']); + $this->ip = $sql_arr['ip']; + $this->vars = base64_decode($sql_arr['vars']); + $this->key = $key; return !empty($this->vars) ? (string) $this->vars : ''; } @@ -151,8 +196,9 @@ class rcube_session */ public function db_write($key, $vars) { - $ts = microtime(true); - $now = $this->db->fromunixtime((int)$ts); + $now = $this->db->now(); + $table = $this->db->table_name('session'); + $ts = microtime(true); // no session row in DB (db_read() returns false) if (!$this->key) { @@ -170,22 +216,19 @@ class rcube_session $newvars = $this->_fixvars($vars, $oldvars); if ($newvars !== $oldvars) { - $this->db->query( - sprintf("UPDATE %s SET vars=?, changed=%s WHERE sess_id=?", - $this->db->table_name('session'), $now), - base64_encode($newvars), $key); + $this->db->query("UPDATE $table " + . "SET changed = $now, vars = ? WHERE sess_id = ?", + base64_encode($newvars), $key); } - else if ($ts - $this->changed > $this->lifetime / 2) { - $this->db->query("UPDATE ".$this->db->table_name('session') - ." SET changed=$now WHERE sess_id=?", $key); + else if ($ts - $this->changed + $this->time_diff > $this->lifetime / 2) { + $this->db->query("UPDATE $table SET changed = $now" + . " WHERE sess_id = ?", $key); } } else { - $this->db->query( - sprintf("INSERT INTO %s (sess_id, vars, ip, created, changed) ". - "VALUES (?, ?, ?, %s, %s)", - $this->db->table_name('session'), $now, $now), - $key, base64_encode($vars), (string)$this->ip); + $this->db->query("INSERT INTO $table (sess_id, vars, ip, created, changed)" + . " VALUES (?, ?, ?, $now, $now)", + $key, base64_encode($vars), (string)$this->ip); } return true; @@ -200,8 +243,18 @@ class rcube_session if ($oldvars !== null) { $a_oldvars = $this->unserialize($oldvars); if (is_array($a_oldvars)) { - foreach ((array)$this->unsets as $k) - unset($a_oldvars[$k]); + // remove unset keys on oldvars + foreach ((array)$this->unsets as $var) { + if (isset($a_oldvars[$var])) { + unset($a_oldvars[$var]); + } + else { + $path = explode('.', $var); + $k = array_pop($path); + $node = &$this->get_node($path, $a_oldvars); + unset($node[$k]); + } + } $newvars = $this->serialize(array_merge( (array)$a_oldvars, (array)$this->unserialize($vars))); @@ -235,25 +288,6 @@ class rcube_session /** - * Garbage collecting function - * - * @param string Session lifetime in seconds - * @return boolean True on success - */ - public function db_gc($maxlifetime) - { - // just delete all expired sessions - $this->db->query( - sprintf("DELETE FROM %s WHERE changed < %s", - $this->db->table_name('session'), $this->db->fromunixtime(time() - $maxlifetime))); - - $this->gc(); - - return true; - } - - - /** * Read session data from memcache * * @param string Session ID @@ -329,11 +363,11 @@ class rcube_session /** * Execute registered garbage collector routines */ - public function gc() + public function gc($maxlifetime) { - foreach ($this->gc_handlers as $fct) { - call_user_func($fct); - } + // move gc execution to the script shutdown function + // see rcube::shutdown() and rcube_session::write_close() + return $this->gc_enabled = $maxlifetime; } @@ -355,6 +389,25 @@ class rcube_session /** + * Garbage collector handler to run on script shutdown + */ + protected function gc_shutdown() + { + if ($this->gc_enabled) { + // just delete all expired sessions + if ($this->storage == 'db') { + $this->db->query("DELETE FROM " . $this->db->table_name('session') + . " WHERE changed < " . $this->db->now(-$this->gc_enabled)); + } + + foreach ($this->gc_handlers as $fct) { + call_user_func($fct); + } + } + } + + + /** * Generate and set new session id * * @param boolean $destroy If enabled the current session will be destroyed @@ -371,9 +424,32 @@ class rcube_session /** + * Append the given value to the certain node in the session data array + * + * @param string Path denoting the session variable where to append the value + * @param string Key name under which to append the new value (use null for appending to an indexed list) + * @param mixed Value to append to the session data array + */ + public function append($path, $key, $value) + { + // re-read session data from DB because it might be outdated + if (!$this->reloaded && microtime(true) - $this->start > 0.5) { + $this->reload(); + $this->reloaded = true; + $this->start = microtime(true); + } + + $node = &$this->get_node(explode('.', $path), $_SESSION); + + if ($key !== null) $node[$key] = $value; + else $node[] = $value; + } + + + /** * Unset a session variable * - * @param string Varibale name + * @param string Variable name (can be a path denoting a certain node in the session array, e.g. compose.attachments.5) * @return boolean True on success */ public function remove($var=null) @@ -383,7 +459,16 @@ class rcube_session } $this->unsets[] = $var; - unset($_SESSION[$var]); + + if (isset($_SESSION[$var])) { + unset($_SESSION[$var]); + } + else { + $path = explode('.', $var); + $key = array_pop($path); + $node = &$this->get_node($path, $_SESSION); + unset($node[$key]); + } return true; } @@ -415,6 +500,23 @@ class rcube_session session_decode($data); } + /** + * Returns a reference to the node in data array referenced by the given path. + * e.g. ['compose','attachments'] will return $_SESSION['compose']['attachments'] + */ + private function &get_node($path, &$data_arr) + { + $node = &$data_arr; + if (!empty($path)) { + foreach ((array)$path as $key) { + if (!isset($node[$key])) + $node[$key] = array(); + $node = &$node[$key]; + } + } + + return $node; + } /** * Serialize session data |