lib/ext lib/kolab_sync_message.php

Aleksander Machniak machniak at kolabsys.com
Thu May 16 18:53:04 CEST 2013


 lib/ext/Roundcube/bootstrap.php             |    6 
 lib/ext/Roundcube/html.php                  |   36 ++++
 lib/ext/Roundcube/rcube.php                 |  205 +++++++++++++++++++++++++++-
 lib/ext/Roundcube/rcube_addressbook.php     |   26 ++-
 lib/ext/Roundcube/rcube_contacts.php        |   40 +++--
 lib/ext/Roundcube/rcube_csv2vcard.php       |   53 ++++++-
 lib/ext/Roundcube/rcube_db.php              |   57 ++++++-
 lib/ext/Roundcube/rcube_db_mysql.php        |    4 
 lib/ext/Roundcube/rcube_db_pgsql.php        |   15 +-
 lib/ext/Roundcube/rcube_enriched.php        |    2 
 lib/ext/Roundcube/rcube_image.php           |    4 
 lib/ext/Roundcube/rcube_imap.php            |    9 -
 lib/ext/Roundcube/rcube_imap_cache.php      |    4 
 lib/ext/Roundcube/rcube_imap_generic.php    |   68 ++++++---
 lib/ext/Roundcube/rcube_ldap.php            |   74 ++++++----
 lib/ext/Roundcube/rcube_message.php         |   10 -
 lib/ext/Roundcube/rcube_mime.php            |  199 ++++++++++++++++-----------
 lib/ext/Roundcube/rcube_output.php          |    2 
 lib/ext/Roundcube/rcube_plugin.php          |   34 ++++
 lib/ext/Roundcube/rcube_plugin_api.php      |  113 +++++++++++++++
 lib/ext/Roundcube/rcube_smtp.php            |   14 +
 lib/ext/Roundcube/rcube_spellchecker.php    |   17 +-
 lib/ext/Roundcube/rcube_string_replacer.php |    4 
 lib/ext/Roundcube/rcube_utils.php           |   18 ++
 lib/ext/Roundcube/rcube_vcard.php           |   27 +++
 lib/kolab_sync_message.php                  |   30 ----
 26 files changed, 835 insertions(+), 236 deletions(-)

New commits:
commit 75a14b1aaaf2f1a1e28d50f338264063c5bbcd93
Author: Aleksander Machniak <alec at alec.pl>
Date:   Thu May 16 18:52:48 2013 +0200

    Update Roundcube Framework

diff --git a/lib/ext/Roundcube/bootstrap.php b/lib/ext/Roundcube/bootstrap.php
index 929a4ff..b7e69cb 100644
--- a/lib/ext/Roundcube/bootstrap.php
+++ b/lib/ext/Roundcube/bootstrap.php
@@ -46,8 +46,10 @@ if (php_sapi_name() != 'cli') {
 
 foreach ($config as $optname => $optval) {
     if ($optval != ini_get($optname) && @ini_set($optname, $optval) === false) {
-        die("ERROR: Wrong '$optname' option value and it wasn't possible to set it to required value ($optval).\n"
-            ."Check your PHP configuration (including php_admin_flag).");
+        $error = "ERROR: Wrong '$optname' option value and it wasn't possible to set it to required value ($optval).\n"
+            . "Check your PHP configuration (including php_admin_flag).";
+        if (defined('STDERR')) fwrite(STDERR, $error); else echo $error;
+        exit(1);
     }
 }
 
diff --git a/lib/ext/Roundcube/html.php b/lib/ext/Roundcube/html.php
index 7b30e60..830ada9 100644
--- a/lib/ext/Roundcube/html.php
+++ b/lib/ext/Roundcube/html.php
@@ -218,7 +218,7 @@ class html
             $attr = array('src' => $attr);
         }
         return self::tag('iframe', $attr, $cont, array_merge(self::$common_attrib,
-            array('src','name','width','height','border','frameborder')));
+            array('src','name','width','height','border','frameborder','onload')));
     }
 
     /**
@@ -678,6 +678,11 @@ class html_table extends html
     {
         $default_attrib = self::$doctype == 'xhtml' ? array('summary' => '', 'border' => 0) : array();
         $this->attrib = array_merge($attrib, $default_attrib);
+
+        if (!empty($attrib['tagname']) && $attrib['tagname'] != 'table') {
+          $this->tagname = $attrib['tagname'];
+          $this->allowed = self::$common_attrib;
+        }
     }
 
     /**
@@ -816,19 +821,20 @@ class html_table extends html
         if (!empty($this->header)) {
             $rowcontent = '';
             foreach ($this->header as $c => $col) {
-                $rowcontent .= self::tag('td', $col->attrib, $col->content);
+                $rowcontent .= self::tag($this->_col_tagname(), $col->attrib, $col->content);
             }
-            $thead = self::tag('thead', null, self::tag('tr', null, $rowcontent, parent::$common_attrib));
+            $thead = $this->tagname == 'table' ? self::tag('thead', null, self::tag('tr', null, $rowcontent, parent::$common_attrib)) :
+                self::tag($this->_row_tagname(), array('class' => 'thead'), $rowcontent, parent::$common_attrib);
         }
 
         foreach ($this->rows as $r => $row) {
             $rowcontent = '';
             foreach ($row->cells as $c => $col) {
-                $rowcontent .= self::tag('td', $col->attrib, $col->content);
+                $rowcontent .= self::tag($this->_col_tagname(), $col->attrib, $col->content);
             }
 
             if ($r < $this->rowindex || count($row->cells)) {
-                $tbody .= self::tag('tr', $row->attrib, $rowcontent, parent::$common_attrib);
+                $tbody .= self::tag($this->_row_tagname(), $row->attrib, $rowcontent, parent::$common_attrib);
             }
         }
 
@@ -837,7 +843,7 @@ class html_table extends html
         }
 
         // add <tbody>
-        $this->content = $thead . self::tag('tbody', null, $tbody);
+        $this->content = $thead . ($this->tagname == 'table' ? self::tag('tbody', null, $tbody) : $tbody);
 
         unset($this->attrib['cols'], $this->attrib['rowsonly']);
         return parent::show();
@@ -862,4 +868,22 @@ class html_table extends html
         $this->rowindex = 0;
     }
 
+    /**
+     * Getter for the corresponding tag name for table row elements
+     */
+    private function _row_tagname()
+    {
+        static $row_tagnames = array('table' => 'tr', 'ul' => 'li', '*' => 'div');
+        return $row_tagnames[$this->tagname] ?: $row_tagnames['*'];
+    }
+
+    /**
+     * Getter for the corresponding tag name for table cell elements
+     */
+    private function _col_tagname()
+    {
+        static $col_tagnames = array('table' => 'td', '*' => 'span');
+        return $col_tagnames[$this->tagname] ?: $col_tagnames['*'];
+    }
+
 }
diff --git a/lib/ext/Roundcube/rcube.php b/lib/ext/Roundcube/rcube.php
index 77da83d..eea2fde 100644
--- a/lib/ext/Roundcube/rcube.php
+++ b/lib/ext/Roundcube/rcube.php
@@ -1082,6 +1082,9 @@ class rcube
                 'message' => $arg->getMessage(),
             );
         }
+        else if (is_string($arg)) {
+            $arg = array('message' => $arg, 'type' => 'php');
+        }
 
         if (empty($arg['code'])) {
             $arg['code'] = 500;
@@ -1094,14 +1097,24 @@ class rcube
             return;
         }
 
-        if (($log || $terminate) && $arg['type'] && $arg['message']) {
+        $cli = php_sapi_name() == 'cli';
+
+        if (($log || $terminate) && !$cli && $arg['type'] && $arg['message']) {
             $arg['fatal'] = $terminate;
             self::log_bug($arg);
         }
 
-        // display error page and terminate script
-        if ($terminate && is_object(self::$instance->output)) {
-            self::$instance->output->raise_error($arg['code'], $arg['message']);
+        // terminate script
+        if ($terminate) {
+            // display error page
+            if (is_object(self::$instance->output)) {
+                self::$instance->output->raise_error($arg['code'], $arg['message']);
+            }
+            else if ($cli) {
+                fwrite(STDERR, 'ERROR: ' . $arg['message']);
+            }
+
+            exit(1);
         }
     }
 
@@ -1140,7 +1153,7 @@ class rcube
 
             if (!self::write_log('errors', $log_entry)) {
                 // send error to PHPs error handler if write_log didn't succeed
-                trigger_error($arg_arr['message']);
+                trigger_error($arg_arr['message'], E_USER_WARNING);
             }
         }
 
@@ -1278,6 +1291,188 @@ class rcube
             return $_SESSION['language'];
         }
     }
