From 8f098e8dead85b6512ac72b2d805314baec72a2f Mon Sep 17 00:00:00 2001
From: Thomas Bruederli <thomas@roundcube.net>
Date: Mon, 17 Sep 2012 16:04:16 -0400
Subject: [PATCH] Merge branch 'master' of github.com:roundcube/roundcubemail

---
 skins/larry/includes/mailtoolbar.html      |    2 
 program/include/rcube_utils.php            |    4 
 program/steps/mail/compose.inc             |   18 +
 installer/check.php                        |   12 +
 tests/Framework/Shared.php                 |   28 +++
 program/include/rcube_charset.php          |   25 ++
 program/include/rcube_shared.inc           |   15 +
 tests/Framework/Charset.php                |  140 +++++++++++++++
 program/include/rcube_db.php               |   18 +
 plugins/password/config.inc.php.dist       |   10 +
 CHANGELOG                                  |    7 
 program/include/rcube_result_thread.php    |    2 
 plugins/password/drivers/sql.php           |   41 +++
 program/steps/addressbook/edit.inc         |    3 
 program/steps/settings/folders.inc         |   14 +
 program/steps/addressbook/save.inc         |    1 
 program/include/rcube_message.php          |    7 
 program/include/rcube_result_index.php     |    4 
 program/steps/mail/sendmail.inc            |   55 +++++
 program/include/rcube_addressbook.php      |    2 
 program/steps/addressbook/import.inc       |   22 +-
 program/steps/mail/search.inc              |    2 
 program/include/rcube_imap.php             |   15 +
 program/include/rcmail.php                 |    8 
 program/steps/settings/save_folder.inc     |   11 
 program/include/rcube_imap_generic.php     |   15 +
 program/js/app.js                          |    2 
 skins/larry/mail.css                       |    1 
 skins/classic/includes/messagetoolbar.html |    2 
 skins/larry/ui.js                          |    2 
 30 files changed, 414 insertions(+), 74 deletions(-)

diff --git a/CHANGELOG b/CHANGELOG
index 1d2225a..8a010a4 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,6 +1,13 @@
 CHANGELOG Roundcube Webmail
 ===========================
 
+- List related text/html part as attachment in plain text mode (#1488677)
+- Use IMAP BINARY (RFC3516) extension to fetch message/part bodies
+- Fix folder creation under public namespace root (#1488665)
+- Fix so "Edit as new" on draft creates a new message (#1488687)
+- 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)
diff --git a/installer/check.php b/installer/check.php
index 52460bb..d6c9f5c 100644
--- a/installer/check.php
+++ b/installer/check.php
@@ -35,12 +35,12 @@
     'suhosin.session.encrypt'       => 0,
     'magic_quotes_runtime'          => 0,
     'magic_quotes_sybase'           => 0,
+    'date.timezone'                 => '-NOTEMPTY-',
 );
 
 $optional_checks = array(
     // required for utils/modcss.inc, should we require this?
     'allow_url_fopen'  => 1,
-    'date.timezone'    => '-NOTEMPTY-',
 );
 
 $source_urls = array(
@@ -171,7 +171,15 @@
     $status = ini_get($var);
     if ($val === '-NOTEMPTY-') {
         if (empty($status)) {
-            $RCI->fail($var, "cannot be empty and needs to be set");
+            $RCI->fail($var, "empty value detected");
+        } else if ($var == 'date.timezone') {
+            try {
+                $tz = new DateTimeZone($status);
+                $RCI->pass($var);
+            }
+            catch (Exception $e) {
+                $RCI->fail($var, "invalid value detected: $status");
+            }
         } else {
             $RCI->pass($var);
         }
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/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_addressbook.php b/program/include/rcube_addressbook.php
index 069ea57..f4f2553 100644
--- a/program/include/rcube_addressbook.php
+++ b/program/include/rcube_addressbook.php
@@ -465,7 +465,7 @@
         $fn = $contact['name'];
 
         if (!$fn)  // default display name composition according to vcard standard
-            $fn = join(' ', array_filter(array($contact['prefix'], $contact['firstname'], $contact['middlename'], $contact['surname'], $contact['suffix'])));
+            $fn = trim(join(' ', array_filter(array($contact['prefix'], $contact['firstname'], $contact['middlename'], $contact['surname'], $contact['suffix']))));
 
         // use email address part for name
         $email = is_array($contact['email']) ? $contact['email'][0] : $contact['email'];
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_db.php b/program/include/rcube_db.php
index f97d70a..eb1ad31 100644
--- a/program/include/rcube_db.php
+++ b/program/include/rcube_db.php
@@ -388,13 +388,19 @@
         $idx = 0;
 
         while ($pos = strpos($query, '?', $pos)) {
-            $val = $this->quote($params[$idx++]);
-            unset($params[$idx-1]);
-            $query = substr_replace($query, $val, $pos, 1);
-            $pos += strlen($val);
+            if ($query[$pos+1] == '?') {  // skip escaped ?
+                $pos += 2;
+            }
+            else {
+                $val = $this->quote($params[$idx++]);
+                unset($params[$idx-1]);
+                $query = substr_replace($query, $val, $pos, 1);
+                $pos += strlen($val);
+            }
         }
 
-        $query = rtrim($query, ';');
+        // replace escaped ? back to normal
+        $query = rtrim(strtr($query, array('??' => '?')), ';');
 
         $this->debug($query);
 
@@ -591,7 +597,7 @@
                 'integer' => PDO::PARAM_INT,
             );
             $type = isset($map[$type]) ? $map[$type] : PDO::PARAM_STR;
-            return $this->dbh->quote($input, $type);
+            return strtr($this->dbh->quote($input, $type), array('?' => '??'));  // escape ?
         }
 
         return 'NULL';
diff --git a/program/include/rcube_imap.php b/program/include/rcube_imap.php
index 66b5c4b..ebf31d5 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') {
@@ -3291,11 +3297,8 @@
         }
 
         // Get folder rights (MYRIGHTS)
