From 2cdaa79dce689b2dc9ef5c7bf3dcbd9446d86c21 Mon Sep 17 00:00:00 2001
From: Paweł Słowik <pawel.slowik@iq.pl>
Date: Thu, 13 Sep 2012 08:24:01 -0400
Subject: [PATCH] Merge branch 'master' of https://github.com/roundcube/roundcubemail

---
 plugins/password/drivers/virtualmin.php        |    4 
 tests/Framework/Shared.php                     |   28 ++
 skins/larry/ie7hacks.css                       |    6 
 skins/larry/addressbook.css                    |    1 
 program/include/rcube_charset.php              |   25 +
 skins/larry/templates/messagepreview.html      |    5 
 program/include/rcube_shared.inc               |   15 +
 tests/Framework/Charset.php                    |  140 +++++++++++
 program/steps/mail/func.inc                    |    9 
 skins/classic/templates/message.html           |   15 -
 plugins/password/config.inc.php.dist           |   10 
 skins/larry/images/contactpic_48px.png         |    0 
 program/js/list.js                             |    4 
 CHANGELOG                                      |   10 
 plugins/managesieve/tests/Parser.php           |    2 
 program/include/rcube_result_thread.php        |    2 
 plugins/password/drivers/sql.php               |   41 ++
 skins/classic/templates/messageerror.html      |   15 -
 program/steps/settings/folders.inc             |   14 +
 skins/larry/templates/message.html             |   63 +++--
 skins/larry/images/contactpic_32px.png         |    0 
 skins/larry/svggradients.css                   |    2 
 config/main.inc.php.dist                       |    2 
 program/include/rcube_result_index.php         |    4 
 program/steps/mail/sendmail.inc                |   55 ++++
 program/steps/mail/headers.inc                 |    3 
 skins/larry/iehacks.css                        |    2 
 skins/larry/svggradient.php                    |    2 
 plugins/managesieve/tests/src/parser_kep14.out |    3 
 skins/larry/templates/messageerror.html        |   10 
 tests/Framework/VCard.php                      |   14 +
 program/steps/mail/search.inc                  |    2 
 program/include/rcube_imap.php                 |    8 
 program/include/rcmail.php                     |    8 
 program/steps/settings/save_folder.inc         |   16 +
 program/include/rcube_vcard.php                |    1 
 skins/larry/styles.css                         |    2 
 program/include/rcube_imap_generic.php         |    2 
 skins/larry/mail.css                           |   84 +++---
 tests/src/photo.vcf                            |   45 +++
 skins/larry/ui.js                              |   42 ++
 41 files changed, 555 insertions(+), 161 deletions(-)

diff --git a/CHANGELOG b/CHANGELOG
index 7654cde..36e80f3 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,6 +1,16 @@
 CHANGELOG Roundcube Webmail
 ===========================
 
