lib/ext lib/kolab_sync_body_converter.php lib/kolab_sync_data_email.php

Aleksander Machniak machniak at kolabsys.com
Thu Jan 17 15:02:47 CET 2013


 lib/ext/Roundcube/bootstrap.php             |   21 
 lib/ext/Roundcube/html.php                  |   22 
 lib/ext/Roundcube/rcube.php                 |   21 
 lib/ext/Roundcube/rcube_addressbook.php     |   57 -
 lib/ext/Roundcube/rcube_base_replacer.php   |    3 
 lib/ext/Roundcube/rcube_browser.php         |    3 
 lib/ext/Roundcube/rcube_cache.php           |    3 
 lib/ext/Roundcube/rcube_charset.php         |   88 +
 lib/ext/Roundcube/rcube_config.php          |    3 
 lib/ext/Roundcube/rcube_contacts.php        |   28 
 lib/ext/Roundcube/rcube_content_filter.php  |    3 
 lib/ext/Roundcube/rcube_csv2vcard.php       |   54 -
 lib/ext/Roundcube/rcube_db.php              |   13 
 lib/ext/Roundcube/rcube_db_mssql.php        |    4 
 lib/ext/Roundcube/rcube_db_mysql.php        |    7 
 lib/ext/Roundcube/rcube_db_pgsql.php        |    4 
 lib/ext/Roundcube/rcube_db_sqlite.php       |   11 
 lib/ext/Roundcube/rcube_db_sqlsrv.php       |    4 
 lib/ext/Roundcube/rcube_enriched.php        |    4 
 lib/ext/Roundcube/rcube_html2text.php       |  691 +++++++++++++
 lib/ext/Roundcube/rcube_image.php           |   31 
 lib/ext/Roundcube/rcube_imap.php            |   27 
 lib/ext/Roundcube/rcube_imap_cache.php      |    4 
 lib/ext/Roundcube/rcube_imap_generic.php    |   41 
 lib/ext/Roundcube/rcube_ldap.php            |   22 
 lib/ext/Roundcube/rcube_message.php         |   69 -
 lib/ext/Roundcube/rcube_message_header.php  |   22 
 lib/ext/Roundcube/rcube_message_part.php    |    4 
 lib/ext/Roundcube/rcube_mime.php            |    7 
 lib/ext/Roundcube/rcube_output.php          |    4 
 lib/ext/Roundcube/rcube_plugin.php          |  649 ++++++------
 lib/ext/Roundcube/rcube_plugin_api.php      |  893 ++++++++--------
 lib/ext/Roundcube/rcube_result_index.php    |    4 
 lib/ext/Roundcube/rcube_result_set.php      |    4 
 lib/ext/Roundcube/rcube_result_thread.php   |    4 
 lib/ext/Roundcube/rcube_session.php         | 1162 +++++++++++-----------
 lib/ext/Roundcube/rcube_smtp.php            |  768 +++++++-------
 lib/ext/Roundcube/rcube_spellchecker.php    |    6 
 lib/ext/Roundcube/rcube_storage.php         |    8 
 lib/ext/Roundcube/rcube_string_replacer.php |  327 +++---
 lib/ext/Roundcube/rcube_user.php            |    4 
 lib/ext/Roundcube/rcube_utils.php           |    3 
 lib/ext/Roundcube/rcube_vcard.php           | 1472 ++++++++++++++--------------
 lib/ext/Roundcube/rcube_washtml.php         |  451 ++++++++
 lib/ext/html2text.php                       |  746 --------------
 lib/kolab_sync_body_converter.php           |    2 
 lib/kolab_sync_data_email.php               |    2 
 47 files changed, 4171 insertions(+), 3609 deletions(-)

New commits:
commit a6efcf32ca8a96de397551b7497eaacccbb76991
Author: Aleksander Machniak <alec at alec.pl>
Date:   Thu Jan 17 14:58:46 2013 +0100

    Update Roundcube Framework

diff --git a/lib/ext/Roundcube/bootstrap.php b/lib/ext/Roundcube/bootstrap.php
index eed7db8..8cea481 100644
--- a/lib/ext/Roundcube/bootstrap.php
+++ b/lib/ext/Roundcube/bootstrap.php
@@ -2,10 +2,8 @@
 
 /*
  +-----------------------------------------------------------------------+
- | program/include/bootstrap.php                                         |
- |                                                                       |
  | This file is part of the Roundcube PHP suite                          |
- | Copyright (C) 2005-2012, The Roundcube Dev Team                       |
+ | Copyright (C) 2005-2013, The Roundcube Dev Team                       |
  |                                                                       |
  | Licensed under the GNU General Public License version 3 or            |
  | any later version with exceptions for skins & plugins.                |
@@ -13,7 +11,6 @@
  |                                                                       |
  | CONTENTS:                                                             |
  |   Roundcube Framework Initialization                                  |
- |                                                                       |
  +-----------------------------------------------------------------------+
  | Author: Thomas Bruederli <roundcube at gmail.com>                        |
  | Author: Aleksander Machniak <alec at alec.pl>                            |
@@ -361,6 +358,22 @@ function format_email($email)
 
 
 /**
+ * Fix version number so it can be used correctly in version_compare()
+ *
+ * @param string $version Version number string
+ *
+ * @param return Version number string
+ */
+function version_parse($version)
+{
+    return str_replace(
+        array('-stable', '-git'),
+        array('.0', '.99'),
+        $version);
+}
+
+
+/**
  * mbstring replacement functions
  */
 if (!extension_loaded('mbstring'))