-        if ($acl && !$options['noselect']) {
-            // skip shared roots
-            if (!$options['is_root'] || $options['namespace'] == 'personal') {
-                $options['rights'] =  (array)$this->my_rights($folder);
-            }
+        if ($acl && ($rights = $this->my_rights($folder))) {
+            $options['rights'] = $rights;
         }
 
         // Set 'norename' flag
diff --git a/program/include/rcube_imap_generic.php b/program/include/rcube_imap_generic.php
index 25e6fc4..cce53ae 100644
--- a/program/include/rcube_imap_generic.php
+++ b/program/include/rcube_imap_generic.php
@@ -2402,15 +2402,22 @@
             $mode = 0;
         }
 
+        // Use BINARY extension when possible (and safe)
+        $binary     = $mode && preg_match('/^[0-9.]+$/', $part) && $this->hasCapability('BINARY');
+        $fetch_mode = $binary ? 'BINARY' : 'BODY';
+
         // format request
-        $reply_key = '* ' . $id;
         $key       = $this->nextTag();
-        $request   = $key . ($is_uid ? ' UID' : '') . " FETCH $id (BODY.PEEK[$part])";
+        $request   = $key . ($is_uid ? ' UID' : '') . " FETCH $id ($fetch_mode.PEEK[$part])";
 
         // send request
         if (!$this->putLine($request)) {
             $this->setError(self::ERROR_COMMAND, "Unable to send command: $request");
             return false;
+        }
+
+        if ($binary) {
+            $mode = -1;
         }
 
         // receive reply line