+
+    /**
+     * Unique Message-ID generator.
+     *
+     * @return string Message-ID
+     */
+    public function gen_message_id()
+    {
+        $local_part  = md5(uniqid('rcube'.mt_rand(), true));
+        $domain_part = $this->user->get_username('domain');
+
+        // Try to find FQDN, some spamfilters doesn't like 'localhost' (#1486924)
+        if (!preg_match('/\.[a-z]+$/i', $domain_part)) {
+            foreach (array($_SERVER['HTTP_HOST'], $_SERVER['SERVER_NAME']) as $host) {
+                $host = preg_replace('/:[0-9]+$/', '', $host);
+                if ($host && preg_match('/\.[a-z]+$/i', $host)) {
+                    $domain_part = $host;
+                }
+            }
+        }
+
+        return sprintf('<%s@%s>', $local_part, $domain_part);
+    }
+
+    /**
+     * Send the given message using the configured method.
+     *
+     * @param object $message    Reference to Mail_MIME object
+     * @param string $from       Sender address string
+     * @param array  $mailto     Array of recipient address strings
+     * @param array  $error      SMTP error array (reference)
+     * @param string $body_file  Location of file with saved message body (reference),
+     *                           used when delay_file_io is enabled
+     * @param array  $options    SMTP options (e.g. DSN request)
+     *
+     * @return boolean Send status.
+     */
+    public function deliver_message(&$message, $from, $mailto, &$error, &$body_file = null, $options = null)
+    {
+        $plugin = $this->plugins->exec_hook('message_before_send', array(
+            'message' => $message,
+            'from'    => $from,
+            'mailto'  => $mailto,
+            'options' => $options,
+        ));
+
+        $from    = $plugin['from'];
+        $mailto  = $plugin['mailto'];
+        $options = $plugin['options'];
+        $message = $plugin['message'];
+        $headers = $message->headers();
+
+        // send thru SMTP server using custom SMTP library
+        if ($this->config->get('smtp_server')) {
+            // generate list of recipients
+            $a_recipients = array($mailto);
+
+            if (strlen($headers['Cc']))
+                $a_recipients[] = $headers['Cc'];
+            if (strlen($headers['Bcc']))
+                $a_recipients[] = $headers['Bcc'];
+
+            // clean Bcc from header for recipients
+            $send_headers = $headers;
+            unset($send_headers['Bcc']);
+            // here too, it because txtHeaders() below use $message->_headers not only $send_headers
+            unset($message->_headers['Bcc']);
+
+            $smtp_headers = $message->txtHeaders($send_headers, true);
+
+            if ($message->getParam('delay_file_io')) {
+                // use common temp dir
+                $temp_dir = $this->config->get('temp_dir');
+                $body_file = tempnam($temp_dir, 'rcmMsg');
+                if (PEAR::isError($mime_result = $message->saveMessageBody($body_file))) {
+                    self::raise_error(array('code' => 650, 'type' => 'php',
+                        'file' => __FILE__, 'line' => __LINE__,
+                        'message' => "Could not create message: ".$mime_result->getMessage()),
+                        TRUE, FALSE);
+                    return false;
+                }
+                $msg_body = fopen($body_file, 'r');
+            }
+            else {
+                $msg_body = $message->get();
+            }
+
+            // send message
+            if (!is_object($this->smtp)) {
+                $this->smtp_init(true);
+            }
+
+            $sent     = $this->smtp->send_mail($from, $a_recipients, $smtp_headers, $msg_body, $options);
+            $response = $this->smtp->get_response();
+            $error    = $this->smtp->get_error();
+
+            // log error
+            if (!$sent) {
+                self::raise_error(array('code' => 800, 'type' => 'smtp',
+                    'line' => __LINE__, 'file' => __FILE__,
+                    'message' => "SMTP error: ".join("\n", $response)), TRUE, FALSE);
+            }
+        }
+        // send mail using PHP's mail() function
+        else {
+            // unset some headers because they will be added by the mail() function
+            $headers_enc = $message->headers($headers);
+            $headers_php = $message->_headers;
+            unset($headers_php['To'], $headers_php['Subject']);
+
+            // reset stored headers and overwrite
+            $message->_headers = array();
+            $header_str = $message->txtHeaders($headers_php);
+
+            // #1485779
+            if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
+                if (preg_match_all('/<([^@]+@[^>]+)>/', $headers_enc['To'], $m)) {
+                    $headers_enc['To'] = implode(', ', $m[1]);
+                }
+            }
+
+            $msg_body = $message->get();
+
+            if (PEAR::isError($msg_body)) {
+                self::raise_error(array('code' => 650, 'type' => 'php',
+                    'file' => __FILE__, 'line' => __LINE__,
+                    'message' => "Could not create message: ".$msg_body->getMessage()),
+                    TRUE, FALSE);
+            }
+            else {
+                $delim   = $this->config->header_delimiter();
+                $to      = $headers_enc['To'];
+                $subject = $headers_enc['Subject'];
+                $header_str = rtrim($header_str);
+
+                if ($delim != "\r\n") {
+                    $header_str = str_replace("\r\n", $delim, $header_str);
+                    $msg_body   = str_replace("\r\n", $delim, $msg_body);
+                    $to         = str_replace("\r\n", $delim, $to);
+                    $subject    = str_replace("\r\n", $delim, $subject);
+                }
+
+                if (ini_get('safe_mode'))
+                    $sent = mail($to, $subject, $msg_body, $header_str);
+                else
+                    $sent = mail($to, $subject, $msg_body, $header_str, "-f$from");
+            }
+        }
+
+        if ($sent) {
+            $this->plugins->exec_hook('message_sent', array('headers' => $headers, 'body' => $msg_body));
+
+            // remove MDN headers after sending
+            unset($headers['Return-Receipt-To'], $headers['Disposition-Notification-To']);
+
+            // get all recipients
+            if ($headers['Cc'])
+                $mailto .= $headers['Cc'];
+            if ($headers['Bcc'])
+                $mailto .= $headers['Bcc'];
+            if (preg_match_all('/<([^@]+@[^>]+)>/', $mailto, $m))
+                $mailto = implode(', ', array_unique($m[1]));
+
+            if ($this->config->get('smtp_log')) {
+                self::write_log('sendmail', sprintf("User %s [%s]; Message for %s; %s",
+                    $this->user->get_username(),
+                    $_SERVER['REMOTE_ADDR'],
+                    $mailto,
+                    !empty($response) ? join('; ', $response) : ''));
+            }
+        }
+
+        if (is_resource($msg_body)) {
+            fclose($msg_body);
+        }
+
+        $message->_headers = array();
+        $message->headers($headers);
+
+        return $sent;
+    }
+
 }
 
 
diff --git a/lib/ext/Roundcube/rcube_addressbook.php b/lib/ext/Roundcube/rcube_addressbook.php
index cbc3c67..d23ad36 100644
--- a/lib/ext/Roundcube/rcube_addressbook.php
+++ b/lib/ext/Roundcube/rcube_addressbook.php
@@ -309,9 +309,14 @@ abstract class rcube_addressbook
      * List all active contact groups of this source
      *
      * @param string  Optional search string to match group name
+     * @param int     Matching mode:
+     *                0 - partial (*abc*),
+     *                1 - strict (=),
+     *                2 - prefix (abc*)
+     *
      * @return array  Indexed list of contact groups, each a hash array
      */