diff --git a/lib/ext/Roundcube/html.php b/lib/ext/Roundcube/html.php
index 5fb574b..522a823 100644
--- a/lib/ext/Roundcube/html.php
+++ b/lib/ext/Roundcube/html.php
@@ -2,8 +2,6 @@
 
 /*
  +-----------------------------------------------------------------------+
- | program/include/html.php                                              |
- |                                                                       |
  | This file is part of the Roundcube Webmail client                     |
  | Copyright (C) 2005-2011, The Roundcube Dev Team                       |
  |                                                                       |
@@ -13,7 +11,6 @@
  |                                                                       |
  | PURPOSE:                                                              |
  |   Helper class to create valid XHTML code                             |
- |                                                                       |
  +-----------------------------------------------------------------------+
  | Author: Thomas Bruederli <roundcube at gmail.com>                        |
  +-----------------------------------------------------------------------+
@@ -172,7 +169,7 @@ class html
             $attr = array('href' => $attr);
         }
         return self::tag('a', $attr, $cont, array_merge(self::$common_attrib,
-        array('href','target','name','rel','onclick','onmouseover','onmouseout','onmousedown','onmouseup')));
+            array('href','target','name','rel','onclick','onmouseover','onmouseout','onmousedown','onmouseup')));
     }
 
     /**
@@ -678,7 +675,7 @@ class html_table extends html
         }
 
         $cell = new stdClass;
-        $cell->attrib = $attr;
+        $cell->attrib  = $attr;
         $cell->content = $cont;
 
         $this->rows[$this->rowindex]->cells[$this->colindex] = $cell;
@@ -702,16 +699,16 @@ class html_table extends html
         }
 
         $cell = new stdClass;
-        $cell->attrib = $attr;
-        $cell->content = $cont;
+        $cell->attrib   = $attr;
+        $cell->content  = $cont;
         $this->header[] = $cell;
     }
 
-     /**
+    /**
      * Remove a column from a table
      * Useful for plugins making alterations
-     * 
-     * @param string $class 
+     *
+     * @param string $class
      */
     public function remove_column($class)
     {
@@ -791,8 +788,9 @@ class html_table extends html
      */
     public function show($attrib = null)
     {
-        if (is_array($attrib))
+        if (is_array($attrib)) {
             $this->attrib = array_merge($this->attrib, $attrib);
+        }
 
         $thead = $tbody = "";
 
@@ -834,7 +832,7 @@ class html_table extends html
      */
     public function size()
     {
-      return count($this->rows);
+        return count($this->rows);
     }
 
     /**
diff --git a/lib/ext/Roundcube/rcube.php b/lib/ext/Roundcube/rcube.php
index cc4905a..a914ae6 100644
--- a/lib/ext/Roundcube/rcube.php
+++ b/lib/ext/Roundcube/rcube.php
@@ -2,8 +2,6 @@
 
 /*
  +-----------------------------------------------------------------------+
- | program/include/rcube.php                                             |
- |                                                                       |
  | This file is part of the Roundcube Webmail client                     |
  | Copyright (C) 2008-2012, The Roundcube Dev Team                       |
  | Copyright (C) 2011-2012, Kolab Systems AG                             |
@@ -1260,13 +1258,30 @@ class rcube
             return $this->decrypt($_SESSION['password']);
         }
     }
+
+
+    /**
+     * Getter for logged user language code.
+     *
+     * @return string User language code
+     */
+    public function get_user_language()
+    {
+        if (is_object($this->user)) {
+            return $this->user->language;
+        }
+        else if (isset($_SESSION['language'])) {
+            return $_SESSION['language'];
+        }
+    }
 }
 
 
 /**
  * Lightweight plugin API class serving as a dummy if plugins are not enabled
  *
- * @package Core
+ * @package Framework
+ * @subpackage Core
  */
 class rcube_dummy_plugin_api
 {
diff --git a/lib/ext/Roundcube/rcube_addressbook.php b/lib/ext/Roundcube/rcube_addressbook.php
index ea8df70..4210627 100644
--- a/lib/ext/Roundcube/rcube_addressbook.php
+++ b/lib/ext/Roundcube/rcube_addressbook.php
@@ -2,8 +2,6 @@
 
 /*
  +-----------------------------------------------------------------------+
- | program/include/rcube_addressbook.php                                 |
- |                                                                       |
  | This file is part of the Roundcube Webmail client                     |
  | Copyright (C) 2006-2012, The Roundcube Dev Team                       |
  |                                                                       |
@@ -13,7 +11,6 @@
  |                                                                       |
  | PURPOSE:                                                              |
  |   Interface to the local address book database                        |
- |                                                                       |
  +-----------------------------------------------------------------------+
  | Author: Thomas Bruederli <roundcube at gmail.com>                        |
  +-----------------------------------------------------------------------+
@@ -48,6 +45,7 @@ abstract class rcube_addressbook
     public $sort_col = 'name';
     public $sort_order = 'ASC';
     public $coltypes = array('name' => array('limit'=>1), 'firstname' => array('limit'=>1), 'surname' => array('limit'=>1), 'email' => array('limit'=>1));
+    public $date_cols = array();
 
     protected $error;
 
@@ -141,7 +139,7 @@ abstract class rcube_addressbook
      */
     function get_error()
     {
-      return $this->error;
+        return $this->error;
     }
 
     /**
@@ -152,7 +150,7 @@ abstract class rcube_addressbook
      */
     protected function set_error($type, $message)
     {
-      $this->error = array('type' => $type, 'message' => $message);
+        $this->error = array('type' => $type, 'message' => $message);
     }
 
     /**
@@ -225,7 +223,6 @@ abstract class rcube_addressbook
         return true;
     }
 
-
     /**
      * Create a new contact record
      *
@@ -410,7 +407,6 @@ abstract class rcube_addressbook
         return array();
     }
 
-
     /**
      * Utility function to return all values of a certain data column
      * either as flat list or grouped by subtype
@@ -443,7 +439,6 @@ abstract class rcube_addressbook
         return $out;
     }
 
-
     /**
      * Normalize the given string for fulltext search.
      * Currently only optimized for Latin-1 characters; to be extended
@@ -491,7 +486,6 @@ abstract class rcube_addressbook
         return $fn;
     }
 
-
     /**
      * Compose the name to display in the contacts list for the given contact record.
      * This respects the settings parameter how to list conacts.
@@ -529,5 +523,50 @@ abstract class rcube_addressbook
         return $fn;
     }
 
+    /**
+     * Compare search value with contact data
+     *
+     * @param string       $colname Data name
+     * @param string|array $value   Data value
+     * @param string       $search  Search value
+     * @param int          $mode    Search mode
+     *
+     * @return bool Comparision result
+     */
+    protected function compare_search_value($colname, $value, $search, $mode)
+    {
+        // The value is a date string, for date we'll
+        // use only strict comparison (mode = 1)
+        // @TODO: partial search, e.g. match only day and month
+        if (in_array($colname, $this->date_cols)) {
+            return (($value = rcube_utils::strtotime($value))
+                && ($search = rcube_utils::strtotime($search))
+                && date('Ymd', $value) == date('Ymd', $search));
+        }
+
+        // composite field, e.g. address
+        foreach ((array)$value as $val) {
+            $val = mb_strtolower($val);
+            switch ($mode) {
+            case 1:
+                $got = ($val == $search);
+                break;
+
+            case 2:
+                $got = ($search == substr($val, 0, strlen($search)));
+                break;
+
+            default:
+                $got = (strpos($val, $search) !== false);
+            }
+
+            if ($got) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
 }
 
diff --git a/lib/ext/Roundcube/rcube_base_replacer.php b/lib/ext/Roundcube/rcube_base_replacer.php
index b2a0fc1..fcd85c2 100644
--- a/lib/ext/Roundcube/rcube_base_replacer.php
+++ b/lib/ext/Roundcube/rcube_base_replacer.php
@@ -2,8 +2,6 @@
 
 /*
  +-----------------------------------------------------------------------+
- | program/include/rcube_base_replacer.php                               |
- |                                                                       |
  | This file is part of the Roundcube Webmail client                     |
  | Copyright (C) 2005-2012, The Roundcube Dev Team                       |
  |                                                                       |
@@ -13,7 +11,6 @@
  |                                                                       |
  | PURPOSE:                                                              |
  |   Provide basic functions for base URL replacement                    |
- |                                                                       |
  +-----------------------------------------------------------------------+
  | Author: Thomas Bruederli <roundcube at gmail.com>                        |
  +-----------------------------------------------------------------------+
diff --git a/lib/ext/Roundcube/rcube_browser.php b/lib/ext/Roundcube/rcube_browser.php
index 154e7ef..d10fe2a 100644
--- a/lib/ext/Roundcube/rcube_browser.php
+++ b/lib/ext/Roundcube/rcube_browser.php
@@ -2,8 +2,6 @@
 
 /*
  +-----------------------------------------------------------------------+
- | program/include/rcube_browser.php                                     |
- |                                                                       |
  | This file is part of the Roundcube Webmail client                     |
  | Copyright (C) 2007-2009, The Roundcube Dev Team                       |
  |                                                                       |
@@ -13,7 +11,6 @@
  |                                                                       |
  | PURPOSE:                                                              |
  |   Class representing the client browser's properties                  |
- |                                                                       |
  +-----------------------------------------------------------------------+
  | Author: Thomas Bruederli <roundcube at gmail.com>                        |
  +-----------------------------------------------------------------------+
diff --git a/lib/ext/Roundcube/rcube_cache.php b/lib/ext/Roundcube/rcube_cache.php
index 3e1ce4f..92f12a8 100644
--- a/lib/ext/Roundcube/rcube_cache.php
+++ b/lib/ext/Roundcube/rcube_cache.php
@@ -2,8 +2,6 @@
 
 /*
  +-----------------------------------------------------------------------+
- | program/include/rcube_cache.php                                       |
- |                                                                       |
  | This file is part of the Roundcube Webmail client                     |
  | Copyright (C) 2011, The Roundcube Dev Team                            |
  | Copyright (C) 2011, Kolab Systems AG                                  |
@@ -14,7 +12,6 @@
  |                                                                       |
  | PURPOSE:                                                              |
  |   Caching engine                                                      |
- |                                                                       |
  +-----------------------------------------------------------------------+
  | Author: Thomas Bruederli <roundcube at gmail.com>                        |
  | Author: Aleksander Machniak <alec at alec.pl>                            |
diff --git a/lib/ext/Roundcube/rcube_charset.php b/lib/ext/Roundcube/rcube_charset.php
index 6135a57..a7f26a3 100644
--- a/lib/ext/Roundcube/rcube_charset.php
+++ b/lib/ext/Roundcube/rcube_charset.php
@@ -2,8 +2,6 @@
 
 /*
  +-----------------------------------------------------------------------+
- | program/include/rcube_charset.php                                     |
- |                                                                       |
  | This file is part of the Roundcube Webmail client                     |
  | Copyright (C) 2005-2012, The Roundcube Dev Team                       |
  | Copyright (C) 2011-2012, Kolab Systems AG                             |
@@ -15,7 +13,6 @@
  |                                                                       |
  | PURPOSE:                                                              |
  |   Provide charset conversion functionality                            |
- |                                                                       |
  +-----------------------------------------------------------------------+
  | Author: Thomas Bruederli <roundcube at gmail.com>                        |
  | Author: Aleksander Machniak <alec at alec.pl>                            |
@@ -649,12 +646,13 @@ class rcube_charset
     /**
      * A method to guess character set of a string.
      *
-     * @param string $string    String.
-     * @param string $failover 	Default result for failover.
+     * @param string $string   String
+     * @param string $failover Default result for failover
+     * @param string $language User language
      *
      * @return string Charset name
      */
-    public static function detect($string, $failover='')
+    public static function detect($string, $failover = null, $language = null)
     {
         if (substr($string, 0, 4) == "\0\0\xFE\xFF") return 'UTF-32BE';  // Big Endian
         if (substr($string, 0, 4) == "\xFF\xFE\0\0") return 'UTF-32LE';  // Little Endian
@@ -669,38 +667,62 @@ class rcube_charset
         if ($string[0] != "\0" && $string[1] == "\0" && $string[2] != "\0" && $string[3] == "\0") return 'UTF-16LE';
 
         if (function_exists('mb_detect_encoding')) {
-            // FIXME: the order is important, because sometimes
-            // iso string is detected as euc-jp and etc.
-            $enc = array(
-                'UTF-8', 'SJIS', 'GB2312',
-                'ISO-8859-1', 'ISO-8859-2', 'ISO-8859-3', 'ISO-8859-4',
-                'ISO-8859-5', 'ISO-8859-6', 'ISO-8859-7', 'ISO-8859-8', 'ISO-8859-9',
-                'ISO-8859-10', 'ISO-8859-13', 'ISO-8859-14', 'ISO-8859-15', 'ISO-8859-16',
-                'WINDOWS-1252', 'WINDOWS-1251', 'EUC-JP', 'EUC-TW', 'KOI8-R', 'BIG5',
-                'ISO-2022-KR', 'ISO-2022-JP',
-            );
+            if (empty($language)) {
+                $rcube    = rcube::get_instance();
+                $language = $rcube->get_user_language();
+            }
+
+            // Prioritize charsets according to current language (#1485669)
+            switch ($language) {
+            case 'ja_JP': // for Japanese
+                $prio = array('ISO-2022-JP', 'JIS', 'UTF-8', 'EUC-JP', 'eucJP-win', 'SJIS', 'SJIS-win');
+                break;
+
+            case 'zh_CN': // for Chinese (Simplified)
+            case 'zh_TW': // for Chinese (Traditional)
+                $prio = array('UTF-8', 'BIG-5', 'GB2312', 'EUC-TW');
+                break;
+
+            case 'ko_KR': // for Korean
+                $prio = array('UTF-8', 'EUC-KR', 'ISO-2022-KR');
+                break;
 
-            $result = mb_detect_encoding($string, join(',', $enc));
+            case 'ru_RU': // for Russian
+                $prio = array('UTF-8', 'WINDOWS-1251', 'KOI8-R');
+                break;
+
+            default:
+                $prio = array('UTF-8', 'SJIS', 'GB2312',
+                    'ISO-8859-1', 'ISO-8859-2', 'ISO-8859-3', 'ISO-8859-4',
+                    'ISO-8859-5', 'ISO-8859-6', 'ISO-8859-7', 'ISO-8859-8', 'ISO-8859-9',
+                    'ISO-8859-10', 'ISO-8859-13', 'ISO-8859-14', 'ISO-8859-15', 'ISO-8859-16',
+                    'WINDOWS-1252', 'WINDOWS-1251', 'EUC-JP', 'EUC-TW', 'KOI8-R', 'BIG-5',
+                    'ISO-2022-KR', 'ISO-2022-JP',
+                );
+            }
+
+            $encodings = array_unique(array_merge($prio, mb_list_encodings()));
+
+            return mb_detect_encoding($string, $encodings);
         }
-        else {
-            // No match, check for UTF-8
-            // from http://w3.org/International/questions/qa-forms-utf-8.html
-            if (preg_match('/\A(
-                [\x09\x0A\x0D\x20-\x7E]
-                | [\xC2-\xDF][\x80-\xBF]
-                | \xE0[\xA0-\xBF][\x80-\xBF]
-                | [\xE1-\xEC\xEE\xEF][\x80-\xBF]{2}
-                | \xED[\x80-\x9F][\x80-\xBF]
-                | \xF0[\x90-\xBF][\x80-\xBF]{2}
-                | [\xF1-\xF3][\x80-\xBF]{3}
-                | \xF4[\x80-\x8F][\x80-\xBF]{2}
-                )*\z/xs', substr($string, 0, 2048))
-            ) {
+
+        // No match, check for UTF-8
+        // from http://w3.org/International/questions/qa-forms-utf-8.html
+        if (preg_match('/\A(
+            [\x09\x0A\x0D\x20-\x7E]
+            | [\xC2-\xDF][\x80-\xBF]
+            | \xE0[\xA0-\xBF][\x80-\xBF]
+            | [\xE1-\xEC\xEE\xEF][\x80-\xBF]{2}
+            | \xED[\x80-\x9F][\x80-\xBF]
+            | \xF0[\x90-\xBF][\x80-\xBF]{2}
+            | [\xF1-\xF3][\x80-\xBF]{3}
+            | \xF4[\x80-\x8F][\x80-\xBF]{2}
+            )*\z/xs', substr($string, 0, 2048))
+        ) {
             return 'UTF-8';
-            }
         }
 
-        return $result ? $result : $failover;
+        return $failover;
     }
 
 
diff --git a/lib/ext/Roundcube/rcube_config.php b/lib/ext/Roundcube/rcube_config.php
index 615faf3..2190dc4 100644
--- a/lib/ext/Roundcube/rcube_config.php
+++ b/lib/ext/Roundcube/rcube_config.php
@@ -2,8 +2,6 @@
 
 /*
  +-----------------------------------------------------------------------+
- | program/include/rcube_config.php                                      |
- |                                                                       |
  | This file is part of the Roundcube Webmail client                     |
  | Copyright (C) 2008-2012, The Roundcube Dev Team                       |
  |                                                                       |
@@ -13,7 +11,6 @@
  |                                                                       |
  | PURPOSE:                                                              |
  |   Class to read configuration settings                                |
- |                                                                       |
  +-----------------------------------------------------------------------+
  | Author: Thomas Bruederli <roundcube at gmail.com>                        |
  +-----------------------------------------------------------------------+
diff --git a/lib/ext/Roundcube/rcube_contacts.php b/lib/ext/Roundcube/rcube_contacts.php
index 5b4292a..c66e986 100644
--- a/lib/ext/Roundcube/rcube_contacts.php
+++ b/lib/ext/Roundcube/rcube_contacts.php
@@ -2,8 +2,6 @@
 
 /*
  +-----------------------------------------------------------------------+
- | program/include/rcube_contacts.php                                    |
- |                                                                       |
  | This file is part of the Roundcube Webmail client                     |
  | Copyright (C) 2006-2012, The Roundcube Dev Team                       |
  |                                                                       |
@@ -13,7 +11,6 @@
  |                                                                       |
  | PURPOSE:                                                              |
  |   Interface to the local address book database                        |
- |                                                                       |
  +-----------------------------------------------------------------------+
  | Author: Thomas Bruederli <roundcube at gmail.com>                        |
  +-----------------------------------------------------------------------+
@@ -63,6 +60,7 @@ class rcube_contacts extends rcube_addressbook
       'jobtitle', 'organization', 'department', 'assistant', 'manager',
       'gender', 'maidenname', 'spouse', 'email', 'phone', 'address',
       'birthday', 'anniversary', 'website', 'im', 'notes', 'photo');
+    public $date_cols = array('birthday', 'anniversary');
 
     const SEPARATOR = ',';
 
@@ -404,32 +402,16 @@ class rcube_contacts extends rcube_addressbook
             for ($i=0; $i<$pages; $i++) {
                 $this->list_records(null, $i, true);
                 while ($row = $this->result->next()) {
-                    $id = $row[$this->primary_key];
+                    $id    = $row[$this->primary_key];
                     $found = array();
                     foreach (preg_grep($regexp, array_keys($row)) as $col) {
                         $pos     = strpos($col, ':');
                         $colname = $pos ? substr($col, 0, $pos) : $col;
                         $search  = $post_search[$colname];
                         foreach ((array)$row[$col] as $value) {
-                            // composite field, e.g. address
-                            foreach ((array)$value as $val) {
-                                $val = mb_strtolower($val);
-                                switch ($mode) {
-                                case 1:
-                                    $got = ($val == $search);
-                                    break;
-                                case 2:
-                                    $got = ($search == substr($val, 0, strlen($search)));
-                                    break;
-                                default:
-                                    $got = (strpos($val, $search) !== false);
-                                    break;
-                                }
-
-                                if ($got) {
-                                    $found[$colname] = true;
-                                    break 2;
-                                }
+                            if ($this->compare_search_value($colname, $value, $search, $mode)) {
+                                $found[$colname] = true;
+                                break 2;
                             }
                         }
                     }
diff --git a/lib/ext/Roundcube/rcube_content_filter.php b/lib/ext/Roundcube/rcube_content_filter.php
index 99916a3..b814bb7 100644
--- a/lib/ext/Roundcube/rcube_content_filter.php
+++ b/lib/ext/Roundcube/rcube_content_filter.php
@@ -2,8 +2,6 @@
 
 /*
  +-----------------------------------------------------------------------+
- | program/include/rcube_content_filter.php                              |
- |                                                                       |
  | This file is part of the Roundcube Webmail client                     |
  | Copyright (C) 2011, The Roundcube Dev Team                            |
  |                                                                       |
@@ -13,7 +11,6 @@
  |                                                                       |
  | PURPOSE:                                                              |
  |   PHP stream filter to detect evil content in mail attachments        |
- |                                                                       |
  +-----------------------------------------------------------------------+
  | Author: Thomas Bruederli <roundcube at gmail.com>                        |
  +-----------------------------------------------------------------------+
diff --git a/lib/ext/Roundcube/rcube_csv2vcard.php b/lib/ext/Roundcube/rcube_csv2vcard.php
index 850c0c4..0d3276b 100644
--- a/lib/ext/Roundcube/rcube_csv2vcard.php
+++ b/lib/ext/Roundcube/rcube_csv2vcard.php
@@ -2,8 +2,6 @@
 
 /*
  +-----------------------------------------------------------------------+
- | program/include/rcube_csv2vcard.php                                   |
- |                                                                       |
  | This file is part of the Roundcube Webmail client                     |
  | Copyright (C) 2008-2012, The Roundcube Dev Team                       |
  |                                                                       |
@@ -126,6 +124,12 @@ class rcube_csv2vcard
         //'work_address_2'        => '',
         'work_country'          => 'country:work',
         'work_zipcode'          => 'zipcode:work',
+        'last'                  => 'surname',
+        'first'                 => 'firstname',
+        'work_city'             => 'locality:work',
+        'work_state'            => 'region:work',
+        'home_city_short'       => 'locality:home',
+        'home_state_short'      => 'region:home',
     );
 
     /**
@@ -273,13 +277,7 @@ class rcube_csv2vcard
 
         // Parse file
         foreach (preg_split("/[\r\n]+/", $csv) as $i => $line) {
-            $line = trim($line);
-            if (empty($line)) {
-                continue;
-            }
-
-            $elements = rcube_utils::explode_quoted_string(',', $line);
-
+            $elements = $this->parse_line($line);
             if (empty($elements)) {
                 continue;
             }
@@ -307,6 +305,35 @@ class rcube_csv2vcard
     }
 
     /**
+     * Parse CSV file line
+     */
+    protected function parse_line($line)
+    {
+        $line = trim($line);
+        if (empty($line)) {
+            return null;
+        }
+
+        $fields = rcube_utils::explode_quoted_string(',', $line);
+
+        // remove quotes if needed
+        if (!empty($fields)) {
+            foreach ($fields as $idx => $value) {
+                if (($len = strlen($value)) > 1 && $value[0] == '"' && $value[$len-1] == '"') {
+                    // remove surrounding quotes
+                    $value = substr($value, 1, -1);
+                    // replace doubled quotes inside the string with single quote
+                    $value = str_replace('""', '"', $value);
+
+                    $fields[$idx] = $value;
+                }
+            }
+        }
+
+        return $fields;
+    }
+
+    /**
      * Parse CSV header line, detect fields mapping
      */
     protected function parse_header($elements)
@@ -369,6 +396,15 @@ class rcube_csv2vcard
             }
         }
 
+        // Convert address(es) to rcube_vcard data
+        foreach ($contact as $idx => $value) {
+            $name = explode(':', $idx);
+            if (in_array($name[0], array('street', 'locality', 'region', 'zipcode', 'country'))) {
+                $contact['address:'.$name[1]][$name[0]] = $value;
+                unset($contact[$idx]);
+            }
+        }
+
         // Create vcard object
         $vcard = new rcube_vcard();
         foreach ($contact as $name => $value) {
diff --git a/lib/ext/Roundcube/rcube_db.php b/lib/ext/Roundcube/rcube_db.php
index 2c471e7..086a38a 100644
--- a/lib/ext/Roundcube/rcube_db.php
+++ b/lib/ext/Roundcube/rcube_db.php
@@ -2,8 +2,6 @@
 
 /**
  +-----------------------------------------------------------------------+
- | program/include/rcube_db.php                                          |
- |                                                                       |
  | This file is part of the Roundcube Webmail client                     |
  | Copyright (C) 2005-2012, The Roundcube Dev Team                       |
  |                                                                       |
@@ -13,19 +11,17 @@
  |                                                                       |
  | PURPOSE:                                                              |
  |   Database wrapper class that implements PHP PDO functions            |
- |                                                                       |
  +-----------------------------------------------------------------------+
  | Author: Aleksander Machniak <alec at alec.pl>                            |
  +-----------------------------------------------------------------------+
 */
 
-
 /**
  * Database independent query interface.
  * This is a wrapper for the PHP PDO.
  *
  * @package   Framework
- * @sbpackage Database
+ * @subpackage Database
  */
 class rcube_db
 {
@@ -404,6 +400,11 @@ class rcube_db
 
         $this->debug($query);
 
+        // destroy reference to previous result, required for SQLite driver (#1488874)
+        $this->last_result = null;
+        $this->db_error_msg = null;
+
+        // send query
         $query = $this->dbh->query($query);
 
         if ($query === false) {
@@ -426,7 +427,7 @@ class rcube_db
      *
      * @param mixed $result Optional query handle
      *
-     * @return int Number of rows or false on failure
+     * @return int Number of (matching) rows
      */
     public function affected_rows($result = null)
     {
diff --git a/lib/ext/Roundcube/rcube_db_mssql.php b/lib/ext/Roundcube/rcube_db_mssql.php
index c95663c..84fe22b 100644
--- a/lib/ext/Roundcube/rcube_db_mssql.php
+++ b/lib/ext/Roundcube/rcube_db_mssql.php
@@ -2,8 +2,6 @@
 
 /**
  +-----------------------------------------------------------------------+
- | program/include/rcube_db_mssql.php                                    |
- |                                                                       |
  | This file is part of the Roundcube Webmail client                     |
  | Copyright (C) 2005-2012, The Roundcube Dev Team                       |
  |                                                                       |
@@ -14,13 +12,11 @@
  | PURPOSE:                                                              |
  |   Database wrapper class that implements PHP PDO functions            |
  |   for MS SQL Server database                                          |
- |                                                                       |
  +-----------------------------------------------------------------------+
  | Author: Aleksander Machniak <alec at alec.pl>                            |
  +-----------------------------------------------------------------------+
 */
 
-
 /**
  * Database independent query interface
  * This is a wrapper for the PHP PDO
diff --git a/lib/ext/Roundcube/rcube_db_mysql.php b/lib/ext/Roundcube/rcube_db_mysql.php
index 1c5ba1d..8ab6403 100644
--- a/lib/ext/Roundcube/rcube_db_mysql.php
+++ b/lib/ext/Roundcube/rcube_db_mysql.php
@@ -2,8 +2,6 @@
 
 /**
  +-----------------------------------------------------------------------+
- | program/include/rcube_db_mysql.php                                    |
- |                                                                       |
  | This file is part of the Roundcube Webmail client                     |
  | Copyright (C) 2005-2012, The Roundcube Dev Team                       |
  |                                                                       |
@@ -14,13 +12,11 @@
  | PURPOSE:                                                              |
  |   Database wrapper class that implements PHP PDO functions            |
  |   for MySQL database                                                  |
- |                                                                       |
  +-----------------------------------------------------------------------+
  | Author: Aleksander Machniak <alec at alec.pl>                            |
  +-----------------------------------------------------------------------+
 */
 
-
 /**
  * Database independent query interface
  *
@@ -130,6 +126,9 @@ class rcube_db_mysql extends rcube_db
         // Always return matching (not affected only) rows count
         $result[PDO::MYSQL_ATTR_FOUND_ROWS] = true;
 
+        // Enable AUTOCOMMIT mode (#1488902)
+        $dsn_options[PDO::ATTR_AUTOCOMMIT] = true;
+
         return $result;
     }
 
diff --git a/lib/ext/Roundcube/rcube_db_pgsql.php b/lib/ext/Roundcube/rcube_db_pgsql.php
index 797860a..cf23c5e 100644
--- a/lib/ext/Roundcube/rcube_db_pgsql.php
+++ b/lib/ext/Roundcube/rcube_db_pgsql.php
@@ -2,8 +2,6 @@
 
 /**
  +-----------------------------------------------------------------------+
- | program/include/rcube_db_pgsql.php                                    |
- |                                                                       |
  | This file is part of the Roundcube Webmail client                     |
  | Copyright (C) 2005-2012, The Roundcube Dev Team                       |
  |                                                                       |
@@ -14,13 +12,11 @@
  | PURPOSE:                                                              |
  |   Database wrapper class that implements PHP PDO functions            |
  |   for PostgreSQL database                                             |
- |                                                                       |
  +-----------------------------------------------------------------------+
  | Author: Aleksander Machniak <alec at alec.pl>                            |
  +-----------------------------------------------------------------------+
 */
 
-
 /**
  * Database independent query interface
  * This is a wrapper for the PHP PDO
diff --git a/lib/ext/Roundcube/rcube_db_sqlite.php b/lib/ext/Roundcube/rcube_db_sqlite.php
index 65dcb6d..145b8a3 100644
--- a/lib/ext/Roundcube/rcube_db_sqlite.php
+++ b/lib/ext/Roundcube/rcube_db_sqlite.php
@@ -2,8 +2,6 @@
 
 /**
  +-----------------------------------------------------------------------+
- | program/include/rcube_db_sqlite.php                                   |
- |                                                                       |
  | This file is part of the Roundcube Webmail client                     |
  | Copyright (C) 2005-2012, The Roundcube Dev Team                       |
  |                                                                       |
@@ -14,13 +12,11 @@
  | PURPOSE:                                                              |
  |   Database wrapper class that implements PHP PDO functions            |
  |   for SQLite database                                                 |
- |                                                                       |
  +-----------------------------------------------------------------------+
  | Author: Aleksander Machniak <alec at alec.pl>                            |
  +-----------------------------------------------------------------------+
 */
 
-
 /**
  * Database independent query interface
  * This is a wrapper for the PHP PDO
@@ -124,12 +120,7 @@ class rcube_db_sqlite extends rcube_db
             $q = $this->query('SELECT name FROM sqlite_master'
                 .' WHERE type = \'table\' ORDER BY name');
 
-            if ($res = $this->_get_result($q)) {
-                $this->tables = $res->fetchAll(PDO::FETCH_COLUMN, 0);
-            }
-            else {
-                $this->tables = array();
-            }
+            $this->tables = $q ? $q->fetchAll(PDO::FETCH_COLUMN, 0) : array();
         }
 
         return $this->tables;
diff --git a/lib/ext/Roundcube/rcube_db_sqlsrv.php b/lib/ext/Roundcube/rcube_db_sqlsrv.php
index 8b6ffe8..e696780 100644
--- a/lib/ext/Roundcube/rcube_db_sqlsrv.php
+++ b/lib/ext/Roundcube/rcube_db_sqlsrv.php
@@ -2,8 +2,6 @@
 
 /**
  +-----------------------------------------------------------------------+
- | program/include/rcube_db_sqlsrv.php                                   |
- |                                                                       |
  | This file is part of the Roundcube Webmail client                     |
  | Copyright (C) 2005-2012, The Roundcube Dev Team                       |
  |                                                                       |
@@ -14,13 +12,11 @@
  | PURPOSE:                                                              |
  |   Database wrapper class that implements PHP PDO functions            |
  |   for MS SQL Server database                                          |
- |                                                                       |
  +-----------------------------------------------------------------------+
  | Author: Aleksander Machniak <alec at alec.pl>                            |
  +-----------------------------------------------------------------------+
 */
 
-
 /**
  * Database independent query interface
  * This is a wrapper for the PHP PDO
diff --git a/lib/ext/Roundcube/rcube_enriched.php b/lib/ext/Roundcube/rcube_enriched.php
index 8b64fe0..8c628c9 100644
--- a/lib/ext/Roundcube/rcube_enriched.php
+++ b/lib/ext/Roundcube/rcube_enriched.php
@@ -2,8 +2,6 @@
 
 /*
  +-----------------------------------------------------------------------+
- | program/include/rcube_enriched.php                                    |
- |                                                                       |
  | This file is part of the Roundcube Webmail client                     |
  | Copyright (C) 2005-2012, The Roundcube Dev Team                       |
  |                                                                       |
@@ -13,14 +11,12 @@
  |                                                                       |
  | PURPOSE:                                                              |
  |   Helper class to convert Enriched to HTML format (RFC 1523, 1896)    |
- |                                                                       |
  +-----------------------------------------------------------------------+
  | Author: Aleksander Machniak <alec at alec.pl>                            |
  | Author: Ryo Chijiiwa (IlohaMail)                                      |
  +-----------------------------------------------------------------------+
 */
 
-
 /**
  * Class for Enriched to HTML conversion
  *
diff --git a/lib/ext/Roundcube/rcube_html2text.php b/lib/ext/Roundcube/rcube_html2text.php
new file mode 100644
index 0000000..0b172eb
--- /dev/null
+++ b/lib/ext/Roundcube/rcube_html2text.php
@@ -0,0 +1,691 @@
+<?php
+
+/**
+ +-----------------------------------------------------------------------+
+ | This file is part of the Roundcube Webmail client                     |
+ | Copyright (C) 2008-2012, The Roundcube Dev Team                       |
+ | Copyright (c) 2005-2007, Jon Abernathy <jon at chuggnutt.com>            |
+ |                                                                       |
+ | Licensed under the GNU General Public License version 3 or            |
+ | any later version with exceptions for skins & plugins.                |
+ | See the README file for a full license statement.                     |
+ |                                                                       |
+ | PURPOSE:                                                              |
+ |   Converts HTML to formatted plain text (based on html2text class)    |
+ +-----------------------------------------------------------------------+
+ | Author: Thomas Bruederli <roundcube at gmail.com>                        |
+ | Author: Aleksander Machniak <alec at alec.pl>                            |
+ | Author: Jon Abernathy <jon at chuggnutt.com>                             |
+ +-----------------------------------------------------------------------+
+ */
+
+/**
+ *  Takes HTML and converts it to formatted, plain text.
+ *
+ *  Thanks to Alexander Krug (http://www.krugar.de/) to pointing out and
+ *  correcting an error in the regexp search array. Fixed 7/30/03.
+ *
+ *  Updated set_html() function's file reading mechanism, 9/25/03.
+ *
+ *  Thanks to Joss Sanglier (http://www.dancingbear.co.uk/) for adding
+ *  several more HTML entity codes to the $search and $replace arrays.
+ *  Updated 11/7/03.
+ *
+ *  Thanks to Darius Kasperavicius (http://www.dar.dar.lt/) for
+ *  suggesting the addition of $allowed_tags and its supporting function
+ *  (which I slightly modified). Updated 3/12/04.
+ *
+ *  Thanks to Justin Dearing for pointing out that a replacement for the
+ *  <TH> tag was missing, and suggesting an appropriate fix.
+ *  Updated 8/25/04.
+ *
+ *  Thanks to Mathieu Collas (http://www.myefarm.com/) for finding a
+ *  display/formatting bug in the _build_link_list() function: email
+ *  readers would show the left bracket and number ("[1") as part of the
+ *  rendered email address.
+ *  Updated 12/16/04.
+ *
+ *  Thanks to Wojciech Bajon (http://histeria.pl/) for submitting code
+ *  to handle relative links, which I hadn't considered. I modified his
+ *  code a bit to handle normal HTTP links and MAILTO links. Also for
+ *  suggesting three additional HTML entity codes to search for.
+ *  Updated 03/02/05.
+ *
+ *  Thanks to Jacob Chandler for pointing out another link condition
+ *  for the _build_link_list() function: "https".
+ *  Updated 04/06/05.
+ *
+ *  Thanks to Marc Bertrand (http://www.dresdensky.com/) for
+ *  suggesting a revision to the word wrapping functionality; if you
+ *  specify a $width of 0 or less, word wrapping will be ignored.
+ *  Updated 11/02/06.
+ *
+ *  *** Big housecleaning updates below:
+ *
+ *  Thanks to Colin Brown (http://www.sparkdriver.co.uk/) for
+ *  suggesting the fix to handle </li> and blank lines (whitespace).
+ *  Christian Basedau (http://www.movetheweb.de/) also suggested the
+ *  blank lines fix.
+ *
+ *  Special thanks to Marcus Bointon (http://www.synchromedia.co.uk/),
+ *  Christian Basedau, Norbert Laposa (http://ln5.co.uk/),
+ *  Bas van de Weijer, and Marijn van Butselaar
+ *  for pointing out my glaring error in the <th> handling. Marcus also
+ *  supplied a host of fixes.
+ *
+ *  Thanks to Jeffrey Silverman (http://www.newtnotes.com/) for pointing
+ *  out that extra spaces should be compressed--a problem addressed with
+ *  Marcus Bointon's fixes but that I had not yet incorporated.
+ *
+ *  Thanks to Daniel Schledermann (http://www.typoconsult.dk/) for
+ *  suggesting a valuable fix with <a> tag handling.
+ *
+ *  Thanks to Wojciech Bajon (again!) for suggesting fixes and additions,
+ *  including the <a> tag handling that Daniel Schledermann pointed
+ *  out but that I had not yet incorporated. I haven't (yet)
+ *  incorporated all of Wojciech's changes, though I may at some
+ *  future time.
+ *
+ *  *** End of the housecleaning updates. Updated 08/08/07.
+ */
+
+/**
+ * Converts HTML to formatted plain text
+ *
+ * @package    Framework
+ * @subpackage Utils
+ */
+class rcube_html2text
+{
+    /**
+     * Contains the HTML content to convert.
+     *
+     * @var string $html
+     */
+    protected $html;
+
+    /**
+     * Contains the converted, formatted text.
+     *
+     * @var string $text
+     */
+    protected $text;
+
+    /**
+     * Maximum width of the formatted text, in columns.
+     *
+     * Set this value to 0 (or less) to ignore word wrapping
+     * and not constrain text to a fixed-width column.
+     *
+     * @var integer $width
+     */
+    protected $width = 70;
+
+    /**
+     * Target character encoding for output text
+     *
+     * @var string $charset
+     */
+    protected $charset = 'UTF-8';
+
+    /**
+     * List of preg* regular expression patterns to search for,
+     * used in conjunction with $replace.
+     *
+     * @var array $search
+     * @see $replace
+     */
+    protected $search = array(
+        "/\r/",                                  // Non-legal carriage return
+        "/[\n\t]+/",                             // Newlines and tabs
+        '/<head[^>]*>.*?<\/head>/i',             // <head>
+        '/<script[^>]*>.*?<\/script>/i',         // <script>s -- which strip_tags supposedly has problems with
+        '/<style[^>]*>.*?<\/style>/i',           // <style>s -- which strip_tags supposedly has problems with
+        '/<p[^>]*>/i',                           // <P>
+        '/<br[^>]*>/i',                          // <br>
+        '/<i[^>]*>(.*?)<\/i>/i',                 // <i>
+        '/<em[^>]*>(.*?)<\/em>/i',               // <em>
+        '/(<ul[^>]*>|<\/ul>)/i',                 // <ul> and </ul>
+        '/(<ol[^>]*>|<\/ol>)/i',                 // <ol> and </ol>
+        '/<li[^>]*>(.*?)<\/li>/i',               // <li> and </li>
+        '/<li[^>]*>/i',                          // <li>
+        '/<hr[^>]*>/i',                          // <hr>
+        '/<div[^>]*>/i',                         // <div>
+        '/(<table[^>]*>|<\/table>)/i',           // <table> and </table>
+        '/(<tr[^>]*>|<\/tr>)/i',                 // <tr> and </tr>
+        '/<td[^>]*>(.*?)<\/td>/i',               // <td> and </td>
+    );
+
+    /**
+     * List of pattern replacements corresponding to patterns searched.
+     *
+     * @var array $replace
+     * @see $search
+     */
+    protected $replace = array(
+        '',                                     // Non-legal carriage return
+        ' ',                                    // Newlines and tabs
+        '',                                     // <head>
+        '',                                     // <script>s -- which strip_tags supposedly has problems with
+        '',                                     // <style>s -- which strip_tags supposedly has problems with
+        "\n\n",                                 // <P>
+        "\n",                                   // <br>
+        '_\\1_',                                // <i>
+        '_\\1_',                                // <em>
+        "\n\n",                                 // <ul> and </ul>
+        "\n\n",                                 // <ol> and </ol>
+        "\t* \\1\n",                            // <li> and </li>
+        "\n\t* ",                               // <li>
+        "\n-------------------------\n",        // <hr>
+        "<div>\n",                              // <div>
+        "\n\n",                                 // <table> and </table>
+        "\n",                                   // <tr> and </tr>
+        "\t\t\\1\n",                            // <td> and </td>
+    );
+
+    /**
+     * List of preg* regular expression patterns to search for,
+     * used in conjunction with $ent_replace.
+     *
+     * @var array $ent_search
+     * @see $ent_replace
+     */
+    protected $ent_search = array(
+        '/&(nbsp|#160);/i',                      // Non-breaking space
+        '/&(quot|rdquo|ldquo|#8220|#8221|#147|#148);/i',
+                                         // Double quotes
+        '/&(apos|rsquo|lsquo|#8216|#8217);/i',   // Single quotes
+        '/>/i',                               // Greater-than
+        '/</i',                               // Less-than
+        '/&(copy|#169);/i',                      // Copyright
+        '/&(trade|#8482|#153);/i',               // Trademark
+        '/&(reg|#174);/i',                       // Registered
+        '/&(mdash|#151|#8212);/i',               // mdash
+        '/&(ndash|minus|#8211|#8722);/i',        // ndash
+        '/&(bull|#149|#8226);/i',                // Bullet
+        '/&(pound|#163);/i',                     // Pound sign
+        '/&(euro|#8364);/i',                     // Euro sign
+        '/&(amp|#38);/i',                        // Ampersand: see _converter()
+        '/[ ]{2,}/',                             // Runs of spaces, post-handling
+    );
+
+    /**
+     * List of pattern replacements corresponding to patterns searched.
+     *
+     * @var array $ent_replace
+     * @see $ent_search
+     */
+    protected $ent_replace = array(
+        ' ',                                    // Non-breaking space
+        '"',                                    // Double quotes
+        "'",                                    // Single quotes
+        '>',
+        '<',
+        '(c)',
+        '(tm)',
+        '(R)',
+        '--',
+        '-',
+        '*',
+        '£',
+        'EUR',                                  // Euro sign. € ?
+        '|+|amp|+|',                            // Ampersand: see _converter()
+        ' ',                                    // Runs of spaces, post-handling
+    );
+
+    /**
+     * List of preg* regular expression patterns to search for
+     * and replace using callback function.
+     *
+     * @var array $callback_search
+     */
+    protected $callback_search = array(
+        '/<(a) [^>]*href=("|\')([^"\']+)\2[^>]*>(.*?)<\/a>/i', // <a href="">
+        '/<(h)[123456]( [^>]*)?>(.*?)<\/h[123456]>/i',         // h1 - h6
+        '/<(b)( [^>]*)?>(.*?)<\/b>/i',                         // <b>
+        '/<(strong)( [^>]*)?>(.*?)<\/strong>/i',               // <strong>
+        '/<(th)( [^>]*)?>(.*?)<\/th>/i',                       // <th> and </th>
+    );
+
+   /**
+    * List of preg* regular expression patterns to search for in PRE body,
+    * used in conjunction with $pre_replace.
+    *
+    * @var array $pre_search
+    * @see $pre_replace
+    */
+    protected $pre_search = array(
+        "/\n/",
+        "/\t/",
+        '/ /',
+        '/<pre[^>]*>/',
+        '/<\/pre>/'
+    );
+
+    /**
+     * List of pattern replacements corresponding to patterns searched for PRE body.
+     *
+     * @var array $pre_replace
+     * @see $pre_search
+     */
+    protected $pre_replace = array(
+        '<br>',
+        '    ',
+        ' ',
+        '',
+        ''
+    );
+
+    /**
+     * Contains a list of HTML tags to allow in the resulting text.
+     *
+     * @var string $allowed_tags
+     * @see set_allowed_tags()
+     */
+    protected $allowed_tags = '';
+
+    /**
+     * Contains the base URL that relative links should resolve to.
+     *
+     * @var string $url
+     */
+    protected $url;
+
+    /**
+     * Indicates whether content in the $html variable has been converted yet.
+     *
+     * @var boolean $_converted
+     * @see $html, $text
+     */
+    protected $_converted = false;
+
+    /**
+     * Contains URL addresses from links to be rendered in plain text.
+     *
+     * @var array $_link_list
+     * @see _build_link_list()
+     */
+    protected $_link_list = array();
+
+    /**
+     * Boolean flag, true if a table of link URLs should be listed after the text.
+     *
+     * @var boolean $_do_links
+     * @see __construct()
+     */
+    protected $_do_links = true;
+
+    /**
+     * Constructor.
+     *
+     * If the HTML source string (or file) is supplied, the class
+     * will instantiate with that source propagated, all that has
+     * to be done it to call get_text().
+     *
+     * @param string $source HTML content
+     * @param boolean $from_file Indicates $source is a file to pull content from
+     * @param boolean $do_links Indicate whether a table of link URLs is desired
+     * @param integer $width Maximum width of the formatted text, 0 for no limit
+     */
+    function __construct($source = '', $from_file = false, $do_links = true, $width = 75, $charset = 'UTF-8')
+    {
+        if (!empty($source)) {
+            $this->set_html($source, $from_file);
+        }
+
+        $this->set_base_url();
+
+        $this->_do_links = $do_links;
+        $this->width     = $width;
+        $this->charset   = $charset;
+    }
+
+    /**
+     * Loads source HTML into memory, either from $source string or a file.
+     *
+     * @param string $source HTML content
+     * @param boolean $from_file Indicates $source is a file to pull content from
+     */
+    function set_html($source, $from_file = false)
+    {
+        if ($from_file && file_exists($source)) {
+            $this->html = file_get_contents($source);
+        }
+        else {
+            $this->html = $source;
+        }
+
+        $this->_converted = false;
+    }
+
+    /**
+     * Returns the text, converted from HTML.
+     *
+     * @return string Plain text
+     */
+    function get_text()
+    {
+        if (!$this->_converted) {
+            $this->_convert();
+        }
+
+        return $this->text;
+    }
+
+    /**
+     * Prints the text, converted from HTML.
+     */
+    function print_text()
+    {
+        print $this->get_text();
+    }
+
+    /**
+     * Sets the allowed HTML tags to pass through to the resulting text.
+     *
+     * Tags should be in the form "<p>", with no corresponding closing tag.
+     */
+    function set_allowed_tags($allowed_tags = '')
+    {
+        if (!empty($allowed_tags)) {
+            $this->allowed_tags = $allowed_tags;
+        }
+    }
+
+    /**
+     * Sets a base URL to handle relative links.
+     */
+    function set_base_url($url = '')
+    {
+        if (empty($url)) {
+            if (!empty($_SERVER['HTTP_HOST'])) {
+                $this->url = 'http://' . $_SERVER['HTTP_HOST'];
+            }
+            else {
+                $this->url = '';
+            }
+        }
+        else {
+            // Strip any trailing slashes for consistency (relative
+            // URLs may already start with a slash like "/file.html")
+            if (substr($url, -1) == '/') {
+                $url = substr($url, 0, -1);
+            }
+            $this->url = $url;
+        }
+    }
+
+    /**
+     * Workhorse function that does actual conversion (calls _converter() method).
+     */
+    protected function _convert()
+    {
+        // Variables used for building the link list
+        $this->_link_list = array();
+
+        $text = trim(stripslashes($this->html));
+
+        // Convert HTML to TXT
+        $this->_converter($text);
+
+        // Add link list
+        if (!empty($this->_link_list)) {
+            $text .= "\n\nLinks:\n------\n";
+            foreach ($this->_link_list as $idx => $url) {
+                $text .= '[' . ($idx+1) . '] ' . $url . "\n";
+            }
+        }
+
+        $this->text       = $text;
+        $this->_converted = true;
+    }
+
+    /**
+     * Workhorse function that does actual conversion.
+     *
+     * First performs custom tag replacement specified by $search and
+     * $replace arrays. Then strips any remaining HTML tags, reduces whitespace
+     * and newlines to a readable format, and word wraps the text to
+     * $width characters.
+     *
+     * @param string Reference to HTML content string
+     */
+    protected function _converter(&$text)
+    {
+        // Convert <BLOCKQUOTE> (before PRE!)
+        $this->_convert_blockquotes($text);
+
+        // Convert <PRE>
+        $this->_convert_pre($text);
+
+        // Run our defined tags search-and-replace
+        $text = preg_replace($this->search, $this->replace, $text);
+
+        // Run our defined tags search-and-replace with callback
+        $text = preg_replace_callback($this->callback_search, array($this, 'tags_preg_callback'), $text);
+
+        // Strip any other HTML tags
+        $text = strip_tags($text, $this->allowed_tags);
+
+        // Run our defined entities/characters search-and-replace
+        $text = preg_replace($this->ent_search, $this->ent_replace, $text);
+
+        // Replace known html entities
+        $text = html_entity_decode($text, ENT_QUOTES, $this->charset);
+
+        // Remove unknown/unhandled entities (this cannot be done in search-and-replace block)
+        $text = preg_replace('/&([a-zA-Z0-9]{2,6}|#[0-9]{2,4});/', '', $text);
+
+        // Convert "|+|amp|+|" into "&", need to be done after handling of unknown entities
+        // This properly handles situation of "&quot;" in input string
+        $text = str_replace('|+|amp|+|', '&', $text);
+
+        // Bring down number of empty lines to 2 max
+        $text = preg_replace("/\n\s+\n/", "\n\n", $text);
+        $text = preg_replace("/[\n]{3,}/", "\n\n", $text);
+
+        // remove leading empty lines (can be produced by eg. P tag on the beginning)
+        $text = ltrim($text, "\n");
+
+        // Wrap the text to a readable format
+        // for PHP versions >= 4.0.2. Default width is 75
+        // If width is 0 or less, don't wrap the text.
+        if ( $this->width > 0 ) {
+            $text = wordwrap($text, $this->width);
+        }
+    }
+
+    /**
+     * Helper function called by preg_replace() on link replacement.
+     *
+     * Maintains an internal list of links to be displayed at the end of the
+     * text, with numeric indices to the original point in the text they
+     * appeared. Also makes an effort at identifying and handling absolute
+     * and relative links.
+     *
+     * @param string $link URL of the link
+     * @param string $display Part of the text to associate number with
+     */
+    protected function _build_link_list( $link, $display )
+    {
+        if (!$this->_do_links || empty($link)) {
+            return $display;
+        }
+
+        // Ignored link types
+        if (preg_match('!^(javascript:|mailto:|#)!i', $link)) {
+            return $display;
+        }
+
+        if (preg_match('!^([a-z][a-z0-9.+-]+:)!i', $link)) {
+            $url = $link;
+        }
+        else {
+            $url = $this->url;
+            if (substr($link, 0, 1) != '/') {
+                $url .= '/';
+            }
+            $url .= "$link";
+        }
+
+        if (($index = array_search($url, $this->_link_list)) === false) {
+            $index = count($this->_link_list);
+            $this->_link_list[] = $url;
+        }
+
+        return $display . ' [' . ($index+1) . ']';
+    }
+
+    /**
+     * Helper function for PRE body conversion.
+     *
+     * @param string HTML content
+     */
+    protected function _convert_pre(&$text)
+    {
+        // get the content of PRE element
+        while (preg_match('/<pre[^>]*>(.*)<\/pre>/ismU', $text, $matches)) {
+            $this->pre_content = $matches[1];
+
+            // Run our defined tags search-and-replace with callback
+            $this->pre_content = preg_replace_callback($this->callback_search,
+                array($this, 'tags_preg_callback'), $this->pre_content);
+
+            // convert the content
+            $this->pre_content = sprintf('<div><br>%s<br></div>',
+                preg_replace($this->pre_search, $this->pre_replace, $this->pre_content));
+
+            // replace the content (use callback because content can contain $0 variable)
+            $text = preg_replace_callback('/<pre[^>]*>.*<\/pre>/ismU',
+                array($this, 'pre_preg_callback'), $text, 1);
+
+            // free memory
+            $this->pre_content = '';
+        }
+    }
+
+    /**
+     * Helper function for BLOCKQUOTE body conversion.
+     *
+     * @param string HTML content
+     */
+    protected function _convert_blockquotes(&$text)
+    {
+        if (preg_match_all('/<\/*blockquote[^>]*>/i', $text, $matches, PREG_OFFSET_CAPTURE)) {
+            $level = 0;
+            $diff = 0;
+            foreach ($matches[0] as $m) {
+                if ($m[0][0] == '<' && $m[0][1] == '/') {
+                    $level--;
+                    if ($level < 0) {
+                        $level = 0; // malformed HTML: go to next blockquote
+                    }
+                    else if ($level > 0) {
+                        // skip inner blockquote
+                    }
+                    else {
+                        $end  = $m[1];
+                        $len  = $end - $taglen - $start;
+                        // Get blockquote content
+                        $body = substr($text, $start + $taglen - $diff, $len);
+
+                        // Set text width
+                        $p_width = $this->width;
+                        if ($this->width > 0) $this->width -= 2;
+                        // Convert blockquote content
+                        $body = trim($body);
+                        $this->_converter($body);
+                        // Add citation markers and create PRE block
+                        $body = preg_replace('/((^|\n)>*)/', '\\1> ', trim($body));
+                        $body = '<pre>' . htmlspecialchars($body) . '</pre>';
+                        // Re-set text width
+                        $this->width = $p_width;
+                        // Replace content
+                        $text = substr($text, 0, $start - $diff)
+                            . $body . substr($text, $end + strlen($m[0]) - $diff);
+
+                        $diff = $len + $taglen + strlen($m[0]) - strlen($body);
+                        unset($body);
+                    }
+                }
+                else {
+                    if ($level == 0) {
+                        $start = $m[1];
+                        $taglen = strlen($m[0]);
+                    }
+                    $level ++;
+                }
+            }
+        }
+    }
+
+    /**
+     * Callback function for preg_replace_callback use.
+     *
+     * @param  array PREG matches
+     * @return string
+     */
+    public function tags_preg_callback($matches)
+    {
+        switch (strtolower($matches[1])) {
+        case 'b':
+        case 'strong':
+            return $this->_toupper($matches[3]);
+        case 'th':
+            return $this->_toupper("\t\t". $matches[3] ."\n");
+        case 'h':
+            return $this->_toupper("\n\n". $matches[3] ."\n\n");
+        case 'a':
+            // Remove spaces in URL (#1487805)
+            $url = str_replace(' ', '', $matches[3]);
+            return $this->_build_link_list($url, $matches[4]);
+        }
+    }
+
+    /**
+     * Callback function for preg_replace_callback use in PRE content handler.
+     *
+     * @param array PREG matches
+     * @return string
+     */
+    public function pre_preg_callback($matches)
+    {
+        return $this->pre_content;
+    }
+
+    /**
+     * Strtoupper function with HTML tags and entities handling.
+     *
+     * @param string $str Text to convert
+     * @return string Converted text
+     */
+    private function _toupper($str)
+    {
+        // string can containg HTML tags
+        $chunks = preg_split('/(<[^>]*>)/', $str, null, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE);
+
+        // convert toupper only the text between HTML tags
+        foreach ($chunks as $idx => $chunk) {
+            if ($chunk[0] != '<') {
+                $chunks[$idx] = $this->_strtoupper($chunk);
+            }
+        }
+
+        return implode($chunks);
+    }
+
+    /**
+     * Strtoupper multibyte wrapper function with HTML entities handling.
+     *
+     * @param string $str Text to convert
+     * @return string Converted text
+     */
+    private function _strtoupper($str)
+    {
+        $str = html_entity_decode($str, ENT_COMPAT, $this->charset);
+        $str = mb_strtoupper($str);
+        $str = htmlspecialchars($str, ENT_COMPAT, $this->charset);
+
+        return $str;
+    }
+}
diff --git a/lib/ext/Roundcube/rcube_image.php b/lib/ext/Roundcube/rcube_image.php
index b72a24c..9695022 100644
--- a/lib/ext/Roundcube/rcube_image.php
+++ b/lib/ext/Roundcube/rcube_image.php
@@ -2,8 +2,6 @@
 
 /*
  +-----------------------------------------------------------------------+
- | program/include/rcube_image.php                                       |
- |                                                                       |
  | This file is part of the Roundcube Webmail client                     |
  | Copyright (C) 2005-2012, The Roundcube Dev Team                       |
  | Copyright (C) 2011-2012, Kolab Systems AG                             |
@@ -14,7 +12,6 @@
  |                                                                       |
  | PURPOSE:                                                              |
  |   Image resizer and converter                                         |
- |                                                                       |
  +-----------------------------------------------------------------------+
  | Author: Thomas Bruederli <roundcube at gmail.com>                        |
  | Author: Aleksander Machniak <alec at alec.pl>                            |
@@ -131,17 +128,20 @@ class rcube_image
         }
 
         // use GD extension
-        $gd_types = array(IMAGETYPE_JPEG, IMAGETYPE_GIF, IMAGETYPE_PNG);
-        if ($props['gd_type'] && in_array($props['gd_type'], $gd_types)) {
-            if ($props['gd_type'] == IMAGETYPE_JPEG) {
+        if ($props['gd_type']) {
+            if ($props['gd_type'] == IMAGETYPE_JPEG && function_exists('imagecreatefromjpeg')) {
                 $image = imagecreatefromjpeg($this->image_file);
             }
-            elseif($props['gd_type'] == IMAGETYPE_GIF) {
+            else if($props['gd_type'] == IMAGETYPE_GIF && function_exists('imagecreatefromgif')) {
                 $image = imagecreatefromgif($this->image_file);
             }
-            elseif($props['gd_type'] == IMAGETYPE_PNG) {
+            else if($props['gd_type'] == IMAGETYPE_PNG && function_exists('imagecreatefrompng')) {
                 $image = imagecreatefrompng($this->image_file);
             }
+            else {
+                // @TODO: print error to the log?
+                return false;
+            }
 
             $scale  = $size / max($props['width'], $props['height']);
             $width  = $props['width']  * $scale;
@@ -219,19 +219,22 @@ class rcube_image
         }
 
         // use GD extension (TIFF isn't supported)
-        $props    = $this->props();
-        $gd_types = array(IMAGETYPE_JPEG, IMAGETYPE_GIF, IMAGETYPE_PNG);
+        $props = $this->props();
 
-        if ($props['gd_type'] && in_array($props['gd_type'], $gd_types)) {
-            if ($props['gd_type'] == IMAGETYPE_JPEG) {
+        if ($props['gd_type']) {
+            if ($props['gd_type'] == IMAGETYPE_JPEG && function_exists('imagecreatefromjpeg')) {
                 $image = imagecreatefromjpeg($this->image_file);
             }
-            else if ($props['gd_type'] == IMAGETYPE_GIF) {
+            else if ($props['gd_type'] == IMAGETYPE_GIF && function_exists('imagecreatefromgif')) {
                 $image = imagecreatefromgif($this->image_file);
             }
-            else if ($props['gd_type'] == IMAGETYPE_PNG) {
+            else if ($props['gd_type'] == IMAGETYPE_PNG && function_exists('imagecreatefrompng')) {
                 $image = imagecreatefrompng($this->image_file);
             }
+            else {
+                // @TODO: print error to the log?
+                return false;
+            }
 
             if ($type == self::TYPE_JPG) {
                 $result = imagejpeg($image, $filename, 75);
diff --git a/lib/ext/Roundcube/rcube_imap.php b/lib/ext/Roundcube/rcube_imap.php
index 8ca24de..74c1f53 100644
--- a/lib/ext/Roundcube/rcube_imap.php
+++ b/lib/ext/Roundcube/rcube_imap.php
@@ -2,8 +2,6 @@
 
 /*
  +-----------------------------------------------------------------------+
- | program/include/rcube_imap.php                                        |
- |                                                                       |
  | This file is part of the Roundcube Webmail client                     |
  | Copyright (C) 2005-2012, The Roundcube Dev Team                       |
  | Copyright (C) 2011-2012, Kolab Systems AG                             |
@@ -14,14 +12,12 @@
  |                                                                       |
  | PURPOSE:                                                              |
  |   IMAP Storage Engine                                                 |
- |                                                                       |
  +-----------------------------------------------------------------------+
  | Author: Thomas Bruederli <roundcube at gmail.com>                        |
  | Author: Aleksander Machniak <alec at alec.pl>                            |
  +-----------------------------------------------------------------------+
 */
 
-
 /**
  * Interface class for accessing an IMAP server
  *
@@ -151,7 +147,7 @@ class rcube_imap extends rcube_storage
 
         $attempt = 0;
         do {
-            $data = rcube::get_instance()->plugins->exec_hook('imap_connect',
+            $data = rcube::get_instance()->plugins->exec_hook('storage_connect',
                 array_merge($this->options, array('host' => $host, 'user' => $user,
                     'attempt' => ++$attempt)));
 
@@ -571,7 +567,7 @@ class rcube_imap extends rcube_storage
      * Get message count for a specific folder
      *
      * @param  string  $folder  Folder name
-     * @param  string  $mode    Mode for count [ALL|THREADS|UNSEEN|RECENT]
+     * @param  string  $mode    Mode for count [ALL|THREADS|UNSEEN|RECENT|EXISTS]
      * @param  boolean $force   Force reading from server and update cache
      * @param  boolean $status  Enables storing folder status info (max UID/count),
      *                          required for folder_status()
@@ -592,7 +588,7 @@ class rcube_imap extends rcube_storage
      * protected method for getting nr of messages
      *
      * @param string  $folder  Folder name
-     * @param string  $mode    Mode for count [ALL|THREADS|UNSEEN|RECENT]
+     * @param string  $mode    Mode for count [ALL|THREADS|UNSEEN|RECENT|EXISTS]
      * @param boolean $force   Force reading from server and update cache
      * @param boolean $status  Enables storing folder status info (max UID/count),
      *                         required for folder_status()
@@ -614,6 +610,10 @@ class rcube_imap extends rcube_storage
             }
         }
 
+        // EXISTS is a special alias for ALL, it allows to get the number
+        // of all messages in a folder also when search is active and with
+        // any skip_deleted setting
+
         $a_folder_cache = $this->get_cache('messagecount');
 
         // return cached value
@@ -644,7 +644,7 @@ class rcube_imap extends rcube_storage
             $count = $this->conn->countRecent($folder);
         }
         // use SEARCH for message counting
-        else if (!empty($this->options['skip_deleted'])) {
+        else if ($mode != 'EXISTS' && !empty($this->options['skip_deleted'])) {
             $search_str = "ALL UNDELETED";
             $keys       = array('COUNT');
 
@@ -683,8 +683,8 @@ class rcube_imap extends rcube_storage
             }
             else {
                 $count = $this->conn->countMessages($folder);
-                if ($status) {
-                    $this->set_folder_stats($folder,'cnt', $count);
+                if ($status && $mode == 'ALL') {
+                    $this->set_folder_stats($folder, 'cnt', $count);
                     $this->set_folder_stats($folder, 'maxuid', $count ? $this->id2uid($count, $folder) : 0);
                 }
             }
@@ -2226,10 +2226,11 @@ class rcube_imap extends rcube_storage
      * @param boolean $is_file True if $message is a filename
      * @param array   $flags   Message flags
      * @param mixed   $date    Message internal date
+     * @param bool    $binary  Enables BINARY append
      *
      * @return int|bool Appended message UID or True on success, False on error
      */
-    public function save_message($folder, &$message, $headers='', $is_file=false, $flags = array(), $date = null)
+    public function save_message($folder, &$message, $headers='', $is_file=false, $flags = array(), $date = null, $binary = false)
     {
         if (!strlen($folder)) {
             $folder = $this->folder;
@@ -2247,10 +2248,10 @@ class rcube_imap extends rcube_storage
         $date = $this->date_format($date);
 
         if ($is_file) {
-            $saved = $this->conn->appendFromFile($folder, $message, $headers, $flags, $date);
+            $saved = $this->conn->appendFromFile($folder, $message, $headers, $flags, $date, $binary);
         }
         else {
-            $saved = $this->conn->append($folder, $message, $flags, $date);
+            $saved = $this->conn->append($folder, $message, $flags, $date, $binary);
         }
 
         if ($saved) {
diff --git a/lib/ext/Roundcube/rcube_imap_cache.php b/lib/ext/Roundcube/rcube_imap_cache.php
index 31214cf..f33ac07 100644
--- a/lib/ext/Roundcube/rcube_imap_cache.php
+++ b/lib/ext/Roundcube/rcube_imap_cache.php
@@ -2,8 +2,6 @@
 
 /*
  +-----------------------------------------------------------------------+
- | program/include/rcube_imap_cache.php                                  |
- |                                                                       |
  | This file is part of the Roundcube Webmail client                     |
  | Copyright (C) 2005-2012, The Roundcube Dev Team                       |
  |                                                                       |
@@ -13,14 +11,12 @@
  |                                                                       |
  | PURPOSE:                                                              |
  |   Caching of IMAP folder contents (messages and index)                |
- |                                                                       |
  +-----------------------------------------------------------------------+
  | Author: Thomas Bruederli <roundcube at gmail.com>                        |
  | Author: Aleksander Machniak <alec at alec.pl>                            |
  +-----------------------------------------------------------------------+
 */
 
-
 /**
  * Interface class for accessing Roundcube messages cache
  *
diff --git a/lib/ext/Roundcube/rcube_imap_generic.php b/lib/ext/Roundcube/rcube_imap_generic.php
index 0f32d83..8d84bf7 100644
--- a/lib/ext/Roundcube/rcube_imap_generic.php
+++ b/lib/ext/Roundcube/rcube_imap_generic.php
@@ -2,8 +2,6 @@
 
 /**
  +-----------------------------------------------------------------------+
- | program/include/rcube_imap_generic.php                                |
- |                                                                       |
  | This file is part of the Roundcube Webmail client                     |
  | Copyright (C) 2005-2012, The Roundcube Dev Team                       |
  | Copyright (C) 2011-2012, Kolab Systems AG                             |
@@ -19,14 +17,12 @@
  |   functionality built-in.                                             |
  |                                                                       |
  |   Based on Iloha IMAP Library. See http://ilohamail.org/ for details  |
- |                                                                       |
  +-----------------------------------------------------------------------+
  | Author: Aleksander Machniak <alec at alec.pl>                            |
  | Author: Ryo Chijiiwa <Ryo at IlohaMail.org>                              |
  +-----------------------------------------------------------------------+
 */
 
-
 /**
  * PHP based wrapper class to connect to an IMAP server
  *
@@ -757,12 +753,16 @@ class rcube_imap_generic
         $this->fp = @fsockopen($host, $this->prefs['port'], $errno, $errstr, $this->prefs['timeout']);
 
         if (!$this->fp) {
+            if (!$errstr) {
+                $errstr = "Unknown reason (fsockopen() function disabled?)";
+            }
             $this->setError(self::ERROR_BAD, sprintf("Could not connect to %s:%d: %s", $host, $this->prefs['port'], $errstr));
             return false;
         }
 
-        if ($this->prefs['timeout'] > 0)
+        if ($this->prefs['timeout'] > 0) {
             stream_set_timeout($this->fp, $this->prefs['timeout']);
+        }
 
         $line = trim(fgets($this->fp, 8192));
 
@@ -1311,6 +1311,11 @@ class rcube_imap_generic
                 if ($cmd == 'LIST' || $cmd == 'LSUB') {
                     list($opts, $delim, $mailbox) = $this->tokenizeResponse($line, 3);
 
+                    // Remove redundant separator at the end of folder name, UW-IMAP bug? (#1488879)
+                    if ($delim) {
+                        $mailbox = rtrim($mailbox, $delim);
+                    }
+
                     // Add to result array
                     if (!$lstatus) {
                         $folders[] = $mailbox;
@@ -2548,10 +2553,11 @@ class rcube_imap_generic
      * @param string $message Message content
      * @param array  $flags   Message flags
      * @param string $date    Message internal date
+     * @param bool   $binary  Enable BINARY append (RFC3516)
      *
      * @return string|bool On success APPENDUID response (if available) or True, False on failure
      */
-    function append($mailbox, &$message, $flags = array(), $date = null)
+    function append($mailbox, &$message, $flags = array(), $date = null, $binary = false)
     {
         unset($this->data['APPENDUID']);
 
@@ -2559,8 +2565,13 @@ class rcube_imap_generic
             return false;
         }
 
-        $message = str_replace("\r", '', $message);
-        $message = str_replace("\n", "\r\n", $message);
+        $binary       = $binary && $this->getCapability('BINARY');
+        $literal_plus = !$binary && $this->prefs['literal+'];
+
+        if (!$binary) {
+            $message = str_replace("\r", '', $message);
+            $message = str_replace("\n", "\r\n", $message);
+        }
 
         $len = strlen($message);
         if (!$len) {
@@ -2573,12 +2584,12 @@ class rcube_imap_generic
         if (!empty($date)) {
             $request .= ' ' . $this->escape($date);
         }
-        $request .= ' {' . $len . ($this->prefs['literal+'] ? '+' : '') . '}';
+        $request .= ' ' . ($binary ? '~' : '') . '{' . $len . ($literal_plus ? '+' : '') . '}';
 
         // send APPEND command
         if ($this->putLine($request)) {
             // Do not wait when LITERAL+ is supported
-            if (!$this->prefs['literal+']) {
+            if (!$literal_plus) {
                 $line = $this->readReply();
 
                 if ($line[0] != '+') {
@@ -2620,10 +2631,11 @@ class rcube_imap_generic
      * @param string $headers Message headers
      * @param array  $flags   Message flags
      * @param string $date    Message internal date
+     * @param bool   $binary  Enable BINARY append (RFC3516)
      *
      * @return string|bool On success APPENDUID response (if available) or True, False on failure
      */
-    function appendFromFile($mailbox, $path, $headers=null, $flags = array(), $date = null)
+    function appendFromFile($mailbox, $path, $headers=null, $flags = array(), $date = null, $binary = false)
     {
         unset($this->data['APPENDUID']);
 
@@ -2654,18 +2666,21 @@ class rcube_imap_generic
             $len += strlen($headers) + strlen($body_separator);
         }
 
+        $binary       = $binary && $this->getCapability('BINARY');
+        $literal_plus = !$binary && $this->prefs['literal+'];
+
         // build APPEND command
         $key = $this->nextTag();
         $request = "$key APPEND " . $this->escape($mailbox) . ' (' . $this->flagsToStr($flags) . ')';
         if (!empty($date)) {
             $request .= ' ' . $this->escape($date);
         }
-        $request .= ' {' . $len . ($this->prefs['literal+'] ? '+' : '') . '}';
+        $request .= ' ' . ($binary ? '~' : '') . '{' . $len . ($literal_plus ? '+' : '') . '}';
 
         // send APPEND command
         if ($this->putLine($request)) {
             // Don't wait when LITERAL+ is supported
-            if (!$this->prefs['literal+']) {
+            if (!$literal_plus) {
                 $line = $this->readReply();
 
                 if ($line[0] != '+') {
diff --git a/lib/ext/Roundcube/rcube_ldap.php b/lib/ext/Roundcube/rcube_ldap.php
index c9a14d8..700c6f6 100644
--- a/lib/ext/Roundcube/rcube_ldap.php
+++ b/lib/ext/Roundcube/rcube_ldap.php
@@ -2,8 +2,6 @@
 
 /*
  +-----------------------------------------------------------------------+
- | program/include/rcube_ldap.php                                        |
- |                                                                       |
  | This file is part of the Roundcube Webmail client                     |
  | Copyright (C) 2006-2012, The Roundcube Dev Team                       |
  | Copyright (C) 2011-2012, Kolab Systems AG                             |
@@ -14,7 +12,6 @@
  |                                                                       |
  | PURPOSE:                                                              |
  |   Interface to an LDAP address directory                              |
- |                                                                       |
  +-----------------------------------------------------------------------+
  | Author: Thomas Bruederli <roundcube at gmail.com>                        |
  |         Andreas Dick <andudi (at) gmx (dot) ch>                       |
@@ -22,7 +19,6 @@
  +-----------------------------------------------------------------------+
 */
 
-
 /**
  * Model class to access an LDAP address directory
  *
@@ -798,27 +794,14 @@ class rcube_ldap extends rcube_addressbook
             $this->_debug("S: ".ldap_count_entries($this->conn, $this->ldap_result)." record(s)");
 
             // get all entries of this page and post-filter those that really match the query
-            $search = mb_strtolower($value);
+            $search  = mb_strtolower($value);
             $entries = ldap_get_entries($this->conn, $this->ldap_result);
 
             for ($i = 0; $i < $entries['count']; $i++) {
                 $rec = $this->_ldap2result($entries[$i]);
                 foreach ($fields as $f) {
                     foreach ((array)$rec[$f] as $val) {
-                        $val = mb_strtolower($val);
-                        switch ($mode) {
-                        case 1:
-                            $got = ($val == $search);
-                            break;
-                        case 2:
-                            $got = ($search == substr($val, 0, strlen($search)));
-                            break;
-                        default:
-                            $got = (strpos($val, $search) !== false);
-                            break;
-                        }
-
-                        if ($got) {
+                        if ($this->compare_search_value($f, $val, $search, $mode)) {
                             $this->result->add($rec);
                             $this->result->count++;
                             break 2;
@@ -1455,6 +1438,7 @@ class rcube_ldap extends rcube_addressbook
                 if ($this->vlv_active && function_exists('ldap_parse_virtuallist_control')) {
                     if (ldap_parse_result($this->conn, $this->ldap_result,
                         $errcode, $matcheddn, $errmsg, $referrals, $serverctrls)
+                        && $serverctrls // can be null e.g. in case of adm. limit error
                     ) {
                         ldap_parse_virtuallist_control($this->conn, $serverctrls,
                             $last_offset, $this->vlv_count, $vresult);
diff --git a/lib/ext/Roundcube/rcube_message.php b/lib/ext/Roundcube/rcube_message.php
index 4ef534a..b52b79b 100644
--- a/lib/ext/Roundcube/rcube_message.php
+++ b/lib/ext/Roundcube/rcube_message.php
@@ -2,8 +2,6 @@
 
 /*
  +-----------------------------------------------------------------------+
- | program/include/rcube_message.php                                     |
- |                                                                       |
  | This file is part of the Roundcube Webmail client                     |
  | Copyright (C) 2008-2010, The Roundcube Dev Team                       |
  |                                                                       |
@@ -19,7 +17,6 @@
  +-----------------------------------------------------------------------+
 */
 
-
 /**
  * Logical representation of a mail message with all its data
  * and related functions
@@ -274,7 +271,7 @@ class rcube_message
                 $out = $this->get_part_content($mime_id);
 
                 // create instance of html2text class
-                $txt = new html2text($out);
+                $txt = new rcube_html2text($out);
                 return $txt->get_text();
             }
         }
@@ -320,8 +317,15 @@ class rcube_message
     private function parse_structure($structure, $recursive = false)
     {
         // real content-type of message/rfc822 part
-        if ($structure->mimetype == 'message/rfc822' && $structure->real_mimetype)
+        if ($structure->mimetype == 'message/rfc822' && $structure->real_mimetype) {
             $mimetype = $structure->real_mimetype;
+
+            // parse headers from message/rfc822 part
+            if (!isset($structure->headers['subject'])) {
+                list($headers, $dump) = explode("\r\n\r\n", $this->get_part_content($structure->mime_id, null, true, 8192));
+                $structure->headers = rcube_mime::parse_headers($headers);
+            }
+        }
         else
             $mimetype = $structure->mimetype;
 
@@ -329,7 +333,7 @@ class rcube_message
         if ($recursive && is_array($structure->headers) && isset($structure->headers['subject'])) {
             $c = new stdClass;
             $c->type = 'headers';
-            $c->headers = &$structure->headers;
+            $c->headers = $structure->headers;
             $this->parts[] = $c;
         }
 
@@ -346,45 +350,59 @@ class rcube_message
 
         // print body if message doesn't have multiple parts
         if ($message_ctype_primary == 'text' && !$recursive) {
+            // parts with unsupported type add to attachments list
+            if (!in_array($message_ctype_secondary, array('plain', 'html', 'enriched'))) {
+                $this->attachments[] = $structure;
+                return;
+            }
+
             $structure->type = 'content';
-            $this->parts[] = &$structure;
+            $this->parts[] = $structure;
 
             // Parse simple (plain text) message body
-            if ($message_ctype_secondary == 'plain')
+            if ($message_ctype_secondary == 'plain') {
                 foreach ((array)$this->uu_decode($structure) as $uupart) {
                     $this->mime_parts[$uupart->mime_id] = $uupart;
                     $this->attachments[] = $uupart;
                 }
+            }
         }
         // the same for pgp signed messages
         else if ($mimetype == 'application/pgp' && !$recursive) {
             $structure->type = 'content';
-            $this->parts[] = &$structure;
+            $this->parts[] = $structure;
         }
         // message contains (more than one!) alternative parts
         else if ($mimetype == 'multipart/alternative'
             && is_array($structure->parts) && count($structure->parts) > 1
         ) {
-            // get html/plaintext parts
-            $plain_part = $html_part = $print_part = $related_part = null;
+            $plain_part   = null;
+            $html_part    = null;
+            $print_part   = null;
+            $related_part = null;
+            $attach_part  = null;
 
+            // get html/plaintext parts, other add to attachments list
             foreach ($structure->parts as $p => $sub_part) {
                 $sub_mimetype = $sub_part->mimetype;
+                $is_multipart = preg_match('/^multipart\/(related|relative|mixed|alternative)/', $sub_mimetype);
 
                 // skip empty text parts
-                if (!$sub_part->size && preg_match('#^text/(plain|html|enriched)$#', $sub_mimetype)) {
+                if (!$sub_part->size && !$is_multipart) {
                     continue;
                 }
 
                 // check if sub part is
-                if ($sub_mimetype == 'text/plain')
+                if ($is_multipart)
+                    $related_part = $p;
+                else if ($sub_mimetype == 'text/plain')
                     $plain_part = $p;
                 else if ($sub_mimetype == 'text/html')
                     $html_part = $p;
                 else if ($sub_mimetype == 'text/enriched')
                     $enriched_part = $p;
-                else if (in_array($sub_mimetype, array('multipart/related', 'multipart/mixed', 'multipart/alternative')))
-                    $related_part = $p;
+                else
+                    $attach_part = $p;
             }
 
             // parse related part (alternative part could be in here)
@@ -400,13 +418,13 @@ class rcube_message
 
             // choose html/plain part to print
             if ($html_part !== null && $this->opt['prefer_html']) {
-                $print_part = &$structure->parts[$html_part];
+                $print_part = $structure->parts[$html_part];
             }
             else if ($enriched_part !== null) {
-                $print_part = &$structure->parts[$enriched_part];
+                $print_part = $structure->parts[$enriched_part];
             }
             else if ($plain_part !== null) {
-                $print_part = &$structure->parts[$plain_part];
+                $print_part = $structure->parts[$plain_part];
             }
 
             // add the right message body
@@ -428,11 +446,16 @@ class rcube_message
 
             // add html part as attachment
             if ($html_part !== null && $structure->parts[$html_part] !== $print_part) {
-                $html_part = &$structure->parts[$html_part];
+                $html_part = $structure->parts[$html_part];
                 $html_part->mimetype = 'text/html';
 
                 $this->attachments[] = $html_part;
             }
+
+            // add unsupported/unrecognized parts to attachments list
+            if ($attach_part) {
+                $this->attachments[] = $structure->parts[$attach_part];
+            }
         }
         // this is an ecrypted message -> create a plaintext body with the according message
         else if ($mimetype == 'multipart/encrypted') {
@@ -537,7 +560,7 @@ class rcube_message
                         continue;
 
                     // part belongs to a related message and is linked
-                    if ($mimetype == 'multipart/related'
+                    if (preg_match('/^multipart\/(related|relative)/', $mimetype)
                         && ($mail_part->headers['content-id'] || $mail_part->headers['content-location'])) {
                         if ($mail_part->headers['content-id'])
                             $mail_part->content_id = preg_replace(array('/^</', '/>$/'), '', $mail_part->headers['content-id']);
@@ -557,9 +580,6 @@ class rcube_message
                     // regular attachment with valid content type
                     // (content-type name regexp according to RFC4288.4.2)
                     else if (preg_match('/^[a-z0-9!#$&.+^_-]+\/[a-z0-9!#$&.+^_-]+$/i', $part_mimetype)) {
-                        if (!$mail_part->filename)
-                            $mail_part->filename = 'Part '.$mail_part->mime_id;
-
                         $this->attachments[] = $mail_part;
                     }
                     // attachment with invalid content type
@@ -579,7 +599,7 @@ class rcube_message
             }
 
             // if this was a related part try to resolve references
-            if ($mimetype == 'multipart/related' && sizeof($this->inline_parts)) {
+            if (preg_match('/^multipart\/(related|relative)/', $mimetype) && sizeof($this->inline_parts)) {
                 $a_replaces = array();
                 $img_regexp = '/^image\/(gif|jpe?g|png|tiff|bmp|svg)/';
 
@@ -624,7 +644,6 @@ class rcube_message
         }
         // message is a single part non-text (without filename)
         else if (preg_match('/application\//i', $mimetype)) {
-            $structure->filename = 'Part '.$structure->mime_id;
             $this->attachments[] = $structure;
         }
     }
diff --git a/lib/ext/Roundcube/rcube_message_header.php b/lib/ext/Roundcube/rcube_message_header.php
index 445d0bd..274ae7f 100644
--- a/lib/ext/Roundcube/rcube_message_header.php
+++ b/lib/ext/Roundcube/rcube_message_header.php
@@ -2,8 +2,6 @@
 
 /**
  +-----------------------------------------------------------------------+
- | program/include/rcube_message_header.php                              |
- |                                                                       |
  | This file is part of the Roundcube Webmail client                     |
  | Copyright (C) 2005-2012, The Roundcube Dev Team                       |
  | Copyright (C) 2011-2012, Kolab Systems AG                             |
@@ -14,7 +12,6 @@
  |                                                                       |
  | PURPOSE:                                                              |
  |   E-mail message headers representation                               |
- |                                                                       |
  +-----------------------------------------------------------------------+
  | Author: Aleksander Machniak <alec at alec.pl>                            |
  +-----------------------------------------------------------------------+
@@ -235,13 +232,30 @@ class rcube_message_header
             $this->others[$name] = $value;
         }
     }
+
+
+    /**
+     * Factory method to instantiate headers from a data array
+     *
+     * @param array Hash array with header values
+     * @return object rcube_message_header instance filled with headers values
+     */
+    public static function from_array($arr)
+    {
+        $obj = new rcube_message_header;
+        foreach ($arr as $k => $v)
+            $obj->set($k, $v);
+
+        return $obj;
+    }
 }
 
 
 /**
  * Class for sorting an array of rcube_message_header objects in a predetermined order.
  *
- * @package Mail
+ * @package    Framework
+ * @subpackage Storage
  * @author  Aleksander Machniak <alec at alec.pl>
  */
 class rcube_message_header_sorter
diff --git a/lib/ext/Roundcube/rcube_message_part.php b/lib/ext/Roundcube/rcube_message_part.php
index c9c9257..4222ba3 100644
--- a/lib/ext/Roundcube/rcube_message_part.php
+++ b/lib/ext/Roundcube/rcube_message_part.php
@@ -2,8 +2,6 @@
 
 /*
  +-----------------------------------------------------------------------+
- | program/include/rcube_message_part.php                                |
- |                                                                       |
  | This file is part of the Roundcube Webmail client                     |
  | Copyright (C) 2005-2012, The Roundcube Dev Team                       |
  | Copyright (C) 2011-2012, Kolab Systems AG                             |
@@ -14,14 +12,12 @@
  |                                                                       |
  | PURPOSE:                                                              |
  |   Class representing a message part                                   |
- |                                                                       |
  +-----------------------------------------------------------------------+
  | Author: Thomas Bruederli <roundcube at gmail.com>                        |
  | Author: Aleksander Machniak <alec at alec.pl>                            |
  +-----------------------------------------------------------------------+
 */
 
-
 /**
  * Class representing a message part
  *
diff --git a/lib/ext/Roundcube/rcube_mime.php b/lib/ext/Roundcube/rcube_mime.php
index 17cb3f0..eef8ca1 100644
--- a/lib/ext/Roundcube/rcube_mime.php
+++ b/lib/ext/Roundcube/rcube_mime.php
@@ -2,8 +2,6 @@
 
 /*
  +-----------------------------------------------------------------------+
- | program/include/rcube_mime.php                                        |
- |                                                                       |
  | This file is part of the Roundcube Webmail client                     |
  | Copyright (C) 2005-2012, The Roundcube Dev Team                       |
  | Copyright (C) 2011-2012, Kolab Systems AG                             |
@@ -14,14 +12,12 @@
  |                                                                       |
  | PURPOSE:                                                              |
  |   MIME message parsing utilities                                      |
- |                                                                       |
  +-----------------------------------------------------------------------+
  | Author: Thomas Bruederli <roundcube at gmail.com>                        |
  | Author: Aleksander Machniak <alec at alec.pl>                            |
  +-----------------------------------------------------------------------+
 */
 
-
 /**
  * Class for parsing MIME messages
  *
@@ -717,6 +713,7 @@ class rcube_mime
             $file_paths[] = $mime_types;
 
         // try common locations
+        $file_paths[] = '/etc/mime.types';
         $file_paths[] = '/etc/httpd/mime.types';
         $file_paths[] = '/etc/httpd2/mime.types';
         $file_paths[] = '/etc/apache/mime.types';
@@ -749,7 +746,7 @@ class rcube_mime
         // fallback to some well-known types most important for daily emails
         if (empty($mime_types)) {
             $mime_extensions = @include(RCUBE_CONFIG_DIR . '/mimetypes.php');
-            $mime_extensions += array('gif' => 'image/gif', 'png' => 'image/png', 'jpg' => 'image/jpg', 'jpeg' => 'image/jpeg', 'tif' => 'image/tiff');
+            $mime_extensions += array('gif' => 'image/gif', 'png' => 'image/png', 'jpg' => 'image/jpeg', 'jpeg' => 'image/jpeg', 'tif' => 'image/tiff');
 
             foreach ($mime_extensions as $ext => $mime)
                 $mime_types[$mime][] = $ext;
diff --git a/lib/ext/Roundcube/rcube_output.php b/lib/ext/Roundcube/rcube_output.php
index 4ef42f5..b8ae86c 100644
--- a/lib/ext/Roundcube/rcube_output.php
+++ b/lib/ext/Roundcube/rcube_output.php
@@ -2,17 +2,15 @@
 
 /*
  +-----------------------------------------------------------------------+
- | program/include/rcube_output.php                                      |
- |                                                                       |
  | This file is part of the Roundcube PHP suite                          |
  | Copyright (C) 2005-2012 The Roundcube Dev Team                        |
  |                                                                       |
  | Licensed under the GNU General Public License version 3 or            |
  | any later version with exceptions for skins & plugins.                |
  | See the README file for a full license statement.                     |
+ |                                                                       |
  | CONTENTS:                                                             |
  |   Abstract class for output generation                                |
- |                                                                       |
  +-----------------------------------------------------------------------+
  | Author: Thomas Bruederli <roundcube at gmail.com>                        |
  | Author: Aleksander Machniak <alec at alec.pl>                            |
diff --git a/lib/ext/Roundcube/rcube_plugin.php b/lib/ext/Roundcube/rcube_plugin.php
index 5db8502..66e77cc 100644
--- a/lib/ext/Roundcube/rcube_plugin.php
+++ b/lib/ext/Roundcube/rcube_plugin.php
@@ -2,10 +2,8 @@
 
 /*
  +-----------------------------------------------------------------------+
- | program/include/rcube_plugin.php                                      |
- |                                                                       |
  | This file is part of the Roundcube Webmail client                     |
- | Copyright (C) 2008-2009, The Roundcube Dev Team                       |
+ | Copyright (C) 2008-2012, The Roundcube Dev Team                       |
  |                                                                       |
  | Licensed under the GNU General Public License version 3 or            |
  | any later version with exceptions for skins & plugins.                |
@@ -27,334 +25,353 @@
  */
 abstract class rcube_plugin
 {
-  /**
-   * Class name of the plugin instance
-   *
-   * @var string
-   */
-  public $ID;
-
-  /**
-   * Instance of Plugin API
-   *
-   * @var rcube_plugin_api
-   */
-  public $api;
-
-  /**
-   * Regular expression defining task(s) to bind with 
-   *
-   * @var string
-   */
-  public $task;
-
-  /**
-   * Disables plugin in AJAX requests
-   *
-   * @var boolean
-   */
-  public $noajax = false;
-
-  /**
-   * Disables plugin in framed mode
-   *
-   * @var boolean
-   */
-  public $noframe = false;
-
-  protected $home;
-  protected $urlbase;
-  private $mytask;
-
-
-  /**
-   * Default constructor.
-   *
-   * @param rcube_plugin_api $api Plugin API
-   */
-  public function __construct($api)
-  {
-    $this->ID = get_class($this);
-    $this->api = $api;
-    $this->home = $api->dir . $this->ID;
-    $this->urlbase = $api->url . $this->ID . '/';
-  }
-
-  /**
-   * Initialization method, needs to be implemented by the plugin itself
-   */
-  abstract function init();
-
-
-  /**
-   * Attempt to load the given plugin which is required for the current plugin
-   *
-   * @param string Plugin name
-   * @return boolean True on success, false on failure
-   */
-  public function require_plugin($plugin_name)
-  {
-    return $this->api->load_plugin($plugin_name);
-  }
-
-
-  /**
-   * Load local config file from plugins directory.
-   * The loaded values are patched over the global configuration.
-   *
-   * @param string $fname Config file name relative to the plugin's folder
-   * @return boolean True on success, false on failure
-   */
-  public function load_config($fname = 'config.inc.php')
-  {
-    $fpath = $this->home.'/'.$fname;
-    $rcube = rcube::get_instance();
-    if (is_file($fpath) && !$rcube->config->load_from_file($fpath)) {
-      rcube::raise_error(array(
-        'code' => 527, 'type' => 'php',
-        'file' => __FILE__, 'line' => __LINE__,
-        'message' => "Failed to load config from $fpath"), true, false);
-      return false;
+    /**
+     * Class name of the plugin instance
+     *
+     * @var string
+     */
+    public $ID;
+
+    /**
+     * Instance of Plugin API
+     *
+     * @var rcube_plugin_api
+     */
+    public $api;
+
+    /**
+     * Regular expression defining task(s) to bind with 
+     *
+     * @var string
+     */
+    public $task;
+
+    /**
+     * Disables plugin in AJAX requests
+     *
+     * @var boolean
+     */
+    public $noajax = false;
+
+    /**
+     * Disables plugin in framed mode
+     *
+     * @var boolean
+     */
+    public $noframe = false;
+
+    protected $home;
+    protected $urlbase;
+    private $mytask;
+
+
+    /**
+     * Default constructor.
+     *
+     * @param rcube_plugin_api $api Plugin API
+     */
+    public function __construct($api)
+    {
+        $this->ID      = get_class($this);
+        $this->api     = $api;
+        $this->home    = $api->dir . $this->ID;
+        $this->urlbase = $api->url . $this->ID . '/';
+    }
+
+    /**
+     * Initialization method, needs to be implemented by the plugin itself
+     */
+    abstract function init();
+
+    /**
+     * Attempt to load the given plugin which is required for the current plugin
+     *
+     * @param string Plugin name
+     * @return boolean True on success, false on failure
+     */
+    public function require_plugin($plugin_name)
+    {
+        return $this->api->load_plugin($plugin_name);
+    }
+
+    /**
+     * Load local config file from plugins directory.
+     * The loaded values are patched over the global configuration.
+     *
+     * @param string $fname Config file name relative to the plugin's folder
+     *
+     * @return boolean True on success, false on failure
+     */
+    public function load_config($fname = 'config.inc.php')
+    {
+        $fpath = $this->home.'/'.$fname;
+        $rcube = rcube::get_instance();
+
+        if (is_file($fpath) && !$rcube->config->load_from_file($fpath)) {
+            rcube::raise_error(array(
+                'code' => 527, 'type' => 'php',
+                'file' => __FILE__, 'line' => __LINE__,
+                'message' => "Failed to load config from $fpath"), true, false);
+            return false;
+        }
+
+        return true;
     }
 
-    return true;
-  }
-
-  /**
-   * Register a callback function for a specific (server-side) hook
-   *
-   * @param string $hook Hook name
-   * @param mixed  $callback Callback function as string or array with object reference and method name
-   */
-  public function add_hook($hook, $callback)
-  {
-    $this->api->register_hook($hook, $callback);
-  }
-
-  /**
-   * Unregister a callback function for a specific (server-side) hook.
-   *
-   * @param string $hook Hook name
-   * @param mixed  $callback Callback function as string or array with object reference and method name
-   */
-  public function remove_hook($hook, $callback)
-  {
-    $this->api->unregister_hook($hook, $callback);
-  }
-
-  /**
-   * Load localized texts from the plugins dir
-   *
-   * @param string $dir Directory to search in
-   * @param mixed  $add2client Make texts also available on the client (array with list or true for all)
-   */
-  public function add_texts($dir, $add2client = false)
-  {
-    $domain = $this->ID;
-    $lang   = $_SESSION['language'];
-    $langs  = array_unique(array('en_US', $lang));
-    $locdir = slashify(realpath(slashify($this->home) . $dir));
-    $texts  = array();
-
-    // Language aliases used to find localization in similar lang, see below
-    $aliases = array(
-        'de_CH' => 'de_DE',
-        'es_AR' => 'es_ES',
-        'fa_AF' => 'fa_IR',
-        'nl_BE' => 'nl_NL',
-        'pt_BR' => 'pt_PT',
-        'zh_CN' => 'zh_TW',
-    );
-
-    // use buffering to handle empty lines/spaces after closing PHP tag
-    ob_start();
-
-    foreach ($langs as $lng) {
-      $fpath = $locdir . $lng . '.inc';
-      if (is_file($fpath) && is_readable($fpath)) {
-        include $fpath;
-        $texts = (array)$labels + (array)$messages + (array)$texts;
-      }
-      else if ($lng != 'en_US') {
-        // Find localization in similar language (#1488401)
-        $alias = null;
-        if (!empty($aliases[$lng])) {
-          $alias = $aliases[$lng];
+    /**
+     * Register a callback function for a specific (server-side) hook
+     *
+     * @param string $hook     Hook name
+     * @param mixed  $callback Callback function as string or array
+     *                         with object reference and method name
+     */
+    public function add_hook($hook, $callback)
+    {
+        $this->api->register_hook($hook, $callback);
+    }
+
+    /**
+     * Unregister a callback function for a specific (server-side) hook.
+     *
+     * @param string $hook     Hook name
+     * @param mixed  $callback Callback function as string or array
+     *                         with object reference and method name
+     */
+    public function remove_hook($hook, $callback)
+    {
+        $this->api->unregister_hook($hook, $callback);
+    }
+
+    /**
+     * Load localized texts from the plugins dir
+     *
+     * @param string $dir        Directory to search in
+     * @param mixed  $add2client Make texts also available on the client
+     *                           (array with list or true for all)
+     */
+    public function add_texts($dir, $add2client = false)
+    {
+        $domain = $this->ID;
+        $lang   = $_SESSION['language'];
+        $langs  = array_unique(array('en_US', $lang));
+        $locdir = slashify(realpath(slashify($this->home) . $dir));
+        $texts  = array();
+
+        // Language aliases used to find localization in similar lang, see below
+        $aliases = array(
+            'de_CH' => 'de_DE',
+            'es_AR' => 'es_ES',
+            'fa_AF' => 'fa_IR',
+            'nl_BE' => 'nl_NL',
+            'pt_BR' => 'pt_PT',
+            'zh_CN' => 'zh_TW',
+        );
+
+        // use buffering to handle empty lines/spaces after closing PHP tag
+        ob_start();
+
+        foreach ($langs as $lng) {
+            $fpath = $locdir . $lng . '.inc';
+            if (is_file($fpath) && is_readable($fpath)) {
+                include $fpath;
+                $texts = (array)$labels + (array)$messages + (array)$texts;
+            }
+            else if ($lng != 'en_US') {
+                // Find localization in similar language (#1488401)
+                $alias = null;
+                if (!empty($aliases[$lng])) {
+                    $alias = $aliases[$lng];
+                }
+                else if ($key = array_search($lng, $aliases)) {
+                    $alias = $key;
+                }
+
+                if (!empty($alias)) {
+                    $fpath = $locdir . $alias . '.inc';
+                    if (is_file($fpath) && is_readable($fpath)) {
+                        include $fpath;
+                        $texts = (array)$labels + (array)$messages + (array)$texts;
+                    }
+                }
+            }
         }
-        else if ($key = array_search($lng, $aliases)) {
-          $alias = $key;
+
+        ob_end_clean();
+
+        // prepend domain to text keys and add to the application texts repository
+        if (!empty($texts)) {
+            $add = array();
+            foreach ($texts as $key => $value) {
+                $add[$domain.'.'.$key] = $value;
+            }
+
+            $rcube = rcube::get_instance();
+            $rcube->load_language($lang, $add);
+
+            // add labels to client
+            if ($add2client) {
+                if (is_array($add2client)) {
+                    $js_labels = array_map(array($this, 'label_map_callback'), $add2client);
+                }
+                else {
+                    $js_labels = array_keys($add);
+                }
+                $rcube->output->add_label($js_labels);
+            }
         }
+    }
+
+    /**
+     * Wrapper for rcube::gettext() adding the plugin ID as domain
+     *
+     * @param string $p Message identifier
+     *
+     * @return string Localized text
+     * @see rcube::gettext()
+     */
+    public function gettext($p)
+    {
+        return rcube::get_instance()->gettext($p, $this->ID);
+    }
 
-        if (!empty($alias)) {
-          $fpath = $locdir . $alias . '.inc';
-          if (is_file($fpath) && is_readable($fpath)) {
-            include $fpath;
-            $texts = (array)$labels + (array)$messages + (array)$texts;
-          }
+    /**
+     * Register this plugin to be responsible for a specific task
+     *
+     * @param string $task Task name (only characters [a-z0-9_.-] are allowed)
+     */
+    public function register_task($task)
+    {
+        if ($this->api->register_task($task, $this->ID)) {
+            $this->mytask = $task;
         }
-      }
     }
 
-    ob_end_clean();
+    /**
+     * Register a handler for a specific client-request action
+     *
+     * The callback will be executed upon a request like /?_task=mail&_action=plugin.myaction
+     *
+     * @param string $action  Action name (should be unique)
+     * @param mixed $callback Callback function as string
+     *                        or array with object reference and method name
+     */
+    public function register_action($action, $callback)
+    {
+        $this->api->register_action($action, $this->ID, $callback, $this->mytask);
+    }
 
-    // prepend domain to text keys and add to the application texts repository
-    if (!empty($texts)) {
-      $add = array();
-      foreach ($texts as $key => $value)
-        $add[$domain.'.'.$key] = $value;
+    /**
+     * Register a handler function for a template object
+     *
+     * When parsing a template for display, tags like <roundcube:object name="plugin.myobject" />
+     * will be replaced by the return value if the registered callback function.
+     *
+     * @param string $name     Object name (should be unique and start with 'plugin.')
+     * @param mixed  $callback Callback function as string or array with object reference
+     *                         and method name
+     */
+    public function register_handler($name, $callback)
+    {
+        $this->api->register_handler($name, $this->ID, $callback);
+    }
 
-      $rcube = rcube::get_instance();
-      $rcube->load_language($lang, $add);
+    /**
+     * Make this javascipt file available on the client
+     *
+     * @param string $fn File path; absolute or relative to the plugin directory
+     */
+    public function include_script($fn)
+    {
+        $this->api->include_script($this->resource_url($fn));
+    }
 
-      // add labels to client
-      if ($add2client) {
-        $js_labels = is_array($add2client) ? array_map(array($this, 'label_map_callback'), $add2client) : array_keys($add);
-        $rcube->output->add_label($js_labels);
-      }
+    /**
+     * Make this stylesheet available on the client
+     *
+     * @param string $fn File path; absolute or relative to the plugin directory
+     */
+    public function include_stylesheet($fn)
+    {
+        $this->api->include_stylesheet($this->resource_url($fn));
+    }
+
+    /**
+     * Append a button to a certain container
+     *
+     * @param array $p Hash array with named parameters (as used in skin templates)
+     * @param string $container Container name where the buttons should be added to
+     *
+     * @see rcube_remplate::button()
+     */
+    public function add_button($p, $container)
+    {
+        if ($this->api->output->type == 'html') {
+            // fix relative paths
+            foreach (array('imagepas', 'imageact', 'imagesel') as $key) {
+                if ($p[$key]) {
+                    $p[$key] = $this->api->url . $this->resource_url($p[$key]);
+                }
+            }
+
+            $this->api->add_content($this->api->output->button($p), $container);
+        }
     }
-  }
-
-  /**
-   * Wrapper for rcube::gettext() adding the plugin ID as domain
-   *
-   * @param string $p Message identifier
-   * @return string Localized text
-   * @see rcube::gettext()
-   */
-  public function gettext($p)
-  {
-    return rcube::get_instance()->gettext($p, $this->ID);
-  }
-
-  /**
-   * Register this plugin to be responsible for a specific task
-   *
-   * @param string $task Task name (only characters [a-z0-9_.-] are allowed)
-   */
-  public function register_task($task)
-  {
-    if ($this->api->register_task($task, $this->ID))
-      $this->mytask = $task;
-  }
-
-  /**
-    * Register a handler for a specific client-request action
-    *
-    * The callback will be executed upon a request like /?_task=mail&_action=plugin.myaction
-    *
-    * @param string $action  Action name (should be unique)
-    * @param mixed $callback Callback function as string or array with object reference and method name
-   */
-  public function register_action($action, $callback)
-  {
-    $this->api->register_action($action, $this->ID, $callback, $this->mytask);
-  }
-
-  /**
-   * Register a handler function for a template object
-   *
-   * When parsing a template for display, tags like <roundcube:object name="plugin.myobject" />
-   * will be replaced by the return value if the registered callback function.
-   *
-   * @param string $name Object name (should be unique and start with 'plugin.')
-   * @param mixed  $callback Callback function as string or array with object reference and method name
-   */
-  public function register_handler($name, $callback)
-  {
-    $this->api->register_handler($name, $this->ID, $callback);
-  }
-
-  /**
-   * Make this javascipt file available on the client
-   *
-   * @param string $fn File path; absolute or relative to the plugin directory
-   */
-  public function include_script($fn)
-  {
-    $this->api->include_script($this->resource_url($fn));
-  }
-
-  /**
-   * Make this stylesheet available on the client
-   *
-   * @param string $fn File path; absolute or relative to the plugin directory
-   */
-  public function include_stylesheet($fn)
-  {
-    $this->api->include_stylesheet($this->resource_url($fn));
-  }
-
-  /**
-   * Append a button to a certain container
-   *
-   * @param array $p Hash array with named parameters (as used in skin templates)
-   * @param string $container Container name where the buttons should be added to
-   * @see rcube_remplate::button()
-   */
-  public function add_button($p, $container)
-  {
-    if ($this->api->output->type == 'html') {
-      // fix relative paths
-      foreach (array('imagepas', 'imageact', 'imagesel') as $key)
-        if ($p[$key])
-          $p[$key] = $this->api->url . $this->resource_url($p[$key]);
-
-      $this->api->add_content($this->api->output->button($p), $container);
+
+    /**
+     * Generate an absolute URL to the given resource within the current
+     * plugin directory
+     *
+     * @param string $fn The file name
+     *
+     * @return string Absolute URL to the given resource
+     */
+    public function url($fn)
+    {
+        return $this->api->url . $this->resource_url($fn);
     }
-  }
-
-  /**
-   * Generate an absolute URL to the given resource within the current
-   * plugin directory
-   *
-   * @param string $fn The file name
-   * @return string Absolute URL to the given resource
-   */
-  public function url($fn)
-  {
-      return $this->api->url . $this->resource_url($fn);
-  }
-
-  /**
-   * Make the given file name link into the plugin directory
-   *
-   * @param string $fn Filename
-   */
-  private function resource_url($fn)
-  {
-    if ($fn[0] != '/' && !preg_match('|^https?://|i', $fn))
-      return $this->ID . '/' . $fn;
-    else
-      return $fn;
-  }
-
-  /**
-   * Provide path to the currently selected skin folder within the plugin directory
-   * with a fallback to the default skin folder.
-   *
-   * @return string Skin path relative to plugins directory
-   */
-  public function local_skin_path()
-  {
-    $rcube = rcube::get_instance();
-    foreach (array($rcube->config->get('skin'), 'larry') as $skin) {
-      $skin_path = 'skins/' . $skin;
-      if (is_dir(realpath(slashify($this->home) . $skin_path)))
-        break;
+
+    /**
+     * Make the given file name link into the plugin directory
+     *
+     * @param string $fn Filename
+     */
+    private function resource_url($fn)
+    {
+        if ($fn[0] != '/' && !preg_match('|^https?://|i', $fn)) {
+            return $this->ID . '/' . $fn;
+        }
+        else {
+            return $fn;
+        }
     }
 
-    return $skin_path;
-  }
+    /**
+     * Provide path to the currently selected skin folder within the plugin directory
+     * with a fallback to the default skin folder.
+     *
+     * @return string Skin path relative to plugins directory
+     */
+    public function local_skin_path()
+    {
+        $rcube = rcube::get_instance();
+        foreach (array($rcube->config->get('skin'), 'larry') as $skin) {
+            $skin_path = 'skins/' . $skin;
+            if (is_dir(realpath(slashify($this->home) . $skin_path))) {
+                break;
+            }
+        }
 
-  /**
-   * Callback function for array_map
-   *
-   * @param string $key Array key.
-   * @return string
-   */
-  private function label_map_callback($key)
-  {
-    return $this->ID.'.'.$key;
-  }
+        return $skin_path;
+    }
 
+    /**
+     * Callback function for array_map
+     *
+     * @param string $key Array key.
+     * @return string
+     */
+    private function label_map_callback($key)
+    {
+        return $this->ID.'.'.$key;
+    }
 }
diff --git a/lib/ext/Roundcube/rcube_plugin_api.php b/lib/ext/Roundcube/rcube_plugin_api.php
index 51cf5d2..8a4cce2 100644
--- a/lib/ext/Roundcube/rcube_plugin_api.php
+++ b/lib/ext/Roundcube/rcube_plugin_api.php
@@ -2,10 +2,8 @@
 
 /*
  +-----------------------------------------------------------------------+
- | program/include/rcube_plugin_api.php                                  |
- |                                                                       |
  | This file is part of the Roundcube Webmail client                     |
- | Copyright (C) 2008-2011, The Roundcube Dev Team                       |
+ | Copyright (C) 2008-2012, The Roundcube Dev Team                       |
  |                                                                       |
  | Licensed under the GNU General Public License version 3 or            |
  | any later version with exceptions for skins & plugins.                |
@@ -13,16 +11,15 @@
  |                                                                       |
  | PURPOSE:                                                              |
  |   Plugins repository                                                  |
- |                                                                       |
  +-----------------------------------------------------------------------+
  | Author: Thomas Bruederli <roundcube at gmail.com>                        |
  +-----------------------------------------------------------------------+
 */
 
 // location where plugins are loade from
-if (!defined('RCUBE_PLUGINS_DIR'))
-  define('RCUBE_PLUGINS_DIR', RCUBE_INSTALL_PATH . 'plugins/');
-
+if (!defined('RCUBE_PLUGINS_DIR')) {
+    define('RCUBE_PLUGINS_DIR', RCUBE_INSTALL_PATH . 'plugins/');
+}
 
 /**
  * The plugin loader and global API
@@ -32,468 +29,476 @@ if (!defined('RCUBE_PLUGINS_DIR'))
  */
 class rcube_plugin_api
 {
-  static protected $instance;
-
-  public $dir;
-  public $url = 'plugins/';
-  public $task = '';
-  public $output;
-
-  public $handlers = array();
-  protected $plugins = array();
-  protected $tasks = array();
-  protected $actions = array();
-  protected $actionmap = array();
-  protected $objectsmap = array();
-  protected $template_contents = array();
-  protected $active_hook = false;
-
-  // Deprecated names of hooks, will be removed after 0.5-stable release
-  protected $deprecated_hooks = array(
-    'create_user'       => 'user_create',
-    'kill_session'      => 'session_destroy',
-    'upload_attachment' => 'attachment_upload',
-    'save_attachment'   => 'attachment_save',
-    'get_attachment'    => 'attachment_get',
-    'cleanup_attachments' => 'attachments_cleanup',
-    'display_attachment' => 'attachment_display',
-    'remove_attachment' => 'attachment_delete',
-    'outgoing_message_headers' => 'message_outgoing_headers',
-    'outgoing_message_body' => 'message_outgoing_body',
-    'address_sources'   => 'addressbooks_list',
-    'get_address_book'  => 'addressbook_get',
-    'create_contact'    => 'contact_create',
-    'save_contact'      => 'contact_update',
-    'contact_save'      => 'contact_update',
-    'delete_contact'    => 'contact_delete',
-    'manage_folders'    => 'folders_list',
-    'list_mailboxes'    => 'mailboxes_list',
-    'save_preferences'  => 'preferences_save',
-    'user_preferences'  => 'preferences_list',
-    'list_prefs_sections' => 'preferences_sections_list',
-    'list_identities'   => 'identities_list',
-    'create_identity'   => 'identity_create',
-    'delete_identity'   => 'identity_delete',
-    'save_identity'     => 'identity_update',
-    'identity_save'     => 'identity_update',
-    // to be removed after 0.8
-    'imap_init'         => 'storage_init',
-    'mailboxes_list'    => 'storage_folders', 
-  );
-
-  /**
-   * This implements the 'singleton' design pattern
-   *
-   * @return rcube_plugin_api The one and only instance if this class
-   */
-  static function get_instance()
-  {
-    if (!self::$instance) {
-      self::$instance = new rcube_plugin_api();
-    }
+    static protected $instance;
+
+    public $dir;
+    public $url = 'plugins/';
+    public $task = '';
+    public $output;
+    public $handlers = array();
+
+    protected $plugins = array();
+    protected $tasks = array();
+    protected $actions = array();
+    protected $actionmap = array();
+    protected $objectsmap = array();
+    protected $template_contents = array();
+    protected $active_hook = false;
+
+    // Deprecated names of hooks, will be removed after 0.5-stable release
+    protected $deprecated_hooks = array(
+        'create_user'       => 'user_create',
+        'kill_session'      => 'session_destroy',
+        'upload_attachment' => 'attachment_upload',
+        'save_attachment'   => 'attachment_save',
+        'get_attachment'    => 'attachment_get',
+        'cleanup_attachments' => 'attachments_cleanup',
+        'display_attachment' => 'attachment_display',
+        'remove_attachment' => 'attachment_delete',
+        'outgoing_message_headers' => 'message_outgoing_headers',
+        'outgoing_message_body' => 'message_outgoing_body',
+        'address_sources'   => 'addressbooks_list',
+        'get_address_book'  => 'addressbook_get',
+        'create_contact'    => 'contact_create',
+        'save_contact'      => 'contact_update',
+        'contact_save'      => 'contact_update',
+        'delete_contact'    => 'contact_delete',
+        'manage_folders'    => 'folders_list',
+        'list_mailboxes'    => 'mailboxes_list',
+        'save_preferences'  => 'preferences_save',
+        'user_preferences'  => 'preferences_list',
+        'list_prefs_sections' => 'preferences_sections_list',
+        'list_identities'   => 'identities_list',
+        'create_identity'   => 'identity_create',
+        'delete_identity'   => 'identity_delete',
+        'save_identity'     => 'identity_update',
+        'identity_save'     => 'identity_update',
+        // to be removed after 0.8
+        'imap_init'         => 'storage_init',
+        'mailboxes_list'    => 'storage_folders',
+        'imap_connect'      => 'storage_connect',
+    );
+
+    /**
+     * This implements the 'singleton' design pattern
+     *
+     * @return rcube_plugin_api The one and only instance if this class
+     */
+    static function get_instance()
+    {
+        if (!self::$instance) {
+            self::$instance = new rcube_plugin_api();
+        }
 
-    return self::$instance;
-  }
-
-
-  /**
-   * Private constructor
-   */
-  protected function __construct()
-  {
-    $this->dir = slashify(RCUBE_PLUGINS_DIR);
-  }
-
-
-  /**
-   * Initialize plugin engine
-   *
-   * This has to be done after rcmail::load_gui() or rcmail::json_init()
-   * was called because plugins need to have access to rcmail->output
-   *
-   * @param object rcube Instance of the rcube base class
-   * @param string Current application task (used for conditional plugin loading)
-   */
-  public function init($app, $task = '')
-  {
-    $this->task = $task;
-    $this->output = $app->output;
-
-    // register an internal hook
-    $this->register_hook('template_container', array($this, 'template_container_hook'));
-
-    // maybe also register a shudown function which triggers shutdown functions of all plugin objects
-  }
-
-
-  /**
-   * Load and init all enabled plugins
-   *
-   * This has to be done after rcmail::load_gui() or rcmail::json_init()
-   * was called because plugins need to have access to rcmail->output
-   *
-   * @param array List of configured plugins to load
-   * @param array List of plugins required by the application
-   */
-  public function load_plugins($plugins_enabled, $required_plugins = array())
-  {
-    foreach ($plugins_enabled as $plugin_name) {
-      $this->load_plugin($plugin_name);
+        return self::$instance;
     }
 
-    // check existance of all required core plugins
-    foreach ($required_plugins as $plugin_name) {
-      $loaded = false;
-      foreach ($this->plugins as $plugin) {
-        if ($plugin instanceof $plugin_name) {
-          $loaded = true;
-          break;
-        }
-      }
-
-      // load required core plugin if no derivate was found
-      if (!$loaded)
-        $loaded = $this->load_plugin($plugin_name);
-
-      // trigger fatal error if still not loaded
-      if (!$loaded) {
-        rcube::raise_error(array('code' => 520, 'type' => 'php',
-          'file' => __FILE__, 'line' => __LINE__,
-          'message' => "Requried plugin $plugin_name was not loaded"), true, true);
-      }
+    /**
+     * Private constructor
+     */
+    protected function __construct()
+    {
+        $this->dir = slashify(RCUBE_PLUGINS_DIR);
     }
-  }
-
-  /**
-   * Load the specified plugin
-   *
-   * @param string Plugin name
-   * @return boolean True on success, false if not loaded or failure
-   */
-  public function load_plugin($plugin_name)
-  {
-    static $plugins_dir;
-
-    if (!$plugins_dir) {
-      $dir = dir($this->dir);
-      $plugins_dir = unslashify($dir->path);
+
+    /**
+     * Initialize plugin engine
+     *
+     * This has to be done after rcmail::load_gui() or rcmail::json_init()
+     * was called because plugins need to have access to rcmail->output
+     *
+     * @param object rcube Instance of the rcube base class
+     * @param string Current application task (used for conditional plugin loading)
+     */
+    public function init($app, $task = '')
+    {
+        $this->task   = $task;
+        $this->output = $app->output;
+
+        // register an internal hook
+        $this->register_hook('template_container', array($this, 'template_container_hook'));
+
+        // maybe also register a shudown function which triggers
+        // shutdown functions of all plugin objects
     }
 
-    // plugin already loaded
-    if ($this->plugins[$plugin_name] || class_exists($plugin_name, false))
-      return true;
-
-    $fn = $plugins_dir . DIRECTORY_SEPARATOR . $plugin_name . DIRECTORY_SEPARATOR . $plugin_name . '.php';
-
-    if (file_exists($fn)) {
-      include($fn);
-
-      // instantiate class if exists
-      if (class_exists($plugin_name, false)) {
-        $plugin = new $plugin_name($this);
-        // check inheritance...
-        if (is_subclass_of($plugin, 'rcube_plugin')) {
-          // ... task, request type and framed mode
-          if ((!$plugin->task || preg_match('/^('.$plugin->task.')$/i', $this->task))
-              && (!$plugin->noajax || (is_object($this->output) && $this->output->type == 'html'))
-              && (!$plugin->noframe || empty($_REQUEST['_framed']))
-          ) {
-            $plugin->init();
-            $this->plugins[$plugin_name] = $plugin;
-          }
-          return true;
+    /**
+     * Load and init all enabled plugins
+     *
+     * This has to be done after rcmail::load_gui() or rcmail::json_init()
+     * was called because plugins need to have access to rcmail->output
+     *
+     * @param array List of configured plugins to load
+     * @param array List of plugins required by the application
+     */
+    public function load_plugins($plugins_enabled, $required_plugins = array())
+    {
+        foreach ($plugins_enabled as $plugin_name) {
+            $this->load_plugin($plugin_name);
+        }
+
+        // check existance of all required core plugins
+        foreach ($required_plugins as $plugin_name) {
+            $loaded = false;
+            foreach ($this->plugins as $plugin) {
+                if ($plugin instanceof $plugin_name) {
+                    $loaded = true;
+                    break;
+                }
+            }
+
+            // load required core plugin if no derivate was found
+            if (!$loaded) {
+                $loaded = $this->load_plugin($plugin_name);
+            }
+
+            // trigger fatal error if still not loaded
+            if (!$loaded) {
+                rcube::raise_error(array(
+                    'code' => 520, 'type' => 'php',
+                    'file' => __FILE__, 'line' => __LINE__,
+                    'message' => "Requried plugin $plugin_name was not loaded"), true, true);
+            }
         }
-      }
-      else {
-        rcube::raise_error(array('code' => 520, 'type' => 'php',
-          'file' => __FILE__, 'line' => __LINE__,
-          'message' => "No plugin class $plugin_name found in $fn"), true, false);
-      }
     }
-    else {
-      rcube::raise_error(array('code' => 520, 'type' => 'php',
-        'file' => __FILE__, 'line' => __LINE__,
-        'message' => "Failed to load plugin file $fn"), true, false);
+
+    /**
+     * Load the specified plugin
+     *
+     * @param string Plugin name
+     *
+     * @return boolean True on success, false if not loaded or failure
+     */
+    public function load_plugin($plugin_name)
+    {
+        static $plugins_dir;
+
+        if (!$plugins_dir) {
+            $dir         = dir($this->dir);
+            $plugins_dir = unslashify($dir->path);
+        }
+
+        // plugin already loaded
+        if ($this->plugins[$plugin_name] || class_exists($plugin_name, false)) {
+            return true;
+        }
+
+        $fn = $plugins_dir . DIRECTORY_SEPARATOR . $plugin_name
+            . DIRECTORY_SEPARATOR . $plugin_name . '.php';
+
+        if (file_exists($fn)) {
+            include $fn;
+
+            // instantiate class if exists
+            if (class_exists($plugin_name, false)) {
+                $plugin = new $plugin_name($this);
+                // check inheritance...
+                if (is_subclass_of($plugin, 'rcube_plugin')) {
+                    // ... task, request type and framed mode
+                    if ((!$plugin->task || preg_match('/^('.$plugin->task.')$/i', $this->task))
+                        && (!$plugin->noajax || (is_object($this->output) && $this->output->type == 'html'))
+                        && (!$plugin->noframe || empty($_REQUEST['_framed']))
+                    ) {
+                        $plugin->init();
+                        $this->plugins[$plugin_name] = $plugin;
+                    }
+                    return true;
+                }
+            }
+            else {
+                rcube::raise_error(array('code' => 520, 'type' => 'php',
+                    'file' => __FILE__, 'line' => __LINE__,
+                    'message' => "No plugin class $plugin_name found in $fn"),
+                    true, false);
+            }
+        }
+        else {
+            rcube::raise_error(array('code' => 520, 'type' => 'php',
+                'file' => __FILE__, 'line' => __LINE__,
+                'message' => "Failed to load plugin file $fn"), true, false);
+        }
+
+        return false;
     }
 
-    return false;
-  }
-
-
-  /**
-   * Allows a plugin object to register a callback for a certain hook
-   *
-   * @param string $hook Hook name
-   * @param mixed  $callback String with global function name or array($obj, 'methodname')
-   */
-  public function register_hook($hook, $callback)
-  {
-    if (is_callable($callback)) {
-      if (isset($this->deprecated_hooks[$hook])) {
-        rcube::raise_error(array('code' => 522, 'type' => 'php',
-          'file' => __FILE__, 'line' => __LINE__,
-          'message' => "Deprecated hook name. ".$hook.' -> '.$this->deprecated_hooks[$hook]), true, false);
-        $hook = $this->deprecated_hooks[$hook];
-      }
-      $this->handlers[$hook][] = $callback;
+    /**
+     * Allows a plugin object to register a callback for a certain hook
+     *
+     * @param string $hook Hook name
+     * @param mixed  $callback String with global function name or array($obj, 'methodname')
+     */
+    public function register_hook($hook, $callback)
+    {
+        if (is_callable($callback)) {
+            if (isset($this->deprecated_hooks[$hook])) {
+                rcube::raise_error(array('code' => 522, 'type' => 'php',
+                    'file' => __FILE__, 'line' => __LINE__,
+                    'message' => "Deprecated hook name. "
+                        . $hook . ' -> ' . $this->deprecated_hooks[$hook]), true, false);
+                $hook = $this->deprecated_hooks[$hook];
+            }
+            $this->handlers[$hook][] = $callback;
+        }
+        else {
+            rcube::raise_error(array('code' => 521, 'type' => 'php',
+                'file' => __FILE__, 'line' => __LINE__,
+                'message' => "Invalid callback function for $hook"), true, false);
+        }
     }
-    else
-      rcube::raise_error(array('code' => 521, 'type' => 'php',
-        'file' => __FILE__, 'line' => __LINE__,
-        'message' => "Invalid callback function for $hook"), true, false);
-  }
-
-  /**
-   * Allow a plugin object to unregister a callback.
-   *
-   * @param string $hook Hook name
-   * @param mixed  $callback String with global function name or array($obj, 'methodname')
-   */
-  public function unregister_hook($hook, $callback)
-  {
-    $callback_id = array_search($callback, $this->handlers[$hook]);
-    if ($callback_id !== false) {
-      unset($this->handlers[$hook][$callback_id]);
+
+    /**
+     * Allow a plugin object to unregister a callback.
+     *
+     * @param string $hook Hook name
+     * @param mixed  $callback String with global function name or array($obj, 'methodname')
+     */
+    public function unregister_hook($hook, $callback)
+    {
+        $callback_id = array_search($callback, $this->handlers[$hook]);
+        if ($callback_id !== false) {
+            unset($this->handlers[$hook][$callback_id]);
+        }
     }
-  }
-
-
-  /**
-   * Triggers a plugin hook.
-   * This is called from the application and executes all registered handlers
-   *
-   * @param string $hook Hook name
-   * @param array $args Named arguments (key->value pairs)
-   * @return array The (probably) altered hook arguments
-   */
-  public function exec_hook($hook, $args = array())
-  {
-    if (!is_array($args))
-      $args = array('arg' => $args);
-
-    $args += array('abort' => false);
-    $this->active_hook = $hook;
-
-    foreach ((array)$this->handlers[$hook] as $callback) {
-      $ret = call_user_func($callback, $args);
-      if ($ret && is_array($ret))
-        $args = $ret + $args;
-
-      if ($args['abort'])
-        break;
+
+    /**
+     * Triggers a plugin hook.
+     * This is called from the application and executes all registered handlers
+     *
+     * @param string $hook Hook name
+     * @param array $args Named arguments (key->value pairs)
+     *
+     * @return array The (probably) altered hook arguments
+     */
+    public function exec_hook($hook, $args = array())
+    {
+        if (!is_array($args)) {
+            $args = array('arg' => $args);
+        }
+
+        $args += array('abort' => false);
+        $this->active_hook = $hook;
+
+        foreach ((array)$this->handlers[$hook] as $callback) {
+            $ret = call_user_func($callback, $args);
+            if ($ret && is_array($ret)) {
+                $args = $ret + $args;
+            }
+
+            if ($args['abort']) {
+                break;
+            }
+        }
+
+        $this->active_hook = false;
+        return $args;
     }
 
-    $this->active_hook = false;
-    return $args;
-  }
-
-
-  /**
-   * Let a plugin register a handler for a specific request
-   *
-   * @param string $action Action name (_task=mail&_action=plugin.foo)
-   * @param string $owner Plugin name that registers this action
-   * @param mixed  $callback Callback: string with global function name or array($obj, 'methodname')
-   * @param string $task Task name registered by this plugin
-   */
-  public function register_action($action, $owner, $callback, $task = null)
-  {
-    // check action name
-    if ($task)
-      $action = $task.'.'.$action;
-    else if (strpos($action, 'plugin.') !== 0)
-      $action = 'plugin.'.$action;
-
-    // can register action only if it's not taken or registered by myself
-    if (!isset($this->actionmap[$action]) || $this->actionmap[$action] == $owner) {
-      $this->actions[$action] = $callback;
-      $this->actionmap[$action] = $owner;
+    /**
+     * Let a plugin register a handler for a specific request
+     *
+     * @param string $action   Action name (_task=mail&_action=plugin.foo)
+     * @param string $owner    Plugin name that registers this action
+     * @param mixed  $callback Callback: string with global function name or array($obj, 'methodname')
+     * @param string $task     Task name registered by this plugin
+     */
+    public function register_action($action, $owner, $callback, $task = null)
+    {
+        // check action name
+        if ($task)
+            $action = $task.'.'.$action;
+        else if (strpos($action, 'plugin.') !== 0)
+            $action = 'plugin.'.$action;
+
+        // can register action only if it's not taken or registered by myself
+        if (!isset($this->actionmap[$action]) || $this->actionmap[$action] == $owner) {
+            $this->actions[$action] = $callback;
+            $this->actionmap[$action] = $owner;
+        }
+        else {
+            rcube::raise_error(array('code' => 523, 'type' => 'php',
+                'file' => __FILE__, 'line' => __LINE__,
+                'message' => "Cannot register action $action;"
+                    ." already taken by another plugin"), true, false);
+        }
     }
-    else {
-      rcube::raise_error(array('code' => 523, 'type' => 'php',
-        'file' => __FILE__, 'line' => __LINE__,
-        'message' => "Cannot register action $action; already taken by another plugin"), true, false);
+
+    /**
+     * This method handles requests like _task=mail&_action=plugin.foo
+     * It executes the callback function that was registered with the given action.
+     *
+     * @param string $action Action name
+     */
+    public function exec_action($action)
+    {
+        if (isset($this->actions[$action])) {
+            call_user_func($this->actions[$action]);
+        }
+        else if (rcube::get_instance()->action != 'refresh') {
+            rcube::raise_error(array('code' => 524, 'type' => 'php',
+                'file' => __FILE__, 'line' => __LINE__,
+                'message' => "No handler found for action $action"), true, true);
+        }
     }
-  }
-
-
-  /**
-   * This method handles requests like _task=mail&_action=plugin.foo
-   * It executes the callback function that was registered with the given action.
-   *
-   * @param string $action Action name
-   */
-  public function exec_action($action)
-  {
-    if (isset($this->actions[$action])) {
-      call_user_func($this->actions[$action]);
+
+    /**
+     * Register a handler function for template objects
+     *
+     * @param string $name Object name
+     * @param string $owner Plugin name that registers this action
+     * @param mixed  $callback Callback: string with global function name or array($obj, 'methodname')
+     */
+    public function register_handler($name, $owner, $callback)
+    {
+        // check name
+        if (strpos($name, 'plugin.') !== 0) {
+            $name = 'plugin.' . $name;
+        }
+
+        // can register handler only if it's not taken or registered by myself
+        if (is_object($this->output)
+            && (!isset($this->objectsmap[$name]) || $this->objectsmap[$name] == $owner)
+        ) {
+            $this->output->add_handler($name, $callback);
+            $this->objectsmap[$name] = $owner;
+        }
+        else {
+            rcube::raise_error(array('code' => 525, 'type' => 'php',
+                'file' => __FILE__, 'line' => __LINE__,
+                'message' => "Cannot register template handler $name;"
+                    ." already taken by another plugin or no output object available"), true, false);
+        }
     }
-    else if (rcube::get_instance()->action != 'refresh') {
-      rcube::raise_error(array('code' => 524, 'type' => 'php',
-        'file' => __FILE__, 'line' => __LINE__,
-        'message' => "No handler found for action $action"), true, true);
+
+    /**
+     * Register this plugin to be responsible for a specific task
+     *
+     * @param string $task Task name (only characters [a-z0-9_.-] are allowed)
+     * @param string $owner Plugin name that registers this action
+     */
+    public function register_task($task, $owner)
+    {
+        // tasks are irrelevant in framework mode
+        if (!class_exists('rcmail', false)) {
+            return true;
+        }
+
+        if ($task != asciiwords($task)) {
+            rcube::raise_error(array('code' => 526, 'type' => 'php',
+                'file' => __FILE__, 'line' => __LINE__,
+                'message' => "Invalid task name: $task."
+                    ." Only characters [a-z0-9_.-] are allowed"), true, false);
+        }
+        else if (in_array($task, rcmail::$main_tasks)) {
+            rcube::raise_error(array('code' => 526, 'type' => 'php',
+                'file' => __FILE__, 'line' => __LINE__,
+                'message' => "Cannot register taks $task;"
+                    ." already taken by another plugin or the application itself"), true, false);
+        }
+        else {
+            $this->tasks[$task] = $owner;
+            rcmail::$main_tasks[] = $task;
+            return true;
+        }
+
+        return false;
     }
-  }
-
-
-  /**
-   * Register a handler function for template objects
-   *
-   * @param string $name Object name
-   * @param string $owner Plugin name that registers this action
-   * @param mixed  $callback Callback: string with global function name or array($obj, 'methodname')
-   */
-  public function register_handler($name, $owner, $callback)
-  {
-    // check name
-    if (strpos($name, 'plugin.') !== 0)
-      $name = 'plugin.'.$name;
-
-    // can register handler only if it's not taken or registered by myself
-    if (is_object($this->output) && (!isset($this->objectsmap[$name]) || $this->objectsmap[$name] == $owner)) {
-      $this->output->add_handler($name, $callback);
-      $this->objectsmap[$name] = $owner;
+
+    /**
+     * Checks whether the given task is registered by a plugin
+     *
+     * @param string $task Task name
+     *
+     * @return boolean True if registered, otherwise false
+     */
+    public function is_plugin_task($task)
+    {
+        return $this->tasks[$task] ? true : false;
     }
-    else {
-      rcube::raise_error(array('code' => 525, 'type' => 'php',
-        'file' => __FILE__, 'line' => __LINE__,
-        'message' => "Cannot register template handler $name; already taken by another plugin or no output object available"), true, false);
+
+    /**
+     * Check if a plugin hook is currently processing.
+     * Mainly used to prevent loops and recursion.
+     *
+     * @param string $hook Hook to check (optional)
+     *
+     * @return boolean True if any/the given hook is currently processed, otherwise false
+     */
+    public function is_processing($hook = null)
+    {
+        return $this->active_hook && (!$hook || $this->active_hook == $hook);
     }
-  }
-
-
-  /**
-   * Register this plugin to be responsible for a specific task
-   *
-   * @param string $task Task name (only characters [a-z0-9_.-] are allowed)
-   * @param string $owner Plugin name that registers this action
-   */
-  public function register_task($task, $owner)
-  {
-    // tasks are irrelevant in framework mode
-    if (!class_exists('rcmail', false))
-      return true;
-
-    if ($task != asciiwords($task)) {
-      rcube::raise_error(array('code' => 526, 'type' => 'php',
-        'file' => __FILE__, 'line' => __LINE__,
-        'message' => "Invalid task name: $task. Only characters [a-z0-9_.-] are allowed"), true, false);
+
+    /**
+     * Include a plugin script file in the current HTML page
+     *
+     * @param string $fn Path to script
+     */
+    public function include_script($fn)
+    {
+        if (is_object($this->output) && $this->output->type == 'html') {
+            $src = $this->resource_url($fn);
+            $this->output->add_header(html::tag('script',
+                array('type' => "text/javascript", 'src' => $src)));
+        }
     }
-    else if (in_array($task, rcmail::$main_tasks)) {
-      rcube::raise_error(array('code' => 526, 'type' => 'php',
-        'file' => __FILE__, 'line' => __LINE__,
-        'message' => "Cannot register taks $task; already taken by another plugin or the application itself"), true, false);
+
+    /**
+     * Include a plugin stylesheet in the current HTML page
+     *
+     * @param string $fn Path to stylesheet
+     */
+    public function include_stylesheet($fn)
+    {
+        if (is_object($this->output) && $this->output->type == 'html') {
+            $src = $this->resource_url($fn);
+            $this->output->include_css($src);
+        }
     }
-    else {
-      $this->tasks[$task] = $owner;
-      rcmail::$main_tasks[] = $task;
-      return true;
+
+    /**
+     * Save the given HTML content to be added to a template container
+     *
+     * @param string $html HTML content
+     * @param string $container Template container identifier
+     */
+    public function add_content($html, $container)
+    {
+        $this->template_contents[$container] .= $html . "\n";
     }
 
-    return false;
-  }
-
-
-  /**
-   * Checks whether the given task is registered by a plugin
-   *
-   * @param string $task Task name
-   * @return boolean True if registered, otherwise false
-   */
-  public function is_plugin_task($task)
-  {
-    return $this->tasks[$task] ? true : false;
-  }
-
-
-  /**
-   * Check if a plugin hook is currently processing.
-   * Mainly used to prevent loops and recursion.
-   *
-   * @param string $hook Hook to check (optional)
-   * @return boolean True if any/the given hook is currently processed, otherwise false
-   */
-  public function is_processing($hook = null)
-  {
-    return $this->active_hook && (!$hook || $this->active_hook == $hook);
-  }
-
-  /**
-   * Include a plugin script file in the current HTML page
-   *
-   * @param string $fn Path to script
-   */
-  public function include_script($fn)
-  {
-    if (is_object($this->output) && $this->output->type == 'html') {
-      $src = $this->resource_url($fn);
-      $this->output->add_header(html::tag('script', array('type' => "text/javascript", 'src' => $src)));
+    /**
+     * Returns list of loaded plugins names
+     *
+     * @return array List of plugin names
+     */
+    public function loaded_plugins()
+    {
+        return array_keys($this->plugins);
     }
-  }
-
-
-  /**
-   * Include a plugin stylesheet in the current HTML page
-   *
-   * @param string $fn Path to stylesheet
-   */
-  public function include_stylesheet($fn)
-  {
-    if (is_object($this->output) && $this->output->type == 'html') {
-      $src = $this->resource_url($fn);
-      $this->output->include_css($src);
+
+    /**
+     * Callback for template_container hooks
+     *
+     * @param array $attrib
+     * @return array
+     */
+    protected function template_container_hook($attrib)
+    {
+        $container = $attrib['name'];
+        return array('content' => $attrib['content'] . $this->template_contents[$container]);
     }
-  }
-
-
-  /**
-   * Save the given HTML content to be added to a template container
-   *
-   * @param string $html HTML content
-   * @param string $container Template container identifier
-   */
-  public function add_content($html, $container)
-  {
-    $this->template_contents[$container] .= $html . "\n";
-  }
-
-
-  /**
-   * Returns list of loaded plugins names
-   *
-   * @return array List of plugin names
-   */
-  public function loaded_plugins()
-  {
-    return array_keys($this->plugins);
-  }
-
-
-  /**
-   * Callback for template_container hooks
-   *
-   * @param array $attrib
-   * @return array
-   */
-  protected function template_container_hook($attrib)
-  {
-    $container = $attrib['name'];
-    return array('content' => $attrib['content'] . $this->template_contents[$container]);
-  }
-
-
-  /**
-   * Make the given file name link into the plugins directory
-   *
-   * @param string $fn Filename
-   * @return string 
-   */
-  protected function resource_url($fn)
-  {
-    if ($fn[0] != '/' && !preg_match('|^https?://|i', $fn))
-      return $this->url . $fn;
-    else
-      return $fn;
-  }
 
+    /**
+     * Make the given file name link into the plugins directory
+     *
+     * @param string $fn Filename
+     * @return string
+     */
+    protected function resource_url($fn)
+    {
+        if ($fn[0] != '/' && !preg_match('|^https?://|i', $fn))
+            return $this->url . $fn;
+        else
+            return $fn;
+    }
 }
diff --git a/lib/ext/Roundcube/rcube_result_index.php b/lib/ext/Roundcube/rcube_result_index.php
index 4d1ae13..5f592c5 100644
--- a/lib/ext/Roundcube/rcube_result_index.php
+++ b/lib/ext/Roundcube/rcube_result_index.php
@@ -2,8 +2,6 @@
 
 /*
  +-----------------------------------------------------------------------+
- | program/include/rcube_result_index.php                                |
- |                                                                       |
  | This file is part of the Roundcube Webmail client                     |
  | Copyright (C) 2005-2011, The Roundcube Dev Team                       |
  | Copyright (C) 2011, Kolab Systems AG                                  |
@@ -14,14 +12,12 @@
  |                                                                       |
  | PURPOSE:                                                              |
  |   SORT/SEARCH/ESEARCH response handler                                |
- |                                                                       |
  +-----------------------------------------------------------------------+
  | Author: Thomas Bruederli <roundcube at gmail.com>                        |
  | Author: Aleksander Machniak <alec at alec.pl>                            |
  +-----------------------------------------------------------------------+
 */
 
-
 /**
  * Class for accessing IMAP's SORT/SEARCH/ESEARCH result
  *
diff --git a/lib/ext/Roundcube/rcube_result_set.php b/lib/ext/Roundcube/rcube_result_set.php
index 456d1c9..1391e5e 100644
--- a/lib/ext/Roundcube/rcube_result_set.php
+++ b/lib/ext/Roundcube/rcube_result_set.php
@@ -2,8 +2,6 @@
 
 /*
  +-----------------------------------------------------------------------+
- | program/include/rcube_result_set.php                                  |
- |                                                                       |
  | This file is part of the Roundcube Webmail client                     |
  | Copyright (C) 2006-2011, The Roundcube Dev Team                       |
  |                                                                       |
@@ -13,13 +11,11 @@
  |                                                                       |
  | PURPOSE:                                                              |
  |   Class representing an address directory result set                  |
- |                                                                       |
  +-----------------------------------------------------------------------+
  | Author: Thomas Bruederli <roundcube at gmail.com>                        |
  +-----------------------------------------------------------------------+
 */
 
-
 /**
  * Roundcube result set class.
  * Representing an address directory result set.
diff --git a/lib/ext/Roundcube/rcube_result_thread.php b/lib/ext/Roundcube/rcube_result_thread.php
index c609bdc..7657550 100644
--- a/lib/ext/Roundcube/rcube_result_thread.php
+++ b/lib/ext/Roundcube/rcube_result_thread.php
@@ -2,8 +2,6 @@
 
 /*
  +-----------------------------------------------------------------------+
- | program/include/rcube_result_thread.php                               |
- |                                                                       |
  | This file is part of the Roundcube Webmail client                     |
  | Copyright (C) 2005-2011, The Roundcube Dev Team                       |
  | Copyright (C) 2011, Kolab Systems AG                                  |
@@ -14,14 +12,12 @@
  |                                                                       |
  | PURPOSE:                                                              |
  |   THREAD response handler                                             |
- |                                                                       |
  +-----------------------------------------------------------------------+
  | Author: Thomas Bruederli <roundcube at gmail.com>                        |
  | Author: Aleksander Machniak <alec at alec.pl>                            |
  +-----------------------------------------------------------------------+
 */
 
-
 /**
  * Class for accessing IMAP's THREAD result
  *
diff --git a/lib/ext/Roundcube/rcube_session.php b/lib/ext/Roundcube/rcube_session.php
index fdbf668..1aa5d58 100644
--- a/lib/ext/Roundcube/rcube_session.php
+++ b/lib/ext/Roundcube/rcube_session.php
@@ -2,8 +2,6 @@
 
 /*
  +-----------------------------------------------------------------------+
- | program/include/rcube_session.php                                     |
- |                                                                       |
  | This file is part of the Roundcube Webmail client                     |
  | Copyright (C) 2005-2012, The Roundcube Dev Team                       |
  | Copyright (C) 2011, Kolab Systems AG                                  |
@@ -14,7 +12,6 @@
  |                                                                       |
  | PURPOSE:                                                              |
  |   Provide database supported session management                       |
- |                                                                       |
  +-----------------------------------------------------------------------+
  | Author: Thomas Bruederli <roundcube at gmail.com>                        |
  | Author: Aleksander Machniak <alec at alec.pl>                            |
@@ -31,602 +28,629 @@
  */
 class rcube_session
 {
-  private $db;
-  private $ip;
-  private $start;
-  private $changed;
-  private $unsets = array();
-  private $gc_handlers = array();
-  private $cookiename = 'roundcube_sessauth';
-  private $vars;
-  private $key;
-  private $now;
-  private $secret = '';
-  private $ip_check = false;
-  private $logging = false;
-  private $memcache;
-
-  /**
-   * Default constructor
-   */
-  public function __construct($db, $config)
-  {
-    $this->db      = $db;
-    $this->start   = microtime(true);
-    $this->ip      = $_SERVER['REMOTE_ADDR'];
-    $this->logging = $config->get('log_session', false);
-
-    $lifetime = $config->get('session_lifetime', 1) * 60;
-    $this->set_lifetime($lifetime);
-
-    // use memcache backend
-    if ($config->get('session_storage', 'db') == 'memcache') {
-      $this->memcache = rcube::get_instance()->get_memcache();
-
-      // set custom functions for PHP session management if memcache is available
-      if ($this->memcache) {
-        session_set_save_handler(
-          array($this, 'open'),
-          array($this, 'close'),
-          array($this, 'mc_read'),
-          array($this, 'mc_write'),
-          array($this, 'mc_destroy'),
-          array($this, 'gc'));
-      }
-      else {
-        rcube::raise_error(array('code' => 604, 'type' => 'db',
-          'line' => __LINE__, 'file' => __FILE__,
-          'message' => "Failed to connect to memcached. Please check configuration"),
-          true, true);
-      }
+    private $db;
+    private $ip;
+    private $start;
+    private $changed;
+    private $unsets = array();
+    private $gc_handlers = array();
+    private $cookiename = 'roundcube_sessauth';
+    private $vars;
+    private $key;
+    private $now;
+    private $secret = '';
+    private $ip_check = false;
+    private $logging = false;
+    private $memcache;
+
+
+    /**
+     * Default constructor
+     */
+    public function __construct($db, $config)
+    {
+        $this->db      = $db;
+        $this->start   = microtime(true);
+        $this->ip      = $_SERVER['REMOTE_ADDR'];
+        $this->logging = $config->get('log_session', false);
+
+        $lifetime = $config->get('session_lifetime', 1) * 60;
+        $this->set_lifetime($lifetime);
+
+        // use memcache backend
+        if ($config->get('session_storage', 'db') == 'memcache') {
+            $this->memcache = rcube::get_instance()->get_memcache();
+
+            // set custom functions for PHP session management if memcache is available
+            if ($this->memcache) {
+                session_set_save_handler(
+                    array($this, 'open'),
+                    array($this, 'close'),
+                    array($this, 'mc_read'),
+                    array($this, 'mc_write'),
+                    array($this, 'mc_destroy'),
+                    array($this, 'gc'));
+            }
+            else {
+                rcube::raise_error(array('code' => 604, 'type' => 'db',
+                    'line' => __LINE__, 'file' => __FILE__,
+                    'message' => "Failed to connect to memcached. Please check configuration"),
+                true, true);
+            }
+        }
+        else {
+            // set custom functions for PHP session management
+            session_set_save_handler(
+                array($this, 'open'),
+                array($this, 'close'),
+                array($this, 'db_read'),
+                array($this, 'db_write'),
+                array($this, 'db_destroy'),
+                array($this, 'db_gc'));
+        }
     }
-    else {
-      // set custom functions for PHP session management
-      session_set_save_handler(
-        array($this, 'open'),
-        array($this, 'close'),
-        array($this, 'db_read'),
-        array($this, 'db_write'),
-        array($this, 'db_destroy'),
-        array($this, 'db_gc'));
-      }
-  }
-
-
-  public function open($save_path, $session_name)
-  {
-    return true;
-  }
-
-
-  public function close()
-  {
-    return true;
-  }
-
-
-  /**
-   * Delete session data for the given key
-   *
-   * @param string Session ID
-   */
-  public function destroy($key)
-  {
-    return $this->memcache ? $this->mc_destroy($key) : $this->db_destroy($key);
-  }
-
-
-  /**
-   * Read session data from database
-   *
-   * @param string Session ID
-   * @return string Session vars
-   */
-  public function db_read($key)
-  {
-    $sql_result = $this->db->query(
-      "SELECT vars, ip, changed 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;
-
-      return !empty($this->vars) ? (string) $this->vars : '';
+
+
+    public function open($save_path, $session_name)
+    {
+        return true;
     }
 
-    return null;
-  }
-
-
-  /**
-   * Save session data.
-   * handler for session_read()
-   *
-   * @param string Session ID
-   * @param string Serialized session vars
-   * @return boolean True on success
-   */
-  public function db_write($key, $vars)
-  {
-    $ts = microtime(true);
-    $now = $this->db->fromunixtime((int)$ts);
-
-    // no session row in DB (db_read() returns false)
-    if (!$this->key) {
-      $oldvars = null;
+
+    public function close()
+    {
+        return true;
     }
-    // use internal data from read() for fast requests (up to 0.5 sec.)
-    else if ($key == $this->key && (!$this->vars || $ts - $this->start < 0.5)) {
-      $oldvars = $this->vars;
+
+
+    /**
+     * Delete session data for the given key
+     *
+     * @param string Session ID
+     */
+    public function destroy($key)
+    {
+        return $this->memcache ? $this->mc_destroy($key) : $this->db_destroy($key);
+    }
+
+
+    /**
+     * Read session data from database
+     *
+     * @param string Session ID
+     *
+     * @return string Session vars
+     */
+    public function db_read($key)
+    {
+        $sql_result = $this->db->query(
+            "SELECT vars, ip, changed 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;
+
+            return !empty($this->vars) ? (string) $this->vars : '';
+        }
+
+        return null;
     }
-    else { // else read data again from DB
-      $oldvars = $this->db_read($key);
+
+
+    /**
+     * Save session data.
+     * handler for session_read()
+     *
+     * @param string Session ID
+     * @param string Serialized session vars
+     *
+     * @return boolean True on success
+     */
+    public function db_write($key, $vars)
+    {
+        $ts  = microtime(true);
+        $now = $this->db->fromunixtime((int)$ts);
+
+        // no session row in DB (db_read() returns false)
+        if (!$this->key) {
+            $oldvars = null;
+        }
+        // use internal data from read() for fast requests (up to 0.5 sec.)
+        else if ($key == $this->key && (!$this->vars || $ts - $this->start < 0.5)) {
+            $oldvars = $this->vars;
+        }
+        else { // else read data again from DB
+            $oldvars = $this->db_read($key);
+        }
+
+        if ($oldvars !== null) {
+            $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);
+            }
+            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 {
+            $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);
+        }
+
+        return true;
     }
 
-    if ($oldvars !== null) {
-      $newvars = $this->_fixvars($vars, $oldvars);
 
-      if ($newvars !== $oldvars) {
+    /**
+     * Merge vars with old vars and apply unsets
+     */
+    private function _fixvars($vars, $oldvars)
+    {
+        if ($oldvars !== null) {
+            $a_oldvars = $this->unserialize($oldvars);
+            if (is_array($a_oldvars)) {
+                foreach ((array)$this->unsets as $k)
+                    unset($a_oldvars[$k]);
+
+                $newvars = $this->serialize(array_merge(
+                    (array)$a_oldvars, (array)$this->unserialize($vars)));
+            }
+            else {
+                $newvars = $vars;
+            }
+        }
+
+        $this->unsets = array();
+        return $newvars;
+    }
+
+
+    /**
+     * Handler for session_destroy()
+     *
+     * @param string Session ID
+     *
+     * @return boolean True on success
+     */
+    public function db_destroy($key)
+    {
+        if ($key) {
+            $this->db->query(sprintf("DELETE FROM %s WHERE sess_id = ?",
+                $this->db->table_name('session')), $key);
+        }
+
+        return true;
+    }
+
+
+    /**
+     * 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("UPDATE %s SET vars=?, changed=%s WHERE sess_id=?",
-            $this->db->table_name('session'), $now),
-          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);
-      }
+            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
+     * @return string Session vars
+     */
+    public function mc_read($key)
+    {
+        if ($value = $this->memcache->get($key)) {
+            $arr = unserialize($value);
+            $this->changed = $arr['changed'];
+            $this->ip      = $arr['ip'];
+            $this->vars    = $arr['vars'];
+            $this->key     = $key;
+
+            return !empty($this->vars) ? (string) $this->vars : '';
+        }
+
+        return null;
+    }
+
+
+    /**
+     * Save session data.
+     * handler for session_read()
+     *
+     * @param string Session ID
+     * @param string Serialized session vars
+     *
+     * @return boolean True on success
+     */
+    public function mc_write($key, $vars)
+    {
+        $ts = microtime(true);
+
+        // no session data in cache (mc_read() returns false)
+        if (!$this->key)
+            $oldvars = null;
+        // use internal data for fast requests (up to 0.5 sec.)
+        else if ($key == $this->key && (!$this->vars || $ts - $this->start < 0.5))
+            $oldvars = $this->vars;
+        else // else read data again
+            $oldvars = $this->mc_read($key);
+
+        $newvars = $oldvars !== null ? $this->_fixvars($vars, $oldvars) : $vars;
+
+        if ($newvars !== $oldvars || $ts - $this->changed > $this->lifetime / 2) {
+            return $this->memcache->set($key, serialize(array('changed' => time(), 'ip' => $this->ip, 'vars' => $newvars)),
+                MEMCACHE_COMPRESSED, $this->lifetime);
+        }
+
+        return true;
+    }
+
+
+    /**
+     * Handler for session_destroy() with memcache backend
+     *
+     * @param string Session ID
+     *
+     * @return boolean True on success
+     */
+    public function mc_destroy($key)
+    {
+        if ($key) {
+            // #1488592: use 2nd argument
+            $this->memcache->delete($key, 0);
+        }
+
+        return true;
+    }
+
+
+    /**
+     * Execute registered garbage collector routines
+     */
+    public function gc()
+    {
+        foreach ($this->gc_handlers as $fct) {
+            call_user_func($fct);
+        }
     }
-    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);
+
+
+    /**
+     * Register additional garbage collector functions
+     *
+     * @param mixed Callback function
+     */
+    public function register_gc_handler($func)
+    {
+        foreach ($this->gc_handlers as $handler) {
+            if ($handler == $func) {
+                return;
+            }
+        }
+
+        $this->gc_handlers[] = $func;
     }
 
-    return true;
-  }
-
-
-  /**
-   * Merge vars with old vars and apply unsets
-   */
-  private function _fixvars($vars, $oldvars)
-  {
-    if ($oldvars !== null) {
-      $a_oldvars = $this->unserialize($oldvars);
-      if (is_array($a_oldvars)) {
-        foreach ((array)$this->unsets as $k)
-          unset($a_oldvars[$k]);
-
-        $newvars = $this->serialize(array_merge(
-          (array)$a_oldvars, (array)$this->unserialize($vars)));
-      }
-      else
-        $newvars = $vars;
+
+    /**
+     * Generate and set new session id
+     *
+     * @param boolean $destroy If enabled the current session will be destroyed
+     */
+    public function regenerate_id($destroy=true)
+    {
+        session_regenerate_id($destroy);
+
+        $this->vars = null;
+        $this->key  = session_id();
+
+        return true;
     }
 
-    $this->unsets = array();
-    return $newvars;
-  }
-
-
-  /**
-   * Handler for session_destroy()
-   *
-   * @param string Session ID
-   *
-   * @return boolean True on success
-   */
-  public function db_destroy($key)
-  {
-    if ($key) {
-      $this->db->query(sprintf("DELETE FROM %s WHERE sess_id = ?", $this->db->table_name('session')), $key);
+
+    /**
+     * Unset a session variable
+     *
+     * @param string Varibale name
+     * @return boolean True on success
+     */
+    public function remove($var=null)
+    {
+        if (empty($var)) {
+            return $this->destroy(session_id());
+        }
+
+        $this->unsets[] = $var;
+        unset($_SESSION[$var]);
+
+        return true;
     }
 
-    return true;
-  }
-
-
-  /**
-   * 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
-   * @return string Session vars
-   */
-  public function mc_read($key)
-  {
-    if ($value = $this->memcache->get($key)) {
-      $arr = unserialize($value);
-      $this->changed = $arr['changed'];
-      $this->ip      = $arr['ip'];
-      $this->vars    = $arr['vars'];
-      $this->key     = $key;
-
-      return !empty($this->vars) ? (string) $this->vars : '';
+
+    /**
+     * Kill this session
+     */
+    public function kill()
+    {
+        $this->vars = null;
+        $this->ip = $_SERVER['REMOTE_ADDR']; // update IP (might have changed)
+        $this->destroy(session_id());
+        rcube_utils::setcookie($this->cookiename, '-del-', time() - 60);
     }
 
-    return null;
-  }
-
-
-  /**
-   * Save session data.
-   * handler for session_read()
-   *
-   * @param string Session ID
-   * @param string Serialized session vars
-   * @return boolean True on success
-   */
-  public function mc_write($key, $vars)
-  {
-    $ts = microtime(true);
-
-    // no session data in cache (mc_read() returns false)
-    if (!$this->key)
-      $oldvars = null;
-    // use internal data for fast requests (up to 0.5 sec.)
-    else if ($key == $this->key && (!$this->vars || $ts - $this->start < 0.5))
-      $oldvars = $this->vars;
-    else // else read data again
-      $oldvars = $this->mc_read($key);
-
-    $newvars = $oldvars !== null ? $this->_fixvars($vars, $oldvars) : $vars;
-
-    if ($newvars !== $oldvars || $ts - $this->changed > $this->lifetime / 2)
-      return $this->memcache->set($key, serialize(array('changed' => time(), 'ip' => $this->ip, 'vars' => $newvars)), MEMCACHE_COMPRESSED, $this->lifetime);
-
-    return true;
-  }
-
-
-  /**
-   * Handler for session_destroy() with memcache backend
-   *
-   * @param string Session ID
-   *
-   * @return boolean True on success
-   */
-  public function mc_destroy($key)
-  {
-    if ($key) {
-      // #1488592: use 2nd argument
-      $this->memcache->delete($key, 0);
+
+    /**
+     * Re-read session data from storage backend
+     */
+    public function reload()
+    {
+        if ($this->key && $this->memcache)
+            $data = $this->mc_read($this->key);
+        else if ($this->key)
+            $data = $this->db_read($this->key);
+
+        if ($data)
+            session_decode($data);
     }
 
-    return true;
-  }
 
+    /**
+     * Serialize session data
+     */
+    private function serialize($vars)
+    {
+        $data = '';
+        if (is_array($vars)) {
+            foreach ($vars as $var=>$value)
+                $data .= $var.'|'.serialize($value);
+        }
+        else {
+            $data = 'b:0;';
+        }
 
-  /**
-   * Execute registered garbage collector routines
-   */
-  public function gc()
-  {
-    foreach ($this->gc_handlers as $fct) {
-      call_user_func($fct);
+        return $data;
     }
-  }
-
-
-  /**
-   * Register additional garbage collector functions
-   *
-   * @param mixed Callback function
-   */
-  public function register_gc_handler($func)
-  {
-    foreach ($this->gc_handlers as $handler) {
-      if ($handler == $func) {
-        return;
-      }
+
+
+    /**
+     * Unserialize session data
+     * http://www.php.net/manual/en/function.session-decode.php#56106
+     */
+    private function unserialize($str)
+    {
+        $str    = (string)$str;
+        $endptr = strlen($str);
+        $p      = 0;
+
+        $serialized = '';
+        $items      = 0;
+        $level      = 0;
+
+        while ($p < $endptr) {
+            $q = $p;
+            while ($str[$q] != '|')
+                if (++$q >= $endptr)
+                    break 2;
+
+            if ($str[$p] == '!') {
+                $p++;
+                $has_value = false;
+            }
+            else {
+                $has_value = true;
+            }
+
+            $name = substr($str, $p, $q - $p);
+            $q++;
+
+            $serialized .= 's:' . strlen($name) . ':"' . $name . '";';
+
+            if ($has_value) {
+                for (;;) {
+                    $p = $q;
+                    switch (strtolower($str[$q])) {
+                    case 'n': // null
+                    case 'b': // boolean
+                    case 'i': // integer
+                    case 'd': // decimal
+                        do $q++;
+                        while ( ($q < $endptr) && ($str[$q] != ';') );
+                        $q++;
+                        $serialized .= substr($str, $p, $q - $p);
+                        if ($level == 0)
+                            break 2;
+                        break;
+                    case 'r': // reference
+                        $q+= 2;
+                        for ($id = ''; ($q < $endptr) && ($str[$q] != ';'); $q++)
+                            $id .= $str[$q];
+                        $q++;
+                        // increment pointer because of outer array
+                        $serialized .= 'R:' . ($id + 1) . ';';
+                        if ($level == 0)
+                            break 2;
+                        break;
+                    case 's': // string
+                        $q+=2;
+                        for ($length=''; ($q < $endptr) && ($str[$q] != ':'); $q++)
+                            $length .= $str[$q];
+                        $q+=2;
+                        $q+= (int)$length + 2;
+                        $serialized .= substr($str, $p, $q - $p);
+                        if ($level == 0)
+                            break 2;
+                        break;
+                    case 'a': // array
+                    case 'o': // object
+                        do $q++;
+                        while ($q < $endptr && $str[$q] != '{');
+                        $q++;
+                        $level++;
+                        $serialized .= substr($str, $p, $q - $p);
+                        break;
+                    case '}': // end of array|object
+                        $q++;
+                        $serialized .= substr($str, $p, $q - $p);
+                        if (--$level == 0)
+                            break 2;
+                        break;
+                    default:
+                        return false;
+                    }
+                }
+            }
+            else {
+                $serialized .= 'N;';
+                $q += 2;
+            }
+            $items++;
+            $p = $q;
+        }
+
+        return unserialize( 'a:' . $items . ':{' . $serialized . '}' );
     }
 
-    $this->gc_handlers[] = $func;
-  }
-
-
-  /**
-   * Generate and set new session id
-   *
-   * @param boolean $destroy If enabled the current session will be destroyed
-   */
-  public function regenerate_id($destroy=true)
-  {
-    session_regenerate_id($destroy);
-
-    $this->vars = null;
-    $this->key  = session_id();
-
-    return true;
-  }
-
-
-  /**
-   * Unset a session variable
-   *
-   * @param string Varibale name
-   * @return boolean True on success
-   */
-  public function remove($var=null)
-  {
-    if (empty($var))
-      return $this->destroy(session_id());
-
-    $this->unsets[] = $var;
-    unset($_SESSION[$var]);
-
-    return true;
-  }
-
-
-  /**
-   * Kill this session
-   */
-  public function kill()
-  {
-    $this->vars = null;
-    $this->ip = $_SERVER['REMOTE_ADDR']; // update IP (might have changed)
-    $this->destroy(session_id());
-    rcube_utils::setcookie($this->cookiename, '-del-', time() - 60);
-  }
-
-
-  /**
-   * Re-read session data from storage backend
-   */
-  public function reload()
-  {
-    if ($this->key && $this->memcache)
-      $data = $this->mc_read($this->key);
-    else if ($this->key)
-      $data = $this->db_read($this->key);
-
-    if ($data)
-     session_decode($data);
-  }
-
-
-  /**
-   * Serialize session data
-   */
-  private function serialize($vars)
-  {
-    $data = '';
-    if (is_array($vars))
-      foreach ($vars as $var=>$value)
-        $data .= $var.'|'.serialize($value);
-    else
-      $data = 'b:0;';
-    return $data;
-  }
-
-
-  /**
-   * Unserialize session data
-   * http://www.php.net/manual/en/function.session-decode.php#56106
-   */
-  private function unserialize($str)
-  {
-    $str = (string)$str;
-    $endptr = strlen($str);
-    $p = 0;
-
-    $serialized = '';
-    $items = 0;
-    $level = 0;
-
-    while ($p < $endptr) {
-      $q = $p;
-      while ($str[$q] != '|')
-        if (++$q >= $endptr) break 2;
-
-      if ($str[$p] == '!') {
-        $p++;
-        $has_value = false;
-      } else {
-        $has_value = true;
-      }
-
-      $name = substr($str, $p, $q - $p);
-      $q++;
-
-      $serialized .= 's:' . strlen($name) . ':"' . $name . '";';
-
-      if ($has_value) {
-        for (;;) {
-          $p = $q;
-          switch (strtolower($str[$q])) {
-            case 'n': /* null */
-            case 'b': /* boolean */
-            case 'i': /* integer */
-            case 'd': /* decimal */
-              do $q++;
-              while ( ($q < $endptr) && ($str[$q] != ';') );
-              $q++;
-              $serialized .= substr($str, $p, $q - $p);
-              if ($level == 0) break 2;
-              break;
-            case 'r': /* reference  */
-              $q+= 2;
-              for ($id = ''; ($q < $endptr) && ($str[$q] != ';'); $q++) $id .= $str[$q];
-              $q++;
-              $serialized .= 'R:' . ($id + 1) . ';'; /* increment pointer because of outer array */
-              if ($level == 0) break 2;
-              break;
-            case 's': /* string */
-              $q+=2;
-              for ($length=''; ($q < $endptr) && ($str[$q] != ':'); $q++) $length .= $str[$q];
-              $q+=2;
-              $q+= (int)$length + 2;
-              $serialized .= substr($str, $p, $q - $p);
-              if ($level == 0) break 2;
-              break;
-            case 'a': /* array */
-            case 'o': /* object */
-              do $q++;
-              while ( ($q < $endptr) && ($str[$q] != '{') );
-              $q++;
-              $level++;
-              $serialized .= substr($str, $p, $q - $p);
-              break;
-            case '}': /* end of array|object */
-              $q++;
-              $serialized .= substr($str, $p, $q - $p);
-              if (--$level == 0) break 2;
-              break;
-            default:
-              return false;
-          }
+
+    /**
+     * Setter for session lifetime
+     */
+    public function set_lifetime($lifetime)
+    {
+        $this->lifetime = max(120, $lifetime);
+
+        // valid time range is now - 1/2 lifetime to now + 1/2 lifetime
+        $now = time();
+        $this->now = $now - ($now % ($this->lifetime / 2));
+    }
+
+
+    /**
+     * Getter for remote IP saved with this session
+     */
+    public function get_ip()
+    {
+        return $this->ip;
+    }
+
+
+    /**
+     * Setter for cookie encryption secret
+     */
+    function set_secret($secret)
+    {
+        $this->secret = $secret;
+    }
+
+
+    /**
+     * Enable/disable IP check
+     */
+    function set_ip_check($check)
+    {
+        $this->ip_check = $check;
+    }
+
+
+    /**
+     * Setter for the cookie name used for session cookie
+     */
+    function set_cookiename($cookiename)
+    {
+        if ($cookiename) {
+            $this->cookiename = $cookiename;
         }
-      } else {
-        $serialized .= 'N;';
-        $q += 2;
-      }
-      $items++;
-      $p = $q;
     }
 
-    return unserialize( 'a:' . $items . ':{' . $serialized . '}' );
-  }
-
-
-  /**
-   * Setter for session lifetime
-   */
-  public function set_lifetime($lifetime)
-  {
-      $this->lifetime = max(120, $lifetime);
-
-      // valid time range is now - 1/2 lifetime to now + 1/2 lifetime
-      $now = time();
-      $this->now = $now - ($now % ($this->lifetime / 2));
-  }
-
-
-  /**
-   * Getter for remote IP saved with this session
-   */
-  public function get_ip()
-  {
-    return $this->ip;
-  }
-
-
-  /**
-   * Setter for cookie encryption secret
-   */
-  function set_secret($secret)
-  {
-    $this->secret = $secret;
-  }
-
-
-  /**
-   * Enable/disable IP check
-   */
-  function set_ip_check($check)
-  {
-    $this->ip_check = $check;
-  }
-
-
-  /**
-   * Setter for the cookie name used for session cookie
-   */
-  function set_cookiename($cookiename)
-  {
-    if ($cookiename)
-      $this->cookiename = $cookiename;
-  }
-
-
-  /**
-   * Check session authentication cookie
-   *
-   * @return boolean True if valid, False if not
-   */
-  function check_auth()
-  {
-    $this->cookie = $_COOKIE[$this->cookiename];
-    $result = $this->ip_check ? $_SERVER['REMOTE_ADDR'] == $this->ip : true;
-
-    if (!$result)
-      $this->log("IP check failed for " . $this->key . "; expected " . $this->ip . "; got " . $_SERVER['REMOTE_ADDR']);
-
-    if ($result && $this->_mkcookie($this->now) != $this->cookie) {
-      $this->log("Session auth check failed for " . $this->key . "; timeslot = " . date('Y-m-d H:i:s', $this->now));
-      $result = false;
-
-      // Check if using id from a previous time slot
-      for ($i = 1; $i <= 2; $i++) {
-        $prev = $this->now - ($this->lifetime / 2) * $i;
-        if ($this->_mkcookie($prev) == $this->cookie) {
-          $this->log("Send new auth cookie for " . $this->key . ": " . $this->cookie);
-          $this->set_auth_cookie();
-          $result = true;
+
+    /**
+     * Check session authentication cookie
+     *
+     * @return boolean True if valid, False if not
+     */
+    function check_auth()
+    {
+        $this->cookie = $_COOKIE[$this->cookiename];
+        $result = $this->ip_check ? $_SERVER['REMOTE_ADDR'] == $this->ip : true;
+
+        if (!$result) {
+            $this->log("IP check failed for " . $this->key . "; expected " . $this->ip . "; got " . $_SERVER['REMOTE_ADDR']);
         }
-      }
+
+        if ($result && $this->_mkcookie($this->now) != $this->cookie) {
+            $this->log("Session auth check failed for " . $this->key . "; timeslot = " . date('Y-m-d H:i:s', $this->now));
+            $result = false;
+
+            // Check if using id from a previous time slot
+            for ($i = 1; $i <= 2; $i++) {
+                $prev = $this->now - ($this->lifetime / 2) * $i;
+                if ($this->_mkcookie($prev) == $this->cookie) {
+                    $this->log("Send new auth cookie for " . $this->key . ": " . $this->cookie);
+                    $this->set_auth_cookie();
+                    $result = true;
+                }
+            }
+        }
+
+        if (!$result) {
+            $this->log("Session authentication failed for " . $this->key
+                . "; invalid auth cookie sent; timeslot = " . date('Y-m-d H:i:s', $prev));
+        }
+
+        return $result;
+    }
+
+
+    /**
+     * Set session authentication cookie
+     */
+    function set_auth_cookie()
+    {
+        $this->cookie = $this->_mkcookie($this->now);
+        rcube_utils::setcookie($this->cookiename, $this->cookie, 0);
+        $_COOKIE[$this->cookiename] = $this->cookie;
     }
 
-    if (!$result)
-      $this->log("Session authentication failed for " . $this->key . "; invalid auth cookie sent; timeslot = " . date('Y-m-d H:i:s', $prev));
-
-    return $result;
-  }
-
-
-  /**
-   * Set session authentication cookie
-   */
-  function set_auth_cookie()
-  {
-    $this->cookie = $this->_mkcookie($this->now);
-    rcube_utils::setcookie($this->cookiename, $this->cookie, 0);
-    $_COOKIE[$this->cookiename] = $this->cookie;
-  }
-
-
-  /**
-   * Create session cookie from session data
-   *
-   * @param int Time slot to use
-   */
-  function _mkcookie($timeslot)
-  {
-    $auth_string = "$this->key,$this->secret,$timeslot";
-    return "S" . (function_exists('sha1') ? sha1($auth_string) : md5($auth_string));
-  }
-
-  /**
-   * Writes debug information to the log
-   */
-  function log($line)
-  {
-    if ($this->logging)
-      rcube::write_log('session', $line);
-  }
 
+    /**
+     * Create session cookie from session data
+     *
+     * @param int Time slot to use
+     */
+    function _mkcookie($timeslot)
+    {
+        $auth_string = "$this->key,$this->secret,$timeslot";
+        return "S" . (function_exists('sha1') ? sha1($auth_string) : md5($auth_string));
+    }
+
+    /**
+     * Writes debug information to the log
+     */
+    function log($line)
+    {
+        if ($this->logging) {
+            rcube::write_log('session', $line);
+        }
+    }
 }
diff --git a/lib/ext/Roundcube/rcube_smtp.php b/lib/ext/Roundcube/rcube_smtp.php
index 96534c0..5c7d220 100644
--- a/lib/ext/Roundcube/rcube_smtp.php
+++ b/lib/ext/Roundcube/rcube_smtp.php
@@ -2,10 +2,8 @@
 
 /*
  +-----------------------------------------------------------------------+
- | program/include/rcube_smtp.php                                        |
- |                                                                       |
  | This file is part of the Roundcube Webmail client                     |
- | Copyright (C) 2005-2010, The Roundcube Dev Team                       |
+ | Copyright (C) 2005-2012, The Roundcube Dev Team                       |
  |                                                                       |
  | Licensed under the GNU General Public License version 3 or            |
  | any later version with exceptions for skins & plugins.                |
@@ -13,15 +11,11 @@
  |                                                                       |
  | PURPOSE:                                                              |
  |   Provide SMTP functionality using socket connections                 |
- |                                                                       |
  +-----------------------------------------------------------------------+
  | Author: Thomas Bruederli <roundcube at gmail.com>                        |
  +-----------------------------------------------------------------------+
 */
 
-// define headers delimiter
-define('SMTP_MIME_CRLF', "\r\n");
-
 /**
  * Class to provide SMTP functionality using PEAR Net_SMTP
  *
@@ -32,439 +26,425 @@ define('SMTP_MIME_CRLF', "\r\n");
  */
 class rcube_smtp
 {
-
-  private $conn = null;
-  private $response;
-  private $error;
-
-
-  /**
-   * SMTP Connection and authentication
-   *
-   * @param string Server host
-   * @param string Server port
-   * @param string User name
-   * @param string Password
-   *
-   * @return bool  Returns true on success, or false on error
-   */
-  public function connect($host=null, $port=null, $user=null, $pass=null)
-  {
-    $rcube = rcube::get_instance();
-
-    // disconnect/destroy $this->conn
-    $this->disconnect();
-
-    // reset error/response var
-    $this->error = $this->response = null;
-
-    // let plugins alter smtp connection config
-    $CONFIG = $rcube->plugins->exec_hook('smtp_connect', array(
-      'smtp_server'    => $host ? $host : $rcube->config->get('smtp_server'),
-      'smtp_port'      => $port ? $port : $rcube->config->get('smtp_port', 25),
-      'smtp_user'      => $user ? $user : $rcube->config->get('smtp_user'),
-      'smtp_pass'      => $pass ? $pass : $rcube->config->get('smtp_pass'),
-      'smtp_auth_cid'  => $rcube->config->get('smtp_auth_cid'),
-      'smtp_auth_pw'   => $rcube->config->get('smtp_auth_pw'),
-      'smtp_auth_type' => $rcube->config->get('smtp_auth_type'),
-      'smtp_helo_host' => $rcube->config->get('smtp_helo_host'),
-      'smtp_timeout'   => $rcube->config->get('smtp_timeout'),
-      'smtp_auth_callbacks' => array(),
-    ));
-
-    $smtp_host = rcube_utils::parse_host($CONFIG['smtp_server']);
-    // when called from Installer it's possible to have empty $smtp_host here
-    if (!$smtp_host) $smtp_host = 'localhost';
-    $smtp_port = is_numeric($CONFIG['smtp_port']) ? $CONFIG['smtp_port'] : 25;
-    $smtp_host_url = parse_url($smtp_host);
-
-    // overwrite port
-    if (isset($smtp_host_url['host']) && isset($smtp_host_url['port']))
+    private $conn = null;
+    private $response;
+    private $error;
+
+    // define headers delimiter
+    const SMTP_MIME_CRLF = "\r\n";
+
+
+    /**
+     * SMTP Connection and authentication
+     *
+     * @param string Server host
+     * @param string Server port
+     * @param string User name
+     * @param string Password
+     *
+     * @return bool  Returns true on success, or false on error
+     */
+    public function connect($host=null, $port=null, $user=null, $pass=null)
     {
-      $smtp_host = $smtp_host_url['host'];
-      $smtp_port = $smtp_host_url['port'];
-    }
+        $rcube = rcube::get_instance();
 
-    // re-write smtp host
-    if (isset($smtp_host_url['host']) && isset($smtp_host_url['scheme']))
-      $smtp_host = sprintf('%s://%s', $smtp_host_url['scheme'], $smtp_host_url['host']);
+        // disconnect/destroy $this->conn
+        $this->disconnect();
 
-    // remove TLS prefix and set flag for use in Net_SMTP::auth()
-    if (preg_match('#^tls://#i', $smtp_host)) {
-      $smtp_host = preg_replace('#^tls://#i', '', $smtp_host);
-      $use_tls = true;
-    }
+        // reset error/response var
+        $this->error = $this->response = null;
+
+        // let plugins alter smtp connection config
+        $CONFIG = $rcube->plugins->exec_hook('smtp_connect', array(
+            'smtp_server'    => $host ? $host : $rcube->config->get('smtp_server'),
+            'smtp_port'      => $port ? $port : $rcube->config->get('smtp_port', 25),
+            'smtp_user'      => $user ? $user : $rcube->config->get('smtp_user'),
+            'smtp_pass'      => $pass ? $pass : $rcube->config->get('smtp_pass'),
+            'smtp_auth_cid'  => $rcube->config->get('smtp_auth_cid'),
+            'smtp_auth_pw'   => $rcube->config->get('smtp_auth_pw'),
+            'smtp_auth_type' => $rcube->config->get('smtp_auth_type'),
+            'smtp_helo_host' => $rcube->config->get('smtp_helo_host'),
+            'smtp_timeout'   => $rcube->config->get('smtp_timeout'),
+            'smtp_auth_callbacks' => array(),
+        ));
+
+        $smtp_host = rcube_utils::parse_host($CONFIG['smtp_server']);
+        // when called from Installer it's possible to have empty $smtp_host here
+        if (!$smtp_host) $smtp_host = 'localhost';
+        $smtp_port     = is_numeric($CONFIG['smtp_port']) ? $CONFIG['smtp_port'] : 25;
+        $smtp_host_url = parse_url($smtp_host);
+
+        // overwrite port
+        if (isset($smtp_host_url['host']) && isset($smtp_host_url['port'])) {
+            $smtp_host = $smtp_host_url['host'];
+            $smtp_port = $smtp_host_url['port'];
+        }
 
-    if (!empty($CONFIG['smtp_helo_host']))
-      $helo_host = $CONFIG['smtp_helo_host'];
-    else if (!empty($_SERVER['SERVER_NAME']))
-      $helo_host = preg_replace('/:\d+$/', '', $_SERVER['SERVER_NAME']);
-    else
-      $helo_host = 'localhost';
+        // re-write smtp host
+        if (isset($smtp_host_url['host']) && isset($smtp_host_url['scheme'])) {
+            $smtp_host = sprintf('%s://%s', $smtp_host_url['scheme'], $smtp_host_url['host']);
+        }
 
-    // IDNA Support
-    $smtp_host = rcube_utils::idn_to_ascii($smtp_host);
+        // remove TLS prefix and set flag for use in Net_SMTP::auth()
+        if (preg_match('#^tls://#i', $smtp_host)) {
+            $smtp_host = preg_replace('#^tls://#i', '', $smtp_host);
+            $use_tls   = true;
+        }
 
-    $this->conn = new Net_SMTP($smtp_host, $smtp_port, $helo_host);
+        if (!empty($CONFIG['smtp_helo_host'])) {
+            $helo_host = $CONFIG['smtp_helo_host'];
+        }
+        else if (!empty($_SERVER['SERVER_NAME'])) {
+            $helo_host = preg_replace('/:\d+$/', '', $_SERVER['SERVER_NAME']);
+        }
+        else {
+            $helo_host = 'localhost';
+        }
 
-    if ($rcube->config->get('smtp_debug'))
-      $this->conn->setDebug(true, array($this, 'debug_handler'));
+        // IDNA Support
+        $smtp_host = rcube_utils::idn_to_ascii($smtp_host);
 
-    // register authentication methods
-    if (!empty($CONFIG['smtp_auth_callbacks']) && method_exists($this->conn, 'setAuthMethod')) {
-      foreach ($CONFIG['smtp_auth_callbacks'] as $callback) {
-        $this->conn->setAuthMethod($callback['name'], $callback['function'],
-          isset($callback['prepend']) ? $callback['prepend'] : true);
-      }
-    }
+        $this->conn = new Net_SMTP($smtp_host, $smtp_port, $helo_host);
 
-    // try to connect to server and exit on failure
-    $result = $this->conn->connect($smtp_timeout);
+        if ($rcube->config->get('smtp_debug')) {
+            $this->conn->setDebug(true, array($this, 'debug_handler'));
+        }
 
-    if (PEAR::isError($result)) {
-      $this->response[] = "Connection failed: ".$result->getMessage();
-      $this->error = array('label' => 'smtpconnerror', 'vars' => array('code' => $this->conn->_code));
-      $this->conn = null;
-      return false;
-    }
+        // register authentication methods
+        if (!empty($CONFIG['smtp_auth_callbacks']) && method_exists($this->conn, 'setAuthMethod')) {
+            foreach ($CONFIG['smtp_auth_callbacks'] as $callback) {
+                $this->conn->setAuthMethod($callback['name'], $callback['function'],
+                    isset($callback['prepend']) ? $callback['prepend'] : true);
+            }
+        }
 
-    // workaround for timeout bug in Net_SMTP 1.5.[0-1] (#1487843)
-    if (method_exists($this->conn, 'setTimeout')
-      && ($timeout = ini_get('default_socket_timeout'))
-    ) {
-      $this->conn->setTimeout($timeout);
-    }
+        // try to connect to server and exit on failure
+        $result = $this->conn->connect($smtp_timeout);
 
-    $smtp_user = str_replace('%u', $rcube->get_user_name(), $CONFIG['smtp_user']);
-    $smtp_pass = str_replace('%p', $rcube->get_user_password(), $CONFIG['smtp_pass']);
-    $smtp_auth_type = empty($CONFIG['smtp_auth_type']) ? NULL : $CONFIG['smtp_auth_type'];
+        if (PEAR::isError($result)) {
+            $this->response[] = "Connection failed: ".$result->getMessage();
+            $this->error = array('label' => 'smtpconnerror', 'vars' => array('code' => $this->conn->_code));
+            $this->conn  = null;
+            return false;
+        }
 
-    if (!empty($CONFIG['smtp_auth_cid'])) {
-      $smtp_authz = $smtp_user;
-      $smtp_user  = $CONFIG['smtp_auth_cid'];
-      $smtp_pass  = $CONFIG['smtp_auth_pw'];
-    }
+        // workaround for timeout bug in Net_SMTP 1.5.[0-1] (#1487843)
+        if (method_exists($this->conn, 'setTimeout')
+            && ($timeout = ini_get('default_socket_timeout'))
+        ) {
+            $this->conn->setTimeout($timeout);
+        }
 
-    // attempt to authenticate to the SMTP server
-    if ($smtp_user && $smtp_pass)
-    {
-      // IDNA Support
-      if (strpos($smtp_user, '@')) {
-        $smtp_user = rcube_utils::idn_to_ascii($smtp_user);
-      }
-
-      $result = $this->conn->auth($smtp_user, $smtp_pass, $smtp_auth_type, $use_tls, $smtp_authz);
-
-      if (PEAR::isError($result))
-      {
-        $this->error = array('label' => 'smtpautherror', 'vars' => array('code' => $this->conn->_code));
-        $this->response[] .= 'Authentication failure: ' . $result->getMessage() . ' (Code: ' . $result->getCode() . ')';
-        $this->reset();
-        $this->disconnect();
-        return false;
-      }
-    }
+        $smtp_user = str_replace('%u', $rcube->get_user_name(), $CONFIG['smtp_user']);
+        $smtp_pass = str_replace('%p', $rcube->get_user_password(), $CONFIG['smtp_pass']);
+        $smtp_auth_type = empty($CONFIG['smtp_auth_type']) ? NULL : $CONFIG['smtp_auth_type'];
 
-    return true;
-  }
-
-
-  /**
-   * Function for sending mail
-   *
-   * @param string Sender e-Mail address
-   *
-   * @param mixed  Either a comma-seperated list of recipients
-   *               (RFC822 compliant), or an array of recipients,
-   *               each RFC822 valid. This may contain recipients not
-   *               specified in the headers, for Bcc:, resending
-   *               messages, etc.
-   * @param mixed  The message headers to send with the mail
-   *               Either as an associative array or a finally
-   *               formatted string
-   * @param mixed  The full text of the message body, including any Mime parts
-   *               or file handle
-   * @param array  Delivery options (e.g. DSN request)
-   *
-   * @return bool  Returns true on success, or false on error
-   */
-  public function send_mail($from, $recipients, &$headers, &$body, $opts=null)
-  {
-    if (!is_object($this->conn))
-      return false;
-
-    // prepare message headers as string
-    if (is_array($headers))
-    {
-      if (!($headerElements = $this->_prepare_headers($headers))) {
-        $this->reset();
-        return false;
-      }
+        if (!empty($CONFIG['smtp_auth_cid'])) {
+            $smtp_authz = $smtp_user;
+            $smtp_user  = $CONFIG['smtp_auth_cid'];
+            $smtp_pass  = $CONFIG['smtp_auth_pw'];
+        }
 
-      list($from, $text_headers) = $headerElements;
-    }
-    else if (is_string($headers))
-      $text_headers = $headers;
-    else
-    {
-      $this->reset();
-      $this->response[] = "Invalid message headers";
-      return false;
+        // attempt to authenticate to the SMTP server
+        if ($smtp_user && $smtp_pass) {
+            // IDNA Support
+            if (strpos($smtp_user, '@')) {
+                $smtp_user = rcube_utils::idn_to_ascii($smtp_user);
+            }
+
+            $result = $this->conn->auth($smtp_user, $smtp_pass, $smtp_auth_type, $use_tls, $smtp_authz);
+
+            if (PEAR::isError($result)) {
+                $this->error = array('label' => 'smtpautherror', 'vars' => array('code' => $this->conn->_code));
+                $this->response[] .= 'Authentication failure: ' . $result->getMessage() . ' (Code: ' . $result->getCode() . ')';
+                $this->reset();
+                $this->disconnect();
+                return false;
+            }
+        }
+
+        return true;
     }
 
-    // exit if no from address is given
-    if (!isset($from))
+    /**
+     * Function for sending mail
+     *
+     * @param string Sender e-Mail address
+     *
+     * @param mixed  Either a comma-seperated list of recipients
+     *               (RFC822 compliant), or an array of recipients,
+     *               each RFC822 valid. This may contain recipients not
+     *               specified in the headers, for Bcc:, resending
+     *               messages, etc.
+     * @param mixed  The message headers to send with the mail
+     *               Either as an associative array or a finally
+     *               formatted string
+     * @param mixed  The full text of the message body, including any Mime parts
+     *               or file handle
+     * @param array  Delivery options (e.g. DSN request)
+     *
+     * @return bool  Returns true on success, or false on error
+     */
+    public function send_mail($from, $recipients, &$headers, &$body, $opts=null)
     {
-      $this->reset();
-      $this->response[] = "No From address has been provided";
-      return false;
-    }
+        if (!is_object($this->conn)) {
+            return false;
+        }
 
-    // RFC3461: Delivery Status Notification
-    if ($opts['dsn']) {
-      $exts = $this->conn->getServiceExtensions();
+        // prepare message headers as string
+        if (is_array($headers)) {
+            if (!($headerElements = $this->_prepare_headers($headers))) {
+                $this->reset();
+                return false;
+            }
 
-      if (isset($exts['DSN'])) {
-        $from_params      = 'RET=HDRS';
-        $recipient_params = 'NOTIFY=SUCCESS,FAILURE';
-      }
-    }
+            list($from, $text_headers) = $headerElements;
+        }
+        else if (is_string($headers)) {
+            $text_headers = $headers;
+        }
+        else {
+            $this->reset();
+            $this->response[] = "Invalid message headers";
+            return false;
+        }
+
+        // exit if no from address is given
+        if (!isset($from)) {
+            $this->reset();
+            $this->response[] = "No From address has been provided";
+            return false;
+        }
+
+        // RFC3461: Delivery Status Notification
+        if ($opts['dsn']) {
+            $exts = $this->conn->getServiceExtensions();
+
+            if (isset($exts['DSN'])) {
+                $from_params      = 'RET=HDRS';
+                $recipient_params = 'NOTIFY=SUCCESS,FAILURE';
+            }
+        }
+
+        // RFC2298.3: remove envelope sender address
+        if (empty($opts['mdn_use_from'])
+            && preg_match('/Content-Type: multipart\/report/', $text_headers)
+            && preg_match('/report-type=disposition-notification/', $text_headers)
+        ) {
+            $from = '';
+        }
+
+        // set From: address
+        if (PEAR::isError($this->conn->mailFrom($from, $from_params))) {
+            $err = $this->conn->getResponse();
+            $this->error = array('label' => 'smtpfromerror', 'vars' => array(
+                'from' => $from, 'code' => $this->conn->_code, 'msg' => $err[1]));
+            $this->response[] = "Failed to set sender '$from'";
+            $this->reset();
+            return false;
+        }
+
+        // prepare list of recipients
+        $recipients = $this->_parse_rfc822($recipients);
+        if (PEAR::isError($recipients)) {
+            $this->error = array('label' => 'smtprecipientserror');
+            $this->reset();
+            return false;
+        }
 
-    // RFC2298.3: remove envelope sender address
-    if (preg_match('/Content-Type: multipart\/report/', $text_headers)
-      && preg_match('/report-type=disposition-notification/', $text_headers)
-    ) {
-      $from = '';
+        // set mail recipients
+        foreach ($recipients as $recipient) {
+            if (PEAR::isError($this->conn->rcptTo($recipient, $recipient_params))) {
+                $err = $this->conn->getResponse();
+                $this->error = array('label' => 'smtptoerror', 'vars' => array(
+                    'to' => $recipient, 'code' => $this->conn->_code, 'msg' => $err[1]));
+                $this->response[] = "Failed to add recipient '$recipient'";
+                $this->reset();
+                return false;
+            }
+        }
+
+        if (is_resource($body)) {
+            // file handle
+            $data         = $body;
+            $text_headers = preg_replace('/[\r\n]+$/', '', $text_headers);
+        }
+        else {
+            // Concatenate headers and body so it can be passed by reference to SMTP_CONN->data
+            // so preg_replace in SMTP_CONN->quotedata will store a reference instead of a copy.
+            // We are still forced to make another copy here for a couple ticks so we don't really
+            // get to save a copy in the method call.
+            $data = $text_headers . "\r\n" . $body;
+
+            // unset old vars to save data and so we can pass into SMTP_CONN->data by reference.
+            unset($text_headers, $body);
+        }
+
+        // Send the message's headers and the body as SMTP data.
+        if (PEAR::isError($result = $this->conn->data($data, $text_headers))) {
+            $err = $this->conn->getResponse();
+            if (!in_array($err[0], array(354, 250, 221))) {
+                $msg = sprintf('[%d] %s', $err[0], $err[1]);
+            }
+            else {
+                $msg = $result->getMessage();
+            }
+
+            $this->error = array('label' => 'smtperror', 'vars' => array('msg' => $msg));
+            $this->response[] = "Failed to send data";
+            $this->reset();
+            return false;
+        }
+
+        $this->response[] = join(': ', $this->conn->getResponse());
+        return true;
     }
 
-    // set From: address
-    if (PEAR::isError($this->conn->mailFrom($from, $from_params)))
+    /**
+     * Reset the global SMTP connection
+     */
+    public function reset()
     {
-      $err = $this->conn->getResponse();
-      $this->error = array('label' => 'smtpfromerror', 'vars' => array(
-        'from' => $from, 'code' => $this->conn->_code, 'msg' => $err[1]));
-      $this->response[] = "Failed to set sender '$from'";
-      $this->reset();
-      return false;
+        if (is_object($this->conn)) {
+            $this->conn->rset();
+        }
     }
 
-    // prepare list of recipients
-    $recipients = $this->_parse_rfc822($recipients);
-    if (PEAR::isError($recipients))
+    /**
+     * Disconnect the global SMTP connection
+     */
+    public function disconnect()
     {
-      $this->error = array('label' => 'smtprecipientserror');
-      $this->reset();
-      return false;
+        if (is_object($this->conn)) {
+            $this->conn->disconnect();
+            $this->conn = null;
+        }
     }
 
-    // set mail recipients
-    foreach ($recipients as $recipient)
+
+    /**
+     * This is our own debug handler for the SMTP connection
+     */
+    public function debug_handler(&$smtp, $message)
     {
-      if (PEAR::isError($this->conn->rcptTo($recipient, $recipient_params))) {
-        $err = $this->conn->getResponse();
-        $this->error = array('label' => 'smtptoerror', 'vars' => array(
-          'to' => $recipient, 'code' => $this->conn->_code, 'msg' => $err[1]));
-        $this->response[] = "Failed to add recipient '$recipient'";
-        $this->reset();
-        return false;
-      }
+        rcube::write_log('smtp', preg_replace('/\r\n$/', '', $message));
     }
 
-    if (is_resource($body))
+    /**
+     * Get error message
+     */
+    public function get_error()
     {
-      // file handle
-      $data = $body;
-      $text_headers = preg_replace('/[\r\n]+$/', '', $text_headers);
-    } else {
-      // Concatenate headers and body so it can be passed by reference to SMTP_CONN->data
-      // so preg_replace in SMTP_CONN->quotedata will store a reference instead of a copy. 
-      // We are still forced to make another copy here for a couple ticks so we don't really 
-      // get to save a copy in the method call.
-      $data = $text_headers . "\r\n" . $body;
-
-      // unset old vars to save data and so we can pass into SMTP_CONN->data by reference.
-      unset($text_headers, $body);
+        return $this->error;
     }
 
-    // Send the message's headers and the body as SMTP data.
-    if (PEAR::isError($result = $this->conn->data($data, $text_headers)))
+    /**
+     * Get server response messages array
+     */
+    public function get_response()
     {
-      $err = $this->conn->getResponse();
-      if (!in_array($err[0], array(354, 250, 221)))
-        $msg = sprintf('[%d] %s', $err[0], $err[1]);
-      else
-        $msg = $result->getMessage();
-
-      $this->error = array('label' => 'smtperror', 'vars' => array('msg' => $msg));
-      $this->response[] = "Failed to send data";
-      $this->reset();
-      return false;
+         return $this->response;
     }
 
-    $this->response[] = join(': ', $this->conn->getResponse());
-    return true;
-  }
-
-
-  /**
-   * Reset the global SMTP connection
-   * @access public
-   */
-  public function reset()
-  {
-    if (is_object($this->conn))
-      $this->conn->rset();
-  }
-
-
-  /**
-   * Disconnect the global SMTP connection
-   * @access public
-   */
-  public function disconnect()
-  {
-    if (is_object($this->conn)) {
-      $this->conn->disconnect();
-      $this->conn = null;
-    }
-  }
-
-
-  /**
-   * This is our own debug handler for the SMTP connection
-   * @access public
-   */
-  public function debug_handler(&$smtp, $message)
-  {
-    rcube::write_log('smtp', preg_replace('/\r\n$/', '', $message));
-  }
-
-
-  /**
-   * Get error message
-   * @access public
-   */
-  public function get_error()
-  {
-    return $this->error;
-  }
-
-
-  /**
-   * Get server response messages array
-   * @access public
-   */
-  public function get_response()
-  {
-    return $this->response;
-  }
-
-
-  /**
-   * Take an array of mail headers and return a string containing
-   * text usable in sending a message.
-   *
-   * @param array $headers The array of headers to prepare, in an associative
-   *              array, where the array key is the header name (ie,
-   *              'Subject'), and the array value is the header
-   *              value (ie, 'test'). The header produced from those
-   *              values would be 'Subject: test'.
-   *
-   * @return mixed Returns false if it encounters a bad address,
-   *               otherwise returns an array containing two
-   *               elements: Any From: address found in the headers,
-   *               and the plain text version of the headers.
-   * @access private
-   */
-  private function _prepare_headers($headers)
-  {
-    $lines = array();
-    $from = null;
-
-    foreach ($headers as $key => $value)
+    /**
+     * Take an array of mail headers and return a string containing
+     * text usable in sending a message.
+     *
+     * @param array $headers The array of headers to prepare, in an associative
+     *              array, where the array key is the header name (ie,
+     *              'Subject'), and the array value is the header
+     *              value (ie, 'test'). The header produced from those
+     *              values would be 'Subject: test'.
+     *
+     * @return mixed Returns false if it encounters a bad address,
+     *               otherwise returns an array containing two
+     *               elements: Any From: address found in the headers,
+     *               and the plain text version of the headers.
+     */
+    private function _prepare_headers($headers)
     {
-      if (strcasecmp($key, 'From') === 0)
-      {
-        $addresses = $this->_parse_rfc822($value);
-
-        if (is_array($addresses))
-          $from = $addresses[0];
-
-        // Reject envelope From: addresses with spaces.
-        if (strpos($from, ' ') !== false)
-          return false;
-
-        $lines[] = $key . ': ' . $value;
-      }
-      else if (strcasecmp($key, 'Received') === 0)
-      {
-        $received = array();
-        if (is_array($value))
-        {
-          foreach ($value as $line)
-            $received[] = $key . ': ' . $line;
-        }
-        else
-        {
-          $received[] = $key . ': ' . $value;
+        $lines = array();
+        $from  = null;
+
+        foreach ($headers as $key => $value) {
+            if (strcasecmp($key, 'From') === 0) {
+                $addresses = $this->_parse_rfc822($value);
+
+                if (is_array($addresses)) {
+                    $from = $addresses[0];
+                }
+
+                // Reject envelope From: addresses with spaces.
+                if (strpos($from, ' ') !== false) {
+                    return false;
+                }
+
+                $lines[] = $key . ': ' . $value;
+            }
+            else if (strcasecmp($key, 'Received') === 0) {
+                $received = array();
+                if (is_array($value)) {
+                    foreach ($value as $line) {
+                        $received[] = $key . ': ' . $line;
+                    }
+                }
+                else {
+                    $received[] = $key . ': ' . $value;
+                }
+
+                // Put Received: headers at the top.  Spam detectors often
+                // flag messages with Received: headers after the Subject:
+                // as spam.
+                $lines = array_merge($received, $lines);
+            }
+            else {
+                // If $value is an array (i.e., a list of addresses), convert
+                // it to a comma-delimited string of its elements (addresses).
+                if (is_array($value)) {
+                    $value = implode(', ', $value);
+                }
+
+                $lines[] = $key . ': ' . $value;
+            }
         }
 
-        // Put Received: headers at the top.  Spam detectors often
-        // flag messages with Received: headers after the Subject:
-        // as spam.
-        $lines = array_merge($received, $lines);
-      }
-      else
-      {
-        // If $value is an array (i.e., a list of addresses), convert
-        // it to a comma-delimited string of its elements (addresses).
-        if (is_array($value))
-          $value = implode(', ', $value);
-
-        $lines[] = $key . ': ' . $value;
-      }
+        return array($from, join(self::SMTP_MIME_CRLF, $lines) . self::SMTP_MIME_CRLF);
     }
 
-    return array($from, join(SMTP_MIME_CRLF, $lines) . SMTP_MIME_CRLF);
-  }
-
-  /**
-   * Take a set of recipients and parse them, returning an array of
-   * bare addresses (forward paths) that can be passed to sendmail
-   * or an smtp server with the rcpt to: command.
-   *
-   * @param mixed Either a comma-seperated list of recipients
-   *              (RFC822 compliant), or an array of recipients,
-   *              each RFC822 valid.
-   *
-   * @return array An array of forward paths (bare addresses).
-   * @access private
-   */
-  private function _parse_rfc822($recipients)
-  {
-    // if we're passed an array, assume addresses are valid and implode them before parsing.
-    if (is_array($recipients))
-      $recipients = implode(', ', $recipients);
-
-    $addresses = array();
-    $recipients = rcube_utils::explode_quoted_string(',', $recipients);
-
-    reset($recipients);
-    while (list($k, $recipient) = each($recipients))
+    /**
+     * Take a set of recipients and parse them, returning an array of
+     * bare addresses (forward paths) that can be passed to sendmail
+     * or an smtp server with the rcpt to: command.
+     *
+     * @param mixed Either a comma-seperated list of recipients
+     *              (RFC822 compliant), or an array of recipients,
+     *              each RFC822 valid.
+     *
+     * @return array An array of forward paths (bare addresses).
+     */
+    private function _parse_rfc822($recipients)
     {
-      $a = rcube_utils::explode_quoted_string(' ', $recipient);
-      while (list($k2, $word) = each($a))
-      {
-        if (strpos($word, "@") > 0 && $word[strlen($word)-1] != '"')
-        {
-          $word = preg_replace('/^<|>$/', '', trim($word));
-          if (in_array($word, $addresses)===false)
-            array_push($addresses, $word);
+        // if we're passed an array, assume addresses are valid and implode them before parsing.
+        if (is_array($recipients)) {
+            $recipients = implode(', ', $recipients);
         }
-      }
-    }
 
-    return $addresses;
-  }
+        $addresses  = array();
+        $recipients = rcube_utils::explode_quoted_string(',', $recipients);
+
+        reset($recipients);
+        while (list($k, $recipient) = each($recipients)) {
+            $a = rcube_utils::explode_quoted_string(' ', $recipient);
+            while (list($k2, $word) = each($a)) {
+                if (strpos($word, "@") > 0 && $word[strlen($word)-1] != '"') {
+                    $word = preg_replace('/^<|>$/', '', trim($word));
+                    if (in_array($word, $addresses) === false) {
+                        array_push($addresses, $word);
+                    }
+                }
+            }
+        }
 
+        return $addresses;
+    }
 }
diff --git a/lib/ext/Roundcube/rcube_spellchecker.php b/lib/ext/Roundcube/rcube_spellchecker.php
index fce2cac..3d4d3a3 100644
--- a/lib/ext/Roundcube/rcube_spellchecker.php
+++ b/lib/ext/Roundcube/rcube_spellchecker.php
@@ -2,8 +2,6 @@
 
 /*
  +-----------------------------------------------------------------------+
- | program/include/rcube_spellchecker.php                                |
- |                                                                       |
  | This file is part of the Roundcube Webmail client                     |
  | Copyright (C) 2011, Kolab Systems AG                                  |
  | Copyright (C) 2008-2011, The Roundcube Dev Team                       |
@@ -14,14 +12,12 @@
  |                                                                       |
  | PURPOSE:                                                              |
  |   Spellchecking using different backends                              |
- |                                                                       |
  +-----------------------------------------------------------------------+
  | Author: Aleksander Machniak <machniak at kolabsys.com>                   |
  | Author: Thomas Bruederli <roundcube at gmail.com>                        |
  +-----------------------------------------------------------------------+
 */
 
-
 /**
  * Helper class for spellchecking with Googielspell and PSpell support.
  *
@@ -447,7 +443,7 @@ class rcube_spellchecker
 
     private function html2text($text)
     {
-        $h2t = new html2text($text, false, true, 0);
+        $h2t = new rcube_html2text($text, false, true, 0);
         return $h2t->get_text();
     }
 
diff --git a/lib/ext/Roundcube/rcube_storage.php b/lib/ext/Roundcube/rcube_storage.php
index 245d911..8a36f1f 100644
--- a/lib/ext/Roundcube/rcube_storage.php
+++ b/lib/ext/Roundcube/rcube_storage.php
@@ -2,8 +2,6 @@
 
 /*
  +-----------------------------------------------------------------------+
- | program/include/rcube_storage.php                                     |
- |                                                                       |
  | This file is part of the Roundcube Webmail client                     |
  | Copyright (C) 2005-2012, The Roundcube Dev Team                       |
  | Copyright (C) 2012, Kolab Systems AG                                  |
@@ -14,14 +12,12 @@
  |                                                                       |
  | PURPOSE:                                                              |
  |   Mail Storage Engine                                                 |
- |                                                                       |
  +-----------------------------------------------------------------------+
  | Author: Thomas Bruederli <roundcube at gmail.com>                        |
  | Author: Aleksander Machniak <alec at alec.pl>                            |
  +-----------------------------------------------------------------------+
 */
 
-
 /**
  * Abstract class for accessing mail messages storage server
  *
@@ -57,6 +53,7 @@ abstract class rcube_storage
     protected $all_headers = array(
         'IN-REPLY-TO',
         'BCC',
+        'SENDER',
         'MESSAGE-ID',
         'CONTENT-TRANSFER-ENCODING',
         'REFERENCES',
@@ -65,6 +62,7 @@ abstract class rcube_storage
         'MAIL-REPLY-TO',
         'RETURN-PATH',
         'DELIVERED-TO',
+        'ENVELOPE-TO',
     );
 
     const UNKNOWN       = 0;
@@ -353,7 +351,7 @@ abstract class rcube_storage
      * Get messages count for a specific folder.
      *
      * @param  string  $folder  Folder name
-     * @param  string  $mode    Mode for count [ALL|THREADS|UNSEEN|RECENT]
+     * @param  string  $mode    Mode for count [ALL|THREADS|UNSEEN|RECENT|EXISTS]
      * @param  boolean $force   Force reading from server and update cache
      * @param  boolean $status  Enables storing folder status info (max UID/count),
      *                          required for folder_status()
diff --git a/lib/ext/Roundcube/rcube_string_replacer.php b/lib/ext/Roundcube/rcube_string_replacer.php
index 584b9f6..49a3781 100644
--- a/lib/ext/Roundcube/rcube_string_replacer.php
+++ b/lib/ext/Roundcube/rcube_string_replacer.php
@@ -2,8 +2,6 @@
 
 /*
  +-----------------------------------------------------------------------+
- | program/include/rcube_string_replacer.php                             |
- |                                                                       |
  | This file is part of the Roundcube Webmail client                     |
  | Copyright (C) 2009-2012, The Roundcube Dev Team                       |
  |                                                                       |
@@ -13,13 +11,11 @@
  |                                                                       |
  | PURPOSE:                                                              |
  |   Handle string replacements based on preg_replace_callback           |
- |                                                                       |
  +-----------------------------------------------------------------------+
  | Author: Thomas Bruederli <roundcube at gmail.com>                        |
  +-----------------------------------------------------------------------+
 */
 
-
 /**
  * Helper class for string replacements based on preg_replace_callback
  *
@@ -28,164 +24,189 @@
  */
 class rcube_string_replacer
 {
-  public static $pattern = '/##str_replacement\[([0-9]+)\]##/';
-  public $mailto_pattern;
-  public $link_pattern;
-  private $values = array();
-
-
-  function __construct()
-  {
-    // Simplified domain expression for UTF8 characters handling
-    // Support unicode/punycode in top-level domain part
-    $utf_domain = '[^?&@"\'\\/()\s\r\t\n]+\\.?([^\\x00-\\x2f\\x3b-\\x40\\x5b-\\x60\\x7b-\\x7f]{2,}|xn--[a-zA-Z0-9]{2,})';
-    $url1 = '.:;,';
-    $url2 = 'a-zA-Z0-9%=#$@+?!&\\/_~\\[\\]{}\*-';
-
-    $this->link_pattern = "/([\w]+:\/\/|\W[Ww][Ww][Ww]\.|^[Ww][Ww][Ww]\.)($utf_domain([$url1]?[$url2]+)*)/";
-    $this->mailto_pattern = "/("
-        ."[-\w!\#\$%&\'*+~\/^`|{}=]+(?:\.[-\w!\#\$%&\'*+~\/^`|{}=]+)*"  // local-part
-        ."@$utf_domain"                                                 // domain-part
-        ."(\?[$url1$url2]+)?"                                           // e.g. ?subject=test...
-        .")/";
-  }
-
-  /**
-   * Add a string to the internal list
-   *
-   * @param string String value 
-   * @return int Index of value for retrieval
-   */
-  public function add($str)
-  {
-    $i = count($this->values);
-    $this->values[$i] = $str;
-    return $i;
-  }
-
-  /**
-   * Build replacement string
-   */
-  public function get_replacement($i)
-  {
-    return '##str_replacement['.$i.']##';
-  }
-
-  /**
-   * Callback function used to build HTML links around URL strings
-   *
-   * @param array Matches result from preg_replace_callback
-   * @return int Index of saved string value
-   */
-  public function link_callback($matches)
-  {
-    $i = -1;
-    $scheme = strtolower($matches[1]);
-
-    if (preg_match('!^(http|ftp|file)s?://!i', $scheme)) {
-      $url = $matches[1] . $matches[2];
+    public static $pattern = '/##str_replacement\[([0-9]+)\]##/';
+    public $mailto_pattern;
+    public $link_pattern;
+    private $values = array();
+
+
+    function __construct()
+    {
+        // Simplified domain expression for UTF8 characters handling
+        // Support unicode/punycode in top-level domain part
+        $utf_domain = '[^?&@"\'\\/()<>\s\r\t\n]+\\.?([^\\x00-\\x2f\\x3b-\\x40\\x5b-\\x60\\x7b-\\x7f]{2,}|xn--[a-zA-Z0-9]{2,})';
+        $url1       = '.:;,';
+        $url2       = 'a-zA-Z0-9%=#$@+?!&\\/_~\\[\\]\\(\\){}\*-';
+
+        $this->link_pattern = "/([\w]+:\/\/|\W[Ww][Ww][Ww]\.|^[Ww][Ww][Ww]\.)($utf_domain([$url1]?[$url2]+)*)/";
+        $this->mailto_pattern = "/("
+            ."[-\w!\#\$%&\'*+~\/^`|{}=]+(?:\.[-\w!\#\$%&\'*+~\/^`|{}=]+)*"  // local-part
+            ."@$utf_domain"                                                 // domain-part
+            ."(\?[$url1$url2]+)?"                                           // e.g. ?subject=test...
+            .")/";
     }
-    else if (preg_match('/^(\W*)(www\.)$/i', $matches[1], $m)) {
-      $url        = $m[2] . $matches[2];
-      $url_prefix = 'http://';
-      $prefix     = $m[1];
+
+    /**
+     * Add a string to the internal list
+     *
+     * @param string String value 
+     * @return int Index of value for retrieval
+     */
+    public function add($str)
+    {
+        $i = count($this->values);
+        $this->values[$i] = $str;
+        return $i;
     }
 
-    if ($url) {
-      $suffix = $this->parse_url_brackets($url);
-      $i = $this->add($prefix . html::a(array(
-          'href' => $url_prefix . $url,
-          'target' => '_blank'
-        ), rcube::Q($url)) . $suffix);
+    /**
+     * Build replacement string
+     */
+    public function get_replacement($i)
+    {
+        return '##str_replacement['.$i.']##';
     }
 
-    // Return valid link for recognized schemes, otherwise, return the unmodified string for unrecognized schemes.
-    return $i >= 0 ? $this->get_replacement($i) : $matches[0];
-  }
-
-  /**
-   * Callback function used to build mailto: links around e-mail strings
-   *
-   * @param array Matches result from preg_replace_callback
-   * @return int Index of saved string value
-   */
-  public function mailto_callback($matches)
-  {
-    $href   = $matches[1];
-    $suffix = $this->parse_url_brackets($href);
-    $i = $this->add(html::a('mailto:' . $href, rcube::Q($href)) . $suffix);
-
-    return $i >= 0 ? $this->get_replacement($i) : '';
-  }
-
-  /**
-   * Look up the index from the preg_replace matches array
-   * and return the substitution value.
-   *
-   * @param array Matches result from preg_replace_callback
-   * @return string Value at index $matches[1]
-   */
-  public function replace_callback($matches)
-  {
-    return $this->values[$matches[1]];
-  }
-
-  /**
-   * Replace all defined (link|mailto) patterns with replacement string
-   *
-   * @param string $str Text
-   *
-   * @return string Text
-   */
-  public function replace($str)
-  {
-    // search for patterns like links and e-mail addresses
-    $str = preg_replace_callback($this->link_pattern, array($this, 'link_callback'), $str);
-    $str = preg_replace_callback($this->mailto_pattern, array($this, 'mailto_callback'), $str);
-
-    return $str;
-  }
-
-  /**
-   * Replace substituted strings with original values
-   */
-  public function resolve($str)
-  {
-    return preg_replace_callback(self::$pattern, array($this, 'replace_callback'), $str);
-  }
-
-  /**
-   * Fixes bracket characters in URL handling
-   */
-  public static function parse_url_brackets(&$url)
-  {
-    // #1487672: special handling of square brackets,
-    // URL regexp allows [] characters in URL, for example:
-    // "http://example.com/?a[b]=c". However we need to handle
-    // properly situation when a bracket is placed at the end
-    // of the link e.g. "[http://example.com]"
-    if (preg_match('/(\\[|\\])/', $url)) {
-      $in = false;
-      for ($i=0, $len=strlen($url); $i<$len; $i++) {
-        if ($url[$i] == '[') {
-          if ($in)
-            break;
-          $in = true;
+    /**
+     * Callback function used to build HTML links around URL strings
+     *
+     * @param array Matches result from preg_replace_callback
+     * @return int Index of saved string value
+     */
+    public function link_callback($matches)
+    {
+        $i = -1;
+        $scheme = strtolower($matches[1]);
+
+        if (preg_match('!^(http|ftp|file)s?://!i', $scheme)) {
+            $url = $matches[1] . $matches[2];
         }
-        else if ($url[$i] == ']') {
-          if (!$in)
-            break;
-          $in = false;
+        else if (preg_match('/^(\W*)(www\.)$/i', $matches[1], $m)) {
+            $url        = $m[2] . $matches[2];
+            $url_prefix = 'http://';
+            $prefix     = $m[1];
         }
-      }
 
-      if ($i<$len) {
-        $suffix = substr($url, $i);
-        $url    = substr($url, 0, $i);
-      }
+        if ($url) {
+            $suffix = $this->parse_url_brackets($url);
+            $i = $this->add($prefix . html::a(array(
+                'href'   => $url_prefix . $url,
+                'target' => '_blank'
+            ), 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];
+    }
+
+    /**
+     * Callback function used to build mailto: links around e-mail strings
+     *
+     * @param array Matches result from preg_replace_callback
+     * @return int Index of saved string value
+     */
+    public function mailto_callback($matches)
+    {
+        $href   = $matches[1];
+        $suffix = $this->parse_url_brackets($href);
+        $i = $this->add(html::a('mailto:' . $href, rcube::Q($href)) . $suffix);
+
+        return $i >= 0 ? $this->get_replacement($i) : '';
+    }
+
+    /**
+     * Look up the index from the preg_replace matches array
+     * and return the substitution value.
+     *
+     * @param array Matches result from preg_replace_callback
+     * @return string Value at index $matches[1]
+     */
+    public function replace_callback($matches)
+    {
+        return $this->values[$matches[1]];
     }
 
-    return $suffix;
-  }
+    /**
+     * Replace all defined (link|mailto) patterns with replacement string
+     *
+     * @param string $str Text
+     *
+     * @return string Text
+     */
+    public function replace($str)
+    {
+        // search for patterns like links and e-mail addresses
+        $str = preg_replace_callback($this->link_pattern, array($this, 'link_callback'), $str);
+        $str = preg_replace_callback($this->mailto_pattern, array($this, 'mailto_callback'), $str);
+
+        return $str;
+    }
 
+    /**
+     * Replace substituted strings with original values
+     */
+    public function resolve($str)
+    {
+        return preg_replace_callback(self::$pattern, array($this, 'replace_callback'), $str);
+    }
+
+    /**
+     * Fixes bracket characters in URL handling
+     */
+    public static function parse_url_brackets(&$url)
+    {
+        // #1487672: special handling of square brackets,
+        // URL regexp allows [] characters in URL, for example:
+        // "http://example.com/?a[b]=c". However we need to handle
+        // properly situation when a bracket is placed at the end
+        // of the link e.g. "[http://example.com]"
+        // Yes, this is not perfect handles correctly only paired characters
+        // but it should work for common cases
+
+        if (preg_match('/(\\[|\\])/', $url)) {
+            $in = false;
+            for ($i=0, $len=strlen($url); $i<$len; $i++) {
+                if ($url[$i] == '[') {
+                    if ($in)
+                        break;
+                    $in = true;
+                }
+                else if ($url[$i] == ']') {
+                    if (!$in)
+                        break;
+                    $in = false;
+                }
+            }
+
+            if ($i < $len) {
+                $suffix = substr($url, $i);
+                $url    = substr($url, 0, $i);
+            }
+        }
+
+        // Do the same for parentheses
+        if (preg_match('/(\\(|\\))/', $url)) {
+            $in = false;
+            for ($i=0, $len=strlen($url); $i<$len; $i++) {
+                if ($url[$i] == '(') {
+                    if ($in)
+                        break;
+                    $in = true;
+                }
+                else if ($url[$i] == ')') {
+                    if (!$in)
+                        break;
+                    $in = false;
+                }
+            }
+
+            if ($i < $len) {
+                $suffix = substr($url, $i);
+                $url    = substr($url, 0, $i);
+            }
+        }
+
+        return $suffix;
+    }
 }
diff --git a/lib/ext/Roundcube/rcube_user.php b/lib/ext/Roundcube/rcube_user.php
index f6b77f5..505b190 100644
--- a/lib/ext/Roundcube/rcube_user.php
+++ b/lib/ext/Roundcube/rcube_user.php
@@ -2,8 +2,6 @@
 
 /*
  +-----------------------------------------------------------------------+
- | program/include/rcube_user.inc                                        |
- |                                                                       |
  | This file is part of the Roundcube Webmail client                     |
  | Copyright (C) 2005-2012, The Roundcube Dev Team                       |
  |                                                                       |
@@ -14,14 +12,12 @@
  | PURPOSE:                                                              |
  |   This class represents a system user linked and provides access      |
  |   to the related database records.                                    |
- |                                                                       |
  +-----------------------------------------------------------------------+
  | Author: Thomas Bruederli <roundcube at gmail.com>                        |
  | Author: Aleksander Machniak <alec at alec.pl>                            |
  +-----------------------------------------------------------------------+
 */
 
-
 /**
  * Class representing a system user
  *
diff --git a/lib/ext/Roundcube/rcube_utils.php b/lib/ext/Roundcube/rcube_utils.php
index 500f2c3..4b68711 100644
--- a/lib/ext/Roundcube/rcube_utils.php
+++ b/lib/ext/Roundcube/rcube_utils.php
@@ -2,8 +2,6 @@
 
 /*
  +-----------------------------------------------------------------------+
- | program/include/rcube_utils.php                                       |
- |                                                                       |
  | This file is part of the Roundcube Webmail client                     |
  | Copyright (C) 2008-2012, The Roundcube Dev Team                       |
  | Copyright (C) 2011-2012, Kolab Systems AG                             |
@@ -20,7 +18,6 @@
  +-----------------------------------------------------------------------+
 */
 
-
 /**
  * Utility class providing common functions
  *
diff --git a/lib/ext/Roundcube/rcube_vcard.php b/lib/ext/Roundcube/rcube_vcard.php
index 45ee601..c2b30af 100644
--- a/lib/ext/Roundcube/rcube_vcard.php
+++ b/lib/ext/Roundcube/rcube_vcard.php
@@ -2,8 +2,6 @@
 
 /*
  +-----------------------------------------------------------------------+
- | program/include/rcube_vcard.php                                       |
- |                                                                       |
  | This file is part of the Roundcube Webmail client                     |
  | Copyright (C) 2008-2012, The Roundcube Dev Team                       |
  |                                                                       |
@@ -19,7 +17,6 @@
  +-----------------------------------------------------------------------+
 */
 
-
 /**
  * Logical representation of a vcard-based address record
  * Provides functions to parse and export vCard data format
@@ -29,765 +26,824 @@
  */
 class rcube_vcard
 {
-  private static $values_decoded = false;
-  private $raw = array(
-    'FN' => array(),
-    'N' => array(array('','','','','')),
-  );
-  private static $fieldmap = array(
-    'phone'    => 'TEL',
-    'birthday' => 'BDAY',
-    'website'  => 'URL',
-    'notes'    => 'NOTE',
-    'email'    => 'EMAIL',
-    'address'  => 'ADR',
-    'jobtitle' => 'TITLE',
-    'department'  => 'X-DEPARTMENT',
-    'gender'      => 'X-GENDER',
-    'maidenname'  => 'X-MAIDENNAME',
-    'anniversary' => 'X-ANNIVERSARY',
-    'assistant'   => 'X-ASSISTANT',
-    'manager'     => 'X-MANAGER',
-    'spouse'      => 'X-SPOUSE',
-    'edit'        => 'X-AB-EDIT',
-  );
-  private $typemap = array('IPHONE' => 'mobile', 'CELL' => 'mobile', 'WORK,FAX' => 'workfax');
-  private $phonetypemap = array('HOME1' => 'HOME', 'BUSINESS1' => 'WORK', 'BUSINESS2' => 'WORK2', 'BUSINESSFAX' => 'WORK,FAX', 'MOBILE' => 'CELL');
-  private $addresstypemap = array('BUSINESS' => 'WORK');
-  private $immap = array('X-JABBER' => 'jabber', 'X-ICQ' => 'icq', 'X-MSN' => 'msn', 'X-AIM' => 'aim', 'X-YAHOO' => 'yahoo', 'X-SKYPE' => 'skype', 'X-SKYPE-USERNAME' => 'skype');
-
-  public $business = false;
-  public $displayname;
-  public $surname;
-  public $firstname;
-  public $middlename;
-  public $nickname;
-  public $organization;
-  public $email = array();
-
-  public static $eol = "\r\n";
-
-  /**
-   * Constructor
-   */
-  public function __construct($vcard = null, $charset = RCUBE_CHARSET, $detect = false, $fieldmap = array())
-  {
-    if (!empty($fielmap))
-      $this->extend_fieldmap($fieldmap);
-
-    if (!empty($vcard))
-      $this->load($vcard, $charset, $detect);
-  }
-
-
-  /**
-   * Load record from (internal, unfolded) vcard 3.0 format
-   *
-   * @param string vCard string to parse
-   * @param string Charset of string values
-   * @param boolean True if loading a 'foreign' vcard and extra heuristics for charset detection is required
-   */
-  public function load($vcard, $charset = RCUBE_CHARSET, $detect = false)
-  {
-    self::$values_decoded = false;
-    $this->raw = self::vcard_decode($vcard);
-
-    // resolve charset parameters
-    if ($charset == null) {
-      $this->raw = self::charset_convert($this->raw);
+    private static $values_decoded = false;
+    private $raw = array(
+        'FN' => array(),
+        'N'  => array(array('','','','','')),
+    );
+    private static $fieldmap = array(
+        'phone'    => 'TEL',
+        'birthday' => 'BDAY',
+        'website'  => 'URL',
+        'notes'    => 'NOTE',
+        'email'    => 'EMAIL',
+        'address'  => 'ADR',
+        'jobtitle' => 'TITLE',
+        'department'  => 'X-DEPARTMENT',
+        'gender'      => 'X-GENDER',
+        'maidenname'  => 'X-MAIDENNAME',
+        'anniversary' => 'X-ANNIVERSARY',
+        'assistant'   => 'X-ASSISTANT',
+        'manager'     => 'X-MANAGER',
+        'spouse'      => 'X-SPOUSE',
+        'edit'        => 'X-AB-EDIT',
+    );
+    private $typemap = array(
+        'IPHONE'   => 'mobile',
+        'CELL'     => 'mobile',
+        'WORK,FAX' => 'workfax',
+    );
+    private $phonetypemap = array(
+        'HOME1'       => 'HOME',
+        'BUSINESS1'   => 'WORK',
+        'BUSINESS2'   => 'WORK2',
+        'BUSINESSFAX' => 'WORK,FAX',
+        'MOBILE'      => 'CELL',
+    );
+    private $addresstypemap = array(
+        'BUSINESS' => 'WORK',
+    );
+    private $immap = array(
+        'X-JABBER' => 'jabber',
+        'X-ICQ'    => 'icq',
+        'X-MSN'    => 'msn',
+        'X-AIM'    => 'aim',
+        'X-YAHOO'  => 'yahoo',
+        'X-SKYPE'  => 'skype',
+        'X-SKYPE-USERNAME' => 'skype',
+    );
+
+    public $business = false;
+    public $displayname;
+    public $surname;
+    public $firstname;
+    public $middlename;
+    public $nickname;
+    public $organization;
+    public $email = array();
+
+    public static $eol = "\r\n";
+
+
+    /**
+     * Constructor
+     */
+    public function __construct($vcard = null, $charset = RCUBE_CHARSET, $detect = false, $fieldmap = array())
+    {
+        if (!empty($fielmap)) {
+            $this->extend_fieldmap($fieldmap);
+        }
+
+        if (!empty($vcard)) {
+            $this->load($vcard, $charset, $detect);
+        }
     }
-    // vcard has encoded values and charset should be detected
-    else if ($detect && self::$values_decoded &&
-      ($detected_charset = self::detect_encoding(self::vcard_encode($this->raw))) && $detected_charset != RCUBE_CHARSET) {
-        $this->raw = self::charset_convert($this->raw, $detected_charset);
+
+    /**
+     * Load record from (internal, unfolded) vcard 3.0 format
+     *
+     * @param string vCard string to parse
+     * @param string Charset of string values
+     * @param boolean True if loading a 'foreign' vcard and extra heuristics for charset detection is required
+     */
+    public function load($vcard, $charset = RCUBE_CHARSET, $detect = false)
+    {
+        self::$values_decoded = false;
+        $this->raw = self::vcard_decode($vcard);
+
+        // resolve charset parameters
+        if ($charset == null) {
+            $this->raw = self::charset_convert($this->raw);
+        }
+        // vcard has encoded values and charset should be detected
+        else if ($detect && self::$values_decoded
+            && ($detected_charset = self::detect_encoding(self::vcard_encode($this->raw)))
+            && $detected_charset != RCUBE_CHARSET
+        ) {
+            $this->raw = self::charset_convert($this->raw, $detected_charset);
+        }
+
+        // consider FN empty if the same as the primary e-mail address
+        if ($this->raw['FN'][0][0] == $this->raw['EMAIL'][0][0]) {
+            $this->raw['FN'][0][0] = '';
+        }
+
+        // find well-known address fields
+        $this->displayname  = $this->raw['FN'][0][0];
+        $this->surname      = $this->raw['N'][0][0];
+        $this->firstname    = $this->raw['N'][0][1];
+        $this->middlename   = $this->raw['N'][0][2];
+        $this->nickname     = $this->raw['NICKNAME'][0][0];
+        $this->organization = $this->raw['ORG'][0][0];
+        $this->business     = ($this->raw['X-ABSHOWAS'][0][0] == 'COMPANY') || (join('', (array)$this->raw['N'][0]) == '' && !empty($this->organization));
+
+        foreach ((array)$this->raw['EMAIL'] as $i => $raw_email) {
+            $this->email[$i] = is_array($raw_email) ? $raw_email[0] : $raw_email;
+        }
+
+        // make the pref e-mail address the first entry in $this->email
+        $pref_index = $this->get_type_index('EMAIL', 'pref');
+        if ($pref_index > 0) {
+            $tmp = $this->email[0];
+            $this->email[0] = $this->email[$pref_index];
+            $this->email[$pref_index] = $tmp;
+        }
     }
 
-    // consider FN empty if the same as the primary e-mail address
-    if ($this->raw['FN'][0][0] == $this->raw['EMAIL'][0][0])
-      $this->raw['FN'][0][0] = '';
-
-    // find well-known address fields
-    $this->displayname = $this->raw['FN'][0][0];
-    $this->surname = $this->raw['N'][0][0];
-    $this->firstname = $this->raw['N'][0][1];
-    $this->middlename = $this->raw['N'][0][2];
-    $this->nickname = $this->raw['NICKNAME'][0][0];
-    $this->organization = $this->raw['ORG'][0][0];
-    $this->business = ($this->raw['X-ABSHOWAS'][0][0] == 'COMPANY') || (join('', (array)$this->raw['N'][0]) == '' && !empty($this->organization));
-
-    foreach ((array)$this->raw['EMAIL'] as $i => $raw_email)
-      $this->email[$i] = is_array($raw_email) ? $raw_email[0] : $raw_email;
-
-    // make the pref e-mail address the first entry in $this->email
-    $pref_index = $this->get_type_index('EMAIL', 'pref');
-    if ($pref_index > 0) {
-      $tmp = $this->email[0];
-      $this->email[0] = $this->email[$pref_index];
-      $this->email[$pref_index] = $tmp;
+    /**
+     * Return vCard data as associative array to be unsed in Roundcube address books
+     *
+     * @return array Hash array with key-value pairs
+     */
+    public function get_assoc()
+    {
+        $out     = array('name' => $this->displayname);
+        $typemap = $this->typemap;
+
+        // copy name fields to output array
+        foreach (array('firstname','surname','middlename','nickname','organization') as $col) {
+            if (strlen($this->$col)) {
+                $out[$col] = $this->$col;
+            }
+        }
+
+        if ($this->raw['N'][0][3])
+            $out['prefix'] = $this->raw['N'][0][3];
+        if ($this->raw['N'][0][4])
+            $out['suffix'] = $this->raw['N'][0][4];
+
+        // convert from raw vcard data into associative data for Roundcube
+        foreach (array_flip(self::$fieldmap) as $tag => $col) {
+            foreach ((array)$this->raw[$tag] as $i => $raw) {
+                if (is_array($raw)) {
+                    $k       = -1;
+                    $key     = $col;
+                    $subtype = '';
+
+                    if (!empty($raw['type'])) {
+                        $combined = join(',', self::array_filter((array)$raw['type'], 'internet,pref', true));
+                        $combined = strtoupper($combined);
+
+                        if ($typemap[$combined]) {
+                            $subtype = $typemap[$combined];
+                        }
+                        else if ($typemap[$raw['type'][++$k]]) {
+                            $subtype = $typemap[$raw['type'][$k]];
+                        }
+                        else {
+                            $subtype = strtolower($raw['type'][$k]);
+                        }
+
+                        while ($k < count($raw['type']) && ($subtype == 'internet' || $subtype == 'pref')) {
+                            $subtype = $typemap[$raw['type'][++$k]] ? $typemap[$raw['type'][$k]] : strtolower($raw['type'][$k]);
+                        }
+                    }
+
+                    // read vcard 2.1 subtype
+                    if (!$subtype) {
+                        foreach ($raw as $k => $v) {
+                            if (!is_numeric($k) && $v === true && ($k = strtolower($k))
+                                && !in_array($k, array('pref','internet','voice','base64'))
+                            ) {
+                                $k_uc    = strtoupper($k);
+                                $subtype = $typemap[$k_uc] ? $typemap[$k_uc] : $k;
+                                break;
+                            }
+                        }
+                    }
+
+                    // force subtype if none set
+                    if (!$subtype && preg_match('/^(email|phone|address|website)/', $key)) {
+                        $subtype = 'other';
+                    }
+
+                    if ($subtype) {
+                        $key .= ':' . $subtype;
+                    }
+
+                    // split ADR values into assoc array
+                    if ($tag == 'ADR') {
+                        list(,, $value['street'], $value['locality'], $value['region'], $value['zipcode'], $value['country']) = $raw;
+                        $out[$key][] = $value;
+                    }
+                    else {
+                        $out[$key][] = $raw[0];
+                    }
+                }
+                else {
+                    $out[$col][] = $raw;
+                }
+            }
+        }
+
+        // handle special IM fields as used by Apple
+        foreach ($this->immap as $tag => $type) {
+            foreach ((array)$this->raw[$tag] as $i => $raw) {
+                $out['im:'.$type][] = $raw[0];
+            }
+        }
+
+        // copy photo data
+        if ($this->raw['PHOTO']) {
+            $out['photo'] = $this->raw['PHOTO'][0][0];
+        }
+
+        return $out;
     }
-  }
-
-
-  /**
-   * Return vCard data as associative array to be unsed in Roundcube address books
-   *
-   * @return array Hash array with key-value pairs
-   */
-  public function get_assoc()
-  {
-    $out = array('name' => $this->displayname);
-    $typemap = $this->typemap;
-
-    // copy name fields to output array
-    foreach (array('firstname','surname','middlename','nickname','organization') as $col) {
-      if (strlen($this->$col))
-        $out[$col] = $this->$col;
+
+    /**
+     * Convert the data structure into a vcard 3.0 string
+     */
+    public function export($folded = true)
+    {
+        $vcard = self::vcard_encode($this->raw);
+        return $folded ? self::rfc2425_fold($vcard) : $vcard;
     }
 
-    if ($this->raw['N'][0][3])
-      $out['prefix'] = $this->raw['N'][0][3];
-    if ($this->raw['N'][0][4])
-      $out['suffix'] = $this->raw['N'][0][4];
-
-    // convert from raw vcard data into associative data for Roundcube
-    foreach (array_flip(self::$fieldmap) as $tag => $col) {
-      foreach ((array)$this->raw[$tag] as $i => $raw) {
-        if (is_array($raw)) {
-          $k = -1;
-          $key = $col;
-          $subtype = '';
-
-          if (!empty($raw['type'])) {
-            $combined = join(',', self::array_filter((array)$raw['type'], 'internet,pref', true));
-            $combined = strtoupper($combined);
-
-            if ($typemap[$combined]) {
-                $subtype = $typemap[$combined];
-            }
-            else if ($typemap[$raw['type'][++$k]]) {
-                $subtype = $typemap[$raw['type'][$k]];
+    /**
+     * Clear the given fields in the loaded vcard data
+     *
+     * @param array List of field names to be reset
+     */
+    public function reset($fields = null)
+    {
+        if (!$fields) {
+            $fields = array_merge(array_values(self::$fieldmap), array_keys($this->immap),
+                array('FN','N','ORG','NICKNAME','EMAIL','ADR','BDAY'));
+        }
+
+        foreach ($fields as $f) {
+            unset($this->raw[$f]);
+        }
+
+        if (!$this->raw['N']) {
+            $this->raw['N'] = array(array('','','','',''));
+        }
+        if (!$this->raw['FN']) {
+            $this->raw['FN'] = array();
+        }
+
+        $this->email = array();
+    }
+
+    /**
+     * Setter for address record fields
+     *
+     * @param string Field name
+     * @param string Field value
+     * @param string Type/section name
+     */
+    public function set($field, $value, $type = 'HOME')
+    {
+        $field   = strtolower($field);
+        $type_uc = strtoupper($type);
+
+        switch ($field) {
+        case 'name':
+        case 'displayname':
+            $this->raw['FN'][0][0] = $this->displayname = $value;
+            break;
+
+        case 'surname':
+            $this->raw['N'][0][0] = $this->surname = $value;
+            break;
+
+        case 'firstname':
+            $this->raw['N'][0][1] = $this->firstname = $value;
+            break;
+
+        case 'middlename':
+            $this->raw['N'][0][2] = $this->middlename = $value;
+            break;
+
+        case 'prefix':
+            $this->raw['N'][0][3] = $value;
+            break;
+
+        case 'suffix':
+            $this->raw['N'][0][4] = $value;
+            break;
+
+        case 'nickname':
+            $this->raw['NICKNAME'][0][0] = $this->nickname = $value;
+            break;
+
+        case 'organization':
+            $this->raw['ORG'][0][0] = $this->organization = $value;
+            break;
+
+        case 'photo':
+            if (strpos($value, 'http:') === 0) {
+                // TODO: fetch file from URL and save it locally?
+                $this->raw['PHOTO'][0] = array(0 => $value, 'url' => true);
             }
             else {
-                $subtype = strtolower($raw['type'][$k]);
+                $this->raw['PHOTO'][0] = array(0 => $value, 'base64' => (bool) preg_match('![^a-z0-9/=+-]!i', $value));
             }
+            break;
+
+        case 'email':
+            $this->raw['EMAIL'][] = array(0 => $value, 'type' => array_filter(array('INTERNET', $type_uc)));
+            $this->email[] = $value;
+            break;
+
+        case 'im':
+            // save IM subtypes into extension fields
+            $typemap = array_flip($this->immap);
+            if ($field = $typemap[strtolower($type)]) {
+                $this->raw[$field][] = array(0 => $value);
+            }
+            break;
 
-            while ($k < count($raw['type']) && ($subtype == 'internet' || $subtype == 'pref'))
-              $subtype = $typemap[$raw['type'][++$k]] ? $typemap[$raw['type'][$k]] : strtolower($raw['type'][$k]);
-          }
-
-          // read vcard 2.1 subtype
-          if (!$subtype) {
-            foreach ($raw as $k => $v) {
-              if (!is_numeric($k) && $v === true && ($k = strtolower($k))
-                && !in_array($k, array('pref','internet','voice','base64'))
-              ) {
-                $k_uc    = strtoupper($k);
-                $subtype = $typemap[$k_uc] ? $typemap[$k_uc] : $k;
-                break;
-              }
+        case 'birthday':
+        case 'anniversary':
+            if (($val = rcube_utils::strtotime($value)) && ($fn = self::$fieldmap[$field])) {
+                $this->raw[$fn][] = array(0 => date('Y-m-d', $val), 'value' => array('date'));
             }
-          }
+            break;
 
-          // force subtype if none set
-          if (!$subtype && preg_match('/^(email|phone|address|website)/', $key))
-            $subtype = 'other';
+        case 'address':
+            if ($this->addresstypemap[$type_uc]) {
+                $type = $this->addresstypemap[$type_uc];
+            }
 
-          if ($subtype)
-            $key .= ':' . $subtype;
+            $value = $value[0] ? $value : array('', '', $value['street'], $value['locality'], $value['region'], $value['zipcode'], $value['country']);
 
-          // split ADR values into assoc array
-          if ($tag == 'ADR') {
-            list(,, $value['street'], $value['locality'], $value['region'], $value['zipcode'], $value['country']) = $raw;
-            $out[$key][] = $value;
-          }
-          else
-            $out[$key][] = $raw[0];
-        }
-        else {
-          $out[$col][] = $raw;
+            // fall through if not empty
+            if (!strlen(join('', $value))) {
+                break;
+            }
+
+        default:
+            if ($field == 'phone' && $this->phonetypemap[$type_uc]) {
+                $type = $this->phonetypemap[$type_uc];
+             }
+
+            if (($tag = self::$fieldmap[$field]) && (is_array($value) || strlen($value))) {
+                $index = count($this->raw[$tag]);
+                $this->raw[$tag][$index] = (array)$value;
+                if ($type) {
+                    $typemap = array_flip($this->typemap);
+                    $this->raw[$tag][$index]['type'] = explode(',', ($typemap[$type_uc] ? $typemap[$type_uc] : $type));
+                }
+            }
+            break;
         }
-      }
     }
 
-    // handle special IM fields as used by Apple
-    foreach ($this->immap as $tag => $type) {
-      foreach ((array)$this->raw[$tag] as $i => $raw) {
-        $out['im:'.$type][] = $raw[0];
-      }
+    /**
+     * Setter for individual vcard properties
+     *
+     * @param string VCard tag name
+     * @param array Value-set of this vcard property
+     * @param boolean Set to true if the value-set should be appended instead of replacing any existing value-set
+     */
+    public function set_raw($tag, $value, $append = false)
+    {
+        $index = $append ? count($this->raw[$tag]) : 0;
+        $this->raw[$tag][$index] = (array)$value;
     }
 
-    // copy photo data
-    if ($this->raw['PHOTO'])
-      $out['photo'] = $this->raw['PHOTO'][0][0];
-
-    return $out;
-  }
-
-
-  /**
-   * Convert the data structure into a vcard 3.0 string
-   */
-  public function export($folded = true)
-  {
-    $vcard = self::vcard_encode($this->raw);
-    return $folded ? self::rfc2425_fold($vcard) : $vcard;
-  }
-
-
-  /**
-   * Clear the given fields in the loaded vcard data
-   *
-   * @param array List of field names to be reset
-   */
-  public function reset($fields = null)
-  {
-    if (!$fields)
-      $fields = array_merge(array_values(self::$fieldmap), array_keys($this->immap), array('FN','N','ORG','NICKNAME','EMAIL','ADR','BDAY'));
-
-    foreach ($fields as $f)
-      unset($this->raw[$f]);
-
-    if (!$this->raw['N'])
-      $this->raw['N'] = array(array('','','','',''));
-    if (!$this->raw['FN'])
-      $this->raw['FN'] = array();
-
-    $this->email = array();
-  }
-
-
-  /**
-   * Setter for address record fields
-   *
-   * @param string Field name
-   * @param string Field value
-   * @param string Type/section name
-   */
-  public function set($field, $value, $type = 'HOME')
-  {
-    $field   = strtolower($field);
-    $type_uc = strtoupper($type);
-
-    switch ($field) {
-      case 'name':
-      case 'displayname':
-        $this->raw['FN'][0][0] = $this->displayname = $value;
-        break;
-
-      case 'surname':
-        $this->raw['N'][0][0] = $this->surname = $value;
-        break;
-
-      case 'firstname':
-        $this->raw['N'][0][1] = $this->firstname = $value;
-        break;
-
-      case 'middlename':
-        $this->raw['N'][0][2] = $this->middlename = $value;
-        break;
-
-      case 'prefix':
-        $this->raw['N'][0][3] = $value;
-        break;
-
-      case 'suffix':
-        $this->raw['N'][0][4] = $value;
-        break;
-
-      case 'nickname':
-        $this->raw['NICKNAME'][0][0] = $this->nickname = $value;
-        break;
-
-      case 'organization':
-        $this->raw['ORG'][0][0] = $this->organization = $value;
-        break;
-
-      case 'photo':
-        if (strpos($value, 'http:') === 0) {
-            // TODO: fetch file from URL and save it locally?
-            $this->raw['PHOTO'][0] = array(0 => $value, 'url' => true);
-        }
-        else {
-            $this->raw['PHOTO'][0] = array(0 => $value, 'base64' => (bool) preg_match('![^a-z0-9/=+-]!i', $value));
-        }
-        break;
-
-      case 'email':
-        $this->raw['EMAIL'][] = array(0 => $value, 'type' => array_filter(array('INTERNET', $type_uc)));
-        $this->email[] = $value;
-        break;
-
-      case 'im':
-        // save IM subtypes into extension fields
-        $typemap = array_flip($this->immap);
-        if ($field = $typemap[strtolower($type)])
-          $this->raw[$field][] = array(0 => $value);
-        break;
-
-      case 'birthday':
-      case 'anniversary':
-        if (($val = rcube_utils::strtotime($value)) && ($fn = self::$fieldmap[$field]))
-          $this->raw[$fn][] = array(0 => date('Y-m-d', $val), 'value' => array('date'));
-        break;
-
-      case 'address':
-        if ($this->addresstypemap[$type_uc])
-          $type = $this->addresstypemap[$type_uc];
-
-        $value = $value[0] ? $value : array('', '', $value['street'], $value['locality'], $value['region'], $value['zipcode'], $value['country']);
-
-        // fall through if not empty
-        if (!strlen(join('', $value)))
-          break;
-
-      default:
-        if ($field == 'phone' && $this->phonetypemap[$type_uc])
-          $type = $this->phonetypemap[$type_uc];
-
-        if (($tag = self::$fieldmap[$field]) && (is_array($value) || strlen($value))) {
-          $index = count($this->raw[$tag]);
-          $this->raw[$tag][$index] = (array)$value;
-          if ($type) {
-            $typemap = array_flip($this->typemap);
-            $this->raw[$tag][$index]['type'] = explode(',', ($typemap[$type_uc] ? $typemap[$type_uc] : $type));
-          }
-        }
-        break;
-    }
-  }
-
-  /**
-   * Setter for individual vcard properties
-   *
-   * @param string VCard tag name
-   * @param array Value-set of this vcard property
-   * @param boolean Set to true if the value-set should be appended instead of replacing any existing value-set
-   */
-  public function set_raw($tag, $value, $append = false)
-  {
-    $index = $append ? count($this->raw[$tag]) : 0;
-    $this->raw[$tag][$index] = (array)$value;
-  }
-
-
-  /**
-   * Find index with the '$type' attribute
-   *
-   * @param string Field name
-   * @return int Field index having $type set
-   */
-  private function get_type_index($field, $type = 'pref')
-  {
-    $result = 0;
-    if ($this->raw[$field]) {
-      foreach ($this->raw[$field] as $i => $data) {
-        if (is_array($data['type']) && in_array_nocase('pref', $data['type']))
-          $result = $i;
-      }
+    /**
+     * Find index with the '$type' attribute
+     *
+     * @param string Field name
+     * @return int Field index having $type set
+     */
+    private function get_type_index($field, $type = 'pref')
+    {
+        $result = 0;
+        if ($this->raw[$field]) {
+            foreach ($this->raw[$field] as $i => $data) {
+                if (is_array($data['type']) && in_array_nocase('pref', $data['type'])) {
+                    $result = $i;
+                }
+            }
+        }
+
+        return $result;
     }
 
-    return $result;
-  }
-
-
-  /**
-   * Convert a whole vcard (array) to UTF-8.
-   * If $force_charset is null, each member value that has a charset parameter will be converted
-   */
-  private static function charset_convert($card, $force_charset = null)
-  {
-    foreach ($card as $key => $node) {
-      foreach ($node as $i => $subnode) {
-        if (is_array($subnode) && (($charset = $force_charset) || ($subnode['charset'] && ($charset = $subnode['charset'][0])))) {
-          foreach ($subnode as $j => $value) {
-            if (is_numeric($j) && is_string($value))
-              $card[$key][$i][$j] = rcube_charset::convert($value, $charset);
-          }
-          unset($card[$key][$i]['charset']);
-        }
-      }
+    /**
+     * Convert a whole vcard (array) to UTF-8.
+     * If $force_charset is null, each member value that has a charset parameter will be converted
+     */
+    private static function charset_convert($card, $force_charset = null)
+    {
+        foreach ($card as $key => $node) {
+            foreach ($node as $i => $subnode) {
+                if (is_array($subnode) && (($charset = $force_charset) || ($subnode['charset'] && ($charset = $subnode['charset'][0])))) {
+                    foreach ($subnode as $j => $value) {
+                        if (is_numeric($j) && is_string($value)) {
+                            $card[$key][$i][$j] = rcube_charset::convert($value, $charset);
+                        }
+                    }
+                    unset($card[$key][$i]['charset']);
+                }
+            }
+        }
+
+        return $card;
     }
 
-    return $card;
-  }
-
-
-  /**
-   * Extends fieldmap definition
-   */
-  public function extend_fieldmap($map)
-  {
-    if (is_array($map))
-      self::$fieldmap = array_merge($map, self::$fieldmap);
-  }
-
-
-  /**
-   * Factory method to import a vcard file
-   *
-   * @param string vCard file content
-   * @return array List of rcube_vcard objects
-   */
-  public static function import($data)
-  {
-    $out = array();
-
-    // check if charsets are specified (usually vcard version < 3.0 but this is not reliable)
-    if (preg_match('/charset=/i', substr($data, 0, 2048)))
-      $charset = null;
-    // detect charset and convert to utf-8
-    else if (($charset = self::detect_encoding($data)) && $charset != RCUBE_CHARSET) {
-      $data = rcube_charset::convert($data, $charset);
-      $data = preg_replace(array('/^[\xFE\xFF]{2}/', '/^\xEF\xBB\xBF/', '/^\x00+/'), '', $data); // also remove BOM
-      $charset = RCUBE_CHARSET;
+    /**
+     * Extends fieldmap definition
+     */
+    public function extend_fieldmap($map)
+    {
+        if (is_array($map)) {
+            self::$fieldmap = array_merge($map, self::$fieldmap);
+        }
     }
 
-    $vcard_block = '';
-    $in_vcard_block = false;
+    /**
+     * Factory method to import a vcard file
+     *
+     * @param string vCard file content
+     *
+     * @return array List of rcube_vcard objects
+     */
+    public static function import($data)
+    {
+        $out = array();
+
+        // check if charsets are specified (usually vcard version < 3.0 but this is not reliable)
+        if (preg_match('/charset=/i', substr($data, 0, 2048))) {
+            $charset = null;
+        }
+        // detect charset and convert to utf-8
+        else if (($charset = self::detect_encoding($data)) && $charset != RCUBE_CHARSET) {
+            $data = rcube_charset::convert($data, $charset);
+            $data = preg_replace(array('/^[\xFE\xFF]{2}/', '/^\xEF\xBB\xBF/', '/^\x00+/'), '', $data); // also remove BOM
+            $charset = RCUBE_CHARSET;
+        }
 
-    foreach (preg_split("/[\r\n]+/", $data) as $i => $line) {
-      if ($in_vcard_block && !empty($line))
-        $vcard_block .= $line . "\n";
+        $vcard_block    = '';
+        $in_vcard_block = false;
 
-      $line = trim($line);
+        foreach (preg_split("/[\r\n]+/", $data) as $i => $line) {
+            if ($in_vcard_block && !empty($line)) {
+                $vcard_block .= $line . "\n";
+            }
 
-      if (preg_match('/^END:VCARD$/i', $line)) {
-        // parse vcard
-        $obj = new rcube_vcard(self::cleanup($vcard_block), $charset, true, self::$fieldmap);
-        if (!empty($obj->displayname) || !empty($obj->email))
-          $out[] = $obj;
+            $line = trim($line);
 
-        $in_vcard_block = false;
-      }
-      else if (preg_match('/^BEGIN:VCARD$/i', $line)) {
-        $vcard_block = $line . "\n";
-        $in_vcard_block = true;
-      }
+            if (preg_match('/^END:VCARD$/i', $line)) {
+                // parse vcard
+                $obj = new rcube_vcard(self::cleanup($vcard_block), $charset, true, self::$fieldmap);
+                if (!empty($obj->displayname) || !empty($obj->email)) {
+                    $out[] = $obj;
+                }
+
+                $in_vcard_block = false;
+            }
+            else if (preg_match('/^BEGIN:VCARD$/i', $line)) {
+                $vcard_block    = $line . "\n";
+                $in_vcard_block = true;
+            }
+        }
+
+        return $out;
     }
 
-    return $out;
-  }
-
-
-  /**
-   * Normalize vcard data for better parsing
-   *
-   * @param string vCard block
-   * @return string Cleaned vcard block
-   */
-  private static function cleanup($vcard)
-  {
-    // Convert special types (like Skype) to normal type='skype' classes with this simple regex ;)
-    $vcard = preg_replace(
-      '/item(\d+)\.(TEL|EMAIL|URL)([^:]*?):(.*?)item\1.X-ABLabel:(?:_\$!<)?([\w-() ]*)(?:>!\$_)?./s',
-      '\2;type=\5\3:\4',
-      $vcard);
-
-    // convert Apple X-ABRELATEDNAMES into X-* fields for better compatibility
-    $vcard = preg_replace_callback(
-      '/item(\d+)\.(X-ABRELATEDNAMES)([^:]*?):(.*?)item\1.X-ABLabel:(?:_\$!<)?([\w-() ]*)(?:>!\$_)?./s',
-      array('self', 'x_abrelatednames_callback'),
-      $vcard);
-
-    // Remove cruft like item1.X-AB*, item1.ADR instead of ADR, and empty lines
-    $vcard = preg_replace(array('/^item\d*\.X-AB.*$/m', '/^item\d*\./m', "/\n+/"), array('', '', "\n"), $vcard);
-
-    // convert X-WAB-GENDER to X-GENDER
-    if (preg_match('/X-WAB-GENDER:(\d)/', $vcard, $matches)) {
-      $value = $matches[1] == '2' ? 'male' : 'female';
-      $vcard = preg_replace('/X-WAB-GENDER:\d/', 'X-GENDER:' . $value, $vcard);
+    /**
+     * Normalize vcard data for better parsing
+     *
+     * @param string vCard block
+     *
+     * @return string Cleaned vcard block
+     */
+    private static function cleanup($vcard)
+    {
+        // Convert special types (like Skype) to normal type='skype' classes with this simple regex ;)
+        $vcard = preg_replace(
+            '/item(\d+)\.(TEL|EMAIL|URL)([^:]*?):(.*?)item\1.X-ABLabel:(?:_\$!<)?([\w-() ]*)(?:>!\$_)?./s',
+            '\2;type=\5\3:\4',
+            $vcard);
+
+        // convert Apple X-ABRELATEDNAMES into X-* fields for better compatibility
+        $vcard = preg_replace_callback(
+            '/item(\d+)\.(X-ABRELATEDNAMES)([^:]*?):(.*?)item\1.X-ABLabel:(?:_\$!<)?([\w-() ]*)(?:>!\$_)?./s',
+            array('self', 'x_abrelatednames_callback'),
+            $vcard);
+
+        // Remove cruft like item1.X-AB*, item1.ADR instead of ADR, and empty lines
+        $vcard = preg_replace(array('/^item\d*\.X-AB.*$/m', '/^item\d*\./m', "/\n+/"), array('', '', "\n"), $vcard);
+
+        // convert X-WAB-GENDER to X-GENDER
+        if (preg_match('/X-WAB-GENDER:(\d)/', $vcard, $matches)) {
+            $value = $matches[1] == '2' ? 'male' : 'female';
+            $vcard = preg_replace('/X-WAB-GENDER:\d/', 'X-GENDER:' . $value, $vcard);
+        }
+
+        // if N doesn't have any semicolons, add some 
+        $vcard = preg_replace('/^(N:[^;\R]*)$/m', '\1;;;;', $vcard);
+
+        return $vcard;
     }
 
-    // if N doesn't have any semicolons, add some 
-    $vcard = preg_replace('/^(N:[^;\R]*)$/m', '\1;;;;', $vcard);
-
-    return $vcard;
-  }
-
-  private static function x_abrelatednames_callback($matches)
-  {
-    return 'X-' . strtoupper($matches[5]) . $matches[3] . ':'. $matches[4];
-  }
-
-  private static function rfc2425_fold_callback($matches)
-  {
-    // chunk_split string and avoid lines breaking multibyte characters
-    $c = 71;
-    $out .= substr($matches[1], 0, $c);
-    for ($n = $c; $c < strlen($matches[1]); $c++) {
-      // break if length > 75 or mutlibyte character starts after position 71
-      if ($n > 75 || ($n > 71 && ord($matches[1][$c]) >> 6 == 3)) {
-        $out .= "\r\n ";
-        $n = 0;
-      }
-      $out .= $matches[1][$c];
-      $n++;
+    private static function x_abrelatednames_callback($matches)
+    {
+        return 'X-' . strtoupper($matches[5]) . $matches[3] . ':'. $matches[4];
     }
 
-    return $out;
-  }
-
-  public static function rfc2425_fold($val)
-  {
-    return preg_replace_callback('/([^\n]{72,})/', array('self', 'rfc2425_fold_callback'), $val);
-  }
-
-
-  /**
-   * Decodes a vcard block (vcard 3.0 format, unfolded)
-   * into an array structure
-   *
-   * @param string vCard block to parse
-   * @return array Raw data structure
-   */
-  private static function vcard_decode($vcard)
-  {
-    // Perform RFC2425 line unfolding and split lines
-    $vcard = preg_replace(array("/\r/", "/\n\s+/"), '', $vcard);
-    $lines = explode("\n", $vcard);
-    $data  = array();
-
-    for ($i=0; $i < count($lines); $i++) {
-      if (!preg_match('/^([^:]+):(.+)$/', $lines[$i], $line))
-        continue;
-
-      if (preg_match('/^(BEGIN|END)$/i', $line[1]))
-        continue;
-
-      // convert 2.1-style "EMAIL;internet;home:" to 3.0-style "EMAIL;TYPE=internet;TYPE=home:"
-      if (($data['VERSION'][0] == "2.1") && preg_match('/^([^;]+);([^:]+)/', $line[1], $regs2) && !preg_match('/^TYPE=/i', $regs2[2])) {
-        $line[1] = $regs2[1];
-        foreach (explode(';', $regs2[2]) as $prop)
-          $line[1] .= ';' . (strpos($prop, '=') ? $prop : 'TYPE='.$prop);
-      }
-
-      if (preg_match_all('/([^\\;]+);?/', $line[1], $regs2)) {
-        $entry = array();
-        $field = strtoupper($regs2[1][0]);
-        $enc   = null;
-
-        foreach($regs2[1] as $attrid => $attr) {
-          if ((list($key, $value) = explode('=', $attr)) && $value) {
-            $value = trim($value);
-            if ($key == 'ENCODING') {
-              $value = strtoupper($value);
-              // add next line(s) to value string if QP line end detected
-              if ($value == 'QUOTED-PRINTABLE') {
-                while (preg_match('/=$/', $lines[$i]))
-                  $line[2] .= "\n" . $lines[++$i];
-              }
-              $enc = $value;
-            }
-            else {
-              $lc_key = strtolower($key);
-              $entry[$lc_key] = array_merge((array)$entry[$lc_key], (array)self::vcard_unquote($value, ','));
+    private static function rfc2425_fold_callback($matches)
+    {
+        // chunk_split string and avoid lines breaking multibyte characters
+        $c = 71;
+        $out .= substr($matches[1], 0, $c);
+        for ($n = $c; $c < strlen($matches[1]); $c++) {
+            // break if length > 75 or mutlibyte character starts after position 71
+            if ($n > 75 || ($n > 71 && ord($matches[1][$c]) >> 6 == 3)) {
+                $out .= "\r\n ";
+                $n = 0;
             }
-          }
-          else if ($attrid > 0) {
-            $entry[strtolower($key)] = true;  // true means attr without =value
-          }
+            $out .= $matches[1][$c];
+            $n++;
         }
 
-        // decode value
-        if ($enc || !empty($entry['base64'])) {
-          // save encoding type (#1488432)
-          if ($enc == 'B') {
-            $entry['encoding'] = 'B';
-            // should we use vCard 3.0 instead?
-            // $entry['base64'] = true;
-          }
-          $line[2] = self::decode_value($line[2], $enc ? $enc : 'base64');
-        }
+        return $out;
+    }
+
+    public static function rfc2425_fold($val)
+    {
+        return preg_replace_callback('/([^\n]{72,})/', array('self', 'rfc2425_fold_callback'), $val);
+    }
 
-        if ($enc != 'B' && empty($entry['base64'])) {
-          $line[2] = self::vcard_unquote($line[2]);
+    /**
+     * Decodes a vcard block (vcard 3.0 format, unfolded)
+     * into an array structure
+     *
+     * @param string vCard block to parse
+     *
+     * @return array Raw data structure
+     */
+    private static function vcard_decode($vcard)
+    {
+        // Perform RFC2425 line unfolding and split lines
+        $vcard = preg_replace(array("/\r/", "/\n\s+/"), '', $vcard);
+        $lines = explode("\n", $vcard);
+        $data  = array();
+
+        for ($i=0; $i < count($lines); $i++) {
+            if (!preg_match('/^([^:]+):(.+)$/', $lines[$i], $line))
+                continue;
+
+            if (preg_match('/^(BEGIN|END)$/i', $line[1]))
+                continue;
+
+            // convert 2.1-style "EMAIL;internet;home:" to 3.0-style "EMAIL;TYPE=internet;TYPE=home:"
+            if ($data['VERSION'][0] == "2.1"
+                && preg_match('/^([^;]+);([^:]+)/', $line[1], $regs2)
+                && !preg_match('/^TYPE=/i', $regs2[2])
+            ) {
+                $line[1] = $regs2[1];
+                foreach (explode(';', $regs2[2]) as $prop) {
+                    $line[1] .= ';' . (strpos($prop, '=') ? $prop : 'TYPE='.$prop);
+                }
+            }
+
+            if (preg_match_all('/([^\\;]+);?/', $line[1], $regs2)) {
+                $entry = array();
+                $field = strtoupper($regs2[1][0]);
+                $enc   = null;
+
+                foreach($regs2[1] as $attrid => $attr) {
+                    if ((list($key, $value) = explode('=', $attr)) && $value) {
+                        $value = trim($value);
+                        if ($key == 'ENCODING') {
+                            $value = strtoupper($value);
+                            // add next line(s) to value string if QP line end detected
+                            if ($value == 'QUOTED-PRINTABLE') {
+                                while (preg_match('/=$/', $lines[$i])) {
+                                    $line[2] .= "\n" . $lines[++$i];
+                                }
+                            }
+                            $enc = $value;
+                        }
+                        else {
+                            $lc_key = strtolower($key);
+                            $entry[$lc_key] = array_merge((array)$entry[$lc_key], (array)self::vcard_unquote($value, ','));
+                        }
+                    }
+                    else if ($attrid > 0) {
+                        $entry[strtolower($key)] = true;  // true means attr without =value
+                    }
+                }
+
+                // decode value
+                if ($enc || !empty($entry['base64'])) {
+                    // save encoding type (#1488432)
+                    if ($enc == 'B') {
+                        $entry['encoding'] = 'B';
+                        // should we use vCard 3.0 instead?
+                        // $entry['base64'] = true;
+                    }
+                    $line[2] = self::decode_value($line[2], $enc ? $enc : 'base64');
+                }
+
+                if ($enc != 'B' && empty($entry['base64'])) {
+                    $line[2] = self::vcard_unquote($line[2]);
+                }
+
+                $entry = array_merge($entry, (array) $line[2]);
+                $data[$field][] = $entry;
+            }
         }
 
-        $entry = array_merge($entry, (array) $line[2]);
-        $data[$field][] = $entry;
-      }
+        unset($data['VERSION']);
+        return $data;
     }
 
-    unset($data['VERSION']);
-    return $data;
-  }
-
-
-  /**
-   * Decode a given string with the encoding rule from ENCODING attributes
-   *
-   * @param string String to decode
-   * @param string Encoding type (quoted-printable and base64 supported)
-   * @return string Decoded 8bit value
-   */
-  private static function decode_value($value, $encoding)
-  {
-    switch (strtolower($encoding)) {
-      case 'quoted-printable':
-        self::$values_decoded = true;
-        return quoted_printable_decode($value);
-
-      case 'base64':
-      case 'b':
-        self::$values_decoded = true;
-        return base64_decode($value);
-
-      default:
-        return $value;
+    /**
+     * Decode a given string with the encoding rule from ENCODING attributes
+     *
+     * @param string String to decode
+     * @param string Encoding type (quoted-printable and base64 supported)
+     *
+     * @return string Decoded 8bit value
+     */
+    private static function decode_value($value, $encoding)
+    {
+        switch (strtolower($encoding)) {
+        case 'quoted-printable':
+            self::$values_decoded = true;
+            return quoted_printable_decode($value);
+
+        case 'base64':
+        case 'b':
+            self::$values_decoded = true;
+            return base64_decode($value);
+
+        default:
+            return $value;
+        }
     }
-  }
-
-
-  /**
-   * Encodes an entry for storage in our database (vcard 3.0 format, unfolded)
-   *
-   * @param array Raw data structure to encode
-   * @return string vCard encoded string
-   */
-  static function vcard_encode($data)
-  {
-    foreach((array)$data as $type => $entries) {
-      /* valid N has 5 properties */
-      while ($type == "N" && is_array($entries[0]) && count($entries[0]) < 5)
-        $entries[0][] = "";
-
-      // make sure FN is not empty (required by RFC2426)
-      if ($type == "FN" && empty($entries))
-        $entries[0] = $data['EMAIL'][0][0];
-
-      foreach((array)$entries as $entry) {
-        $attr = '';
-        if (is_array($entry)) {
-          $value = array();
-          foreach($entry as $attrname => $attrvalues) {
-            if (is_int($attrname)) {
-              if (!empty($entry['base64']) || $entry['encoding'] == 'B') {
-                $attrvalues = base64_encode($attrvalues);
-              }
-              $value[] = $attrvalues;
+
+    /**
+     * Encodes an entry for storage in our database (vcard 3.0 format, unfolded)
+     *
+     * @param array Raw data structure to encode
+     *
+     * @return string vCard encoded string
+     */
+    static function vcard_encode($data)
+    {
+        foreach ((array)$data as $type => $entries) {
+            // valid N has 5 properties
+            while ($type == "N" && is_array($entries[0]) && count($entries[0]) < 5) {
+                $entries[0][] = "";
             }
-            else if (is_bool($attrvalues)) {
-              if ($attrvalues) {
-                $attr .= strtoupper(";$attrname");    // true means just tag, not tag=value, as in PHOTO;BASE64:...
-              }
+
+            // make sure FN is not empty (required by RFC2426)
+            if ($type == "FN" && empty($entries)) {
+                $entries[0] = $data['EMAIL'][0][0];
             }
-            else {
-              foreach((array)$attrvalues as $attrvalue)
-                $attr .= strtoupper(";$attrname=") . self::vcard_quote($attrvalue, ',');
+
+            foreach ((array)$entries as $entry) {
+                $attr = '';
+                if (is_array($entry)) {
+                    $value = array();
+                    foreach ($entry as $attrname => $attrvalues) {
+                        if (is_int($attrname)) {
+                            if (!empty($entry['base64']) || $entry['encoding'] == 'B') {
+                                $attrvalues = base64_encode($attrvalues);
+                            }
+                            $value[] = $attrvalues;
+                        }
+                        else if (is_bool($attrvalues)) {
+                            // true means just tag, not tag=value, as in PHOTO;BASE64:...
+                            if ($attrvalues) {
+                                $attr .= strtoupper(";$attrname");
+                            }
+                        }
+                        else {
+                            foreach((array)$attrvalues as $attrvalue) {
+                                $attr .= strtoupper(";$attrname=") . self::vcard_quote($attrvalue, ',');
+                            }
+                        }
+                    }
+                }
+                else {
+                    $value = $entry;
+                }
+
+                // skip empty entries
+                if (self::is_empty($value)) {
+                    continue;
+                }
+
+                $vcard .= self::vcard_quote($type) . $attr . ':' . self::vcard_quote($value) . self::$eol;
             }
-          }
         }
-        else {
-          $value = $entry;
-        }
-
-        // skip empty entries
-        if (self::is_empty($value))
-          continue;
 
-        $vcard .= self::vcard_quote($type) . $attr . ':' . self::vcard_quote($value) . self::$eol;
-      }
+        return 'BEGIN:VCARD' . self::$eol . 'VERSION:3.0' . self::$eol . $vcard . 'END:VCARD';
     }
 
-    return 'BEGIN:VCARD' . self::$eol . 'VERSION:3.0' . self::$eol . $vcard . 'END:VCARD';
-  }
-
-
-  /**
-   * Join indexed data array to a vcard quoted string
-   *
-   * @param array Field data
-   * @param string Separator
-   * @return string Joined and quoted string
-   */
-  private static function vcard_quote($s, $sep = ';')
-  {
-    if (is_array($s)) {
-      foreach($s as $part) {
-        $r[] = self::vcard_quote($part, $sep);
-      }
-      return(implode($sep, (array)$r));
-    }
-    else {
-      return strtr($s, array('\\' => '\\\\', "\r" => '', "\n" => '\n', ',' => '\,', ';' => '\;'));
-    }
-  }
-
-
-  /**
-   * Split quoted string
-   *
-   * @param string vCard string to split
-   * @param string Separator char/string
-   * @return array List with splited values
-   */
-  private static function vcard_unquote($s, $sep = ';')
-  {
-    // break string into parts separated by $sep, but leave escaped $sep alone
-    if (count($parts = explode($sep, strtr($s, array("\\$sep" => "\007")))) > 1) {
-      foreach($parts as $s) {
-        $result[] = self::vcard_unquote(strtr($s, array("\007" => "\\$sep")), $sep);
-      }
-      return $result;
+    /**
+     * Join indexed data array to a vcard quoted string
+     *
+     * @param array Field data
+     * @param string Separator
+     *
+     * @return string Joined and quoted string
+     */
+    private static function vcard_quote($s, $sep = ';')
+    {
+        if (is_array($s)) {
+            foreach($s as $part) {
+                $r[] = self::vcard_quote($part, $sep);
+            }
+            return(implode($sep, (array)$r));
+        }
+
+        return strtr($s, array('\\' => '\\\\', "\r" => '', "\n" => '\n', ',' => '\,', ';' => '\;'));
     }
-    else {
-      return strtr($s, array("\r" => '', '\\\\' => '\\', '\n' => "\n", '\N' => "\n", '\,' => ',', '\;' => ';', '\:' => ':'));
+
+    /**
+     * Split quoted string
+     *
+     * @param string vCard string to split
+     * @param string Separator char/string
+     *
+     * @return array List with splited values
+     */
+    private static function vcard_unquote($s, $sep = ';')
+    {
+        // break string into parts separated by $sep
+        if (!empty($sep)) {
+            // Handle properly backslash escaping (#1488896)
+            $rep1 = array("\\\\" => "\010", "\\$sep" => "\007");
+            $rep2 = array("\007" => "\\$sep", "\010" => "\\\\");
+
+            if (count($parts = explode($sep, strtr($s, $rep1))) > 1) {
+                foreach ($parts as $s) {
+                    $result[] = self::vcard_unquote(strtr($s, $rep2));
+                }
+                return $result;
+            }
+        }
+
+        return strtr($s, array("\r" => '', '\\\\' => '\\', '\n' => "\n", '\N' => "\n", '\,' => ',', '\;' => ';'));
     }
-  }
-
-
-  /**
-   * Check if vCard entry is empty: empty string or an array with
-   * all entries empty.
-   *
-   * @param mixed $value Attribute value (string or array)
-   *
-   * @return bool True if the value is empty, False otherwise
-   */
-  private static function is_empty($value)
-  {
-    foreach ((array)$value as $v) {
-      if (((string)$v) !== '') {
-        return false;
-      }
+
+    /**
+     * Check if vCard entry is empty: empty string or an array with
+     * all entries empty.
+     *
+     * @param mixed $value Attribute value (string or array)
+     *
+     * @return bool True if the value is empty, False otherwise
+     */
+    private static function is_empty($value)
+    {
+        foreach ((array)$value as $v) {
+            if (((string)$v) !== '') {
+                return false;
+            }
+        }
+
+        return true;
     }
 
-    return true;
-  }
-
-  /**
-   * Extract array values by a filter
-   *
-   * @param array Array to filter
-   * @param keys Array or comma separated list of values to keep
-   * @param boolean Invert key selection: remove the listed values
-   * @return array The filtered array
-   */
-  private static function array_filter($arr, $values, $inverse = false)
-  {
-    if (!is_array($values))
-      $values = explode(',', $values);
-
-    $result = array();
-    $keep = array_flip((array)$values);
-    foreach ($arr as $key => $val)
-      if ($inverse != isset($keep[strtolower($val)]))
-        $result[$key] = $val;
-
-    return $result;
-  }
-
-  /**
-   * Returns UNICODE type based on BOM (Byte Order Mark)
-   *
-   * @param string Input string to test
-   * @return string Detected encoding
-   */
-  private static function detect_encoding($string)
-  {
-    $fallback = rcube::get_instance()->config->get('default_charset', 'ISO-8859-1'); // fallback to Latin-1
-
-    return rcube_charset::detect($string, $fallback);
-  }
+    /**
+     * Extract array values by a filter
+     *
+     * @param array Array to filter
+     * @param keys Array or comma separated list of values to keep
+     * @param boolean Invert key selection: remove the listed values
+     *
+     * @return array The filtered array
+     */
+    private static function array_filter($arr, $values, $inverse = false)
+    {
+        if (!is_array($values)) {
+            $values = explode(',', $values);
+        }
+
+        $result = array();
+        $keep   = array_flip((array)$values);
 
+        foreach ($arr as $key => $val) {
+            if ($inverse != isset($keep[strtolower($val)])) {
+                $result[$key] = $val;
+            }
+        }
+
+        return $result;
+    }
+
+    /**
+     * Returns UNICODE type based on BOM (Byte Order Mark)
+     *
+     * @param string Input string to test
+     *
+     * @return string Detected encoding
+     */
+    private static function detect_encoding($string)
+    {
+        $fallback = rcube::get_instance()->config->get('default_charset', 'ISO-8859-1'); // fallback to Latin-1
+
+        return rcube_charset::detect($string, $fallback);
+    }
 }
diff --git a/lib/ext/Roundcube/rcube_washtml.php b/lib/ext/Roundcube/rcube_washtml.php
new file mode 100644
index 0000000..715c460
--- /dev/null
+++ b/lib/ext/Roundcube/rcube_washtml.php
@@ -0,0 +1,451 @@
+<?php
+
+/**
+ +-----------------------------------------------------------------------+
+ | This file is part of the Roundcube Webmail client                     |
+ | Copyright (C) 2008-2012, The Roundcube Dev Team                       |
+ |                                                                       |
+ | Licensed under the GNU General Public License version 3 or            |
+ | any later version with exceptions for skins & plugins.                |
+ | See the README file for a full license statement.                     |
+ |                                                                       |
+ | PURPOSE:                                                              |
+ |   Utility class providing HTML sanityzer (based on Washtml class)     |
+ +-----------------------------------------------------------------------+
+ | Author: Thomas Bruederli <roundcube at gmail.com>                        |
+ | Author: Aleksander Machniak <alec at alec.pl>                            |
+ | Author: Frederic Motte <fmotte at ubixis.com>                            |
+ +-----------------------------------------------------------------------+
+ */
+
+/**
+ *                Washtml, a HTML sanityzer.
+ *
+ * Copyright (c) 2007 Frederic Motte <fmotte at ubixis.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * OVERVIEW:
+ *
+ * Wahstml take an untrusted HTML and return a safe html string.
+ *
+ * SYNOPSIS:
+ *
+ * $washer = new washtml($config);
+ * $washer->wash($html);
+ * It return a sanityzed string of the $html parameter without html and head tags.
+ * $html is a string containing the html code to wash.
+ * $config is an array containing options:
+ *   $config['allow_remote'] is a boolean to allow link to remote images.
+ *   $config['blocked_src'] string with image-src to be used for blocked remote images
+ *   $config['show_washed'] is a boolean to include washed out attributes as x-washed
+ *   $config['cid_map'] is an array where cid urls index urls to replace them.
+ *   $config['charset'] is a string containing the charset of the HTML document if it is not defined in it.
+ * $washer->extlinks is a reference to a boolean that is set to true if remote images were removed. (FE: show remote images link)
+ *
+ * INTERNALS:
+ *
+ * Only tags and attributes in the static lists $html_elements and $html_attributes
+ * are kept, inline styles are also filtered: all style identifiers matching
+ * /[a-z\-]/i are allowed. Values matching colors, sizes, /[a-z\-]/i and safe
+ * urls if allowed and cid urls if mapped are kept.
+ *
+ * Roundcube Changes:
+ * - added $block_elements
+ * - changed $ignore_elements behaviour
+ * - added RFC2397 support
+ * - base URL support
+ * - invalid HTML comments removal before parsing
+ * - "fixing" unitless CSS values for XHTML output
+ * - base url resolving
+ */
+
+/**
+ * Utility class providing HTML sanityzer
+ *
+ * @package    Framework
+ * @subpackage Utils
+ */
+class rcube_washtml
+{
+    /* Allowed HTML elements (default) */
+    static $html_elements = array('a', 'abbr', 'acronym', 'address', 'area', 'b',
+        'basefont', 'bdo', 'big', 'blockquote', 'br', 'caption', 'center',
+        'cite', 'code', 'col', 'colgroup', 'dd', 'del', 'dfn', 'dir', 'div', 'dl',
+        'dt', 'em', 'fieldset', 'font', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'hr', 'i',
+        'ins', 'label', 'legend', 'li', 'map', 'menu', 'nobr', 'ol', 'p', 'pre', 'q',
+        's', 'samp', 'small', 'span', 'strike', 'strong', 'sub', 'sup', 'table',
+        'tbody', 'td', 'tfoot', 'th', 'thead', 'tr', 'tt', 'u', 'ul', 'var', 'wbr', 'img',
+        // form elements
+        'button', 'input', 'textarea', 'select', 'option', 'optgroup'
+    );
+
+    /* Ignore these HTML tags and their content */
+    static $ignore_elements = array('script', 'applet', 'embed', 'object', 'style');
+
+    /* Allowed HTML attributes */
+    static $html_attribs = array('name', 'class', 'title', 'alt', 'width', 'height',
+        'align', 'nowrap', 'col', 'row', 'id', 'rowspan', 'colspan', 'cellspacing',
+        'cellpadding', 'valign', 'bgcolor', 'color', 'border', 'bordercolorlight',
+        'bordercolordark', 'face', 'marginwidth', 'marginheight', 'axis', 'border',
+        'abbr', 'char', 'charoff', 'clear', 'compact', 'coords', 'vspace', 'hspace',
+        'cellborder', 'size', 'lang', 'dir', 'usemap', 'shape', 'media',
+        // attributes of form elements
+        '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',
+    );
+
+    /* State for linked objects in HTML */
+    public $extlinks = false;
+
+    /* Current settings */
+    private $config = array();
+
+    /* Registered callback functions for tags */
+    private $handlers = array();
+
+    /* Allowed HTML elements */
+    private $_html_elements = array();
+
+    /* 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();
+
+    /* Allowed HTML attributes */
+    private $_html_attribs = array();
+
+
+    /**
+     * Class constructor
+     */
+    public function __construct($p = array())
+    {
+        $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);
+
+        unset($p['html_elements'], $p['html_attribs'], $p['ignore_elements'], $p['block_elements']);
+
+        $this->config = $p + array('show_washed' => true, 'allow_remote' => false, 'cid_map' => array());
+    }
+
+    /**
+     * Register a callback function for a certain tag
+     */
+    public function add_callback($tagName, $callback)
+    {
+        $this->handlers[$tagName] = $callback;
+    }
+
+    /**
+     * Check CSS style
+     */
+    private function wash_style($style)
+    {
+        $s = '';
+
+        foreach (explode(';', $style) as $declaration) {
+            if (preg_match('/^\s*([a-z\-]+)\s*:\s*(.*)\s*$/i', $declaration, $match)) {
+                $cssid = $match[1];
+                $str   = $match[2];
+                $value = '';
+
+                while (sizeof($str) > 0 &&
+                    preg_match('/^(url\(\s*[\'"]?([^\'"\)]*)[\'"]?\s*\)'./*1,2*/
+                        '|rgb\(\s*[0-9]+\s*,\s*[0-9]+\s*,\s*[0-9]+\s*\)'.
+                        '|-?[0-9.]+\s*(em|ex|px|cm|mm|in|pt|pc|deg|rad|grad|ms|s|hz|khz|%)?'.
+                        '|#[0-9a-f]{3,6}'.
+                        '|[a-z0-9", -]+'.
+                        ')\s*/i', $str, $match)
+                ) {
+                    if ($match[2]) {
+                        if (($src = $this->config['cid_map'][$match[2]])
+                            || ($src = $this->config['cid_map'][$this->config['base_url'].$match[2]])
+                        ) {
+                            $value .= ' url('.htmlspecialchars($src, ENT_QUOTES) . ')';
+                        }
+                        else if (preg_match('!^(https?:)?//[a-z0-9/._+-]+$!i', $match[2], $url)) {
+                            if ($this->config['allow_remote']) {
+                                $value .= ' url('.htmlspecialchars($url[0], ENT_QUOTES).')';
+                            }
+                            else {
+                                $this->extlinks = true;
+                            }
+                        }
+                        else if (preg_match('/^data:.+/i', $match[2])) { // RFC2397
+                            $value .= ' url('.htmlspecialchars($match[2], ENT_QUOTES).')';
+                        }
+                    }
+                    else {
+                        // whitelist ?
+                        $value .= ' ' . $match[0];
+
+                        // #1488535: Fix size units, so width:800 would be changed to width:800px
+                        if (preg_match('/(left|right|top|bottom|width|height)/i', $cssid)
+                            && preg_match('/^[0-9]+$/', $match[0])
+                        ) {
+                            $value .= 'px';
+                        }
+                    }
+
+                    $str = substr($str, strlen($match[0]));
+                }
+
+                if (isset($value[0])) {
+                    $s .= ($s?' ':'') . $cssid . ':' . $value . ';';
+                }
+            }
+        }
+
+        return $s;
+    }
+
+    /**
+     * Take a node and return allowed attributes and check values
+     */
+    private function wash_attribs($node)
+    {
+        $t = '';
+        $washed = '';
+
+        foreach ($node->attributes as $key => $plop) {
+            $key   = strtolower($key);
+            $value = $node->getAttribute($key);
+
+            if (isset($this->_html_attribs[$key]) ||
+                ($key == 'href' && !preg_match('!^(javascript|vbscript|data:text)!i', $value)
+                    && preg_match('!^([a-z][a-z0-9.+-]+:|//|#).+!i', $value))
+            ) {
+                $t .= ' ' . $key . '="' . htmlspecialchars($value, ENT_QUOTES) . '"';
+            }
+            else if ($key == 'style' && ($style = $this->wash_style($value))) {
+                $quot = strpos($style, '"') !== false ? "'" : '"';
+                $t .= ' style=' . $quot . $style . $quot;
+            }
+            else if ($key == 'background' || ($key == 'src' && strtolower($node->tagName) == 'img')) { //check tagName anyway
+                if (($src = $this->config['cid_map'][$value])
+                    || ($src = $this->config['cid_map'][$this->config['base_url'].$value])
+                ) {
+                    $t .= ' ' . $key . '="' . htmlspecialchars($src, ENT_QUOTES) . '"';
+                }
+                else if (preg_match('/^(http|https|ftp):.+/i', $value)) {
+                    if ($this->config['allow_remote']) {
+                        $t .= ' ' . $key . '="' . htmlspecialchars($value, ENT_QUOTES) . '"';
+                    }
+                    else {
+                        $this->extlinks = true;
+                        if ($this->config['blocked_src']) {
+                            $t .= ' ' . $key . '="' . htmlspecialchars($this->config['blocked_src'], ENT_QUOTES) . '"';
+                        }
+                    }
+                }
+                else if (preg_match('/^data:.+/i', $value)) { // RFC2397
+                    $t .= ' ' . $key . '="' . htmlspecialchars($value, ENT_QUOTES) . '"';
+                }
+            }
+            else {
+                $washed .= ($washed ? ' ' : '') . $key;
+            }
+        }
+
+        return $t . ($washed && $this->config['show_washed'] ? ' x-washed="'.$washed.'"' : '');
+    }
+
+    /**
+     * The main loop that recurse on a node tree.
+     * It output only allowed tags with allowed attributes
+     * and allowed inline styles
+     */
+    private function dumpHtml($node)
+    {
+        if (!$node->hasChildNodes()) {
+            return '';
+        }
+
+        $node = $node->firstChild;
+        $dump = '';
+
+        do {
+            switch($node->nodeType) {
+            case XML_ELEMENT_NODE: //Check element
+                $tagName = strtolower($node->tagName);
+                if ($callback = $this->handlers[$tagName]) {
+                    $dump .= call_user_func($callback, $tagName,
+                        $this->wash_attribs($node), $this->dumpHtml($node), $this);
+                }
+                else if (isset($this->_html_elements[$tagName])) {
+                    $content = $this->dumpHtml($node);
+                    $dump .= '<' . $tagName . $this->wash_attribs($node) .
+                        ($content != '' || isset($this->_block_elements[$tagName]) ? ">$content</$tagName>" : ' />');
+                }
+                else if (isset($this->_ignore_elements[$tagName])) {
+                    $dump .= '<!-- ' . htmlspecialchars($tagName, ENT_QUOTES) . ' not allowed -->';
+                }
+                else {
+                    $dump .= '<!-- ' . htmlspecialchars($tagName, ENT_QUOTES) . ' ignored -->';
+                    $dump .= $this->dumpHtml($node); // ignore tags not its content
+                }
+                break;
+
+            case XML_CDATA_SECTION_NODE:
+                $dump .= $node->nodeValue;
+                break;
+
+            case XML_TEXT_NODE:
+                $dump .= htmlspecialchars($node->nodeValue);
+                break;
+
+            case XML_HTML_DOCUMENT_NODE:
+                $dump .= $this->dumpHtml($node);
+                break;
+
+            case XML_DOCUMENT_TYPE_NODE:
+                break;
+
+            default:
+                $dump . '<!-- node type ' . $node->nodeType . ' -->';
+            }
+        } while($node = $node->nextSibling);
+
+        return $dump;
+    }
+
+    /**
+     * Main function, give it untrusted HTML, tell it if you allow loading
+     * remote images and give it a map to convert "cid:" urls.
+     */
+    public function wash($html)
+    {
+        // Charset seems to be ignored (probably if defined in the HTML document)
+        $node = new DOMDocument('1.0', $this->config['charset']);
+        $this->extlinks = false;
+
+        $html = $this->cleanup($html);
+
+        // Find base URL for images
+        if (preg_match('/<base\s+href=[\'"]*([^\'"]+)/is', $html, $matches)) {
+            $this->config['base_url'] = $matches[1];
+        }
+        else {
+            $this->config['base_url'] = '';
+        }
+
+        @$node->loadHTML($html);
+        return $this->dumpHtml($node);
+    }
+
+    /**
+     * Getter for config parameters
+     */
+    public function get_config($prop)
+    {
+        return $this->config[$prop];
+    }
+
+    /**
+     * Clean HTML input
+     */
+    private function cleanup($html)
+    {
+        // special replacements (not properly handled by washtml class)
+        $html_search = array(
+            '/(<\/nobr>)(\s+)(<nobr>)/i',       // space(s) between <NOBR>
+            '/<title[^>]*>[^<]*<\/title>/i',    // PHP bug #32547 workaround: remove title tag
+            '/^(\0\0\xFE\xFF|\xFF\xFE\0\0|\xFE\xFF|\xFF\xFE|\xEF\xBB\xBF)/',    // byte-order mark (only outlook?)
+            '/<html\s[^>]+>/i',                 // washtml/DOMDocument cannot handle xml namespaces
+        );
+
+        $html_replace = array(
+            '\\1'.'   '.'\\3',
+            '',
+            '',
+            '<html>',
+        );
+        $html = preg_replace($html_search, $html_replace, trim($html));
+
+        // PCRE errors handling (#1486856), should we use something like for every preg_* use?
+        if ($html === null && ($preg_error = preg_last_error()) != PREG_NO_ERROR) {
+            $errstr = "Could not clean up HTML message! PCRE Error: $preg_error.";
+
+            if ($preg_error == PREG_BACKTRACK_LIMIT_ERROR) {
+                $errstr .= " Consider raising pcre.backtrack_limit!";
+            }
+            if ($preg_error == PREG_RECURSION_LIMIT_ERROR) {
+                $errstr .= " Consider raising pcre.recursion_limit!";
+            }
+
+            rcube::raise_error(array('code' => 620, 'type' => 'php',
+                'line' => __LINE__, 'file' => __FILE__,
+                'message' => $errstr), true, false);
+            return '';
+        }
+
+        // fix (unknown/malformed) HTML tags before "wash"
+        $html = preg_replace_callback('/(<[\/]*)([^\s>]+)/', array($this, 'html_tag_callback'), $html);
+
+        // Remove invalid HTML comments (#1487759)
+        // Don't remove valid conditional comments
+        $html = preg_replace('/<!--[^->[\n]*>/', '', $html);
+
+        // turn relative into absolute urls
+        $html = self::resolve_base($html);
+
+        return $html;
+    }
+
+    /**
+     * Callback function for HTML tags fixing
+     */
+    public static function html_tag_callback($matches)
+    {
+        $tagname = $matches[2];
+        $tagname = preg_replace(array(
+            '/:.*$/',               // Microsoft's Smart Tags <st1:xxxx>
+            '/[^a-z0-9_\[\]\!-]/i', // forbidden characters
+        ), '', $tagname);
+
+        return $matches[1] . $tagname;
+    }
+
+    /**
+     * Convert all relative URLs according to a <base> in HTML
+     */
+    public static function resolve_base($body)
+    {
+        // check for <base href=...>
+        if (preg_match('!(<base.*href=["\']?)([hftps]{3,5}://[a-z0-9/.%-]+)!i', $body, $regs)) {
+            $replacer = new rcube_base_replacer($regs[2]);
+            $body     = $replacer->replace($body);
+        }
+
+        return $body;
+    }
+}
+
diff --git a/lib/ext/html2text.php b/lib/ext/html2text.php
deleted file mode 100644
index 761d61f..0000000
--- a/lib/ext/html2text.php
+++ /dev/null
@@ -1,746 +0,0 @@
-<?php
-
-/*************************************************************************
- *                                                                       *
- * class.html2text.inc                                                   *
- *                                                                       *
- *************************************************************************
- *                                                                       *
- * Converts HTML to formatted plain text                                 *
- *                                                                       *
- * Copyright (c) 2005-2007 Jon Abernathy <jon at chuggnutt.com>             *
- * All rights reserved.                                                  *
- *                                                                       *
- * This script is free software; you can redistribute it and/or modify   *
- * it under the terms of the GNU General Public License as published by  *
- * the Free Software Foundation; either version 2 of the License, or     *
- * (at your option) any later version.                                   *
- *                                                                       *
- * The GNU General Public License can be found at                        *
- * http://www.gnu.org/copyleft/gpl.html.                                 *
- *                                                                       *
- * This script is distributed in the hope that it will be useful,        *
- * but WITHOUT ANY WARRANTY; without even the implied warranty of        *
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the          *
- * GNU General Public License for more details.                          *
- *                                                                       *
- * Author(s): Jon Abernathy <jon at chuggnutt.com>                          *
- *                                                                       *
- * Last modified: 08/08/07                                               *
- *                                                                       *
- *************************************************************************/
-
-
-/**
- *  Takes HTML and converts it to formatted, plain text.
- *
- *  Thanks to Alexander Krug (http://www.krugar.de/) to pointing out and
- *  correcting an error in the regexp search array. Fixed 7/30/03.
- *
- *  Updated set_html() function's file reading mechanism, 9/25/03.
- *
- *  Thanks to Joss Sanglier (http://www.dancingbear.co.uk/) for adding
- *  several more HTML entity codes to the $search and $replace arrays.
- *  Updated 11/7/03.
- *
- *  Thanks to Darius Kasperavicius (http://www.dar.dar.lt/) for
- *  suggesting the addition of $allowed_tags and its supporting function
- *  (which I slightly modified). Updated 3/12/04.
- *
- *  Thanks to Justin Dearing for pointing out that a replacement for the
- *  <TH> tag was missing, and suggesting an appropriate fix.
- *  Updated 8/25/04.
- *
- *  Thanks to Mathieu Collas (http://www.myefarm.com/) for finding a
- *  display/formatting bug in the _build_link_list() function: email
- *  readers would show the left bracket and number ("[1") as part of the
- *  rendered email address.
- *  Updated 12/16/04.
- *
- *  Thanks to Wojciech Bajon (http://histeria.pl/) for submitting code
- *  to handle relative links, which I hadn't considered. I modified his
- *  code a bit to handle normal HTTP links and MAILTO links. Also for
- *  suggesting three additional HTML entity codes to search for.
- *  Updated 03/02/05.
- *
- *  Thanks to Jacob Chandler for pointing out another link condition
- *  for the _build_link_list() function: "https".
- *  Updated 04/06/05.
- *
- *  Thanks to Marc Bertrand (http://www.dresdensky.com/) for
- *  suggesting a revision to the word wrapping functionality; if you
- *  specify a $width of 0 or less, word wrapping will be ignored.
- *  Updated 11/02/06.
- *
- *  *** Big housecleaning updates below:
- *
- *  Thanks to Colin Brown (http://www.sparkdriver.co.uk/) for
- *  suggesting the fix to handle </li> and blank lines (whitespace).
- *  Christian Basedau (http://www.movetheweb.de/) also suggested the
- *  blank lines fix.
- *
- *  Special thanks to Marcus Bointon (http://www.synchromedia.co.uk/),
- *  Christian Basedau, Norbert Laposa (http://ln5.co.uk/),
- *  Bas van de Weijer, and Marijn van Butselaar
- *  for pointing out my glaring error in the <th> handling. Marcus also
- *  supplied a host of fixes.
- *
- *  Thanks to Jeffrey Silverman (http://www.newtnotes.com/) for pointing
- *  out that extra spaces should be compressed--a problem addressed with
- *  Marcus Bointon's fixes but that I had not yet incorporated.
- *
- *  Thanks to Daniel Schledermann (http://www.typoconsult.dk/) for
- *  suggesting a valuable fix with <a> tag handling.
- *
- *  Thanks to Wojciech Bajon (again!) for suggesting fixes and additions,
- *  including the <a> tag handling that Daniel Schledermann pointed
- *  out but that I had not yet incorporated. I haven't (yet)
- *  incorporated all of Wojciech's changes, though I may at some
- *  future time.
- *
- *  *** End of the housecleaning updates. Updated 08/08/07.
- *
- *  @author Jon Abernathy <jon at chuggnutt.com>
- *  @version 1.0.0
- *  @since PHP 4.0.2
- */
-class html2text
-{
-
-    /**
-     *  Contains the HTML content to convert.
-     *
-     *  @var string $html
-     *  @access public
-     */
-    var $html;
-
-    /**
-     *  Contains the converted, formatted text.
-     *
-     *  @var string $text
-     *  @access public
-     */
-    var $text;
-
-    /**
-     *  Maximum width of the formatted text, in columns.
-     *
-     *  Set this value to 0 (or less) to ignore word wrapping
-     *  and not constrain text to a fixed-width column.
-     *
-     *  @var integer $width
-     *  @access public
-     */
-    var $width = 70;
-
-    /**
-     *  List of preg* regular expression patterns to search for,
-     *  used in conjunction with $replace.
-     *
-     *  @var array $search
-     *  @access public
-     *  @see $replace
-     */
-    var $search = array(
-        "/\r/",                                  // Non-legal carriage return
-        "/[\n\t]+/",                             // Newlines and tabs
-        '/<head[^>]*>.*?<\/head>/i',             // <head>
-        '/<script[^>]*>.*?<\/script>/i',         // <script>s -- which strip_tags supposedly has problems with
-        '/<style[^>]*>.*?<\/style>/i',           // <style>s -- which strip_tags supposedly has problems with
-        '/<p[^>]*>/i',                           // <P>
-        '/<br[^>]*>/i',                          // <br>
-        '/<i[^>]*>(.*?)<\/i>/i',                 // <i>
-        '/<em[^>]*>(.*?)<\/em>/i',               // <em>
-        '/(<ul[^>]*>|<\/ul>)/i',                 // <ul> and </ul>
-        '/(<ol[^>]*>|<\/ol>)/i',                 // <ol> and </ol>
-        '/<li[^>]*>(.*?)<\/li>/i',               // <li> and </li>
-        '/<li[^>]*>/i',                          // <li>
-        '/<hr[^>]*>/i',                          // <hr>
-        '/<div[^>]*>/i',                         // <div>
-        '/(<table[^>]*>|<\/table>)/i',           // <table> and </table>
-        '/(<tr[^>]*>|<\/tr>)/i',                 // <tr> and </tr>
-        '/<td[^>]*>(.*?)<\/td>/i',               // <td> and </td>
-    );
-
-    /**
-     *  List of pattern replacements corresponding to patterns searched.
-     *
-     *  @var array $replace
-     *  @access public
-     *  @see $search
-     */
-    var $replace = array(
-        '',                                     // Non-legal carriage return
-        ' ',                                    // Newlines and tabs
-        '',                                     // <head>
-        '',                                     // <script>s -- which strip_tags supposedly has problems with
-        '',                                     // <style>s -- which strip_tags supposedly has problems with
-        "\n\n",                                 // <P>
-        "\n",                                   // <br>
-        '_\\1_',                                // <i>
-        '_\\1_',                                // <em>
-        "\n\n",                                 // <ul> and </ul>
-        "\n\n",                                 // <ol> and </ol>
-        "\t* \\1\n",                            // <li> and </li>
-        "\n\t* ",                               // <li>
-        "\n-------------------------\n",        // <hr>
-        "<div>\n",                              // <div>
-        "\n\n",                                 // <table> and </table>
-        "\n",                                   // <tr> and </tr>
-        "\t\t\\1\n",                            // <td> and </td>
-    );
-
-    /**
-     *  List of preg* regular expression patterns to search for,
-     *  used in conjunction with $ent_replace.
-     *
-     *  @var array $ent_search
-     *  @access public
-     *  @see $ent_replace
-     */
-    var $ent_search = array(
-        '/&(nbsp|#160);/i',                      // Non-breaking space
-        '/&(quot|rdquo|ldquo|#8220|#8221|#147|#148);/i',
-                                         // Double quotes
-        '/&(apos|rsquo|lsquo|#8216|#8217);/i',   // Single quotes
-        '/>/i',                               // Greater-than
-        '/</i',                               // Less-than
-        '/&(copy|#169);/i',                      // Copyright
-        '/&(trade|#8482|#153);/i',               // Trademark
-        '/&(reg|#174);/i',                       // Registered
-        '/&(mdash|#151|#8212);/i',               // mdash
-        '/&(ndash|minus|#8211|#8722);/i',        // ndash
-        '/&(bull|#149|#8226);/i',                // Bullet
-        '/&(pound|#163);/i',                     // Pound sign
-        '/&(euro|#8364);/i',                     // Euro sign
-        '/&(amp|#38);/i',                        // Ampersand: see _converter()
-        '/[ ]{2,}/',                             // Runs of spaces, post-handling
-    );
-
-    /**
-     *  List of pattern replacements corresponding to patterns searched.
-     *
-     *  @var array $ent_replace
-     *  @access public
-     *  @see $ent_search
-     */
-    var $ent_replace = array(
-        ' ',                                    // Non-breaking space
-        '"',                                    // Double quotes
-        "'",                                    // Single quotes
-        '>',
-        '<',
-        '(c)',
-        '(tm)',
-        '(R)',
-        '--',
-        '-',
-        '*',
-        '£',
-        'EUR',                                  // Euro sign. € ?
-        '|+|amp|+|',                            // Ampersand: see _converter()
-        ' ',                                    // Runs of spaces, post-handling
-    );
-
-    /**
-     *  List of preg* regular expression patterns to search for
-     *  and replace using callback function.
-     *
-     *  @var array $callback_search
-     *  @access public
-     */
-    var $callback_search = array(
-        '/<(a) [^>]*href=("|\')([^"\']+)\2[^>]*>(.*?)<\/a>/i', // <a href="">
-        '/<(h)[123456]( [^>]*)?>(.*?)<\/h[123456]>/i',         // h1 - h6
-        '/<(b)( [^>]*)?>(.*?)<\/b>/i',                         // <b>
-        '/<(strong)( [^>]*)?>(.*?)<\/strong>/i',               // <strong>
-        '/<(th)( [^>]*)?>(.*?)<\/th>/i',                       // <th> and </th>
-    );
-
-   /**
-    *  List of preg* regular expression patterns to search for in PRE body,
-    *  used in conjunction with $pre_replace.
-    *
-    *  @var array $pre_search
-    *  @access public
-    *  @see $pre_replace
-    */
-    var $pre_search = array(
-        "/\n/",
-        "/\t/",
-        '/ /',
-        '/<pre[^>]*>/',
-        '/<\/pre>/'
-    );
-
-    /**
-     *  List of pattern replacements corresponding to patterns searched for PRE body.
-     *
-     *  @var array $pre_replace
-     *  @access public
-     *  @see $pre_search
-     */
-    var $pre_replace = array(
-        '<br>',
-        '    ',
-        ' ',
-        '',
-        ''
-    );
-
-    /**
-     *  Contains a list of HTML tags to allow in the resulting text.
-     *
-     *  @var string $allowed_tags
-     *  @access public
-     *  @see set_allowed_tags()
-     */
-    var $allowed_tags = '';
-
-    /**
-     *  Contains the base URL that relative links should resolve to.
-     *
-     *  @var string $url
-     *  @access public
-     */
-    var $url;
-
-    /**
-     *  Indicates whether content in the $html variable has been converted yet.
-     *
-     *  @var boolean $_converted
-     *  @access private
-     *  @see $html, $text
-     */
-    var $_converted = false;
-
-    /**
-     *  Contains URL addresses from links to be rendered in plain text.
-     *
-     *  @var array $_link_list
-     *  @access private
-     *  @see _build_link_list()
-     */
-    var $_link_list = array();
-
-    /**
-     * Boolean flag, true if a table of link URLs should be listed after the text.
-     *
-     * @var boolean $_do_links
-     * @access private
-     * @see html2text()
-     */
-    var $_do_links = true;
-
-    /**
-     *  Constructor.
-     *
-     *  If the HTML source string (or file) is supplied, the class
-     *  will instantiate with that source propagated, all that has
-     *  to be done it to call get_text().
-     *
-     *  @param string $source HTML content
-     *  @param boolean $from_file Indicates $source is a file to pull content from
-     *  @param boolean $do_links Indicate whether a table of link URLs is desired
-     *  @param integer $width Maximum width of the formatted text, 0 for no limit
-     *  @access public
-     *  @return void
-     */
-    function html2text( $source = '', $from_file = false, $do_links = true, $width = 75 )
-    {
-        if ( !empty($source) ) {
-            $this->set_html($source, $from_file);
-        }
-
-        $this->set_base_url();
-        $this->_do_links = $do_links;
-        $this->width = $width;
-    }
-
-    /**
-     *  Loads source HTML into memory, either from $source string or a file.
-     *
-     *  @param string $source HTML content
-     *  @param boolean $from_file Indicates $source is a file to pull content from
-     *  @access public
-     *  @return void
-     */
-    function set_html( $source, $from_file = false )
-    {
-        if ( $from_file && file_exists($source) ) {
-            $this->html = file_get_contents($source);
-        }
-        else
-            $this->html = $source;
-
-        $this->_converted = false;
-    }
-
-    /**
-     *  Returns the text, converted from HTML.
-     *
-     *  @access public
-     *  @return string
-     */
-    function get_text()
-    {
-        if ( !$this->_converted ) {
-            $this->_convert();
-        }
-
-        return $this->text;
-    }
-
-    /**
-     *  Prints the text, converted from HTML.
-     *
-     *  @access public
-     *  @return void
-     */
-    function print_text()
-    {
-        print $this->get_text();
-    }
-
-    /**
-     *  Alias to print_text(), operates identically.
-     *
-     *  @access public
-     *  @return void
-     *  @see print_text()
-     */
-    function p()
-    {
-        print $this->get_text();
-    }
-
-    /**
-     *  Sets the allowed HTML tags to pass through to the resulting text.
-     *
-     *  Tags should be in the form "<p>", with no corresponding closing tag.
-     *
-     *  @access public
-     *  @return void
-     */
-    function set_allowed_tags( $allowed_tags = '' )
-    {
-        if ( !empty($allowed_tags) ) {
-            $this->allowed_tags = $allowed_tags;
-        }
-    }
-
-    /**
-     *  Sets a base URL to handle relative links.
-     *
-     *  @access public
-     *  @return void
-     */
-    function set_base_url( $url = '' )
-    {
-        if ( empty($url) ) {
-            if ( !empty($_SERVER['HTTP_HOST']) ) {
-                $this->url = 'http://' . $_SERVER['HTTP_HOST'];
-            } else {
-                $this->url = '';
-            }
-        } else {
-            // Strip any trailing slashes for consistency (relative
-            // URLs may already start with a slash like "/file.html")
-            if ( substr($url, -1) == '/' ) {
-                $url = substr($url, 0, -1);
-            }
-            $this->url = $url;
-        }
-    }
-
-    /**
-     *  Workhorse function that does actual conversion (calls _converter() method).
-     *
-     *  @access private
-     *  @return void
-     */
-    function _convert()
-    {
-        // Variables used for building the link list
-        $this->_link_list = array();
-
-        $text = trim(stripslashes($this->html));
-
-        // Convert HTML to TXT
-        $this->_converter($text);
-
-        // Add link list
-        if (!empty($this->_link_list)) {
-            $text .= "\n\nLinks:\n------\n";
-            foreach ($this->_link_list as $idx => $url) {
-                $text .= '[' . ($idx+1) . '] ' . $url . "\n";
-            }
-        }
-
-        $this->text = $text;
-
-        $this->_converted = true;
-    }
-
-    /**
-     *  Workhorse function that does actual conversion.
-     *
-     *  First performs custom tag replacement specified by $search and
-     *  $replace arrays. Then strips any remaining HTML tags, reduces whitespace
-     *  and newlines to a readable format, and word wraps the text to
-     *  $width characters.
-     *
-     *  @param string Reference to HTML content string
-     *
-     *  @access private
-     *  @return void
-     */
-    function _converter(&$text)
-    {
-        // Convert <BLOCKQUOTE> (before PRE!)
-        $this->_convert_blockquotes($text);
-
-        // Convert <PRE>
-        $this->_convert_pre($text);
-
-        // Run our defined tags search-and-replace
-        $text = preg_replace($this->search, $this->replace, $text);
-
-        // Run our defined tags search-and-replace with callback
-        $text = preg_replace_callback($this->callback_search, array('html2text', '_preg_callback'), $text);
-
-        // Strip any other HTML tags
-        $text = strip_tags($text, $this->allowed_tags);
-
-        // Run our defined entities/characters search-and-replace
-        $text = preg_replace($this->ent_search, $this->ent_replace, $text);
-
-        // Replace known html entities
-        $text = html_entity_decode($text, ENT_QUOTES, 'UTF-8');
-
-        // Remove unknown/unhandled entities (this cannot be done in search-and-replace block)
-        $text = preg_replace('/&([a-zA-Z0-9]{2,6}|#[0-9]{2,4});/', '', $text);
-
-        // Convert "|+|amp|+|" into "&", need to be done after handling of unknown entities
-        // This properly handles situation of "&quot;" in input string
-        $text = str_replace('|+|amp|+|', '&', $text);
-
-        // Bring down number of empty lines to 2 max
-        $text = preg_replace("/\n\s+\n/", "\n\n", $text);
-        $text = preg_replace("/[\n]{3,}/", "\n\n", $text);
-
-        // remove leading empty lines (can be produced by eg. P tag on the beginning)
-        $text = ltrim($text, "\n");
-
-        // Wrap the text to a readable format
-        // for PHP versions >= 4.0.2. Default width is 75
-        // If width is 0 or less, don't wrap the text.
-        if ( $this->width > 0 ) {
-            $text = wordwrap($text, $this->width);
-        }
-    }
-
-    /**
-     *  Helper function called by preg_replace() on link replacement.
-     *
-     *  Maintains an internal list of links to be displayed at the end of the
-     *  text, with numeric indices to the original point in the text they
-     *  appeared. Also makes an effort at identifying and handling absolute
-     *  and relative links.
-     *
-     *  @param string $link URL of the link
-     *  @param string $display Part of the text to associate number with
-     *  @access private
-     *  @return string
-     */
-    function _build_link_list( $link, $display )
-    {
-        if (!$this->_do_links || empty($link)) {
-            return $display;
-        }
-
-        // Ignored link types
-        if (preg_match('!^(javascript:|mailto:|#)!i', $link)) {
-            return $display;
-        }
-
-        if (preg_match('!^([a-z][a-z0-9.+-]+:)!i', $link)) {
-            $url = $link;
-        }
-        else {
-            $url = $this->url;
-            if (substr($link, 0, 1) != '/') {
-                $url .= '/';
-            }
-            $url .= "$link";
-        }
-
-        if (($index = array_search($url, $this->_link_list)) === false) {
-            $index = count($this->_link_list);
-            $this->_link_list[] = $url;
-        }
-
-        return $display . ' [' . ($index+1) . ']';
-    }
-
-    /**
-     *  Helper function for PRE body conversion.
-     *
-     *  @param string HTML content
-     *  @access private
-     */
-    function _convert_pre(&$text)
-    {
-        // get the content of PRE element
-        while (preg_match('/<pre[^>]*>(.*)<\/pre>/ismU', $text, $matches)) {
-            $this->pre_content = $matches[1];
-
-            // Run our defined tags search-and-replace with callback
-            $this->pre_content = preg_replace_callback($this->callback_search,
-                array('html2text', '_preg_callback'), $this->pre_content);
-
-            // convert the content
-            $this->pre_content = sprintf('<div><br>%s<br></div>',
-                preg_replace($this->pre_search, $this->pre_replace, $this->pre_content));
-
-            // replace the content (use callback because content can contain $0 variable)
-            $text = preg_replace_callback('/<pre[^>]*>.*<\/pre>/ismU',
-                array('html2text', '_preg_pre_callback'), $text, 1);
-
-            // free memory
-            $this->pre_content = '';
-        }
-    }
-
-    /**
-     *  Helper function for BLOCKQUOTE body conversion.
-     *
-     *  @param string HTML content
-     *  @access private
-     */
-    function _convert_blockquotes(&$text)
-    {
-        if (preg_match_all('/<\/*blockquote[^>]*>/i', $text, $matches, PREG_OFFSET_CAPTURE)) {
-            $level = 0;
-            $diff = 0;
-            foreach ($matches[0] as $m) {
-                if ($m[0][0] == '<' && $m[0][1] == '/') {
-                    $level--;
-                    if ($level < 0) {
-                        $level = 0; // malformed HTML: go to next blockquote
-                    }
-                    else if ($level > 0) {
-                        // skip inner blockquote
-                    }
-                    else {
-                        $end  = $m[1];
-                        $len  = $end - $taglen - $start;
-                        // Get blockquote content
-                        $body = substr($text, $start + $taglen - $diff, $len);
-
-                        // Set text width
-                        $p_width = $this->width;
-                        if ($this->width > 0) $this->width -= 2;
-                        // Convert blockquote content
-                        $body = trim($body);
-                        $this->_converter($body);
-                        // Add citation markers and create PRE block
-                        $body = preg_replace('/((^|\n)>*)/', '\\1> ', trim($body));
-                        $body = '<pre>' . htmlspecialchars($body) . '</pre>';
-                        // Re-set text width
-                        $this->width = $p_width;
-                        // Replace content
-                        $text = substr($text, 0, $start - $diff)
-                            . $body . substr($text, $end + strlen($m[0]) - $diff);
-
-                        $diff = $len + $taglen + strlen($m[0]) - strlen($body);
-                        unset($body);
-                    }
-                }
-                else {
-                    if ($level == 0) {
-                        $start = $m[1];
-                        $taglen = strlen($m[0]);
-                    }
-                    $level ++;
-                }
-            }
-        }
-    }
-
-    /**
-     *  Callback function for preg_replace_callback use.
-     *
-     *  @param  array PREG matches
-     *  @return string
-     */
-    private function _preg_callback($matches)
-    {
-        switch (strtolower($matches[1])) {
-        case 'b':
-        case 'strong':
-            return $this->_toupper($matches[3]);
-        case 'th':
-            return $this->_toupper("\t\t". $matches[3] ."\n");
-        case 'h':
-            return $this->_toupper("\n\n". $matches[3] ."\n\n");
-        case 'a':
-            // Remove spaces in URL (#1487805)
-            $url = str_replace(' ', '', $matches[3]);
-            return $this->_build_link_list($url, $matches[4]);
-        }
-    }
-
-    /**
-     *  Callback function for preg_replace_callback use in PRE content handler.
-     *
-     *  @param  array PREG matches
-     *  @return string
-     */
-    private function _preg_pre_callback($matches)
-    {
-        return $this->pre_content;
-    }
-
-    /**
-     * Strtoupper function with HTML tags and entities handling.
-     *
-     * @param string $str Text to convert
-     * @return string Converted text
-     */
-    private function _toupper($str)
-    {
-        // string can containg HTML tags
-        $chunks = preg_split('/(<[^>]*>)/', $str, null, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE);
-
-        // convert toupper only the text between HTML tags
-        foreach ($chunks as $idx => $chunk) {
-            if ($chunk[0] != '<') {
-                $chunks[$idx] = $this->_strtoupper($chunk);
-            }
-        }
-
-        return implode($chunks);
-    }
-
-    /**
-     * Strtoupper multibyte wrapper function with HTML entities handling.
-     *
-     * @param string $str Text to convert
-     * @return string Converted text
-     */
-    private function _strtoupper($str)
-    {
-        $str = html_entity_decode($str, ENT_COMPAT, RCUBE_CHARSET);
-
-        if (function_exists('mb_strtoupper'))
-            $str = mb_strtoupper($str);
-        else
-            $str = strtoupper($str);
-
-        $str = htmlspecialchars($str, ENT_COMPAT, RCUBE_CHARSET);
-
-        return $str;
-    }
-}
diff --git a/lib/kolab_sync_body_converter.php b/lib/kolab_sync_body_converter.php
index 69b24db..11e544c 100644
--- a/lib/kolab_sync_body_converter.php
+++ b/lib/kolab_sync_body_converter.php
@@ -106,7 +106,7 @@ class kolab_sync_body_converter
     {
         switch ($type) {
         case Syncroton_Model_EmailBody::TYPE_PLAINTEXT:
-            $txt = new html2text($this->text, false, true);
+            $txt = new rcube_html2text($this->text, false, true);
             return $txt->get_text();
         case Syncroton_Model_EmailBody::TYPE_RTF:
             // @TODO
diff --git a/lib/kolab_sync_data_email.php b/lib/kolab_sync_data_email.php
index 9a3c336..0bc3be8 100644
--- a/lib/kolab_sync_data_email.php
+++ b/lib/kolab_sync_data_email.php
@@ -1143,7 +1143,7 @@ class kolab_sync_data_email extends kolab_sync_data implements Syncroton_Data_ID
             }
 
             if ($part->ctype_secondary == 'html') {
-                $txt = new html2text($body, false, true);
+                $txt = new rcube_html2text($body, false, true);
                 $body = $txt->get_text();
             }
             else {





More information about the commits mailing list