38 changes: 16 additions & 22 deletions program/js/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -405,16 +405,8 @@ function rcube_webmail()
this.enable_command('image-scale', 'image-rotate', !!/^image\//.test(this.env.mimetype));

// Mozilla's PDF.js viewer does not allow printing from host page (#5125)
// to minimize user confusion we disable the Print button
if (bw.mz && this.env.mimetype == 'application/pdf') {
n = 0; // there will be two onload events, first for the preload page
$(this.gui_objects.messagepartframe).on('load', function() {
if (n++) try { if (this.contentWindow.document) ref.enable_command('print', true); }
catch (e) {/* ignore */}
});
}
else
this.enable_command('print', true);
// to minimize user confusion we disable the Print button on Firefox < 75
this.enable_command('print', this.env.mimetype != 'application/pdf' || !bw.mz || bw.vendver >= 75);

if (this.env.is_message) {
this.enable_command('reply', 'reply-all', 'edit', 'viewsource',
Expand Down Expand Up @@ -735,7 +727,7 @@ function rcube_webmail()
/*********************************************************/

// execute a specific command on the web client
this.command = function(command, props, obj, event)
this.command = function(command, props, obj, event, allow_disabled)
{
var ret;

Expand All @@ -753,7 +745,7 @@ function rcube_webmail()
}

// command not supported or allowed
if (!this.commands[command]) {
if (!allow_disabled && !this.commands[command]) {
// pass command to parent window
if (this.is_framed())
parent.rcmail.command(command, props);
Expand Down Expand Up @@ -3198,12 +3190,13 @@ function rcube_webmail()
this.copy_messages = function(mbox, event, uids)
{
if (mbox && typeof mbox === 'object') {
if (mbox.uids) uids = mbox.uids;
mbox = mbox.id;
}
else if (!mbox) {
uids = this.env.uid ? [this.env.uid] : this.message_list.get_selection();
return this.folder_selector(event, function(folder) {
ref.copy_messages(folder, null, uids);
return this.folder_selector(event, function(folder, obj) {
ref.command('copy', {id: folder, uids: uids}, obj, event, true);
});
}

Expand All @@ -3225,12 +3218,13 @@ function rcube_webmail()
this.move_messages = function(mbox, event, uids)
{
if (mbox && typeof mbox === 'object') {
if (mbox.uids) uids = mbox.uids;
mbox = mbox.id;
}
else if (!mbox) {
uids = this.env.uid ? [this.env.uid] : this.message_list.get_selection();
return this.folder_selector(event, function(folder) {
ref.move_messages(folder, null, uids);
return this.folder_selector(event, function(folder, obj) {
ref.command('move', {id: folder, uids: uids}, obj, event, true);
});
}

Expand Down Expand Up @@ -3905,7 +3899,7 @@ function rcube_webmail()
$.each(['to', 'cc', 'bcc'], function(i,field) {
var pos, rcpt, val = $.trim($('[name="_' + field + '"]').val());
while (val.length && rcube_check_email(val, true)) {
rcpt = RegExp.$2;
rcpt = RegExp.$2.replace(/^<+/, '').replace(/>+$/, '');
recipients.push(rcpt);
val = val.substr(val.indexOf(rcpt) + rcpt.length + 1).replace(/^\s*,\s*/, '');
}
Expand Down Expand Up @@ -6078,10 +6072,10 @@ function rcube_webmail()
input.value = pre + to + end;

// set caret to insert pos
this.set_caret_pos(input, p + to.length);
this.set_caret_pos(input, cpos + to.length - from.length);

// run onchange action on the element
$(input).change();
$(input).trigger('change', [true]);
};

this.ksearch_click = function(node)
Expand Down Expand Up @@ -6758,7 +6752,7 @@ function rcube_webmail()
var key = 'G'+prop.source+prop.id,
link = $('<a>').attr({href: '#', rel: prop.source + ':' + prop.id})
.click(function() { return ref.command('listgroup', prop, this); })
.html(prop.name);
.text(prop.name);

this.env.contactfolders[key] = this.env.contactgroups[key] = prop;
this.treelist.insert({ id:key, html:link, classes:['contactgroup'] }, prop.source, 'contactgroup');
Expand Down Expand Up @@ -6794,11 +6788,11 @@ function rcube_webmail()
newnode.id = newkey;
newnode.html = $('<a>').attr({href: '#', rel: prop.source + ':' + prop.newid})
.click(function() { return ref.command('listgroup', newprop, this); })
.html(prop.name);
.text(prop.name);
}
// update displayed group name
else {
$(this.treelist.get_item(key)).children().first().html(prop.name);
$(this.treelist.get_item(key)).children().first().text(prop.name);
this.env.contactfolders[key].name = this.env.contactgroups[key].name = prop.name;

if (prop.source == this.env.source && prop.id == this.env.group)
Expand Down
4 changes: 2 additions & 2 deletions program/js/editor.js
Original file line number Diff line number Diff line change
Expand Up @@ -331,7 +331,7 @@ function rcube_text_editor(config, id)
if (is_sig)
data = data.replace(sig_mark, "\n" + signature.text);

input.val(data).focus();
input.val(data).focus().trigger('input');
rcmail.set_caret_pos(input.get(0), 0);
};

Expand Down Expand Up @@ -664,7 +664,7 @@ function rcube_text_editor(config, id)
title: rcmail.get_label('select' + type),
width: 500,
html: '<div id="image-selector" class="image-selector file-upload"><ul id="image-selector-list" class="attachmentslist"></ul></div>',
buttons: [{text: 'Cancel', onclick: function() { ref.file_browser_close(); }}]
buttons: [{text: rcmail.get_label('close'), onclick: function() { ref.file_browser_close(); }}]
});

rcmail.env.file_browser_field = field_name;
Expand Down
2 changes: 1 addition & 1 deletion program/lib/Roundcube/bootstrap.php
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@
}

// framework constants
define('RCUBE_VERSION', '1.4.3');
define('RCUBE_VERSION', '1.4.4');
define('RCUBE_CHARSET', 'UTF-8');
define('RCUBE_TEMP_FILE_PREFIX', 'RCMTEMP');

Expand Down
2 changes: 1 addition & 1 deletion program/lib/Roundcube/rcube_config.php
Original file line number Diff line number Diff line change
Expand Up @@ -592,7 +592,7 @@ public function keyservers()
$list = (array) $this->prop['keyservers'];

foreach ($list as $idx => $host) {
if (!preg_match('|^[a-z]://|', $host)) {
if (!preg_match('|^[a-z]+://|', $host)) {
$host = "https://$host";
}

Expand Down
8 changes: 5 additions & 3 deletions program/lib/Roundcube/rcube_image.php
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,8 @@ public function resize($size, $filename = null, $browser_compat = false)
'size' => $width . 'x' . $height,
);

$result = rcube::exec($convert . ' 2>&1 -flatten -auto-orient -colorspace sRGB -strip'
$result = rcube::exec(escapeshellcmd($convert)
. ' 2>&1 -flatten -auto-orient -colorspace sRGB -strip'
. ' -quality {quality} -resize {size} {intype}:{in} {type}:{out}', $p);
}
// use PHP's Imagick class
Expand Down Expand Up @@ -323,7 +324,8 @@ public function convert($type, $filename = null)
$p['out'] = $filename;
$p['type'] = self::$extensions[$type];

$result = rcube::exec($convert . ' 2>&1 -colorspace sRGB -strip -flatten -quality 75 {in} {type}:{out}', $p);
$result = rcube::exec(escapeshellcmd($convert)
. ' 2>&1 -colorspace sRGB -strip -flatten -quality 75 {in} {type}:{out}', $p);

if ($result === '') {
chmod($filename, 0600);
Expand Down Expand Up @@ -419,7 +421,7 @@ private function identify()
// use ImageMagick in command line
if ($cmd = $rcube->config->get('im_identify_path')) {
$args = array('in' => $this->image_file, 'format' => "%m %[fx:w] %[fx:h]");
$id = rcube::exec($cmd. ' 2>/dev/null -format {format} {in}', $args);
$id = rcube::exec(escapeshellcmd($cmd) . ' 2>/dev/null -format {format} {in}', $args);

if ($id) {
return explode(' ', strtolower($id));
Expand Down
13 changes: 11 additions & 2 deletions program/lib/Roundcube/rcube_imap.php
Original file line number Diff line number Diff line change
Expand Up @@ -1900,8 +1900,11 @@ public function get_message($uid, $folder = null)
}

// Check internal cache
if (!empty($this->icache['message'])) {
if (($headers = $this->icache['message']) && $headers->uid == $uid) {
if (!empty($this->icache['message']) && ($headers = $this->icache['message'])) {
// Make sure the folder and UID is what we expect.
// In case when the same process works with folders that are personal
// and shared two folders can contain the same UIDs.
if ($headers->uid == $uid && $headers->folder == $folder) {
return $headers;
}
}
Expand Down Expand Up @@ -3293,6 +3296,12 @@ public function create_folder($folder, $subscribe = false, $type = null, $nosele

$result = $this->conn->createFolder($folder, $type ? array("\\" . ucfirst($type)) : null);

// Folder creation may fail when specific special-use flag is not supported.
// Try to create it anyway with no flag specified (#7147)
if (!$result && $type) {
$result = $this->conn->createFolder($folder);
}

// try to subscribe it
if ($result) {
// clear cache
Expand Down
58 changes: 34 additions & 24 deletions program/lib/Roundcube/rcube_imap_generic.php
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,38 @@ protected function readLine($size = 1024)
return $line;
}

/**
* Reads a line of data from the connection stream inluding all
* string continuation literals.
*
* @param int $size Buffer size
*
* @return string Line of text response
*/
protected function readFullLine($size = 1024)
{
$line = $this->readLine($size);

// include all string literels untile the real end of "line"
while (preg_match('/\{([0-9]+)\}\r\n$/', $line, $m)) {
$bytes = $m[1];
$out = '';

while (strlen($out) < $bytes) {
$out = $this->readBytes($bytes);
if ($out === null) {
break;
}

$line .= $out;
}

$line .= $this->readLine($size);
}

return $line;
}

/**
* Reads more data from the connection stream when provided
* data contain string literal
Expand Down Expand Up @@ -2397,7 +2429,7 @@ public function fetch($mailbox, $message_set, $is_uid = false, $query_items = ar
}

do {
$line = $this->readLine(4096);
$line = $this->readFullLine(4096);

if (!$line) {
break;
Expand All @@ -2421,27 +2453,6 @@ public function fetch($mailbox, $message_set, $is_uid = false, $query_items = ar
$line = substr($line, strlen($m[0]) + 2);
$ln = 0;

// get complete entry
while (preg_match('/\{([0-9]+)\}\r\n$/', $line, $m)) {
$bytes = $m[1];
$out = '';

while (strlen($out) < $bytes) {
$out = $this->readBytes($bytes);
if ($out === null) {
break;
}
$line .= $out;
}

$str = $this->readLine(4096);
if ($str === false) {
break;
}

$line .= $str;
}

// Tokenize response and assign to object properties
while (list($name, $value) = $this->tokenizeResponse($line, 2)) {
if ($name == 'UID') {
Expand Down Expand Up @@ -3721,10 +3732,9 @@ public function execute($command, $arguments = array(), $options = 0, $filter =

// Parse response
do {
$line = $this->readLine(4096);
$line = $this->readFullLine(4096);

if ($response !== null) {
// TODO: Better string literals handling with filter
if (!$filter || preg_match($filter, $line)) {
$response .= $line;
}
Expand Down
16 changes: 16 additions & 0 deletions program/lib/Roundcube/rcube_plugin_api.php
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,14 @@ public function load_plugin($plugin_name, $force = false, $require = true)
$plugins_dir = unslashify($dir->path);
}

// Validate the plugin name to prevent from path traversal
if (preg_match('/[^a-zA-Z0-9_-]/', $plugin_name)) {
rcube::raise_error(array('code' => 520,
'file' => __FILE__, 'line' => __LINE__,
'message' => "Invalid plugin name: $plugin_name"), true, false);
return false;
}

// plugin already loaded?
if (!$this->plugins[$plugin_name]) {
$fn = "$plugins_dir/$plugin_name/$plugin_name.php";
Expand Down Expand Up @@ -283,6 +291,14 @@ public function get_info($plugin_name)
$fn = unslashify($dir->path) . "/$plugin_name/$plugin_name.php";
$info = false;

// Validate the plugin name to prevent from path traversal
if (preg_match('/[^a-zA-Z0-9_-]/', $plugin_name)) {
rcube::raise_error(array('code' => 520,
'file' => __FILE__, 'line' => __LINE__,
'message' => "Invalid plugin name: $plugin_name"), true, false);
return false;
}

if (!class_exists($plugin_name, false)) {
if (is_readable($fn)) {
include($fn);
Expand Down
9 changes: 5 additions & 4 deletions program/lib/Roundcube/rcube_washtml.php
Original file line number Diff line number Diff line change
Expand Up @@ -548,9 +548,6 @@ private function dumpHtml($node, $level = 20)
break;

case XML_CDATA_SECTION_NODE:
$dump .= $node->nodeValue;
break;

case XML_TEXT_NODE:
$dump .= htmlspecialchars($node->nodeValue, ENT_COMPAT | ENT_HTML401 | ENT_SUBSTITUTE, $this->config['charset']);
break;
Expand Down Expand Up @@ -595,7 +592,11 @@ public function wash($html)
$method = $this->is_xml ? 'loadXML' : 'loadHTML';

// DOMDocument does not support HTML5, try Masterminds parser if available
if (!$this->is_xml && class_exists('Masterminds\HTML5')) {
if (!$this->is_xml && class_exists('Masterminds\HTML5')
// HTML5 parser is slow with content that contains a lot of tags
// disable it for such cases (https://github.com/Masterminds/html5-php/issues/181)
&& substr_count($html, '<') < 10000
) {
try {
$html5 = new Masterminds\HTML5();
$node = $html5->loadHTML($this->fix_html5($html));
Expand Down
4 changes: 1 addition & 3 deletions program/steps/addressbook/groups.inc
Original file line number Diff line number Diff line change
Expand Up @@ -92,8 +92,6 @@ else if ($RCMAIL->action == 'group-create') {
}

if ($created && $OUTPUT->ajax_call) {
$created['name'] = rcube::Q($created['name']);

$OUTPUT->show_message('groupcreated', 'confirmation');
$OUTPUT->command('insert_contact_group', array('source' => $source) + $created);
}
Expand All @@ -120,7 +118,7 @@ else if ($RCMAIL->action == 'group-rename') {
if ($newname && $OUTPUT->ajax_call) {
$OUTPUT->show_message('grouprenamed', 'confirmation');
$OUTPUT->command('update_contact_group', array(
'source' => $source, 'id' => $gid, 'name' => rcube::Q($newname), 'newid' => $newgid));
'source' => $source, 'id' => $gid, 'name' => $newname, 'newid' => $newgid));
}
else if (!$newname) {
$OUTPUT->show_message($plugin['message'] ?: 'errorsaving', 'error');
Expand Down
4 changes: 2 additions & 2 deletions program/steps/addressbook/qrcode.inc
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,8 @@ function rcmail_contact_qrcode($contact)
$vcard = new rcube_vcard();

// QR code input is limited, use only common fields
$fields = array('firstname', 'surname', 'middlename', 'nickname', 'organization',
'prefix', 'suffix', 'phone', 'email', 'jobtitle');
$fields = array('name', 'firstname', 'surname', 'middlename', 'nickname',
'organization', 'phone', 'email', 'jobtitle', 'prefix', 'suffix');

foreach ($contact as $field => $value) {
list($field, $section) = explode(':', $field, 2);
Expand Down
86 changes: 50 additions & 36 deletions program/steps/mail/compose.inc
Original file line number Diff line number Diff line change
Expand Up @@ -520,13 +520,11 @@ function rcmail_prepare_message_body()
$isHtml = rcmail_compose_editor_mode();
$messages = array();

// save inline images to files (before HTML body washing)
if ($COMPOSE['mode'] == rcmail_sendmail::MODE_REPLY) {
rcmail_write_inline_attachments($MESSAGE);
}
// save attachments to files (before HTML body washing)
else {
rcmail_write_compose_attachments($MESSAGE, $isHtml);
// Create a (fake) image attachments map. We need it before we handle
// the message body. After that we'll go throughout the list and check
// which images were used in the body and attach them for real or skip.
if ($isHtml) {
$CID_MAP = rcmail_cid_map($MESSAGE);
}

// set is_safe flag (before HTML body washing)
Expand Down Expand Up @@ -595,6 +593,10 @@ function rcmail_prepare_message_body()
else if ($COMPOSE['mode'] == rcmail_sendmail::MODE_DRAFT || $COMPOSE['mode'] == rcmail_sendmail::MODE_EDIT) {
$body = rcmail_create_draft_body($body, $isHtml);
}

// Save forwarded files (or inline images) as attachments
// This will also update inline images location in the body
rcmail_write_compose_attachments($MESSAGE, $isHtml, $body);
}
// new message
else {
Expand Down Expand Up @@ -936,12 +938,12 @@ function rcmail_remove_signature($body)
return $body;
}

function rcmail_write_compose_attachments(&$message, $bodyIsHtml)
function rcmail_write_compose_attachments(&$message, $bodyIsHtml, &$message_body)
{
global $RCMAIL, $COMPOSE, $CID_MAP;

if ($message->pgp_mime || !empty($COMPOSE['forward_attachments'])) {
return $CID_MAP;
return;
}

$messages = array();
Expand All @@ -956,7 +958,12 @@ function rcmail_write_compose_attachments(&$message, $bodyIsHtml)
$messages[] = $part->mime_id;
}

if ($part->disposition == 'attachment' || ($part->disposition == 'inline' && $bodyIsHtml) || $part->filename) {
if (
$part->disposition == 'attachments'
|| ($part->disposition == 'inline' && $bodyIsHtml)
|| $part->filename
|| $part->mimetype == 'message/rfc822'
) {
// skip parts that aren't valid attachments
if ($part->ctype_primary == 'multipart' || $part->mimetype == 'application/ms-tnef') {
continue;
Expand All @@ -967,11 +974,6 @@ function rcmail_write_compose_attachments(&$message, $bodyIsHtml)
continue;
}

// skip inline images when forwarding in text mode
if ($part->content_id && $part->disposition == 'inline' && !$bodyIsHtml && $COMPOSE['mode'] == rcmail_sendmail::MODE_FORWARD) {
continue;
}

// skip version.txt parts of multipart/encrypted messages
if ($message->pgp_mime && $part->mimetype == 'application/pgp-encrypted' && $part->filename == 'version.txt') {
continue;
Expand All @@ -984,63 +986,75 @@ function rcmail_write_compose_attachments(&$message, $bodyIsHtml)
}
}

$replace = null;

// skip inline images when not used in the body
if ($part->disposition == 'inline') {
if (!$bodyIsHtml) {
continue;
}

$idx = $part->content_id ? ('cid:' . $part->content_id) : $part->content_location;

if ($idx && isset($CID_MAP[$idx]) && strpos($message_body, $CID_MAP[$idx]) !== false) {
$replace = $CID_MAP[$idx];
}
else {
continue;
}
}
// skip any other attachment on Reply
else if ($COMPOSE['mode'] == rcmail_sendmail::MODE_REPLY) {
continue;
}

if (($attachment = $loaded_attachments[rcmail_attachment_name($part) . $part->mimetype])
|| ($attachment = rcmail_save_attachment($message, $pid, $COMPOSE['id']))
) {
if ($bodyIsHtml && ($part->content_id || $part->content_location)) {
if ($replace) {
$url = sprintf('%s&_id=%s&_action=display-attachment&_file=rcmfile%s',
$RCMAIL->comm_path, $COMPOSE['id'], $attachment['id']);

if ($part->content_id)
$CID_MAP['cid:'.$part->content_id] = $url;
else
$CID_MAP[$part->content_location] = $url;
$message_body = str_replace($replace, $url, $message_body);
}
}
}
}

$COMPOSE['forward_attachments'] = true;

return $CID_MAP;
}

function rcmail_write_inline_attachments(&$message)
// Create a map of attachment content-id/content-locations
function rcmail_cid_map($message)
{
global $RCMAIL, $COMPOSE, $CID_MAP;

if ($message->pgp_mime) {
return $CID_MAP;
return array();
}

$messages = array();
$map = array();

foreach ((array) $message->mime_parts() as $pid => $part) {
if ($part->mimetype == 'message/rfc822') {
$messages[] = $part->mime_id;
}

if (($part->content_id || $part->content_location) && $part->filename) {
if ($part->content_id || $part->content_location) {
// skip attachments included in message/rfc822 attachment (#1486487, #1490607)
foreach ($messages as $mimeid) {
if (strpos($part->mime_id, $mimeid . '.') === 0) {
continue 2;
}
}

if ($attachment = rcmail_save_attachment($message, $pid, $COMPOSE['id'])) {
$url = sprintf('%s&_id=%s&_action=display-attachment&_file=rcmfile%s',
$RCMAIL->comm_path, $COMPOSE['id'], $attachment['id']);
$url = sprintf('RCMAP%s', md5($message->folder . '/' . $message->uid . '/' . $pid));
$idx = $part->content_id ? ('cid:' . $part->content_id) : $part->content_location;

if ($part->content_id)
$CID_MAP['cid:'.$part->content_id] = $url;
else
$CID_MAP[$part->content_location] = $url;
}
$map[$idx] = $url;
}
}

return $CID_MAP;
return $map;
}

// Creates attachment(s) from the forwarded message(s)
Expand Down
14 changes: 12 additions & 2 deletions program/steps/settings/save_folder.inc
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,12 @@ if (!$error && !strlen($old_imap)) {
}
else {
// show error message
$OUTPUT->show_message($plugin['message'] ?: 'errorsaving', 'error', null, false);
if (!empty($plugin['message'])) {
$OUTPUT->show_message($plugin['message'], 'error', null, false);
}
else {
$RCMAIL->display_server_error('errorsaving');
}
}
}
// update a mailbox
Expand Down Expand Up @@ -198,7 +203,12 @@ else if (!$error) {
}
else {
// show error message
$OUTPUT->show_message($plugin['message'] ?: 'errorsaving', 'error', null, false);
if (!empty($plugin['message'])) {
$OUTPUT->show_message($plugin['message'], 'error', null, false);
}
else {
$RCMAIL->display_server_error('errorsaving');
}
}
}

Expand Down
2 changes: 1 addition & 1 deletion public_html/index.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
/*
+-----------------------------------------------------------------------+
| Roundcube Webmail IMAP Client |
| Version 1.4.3 |
| Version 1.4.4 |
| |
| Copyright (C) The Roundcube Dev Team |
| |
Expand Down
4 changes: 2 additions & 2 deletions skins/elastic/styles/colors.less
Original file line number Diff line number Diff line change
Expand Up @@ -85,9 +85,9 @@
@color-list-border: @color-black-shade-bg;
@color-list-badge: #fff;
@color-list-badge-background: @color-main;
@color-list-recent: blue;
@color-list-recent: darken(@color-main, 20%);
@color-list-recent-badge: #fff;
@color-list-recent-badge-background: @color-list-recent;
@color-list-recent-badge-background: @color-main;

@color-list-pagenav: @color-black-shade-text;
@color-list-icon: fadeout(@color-list-secondary, 25%);
Expand Down
4 changes: 4 additions & 0 deletions skins/elastic/styles/print.less
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ body {
#print-layout {
margin: 1rem;

#logo {
max-height: 80px;
}

// Overwrites for mail message printouts

.image-attachment {
Expand Down
6 changes: 4 additions & 2 deletions skins/elastic/styles/widgets/editor.less
Original file line number Diff line number Diff line change
Expand Up @@ -88,11 +88,12 @@
right: 0;
top: 0;
position: absolute;
height: (@layout-header-height - .7rem);
height: (@layout-header-height - .75rem);
width: 1.25em;
margin: .25rem;
margin: 0 .25rem;
padding: .1rem .75rem;
cursor: pointer;
outline: 0;

&:before {
&:extend(.font-icon-class);
Expand Down Expand Up @@ -898,6 +899,7 @@ html.touch .mce-grid td {
font-family: monospace;
width: 100% !important;
padding-top: 2.5rem;
resize: none;
}

& > iframe { // e.g. mailvelope frame
Expand Down
16 changes: 9 additions & 7 deletions skins/elastic/styles/widgets/lists.less
Original file line number Diff line number Diff line change
Expand Up @@ -478,10 +478,6 @@ ul.treelist {
}
}

&.recent {
color: @color-list-recent;
}

.unreadcount {
position: absolute;
top: 0;
Expand All @@ -502,9 +498,15 @@ ul.treelist {
}
}

&.recent > .unreadcount {
background: @color-list-recent-badge-background;
color: @color-list-recent-badge;
&.recent {
& > a {
color: @color-list-recent;

& > .unreadcount {
background: @color-list-recent-badge-background;
color: @color-list-recent-badge;
}
}
}

&.root {
Expand Down
65 changes: 39 additions & 26 deletions skins/elastic/ui.js
Original file line number Diff line number Diff line change
Expand Up @@ -3173,8 +3173,8 @@ function rcube_elastic_ui()

return result.recipients.length > 0;
},
parse_func = function(e) {
var paste, value = this.value;
parse_func = function(e, ac) {
var last, paste, value = this.value;

// On paste the text is not yet in the input we have to use clipboard.
// Also because on paste new-line characters are replaced by spaces (#6460)
Expand All @@ -3185,6 +3185,16 @@ function rcube_elastic_ui()
value = value.substring(0, this.selectionStart) + paste + value.substring(this.selectionEnd);
e.preventDefault();
}
// #7231: When clicking on autocompletion list a change event
// is fired twice. We have to remove last recipient box if it is
// the same recpient (with incomplete email address).
// FIXME: Anyone with a better solution?
else if (ac) {
last = list.find('li.recipient').last();
if (last.length && this.value.indexOf(last.text().replace(/[ ,]+$/, '')) > -1) {
last.remove();
}
}

update_func(value);
},
Expand Down Expand Up @@ -3741,37 +3751,40 @@ function rcube_elastic_ui()
*/
function textarea_autoresize_init(textarea)
{
var resize = function(e) {
clearTimeout(env.textarea_timer);
env.textarea_timer = setTimeout(function() {
var area = $(e.target),
initial_height = area.data('initial-height'),
scroll_height = area[0].scrollHeight;

// do nothing when the area is hidden
if (!scroll_height) {
var padding = parseInt($(textarea).css('padding-top')) + parseInt($(textarea).css('padding-bottom')) + 2,
// FIXME: Is there a better way to get initial height of the textarea?
// At this moment clientHeight/offsetHeight is 0.
min_height = ($(textarea)[0].rows || 5) * 21,
resize = function(e) {
if (this.scrollHeight - padding <= min_height) {
return;
}

if (!initial_height) {
area.data('initial-height', initial_height = scroll_height);
}
// To fix scroll-jump issue in Edge we'll find the scrolling parent
// and re-apply scrollTop value after we reset textarea height
var scroll_element, scroll_pos = 0;
$(e.target).parents().each(function() {
if (this.scrollTop > 0) {
scroll_element = this;
scroll_pos = this.scrollTop;
return false;
}
});

// strange effect in Chrome/Firefox when you delete a line in the textarea
// the scrollHeight is not decreased by the line height, but by 2px
// so jumps up many times in small steps, we'd rather use one big step
if (area.outerHeight() - scroll_height == 2) {
scroll_height -= 19; // 21px is the assumed line height
var oldHeight = $(this).outerHeight();
$(this).outerHeight(0);
var newHeight = Math.max(min_height, this.scrollHeight);
$(this).outerHeight(oldHeight);
if (newHeight !== oldHeight) {
$(this).height(newHeight);
}

area.outerHeight(Math.max(initial_height, scroll_height));
}, 10);
};

$(textarea).css('overflow-y', 'hidden').on('input', resize).trigger('input');
if (scroll_pos) {
scroll_element.scrollTop = scroll_pos;
}
};

// Make sure the height is up-to-date also in time intervals
setInterval(function() { $(textarea).trigger('input'); }, 1000);
$(textarea).on('input', resize).trigger('input');
};

// Inititalizes smart list input
Expand Down
13 changes: 13 additions & 0 deletions tests/Framework/Washtml.php
Original file line number Diff line number Diff line change
Expand Up @@ -506,4 +506,17 @@ function test_missing_tags()

$this->assertContains('First line', $washed);
}

/**
* Test CDATA cleanup
*/
function test_cdata()
{
$html = '<p><![CDATA[<script>alert(document.cookie)</script>]]></p>';

$washer = new rcube_washtml;
$washed = $washer->wash($html);

$this->assertTrue(strpos($washed, '<script>') === false, "CDATA content");
}
}
84 changes: 0 additions & 84 deletions tests/MailFunc.php
Original file line number Diff line number Diff line change
Expand Up @@ -276,88 +276,4 @@ public function test_html_link_xss()
$this->assertNotContains('onerror=alert(1)//">test', $body);
$this->assertContains('<a style="x: &gt;"', $body);
}

/**
* Test identities selection using Return-Path header
*/
function test_rcmail_identity_select()
{
$identities = array(
array(
'name' => 'Test',
'email_ascii' => 'addr@domain.tld',
'ident' => 'Test <addr@domain.tld>',
),
array(
'name' => 'Test',
'email_ascii' => 'thing@domain.tld',
'ident' => 'Test <thing@domain.tld>',
),
array(
'name' => 'Test',
'email_ascii' => 'other@domain.tld',
'ident' => 'Test <other@domain.tld>',
),
);

$message = new stdClass;
$message->headers = new rcube_message_header;
$message->headers->set('Return-Path', '<some_thing@domain.tld>');
$res = rcmail_identity_select($message, $identities);

$this->assertSame($identities[0], $res);

$message->headers->set('Return-Path', '<thing@domain.tld>');
$res = rcmail_identity_select($message, $identities);

$this->assertSame($identities[1], $res);
}

/**
* Test identities selection (#1489378)
*/
function test_rcmail_identity_select2()
{
$identities = array(
array(
'name' => 'Test 1',
'email_ascii' => 'addr1@domain.tld',
'ident' => 'Test 1 <addr1@domain.tld>',
),
array(
'name' => 'Test 2',
'email_ascii' => 'addr2@domain.tld',
'ident' => 'Test 2 <addr2@domain.tld>',
),
array(
'name' => 'Test 3',
'email_ascii' => 'addr3@domain.tld',
'ident' => 'Test 3 <addr3@domain.tld>',
),
array(
'name' => 'Test 4',
'email_ascii' => 'addr2@domain.tld',
'ident' => 'Test 4 <addr2@domain.tld>',
),
);

$message = new stdClass;
$message->headers = new rcube_message_header;

$message->headers->set('From', '<addr2@domain.tld>');
$res = rcmail_identity_select($message, $identities);
$this->assertSame($identities[1], $res);

$message->headers->set('From', 'Test 2 <addr2@domain.tld>');
$res = rcmail_identity_select($message, $identities);
$this->assertSame($identities[1], $res);

$message->headers->set('From', 'Other <addr2@domain.tld>');
$res = rcmail_identity_select($message, $identities);
$this->assertSame($identities[1], $res);

$message->headers->set('From', 'Test 4 <addr2@domain.tld>');
$res = rcmail_identity_select($message, $identities);
$this->assertSame($identities[3], $res);
}
}
1 change: 1 addition & 0 deletions tests/Rcmail/OutputHtml.php
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<?php

/**
* Test class to test rcmail_output_html class
*
Expand Down
165 changes: 165 additions & 0 deletions tests/Rcmail/Sendmail.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
<?php

/**
* Test class to test rcmail_sendmail class
*
* @package Tests
*/
class Rcmail_RcmailSendmail extends PHPUnit\Framework\TestCase
{
/**
* Test rcmail_sendmail::identity_select()
*/
function test_identity_select()
{
$message = new StdClass;
$message->headers = new rcube_message_header;
$message->headers->charset = 'UTF-8';
$message->headers->to = '';
$message->headers->from = '';
$message->headers->cc = '';
$message->headers->other = [];

$result = rcmail_sendmail::identity_select($message, []);
$this->assertSame(null, $result);

$identities = [
[
'identity_id' => 1,
'user_id' => 1,
'standard' => 1,
'name' => 'Default',
'email' => 'default@domain.tld',
'email_ascii' => 'default@domain.tld',
'ident' => 'Default <default@domain.tld>',
],
[
'identity_id' => 2,
'user_id' => 1,
'standard' => 0,
'name' => 'Identity One',
'email' => 'ident1@domain.tld',
'email_ascii' => 'ident1@domain.tld',
'ident' => '"Identity One" <ident1@domain.tld>',
],
[
'identity_id' => 3,
'user_id' => 1,
'standard' => 0,
'name' => 'Identity Two',
'email' => 'ident2@domain.tld',
'email_ascii' => 'ident2@domain.tld',
'ident' => '"Identity Two" <ident2@domain.tld>',
],
];

$message->headers->to = 'ident2@domain.tld';
$message->headers->from = 'from@other.domain.tld';

$result = rcmail_sendmail::identity_select($message, $identities);
$this->assertSame($identities[2], $result);

$message->headers->to = 'ident1@domain.tld';
$message->headers->from = 'from@other.domain.tld';

$result = rcmail_sendmail::identity_select($message, $identities);
$this->assertSame($identities[1], $result);

// #7211
$message->headers->to = 'ident1@domain.tld';
$message->headers->from = 'ident2@domain.tld';

$result = rcmail_sendmail::identity_select($message, $identities);
$this->assertSame($identities[1], $result);

$message->headers->to = 'ident2@domain.tld';
$message->headers->from = 'ident1@domain.tld';

$result = rcmail_sendmail::identity_select($message, $identities);
$this->assertSame($identities[2], $result);
}

/**
* Test identities selection using Return-Path header
*/
function test_identity_select_return_path()
{
$identities = array(
array(
'name' => 'Test',
'email_ascii' => 'addr@domain.tld',
'ident' => 'Test <addr@domain.tld>',
),
array(
'name' => 'Test',
'email_ascii' => 'thing@domain.tld',
'ident' => 'Test <thing@domain.tld>',
),
array(
'name' => 'Test',
'email_ascii' => 'other@domain.tld',
'ident' => 'Test <other@domain.tld>',
),
);

$message = new stdClass;
$message->headers = new rcube_message_header;
$message->headers->set('Return-Path', '<some_thing@domain.tld>');
$res = rcmail_sendmail::identity_select($message, $identities);

$this->assertSame($identities[0], $res);

$message->headers->set('Return-Path', '<thing@domain.tld>');
$res = rcmail_sendmail::identity_select($message, $identities);

$this->assertSame($identities[1], $res);
}

/**
* Test identities selection (#1489378)
*/
function test_identity_select_more()
{
$identities = array(
array(
'name' => 'Test 1',
'email_ascii' => 'addr1@domain.tld',
'ident' => 'Test 1 <addr1@domain.tld>',
),
array(
'name' => 'Test 2',
'email_ascii' => 'addr2@domain.tld',
'ident' => 'Test 2 <addr2@domain.tld>',
),
array(
'name' => 'Test 3',
'email_ascii' => 'addr3@domain.tld',
'ident' => 'Test 3 <addr3@domain.tld>',
),
array(
'name' => 'Test 4',
'email_ascii' => 'addr2@domain.tld',
'ident' => 'Test 4 <addr2@domain.tld>',
),
);

$message = new stdClass;
$message->headers = new rcube_message_header;

$message->headers->set('From', '<addr2@domain.tld>');
$res = rcmail_sendmail::identity_select($message, $identities);
$this->assertSame($identities[1], $res);

$message->headers->set('From', 'Test 2 <addr2@domain.tld>');
$res = rcmail_sendmail::identity_select($message, $identities);
$this->assertSame($identities[1], $res);

$message->headers->set('From', 'Other <addr2@domain.tld>');
$res = rcmail_sendmail::identity_select($message, $identities);
$this->assertSame($identities[1], $res);

$message->headers->set('From', 'Test 4 <addr2@domain.tld>');
$res = rcmail_sendmail::identity_select($message, $identities);
$this->assertSame($identities[3], $res);
}
}
1 change: 1 addition & 0 deletions tests/phpunit.xml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
<file>MailFunc.php</file>
<file>Rcmail/OutputHtml.php</file>
<file>Rcmail/Rcmail.php</file>
<file>Rcmail/Sendmail.php</file>
</testsuite>
<testsuite name="Plugins Tests">
<file>./../plugins/acl/tests/Acl.php</file>
Expand Down