-    function list_groups($search = null)
+    function list_groups($search = null, $mode = 0)
     {
         /* empty for address books don't supporting groups */
         return array();
@@ -370,9 +375,10 @@ abstract class rcube_addressbook
     /**
      * Add the given contact records the a certain group
      *
-     * @param string  Group identifier
-     * @param array   List of contact identifiers to be added
-     * @return int    Number of contacts added
+     * @param string       Group identifier
+     * @param array|string List of contact identifiers to be added
+     *
+     * @return int Number of contacts added
      */
     function add_to_group($group_id, $ids)
     {
@@ -383,9 +389,10 @@ abstract class rcube_addressbook
     /**
      * Remove the given contact records from a certain group
      *
-     * @param string  Group identifier
-     * @param array   List of contact identifiers to be removed
-     * @return int    Number of deleted group members
+     * @param string       Group identifier
+     * @param array|string List of contact identifiers to be removed
+     *
+     * @return int Number of deleted group members
      */
     function remove_from_group($group_id, $ids)
     {
@@ -425,7 +432,7 @@ abstract class rcube_addressbook
                     $out = array_merge($out, (array)$values);
                 }
                 else {
-                    list($f, $type) = explode(':', $c);
+                    list(, $type) = explode(':', $c);
                     $out[$type] = array_merge((array)$out[$type], (array)$values);
                 }
             }
@@ -528,7 +535,7 @@ abstract class rcube_addressbook
      */
     public static function compose_contact_key($contact, $sort_col)
     {
-        $key = $contact[$sort_col] . ':' . $row['sourceid'];
+        $key = $contact[$sort_col] . ':' . $contact['sourceid'];
 
         // add email to a key to not skip contacts with the same name (#1488375)
         if (!empty($contact['email'])) {
@@ -538,7 +545,6 @@ abstract class rcube_addressbook
          return $key;
     }
 
-
     /**
      * Compare search value with contact data
      *
diff --git a/lib/ext/Roundcube/rcube_contacts.php b/lib/ext/Roundcube/rcube_contacts.php
index c66e986..3919cdc 100644
--- a/lib/ext/Roundcube/rcube_contacts.php
+++ b/lib/ext/Roundcube/rcube_contacts.php
@@ -137,16 +137,34 @@ class rcube_contacts extends rcube_addressbook
      * List all active contact groups of this source
      *
      * @param string  Search string to match group name
+     * @param int     Matching mode:
+     *                0 - partial (*abc*),
+     *                1 - strict (=),
+     *                2 - prefix (abc*)
+     *
      * @return array  Indexed list of contact groups, each a hash array
      */
-    function list_groups($search = null)
+    function list_groups($search = null, $mode = 0)
     {
         $results = array();
 
         if (!$this->groups)
             return $results;
 
-        $sql_filter = $search ? " AND " . $this->db->ilike('name', '%'.$search.'%') : '';
+        if ($search) {
+            switch (intval($mode)) {
+            case 1:
+                $sql_filter = $this->db->ilike('name', $search);
+                break;
+            case 2:
+                $sql_filter = $this->db->ilike('name', $search . '%');
+                break;
+            default:
+                $sql_filter = $this->db->ilike('name', '%' . $search . '%');
+            }
+
+            $sql_filter = " AND $sql_filter";
+        }
 
         $sql_result = $this->db->query(
             "SELECT * FROM ".$this->db->table_name($this->db_groups).
@@ -626,10 +644,6 @@ class rcube_contacts extends rcube_addressbook
             $insert_id = $this->db->insert_id($this->db_name);
         }
 
-        // also add the newly created contact to the active group
-        if ($insert_id && $this->group_id)
-            $this->add_to_group($this->group_id, $insert_id);
-
         $this->cache = null;
 
         return $insert_id;
@@ -883,9 +897,10 @@ class rcube_contacts extends rcube_addressbook
     /**
      * Add the given contact records the a certain group
      *
-     * @param string  Group identifier
-     * @param array   List of contact identifiers to be added
-     * @return int    Number of contacts added 
+     * @param string       Group identifier
+     * @param array|string List of contact identifiers to be added
+     *
+     * @return int Number of contacts added
      */
     function add_to_group($group_id, $ids)
     {
@@ -930,9 +945,10 @@ class rcube_contacts extends rcube_addressbook
     /**
      * Remove the given contact records from a certain group
      *
-     * @param string  Group identifier
-     * @param array   List of contact identifiers to be removed
-     * @return int    Number of deleted group members
+     * @param string       Group identifier
+     * @param array|string List of contact identifiers to be removed
+     *
+     * @return int Number of deleted group members
      */
     function remove_from_group($group_id, $ids)
     {
diff --git a/lib/ext/Roundcube/rcube_csv2vcard.php b/lib/ext/Roundcube/rcube_csv2vcard.php
index 0d3276b..fb8d8f1 100644
--- a/lib/ext/Roundcube/rcube_csv2vcard.php
+++ b/lib/ext/Roundcube/rcube_csv2vcard.php
@@ -130,6 +130,21 @@ class rcube_csv2vcard
         'work_state'            => 'region:work',
         'home_city_short'       => 'locality:home',
         'home_state_short'      => 'region:home',
+
+        // Atmail
+        'date_of_birth'         => 'birthday',
+        'email'                 => 'email:pref',
+        'home_mobile'           => 'phone:cell',
+        'home_zip'              => 'zipcode:home',
+        'info'                  => 'notes',
+        'user_photo'            => 'photo',
+        'url'                   => 'website:homepage',
+        'work_company'          => 'organization',
+        'work_dept'             => 'departament',
+        'work_fax'              => 'phone:work,fax',
+        'work_mobile'           => 'phone:work,cell',
+        'work_title'            => 'jobtitle',
+        'work_zip'              => 'zipcode:work',
     );
 
     /**
@@ -230,8 +245,29 @@ class rcube_csv2vcard
         'work_phone'        => "Work Phone",
         'work_address'      => "Work Address",
         //'work_address_2'    => "Work Address 2",
+        'work_city'         => "Work City",
         'work_country'      => "Work Country",
+        'work_state'        => "Work State",
         'work_zipcode'      => "Work ZipCode",
+
+        // Atmail
+        'date_of_birth'     => "Date of Birth",
+        'email'             => "Email",
+        //'email_2'         => "Email2",
+        //'email_3'         => "Email3",
+        //'email_4'         => "Email4",
+        //'email_5'         => "Email5",
+        'home_mobile'       => "Home Mobile",
+        'home_zip'          => "Home Zip",
+        'info'              => "Info",
+        'user_photo'        => "User Photo",
+        'url'               => "URL",
+        'work_company'      => "Work Company",
+        'work_dept'         => "Work Dept",
+        'work_fax'          => "Work Fax",
+        'work_mobile'       => "Work Mobile",
+        'work_title'        => "Work Title",
+        'work_zip'          => "Work Zip",
     );
 
     protected $local_label_map = array();
@@ -268,7 +304,6 @@ class rcube_csv2vcard
     {
         // convert to UTF-8
         $head     = substr($csv, 0, 4096);
-        $fallback = rcube::get_instance()->config->get('default_charset', 'ISO-8859-1'); // fallback to Latin-1?
         $charset  = rcube_charset::detect($head, RCUBE_CHARSET);
         $csv      = rcube_charset::convert($csv, $charset);
         $head     = '';
@@ -276,7 +311,7 @@ class rcube_csv2vcard
         $this->map = array();
 
         // Parse file
-        foreach (preg_split("/[\r\n]+/", $csv) as $i => $line) {
+        foreach (preg_split("/[\r\n]+/", $csv) as $line) {
             $elements = $this->parse_line($line);
             if (empty($elements)) {
                 continue;
@@ -353,6 +388,12 @@ class rcube_csv2vcard
         if (!empty($this->local_label_map)) {
             for ($i = 0; $i < $size; $i++) {
                 $label = $this->local_label_map[$elements[$i]];
+
+                // special localization label
+                if ($label && $label[0] == '_') {
+                    $label = substr($label, 1);
+                }
+
                 if ($label && !empty($this->csv2vcard_map[$label])) {
                     $map2[$i] = $this->csv2vcard_map[$label];
                 }
@@ -384,9 +425,13 @@ class rcube_csv2vcard
             $contact['birthday'] = $contact['birthday-y'] .'-' .$contact['birthday-m'] . '-' . $contact['birthday-d'];
         }
 
+        // Empty dates, e.g. "0/0/00", "0000-00-00 00:00:00"
         foreach (array('birthday', 'anniversary') as $key) {
-            if (!empty($contact[$key]) && $contact[$key] == '0/0/00') { // @TODO: localization?
-                unset($contact[$key]);
+            if (!empty($contact[$key])) {
+                $date = preg_replace('/[0[:^word:]]/', '', $contact[$key]);
+                if (empty($date)) {
+                    unset($contact[$key]);
+                }
             }
         }
 
diff --git a/lib/ext/Roundcube/rcube_db.php b/lib/ext/Roundcube/rcube_db.php
index 4e6684c..5da38c8 100644
--- a/lib/ext/Roundcube/rcube_db.php
+++ b/lib/ext/Roundcube/rcube_db.php
@@ -47,6 +47,7 @@ class rcube_db
         'identifier_end'   => '"',
     );
 
+    const DEBUG_LINE_LENGTH = 4096;
 
     /**
      * Factory, returns driver-specific instance of the class
@@ -128,7 +129,7 @@ class rcube_db
         $dsn_string  = $this->dsn_string($dsn);
         $dsn_options = $this->dsn_options($dsn);
 
-        if ($db_pconn) {
+        if ($this->db_pconn) {
             $dsn_options[PDO::ATTR_PERSISTENT] = true;
         }
 
@@ -255,6 +256,11 @@ class rcube_db
     protected function debug($query)
     {
         if ($this->options['debug_mode']) {
+            if (($len = strlen($query)) > self::DEBUG_LINE_LENGTH) {
+                $diff  = $len - self::DEBUG_LINE_LENGTH;
+                $query = substr($query, 0, self::DEBUG_LINE_LENGTH)
+                    . "... [truncated $diff bytes]";
+            }
             rcube::write_log('sql', '[' . (++$this->db_index) . '] ' . $query . ';');
         }
     }
@@ -405,21 +411,22 @@ class rcube_db
         $this->db_error_msg = null;
 
         // send query
-        $query = $this->dbh->query($query);
+        $result = $this->dbh->query($query);
 
-        if ($query === false) {
+        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), true, false);
+                'message' => $this->db_error_msg . " (SQL Query: $query)"
+                ), true, false);
         }
 
-        $this->last_result = $query;
+        $this->last_result = $result;
 
-        return $query;
+        return $result;
     }
 
     /**
@@ -634,6 +641,22 @@ class rcube_db
     }
 
     /**
+     * Escapes a string so it can be safely used in a query
+     *
+     * @param string $str A string to escape
+     *
+     * @return string Escaped string for use in a query
+     */
+    public function escape($str)
+    {
+        if (is_null($str)) {
+            return 'NULL';
+        }
+
+        return substr($this->quote($str), 1, -1);
+    }
+
+    /**
      * Quotes a string so it can be safely used as a table or column name
      *
      * @param string $str Value to quote
@@ -648,6 +671,20 @@ class rcube_db
     }
 
     /**
+     * Escapes a string so it can be safely used in a query
+     *
+     * @param string $str A string to escape
+     *
+     * @return string Escaped string for use in a query
+     * @deprecated    Replaced by rcube_db::escape
+     * @see           rcube_db::escape
+     */
+    public function escapeSimple($str)
+    {
+        return $this->escape($str);
+    }
+
+    /**
      * Quotes a string so it can be safely used as a table or column name
      *
      * @param string $str Value to quote
@@ -816,11 +853,9 @@ class rcube_db
     {
         $rcube = rcube::get_instance();
 
-        // return table name if configured
-        $config_key = 'db_table_'.$table;
-
-        if ($name = $rcube->config->get($config_key)) {
-            return $name;
+        // add prefix to the table name if configured
+        if ($prefix = $rcube->config->get('db_prefix')) {
+            return $prefix . $table;
         }
 
         return $table;
diff --git a/lib/ext/Roundcube/rcube_db_mysql.php b/lib/ext/Roundcube/rcube_db_mysql.php
index 8ab6403..b2cbab2 100644
--- a/lib/ext/Roundcube/rcube_db_mysql.php
+++ b/lib/ext/Roundcube/rcube_db_mysql.php
@@ -127,7 +127,7 @@ class rcube_db_mysql extends rcube_db
         $result[PDO::MYSQL_ATTR_FOUND_ROWS] = true;
 
         // Enable AUTOCOMMIT mode (#1488902)
-        $dsn_options[PDO::ATTR_AUTOCOMMIT] = true;
+        $result[PDO::ATTR_AUTOCOMMIT] = true;
 
         return $result;
     }
@@ -147,7 +147,7 @@ class rcube_db_mysql extends rcube_db
 
             $result = $this->query('SHOW VARIABLES');
 
-            while ($sql_arr = $this->fetch_array($result)) {
+            while ($row = $this->fetch_array($result)) {
                 $this->variables[$row[0]] = $row[1];
             }
         }
diff --git a/lib/ext/Roundcube/rcube_db_pgsql.php b/lib/ext/Roundcube/rcube_db_pgsql.php
index cf23c5e..adfd220 100644
--- a/lib/ext/Roundcube/rcube_db_pgsql.php
+++ b/lib/ext/Roundcube/rcube_db_pgsql.php
@@ -53,19 +53,20 @@ class rcube_db_pgsql extends rcube_db
     /**
      * Return correct name for a specific database sequence
      *
-     * @param string $sequence Secuence name
+     * @param string $table Table name
      *
      * @return string Translated sequence name
      */
-    protected function sequence_name($sequence)
+    protected function sequence_name($table)
     {
-        $rcube = rcube::get_instance();
+        // Note: we support only one sequence per table
+        // Note: The sequence name must be <table_name>_seq
+        $sequence = $table . '_seq';
+        $rcube    = rcube::get_instance();
 
         // return sequence name if configured
-        $config_key = 'db_sequence_'.$sequence;
-
-        if ($name = $rcube->config->get($config_key)) {
-            return $name;
+        if ($prefix = $rcube->config->get('db_prefix')) {
+            return $prefix . $sequence;
         }
 
         return $sequence;
diff --git a/lib/ext/Roundcube/rcube_enriched.php b/lib/ext/Roundcube/rcube_enriched.php
index 8c628c9..12deb33 100644
--- a/lib/ext/Roundcube/rcube_enriched.php
+++ b/lib/ext/Roundcube/rcube_enriched.php
@@ -118,7 +118,7 @@ class rcube_enriched
             $quoted = '';
             $lines  = explode('<br>', $a[2]);
 
-            foreach ($lines as $n => $line)
+            foreach ($lines as $line)
                 $quoted .= '>'.$line.'<br>';
 
             $body = $a[1].'<span class="quotes">'.$quoted.'</span>'.$a[3];
diff --git a/lib/ext/Roundcube/rcube_image.php b/lib/ext/Roundcube/rcube_image.php
index a55ba16..735a0df 100644
--- a/lib/ext/Roundcube/rcube_image.php
+++ b/lib/ext/Roundcube/rcube_image.php
@@ -124,6 +124,7 @@ class rcube_image
             }
 
             if ($result === '') {
+                @chmod($filename, 0600);
                 return $type;
             }
         }
@@ -183,6 +184,7 @@ class rcube_image
             }
 
             if ($result) {
+                @chmod($filename, 0600);
                 return $type;
             }
         }
@@ -223,6 +225,7 @@ class rcube_image
             $result = rcube::exec($convert . ' 2>&1 -colorspace RGB -quality 75 {in} {type}:{out}', $p);
 
             if ($result === '') {
+                @chmod($filename, 0600);
                 return true;
             }
         }
@@ -256,6 +259,7 @@ class rcube_image
             }
 
             if ($result) {
+                @chmod($filename, 0600);
                 return true;
             }
         }
