From 99d9f50a0000447d0a752e6c43716237dc0da176 Mon Sep 17 00:00:00 2001
From: Thomas Bruederli <thomas@roundcube.net>
Date: Sun, 09 Sep 2012 14:23:56 -0400
Subject: [PATCH] Merge branch 'master' of github.com:roundcube/roundcubemail

---
 plugins/managesieve/tests/src/parser_include    |    7 
 plugins/managesieve/tests/src/parser_relational |    6 
 skins/classic/splitter.js                       |    4 
 plugins/acl/acl.php                             |    3 
 tests/Framework/ResultThread.php                |   20 
 plugins/managesieve/tests/src/parser_prefix     |    5 
 program/include/rcube_session.php               |   11 
 program/include/rcube_plugin.php                |    2 
 tests/Framework/Charset.php                     |   28 
 skins/larry/includes/links.html                 |    1 
 program/include/rcube_mime.php                  |    4 
 program/include/rcube_imap_cache.php            |   52 
 program/steps/settings/func.inc                 |   16 
 tests/Framework/Smtp.php                        |   20 
 skins/larry/templates/compose.html              |    2 
 program/include/html.php                        |   30 
 tests/Framework/BaseReplacer.php                |   20 
 program/include/rcube_ldap.php                  |   17 
 skins/larry/templates/message.html              |   63 
 tests/Framework/Rcube.php                       |   20 
 program/steps/mail/viewsource.inc               |    2 
 program/include/rcube_user.php                  |    2 
 program/include/rcube_config.php                |    5 
 program/steps/settings/save_prefs.inc           |    4 
 tests/Framework/ResultIndex.php                 |   20 
 plugins/managesieve/tests/src/parser_kep14.out  |    3 
 tests/Framework/Spellchecker.php                |   20 
 program/include/rcube_output_html.php           |   39 
 plugins/hide_blockquote/hide_blockquote.php     |    6 
 tests/Framework/MessagePart.php                 |   20 
 program/js/app.js                               |  186 +-
 program/steps/addressbook/search.inc            |    7 
 plugins/hide_blockquote/skins/larry/style.css   |    0 
 plugins/managesieve/tests/src/parser_variables  |   12 
 tests/Framework/MessageHeader.php               |   20 
 program/include/rcube_shared.inc                |   33 
 plugins/managesieve/tests/src/parser_imapflags  |    7 
 tests/Framework/ImapGeneric.php                 |   20 
 program/include/rcube_bc.inc                    |    5 
 tests/Framework/Imap.php                        |   20 
 tests/phpunit.xml                               |   37 
 program/include/rcube_storage.php               |    7 
 skins/classic/templates/message.html            |   15 
 plugins/managesieve/tests/src/parser_kep14      |    2 
 program/include/rcube_db.php                    |    4 
 program/js/list.js                              |    4 
 plugins/managesieve/tests/src/parser_body       |   17 
 plugins/managesieve/tests/Parser.php            |   54 
 skins/classic/templates/messageerror.html       |   15 
 program/steps/settings/folders.inc              |   12 
 skins/classic/templates/compose.html            |    2 
 plugins/managesieve/tests/src/parser_subaddress |   11 
 tests/Framework/ContentFilter.php               |   20 
 program/include/rcube_message.php               |   28 
 program/steps/mail/sendmail.inc                 |   15 
 skins/larry/iehacks.css                         |    2 
 program/include/rcube.php                       | 1485 ++++++++++---------
 plugins/managesieve/tests/Tokenizer.php         |   33 
 skins/classic/functions.js                      |   15 
 program/include/clisetup.php                    |   49 
 tests/src/photo.vcf                             |   45 
 program/lib/washtml.php                         |    8 
 skins/larry/ui.js                               |   62 
 program/js/common.js                            |    7 
 tests/Framework/Html.php                        |   46 
 plugins/password/drivers/virtualmin.php         |    4 
 program/steps/mail/compose.inc                  |   83 
 skins/larry/ie7hacks.css                        |   16 
 skins/larry/addressbook.css                     |    1 
 program/include/rcube_charset.php               |    6 
 skins/larry/templates/messagepreview.html       |    5 
 installer/rcube_install.php                     |   11 
 tests/MailFunc.php                              |  172 ++
 program/localization/pl_PL/labels.inc           |    1 
 skins/larry/images/contactpic_48px.png          |    0 
 program/include/rcube_smtp.php                  |    2 
 CHANGELOG                                       |   45 
 plugins/acl/skins/larry/templates/table.html    |    8 
 plugins/managesieve/tests/src/parser_vacation   |   12 
 tests/Framework/Mime.php                        |  123 +
 tests/Framework/StringReplacer.php              |   20 
 skins/larry/svggradients.css                    |    2 
 config/main.inc.php.dist                        |   27 
 tests/bootstrap.php                             |   35 
 program/steps/mail/headers.inc                  |    3 
 skins/larry/templates/messageerror.html         |   10 
 skins/classic/includes/links.html               |    1 
 program/steps/settings/save_folder.inc          |    5 
 skins/larry/mail.css                            |   90 
 tests/Framework/ResultSet.php                   |   20 
 program/include/rcube_utils.php                 |   67 
 tests/Framework/Shared.php                      |  204 ++
 plugins/acl/skins/larry/acl.css                 |    4 
 tests/Framework/Cache.php                       |   20 
 plugins/managesieve/tests/src/parser            |   52 
 tests/HtmlToText.php                            |   32 
 plugins/managesieve/tests/src/parser.out        |   52 
 program/steps/mail/func.inc                     |   17 
 program/steps/addressbook/export.inc            |    2 
 tests/Framework/Utils.php                       |  196 ++
 tests/Framework/Image.php                       |   20 
 program/include/rcube_plugin_api.php            |    4 
 program/lib/html2text.php                       |   34 
 plugins/virtuser_query/virtuser_query.php       |    5 
 skins/larry/images/contactpic_32px.png          |    0 
 program/include/rcube_browser.php               |    2 
 skins/larry/svggradient.php                     |    2 
 /dev/null                                       |   65 
 tests/Framework/VCard.php                       |   73 
 program/include/rcube_cache.php                 |    4 
 program/include/rcube_imap.php                  |   33 
 tests/Framework/Browser.php                     |   20 
 program/include/rcmail.php                      |   35 
 program/include/rcube_vcard.php                 |    3 
 program/localization/en_US/labels.inc           |   13 
 program/js/googiespell.js                       |    4 
 skins/larry/styles.css                          |    6 
 tests/Framework/User.php                        |   20 
 program/include/rcube_imap_generic.php          |   11 
 119 files changed, 3,142 insertions(+), 1,317 deletions(-)

diff --git a/CHANGELOG b/CHANGELOG
index 3441f1e..1d2225a 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,11 +1,28 @@
 CHANGELOG Roundcube Webmail
 ===========================
 