+- Fix invalid error message on deleting mail from read only folder (#1488694)
+- Fix error where session wasn't updated after folder rename/delete (#1488692)
+- Replace data URIs of images (pasted in HTML editor) with inline attachments (#1488502)
+- 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'
diff --git a/config/main.inc.php.dist b/config/main.inc.php.dist
index 7e07341..a6661c3 100644
--- a/config/main.inc.php.dist
+++ b/config/main.inc.php.dist
@@ -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;
 
diff --git a/plugins/managesieve/tests/Parser.php b/plugins/managesieve/tests/Parser.php
index 06b644b..00915cc 100644
--- a/plugins/managesieve/tests/Parser.php
+++ b/plugins/managesieve/tests/Parser.php
@@ -31,7 +31,7 @@
         $result   = array();
 
         while ($file = readdir($dir)) {
-            if (preg_match('/^[a-z_]+$/', $file)) {
+            if (preg_match('/^[a-z0-9_]+$/', $file)) {
                 $input = file_get_contents($dir_path . '/' . $file);
 
                 if (file_exists($dir_path . '/' . $file . '.out')) {
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/password/config.inc.php.dist b/plugins/password/config.inc.php.dist
index 37c7931..8d7b433 100644
--- a/plugins/password/config.inc.php.dist
+++ b/plugins/password/config.inc.php.dist
@@ -36,7 +36,8 @@
 // The query can contain the following macros that will be expanded as follows:
 //      %p is replaced with the plaintext new password
 //      %c is replaced with the crypt version of the new password, MD5 if available
-//         otherwise DES.
+//         otherwise DES. More hash function can be enabled using the password_crypt_hash 
+//         configuration parameter.
 //      %D is replaced with the dovecotpw-crypted version of the new password
 //      %o is replaced with the password before the change
 //      %n is replaced with the hashed version of the new password
@@ -51,6 +52,13 @@
 // Default: "SELECT update_passwd(%c, %u)"
 $rcmail_config['password_query'] = 'SELECT update_passwd(%c, %u)';
 
+// By default the crypt() function which is used to create the '%c' 
+// parameter uses the md5 algorithm. To use different algorithms 
+// you can choose between: des, md5, blowfish, sha256, sha512.
+// Before using other hash functions than des or md5 please make sure
+// your operating system supports the other hash functions.
+$rcmail_config['password_crypt_hash'] = 'md5';
+
 // By default domains in variables are using unicode.
 // Enable this option to use punycoded names
 $rcmail_config['password_idn_ascii'] = false;
diff --git a/plugins/password/drivers/sql.php b/plugins/password/drivers/sql.php
index 449e2df..8bdcabf 100644
--- a/plugins/password/drivers/sql.php
+++ b/plugins/password/drivers/sql.php
@@ -40,13 +40,38 @@
         // crypted password
         if (strpos($sql, '%c') !== FALSE) {
             $salt = '';
-            if (CRYPT_MD5) {
-                // Always use eight salt characters for MD5 (#1488136)
-    	        $len = 8;
-            } else if (CRYPT_STD_DES) {
-        	    $len = 2;
-            } else {
-        	    return PASSWORD_CRYPT_ERROR;
+
+            if (!($crypt_hash = $rcmail->config->get('password_crypt_hash')))
+            {
+                if (CRYPT_MD5)
+                    $crypt_hash = 'md5';
+                else if (CRYPT_STD_DES)
+                    $crypt_hash = 'des';
+            }
+            
+            switch ($crypt_hash)
+            {
+            case 'md5':
+                $len = 8;
+                $salt_hashindicator = '$1$';
+                break;
+            case 'des':
+                $len = 2;
+                break;
+            case 'blowfish':
+                $len = 22;
+                $salt_hashindicator = '$2a$';
+                break;
+            case 'sha256':
+                $len = 16;
+                $salt_hashindicator = '$5$';
+                break;
+            case 'sha512':
+                $len = 16;
+                $salt_hashindicator = '$6$';
+                break;
+            default:
+                return PASSWORD_CRYPT_ERROR;
             }
 
             //Restrict the character set used as salt (#1488136)
@@ -55,7 +80,7 @@
     	        $salt .= $seedchars[rand(0, 63)];
             }
 
-            $sql = str_replace('%c',  $db->quote(crypt($passwd, CRYPT_MD5 ? '$1$'.$salt.'$' : $salt)), $sql);
+            $sql = str_replace('%c',  $db->quote(crypt($passwd, $salt_hashindicator ? $salt_hashindicator .$salt.'$' : $salt)), $sql);
         }
 
         // dovecotpw
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/program/include/rcmail.php b/program/include/rcmail.php
index 02f38e6..5a9a1fa 100644
--- a/program/include/rcmail.php
+++ b/program/include/rcmail.php
@@ -1774,10 +1774,7 @@
         $err_code = $this->storage->get_error_code();
         $res_code = $this->storage->get_response_code();
 
-        if ($err_code < 0) {
-            $this->output->show_message('storageerror', 'error');
-        }
-        else if ($res_code == rcube_storage::NOPERM) {
+        if ($res_code == rcube_storage::NOPERM) {
             $this->output->show_message('errornoperm', 'error');
         }
         else if ($res_code == rcube_storage::READONLY) {
@@ -1792,6 +1789,9 @@
                 $this->output->show_message('servererrormsg', 'error', array('msg' => $err_str));
             }
         }
+        else if ($err_code < 0) {
+            $this->output->show_message('storageerror', 'error');
+        }
         else if ($fallback) {
             $this->output->show_message($fallback, 'error', $fallback_args);
         }
diff --git a/program/include/rcube_charset.php b/program/include/rcube_charset.php
index 1740a60..35c6972 100644
--- a/program/include/rcube_charset.php
+++ b/program/include/rcube_charset.php
@@ -86,7 +86,7 @@
      * Sometimes charset string is malformed, there are also charset aliases 
      * but we need strict names for charset conversion (specially utf8 class)
      *
-     * @param  string Input charset name
+     * @param string $input Input charset name
      *
      * @return string The validated charset name
      */
@@ -176,9 +176,10 @@
     {
         static $iconv_options   = null;
         static $mbstring_list   = null;
+        static $mbstring_sch    = null;
         static $conv            = null;
 
-        $to   = empty($to) ? strtoupper(RCMAIL_CHARSET) : self::parse_charset($to);
+        $to   = empty($to) ? strtoupper(RCMAIL_CHARSET) : $to;
         $from = self::parse_charset($from);
 
         // It is a common case when UTF-16 charset is used with US-ASCII content (#1488654)
@@ -221,6 +222,7 @@
 
         if ($mbstring_list === null) {
             if (extension_loaded('mbstring')) {
+                $mbstring_sch  = mb_substitute_character();
                 $mbstring_list = mb_list_encodings();
                 $mbstring_list = array_map('strtoupper', $mbstring_list);
             }
@@ -229,14 +231,25 @@
         // convert charset using mbstring module
         if ($mbstring_list !== null) {
             $aliases['WINDOWS-1257'] = 'ISO-8859-13';
+            // it happens that mbstring supports ASCII but not US-ASCII
+            if (($from == 'US-ASCII' || $to == 'US-ASCII') && !in_array('US-ASCII', $mbstring_list)) {
+                $aliases['US-ASCII'] = 'ASCII';
+            }
 
             $mb_from = $aliases[$from] ? $aliases[$from] : $from;
             $mb_to   = $aliases[$to] ? $aliases[$to] : $to;
 
             // return if encoding found, string matches encoding and convert succeeded
             if (in_array($mb_from, $mbstring_list) && in_array($mb_to, $mbstring_list)) {
-                if (mb_check_encoding($str, $mb_from) && ($out = mb_convert_encoding($str, $mb_to, $mb_from))) {
-                    return $out;
+                if (mb_check_encoding($str, $mb_from)) {
+                    // Do the same as //IGNORE with iconv
+                    mb_substitute_character('none');
+                    $out = mb_convert_encoding($str, $mb_to, $mb_from);
+                    mb_substitute_character($mbstring_sch);
+
+                    if ($out !== false) {
+                        return $out;
+                    }
                 }
             }
         }
@@ -646,14 +659,14 @@
             return $failover;
         }
 
-        // FIXME: the order is important, because sometimes 
+        // FIXME: the order is important, because sometimes
         // iso string is detected as euc-jp and etc.
         $enc = array(
             'UTF-8', 'SJIS', 'BIG5', 'GB2312',
             'ISO-8859-1', 'ISO-8859-2', 'ISO-8859-3', 'ISO-8859-4',
             'ISO-8859-5', 'ISO-8859-6', 'ISO-8859-7', 'ISO-8859-8', 'ISO-8859-9',
             'ISO-8859-10', 'ISO-8859-13', 'ISO-8859-14', 'ISO-8859-15', 'ISO-8859-16',
-            'WINDOWS-1252', 'WINDOWS-1251', 'EUC-JP', 'EUC-TW', 'KOI8-R', 
+            'WINDOWS-1252', 'WINDOWS-1251', 'EUC-JP', 'EUC-TW', 'KOI8-R',
             'ISO-2022-KR', 'ISO-2022-JP'
         );
 
diff --git a/program/include/rcube_imap.php b/program/include/rcube_imap.php
index 66b5c4b..0b2f84d 100644
--- a/program/include/rcube_imap.php
+++ b/program/include/rcube_imap.php
@@ -1434,6 +1434,12 @@
             $criteria = 'UNDELETED '.$criteria;
         }
 
+        // unset CHARSET if criteria string is ASCII, this way
+        // SEARCH won't be re-sent after "unsupported charset" response
+        if ($charset && $charset != 'US-ASCII' && is_ascii($criteria)) {
+            $charset = 'US-ASCII';
+        }
+
         if ($this->threading) {
             $threads = $this->conn->thread($folder, $this->threading, $criteria, true, $charset);
 
@@ -1465,7 +1471,7 @@
         }
 
         $messages = $this->conn->search($folder,
-            ($charset ? "CHARSET $charset " : '') . $criteria, true);
+            ($charset && $charset != 'US-ASCII' ? "CHARSET $charset " : '') . $criteria, true);
 
         // Error, try with US-ASCII (some servers may support only US-ASCII)
         if ($messages->is_error() && $charset && $charset != 'US-ASCII') {
diff --git a/program/include/rcube_imap_generic.php b/program/include/rcube_imap_generic.php
index c3cfabc..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);
diff --git a/program/include/rcube_result_index.php b/program/include/rcube_result_index.php
index cc1615d..334ec85 100644
--- a/program/include/rcube_result_index.php
+++ b/program/include/rcube_result_index.php
@@ -61,10 +61,14 @@
         for ($i=0, $len=count($data); $i<$len; $i++) {
             $data_item = &$data[$i];
             if (preg_match('/^ SORT/i', $data_item)) {
+                // valid response, initialize raw_data for is_error()
+                $this->raw_data = '';
                 $data_item = substr($data_item, 5);
                 break;
             }
             else if (preg_match('/^ (E?SEARCH)/i', $data_item, $m)) {
+                // valid response, initialize raw_data for is_error()
+                $this->raw_data = '';
                 $data_item = substr($data_item, strlen($m[0]));
 
                 if (strtoupper($m[1]) == 'ESEARCH') {
diff --git a/program/include/rcube_result_thread.php b/program/include/rcube_result_thread.php
index 214aec2..09fa465 100644
--- a/program/include/rcube_result_thread.php
+++ b/program/include/rcube_result_thread.php
@@ -61,6 +61,8 @@
         // ...skip unilateral untagged server responses
         for ($i=0, $len=count($data); $i<$len; $i++) {
             if (preg_match('/^ THREAD/i', $data[$i])) {
+                // valid response, initialize raw_data for is_error()
+                $this->raw_data = '';
                 $data[$i] = substr($data[$i], 7);
                 break;
             }
diff --git a/program/include/rcube_shared.inc b/program/include/rcube_shared.inc
index c15305c..4577c6d 100644
--- a/program/include/rcube_shared.inc
+++ b/program/include/rcube_shared.inc
@@ -255,6 +255,21 @@
 
 
 /**
+ * Check if a string contains only ascii characters
+ *
+ * @param string $str           String to check
+ * @param bool   $control_chars Includes control characters
+ *
+ * @return bool
+ */
+function is_ascii($str, $control_chars = true)
+{
+    $regexp = $control_chars ? '/[^\x00-\x7F]/' : '/[^\x20-\x7E]/';
+    return preg_match($regexp, $str) ? false : true;
+}
+
+
+/**
  * Remove single and double quotes from a given string
  *
  * @param string Input value
diff --git a/program/include/rcube_vcard.php b/program/include/rcube_vcard.php
index 2bfd474..49b312c 100644
--- a/program/include/rcube_vcard.php
+++ b/program/include/rcube_vcard.php
@@ -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/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/steps/mail/func.inc b/program/steps/mail/func.inc
index 45582d4..8bf80a6 100644
--- a/program/steps/mail/func.inc
+++ b/program/steps/mail/func.inc
@@ -1055,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;
 }
 
 
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/search.inc b/program/steps/mail/search.inc
index 6706809..db5424b 100644
--- a/program/steps/mail/search.inc
+++ b/program/steps/mail/search.inc
@@ -100,7 +100,7 @@
 if (!empty($subject)) {
   $search_str .= str_repeat(' OR', count($subject)-1);
   foreach ($subject as $sub)
-    $search_str .= sprintf(" %s {%d}\r\n%s", $sub, strlen($search), $search);
+    $search_str .= ' ' . $sub . ' ' . rcube_imap_generic::escape($search);
 }
 
 $search_str  = trim($search_str);
diff --git a/program/steps/mail/sendmail.inc b/program/steps/mail/sendmail.inc
index 5777517..5c2c6de 100644
--- a/program/steps/mail/sendmail.inc
+++ b/program/steps/mail/sendmail.inc
@@ -93,9 +93,8 @@
  * to this:
  *
  * <img src="/path/on/server/.../tiny_mce/plugins/emotions/images/smiley-cool.gif" border="0" alt="Cool" title="Cool" />
- * ...
  */
-function rcmail_fix_emoticon_paths(&$mime_message)
+function rcmail_fix_emoticon_paths($mime_message)
 {
   global $CONFIG;
 
@@ -134,8 +133,53 @@
   }
 
   $mime_message->setHTMLBody($body);
+}
 
-  return $body;
+/**
+ * Extract image attachments from HTML content (data URIs)
+ */
+function rcmail_extract_inline_images($mime_message, $from)
+{
+    $body   = $mime_message->getHTMLBody();
+    $offset = 0;
+    $list   = array();
+    $regexp = '# src=[\'"](data:(image/[a-z]+);base64,([a-z0-9+/=\r\n]+))([\'"])#i';
+
+    // get domain for the Content-ID, must be the same as in Mail_Mime::get()
+    if (preg_match('#@([0-9a-zA-Z\-\.]+)#', $from, $matches)) {
+        $domain = $matches[1];
+    } else {
+        $domain = 'localhost';
+    }
+
+    if (preg_match_all($regexp, $body, $matches, PREG_OFFSET_CAPTURE)) {
+        foreach ($matches[1] as $idx => $m) {
+            $data = preg_replace('/\r\n/', '', $matches[3][$idx][0]);
+            $data = base64_decode($data);
+
+            if (empty($data)) {
+                continue;
+            }
+
+            $hash      = md5($data) . '@' . $domain;
+            $mime_type = $matches[2][$idx][0];
+            $name      = $list[$hash];
+
+            // add the image to the MIME message
+            if (!$name) {
+                $ext         = preg_replace('#^[^/]+/#', '', $mime_type);
+                $name        = substr($hash, 0, 8) . '.' . $ext;
+                $list[$hash] = $name;
+
+                $mime_message->addHTMLImage($data, $mime_type, $name, false, $hash);
+            }
+
+            $body = substr_replace($body, $name, $m[1] + $offset, strlen($m[0]));
+            $offset += strlen($name) - strlen($m[0]);
+        }
+    }
+
+    $mime_message->setHTMLBody($body);
 }
 
 /**
@@ -522,7 +566,10 @@
 
   // look for "emoticon" images from TinyMCE and change their src paths to
   // be file paths on the server instead of URL paths.
-  $message_body = rcmail_fix_emoticon_paths($MAIL_MIME);
+  rcmail_fix_emoticon_paths($MAIL_MIME);
+
+  // Extract image Data URIs into message attachments (#1488502)
+  rcmail_extract_inline_images($MAIL_MIME, $from);
 }
 else {
   $plugin = $RCMAIL->plugins->exec_hook('message_outgoing_body',
diff --git a/program/steps/settings/folders.inc b/program/steps/settings/folders.inc
index 6ca7049..3231ed6 100644
--- a/program/steps/settings/folders.inc
+++ b/program/steps/settings/folders.inc
@@ -85,6 +85,11 @@
         else {
             $deleted = $plugin['result'];
         }
+
+        // #1488692: update session
+        if ($deleted && $_SESSION['mbox'] === $mbox) {
+            $RCMAIL->session->remove('mbox');
+        }
     }
 
     if ($OUTPUT->ajax_call && $deleted) {
@@ -393,15 +398,20 @@
         foreach ($a_threaded as $key => $val) {
             if ($key == $oldname) {
                 unset($a_threaded[$key]);
-    	        $a_threaded[$newname] = true;
+                $a_threaded[$newname] = true;
             }
             else if (preg_match($oldprefix, $key)) {
                 unset($a_threaded[$key]);
-	            $a_threaded[preg_replace($oldprefix, $newname.$delimiter, $key)] = true;
+                $a_threaded[preg_replace($oldprefix, $newname.$delimiter, $key)] = true;
             }
         }
         $RCMAIL->user->save_prefs(array('message_threading' => $a_threaded));
 
+        // #1488692: update session
+        if ($_SESSION['mbox'] === $oldname) {
+            $_SESSION['mbox'] = $newname;
+        }
+
         return true;
     }
 
diff --git a/program/steps/settings/save_folder.inc b/program/steps/settings/save_folder.inc
index 09f76ac..877b0fb 100644
--- a/program/steps/settings/save_folder.inc
+++ b/program/steps/settings/save_folder.inc
@@ -1,11 +1,11 @@
 <?php
 
-/*
+/**
  +-----------------------------------------------------------------------+
  | program/steps/settings/save_folder.inc                                |
  |                                                                       |
  | This file is part of the Roundcube Webmail client                     |
- | Copyright (C) 2005-2009, 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.                |
@@ -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']    = '';
@@ -167,7 +170,7 @@
                     }
                     else if (preg_match($oldprefix, $key)) {
                         unset($a_threaded[$key]);
-  	                    $a_threaded[preg_replace($oldprefix, $folder['name'].$delimiter, $key)] = true;
+                        $a_threaded[preg_replace($oldprefix, $folder['name'].$delimiter, $key)] = true;
                     }
                 }
             }
@@ -180,7 +183,12 @@
         }
 
         $OUTPUT->show_message('folderupdated', 'confirmation');
+
         if ($rename) {
+            // #1488692: update session
+            if ($_SESSION['mbox'] === $folder['oldname']) {
+                $_SESSION['mbox'] = $folder['name'];
+            }
             rcmail_update_folder_row($folder['name'], $folder['oldname'], $folder['subscribe'], $folder['class']);
             $OUTPUT->send('iframe');
         }
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 024c35b..935a504 100644
--- a/skins/larry/ie7hacks.css
+++ b/skins/larry/ie7hacks.css
@@ -29,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;
@@ -45,7 +45,7 @@
 
 .pagenav a.button,
 .pagenav a.button span.inner,
-#messagepreviewheader .iconlink,
+#messageheader .iconlink,
 #uploadform a.iconlink {
 	display: inline;
 }
@@ -67,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/mail.css b/skins/larry/mail.css
index 6185840..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 {
@@ -676,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;
@@ -708,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;
@@ -783,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 {
@@ -797,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 {
@@ -828,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;
@@ -858,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;
 /*
@@ -917,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 1999698..f2d4888 100644
--- a/skins/larry/styles.css
+++ b/skins/larry/styles.css
@@ -661,7 +661,7 @@
 	left: 0;
 	bottom: 0;
 	width: 100%;
-	min-width: 1150px;
+	min-width: 1024px;
 }
 
 .scroller {
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/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 474c480..c221b86 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); });
@@ -145,7 +144,7 @@
         new rcube_splitter({ id:'identviewsplitter', p1:'#identitieslist', p2:'#identity-details',
           orientation:'v', relative:true, start:266, min:180, size:12 }).init();
       }
-      else if (rcmail.env.action == 'preferences') {
+      else if (rcmail.env.action == 'preferences' || !rcmail.env.action) {
         new rcube_splitter({ id:'prefviewsplitter', p1:'#sectionslist', p2:'#preferences-box',
           orientation:'v', relative:true, start:266, min:180, size:12 }).init();
       }
@@ -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
@@ -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) {
@@ -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;
   }
 
 
diff --git a/tests/Framework/Charset.php b/tests/Framework/Charset.php
index 9e3fad4..1fd1654 100644
--- a/tests/Framework/Charset.php
+++ b/tests/Framework/Charset.php
@@ -14,15 +14,149 @@
     function data_clean()
     {
         return array(
-            array('', '', 'Empty string'),
+            array('', ''),
+            array("\xC1", ''),
         );
     }
 
     /**
      * @dataProvider data_clean
      */
-    function test_clean($input, $output, $title)
+    function test_clean($input, $output)
     {
-        $this->assertEquals(rcube_charset::clean($input), $output, $title);
+        $this->assertEquals($output, rcube_charset::clean($input));
     }
+
+    /**
+     * Data for test_parse_charset()
+     */
+    function data_parse_charset()
+    {
+        return array(
+            array('UTF8', 'UTF-8'),
+            array('WIN1250', 'WINDOWS-1250'),
+        );
+    }
+
+    /**
+     * @dataProvider data_parse_charset
+     */
+    function test_parse_charset($input, $output)
+    {
+        $this->assertEquals($output, rcube_charset::parse_charset($input));
+    }
+
+    /**
+     * Data for test_convert()
+     */
+    function data_convert()
+    {
+        return array(
+            array('ö', 'ö', 'UTF-8', 'UTF-8'),
+            array('ö', '', 'UTF-8', 'US-ASCII'),
+            array('aż', 'a', 'UTF-8', 'US-ASCII'),
+            array('&BCAEMARBBEEESwQ7BDoEOA-', 'Рассылки', 'UTF7-IMAP', 'UTF-8'),
+            array('Рассылки', '&BCAEMARBBEEESwQ7BDoEOA-', 'UTF-8', 'UTF7-IMAP'),
+        );
+    }
+
+    /**
+     * @dataProvider data_convert
+     */
+    function test_convert($input, $output, $from, $to)
+    {
+        $this->assertEquals($output, rcube_charset::convert($input, $from, $to));
+    }
+
+    /**
+     * Data for test_utf7_to_utf8()
+     */
+    function data_utf7_to_utf8()
+    {
+        return array(
+            array('+BCAEMARBBEEESwQ7BDoEOA-', 'Рассылки'),
+        );
+    }
+
+    /**
+     * @dataProvider data_utf7_to_utf8
+     */
+    function test_utf7_to_utf8($input, $output)
+    {
+        $this->assertEquals($output, rcube_charset::utf7_to_utf8($input));
+    }
+
+    /**
+     * Data for test_utf7imap_to_utf8()
+     */
+    function data_utf7imap_to_utf8()
+    {
+        return array(
+            array('&BCAEMARBBEEESwQ7BDoEOA-', 'Рассылки'),
+        );
+    }
+
+    /**
+     * @dataProvider data_utf7imap_to_utf8
+     */
+    function test_utf7imap_to_utf8($input, $output)
+    {
+        $this->assertEquals($output, rcube_charset::utf7imap_to_utf8($input));
+    }
+
+    /**
+     * Data for test_utf8_to_utf7imap()
+     */
+    function data_utf8_to_utf7imap()
+    {
+        return array(
+            array('Рассылки', '&BCAEMARBBEEESwQ7BDoEOA-'),
+        );
+    }
+
+    /**
+     * @dataProvider data_utf8_to_utf7imap
+     */
+    function test_utf8_to_utf7imap($input, $output)
+    {
+        $this->assertEquals($output, rcube_charset::utf8_to_utf7imap($input));
+    }
+
+    /**
+     * Data for test_utf16_to_utf8()
+     */
+    function data_utf16_to_utf8()
+    {
+        return array(
+            array(base64_decode('BCAEMARBBEEESwQ7BDoEOA=='), 'Рассылки'),
+        );
+    }
+
+    /**
+     * @dataProvider data_utf16_to_utf8
+     */
+    function test_utf16_to_utf8($input, $output)
+    {
+        $this->assertEquals($output, rcube_charset::utf16_to_utf8($input));
+    }
+
+    /**
+     * Data for test_detect()
+     */
+    function data_detect()
+    {
+        return array(
+            array('', '', 'UTF-8'),
+            array('a', 'UTF-8', 'UTF-8'),
+        );
+    }
+
+    /**
+     * @dataProvider data_detect
+     */
+    function test_detect($input, $fallback, $output)
+    {
+        $this->assertEquals($output, rcube_charset::detect($input, $fallback));
+    }
+
 }
diff --git a/tests/Framework/Shared.php b/tests/Framework/Shared.php
index 99ef829..0394cd0 100644
--- a/tests/Framework/Shared.php
+++ b/tests/Framework/Shared.php
@@ -201,4 +201,32 @@
 
     }
 
+    /**
+     * rcube_shared.inc: is_ascii()
+     */
+    function test_is_ascii()
+    {
+        $result = is_ascii("0123456789");
+        $this->assertTrue($result, "Valid ASCII (numbers)");
+
+        $result = is_ascii("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz");
+        $this->assertTrue($result, "Valid ASCII (letters)");
+
+        $result = is_ascii(" !\"#\$%&'()*+,-./:;<=>?@[\\^_`{|}~");
+        $this->assertTrue($result, "Valid ASCII (special characters)");
+
+        $result = is_ascii("\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F"
+            ."\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F");
+        $this->assertTrue($result, "Valid ASCII (control characters)");
+
+        $result = is_ascii("\n", false);
+        $this->assertFalse($result, "Valid ASCII (control characters)");
+
+        $result = is_ascii("ż");
+        $this->assertFalse($result, "Invalid ASCII (UTF-8 character)");
+
+        $result = is_ascii("ż", false);
+        $this->assertFalse($result, "Invalid ASCII (UTF-8 character [2])");
+    }
+
 }
diff --git a/tests/Framework/VCard.php b/tests/Framework/VCard.php
index a830c2c..56ca9d7 100644
--- a/tests/Framework/VCard.php
+++ b/tests/Framework/VCard.php
@@ -49,6 +49,20 @@
         $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'));
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

--
Gitblit v1.9.1