2 commits - lib/ext lib/kolab_sync.php

Aleksander Machniak machniak at kolabsys.com
Mon Jun 17 10:21:32 CEST 2013


 lib/ext/Roundcube/bootstrap.php          |   27 ----
 lib/ext/Roundcube/html.php               |    4 
 lib/ext/Roundcube/rcube.php              |   56 +++++---
 lib/ext/Roundcube/rcube_cache.php        |   76 ++++++++---
 lib/ext/Roundcube/rcube_cache_shared.php |   57 +++++++-
 lib/ext/Roundcube/rcube_config.php       |   24 +++
 lib/ext/Roundcube/rcube_db.php           |  118 ++++++++++-------
 lib/ext/Roundcube/rcube_db_mssql.php     |   32 +++-
 lib/ext/Roundcube/rcube_db_mysql.php     |   21 ++-
 lib/ext/Roundcube/rcube_db_pgsql.php     |   66 +++++++++-
 lib/ext/Roundcube/rcube_db_sqlite.php    |   47 ++-----
 lib/ext/Roundcube/rcube_db_sqlsrv.php    |   29 +++-
 lib/ext/Roundcube/rcube_image.php        |    8 +
 lib/ext/Roundcube/rcube_imap.php         |  123 +++++++++---------
 lib/ext/Roundcube/rcube_imap_cache.php   |  204 +++++++++++++++++++++----------
 lib/ext/Roundcube/rcube_ldap.php         |    8 -
 lib/ext/Roundcube/rcube_mime.php         |   52 +++----
 lib/ext/Roundcube/rcube_session.php      |  134 +++++++++++++-------
 lib/ext/Roundcube/rcube_storage.php      |    2 
 lib/ext/Roundcube/rcube_user.php         |    9 -
 lib/ext/Roundcube/rcube_utils.php        |   17 +-
 lib/ext/Roundcube/rcube_washtml.php      |   17 +-
 lib/kolab_sync.php                       |    3 
 23 files changed, 734 insertions(+), 400 deletions(-)

New commits:
commit 4c06e6f6b4ae446199e0217956abee7dda8ce67d
Author: Aleksander Machniak <alec at alec.pl>
Date:   Mon Jun 17 10:21:14 2013 +0200

    Enable cache GC

diff --git a/lib/kolab_sync.php b/lib/kolab_sync.php
index 5114170..d27608a 100644
--- a/lib/kolab_sync.php
+++ b/lib/kolab_sync.php
@@ -383,6 +383,9 @@ class kolab_sync extends rcube
     {
         parent::shutdown();
 
+        // cache garbage collector
+        $this->gc_run();
+
         // write performance stats to logs/console
         if ($this->config->get('devel_mode')) {
             if (function_exists('memory_get_usage'))


commit daf5a06c709e7347fe49acaa8170405da699504d
Author: Aleksander Machniak <alec at alec.pl>
Date:   Mon Jun 17 10:19:22 2013 +0200

    Update Roundcube Framework

diff --git a/lib/ext/Roundcube/bootstrap.php b/lib/ext/Roundcube/bootstrap.php
index b7e69cb..68d3142 100644
--- a/lib/ext/Roundcube/bootstrap.php
+++ b/lib/ext/Roundcube/bootstrap.php
@@ -39,7 +39,6 @@ $config = array(
 if (php_sapi_name() != 'cli') {
     $config += array(
         'suhosin.session.encrypt' => 0,
-        'session.auto_start'      => 0,
         'file_uploads'            => 1,
     );
 }
@@ -293,32 +292,6 @@ function is_ascii($str, $control_chars = true)
 
 
 /**
- * Remove single and double quotes from a given string
- *
- * @param string Input value
- *
- * @return string Dequoted string
- */
-function strip_quotes($str)
-{
-    return str_replace(array("'", '"'), '', $str);
-}
-
-
-/**
- * Remove new lines characters from given string
- *
- * @param string $str  Input value
- *
- * @return string Stripped string
- */
-function strip_newlines($str)
-{
-    return preg_replace('/[\r\n]/', '', $str);
-}
-
-
-/**
  * Compose a valid representation of name and e-mail address
  *
  * @param string $email  E-mail address
diff --git a/lib/ext/Roundcube/html.php b/lib/ext/Roundcube/html.php
index eb23c8b..3e6e47a 100644
--- a/lib/ext/Roundcube/html.php
+++ b/lib/ext/Roundcube/html.php
@@ -360,8 +360,8 @@ class html_inputfield extends html
     protected $allowed = array(
         'type','name','value','size','tabindex','autocapitalize',
         'autocomplete','checked','onchange','onclick','disabled','readonly',
-        'spellcheck','results','maxlength','src','multiple','placeholder',
-        'autofocus',
+        'spellcheck','results','maxlength','src','multiple','accept',
+        'placeholder','autofocus',
     );
 
     /**
diff --git a/lib/ext/Roundcube/rcube.php b/lib/ext/Roundcube/rcube.php
index 4471ace..21b49f4 100644
--- a/lib/ext/Roundcube/rcube.php
+++ b/lib/ext/Roundcube/rcube.php
@@ -99,7 +99,6 @@ class rcube
     protected $texts;
     protected $caches = array();
     protected $shutdown_functions = array();
-    protected $expunge_cache = false;
 
 
     /**
@@ -457,30 +456,40 @@ class rcube
         ini_set('session.name', $sess_name ? $sess_name : 'roundcube_sessid');
         ini_set('session.use_cookies', 1);
         ini_set('session.use_only_cookies', 1);
-        ini_set('session.serialize_handler', 'php');
         ini_set('session.cookie_httponly', 1);
 
         // use database for storing session data
         $this->session = new rcube_session($this->get_dbh(), $this->config);
 
-        $this->session->register_gc_handler(array($this, 'temp_gc'));
-        $this->session->register_gc_handler(array($this, 'cache_gc'));
-
+        $this->session->register_gc_handler(array($this, 'gc'));
         $this->session->set_secret($this->config->get('des_key') . dirname($_SERVER['SCRIPT_NAME']));
         $this->session->set_ip_check($this->config->get('ip_check'));
 
         // start PHP session (if not in CLI mode)
         if ($_SERVER['REMOTE_ADDR']) {
-            session_start();
+            $this->session->start();
         }
     }
 
 
     /**
+     * Garbage collector - cache/temp cleaner
+     */
+    public function gc()
+    {
+        rcube_cache::gc();
+        rcube_cache_shared::gc();
+        $this->get_storage()->cache_gc();
+
+        $this->gc_temp();
+    }
+
+
+    /**
      * Garbage collector function for temp files.
      * Remove temp files older than two days
      */
-    public function temp_gc()
+    public function gc_temp()
     {
         $tmp = unslashify($this->config->get('temp_dir'));
         $expire = time() - 172800;  // expire in 48 hours
@@ -502,14 +511,21 @@ class rcube
 
 
     /**
-     * Garbage collector for cache entries.
-     * Set flag to expunge caches on shutdown
+     * Runs garbage collector with probability based on
+     * session settings. This is intended for environments
+     * without a session.
      */
-    public function cache_gc()
+    public function gc_run()
     {
-        // because this gc function is called before storage is initialized,
-        // we just set a flag to expunge storage cache on shutdown.
-        $this->expunge_cache = true;
+        $probability = (int) ini_get('session.gc_probability');
+        $divisor     = (int) ini_get('session.gc_divisor');
+
+        if ($divisor > 0 && $probability > 0) {
+            $random = mt_rand(1, $divisor);
+            if ($random <= $probability) {
+                $this->gc();
+            }
+        }
     }
 
 
@@ -893,23 +909,25 @@ class rcube
             call_user_func($function);
         }
 
+        // write session data as soon as possible and before
+        // closing database connection, don't do this before
+        // registered shutdown functions, they may need the session
+        // Note: this will run registered gc handlers (ie. cache gc)
+        if ($_SERVER['REMOTE_ADDR'] && is_object($this->session)) {
+            $this->session->write_close();
+        }
+
         if (is_object($this->smtp)) {
             $this->smtp->disconnect();
         }
 
         foreach ($this->caches as $cache) {
             if (is_object($cache)) {
-                if ($this->expunge_cache) {
-                    $cache->expunge();
-                }
                 $cache->close();
             }
         }
 
         if (is_object($this->storage)) {
-            if ($this->expunge_cache) {
-                $this->storage->expunge_cache();
-            }
             $this->storage->close();
         }
     }
diff --git a/lib/ext/Roundcube/rcube_cache.php b/lib/ext/Roundcube/rcube_cache.php
index 129f324..a708cb2 100644
--- a/lib/ext/Roundcube/rcube_cache.php
+++ b/lib/ext/Roundcube/rcube_cache.php
@@ -38,6 +38,7 @@ class rcube_cache
     private $type;
     private $userid;
     private $prefix;
+    private $table;
     private $ttl;
     private $packed;
     private $index;
@@ -71,8 +72,9 @@ class rcube_cache
             $this->db   = function_exists('apc_exists'); // APC 3.1.4 required
         }
         else {
-            $this->type = 'db';
-            $this->db   = $rcube->get_dbh();
+            $this->type  = 'db';
+            $this->db    = $rcube->get_dbh();
+            $this->table = $this->db->table_name('cache');
         }
 
         // convert ttl string to seconds
@@ -145,7 +147,7 @@ class rcube_cache
      */
     function write($key, $data)
     {
-        return $this->write_record($key, $this->packed ? serialize($data) : $data);
+        return $this->write_record($key, $this->serialize($data));
     }
 
 
@@ -194,18 +196,29 @@ class rcube_cache
     {
         if ($this->type == 'db' && $this->db && $this->ttl) {
             $this->db->query(
-                "DELETE FROM ".$this->db->table_name('cache').
+                "DELETE FROM ".$this->table.
                 " WHERE user_id = ?".
                 " AND cache_key LIKE ?".
-                " AND " . $this->db->unixtimestamp('created')." < ?",
+                " AND expires < " . $this->db->now(),
                 $this->userid,
-                $this->prefix.'.%',
-                time() - $this->ttl);
+                $this->prefix.'.%');
         }
     }
 
 
     /**
+     * Remove expired records of all caches
+     */
+    static function gc()
+    {
+        $rcube = rcube::get_instance();
+        $db    = $rcube->get_dbh();
+
+        $db->query("DELETE FROM " . $db->table_name('cache') . " WHERE expires < " . $db->now());
+    }
+
+
+    /**
      * Writes the cache back to the DB.
      */
     function close()