diff --git a/lib/ext/Roundcube/rcube_imap.php b/lib/ext/Roundcube/rcube_imap.php
index c679851..43c61fd 100644
--- a/lib/ext/Roundcube/rcube_imap.php
+++ b/lib/ext/Roundcube/rcube_imap.php
@@ -981,7 +981,7 @@ class rcube_imap extends rcube_storage
             // use memory less expensive (and quick) method for big result set
             $index = clone $this->index('', $this->sort_field, $this->sort_order);
             // get messages uids for one page...
-            $index->slice($start_msg, min($cnt-$from, $this->page_size));
+            $index->slice($from, min($cnt-$from, $this->page_size));
 
             if ($slice) {
                 $index->slice(-$slice, $slice);
@@ -1423,8 +1423,6 @@ class rcube_imap extends rcube_storage
      */
     protected function search_index($folder, $criteria='ALL', $charset=NULL, $sort_field=NULL)
     {
-        $orig_criteria = $criteria;
-
         if (!$this->check_connection()) {
             if ($this->threading) {
                 return new rcube_result_thread();
@@ -2727,7 +2725,7 @@ class rcube_imap extends rcube_storage
 
         // filter folders list according to rights requirements
         if ($rights && $this->get_capability('ACL')) {
-            $a_folders = $this->filter_rights($a_folders, $rights);
+            $a_mboxes = $this->filter_rights($a_mboxes, $rights);
         }
 
         // filter folders and sort them
@@ -2783,7 +2781,6 @@ class rcube_imap extends rcube_storage
      */
     private function list_folders_update(&$result, $type = null)
     {
-        $delim     = $this->get_hierarchy_delimiter();
         $namespace = $this->get_namespace();
         $search    = array();
 
@@ -3846,7 +3843,7 @@ class rcube_imap extends rcube_storage
         $delimiter = $this->get_hierarchy_delimiter();
 
         // find default folders and skip folders starting with '.'
-        foreach ($a_folders as $i => $folder) {
+        foreach ($a_folders as $folder) {
             if ($folder[0] == '.') {
                 continue;
             }
diff --git a/lib/ext/Roundcube/rcube_imap_cache.php b/lib/ext/Roundcube/rcube_imap_cache.php
index 748474a..47d9aaf 100644
--- a/lib/ext/Roundcube/rcube_imap_cache.php
+++ b/lib/ext/Roundcube/rcube_imap_cache.php
@@ -430,7 +430,7 @@ class rcube_imap_cache
                     ." AND uid = ?",
                 $flags, $msg, $this->userid, $mailbox, (int) $message->uid);
 
-            if ($this->db->affected_rows()) {
+            if ($this->db->affected_rows($res)) {
                 return;
             }
         }
@@ -983,7 +983,7 @@ class rcube_imap_cache
                 $uids, true, array('FLAGS'), $index['modseq'], $qresync);
 
             if (!empty($result)) {
-                foreach ($result as $id => $msg) {
+                foreach ($result as $msg) {
                     $uid = $msg->uid;
                     // Remove deleted message
                     if ($this->skip_deleted && !empty($msg->flags['DELETED'])) {
diff --git a/lib/ext/Roundcube/rcube_imap_generic.php b/lib/ext/Roundcube/rcube_imap_generic.php
index 04dc594..292b932 100644
--- a/lib/ext/Roundcube/rcube_imap_generic.php
+++ b/lib/ext/Roundcube/rcube_imap_generic.php
@@ -72,6 +72,8 @@ class rcube_imap_generic
     const COMMAND_CAPABILITY = 2;
     const COMMAND_LASTLINE   = 4;
 
+    const DEBUG_LINE_LENGTH = 4098; // 4KB + 2B for \r\n
+
     /**
      * Object constructor
      */
@@ -746,7 +748,7 @@ class rcube_imap_generic
         }
 
         if ($this->prefs['timeout'] <= 0) {
-            $this->prefs['timeout'] = ini_get('default_socket_timeout');
+            $this->prefs['timeout'] = max(0, intval(ini_get('default_socket_timeout')));
         }
 
         // Connect
@@ -1077,7 +1079,7 @@ class rcube_imap_generic
         }
 
         if (!$this->data['READ-WRITE']) {
-            $this->setError(self::ERROR_READONLY, "Mailbox is read-only", 'EXPUNGE');
+            $this->setError(self::ERROR_READONLY, "Mailbox is read-only");
             return false;
         }
 
@@ -1652,7 +1654,6 @@ class rcube_imap_generic
         }
 
         if (!empty($criteria)) {
-            $modseq = stripos($criteria, 'MODSEQ') !== false;
             $params .= ($params ? ' ' : '') . $criteria;
         }
         else {
@@ -1791,7 +1792,6 @@ class rcube_imap_generic
                 if ($skip_deleted && preg_match('/FLAGS \(([^)]+)\)/', $line, $matches)) {
                     $flags = explode(' ', strtoupper($matches[1]));
                     if (in_array('\\DELETED', $flags)) {
-                        $deleted[$id] = $id;
                         continue;
                     }
                 }
@@ -1936,7 +1936,7 @@ class rcube_imap_generic
         }
 
         if (!$this->data['READ-WRITE']) {
-            $this->setError(self::ERROR_READONLY, "Mailbox is read-only", 'STORE');
+            $this->setError(self::ERROR_READONLY, "Mailbox is read-only");
             return false;
         }
 
@@ -1997,7 +1997,7 @@ class rcube_imap_generic
         }
 
         if (!$this->data['READ-WRITE']) {
-            $this->setError(self::ERROR_READONLY, "Mailbox is read-only", 'STORE');
+            $this->setError(self::ERROR_READONLY, "Mailbox is read-only");
             return false;
         }
 
@@ -2172,7 +2172,7 @@ class rcube_imap_generic
                 // create array with header field:data
                 if (!empty($headers)) {
                     $headers = explode("\n", trim($headers));
-                    foreach ($headers as $hid => $resln) {
+                    foreach ($headers as $resln) {
                         if (ord($resln[0]) <= 32) {
                             $lines[$ln] .= (empty($lines[$ln]) ? '' : "\n") . trim($resln);
                         } else {
@@ -2180,7 +2180,7 @@ class rcube_imap_generic
                         }
                     }
 
-                    while (list($lines_key, $str) = each($lines)) {
+                    foreach ($lines as $str) {
                         list($field, $string) = explode(':', $str, 2);
 
                         $field  = strtolower($field);
@@ -2475,6 +2475,7 @@ class rcube_imap_generic
         $key     = $this->nextTag();
         $request = $key . ($is_uid ? ' UID' : '') . " FETCH $id ($fetch_mode.PEEK[$part]$partial)";
         $result  = false;
+        $found   = false;
 
         // send request
         if (!$this->putLine($request)) {
@@ -2494,18 +2495,25 @@ class rcube_imap_generic
                 break;
             }
 
-            if (!preg_match('/^\* ([0-9]+) FETCH (.*)$/', $line, $m)) {
+            // skip irrelevant untagged responses (we have a result already)
+            if ($found || !preg_match('/^\* ([0-9]+) FETCH (.*)$/', $line, $m)) {
                 continue;
             }
 
             $line = $m[2];
-            $last = substr($line, -1);
 
             // handle one line response
-            if ($line[0] == '(' && $last == ')') {
+            if ($line[0] == '(' && substr($line, -1) == ')') {
                 // tokenize content inside brackets
-                $tokens = $this->tokenizeResponse(preg_replace('/(^\(|\$)/', '', $line));
-                $result = count($tokens) == 1 ? $tokens[0] : false;
+                $tokens = $this->tokenizeResponse(preg_replace('/(^\(|\)$)/', '', $line));
+
+                for ($i=0; $i<count($tokens); $i+=2) {
+                    if (preg_match('/^(BODY|BINARY)/i', $tokens[$i])) {
+                        $result = $tokens[$i+1];
+                        $found  = true;
+                        break;
+                    }
+                }
 
                 if ($result !== false) {
                     if ($mode == 1) {
@@ -2523,6 +2531,7 @@ class rcube_imap_generic
             else if (preg_match('/\{([0-9]+)\}$/', $line, $m)) {
                 $bytes = (int) $m[1];
                 $prev  = '';
+                $found = true;
 
                 while ($bytes > 0) {
                     $line = $this->readLine(8192);
@@ -3531,7 +3540,7 @@ class rcube_imap_generic
 
         if (is_array($element)) {
             reset($element);
-            while (list($key, $value) = each($element)) {
+            foreach ($element as $value) {
                 $string .= ' ' . self::r_implode($value);
             }
         }
@@ -3667,8 +3676,20 @@ class rcube_imap_generic
      */
     static function strToTime($date)
     {
-        // support non-standard "GMTXXXX" literal
-        $date = preg_replace('/GMT\s*([+-][0-9]+)/', '\\1', $date);
+        // Clean malformed data
+        $date = preg_replace(
+            array(
+                '/GMT\s*([+-][0-9]+)/',                     // support non-standard "GMTXXXX" literal
+                '/[^a-z0-9\x20\x09:+-]/i',                  // remove any invalid characters
+                '/\s*(Mon|Tue|Wed|Thu|Fri|Sat|Sun)\s*/i',   // remove weekday names
+            ),
+            array(
+                '\\1',
+                '',
+                '',
+            ), $date);
+
+        $date = trim($date);
 
         // if date parsing fails, we have a date in non-rfc format
         // remove token from the end and try again
@@ -3738,9 +3759,10 @@ class rcube_imap_generic
     /**
      * Set the value of the debugging flag.
      *
-     * @param   boolean $debug      New value for the debugging flag.
+     * @param boolean  $debug   New value for the debugging flag.
+     * @param callback $handler Logging handler function
      *
-     * @since   0.5-stable
+     * @since 0.5-stable
      */
     function setDebug($debug, $handler = null)
     {
@@ -3751,12 +3773,18 @@ class rcube_imap_generic
     /**
      * Write the given debug text to the current debug output handler.
      *
-     * @param   string  $message    Debug mesage text.
+     * @param string $message Debug mesage text.
      *
-     * @since   0.5-stable
+     * @since 0.5-stable
      */
     private function debug($message)
     {
+        if (($len = strlen($message)) > self::DEBUG_LINE_LENGTH) {
+            $diff    = $len - self::DEBUG_LINE_LENGTH;
+            $message = substr($message, 0, self::DEBUG_LINE_LENGTH)
+                . "... [truncated $diff bytes]";
+        }
+
         if ($this->resourceid) {
             $message = sprintf('[%s] %s', $this->resourceid, $message);
         }
diff --git a/lib/ext/Roundcube/rcube_ldap.php b/lib/ext/Roundcube/rcube_ldap.php
index a2dd163..70163b2 100644
--- a/lib/ext/Roundcube/rcube_ldap.php
+++ b/lib/ext/Roundcube/rcube_ldap.php
@@ -169,7 +169,7 @@ class rcube_ldap extends rcube_addressbook
         // Build sub_fields filter
         if (!empty($this->prop['sub_fields']) && is_array($this->prop['sub_fields'])) {
             $this->sub_filter = '';
-            foreach ($this->prop['sub_fields'] as $attr => $class) {
+            foreach ($this->prop['sub_fields'] as $class) {
                 if (!empty($class)) {
                     $class = is_array($class) ? array_pop($class) : $class;
                     $this->sub_filter .= '(objectClass=' . $class . ')';
@@ -1035,7 +1035,6 @@ class rcube_ldap extends rcube_addressbook
                 $mail_field  = $this->fieldmap['email'];
 
                 // try to extract surname and firstname from displayname
-                $reverse_map = array_flip($this->fieldmap);
                 $name_parts  = preg_split('/[\s,.]+/', $save_data['name']);
 
                 if ($sn_field && $missing[$sn_field]) {
@@ -1107,7 +1106,7 @@ class rcube_ldap extends rcube_addressbook
         // Remove attributes that need to be added separately (child objects)
         $xfields = array();
         if (!empty($this->prop['sub_fields']) && is_array($this->prop['sub_fields'])) {
-            foreach ($this->prop['sub_fields'] as $xf => $xclass) {
+            foreach (array_keys($this->prop['sub_fields']) as $xf) {
                 if (!empty($newentry[$xf])) {
                     $xfields[$xf] = $newentry[$xf];
                     unset($newentry[$xf]);
@@ -1170,7 +1169,7 @@ class rcube_ldap extends rcube_addressbook
             }
         }
 
-        foreach ($this->fieldmap as $col => $fld) {
+        foreach ($this->fieldmap as $fld) {
             if ($fld) {
                 $val = $ldap_data[$fld];
                 $old = $old_data[$fld];
@@ -1396,6 +1395,10 @@ class rcube_ldap extends rcube_addressbook
      */
     protected function add_autovalues(&$attrs)
     {
+        if (empty($this->prop['autovalues'])) {
+            return;
+        }
+
         $attrvals = array();
         foreach ($attrs as $k => $v) {
             $attrvals['{'.$k.'}'] = is_array($v) ? $v[0] : $v;
@@ -1403,13 +1406,24 @@ class rcube_ldap extends rcube_addressbook
 
         foreach ((array)$this->prop['autovalues'] as $lf => $templ) {
             if (empty($attrs[$lf])) {
-                // replace {attr} placeholders with concrete attribute values
-                $templ = preg_replace('/\{\w+\}/', '', strtr($templ, $attrvals));
+                if (strpos($templ, '(') !== false) {
+                    // replace {attr} placeholders with (escaped!) attribute values to be safely eval'd
+                    $code = preg_replace('/\{\w+\}/', '', strtr($templ, array_map('addslashes', $attrvals)));
+                    $fn   = create_function('', "return ($code);");
+                    if (!$fn) {
+                        rcube::raise_error(array(
+                            'code' => 505, 'type' => 'php',
+                            'file' => __FILE__, 'line' => __LINE__,
+                            'message' => "Expression parse error on: ($code)"), true, false);
+                        continue;
+                    }
 
-                if (strpos($templ, '(') !== false)
-                    $attrs[$lf] = eval("return ($templ);");
-                else
-                    $attrs[$lf] = $templ;
+                    $attrs[$lf] = $fn();
+                }
+                else {
+                    // replace {attr} placeholders with concrete attribute values
+                    $attrs[$lf] = preg_replace('/\{\w+\}/', '', strtr($templ, $attrvals));
+                }
             }
         }
     }
@@ -1715,9 +1729,14 @@ class rcube_ldap extends rcube_addressbook
      * List all active contact groups of this source
      *
      * @param string  Optional search string to match group name
+     * @param int     Matching mode:
+     *                0 - partial (*abc*),
+     *                1 - strict (=),
+     *                2 - prefix (abc*)
+     *
      * @return array  Indexed list of contact groups, each a hash array
      */
-    function list_groups($search = null)
+    function list_groups($search = null, $mode = 0)
     {
         if (!$this->groups)
             return array();
@@ -1729,10 +1748,10 @@ class rcube_ldap extends rcube_addressbook
 
         $groups = array();
         if ($search) {
-            $search = mb_strtolower($search);
             foreach ($group_cache as $group) {
-                if (strpos(mb_strtolower($group['name']), $search) !== false)
+                if ($this->compare_search_value('name', $group['name'], $search, $mode)) {
                     $groups[] = $group;
+                }
             }
         }
         else
@@ -1763,7 +1782,7 @@ class rcube_ldap extends rcube_addressbook
             $vlv_active = $this->_vlv_set_controls($this->prop['groups'], $vlv_page+1, $page_size);
         }
 
-        $function = $this->_scope2func($this->prop['groups']['scope'], $ns_function);
+        $function = $this->_scope2func($this->prop['groups']['scope']);
         $res = @$function($this->conn, $base_dn, $filter, array_unique(array('dn', 'objectClass', $name_attr, $email_attr, $sort_attr)));
         if ($res === false)
         {
@@ -1921,9 +1940,10 @@ class rcube_ldap extends rcube_addressbook
     /**
      * Add the given contact records the a certain group
      *
-     * @param string  Group identifier
-     * @param array   List of contact identifiers to be added
-     * @return int    Number of contacts added
+     * @param string       Group identifier
+     * @param array|string List of contact identifiers to be added
+     *
+     * @return int Number of contacts added
      */
     function add_to_group($group_id, $contact_ids)
     {
@@ -1937,8 +1957,8 @@ class rcube_ldap extends rcube_addressbook
         $group_name  = $group_cache[$group_id]['name'];
         $member_attr = $group_cache[$group_id]['member_attr'];
         $group_dn    = "cn=$group_name,$base_dn";
+        $new_attrs   = array();
 
-        $new_attrs = array();
         foreach ($contact_ids as $id)
             $new_attrs[$member_attr][] = self::dn_decode($id);
 
@@ -1949,28 +1969,32 @@ class rcube_ldap extends rcube_addressbook
 
         $this->cache->remove('groups');
 
-        return count($new_attrs['member']);
+        return count($new_attrs[$member_attr]);
     }
 
     /**
      * Remove the given contact records from a certain group
      *
-     * @param string  Group identifier
-     * @param array   List of contact identifiers to be removed
-     * @return int    Number of deleted group members
+     * @param string       Group identifier
+     * @param array|string List of contact identifiers to be removed
+     *
+     * @return int Number of deleted group members
      */
     function remove_from_group($group_id, $contact_ids)
     {
         if (($group_cache = $this->cache->get('groups')) === null)
             $group_cache = $this->_fetch_groups();
 
+        if (!is_array($contact_ids))
+            $contact_ids = explode(',', $contact_ids);
+
         $base_dn     = $this->groups_base_dn;
         $group_name  = $group_cache[$group_id]['name'];
         $member_attr = $group_cache[$group_id]['member_attr'];
         $group_dn    = "cn=$group_name,$base_dn";
+        $del_attrs   = array();
 
-        $del_attrs = array();
-        foreach (explode(",", $contact_ids) as $id)
+        foreach ($contact_ids as $id)
             $del_attrs[$member_attr][] = self::dn_decode($id);
 
         if (!$this->ldap_mod_del($group_dn, $del_attrs)) {
@@ -1980,7 +2004,7 @@ class rcube_ldap extends rcube_addressbook
 
         $this->cache->remove('groups');
 
-        return count($del_attrs['member']);
+        return count($del_attrs[$member_attr]);
     }
 
     /**
diff --git a/lib/ext/Roundcube/rcube_message.php b/lib/ext/Roundcube/rcube_message.php
index 69735fc..a42d3fb 100644
--- a/lib/ext/Roundcube/rcube_message.php
+++ b/lib/ext/Roundcube/rcube_message.php
@@ -149,12 +149,13 @@ class rcube_message
      * Compose a valid URL for getting a message part
      *
      * @param string $mime_id Part MIME-ID
+     * @param mixed  $embed Mimetype class for parts to be embedded
      * @return string URL or false if part does not exist
      */
     public function get_part_url($mime_id, $embed = false)
     {
         if ($this->mime_parts[$mime_id])
-            return $this->opt['get_url'] . '&_part=' . $mime_id . ($embed ? '&_embed=1' : '');
+            return $this->opt['get_url'] . '&_part=' . $mime_id . ($embed ? '&_embed=1&_mimeclass=' . $embed : '');
         else
             return false;
     }
@@ -361,7 +362,7 @@ class rcube_message
 
             // parse headers from message/rfc822 part
             if (!isset($structure->headers['subject']) && !isset($structure->headers['from'])) {
-                list($headers, $dump) = explode("\r\n\r\n", $this->get_part_content($structure->mime_id, null, true, 8192));
+                list($headers, ) = explode("\r\n\r\n", $this->get_part_content($structure->mime_id, null, true, 32768));
                 $structure->headers = rcube_mime::parse_headers($headers);
             }
         }
@@ -369,7 +370,8 @@ class rcube_message
             $mimetype = $structure->mimetype;
 
         // show message headers
-        if ($recursive && is_array($structure->headers) && (isset($structure->headers['subject']) || isset($structure->headers['from']))) {
+        if ($recursive && is_array($structure->headers) &&
+                (isset($structure->headers['subject']) || $structure->headers['from'] || $structure->headers['to'])) {
             $c = new stdClass;
             $c->type = 'headers';
             $c->headers = $structure->headers;
@@ -642,7 +644,7 @@ class rcube_message
                 $img_regexp = '/^image\/(gif|jpe?g|png|tiff|bmp|svg)/';
 
                 foreach ($this->inline_parts as $inline_object) {
-                    $part_url = $this->get_part_url($inline_object->mime_id, true);
+                    $part_url = $this->get_part_url($inline_object->mime_id, $inline_object->ctype_primary);
                     if (isset($inline_object->content_id))
                         $a_replaces['cid:'.$inline_object->content_id] = $part_url;
                     if ($inline_object->content_location) {
diff --git a/lib/ext/Roundcube/rcube_mime.php b/lib/ext/Roundcube/rcube_mime.php
index 7cd5207..5968288 100644
--- a/lib/ext/Roundcube/rcube_mime.php
+++ b/lib/ext/Roundcube/rcube_mime.php
@@ -127,10 +127,11 @@ class rcube_mime
      * @param int     $max      List only this number of addresses
      * @param boolean $decode   Decode address strings
      * @param string  $fallback Fallback charset if none specified
+     * @param boolean $addronly Return flat array with e-mail addresses only
      *
-     * @return array  Indexed list of addresses
+     * @return array Indexed list of addresses
      */
-    static function decode_address_list($input, $max = null, $decode = true, $fallback = null)
+    static function decode_address_list($input, $max = null, $decode = true, $fallback = null, $addronly = false)
     {
         $a   = self::parse_address_list($input, $decode, $fallback);
         $out = array();
@@ -145,20 +146,21 @@ class rcube_mime
         foreach ($a as $val) {
             $j++;
             $address = trim($val['address']);
-            $name    = trim($val['name']);
 
-            if ($name && $address && $name != $address)
-                $string = sprintf('%s <%s>', preg_match("/$special_chars/", $name) ? '"'.addcslashes($name, '"').'"' : $name, $address);
-            else if ($address)
-                $string = $address;
-            else if ($name)
-                $string = $name;
-
-            $out[$j] = array(
-                'name'   => $name,
-                'mailto' => $address,
-                'string' => $string
-            );
+            if ($addronly) {
+                $out[$j] = $address;
+            }
+            else {
+                $name = trim($val['name']);
+                if ($name && $address && $name != $address)
+                    $string = sprintf('%s <%s>', preg_match("/$special_chars/", $name) ? '"'.addcslashes($name, '"').'"' : $name, $address);
+                else if ($address)
+                    $string = $address;
+                else if ($name)
+                    $string = $name;
+
+                $out[$j] = array('name' => $name, 'mailto' => $address, 'string' => $string);
+            }
 
             if ($max && $j==$max)
                 break;
@@ -359,6 +361,11 @@ class rcube_mime
                 $address = $m[1];
                 $name    = '';
             }
+            // special case (#1489092)
+            else if (preg_match('/(\s*<MAILER-DAEMON>)$/', $val, $m)) {
+                $address = 'MAILER-DAEMON';
+                $name    = substr($val, 0, -strlen($m[1]));
+            }
             else {
                 $name = $val;
             }
@@ -476,9 +483,10 @@ class rcube_mime
         $q_level = 0;
 
         foreach ($text as $idx => $line) {
-            if ($line[0] == '>') {
-                // remove quote chars, store level in $q
-                $line = preg_replace('/^>+/', '', $line, -1, $q);
+            if (preg_match('/^(>+)/', $line, $m)) {
+                // remove quote chars
+                $q    = strlen($m[1]);
+                $line = preg_replace('/^>+/', '', $line);
                 // remove (optional) space-staffing
                 $line = preg_replace('/^ /', '', $line);
 
@@ -541,9 +549,10 @@ class rcube_mime
 
         foreach ($text as $idx => $line) {
             if ($line != '-- ') {
-                if ($line[0] == '>') {
-                    // remove quote chars, store level in $level
-                    $line   = preg_replace('/^>+/', '', $line, -1, $level);
+                if (preg_match('/^(>+)/', $line, $m)) {
+                    // remove quote chars
+                    $level  = strlen($m[1]);
+                    $line   = preg_replace('/^>+/', '', $line);
                     // remove (optional) space-staffing and spaces before the line end
                     $line   = preg_replace('/(^ | +$)/', '', $line);
                     $prefix = str_repeat('>', $level) . ' ';
@@ -564,82 +573,122 @@ class rcube_mime
 
 
     /**
-     * Improved wordwrap function.
+     * Improved wordwrap function with multibyte support.
+     * The code is based on Zend_Text_MultiByte::wordWrap().
      *
-     * @param string $string  Text to wrap
-     * @param int    $width   Line width
-     * @param string $break   Line separator
-     * @param bool   $cut     Enable to cut word
-     * @param string $charset Charset of $string
+     * @param string $string      Text to wrap
+     * @param int    $width       Line width
+     * @param string $break       Line separator
+     * @param bool   $cut         Enable to cut word
+     * @param string $charset     Charset of $string
+     * @param bool   $wrap_quoted When enabled quoted lines will not be wrapped
      *
      * @return string Text
      */
-    public static function wordwrap($string, $width=75, $break="\n", $cut=false, $charset=null)
+    public static function wordwrap($string, $width=75, $break="\n", $cut=false, $charset=null, $wrap_quoted=true)
     {
-        if ($charset && function_exists('mb_internal_encoding')) {
-            mb_internal_encoding($charset);
+        if (!$charset) {
+            $charset = RCUBE_CHARSET;
         }
 
-        $para   = preg_split('/\r?\n/', $string);
-        $string = '';
-
-        while (count($para)) {
-            $line = array_shift($para);
-            if ($line[0] == '>') {
-                $string .= $line . (count($para) ? $break : '');
-                continue;
+        // 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';
+
+        // 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);
+
+            // quoted line (do not wrap)
+            if ($wrap_quoted && $string[0] == '>') {
+                if ($breakPos === $stringLength - 1 || $breakPos === false) {
+                    $subString = $string;
+                    $cutLength = null;
+                }
+                else {
+                    $subString = $substr_func($string, 0, $breakPos, $charset);
+                    $cutLength = $breakPos + 1;
+                }
             }
+            // next line found and current line is shorter than the limit
+            else if ($breakPos !== false && $breakPos < $width) {
+                if ($breakPos === $stringLength - 1) {
+                    $subString = $string;
+                    $cutLength = null;
+                }
+                else {
+                    $subString = $substr_func($string, 0, $breakPos, $charset);
+                    $cutLength = $breakPos + 1;
+                }
+            }
+            else {
+                $subString = $substr_func($string, 0, $width, $charset);
 
-            $list = explode(' ', $line);
-            $len = 0;
-            while (count($list)) {
-                $line   = array_shift($list);
-                $l      = mb_strlen($line);
-                $space  = $len ? 1 : 0;
-                $newlen = $len + $l + $space;
-
-                if ($newlen <= $width) {
-                    $string .= ($space ? ' ' : '').$line;
-                    $len += ($space + $l);
+                // last line
+                if ($breakPos === false && $subString === $string) {
+                    $cutLength = null;
                 }
                 else {
-                    if ($l > $width) {
-                        if ($cut) {
-                            $start = 0;
-                            while ($l) {
-                                $str = mb_substr($line, $start, $width);
-                                $strlen = mb_strlen($str);
-                                $string .= ($len ? $break : '').$str;
-                                $start += $strlen;
-                                $l -= $strlen;
-                                $len = $strlen;
-                            }
+                    $nextChar = $substr_func($string, $width, 1, $charset);
+
+                    if ($nextChar === ' ' || $nextChar === $separator) {
+                        $afterNextChar = $substr_func($string, $width + 1, 1, $charset);
+
+                        if ($afterNextChar === false) {
+                            $subString .= $nextChar;
+                        }
+
+                        $cutLength = $strlen_func($subString, $charset) + 1;
+                    }
+                    else {
+                        if ($strrpos_func[0] == 'm') {
+                            $spacePos = $strrpos_func($subString, ' ', 0, $charset);
                         }
                         else {
-                            $string .= ($len ? $break : '').$line;
-                            if (count($list)) {
-                                $string .= $break;
+                            $spacePos = $strrpos_func($subString, ' ', $charset);
+                        }
+
+                        if ($spacePos !== false) {
+                            $subString = $substr_func($subString, 0, $spacePos, $charset);
+                            $cutLength = $spacePos + 1;
+                        }
+                        else if ($cut === false) {
+                            $spacePos = $strpos_func($string, ' ', 0, $charset);
+
+                            if ($spacePos !== false && $spacePos < $breakPos) {
+                                $subString = $substr_func($string, 0, $spacePos, $charset);
+                                $cutLength = $spacePos + 1;
+                            }
+                            else {
+                                $subString = $substr_func($string, 0, $breakPos, $charset);
+                                $cutLength = $breakPos + 1;
                             }
-                            $len = 0;
                         }
-                    }
-                    else {
-                        $string .= $break.$line;
-                        $len = $l;
+                        else {
+                            $subString = $substr_func($subString, 0, $width, $charset);
+                            $cutLength = $width;
+                        }
                     }
                 }
             }
 
-            if (count($para)) {
-                $string .= $break;
-            }
-        }
+            $result[] = $subString;
 
-        if ($charset && function_exists('mb_internal_encoding')) {
-            mb_internal_encoding(RCUBE_CHARSET);
+            if ($cutLength !== null) {
+                $string = $substr_func($string, $cutLength, ($stringLength - $cutLength), $charset);
+            }
+            else {
+                break;
+            }
         }
 
-        return $string;
+        return implode($break, $result);
     }
 
 
diff --git a/lib/ext/Roundcube/rcube_output.php b/lib/ext/Roundcube/rcube_output.php
index b8ae86c..7ccf9a0 100644
--- a/lib/ext/Roundcube/rcube_output.php
+++ b/lib/ext/Roundcube/rcube_output.php
@@ -162,7 +162,7 @@ abstract class rcube_output
             header("Cache-Control: private, must-revalidate");
         }
         else {
-            header("Cache-Control: private, no-cache, must-revalidate, post-check=0, pre-check=0");
+            header("Cache-Control: private, no-cache, no-store, must-revalidate, post-check=0, pre-check=0");
             header("Pragma: no-cache");
         }
     }
diff --git a/lib/ext/Roundcube/rcube_plugin.php b/lib/ext/Roundcube/rcube_plugin.php
index 167a9eb..3153a84 100644
--- a/lib/ext/Roundcube/rcube_plugin.php
+++ b/lib/ext/Roundcube/rcube_plugin.php
@@ -92,6 +92,16 @@ abstract class rcube_plugin
     abstract function init();
 
     /**
+     * Provide information about this
+     *
+     * @return array Meta information about a plugin or false if not implemented
+     */
+    public static function info()
+    {
+        return false;
+    }
+
+    /**
      * Attempt to load the given plugin which is required for the current plugin
      *
      * @param string Plugin name
@@ -217,7 +227,7 @@ abstract class rcube_plugin
             $rcube->load_language($lang, $add);
 
             // add labels to client
-            if ($add2client) {
+            if ($add2client && method_exists($rcube->output, 'add_label')) {
                 if (is_array($add2client)) {
                     $js_labels = array_map(array($this, 'label_map_callback'), $add2client);
                 }
@@ -230,6 +240,24 @@ abstract class rcube_plugin
     }
 
     /**
+     * Wrapper for add_label() adding the plugin ID as domain
+     */
+    public function add_label()
+    {
+        $rcube = rcube::get_instance();
+
+        if (method_exists($rcube->output, 'add_label')) {
+            $args = func_get_args();
+            if (count($args) == 1 && is_array($args[0])) {
+                $args = $args[0];
+            }
+
+            $args = array_map(array($this, 'label_map_callback'), $args);
+            $rcube->output->add_label($args);
+        }
+    }
+
+    /**
      * Wrapper for rcube::gettext() adding the plugin ID as domain
      *
      * @param string $p Message identifier
@@ -380,6 +408,10 @@ abstract class rcube_plugin
      */
     private function label_map_callback($key)
     {
+        if (strpos($key, $this->ID.'.') === 0) {
+            return $key;
+        }
+
         return $this->ID.'.'.$key;
     }
 }
diff --git a/lib/ext/Roundcube/rcube_plugin_api.php b/lib/ext/Roundcube/rcube_plugin_api.php
index a89f147..33f04ea 100644
--- a/lib/ext/Roundcube/rcube_plugin_api.php
+++ b/lib/ext/Roundcube/rcube_plugin_api.php
@@ -228,6 +228,119 @@ class rcube_plugin_api
     }
 
     /**
+     * Get information about a specific plugin.
+     * This is either provided my a plugin's info() method or extracted from a package.xml or a composer.json file
+     *
+     * @param string Plugin name
+     * @return array Meta information about a plugin or False if plugin was not found
+     */
+    public function get_info($plugin_name)
+    {
+      static $composer_lock, $license_uris = array(
+        'Apache'     => 'http://www.apache.org/licenses/LICENSE-2.0.html',
+        'Apache-2'   => 'http://www.apache.org/licenses/LICENSE-2.0.html',
+        'Apache-1'   => 'http://www.apache.org/licenses/LICENSE-1.0',
+        'Apache-1.1' => 'http://www.apache.org/licenses/LICENSE-1.1',
+        'GPL'        => 'http://www.gnu.org/licenses/gpl.html',
+        'GPLv2'      => 'http://www.gnu.org/licenses/gpl-2.0.html',
+        'GPL-2.0'    => 'http://www.gnu.org/licenses/gpl-2.0.html',
+        'GPLv3'      => 'http://www.gnu.org/licenses/gpl-3.0.html',
+        'GPL-3.0'    => 'http://www.gnu.org/licenses/gpl-3.0.html',
+        'GPL-3.0+'   => 'http://www.gnu.org/licenses/gpl.html',
+        'GPL-2.0+'   => 'http://www.gnu.org/licenses/gpl.html',
+        'LGPL'       => 'http://www.gnu.org/licenses/lgpl.html',
+        'LGPLv2'     => 'http://www.gnu.org/licenses/lgpl-2.0.html',
+        'LGPLv2.1'   => 'http://www.gnu.org/licenses/lgpl-2.1.html',
+        'LGPLv3'     => 'http://www.gnu.org/licenses/lgpl.html',
+        'LGPL-2.0'   => 'http://www.gnu.org/licenses/lgpl-2.0.html',
+        'LGPL-2.1'   => 'http://www.gnu.org/licenses/lgpl-2.1.html',
+        'LGPL-3.0'   => 'http://www.gnu.org/licenses/lgpl.html',
+        'LGPL-3.0+'  => 'http://www.gnu.org/licenses/lgpl.html',
+        'BSD'          => 'http://opensource.org/licenses/bsd-license.html',
+        'BSD-2-Clause' => 'http://opensource.org/licenses/BSD-2-Clause',
+        'BSD-3-Clause' => 'http://opensource.org/licenses/BSD-3-Clause',
+        'FreeBSD'      => 'http://opensource.org/licenses/BSD-2-Clause',
+        'MIT'          => 'http://www.opensource.org/licenses/mit-license.php',
+        'PHP'          => 'http://opensource.org/licenses/PHP-3.0',
+        'PHP-3'        => 'http://www.php.net/license/3_01.txt',
+        'PHP-3.0'      => 'http://www.php.net/license/3_0.txt',
+        'PHP-3.01'     => 'http://www.php.net/license/3_01.txt',
+      );
+
+      $dir = dir($this->dir);
+      $fn = unslashify($dir->path) . DIRECTORY_SEPARATOR . $plugin_name . DIRECTORY_SEPARATOR . $plugin_name . '.php';
+      $info = false;
+
+      if (!class_exists($plugin_name))
+        include($fn);
+
+      if (class_exists($plugin_name))
+        $info = $plugin_name::info();
+
+      // fall back to composer.json file
+      if (!$info) {
+        $composer = INSTALL_PATH . "/plugins/$plugin_name/composer.json";
+        if (file_exists($composer) && ($json = @json_decode(file_get_contents($composer), true))) {
+          list($info['vendor'], $info['name']) = explode('/', $json['name']);
+          $info['license'] = $json['license'];
+          if ($license_uri = $license_uris[$info['license']])
+            $info['license_uri'] = $license_uri;
+        }
+
+        // read local composer.lock file (once)
+        if (!isset($composer_lock)) {
+          $composer_lock = @json_decode(@file_get_contents(INSTALL_PATH . "/composer.lock"), true);
+          if ($composer_lock['packages']) {
+            foreach ($composer_lock['packages'] as $i => $package) {
+              $composer_lock['installed'][$package['name']] = $package;
+            }
+          }
+        }
+
+        // load additional information from local composer.lock file
+        if ($lock = $composer_lock['installed'][$json['name']]) {
+          $info['version'] = $lock['version'];
+          $info['uri']     = $lock['homepage'] ? $lock['homepage'] : $lock['source']['uri'];
+          $info['src_uri'] = $lock['dist']['uri'] ? $lock['dist']['uri'] : $lock['source']['uri'];
+        }
+      }
+
+      // fall back to package.xml file
+      if (!$info) {
+        $package = INSTALL_PATH . "/plugins/$plugin_name/package.xml";
+        if (file_exists($package) && ($file = file_get_contents($package))) {
+          $doc = new DOMDocument();
+          $doc->loadXML($file);
+          $xpath = new DOMXPath($doc);
+          $xpath->registerNamespace('rc', "http://pear.php.net/dtd/package-2.0");
+
+          // XPaths of plugin metadata elements
+          $metadata = array(
+            'name'    => 'string(//rc:package/rc:name)',
+            'version' => 'string(//rc:package/rc:version/rc:release)',
+            'license' => 'string(//rc:package/rc:license)',
+            'license_uri' => 'string(//rc:package/rc:license/@uri)',
+            'src_uri' => 'string(//rc:package/rc:srcuri)',
+            'uri' => 'string(//rc:package/rc:uri)',
+          );
+
+          foreach ($metadata as $key => $path) {
+            $info[$key] = $xpath->evaluate($path);
+          }
+
+          // dependent required plugins (can be used, but not included in config)
+          $deps = $xpath->evaluate('//rc:package/rc:dependencies/rc:required/rc:package/rc:name');
+          for ($i = 0; $i < $deps->length; $i++) {
+            $dn = $deps->item($i)->nodeValue;
+            $info['requires'][] = $dn;
+          }
+        }
+      }
+
+      return $info;
+    }
+
+    /**
      * Allows a plugin object to register a callback for a certain hook
      *
      * @param string $hook Hook name
diff --git a/lib/ext/Roundcube/rcube_smtp.php b/lib/ext/Roundcube/rcube_smtp.php
index 5c7d220..60b1389 100644
--- a/lib/ext/Roundcube/rcube_smtp.php
+++ b/lib/ext/Roundcube/rcube_smtp.php
@@ -33,6 +33,8 @@ class rcube_smtp
     // define headers delimiter
     const SMTP_MIME_CRLF = "\r\n";
 
+    const DEBUG_LINE_LENGTH = 4098; // 4KB + 2B for \r\n
+
 
     /**
      * SMTP Connection and authentication
@@ -119,7 +121,7 @@ class rcube_smtp
         }
 
         // try to connect to server and exit on failure
-        $result = $this->conn->connect($smtp_timeout);
+        $result = $this->conn->connect($CONFIG['smtp_timeout']);
 
         if (PEAR::isError($result)) {
             $this->response[] = "Connection failed: ".$result->getMessage();
@@ -327,6 +329,12 @@ class rcube_smtp
      */
     public function debug_handler(&$smtp, $message)
     {
+        if (($len = strlen($message)) > self::DEBUG_LINE_LENGTH) {
+            $diff    = $len - self::DEBUG_LINE_LENGTH;
+            $message = substr($message, 0, self::DEBUG_LINE_LENGTH)
+                . "... [truncated $diff bytes]";
+        }
+
         rcube::write_log('smtp', preg_replace('/\r\n$/', '', $message));
     }
 
@@ -433,9 +441,9 @@ class rcube_smtp
         $recipients = rcube_utils::explode_quoted_string(',', $recipients);
 
         reset($recipients);
-        while (list($k, $recipient) = each($recipients)) {
+        foreach ($recipients as $recipient) {
             $a = rcube_utils::explode_quoted_string(' ', $recipient);
-            while (list($k2, $word) = each($a)) {
+            foreach ($a as $word) {
                 if (strpos($word, "@") > 0 && $word[strlen($word)-1] != '"') {
                     $word = preg_replace('/^<|>$/', '', trim($word));
                     if (in_array($word, $addresses) === false) {
diff --git a/lib/ext/Roundcube/rcube_spellchecker.php b/lib/ext/Roundcube/rcube_spellchecker.php
index 816bcad..60aec50 100644
--- a/lib/ext/Roundcube/rcube_spellchecker.php
+++ b/lib/ext/Roundcube/rcube_spellchecker.php
@@ -314,11 +314,6 @@ class rcube_spellchecker
         if (!$this->plink) {
             if (!extension_loaded('pspell')) {
                 $this->error = "Pspell extension not available";
-                rcube::raise_error(array(
-                    'code' => 500, 'type' => 'php',
-                    'file' => __FILE__, 'line' => __LINE__,
-                    'message' => $this->error), true, false);
-
                 return;
             }
 
@@ -372,9 +367,19 @@ class rcube_spellchecker
             fclose($fp);
         }
 
+        // parse HTTP response
+        if (preg_match('!^HTTP/1.\d (\d+)(.+)!', $store, $m)) {
+            $http_status = $m[1];
+            if ($http_status != '200')
+                $this->error = 'HTTP ' . $m[1] . $m[2];
+        }
+
         if (!$store) {
             $this->error = "Empty result from spelling engine";
         }
+        else if (preg_match('/<spellresult error="([^"]+)"/', $store, $m)) {
+            $this->error = "Error code $m[1] returned";
+        }
 
         preg_match_all('/<c o="([^"]*)" l="([^"]*)" s="([^"]*)">([^<]*)<\/c>/', $store, $matches, PREG_SET_ORDER);
 
@@ -588,7 +593,7 @@ class rcube_spellchecker
 
         if (empty($plugin['abort'])) {
             $dict = array();
-            $this->rc->db->query(
+            $sql_result = $this->rc->db->query(
                 "SELECT data FROM ".$this->rc->db->table_name('dictionary')
                 ." WHERE user_id ". ($plugin['userid'] ? "= ".$this->rc->db->quote($plugin['userid']) : "IS NULL")
                     ." AND " . $this->rc->db->quoteIdentifier('language') . " = ?",
diff --git a/lib/ext/Roundcube/rcube_string_replacer.php b/lib/ext/Roundcube/rcube_string_replacer.php
index b8768bc..0fc90a5 100644
--- a/lib/ext/Roundcube/rcube_string_replacer.php
+++ b/lib/ext/Roundcube/rcube_string_replacer.php
@@ -95,12 +95,12 @@ class rcube_string_replacer
             $attrib = (array)$this->options['link_attribs'];
             $attrib['href'] = $url_prefix . $url;
 
-            $i = $this->add($prefix . html::a($attrib, rcube::Q($url)) . $suffix);
+            $i = $this->add(html::a($attrib, rcube::Q($url)) . $suffix);
         }
 
         // Return valid link for recognized schemes, otherwise
         // return the unmodified string for unrecognized schemes.
-        return $i >= 0 ? $this->get_replacement($i) : $matches[0];
+        return $i >= 0 ? $prefix . $this->get_replacement($i) : $matches[0];
     }
 
     /**
diff --git a/lib/ext/Roundcube/rcube_utils.php b/lib/ext/Roundcube/rcube_utils.php
index 1ae782a..8467107 100644
--- a/lib/ext/Roundcube/rcube_utils.php
+++ b/lib/ext/Roundcube/rcube_utils.php
@@ -404,7 +404,7 @@ class rcube_utils
         $out = array();
         $src = $mode == self::INPUT_GET ? $_GET : ($mode == self::INPUT_POST ? $_POST : $_REQUEST);
 
-        foreach ($src as $key => $value) {
+        foreach (array_keys($src) as $key) {
             $fname = $key[0] == '_' ? substr($key, 1) : $key;
             if ($ignore && !preg_match('/^(' . $ignore . ')$/', $fname)) {
                 $out[$fname] = self::get_input_value($key, $mode);
@@ -729,8 +729,20 @@ class rcube_utils
             return $date;
         }
 
-        // support non-standard "GMTXXXX" literal
-        $date = preg_replace('/GMT\s*([+-][0-9]+)/', '\\1', $date);
+        // Clean malformed data
+        $date = preg_replace(
+            array(
+                '/GMT\s*([+-][0-9]+)/',                     // support non-standard "GMTXXXX" literal
+                '/[^a-z0-9\x20\x09:+-]/i',                  // remove any invalid characters
+                '/\s*(Mon|Tue|Wed|Thu|Fri|Sat|Sun)\s*/i',   // remove weekday names
+            ),
+            array(
+                '\\1',
+                '',
+                '',
+            ), $date);
+
+        $date = trim($date);
 
         // if date parsing fails, we have a date in non-rfc format.
         // remove token from the end and try again
diff --git a/lib/ext/Roundcube/rcube_vcard.php b/lib/ext/Roundcube/rcube_vcard.php
index 54bb952..90acb21 100644
--- a/lib/ext/Roundcube/rcube_vcard.php
+++ b/lib/ext/Roundcube/rcube_vcard.php
@@ -90,7 +90,7 @@ class rcube_vcard
      */
     public function __construct($vcard = null, $charset = RCUBE_CHARSET, $detect = false, $fieldmap = array())
     {
-        if (!empty($fielmap)) {
+        if (!empty($fieldmap)) {
             $this->extend_fieldmap($fieldmap);
         }
 
@@ -481,7 +481,7 @@ class rcube_vcard
         $vcard_block    = '';
         $in_vcard_block = false;
 
-        foreach (preg_split("/[\r\n]+/", $data) as $i => $line) {
+        foreach (preg_split("/[\r\n]+/", $data) as $line) {
             if ($in_vcard_block && !empty($line)) {
                 $vcard_block .= $line . "\n";
             }
@@ -784,9 +784,30 @@ class rcube_vcard
                 }
                 return $result;
             }
+
+            $s = strtr($s, $rep2);
+        }
+
+        // some implementations (GMail) use non-standard backslash before colon (#1489085)
+        // we will handle properly any backslashed character - removing dummy backslahes
+        // return strtr($s, array("\r" => '', '\\\\' => '\\', '\n' => "\n", '\N' => "\n", '\,' => ',', '\;' => ';'));
+
+        $s   = str_replace("\r", '', $s);
+        $pos = 0;
+
+        while (($pos = strpos($s, '\\', $pos)) !== false) {
+            $next = substr($s, $pos + 1, 1);
+            if ($next == 'n' || $next == 'N') {
+                $s = substr_replace($s, "\n", $pos, 2);
+            }
+            else {
+                $s = substr_replace($s, '', $pos, 1);
+            }
+
+            $pos += 1;
         }
 
-        return strtr($s, array("\r" => '', '\\\\' => '\\', '\n' => "\n", '\N' => "\n", '\,' => ',', '\;' => ';'));
+        return $s;
     }
 
     /**
diff --git a/lib/kolab_sync_message.php b/lib/kolab_sync_message.php
index 63dfc10..32e9518 100644
--- a/lib/kolab_sync_message.php
+++ b/lib/kolab_sync_message.php
@@ -171,7 +171,7 @@ class kolab_sync_message
             $headers['From'] = $this->get_identity();
         }
         if (empty($headers['Message-ID'])) {
-            $headers['Message-ID'] = $this->gen_message_id();
+            $headers['Message-ID'] = $rcube->gen_message_id();
         }
 
         // remove empty headers
@@ -275,6 +275,7 @@ class kolab_sync_message
 
         return $sent;
     }
+
     /**
      * MIME message parser
      *
@@ -333,6 +334,9 @@ class kolab_sync_message
         $this->body    = $message;
     }
 
+    /**
+     * Normalize (fix) header names
+     */
     protected function normalize_header_name($name)
     {
         $headers_map = array(
@@ -411,30 +415,6 @@ class kolab_sync_message
         }
     }
 
-    /**
-     * Unique Message-ID generator.
-     *
-     * @return string Message-ID
-     */
-    protected function gen_message_id()
-    {
-        $user        = kolab_sync::get_instance()->user;
-        $local_part  = md5(uniqid('rcmail'.mt_rand(),true));
-        $domain_part = $user->get_username('domain');
-
-        // Try to find FQDN, some spamfilters doesn't like 'localhost' (#1486924)
-        if (!preg_match('/\.[a-z]+$/i', $domain_part)) {
-            foreach (array($_SERVER['HTTP_HOST'], $_SERVER['SERVER_NAME']) as $host) {
-                $host = preg_replace('/:[0-9]+$/', '', $host);
-                if ($host && preg_match('/\.[a-z]+$/i', $host)) {
-                    $domain_part = $host;
-                }
-            }
-        }
-
-        return sprintf('<%s@%s>', $local_part, $domain_part);
-    }
-
     protected function save_content_type($ctype, $params = array())
     {
         $this->ctype        = $ctype;





More information about the commits mailing list