Thomas Bruederli
2012-09-17 e8e2e76ed987d911ec878345e88d3c611a4b7b32
Merge branch 'master' of github.com:roundcube/roundcubemail
1 files added
31 files modified
512 ■■■■ changed files
CHANGELOG 9 ●●●●● patch | view | raw | blame | history
config/main.inc.php.dist 2 ●●● patch | view | raw | blame | history
installer/check.php 12 ●●●● patch | view | raw | blame | history
plugins/password/config.inc.php.dist 10 ●●●●● patch | view | raw | blame | history
plugins/password/drivers/sql.php 35 ●●●● patch | view | raw | blame | history
program/include/rcmail.php 8 ●●●● patch | view | raw | blame | history
program/include/rcube_charset.php 19 ●●●● patch | view | raw | blame | history
program/include/rcube_imap.php 15 ●●●●● patch | view | raw | blame | history
program/include/rcube_imap_generic.php 15 ●●●● patch | view | raw | blame | history
program/include/rcube_message.php 7 ●●●● patch | view | raw | blame | history
program/include/rcube_result_index.php 4 ●●●● patch | view | raw | blame | history
program/include/rcube_result_thread.php 2 ●●●●● patch | view | raw | blame | history
program/include/rcube_shared.inc 15 ●●●●● patch | view | raw | blame | history
program/include/rcube_utils.php 4 ●●●● patch | view | raw | blame | history
program/include/rcube_vcard.php 1 ●●●● patch | view | raw | blame | history
program/js/app.js 2 ●●● patch | view | raw | blame | history
program/steps/addressbook/edit.inc 3 ●●●●● patch | view | raw | blame | history
program/steps/addressbook/import.inc 20 ●●●●● patch | view | raw | blame | history
program/steps/addressbook/save.inc 1 ●●●● patch | view | raw | blame | history
program/steps/mail/compose.inc 18 ●●●● patch | view | raw | blame | history
program/steps/mail/search.inc 2 ●●● patch | view | raw | blame | history
program/steps/mail/sendmail.inc 55 ●●●●● patch | view | raw | blame | history
program/steps/settings/folders.inc 10 ●●●●● patch | view | raw | blame | history
program/steps/settings/save_folder.inc 9 ●●●● patch | view | raw | blame | history
skins/classic/includes/messagetoolbar.html 2 ●●● patch | view | raw | blame | history
skins/larry/includes/mailtoolbar.html 2 ●●● patch | view | raw | blame | history
skins/larry/mail.css 1 ●●●● patch | view | raw | blame | history
skins/larry/ui.js 2 ●●● patch | view | raw | blame | history
tests/Framework/Charset.php 140 ●●●●● patch | view | raw | blame | history
tests/Framework/Shared.php 28 ●●●●● patch | view | raw | blame | history
tests/Framework/VCard.php 14 ●●●●● patch | view | raw | blame | history
tests/src/photo.vcf 45 ●●●●● patch | view | raw | blame | history
CHANGELOG
@@ -1,6 +1,15 @@
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)
- Remove (too big) min-width on mail screen
- Add full headers view in message preview window (#1488538)
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;
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);
        }
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;
plugins/password/drivers/sql.php
@@ -40,12 +40,37 @@
        // crypted password
        if (strpos($sql, '%c') !== FALSE) {
            $salt = '';
            if (CRYPT_MD5) {
                // Always use eight salt characters for MD5 (#1488136)
            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;
            } else if (CRYPT_STD_DES) {
                $salt_hashindicator = '$1$';
                break;
            case 'des':
                $len = 2;
            } else {
                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;
            }
@@ -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
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);
        }
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,17 +231,28 @@
        // 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))) {
                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;
                }
            }
        }
        }
        // convert charset using bundled classes/functions
        if ($to == 'UTF-8') {
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
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);
@@ -2400,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
@@ -2455,7 +2464,7 @@
            $prev     = '';
            while ($bytes > 0) {
                $line = $this->readLine(4096);
                $line = $this->readLine(8192);
                if ($line === NULL) {
                    break;
program/include/rcube_message.php
@@ -494,9 +494,14 @@
                    }
                    // 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') {
                    $this->parse_structure($mail_part, true);
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') {
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;
            }
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
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) {
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]))
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;
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);
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
                if ($email) {
                $existing = $CONTACTS->search('email', $email, 1, false);
                if (!$existing->count && $vcard->displayname) {  // compare display name
                }
                // 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;
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);
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;
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);
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',
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) {
@@ -402,6 +407,11 @@
        }
        $RCMAIL->user->save_prefs(array('message_threading' => $a_threaded));
        // #1488692: update session
        if ($_SESSION['mbox'] === $oldname) {
            $_SESSION['mbox'] = $newname;
        }
        return true;
    }
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.                |
@@ -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');
        }
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" />
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" />
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;
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();
      }
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));
    }
}
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])");
    }
}
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'));
tests/src/photo.vcf
New file
@@ -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