pull/24/head
Paweł Słowik 14 years ago
commit 2cdaa79dce

@ -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'

@ -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;

@ -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')) {

@ -0,0 +1,3 @@
require ["variables"];
set "EDITOR" "Roundcube";
set "EDITOR_VERSION" "123";

@ -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;

@ -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

@ -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);
}

@ -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);
}

@ -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'
);

@ -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') {

@ -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);

@ -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') {

@ -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;
}

@ -254,6 +254,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
*

@ -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]))

@ -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))

@ -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;
}

@ -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]+/',

@ -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);

@ -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',

@ -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;
}

@ -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');
}

@ -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>

@ -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 />

@ -34,7 +34,6 @@
position: absolute;
top: -6px;
left: 0;
right: 260px;
height: 40px;
white-space: nowrap;
z-index: 10;

@ -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;
}

@ -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);
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

@ -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 {

@ -661,7 +661,7 @@ a.iconlink.upload {
left: 0;
bottom: 0;
width: 100%;
min-width: 1150px;
min-width: 1024px;
}
.scroller {

@ -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");

@ -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);
}

@ -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="&amp;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 -->

@ -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 -->

@ -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">

@ -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;
}

@ -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));
}
}

@ -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])");
}
}

@ -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'));

@ -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
Loading…
Cancel
Save