-- Fix focus on the list when list row is clicked (#1488600)
-- Added separate From and To columns apart from smart From/To column (#1486891)
-- Fix fallback to Larry skin when configured skin isn't available (#1488591)
-- Fix (workaround) delete operations with some versions of memcache (#1488592)
-- Fix (disable) request validation for spell and spell_html actions
+- Fix PLAIN authentication for some IMAP servers (#1488674)
+- Fix encoding vCard file when contains PHOTO;ENCODING=b (#1488683)
+- Fix focus issue in IE when selecting message row (#1488620)
+- Remove (too big) min-width on mail screen
+- Add full headers view in message preview window (#1488538)
+- Fix message display page issues - unified with message preview (#1488590, #1488642)
+- Fix displaying all headers when they contain malformed characters (#1488666)
+- Fix decoding of HTML messages with UTF-16 charset specified (#1488654)
+- Fix quota capability detection so it can be overwritten by a plugin (#1488655)
+- Added template object 'frame'
+- Fix identity selection on reply (#1488101)
+- Add option to enable HTML editor on forwarding (#1488517)
+- Add option to not include original message on reply, rename option top_posting to reply_mode (#1485149)
+- Fix Larry's messages list filter in IE (#1488632)
+- Fix more IE issues by disabling Compat. mode with X-UA-Compatible meta tag (#1488626)
+- Fix setting locales under Solaris - use additional .UTF-8 suffix (#1488628)
+- Fix email address validation for addresses with IP address in domain part
+- Fix Larry skin issues in IE7 compat. mode (#1488618)
+- Fix so subscribed non-existing/non-accessible shared folder can be unsubscribed
+- Added session_path config option and unified cookies settings in javascript
+- Added "Undeleted" option to messages list filter
+- Rewritten test scripts for PHPUnit
 - Add new DB abstraction layer based on PHP PDO, supporting SQLite3 (#1488332)
 - Removed PEAR::MDB2 package
 - Removed users.alias column, added option ('user_aliases')
@@ -31,6 +48,24 @@
     Move global functions from main.inc and rcube_shared.inc into classes
     Better classes separation
 
+RELEASE 0.8.1
+-------------
+- Fix bug where domain name was converted to lower-case even with login_lc=false (#1488593)
+- Fix lower-casing email address on replies (#1488598)
+- Fix line separator in exported messages (#1488603)
+- Fix XSS issue where plain signatures wasn't secured in HTML mode (#1488613)
+- Fix XSS issue where href="javascript:" wasn't secured (#1488613)
+- Fix impossible to create message with empty plain text part (#1488610)
+- Fix stripped apostrophes when replying in plain text to HTML message (#1488606)
+- Fix inactive Save search option after advanced search (#1488607)
+- Fix Remove from group option is active for contact search result (#1488608)
+- Disable autocapitalization in login form on iPad/iPhone (#1488609)
+- Fix focus on the list when list row is clicked (#1488600)
+- Added separate From and To columns apart from smart From/To column (#1486891)
+- Fix fallback to Larry skin when configured skin isn't available (#1488591)
+- Fix (workaround) delete operations with some versions of memcache (#1488592)
+- Fix (disable) request validation for spell and spell_html actions
+
 RELEASE 0.8.0
 -------------
 - Don't show product version on login screen (can be enabled by config)
diff --git a/config/main.inc.php.dist b/config/main.inc.php.dist
index 58f9ca8..a6661c3 100644
--- a/config/main.inc.php.dist
+++ b/config/main.inc.php.dist
@@ -21,7 +21,7 @@
 // LOGGING/DEBUGGING
 // ----------------------------------
 
-// system error reporting: 1 = log; 2 = report (not implemented yet), 4 = show, 8 = trace
+// system error reporting, sum of: 1 = log; 4 = show, 8 = trace
 $rcmail_config['debug_level'] = 1;
 
 // log driver:  'syslog' or 'file'.
@@ -78,7 +78,7 @@
 // TCP port used for IMAP connections
 $rcmail_config['default_port'] = 143;
 
-// IMAP AUTH type (DIGEST-MD5, CRAM-MD5, LOGIN, PLAIN or empty to use
+// IMAP AUTH type (DIGEST-MD5, CRAM-MD5, LOGIN, PLAIN or null to use
 // best server supported one)
 $rcmail_config['imap_auth_type'] = null;
 
@@ -224,11 +224,12 @@
 // 0 - disabled, 1 - username and host only, 2 - username, host, password
 $rcmail_config['login_autocomplete'] = 0;
 
-// If users authentication is not case sensitive this must be enabled.
-// You can also use it to force conversion of logins to lower case.
+// Forces conversion of logins to lower case.
+// 0 - disabled, 1 - only domain part, 2 - domain and local part.
+// If users authentication is not case-sensitive this must be enabled.
 // After enabling it all user records need to be updated, e.g. with query:
 // UPDATE users SET username = LOWER(username);
-$rcmail_config['login_lc'] = false;
+$rcmail_config['login_lc'] = 0;
 
 // Includes should be interpreted as PHP files
 $rcmail_config['skin_include_php'] = false;
@@ -240,11 +241,14 @@
 // must be greater than 'keep_alive'/60
 $rcmail_config['session_lifetime'] = 10;
 
-// session domain: .example.org
+// Session domain: .example.org
 $rcmail_config['session_domain'] = '';
 
-// session name. Default: 'roundcube_sessid'
+// Session name. Default: 'roundcube_sessid'
 $rcmail_config['session_name'] = null;
+
+// Session path. Defaults to PHP session.cookie_path setting.
+$rcmail_config['session_path'] = null;
 
 // Backend to use for session storage. Can either be 'db' (default) or 'memcache'
 // If set to memcache, a list of servers need to be specified in 'memcache_hosts'
@@ -721,7 +725,7 @@
 $rcmail_config['show_images'] = 0;
 
 // compose html formatted messages by default
-// 0 - never, 1 - always, 2 - on reply to HTML message only 
+// 0 - never, 1 - always, 2 - on reply to HTML message, 3 - on forward or reply to HTML message
 $rcmail_config['htmleditor'] = 0;
 
 // show pretty dates as standard
@@ -779,8 +783,11 @@
 // 2 - Expand only threads with unread messages 
 $rcmail_config['autoexpand_threads'] = 0;
 
-// When replying place cursor above original message (top posting)
-$rcmail_config['top_posting'] = false;
+// When replying:
+// -1 - don't cite the original message
+// 0  - place cursor below the original message
+// 1  - place cursor above original message (top posting)
+$rcmail_config['reply_mode'] = 0;
 
 // When replying strip original signature from message
 $rcmail_config['strip_existing_sig'] = true;
diff --git a/installer/rcube_install.php b/installer/rcube_install.php
index bfb111f..5af8713 100644
--- a/installer/rcube_install.php
+++ b/installer/rcube_install.php
@@ -35,13 +35,14 @@
 
   var $obsolete_config = array('db_backend', 'double_auth');
   var $replaced_config = array(
-    'skin_path' => 'skin',
-    'locale_string' => 'language',
-    'multiple_identities' => 'identities_level',
+    'skin_path'            => 'skin',
+    'locale_string'        => 'language',
+    'multiple_identities'  => 'identities_level',
     'addrbook_show_images' => 'show_images',
-    'imap_root' => 'imap_ns_personal',
-    'pagesize' => 'mail_pagesize',
+    'imap_root'            => 'imap_ns_personal',
+    'pagesize'             => 'mail_pagesize',
     'default_imap_folders' => 'default_folders',
+    'top_posting'          => 'reply_mode',
   );
 
   // these config options are required for a working system
diff --git a/plugins/acl/acl.php b/plugins/acl/acl.php
index 1442504..1952dad 100644
--- a/plugins/acl/acl.php
+++ b/plugins/acl/acl.php
@@ -233,8 +233,7 @@
 
         // Advanced rights
         $attrib['id'] = 'advancedrights';
-        foreach ($supported as $val) {
-            $id = "acl$val";
+        foreach ($supported as $idx => $val) {
             $ul .= html::tag('li', null,
                 $input->show('', array(
                     'name' => "acl[$val]", 'value' => $val, 'id' => $id))
diff --git a/plugins/acl/skins/larry/acl.css b/plugins/acl/skins/larry/acl.css
index 5e2448e..e392a26 100644
--- a/plugins/acl/skins/larry/acl.css
+++ b/plugins/acl/skins/larry/acl.css
@@ -123,3 +123,7 @@
 {
   margin-left: 0.5em;
 }
+
+ul.toolbarmenu li span.delete {
+  background-position: 0 -1509px;
+}
diff --git a/plugins/acl/skins/larry/templates/table.html b/plugins/acl/skins/larry/templates/table.html
index 7f99f6f..3cf8292 100644
--- a/plugins/acl/skins/larry/templates/table.html
+++ b/plugins/acl/skins/larry/templates/table.html
@@ -3,14 +3,14 @@
     <roundcube:object name="acltable" id="acltable" class="records-table" />
 </div>
 <div id="acllist-footer" class="boxfooter">
-    <roundcube:button command="acl-create" id="aclcreatelink" type="link" title="acl.newuser" class="listbutton add disabled" classAct="listbutton add" innerClass="inner" content="+" /><roundcube:button name="aclmenulink" id="aclmenulink" type="link" title="acl.actions" class="listbutton groupactions"onclick="UI.show_popup('aclmenu');return false" innerClass="inner" content="&#9881;" />
+    <roundcube:button command="acl-create" id="aclcreatelink" type="link" title="acl.newuser" class="listbutton add disabled" classAct="listbutton add" innerClass="inner" content="+" /><roundcube:button name="aclmenulink" id="aclmenulink" type="link" title="acl.actions" class="listbutton groupactions"onclick="UI.show_popup('aclmenu', undefined, {above:1});return false" innerClass="inner" content="&#9881;" />
 </div>
 </div>
 
 <div id="aclmenu" class="popupmenu">
-    <ul class="toolbarmenu selectable">
-        <li><roundcube:button command="acl-edit" label="edit" classAct="active" /></li>
-        <li><roundcube:button command="acl-delete" label="delete" classAct="active" /></li>
+    <ul class="toolbarmenu selectable iconized">
+        <li><roundcube:button command="acl-edit" label="edit" class="icon" classAct="icon active" innerclass="icon edit" /></li>
+        <li><roundcube:button command="acl-delete" label="delete" class="icon" classAct="icon active" innerclass="icon delete" /></li>
         <roundcube:if condition="!in_array('acl_advanced_mode', (array)config:dont_override)" />
             <li><roundcube:button name="acl-switch" id="acl-switch" label="acl.advanced" onclick="rcmail.command('acl-mode-switch')" class="active" /></li>
         <roundcube:endif />
diff --git a/plugins/hide_blockquote/hide_blockquote.php b/plugins/hide_blockquote/hide_blockquote.php
index ca0273a..7af163d 100644
--- a/plugins/hide_blockquote/hide_blockquote.php
+++ b/plugins/hide_blockquote/hide_blockquote.php
@@ -27,11 +27,7 @@
             && ($limit = $rcmail->config->get('hide_blockquote_limit'))
         ) {
             // include styles
-            $skin = $rcmail->config->get('skin');
-            if (!file_exists($this->home."/skins/$skin/style.css")) {
-                $skin = 'default';
-            }
-            $this->include_stylesheet("skins/$skin/style.css");
+            $this->include_stylesheet($this->local_skin_path() . "/style.css");
 
             // Script and localization
             $this->include_script('hide_blockquote.js');
diff --git a/plugins/hide_blockquote/skins/default/style.css b/plugins/hide_blockquote/skins/larry/style.css
similarity index 100%
rename from plugins/hide_blockquote/skins/default/style.css
rename to plugins/hide_blockquote/skins/larry/style.css
diff --git a/plugins/managesieve/tests/Makefile b/plugins/managesieve/tests/Makefile
deleted file mode 100644
index 072be2f..0000000
--- a/plugins/managesieve/tests/Makefile
+++ /dev/null
@@ -1,7 +0,0 @@
-
-clean:
-	rm -f *.log *.php *.diff *.exp *.out
-
-
-test:
-	pear run-tests *.phpt
diff --git a/plugins/managesieve/tests/Parser.php b/plugins/managesieve/tests/Parser.php
new file mode 100644
index 0000000..00915cc
--- /dev/null
+++ b/plugins/managesieve/tests/Parser.php
@@ -0,0 +1,54 @@
+<?php
+
+class Parser extends PHPUnit_Framework_TestCase
+{
+
+    function setUp()
+    {
+        include_once dirname(__FILE__) . '/../lib/rcube_sieve_script.php';
+    }
+
+    /**
+     * Sieve script parsing
+     *
+     * @dataProvider data_parser
+     */
+    function test_parser($input, $output, $message)
+    {
+        $script = new rcube_sieve_script($input);
+        $result = $script->as_text();
+
+        $this->assertEquals(trim($result), trim($output), $message);
+    }
+
+    /**
+     * Data provider for test_parser()
+     */
+    function data_parser()
+    {
+        $dir_path = realpath(dirname(__FILE__) . '/src');
+        $dir      = opendir($dir_path);
+        $result   = array();
+
+        while ($file = readdir($dir)) {
+            if (preg_match('/^[a-z0-9_]+$/', $file)) {
+                $input = file_get_contents($dir_path . '/' . $file);
+
+                if (file_exists($dir_path . '/' . $file . '.out')) {
+                    $output = file_get_contents($dir_path . '/' . $file . '.out');
+                }
+                else {
+                    $output = $input;
+                }
+
+                $result[] = array(
+                    'input'   => $input,
+                    'output'  => $output,
+                    'message' => "Error in parsing '$file' file",
+                );
+            }
+        }
+
+        return $result;
+    }
+}
diff --git a/plugins/managesieve/tests/Tokenizer.php b/plugins/managesieve/tests/Tokenizer.php
new file mode 100644
index 0000000..8c0bced
--- /dev/null
+++ b/plugins/managesieve/tests/Tokenizer.php
@@ -0,0 +1,33 @@
+<?php
+
+class Tokenizer extends PHPUnit_Framework_TestCase
+{
+
+    function setUp()
+    {
+        include_once dirname(__FILE__) . '/../lib/rcube_sieve_script.php';
+    }
+
+    function data_tokenizer()
+    {
+        return array(
+            array(1, "text: #test\nThis is test ; message;\nMulti line\n.\n;\n", '"This is test ; message;\nMulti line"'),
+            array(0, '["test1","test2"]', '[["test1","test2"]]'),
+            array(1, '["test"]', '["test"]'),
+            array(1, '"te\\"st"', '"te\\"st"'),
+            array(0, 'test #comment', '["test"]'),
+            array(0, "text:\ntest\n.\ntext:\ntest\n.\n", '["test","test"]'),
+            array(1, '"\\a\\\\\\"a"', '"a\\\\\\"a"'),
+        );
+    }
+
+    /**
+     * @dataProvider data_tokenizer
+     */
+    function test_tokenizer($num, $input, $output)
+    {
+        $res = json_encode(rcube_sieve_script::tokenize($input, $num));
+
+        $this->assertEquals(trim($res), trim($output));
+    }
+}
diff --git a/plugins/managesieve/tests/parser.phpt b/plugins/managesieve/tests/parser.phpt
deleted file mode 100644
index aec0421..0000000
--- a/plugins/managesieve/tests/parser.phpt
+++ /dev/null
@@ -1,120 +0,0 @@
---TEST--
-Main test of script parser
---SKIPIF--
---FILE--
-<?php
-include '../lib/rcube_sieve_script.php';
-
-$txt = '
-require ["fileinto","reject","envelope"];
-# rule:[spam]
-if anyof (header :contains "X-DSPAM-Result" "Spam")
-{
-	fileinto "Spam";
-	stop;
-}
-# rule:[test1]
-if anyof (header :comparator "i;ascii-casemap" :contains ["From","To"] "test@domain.tld")
-{
-	discard;
-	stop;
-}
-# rule:[test2]
-if anyof (not header :comparator "i;octet" :contains ["Subject"] "[test]", header :contains "Subject" "[test2]")
-{
-	fileinto "test";
-	stop;
-}
-# rule:[comments]
-if anyof (true) /* comment
- * "comment" #comment */ {
-    /* comment */ stop;
-# comment
-}
-# rule:[reject]
-if size :over 5000K {
-	reject "Message over 5MB size limit. Please contact me before sending this.";
-}
-# rule:[false]
-if false # size :over 5000K
-{
-	stop; /* rule disabled */
-}
-# rule:[true]
-if true
-{
-	stop;
-}
-fileinto "Test";
-# rule:[address test]
-if address :all :is "From" "nagios@domain.tld"
-{
-	fileinto "domain.tld";
-	stop;
-}
-# rule:[envelope test]
-if envelope :domain :is "From" "domain.tld"
-{
-	fileinto "domain.tld";
-	stop;
-}
-';
-
-$s = new rcube_sieve_script($txt);
-echo $s->as_text();
-
-// -------------------------------------------------------------------------------
-?>
---EXPECT--
-require ["fileinto","reject","envelope"];
-# rule:[spam]
-if header :contains "X-DSPAM-Result" "Spam"
-{
-	fileinto "Spam";
-	stop;
-}
-# rule:[test1]
-if header :contains ["From","To"] "test@domain.tld"
-{
-	discard;
-	stop;
-}
-# rule:[test2]
-if anyof (not header :comparator "i;octet" :contains "Subject" "[test]", header :contains "Subject" "[test2]")
-{
-	fileinto "test";
-	stop;
-}
-# rule:[comments]
-if true
-{
-	stop;
-}
-# rule:[reject]
-if size :over 5000K
-{
-	reject "Message over 5MB size limit. Please contact me before sending this.";
-}
-# rule:[false]
-if false # size :over 5000K
-{
-	stop;
-}
-# rule:[true]
-if true
-{
-	stop;
-}
-fileinto "Test";
-# rule:[address test]
-if address :all :is "From" "nagios@domain.tld"
-{
-	fileinto "domain.tld";
-	stop;
-}
-# rule:[envelope test]
-if envelope :domain :is "From" "domain.tld"
-{
-	fileinto "domain.tld";
-	stop;
-}
diff --git a/plugins/managesieve/tests/parser_body.phpt b/plugins/managesieve/tests/parser_body.phpt
deleted file mode 100644
index 08ad549..0000000
--- a/plugins/managesieve/tests/parser_body.phpt
+++ /dev/null
@@ -1,49 +0,0 @@
---TEST--
-Test of Sieve body extension (RFC5173)
---SKIPIF--
---FILE--
-<?php
-include '../lib/rcube_sieve_script.php';
-
-$txt = '
-require ["body","fileinto"];
-if body :raw :contains "MAKE MONEY FAST"
-{
-	stop;
-}
-if body :content "text" :contains ["missile","coordinates"]
-{
-	fileinto "secrets";
-}
-if body :content "audio/mp3" :contains ""
-{
-	fileinto "jukebox";
-}
-if body :text :contains "project schedule"
-{
-	fileinto "project/schedule";
-}
-';
-
-$s = new rcube_sieve_script($txt);
-echo $s->as_text();
-
-?>
---EXPECT--
-require ["body","fileinto"];
-if body :raw :contains "MAKE MONEY FAST"
-{
-	stop;
-}
-if body :content "text" :contains ["missile","coordinates"]
-{
-	fileinto "secrets";
-}
-if body :content "audio/mp3" :contains ""
-{
-	fileinto "jukebox";
-}
-if body :text :contains "project schedule"
-{
-	fileinto "project/schedule";
-}
diff --git a/plugins/managesieve/tests/parser_imapflags.phpt b/plugins/managesieve/tests/parser_imapflags.phpt
deleted file mode 100644
index a4bc465..0000000
--- a/plugins/managesieve/tests/parser_imapflags.phpt
+++ /dev/null
@@ -1,28 +0,0 @@
---TEST--
-Test of Sieve vacation extension (RFC5232)
---SKIPIF--
---FILE--
-<?php
-include '../lib/rcube_sieve_script.php';
-
-$txt = '
-require ["imapflags"];
-# rule:[imapflags]
-if header :matches "Subject" "^Test$" {
-    setflag "\\\\Seen";
-    addflag ["\\\\Answered","\\\\Deleted"];
-}
-';
-
-$s = new rcube_sieve_script($txt, array('imapflags'));
-echo $s->as_text();
-
-?>
---EXPECT--
-require ["imapflags"];
-# rule:[imapflags]
-if header :matches "Subject" "^Test$"
-{
-	setflag "\\Seen";
-	addflag ["\\Answered","\\Deleted"];
-}
diff --git a/plugins/managesieve/tests/parser_include.phpt b/plugins/managesieve/tests/parser_include.phpt
deleted file mode 100644
index addc0d4..0000000
--- a/plugins/managesieve/tests/parser_include.phpt
+++ /dev/null
@@ -1,30 +0,0 @@
---TEST--
-Test of Sieve include extension
---SKIPIF--
---FILE--
-<?php
-include '../lib/rcube_sieve_script.php';
-
-$txt = '
-require ["include"];
-
-include "script.sieve";
-# rule:[two]
-if true
-{
-    include :optional "second.sieve";
-}
-';
-
-$s = new rcube_sieve_script($txt, array(), array('variables'));
-echo $s->as_text();
-
-?>
---EXPECT--
-require ["include"];
-include "script.sieve";
-# rule:[two]
-if true
-{
-	include :optional "second.sieve";
-}
diff --git a/plugins/managesieve/tests/parser_kep14.phpt b/plugins/managesieve/tests/parser_kep14.phpt
deleted file mode 100644
index dcdbd48..0000000
--- a/plugins/managesieve/tests/parser_kep14.phpt
+++ /dev/null
@@ -1,19 +0,0 @@
---TEST--
-Test of Kolab's KEP:14 implementation
---SKIPIF--
---FILE--
-<?php
-include '../lib/rcube_sieve_script.php';
-
-$txt = '
-# EDITOR Roundcube
-# EDITOR_VERSION 123
-';
-
-$s = new rcube_sieve_script($txt, array('body'));
-echo $s->as_text();
-
-?>
---EXPECT--
-# EDITOR Roundcube
-# EDITOR_VERSION 123
diff --git a/plugins/managesieve/tests/parser_prefix.phpt b/plugins/managesieve/tests/parser_prefix.phpt
deleted file mode 100644
index c87e965..0000000
--- a/plugins/managesieve/tests/parser_prefix.phpt
+++ /dev/null
@@ -1,25 +0,0 @@
---TEST--
-Test of prefix comments handling
---SKIPIF--
---FILE--
-<?php
-include '../lib/rcube_sieve_script.php';
-
-$txt = '
-# this is a comment
-# and the second line
-
-require ["variables"];
-set "b" "c";
-';
-
-$s = new rcube_sieve_script($txt, array(), array('variables'));
-echo $s->as_text();
-
-?>
---EXPECT--
-# this is a comment
-# and the second line
-
-require ["variables"];
-set "b" "c";
diff --git a/plugins/managesieve/tests/parser_relational.phpt b/plugins/managesieve/tests/parser_relational.phpt
deleted file mode 100644
index 6b6f29f..0000000
--- a/plugins/managesieve/tests/parser_relational.phpt
+++ /dev/null
@@ -1,25 +0,0 @@
---TEST--
-Test of Sieve relational extension (RFC5231)
---SKIPIF--
---FILE--
-<?php
-include '../lib/rcube_sieve_script.php';
-
-$txt = '
-require ["relational","comparator-i;ascii-numeric"];
-# rule:[redirect]
-if header :value "ge" :comparator "i;ascii-numeric"
-    ["X-Spam-score"] ["14"] {redirect "test@test.tld";}
-';
-
-$s = new rcube_sieve_script($txt);
-echo $s->as_text();
-
-?>
---EXPECT--
-require ["relational","comparator-i;ascii-numeric"];
-# rule:[redirect]
-if header :value "ge" :comparator "i;ascii-numeric" "X-Spam-score" "14"
-{
-	redirect "test@test.tld";
-}
diff --git a/plugins/managesieve/tests/parser_vacation.phpt b/plugins/managesieve/tests/parser_vacation.phpt
deleted file mode 100644
index a603ff6..0000000
--- a/plugins/managesieve/tests/parser_vacation.phpt
+++ /dev/null
@@ -1,39 +0,0 @@
---TEST--
-Test of Sieve vacation extension (RFC5230)
---SKIPIF--
---FILE--
-<?php
-include '../lib/rcube_sieve_script.php';
-
-$txt = '
-require ["vacation"];
-# rule:[test-vacation]
-if anyof (header :contains "Subject" "vacation")
-{
-	vacation :days 1 text:
-# test
-test test /* test */
-test
-.
-;
-	stop;
-}
-';
-
-$s = new rcube_sieve_script($txt);
-echo $s->as_text();
-
-?>
---EXPECT--
-require ["vacation"];
-# rule:[test-vacation]
-if header :contains "Subject" "vacation"
-{
-	vacation :days 1 text:
-# test
-test test /* test */
-test
-.
-;
-	stop;
-}
diff --git a/plugins/managesieve/tests/parser_variables.phpt b/plugins/managesieve/tests/parser_variables.phpt
deleted file mode 100644
index cf1f8fc..0000000
--- a/plugins/managesieve/tests/parser_variables.phpt
+++ /dev/null
@@ -1,39 +0,0 @@
---TEST--
-Test of Sieve variables extension
---SKIPIF--
---FILE--
-<?php
-include '../lib/rcube_sieve_script.php';
-
-$txt = '
-require ["variables"];
-set "honorific" "Mr";
-set "vacation" text:
-Dear ${HONORIFIC} ${last_name},
-I am out, please leave a message after the meep.
-.
-;
-set :length "b" "${a}";
-set :lower "b" "${a}";
-set :upperfirst "b" "${a}";
-set :upperfirst :lower "b" "${a}";
-set :quotewildcard "b" "Rock*";
-';
-
-$s = new rcube_sieve_script($txt, array(), array('variables'));
-echo $s->as_text();
-
-?>
---EXPECT--
-require ["variables"];
-set "honorific" "Mr";
-set "vacation" text:
-Dear ${HONORIFIC} ${last_name},
-I am out, please leave a message after the meep.
-.
-;
-set :length "b" "${a}";
-set :lower "b" "${a}";
-set :upperfirst "b" "${a}";
-set :upperfirst :lower "b" "${a}";
-set :quotewildcard "b" "Rock*";
diff --git a/plugins/managesieve/tests/parset_subaddress.phpt b/plugins/managesieve/tests/parset_subaddress.phpt
deleted file mode 100644
index 6d4d03c..0000000
--- a/plugins/managesieve/tests/parset_subaddress.phpt
+++ /dev/null
@@ -1,38 +0,0 @@
---TEST--
-Test of Sieve subaddress extension (RFC5233)
---SKIPIF--
---FILE--
-<?php
-include '../lib/rcube_sieve_script.php';
-
-$txt = '
-require ["envelope","subaddress","fileinto"];
-if envelope :user "To" "postmaster"
-{
-	fileinto "postmaster";
-	stop;
-}
-if envelope :detail :is "To" "mta-filters"
-{
-	fileinto "mta-filters";
-	stop;
-}
-';
-
-$s = new rcube_sieve_script($txt);
-echo $s->as_text();
-
-// -------------------------------------------------------------------------------
-?>
---EXPECT--
-require ["envelope","subaddress","fileinto"];
-if envelope :user "To" "postmaster"
-{
-	fileinto "postmaster";
-	stop;
-}
-if envelope :detail :is "To" "mta-filters"
-{
-	fileinto "mta-filters";
-	stop;
-}
diff --git a/plugins/managesieve/tests/src/parser b/plugins/managesieve/tests/src/parser
new file mode 100644
index 0000000..9c4717b
--- /dev/null
+++ b/plugins/managesieve/tests/src/parser
@@ -0,0 +1,52 @@
+require ["fileinto","reject","envelope"];
+# rule:[spam]
+if anyof (header :contains "X-DSPAM-Result" "Spam")
+{
+	fileinto "Spam";
+	stop;
+}
+# rule:[test1]
+if anyof (header :comparator "i;ascii-casemap" :contains ["From","To"] "test@domain.tld")
+{
+	discard;
+	stop;
+}
+# rule:[test2]
+if anyof (not header :comparator "i;octet" :contains ["Subject"] "[test]", header :contains "Subject" "[test2]")
+{
+	fileinto "test";
+	stop;
+}
+# rule:[comments]
+if anyof (true) /* comment
+ * "comment" #comment */ {
+    /* comment */ stop;
+# comment
+}
+# rule:[reject]
+if size :over 5000K {
+	reject "Message over 5MB size limit. Please contact me before sending this.";
+}
+# rule:[false]
+if false # size :over 5000K
+{
+	stop; /* rule disabled */
+}
+# rule:[true]
+if true
+{
+	stop;
+}
+fileinto "Test";
+# rule:[address test]
+if address :all :is "From" "nagios@domain.tld"
+{
+	fileinto "domain.tld";
+	stop;
+}
+# rule:[envelope test]
+if envelope :domain :is "From" "domain.tld"
+{
+	fileinto "domain.tld";
+	stop;
+}
diff --git a/plugins/managesieve/tests/src/parser.out b/plugins/managesieve/tests/src/parser.out
new file mode 100644
index 0000000..385c889
--- /dev/null
+++ b/plugins/managesieve/tests/src/parser.out
@@ -0,0 +1,52 @@
+require ["fileinto","reject","envelope"];
+# rule:[spam]
+if header :contains "X-DSPAM-Result" "Spam"
+{
+	fileinto "Spam";
+	stop;
+}
+# rule:[test1]
+if header :contains ["From","To"] "test@domain.tld"
+{
+	discard;
+	stop;
+}
+# rule:[test2]
+if anyof (not header :comparator "i;octet" :contains "Subject" "[test]", header :contains "Subject" "[test2]")
+{
+	fileinto "test";
+	stop;
+}
+# rule:[comments]
+if true
+{
+	stop;
+}
+# rule:[reject]
+if size :over 5000K
+{
+	reject "Message over 5MB size limit. Please contact me before sending this.";
+}
+# rule:[false]
+if false # size :over 5000K
+{
+	stop;
+}
+# rule:[true]
+if true
+{
+	stop;
+}
+fileinto "Test";
+# rule:[address test]
+if address :all :is "From" "nagios@domain.tld"
+{
+	fileinto "domain.tld";
+	stop;
+}
+# rule:[envelope test]
+if envelope :domain :is "From" "domain.tld"
+{
+	fileinto "domain.tld";
+	stop;
+}
diff --git a/plugins/managesieve/tests/src/parser_body b/plugins/managesieve/tests/src/parser_body
new file mode 100644
index 0000000..bd142ed
--- /dev/null
+++ b/plugins/managesieve/tests/src/parser_body
@@ -0,0 +1,17 @@
+require ["body","fileinto"];
+if body :raw :contains "MAKE MONEY FAST"
+{
+	stop;
+}
+if body :content "text" :contains ["missile","coordinates"]
+{
+	fileinto "secrets";
+}
+if body :content "audio/mp3" :contains ""
+{
+	fileinto "jukebox";
+}
+if body :text :contains "project schedule"
+{
+	fileinto "project/schedule";
+}
diff --git a/plugins/managesieve/tests/src/parser_imapflags b/plugins/managesieve/tests/src/parser_imapflags
new file mode 100644
index 0000000..e67bf7c
--- /dev/null
+++ b/plugins/managesieve/tests/src/parser_imapflags
@@ -0,0 +1,7 @@
+require ["imap4flags"];
+# rule:[imapflags]
+if header :matches "Subject" "^Test$"
+{
+	setflag "\\Seen";
+	addflag ["\\Answered","\\Deleted"];
+}
diff --git a/plugins/managesieve/tests/src/parser_include b/plugins/managesieve/tests/src/parser_include
new file mode 100644
index 0000000..b5585a4
--- /dev/null
+++ b/plugins/managesieve/tests/src/parser_include
@@ -0,0 +1,7 @@
+require ["include"];
+include "script.sieve";
+# rule:[two]
+if true
+{
+	include :optional "second.sieve";
+}
diff --git a/plugins/managesieve/tests/src/parser_kep14 b/plugins/managesieve/tests/src/parser_kep14
new file mode 100644
index 0000000..1ded8d8
--- /dev/null
+++ b/plugins/managesieve/tests/src/parser_kep14
@@ -0,0 +1,2 @@
+# EDITOR Roundcube
+# EDITOR_VERSION 123
diff --git a/plugins/managesieve/tests/src/parser_kep14.out b/plugins/managesieve/tests/src/parser_kep14.out
new file mode 100644
index 0000000..cb7faa7
--- /dev/null
+++ b/plugins/managesieve/tests/src/parser_kep14.out
@@ -0,0 +1,3 @@
+require ["variables"];
+set "EDITOR" "Roundcube";
+set "EDITOR_VERSION" "123";
diff --git a/plugins/managesieve/tests/src/parser_prefix b/plugins/managesieve/tests/src/parser_prefix
new file mode 100644
index 0000000..9f6a33a
--- /dev/null
+++ b/plugins/managesieve/tests/src/parser_prefix
@@ -0,0 +1,5 @@
+# this is a comment
+# and the second line
+
+require ["variables"];
+set "b" "c";
diff --git a/plugins/managesieve/tests/src/parser_relational b/plugins/managesieve/tests/src/parser_relational
new file mode 100644
index 0000000..0a92fde
--- /dev/null
+++ b/plugins/managesieve/tests/src/parser_relational
@@ -0,0 +1,6 @@
+require ["relational","comparator-i;ascii-numeric"];
+# rule:[redirect]
+if header :value "ge" :comparator "i;ascii-numeric" "X-Spam-score" "14"
+{
+	redirect "test@test.tld";
+}
diff --git a/plugins/managesieve/tests/src/parser_subaddress b/plugins/managesieve/tests/src/parser_subaddress
new file mode 100644
index 0000000..f106b79
--- /dev/null
+++ b/plugins/managesieve/tests/src/parser_subaddress
@@ -0,0 +1,11 @@
+require ["envelope","subaddress","fileinto"];
+if envelope :user "To" "postmaster"
+{
+	fileinto "postmaster";
+	stop;
+}
+if envelope :detail :is "To" "mta-filters"
+{
+	fileinto "mta-filters";
+	stop;
+}
diff --git a/plugins/managesieve/tests/src/parser_vacation b/plugins/managesieve/tests/src/parser_vacation
new file mode 100644
index 0000000..93026db
--- /dev/null
+++ b/plugins/managesieve/tests/src/parser_vacation
@@ -0,0 +1,12 @@
+require ["vacation"];
+# rule:[test-vacation]
+if header :contains "Subject" "vacation"
+{
+	vacation :days 1 text:
+# test
+test test /* test */
+test
+.
+;
+	stop;
+}
diff --git a/plugins/managesieve/tests/src/parser_variables b/plugins/managesieve/tests/src/parser_variables
new file mode 100644
index 0000000..bd5941c
--- /dev/null
+++ b/plugins/managesieve/tests/src/parser_variables
@@ -0,0 +1,12 @@
+require ["variables"];
+set "honorific" "Mr";
+set "vacation" text:
+Dear ${HONORIFIC} ${last_name},
+I am out, please leave a message after the meep.
+.
+;
+set :length "b" "${a}";
+set :lower "b" "${a}";
+set :upperfirst "b" "${a}";
+set :upperfirst :lower "b" "${a}";
+set :quotewildcard "b" "Rock*";
diff --git a/plugins/managesieve/tests/tokenize.phpt b/plugins/managesieve/tests/tokenize.phpt
deleted file mode 100644
index f988653..0000000
--- a/plugins/managesieve/tests/tokenize.phpt
+++ /dev/null
@@ -1,66 +0,0 @@
---TEST--
-Script parsing: tokenizer
---SKIPIF--
---FILE--
-<?php
-include '../lib/rcube_sieve_script.php';
-
-$txt[1] = array(1, 'text: #test
-This is test ; message;
-Multi line
-.
-;
-');
-$txt[2] = array(0, '["test1","test2"]');
-$txt[3] = array(1, '["test"]');
-$txt[4] = array(1, '"te\\"st"');
-$txt[5] = array(0, 'test #comment');
-$txt[6] = array(0, 'text:
-test
-.
-text:
-test
-.
-');
-$txt[7] = array(1, '"\\a\\\\\\"a"');
-
-foreach ($txt as $idx => $t) {
-    echo "[$idx]---------------\n"; 
-    var_dump(rcube_sieve_script::tokenize($t[1], $t[0]));
-}
-?>
---EXPECT--
-[1]---------------
-string(34) "This is test ; message;
-Multi line"
-[2]---------------
-array(1) {
-  [0]=>
-  array(2) {
-    [0]=>
-    string(5) "test1"
-    [1]=>
-    string(5) "test2"
-  }
-}
-[3]---------------
-array(1) {
-  [0]=>
-  string(4) "test"
-}
-[4]---------------
-string(5) "te"st"
-[5]---------------
-array(1) {
-  [0]=>
-  string(4) "test"
-}
-[6]---------------
-array(2) {
-  [0]=>
-  string(4) "test"
-  [1]=>
-  string(4) "test"
-}
-[7]---------------
-string(4) "a\"a"
diff --git a/plugins/password/drivers/virtualmin.php b/plugins/password/drivers/virtualmin.php
index b2547e0..f9eca96 100644
--- a/plugins/password/drivers/virtualmin.php
+++ b/plugins/password/drivers/virtualmin.php
@@ -48,6 +48,10 @@
             $pieces = explode("_", $username);
             $domain = $pieces[0];
             break;
+		case 8: // domain taken from alias, username left as it was
+			$email = $rcmail->user->data['alias'];
+			$domain = substr(strrchr($email, "@"), 1);
+			break;
         default: // username@domain
             $domain = substr(strrchr($username, "@"), 1);
         }
diff --git a/plugins/virtuser_query/virtuser_query.php b/plugins/virtuser_query/virtuser_query.php
index 21a869c..073b4e2 100644
--- a/plugins/virtuser_query/virtuser_query.php
+++ b/plugins/virtuser_query/virtuser_query.php
@@ -14,8 +14,11 @@
  *
  * $rcmail_config['virtuser_query'] = array('email' => '', 'user' => '', 'host' => '');
  *
+ * The email query can return more than one record to create more identities.
+ * This requires identities_level option to be set to value less than 2.
+ *
  * @version @package_version@
- * @author Aleksander Machniak
+ * @author Aleksander Machniak <alec@alec.pl>
  * @author Steffen Vogel
  */
 class virtuser_query extends rcube_plugin
diff --git a/program/include/clisetup.php b/program/include/clisetup.php
index 039020b..a9af90a 100644
--- a/program/include/clisetup.php
+++ b/program/include/clisetup.php
@@ -33,33 +33,36 @@
  */
 function get_opt($aliases = array())
 {
-	$args = array();
-	for ($i=1; $i < count($_SERVER['argv']); $i++) {
-		$arg = $_SERVER['argv'][$i];
-		$value = true;
-		$key = null;
+    $args = array();
 
-		if ($arg[0] == '-') {
-			$key = preg_replace('/^-+/', '', $arg);
-			$sp = strpos($arg, '=');
-			if ($sp > 0) {
-				$key = substr($key, 0, $sp - 2);
-				$value = substr($arg, $sp+1);
-			}
-			else if (strlen($_SERVER['argv'][$i+1]) && $_SERVER['argv'][$i+1][0] != '-') {
-				$value = $_SERVER['argv'][++$i];
-			}
+    for ($i=1; $i < count($_SERVER['argv']); $i++) {
+        $arg   = $_SERVER['argv'][$i];
+        $value = true;
+        $key   = null;
 
-			$args[$key] = is_string($value) ? preg_replace(array('/^["\']/', '/["\']$/'), '', $value) : $value;
-		}
-		else
-			$args[] = $arg;
+        if ($arg[0] == '-') {
+            $key = preg_replace('/^-+/', '', $arg);
+            $sp  = strpos($arg, '=');
+            if ($sp > 0) {
+                $key   = substr($key, 0, $sp - 2);
+                $value = substr($arg, $sp+1);
+            }
+            else if (strlen($_SERVER['argv'][$i+1]) && $_SERVER['argv'][$i+1][0] != '-') {
+                $value = $_SERVER['argv'][++$i];
+            }
 
-		if ($alias = $aliases[$key])
-			$args[$alias] = $args[$key];
-	}
+            $args[$key] = is_string($value) ? preg_replace(array('/^["\']/', '/["\']$/'), '', $value) : $value;
+        }
+        else {
+            $args[] = $arg;
+        }
 
-	return $args;
+        if ($alias = $aliases[$key]) {
+            $args[$alias] = $args[$key];
+        }
+    }
+
+    return $args;
 }
 
 
diff --git a/program/include/html.php b/program/include/html.php
index b42da1d..c6507f8 100644
--- a/program/include/html.php
+++ b/program/include/html.php
@@ -154,7 +154,7 @@
             $attr = array('src' => $attr);
         }
         return self::tag('img', $attr + array('alt' => ''), null, array_merge(self::$common_attrib,
-	        array('src','alt','width','height','border','usemap','onclick')));
+            array('src','alt','width','height','border','usemap','onclick')));
     }
 
     /**
@@ -171,7 +171,7 @@
             $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')));
     }
 
     /**
@@ -358,7 +358,7 @@
     protected $tagname = 'input';
     protected $type = 'text';
     protected $allowed = array(
-        'type','name','value','size','tabindex',
+        'type','name','value','size','tabindex','autocapitalize',
         'autocomplete','checked','onchange','onclick','disabled','readonly',
         'spellcheck','results','maxlength','src','multiple','placeholder',
     );
@@ -532,7 +532,7 @@
 {
     protected $tagname = 'textarea';
     protected $allowed = array('name','rows','cols','wrap','tabindex',
-	'onchange','disabled','readonly','spellcheck');
+        'onchange','disabled','readonly','spellcheck');
 
     /**
      * Get HTML code for this object
@@ -563,7 +563,7 @@
         }
 
         return self::tag($this->tagname, $this->attrib, $value,
-	        array_merge(self::$common_attrib, $this->allowed));
+            array_merge(self::$common_attrib, $this->allowed));
     }
 }
 
@@ -591,7 +591,7 @@
     protected $tagname = 'select';
     protected $options = array();
     protected $allowed = array('name','size','tabindex','autocomplete',
-	'multiple','onchange','disabled','rel');
+        'multiple','onchange','disabled','rel');
 
     /**
      * Add a new option to this drop-down
@@ -655,7 +655,7 @@
 {
     protected $tagname = 'table';
     protected $allowed = array('id','class','style','width','summary',
-	    'cellpadding','cellspacing','border');
+        'cellpadding','cellspacing','border');
 
     private $header = array();
     private $rows = array();
@@ -705,8 +705,9 @@
      */
     public function add_header($attr, $cont)
     {
-        if (is_string($attr))
-    	    $attr = array('class' => $attr);
+        if (is_string($attr)) {
+            $attr = array('class' => $attr);
+        }
 
         $cell = new stdClass;
         $cell->attrib = $attr;
@@ -763,11 +764,13 @@
      */
     public function set_row_attribs($attr = array(), $index = null)
     {
-        if (is_string($attr))
-    	    $attr = array('class' => $attr);
+        if (is_string($attr)) {
+            $attr = array('class' => $attr);
+        }
 
-        if ($index === null)
+        if ($index === null) {
             $index = $this->rowindex;
+        }
 
         $this->rows[$index]->attrib = $attr;
     }
@@ -781,8 +784,9 @@
      */
     public function get_row_attribs($index = null)
     {
-        if ($index === null)
+        if ($index === null) {
             $index = $this->rowindex;
+        }
 
         return $this->rows[$index] ? $this->rows[$index]->attrib : null;
     }
diff --git a/program/include/rcmail.php b/program/include/rcmail.php
index a6b0bcd..02f38e6 100644
--- a/program/include/rcmail.php
+++ b/program/include/rcmail.php
@@ -157,14 +157,16 @@
       $this->config->set_user_prefs((array)$this->user->get_prefs());
     }
 
-    $_SESSION['language'] = $this->user->language = $this->language_prop($this->config->get('language', $_SESSION['language']));
+    $lang = $this->language_prop($this->config->get('language', $_SESSION['language']));
+    $_SESSION['language'] = $this->user->language = $lang;
 
     // set localization
-    setlocale(LC_ALL, $_SESSION['language'] . '.utf8', 'en_US.utf8');
+    setlocale(LC_ALL, $lang . '.utf8', $lang . '.UTF-8', 'en_US.utf8', 'en_US.UTF-8');
 
     // workaround for http://bugs.php.net/bug.php?id=18556
-    if (in_array($_SESSION['language'], array('tr_TR', 'ku', 'az_AZ')))
-      setlocale(LC_CTYPE, 'en_US' . '.utf8');
+    if (in_array($lang, array('tr_TR', 'ku', 'az_AZ'))) {
+      setlocale(LC_CTYPE, 'en_US.utf8', 'en_US.UTF-8');
+    }
   }
 
 
@@ -306,7 +308,7 @@
 
   /**
    * Init output object for GUI and add common scripts.
-   * This will instantiate a rcmail_template object and set
+   * This will instantiate a rcube_output_html object and set
    * environment vars according to the current session and configuration
    *
    * @param boolean True if this request is loaded in a (i)frame
@@ -453,7 +455,14 @@
     // Convert username to lowercase. If storage backend
     // is case-insensitive we need to store always the same username (#1487113)
     if ($config['login_lc']) {
-      $username = mb_strtolower($username);
+      if ($config['login_lc'] == 2 || $config['login_lc'] === true) {
+        $username = mb_strtolower($username);
+      }
+      else if (strpos($username, '@')) {
+        // lowercase domain name
+        list($local, $domain) = explode('@', $username);
+        $username = $local . '@' . mb_strtolower($domain);
+      }
     }
 
     // try to resolve email address from virtuser table
@@ -463,17 +472,13 @@
 
     // Here we need IDNA ASCII
     // Only rcube_contacts class is using domain names in Unicode
-    $host = rcube_utils::idn_to_ascii($host);
-    if (strpos($username, '@')) {
-      // lowercase domain name
-      list($local, $domain) = explode('@', $username);
-      $username = $local . '@' . mb_strtolower($domain);
-      $username = rcube_utils::idn_to_ascii($username);
-    }
+    $host     = rcube_utils::idn_to_ascii($host);
+    $username = rcube_utils::idn_to_ascii($username);
 
     // user already registered -> overwrite username
-    if ($user = rcube_user::query($username, $host))
+    if ($user = rcube_user::query($username, $host)) {
       $username = $user->data['username'];
+    }
 
     $storage = $this->get_storage();
 
@@ -1204,7 +1209,7 @@
         }
         else {
             if (!empty($date)) {
-                $timestamp = rcube_strtotime($date);
+                $timestamp = rcube_utils::strtotime($date);
             }
 
             if (empty($timestamp)) {
diff --git a/program/include/rcube.php b/program/include/rcube.php
index 494b5c3..0e40b3c 100644
--- a/program/include/rcube.php
+++ b/program/include/rcube.php
@@ -25,364 +25,372 @@
  * Base class of the Roundcube Framework
  * implemented as singleton
  *
- * @package Core
+ * @package    Framework
+ * @subpackage Core
  */
 class rcube
 {
-  const INIT_WITH_DB = 1;
-  const INIT_WITH_PLUGINS = 2;
+    const INIT_WITH_DB = 1;
+    const INIT_WITH_PLUGINS = 2;
 
-  /**
-   * Singleton instace of rcube
-   *
-   * @var rcmail
-   */
-  static protected $instance;
+    /**
+     * Singleton instace of rcube
+     *
+     * @var rcmail
+     */
+    static protected $instance;
 
-  /**
-   * Stores instance of rcube_config.
-   *
-   * @var rcube_config
-   */
-  public $config;
+    /**
+     * Stores instance of rcube_config.
+     *
+     * @var rcube_config
+     */
+    public $config;
 
-  /**
-   * Instace of database class.
-   *
-   * @var rcube_pdo
-   */
-  public $db;
+    /**
+     * Instace of database class.
+     *
+     * @var rcube_db
+     */
+    public $db;
 
-  /**
-   * Instace of Memcache class.
-   *
-   * @var Memcache
-   */
-  public $memcache;
+    /**
+     * Instace of Memcache class.
+     *
+     * @var Memcache
+     */
+    public $memcache;
 
-  /**
-   * Instace of rcube_session class.
-   *
-   * @var rcube_session
-   */
-  public $session;
+   /**
+     * Instace of rcube_session class.
+     *
+     * @var rcube_session
+     */
+    public $session;
 
-  /**
-   * Instance of rcube_smtp class.
-   *
-   * @var rcube_smtp
-   */
-  public $smtp;
+    /**
+     * Instance of rcube_smtp class.
+     *
+     * @var rcube_smtp
+     */
+    public $smtp;
 
-  /**
-   * Instance of rcube_storage class.
-   *
-   * @var rcube_storage
-   */
-  public $storage;
+    /**
+     * Instance of rcube_storage class.
+     *
+     * @var rcube_storage
+     */
+    public $storage;
 
-  /**
-   * Instance of rcube_output class.
-   *
-   * @var rcube_output
-   */
-  public $output;
+    /**
+     * Instance of rcube_output class.
+     *
+     * @var rcube_output
+     */
+    public $output;
 
-  /**
-   * Instance of rcube_plugin_api.
-   *
-   * @var rcube_plugin_api
-   */
-  public $plugins;
+    /**
+     * Instance of rcube_plugin_api.
+     *
+     * @var rcube_plugin_api
+     */
+    public $plugins;
 
 
-  /* private/protected vars */
-  protected $texts;
-  protected $caches = array();
-  protected $shutdown_functions = array();
-  protected $expunge_cache = false;
+    /* private/protected vars */
+    protected $texts;
+    protected $caches = array();
+    protected $shutdown_functions = array();
+    protected $expunge_cache = false;
 
 
-  /**
-   * This implements the 'singleton' design pattern
-   *
-   * @param integer Options to initialize with this instance. See rcube::INIT_WITH_* constants
-   * @return rcube The one and only instance
-   */
-  static function get_instance($mode = 0)
-  {
-    if (!self::$instance) {
-      self::$instance = new rcube();
-      self::$instance->init($mode);
-    }
-
-    return self::$instance;
-  }
-
-
-  /**
-   * Private constructor
-   */
-  protected function __construct()
-  {
-    // load configuration
-    $this->config = new rcube_config();
-    $this->plugins = new rcube_dummy_plugin_api;
-
-    register_shutdown_function(array($this, 'shutdown'));
-  }
-
-
-  /**
-   * Initial startup function
-   */
-  protected function init($mode = 0)
-  {
-    // initialize syslog
-    if ($this->config->get('log_driver') == 'syslog') {
-      $syslog_id = $this->config->get('syslog_id', 'roundcube');
-      $syslog_facility = $this->config->get('syslog_facility', LOG_USER);
-      openlog($syslog_id, LOG_ODELAY, $syslog_facility);
-    }
-
-    // connect to database
-    if ($mode & self::INIT_WITH_DB) {
-      $this->get_dbh();
-    }
-
-    // create plugin API and load plugins
-    if ($mode & self::INIT_WITH_PLUGINS) {
-      $this->plugins = rcube_plugin_api::get_instance();
-    }
-  }
-
-
-  /**
-   * Get the current database connection
-   *
-   * @return rcube_pdo  Database connection object
-   */
-  public function get_dbh()
-  {
-    if (!$this->db) {
-      $config_all = $this->config->all();
-      $this->db = rcube_db::factory($config_all['db_dsnw'], $config_all['db_dsnr'], $config_all['db_persistent']);
-      $this->db->set_debug((bool)$config_all['sql_debug']);
-    }
-
-    return $this->db;
-  }
-
-
-  /**
-   * Get global handle for memcache access
-   *
-   * @return object Memcache
-   */
-  public function get_memcache()
-  {
-    if (!isset($this->memcache)) {
-      // no memcache support in PHP
-      if (!class_exists('Memcache')) {
-        $this->memcache = false;
-        return false;
-      }
-
-      $this->memcache = new Memcache;
-      $this->mc_available = 0;
-
-      // add all configured hosts to pool
-      $pconnect = $this->config->get('memcache_pconnect', true);
-      foreach ($this->config->get('memcache_hosts', array()) as $host) {
-        if (substr($host, 0, 7) != 'unix://') {
-          list($host, $port) = explode(':', $host);
-          if (!$port) $port = 11211;
-        }
-        else {
-          $port = 0;
+    /**
+     * This implements the 'singleton' design pattern
+     *
+     * @param integer Options to initialize with this instance. See rcube::INIT_WITH_* constants
+     *
+     * @return rcube The one and only instance
+     */
+    static function get_instance($mode = 0)
+    {
+        if (!self::$instance) {
+            self::$instance = new rcube();
+            self::$instance->init($mode);
         }
 
-        $this->mc_available += intval($this->memcache->addServer($host, $port, $pconnect, 1, 1, 15, false, array($this, 'memcache_failure')));
-      }
-
-      // test connection and failover (will result in $this->mc_available == 0 on complete failure)
-      $this->memcache->increment('__CONNECTIONTEST__', 1);  // NOP if key doesn't exist
-
-      if (!$this->mc_available)
-        $this->memcache = false;
+        return self::$instance;
     }
 
-    return $this->memcache;
-  }
 
+    /**
+     * Private constructor
+     */
+    protected function __construct()
+    {
+        // load configuration
+        $this->config  = new rcube_config;
+        $this->plugins = new rcube_dummy_plugin_api;
 
-  /**
-   * Callback for memcache failure
-   */
-  public function memcache_failure($host, $port)
-  {
-    static $seen = array();
-
-    // only report once
-    if (!$seen["$host:$port"]++) {
-      $this->mc_available--;
-      self::raise_error(array('code' => 604, 'type' => 'db',
-        'line' => __LINE__, 'file' => __FILE__,
-        'message' => "Memcache failure on host $host:$port"),
-        true, false);
-    }
-  }
-
-
-  /**
-   * Initialize and get cache object
-   *
-   * @param string $name   Cache identifier
-   * @param string $type   Cache type ('db', 'apc' or 'memcache')
-   * @param string $ttl    Expiration time for cache items
-   * @param bool   $packed Enables/disables data serialization
-   *
-   * @return rcube_cache Cache object
-   */
-  public function get_cache($name, $type='db', $ttl=0, $packed=true)
-  {
-    if (!isset($this->caches[$name]) && ($userid = $this->get_user_id())) {
-      $this->caches[$name] = new rcube_cache($type, $userid, $name, $ttl, $packed);
+        register_shutdown_function(array($this, 'shutdown'));
     }
 
-    return $this->caches[$name];
-  }
 
+    /**
+     * Initial startup function
+     */
+    protected function init($mode = 0)
+    {
+        // initialize syslog
+        if ($this->config->get('log_driver') == 'syslog') {
+            $syslog_id       = $this->config->get('syslog_id', 'roundcube');
+            $syslog_facility = $this->config->get('syslog_facility', LOG_USER);
+            openlog($syslog_id, LOG_ODELAY, $syslog_facility);
+        }
 
-  /**
-   * Create SMTP object and connect to server
-   *
-   * @param boolean True if connection should be established
-   */
-  public function smtp_init($connect = false)
-  {
-    $this->smtp = new rcube_smtp();
+        // connect to database
+        if ($mode & self::INIT_WITH_DB) {
+            $this->get_dbh();
+        }
 
-    if ($connect)
-      $this->smtp->connect();
-  }
-
-
-  /**
-   * Initialize and get storage object
-   *
-   * @return rcube_storage Storage object
-   */
-  public function get_storage()
-  {
-    // already initialized
-    if (!is_object($this->storage)) {
-      $this->storage_init();
+        // create plugin API and load plugins
+        if ($mode & self::INIT_WITH_PLUGINS) {
+            $this->plugins = rcube_plugin_api::get_instance();
+        }
     }
 
-    return $this->storage;
-  }
 
+    /**
+     * Get the current database connection
+     *
+     * @return rcube_db Database object
+     */
+    public function get_dbh()
+    {
+        if (!$this->db) {
+            $config_all = $this->config->all();
+            $this->db = rcube_db::factory($config_all['db_dsnw'], $config_all['db_dsnr'], $config_all['db_persistent']);
+            $this->db->set_debug((bool)$config_all['sql_debug']);
+        }
 
-  /**
-   * Initialize storage object
-   */
-  public function storage_init()
-  {
-    // already initialized
-    if (is_object($this->storage)) {
-      return;
+        return $this->db;
     }
 
-    $driver = $this->config->get('storage_driver', 'imap');
-    $driver_class = "rcube_{$driver}";
 
-    if (!class_exists($driver_class)) {
-      self::raise_error(array(
-        'code' => 700, 'type' => 'php',
-        'file' => __FILE__, 'line' => __LINE__,
-        'message' => "Storage driver class ($driver) not found!"),
-        true, true);
+    /**
+     * Get global handle for memcache access
+     *
+     * @return object Memcache
+     */
+    public function get_memcache()
+    {
+        if (!isset($this->memcache)) {
+            // no memcache support in PHP
+            if (!class_exists('Memcache')) {
+                $this->memcache = false;
+                return false;
+            }
+
+            $this->memcache     = new Memcache;
+            $this->mc_available = 0;
+
+            // add all configured hosts to pool
+            $pconnect = $this->config->get('memcache_pconnect', true);
+            foreach ($this->config->get('memcache_hosts', array()) as $host) {
+                if (substr($host, 0, 7) != 'unix://') {
+                    list($host, $port) = explode(':', $host);
+                    if (!$port) $port = 11211;
+                }
+                else {
+                    $port = 0;
+                }
+
+                $this->mc_available += intval($this->memcache->addServer(
+                    $host, $port, $pconnect, 1, 1, 15, false, array($this, 'memcache_failure')));
+            }
+
+            // test connection and failover (will result in $this->mc_available == 0 on complete failure)
+            $this->memcache->increment('__CONNECTIONTEST__', 1);  // NOP if key doesn't exist
+
+            if (!$this->mc_available) {
+                $this->memcache = false;
+            }
+        }
+
+        return $this->memcache;
     }
 
-    // Initialize storage object
-    $this->storage = new $driver_class;
 
-    // for backward compat. (deprecated, will be removed)
-    $this->imap = $this->storage;
+    /**
+     * Callback for memcache failure
+     */
+    public function memcache_failure($host, $port)
+    {
+        static $seen = array();
 
-    // enable caching of mail data
-    $storage_cache  = $this->config->get("{$driver}_cache");
-    $messages_cache = $this->config->get('messages_cache');
-    // for backward compatybility
-    if ($storage_cache === null && $messages_cache === null && $this->config->get('enable_caching')) {
-        $storage_cache  = 'db';
-        $messages_cache = true;
+        // only report once
+        if (!$seen["$host:$port"]++) {
+            $this->mc_available--;
+            self::raise_error(array(
+                'code' => 604, 'type' => 'db',
+                'line' => __LINE__, 'file' => __FILE__,
+                'message' => "Memcache failure on host $host:$port"),
+                true, false);
+        }
     }
 
-    if ($storage_cache)
-        $this->storage->set_caching($storage_cache);
-    if ($messages_cache)
-        $this->storage->set_messages_caching(true);
 
-    // set pagesize from config
-    $pagesize = $this->config->get('mail_pagesize');
-    if (!$pagesize) {
-        $pagesize = $this->config->get('pagesize', 50);
-    }
-    $this->storage->set_pagesize($pagesize);
+    /**
+     * Initialize and get cache object
+     *
+     * @param string $name   Cache identifier
+     * @param string $type   Cache type ('db', 'apc' or 'memcache')
+     * @param string $ttl    Expiration time for cache items
+     * @param bool   $packed Enables/disables data serialization
+     *
+     * @return rcube_cache Cache object
+     */
+    public function get_cache($name, $type='db', $ttl=0, $packed=true)
+    {
+        if (!isset($this->caches[$name]) && ($userid = $this->get_user_id())) {
+            $this->caches[$name] = new rcube_cache($type, $userid, $name, $ttl, $packed);
+        }
 
-    // set class options
-    $options = array(
-      'auth_type'   => $this->config->get("{$driver}_auth_type", 'check'),
-      'auth_cid'    => $this->config->get("{$driver}_auth_cid"),
-      'auth_pw'     => $this->config->get("{$driver}_auth_pw"),
-      'debug'       => (bool) $this->config->get("{$driver}_debug"),
-      'force_caps'  => (bool) $this->config->get("{$driver}_force_caps"),
-      'timeout'     => (int) $this->config->get("{$driver}_timeout"),
-      'skip_deleted' => (bool) $this->config->get('skip_deleted'),
-      'driver'      => $driver,
-    );
-
-    if (!empty($_SESSION['storage_host'])) {
-      $options['host']     = $_SESSION['storage_host'];
-      $options['user']     = $_SESSION['username'];
-      $options['port']     = $_SESSION['storage_port'];
-      $options['ssl']      = $_SESSION['storage_ssl'];
-      $options['password'] = $this->decrypt($_SESSION['password']);
-      $_SESSION[$driver.'_host'] = $_SESSION['storage_host'];
+        return $this->caches[$name];
     }
 
-    $options = $this->plugins->exec_hook("storage_init", $options);
 
-    // for backward compat. (deprecated, to be removed)
-    $options = $this->plugins->exec_hook("imap_init", $options);
+    /**
+     * Create SMTP object and connect to server
+     *
+     * @param boolean True if connection should be established
+     */
+    public function smtp_init($connect = false)
+    {
+        $this->smtp = new rcube_smtp();
 
-    $this->storage->set_options($options);
-    $this->set_storage_prop();
-  }
-
-
-  /**
-   * Set storage parameters.
-   * This must be done AFTER connecting to the server!
-   */
-  protected function set_storage_prop()
-  {
-    $storage = $this->get_storage();
-
-    $storage->set_charset($this->config->get('default_charset', RCMAIL_CHARSET));
-
-    if ($default_folders = $this->config->get('default_folders')) {
-      $storage->set_default_folders($default_folders);
+        if ($connect) {
+            $this->smtp->connect();
+        }
     }
-    if (isset($_SESSION['mbox'])) {
-      $storage->set_folder($_SESSION['mbox']);
+
+
+    /**
+     * Initialize and get storage object
+     *
+     * @return rcube_storage Storage object
+     */
+    public function get_storage()
+    {
+        // already initialized
+        if (!is_object($this->storage)) {
+            $this->storage_init();
+        }
+
+        return $this->storage;
     }
-    if (isset($_SESSION['page'])) {
-      $storage->set_page($_SESSION['page']);
+
+
+    /**
+     * Initialize storage object
+     */
+    public function storage_init()
+    {
+        // already initialized
+        if (is_object($this->storage)) {
+            return;
+        }
+
+        $driver       = $this->config->get('storage_driver', 'imap');
+        $driver_class = "rcube_{$driver}";
+
+        if (!class_exists($driver_class)) {
+            self::raise_error(array(
+                'code' => 700, 'type' => 'php',
+                'file' => __FILE__, 'line' => __LINE__,
+                'message' => "Storage driver class ($driver) not found!"),
+                true, true);
+        }
+
+        // Initialize storage object
+        $this->storage = new $driver_class;
+
+        // for backward compat. (deprecated, will be removed)
+        $this->imap = $this->storage;
+
+        // enable caching of mail data
+        $storage_cache  = $this->config->get("{$driver}_cache");
+        $messages_cache = $this->config->get('messages_cache');
+        // for backward compatybility
+        if ($storage_cache === null && $messages_cache === null && $this->config->get('enable_caching')) {
+            $storage_cache  = 'db';
+            $messages_cache = true;
+        }
+
+        if ($storage_cache) {
+            $this->storage->set_caching($storage_cache);
+        }
+        if ($messages_cache) {
+            $this->storage->set_messages_caching(true);
+        }
+
+        // set pagesize from config
+        $pagesize = $this->config->get('mail_pagesize');
+        if (!$pagesize) {
+            $pagesize = $this->config->get('pagesize', 50);
+        }
+        $this->storage->set_pagesize($pagesize);
+
+        // set class options
+        $options = array(
+            'auth_type'   => $this->config->get("{$driver}_auth_type", 'check'),
+            'auth_cid'    => $this->config->get("{$driver}_auth_cid"),
+            'auth_pw'     => $this->config->get("{$driver}_auth_pw"),
+            'debug'       => (bool) $this->config->get("{$driver}_debug"),
+            'force_caps'  => (bool) $this->config->get("{$driver}_force_caps"),
+            'timeout'     => (int) $this->config->get("{$driver}_timeout"),
+            'skip_deleted' => (bool) $this->config->get('skip_deleted'),
+            'driver'      => $driver,
+        );
+
+        if (!empty($_SESSION['storage_host'])) {
+            $options['host']     = $_SESSION['storage_host'];
+            $options['user']     = $_SESSION['username'];
+            $options['port']     = $_SESSION['storage_port'];
+            $options['ssl']      = $_SESSION['storage_ssl'];
+            $options['password'] = $this->decrypt($_SESSION['password']);
+            $_SESSION[$driver.'_host'] = $_SESSION['storage_host'];
+        }
+
+        $options = $this->plugins->exec_hook("storage_init", $options);
+
+        // for backward compat. (deprecated, to be removed)
+        $options = $this->plugins->exec_hook("imap_init", $options);
+
+        $this->storage->set_options($options);
+        $this->set_storage_prop();
     }
-  }
+
+
+    /**
+     * Set storage parameters.
+     * This must be done AFTER connecting to the server!
+     */
+    protected function set_storage_prop()
+    {
+        $storage = $this->get_storage();
+
+        $storage->set_charset($this->config->get('default_charset', RCMAIL_CHARSET));
+
+        if ($default_folders = $this->config->get('default_folders')) {
+            $storage->set_default_folders($default_folders);
+        }
+        if (isset($_SESSION['mbox'])) {
+            $storage->set_folder($_SESSION['mbox']);
+        }
+        if (isset($_SESSION['page'])) {
+            $storage->set_page($_SESSION['page']);
+        }
+    }
 
 
     /**
@@ -397,11 +405,16 @@
 
         $sess_name   = $this->config->get('session_name');
         $sess_domain = $this->config->get('session_domain');
+        $sess_path   = $this->config->get('session_path');
         $lifetime    = $this->config->get('session_lifetime', 0) * 60;
 
         // set session domain
         if ($sess_domain) {
             ini_set('session.cookie_domain', $sess_domain);
+        }
+        // set session path
+        if ($sess_path) {
+            ini_set('session.cookie_path', $sess_path);
         }
         // set session garbage collecting time according to session_lifetime
         if ($lifetime) {
@@ -492,433 +505,469 @@
     }
 
 
-  /**
-   * Get localized text in the desired language
-   *
-   * @param mixed   $attrib  Named parameters array or label name
-   * @param string  $domain  Label domain (plugin) name
-   *
-   * @return string Localized text
-   */
-  public function gettext($attrib, $domain=null)
-  {
-    // load localization files if not done yet
-    if (empty($this->texts))
-      $this->load_language();
-
-    // extract attributes
-    if (is_string($attrib))
-      $attrib = array('name' => $attrib);
-
-    $name = $attrib['name'] ? $attrib['name'] : '';
-
-    // attrib contain text values: use them from now
-    if (($setval = $attrib[strtolower($_SESSION['language'])]) || ($setval = $attrib['en_us']))
-        $this->texts[$name] = $setval;
-
-    // check for text with domain
-    if ($domain && ($text = $this->texts[$domain.'.'.$name]))
-      ;
-    // text does not exist
-    else if (!($text = $this->texts[$name])) {
-      return "[$name]";
-    }
-
-    // replace vars in text
-    if (is_array($attrib['vars'])) {
-      foreach ($attrib['vars'] as $var_key => $var_value)
-        $text = str_replace($var_key[0]!='$' ? '$'.$var_key : $var_key, $var_value, $text);
-    }
-
-    // format output
-    if (($attrib['uppercase'] && strtolower($attrib['uppercase']=='first')) || $attrib['ucfirst'])
-      return ucfirst($text);
-    else if ($attrib['uppercase'])
-      return mb_strtoupper($text);
-    else if ($attrib['lowercase'])
-      return mb_strtolower($text);
-
-    return strtr($text, array('\n' => "\n"));
-  }
-
-
-  /**
-   * Check if the given text label exists
-   *
-   * @param string  $name       Label name
-   * @param string  $domain     Label domain (plugin) name or '*' for all domains
-   * @param string  $ref_domain Sets domain name if label is found
-   *
-   * @return boolean True if text exists (either in the current language or in en_US)
-   */
-  public function text_exists($name, $domain = null, &$ref_domain = null)
-  {
-    // load localization files if not done yet
-    if (empty($this->texts))
-      $this->load_language();
-
-    if (isset($this->texts[$name])) {
-        $ref_domain = '';
-        return true;
-    }
-
-    // any of loaded domains (plugins)
-    if ($domain == '*') {
-      foreach ($this->plugins->loaded_plugins() as $domain)
-        if (isset($this->texts[$domain.'.'.$name])) {
-          $ref_domain = $domain;
-          return true;
-        }
-    }
-    // specified domain
-    else if ($domain) {
-      $ref_domain = $domain;
-      return isset($this->texts[$domain.'.'.$name]);
-    }
-
-    return false;
-  }
-
-  /**
-   * Load a localization package
-   *
-   * @param string Language ID
-   */
-  public function load_language($lang = null, $add = array())
-  {
-    $lang = $this->language_prop(($lang ? $lang : $_SESSION['language']));
-
-    // load localized texts
-    if (empty($this->texts) || $lang != $_SESSION['language']) {
-      $this->texts = array();
-
-      // handle empty lines after closing PHP tag in localization files
-      ob_start();
-
-      // get english labels (these should be complete)
-      @include(INSTALL_PATH . 'program/localization/en_US/labels.inc');
-      @include(INSTALL_PATH . 'program/localization/en_US/messages.inc');
-
-      if (is_array($labels))
-        $this->texts = $labels;
-      if (is_array($messages))
-        $this->texts = array_merge($this->texts, $messages);
-
-      // include user language files
-      if ($lang != 'en' && $lang != 'en_US' && is_dir(INSTALL_PATH . 'program/localization/' . $lang)) {
-        include_once(INSTALL_PATH . 'program/localization/' . $lang . '/labels.inc');
-        include_once(INSTALL_PATH . 'program/localization/' . $lang . '/messages.inc');
-
-        if (is_array($labels))
-          $this->texts = array_merge($this->texts, $labels);
-        if (is_array($messages))
-          $this->texts = array_merge($this->texts, $messages);
-      }
-
-      ob_end_clean();
-
-      $_SESSION['language'] = $lang;
-    }
-
-    // append additional texts (from plugin)
-    if (is_array($add) && !empty($add))
-      $this->texts += $add;
-  }
-
-
-  /**
-   * Check the given string and return a valid language code
-   *
-   * @param string Language code
-   * @return string Valid language code
-   */
-  protected function language_prop($lang)
-  {
-    static $rcube_languages, $rcube_language_aliases;
-
-    // user HTTP_ACCEPT_LANGUAGE if no language is specified
-    if (empty($lang) || $lang == 'auto') {
-       $accept_langs = explode(',', $_SERVER['HTTP_ACCEPT_LANGUAGE']);
-       $lang = str_replace('-', '_', $accept_langs[0]);
-     }
-
-    if (empty($rcube_languages)) {
-      @include(INSTALL_PATH . 'program/localization/index.inc');
-    }
-
-    // check if we have an alias for that language
-    if (!isset($rcube_languages[$lang]) && isset($rcube_language_aliases[$lang])) {
-      $lang = $rcube_language_aliases[$lang];
-    }
-    // try the first two chars
-    else if (!isset($rcube_languages[$lang])) {
-      $short = substr($lang, 0, 2);
-
-      // check if we have an alias for the short language code
-      if (!isset($rcube_languages[$short]) && isset($rcube_language_aliases[$short])) {
-        $lang = $rcube_language_aliases[$short];
-      }
-      // expand 'nn' to 'nn_NN'
-      else if (!isset($rcube_languages[$short])) {
-        $lang = $short.'_'.strtoupper($short);
-      }
-    }
-
-    if (!isset($rcube_languages[$lang]) || !is_dir(INSTALL_PATH . 'program/localization/' . $lang)) {
-      $lang = 'en_US';
-    }
-
-    return $lang;
-  }
-
-
-  /**
-   * Read directory program/localization and return a list of available languages
-   *
-   * @return array List of available localizations
-   */
-  public function list_languages()
-  {
-    static $sa_languages = array();
-
-    if (!sizeof($sa_languages)) {
-      @include(INSTALL_PATH . 'program/localization/index.inc');
-
-      if ($dh = @opendir(INSTALL_PATH . 'program/localization')) {
-        while (($name = readdir($dh)) !== false) {
-          if ($name[0] == '.' || !is_dir(INSTALL_PATH . 'program/localization/' . $name))
-            continue;
-
-          if ($label = $rcube_languages[$name])
-            $sa_languages[$name] = $label;
-        }
-        closedir($dh);
-      }
-    }
-
-    return $sa_languages;
-  }
-
-
-  /**
-   * Encrypt using 3DES
-   *
-   * @param string $clear clear text input
-   * @param string $key encryption key to retrieve from the configuration, defaults to 'des_key'
-   * @param boolean $base64 whether or not to base64_encode() the result before returning
-   *
-   * @return string encrypted text
-   */
-  public function encrypt($clear, $key = 'des_key', $base64 = true)
-  {
-    if (!$clear)
-      return '';
-
-    /*-
-     * Add a single canary byte to the end of the clear text, which
-     * will help find out how much of padding will need to be removed
-     * upon decryption; see http://php.net/mcrypt_generic#68082
+    /**
+     * Get localized text in the desired language
+     *
+     * @param mixed   $attrib  Named parameters array or label name
+     * @param string  $domain  Label domain (plugin) name
+     *
+     * @return string Localized text
      */
-    $clear = pack("a*H2", $clear, "80");
+    public function gettext($attrib, $domain=null)
+    {
+        // load localization files if not done yet
+        if (empty($this->texts)) {
+            $this->load_language();
+        }
 
-    if (function_exists('mcrypt_module_open') &&
-        ($td = mcrypt_module_open(MCRYPT_TripleDES, "", MCRYPT_MODE_CBC, ""))) {
-      $iv = $this->create_iv(mcrypt_enc_get_iv_size($td));
-      mcrypt_generic_init($td, $this->config->get_crypto_key($key), $iv);
-      $cipher = $iv . mcrypt_generic($td, $clear);
-      mcrypt_generic_deinit($td);
-      mcrypt_module_close($td);
-    }
-    else {
-      @include_once 'des.inc';
+        // extract attributes
+        if (is_string($attrib)) {
+            $attrib = array('name' => $attrib);
+        }
 
-      if (function_exists('des')) {
-        $des_iv_size = 8;
-        $iv = $this->create_iv($des_iv_size);
-        $cipher = $iv . des($this->config->get_crypto_key($key), $clear, 1, 1, $iv);
-      }
-      else {
-        self::raise_error(array(
-          'code' => 500, 'type' => 'php',
-          'file' => __FILE__, 'line' => __LINE__,
-          'message' => "Could not perform encryption; make sure Mcrypt is installed or lib/des.inc is available"
-        ), true, true);
-      }
+        $name = $attrib['name'] ? $attrib['name'] : '';
+
+        // attrib contain text values: use them from now
+        if (($setval = $attrib[strtolower($_SESSION['language'])]) || ($setval = $attrib['en_us'])) {
+            $this->texts[$name] = $setval;
+        }
+
+        // check for text with domain
+        if ($domain && ($text = $this->texts[$domain.'.'.$name])) {
+        }
+        // text does not exist
+        else if (!($text = $this->texts[$name])) {
+            return "[$name]";
+        }
+
+        // replace vars in text
+        if (is_array($attrib['vars'])) {
+            foreach ($attrib['vars'] as $var_key => $var_value) {
+                $text = str_replace($var_key[0]!='$' ? '$'.$var_key : $var_key, $var_value, $text);
+            }
+        }
+
+        // format output
+        if (($attrib['uppercase'] && strtolower($attrib['uppercase'] == 'first')) || $attrib['ucfirst']) {
+            return ucfirst($text);
+        }
+        else if ($attrib['uppercase']) {
+            return mb_strtoupper($text);
+        }
+        else if ($attrib['lowercase']) {
+            return mb_strtolower($text);
+        }
+
+        return strtr($text, array('\n' => "\n"));
     }
 
-    return $base64 ? base64_encode($cipher) : $cipher;
-  }
 
-  /**
-   * Decrypt 3DES-encrypted string
-   *
-   * @param string $cipher encrypted text
-   * @param string $key encryption key to retrieve from the configuration, defaults to 'des_key'
-   * @param boolean $base64 whether or not input is base64-encoded
-   *
-   * @return string decrypted text
-   */
-  public function decrypt($cipher, $key = 'des_key', $base64 = true)
-  {
-    if (!$cipher)
-      return '';
+    /**
+     * Check if the given text label exists
+     *
+     * @param string  $name       Label name
+     * @param string  $domain     Label domain (plugin) name or '*' for all domains
+     * @param string  $ref_domain Sets domain name if label is found
+     *
+     * @return boolean True if text exists (either in the current language or in en_US)
+     */
+    public function text_exists($name, $domain = null, &$ref_domain = null)
+    {
+        // load localization files if not done yet
+        if (empty($this->texts)) {
+            $this->load_language();
+        }
 
-    $cipher = $base64 ? base64_decode($cipher) : $cipher;
+        if (isset($this->texts[$name])) {
+            $ref_domain = '';
+            return true;
+        }
 
-    if (function_exists('mcrypt_module_open') &&
-        ($td = mcrypt_module_open(MCRYPT_TripleDES, "", MCRYPT_MODE_CBC, ""))) {
-      $iv_size = mcrypt_enc_get_iv_size($td);
-      $iv = substr($cipher, 0, $iv_size);
+        // any of loaded domains (plugins)
+        if ($domain == '*') {
+            foreach ($this->plugins->loaded_plugins() as $domain) {
+                if (isset($this->texts[$domain.'.'.$name])) {
+                    $ref_domain = $domain;
+                    return true;
+                }
+            }
+        }
+        // specified domain
+        else if ($domain) {
+            $ref_domain = $domain;
+            return isset($this->texts[$domain.'.'.$name]);
+        }
 
-      // session corruption? (#1485970)
-      if (strlen($iv) < $iv_size)
+        return false;
+    }
+
+
+    /**
+     * Load a localization package
+     *
+     * @param string Language ID
+     * @param array  Additional text labels/messages
+     */
+    public function load_language($lang = null, $add = array())
+    {
+        $lang = $this->language_prop(($lang ? $lang : $_SESSION['language']));
+
+        // load localized texts
+        if (empty($this->texts) || $lang != $_SESSION['language']) {
+            $this->texts = array();
+
+            // handle empty lines after closing PHP tag in localization files
+            ob_start();
+
+            // get english labels (these should be complete)
+            @include(INSTALL_PATH . 'program/localization/en_US/labels.inc');
+            @include(INSTALL_PATH . 'program/localization/en_US/messages.inc');
+
+            if (is_array($labels))
+                $this->texts = $labels;
+            if (is_array($messages))
+                $this->texts = array_merge($this->texts, $messages);
+
+            // include user language files
+            if ($lang != 'en' && $lang != 'en_US' && is_dir(INSTALL_PATH . 'program/localization/' . $lang)) {
+                include_once(INSTALL_PATH . 'program/localization/' . $lang . '/labels.inc');
+                include_once(INSTALL_PATH . 'program/localization/' . $lang . '/messages.inc');
+
+                if (is_array($labels))
+                    $this->texts = array_merge($this->texts, $labels);
+                if (is_array($messages))
+                    $this->texts = array_merge($this->texts, $messages);
+            }
+
+            ob_end_clean();
+
+            $_SESSION['language'] = $lang;
+        }
+
+        // append additional texts (from plugin)
+        if (is_array($add) && !empty($add)) {
+            $this->texts += $add;
+        }
+    }
+
+
+    /**
+     * Check the given string and return a valid language code
+     *
+     * @param string Language code
+     *
+     * @return string Valid language code
+     */
+    protected function language_prop($lang)
+    {
+        static $rcube_languages, $rcube_language_aliases;
+
+        // user HTTP_ACCEPT_LANGUAGE if no language is specified
+        if (empty($lang) || $lang == 'auto') {
+            $accept_langs = explode(',', $_SERVER['HTTP_ACCEPT_LANGUAGE']);
+            $lang         = str_replace('-', '_', $accept_langs[0]);
+        }
+
+        if (empty($rcube_languages)) {
+            @include(INSTALL_PATH . 'program/localization/index.inc');
+        }
+
+        // check if we have an alias for that language
+        if (!isset($rcube_languages[$lang]) && isset($rcube_language_aliases[$lang])) {
+            $lang = $rcube_language_aliases[$lang];
+        }
+        // try the first two chars
+        else if (!isset($rcube_languages[$lang])) {
+            $short = substr($lang, 0, 2);
+
+            // check if we have an alias for the short language code
+            if (!isset($rcube_languages[$short]) && isset($rcube_language_aliases[$short])) {
+                $lang = $rcube_language_aliases[$short];
+            }
+            // expand 'nn' to 'nn_NN'
+            else if (!isset($rcube_languages[$short])) {
+                $lang = $short.'_'.strtoupper($short);
+            }
+        }
+
+        if (!isset($rcube_languages[$lang]) || !is_dir(INSTALL_PATH . 'program/localization/' . $lang)) {
+            $lang = 'en_US';
+        }
+
+        return $lang;
+    }
+
+
+    /**
+     * Read directory program/localization and return a list of available languages
+     *
+     * @return array List of available localizations
+     */
+    public function list_languages()
+    {
+        static $sa_languages = array();
+
+        if (!sizeof($sa_languages)) {
+            @include(INSTALL_PATH . 'program/localization/index.inc');
+
+            if ($dh = @opendir(INSTALL_PATH . 'program/localization')) {
+                while (($name = readdir($dh)) !== false) {
+                    if ($name[0] == '.' || !is_dir(INSTALL_PATH . 'program/localization/' . $name)) {
+                        continue;
+                    }
+
+                    if ($label = $rcube_languages[$name]) {
+                        $sa_languages[$name] = $label;
+                    }
+                }
+                closedir($dh);
+            }
+        }
+
+        return $sa_languages;
+    }
+
+
+    /**
+     * Encrypt using 3DES
+     *
+     * @param string $clear clear text input
+     * @param string $key encryption key to retrieve from the configuration, defaults to 'des_key'
+     * @param boolean $base64 whether or not to base64_encode() the result before returning
+     *
+     * @return string encrypted text
+     */
+    public function encrypt($clear, $key = 'des_key', $base64 = true)
+    {
+        if (!$clear) {
+            return '';
+        }
+
+        /*-
+         * Add a single canary byte to the end of the clear text, which
+         * will help find out how much of padding will need to be removed
+         * upon decryption; see http://php.net/mcrypt_generic#68082
+         */
+        $clear = pack("a*H2", $clear, "80");
+
+        if (function_exists('mcrypt_module_open') &&
+            ($td = mcrypt_module_open(MCRYPT_TripleDES, "", MCRYPT_MODE_CBC, ""))
+        ) {
+            $iv = $this->create_iv(mcrypt_enc_get_iv_size($td));
+            mcrypt_generic_init($td, $this->config->get_crypto_key($key), $iv);
+            $cipher = $iv . mcrypt_generic($td, $clear);
+            mcrypt_generic_deinit($td);
+            mcrypt_module_close($td);
+        }
+        else {
+            @include_once 'des.inc';
+
+            if (function_exists('des')) {
+                $des_iv_size = 8;
+                $iv = $this->create_iv($des_iv_size);
+                $cipher = $iv . des($this->config->get_crypto_key($key), $clear, 1, 1, $iv);
+            }
+            else {
+                self::raise_error(array(
+                    'code' => 500, 'type' => 'php',
+                    'file' => __FILE__, 'line' => __LINE__,
+                    'message' => "Could not perform encryption; make sure Mcrypt is installed or lib/des.inc is available"
+                    ), true, true);
+            }
+        }
+
+        return $base64 ? base64_encode($cipher) : $cipher;
+    }
+
+
+    /**
+     * Decrypt 3DES-encrypted string
+     *
+     * @param string $cipher encrypted text
+     * @param string $key encryption key to retrieve from the configuration, defaults to 'des_key'
+     * @param boolean $base64 whether or not input is base64-encoded
+     *
+     * @return string decrypted text
+     */
+    public function decrypt($cipher, $key = 'des_key', $base64 = true)
+    {
+        if (!$cipher) {
+            return '';
+        }
+
+        $cipher = $base64 ? base64_decode($cipher) : $cipher;
+
+        if (function_exists('mcrypt_module_open') &&
+            ($td = mcrypt_module_open(MCRYPT_TripleDES, "", MCRYPT_MODE_CBC, ""))
+        ) {
+            $iv_size = mcrypt_enc_get_iv_size($td);
+            $iv = substr($cipher, 0, $iv_size);
+
+            // session corruption? (#1485970)
+            if (strlen($iv) < $iv_size) {
+                return '';
+            }
+
+            $cipher = substr($cipher, $iv_size);
+            mcrypt_generic_init($td, $this->config->get_crypto_key($key), $iv);
+            $clear = mdecrypt_generic($td, $cipher);
+            mcrypt_generic_deinit($td);
+            mcrypt_module_close($td);
+        }
+        else {
+            @include_once 'des.inc';
+
+            if (function_exists('des')) {
+                $des_iv_size = 8;
+                $iv = substr($cipher, 0, $des_iv_size);
+                $cipher = substr($cipher, $des_iv_size);
+                $clear = des($this->config->get_crypto_key($key), $cipher, 0, 1, $iv);
+            }
+            else {
+                self::raise_error(array(
+                    'code' => 500, 'type' => 'php',
+                    'file' => __FILE__, 'line' => __LINE__,
+                    'message' => "Could not perform decryption; make sure Mcrypt is installed or lib/des.inc is available"
+                    ), true, true);
+            }
+        }
+
+        /*-
+         * Trim PHP's padding and the canary byte; see note in
+         * rcube::encrypt() and http://php.net/mcrypt_generic#68082
+         */
+        $clear = substr(rtrim($clear, "\0"), 0, -1);
+
+        return $clear;
+    }
+
+
+    /**
+     * Generates encryption initialization vector (IV)
+     *
+     * @param int Vector size
+     *
+     * @return string Vector string
+     */
+    private function create_iv($size)
+    {
+        // mcrypt_create_iv() can be slow when system lacks entrophy
+        // we'll generate IV vector manually
+        $iv = '';
+        for ($i = 0; $i < $size; $i++) {
+            $iv .= chr(mt_rand(0, 255));
+        }
+
+        return $iv;
+    }
+
+
+    /**
+     * Build a valid URL to this instance of Roundcube
+     *
+     * @param mixed Either a string with the action or url parameters as key-value pairs
+     * @return string Valid application URL
+     */
+    public function url($p)
+    {
+        // STUB: should be overloaded by the application
         return '';
-
-      $cipher = substr($cipher, $iv_size);
-      mcrypt_generic_init($td, $this->config->get_crypto_key($key), $iv);
-      $clear = mdecrypt_generic($td, $cipher);
-      mcrypt_generic_deinit($td);
-      mcrypt_module_close($td);
-    }
-    else {
-      @include_once 'des.inc';
-
-      if (function_exists('des')) {
-        $des_iv_size = 8;
-        $iv = substr($cipher, 0, $des_iv_size);
-        $cipher = substr($cipher, $des_iv_size);
-        $clear = des($this->config->get_crypto_key($key), $cipher, 0, 1, $iv);
-      }
-      else {
-        self::raise_error(array(
-          'code' => 500, 'type' => 'php',
-          'file' => __FILE__, 'line' => __LINE__,
-          'message' => "Could not perform decryption; make sure Mcrypt is installed or lib/des.inc is available"
-        ), true, true);
-      }
     }
 
-    /*-
-     * Trim PHP's padding and the canary byte; see note in
-     * rcube::encrypt() and http://php.net/mcrypt_generic#68082
+
+    /**
+     * Function to be executed in script shutdown
+     * Registered with register_shutdown_function()
      */
-    $clear = substr(rtrim($clear, "\0"), 0, -1);
-
-    return $clear;
-  }
-
-  /**
-   * Generates encryption initialization vector (IV)
-   *
-   * @param int Vector size
-   * @return string Vector string
-   */
-  private function create_iv($size)
-  {
-    // mcrypt_create_iv() can be slow when system lacks entrophy
-    // we'll generate IV vector manually
-    $iv = '';
-    for ($i = 0; $i < $size; $i++)
-        $iv .= chr(mt_rand(0, 255));
-    return $iv;
-  }
-
-
-  /**
-   * Build a valid URL to this instance of Roundcube
-   *
-   * @param mixed Either a string with the action or url parameters as key-value pairs
-   * @return string Valid application URL
-   */
-  public function url($p)
-  {
-      // STUB: should be overloaded by the application
-      return '';
-  }
-
-
-  /**
-   * Function to be executed in script shutdown
-   * Registered with register_shutdown_function()
-   */
-  public function shutdown()
-  {
-    foreach ($this->shutdown_functions as $function)
-      call_user_func($function);
-
-    if (is_object($this->smtp))
-      $this->smtp->disconnect();
-
-    foreach ($this->caches as $cache) {
-        if (is_object($cache))
-            $cache->close();
-    }
-
-    if (is_object($this->storage)) {
-      if ($this->expunge_cache)
-        $this->storage->expunge_cache();
-      $this->storage->close();
-    }
-  }
-
-
-  /**
-   * Registers shutdown function to be executed on shutdown.
-   * The functions will be executed before destroying any
-   * objects like smtp, imap, session, etc.
-   *
-   * @param callback Function callback
-   */
-  public function add_shutdown_function($function)
-  {
-    $this->shutdown_functions[] = $function;
-  }
-
-
-  /**
-   * Construct shell command, execute it and return output as string.
-   * Keywords {keyword} are replaced with arguments
-   *
-   * @param $cmd Format string with {keywords} to be replaced
-   * @param $values (zero, one or more arrays can be passed)
-   * @return output of command. shell errors not detectable
-   */
-  public static function exec(/* $cmd, $values1 = array(), ... */)
-  {
-    $args = func_get_args();
-    $cmd = array_shift($args);
-    $values = $replacements = array();
-
-    // merge values into one array
-    foreach ($args as $arg)
-      $values += (array)$arg;
-
-    preg_match_all('/({(-?)([a-z]\w*)})/', $cmd, $matches, PREG_SET_ORDER);
-    foreach ($matches as $tags) {
-      list(, $tag, $option, $key) = $tags;
-      $parts = array();
-
-      if ($option) {
-        foreach ((array)$values["-$key"] as $key => $value) {
-          if ($value === true || $value === false || $value === null)
-            $parts[] = $value ? $key : "";
-          else foreach ((array)$value as $val)
-            $parts[] = "$key " . escapeshellarg($val);
+    public function shutdown()
+    {
+        foreach ($this->shutdown_functions as $function) {
+            call_user_func($function);
         }
-      }
-      else {
-        foreach ((array)$values[$key] as $value)
-          $parts[] = escapeshellarg($value);
-      }
 
-      $replacements[$tag] = join(" ", $parts);
+        if (is_object($this->smtp)) {
+            $this->smtp->disconnect();
+        }
+
+        foreach ($this->caches as $cache) {
+            if (is_object($cache)) {
+                $cache->close();
+            }
+        }
+
+        if (is_object($this->storage)) {
+            if ($this->expunge_cache) {
+                $this->storage->expunge_cache();
+            }
+            $this->storage->close();
+        }
     }
 
-    // use strtr behaviour of going through source string once
-    $cmd = strtr($cmd, $replacements);
 
-    return (string)shell_exec($cmd);
-  }
+    /**
+     * Registers shutdown function to be executed on shutdown.
+     * The functions will be executed before destroying any
+     * objects like smtp, imap, session, etc.
+     *
+     * @param callback Function callback
+     */
+    public function add_shutdown_function($function)
+    {
+        $this->shutdown_functions[] = $function;
+    }
+
+
+    /**
+     * Construct shell command, execute it and return output as string.
+     * Keywords {keyword} are replaced with arguments
+     *
+     * @param $cmd Format string with {keywords} to be replaced
+     * @param $values (zero, one or more arrays can be passed)
+     *
+     * @return output of command. shell errors not detectable
+     */
+    public static function exec(/* $cmd, $values1 = array(), ... */)
+    {
+        $args   = func_get_args();
+        $cmd    = array_shift($args);
+        $values = $replacements = array();
+
+        // merge values into one array
+        foreach ($args as $arg) {
+            $values += (array)$arg;
+        }
+
+        preg_match_all('/({(-?)([a-z]\w*)})/', $cmd, $matches, PREG_SET_ORDER);
+        foreach ($matches as $tags) {
+            list(, $tag, $option, $key) = $tags;
+            $parts = array();
+
+            if ($option) {
+                foreach ((array)$values["-$key"] as $key => $value) {
+                    if ($value === true || $value === false || $value === null) {
+                        $parts[] = $value ? $key : "";
+                    }
+                    else {
+                        foreach ((array)$value as $val) {
+                            $parts[] = "$key " . escapeshellarg($val);
+                        }
+                    }
+                }
+            }
+            else {
+                foreach ((array)$values[$key] as $value) {
+                    $parts[] = escapeshellarg($value);
+                }
+            }
+
+            $replacements[$tag] = join(" ", $parts);
+        }
+
+        // use strtr behaviour of going through source string once
+        $cmd = strtr($cmd, $replacements);
+
+        return (string)shell_exec($cmd);
+    }
 
 
     /**
diff --git a/program/include/rcube_bc.inc b/program/include/rcube_bc.inc
index 1932f86..1894873 100644
--- a/program/include/rcube_bc.inc
+++ b/program/include/rcube_bc.inc
@@ -38,11 +38,6 @@
     return rcmail::get_instance()->db->table_name($table);
 }
 
-function get_sequence_name($sequence)
-{
-    return rcmail::get_instance()->db->sequence_name($sequence);
-}
-
 function rcube_label($p, $domain=null)
 {
     return rcmail::get_instance()->gettext($p, $domain);
diff --git a/program/include/rcube_browser.php b/program/include/rcube_browser.php
index 06033e0..7cfae70 100644
--- a/program/include/rcube_browser.php
+++ b/program/include/rcube_browser.php
@@ -20,8 +20,6 @@
 */
 
 /**
- * rcube_browser
- *
  * Provide details about the client's browser based on the User-Agent header
  *
  * @package Core
diff --git a/program/include/rcube_cache.php b/program/include/rcube_cache.php
index cdb1dd5..4e60dea 100644
--- a/program/include/rcube_cache.php
+++ b/program/include/rcube_cache.php
@@ -254,7 +254,7 @@
             }
             else if ($this->type == 'apc') {
                 $data = apc_fetch($this->ckey($key));
-	        }
+            }
 
             if ($data) {
                 $md5sum = md5($data);
@@ -294,7 +294,7 @@
                 }
 
                 $this->cache[$key]      = $data;
-	            $this->cache_sums[$key] = $md5sum;
+                $this->cache_sums[$key] = $md5sum;
             }
             else {
                 $this->cache[$key] = null;
diff --git a/program/include/rcube_charset.php b/program/include/rcube_charset.php
index 380d149..1740a60 100644
--- a/program/include/rcube_charset.php
+++ b/program/include/rcube_charset.php
@@ -181,6 +181,12 @@
         $to   = empty($to) ? strtoupper(RCMAIL_CHARSET) : self::parse_charset($to);
         $from = self::parse_charset($from);
 
+        // It is a common case when UTF-16 charset is used with US-ASCII content (#1488654)
+        // In that case we can just skip the conversion (use UTF-8)
+        if ($from == 'UTF-16' && !preg_match('/[^\x00-\x7F]/', $str)) {
+            $from = 'UTF-8';
+        }
+
         if ($from == $to || empty($str) || empty($from)) {
             return $str;
         }
diff --git a/program/include/rcube_config.php b/program/include/rcube_config.php
index 2fe0d97..41acc80 100644
--- a/program/include/rcube_config.php
+++ b/program/include/rcube_config.php
@@ -42,6 +42,7 @@
         'default_folders'      => 'default_imap_folders',
         'mail_pagesize'        => 'pagesize',
         'addressbook_pagesize' => 'pagesize',
+        'reply_mode'           => 'top_posting',
     );
 
 
@@ -324,7 +325,7 @@
         if (strlen($key) != 24) {
             rcube::raise_error(array(
                 'code' => 500, 'type' => 'php',
-	            'file' => __FILE__, 'line' => __LINE__,
+                'file' => __FILE__, 'line' => __LINE__,
                 'message' => "Configured crypto key '$key' is not exactly 24 bytes long"
             ), true, true);
         }
@@ -348,7 +349,7 @@
             else
                 rcube::raise_error(array(
                     'code' => 500, 'type' => 'php',
-	                'file' => __FILE__, 'line' => __LINE__,
+                    'file' => __FILE__, 'line' => __LINE__,
                     'message' => "Invalid mail_header_delimiter setting"
                 ), true, false);
         }
diff --git a/program/include/rcube_db.php b/program/include/rcube_db.php
index 042ca15..f97d70a 100644
--- a/program/include/rcube_db.php
+++ b/program/include/rcube_db.php
@@ -576,6 +576,10 @@
             return intval($input);
         }
 
+        if (is_null($input)) {
+            return 'NULL';
+        }
+
         // create DB handle if not available
         if (!$this->dbh) {
             $this->db_connect('r');
diff --git a/program/include/rcube_imap.php b/program/include/rcube_imap.php
index 4ab06cf..66b5c4b 100644
--- a/program/include/rcube_imap.php
+++ b/program/include/rcube_imap.php
@@ -359,11 +359,11 @@
 
         return array(
             $this->search_string,
-	        $this->search_set,
-        	$this->search_charset,
-        	$this->search_sort_field,
-        	$this->search_sorted,
-	    );
+            $this->search_set,
+            $this->search_charset,
+            $this->search_sort_field,
+            $this->search_sorted,
+        );
     }
 
 
@@ -2138,14 +2138,17 @@
 
     /**
      * Sends the whole message source to stdout
+     *
+     * @param int  $uid       Message UID
+     * @param bool $formatted Enables line-ending formatting
      */
-    public function print_raw_body($uid)
+    public function print_raw_body($uid, $formatted = true)
     {
         if (!$this->check_connection()) {
             return;
         }
 
-        $this->conn->handlePartBody($this->folder, $uid, true, NULL, NULL, true);
+        $this->conn->handlePartBody($this->folder, $uid, true, null, null, true, null, $formatted);
     }
 
 
@@ -2217,6 +2220,10 @@
     {
         if (!strlen($folder)) {
             $folder = $this->folder;
+        }
+
+        if (!$this->check_connection()) {
+            return false;
         }
 
         // make sure folder exists
@@ -3847,12 +3854,12 @@
     protected function rsort($folder, $delimiter, &$list, &$out)
     {
         while (list($key, $name) = each($list)) {
-	        if (strpos($name, $folder.$delimiter) === 0) {
-	            // set the type of folder name variable (#1485527)
-    	        $out[] = (string) $name;
-	            unset($list[$key]);
-	            $this->rsort($name, $delimiter, $list, $out);
-	        }
+            if (strpos($name, $folder.$delimiter) === 0) {
+                // set the type of folder name variable (#1485527)
+                $out[] = (string) $name;
+                unset($list[$key]);
+                $this->rsort($name, $delimiter, $list, $out);
+            }
         }
         reset($list);
     }
diff --git a/program/include/rcube_imap_cache.php b/program/include/rcube_imap_cache.php
index a061a1f..f36ace0 100644
--- a/program/include/rcube_imap_cache.php
+++ b/program/include/rcube_imap_cache.php
@@ -5,7 +5,7 @@
  | program/include/rcube_imap_cache.php                                  |
  |                                                                       |
  | This file is part of the Roundcube Webmail client                     |
- | Copyright (C) 2005-2011, 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.                |
@@ -350,11 +350,11 @@
     function get_message($mailbox, $uid, $update = true, $cache = true)
     {
         // Check internal cache
-        if ($this->icache['message']
-            && $this->icache['message']['mailbox'] == $mailbox
-            && $this->icache['message']['object']->uid == $uid
+        if ($this->icache['__message']
+            && $this->icache['__message']['mailbox'] == $mailbox
+            && $this->icache['__message']['object']->uid == $uid
         ) {
-            return $this->icache['message']['object'];
+            return $this->icache['__message']['object'];
         }
 
         $sql_result = $this->db->query(
@@ -386,7 +386,7 @@
             // Save current message from internal cache
             $this->save_icache();
 
-            $this->icache['message'] = array(
+            $this->icache['__message'] = array(
                 'object'  => $message,
                 'mailbox' => $mailbox,
                 'exists'  => $found,
@@ -459,20 +459,28 @@
      */
     function change_flag($mailbox, $uids, $flag, $enabled = false)
     {
+        if (empty($uids)) {
+            return;
+        }
+
         $flag = strtoupper($flag);
         $idx  = (int) array_search($flag, $this->flags);
+        $uids = (array) $uids;
 
         if (!$idx) {
             return;
         }
 
         // Internal cache update
-        if ($uids && count($uids) == 1 && ($uid = current($uids))
-            && ($message = $this->icache['message'])
-            && $message['mailbox'] == $mailbox && $message['object']->uid == $uid
+        if (($message = $this->icache['__message'])
+            && $message['mailbox'] === $mailbox
+            && in_array($message['object']->uid, $uids)
         ) {
             $message['object']->flags[$flag] = $enabled;
-            return;
+
+            if (count($uids) == 1) {
+                return;
+            }
         }
 
         $this->db->query(
@@ -481,7 +489,7 @@
             .", flags = flags ".($enabled ? "+ $idx" : "- $idx")
             ." WHERE user_id = ?"
                 ." AND mailbox = ?"
-                .($uids !== null ? " AND uid IN (".$this->db->array2list((array)$uids, 'integer').")" : "")
+                .($uids !== null ? " AND uid IN (".$this->db->array2list($uids, 'integer').")" : "")
                 ." AND (flags & $idx) ".($enabled ? "= 0" : "= $idx"),
             $this->userid, $mailbox);
     }
@@ -503,10 +511,11 @@
         }
         else {
             // Remove the message from internal cache
-            if (!empty($uids) && !is_array($uids) && ($message = $this->icache['message'])
-                && $message['mailbox'] == $mailbox && $message['object']->uid == $uids
+            if (!empty($uids) && ($message = $this->icache['__message'])
+                && $message['mailbox'] === $mailbox
+                && in_array($message['object']->uid, (array)$uids)
             ) {
-                $this->icache['message'] = null;
+                $this->icache['__message'] = null;
             }
 
             $this->db->query(
@@ -608,13 +617,13 @@
         // get expiration timestamp
         $ts = get_offset_time($ttl, -1);
 
-        $this->db->query("DELETE FROM ".get_table_name('cache_messages')
+        $this->db->query("DELETE FROM ".$this->db->table_name('cache_messages')
               ." WHERE changed < " . $this->db->fromunixtime($ts));
 
-        $this->db->query("DELETE FROM ".get_table_name('cache_index')
+        $this->db->query("DELETE FROM ".$this->db->table_name('cache_index')
               ." WHERE changed < " . $this->db->fromunixtime($ts));
 
-        $this->db->query("DELETE FROM ".get_table_name('cache_thread')
+        $this->db->query("DELETE FROM ".$this->db->table_name('cache_thread')
               ." WHERE changed < " . $this->db->fromunixtime($ts));
     }
 
@@ -762,6 +771,11 @@
     {
         $object    = $index['object'];
         $is_thread = is_a($object, 'rcube_result_thread');
+
+        // sanity check
+        if (empty($object)) {
+            return false;
+        }
 
         // Get mailbox data (UIDVALIDITY, counters, etc.) for status check
         $mbox_data = $this->imap->folder_data($mailbox);
@@ -1078,7 +1092,7 @@
     private function save_icache()
     {
         // Save current message from internal cache
-        if ($message = $this->icache['message']) {
+        if ($message = $this->icache['__message']) {
             // clean up some object's data
             $object = $this->message_object_prepare($message['object']);
 
@@ -1089,7 +1103,7 @@
                 $this->add_message($message['mailbox'], $object, !$message['exists']);
             }
 
-            $this->icache['message']['md5sum'] = $md5sum;
+            $this->icache['__message']['md5sum'] = $md5sum;
         }
     }
 
diff --git a/program/include/rcube_imap_generic.php b/program/include/rcube_imap_generic.php
index 8d956f2..25e6fc4 100644
--- a/program/include/rcube_imap_generic.php
+++ b/program/include/rcube_imap_generic.php
@@ -530,6 +530,7 @@
                 }
                 else {
                     $authc = $user;
+                    $user  = '';
                 }
                 $auth_sasl = Auth_SASL::factory('digestmd5');
                 $reply = base64_encode($auth_sasl->getResponse($authc, $pass,
@@ -568,6 +569,7 @@
             }
             else {
                 $authc = $user;
+                $user  = '';
             }
 
             $reply = base64_encode($user . chr(0) . $authc . chr(0) . $pass);
@@ -2377,7 +2379,7 @@
         return $this->handlePartBody($mailbox, $id, $is_uid, $part);
     }
 
-    function handlePartBody($mailbox, $id, $is_uid=false, $part='', $encoding=NULL, $print=NULL, $file=NULL)
+    function handlePartBody($mailbox, $id, $is_uid=false, $part='', $encoding=NULL, $print=NULL, $file=NULL, $formatted=true)
     {
         if (!$this->select($mailbox)) {
             return false;
@@ -2494,7 +2496,7 @@
                         continue;
                     $line = convert_uudecode($line);
                 // default
-                } else {
+                } else if ($formatted) {
                     $line = rtrim($line, "\t\r\n\0\x0B") . "\n";
                 }
 
@@ -2538,7 +2540,7 @@
     {
         unset($this->data['APPENDUID']);
 
-        if (!$mailbox) {
+        if ($mailbox === null || $mailbox === '') {
             return false;
         }
 
@@ -2603,7 +2605,7 @@
     {
         unset($this->data['APPENDUID']);
 
-        if (!$mailbox) {
+        if ($mailbox === null || $mailbox === '') {
             return false;
         }
 
@@ -2612,6 +2614,7 @@
         if (file_exists(realpath($path))) {
             $in_fp = fopen($path, 'r');
         }
+
         if (!$in_fp) {
             $this->setError(self::ERROR_UNKNOWN, "Couldn't open $path for reading");
             return false;
diff --git a/program/include/rcube_ldap.php b/program/include/rcube_ldap.php
index 3a7fc18..ad2ccdd 100644
--- a/program/include/rcube_ldap.php
+++ b/program/include/rcube_ldap.php
@@ -139,6 +139,11 @@
                     unset($this->coltypes[$childcol]);  // remove address child col from global coltypes list
                 }
             }
+
+            // at least one address type must be specified
+            if (empty($this->coltypes['address']['subtypes'])) {
+                $this->coltypes['address']['subtypes'] = array('home');
+            }
         }
         else if ($this->coltypes['address']) {
             $this->coltypes['address'] += array('type' => 'textarea', 'childs' => null, 'size' => 40);
@@ -767,9 +772,9 @@
         }
 
         // use VLV pseudo-search for autocompletion
-        $rcmail = rcmail::get_instance();
+        $rcube = rcube::get_instance();
 
-        if ($this->prop['vlv_search'] && $this->conn && join(',', (array)$fields) == join(',', $rcmail->config->get('contactlist_fields')))
+        if ($this->prop['vlv_search'] && $this->conn && join(',', (array)$fields) == join(',', $rcube->config->get('contactlist_fields')))
         {
             // add general filter to query
             if (!empty($this->prop['filter']) && empty($this->filter))
@@ -2027,12 +2032,12 @@
         # a0 = type context-specific/constructed with a length of 06 (6) bytes following
         # 02 = type integer with 2 bytes following (offset): 01 01 (ie 1)
         # 02 = type integer with 2 bytes following (contentCount):  01 00
-        
+
         # whith a search string present:
         # 81 = type context-specific/constructed with a length of 04 (4) bytes following (the length will change here)
         # 81 indicates a user string is present where as a a0 indicates just a offset search
         # 81 = type context-specific/constructed with a length of 06 (6) bytes following
-        
+
         # the following info was taken from the ISO/IEC 8825-1:2003 x.690 standard re: the
         # encoding of integer values (note: these values are in
         # two-complement form so since offset will never be negative bit 8 of the
@@ -2042,7 +2047,7 @@
         # of the second (to the left of first octet) octet:
         # a) shall not all be ones; and
         # b) shall not all be zero
-        
+
         if ($search)
         {
             $search = preg_replace('/[^-[:alpha:] ,.()0-9]+/', '', $search);
@@ -2062,7 +2067,7 @@
             // now compute length over $str
             $str = self::_ber_addseq($str, 'a0');
         }
-        
+
         // now tack on records per page
         $str = "020100" . self::_ber_addseq(self::_ber_encode_int($rpp-1), '02') . $str;
 
diff --git a/program/include/rcube_message.php b/program/include/rcube_message.php
index 9d36acf..6af1d01 100644
--- a/program/include/rcube_message.php
+++ b/program/include/rcube_message.php
@@ -50,13 +50,14 @@
      */
     private $mime;
     private $opt = array();
-    private $inline_parts = array();
     private $parse_alternative = false;
 
-    public $uid = null;
+    public $uid;
+    public $folder;
     public $headers;
     public $parts = array();
     public $mime_parts = array();
+    public $inline_parts = array();
     public $attachments = array();
     public $subject = '';
     public $sender = null;
@@ -68,16 +69,21 @@
      *
      * Provide a uid, and parse message structure.
      *
-     * @param string $uid The message UID.
+     * @param string $uid    The message UID.
+     * @param string $folder Folder name
      *
      * @see self::$app, self::$storage, self::$opt, self::$parts
      */
-    function __construct($uid)
+    function __construct($uid, $folder = null)
     {
         $this->uid  = $uid;
         $this->app  = rcube::get_instance();
         $this->storage = $this->app->get_storage();
+        $this->folder  = strlen($folder) ? $folder : $this->storage->get_folder();
         $this->storage->set_options(array('all_headers' => true));
+
+        // Set current folder
+        $this->storage->set_folder($this->folder);
 
         $this->headers = $this->storage->get_message($uid);
 
@@ -179,10 +185,12 @@
                 }
                 return $fp ? true : $part->body;
             }
+
             // get from IMAP
+            $this->storage->set_folder($this->folder);
+
             return $this->storage->get_message_part($this->uid, $mime_id, $part, NULL, $fp, $skip_charset_conv);
-        } else
-            return null;
+        }
     }
 
 
@@ -637,8 +645,10 @@
     function tnef_decode(&$part)
     {
         // @TODO: attachment may be huge, hadle it via file
-        if (!isset($part->body))
+        if (!isset($part->body)) {
+            $this->storage->set_folder($this->folder);
             $part->body = $this->storage->get_message_part($this->uid, $part->mime_id, $part);
+        }
 
         $parts = array();
         $tnef = new tnef_decoder;
@@ -673,8 +683,10 @@
     function uu_decode(&$part)
     {
         // @TODO: messages may be huge, hadle body via file
-        if (!isset($part->body))
+        if (!isset($part->body)) {
+            $this->storage->set_folder($this->folder);
             $part->body = $this->storage->get_message_part($this->uid, $part->mime_id, $part);
+        }
 
         $parts = array();
         // FIXME: line length is max.65?
diff --git a/program/include/rcube_mime.php b/program/include/rcube_mime.php
index e1f736a..d8e04a9 100644
--- a/program/include/rcube_mime.php
+++ b/program/include/rcube_mime.php
@@ -541,10 +541,10 @@
                     $prefix = $regs[0];
                     $level = strlen($prefix);
                     $line  = rtrim(substr($line, $level));
-                    $line  = $prefix . rc_wordwrap($line, $length - $level - 2, " \r\n$prefix ");
+                    $line  = $prefix . self::wordwrap($line, $length - $level - 2, " \r\n$prefix ");
                 }
                 else if ($line) {
-                    $line = rc_wordwrap(rtrim($line), $length - 2, " \r\n");
+                    $line = self::wordwrap(rtrim($line), $length - 2, " \r\n");
                     // space-stuffing
                     $line = preg_replace('/(^|\r\n)(From| |>)/', '\\1 \\2', $line);
                 }
diff --git a/program/include/rcube_output_html.php b/program/include/rcube_output_html.php
index 30512d2..2743e77 100644
--- a/program/include/rcube_output_html.php
+++ b/program/include/rcube_output_html.php
@@ -67,6 +67,11 @@
         $this->set_env('task', $task);
         $this->set_env('x_frame_options', $this->config->get('x_frame_options', 'sameorigin'));
 
+        // add cookie info
+        $this->set_env('cookie_domain', ini_get('session.cookie_domain'));
+        $this->set_env('cookie_path', ini_get('session.cookie_path'));
+        $this->set_env('cookie_secure', ini_get('session.cookie_secure'));
+
         // load the correct skin (in case user-defined)
         $skin = $this->config->get('skin');
         $this->set_skin($skin);
@@ -395,7 +400,7 @@
                 'line' => __LINE__,
                 'file' => __FILE__,
                 'message' => 'Error loading template for '.$realname
-                ), true, true);
+                ), true, $write);
             return false;
         }
 
@@ -691,6 +696,11 @@
                 if ($attrib['name'] || $attrib['command']) {
                     return $this->button($attrib);
                 }
+                break;
+
+            // frame
+            case 'frame':
+                return $this->frame($attrib);
                 break;
 
             // show a label
@@ -1270,6 +1280,30 @@
     }
 
 
+    /**
+     * Returns iframe object, registers some related env variables
+     *
+     * @param array $attrib HTML attributes
+     *
+     * @return string IFRAME element
+     */
+    public function frame($attrib)
+    {
+        if (!$attrib['id']) {
+            $attrib['id'] = 'rcmframe';
+        }
+
+        if (!$attrib['name']) {
+            $attrib['name'] = $attrib['id'];
+        }
+
+        $this->set_env('contentframe', $attrib['id']);
+        $this->set_env('blankpage', $attrib['src'] ? $this->abs_url($attrib['src']) : 'program/resources/blank.gif');
+
+        return html::iframe($attrib);
+    }
+
+
     /*  ************* common functions delivering gui objects **************  */
 
 
@@ -1378,6 +1412,9 @@
         if (empty($url) && !preg_match('/_(task|action)=logout/', $_SERVER['QUERY_STRING']))
             $url = $_SERVER['QUERY_STRING'];
 
+        // Disable autocapitalization on iPad/iPhone (#1488609)
+        $attrib['autocapitalize'] = 'off';
+
         // set atocomplete attribute
         $user_attrib = $autocomplete > 0 ? array() : array('autocomplete' => 'off');
         $host_attrib = $autocomplete > 0 ? array() : array('autocomplete' => 'off');
diff --git a/program/include/rcube_plugin.php b/program/include/rcube_plugin.php
index b1ec32a..c103573 100644
--- a/program/include/rcube_plugin.php
+++ b/program/include/rcube_plugin.php
@@ -336,7 +336,7 @@
   public function local_skin_path()
   {
     $rcmail = rcube::get_instance();
-    foreach (array($rcmail->config->get('skin'),'default') as $skin) {
+    foreach (array($rcmail->config->get('skin'), 'larry') as $skin) {
       $skin_path = 'skins/' . $skin;
       if (is_dir(realpath(slashify($this->home) . $skin_path)))
         break;
diff --git a/program/include/rcube_plugin_api.php b/program/include/rcube_plugin_api.php
index 8c1e125..9ef68ca 100644
--- a/program/include/rcube_plugin_api.php
+++ b/program/include/rcube_plugin_api.php
@@ -32,12 +32,12 @@
 class rcube_plugin_api
 {
   static private $instance;
-  
+
   public $dir;
   public $url = 'plugins/';
   public $task = '';
   public $output;
-  
+
   public $handlers = array();
   private $plugins = array();
   private $tasks = array();
diff --git a/program/include/rcube_session.php b/program/include/rcube_session.php
index b6a0ccf..6192466 100644
--- a/program/include/rcube_session.php
+++ b/program/include/rcube_session.php
@@ -531,7 +531,7 @@
   public function set_keep_alive($keep_alive)
   {
     $this->keep_alive = $keep_alive;
-    
+
     if ($this->lifetime < $keep_alive)
         $this->set_lifetime($keep_alive + 30);
   }
@@ -551,7 +551,7 @@
   {
     return $this->ip;
   }
-  
+
   /**
    * Setter for cookie encryption secret
    */
@@ -568,7 +568,8 @@
   {
     $this->ip_check = $check;
   }
-  
+
+
   /**
    * Setter for the cookie name used for session cookie
    */
@@ -605,7 +606,7 @@
           $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));
@@ -637,7 +638,7 @@
   }
 
   /**
-   * 
+   * Writes debug information to the log
    */
   function log($line)
   {
diff --git a/program/include/rcube_shared.inc b/program/include/rcube_shared.inc
index 85f2784..c15305c 100644
--- a/program/include/rcube_shared.inc
+++ b/program/include/rcube_shared.inc
@@ -108,11 +108,11 @@
 
 
 /**
- * Remove slash at the end of the string
+ * Remove slashes at the end of the string
  */
 function unslashify($str)
 {
-  return preg_replace('/\/$/', '', $str);
+  return preg_replace('/\/+$/', '', $str);
 }
 
 
@@ -231,7 +231,7 @@
 {
     $keys = array();
 
-    if (!empty($array)) {
+    if (!empty($array) && is_array($array)) {
         foreach ($array as $key => $child) {
             $keys[] = $key;
             foreach (array_keys_recursive($child) as $val) {
@@ -255,7 +255,7 @@
 
 
 /**
- * Remove single and double quotes from given string
+ * Remove single and double quotes from a given string
  *
  * @param string Input value
  *
@@ -299,6 +299,29 @@
         }
 
         return "$name <$email>";
+    }
+
+    return $email;
+}
+
+
+/**
+ * Format e-mail address
+ *
+ * @param string $email E-mail address
+ *
+ * @return string Formatted e-mail address
+ */
+function format_email($email)
+{
+    $email = trim($email);
+    $parts = explode('@', $email);
+    $count = count($parts);
+
+    if ($count > 1) {
+        $parts[$count-1] = mb_strtolower($parts[$count-1]);
+
+        $email = implode('@', $parts);
     }
 
     return $email;
@@ -399,7 +422,6 @@
 {
     $filename = preg_replace(
         array(
-            '/MDB2_(.+)/',
             '/Mail_(.+)/',
             '/Net_(.+)/',
             '/Auth_(.+)/',
@@ -407,7 +429,6 @@
             '/^utf8$/',
         ),
         array(
-            'Mail/\\1',
             'Mail/\\1',
             'Net/\\1',
             'Auth/\\1',
diff --git a/program/include/rcube_smtp.php b/program/include/rcube_smtp.php
index e574824..b28be52 100644
--- a/program/include/rcube_smtp.php
+++ b/program/include/rcube_smtp.php
@@ -423,7 +423,7 @@
         $lines[] = $key . ': ' . $value;
       }
     }
-    
+
     return array($from, join(SMTP_MIME_CRLF, $lines) . SMTP_MIME_CRLF);
   }
 
diff --git a/program/include/rcube_storage.php b/program/include/rcube_storage.php
index 1075b0f..f83e240 100644
--- a/program/include/rcube_storage.php
+++ b/program/include/rcube_storage.php
@@ -195,7 +195,7 @@
      */
     public function set_folder($folder)
     {
-        if ($this->folder == $folder) {
+        if ($this->folder === $folder) {
             return;
         }
 
@@ -502,8 +502,11 @@
 
     /**
      * Sends the whole message source to stdout
+     *
+     * @param int  $uid       Message UID
+     * @param bool $formatted Enables line-ending formatting
      */
-    abstract function print_raw_body($uid);
+    abstract function print_raw_body($uid, $formatted = true);
 
 
     /**
diff --git a/program/include/rcube_user.php b/program/include/rcube_user.php
index 644d24b..29eb0f2 100644
--- a/program/include/rcube_user.php
+++ b/program/include/rcube_user.php
@@ -443,7 +443,7 @@
         }
 
         $data = $rcube->plugins->exec_hook('user_create',
-	        array('user'=>$user, 'user_name'=>$user_name, 'user_email'=>$user_email, 'host'=>$host));
+            array('user'=>$user, 'user_name'=>$user_name, 'user_email'=>$user_email, 'host'=>$host));
 
         // plugin aborted this operation
         if ($data['abort'])
diff --git a/program/include/rcube_utils.php b/program/include/rcube_utils.php
index 8762a20..9bedf21 100644
--- a/program/include/rcube_utils.php
+++ b/program/include/rcube_utils.php
@@ -84,17 +84,17 @@
 
         // from PEAR::Validate
         $regexp = '&^(?:
-	        ("\s*(?:[^"\f\n\r\t\v\b\s]+\s*)+")| 			 	            #1 quoted name
-	        ([-\w!\#\$%\&\'*+~/^`|{}=]+(?:\.[-\w!\#\$%\&\'*+~/^`|{}=]+)*)) 	#2 OR dot-atom (RFC5322)
-	        $&xi';
+            ("\s*(?:[^"\f\n\r\t\v\b\s]+\s*)+")|                             #1 quoted name
+            ([-\w!\#\$%\&\'*+~/^`|{}=]+(?:\.[-\w!\#\$%\&\'*+~/^`|{}=]+)*))  #2 OR dot-atom (RFC5322)
+            $&xi';
 
         if (!preg_match($regexp, $local_part)) {
             return false;
         }
 
-        // Check domain part
-        if (preg_match('/^\[*(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])(\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])){3}\]*$/', $domain_part)) {
-            return true; // IP address
+        // Validate domain part
+        if (preg_match('/^\[((IPv6:[0-9a-f:.]+)|([0-9.]+))\]$/i', $domain_part, $matches)) {
+            return self::check_ip(preg_replace('/^IPv6:/i', '', $matches[1])); // valid IPv4 or IPv6 address
         }
         else {
             // If not an IP address
@@ -108,6 +108,11 @@
                 if (!preg_match('/^(([A-Za-z0-9][A-Za-z0-9-]{0,61}[A-Za-z0-9])|([A-Za-z0-9]))$/', $part)) {
                     return false;
                 }
+            }
+
+            // last domain part
+            if (preg_match('/[^a-zA-Z]/', array_pop($domain_array))) {
+                return false;
             }
 
             $rcube = rcube::get_instance();
@@ -141,6 +146,52 @@
         return false;
     }
 
+
+    /**
+     * Validates IPv4 or IPv6 address
+     *
+     * @param string $ip IP address in v4 or v6 format
+     *
+     * @return bool True if the address is valid
+     */
+    public static function check_ip($ip)
+    {
+        // IPv6, but there's no build-in IPv6 support
+        if (strpos($ip, ':') !== false && !defined('AF_INET6')) {
+            $parts = explode(':', $domain_part);
+            $count = count($parts);
+
+            if ($count > 8 || $count < 2) {
+                return false;
+            }
+
+            foreach ($parts as $idx => $part) {
+                $length = strlen($part);
+                if (!$length) {
+                    // there can be only one ::
+                    if ($found_empty) {
+                        return false;
+                    }
+                    $found_empty = true;
+                }
+                // last part can be an IPv4 address
+                else if ($idx == $count - 1) {
+                    if (!preg_match('/^[0-9a-f]{1,4}$/i', $part)) {
+                        return @inet_pton($part) !== false;
+                    }
+                }
+                else if (!preg_match('/^[0-9a-f]{1,4}$/i', $part)) {
+                    return false;
+                }
+            }
+
+            return true;
+        }
+
+        return @inet_pton($ip) !== false;
+    }
+
+
     /**
      * Check whether the HTTP referer matches the current request
      *
@@ -149,8 +200,8 @@
     public static function check_referer()
     {
         $uri = parse_url($_SERVER['REQUEST_URI']);
-        $referer = parse_url(rcube_request_header('Referer'));
-        return $referer['host'] == rcube_request_header('Host') && $referer['path'] == $uri['path'];
+        $referer = parse_url(self::request_header('Referer'));
+        return $referer['host'] == self::request_header('Host') && $referer['path'] == $uri['path'];
     }
 
 
diff --git a/program/include/rcube_vcard.php b/program/include/rcube_vcard.php
index 37cd3ab..49b312c 100644
--- a/program/include/rcube_vcard.php
+++ b/program/include/rcube_vcard.php
@@ -313,7 +313,7 @@
 
       case 'birthday':
       case 'anniversary':
-        if (($val = rcube_strtotime($value)) && ($fn = self::$fieldmap[$field]))
+        if (($val = rcube_utils::strtotime($value)) && ($fn = self::$fieldmap[$field]))
           $this->raw[$fn][] = array(0 => date('Y-m-d', $val), 'value' => array('date'));
         break;
 
@@ -555,6 +555,7 @@
           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]))
diff --git a/program/js/app.js b/program/js/app.js
index 214a5cb..48de217 100644
--- a/program/js/app.js
+++ b/program/js/app.js
@@ -976,7 +976,7 @@
             // do reply-list, when list is detected and popup menu wasn't used 
             url._all = (!props && this.commands['reply-list'] ? 'list' : 'all');
           else if (command == 'reply-list')
-            url._all = list;
+            url._all = 'list';
 
           this.goto_url('compose', url, true);
         }
@@ -1447,29 +1447,21 @@
 
   this.doc_mouse_up = function(e)
   {
-    var model, list, li, id;
+    var model, list, id;
 
     // ignore event if jquery UI dialog is open
     if ($(rcube_event.get_target(e)).closest('.ui-dialog, .ui-widget-overlay').length)
       return;
 
-    if (list = this.message_list) {
-      if (!rcube_mouse_is_over(e, list.list.parentNode))
-        list.blur();
-      else
-        list.focus();
+    if (list = this.message_list)
       model = this.env.mailboxes;
-    }
-    else if (list = this.contact_list) {
-      if (!rcube_mouse_is_over(e, list.list.parentNode))
-        list.blur();
-      else
-        list.focus();
+    else if (list = this.contact_list)
       model = this.env.contactfolders;
-    }
-    else if (this.ksearch_value) {
+    else if (this.ksearch_value)
       this.ksearch_blur();
-    }
+
+    if (list && !rcube_mouse_is_over(e, list.list.parentNode))
+      list.blur();
 
     // handle mouse release when dragging
     if (this.drag_active && model && this.env.last_folder_target) {
@@ -1546,14 +1538,17 @@
     if (list.multi_selecting || !this.env.contentframe)
       return;
 
-    if (list.get_single_selection() && window.frames && window.frames[this.env.contentframe]) {
-      if (window.frames[this.env.contentframe].location.href.indexOf(this.env.blankpage)>=0) {
-        if (this.preview_timer)
-          clearTimeout(this.preview_timer);
-        if (this.preview_read_timer)
-          clearTimeout(this.preview_read_timer);
-        this.preview_timer = setTimeout(function(){ ref.msglist_get_preview(); }, 200);
-      }
+    if (list.get_single_selection())
+      return;
+
+    var win = this.get_frame_window(this.env.contentframe);
+
+    if (win && win.location.href.indexOf(this.env.blankpage)>=0) {
+      if (this.preview_timer)
+        clearTimeout(this.preview_timer);
+      if (this.preview_read_timer)
+        clearTimeout(this.preview_read_timer);
+      this.preview_timer = setTimeout(function(){ ref.msglist_get_preview(); }, 200);
     }
   };
 
@@ -1918,12 +1913,12 @@
     if (!id)
       return;
 
-    var target = window,
+    var win, target = window,
       action = preview ? 'preview': 'show',
       url = '&_action='+action+'&_uid='+id+'&_mbox='+urlencode(this.env.mailbox);
 
-    if (preview && this.env.contentframe && window.frames && window.frames[this.env.contentframe]) {
-      target = window.frames[this.env.contentframe];
+    if (preview && (win = this.get_frame_window(this.env.contentframe))) {
+      target = win;
       url += '&_framed=1';
     }
 
@@ -1960,18 +1955,35 @@
 
   this.show_contentframe = function(show)
   {
-    var frm, win;
-    if (this.env.contentframe && (frm = $('#'+this.env.contentframe)) && frm.length) {
-      if (!show && (win = window.frames[this.env.contentframe])) {
+    var frame, win, name = this.env.contentframe;
+
+    if (name && (frame = this.get_frame_element(name))) {
+      if (!show && (win = this.get_frame_window(name))) {
         if (win.location && win.location.href.indexOf(this.env.blankpage)<0)
           win.location.href = this.env.blankpage;
       }
       else if (!bw.safari && !bw.konq)
-        frm[show ? 'show' : 'hide']();
-      }
+        $(frame)[show ? 'show' : 'hide']();
+    }
 
     if (!show && this.busy)
       this.set_busy(false, null, this.env.frame_lock);
+  };
+
+  this.get_frame_element = function(id)
+  {
+    var frame;
+
+    if (id && (frame = document.getElementById(id)))
+      return frame;
+  };
+
+  this.get_frame_window = function(id)
+  {
+    var frame = this.get_frame_element(id);
+
+    if (frame && frame.name && window.frames)
+      return window.frames[frame.name];
   };
 
   this.lock_frame = function()
@@ -2017,7 +2029,7 @@
   // list messages of a specific mailbox
   this.list_mailbox = function(mbox, page, sort, url)
   {
-    var target = window;
+    var win, target = window;
 
     if (typeof url != 'object')
       url = {};
@@ -2056,8 +2068,8 @@
       return;
     }
 
-    if (this.env.contentframe && window.frames && window.frames[this.env.contentframe]) {
-      target = window.frames[this.env.contentframe];
+    if (win = this.get_frame_window(this.env.contentframe)) {
+      target = win;
       url._framed = 1;
     }
 
@@ -2652,34 +2664,37 @@
   // set a specific flag to one or more messages
   this.mark_message = function(flag, uid)
   {
-    var a_uids = [], r_uids = [], len, n, id,
-      selection = this.message_list ? this.message_list.get_selection() : [];
+    var a_uids = [], r_uids = [], len, n, id, selection,
+      list = this.message_list;
 
     if (uid)
       a_uids[0] = uid;
     else if (this.env.uid)
       a_uids[0] = this.env.uid;
-    else if (this.message_list) {
+    else if (list) {
+      selection = list.get_selection();
       for (n=0, len=selection.length; n<len; n++) {
           a_uids.push(selection[n]);
       }
     }
 
-    if (!this.message_list)
+    if (!list)
       r_uids = a_uids;
-    else
+    else {
+      list.focus();
       for (n=0, len=a_uids.length; n<len; n++) {
         id = a_uids[n];
-        if ((flag=='read' && this.message_list.rows[id].unread) 
-            || (flag=='unread' && !this.message_list.rows[id].unread)
-            || (flag=='delete' && !this.message_list.rows[id].deleted)
-            || (flag=='undelete' && this.message_list.rows[id].deleted)
-            || (flag=='flagged' && !this.message_list.rows[id].flagged)
-            || (flag=='unflagged' && this.message_list.rows[id].flagged))
+        if ((flag=='read' && list.rows[id].unread) 
+            || (flag=='unread' && !list.rows[id].unread)
+            || (flag=='delete' && !list.rows[id].deleted)
+            || (flag=='undelete' && list.rows[id].deleted)
+            || (flag=='flagged' && !list.rows[id].flagged)
+            || (flag=='unflagged' && list.rows[id].flagged))
         {
           r_uids.push(id);
         }
       }
+    }
 
     // nothing to do
     if (!r_uids.length && !this.select_all_mode)
@@ -3298,8 +3313,7 @@
       input_message = $("[name='_message']"),
       message = input_message.val(),
       is_html = ($("input[name='_is_html']").val() == '1'),
-      sig = this.env.identity,
-      sig_separator = this.env.sig_above && (this.env.compose_mode == 'reply' || this.env.compose_mode == 'forward') ? '---' : '-- ';
+      sig = this.env.identity;
 
     // enable manual signature insert
     if (this.env.signatures && this.env.signatures[id]) {
@@ -3312,12 +3326,8 @@
     if (!is_html) {
       // remove the 'old' signature
       if (show_sig && sig && this.env.signatures && this.env.signatures[sig]) {
-
-        sig = this.env.signatures[sig].is_html ? this.env.signatures[sig].plain_text : this.env.signatures[sig].text;
+        sig = this.env.signatures[sig].text;
         sig = sig.replace(/\r\n/g, '\n');
-
-        if (!sig.match(/^--[ -]\n/m))
-          sig = sig_separator + '\n' + sig;
 
         p = this.env.sig_above ? message.indexOf(sig) : message.lastIndexOf(sig);
         if (p >= 0)
@@ -3325,11 +3335,8 @@
       }
       // add the new signature string
       if (show_sig && this.env.signatures && this.env.signatures[id]) {
-        sig = this.env.signatures[id]['is_html'] ? this.env.signatures[id]['plain_text'] : this.env.signatures[id]['text'];
+        sig = this.env.signatures[id].text;
         sig = sig.replace(/\r\n/g, '\n');
-
-        if (!sig.match(/^--[ -]\n/m))
-          sig = sig_separator + '\n' + sig;
 
         if (this.env.sig_above) {
           if (p >= 0) { // in place of removed signature
@@ -3394,21 +3401,8 @@
         }
       }
 
-      if (this.env.signatures[id]) {
-        if (this.env.signatures[id].is_html) {
-          sig = this.env.signatures[id].text;
-          if (!this.env.signatures[id].plain_text.match(/^--[ -]\r?\n/m))
-            sig = sig_separator + '<br />' + sig;
-        }
-        else {
-          sig = this.env.signatures[id].text;
-          if (!sig.match(/^--[ -]\r?\n/m))
-            sig = sig_separator + '\n' + sig;
-          sig = '<pre>' + sig + '</pre>';
-        }
-
-        sigElem.innerHTML = sig;
-      }
+      if (this.env.signatures[id])
+        sigElem.innerHTML = this.env.signatures[id].html;
     }
 
     this.env.identity = id;
@@ -4031,8 +4025,7 @@
 
     // if a group is currently selected, and there is at least one contact selected
     // thend we can enable the group-remove-selected command
-    this.enable_command('group-remove-selected', typeof this.env.group != 'undefined' && list.selection.length > 0);
-
+    this.enable_command('group-remove-selected', this.env.group && list.selection.length > 0);
     this.enable_command('compose', this.env.group || list.selection.length > 0);
     this.enable_command('edit', id && writable);
     this.enable_command('delete', list.selection.length && writable);
@@ -4042,7 +4035,7 @@
 
   this.list_contacts = function(src, group, page)
   {
-    var folder, url = {},
+    var win, folder, url = {},
       target = window;
 
     if (!src)
@@ -4074,8 +4067,8 @@
       return;
     }
 
-    if (this.env.contentframe && window.frames && window.frames[this.env.contentframe]) {
-      target = window.frames[this.env.contentframe];
+    if (win = this.get_frame_window(this.env.contentframe)) {
+      target = win;
       url._framed = 1;
     }
 
@@ -4131,11 +4124,11 @@
   // load contact record
   this.load_contact = function(cid, action, framed)
   {
-    var url = {}, target = window;
+    var win, url = {}, target = window;
 
-    if (this.env.contentframe && window.frames && window.frames[this.env.contentframe]) {
+    if (win = this.get_frame_window(this.env.contentframe)) {
       url._framed = 1;
-      target = window.frames[this.env.contentframe];
+      target = win;
       this.show_contentframe(true);
 
       // load dummy content
@@ -4753,11 +4746,11 @@
   // load advanced search page
   this.advanced_search = function()
   {
-    var url = {_form: 1, _action: 'search'}, target = window;
+    var win, url = {_form: 1, _action: 'search'}, target = window;
 
-    if (this.env.contentframe && window.frames && window.frames[this.env.contentframe]) {
+    if (win = this.get_frame_window(this.env.contentframe)) {
       url._framed = 1;
-      target = window.frames[this.env.contentframe];
+      target = win;
       this.contact_list.clear_selection();
     }
 
@@ -4879,13 +4872,13 @@
   // preferences section select and load options frame
   this.section_select = function(list)
   {
-    var id = list.get_single_selection(), target = window,
+    var win, id = list.get_single_selection(), target = window,
       url = {_action: 'edit-prefs', _section: id};
 
     if (id) {
-      if (this.env.contentframe && window.frames && window.frames[this.env.contentframe]) {
+      if (win = this.get_frame_window(this.env.contentframe)) {
         url._framed = 1;
-        target = window.frames[this.env.contentframe];
+        target = win;
       }
       this.location_href(url, target, true);
     }
@@ -4908,13 +4901,12 @@
     if (action == 'edit-identity' && (!id || id == this.env.iid))
       return false;
 
-    var target = window,
+    var win, target = window,
       url = {_action: action, _iid: id};
 
-    if (this.env.contentframe && window.frames && window.frames[this.env.contentframe]) {
+    if (win = this.get_frame_window(this.env.contentframe)) {
       url._framed = 1;
-      target = window.frames[this.env.contentframe];
-      document.getElementById(this.env.contentframe).style.visibility = 'inherit';
+      target = win;
     }
 
     if (action && (id || action == 'add-identity')) {
@@ -5290,14 +5282,14 @@
   // when user select a folder in manager
   this.show_folder = function(folder, path, force)
   {
-    var target = window,
+    var win, target = window,
       url = '&_action=edit-folder&_mbox='+urlencode(folder);
 
     if (path)
       url += '&_path='+urlencode(path);
 
-    if (this.env.contentframe && window.frames && window.frames[this.env.contentframe]) {
-      target = window.frames[this.env.contentframe];
+    if (win = this.get_frame_window(this.env.contentframe)) {
+      target = win;
       url += '&_framed=1';
     }
 
@@ -6607,6 +6599,12 @@
     return 0;
   };
 
+  // Cookie setter
+  this.set_cookie = function(name, value, expires)
+  {
+    setCookie(name, value, expires, this.env.cookie_path, this.env.cookie_domain, this.env.cookie_secure);
+  }
+
 }  // end object rcube_webmail
 
 
@@ -6637,6 +6635,8 @@
   }
 };
 
+rcube_webmail.prototype.get_cookie = getCookie;
+
 // copy event engine prototype
 rcube_webmail.prototype.addEventListener = rcube_event_engine.prototype.addEventListener;
 rcube_webmail.prototype.removeEventListener = rcube_event_engine.prototype.removeEventListener;
diff --git a/program/js/common.js b/program/js/common.js
index fdef345..f9e945c 100644
--- a/program/js/common.js
+++ b/program/js/common.js
@@ -494,12 +494,15 @@
       atom = '[^\\x00-\\x20\\x22\\x28\\x29\\x2c\\x2e\\x3a-\\x3c\\x3e\\x40\\x5b-\\x5d\\x7f-\\xff]+',
       quoted_pair = '\\x5c[\\x00-\\x7f]',
       quoted_string = '\\x22('+qtext+'|'+quoted_pair+')*\\x22',
+      ipv4 = '\\[(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])(\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])){3}\\]',
+      ipv6 = '\\[IPv6:[0-9a-f:.]+\\]',
+      ip_addr = '(' + ipv4 + ')|(' + ipv6 + ')',
       // Use simplified domain matching, because we need to allow Unicode characters here
       // So, e-mail address should be validated also on server side after idn_to_ascii() use
       //domain_literal = '\\x5b('+dtext+'|'+quoted_pair+')*\\x5d',
       //sub_domain = '('+atom+'|'+domain_literal+')',
       // allow punycode/unicode top-level domain
-      domain = '([^@\\x2e]+\\x2e)+([^\\x00-\\x40\\x5b-\\x60\\x7b-\\x7f]{2,}|xn--[a-z0-9]{2,})',
+      domain = '(('+ip_addr+')|(([^@\\x2e]+\\x2e)+([^\\x00-\\x40\\x5b-\\x60\\x7b-\\x7f]{2,}|xn--[a-z0-9]{2,})))',
       // ICANN e-mail test (http://idn.icann.org/E-mail_test)
       icann_domains = [
         '\\u0645\\u062b\\u0627\\u0644\\x2e\\u0625\\u062e\\u062a\\u0628\\u0627\\u0631',
@@ -526,7 +529,6 @@
 
   return false;
 };
-
 
 // recursively copy an object
 function rcube_clone_object(obj)
@@ -635,6 +637,7 @@
   return unescape(dc.substring(begin + prefix.length, end));
 };
 
+// deprecated aliases, to be removed, use rcmail.set_cookie/rcmail.get_cookie
 roundcube_browser.prototype.set_cookie = setCookie;
 roundcube_browser.prototype.get_cookie = getCookie;
 
diff --git a/program/js/googiespell.js b/program/js/googiespell.js
index 9f1b41b..478858b 100644
--- a/program/js/googiespell.js
+++ b/program/js/googiespell.js
@@ -25,7 +25,7 @@
 function GoogieSpell(img_dir, server_url, has_dict)
 {
     var ref = this,
-        cookie_value = getCookie('language');
+        cookie_value = rcmail.get_cookie('language');
 
     GOOGIE_CUR_LANG = cookie_value != null ? cookie_value : GOOGIE_DEFAULT_LANG;
 
@@ -150,7 +150,7 @@
     //Set cookie
     var now = new Date();
     now.setTime(now.getTime() + 365 * 24 * 60 * 60 * 1000);
-    setCookie('language', lan_code, now);
+    rcmail.set_cookie('language', lan_code, now);
 };
 
 this.setForceWidthHeight = function(width, height)
diff --git a/program/js/list.js b/program/js/list.js
index e84124b..1457382 100644
--- a/program/js/list.js
+++ b/program/js/list.js
@@ -231,8 +231,8 @@
     }
   }
 
-  // Un-focus already focused elements
-  $(document.activeElement).blur();
+  // Un-focus already focused elements (#1487123, #1487316, #1488600, #1488620)
+  $(':focus:not(body)').blur();
   $('iframe').each(function() { this.blur(); });
 
   if (e || (e = window.event))
diff --git a/program/lib/html2text.php b/program/lib/html2text.php
index 9de2e96..28c5ae0 100644
--- a/program/lib/html2text.php
+++ b/program/lib/html2text.php
@@ -89,7 +89,7 @@
  *  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
+ *  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,
@@ -200,7 +200,7 @@
     var $ent_search = array(
         '/&(nbsp|#160);/i',                      // Non-breaking space
         '/&(quot|rdquo|ldquo|#8220|#8221|#147|#148);/i',
-		                                         // Double quotes
+                                         // Double quotes
         '/&(apos|rsquo|lsquo|#8216|#8217);/i',   // Single quotes
         '/&gt;/i',                               // Greater-than
         '/&lt;/i',                               // Less-than
@@ -437,11 +437,11 @@
     function set_base_url( $url = '' )
     {
         if ( empty($url) ) {
-        	if ( !empty($_SERVER['HTTP_HOST']) ) {
-	            $this->url = 'http://' . $_SERVER['HTTP_HOST'];
-        	} else {
-	            $this->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")
@@ -515,7 +515,7 @@
         $text = preg_replace($this->ent_search, $this->ent_replace, $text);
 
         // Replace known html entities
-        $text = html_entity_decode($text, ENT_COMPAT, 'UTF-8');
+        $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);
@@ -535,7 +535,7 @@
         // 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);
+            $text = wordwrap($text, $this->width);
         }
     }
 
@@ -554,16 +554,16 @@
      */
     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 (!$this->_do_links || empty($link)) {
+            return $display;
         }
 
-	    if (preg_match('!^([a-z][a-z0-9.+-]+:)!i', $link)) {
+        // 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 {
diff --git a/program/lib/washtml.php b/program/lib/washtml.php
index c12315f..98ae5ed 100644
--- a/program/lib/washtml.php
+++ b/program/lib/washtml.php
@@ -214,8 +214,11 @@
       $key = strtolower($key);
       $value = $node->getAttribute($key);
       if (isset($this->_html_attribs[$key]) ||
-         ($key == 'href' && preg_match('!^([a-z][a-z0-9.+-]+:|//|#).+!i', $value)))
+         ($key == 'href' && !preg_match('!^javascript!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;
@@ -237,7 +240,8 @@
         else if (preg_match('/^data:.+/i', $value)) { // RFC2397
           $t .= ' ' . $key . '="' . htmlspecialchars($value, ENT_QUOTES) . '"';
         }
-      } else
+      }
+      else
         $washed .= ($washed?' ':'') . $key;
     }
     return $t . ($washed && $this->config['show_washed']?' x-washed="'.$washed.'"':'');
diff --git a/program/localization/en_US/labels.inc b/program/localization/en_US/labels.inc
index 94bae19..9882c19 100644
--- a/program/localization/en_US/labels.inc
+++ b/program/localization/en_US/labels.inc
@@ -1,7 +1,6 @@
 <?php
 
 /*
-
  +-----------------------------------------------------------------------+
  | language/en_US/labels.inc                                             |
  |                                                                       |
@@ -15,9 +14,6 @@
  +-----------------------------------------------------------------------+
  | Author: Thomas Bruederli <roundcube@gmail.com>                        |
  +-----------------------------------------------------------------------+
-
- @version $Id$
-
 */
 
 $labels = array();
@@ -163,6 +159,7 @@
 $labels['flagged'] = 'Flagged';
 $labels['unanswered'] = 'Unanswered';
 $labels['deleted'] = 'Deleted';
+$labels['undeleted'] = 'Not deleted';
 $labels['invert'] = 'Invert';
 $labels['filter'] = 'Filter';
 $labels['list'] = 'List';
@@ -384,7 +381,8 @@
 $labels['signature'] = 'Signature';
 $labels['dstactive']  = 'Daylight saving time';
 $labels['htmleditor'] = 'Compose HTML messages';
-$labels['htmlonreply'] = 'on reply to HTML message only';
+$labels['htmlonreply'] = 'on reply to HTML message';
+$labels['htmlonreplyandforward'] = 'on forward or reply to HTML message';
 $labels['htmlsignature'] = 'HTML signature';
 $labels['previewpane'] = 'Show preview pane';
 $labels['skin'] = 'Interface skin';
@@ -432,8 +430,9 @@
 $labels['newmessage'] = 'New Message';
 $labels['signatureoptions'] = 'Signature Options';
 $labels['whenreplying'] = 'When replying';
-$labels['replytopposting'] = 'start new message above original';
-$labels['replybottomposting'] = 'start new message below original';
+$labels['replyempty'] = 'do not quote the original message';
+$labels['replytopposting'] = 'start new message above the quote';
+$labels['replybottomposting'] = 'start new message below the quote';
 $labels['replyremovesignature'] = 'When replying remove original signature from message';
 $labels['autoaddsignature'] = 'Automatically add signature';
 $labels['newmessageonly'] = 'new message only';
diff --git a/program/localization/pl_PL/labels.inc b/program/localization/pl_PL/labels.inc
index 92da1f6..d5ffcaa 100644
--- a/program/localization/pl_PL/labels.inc
+++ b/program/localization/pl_PL/labels.inc
@@ -134,6 +134,7 @@
 $labels['flagged'] = 'Oznaczone';
 $labels['unanswered'] = 'Bez odpowiedzi';
 $labels['deleted'] = 'Usunięte';
+$labels['undeleted'] = 'Nieusunięte';
 $labels['invert'] = 'Odwróć';
 $labels['filter'] = 'Filtr';
 $labels['list'] = 'Lista';
diff --git a/program/steps/addressbook/export.inc b/program/steps/addressbook/export.inc
index 84a63ae..850795c 100644
--- a/program/steps/addressbook/export.inc
+++ b/program/steps/addressbook/export.inc
@@ -86,7 +86,7 @@
         foreach ($row as $key => $values) {
             list($field, $section) = explode(':', $key);
             foreach ((array)$values as $value) {
-                if (is_array($value) || strlen($value))
+                if (is_array($value) || @strlen($value))
                     $vcard->set($field, $value, strtoupper($section));
             }
         }
diff --git a/program/steps/addressbook/search.inc b/program/steps/addressbook/search.inc
index d31e54b..8513250 100644
--- a/program/steps/addressbook/search.inc
+++ b/program/steps/addressbook/search.inc
@@ -237,9 +237,12 @@
     $OUTPUT->command('set_env', 'source', '');
     $OUTPUT->command('set_env', 'group', '');
 
-    // unselect currently selected directory/group
-    if (!$sid)
+    if (!$sid) {
+        // unselect currently selected directory/group
         $OUTPUT->command('unselect_directory');
+        // enable "Save search" command
+        $OUTPUT->command('enable_command', 'search-create', true);
+    }
     $OUTPUT->command('update_group_commands');
 
     // send response
diff --git a/program/steps/mail/compose.inc b/program/steps/mail/compose.inc
index 71a1c0f..29e1267 100644
--- a/program/steps/mail/compose.inc
+++ b/program/steps/mail/compose.inc
@@ -139,7 +139,7 @@
 // set current mailbox in client environment
 $OUTPUT->set_env('mailbox', $RCMAIL->storage->get_folder());
 $OUTPUT->set_env('sig_above', $RCMAIL->config->get('sig_above', false));
-$OUTPUT->set_env('top_posting', $RCMAIL->config->get('top_posting', false));
+$OUTPUT->set_env('top_posting', intval($RCMAIL->config->get('reply_mode')) > 0);
 $OUTPUT->set_env('recipients_separator', trim($RCMAIL->config->get('recipients_separator', ',')));
 
 // default font for HTML editor
@@ -252,7 +252,8 @@
 if (count($MESSAGE->identities))
 {
   foreach ($MESSAGE->identities as $idx => $ident) {
-    $email = mb_strtolower(rcube_idn_to_utf8($ident['email']));
+    $ident['email'] = format_email($ident['email']);
+    $email = format_email(rcube_idn_to_utf8($ident['email']));
 
     $MESSAGE->identities[$idx]['email_ascii'] = $ident['email'];
     $MESSAGE->identities[$idx]['ident']       = format_email_recipient($ident['email'], $ident['name']);
@@ -277,7 +278,7 @@
     $a_to = rcube_mime::decode_address_list($MESSAGE->headers->to, null, true, $MESSAGE->headers->charset);
     foreach ($a_to as $addr) {
       if (!empty($addr['mailto'])) {
-        $a_recipients[] = strtolower($addr['mailto']);
+        $a_recipients[] = format_email($addr['mailto']);
         $a_names[]      = $addr['name'];
       }
     }
@@ -286,7 +287,7 @@
       $a_cc = rcube_mime::decode_address_list($MESSAGE->headers->cc, null, true, $MESSAGE->headers->charset);
       foreach ($a_cc as $addr) {
         if (!empty($addr['mailto'])) {
-          $a_recipients[] = strtolower($addr['mailto']);
+          $a_recipients[] = format_email($addr['mailto']);
           $a_names[]      = $addr['name'];
         }
       }
@@ -294,16 +295,12 @@
   }
 
   $from_idx         = null;
-  $default_identity = null;
+  $found_idx        = null;
+  $default_identity = 0; // default identity is always first on the list
   $return_path      = $MESSAGE->headers->others['return-path'];
 
   // Select identity
   foreach ($MESSAGE->identities as $idx => $ident) {
-    // save default identity ID
-    if ($ident['standard']) {
-      $default_identity = $idx;
-    }
-
     // use From header
     if (in_array($compose_mode, array(RCUBE_COMPOSE_DRAFT, RCUBE_COMPOSE_EDIT))) {
       if ($MESSAGE->headers->from == $ident['ident']) {
@@ -318,11 +315,20 @@
     }
     // use replied message recipients
     else if (($found = array_search($ident['email_ascii'], $a_recipients)) !== false) {
-      // match identity name, prefer default identity
-      if ($from_idx === null || ($a_names[$found] && $ident['name'] && $a_names[$found] == $ident['name'])) {
+      if ($found_idx === null) {
+        $found_idx = $idx;
+      }
+      // match identity name
+      if ($a_names[$found] && $ident['name'] && $a_names[$found] == $ident['name']) {
         $from_idx = $idx;
+        break;
       }
     }
+  }
+
+  // If matching by name+address doesn't found any amtches, get first found address (identity)
+  if ($from_idx === null) {
+    $from_idx = $found_idx;
   }
 
   // Fallback using Return-Path
@@ -335,12 +341,7 @@
     }
   }
 
-  // Still no ID, use default/first identity
-  if ($from_idx === null) {
-    $from_idx = $default_identity !== null ? $default_identity : key(reset($MESSAGE->identities));
-  }
-
-  $ident   = $MESSAGE->identities[$from_idx];
+  $ident   = $MESSAGE->identities[$from_idx !== null ? $from_idx : $default_identity];
   $from_id = $ident['identity_id'];
 
   $MESSAGE->compose['from_email'] = $ident['email'];
@@ -433,7 +434,7 @@
       if (empty($addr_part['mailto']))
         continue;
 
-      $mailto = mb_strtolower(rcube_idn_to_utf8($addr_part['mailto']));
+      $mailto = format_email(rcube_idn_to_utf8($addr_part['mailto']));
 
       if (!in_array($mailto, $a_recipients)
         && ($header == 'to' || empty($MESSAGE->compose['from_email']) || $mailto != $MESSAGE->compose['from_email'])
@@ -529,7 +530,7 @@
 
 function rcmail_compose_header_from($attrib)
 {
-  global $MESSAGE, $OUTPUT;
+  global $MESSAGE, $OUTPUT, $RCMAIL, $compose_mode;
 
   // pass the following attributes to the form class
   $field_attrib = array('name' => '_from');
@@ -540,6 +541,8 @@
   if (count($MESSAGE->identities))
   {
     $a_signatures = array();
+    $separator    = $RCMAIL->config->get('sig_above')
+      && ($compose_mode == RCUBE_COMPOSE_REPLY || $compose_mode == RCUBE_COMPOSE_FORWARD) ? '---' : '-- ';
 
     $field_attrib['onchange'] = JS_OBJECT_NAME.".change_identity(this)";
     $select_from = new html_select($field_attrib);
@@ -553,13 +556,27 @@
       // add signature to array
       if (!empty($sql_arr['signature']) && empty($COMPOSE['param']['nosig']))
       {
-        $a_signatures[$identity_id]['text'] = $sql_arr['signature'];
-        $a_signatures[$identity_id]['is_html'] = ($sql_arr['html_signature'] == 1) ? true : false;
-        if ($a_signatures[$identity_id]['is_html'])
-        {
-            $h2t = new html2text($a_signatures[$identity_id]['text'], false, false);
-            $a_signatures[$identity_id]['plain_text'] = trim($h2t->get_text());
+        $text = $html = $sql_arr['signature'];
+
+        if ($sql_arr['html_signature']) {
+            $h2t  = new html2text($sql_arr['signature'], false, false);
+            $text = trim($h2t->get_text());
         }
+        else {
+            $html = htmlentities($html, ENT_NOQUOTES, RCMAIL_CHARSET);
+        }
+
+        if (!preg_match('/^--[ -]\r?\n/m', $text)) {
+            $text = $separator . "\n" . $text;
+            $html = $separator . "<br>" . $html;
+        }
+
+        if (!$sql_arr['html_signature']) {
+            $html = "<pre>" . $html . "</pre>";
+        }
+
+        $a_signatures[$identity_id]['text'] = $text;
+        $a_signatures[$identity_id]['html'] = $html;
       }
     }
 
@@ -593,9 +610,12 @@
     $useHtml = $MESSAGE->has_html_part(false);
   }
   else if ($compose_mode == RCUBE_COMPOSE_REPLY) {
-    $useHtml = ($html_editor == 1 || ($html_editor == 2 && $MESSAGE->has_html_part(false)));
+    $useHtml = ($html_editor == 1 || ($html_editor >= 2 && $MESSAGE->has_html_part(false)));
   }
-  else { // RCUBE_COMPOSE_FORWARD or NEW
+  else if ($compose_mode == RCUBE_COMPOSE_FORWARD) {
+    $useHtml = ($html_editor == 1 || ($html_editor == 3 && $MESSAGE->has_html_part(false)));
+  }
+  else {
     $useHtml = ($html_editor == 1);
   }
 
@@ -624,7 +644,7 @@
       rcmail_write_forward_attachment($MESSAGE);
   }
   // reply/edit/draft/forward
-  else if ($compose_mode) {
+  else if ($compose_mode && ($compose_mode != RCUBE_COMPOSE_REPLY || $RCMAIL->config->get('reply_mode') != -1)) {
     $isHtml = rcmail_compose_editor_mode();
 
     if (!empty($MESSAGE->parts)) {
@@ -889,8 +909,9 @@
     $prefix .= "\n";
     $suffix = '';
 
-    if ($RCMAIL->config->get('top_posting'))
+    if (intval($RCMAIL->config->get('reply_mode')) > 0) { // top-posting
       $prefix = "\n\n\n" . $prefix;
+    }
   }
   else {
     // save inline images to files
@@ -904,7 +925,7 @@
     $prefix = '<p>' . Q($prefix) . "</p>\n";
     $prefix .= '<blockquote>';
 
-    if ($RCMAIL->config->get('top_posting')) {
+    if (intval($RCMAIL->config->get('reply_mode')) > 0) { // top-posting
       $prefix = '<br>' . $prefix;
       $suffix = '</blockquote>';
     }
diff --git a/program/steps/mail/func.inc b/program/steps/mail/func.inc
index 3d65eac..8bf80a6 100644
--- a/program/steps/mail/func.inc
+++ b/program/steps/mail/func.inc
@@ -90,11 +90,13 @@
   // set current mailbox and some other vars in client environment
   $OUTPUT->set_env('mailbox', $mbox_name);
   $OUTPUT->set_env('pagesize', $RCMAIL->storage->get_pagesize());
-  $OUTPUT->set_env('quota', $RCMAIL->storage->get_capability('QUOTA'));
   $OUTPUT->set_env('delimiter', $RCMAIL->storage->get_hierarchy_delimiter());
   $OUTPUT->set_env('threading', $threading);
   $OUTPUT->set_env('threads', $threading || $RCMAIL->storage->get_capability('THREAD'));
   $OUTPUT->set_env('preview_pane_mark_read', $RCMAIL->config->get('preview_pane_mark_read', 0));
+  if ($RCMAIL->storage->get_capability('QUOTA')) {
+    $OUTPUT->set_env('quota', true);
+  }
 
   if ($CONFIG['delete_junk'])
     $OUTPUT->set_env('delete_junk', true);
@@ -1053,12 +1055,17 @@
   global $OUTPUT;
 
   $html = html::div(array('id' => "all-headers", 'class' => "all", 'style' => 'display:none'), html::div(array('id' => 'headers-source'), ''));
-  $html .= html::div(array('class' => "more-headers show-headers", 'onclick' => "return ".JS_OBJECT_NAME.".command('show-headers','',this)"), '');
+
+  if (!get_boolean($attrib['no-switch'])) {
+    $html .= html::div(array('class' => "more-headers show-headers", 'onclick' => "return ".JS_OBJECT_NAME.".command('show-headers','',this)"), '');
+  }
+
+  unset($attrib['no-switch']);
 
   $OUTPUT->add_gui_object('all_headers_row', 'all-headers');
   $OUTPUT->add_gui_object('all_headers_box', 'headers-source');
 
-  return html::div($attrib, $html);
+  return count($attrib) > 1 ? html::div($attrib, $html) : $html;
 }
 
 
@@ -1712,8 +1719,10 @@
   $select_filter->add(rcube_label('unread'), 'UNSEEN');
   $select_filter->add(rcube_label('flagged'), 'FLAGGED');
   $select_filter->add(rcube_label('unanswered'), 'UNANSWERED');
-  if (!$CONFIG['skip_deleted'])
+  if (!$CONFIG['skip_deleted']) {
     $select_filter->add(rcube_label('deleted'), 'DELETED');
+    $select_filter->add(rcube_label('undeleted'), 'UNDELETED');
+  }
   $select_filter->add(rcube_label('priority').': '.rcube_label('highest'), 'HEADER X-PRIORITY 1');
   $select_filter->add(rcube_label('priority').': '.rcube_label('high'), 'HEADER X-PRIORITY 2');
   $select_filter->add(rcube_label('priority').': '.rcube_label('normal'), 'NOT HEADER X-PRIORITY 1 NOT HEADER X-PRIORITY 2 NOT HEADER X-PRIORITY 4 NOT HEADER X-PRIORITY 5');
diff --git a/program/steps/mail/headers.inc b/program/steps/mail/headers.inc
index 4d66273..cad113f 100644
--- a/program/steps/mail/headers.inc
+++ b/program/steps/mail/headers.inc
@@ -24,7 +24,8 @@
     $source = $RCMAIL->storage->get_raw_headers($uid);
 
     if ($source !== false) {
-        $source = htmlspecialchars(trim($source));
+        $source = trim(rcube_charset::clean($source));
+        $source = htmlspecialchars($source);
         $source = preg_replace(
             array(
                 '/\n[\t\s]+/',
diff --git a/program/steps/mail/sendmail.inc b/program/steps/mail/sendmail.inc
index 70f1af7..5777517 100644
--- a/program/steps/mail/sendmail.inc
+++ b/program/steps/mail/sendmail.inc
@@ -511,14 +511,9 @@
   $h2t = new html2text($plugin['body'], false, true, 0);
   $plainTextPart = rc_wordwrap($h2t->get_text(), $LINE_LENGTH, "\r\n");
   $plainTextPart = wordwrap($plainTextPart, 998, "\r\n", true);
-  if (!$plainTextPart) {
-    // empty message body breaks attachment handling in drafts
-    $plainTextPart = "\r\n";
-  }
-  else {
-    // make sure all line endings are CRLF (#1486712)
-    $plainTextPart = preg_replace('/\r?\n/', "\r\n", $plainTextPart);
-  }
+
+  // make sure all line endings are CRLF (#1486712)
+  $plainTextPart = preg_replace('/\r?\n/', "\r\n", $plainTextPart);
 
   $plugin = $RCMAIL->plugins->exec_hook('message_outgoing_body',
     array('body' => $plainTextPart, 'type' => 'alternative', 'message' => $MAIL_MIME));
@@ -542,10 +537,6 @@
     $message_body = rc_wordwrap($message_body, $LINE_LENGTH, "\r\n");
 
   $message_body = wordwrap($message_body, 998, "\r\n", true);
-  if (!strlen($message_body)) { 
-    // empty message body breaks attachment handling in drafts 
-    $message_body = "\r\n"; 
-  }
 
   $MAIL_MIME->setTXTBody($message_body, false, true);
 }
diff --git a/program/steps/mail/viewsource.inc b/program/steps/mail/viewsource.inc
index 59ccb38..c560d7d 100644
--- a/program/steps/mail/viewsource.inc
+++ b/program/steps/mail/viewsource.inc
@@ -44,7 +44,7 @@
     header("Content-Disposition: attachment; filename=\"$filename\"");
   }
 
-  $RCMAIL->storage->print_raw_body($uid);
+  $RCMAIL->storage->print_raw_body($uid, empty($_GET['_save']));
 }
 else
 {
diff --git a/program/steps/settings/folders.inc b/program/steps/settings/folders.inc
index 2691a6e..6ca7049 100644
--- a/program/steps/settings/folders.inc
+++ b/program/steps/settings/folders.inc
@@ -44,8 +44,8 @@
 
         if ($result) {
             // Handle subscription of protected folder (#1487656)
-            if ($CONFIG['protect_default_folders'] == true
-                && in_array($mbox, $CONFIG['default_folders'])
+            if ($RCMAIL->config->get('protect_default_folders')
+                && in_array($mbox, (array)$RCMAIL->config->get('default_folders'))
             ) {
                 $OUTPUT->command('disable_subscription', $mbox);
             }
@@ -321,8 +321,8 @@
                 }
             }
         }
-        // check if the folder is shared, then disable subscription option on it
-        if (!$disabled && $folder['virtual'] && !empty($namespace)) {
+        // check if the folder is shared, then disable subscription option on it (if not subscribed already)
+        if (!$disabled && !$subscribed && $folder['virtual'] && !empty($namespace)) {
             $tmp_ns = array_merge((array)$namespace['other'], (array)$namespace['shared']);
             foreach ($tmp_ns as $item) {
                 if (strpos($folder['id'], $item[0]) === 0) {
@@ -411,8 +411,10 @@
 
 $OUTPUT->set_pagetitle(rcube_label('folders'));
 $OUTPUT->include_script('list.js');
-$OUTPUT->set_env('quota', $STORAGE->get_capability('QUOTA'));
 $OUTPUT->set_env('prefix_ns', $STORAGE->get_namespace('prefix'));
+if ($STORAGE->get_capability('QUOTA')) {
+    $OUTPUT->set_env('quota', true);
+}
 
 // add some labels to client
 $OUTPUT->add_label('deletefolderconfirm', 'purgefolderconfirm', 'folderdeleting',
diff --git a/program/steps/settings/func.inc b/program/steps/settings/func.inc
index 3f5ef53..59b4e37 100644
--- a/program/steps/settings/func.inc
+++ b/program/steps/settings/func.inc
@@ -470,6 +470,7 @@
       $select_htmleditor->add(rcube_label('never'), 0);
       $select_htmleditor->add(rcube_label('always'), 1);
       $select_htmleditor->add(rcube_label('htmlonreply'), 2);
+      $select_htmleditor->add(rcube_label('htmlonreplyandforward'), 3);
 
       $blocks['main']['options']['htmleditor'] = array(
         'title' => html::label($field_id, Q(rcube_label('htmleditor'))),
@@ -544,16 +545,17 @@
       );
     }
 
-    if (!isset($no_override['top_posting'])) {
-      $field_id = 'rcmfd_top_posting';
-      $select_replymode = new html_select(array('name' => '_top_posting', 'id' => $field_id,
-        'onchange' => "\$('#rcmfd_sig_above').attr('disabled',this.selectedIndex==0)"));
+    if (!isset($no_override['reply_mode'])) {
+      $field_id = 'rcmfd_reply_mode';
+      $select_replymode = new html_select(array('name' => '_reply_mode', 'id' => $field_id,
+        'onchange' => "\$('#rcmfd_sig_above').attr('disabled',this.selectedIndex<2)"));
+      $select_replymode->add(rcube_label('replyempty'), -1);
       $select_replymode->add(rcube_label('replybottomposting'), 0);
       $select_replymode->add(rcube_label('replytopposting'), 1);
 
-      $blocks['main']['options']['top_posting'] = array(
+      $blocks['main']['options']['reply_mode'] = array(
         'title' => html::label($field_id, Q(rcube_label('whenreplying'))),
-        'content' => $select_replymode->show($config['top_posting']?1:0),
+        'content' => $select_replymode->show(intval($config['reply_mode'])),
       );
     }
 
@@ -597,7 +599,7 @@
 
     if (!isset($no_override['sig_above'])) {
       $field_id = 'rcmfd_sig_above';
-      $select_sigabove = new html_select(array('name' => '_sig_above', 'id' => $field_id, 'disabled' => !$config['top_posting']));
+      $select_sigabove = new html_select(array('name' => '_sig_above', 'id' => $field_id, 'disabled' => $config['reply_mode'] < 1));
       $select_sigabove->add(rcube_label('belowquote'), 0);
       $select_sigabove->add(rcube_label('abovequote'), 1);
 
diff --git a/program/steps/settings/save_folder.inc b/program/steps/settings/save_folder.inc
index 09f76ac..73cc5e4 100644
--- a/program/steps/settings/save_folder.inc
+++ b/program/steps/settings/save_folder.inc
@@ -80,7 +80,10 @@
     }
 }
 
-if (!$error) {
+if ($error) {
+    $OUTPUT->command('display_message', $error, 'error');
+}
+else {
     $folder['name']     = $name_imap;
     $folder['oldname']  = $old_imap;
     $folder['class']    = '';
diff --git a/program/steps/settings/save_prefs.inc b/program/steps/settings/save_prefs.inc
index 88fa529..dc14992 100644
--- a/program/steps/settings/save_prefs.inc
+++ b/program/steps/settings/save_prefs.inc
@@ -82,9 +82,9 @@
       'spellcheck_ignore_nums' => isset($_POST['_spellcheck_ignore_nums']) ? TRUE : FALSE,
       'spellcheck_ignore_caps' => isset($_POST['_spellcheck_ignore_caps']) ? TRUE : FALSE,
       'show_sig'           => isset($_POST['_show_sig']) ? intval($_POST['_show_sig']) : 1,
-      'top_posting'        => !empty($_POST['_top_posting']),
+      'reply_mode'         => isset($_POST['_reply_mode']) ? intval($_POST['_reply_mode']) : 0,
       'strip_existing_sig' => isset($_POST['_strip_existing_sig']),
-      'sig_above'          => !empty($_POST['_sig_above']) && !empty($_POST['_top_posting']),
+      'sig_above'          => !empty($_POST['_sig_above']) && $_POST['_reply_mode'] < 1,
       'default_font'       => get_input_value('_default_font', RCUBE_INPUT_POST),
       'forward_attachment' => !empty($_POST['_forward_attachment']),
     );
diff --git a/skins/classic/functions.js b/skins/classic/functions.js
index 9b83b90..73c43f6 100644
--- a/skins/classic/functions.js
+++ b/skins/classic/functions.js
@@ -28,11 +28,6 @@
   $('a', tab).removeAttr('onclick').click(function() { return false; });
 }
 
-function rcube_show_advanced(visible)
-{
-  $('tr.advanced').css('display', (visible ? (bw.ie ? 'block' : 'table-row') : 'none'));
-}
-
 // Fieldsets-to-tabs converter
 // Warning: don't place "caller" <script> inside page element (id)
 function rcube_init_tabs(id, current)
@@ -530,7 +525,7 @@
   if ((row = document.getElementById('compose-' + id))) {
     var div = document.getElementById('compose-div'),
       headers_div = document.getElementById('compose-headers-div');
-    row.style.display = (document.all && !window.opera) ? 'block' : 'table-row';
+    $(row).show();
     div.style.top = (parseInt(headers_div.offsetHeight, 10) + 3) + 'px';
     this.resize_compose_body();
   }
@@ -550,11 +545,11 @@
   for (var i=0; i<links.length; i++)
     if (links[i].style.display != 'none')
       for (var j=i+1; j<links.length; j++)
-	    if (links[j].style.display != 'none')
+        if (links[j].style.display != 'none')
           if ((ns = this.next_sibling(links[i]))) {
-	        ns.style.display = '';
-	        break;
-	      }
+            ns.style.display = '';
+            break;
+          }
 
   document.getElementById('_' + id).value = '';
 
diff --git a/skins/classic/includes/links.html b/skins/classic/includes/links.html
index 8267322..6d8d03c 100644
--- a/skins/classic/includes/links.html
+++ b/skins/classic/includes/links.html
@@ -1,3 +1,4 @@
+<meta http-equiv="X-UA-Compatible" content="IE=EDGE" />
 <link rel="index" href="$__comm_path" />
 <link rel="shortcut icon" href="/images/favicon.ico"/>
 <link rel="stylesheet" type="text/css" href="/common.css" />
diff --git a/skins/classic/splitter.js b/skins/classic/splitter.js
index 59ebb51..3f1c973 100644
--- a/skins/classic/splitter.js
+++ b/skins/classic/splitter.js
@@ -47,7 +47,7 @@
       rcube_event.add_listener({element: window, event:'resize', object:this, method:'onResize'});
 
     // read saved position from cookie
-    var cookie = bw.get_cookie(this.id);
+    var cookie = rcmail.get_cookie(this.id);
     if (cookie && !isNaN(cookie)) {
       this.pos = parseFloat(cookie);
       this.resize();
@@ -197,7 +197,7 @@
   {
     var exp = new Date();
     exp.setYear(exp.getFullYear() + 1);
-    bw.set_cookie(this.id, this.pos, exp);
+    rcmail.set_cookie(this.id, this.pos, exp);
   };
 
 } // end class rcube_splitter
diff --git a/skins/classic/templates/compose.html b/skins/classic/templates/compose.html
index caebf31..1e1403e 100644
--- a/skins/classic/templates/compose.html
+++ b/skins/classic/templates/compose.html
@@ -79,7 +79,7 @@
                 <a href="#bcc" onclick="return rcmail_ui.hide_header_form('bcc');"><img src="/images/icons/minus.gif" alt="" width="13" height="11" title="<roundcube:label name='delete' />" /></a>
                 <label for="_bcc"><roundcube:label name="bcc" /></label>
             </td>
-            <td colspan="2" class="editfield"><roundcube:object name="composeHeaders" part="bcc" form="form" id="_bcc" cols="70" rows="2" tabindex="4" /></td>
+            <td class="editfield"><roundcube:object name="composeHeaders" part="bcc" form="form" id="_bcc" cols="70" rows="2" tabindex="4" /></td>
         </tr><tr id="compose-replyto">
             <td class="title top">
                 <a href="#replyto" onclick="return rcmail_ui.hide_header_form('replyto');"><img src="/images/icons/minus.gif" alt="" width="13" height="11" title="<roundcube:label name='delete' />" /></a>
diff --git a/skins/classic/templates/message.html b/skins/classic/templates/message.html
index 714540b..c03376e 100644
--- a/skins/classic/templates/message.html
+++ b/skins/classic/templates/message.html
@@ -23,11 +23,9 @@
 <div id="mailboxlist-container">
 <div id="mailboxlist-title" class="boxtitle"><roundcube:label name="mailboxlist" /></div>
 <div class="boxlistcontent">
-<roundcube:object name="mailboxlist" id="mailboxlist" maxlength="25" />
+    <roundcube:object name="mailboxlist" id="mailboxlist" maxlength="25" />
 </div>
-<div class="boxfooter">
-  <roundcube:button name="mailboxmenulink" id="mailboxmenulink" type="link" title="folderactions" class="button groupactions" onclick="rcmail_ui.show_popup('mailboxmenu');return false" content=" " />
-</div>
+<div class="boxfooter"></div>
 </div>
 </div>
 
@@ -56,15 +54,6 @@
     var mailviewsplitv = new rcube_splitter({id:'mailviewsplitterv', p1: 'mailboxlist-container', p2: 'messageframe', orientation: 'v', relative: true, start: 165});
     rcmail.add_onload('mailviewsplitv.init()');
 </script>
-
-<div id="mailboxoptionsmenu" class="popupmenu">
-  <ul>
-    <li><roundcube:button command="expunge" type="link" label="compact" classAct="active" /></li>
-    <li class="separator_below"><roundcube:button command="purge" type="link" label="empty" classAct="active" /></li>
-    <li><roundcube:button command="folders" task="settings" type="link" label="managefolders" classAct="active" /></li>
-    <roundcube:container name="mailboxoptions" id="mailboxoptionsmenu" />
-  </ul>
-</div>
 
 </body>
 </html>
diff --git a/skins/classic/templates/messageerror.html b/skins/classic/templates/messageerror.html
index 9af45f4..918e309 100644
--- a/skins/classic/templates/messageerror.html
+++ b/skins/classic/templates/messageerror.html
@@ -42,11 +42,9 @@
 <div id="mailboxlist-container">
 <div class="boxtitle"><roundcube:label name="mailboxlist" /></div>
 <div class="boxlistcontent">
-<roundcube:object name="mailboxlist" id="mailboxlist" folder_filter="mail" />
+    <roundcube:object name="mailboxlist" id="mailboxlist" folder_filter="mail" />
 </div>
-<div class="boxfooter">
-  <roundcube:button name="mailboxmenulink" id="mailboxmenulink" type="link" title="folderactions" class="button groupactions" onclick="rcmail_ui.show_popup('mailboxmenu');return false" content=" " />
-</div>
+<div class="boxfooter"></div>
 </div>
 </div>
 
@@ -62,15 +60,6 @@
     var mailviewsplitv = new rcube_splitter({id:'mailviewsplitterv', p1: 'mailboxlist-container', p2: 'messageframe', orientation: 'v', relative: true, start: 165});
     rcmail.add_onload('mailviewsplitv.init()');
 </script>
-
-<div id="mailboxoptionsmenu" class="popupmenu">
-  <ul>
-    <li><roundcube:button command="expunge" type="link" label="compact" classAct="active" /></li>
-    <li class="separator_below"><roundcube:button command="purge" type="link" label="empty" classAct="active" /></li>
-    <li><roundcube:button command="folders" task="settings" type="link" label="managefolders" classAct="active" /></li>
-    <roundcube:container name="mailboxoptions" id="mailboxoptionsmenu" />
-  </ul>
-</div>
 
 </body>
 <roundcube:endif />
diff --git a/skins/larry/addressbook.css b/skins/larry/addressbook.css
index fe087ae..74bc0d7 100644
--- a/skins/larry/addressbook.css
+++ b/skins/larry/addressbook.css
@@ -34,7 +34,6 @@
 	position: absolute;
 	top: -6px;
 	left: 0;
-	right: 260px;
 	height: 40px;
 	white-space: nowrap;
 	z-index: 10;
diff --git a/skins/larry/ie7hacks.css b/skins/larry/ie7hacks.css
index 6161d03..935a504 100644
--- a/skins/larry/ie7hacks.css
+++ b/skins/larry/ie7hacks.css
@@ -7,9 +7,15 @@
  * License. It is allowed to copy, distribute, transmit and to adapt the work
  * by keeping credits to the original autors in the README file.
  * See http://creativecommons.org/licenses/by-sa/3.0/ for details.
- *
- * $Id$
  */
+
+/* #1488618 */
+#mainscreen {
+  height: expression((parseInt(document.documentElement.clientHeight)-108)+'px');
+}
+#mainscreen.offset {
+  height: expression((parseInt(document.documentElement.clientHeight)-150)+'px');
+}
 
 input.button {
 	display: inline;
@@ -23,7 +29,7 @@
 .boxfooter .listbutton .inner,
 .attachmentslist li a.delete,
 .attachmentslist li a.cancelupload,
-#messagepreviewheader .iconlink {
+#messageheader .iconlink {
 	/* workaround for text-indent which also offsets the background image */
 	text-indent: 0;
 	font-size: 0;
@@ -39,7 +45,7 @@
 
 .pagenav a.button,
 .pagenav a.button span.inner,
-#messagepreviewheader .iconlink,
+#messageheader .iconlink,
 #uploadform a.iconlink {
 	display: inline;
 }
@@ -61,7 +67,7 @@
 	text-align: left;
 }
 
-#messagepreviewheader .iconlink {
+#messageheader .iconlink {
 	color: #fff;
 	height: 14px;
 }
diff --git a/skins/larry/iehacks.css b/skins/larry/iehacks.css
index 2882021..bba93dc 100644
--- a/skins/larry/iehacks.css
+++ b/skins/larry/iehacks.css
@@ -143,7 +143,7 @@
 	filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#005d76', endColorstr='#004558', GradientType=0);
 }
 
-#messageheader, #partheader, #composeheaders {
+#partheader, #composeheaders {
 	filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#e9e9e9', GradientType=0);
 }
 
diff --git a/skins/larry/images/contactpic_32px.png b/skins/larry/images/contactpic_32px.png
index 276f197..25a8141 100644
--- a/skins/larry/images/contactpic_32px.png
+++ b/skins/larry/images/contactpic_32px.png
Binary files differ
diff --git a/skins/larry/images/contactpic_48px.png b/skins/larry/images/contactpic_48px.png
new file mode 100644
index 0000000..9cd3bce
--- /dev/null
+++ b/skins/larry/images/contactpic_48px.png
Binary files differ
diff --git a/skins/larry/includes/links.html b/skins/larry/includes/links.html
index 0ddc2e1..8bd8012 100644
--- a/skins/larry/includes/links.html
+++ b/skins/larry/includes/links.html
@@ -1,3 +1,4 @@
+<meta http-equiv="X-UA-Compatible" content="IE=EDGE" />
 <link rel="shortcut icon" href="/images/favicon.ico"/>
 <link rel="stylesheet" type="text/css" href="/styles.css" />
 <roundcube:if condition="in_array(env:task, array('mail','addressbook','settings'))" />
diff --git a/skins/larry/mail.css b/skins/larry/mail.css
index 4fff243..496cbbd 100644
--- a/skins/larry/mail.css
+++ b/skins/larry/mail.css
@@ -38,16 +38,16 @@
 	bottom: 28px;
 }
 
-#mailview-top.fullheight {
-	border-radius: 4px 4px 0 0;
-}
-
 #mailview-bottom {
 	position: absolute;
 	left: 0;
 	bottom: 0;
 	width: 100%;
 	height: 26px;
+}
+
+#mailview-top.fullheight {
+	border-radius: 4px 4px 0 0;
 }
 
 #folderlist-header {
@@ -341,7 +341,6 @@
 #messagetoolbar {
 	position: absolute;
 	top: -6px;
-	right: 390px;
 	left: 0;
 	height: 40px;
 	white-space: nowrap;
@@ -362,7 +361,7 @@
 	position: absolute;
 	right: 0;
 	top: 0;
-	width: 240px;
+	width: 400px;
 }
 
 #mailpreviewtoggle {
@@ -383,11 +382,7 @@
 /*** message list ***/
 
 #messagelist thead td:first-child {
-	border-radius: 4px 0 0 0;
-}
-
-#messagelist thead td:last-child {
-	border-radius: 0 4px 0 0;
+	border-radius: 4px 0 0 0; /* for Chrome */
 }
 
 #messagelist tr td.attachment,
@@ -680,15 +675,14 @@
 
 #messagecontent {
 	position: absolute;
-	top: 140px;
+	top: 0;
 	left: 0;
 	width: 100%;
-	bottom: 0;
+	bottom: 28px;
 	overflow: auto;
 	border-radius: 4px 4px 0 0;
 }
 
-#messageheader,
 #partheader,
 #composeheaders {
 	position: relative;
@@ -712,7 +706,7 @@
 
 h3.subject {
 	font-size: 14px;
-	margin: 0 8em 0 0;
+	margin: 0 13em 0 0;
 	padding: 8px 8px 4px 8px;
 	white-space: nowrap;
 	overflow: hidden;
@@ -787,6 +781,7 @@
 	background: -ms-linear-gradient(left, #fbfbfb 0, #e9e9e9 100%);
 	background: linear-gradient(left, #fbfbfb 0, #e9e9e9 100%);
 	border-right: 1px solid #dfdfdf;
+	border-radius: 3px 0 0 0; /* for Opera */
 }
 
 #previewheaderstoggle .iconlink {
@@ -801,28 +796,29 @@
 
 #previewheaderstoggle.remove .iconlink {
 	top: auto;
-	bottom: 5px;
+	bottom: 15px;
 	background-position: -5px -242px;
 }
 
-div.more-headers {
-	cursor: pointer;
-	height: 10px;
-	background: url(images/buttons.png) center -1619px no-repeat;
+#previewheaderstoggle .iconlink.allheaders {
+	display: none;
 }
 
-div.hide-headers {
-	background-position: center -1629px;
+#previewheaderstoggle.remove .iconlink.allheaders {
+	top: auto;
+	bottom: 2px;
+	display: inline-block;
+	background-position: -27px -242px;
 }
 
 #all-headers {
 	position: relative;
-	margin: 0 10px;
+	margin: 2px 0;
 	padding: 0;
 	height: 180px;
-	border: 1px solid #bbb;
+	background-color: #f0f0f0;
+	overflow: hidden;
 	border-radius: 4px;
-	background: #fff;
 }
 
 #headers-source {
@@ -832,25 +828,30 @@
 	left: 0;
 	right: 0;
 	bottom: 0;
-	padding: 2px 5px;
+	padding: 2px;
 	overflow: auto;
 	text-align: left;
-	color: #333;
+	color: #666;
 }
 
-#messagepreviewheader {
+#messageheader {
 	position: relative;
 	height: auto;
 	margin: 0 8px 0 0;
-	padding: 0 0 6px 72px;
+	padding: 0 0 0 72px;
 	border-bottom: 2px solid #f0f0f0;
 }
 
-#messagepreviewheader h3.subject {
+#messagecontent #messageheader {
+	padding: 0 0 0 90px;
+	min-height: 68px;
+}
+
+#messageheader h3.subject {
 	padding: 8px 8px 2px 0;
 }
 
-#messagepreviewheader #contactphoto {
+#messageheader #contactphoto {
 	display: block;
 	position: absolute;
 	top: 11px;
@@ -862,52 +863,40 @@
 	border-radius: 3px;
 }
 
-#messagepreviewheader #contactphoto img {
+#messageheader #contactphoto img {
 	width: 32px;
 	height: auto;
 	border-radius: 3px;
 }
 
-#messageheader #contactphoto {
-	display: block;
-	position: absolute;
-	top: 40px;
-	right: 10px;
+#messagecontent #messageheader #contactphoto {
+	top: 11px;
+	left: 31px;
 	width: 48px;
 	height: 48px;
-	overflow: hidden;
+	background: url(images/contactpic_48px.png) center center no-repeat #fff;
 	border-radius: 4px;
 }
 
-#messageheader #contactphoto img {
+#messagecontent #messageheader #contactphoto img {
 	width: 48px;
 	height: auto;
 	border-radius: 4px;
 }
 
-#messagepreviewheader #countcontrols,
 #messageheader #countcontrols {
 	position: absolute;
 	top: 8px;
-	right: 8px;
-	width: 20em;
+	right: 0;
 	text-align: right;
 	white-space: nowrap;
 }
 
-#messageheader .pagenav .countdisplay {
-	min-width: 0;
-	padding-right: 0.5em;
-	white-space: nowrap;
-}
-
-#messagecontent .leftcol,
 #messagepreview .leftcol {
 	margin-right: 252px;
 	overflow-x: auto;
 }
 
-#messagecontent .rightcol,
 #messagepreview .rightcol {
 	float: right;
 /*
@@ -921,6 +910,7 @@
 	min-height: 200px;
 	background: #f0f0f0;
 	padding: 8px;
+	border-radius: 4px;
 }
 
 #messagebody {
diff --git a/skins/larry/styles.css b/skins/larry/styles.css
index 0a72c50..f2d4888 100644
--- a/skins/larry/styles.css
+++ b/skins/larry/styles.css
@@ -647,6 +647,7 @@
 .uibox {
 	border: 1px solid #a3a3a3;
 	border-radius: 4px;
+	overflow: hidden;
 	box-shadow: 0 0 2px #999;
 	-o-box-shadow: 0 0 2px #999;
 	-webkit-box-shadow: 0 0 2px #999;
@@ -660,7 +661,7 @@
 	left: 0;
 	bottom: 0;
 	width: 100%;
-	min-width: 1150px;
+	min-width: 1024px;
 }
 
 .scroller {
@@ -698,7 +699,8 @@
 	left: 0;
 	width: 100%;
 	bottom: 0;
-	overflow: auto;
+	overflow-x: hidden;
+	overflow-y: auto;
 }
 
 .listbox .scroller.withfooter {
diff --git a/skins/larry/svggradient.php b/skins/larry/svggradient.php
index c54bdec..8db2c5f 100644
--- a/skins/larry/svggradient.php
+++ b/skins/larry/svggradient.php
@@ -11,6 +11,8 @@
  * See http://creativecommons.org/licenses/by-sa/3.0/ for details.
  */
 
+ini_set('error_reporting', E_ALL &~ (E_NOTICE | E_STRICT));
+
 header('Content-Type: image/svg+xml');
 header("Expires: ".gmdate("D, d M Y H:i:s", time()+864000)." GMT");
 header("Cache-Control: max-age=864000");
diff --git a/skins/larry/svggradients.css b/skins/larry/svggradients.css
index 143fb37..4f1dd8a 100644
--- a/skins/larry/svggradients.css
+++ b/skins/larry/svggradients.css
@@ -133,7 +133,7 @@
 	background-image: url(svggradient.php?c=005d76;004558);
 }
 
-#messageheader, #partheader, #composeheaders {
+#partheader, #composeheaders {
 	background-image: url(svggradient.php?c=ffffff;e9e9e9);
 }
 
diff --git a/skins/larry/templates/compose.html b/skins/larry/templates/compose.html
index ff0c833..d29c1bd 100644
--- a/skins/larry/templates/compose.html
+++ b/skins/larry/templates/compose.html
@@ -131,7 +131,7 @@
 		</span>
 		<roundcube:endif />
 		<span class="composeoption">
-			<label><label for="rcmcomposepriority"><roundcube:label name="priority" />
+			<label for="rcmcomposepriority"><roundcube:label name="priority" />
 				<roundcube:object name="prioritySelector" form="form" id="rcmcomposepriority" /></label>
 		</span>
 		<span class="composeoption">
diff --git a/skins/larry/templates/message.html b/skins/larry/templates/message.html
index 1becd71..89b7bd8 100644
--- a/skins/larry/templates/message.html
+++ b/skins/larry/templates/message.html
@@ -24,20 +24,38 @@
 
 <!-- folders list -->
 <div id="mailboxcontainer" class="uibox listbox">
-<div class="scroller">
-<roundcube:object name="mailboxlist" id="mailboxlist" class="listing" folder_filter="mail" unreadwrap="%s" />
-</div>
+    <div class="scroller">
+        <roundcube:object name="mailboxlist" id="mailboxlist" class="listing" folder_filter="mail" unreadwrap="%s" />
+    </div>
 </div>
 
-</div>
+</div><!-- end mailview-left -->
 
-<div id="mailview-right">
+<div id="mailview-right" class="uibox" style="top: 42px">
 
-<div id="mailview-top">
-<div id="messageheader" class="uibox">
-<h2 class="subject"><roundcube:object name="messageHeaders" valueOf="subject" /></h2>
-<roundcube:object name="messageHeaders" class="headers-table" addicon="/images/addcontact.png" exclude="subject" />
-<roundcube:object name="messageFullHeaders" id="full-headers" />
+<div id="messagecontent">
+
+<div id="messageheader">
+<h3 class="subject"><roundcube:object name="messageHeaders" valueOf="subject" /></h3>
+
+<a href="#details" id="previewheaderstoggle"><span class="iconlink"></span><span id="headerstoggleall" class="iconlink allheaders"></span></a>
+
+<div id="contactphoto"><roundcube:object name="contactphoto" /></div>
+
+<table class="headers-table" id="preview-shortheaders"><tbody><tr>
+<roundcube:if condition="env:mailbox == config:drafts_mbox || env:mailbox == config:sent_mbox">
+	<td class="header-title"><roundcube:label name="to" /></td>
+	<td class="header from"><roundcube:object name="messageHeaders" valueOf="to" addicon="/images/addcontact.png" /></td>
+<roundcube:else />
+	<td class="header-title"><roundcube:label name="from" /></td>
+	<td class="header from"><roundcube:object name="messageHeaders" valueOf="from" addicon="/images/addcontact.png" /></td>
+<roundcube:endif />
+	<td class="header-title"><roundcube:label name="date" /></td>
+	<td class="header from"><roundcube:object name="messageHeaders" valueOf="date" /></td>
+</tr></tbody></table>
+
+<roundcube:object name="messageHeaders" id="preview-allheaders" class="headers-table" addicon="/images/addcontact.png" exclude="subject,replyto" />
+<roundcube:object name="messageFullHeaders" no-switch="true" />
 
 <!-- record navigation -->
 <div id="countcontrols" class="pagenav">
@@ -46,24 +64,21 @@
 	<roundcube:button command="nextmessage" type="link" class="button nextpage disabled" classAct="button nextpage" classSel="button nextpage pressed" innerClass="inner" title="nextmessage" content="&amp;gt;" />
 </div>
 
-<div id="contactphoto"><roundcube:object name="contactphoto" /></div>
+</div><!-- end messageheader -->
+
+<div id="messagepreview">
+    <div class="rightcol">
+        <roundcube:object name="messageAttachments" id="attachment-list" class="attachmentslist" />
+    </div>
+    <div class="leftcol">
+        <roundcube:object name="messageObjects" id="message-objects" />
+        <roundcube:object name="messageBody" id="messagebody" />
+    </div>
 </div>
 
-<div id="messagecontent" class="uibox">
-<div class="rightcol">
-<roundcube:object name="messageAttachments" id="attachment-list" class="attachmentslist" />
-</div>
-<div class="leftcol">
-<roundcube:object name="messageObjects" id="message-objects" />
-<roundcube:object name="messageBody" id="messagebody" />
-</div>
-</div>
+</div><!-- end messagecontent -->
 
-</div><!-- end mailview-top -->
-
-<div id="mailview-bottom" class="uibox">
 <roundcube:object name="message" id="message" class="statusbar" />
-</div>
 
 </div><!-- end mailview-right -->
 
diff --git a/skins/larry/templates/messageerror.html b/skins/larry/templates/messageerror.html
index 70181f1..2f52432 100644
--- a/skins/larry/templates/messageerror.html
+++ b/skins/larry/templates/messageerror.html
@@ -27,8 +27,6 @@
 
 </div>
 
-<div id="mailview-right">
-
 <!-- toolbar -->
 <div id="messagetoolbar" class="fullwidth">
 	<div id="mailtoolbar" class="toolbar">
@@ -36,11 +34,11 @@
 	</div>
 </div>
 
-<div id="mailview-top" class="uibox watermark"></div>
+<div id="mailview-right" class="uibox" style="top: 42px">
 
-<div id="mailview-bottom" class="uibox">
-	<roundcube:object name="message" id="message" class="statusbar" />
-</div>
+<div id="messagecontent" class="watermark"></div>
+
+<roundcube:object name="message" id="message" class="statusbar" />
 
 </div><!-- end mailview-right -->
 
diff --git a/skins/larry/templates/messagepreview.html b/skins/larry/templates/messagepreview.html
index b53683e..74c414b 100644
--- a/skins/larry/templates/messagepreview.html
+++ b/skins/larry/templates/messagepreview.html
@@ -6,10 +6,10 @@
 </head>
 <body class="iframe fullheight">
 
-<div id="messagepreviewheader">
+<div id="messageheader">
 <h3 class="subject"><roundcube:object name="messageHeaders" valueOf="subject" /></h3>
 
-<a href="#details" id="previewheaderstoggle"><span class="iconlink"></span></a>
+<a href="#details" id="previewheaderstoggle"><span class="iconlink"></span><span id="headerstoggleall" class="iconlink allheaders"></a>
 <div id="contactphoto"><roundcube:object name="contactphoto" /></div>
 
 <table class="headers-table" id="preview-shortheaders"><tbody><tr>
@@ -25,6 +25,7 @@
 </tr></tbody></table>
 
 <roundcube:object name="messageHeaders" id="preview-allheaders" class="headers-table" addicon="/images/addcontact.png" exclude="subject,replyto" />
+<roundcube:object name="messageFullHeaders" no-switch="true" />
 
 <!-- record navigation -->
 <div id="countcontrols" class="pagenav">
diff --git a/skins/larry/ui.js b/skins/larry/ui.js
index b6056b6..e3b5eef 100644
--- a/skins/larry/ui.js
+++ b/skins/larry/ui.js
@@ -74,9 +74,8 @@
 
       if (rcmail.env.action == 'show' || rcmail.env.action == 'preview') {
         layout_messageview();
-        rcmail.addEventListener('aftershow-headers', function() { layout_messageview(); });
-        rcmail.addEventListener('afterhide-headers', function() { layout_messageview(); });
-        $('#previewheaderstoggle').click(function(e){ toggle_preview_headers(this); return false });
+        $('#previewheaderstoggle').click(function(e){ toggle_preview_headers(this); return false; });
+        $('#headerstoggleall').click(function(e){ toggle_all_headers(this); return false; });
       }
       else if (rcmail.env.action == 'compose') {
         rcmail.addEventListener('aftertoggle-editor', function(){ window.setTimeout(function(){ layout_composeview() }, 200); });
@@ -162,6 +161,12 @@
 
         new rcube_scroller('#directorylist-content', '#directorylist-header', '#directorylist-footer');
       }
+    }
+
+    // set min-width to show all toolbar buttons
+    var screen = $('.minwidth');
+    if (screen.length) {
+      screen.css('min-width', $('.toolbar').width() + $('#quicksearchbar').parent().width() + 20);
     }
 
     // turn a group of fieldsets into tabs
@@ -254,11 +259,11 @@
    */
   function resize()
   {
-    if (rcmail.env.task == 'mail' && (rcmail.env.action == 'show' || rcmail.env.action == 'preview')) {
-      layout_messageview();
-    }
-    if (rcmail.env.task == 'mail' && rcmail.env.action == 'compose') {
-      layout_composeview();
+    if (rcmail.env.task == 'mail') {
+      if (rcmail.env.action == 'show' || rcmail.env.action == 'preview')
+        layout_messageview();
+      else if (rcmail.env.action == 'compose')
+        layout_composeview();
     }
 
     // make iframe footer buttons float if scrolling is active
@@ -267,13 +272,13 @@
         body = $(document.body),
         floating = footer.hasClass('floating'),
         overflow = body.outerHeight(true) > $(window).height();
+
       if (overflow != floating) {
         var action = overflow ? 'addClass' : 'removeClass';
         footer[action]('floating');
         body[action]('floatingbuttons');
       }
-    })
-
+    });
   }
 
   /**
@@ -315,7 +320,6 @@
    */
   function layout_messageview()
   {
-    $('#messagecontent').css('top', ($('#messageheader').outerHeight() + 10) + 'px');
     $('#message-objects div a').addClass('button');
 
     if (!$('#attachment-list li').length) {
@@ -461,7 +465,7 @@
     var button = $(e.target),
       frame = $('#mailpreviewframe'),
       visible = !frame.is(':visible'),
-      splitter = mailviewsplit.pos || parseInt(bw.get_cookie('mailviewsplitter') || 320),
+      splitter = mailviewsplit.pos || parseInt(rcmail.get_cookie('mailviewsplitter') || 320),
       topstyles, bottomstyles, uid;
 
     frame.toggle();
@@ -508,13 +512,31 @@
   {
     $('#preview-shortheaders').toggle();
     var full = $('#preview-allheaders').toggle(),
-      button = $('a#previewheaderstoggle');
+      button = $('#previewheaderstoggle');
+
+    if (!$('#headerstoggleall').length)
+      $('#all-headers').toggle();
 
     // add toggle button to full headers table
-    if (full.is(':visible'))
-      button.attr('href', '#hide').removeClass('add').addClass('remove')
-    else
-      button.attr('href', '#details').removeClass('remove').addClass('add')
+    if (full.is(':visible')) {
+      button.attr('href', '#hide').removeClass('add').addClass('remove');
+    }
+    else {
+      button.attr('href', '#details').removeClass('remove').addClass('add');
+    }
+  }
+
+
+  /**
+   * Show/hide all message headers
+   */
+  function toggle_all_headers(button)
+  {
+    rcmail.command('show-headers', '', button);
+    $(button).remove();
+    $('#previewheaderstoggle span').css({bottom: '5px'});
+
+    return false;
   }
 
 
@@ -847,6 +869,8 @@
       // Select/unselect tab
       $('#tab'+idx).toggleClass('selected', idx==index);
     });
+
+    resize();
   }
 
   /**
@@ -974,7 +998,7 @@
       $(window).resize(onResize);
 
     // read saved position from cookie
-    var cookie = bw.get_cookie(this.id);
+    var cookie = rcmail.get_cookie(this.id);
     if (cookie && !isNaN(cookie)) {
       this.pos = parseFloat(cookie);
       this.resize();
@@ -1135,7 +1159,7 @@
   {
     var exp = new Date();
     exp.setYear(exp.getFullYear() + 1);
-    bw.set_cookie(this.id, this.pos, exp);
+    rcmail.set_cookie(this.id, this.pos, exp);
   };
 
 } // end class rcube_splitter
diff --git a/tests/Framework/BaseReplacer.php b/tests/Framework/BaseReplacer.php
new file mode 100644
index 0000000..e00b9e5
--- /dev/null
+++ b/tests/Framework/BaseReplacer.php
@@ -0,0 +1,20 @@
+<?php
+
+/**
+ * Test class to test rcube_base_replacer class
+ *
+ * @package Tests
+ */
+class Framework_BaseReplacer extends PHPUnit_Framework_TestCase
+{
+
+    /**
+     * Class constructor
+     */
+    function test_class()
+    {
+        $object = new rcube_base_replacer('test');
+
+        $this->assertInstanceOf('rcube_base_replacer', $object, "Class constructor");
+    }
+}
diff --git a/tests/Framework/Browser.php b/tests/Framework/Browser.php
new file mode 100644
index 0000000..c3860d8
--- /dev/null
+++ b/tests/Framework/Browser.php
@@ -0,0 +1,20 @@
+<?php
+
+/**
+ * Test class to test rcube_browser class
+ *
+ * @package Tests
+ */
+class Framework_Browser extends PHPUnit_Framework_TestCase
+{
+
+    /**
+     * Class constructor
+     */
+    function test_class()
+    {
+        $object = new rcube_browser();
+
+        $this->assertInstanceOf('rcube_browser', $object, "Class constructor");
+    }
+}
diff --git a/tests/Framework/Cache.php b/tests/Framework/Cache.php
new file mode 100644
index 0000000..dc026a6
--- /dev/null
+++ b/tests/Framework/Cache.php
@@ -0,0 +1,20 @@
+<?php
+
+/**
+ * Test class to test rcube_cache class
+ *
+ * @package Tests
+ */
+class Framework_Cache extends PHPUnit_Framework_TestCase
+{
+
+    /**
+     * Class constructor
+     */
+    function test_class()
+    {
+        $object = new rcube_cache('db', 1);
+
+        $this->assertInstanceOf('rcube_cache', $object, "Class constructor");
+    }
+}
diff --git a/tests/Framework/Charset.php b/tests/Framework/Charset.php
new file mode 100644
index 0000000..9e3fad4
--- /dev/null
+++ b/tests/Framework/Charset.php
@@ -0,0 +1,28 @@
+<?php
+
+/**
+ * Test class to test rcube_charset class
+ *
+ * @package Tests
+ */
+class Framework_Charset extends PHPUnit_Framework_TestCase
+{
+
+    /**
+     * Data for test_clean()
+     */
+    function data_clean()
+    {
+        return array(
+            array('', '', 'Empty string'),
+        );
+    }
+
+    /**
+     * @dataProvider data_clean
+     */
+    function test_clean($input, $output, $title)
+    {
+        $this->assertEquals(rcube_charset::clean($input), $output, $title);
+    }
+}
diff --git a/tests/Framework/ContentFilter.php b/tests/Framework/ContentFilter.php
new file mode 100644
index 0000000..9bee936
--- /dev/null
+++ b/tests/Framework/ContentFilter.php
@@ -0,0 +1,20 @@
+<?php
+
+/**
+ * Test class to test rcube_content_filter class
+ *
+ * @package Tests
+ */
+class Framework_ContentFilter extends PHPUnit_Framework_TestCase
+{
+
+    /**
+     * Class constructor
+     */
+    function test_class()
+    {
+        $object = new rcube_content_filter();
+
+        $this->assertInstanceOf('rcube_content_filter', $object, "Class constructor");
+    }
+}
diff --git a/tests/Framework/Html.php b/tests/Framework/Html.php
new file mode 100644
index 0000000..8a27bac
--- /dev/null
+++ b/tests/Framework/Html.php
@@ -0,0 +1,46 @@
+<?php
+
+/**
+ * Test class to test rcube_html class
+ *
+ * @package Tests
+ */
+class Framework_Html extends PHPUnit_Framework_TestCase
+{
+
+    /**
+     * Class constructor
+     */
+    function test_class()
+    {
+        $object = new html;
+
+        $this->assertInstanceOf('html', $object, "Class constructor");
+    }
+
+    /**
+     * Data for test_quote()
+     */
+    function data_quote()
+    {
+        return array(
+            array('abc', 'abc'),
+            array('?', '?'),
+            array('"', '&quot;'),
+            array('<', '&lt;'),
+            array('>', '&gt;'),
+            array('&', '&amp;'),
+            array('&amp;', '&amp;amp;'),
+            array('&amp;', '&amp;', true),
+        );
+    }
+
+    /**
+     * Test for quote()
+     * @dataProvider data_quote
+     */
+    function test_quote($str, $result, $validate = false)
+    {
+        $this->assertEquals(html::quote($str, $validate), $result);
+    }
+}
diff --git a/tests/Framework/Image.php b/tests/Framework/Image.php
new file mode 100644
index 0000000..31e8520
--- /dev/null
+++ b/tests/Framework/Image.php
@@ -0,0 +1,20 @@
+<?php
+
+/**
+ * Test class to test rcube_image class
+ *
+ * @package Tests
+ */
+class Framework_Image extends PHPUnit_Framework_TestCase
+{
+
+    /**
+     * Class constructor
+     */
+    function test_class()
+    {
+        $object = new rcube_image('test');
+
+        $this->assertInstanceOf('rcube_image', $object, "Class constructor");
+    }
+}
diff --git a/tests/Framework/Imap.php b/tests/Framework/Imap.php
new file mode 100644
index 0000000..3f52e07
--- /dev/null
+++ b/tests/Framework/Imap.php
@@ -0,0 +1,20 @@
+<?php
+
+/**
+ * Test class to test rcube_imap class
+ *
+ * @package Tests
+ */
+class Framework_Imap extends PHPUnit_Framework_TestCase
+{
+
+    /**
+     * Class constructor
+     */
+    function test_class()
+    {
+        $object = new rcube_imap;
+
+        $this->assertInstanceOf('rcube_imap', $object, "Class constructor");
+    }
+}
diff --git a/tests/Framework/ImapGeneric.php b/tests/Framework/ImapGeneric.php
new file mode 100644
index 0000000..0b2cc3d
--- /dev/null
+++ b/tests/Framework/ImapGeneric.php
@@ -0,0 +1,20 @@
+<?php
+
+/**
+ * Test class to test rcube_imap_generic class
+ *
+ * @package Tests
+ */
+class Framework_ImapGeneric extends PHPUnit_Framework_TestCase
+{
+
+    /**
+     * Class constructor
+     */
+    function test_class()
+    {
+        $object = new rcube_imap_generic;
+
+        $this->assertInstanceOf('rcube_imap_generic', $object, "Class constructor");
+    }
+}
diff --git a/tests/Framework/MessageHeader.php b/tests/Framework/MessageHeader.php
new file mode 100644
index 0000000..e5bc175
--- /dev/null
+++ b/tests/Framework/MessageHeader.php
@@ -0,0 +1,20 @@
+<?php
+
+/**
+ * Test class to test rcube_message_header class
+ *
+ * @package Tests
+ */
+class Framework_MessageHeader extends PHPUnit_Framework_TestCase
+{
+
+    /**
+     * Class constructor
+     */
+    function test_class()
+    {
+        $object = new rcube_message_header;
+
+        $this->assertInstanceOf('rcube_message_header', $object, "Class constructor");
+    }
+}
diff --git a/tests/Framework/MessagePart.php b/tests/Framework/MessagePart.php
new file mode 100644
index 0000000..deb4260
--- /dev/null
+++ b/tests/Framework/MessagePart.php
@@ -0,0 +1,20 @@
+<?php
+
+/**
+ * Test class to test rcube_message_part class
+ *
+ * @package Tests
+ */
+class Framework_MessagePart extends PHPUnit_Framework_TestCase
+{
+
+    /**
+     * Class constructor
+     */
+    function test_class()
+    {
+        $object = new rcube_message_part;
+
+        $this->assertInstanceOf('rcube_message_part', $object, "Class constructor");
+    }
+}
diff --git a/tests/Framework/Mime.php b/tests/Framework/Mime.php
new file mode 100644
index 0000000..dcd5599
--- /dev/null
+++ b/tests/Framework/Mime.php
@@ -0,0 +1,123 @@
+<?php
+
+/**
+ * Test class to test rcube_mime class
+ *
+ * @package Tests
+ */
+class Framework_Mime extends PHPUnit_Framework_TestCase
+{
+
+    /**
+     * Test decoding of single e-mail address strings
+     * Uses rcube_mime::decode_address_list()
+     */
+    function test_decode_single_address()
+    {
+        $headers = array(
+            0  => 'test@domain.tld',
+            1  => '<test@domain.tld>',
+            2  => 'Test <test@domain.tld>',
+            3  => 'Test Test <test@domain.tld>',
+            4  => 'Test Test<test@domain.tld>',
+            5  => '"Test Test" <test@domain.tld>',
+            6  => '"Test Test"<test@domain.tld>',
+            7  => '"Test \\" Test" <test@domain.tld>',
+            8  => '"Test<Test" <test@domain.tld>',
+            9  => '=?ISO-8859-1?B?VGVzdAo=?= <test@domain.tld>',
+            10 => '=?ISO-8859-1?B?VGVzdAo=?=<test@domain.tld>', // #1487068
+            // comments in address (#1487673)
+            11 => 'Test (comment) <test@domain.tld>',
+            12 => '"Test" (comment) <test@domain.tld>',
+            13 => '"Test (comment)" (comment) <test@domain.tld>',
+            14 => '(comment) <test@domain.tld>',
+            15 => 'Test <test@(comment)domain.tld>',
+            16 => 'Test Test ((comment)) <test@domain.tld>',
+            17 => 'test@domain.tld (comment)',
+            18 => '"Test,Test" <test@domain.tld>',
+            // 1487939
+            19 => 'Test <"test test"@domain.tld>',
+            20 => '<"test test"@domain.tld>',
+            21 => '"test test"@domain.tld',
+        );
+
+        $results = array(
+            0  => array(1, '', 'test@domain.tld'),
+            1  => array(1, '', 'test@domain.tld'),
+            2  => array(1, 'Test', 'test@domain.tld'),
+            3  => array(1, 'Test Test', 'test@domain.tld'),
+            4  => array(1, 'Test Test', 'test@domain.tld'),
+            5  => array(1, 'Test Test', 'test@domain.tld'),
+            6  => array(1, 'Test Test', 'test@domain.tld'),
+            7  => array(1, 'Test " Test', 'test@domain.tld'),
+            8  => array(1, 'Test<Test', 'test@domain.tld'),
+            9  => array(1, 'Test', 'test@domain.tld'),
+            10 => array(1, 'Test', 'test@domain.tld'),
+            11 => array(1, 'Test', 'test@domain.tld'),
+            12 => array(1, 'Test', 'test@domain.tld'),
+            13 => array(1, 'Test (comment)', 'test@domain.tld'),
+            14 => array(1, '', 'test@domain.tld'),
+            15 => array(1, 'Test', 'test@domain.tld'),
+            16 => array(1, 'Test Test', 'test@domain.tld'),
+            17 => array(1, '', 'test@domain.tld'),
+            18 => array(1, 'Test,Test', 'test@domain.tld'),
+            19 => array(1, 'Test', '"test test"@domain.tld'),
+            20 => array(1, '', '"test test"@domain.tld'),
+            21 => array(1, '', '"test test"@domain.tld'),
+        );
+
+        foreach ($headers as $idx => $header) {
+            $res = rcube_mime::decode_address_list($header);
+
+            $this->assertEquals($results[$idx][0], count($res), "Rows number in result for header: " . $header);
+            $this->assertEquals($results[$idx][1], $res[1]['name'], "Name part decoding for header: " . $header);
+            $this->assertEquals($results[$idx][2], $res[1]['mailto'], "Email part decoding for header: " . $header);
+        }
+    }
+
+    /**
+     * Test decoding of header values
+     * Uses rcube_mime::decode_mime_string()
+     */
+    function test_header_decode_qp()
+    {
+        $test = array(
+            // #1488232: invalid character "?"
+            'quoted-printable (1)' => array(
+                'in'  => '=?utf-8?Q?Certifica=C3=A7=C3=A3??=',
+                'out' => 'Certifica=C3=A7=C3=A3?',
+            ),
+            'quoted-printable (2)' => array(
+                'in'  => '=?utf-8?Q?Certifica=?= =?utf-8?Q?C3=A7=C3=A3?=',
+                'out' => 'Certifica=C3=A7=C3=A3',
+            ),
+            'quoted-printable (3)' => array(
+                'in'  => '=?utf-8?Q??= =?utf-8?Q??=',
+                'out' => '',
+            ),
+            'quoted-printable (4)' => array(
+                'in'  => '=?utf-8?Q??= a =?utf-8?Q??=',
+                'out' => ' a ',
+            ),
+            'quoted-printable (5)' => array(
+                'in'  => '=?utf-8?Q?a?= =?utf-8?Q?b?=',
+                'out' => 'ab',
+            ),
+            'quoted-printable (6)' => array(
+                'in'  => '=?utf-8?Q?   ?= =?utf-8?Q?a?=',
+                'out' => '   a',
+            ),
+            'quoted-printable (7)' => array(
+                'in'  => '=?utf-8?Q?___?= =?utf-8?Q?a?=',
+                'out' => '   a',
+            ),
+        );
+
+        foreach ($test as $idx => $item) {
+            $res = rcube_mime::decode_mime_string($item['in'], 'UTF-8');
+            $res = quoted_printable_encode($res);
+
+            $this->assertEquals($item['out'], $res, "Header decoding for: " . $idx);
+        }
+    }
+}
diff --git a/tests/Framework/Rcube.php b/tests/Framework/Rcube.php
new file mode 100644
index 0000000..637558d
--- /dev/null
+++ b/tests/Framework/Rcube.php
@@ -0,0 +1,20 @@
+<?php
+
+/**
+ * Test class to test rcube class
+ *
+ * @package Tests
+ */
+class Framework_Rcube extends PHPUnit_Framework_TestCase
+{
+
+    /**
+     * Class constructor
+     */
+    function test_class()
+    {
+        $object = rcube::get_instance();
+
+        $this->assertInstanceOf('rcube', $object, "Class singleton");
+    }
+}
diff --git a/tests/Framework/ResultIndex.php b/tests/Framework/ResultIndex.php
new file mode 100644
index 0000000..efbba6d
--- /dev/null
+++ b/tests/Framework/ResultIndex.php
@@ -0,0 +1,20 @@
+<?php
+
+/**
+ * Test class to test rcube_result_index class
+ *
+ * @package Tests
+ */
+class Framework_ResultIndex extends PHPUnit_Framework_TestCase
+{
+
+    /**
+     * Class constructor
+     */
+    function test_class()
+    {
+        $object = new rcube_result_index;
+
+        $this->assertInstanceOf('rcube_result_index', $object, "Class constructor");
+    }
+}
diff --git a/tests/Framework/ResultSet.php b/tests/Framework/ResultSet.php
new file mode 100644
index 0000000..2d04e53
--- /dev/null
+++ b/tests/Framework/ResultSet.php
@@ -0,0 +1,20 @@
+<?php
+
+/**
+ * Test class to test rcube_result_set class
+ *
+ * @package Tests
+ */
+class Framework_ResultSet extends PHPUnit_Framework_TestCase
+{
+
+    /**
+     * Class constructor
+     */
+    function test_class()
+    {
+        $object = new rcube_result_set;
+
+        $this->assertInstanceOf('rcube_result_set', $object, "Class constructor");
+    }
+}
diff --git a/tests/Framework/ResultThread.php b/tests/Framework/ResultThread.php
new file mode 100644
index 0000000..f980845
--- /dev/null
+++ b/tests/Framework/ResultThread.php
@@ -0,0 +1,20 @@
+<?php
+
+/**
+ * Test class to test rcube_result_thread class
+ *
+ * @package Tests
+ */
+class Framework_ResultThread extends PHPUnit_Framework_TestCase
+{
+
+    /**
+     * Class constructor
+     */
+    function test_class()
+    {
+        $object = new rcube_result_thread;
+
+        $this->assertInstanceOf('rcube_result_thread', $object, "Class constructor");
+    }
+}
diff --git a/tests/Framework/Shared.php b/tests/Framework/Shared.php
new file mode 100644
index 0000000..99ef829
--- /dev/null
+++ b/tests/Framework/Shared.php
@@ -0,0 +1,204 @@
+<?php
+
+/**
+ * Test class to test rcube_shared functions
+ *
+ * @package Tests
+ */
+class Framework_Shared extends PHPUnit_Framework_TestCase
+{
+
+    /**
+     * rcube_shared.inc: in_array_nocase()
+     */
+    function test_in_array_nocase()
+    {
+        $haystack = array('Test');
+        $needle = 'test';
+        $result = in_array_nocase($needle, $haystack);
+
+        $this->assertTrue($result, $title);
+
+        $result = in_array_nocase($needle, null);
+
+        $this->assertFalse($result, $title);
+    }
+
+    /**
+     * rcube_shared.inc: get_boolean()
+     */
+    function test_get_boolean()
+    {
+        $input = array(
+            false, 'false', '0', 'no', 'off', 'nein', 'FALSE', '', null,
+        );
+
+        foreach ($input as $idx => $value) {
+            $this->assertFalse(get_boolean($value), "Invalid result for $idx test item");
+        }
+
+        $input = array(
+            true, 'true', '1', 1, 'yes', 'anything', 1000,
+        );
+
+        foreach ($input as $idx => $value) {
+            $this->assertTrue(get_boolean($value), "Invalid result for $idx test item");
+        }
+    }
+
+    /**
+     * rcube_shared.inc: parse_bytes()
+     */
+    function test_parse_bytes()
+    {
+        $data = array(
+            '1'      => 1,
+            '1024'   => 1024,
+            '2k'     => 2 * 1024,
+            '2 k'     => 2 * 1024,
+            '2kb'    => 2 * 1024,
+            '2kB'    => 2 * 1024,
+            '2m'     => 2 * 1048576,
+            '2 m'     => 2 * 1048576,
+            '2mb'    => 2 * 1048576,
+            '2mB'    => 2 * 1048576,
+            '2g'     => 2 * 1024 * 1048576,
+            '2 g'     => 2 * 1024 * 1048576,
+            '2gb'    => 2 * 1024 * 1048576,
+            '2gB'    => 2 * 1024 * 1048576,
+        );
+
+        foreach ($data as $value => $expected) {
+            $result = parse_bytes($value);
+            $this->assertEquals($expected, $result, "Invalid parse_bytes() result for $value");
+        }
+    }
+
+    /**
+     * rcube_shared.inc: slashify()
+     */
+    function test_slashify()
+    {
+        $data = array(
+            'test'    => 'test/',
+            'test/'   => 'test/',
+            ''        => '/',
+            "\\"      => "\\/",
+        );
+
+        foreach ($data as $value => $expected) {
+            $result = slashify($value);
+            $this->assertEquals($expected, $result, "Invalid slashify() result for $value");
+        }
+
+    }
+
+    /**
+     * rcube_shared.inc: unslashify()
+     */
+    function test_unslashify()
+    {
+        $data = array(
+            'test'      => 'test',
+            'test/'     => 'test',
+            '/'         => '',
+            "\\/"       => "\\",
+            'test/test' => 'test/test',
+            'test//'    => 'test',
+        );
+
+        foreach ($data as $value => $expected) {
+            $result = unslashify($value);
+            $this->assertEquals($expected, $result, "Invalid unslashify() result for $value");
+        }
+
+    }
+
+    /**
+     * rcube_shared.inc: get_offset_sec()
+     */
+    function test_get_offset_sec()
+    {
+        $data = array(
+            '1s'    => 1,
+            '1m'    => 1 * 60,
+            '1h'    => 1 * 60 * 60,
+            '1d'    => 1 * 60 * 60 * 24,
+            '1w'    => 1 * 60 * 60 * 24 * 7,
+            '1y'    => (int) '1y',
+            100     => 100,
+            '100'   => 100,
+        );
+
+        foreach ($data as $value => $expected) {
+            $result = get_offset_sec($value);
+            $this->assertEquals($expected, $result, "Invalid get_offset_sec() result for $value");
+        }
+
+    }
+
+    /**
+     * rcube_shared.inc: array_keys_recursive()
+     */
+    function test_array_keys_recursive()
+    {
+        $input = array(
+            'one' => array(
+                'two' => array(
+                    'three' => array(),
+                    'four' => 'something',
+                ),
+            ),
+            'five' => 'test',
+        );
+
+        $result     = array_keys_recursive($input);
+        $input_str  = 'one,two,three,four,five';
+        $result_str = implode(',', $result);
+
+        $this->assertEquals($input_str, $result_str, "Invalid array_keys_recursive() result");
+    }
+
+    /**
+     * rcube_shared.inc: format_email()
+     */
+    function test_format_email()
+    {
+        $data = array(
+            ''                 => '',
+            'test'             => 'test',
+            'test@test.tld'    => 'test@test.tld',
+            'test@[127.0.0.1]' => 'test@[127.0.0.1]',
+            'TEST@TEST.TLD'    => 'TEST@test.tld',
+        );
+
+        foreach ($data as $value => $expected) {
+            $result = format_email($value);
+            $this->assertEquals($expected, $result, "Invalid format_email() result for $value");
+        }
+
+    }
+
+    /**
+     * rcube_shared.inc: format_email_recipient()
+     */
+    function test_format_email_recipient()
+    {
+        $data = array(
+            ''                          => array(''),
+            'test'                      => array('test'),
+            'test@test.tld'             => array('test@test.tld'),
+            'test@[127.0.0.1]'          => array('test@[127.0.0.1]'),
+            'TEST@TEST.TLD'             => array('TEST@TEST.TLD'),
+            'TEST <test@test.tld>'      => array('test@test.tld', 'TEST'),
+            '"TEST\"" <test@test.tld>'  => array('test@test.tld', 'TEST"'),
+        );
+
+        foreach ($data as $expected => $value) {
+            $result = format_email_recipient($value[0], $value[1]);
+            $this->assertEquals($expected, $result, "Invalid format_email_recipient()");
+        }
+
+    }
+
+}
diff --git a/tests/Framework/Smtp.php b/tests/Framework/Smtp.php
new file mode 100644
index 0000000..4bd78d0
--- /dev/null
+++ b/tests/Framework/Smtp.php
@@ -0,0 +1,20 @@
+<?php
+
+/**
+ * Test class to test rcube_smtp class
+ *
+ * @package Tests
+ */
+class Framework_Smtp extends PHPUnit_Framework_TestCase
+{
+
+    /**
+     * Class constructor
+     */
+    function test_class()
+    {
+        $object = new rcube_smtp;
+
+        $this->assertInstanceOf('rcube_smtp', $object, "Class constructor");
+    }
+}
diff --git a/tests/Framework/Spellchecker.php b/tests/Framework/Spellchecker.php
new file mode 100644
index 0000000..9c3e92f
--- /dev/null
+++ b/tests/Framework/Spellchecker.php
@@ -0,0 +1,20 @@
+<?php
+
+/**
+ * Test class to test rcube_spellchecker class
+ *
+ * @package Tests
+ */
+class Framework_Spellchecker extends PHPUnit_Framework_TestCase
+{
+
+    /**
+     * Class constructor
+     */
+    function test_class()
+    {
+        $object = new rcube_spellchecker;
+
+        $this->assertInstanceOf('rcube_spellchecker', $object, "Class constructor");
+    }
+}
diff --git a/tests/Framework/StringReplacer.php b/tests/Framework/StringReplacer.php
new file mode 100644
index 0000000..11210c0
--- /dev/null
+++ b/tests/Framework/StringReplacer.php
@@ -0,0 +1,20 @@
+<?php
+
+/**
+ * Test class to test rcube_string_replacer class
+ *
+ * @package Tests
+ */
+class Framework_StringReplacer extends PHPUnit_Framework_TestCase
+{
+
+    /**
+     * Class constructor
+     */
+    function test_class()
+    {
+        $sr = new rcube_string_replacer;
+
+        $this->assertInstanceOf('rcube_string_replacer', $sr, "Class constructor");
+    }
+}
diff --git a/tests/Framework/User.php b/tests/Framework/User.php
new file mode 100644
index 0000000..3b1983c
--- /dev/null
+++ b/tests/Framework/User.php
@@ -0,0 +1,20 @@
+<?php
+
+/**
+ * Test class to test rcube_user class
+ *
+ * @package Tests
+ */
+class Framework_User extends PHPUnit_Framework_TestCase
+{
+
+    /**
+     * Class constructor
+     */
+    function test_class()
+    {
+        $user = new rcube_user;
+
+        $this->assertInstanceOf('rcube_user', $user, "Class constructor");
+    }
+}
diff --git a/tests/Framework/Utils.php b/tests/Framework/Utils.php
new file mode 100644
index 0000000..e588359
--- /dev/null
+++ b/tests/Framework/Utils.php
@@ -0,0 +1,196 @@
+<?php
+
+/**
+ * Test class to test rcube_utils class
+ *
+ * @package Tests
+ */
+class Framework_Utils extends PHPUnit_Framework_TestCase
+{
+
+    /**
+     * Valid email addresses for test_valid_email()
+     */
+    function data_valid_email()
+    {
+        return array(
+            array('email@domain.com', 'Valid email'),
+            array('firstname.lastname@domain.com', 'Email contains dot in the address field'),
+            array('email@subdomain.domain.com', 'Email contains dot with subdomain'),
+            array('firstname+lastname@domain.com', 'Plus sign is considered valid character'),
+            array('email@[123.123.123.123]', 'Square bracket around IP address'),
+            array('email@[IPv6:::1]', 'Square bracket around IPv6 address (1)'),
+            array('email@[IPv6:::1.2.3.4]', 'Square bracket around IPv6 address (2)'),
+            array('email@[IPv6:2001:2d12:c4fe:5afe::1]', 'Square bracket around IPv6 address (3)'),
+            array('"email"@domain.com', 'Quotes around email is considered valid'),
+            array('1234567890@domain.com', 'Digits in address are valid'),
+            array('email@domain-one.com', 'Dash in domain name is valid'),
+            array('_______@domain.com', 'Underscore in the address field is valid'),
+            array('email@domain.name', '.name is valid Top Level Domain name'),
+            array('email@domain.co.jp', 'Dot in Top Level Domain name also considered valid (use co.jp as example here)'),
+            array('firstname-lastname@domain.com', 'Dash in address field is valid'),
+        );
+    }
+
+    /**
+     * Invalid email addresses for test_invalid_email()
+     */
+    function data_invalid_email()
+    {
+        return array(
+            array('plainaddress', 'Missing @ sign and domain'),
+            array('#@%^%#$@#$@#.com', 'Garbage'),
+            array('@domain.com', 'Missing username'),
+            array('Joe Smith <email@domain.com>', 'Encoded html within email is invalid'),
+            array('email.domain.com', 'Missing @'),
+            array('email@domain@domain.com', 'Two @ sign'),
+            array('.email@domain.com', 'Leading dot in address is not allowed'),
+            array('email.@domain.com', 'Trailing dot in address is not allowed'),
+            array('email..email@domain.com', 'Multiple dots'),
+            array('あいうえお@domain.com', 'Unicode char as address'),
+            array('email@domain.com (Joe Smith)', 'Text followed email is not allowed'),
+            array('email@domain', 'Missing top level domain (.com/.net/.org/etc)'),
+            array('email@-domain.com', 'Leading dash in front of domain is invalid'),
+//            array('email@domain.web', '.web is not a valid top level domain'),
+            array('email@123.123.123.123', 'IP address without brackets'),
+            array('email@2001:2d12:c4fe:5afe::1', 'IPv6 address without brackets'),
+            array('email@IPv6:2001:2d12:c4fe:5afe::1', 'IPv6 address without brackets (2)'),
+            array('email@[111.222.333.44444]', 'Invalid IP format'),
+            array('email@[111.222.255.257]', 'Invalid IP format (2)'),
+            array('email@[.222.255.257]', 'Invalid IP format (3)'),
+            array('email@[::1]', 'Invalid IPv6 format (1)'),
+            array('email@[IPv6:2001:23x2:1]', 'Invalid IPv6 format (2)'),
+            array('email@[IPv6:1111:2222:33333::4444:5555]', 'Invalid IPv6 format (3)'),
+            array('email@[IPv6:1111::3333::4444:5555]', 'Invalid IPv6 format (4)'),
+            array('email@domain..com', 'Multiple dot in the domain portion is invalid'),
+        );
+    }
+
+    /**
+     * @dataProvider data_valid_email
+     */
+    function test_valid_email($email, $title)
+    {
+        $this->assertTrue(rcube_utils::check_email($email, false), $title);
+    }
+
+    /**
+     * @dataProvider data_invalid_email
+     */
+    function test_invalid_email($email, $title)
+    {
+        $this->assertFalse(rcube_utils::check_email($email, false), $title);
+    }
+
+    /**
+     * Valid IP addresses for test_valid_ip()
+     */
+    function data_valid_ip()
+    {
+        return array(
+            array('0.0.0.0'),
+            array('123.123.123.123'),
+            array('::'),
+            array('::1'),
+            array('::1.2.3.4'),
+            array('2001:2d12:c4fe:5afe::1'),
+        );
+    }
+
+    /**
+     * Valid IP addresses for test_invalid_ip()
+     */
+    function data_invalid_ip()
+    {
+        return array(
+            array(''),
+            array(0),
+            array('123.123.123.1234'),
+            array('1.1.1.1.1'),
+            array('::1.2.3.260'),
+            array('::1.0'),
+            array('2001::c4fe:5afe::1'),
+        );
+    }
+
+    /**
+     * @dataProvider data_valid_ip
+     */
+    function test_valid_ip($ip)
+    {
+        $this->assertTrue(rcube_utils::check_ip($ip));
+    }
+
+    /**
+     * @dataProvider data_invalid_ip
+     */
+    function test_invalid_ip($ip)
+    {
+        $this->assertFalse(rcube_utils::check_ip($ip));
+    }
+
+    /**
+     * Data for test_rep_specialchars_output()
+     */
+    function data_rep_specialchars_output()
+    {
+        return array(
+            array('', '', 'abc', 'abc'),
+            array('', '', '?', '?'),
+            array('', '', '"', '&quot;'),
+            array('', '', '<', '&lt;'),
+            array('', '', '>', '&gt;'),
+            array('', '', '&', '&amp;'),
+            array('', '', '&amp;', '&amp;amp;'),
+            array('', '', '<a>', '&lt;a&gt;'),
+            array('', 'remove', '<a>', ''),
+        );
+    }
+
+    /**
+     * Test for rep_specialchars_output
+     * @dataProvider data_rep_specialchars_output
+     */
+    function test_rep_specialchars_output($type, $mode, $str, $res)
+    {
+        $result = rcube_utils::rep_specialchars_output(
+            $str, $type ? $type : 'html', $mode ? $mode : 'strict');
+
+        $this->assertEquals($result, $res);
+    }
+
+    /**
+     * rcube_utils::mod_css_styles()
+     */
+    function test_mod_css_styles()
+    {
+        $css = file_get_contents(TESTS_DIR . 'src/valid.css');
+        $mod = rcube_utils::mod_css_styles($css, 'rcmbody');
+
+        $this->assertRegExp('/#rcmbody\s+\{/', $mod, "Replace body style definition");
+        $this->assertRegExp('/#rcmbody h1\s\{/', $mod, "Prefix tag styles (single)");
+        $this->assertRegExp('/#rcmbody h1, #rcmbody h2, #rcmbody h3, #rcmbody textarea\s+\{/', $mod, "Prefix tag styles (multiple)");
+        $this->assertRegExp('/#rcmbody \.noscript\s+\{/', $mod, "Prefix class styles");
+    }
+
+    /**
+     * rcube_utils::mod_css_styles()
+     */
+    function test_mod_css_styles_xss()
+    {
+        $mod = rcube_utils::mod_css_styles("body.main2cols { background-image: url('../images/leftcol.png'); }", 'rcmbody');
+        $this->assertEquals("/* evil! */", $mod, "No url() values allowed");
+
+        $mod = rcube_utils::mod_css_styles("@import url('http://localhost/somestuff/css/master.css');", 'rcmbody');
+        $this->assertEquals("/* evil! */", $mod, "No import statements");
+
+        $mod = rcube_utils::mod_css_styles("left:expression(document.body.offsetWidth-20)", 'rcmbody');
+        $this->assertEquals("/* evil! */", $mod, "No expression properties");
+
+        $mod = rcube_utils::mod_css_styles("left:exp/*  */ression( alert(&#039;xss3&#039;) )", 'rcmbody');
+        $this->assertEquals("/* evil! */", $mod, "Don't allow encoding quirks");
+
+        $mod = rcube_utils::mod_css_styles("background:\\0075\\0072\\006c( javascript:alert(&#039;xss&#039;) )", 'rcmbody');
+        $this->assertEquals("/* evil! */", $mod, "Don't allow encoding quirks (2)");
+    }
+}
diff --git a/tests/Framework/VCard.php b/tests/Framework/VCard.php
new file mode 100644
index 0000000..56ca9d7
--- /dev/null
+++ b/tests/Framework/VCard.php
@@ -0,0 +1,73 @@
+<?php
+
+/**
+ * Unit tests for class rcube_vcard
+ *
+ * @package Tests
+ */
+class Framework_VCard extends PHPUnit_Framework_TestCase
+{
+
+    function _srcpath($fn)
+    {
+        return realpath(dirname(__FILE__) . '/../src/' . $fn);
+    }
+
+    function test_parse_one()
+    {
+        $vcard = new rcube_vcard(file_get_contents($this->_srcpath('apple.vcf')));
+
+        $this->assertTrue($vcard->business, "Identify as business record");
+        $this->assertEquals("Apple Computer AG", $vcard->displayname, "FN => displayname");
+        $this->assertEquals("", $vcard->firstname, "No person name set");
+    }
+
+    function test_parse_two()
+    {
+        $vcard = new rcube_vcard(file_get_contents($this->_srcpath('johndoe.vcf')), null);
+
+        $this->assertFalse($vcard->business, "Identify as private record");
+        $this->assertEquals("John Doë", $vcard->displayname, "Decode according to charset attribute");
+        $this->assertEquals("roundcube.net", $vcard->organization, "Test organization field");
+        $this->assertCount(2, $vcard->email, "List two e-mail addresses");
+        $this->assertEquals("roundcube@gmail.com", $vcard->email[0], "Use PREF e-mail as primary");
+    }
+
+    function test_import()
+    {
+        $input = file_get_contents($this->_srcpath('apple.vcf'));
+        $input .= file_get_contents($this->_srcpath('johndoe.vcf'));
+
+        $vcards = rcube_vcard::import($input);
+
+        $this->assertCount(2, $vcards, "Detected 2 vcards");
+        $this->assertEquals("Apple Computer AG", $vcards[0]->displayname, "FN => displayname");
+        $this->assertEquals("John Doë", $vcards[1]->displayname, "Displayname with correct charset");
+
+        // http://trac.roundcube.net/ticket/1485542
+        $vcards2 = rcube_vcard::import(file_get_contents($this->_srcpath('thebat.vcf')));
+        $this->assertEquals("Iksiñski", $vcards2[0]->surname, "Detect charset in encoded values");
+    }
+
+    function test_import_photo_encoding()
+    {
+        $input = file_get_contents($this->_srcpath('photo.vcf'));
+
+        $vcards = rcube_vcard::import($input);
+        $vcard = $vcards[0]->get_assoc();
+
+        $this->assertCount(1, $vcards, "Detected 1 vcard");
+
+        // ENCODING=b case (#1488683)
+        $this->assertEquals("/9j/4AAQSkZJRgABAQA", substr(base64_encode($vcard['photo']), 0, 19), "Photo decoding");
+        $this->assertEquals("Müller", $vcard['surname'], "Unicode characters");
+    }
+
+    function test_encodings()
+    {
+        $input = file_get_contents($this->_srcpath('utf-16_sample.vcf'));
+
+        $vcards = rcube_vcard::import($input);
+        $this->assertEquals("Ǽgean ĽdaMonté", $vcards[0]->displayname, "Decoded from UTF-16");
+    }
+}
diff --git a/tests/html_to_text.php b/tests/HtmlToText.php
similarity index 75%
rename from tests/html_to_text.php
rename to tests/HtmlToText.php
index aabc1a8..b90c61a 100644
--- a/tests/html_to_text.php
+++ b/tests/HtmlToText.php
@@ -5,18 +5,12 @@
  *
  * @package Tests
  */
-class rcube_test_html2text extends UnitTestCase
+class HtmlToText extends PHPUnit_Framework_TestCase
 {
 
-    function __construct()
+    function data_html2text()
     {
-        $this->UnitTestCase("HTML-to-Text conversion tests");
-
-    }
-
-    function test_html2text()
-    {
-        $data = array(
+        return array(
             0 => array(
                 'title' => 'Test entry',
                 'in'    => '',
@@ -48,14 +42,18 @@
                 'out'   => 'Ś',
             ),
         );
-
-        $ht = new html2text(null, false, false);
-
-        foreach ($data as $idx => $item) {
-            $ht->set_html($item['in']);
-            $res = $ht->get_text();
-            $this->assertEqual($item['out'], $res, $item['title'] . "($idx)");
-        }
     }
 
+    /**
+     * @dataProvider data_html2text
+     */
+    function test_html2text($title, $in, $out)
+    {
+        $ht = new html2text(null, false, false);
+
+        $ht->set_html($in);
+        $res = $ht->get_text();
+
+        $this->assertEquals($out, $res, $title);
+    }
 }
diff --git a/tests/MailFunc.php b/tests/MailFunc.php
new file mode 100644
index 0000000..967277c
--- /dev/null
+++ b/tests/MailFunc.php
@@ -0,0 +1,172 @@
+<?php
+
+/**
+ * Test class to test steps/mail/func.inc functions
+ *
+ * @package Tests
+ */
+class MailFunc extends PHPUnit_Framework_TestCase
+{
+
+    function setUp()
+    {
+        // simulate environment to successfully include func.inc
+        $GLOBALS['RCMAIL'] = $RCMAIL = rcmail::get_instance();
+        $GLOBALS['OUTPUT'] = $OUTPUT = $RCMAIL->load_gui();
+        $RCMAIL->action = 'autocomplete';
+        $RCMAIL->storage_init(false);
+
+        require_once INSTALL_PATH . 'program/steps/mail/func.inc';
+
+        $GLOBALS['EMAIL_ADDRESS_PATTERN'] = $EMAIL_ADDRESS_PATTERN;
+    }
+
+    /**
+     * Helper method to create a HTML message part object
+     */
+    function get_html_part($body)
+    {
+        $part = new rcube_message_part;
+        $part->ctype_primary = 'text';
+        $part->ctype_secondary = 'html';
+        $part->body = file_get_contents(TESTS_DIR . $body);
+        $part->replaces = array();
+        return $part;
+    }
+
+
+    /**
+     * Test sanitization of a "normal" html message
+     */
+    function test_html()
+    {
+        $part = $this->get_html_part('src/htmlbody.txt');
+        $part->replaces = array('ex1.jpg' => 'part_1.2.jpg', 'ex2.jpg' => 'part_1.2.jpg');
+
+        // render HTML in normal mode
+        $html = rcmail_html4inline(rcmail_print_body($part, array('safe' => false)), 'foo');
+
+        $this->assertRegExp('/src="'.$part->replaces['ex1.jpg'].'"/', $html, "Replace reference to inline image");
+        $this->assertRegExp('#background="./program/resources/blocked.gif"#', $html, "Replace external background image");
+        $this->assertNotRegExp('/ex3.jpg/', $html, "No references to external images");
+        $this->assertNotRegExp('/<meta [^>]+>/', $html, "No meta tags allowed");
+        //$this->assertNoPattern('/<style [^>]+>/', $html, "No style tags allowed");
+        $this->assertNotRegExp('/<form [^>]+>/', $html, "No form tags allowed");
+        $this->assertRegExp('/Subscription form/', $html, "Include <form> contents");
+        $this->assertRegExp('/<!-- link ignored -->/', $html, "No external links allowed");
+        $this->assertRegExp('/<a[^>]+ target="_blank">/', $html, "Set target to _blank");
+        $this->assertTrue($GLOBALS['REMOTE_OBJECTS'], "Remote object detected");
+
+        // render HTML in safe mode
+        $html2 = rcmail_html4inline(rcmail_print_body($part, array('safe' => true)), 'foo');
+
+        $this->assertRegExp('/<style [^>]+>/', $html2, "Allow styles in safe mode");
+        $this->assertRegExp('#src="http://evilsite.net/mailings/ex3.jpg"#', $html2, "Allow external images in HTML (safe mode)");
+        $this->assertRegExp("#url\('?http://evilsite.net/newsletter/image/bg/bg-64.jpg'?\)#", $html2, "Allow external images in CSS (safe mode)");
+        $css = '<link rel="stylesheet" .+_u=tmp-[a-z0-9]+\.css.+_action=modcss';
+        $this->assertRegExp('#'.$css.'#Ui', $html2, "Filter (anonymized) external styleseehts with utils/modcss.inc");
+    }
+
+    /**
+     * Test the elimination of some trivial XSS vulnerabilities
+     */
+    function test_html_xss()
+    {
+        $part = $this->get_html_part('src/htmlxss.txt');
+        $washed = rcmail_print_body($part, array('safe' => true));
+
+        $this->assertNotRegExp('/src="skins/', $washed, "Remove local references");
+        $this->assertNotRegExp('/\son[a-z]+/', $washed, "Remove on* attributes");
+
+        $html = rcmail_html4inline($washed, 'foo');
+        $this->assertNotRegExp('/onclick="return rcmail.command(\'compose\',\'xss@somehost.net\',this)"/', $html, "Clean mailto links");
+        $this->assertNotRegExp('/alert/', $html, "Remove alerts");
+    }
+
+    /**
+     * Test HTML sanitization to fix the CSS Expression Input Validation Vulnerability
+     * reported at http://www.securityfocus.com/bid/26800/
+     */
+    function test_html_xss2()
+    {
+        $part = $this->get_html_part('src/BID-26800.txt');
+        $washed = rcmail_html4inline(rcmail_print_body($part, array('safe' => true)), 'dabody', '', $attr, true);
+
+        $this->assertNotRegExp('/alert|expression|javascript|xss/', $washed, "Remove evil style blocks");
+        $this->assertNotRegExp('/font-style:italic/', $washed, "Allow valid styles");
+    }
+
+    /**
+     * Test washtml class on non-unicode characters (#1487813)
+     */
+    function test_washtml_utf8()
+    {
+        $part = $this->get_html_part('src/invalidchars.html');
+        $washed = rcmail_print_body($part);
+
+        $this->assertRegExp('/<p>символ<\/p>/', $washed, "Remove non-unicode characters from HTML message body");
+    }
+
+    /**
+     * Test links pattern replacements in plaintext messages
+     */
+    function test_plaintext()
+    {
+        $part = new rcube_message_part;
+        $part->ctype_primary = 'text';
+        $part->ctype_secondary = 'plain';
+        $part->body = quoted_printable_decode(file_get_contents(TESTS_DIR . 'src/plainbody.txt'));
+        $html = rcmail_print_body($part, array('safe' => true));
+
+        $this->assertRegExp('/<a href="mailto:nobody@roundcube.net" onclick="return rcmail.command\(\'compose\',\'nobody@roundcube.net\',this\)">nobody@roundcube.net<\/a>/', $html, "Mailto links with onclick");
+        $this->assertRegExp('#<a href="http://www.apple.com/legal/privacy" target="_blank">http://www.apple.com/legal/privacy</a>#', $html, "Links with target=_blank");
+        $this->assertRegExp('#\\[<a href="http://example.com/\\?tx\\[a\\]=5" target="_blank">http://example.com/\\?tx\\[a\\]=5</a>\\]#', $html, "Links with square brackets");
+    }
+
+    /**
+     * Test mailto links in html messages
+     */
+    function test_mailto()
+    {
+        $part = $this->get_html_part('src/mailto.txt');
+
+        // render HTML in normal mode
+        $html = rcmail_html4inline(rcmail_print_body($part, array('safe' => false)), 'foo');
+
+        $mailto = '<a href="mailto:me@me.com?subject=this is the subject&amp;body=this is the body"'
+            .' onclick="return rcmail.command(\'compose\',\'me@me.com?subject=this is the subject&amp;body=this is the body\',this)">e-mail</a>';
+
+        $this->assertRegExp('|'.preg_quote($mailto, '|').'|', $html, "Extended mailto links");
+    }
+
+    /**
+     * Test the elimination of HTML comments
+     */
+    function test_html_comments()
+    {
+        $part = $this->get_html_part('src/htmlcom.txt');
+        $washed = rcmail_print_body($part, array('safe' => true));
+
+        // #1487759
+        $this->assertRegExp('|<p>test1</p>|', $washed, "Buggy HTML comments");
+        // but conditional comments (<!--[if ...) should be removed
+        $this->assertNotRegExp('|<p>test2</p>|', $washed, "Conditional HTML comments");
+    }
+
+    /**
+     * Test URI base resolving in HTML messages
+     */
+    function test_resolve_base()
+    {
+        $html = file_get_contents(TESTS_DIR . 'src/htmlbase.txt');
+        $html = rcmail_resolve_base($html);
+
+        $this->assertRegExp('|src="http://alec\.pl/dir/img1\.gif"|', $html, "URI base resolving [1]");
+        $this->assertRegExp('|src="http://alec\.pl/dir/img2\.gif"|', $html, "URI base resolving [2]");
+        $this->assertRegExp('|src="http://alec\.pl/img3\.gif"|', $html, "URI base resolving [3]");
+
+        // base resolving exceptions
+        $this->assertRegExp('|src="cid:theCID"|', $html, "URI base resolving exception [1]");
+        $this->assertRegExp('|src="http://other\.domain\.tld/img3\.gif"|', $html, "URI base resolving exception [2]");
+    }
+}
diff --git a/tests/bootstrap.php b/tests/bootstrap.php
new file mode 100644
index 0000000..a9e2561
--- /dev/null
+++ b/tests/bootstrap.php
@@ -0,0 +1,35 @@
+<?php
+
+/*
+ +-----------------------------------------------------------------------+
+ | tests/bootstrap.php                                                   |
+ |                                                                       |
+ | This file is part of the Roundcube Webmail client                     |
+ | Copyright (C) 2009-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:                                                              |
+ |   Environment initialization script for unit tests                    |
+ +-----------------------------------------------------------------------+
+ | Author: Thomas Bruederli <roundcube@gmail.com>                        |
+ | Author: Aleksander Machniak <alec@alec.pl>                            |
+ +-----------------------------------------------------------------------+
+*/
+
+if (php_sapi_name() != 'cli')
+  die("Not in shell mode (php-cli)");
+
+if (!defined('INSTALL_PATH')) define('INSTALL_PATH', realpath(dirname(__FILE__) . '/..') . '/' );
+
+define('TESTS_DIR', dirname(__FILE__) . '/');
+
+if (@is_dir(TESTS_DIR . 'config')) {
+    define('RCMAIL_CONFIG_DIR', TESTS_DIR . 'config');
+}
+
+require_once(INSTALL_PATH . 'program/include/iniset.php');
+
+rcmail::get_instance()->config->set('devel_mode', false);
diff --git a/tests/maildecode.php b/tests/maildecode.php
deleted file mode 100644
index 4ac4993..0000000
--- a/tests/maildecode.php
+++ /dev/null
@@ -1,130 +0,0 @@
-<?php
-
-/**
- * Test class to test messages decoding functions
- *
- * @package Tests
- */
-class rcube_test_maildecode extends UnitTestCase
-{
-  private $app;
-
-  function __construct()
-  {
-    $this->UnitTestCase('Mail headers decoding tests');
-  }
-
-  /**
-   * Test decoding of single e-mail address strings
-   * Uses rcube_mime::decode_address_list()
-   */
-  function test_decode_single_address()
-  {
-    $headers = array(
-        0  => 'test@domain.tld',
-        1  => '<test@domain.tld>',
-        2  => 'Test <test@domain.tld>',
-        3  => 'Test Test <test@domain.tld>',
-        4  => 'Test Test<test@domain.tld>',
-        5  => '"Test Test" <test@domain.tld>',
-        6  => '"Test Test"<test@domain.tld>',
-        7  => '"Test \\" Test" <test@domain.tld>',
-        8  => '"Test<Test" <test@domain.tld>',
-        9  => '=?ISO-8859-1?B?VGVzdAo=?= <test@domain.tld>',
-        10 => '=?ISO-8859-1?B?VGVzdAo=?=<test@domain.tld>', // #1487068
-        // comments in address (#1487673)
-        11 => 'Test (comment) <test@domain.tld>',
-        12 => '"Test" (comment) <test@domain.tld>',
-        13 => '"Test (comment)" (comment) <test@domain.tld>',
-        14 => '(comment) <test@domain.tld>',
-        15 => 'Test <test@(comment)domain.tld>',
-        16 => 'Test Test ((comment)) <test@domain.tld>',
-        17 => 'test@domain.tld (comment)',
-        18 => '"Test,Test" <test@domain.tld>',
-        // 1487939
-        19 => 'Test <"test test"@domain.tld>',
-        20 => '<"test test"@domain.tld>',
-        21 => '"test test"@domain.tld',
-    );
-
-    $results = array(
-        0  => array(1, '', 'test@domain.tld'),
-        1  => array(1, '', 'test@domain.tld'),
-        2  => array(1, 'Test', 'test@domain.tld'),
-        3  => array(1, 'Test Test', 'test@domain.tld'),
-        4  => array(1, 'Test Test', 'test@domain.tld'),
-        5  => array(1, 'Test Test', 'test@domain.tld'),
-        6  => array(1, 'Test Test', 'test@domain.tld'),
-        7  => array(1, 'Test " Test', 'test@domain.tld'),
-        8  => array(1, 'Test<Test', 'test@domain.tld'),
-        9  => array(1, 'Test', 'test@domain.tld'),
-        10 => array(1, 'Test', 'test@domain.tld'),
-        11 => array(1, 'Test', 'test@domain.tld'),
-        12 => array(1, 'Test', 'test@domain.tld'),
-        13 => array(1, 'Test (comment)', 'test@domain.tld'),
-        14 => array(1, '', 'test@domain.tld'),
-        15 => array(1, 'Test', 'test@domain.tld'),
-        16 => array(1, 'Test Test', 'test@domain.tld'),
-        17 => array(1, '', 'test@domain.tld'),
-        18 => array(1, 'Test,Test', 'test@domain.tld'),
-        19 => array(1, 'Test', '"test test"@domain.tld'),
-        20 => array(1, '', '"test test"@domain.tld'),
-        21 => array(1, '', '"test test"@domain.tld'),
-    );
-
-    foreach ($headers as $idx => $header) {
-      $res = rcube_mime::decode_address_list($header);
-
-      $this->assertEqual($results[$idx][0], count($res), "Rows number in result for header: " . $header);
-      $this->assertEqual($results[$idx][1], $res[1]['name'], "Name part decoding for header: " . $header);
-      $this->assertEqual($results[$idx][2], $res[1]['mailto'], "Email part decoding for header: " . $header);
-    }
-  }
-
-  /**
-   * Test decoding of header values
-   * Uses rcube_mime::decode_mime_string()
-   */
-  function test_header_decode_qp()
-  {
-    $test = array(
-      // #1488232: invalid character "?"
-      'quoted-printable (1)' => array(
-        'in'  => '=?utf-8?Q?Certifica=C3=A7=C3=A3??=',
-        'out' => 'Certifica=C3=A7=C3=A3?',
-      ),
-      'quoted-printable (2)' => array(
-        'in'  => '=?utf-8?Q?Certifica=?= =?utf-8?Q?C3=A7=C3=A3?=',
-        'out' => 'Certifica=C3=A7=C3=A3',
-      ),
-      'quoted-printable (3)' => array(
-        'in'  => '=?utf-8?Q??= =?utf-8?Q??=',
-        'out' => '',
-      ),
-      'quoted-printable (4)' => array(
-        'in'  => '=?utf-8?Q??= a =?utf-8?Q??=',
-        'out' => ' a ',
-      ),
-      'quoted-printable (5)' => array(
-        'in'  => '=?utf-8?Q?a?= =?utf-8?Q?b?=',
-        'out' => 'ab',
-      ),
-      'quoted-printable (6)' => array(
-        'in'  => '=?utf-8?Q?   ?= =?utf-8?Q?a?=',
-        'out' => '   a',
-      ),
-      'quoted-printable (7)' => array(
-        'in'  => '=?utf-8?Q?___?= =?utf-8?Q?a?=',
-        'out' => '   a',
-      ),
-    );
-
-    foreach ($test as $idx => $item) {
-      $res = rcube_mime::decode_mime_string($item['in'], 'UTF-8');
-      $res = quoted_printable_encode($res);
-
-      $this->assertEqual($item['out'], $res, "Header decoding for: " . $idx);
-    }
-
-  }
-}
diff --git a/tests/mailfunc.php b/tests/mailfunc.php
deleted file mode 100644
index 493ce94..0000000
--- a/tests/mailfunc.php
+++ /dev/null
@@ -1,173 +0,0 @@
-<?php
-
-/**
- * Test class to test steps/mail/func.inc functions
- *
- * @package Tests
- */
-class rcube_test_mailfunc extends UnitTestCase
-{
-
-  function __construct()
-  {
-    $this->UnitTestCase('Mail body rendering tests');
-    
-    // simulate environment to successfully include func.inc
-    $GLOBALS['RCMAIL'] = $RCMAIL = rcmail::get_instance();
-    $GLOBALS['OUTPUT'] = $OUTPUT = $RCMAIL->load_gui();
-    $RCMAIL->action = 'autocomplete';
-    $RCMAIL->storage_init(false);
-    
-    require_once INSTALL_PATH . 'program/steps/mail/func.inc';
-    
-    $GLOBALS['EMAIL_ADDRESS_PATTERN'] = $EMAIL_ADDRESS_PATTERN;
-  }
-
-  /**
-   * Helper method to create a HTML message part object
-   */
-  function get_html_part($body)
-  {
-    $part = new rcube_message_part;
-    $part->ctype_primary = 'text';
-    $part->ctype_secondary = 'html';
-    $part->body = file_get_contents(TESTS_DIR . $body);
-    $part->replaces = array();
-    return $part;
-  }
-
-  /**
-   * Test sanitization of a "normal" html message
-   */
-  function test_html()
-  {
-    $part = $this->get_html_part('src/htmlbody.txt');
-    $part->replaces = array('ex1.jpg' => 'part_1.2.jpg', 'ex2.jpg' => 'part_1.2.jpg');
-    
-    // render HTML in normal mode
-    $html = rcmail_html4inline(rcmail_print_body($part, array('safe' => false)), 'foo');
-
-    $this->assertPattern('/src="'.$part->replaces['ex1.jpg'].'"/', $html, "Replace reference to inline image");
-    $this->assertPattern('#background="./program/resources/blocked.gif"#', $html, "Replace external background image");
-    $this->assertNoPattern('/ex3.jpg/', $html, "No references to external images");
-    $this->assertNoPattern('/<meta [^>]+>/', $html, "No meta tags allowed");
-    //$this->assertNoPattern('/<style [^>]+>/', $html, "No style tags allowed");
-    $this->assertNoPattern('/<form [^>]+>/', $html, "No form tags allowed");
-    $this->assertPattern('/Subscription form/', $html, "Include <form> contents");
-    $this->assertPattern('/<!-- link ignored -->/', $html, "No external links allowed");
-    $this->assertPattern('/<a[^>]+ target="_blank">/', $html, "Set target to _blank");
-    $this->assertTrue($GLOBALS['REMOTE_OBJECTS'], "Remote object detected");
-    
-    // render HTML in safe mode
-    $html2 = rcmail_html4inline(rcmail_print_body($part, array('safe' => true)), 'foo');
-    
-    $this->assertPattern('/<style [^>]+>/', $html2, "Allow styles in safe mode");
-    $this->assertPattern('#src="http://evilsite.net/mailings/ex3.jpg"#', $html2, "Allow external images in HTML (safe mode)");
-    $this->assertPattern("#url\('?http://evilsite.net/newsletter/image/bg/bg-64.jpg'?\)#", $html2, "Allow external images in CSS (safe mode)");
-    $css = '<link rel="stylesheet" .+_u=tmp-[a-z0-9]+\.css.+_action=modcss';
-    $this->assertPattern('#'.$css.'#Ui', $html2, "Filter (anonymized) external styleseehts with utils/modcss.inc");
-  }
-
-  /**
-   * Test the elimination of some trivial XSS vulnerabilities
-   */
-  function test_html_xss()
-  {
-    $part = $this->get_html_part('src/htmlxss.txt');
-    $washed = rcmail_print_body($part, array('safe' => true));
-    
-    $this->assertNoPattern('/src="skins/', $washed, "Remove local references");
-    $this->assertNoPattern('/\son[a-z]+/', $washed, "Remove on* attributes");
-    
-    $html = rcmail_html4inline($washed, 'foo');
-    $this->assertNoPattern('/onclick="return rcmail.command(\'compose\',\'xss@somehost.net\',this)"/', $html, "Clean mailto links");
-    $this->assertNoPattern('/alert/', $html, "Remove alerts");
-  }
-
-  /**
-   * Test HTML sanitization to fix the CSS Expression Input Validation Vulnerability
-   * reported at http://www.securityfocus.com/bid/26800/
-   */
-  function test_html_xss2()
-  {
-    $part = $this->get_html_part('src/BID-26800.txt');
-    $washed = rcmail_html4inline(rcmail_print_body($part, array('safe' => true)), 'dabody', '', $attr, true);
-
-    $this->assertNoPattern('/alert|expression|javascript|xss/', $washed, "Remove evil style blocks");
-    $this->assertNoPattern('/font-style:italic/', $washed, "Allow valid styles");
-  }
-
-  /**
-   * Test washtml class on non-unicode characters (#1487813)
-   */
-  function test_washtml_utf8()
-  {
-    $part = $this->get_html_part('src/invalidchars.html');
-    $washed = rcmail_print_body($part);
-
-    $this->assertPattern('/<p>символ<\/p>/', $washed, "Remove non-unicode characters from HTML message body");
-  }
-
-  /**
-   * Test links pattern replacements in plaintext messages
-   */
-  function test_plaintext()
-  {
-    $part = new rcube_message_part;
-    $part->ctype_primary = 'text';
-    $part->ctype_secondary = 'plain';
-    $part->body = quoted_printable_decode(file_get_contents(TESTS_DIR . 'src/plainbody.txt'));
-    $html = rcmail_print_body($part, array('safe' => true));
-
-    $this->assertPattern('/<a href="mailto:nobody@roundcube.net" onclick="return rcmail.command\(\'compose\',\'nobody@roundcube.net\',this\)">nobody@roundcube.net<\/a>/', $html, "Mailto links with onclick");
-    $this->assertPattern('#<a href="http://www.apple.com/legal/privacy" target="_blank">http://www.apple.com/legal/privacy</a>#', $html, "Links with target=_blank");
-    $this->assertPattern('#\\[<a href="http://example.com/\\?tx\\[a\\]=5" target="_blank">http://example.com/\\?tx\\[a\\]=5</a>\\]#', $html, "Links with square brackets");
-  }
-
-  /**
-   * Test mailto links in html messages
-   */
-  function test_mailto()
-  {
-    $part = $this->get_html_part('src/mailto.txt');
-
-    // render HTML in normal mode
-    $html = rcmail_html4inline(rcmail_print_body($part, array('safe' => false)), 'foo');
-
-    $mailto = '<a href="mailto:me@me.com?subject=this is the subject&amp;body=this is the body"'
-      .' onclick="return rcmail.command(\'compose\',\'me@me.com?subject=this is the subject&amp;body=this is the body\',this)">e-mail</a>';
-
-    $this->assertPattern('|'.preg_quote($mailto, '|').'|', $html, "Extended mailto links");
-  }
-
-  /**
-   * Test the elimination of HTML comments
-   */
-  function test_html_comments()
-  {
-    $part = $this->get_html_part('src/htmlcom.txt');
-    $washed = rcmail_print_body($part, array('safe' => true));
-
-    // #1487759
-    $this->assertPattern('|<p>test1</p>|', $washed, "Buggy HTML comments");
-    // but conditional comments (<!--[if ...) should be removed
-    $this->assertNoPattern('|<p>test2</p>|', $washed, "Conditional HTML comments");
-  }
-
-  /**
-   * Test URI base resolving in HTML messages
-   */
-  function test_resolve_base()
-  {
-    $html = file_get_contents(TESTS_DIR . 'src/htmlbase.txt');
-    $html = rcmail_resolve_base($html);
-
-    $this->assertPattern('|src="http://alec\.pl/dir/img1\.gif"|', $html, "URI base resolving [1]");
-    $this->assertPattern('|src="http://alec\.pl/dir/img2\.gif"|', $html, "URI base resolving [2]");
-    $this->assertPattern('|src="http://alec\.pl/img3\.gif"|', $html, "URI base resolving [3]");
-
-    // base resolving exceptions
-    $this->assertPattern('|src="cid:theCID"|', $html, "URI base resolving exception [1]");
-    $this->assertPattern('|src="http://other\.domain\.tld/img3\.gif"|', $html, "URI base resolving exception [2]");
-  }
-}
diff --git a/tests/modcss.php b/tests/modcss.php
deleted file mode 100644
index 945cac3..0000000
--- a/tests/modcss.php
+++ /dev/null
@@ -1,45 +0,0 @@
-<?php
-
-/**
- * Test class to test rcmail_mod_css_styles and XSS vulnerabilites
- *
- * @package Tests
- */
-class rcube_test_modcss extends UnitTestCase
-{
-
-  function __construct()
-  {
-    $this->UnitTestCase('CSS modification and vulnerability tests');
-  }
-  
-  function test_modcss()
-  {
-    $css = file_get_contents(TESTS_DIR . 'src/valid.css');
-    $mod = rcmail_mod_css_styles($css, 'rcmbody');
-
-    $this->assertPattern('/#rcmbody\s+\{/', $mod, "Replace body style definition");
-    $this->assertPattern('/#rcmbody h1\s\{/', $mod, "Prefix tag styles (single)");
-    $this->assertPattern('/#rcmbody h1, #rcmbody h2, #rcmbody h3, #rcmbody textarea\s+\{/', $mod, "Prefix tag styles (multiple)");
-    $this->assertPattern('/#rcmbody \.noscript\s+\{/', $mod, "Prefix class styles");
-  }
-  
-  function test_xss()
-  {
-    $mod = rcmail_mod_css_styles("body.main2cols { background-image: url('../images/leftcol.png'); }", 'rcmbody');
-    $this->assertEqual("/* evil! */", $mod, "No url() values allowed");
-    
-    $mod = rcmail_mod_css_styles("@import url('http://localhost/somestuff/css/master.css');", 'rcmbody');
-    $this->assertEqual("/* evil! */", $mod, "No import statements");
-    
-    $mod = rcmail_mod_css_styles("left:expression(document.body.offsetWidth-20)", 'rcmbody');
-    $this->assertEqual("/* evil! */", $mod, "No expression properties");
-    
-    $mod = rcmail_mod_css_styles("left:exp/*  */ression( alert(&#039;xss3&#039;) )", 'rcmbody');
-    $this->assertEqual("/* evil! */", $mod, "Don't allow encoding quirks");
-    
-    $mod = rcmail_mod_css_styles("background:\\0075\\0072\\006c( javascript:alert(&#039;xss&#039;) )", 'rcmbody');
-    $this->assertEqual("/* evil! */", $mod, "Don't allow encoding quirks (2)");
-  }
-  
-}
diff --git a/tests/phpunit.xml b/tests/phpunit.xml
new file mode 100644
index 0000000..8b38832
--- /dev/null
+++ b/tests/phpunit.xml
@@ -0,0 +1,37 @@
+<phpunit backupGlobals="false"
+    bootstrap="bootstrap.php"
+    colors="true">
+    <testsuites>
+        <testsuite name="All Tests">
+            <file>Framework/BaseReplacer.php</file>
+            <file>Framework/Browser.php</file>
+            <file>Framework/Cache.php</file>
+            <file>Framework/Charset.php</file>
+            <file>Framework/ContentFilter.php</file>
+            <file>Framework/Html.php</file>
+            <file>Framework/Imap.php</file>
+            <file>Framework/ImapGeneric.php</file>
+            <file>Framework/Image.php</file>
+            <file>Framework/MessageHeader.php</file>
+            <file>Framework/MessagePart.php</file>
+            <file>Framework/Mime.php</file>
+            <file>Framework/Rcube.php</file>
+            <file>Framework/ResultIndex.php</file>
+            <file>Framework/ResultSet.php</file>
+            <file>Framework/ResultThread.php</file>
+            <file>Framework/Shared.php</file>
+            <file>Framework/Smtp.php</file>
+            <file>Framework/Spellchecker.php</file>
+            <file>Framework/StringReplacer.php</file>
+            <file>Framework/User.php</file>
+            <file>Framework/Utils.php</file>
+            <file>Framework/VCard.php</file>
+            <file>HtmlToText.php</file>
+            <file>MailFunc.php</file>
+        </testsuite>
+        <testsuite name="managesieve">
+            <file>./../plugins/managesieve/tests/Parser.php</file>
+            <file>./../plugins/managesieve/tests/Tokenizer.php</file>
+        </testsuite>
+    </testsuites>
+</phpunit>
diff --git a/tests/runtests.sh b/tests/runtests.sh
deleted file mode 100755
index 9cfeb0a..0000000
--- a/tests/runtests.sh
+++ /dev/null
@@ -1,54 +0,0 @@
-#!/usr/bin/env php
-<?php
-
-/*
- +-----------------------------------------------------------------------+
- | tests/runtests.sh                                                     |
- |                                                                       |
- | This file is part of the Roundcube Webmail client                     |
- | Copyright (C) 2009, 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:                                                              |
- |   Run-script for unit tests based on http://simpletest.org            |
- |   All .php files in this folder will be treated as tests              |
- +-----------------------------------------------------------------------+
- | Author: Thomas Bruederli <roundcube@gmail.com>                        |
- +-----------------------------------------------------------------------+
-*/
-
-if (php_sapi_name() != 'cli')
-  die("Not in shell mode (php-cli)");
-
-if (!defined('SIMPLETEST'))   define('SIMPLETEST', '/www/simpletest/');
-if (!defined('INSTALL_PATH')) define('INSTALL_PATH', realpath(dirname(__FILE__) . '/..') . '/' );
-
-define('TESTS_DIR', dirname(__FILE__) . '/');
-define('RCMAIL_CONFIG_DIR', TESTS_DIR . 'config');
-
-require_once(SIMPLETEST . 'unit_tester.php');
-require_once(SIMPLETEST . 'reporter.php');
-require_once(INSTALL_PATH . 'program/include/iniset.php');
-
-if (count($_SERVER['argv']) > 1) {
-  $testfiles = array();
-  for ($i=1; $i < count($_SERVER['argv']); $i++)
-    $testfiles[] = realpath('./' . $_SERVER['argv'][$i]);
-}
-else {
-  $testfiles = glob(TESTS_DIR . '*.php');
-}
-
-$test = new TestSuite('Roundcube unit tests');
-$reporter = new TextReporter();
-
-foreach ($testfiles as $fn) {
-  $test->addTestFile($fn);
-}
-
-$test->run($reporter);
-
-?>
diff --git a/tests/src/photo.vcf b/tests/src/photo.vcf
new file mode 100644
index 0000000..c3a8050
--- /dev/null
+++ b/tests/src/photo.vcf
@@ -0,0 +1,45 @@
+BEGIN:VCARD
+VERSION:3.0
+N:Müller;Jörg;;;
+FN:Apple Computer AG
+ORG:Apple Computer AG;
+PHOTO;ENCODING=b:
+  /9j/4AAQSkZJRgABAQAAAQABAAD/7QAcUGhvdG9zaG9wIDMuMAA4QklNBAQAAAAAAAD/2wBDAAEB
+  AQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEB
+  AQEBAQH/2wBDAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEB
+  AQEBAQEBAQEBAQEBAQEBAQH/wAARCAAwADADAREAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAA
+  AAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEI
+  I0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlq
+  c3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW
+  19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL
+  /8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLR
+  ChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOE
+  hYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn
+  6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwD+/igAoAKAPmH43ftT+CfgzqNt4bNjeeLvGV2IHXw7
+  pVxDbLZx3LBbdtU1GVLhbN7jIMFvHa3VzIpWRoY4mWQ9dDCTrrmuoQ/mavfvZXV7dW2jkr4ynQfL
+  Zzn1inZL1lZ6+ST87H0lp1zLe6fY3k9s1nNd2dtczWjv5j2ss8KSyWzybU3tA7mJn2JuKk7Vzgcr
+  Vm1e9m1fvrv8zqi+aKbVm0nbtdXt8i5SGFAHHeOfH3hH4b6DP4k8Z61a6JpUBCCW4LPNdTsCUtbK
+  1iD3F5dSYO2C3jd8AuwVFZhdOnOrLlhFyf4Lzb6IzqVYUo81SSiundvslu3/AEz5i0n9u74Fanqo
+  064m8UaNbvII49X1TRUGnnORvmFje3t5BGTjDtatwcyCPBrreArpX9xvqlJ3/FJP7zlWYUHKzVRL
+  +ZxVvnaTa+4+QLvVPgJ4U+LutfFXx78QJfi3q954iuvEOieHvBmkyzaVbO1x5ulPruq6pLawTvp1
+  uLdIrK08xFmgXzl2oI67LV50o0qdP2K5VGUptKXnyxi29XfVtb6HDehGtKpUm6zcnNKCfK9brnlK
+  3pZJ37pH3/8ACj9qb4T/ABd1FdD0TUr3SPETozwaJ4ht47C5vQgLONOnjnuLS8dVBYwJOt1tDMIN
+  oLV51XCVqK5pJSj1cW3b1uk1vvqvM9Kji6VZ8qbjJ7KVlf0abTf4n0dXMdQUAfhf+2J8UNW8ffFz
+  W9Cnlki0XwDqOp+GdNsFdhB9qsr6a31DUDHu2tcXbwojSEbhFCka4Uc+9hKUadGMl8VRKcn11V0v
+  RJng4urKpWkntTlKCXTRtN+re58n11HKFAH6PfsGfBvQfEl3rHxR8Q2y38vhrUrfT/DNpIT5FvqY
+  iF1carIgI8ye2R4Y7RXykbySTbS6xlfOx9aUVGlF2503N+W1vnrc9HAUYzlKrJX5GuVf3t7+dvu3
+  vc/WKvIPXCgD8FP2s/BF/wCC/jd4wlu0It/Fmp6h4usJf4JINZ1G7ndVbu0UpZZBztY446V9BhZq
+  dCnZ/DFQfrFJHz+Kg4V6l/tSc15qTb/rzPmqug5woA++v2J/j74d+HN9rHgLxndrpmjeJr62vtJ1
+  mbP2Sw1dY/s0ltfOM+Rb30Yh8u5ZTHFPEFlZEk3Dgx2HlVUZw1lBNOPVp9vNa6X22137sFiI0ZSh
+  N2jNpqXSLSe/k+/R+p+w1eMe0FAHxz+2P8DLr4r+CIPEPhy2Nx4y8FJdXVnaxrmfWdGlAk1DSosK
+  WkuozEt5p8fHmTLNADuuQR24KuqU3GTtCpbXtLo32T2b9GcWNw7qwU4q84X06yi9Wl531XfXyPxK
+  ME4nNsYZRciUwG3MbicTh/LMJix5glD/ACGPbv3/AC4zxXtniFvUNJ1XSmjXVNM1DTWmUvCuoWVz
+  ZtKgxloxcRxmRRkZZcgZGTzSTT2afo7hqtz6w/ZB+BV78UPHtl4n1eykHgfwdewajfXE0bCDVtVt
+  mE+n6PAzAJOBOkdxqAUssdsnlSDNwoPLi66pU3FP95NNJdk95P06d35XOvCUHWqJtfu4NOT7vdR8
+  7vfsvVH7gV4R7oUAFAHKReA/A8Gsy+IofBnhSHxBO7ST67F4d0iPWZnb7zy6mlmL2R2/iZ5yT3NX
+  7Spbl9pPl/l55W+69iPZUubm9nDmvfm5I81+97XuX9a8MeGvEtsLLxF4e0PX7MMGFprWk2Gq2wZe
+  jCC+t54tw7HZkdqUZzg7wlKL7xk4v700OUIT0lCMl2lFS/NMuaXpOlaHZQ6Zoumafo+nW4It9P0u
+  yttPsoATkiG1tI4oIgTyQkagnmk5Sk7ybk+7bb+96jjGMVaMVFdopJfcjQpDP//Z
+X-ABShowAs:COMPANY
+X-ABUID:2E4CB084-4767-4C85-BBCA-805B1DCB1C8E\:ABPerson
+END:VCARD
diff --git a/tests/vcards.php b/tests/vcards.php
deleted file mode 100644
index 22f7cdd..0000000
--- a/tests/vcards.php
+++ /dev/null
@@ -1,65 +0,0 @@
-<?php
-
-/**
- * Unit tests for class rcube_vcard
- *
- * @package Tests
- */
-class rcube_test_vcards extends UnitTestCase
-{
-
-  function __construct()
-  {
-    $this->UnitTestCase('Vcard encoding/decoding tests');
-  }
-  
-  function _srcpath($fn)
-  {
-    return realpath(dirname(__FILE__) . '/src/' . $fn);
-  }
-  
-  function test_parse_one()
-  {
-    $vcard = new rcube_vcard(file_get_contents($this->_srcpath('apple.vcf')));
-    
-    $this->assertEqual(true, $vcard->business, "Identify as business record");
-    $this->assertEqual("Apple Computer AG", $vcard->displayname, "FN => displayname");
-    $this->assertEqual("", $vcard->firstname, "No person name set");
-  }
-
-  function test_parse_two()
-  {
-    $vcard = new rcube_vcard(file_get_contents($this->_srcpath('johndoe.vcf')), null);
-    
-    $this->assertEqual(false, $vcard->business, "Identify as private record");
-    $this->assertEqual("John Doë", $vcard->displayname, "Decode according to charset attribute");
-    $this->assertEqual("roundcube.net", $vcard->organization, "Test organization field");
-    $this->assertEqual(2, count($vcard->email), "List two e-mail addresses");
-    $this->assertEqual("roundcube@gmail.com", $vcard->email[0], "Use PREF e-mail as primary");
-  }
-  
-  function test_import()
-  {
-    $input = file_get_contents($this->_srcpath('apple.vcf'));
-    $input .= file_get_contents($this->_srcpath('johndoe.vcf'));
-    
-    $vcards = rcube_vcard::import($input);
-
-    $this->assertEqual(2, count($vcards), "Detected 2 vcards");
-    $this->assertEqual("Apple Computer AG", $vcards[0]->displayname, "FN => displayname");
-    $this->assertEqual("John Doë", $vcards[1]->displayname, "Displayname with correct charset");
-    
-    // http://trac.roundcube.net/ticket/1485542
-    $vcards2 = rcube_vcard::import(file_get_contents($this->_srcpath('thebat.vcf')));
-    $this->assertEqual("Iksiñski", $vcards2[0]->surname, "Detect charset in encoded values");
-  }
-  
-  function test_encodings()
-  {
-      $input = file_get_contents($this->_srcpath('utf-16_sample.vcf'));
-      
-      $vcards = rcube_vcard::import($input);
-      $this->assertEqual("Ǽgean ĽdaMonté", $vcards[0]->displayname, "Decoded from UTF-16");
-  }
-  
-}

--
Gitblit v1.9.1