@@ -2457,13 +2464,13 @@
             $prev     = '';
 
             while ($bytes > 0) {
-                $line = $this->readLine(4096);
+                $line = $this->readLine(8192);
 
                 if ($line === NULL) {
                     break;
                 }
 
-                $len  = strlen($line);
+                $len = strlen($line);
 
                 if ($len > $bytes) {
                     $line = substr($line, 0, $bytes);
diff --git a/program/include/rcube_message.php b/program/include/rcube_message.php
index 6af1d01..fe2fcf3 100644
--- a/program/include/rcube_message.php
+++ b/program/include/rcube_message.php
@@ -494,8 +494,13 @@
                     }
 
                     // list as attachment as well
-                    if (!empty($mail_part->filename))
+                    if (!empty($mail_part->filename)) {
                         $this->attachments[] = $mail_part;
+                    }
+                    // list html part as attachment (here the part is most likely inside a multipart/related part)
+                    else if ($this->parse_alternative && ($secondary_type == 'html' && !$this->opt['prefer_html'])) {
+                        $this->attachments[] = $mail_part;
+                    }
                 }
                 // part message/*
                 else if ($primary_type == 'message') {
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_utils.php b/program/include/rcube_utils.php
index 9bedf21..c8457b7 100644
--- a/program/include/rcube_utils.php
+++ b/program/include/rcube_utils.php
@@ -221,6 +221,10 @@
         static $js_rep_table = false;
         static $xml_rep_table = false;
 
+        if (!is_string($str)) {
+            $str = strval($str);
+        }
+
         // encode for HTML output
         if ($enctype == 'html') {
             if (!$html_encode_arr) {
diff --git a/program/js/app.js b/program/js/app.js
index 48de217..2182a2b 100644
--- a/program/js/app.js
+++ b/program/js/app.js
@@ -669,7 +669,7 @@
           this.load_identity(props, 'edit-identity');
         else if (this.task == 'mail' && (cid = this.get_single_uid())) {
           url = { _mbox: this.env.mailbox };
-          url[this.env.mailbox == this.env.drafts_mailbox ? '_draft_uid' : '_uid'] = cid;
+          url[this.env.mailbox == this.env.drafts_mailbox && props != 'new' ? '_draft_uid' : '_uid'] = cid;
           this.goto_url('compose', url, true);
         }
         break;
diff --git a/program/steps/addressbook/edit.inc b/program/steps/addressbook/edit.inc
index 0f1fd66..90069a7 100644
--- a/program/steps/addressbook/edit.inc
+++ b/program/steps/addressbook/edit.inc
@@ -117,9 +117,6 @@
 
     $record = rcmail_get_edit_record();
 
-    // add some labels to client
-    $RCMAIL->output->add_label('noemailwarning', 'nonamewarning');
-
     // copy (parsed) address template to client
     if (preg_match_all('/\{([a-z0-9]+)\}([^{]*)/i', $RCMAIL->config->get('address_template', ''), $templ, PREG_SET_ORDER))
       $RCMAIL->output->set_env('address_template', $templ);
diff --git a/program/steps/addressbook/import.inc b/program/steps/addressbook/import.inc
index 654a336..15e04b8 100644
--- a/program/steps/addressbook/import.inc
+++ b/program/steps/addressbook/import.inc
@@ -189,32 +189,36 @@
         $IMPORT_STATS->names = array();
         $IMPORT_STATS->skipped_names = array();
         $IMPORT_STATS->count = count($vcards);
-        $IMPORT_STATS->inserted = $IMPORT_STATS->skipped = $IMPORT_STATS->nomail = $IMPORT_STATS->errors = 0;
+        $IMPORT_STATS->inserted = $IMPORT_STATS->skipped = $IMPORT_STATS->invalid = $IMPORT_STATS->errors = 0;
 
         if ($replace) {
             $CONTACTS->delete_all();
         }
 
         foreach ($vcards as $vcard) {
-            $email    = $vcard->email[0];
             $a_record = $vcard->get_assoc();
 
-            // skip entries without an e-mail address or invalid
-            if (empty($email) || !$CONTACTS->validate($a_record, true)) {
-                $IMPORT_STATS->nomail++;
+            // skip invalid (incomplete) entries
+            if (!$CONTACTS->validate($a_record, true)) {
+                $IMPORT_STATS->invalid++;
                 continue;
             }
 
             // We're using UTF8 internally
+            $email = $vcard->email[0];
             $email = rcube_idn_to_utf8($email);
 
-            if (!$replace && $email) {
+            if (!$replace) {
+                $existing = null;
                 // compare e-mail address
-                $existing = $CONTACTS->search('email', $email, 1, false);
-                if (!$existing->count && $vcard->displayname) {  // compare display name
+                if ($email) {
+                    $existing = $CONTACTS->search('email', $email, 1, false);
+                }
+                // compare display name if email not found
+                if ((!$existing || !$existing->count) && $vcard->displayname) {
                     $existing = $CONTACTS->search('name', $vcard->displayname, 1, false);
                 }
-                if ($existing->count) {
+                if ($existing && $existing->count) {
                     $IMPORT_STATS->skipped++;
                     $IMPORT_STATS->skipped_names[] = $vcard->displayname ? $vcard->displayname : $email;
                     continue;
diff --git a/program/steps/addressbook/save.inc b/program/steps/addressbook/save.inc
index 3bfce3b..887e498 100644
--- a/program/steps/addressbook/save.inc
+++ b/program/steps/addressbook/save.inc
@@ -161,7 +161,6 @@
     $source = $orig_source;
 
   // show notice if existing contacts with same e-mail are found
-  $existing = false;
   foreach ($CONTACTS->get_col_values('email', $a_record, true) as $email) {
       if ($email && ($res = $CONTACTS->search('email', $email, 1, false, true)) && $res->count) {
           $OUTPUT->show_message('contactexists', 'notice', null, false);
diff --git a/program/steps/mail/compose.inc b/program/steps/mail/compose.inc
index 29e1267..04efe7d 100644
--- a/program/steps/mail/compose.inc
+++ b/program/steps/mail/compose.inc
@@ -1047,15 +1047,23 @@
 
 function rcmail_write_compose_attachments(&$message, $bodyIsHtml)
 {
-  global $RCMAIL, $COMPOSE;
+  global $RCMAIL, $COMPOSE, $compose_mode;
 
   $cid_map = $messages = array();
   foreach ((array)$message->mime_parts as $pid => $part)
   {
-    if (($part->ctype_primary != 'message' || !$bodyIsHtml) && $part->ctype_primary != 'multipart' && 
-        ($part->disposition == 'attachment' || ($part->disposition == 'inline' && $bodyIsHtml) || $part->filename)
-        && $part->mimetype != 'application/ms-tnef'
-    ) {
+    if ($part->disposition == 'attachment' || ($part->disposition == 'inline' && $bodyIsHtml) || $part->filename) {
+      if ($part->ctype_primary == 'message' || $part->ctype_primary == 'multipart') {
+        continue;
+      }
+      if ($part->mimetype == 'application/ms-tnef') {
+        continue;
+      }
+      // skip inline images when forwarding in plain text
+      if ($part->content_id && !$bodyIsHtml && $compose_mode == RCUBE_COMPOSE_FORWARD) {
+        continue;
+      }
+
       $skip = false;
       if ($part->mimetype == 'message/rfc822') {
         $messages[] = $part->mime_id;
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 73cc5e4..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.                |
@@ -170,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;
                     }
                 }
             }
@@ -183,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/includes/messagetoolbar.html b/skins/classic/includes/messagetoolbar.html
index 3f4995b..ecaf8f7 100644
--- a/skins/classic/includes/messagetoolbar.html
+++ b/skins/classic/includes/messagetoolbar.html
@@ -45,7 +45,7 @@
   <ul class="toolbarmenu">
     <li><roundcube:button class="printlink" command="print" label="printmessage" classAct="printlink active" /></li>
     <li><roundcube:button class="downloadlink" command="download" label="emlsave" classAct="downloadlink active" /></li>
-    <li><roundcube:button class="editlink" command="edit" label="editasnew" classAct="editlink active" /></li>
+    <li><roundcube:button class="editlink" command="edit" prop="new" label="editasnew" classAct="editlink active" /></li>
     <li class="separator_below"><roundcube:button class="sourcelink" command="viewsource" label="viewsource" classAct="sourcelink active" /></li>
     <li><roundcube:button class="openlink" command="open" label="openinextwin" target="_blank" classAct="openlink active" /></li>
     <roundcube:container name="messagemenu" id="messagemenu" />
diff --git a/skins/larry/includes/mailtoolbar.html b/skins/larry/includes/mailtoolbar.html
index f750e06..60cebe0 100644
--- a/skins/larry/includes/mailtoolbar.html
+++ b/skins/larry/includes/mailtoolbar.html
@@ -37,7 +37,7 @@
   <ul class="toolbarmenu iconized">
 	<li><roundcube:button command="print" label="printmessage" class="icon" classAct="icon active" innerclass="icon print" /></li>
 	<li><roundcube:button command="download" label="emlsave" class="icon" classAct="icon active" innerclass="icon download" /></li>
-	<li><roundcube:button command="edit" label="editasnew" class="icon" classAct="icon active" innerclass="icon edit" /></li>
+	<li><roundcube:button command="edit" prop="new" label="editasnew" class="icon" classAct="icon active" innerclass="icon edit" /></li>
 	<li><roundcube:button command="viewsource" label="viewsource" class="icon" classAct="icon active" innerclass="icon viewsource" /></li>
 	<li><roundcube:button command="open" label="openinextwin" target="_blank" class="icon" classAct="icon active" innerclass="icon extwin" /></li>
 	<roundcube:container name="messagemenu" id="messagemenu" />
diff --git a/skins/larry/mail.css b/skins/larry/mail.css
index 496cbbd..04f56e1 100644
--- a/skins/larry/mail.css
+++ b/skins/larry/mail.css
@@ -837,6 +837,7 @@
 #messageheader {
 	position: relative;
 	height: auto;
+	min-height: 52px;
 	margin: 0 8px 0 0;
 	padding: 0 0 0 72px;
 	border-bottom: 2px solid #f0f0f0;
diff --git a/skins/larry/ui.js b/skins/larry/ui.js
index e3b5eef..c221b86 100644
--- a/skins/larry/ui.js
+++ b/skins/larry/ui.js
@@ -144,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();
       }
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])");
+    }
+
 }

--
Gitblit v1.9.1