diff options
41 files changed, 554 insertions, 160 deletions
@@ -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 7e07341a9..a6661c323 100644 --- a/config/main.inc.php.dist +++ b/config/main.inc.php.dist @@ -78,7 +78,7 @@ $rcmail_config['default_host'] = ''; // 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 06b644b34..00915cc20 100644 --- a/plugins/managesieve/tests/Parser.php +++ b/plugins/managesieve/tests/Parser.php @@ -31,7 +31,7 @@ class Parser extends PHPUnit_Framework_TestCase $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 000000000..cb7faa7f8 --- /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 37c79315d..8d7b433af 100644 --- a/plugins/password/config.inc.php.dist +++ b/plugins/password/config.inc.php.dist @@ -36,7 +36,8 @@ $rcmail_config['password_db_dsn'] = ''; // 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 @@ $rcmail_config['password_db_dsn'] = ''; // 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 449e2df5b..8bdcabf83 100644 --- a/plugins/password/drivers/sql.php +++ b/plugins/password/drivers/sql.php @@ -40,13 +40,38 @@ class rcube_sql_password // 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 @@ class rcube_sql_password $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 b2547e07f..f9eca9633 100644 --- a/plugins/password/drivers/virtualmin.php +++ b/plugins/password/drivers/virtualmin.php @@ -48,6 +48,10 @@ class rcube_virtualmin_password $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 02f38e647..5a9a1fa86 100644 --- a/program/include/rcmail.php +++ b/program/include/rcmail.php @@ -1774,10 +1774,7 @@ class rcmail extends rcube $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 @@ class rcmail extends rcube $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 1740a6096..35c69729b 100644 --- a/program/include/rcube_charset.php +++ b/program/include/rcube_charset.php @@ -86,7 +86,7 @@ class rcube_charset * 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 @@ class rcube_charset { 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 @@ class rcube_charset 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 @@ class rcube_charset // 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 @@ class rcube_charset 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 66b5c4bd6..0b2f84d4f 100644 --- a/program/include/rcube_imap.php +++ b/program/include/rcube_imap.php @@ -1434,6 +1434,12 @@ class rcube_imap extends rcube_storage $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 @@ class rcube_imap extends rcube_storage } $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 c3cfabc3a..25e6fc421 100644 --- a/program/include/rcube_imap_generic.php +++ b/program/include/rcube_imap_generic.php @@ -530,6 +530,7 @@ class rcube_imap_generic } else { $authc = $user; + $user = ''; } $auth_sasl = Auth_SASL::factory('digestmd5'); $reply = base64_encode($auth_sasl->getResponse($authc, $pass, @@ -568,6 +569,7 @@ class rcube_imap_generic } 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 cc1615d35..334ec8530 100644 --- a/program/include/rcube_result_index.php +++ b/program/include/rcube_result_index.php @@ -61,10 +61,14 @@ class rcube_result_index 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 214aec217..09fa46522 100644 --- a/program/include/rcube_result_thread.php +++ b/program/include/rcube_result_thread.php @@ -61,6 +61,8 @@ class rcube_result_thread // ...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 c15305c08..4577c6df5 100644 --- a/program/include/rcube_shared.inc +++ b/program/include/rcube_shared.inc @@ -255,6 +255,21 @@ function asciiwords($str, $css_id = false, $replace_with = '') /** + * 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 2bfd474c6..49b312c5c 100644 --- a/program/include/rcube_vcard.php +++ b/program/include/rcube_vcard.php @@ -555,6 +555,7 @@ class rcube_vcard 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 e84124b7c..1457382a4 100644 --- a/program/js/list.js +++ b/program/js/list.js @@ -231,8 +231,8 @@ focus: function(e) } } - // 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 45582d40d..8bf80a6ee 100644 --- a/program/steps/mail/func.inc +++ b/program/steps/mail/func.inc @@ -1055,12 +1055,17 @@ function rcmail_message_full_headers($attrib, $headers=NULL) 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 4d6627393..cad113f68 100644 --- a/program/steps/mail/headers.inc +++ b/program/steps/mail/headers.inc @@ -24,7 +24,8 @@ if ($uid = get_input_value('_uid', RCUBE_INPUT_POST)) $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 670680959..db5424b3b 100644 --- a/program/steps/mail/search.inc +++ b/program/steps/mail/search.inc @@ -100,7 +100,7 @@ $search = isset($srch) ? trim($srch) : trim($str); 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 577751742..5c2c6de20 100644 --- a/program/steps/mail/sendmail.inc +++ b/program/steps/mail/sendmail.inc @@ -93,9 +93,8 @@ function rcmail_get_identity($id) * 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 @@ function rcmail_fix_emoticon_paths(&$mime_message) } $mime_message->setHTMLBody($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); - return $body; + 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 @@ if ($isHtml) { // 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 6ca704998..3231ed644 100644 --- a/program/steps/settings/folders.inc +++ b/program/steps/settings/folders.inc @@ -85,6 +85,11 @@ else if ($RCMAIL->action == 'delete-folder') 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 @@ function rcmail_rename_folder($oldname, $newname) 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 09f76ac27..877b0fbbe 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 && strlen($path) && (!strlen($old_imap) || $old_imap != $name_imap)) } } -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 (!$error) { } 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 @@ else if (!$error) { } $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 714540b78..c03376e4a 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" /> -</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=" " /> + <roundcube:object name="mailboxlist" id="mailboxlist" maxlength="25" /> </div> +<div class="boxfooter"></div> </div> </div> @@ -57,14 +55,5 @@ 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 9af45f432..918e3092a 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" /> -</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=" " /> + <roundcube:object name="mailboxlist" id="mailboxlist" folder_filter="mail" /> </div> +<div class="boxfooter"></div> </div> </div> @@ -63,15 +61,6 @@ 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 fe087aece..74bc0d7d8 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 024c35bb2..935a504fe 100644 --- a/skins/larry/ie7hacks.css +++ b/skins/larry/ie7hacks.css @@ -29,7 +29,7 @@ a.deletebutton, .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 @@ a.deletebutton, .pagenav a.button, .pagenav a.button span.inner, -#messagepreviewheader .iconlink, +#messageheader .iconlink, #uploadform a.iconlink { display: inline; } @@ -67,7 +67,7 @@ a.deletebutton, text-align: left; } -#messagepreviewheader .iconlink { +#messageheader .iconlink { color: #fff; height: 14px; } diff --git a/skins/larry/iehacks.css b/skins/larry/iehacks.css index 288202111..bba93dc33 100644 --- a/skins/larry/iehacks.css +++ b/skins/larry/iehacks.css @@ -143,7 +143,7 @@ ul.toolbarmenu li a.active:hover, 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 Binary files differindex 276f1974f..25a81418d 100644 --- a/skins/larry/images/contactpic_32px.png +++ b/skins/larry/images/contactpic_32px.png diff --git a/skins/larry/images/contactpic_48px.png b/skins/larry/images/contactpic_48px.png Binary files differnew file mode 100644 index 000000000..9cd3bceaf --- /dev/null +++ b/skins/larry/images/contactpic_48px.png diff --git a/skins/larry/mail.css b/skins/larry/mail.css index 61858406b..496cbbd15 100644 --- a/skins/larry/mail.css +++ b/skins/larry/mail.css @@ -38,10 +38,6 @@ bottom: 28px; } -#mailview-top.fullheight { - border-radius: 4px 4px 0 0; -} - #mailview-bottom { position: absolute; left: 0; @@ -50,6 +46,10 @@ height: 26px; } +#mailview-top.fullheight { + border-radius: 4px 4px 0 0; +} + #folderlist-header { width: 100%; height: 12px; @@ -341,7 +341,6 @@ a.iconbutton.threadmode.selected { #messagetoolbar { position: absolute; top: -6px; - right: 390px; left: 0; height: 40px; white-space: nowrap; @@ -362,7 +361,7 @@ a.iconbutton.threadmode.selected { position: absolute; right: 0; top: 0; - width: 240px; + width: 400px; } #mailpreviewtoggle { @@ -676,15 +675,14 @@ a.iconbutton.threadmode.selected { #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 @@ h2.subject { 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 @@ h3.subject { 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 @@ h3.subject { #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 @@ div.hide-headers { 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 @@ div.hide-headers { 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 @@ div.hide-headers { min-height: 200px; background: #f0f0f0; padding: 8px; + border-radius: 4px; } #messagebody { diff --git a/skins/larry/styles.css b/skins/larry/styles.css index 199969810..f2d4888b1 100644 --- a/skins/larry/styles.css +++ b/skins/larry/styles.css @@ -661,7 +661,7 @@ a.iconlink.upload { 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 c54bdec17..8db2c5f63 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 143fb375f..4f1dd8a05 100644 --- a/skins/larry/svggradients.css +++ b/skins/larry/svggradients.css @@ -133,7 +133,7 @@ ul.toolbarmenu li a.active:hover, 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 1becd711a..89b7bd808 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" class="uibox" style="top: 42px"> + +<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="mailview-right"> +<div id="contactphoto"><roundcube:object name="contactphoto" /></div> -<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" /> +<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="&gt;" /> </div> -<div id="contactphoto"><roundcube:object name="contactphoto" /></div> -</div> +</div><!-- end messageheader --> -<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 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><!-- end mailview-top --> +</div><!-- end messagecontent --> -<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 70181f174..2f5243200 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 b53683ec1..74c414b0d 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 474c480e8..c221b8681 100644 --- a/skins/larry/ui.js +++ b/skins/larry/ui.js @@ -74,9 +74,8 @@ function rcube_mail_ui() 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 @@ function rcube_mail_ui() 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(); } @@ -164,6 +163,12 @@ function rcube_mail_ui() } } + // 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 $('.tabbed').each(function(idx, elem){ init_tabs(elem); }) @@ -315,7 +320,6 @@ function rcube_mail_ui() */ 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 @@ function rcube_mail_ui() { $('#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 9e3fad4d3..1fd1654dc 100644 --- a/tests/Framework/Charset.php +++ b/tests/Framework/Charset.php @@ -14,15 +14,149 @@ class Framework_Charset extends PHPUnit_Framework_TestCase 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 99ef829da..0394cd025 100644 --- a/tests/Framework/Shared.php +++ b/tests/Framework/Shared.php @@ -201,4 +201,32 @@ class Framework_Shared extends PHPUnit_Framework_TestCase } + /** + * 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 a830c2cbc..56ca9d721 100644 --- a/tests/Framework/VCard.php +++ b/tests/Framework/VCard.php @@ -49,6 +49,20 @@ class Framework_VCard extends PHPUnit_Framework_TestCase $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 000000000..c3a805009 --- /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
|