@@ -219,7 +232,7 @@ class rcube_cache
             if ($this->cache_changes[$key]) {
                 // Make sure we're not going to write unchanged data
                 // by comparing current md5 sum with the sum calculated on DB read
-                $data = $this->packed ? serialize($data) : $data;
+                $data = $this->serialize($data);
 
                 if (!$this->cache_sums[$key] || $this->cache_sums[$key] != md5($data)) {
                     $this->write_record($key, $data);
@@ -255,7 +268,7 @@ class rcube_cache
 
             if ($data) {
                 $md5sum = md5($data);
-                $data   = $this->packed ? unserialize($data) : $data;
+                $data   = $this->unserialize($data);
 
                 if ($nostore) {
                     return $data;
@@ -271,7 +284,7 @@ class rcube_cache
         else {
             $sql_result = $this->db->limitquery(
                 "SELECT data, cache_key".
-                " FROM ".$this->db->table_name('cache').
+                " FROM " . $this->table.
                 " WHERE user_id = ?".
                 " AND cache_key = ?".
                 // for better performance we allow more records for one key
@@ -283,7 +296,7 @@ class rcube_cache
                 $key = substr($sql_arr['cache_key'], strlen($this->prefix)+1);
                 $md5sum = $sql_arr['data'] ? md5($sql_arr['data']) : null;
                 if ($sql_arr['data']) {
-                    $data = $this->packed ? unserialize($sql_arr['data']) : $sql_arr['data'];
+                    $data = $this->unserialize($sql_arr['data']);
                 }
 
                 if ($nostore) {
@@ -326,7 +339,7 @@ class rcube_cache
         // Remove NULL rows (here we don't need to check if the record exist)
         if ($data == 'N;') {
             $this->db->query(
-                "DELETE FROM ".$this->db->table_name('cache').
+                "DELETE FROM " . $this->table.
                 " WHERE user_id = ?".
                 " AND cache_key = ?",
                 $this->userid, $key);
@@ -337,8 +350,10 @@ class rcube_cache
         // update existing cache record
         if ($key_exists) {
             $result = $this->db->query(
-                "UPDATE ".$this->db->table_name('cache').
-                " SET created = ". $this->db->now().", data = ?".
+                "UPDATE " . $this->table.
+                " SET created = " . $this->db->now().
+                    ", expires = " . ($this->ttl ? $this->db->now($this->ttl) : 'NULL').
+                    ", data = ?".
                 " WHERE user_id = ?".
                 " AND cache_key = ?",
                 $data, $this->userid, $key);
@@ -348,9 +363,9 @@ class rcube_cache
             // for better performance we allow more records for one key
             // so, no need to check if record exist (see rcube_cache::read_record())
             $result = $this->db->query(
-                "INSERT INTO ".$this->db->table_name('cache').
-                " (created, user_id, cache_key, data)".
-                " VALUES (".$this->db->now().", ?, ?, ?)",
+                "INSERT INTO " . $this->table.
+                " (created, expires, user_id, cache_key, data)".
+                " VALUES (" . $this->db->now() . ", " . ($this->ttl ? $this->db->now($this->ttl) : 'NULL') . ", ?, ?, ?)",
                 $this->userid, $key, $data);
         }
 
@@ -364,7 +379,6 @@ class rcube_cache
      * @param string  $key         Cache key name or pattern
      * @param boolean $prefix_mode Enable it to clear all keys starting
      *                             with prefix specified in $key
-     *
      */
     private function remove_record($key=null, $prefix_mode=false)
     {
@@ -412,7 +426,7 @@ class rcube_cache
         }
 
         $this->db->query(
-            "DELETE FROM ".$this->db->table_name('cache').
+            "DELETE FROM " . $this->table.
             " WHERE user_id = ?" . $where,
             $this->userid);
     }
@@ -553,4 +567,28 @@ class rcube_cache
         // This way each cache will have its own index
         return sprintf('%d:%s%s', $this->userid, $this->prefix, 'INDEX');
     }
+
+    /**
+     * Serializes data for storing
+     */
+    private function serialize($data)
+    {
+        if ($this->type == 'db') {
+            return $this->db->encode($data, $this->packed);
+        }
+
+        return $this->packed ? serialize($data) : $data;
+    }
+
+    /**
+     * Unserializes serialized data
+     */
+    private function unserialize($data)
+    {
+        if ($this->type == 'db') {
+            return $this->db->decode($data, $this->packed);
+        }
+
+        return $this->packed ? @unserialize($data) : $data;
+    }
 }
diff --git a/lib/ext/Roundcube/rcube_cache_shared.php b/lib/ext/Roundcube/rcube_cache_shared.php
index 5983bd3..8f25740 100644
--- a/lib/ext/Roundcube/rcube_cache_shared.php
+++ b/lib/ext/Roundcube/rcube_cache_shared.php
@@ -144,7 +144,7 @@ class rcube_cache_shared
      */
     function write($key, $data)
     {
-        return $this->write_record($key, $this->packed ? serialize($data) : $data);
+        return $this->write_record($key, $this->serialize($data));
     }
 
 
@@ -195,14 +195,25 @@ class rcube_cache_shared
             $this->db->query(
                 "DELETE FROM " . $this->table
                 . " WHERE cache_key LIKE ?"
-                . " AND " . $this->db->unixtimestamp('created') . " < ?",
-                $this->prefix . '.%',
-                time() - $this->ttl);
+                . " AND expires < " . $this->db->now(),
+                $this->prefix . '.%');
         }
     }
 
 
     /**
+     * Remove expired records of all caches
+     */
+    static function gc()
+    {
+        $rcube = rcube::get_instance();
+        $db    = $rcube->get_dbh();
+
+        $db->query("DELETE FROM " . $db->table_name('cache_shared') . " WHERE expires < " . $db->now());
+    }
+
+
+    /**
      * Writes the cache back to the DB.
      */
     function close()
@@ -216,7 +227,7 @@ class rcube_cache_shared
             if ($this->cache_changes[$key]) {
                 // Make sure we're not going to write unchanged data
                 // by comparing current md5 sum with the sum calculated on DB read
-                $data = $this->packed ? serialize($data) : $data;
+                $data = $this->serialize($data);
 
                 if (!$this->cache_sums[$key] || $this->cache_sums[$key] != md5($data)) {
                     $this->write_record($key, $data);
@@ -252,7 +263,7 @@ class rcube_cache_shared
 
             if ($data) {
                 $md5sum = md5($data);
-                $data   = $this->packed ? unserialize($data) : $data;
+                $data   = $this->unserialize($data);
 
                 if ($nostore) {
                     return $data;
@@ -278,7 +289,7 @@ class rcube_cache_shared
             if ($sql_arr = $this->db->fetch_assoc($sql_result)) {
                 $md5sum = $sql_arr['data'] ? md5($sql_arr['data']) : null;
                 if ($sql_arr['data']) {
-                    $data = $this->packed ? unserialize($sql_arr['data']) : $sql_arr['data'];
+                    $data = $this->unserialize($sql_arr['data']);
                 }
 
                 if ($nostore) {
@@ -328,7 +339,9 @@ class rcube_cache_shared
         if ($key_exists) {
             $result = $this->db->query(
                 "UPDATE " . $this->table .
-                " SET created = " . $this->db->now() . ", data = ?" .
+                " SET created = " . $this->db->now() .
+                    ", expires = " . ($this->ttl ? $this->db->now($this->ttl) : 'NULL') .
+                    ", data = ?".
                 " WHERE cache_key = ?",
                 $data, $key);
         }
@@ -338,8 +351,8 @@ class rcube_cache_shared
             // so, no need to check if record exist (see rcube_cache::read_record())
             $result = $this->db->query(
                 "INSERT INTO ".$this->table.
-                " (created, cache_key, data)".
-                " VALUES (".$this->db->now().", ?, ?)",
+                " (created, expires, cache_key, data)".
+                " VALUES (".$this->db->now().", " . ($this->ttl ? $this->db->now($this->ttl) : 'NULL') . ", ?, ?)",
                 $key, $data);
         }
 
@@ -541,4 +554,28 @@ class rcube_cache_shared
         // This way each cache will have its own index
         return $this->prefix . 'INDEX';
     }
+
+    /**
+     * Serializes data for storing
+     */
+    private function serialize($data)
+    {
+        if ($this->type == 'db') {
+            return $this->db->encode($data, $this->packed);
+        }
+
+        return $this->packed ? serialize($data) : $data;
+    }
+
+    /**
+     * Unserializes serialized data
+     */
+    private function unserialize($data)
+    {
+        if ($this->type == 'db') {
+            return $this->db->decode($data, $this->packed);
+        }
+
+        return $this->packed ? @unserialize($data) : $data;
+    }
 }
diff --git a/lib/ext/Roundcube/rcube_config.php b/lib/ext/Roundcube/rcube_config.php
index 2190dc4..18055f7 100644
--- a/lib/ext/Roundcube/rcube_config.php
+++ b/lib/ext/Roundcube/rcube_config.php
@@ -43,6 +43,8 @@ class rcube_config
         'reply_mode'           => 'top_posting',
         'refresh_interval'     => 'keep_alive',
         'min_refresh_interval' => 'min_keep_alive',
+        'messages_cache_ttl'   => 'message_cache_lifetime',
+        'redundant_attachments_cache_ttl' => 'redundant_attachments_memcache_ttl',
     );
 
 
@@ -174,7 +176,7 @@ class rcube_config
             ob_end_clean();
 
             if (is_array($rcmail_config)) {
-                $this->prop = array_merge($this->prop, $rcmail_config, $this->userprefs);
+                $this->merge($rcmail_config);
                 return true;
             }
         }
@@ -195,9 +197,6 @@ class rcube_config
         if (isset($this->prop[$name])) {
             $result = $this->prop[$name];
         }
-        else if (isset($this->legacy_props[$name])) {
-            return $this->get($this->legacy_props[$name], $def);
-        }
         else {
             $result = $def;
         }
@@ -241,6 +240,7 @@ class rcube_config
     public function merge($prefs)
     {
         $this->prop = array_merge($this->prop, $prefs, $this->userprefs);
+        $this->fix_legacy_props();
     }
 
 
@@ -273,6 +273,8 @@ class rcube_config
         $this->userprefs = $prefs;
         $this->prop      = array_merge($this->prop, $prefs);
 
+        $this->fix_legacy_props();
+
         // override timezone settings with client values
         if ($this->prop['timezone'] == 'auto') {
             $this->prop['_timezone_value'] = isset($_SESSION['timezone']) ? $this->client_timezone() : $this->prop['_timezone_value'];
@@ -435,4 +437,18 @@ class rcube_config
         return date_default_timezone_get();
     }
 
+    /**
+     * Convert legacy options into new ones
+     */
+    private function fix_legacy_props()
+    {
+        foreach ($this->legacy_props as $new => $old) {
+            if (isset($this->prop[$old])) {
+                if (!isset($this->prop[$new])) {
+                    $this->prop[$new] = $this->prop[$old];
+                }
+                unset($this->prop[$old]);
+            }
+        }
+    }
 }
diff --git a/lib/ext/Roundcube/rcube_db.php b/lib/ext/Roundcube/rcube_db.php
index 5da38c8..8520700 100644
--- a/lib/ext/Roundcube/rcube_db.php
+++ b/lib/ext/Roundcube/rcube_db.php
@@ -100,27 +100,15 @@ class rcube_db
 
         $this->db_dsnw_array = self::parse_dsn($db_dsnw);
         $this->db_dsnr_array = self::parse_dsn($db_dsnr);
-
-        // Initialize driver class
-        $this->init();
-    }
-
-    /**
-     * Initialization of the object with driver specific code
-     */
-    protected function init()
-    {
-        // To be used by driver classes
     }
 
     /**
      * Connect to specific database
      *
-     * @param array $dsn DSN for DB connections
-     *
-     * @return PDO database handle
+     * @param array  $dsn  DSN for DB connections
+     * @param string $mode Connection mode (r|w)
      */
-    protected function dsn_connect($dsn)
+    protected function dsn_connect($dsn, $mode)
     {
         $this->db_error     = false;
         $this->db_error_msg = null;
@@ -158,9 +146,10 @@ class rcube_db
             return null;
         }
 
+        $this->dbh          = $dbh;
+        $this->db_mode      = $mode;
+        $this->db_connected = true;
         $this->conn_configure($dsn, $dbh);
-
-        return $dbh;
     }
 
     /**
@@ -183,16 +172,6 @@ class rcube_db
     }
 
     /**
-     * Driver-specific database character set setting
-     *
-     * @param string $charset Character set name
-     */
-    protected function set_charset($charset)
-    {
-        $this->query("SET NAMES 'utf8'");
-    }
-
-    /**
      * Connect to appropriate database depending on the operation
      *
      * @param string $mode Connection mode (r|w)
@@ -219,23 +198,14 @@ class rcube_db
 
         $dsn = ($mode == 'r') ? $this->db_dsnr_array : $this->db_dsnw_array;
 
-        $this->dbh          = $this->dsn_connect($dsn);
-        $this->db_connected = is_object($this->dbh);
+        $this->dsn_connect($dsn, $mode);
 
         // use write-master when read-only fails
         if (!$this->db_connected && $mode == 'r' && $this->is_replicated()) {
-            $mode = 'w';
-            $this->dbh          = $this->dsn_connect($this->db_dsnw_array);
-            $this->db_connected = is_object($this->dbh);
+            $this->dsn_connect($this->db_dsnw_array, 'w');
         }
 
-        if ($this->db_connected) {
-            $this->db_mode = $mode;
-            $this->set_charset('utf8');
-        }
-        else {
-            $this->conn_failure = true;
-        }
+        $this->conn_failure = !$this->db_connected;
     }
 
     /**
@@ -368,8 +338,10 @@ class rcube_db
      */
     protected function _query($query, $offset, $numrows, $params)
     {
+        $query = trim($query);
+
         // Read or write ?
-        $mode = preg_match('/^(select|show)/i', ltrim($query)) ? 'r' : 'w';
+        $mode = preg_match('/^(select|show|set)/i', $query) ? 'r' : 'w';
 
         $this->db_connect($mode);
 
@@ -415,13 +387,16 @@ class rcube_db
 
         if ($result === false) {
             $error = $this->dbh->errorInfo();
-            $this->db_error = true;
-            $this->db_error_msg = sprintf('[%s] %s', $error[1], $error[2]);
 
-            rcube::raise_error(array('code' => 500, 'type' => 'db',
-                'line' => __LINE__, 'file' => __FILE__,
-                'message' => $this->db_error_msg . " (SQL Query: $query)"
-                ), true, false);
+            if (empty($this->options['ignore_key_errors']) || $error[0] != '23000') {
+                $this->db_error = true;
+                $this->db_error_msg = sprintf('[%s] %s', $error[1], $error[2]);
+
+                rcube::raise_error(array('code' => 500, 'type' => 'db',
+                    'line' => __LINE__, 'file' => __FILE__,
+                    'message' => $this->db_error_msg . " (SQL Query: $query)"
+                    ), true, false);
+            }
         }
 
         $this->last_result = $result;
@@ -708,11 +683,19 @@ class rcube_db
     /**
      * Return SQL function for current time and date
      *
+     * @param int $interval Optional interval (in seconds) to add/subtract
+     *
      * @return string SQL function to use in query
      */
-    public function now()
+    public function now($interval = 0)
     {
-        return "now()";
+        if ($interval) {
+            $add = ' ' . ($interval > 0 ? '+' : '-') . ' INTERVAL ';
+            $add .= $interval > 0 ? intval($interval) : intval($interval) * -1;
+            $add .= ' SECOND';
+        }
+
+        return "now()" . $add;
     }
 
     /**
@@ -795,12 +778,19 @@ class rcube_db
     /**
      * Encodes non-UTF-8 characters in string/array/object (recursive)
      *
-     * @param mixed $input Data to fix
+     * @param mixed $input      Data to fix
+     * @param bool  $serialized Enable serialization
      *
      * @return mixed Properly UTF-8 encoded data
      */
-    public static function encode($input)
+    public static function encode($input, $serialized = false)
     {
+        // use Base64 encoding to workaround issues with invalid
+        // or null characters in serialized string (#1489142)
+        if ($serialized) {
+            return base64_encode(serialize($input));
+        }
+
         if (is_object($input)) {
             foreach (get_object_vars($input) as $idx => $value) {
                 $input->$idx = self::encode($value);
@@ -811,6 +801,7 @@ class rcube_db
             foreach ($input as $idx => $value) {
                 $input[$idx] = self::encode($value);
             }
+
             return $input;
         }
 
@@ -820,12 +811,24 @@ class rcube_db
     /**
      * Decodes encoded UTF-8 string/object/array (recursive)
      *
-     * @param mixed $input Input data
+     * @param mixed $input      Input data
+     * @param bool  $serialized Enable serialization
      *
      * @return mixed Decoded data
      */
-    public static function decode($input)
+    public static function decode($input, $serialized = false)
     {
+        // use Base64 encoding to workaround issues with invalid
+        // or null characters in serialized string (#1489142)
+        if ($serialized) {
+            // Keep backward compatybility where base64 wasn't used
+            if (strpos(substr($input, 0, 16), ':') !== false) {
+                return self::decode(@unserialize($input));
+            }
+
+            return @unserialize(base64_decode($input));
+        }
+
         if (is_object($input)) {
             foreach (get_object_vars($input) as $idx => $value) {
                 $input->$idx = self::decode($value);
@@ -862,6 +865,17 @@ class rcube_db
     }
 
     /**
+     * Set class option value
+     *
+     * @param string $name  Option name
+     * @param mixed  $value Option value
+     */
+    public function set_option($name, $value)
+    {
+        $this->options[$name] = $value;
+    }
+
+    /**
      * MDB2 DSN string parser
      *
      * @param string $sequence Secuence name
diff --git a/lib/ext/Roundcube/rcube_db_mssql.php b/lib/ext/Roundcube/rcube_db_mssql.php
index 37a4267..3c1b9d7 100644
--- a/lib/ext/Roundcube/rcube_db_mssql.php
+++ b/lib/ext/Roundcube/rcube_db_mssql.php
@@ -29,38 +29,52 @@ class rcube_db_mssql extends rcube_db
     public $db_provider = 'mssql';
 
     /**
-     * Driver initialization
+     * Object constructor
+     *
+     * @param string $db_dsnw DSN for read/write operations
+     * @param string $db_dsnr Optional DSN for read only operations
+     * @param bool   $pconn   Enables persistent connections
      */
-    protected function init()
+    public function __construct($db_dsnw, $db_dsnr = '', $pconn = false)
     {
+        parent::__construct($db_dsnw, $db_dsnr, $pconn);
+
         $this->options['identifier_start'] = '[';
         $this->options['identifier_end'] = ']';
     }
 
     /**
-     * Character setting
+     * Driver-specific configuration of database connection
+     *
+     * @param array $dsn DSN for DB connections
+     * @param PDO   $dbh Connection handler
      */
-    protected function set_charset($charset)
+    protected function conn_configure($dsn, $dbh)
     {
-        // UTF-8 is default
+        // Set date format in case of non-default language (#1488918)
+        $this->query("SET DATEFORMAT ymd");
     }
 
     /**
      * Return SQL function for current time and date
      *
+     * @param int $interval Optional interval (in seconds) to add/subtract
+     *
      * @return string SQL function to use in query
      */
-    public function now()
+    public function now($interval = 0)
     {
+        if ($interval) {
+            $interval = intval($interval);
+            return "dateadd(second, $interval, getdate())";
+        }
+
         return "getdate()";
     }
 
     /**
      * Return SQL statement to convert a field value into a unix timestamp
      *
-     * This method is deprecated and should not be used anymore due to limitations
-     * of timestamp functions in Mysql (year 2038 problem)
-     *
      * @param string $field Field name
      *
      * @return string SQL statement to use in query
diff --git a/lib/ext/Roundcube/rcube_db_mysql.php b/lib/ext/Roundcube/rcube_db_mysql.php
index 2d42610..6fa5ad7 100644
--- a/lib/ext/Roundcube/rcube_db_mysql.php
+++ b/lib/ext/Roundcube/rcube_db_mysql.php
@@ -30,9 +30,13 @@ class rcube_db_mysql extends rcube_db
     public $db_provider = 'mysql';
 
     /**
-     * Driver initialization/configuration
+     * Object constructor
+     *
+     * @param string $db_dsnw DSN for read/write operations
+     * @param string $db_dsnr Optional DSN for read only operations
+     * @param bool   $pconn   Enables persistent connections
      */
-    protected function init()
+    public function __construct($db_dsnw, $db_dsnr = '', $pconn = false)
     {
         if (version_compare(PHP_VERSION, '5.3.0', '<')) {
             rcube::raise_error(array('code' => 600, 'type' => 'db',
@@ -41,12 +45,25 @@ class rcube_db_mysql extends rcube_db
                 true, true);
         }
 
+        parent::__construct($db_dsnw, $db_dsnr, $pconn);
+
         // SQL identifiers quoting
         $this->options['identifier_start'] = '`';
         $this->options['identifier_end'] = '`';
     }
 
     /**
+     * Driver-specific configuration of database connection
+     *
+     * @param array $dsn DSN for DB connections
+     * @param PDO   $dbh Connection handler
+     */
+    protected function conn_configure($dsn, $dbh)
+    {
+        $this->query("SET NAMES 'utf8'");
+    }
+
+    /**
      * Abstract SQL statement for value concatenation
      *
      * @return string SQL statement to be used in query
diff --git a/lib/ext/Roundcube/rcube_db_pgsql.php b/lib/ext/Roundcube/rcube_db_pgsql.php
index adfd220..d72c9d6 100644
--- a/lib/ext/Roundcube/rcube_db_pgsql.php
+++ b/lib/ext/Roundcube/rcube_db_pgsql.php
@@ -29,6 +29,17 @@ class rcube_db_pgsql extends rcube_db
     public $db_provider = 'postgres';
 
     /**
+     * Driver-specific configuration of database connection
+     *
+     * @param array $dsn DSN for DB connections
+     * @param PDO   $dbh Connection handler
+     */
+    protected function conn_configure($dsn, $dbh)
+    {
+        $this->query("SET NAMES 'utf8'");
+    }
+
+    /**
      * Get last inserted record ID
      *
      * @param string $table Table name (to find the incremented sequence)
@@ -75,9 +86,6 @@ class rcube_db_pgsql extends rcube_db
     /**
      * Return SQL statement to convert a field value into a unix timestamp
      *
-     * This method is deprecated and should not be used anymore due to limitations
-     * of timestamp functions in Mysql (year 2038 problem)
-     *
      * @param string $field Field name
      *
      * @return string SQL statement to use in query
@@ -89,6 +97,24 @@ class rcube_db_pgsql extends rcube_db
     }
 
     /**
+     * Return SQL function for current time and date
+     *
+     * @param int $interval Optional interval (in seconds) to add/subtract
+     *
+     * @return string SQL function to use in query
+     */
+    public function now($interval = 0)
+    {
+        if ($interval) {
+            $add = ' ' . ($interval > 0 ? '+' : '-') . " interval '";
+            $add .= $interval > 0 ? intval($interval) : intval($interval) * -1;
+            $add .= " seconds'";
+        }
+
+        return "now()" . $add;
+    }
+
+    /**
      * Return SQL statement for case insensitive LIKE
      *
      * @param string $column Field name
@@ -130,4 +156,38 @@ class rcube_db_pgsql extends rcube_db
         return isset($this->variables[$varname]) ? $this->variables[$varname] : $default;
     }
 
+    /**
+     * Returns PDO DSN string from DSN array
+     *
+     * @param array $dsn DSN parameters
+     *
+     * @return string DSN string
+     */
+    protected function dsn_string($dsn)
+    {
+        $params = array();
+        $result = 'pgsql:';
+
+        if ($dsn['hostspec']) {
+            $params[] = 'host=' . $dsn['hostspec'];
+        }
+        else if ($dsn['socket']) {
+            $params[] = 'host=' . $dsn['socket'];
+        }
+
+        if ($dsn['port']) {
+            $params[] = 'port=' . $dsn['port'];
+        }
+
+        if ($dsn['database']) {
+            $params[] = 'dbname=' . $dsn['database'];
+        }
+
+        if (!empty($params)) {
+            $result .= implode(';', $params);
+        }
+
+        return $result;
+    }
+
 }
diff --git a/lib/ext/Roundcube/rcube_db_sqlite.php b/lib/ext/Roundcube/rcube_db_sqlite.php
index 145b8a3..b66c560 100644
--- a/lib/ext/Roundcube/rcube_db_sqlite.php
+++ b/lib/ext/Roundcube/rcube_db_sqlite.php
@@ -29,13 +29,6 @@ class rcube_db_sqlite extends rcube_db
     public $db_provider = 'sqlite';
 
     /**
-     * Database character set
-     */
-    protected function set_charset($charset)
-    {
-    }
-
-    /**
      * Prepare connection
      */
     protected function conn_prepare($dsn)
@@ -56,10 +49,6 @@ class rcube_db_sqlite extends rcube_db
      */
     protected function conn_configure($dsn, $dbh)
     {
-        // we emulate via callback some missing functions
-        $dbh->sqliteCreateFunction('unix_timestamp', array('rcube_db_sqlite', 'sqlite_unix_timestamp'), 1);
-        $dbh->sqliteCreateFunction('now', array('rcube_db_sqlite', 'sqlite_now'), 0);
-
         // Initialize database structure in file is empty
         if (!empty($dsn['database']) && !filesize($dsn['database'])) {
             $data = file_get_contents(RCUBE_INSTALL_PATH . 'SQL/sqlite.initial.sql');
@@ -83,30 +72,32 @@ class rcube_db_sqlite extends rcube_db
     }
 
     /**
-     * Callback for sqlite: unix_timestamp()
+     * Return SQL statement to convert a field value into a unix timestamp
+     *
+     * @param string $field Field name
+     *
+     * @return string  SQL statement to use in query
+     * @deprecated
      */
-    public static function sqlite_unix_timestamp($timestamp = '')
+    public function unixtimestamp($field)
     {
-        $timestamp = trim($timestamp);
-        if (!$timestamp) {
-            $ret = time();
-        }
-        else if (!preg_match('/^[0-9]+$/s', $timestamp)) {
-            $ret = strtotime($timestamp);
-        }
-        else {
-            $ret = $timestamp;
-        }
-
-        return $ret;
+        return "strftime('%s', $field)";
     }
 
     /**
-     * Callback for sqlite: now()
+     * Return SQL function for current time and date
+     *
+     * @param int $interval Optional interval (in seconds) to add/subtract
+     *
+     * @return string SQL function to use in query
      */
-    public static function sqlite_now()
+    public function now($interval = 0)
     {
-        return date("Y-m-d H:i:s");
+        if ($interval) {
+            $add = ($interval > 0 ? '+' : '') . intval($interval) . ' seconds';
+        }
+
+        return "datetime('now'" . ($add ? ",'$add'" : "") . ")";
     }
 
     /**
diff --git a/lib/ext/Roundcube/rcube_db_sqlsrv.php b/lib/ext/Roundcube/rcube_db_sqlsrv.php
index e5dfb11..45c41cd 100644
--- a/lib/ext/Roundcube/rcube_db_sqlsrv.php
+++ b/lib/ext/Roundcube/rcube_db_sqlsrv.php
@@ -29,29 +29,46 @@ class rcube_db_sqlsrv extends rcube_db
     public $db_provider = 'mssql';
 
     /**
-     * Driver initialization
+     * Object constructor
+     *
+     * @param string $db_dsnw DSN for read/write operations
+     * @param string $db_dsnr Optional DSN for read only operations
+     * @param bool   $pconn   Enables persistent connections
      */
-    protected function init()
+    public function __construct($db_dsnw, $db_dsnr = '', $pconn = false)
     {
+        parent::__construct($db_dsnw, $db_dsnr, $pconn);
+
         $this->options['identifier_start'] = '[';
         $this->options['identifier_end'] = ']';
     }
 
     /**
-     * Database character set setting
+     * Driver-specific configuration of database connection
+     *
+     * @param array $dsn DSN for DB connections
+     * @param PDO   $dbh Connection handler
      */
-    protected function set_charset($charset)
+    protected function conn_configure($dsn, $dbh)
     {
-        // UTF-8 is default
+        // Set date format in case of non-default language (#1488918)
+        $this->query("SET DATEFORMAT ymd");
     }
 
     /**
      * Return SQL function for current time and date
      *
+     * @param int $interval Optional interval (in seconds) to add/subtract
+     *
      * @return string SQL function to use in query
      */
-    public function now()
+    public function now($interval = 0)
     {
+        if ($interval) {
+            $interval = intval($interval);
+            return "dateadd(second, $interval, getdate())";
+        }
+
         return "getdate()";
     }
 
diff --git a/lib/ext/Roundcube/rcube_image.php b/lib/ext/Roundcube/rcube_image.php
index 735a0df..09bb4e8 100644
--- a/lib/ext/Roundcube/rcube_image.php
+++ b/lib/ext/Roundcube/rcube_image.php
@@ -93,6 +93,10 @@ class rcube_image
         $convert = $rcube->config->get('im_convert_path', false);
         $props   = $this->props();
 
+        if (empty($props)) {
+            return false;
+        }
+
         if (!$filename) {
             $filename = $this->image_file;
         }
@@ -148,6 +152,10 @@ class rcube_image
                 return false;
             }
 
+            if ($image === false) {
+                return false;
+            }
+
             $scale = $size / max($props['width'], $props['height']);
 
             // Imagemagick resize is implemented in shrinking mode (see -resize argument above)
diff --git a/lib/ext/Roundcube/rcube_imap.php b/lib/ext/Roundcube/rcube_imap.php
index 31e7079..3ca8a07 100644
--- a/lib/ext/Roundcube/rcube_imap.php
+++ b/lib/ext/Roundcube/rcube_imap.php
@@ -308,14 +308,7 @@ class rcube_imap extends rcube_storage
      */
     public function set_folder($folder)
     {
-        if ($this->folder == $folder) {
-            return;
-        }
-
         $this->folder = $folder;
-
-        // clear messagecount cache for this folder
-        $this->clear_messagecount($folder);
     }
 
 
@@ -626,7 +619,7 @@ class rcube_imap extends rcube_storage
         }
 
         if ($mode == 'THREADS') {
-            $res   = $this->fetch_threads($folder, $force);
+            $res   = $this->threads($folder);
             $count = $res->count();
 
             if ($status) {
@@ -656,11 +649,11 @@ class rcube_imap extends rcube_storage
                     $keys[] = 'ALL';
                 }
                 if ($status) {
-                    $keys[]   = 'MAX';
+                    $keys[] = 'MAX';
                 }
             }
 
-            // @TODO: if $force==false && $mode == 'ALL' we could try to use cache index here
+            // @TODO: if $mode == 'ALL' we could try to use cache index here
 
             // get message count using (E)SEARCH
             // not very performant but more precise (using UNDELETED)
@@ -791,7 +784,7 @@ class rcube_imap extends rcube_storage
             $threads = $mcache->get_thread($folder);
         }
         else {
-            $threads = $this->fetch_threads($folder);
+            $threads = $this->threads($folder);
         }
 
         return $this->fetch_thread_headers($folder, $threads, $page, $slice);
@@ -800,32 +793,47 @@ class rcube_imap extends rcube_storage
     /**
      * Method for fetching threads data
      *
-     * @param  string $folder  Folder name
-     * @param  bool   $force   Use IMAP server, no cache
+     * @param  string $folder Folder name
      *
      * @return rcube_imap_thread Thread data object
      */
-    function fetch_threads($folder, $force = false)
+    function threads($folder)
     {
-        if (!$force && ($mcache = $this->get_mcache_engine())) {
+        if ($mcache = $this->get_mcache_engine()) {
             // don't store in self's internal cache, cache has it's own internal cache
             return $mcache->get_thread($folder);
         }
 
-        if (empty($this->icache['threads'])) {
-            if (!$this->check_connection()) {
-                return new rcube_result_thread();
+        if (!empty($this->icache['threads'])) {
+            if ($this->icache['threads']->get_parameters('MAILBOX') == $folder) {
+                return $this->icache['threads'];
             }
+        }
+
+        // get all threads
+        $result = $this->threads_direct($folder);
+
+        // add to internal (fast) cache
+        return $this->icache['threads'] = $result;
+    }
 
-            // get all threads
-            $result = $this->conn->thread($folder, $this->threading,
-                $this->options['skip_deleted'] ? 'UNDELETED' : '', true);
 
-            // add to internal (fast) cache
-            $this->icache['threads'] = $result;
+    /**
+     * Method for direct fetching of threads data
+     *
+     * @param  string $folder Folder name
+     *
+     * @return rcube_imap_thread Thread data object
+     */
+    function threads_direct($folder)
+    {
+        if (!$this->check_connection()) {
+            return new rcube_result_thread();
         }
 
-        return $this->icache['threads'];
+        // get all threads
+        return $this->conn->thread($folder, $this->threading,
+            $this->options['skip_deleted'] ? 'UNDELETED' : '', true);
     }
 
 
@@ -1180,12 +1188,13 @@ class rcube_imap extends rcube_storage
      * @param string $folder     Folder to get index from
      * @param string $sort_field Sort column
      * @param string $sort_order Sort order [ASC, DESC]
+     * @param bool   $no_threads Get not threaded index
      *
      * @return rcube_result_index|rcube_result_thread List of messages (UIDs)
      */
-    public function index($folder = '', $sort_field = NULL, $sort_order = NULL)
+    public function index($folder = '', $sort_field = NULL, $sort_order = NULL, $no_threads = false)
     {
-        if ($this->threading) {
+        if (!$no_threads && $this->threading) {
             return $this->thread_index($folder, $sort_field, $sort_order);
         }
 
@@ -1244,17 +1253,13 @@ class rcube_imap extends rcube_storage
      * @param string $folder     Folder to get index from
      * @param string $sort_field Sort column
      * @param string $sort_order Sort order [ASC, DESC]
-     * @param bool   $skip_cache Disables cache usage
      *
      * @return rcube_result_index Sorted list of message UIDs
      */
-    public function index_direct($folder, $sort_field = null, $sort_order = null, $skip_cache = true)
+    public function index_direct($folder, $sort_field = null, $sort_order = null)
     {
-        if (!$skip_cache && ($mcache = $this->get_mcache_engine())) {
-            $index = $mcache->get_index($folder, $sort_field, $sort_order);
-        }
         // use message index sort as default sorting
-        else if (!$sort_field) {
+        if (!$sort_field) {
             // use search result from count() if possible
             if ($this->options['skip_deleted'] && !empty($this->icache['undeleted_idx'])
                 && $this->icache['undeleted_idx']->get_parameters('ALL') !== null
@@ -1315,7 +1320,7 @@ class rcube_imap extends rcube_storage
         }
         else {
             // get all threads (default sort order)
-            $threads = $this->fetch_threads($folder);
+            $threads = $this->threads($folder);
         }
 
         $this->set_sort_order($sort_field, $sort_order);
@@ -1326,9 +1331,10 @@ class rcube_imap extends rcube_storage
 
 
     /**
-     * Sort threaded result, using THREAD=REFS method
+     * Sort threaded result, using THREAD=REFS method if available.
+     * If not, use any method and re-sort the result in THREAD=REFS way.
      *
-     * @param rcube_result_thread $threads  Threads result set
+     * @param rcube_result_thread $threads Threads result set
      */
     protected function sort_threads($threads)
     {
@@ -1340,17 +1346,16 @@ class rcube_imap extends rcube_storage
         // THREAD=REFERENCES:     sorting by sent date of root message
         // THREAD=REFS:           sorting by the most recent date in each thread
 
-        if ($this->sort_field && ($this->sort_field != 'date' || $this->get_capability('THREAD') != 'REFS')) {
-            $index = $this->index_direct($this->folder, $this->sort_field, $this->sort_order, false);
+        if ($this->get_capability('THREAD') != 'REFS') {
+            $sortby = $this->sort_field ? $this->sort_field : 'date';
+            $index  = $this->index($this->folder, $sortby, $this->sort_order, true);
 
             if (!$index->is_empty()) {
                 $threads->sort($index);
             }
         }
-        else {
-            if ($this->sort_order != $threads->get_parameters('ORDER')) {
-                $threads->revert();
-            }
+        else if ($this->sort_order != $threads->get_parameters('ORDER')) {
+            $threads->revert();
         }
     }
 
@@ -3691,7 +3696,7 @@ class rcube_imap extends rcube_storage
     {
         if ($this->caching && !$this->cache) {
             $rcube = rcube::get_instance();
-            $ttl = $rcube->config->get('message_cache_lifetime', '10d');
+            $ttl   = $rcube->config->get('imap_cache_ttl', '10d');
             $this->cache = $rcube->get_cache('IMAP', $this->caching, $ttl);
         }
 
@@ -3739,24 +3744,6 @@ class rcube_imap extends rcube_storage
         }
     }
 
-    /**
-     * Delete outdated cache entries
-     */
-    public function expunge_cache()
-    {
-        if ($this->mcache) {
-            $ttl = rcube::get_instance()->config->get('message_cache_lifetime', '10d');
-            $this->mcache->expunge($ttl);
-        }
-
-/*
-        // this cache is expunged by rcube class
-        if ($this->cache) {
-            $this->cache->expunge();
-        }
-*/
-    }
-
 
     /* --------------------------------
      *   message caching methods
@@ -3790,8 +3777,9 @@ class rcube_imap extends rcube_storage
         if ($this->messages_caching && !$this->mcache) {
             $rcube = rcube::get_instance();
             if (($dbh = $rcube->get_dbh()) && ($userid = $rcube->get_user_id())) {
+                $ttl = $rcube->config->get('messages_cache_ttl', '10d');
                 $this->mcache = new rcube_imap_cache(
-                    $dbh, $this, $userid, $this->options['skip_deleted']);
+                    $dbh, $this, $userid, $this->options['skip_deleted'], $ttl);
             }
         }
 
@@ -3813,6 +3801,15 @@ class rcube_imap extends rcube_storage
     }
 
 
+    /**
+     * Delete outdated cache entries
+     */
+    function cache_gc()
+    {
+        rcube_imap_cache::gc();
+    }
+
+
     /* --------------------------------
      *         protected methods
      * --------------------------------*/
@@ -4106,9 +4103,9 @@ class rcube_imap extends rcube_storage
         return $this->index($folder, $sort_field, $sort_order);
     }
 
-    public function message_index_direct($folder, $sort_field = null, $sort_order = null, $skip_cache = true)
+    public function message_index_direct($folder, $sort_field = null, $sort_order = null)
     {
-        return $this->index_direct($folder, $sort_field, $sort_order, $skip_cache);
+        return $this->index_direct($folder, $sort_field, $sort_order);
     }
 
     public function list_mailboxes($root='', $name='*', $filter=null, $rights=null, $skip_sort=false)
diff --git a/lib/ext/Roundcube/rcube_imap_cache.php b/lib/ext/Roundcube/rcube_imap_cache.php
index 47d9aaf..061ac54 100644
--- a/lib/ext/Roundcube/rcube_imap_cache.php
+++ b/lib/ext/Roundcube/rcube_imap_cache.php
@@ -49,6 +49,13 @@ class rcube_imap_cache
     private $userid;
 
     /**
+     * Expiration time in seconds
+     *
+     * @var int
+     */
+    private $ttl;
+
+    /**
      * Internal (in-memory) cache
      *
      * @var array
@@ -83,13 +90,25 @@ class rcube_imap_cache
 
     /**
      * Object constructor.
+     *
+     * @param rcube_db   $db           DB handler
+     * @param rcube_imap $imap         IMAP handler
+     * @param int        $userid       User identifier
+     * @param bool       $skip_deleted skip_deleted flag
+     * @param string     $ttl          Expiration time of memcache/apc items
+     *
      */
-    function __construct($db, $imap, $userid, $skip_deleted)
+    function __construct($db, $imap, $userid, $skip_deleted, $ttl=0)
     {
+        // convert ttl string to seconds
+        $ttl = get_offset_sec($ttl);
+        if ($ttl > 2592000) $ttl = 2592000;
+
         $this->db           = $db;
         $this->imap         = $imap;
         $this->userid       = $userid;
         $this->skip_deleted = $skip_deleted;
+        $this->ttl          = $ttl;
     }
 
 
@@ -215,9 +234,7 @@ class rcube_imap_cache
      * Return messages thread.
      * If threaded index doesn't exist or is invalid, will be updated.
      *
-     * @param string  $mailbox     Folder name
-     * @param string  $sort_field  Sorting column
-     * @param string  $sort_order  Sorting order (ASC|DESC)
+     * @param string $mailbox Folder name
      *
      * @return array Messages threaded index
      */
@@ -256,19 +273,11 @@ class rcube_imap_cache
         if ($index === null) {
             // Get mailbox data (UIDVALIDITY, counters, etc.) for status check
             $mbox_data = $this->imap->folder_data($mailbox);
-
-            if ($mbox_data['EXISTS']) {
-                // get all threads (default sort order)
-                $threads = $this->imap->fetch_threads($mailbox, true);
-            }
-            else {
-                $threads = new rcube_result_thread($mailbox, '* THREAD');
-            }
-
-            $index['object'] = $threads;
+            // Get THREADS result
+            $index['object'] = $this->get_thread_data($mailbox, $mbox_data);
 
             // insert/update
-            $this->add_thread_row($mailbox, $threads, $mbox_data, $exists);
+            $this->add_thread_row($mailbox, $index['object'], $mbox_data, $exists);
         }
 
         $this->icache[$mailbox]['thread'] = $index;
@@ -407,8 +416,8 @@ class rcube_imap_cache
             return;
         }
 
-        $msg   = serialize($this->db->encode(clone $message));
         $flags = 0;
+        $msg   = clone $message;
 
         if (!empty($message->flags)) {
             foreach ($this->flags as $idx => $flag) {
@@ -417,14 +426,16 @@ class rcube_imap_cache
                 }
             }
         }
+
         unset($msg->flags);
+        $msg = $this->db->encode($msg, true);
 
         // update cache record (even if it exists, the update
         // here will work as select, assume row exist if affected_rows=0)
         if (!$force) {
             $res = $this->db->query(
                 "UPDATE ".$this->db->table_name('cache_messages')
-                ." SET flags = ?, data = ?, changed = ".$this->db->now()
+                ." SET flags = ?, data = ?, expires = " . ($this->ttl ? $this->db->now($this->ttl) : 'NULL')
                 ." WHERE user_id = ?"
                     ." AND mailbox = ?"
                     ." AND uid = ?",
@@ -435,12 +446,29 @@ class rcube_imap_cache
             }
         }
 
+        $this->db->set_option('ignore_key_errors', true);
+
         // insert new record
-        $this->db->query(
+        $res = $this->db->query(
             "INSERT INTO ".$this->db->table_name('cache_messages')
-            ." (user_id, mailbox, uid, flags, changed, data)"
-            ." VALUES (?, ?, ?, ?, ".$this->db->now().", ?)",
+            ." (user_id, mailbox, uid, flags, expires, data)"
+            ." VALUES (?, ?, ?, ?, ". ($this->ttl ? $this->db->now($this->ttl) : 'NULL') . ", ?)",
             $this->userid, $mailbox, (int) $message->uid, $flags, $msg);
+
+        // race-condition, insert failed so try update (#1489146)
+        // thanks to ignore_key_errors "duplicate row" errors will be ignored
+        if ($force && !$res && !$this->db->is_error($res)) {
+            $this->db->query(
+                "UPDATE ".$this->db->table_name('cache_messages')
+                ." SET expires = " . ($this->ttl ? $this->db->now($this->ttl) : 'NULL')
+                    .", flags = ?, data = ?"
+                ." WHERE user_id = ?"
+                    ." AND mailbox = ?"
+                    ." AND uid = ?",
+                $flags, $msg, $this->userid, $mailbox, (int) $message->uid);
+        }
+
+        $this->db->set_option('ignore_key_errors', false);
     }
 
 
@@ -481,7 +509,7 @@ class rcube_imap_cache
 
         $this->db->query(
             "UPDATE ".$this->db->table_name('cache_messages')
-            ." SET changed = ".$this->db->now()
+            ." SET expires = ". ($this->ttl ? $this->db->now($this->ttl) : 'NULL')
             .", flags = flags ".($enabled ? "+ $idx" : "- $idx")
             ." WHERE user_id = ?"
                 ." AND mailbox = ?"
@@ -604,23 +632,21 @@ class rcube_imap_cache
 
 
     /**
-     * Delete cache entries older than TTL
-     *
-     * @param string $ttl  Lifetime of message cache entries
+     * Delete expired cache entries
      */
-    function expunge($ttl)
+    static function gc()
     {
-        // get expiration timestamp
-        $ts = get_offset_time($ttl, -1);
+        $rcube = rcube::get_instance();
+        $db    = $rcube->get_dbh();
 
-        $this->db->query("DELETE FROM ".$this->db->table_name('cache_messages')
-              ." WHERE changed < " . $this->db->fromunixtime($ts));
+        $db->query("DELETE FROM ".$db->table_name('cache_messages')
+              ." WHERE expires < " . $db->now());
 
-        $this->db->query("DELETE FROM ".$this->db->table_name('cache_index')
-              ." WHERE changed < " . $this->db->fromunixtime($ts));
+        $db->query("DELETE FROM ".$db->table_name('cache_index')
+              ." WHERE expires < " . $db->now());
 
-        $this->db->query("DELETE FROM ".$this->db->table_name('cache_thread')
-              ." WHERE changed < " . $this->db->fromunixtime($ts));
+        $db->query("DELETE FROM ".$db->table_name('cache_thread')
+              ." WHERE expires < " . $db->now());
     }
 
 
@@ -639,7 +665,7 @@ class rcube_imap_cache
 
         if ($sql_arr = $this->db->fetch_assoc($sql_result)) {
             $data  = explode('@', $sql_arr['data']);
-            $index = @unserialize($data[0]);
+            $index = $this->db->decode($data[0], true);
             unset($data[0]);
 
             if (empty($index)) {
@@ -676,7 +702,7 @@ class rcube_imap_cache
 
         if ($sql_arr = $this->db->fetch_assoc($sql_result)) {
             $data   = explode('@', $sql_arr['data']);
-            $thread = @unserialize($data[0]);
+            $thread = $this->db->decode($data[0], true);
             unset($data[0]);
 
             if (empty($thread)) {
@@ -702,7 +728,7 @@ class rcube_imap_cache
         $data, $mbox_data = array(), $exists = false, $modseq = null)
     {
         $data = array(
-            serialize($data),
+            $this->db->encode($data, true),
             $sort_field,
             (int) $this->skip_deleted,
             (int) $mbox_data['UIDVALIDITY'],
@@ -712,20 +738,38 @@ class rcube_imap_cache
         $data = implode('@', $data);
 
         if ($exists) {
-            $sql_result = $this->db->query(
+            $res = $this->db->query(
                 "UPDATE ".$this->db->table_name('cache_index')
-                ." SET data = ?, valid = 1, changed = ".$this->db->now()
+                ." SET data = ?, valid = 1, expires = " . ($this->ttl ? $this->db->now($this->ttl) : 'NULL')
                 ." WHERE user_id = ?"
                     ." AND mailbox = ?",
                 $data, $this->userid, $mailbox);
+
+            if ($this->db->affected_rows($res)) {
+                return;
+            }
         }
-        else {
-            $sql_result = $this->db->query(
-                "INSERT INTO ".$this->db->table_name('cache_index')
-                ." (user_id, mailbox, data, valid, changed)"
-                ." VALUES (?, ?, ?, 1, ".$this->db->now().")",
-                $this->userid, $mailbox, $data);
+
+        $this->db->set_option('ignore_key_errors', true);
+
+        $res = $this->db->query(
+            "INSERT INTO ".$this->db->table_name('cache_index')
+            ." (user_id, mailbox, valid, expires, data)"
+            ." VALUES (?, ?, 1, ". ($this->ttl ? $this->db->now($this->ttl) : 'NULL') .", ?)",
+            $this->userid, $mailbox, $data);
+
+        // race-condition, insert failed so try update (#1489146)
+        // thanks to ignore_key_errors "duplicate row" errors will be ignored
+        if (!$exists && !$res && !$this->db->is_error($res)) {
+            $res = $this->db->query(
+                "UPDATE ".$this->db->table_name('cache_index')
+                ." SET data = ?, valid = 1, expires = " . ($this->ttl ? $this->db->now($this->ttl) : 'NULL')
+                ." WHERE user_id = ?"
+                    ." AND mailbox = ?",
+                $data, $this->userid, $mailbox);
         }
+
+        $this->db->set_option('ignore_key_errors', false);
     }
 
 
@@ -735,28 +779,48 @@ class rcube_imap_cache
     private function add_thread_row($mailbox, $data, $mbox_data = array(), $exists = false)
     {
         $data = array(
-            serialize($data),
+            $this->db->encode($data, true),
             (int) $this->skip_deleted,
             (int) $mbox_data['UIDVALIDITY'],
             (int) $mbox_data['UIDNEXT'],
         );
         $data = implode('@', $data);
 
+        $expires = ($this->ttl ? $this->db->now($this->ttl) : 'NULL');
+
         if ($exists) {
-            $sql_result = $this->db->query(
+            $res = $this->db->query(
                 "UPDATE ".$this->db->table_name('cache_thread')
-                ." SET data = ?, changed = ".$this->db->now()
+                ." SET data = ?, expires = $expires"
                 ." WHERE user_id = ?"
                     ." AND mailbox = ?",
                 $data, $this->userid, $mailbox);
+
+            if ($this->db->affected_rows($res)) {
+                return;
+            }
         }
-        else {
-            $sql_result = $this->db->query(
-                "INSERT INTO ".$this->db->table_name('cache_thread')
-                ." (user_id, mailbox, data, changed)"
-                ." VALUES (?, ?, ?, ".$this->db->now().")",
-                $this->userid, $mailbox, $data);
+
+        $this->db->set_option('ignore_key_errors', true);
+
+        $res = $this->db->query(
+            "INSERT INTO ".$this->db->table_name('cache_thread')
+            ." (user_id, mailbox, expires, data)"
+            ." VALUES (?, ?, $expires, ?)",
+            $this->userid, $mailbox, $data);
+
+        // race-condition, insert failed so try update (#1489146)
+        // thanks to ignore_key_errors "duplicate row" errors will be ignored
+        if (!$exists && !$res && !$this->db->is_error($res)) {
+            $this->db->query(
+                "UPDATE ".$this->db->table_name('cache_thread')
+                ." SET expires = $expires, data = ?"
+                ." WHERE user_id = ?"
+                    ." AND mailbox = ?",
+                $data, $this->userid, $mailbox);
         }
+
+        $this->db->set_option('ignore_key_errors', false);
     }
 
 
@@ -1004,7 +1068,7 @@ class rcube_imap_cache
 
                     $this->db->query(
                         "UPDATE ".$this->db->table_name('cache_messages')
-                        ." SET flags = ?, changed = ".$this->db->now()
+                        ." SET flags = ?, expires = " . ($this->ttl ? $this->db->now($this->ttl) : 'NULL')
                         ." WHERE user_id = ?"
                             ." AND mailbox = ?"
                             ." AND uid = ?"
@@ -1032,17 +1096,18 @@ class rcube_imap_cache
             }
         }
 
-        // Invalidate thread index (?)
-        if (!$index['valid']) {
-            $this->remove_thread($mailbox);
-        }
-
         $sort_field = $index['sort_field'];
         $sort_order = $index['object']->get_parameters('ORDER');
         $exists     = true;
 
         // Validate index
         if (!$this->validate($mailbox, $index, $exists)) {
+            // Invalidate (remove) thread index
+            // if $exists=false it was already removed in validate()
+            if ($exists) {
+                $this->remove_thread($mailbox);
+            }
+
             // Update index
             $data = $this->get_index_data($mailbox, $sort_field, $sort_order, $mbox_data);
         }
@@ -1067,7 +1132,7 @@ class rcube_imap_cache
      */
     private function build_message($sql_arr)
     {
-        $message = $this->db->decode(unserialize($sql_arr['data']));
+        $message = $this->db->decode($sql_arr['data'], true);
 
         if ($message) {
             $message->flags = array();
@@ -1150,6 +1215,25 @@ class rcube_imap_cache
 
         return $index;
     }
+
+
+    /**
+     * Fetches thread data from IMAP server
+     */
+    private function get_thread_data($mailbox, $mbox_data = array())
+    {
+        if (empty($mbox_data)) {
+            $mbox_data = $this->imap->folder_data($mailbox);
+        }
+
+        if ($mbox_data['EXISTS']) {
+            // get all threads (default sort order)
+            return $this->imap->threads_direct($mailbox);
+        }
+
+        return new rcube_result_thread($mailbox, '* THREAD');
+    }
+
 }
 
 // for backward compat.
diff --git a/lib/ext/Roundcube/rcube_ldap.php b/lib/ext/Roundcube/rcube_ldap.php
index 70163b2..39a48b4 100644
--- a/lib/ext/Roundcube/rcube_ldap.php
+++ b/lib/ext/Roundcube/rcube_ldap.php
@@ -185,8 +185,12 @@ class rcube_ldap extends rcube_addressbook
         $this->mail_domain = $mail_domain;
 
         // initialize cache
-        $rcube = rcube::get_instance();
-        $this->cache = $rcube->get_cache('LDAP.' . asciiwords($this->prop['name']), 'db', 600);
+        $rcube      = rcube::get_instance();
+        $cache_type = $rcube->config->get('ldap_cache', 'db');
+        $cache_ttl  = $rcube->config->get('ldap_cache_ttl', '10m');
+        $cache_name = 'LDAP.' . asciiwords($this->prop['name']);
+
+        $this->cache = $rcube->get_cache($cache_name, $cache_type, $cache_ttl);
 
         $this->_connect();
     }
diff --git a/lib/ext/Roundcube/rcube_mime.php b/lib/ext/Roundcube/rcube_mime.php
index 5258af5..572540f 100644
--- a/lib/ext/Roundcube/rcube_mime.php
+++ b/lib/ext/Roundcube/rcube_mime.php
@@ -587,23 +587,20 @@ class rcube_mime
      */
     public static function wordwrap($string, $width=75, $break="\n", $cut=false, $charset=null, $wrap_quoted=true)
     {
-        if (!$charset) {
-            $charset = RCUBE_CHARSET;
-        }
+        // Note: Never try to use iconv instead of mbstring functions here
+        //       Iconv's substr/strlen are 100x slower (#1489113)
 
-        // detect available functions
-        $strlen_func  = function_exists('iconv_strlen') ? 'iconv_strlen' : 'mb_strlen';
-        $strpos_func  = function_exists('iconv_strpos') ? 'iconv_strpos' : 'mb_strpos';
-        $strrpos_func = function_exists('iconv_strrpos') ? 'iconv_strrpos' : 'mb_strrpos';
-        $substr_func  = function_exists('iconv_substr') ? 'iconv_substr' : 'mb_substr';
+        if ($charset && $charset != RCUBE_CHARSET && function_exists('mb_internal_encoding')) {
+            mb_internal_encoding($charset);
+        }
 
         // Convert \r\n to \n, this is our line-separator
         $string       = str_replace("\r\n", "\n", $string);
         $separator    = "\n"; // must be 1 character length
         $result       = array();
 
-        while (($stringLength = $strlen_func($string, $charset)) > 0) {
-            $breakPos = $strpos_func($string, $separator, 0, $charset);
+        while (($stringLength = mb_strlen($string)) > 0) {
+            $breakPos = mb_strpos($string, $separator, 0);
 
             // quoted line (do not wrap)
             if ($wrap_quoted && $string[0] == '>') {
@@ -612,7 +609,7 @@ class rcube_mime
                     $cutLength = null;
                 }
                 else {
-                    $subString = $substr_func($string, 0, $breakPos, $charset);
+                    $subString = mb_substr($string, 0, $breakPos);
                     $cutLength = $breakPos + 1;
                 }
             }
@@ -623,39 +620,34 @@ class rcube_mime
                     $cutLength = null;
                 }
                 else {
-                    $subString = $substr_func($string, 0, $breakPos, $charset);
+                    $subString = mb_substr($string, 0, $breakPos);
                     $cutLength = $breakPos + 1;
                 }
             }
             else {
-                $subString = $substr_func($string, 0, $width, $charset);
+                $subString = mb_substr($string, 0, $width);
 
                 // last line
                 if ($breakPos === false && $subString === $string) {
                     $cutLength = null;
                 }
                 else {
-                    $nextChar = $substr_func($string, $width, 1, $charset);
+                    $nextChar = mb_substr($string, $width, 1);
 
                     if ($nextChar === ' ' || $nextChar === $separator) {
-                        $afterNextChar = $substr_func($string, $width + 1, 1, $charset);
+                        $afterNextChar = mb_substr($string, $width + 1, 1);
 
                         if ($afterNextChar === false) {
                             $subString .= $nextChar;
                         }
 
-                        $cutLength = $strlen_func($subString, $charset) + 1;
+                        $cutLength = mb_strlen($subString) + 1;
                     }
                     else {
-                        if ($strrpos_func[0] == 'm') {
-                            $spacePos = $strrpos_func($subString, ' ', 0, $charset);
-                        }
-                        else {
-                            $spacePos = $strrpos_func($subString, ' ', $charset);
-                        }
+                        $spacePos = mb_strrpos($subString, ' ', 0);
 
                         if ($spacePos !== false) {
-                            $subString = $substr_func($subString, 0, $spacePos, $charset);
+                            $subString = mb_substr($subString, 0, $spacePos);
                             $cutLength = $spacePos + 1;
                         }
                         else if ($cut === false && $breakPos === false) {
@@ -663,19 +655,19 @@ class rcube_mime
                             $cutLength = null;
                         }
                         else if ($cut === false) {
-                            $spacePos = $strpos_func($string, ' ', 0, $charset);
+                            $spacePos = mb_strpos($string, ' ', 0);
 
                             if ($spacePos !== false && $spacePos < $breakPos) {
-                                $subString = $substr_func($string, 0, $spacePos, $charset);
+                                $subString = mb_substr($string, 0, $spacePos);
                                 $cutLength = $spacePos + 1;
                             }
                             else {
-                                $subString = $substr_func($string, 0, $breakPos, $charset);
+                                $subString = mb_substr($string, 0, $breakPos);
                                 $cutLength = $breakPos + 1;
                             }
                         }
                         else {
-                            $subString = $substr_func($subString, 0, $width, $charset);
+                            $subString = mb_substr($subString, 0, $width);
                             $cutLength = $width;
                         }
                     }
@@ -685,13 +677,17 @@ class rcube_mime
             $result[] = $subString;
 
             if ($cutLength !== null) {
-                $string = $substr_func($string, $cutLength, ($stringLength - $cutLength), $charset);
+                $string = mb_substr($string, $cutLength, ($stringLength - $cutLength));
             }
             else {
                 break;
             }
         }
 
+        if ($charset && $charset != RCUBE_CHARSET && function_exists('mb_internal_encoding')) {
+            mb_internal_encoding(RCUBE_CHARSET);
+        }
+
         return implode($break, $result);
     }
 
diff --git a/lib/ext/Roundcube/rcube_session.php b/lib/ext/Roundcube/rcube_session.php
index dedde22..615ec6f 100644
--- a/lib/ext/Roundcube/rcube_session.php
+++ b/lib/ext/Roundcube/rcube_session.php
@@ -32,6 +32,7 @@ class rcube_session
     private $ip;
     private $start;
     private $changed;
+    private $time_diff = 0;
     private $reloaded = false;
     private $unsets = array();
     private $gc_handlers = array();
@@ -42,6 +43,7 @@ class rcube_session
     private $secret = '';
     private $ip_check = false;
     private $logging = false;
+    private $storage;
     private $memcache;
 
 
@@ -59,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'),
@@ -79,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'),
@@ -87,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'];
         }
     }
 
@@ -116,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
@@ -125,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 : '';
         }
@@ -152,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) {
@@ -171,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;
@@ -246,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
@@ -340,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;
     }
 
 
@@ -366,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
diff --git a/lib/ext/Roundcube/rcube_storage.php b/lib/ext/Roundcube/rcube_storage.php
index 700d12f..b17291b 100644
--- a/lib/ext/Roundcube/rcube_storage.php
+++ b/lib/ext/Roundcube/rcube_storage.php
@@ -986,6 +986,6 @@ abstract class rcube_storage
     /**
      * Delete outdated cache entries
      */
-    abstract function expunge_cache();
+    abstract function cache_gc();
 
 }  // end class rcube_storage
diff --git a/lib/ext/Roundcube/rcube_user.php b/lib/ext/Roundcube/rcube_user.php
index 505b190..5e9c9af 100644
--- a/lib/ext/Roundcube/rcube_user.php
+++ b/lib/ext/Roundcube/rcube_user.php
@@ -495,9 +495,9 @@ class rcube_user
             "INSERT INTO ".$dbh->table_name('users').
             " (created, last_login, username, mail_host, language)".
             " VALUES (".$dbh->now().", ".$dbh->now().", ?, ?, ?)",
-            strip_newlines($data['user']),
-            strip_newlines($data['host']),
-            strip_newlines($data['language']));
+            $data['user'],
+            $data['host'],
+            $data['language']);
 
         if ($user_id = $dbh->insert_id('users')) {
             // create rcube_user instance to make plugin hooks work
@@ -517,7 +517,7 @@ class rcube_user
                 if (empty($user_email)) {
                     $user_email = strpos($data['user'], '@') ? $user : sprintf('%s@%s', $data['user'], $mail_domain);
                 }
-                $email_list[] = strip_newlines($user_email);
+                $email_list[] = $user_email;
             }
             // identities_level check
             else if (count($email_list) > 1 && $rcube->config->get('identities_level', 0) > 1) {
@@ -547,7 +547,6 @@ class rcube_user
                     $record['name'] = $user_name != $record['email'] ? $user_name : '';
                 }
 
-                $record['name']     = strip_newlines($record['name']);
                 $record['user_id']  = $user_id;
                 $record['standard'] = $standard;
 
diff --git a/lib/ext/Roundcube/rcube_utils.php b/lib/ext/Roundcube/rcube_utils.php
index 8467107..29baa82 100644
--- a/lib/ext/Roundcube/rcube_utils.php
+++ b/lib/ext/Roundcube/rcube_utils.php
@@ -510,17 +510,24 @@ class rcube_utils
      */
     public static function file2class($mimetype, $filename)
     {
+        $mimetype = strtolower($mimetype);
+        $filename = strtolower($filename);
+
         list($primary, $secondary) = explode('/', $mimetype);
 
         $classes = array($primary ? $primary : 'unknown');
+
         if ($secondary) {
             $classes[] = $secondary;
         }
-        if (preg_match('/\.([a-z0-9]+)$/i', $filename, $m)) {
-            $classes[] = $m[1];
+
+        if (preg_match('/\.([a-z0-9]+)$/', $filename, $m)) {
+            if (!in_array($m[1], $classes)) {
+                $classes[] = $m[1];
+            }
         }
 
-        return strtolower(join(" ", $classes));
+        return join(" ", $classes);
     }
 
 
@@ -726,7 +733,7 @@ class rcube_utils
             return mktime(0,0,0, intval($matches[2]), intval($matches[3]), intval($matches[1]));
         }
         else if (is_numeric($date)) {
-            return $date;
+            return (int) $date;
         }
 
         // Clean malformed data
@@ -755,7 +762,7 @@ class rcube_utils
             $date = implode(' ', $d);
         }
 
-        return $ts;
+        return (int) $ts;
     }
 
 
diff --git a/lib/ext/Roundcube/rcube_washtml.php b/lib/ext/Roundcube/rcube_washtml.php
index a11371c..6b2efcc 100644
--- a/lib/ext/Roundcube/rcube_washtml.php
+++ b/lib/ext/Roundcube/rcube_washtml.php
@@ -113,10 +113,9 @@ class rcube_washtml
         'type', 'rows', 'cols', 'disabled', 'readonly', 'checked', 'multiple', 'value'
     );
 
-    /* Block elements which could be empty but cannot be returned in short form (<tag />) */
-    static $block_elements = array('div', 'p', 'pre', 'blockquote', 'a', 'font', 'center',
-        'table', 'ul', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'ol', 'dl', 'strong',
-        'i', 'b', 'u', 'span',
+    /* Elements which could be empty and be returned in short form (<tag />) */
+    static $void_elements = array('area', 'base', 'br', 'col', 'command', 'embed', 'hr',
+        'img', 'input', 'keygen', 'link', 'meta', 'param', 'source', 'track', 'wbr'
     );
 
     /* State for linked objects in HTML */
@@ -134,8 +133,8 @@ class rcube_washtml
     /* Ignore these HTML tags but process their content */
     private $_ignore_elements = array();
 
-    /* Block elements which could be empty but cannot be returned in short form (<tag />) */
-    private $_block_elements = array();
+    /* Elements which could be empty and be returned in short form (<tag />) */
+    private $_void_elements = array();
 
     /* Allowed HTML attributes */
     private $_html_attribs = array();
@@ -152,9 +151,9 @@ class rcube_washtml
         $this->_html_elements   = array_flip((array)$p['html_elements']) + array_flip(self::$html_elements) ;
         $this->_html_attribs    = array_flip((array)$p['html_attribs']) + array_flip(self::$html_attribs);
         $this->_ignore_elements = array_flip((array)$p['ignore_elements']) + array_flip(self::$ignore_elements);
-        $this->_block_elements  = array_flip((array)$p['block_elements']) + array_flip(self::$block_elements);
+        $this->_void_elements   = array_flip((array)$p['void_elements']) + array_flip(self::$void_elements);
 
-        unset($p['html_elements'], $p['html_attribs'], $p['ignore_elements'], $p['block_elements']);
+        unset($p['html_elements'], $p['html_attribs'], $p['ignore_elements'], $p['void_elements']);
 
         $this->config = $p + array('show_washed' => true, 'allow_remote' => false, 'cid_map' => array());
     }
@@ -321,7 +320,7 @@ class rcube_washtml
                 else if (isset($this->_html_elements[$tagName])) {
                     $content = $this->dumpHtml($node, $level);
                     $dump .= '<' . $tagName . $this->wash_attribs($node) .
-                        ($content != '' || isset($this->_block_elements[$tagName]) ? ">$content</$tagName>" : ' />');
+                        ($content === '' && isset($this->_void_elements[$tagName]) ? ' />' : ">$content</$tagName>");
                 }
                 else if (isset($this->_ignore_elements[$tagName])) {
                     $dump .= '<!-- ' . htmlspecialchars($tagName, ENT_QUOTES) . ' not allowed -->';





More information about the commits mailing list