From c344b64f13e7aa3c87c423cc14e57536d28c40b6 Mon Sep 17 00:00:00 2001 From: Thomas Bruederli <thomas@roundcube.net> Date: Fri, 18 Oct 2013 06:50:25 -0400 Subject: [PATCH] Get supported spell-check languages from the configured backend; replace suspended google spell service with our new service at spell.roundcube.net --- program/steps/mail/compose.inc | 264 ++++++++++++++++++++++++++++++++++++---------------- 1 files changed, 180 insertions(+), 84 deletions(-) diff --git a/program/steps/mail/compose.inc b/program/steps/mail/compose.inc index 5e1d95d..166a583 100644 --- a/program/steps/mail/compose.inc +++ b/program/steps/mail/compose.inc @@ -54,30 +54,12 @@ $COMPOSE_ID = uniqid(mt_rand()); $_SESSION['compose_data_'.$COMPOSE_ID] = array( 'id' => $COMPOSE_ID, - 'param' => request2param(RCUBE_INPUT_GET), + 'param' => rcube_utils::request2param(RCUBE_INPUT_GET, 'task|action', true), 'mailbox' => $RCMAIL->storage->get_folder(), ); $COMPOSE =& $_SESSION['compose_data_'.$COMPOSE_ID]; - // process values like "mailto:foo@bar.com?subject=new+message&cc=another" - if ($COMPOSE['param']['to']) { - // #1486037: remove "mailto:" prefix - $COMPOSE['param']['to'] = preg_replace('/^mailto:/i', '', $COMPOSE['param']['to']); - $mailto = explode('?', $COMPOSE['param']['to']); - if (count($mailto) > 1) { - $COMPOSE['param']['to'] = $mailto[0]; - parse_str($mailto[1], $query); - foreach ($query as $f => $val) - $COMPOSE['param'][$f] = $val; - } - } - - // select folder where to save the sent message - $COMPOSE['param']['sent_mbox'] = $RCMAIL->config->get('sent_mbox'); - - // pipe compose parameters thru plugins - $plugin = $RCMAIL->plugins->exec_hook('message_compose', $COMPOSE); - $COMPOSE['param'] = array_merge($COMPOSE['param'], $plugin['param']); + rcmail_process_compose_params($COMPOSE); // add attachments listed by message_compose hook if (is_array($plugin['attachments'])) { @@ -143,9 +125,14 @@ $OUTPUT->set_env('recipients_separator', trim($RCMAIL->config->get('recipients_separator', ','))); // default font for HTML editor -$font = rcube_fontdefs($RCMAIL->config->get('default_font', 'Verdana')); +$font = rcube_fontdefs($RCMAIL->config->get('default_font')); if ($font && !is_array($font)) { $OUTPUT->set_env('default_font', $font); +} + +// default font size for HTML editor +if ($font_size = $RCMAIL->config->get('default_font_size')) { + $OUTPUT->set_env('default_font_size', $font_size); } // get reference message and set compose mode @@ -165,11 +152,16 @@ else if ($msg_uid = $COMPOSE['param']['uid']) { $compose_mode = RCUBE_COMPOSE_EDIT; } + +$COMPOSE['mode'] = $compose_mode; $OUTPUT->set_env('compose_mode', $compose_mode); $config_show_sig = $RCMAIL->config->get('show_sig', 1); if ($compose_mode == RCUBE_COMPOSE_EDIT || $compose_mode == RCUBE_COMPOSE_DRAFT) { // don't add signature in draft/edit mode, we'll also not remove the old-one + // but only on page display, later we should be able to change identity/sig (#1489229) + if ($config_show_sig == 1 || $config_show_sig == 2) + $OUTPUT->set_env('show_sig_later', true); } else if ($config_show_sig == 1) $OUTPUT->set_env('show_sig', true); @@ -204,7 +196,10 @@ if (!empty($MESSAGE->headers->charset)) $RCMAIL->storage->set_charset($MESSAGE->headers->charset); - if ($compose_mode == RCUBE_COMPOSE_REPLY) { + if (!$MESSAGE->headers) { + // error + } + else if ($compose_mode == RCUBE_COMPOSE_REPLY) { $COMPOSE['reply_uid'] = $msg_uid; $COMPOSE['reply_msgid'] = $MESSAGE->headers->messageID; $COMPOSE['references'] = trim($MESSAGE->headers->references . " " . $MESSAGE->headers->messageID); @@ -219,8 +214,8 @@ $COMPOSE['param']['sent_mbox'] = $sent_folder; } } - else if ($compose_mode == RCUBE_COMPOSE_DRAFT) { - if ($draft_info = $MESSAGE->headers->get('x-draft-info')) { + else if ($compose_mode == RCUBE_COMPOSE_DRAFT || $compose_mode == RCUBE_COMPOSE_EDIT) { + if ($compose_mode == RCUBE_COMPOSE_DRAFT && ($draft_info = $MESSAGE->headers->get('x-draft-info'))) { // get reply_uid/forward_uid to flag the original message when sending $info = rcmail_draftinfo_decode($draft_info); @@ -242,11 +237,19 @@ if ($in_reply_to = $MESSAGE->headers->get('in-reply-to')) $COMPOSE['reply_msgid'] = '<' . $in_reply_to . '>'; - $COMPOSE['references'] = $MESSAGE->headers->references; + $COMPOSE['references'] = $MESSAGE->headers->references; } } else { $MESSAGE = new stdClass(); + + // apply mailto: URL parameters + if (!empty($COMPOSE['param']['in-reply-to'])) { + $COMPOSE['reply_msgid'] = '<' . $COMPOSE['param']['in-reply-to'] . '>'; + } + if (!empty($COMPOSE['param']['references'])) { + $COMPOSE['references'] = $COMPOSE['param']['references']; + } } $MESSAGE->compose = array(); @@ -316,8 +319,8 @@ else if (!empty($MESSAGE->headers->from)) $fvalue = $MESSAGE->headers->from; - // Reply to message sent by yourself (#1487074) - if (!empty($ident) && $fvalue == $ident['ident']) { + // Reply to message sent by yourself (#1487074, #1489230) + if (!empty($ident) && in_array($ident['ident'], array($fvalue, $MESSAGE->headers->from))) { $fvalue = $MESSAGE->headers->to; } } @@ -327,7 +330,8 @@ $fvalue .= $v; if ($v = $MESSAGE->headers->cc) $fvalue .= (!empty($fvalue) ? $separator : '') . $v; - if ($v = $MESSAGE->headers->get('Sender', false)) + // Use Sender header (#1489011) + if (($v = $MESSAGE->headers->get('Sender', false)) && strpos($v, '-bounces@') === false) $fvalue .= (!empty($fvalue) ? $separator : '') . $v; // When To: and Reply-To: are the same we add From: address to the list (#1489037) @@ -370,7 +374,12 @@ $mailto = format_email(rcube_idn_to_utf8($addr_part['mailto'])); if (!in_array($mailto, $a_recipients) - && ($header == 'to' || empty($MESSAGE->compose['from_email']) || $mailto != $MESSAGE->compose['from_email']) + && ( + $header == 'to' + || $compose_mode != RCUBE_COMPOSE_REPLY + || empty($MESSAGE->compose['from_email']) + || $mailto != $MESSAGE->compose['from_email'] + ) ) { if ($addr_part['name'] && $addr_part['mailto'] != $addr_part['name']) $string = format_email_recipient($mailto, $addr_part['name']); @@ -394,6 +403,53 @@ /****** compose mode functions ********/ + +// process compose request parameters +function rcmail_process_compose_params(&$COMPOSE) +{ + if ($COMPOSE['param']['to']) { + $mailto = explode('?', $COMPOSE['param']['to'], 2); + + // #1486037: remove "mailto:" prefix + $COMPOSE['param']['to'] = preg_replace('/^mailto:/i', '', $mailto[0]); + + // Supported case-insensitive tokens in mailto URL + $url_tokens = array('to', 'cc', 'bcc', 'reply-to', 'in-reply-to', 'references', 'subject', 'body'); + + if (!empty($mailto[1])) { + parse_str($mailto[1], $query); + foreach ($query as $f => $val) { + if (($key = array_search(strtolower($f), $url_tokens)) !== false) { + $f = $url_tokens[$key]; + } + + // merge mailto: addresses with addresses from 'to' parameter + if ($f == 'to' && !empty($COMPOSE['param']['to'])) { + $to_addresses = rcube_mime::decode_address_list($COMPOSE['param']['to'], null, true, null, true); + $add_addresses = rcube_mime::decode_address_list($val, null, true); + foreach ($add_addresses as $addr) { + if (!in_array($addr['mailto'], $to_addresses)) { + $to_addresses[] = $addr['mailto']; + $COMPOSE['param']['to'] = (!empty($to_addresses) ? ', ' : '') . $addr['string']; + } + } + } + else { + $COMPOSE['param'][$f] = $val; + } + } + } + } + + $RCMAIL = rcmail::get_instance(); + + // select folder where to save the sent message + $COMPOSE['param']['sent_mbox'] = $RCMAIL->config->get('sent_mbox'); + + // pipe compose parameters thru plugins + $plugin = $RCMAIL->plugins->exec_hook('message_compose', $COMPOSE); + $COMPOSE['param'] = array_merge($COMPOSE['param'], $plugin['param']); +} function rcmail_compose_headers($attrib) { @@ -474,6 +530,7 @@ if (count($MESSAGE->identities)) { $a_signatures = array(); + $identities = array(); $separator = intval($RCMAIL->config->get('reply_mode')) > 0 && ($compose_mode == RCUBE_COMPOSE_REPLY || $compose_mode == RCUBE_COMPOSE_FORWARD) ? '---' : '-- '; @@ -511,12 +568,21 @@ $a_signatures[$identity_id]['text'] = $text; $a_signatures[$identity_id]['html'] = $html; } + + // add bcc and reply-to + if (!empty($sql_arr['reply-to'])) { + $identities[$identity_id]['replyto'] = $sql_arr['reply-to']; + } + if (!empty($sql_arr['bcc'])) { + $identities[$identity_id]['bcc'] = $sql_arr['bcc']; + } } $out = $select_from->show($MESSAGE->compose['from']); // add signatures to client $OUTPUT->set_env('signatures', $a_signatures); + $OUTPUT->set_env('identities', $identities); } // no identities, display text input field else { @@ -585,13 +651,30 @@ } // reply/edit/draft/forward else if ($compose_mode && ($compose_mode != RCUBE_COMPOSE_REPLY || intval($RCMAIL->config->get('reply_mode')) != -1)) { - $isHtml = rcmail_compose_editor_mode(); + $isHtml = rcmail_compose_editor_mode(); + $messages = array(); if (!empty($MESSAGE->parts)) { + // collect IDs of message/rfc822 parts + if ($compose_mode == RCUBE_COMPOSE_EDIT || $compose_mode == RCUBE_COMPOSE_DRAFT) { + foreach ($MESSAGE->attachments as $part) { + if ($part->mimetype == 'message/rfc822') { + $messages[] = $part->mime_id; + } + } + } + foreach ($MESSAGE->parts as $part) { // skip no-content and attachment parts (#1488557) if ($part->type != 'content' || !$part->size || $MESSAGE->is_attachment($part)) { continue; + } + + // skip all content parts inside the message/rfc822 part in DRAFT/EDIT mode + foreach ($messages as $mimeid) { + if (strpos($part->mime_id, $mimeid . '.') === 0) { + continue 2; + } } if ($part_body = rcmail_compose_part_body($part, $isHtml)) { @@ -769,29 +852,14 @@ // Set language list if (!empty($CONFIG['enable_spellcheck'])) { - $engine = $RCMAIL->config->get('spellcheck_engine','googie'); + $engine = new rcube_spellchecker(); $dictionary = (bool) $RCMAIL->config->get('spellcheck_dictionary'); - $spellcheck_langs = (array) $RCMAIL->config->get('spellcheck_languages', - array('da'=>'Dansk', 'de'=>'Deutsch', 'en' => 'English', 'es'=>'Español', - 'fr'=>'Français', 'it'=>'Italiano', 'nl'=>'Nederlands', 'pl'=>'Polski', - 'pt'=>'Português', 'ru'=>'Русский', 'fi'=>'Suomi', 'sv'=>'Svenska')); + $spellcheck_langs = $engine->languages(); + $lang = $_SESSION['language']; - // googie works only with two-letter codes - if ($engine == 'googie') { - $lang = strtolower(substr($_SESSION['language'], 0, 2)); - - $spellcheck_langs_googie = array(); - foreach ($spellcheck_langs as $key => $name) - $spellcheck_langs_googie[strtolower(substr($key,0,2))] = $name; - $spellcheck_langs = $spellcheck_langs_googie; - } - else { - $lang = $_SESSION['language']; - - // if not found in the list, try with two-letter code - if (!$spellcheck_langs[$lang]) - $lang = strtolower(substr($lang, 0, 2)); - } + // if not found in the list, try with two-letter code + if (!$spellcheck_langs[$lang]) + $lang = strtolower(substr($lang, 0, 2)); if (!$spellcheck_langs[$lang]) $lang = 'en'; @@ -913,10 +981,10 @@ $prefix .= rcube_label('from') . ': ' . $MESSAGE->get_header('from') . "\n"; $prefix .= rcube_label('to') . ': ' . $MESSAGE->get_header('to') . "\n"; - if ($MESSAGE->headers->cc) - $prefix .= rcube_label('cc') . ': ' . $MESSAGE->get_header('cc') . "\n"; - if ($MESSAGE->headers->replyto && $MESSAGE->headers->replyto != $MESSAGE->headers->from) - $prefix .= rcube_label('replyto') . ': ' . $MESSAGE->get_header('replyto') . "\n"; + if ($cc = $MESSAGE->headers->get('cc')) + $prefix .= rcube_label('cc') . ': ' . $cc . "\n"; + if (($replyto = $MESSAGE->headers->get('reply-to')) && $replyto != $MESSAGE->get_header('from')) + $prefix .= rcube_label('replyto') . ': ' . $replyto . "\n"; $prefix .= "\n"; $body = trim($body, "\r\n"); @@ -939,15 +1007,13 @@ rcube_label('from'), Q($MESSAGE->get_header('from'), 'replace'), rcube_label('to'), Q($MESSAGE->get_header('to'), 'replace')); - if ($MESSAGE->headers->cc) + if ($cc = $MESSAGE->headers->get('cc')) $prefix .= sprintf("<tr><th align=\"right\" nowrap=\"nowrap\" valign=\"baseline\">%s: </th><td>%s</td></tr>", - rcube_label('cc'), - Q($MESSAGE->get_header('cc'), 'replace')); + rcube_label('cc'), Q($cc, 'replace')); - if ($MESSAGE->headers->replyto && $MESSAGE->headers->replyto != $MESSAGE->headers->from) + if (($replyto = $MESSAGE->headers->get('reply-to')) && $replyto != $MESSAGE->get_header('from')) $prefix .= sprintf("<tr><th align=\"right\" nowrap=\"nowrap\" valign=\"baseline\">%s: </th><td>%s</td></tr>", - rcube_label('replyto'), - Q($MESSAGE->get_header('replyto'), 'replace')); + rcube_label('replyto'), Q($replyto, 'replace')); $prefix .= "</tbody></table><br>"; } @@ -969,10 +1035,19 @@ && count($MESSAGE->mime_parts) > 0) { $cid_map = rcmail_write_compose_attachments($MESSAGE, $bodyIsHtml); + } + + // clean up HTML tags - XSS prevention (#1489251) + if ($bodyIsHtml) { + $body = rcmail_wash_html($body, array('safe' => 1), $cid_map); + + // remove comments (produced by washtml) + $body = preg_replace('/<!--[^>]+-->/', '', $body); // replace cid with href in inline images links - if ($cid_map) + if (!empty($cid_map)) { $body = str_replace(array_keys($cid_map), array_values($cid_map), $body); + } } return $body; @@ -1010,7 +1085,9 @@ $loaded_attachments[$attachment['name'] . $attachment['mimetype']] = $attachment; } - $cid_map = $messages = array(); + $cid_map = array(); + $messages = array(); + foreach ((array)$message->mime_parts as $pid => $part) { if ($part->disposition == 'attachment' || ($part->disposition == 'inline' && $bodyIsHtml) || $part->filename) { @@ -1022,25 +1099,32 @@ if ($part->ctype_primary == 'message' && $compose_mode == RCUBE_COMPOSE_REPLY) { continue; } - // skip inline images when forwarding in plain text - if ($part->content_id && !$bodyIsHtml && $compose_mode == RCUBE_COMPOSE_FORWARD) { + // skip inline images when forwarding in text mode + if ($part->content_id && $part->disposition == 'inline' && !$bodyIsHtml && $compose_mode == RCUBE_COMPOSE_FORWARD) { continue; } - $skip = false; + // skip message/rfc822 attachments on forwards (#1489214) + // Thunderbird when forwarding in inline mode displays such attachments + // and skips any attachments from inside of such part, this however + // skipped e.g. images used in HTML body or other attachments. So, + // better to skip .eml attachments but not their content (included files). if ($part->mimetype == 'message/rfc822') { + if ($compose_mode == RCUBE_COMPOSE_FORWARD) { + continue; + } $messages[] = $part->mime_id; - } else if ($messages) { + } + else if ($compose_mode != RCUBE_COMPOSE_FORWARD) { // skip attachments included in message/rfc822 attachment (#1486487) foreach ($messages as $mimeid) - if (strpos($part->mime_id, $mimeid.'.') === 0) { - $skip = true; - break; + if (strpos($part->mime_id, $mimeid . '.') === 0) { + continue 2; } } - if (!$skip && (($attachment = $loaded_attachments[rcmail_attachment_name($part) . $part->mimetype]) - || ($attachment = rcmail_save_attachment($message, $pid)))) { + if (($attachment = $loaded_attachments[rcmail_attachment_name($part) . $part->mimetype]) + || ($attachment = rcmail_save_attachment($message, $pid))) { $COMPOSE['attachments'][$attachment['id']] = $attachment; if ($bodyIsHtml && ($part->content_id || $part->content_location)) { $url = sprintf('%s&_id=%s&_action=display-attachment&_file=rcmfile%s', @@ -1313,8 +1397,9 @@ if (!$attrib['id']) $attrib['id'] = 'rcmAttachmentList'; - $out = "\n"; + $out = "\n"; $jslist = array(); + $button = ''; if (is_array($COMPOSE['attachments'])) { if ($attrib['deleteicon']) { @@ -1323,27 +1408,38 @@ 'alt' => rcube_label('delete') )); } - else + else if (rcube_utils::get_boolean($attrib['textbuttons'])) { $button = Q(rcube_label('delete')); + } foreach ($COMPOSE['attachments'] as $id => $a_prop) { if (empty($a_prop)) continue; - $out .= html::tag('li', array('id' => 'rcmfile'.$id, 'class' => rcmail_filetype2classname($a_prop['mimetype'], $a_prop['name'])), + $out .= html::tag('li', + array( + 'id' => 'rcmfile'.$id, + 'class' => rcmail_filetype2classname($a_prop['mimetype'], $a_prop['name']), + 'onmouseover' => "rcube_webmail.long_subject_title_ex(this, 0)", + ), html::a(array( 'href' => "#delete", 'title' => rcube_label('delete'), 'onclick' => sprintf("return %s.command('remove-attachment','rcmfile%s', this)", JS_OBJECT_NAME, $id), - 'class' => 'delete'), - $button) . Q($a_prop['name'])); + 'class' => 'delete' + ), + $button + ) . Q($a_prop['name']) + ); - $jslist['rcmfile'.$id] = array('name' => $a_prop['name'], 'complete' => true, 'mimetype' => $a_prop['mimetype']); + $jslist['rcmfile'.$id] = array('name' => $a_prop['name'], 'complete' => true, 'mimetype' => $a_prop['mimetype']); } } if ($attrib['deleteicon']) $COMPOSE['deleteicon'] = $CONFIG['skin_path'] . $attrib['deleteicon']; + else if (rcube_utils::get_boolean($attrib['textbuttons'])) + $COMPOSE['textbuttons'] = true; if ($attrib['cancelicon']) $OUTPUT->set_env('cancelicon', $CONFIG['skin_path'] . $attrib['cancelicon']); if ($attrib['loadingicon']) @@ -1370,7 +1466,7 @@ $out = html::div($attrib, $OUTPUT->form_tag(array('id' => $attrib['id'].'Frm', 'name' => 'uploadform', 'method' => 'post', 'enctype' => 'multipart/form-data'), - html::div(null, rcmail_compose_attachment_field(array('size' => $attrib['attachmentfieldsize']))) . + html::div(null, rcmail_compose_attachment_field()) . html::div('hint', rcube_label(array('name' => 'maxuploadsize', 'vars' => array('size' => $max_filesize)))) . (get_boolean($attrib['buttons']) ? html::div('buttons', $button->show(rcube_label('close'), array('class' => 'button', 'onclick' => "$('#$attrib[id]').hide()")) . ' ' . @@ -1384,7 +1480,7 @@ } -function rcmail_compose_attachment_field($attrib) +function rcmail_compose_attachment_field($attrib = array()) { $attrib['type'] = 'file'; $attrib['name'] = '_attachments[]'; @@ -1410,17 +1506,17 @@ rcube_label('normal'), rcube_label('high'), rcube_label('highest')), - array(5, 4, 0, 2, 1)); + array('5', '4', '0', '2', '1')); if (isset($_POST['_priority'])) $sel = $_POST['_priority']; - else if (intval($MESSAGE->headers->priority) != 3) - $sel = intval($MESSAGE->headers->priority); + else if (isset($MESSAGE->headers->priority) && intval($MESSAGE->headers->priority) != 3) + $sel = $MESSAGE->headers->priority; else $sel = 0; $out = $form_start ? "$form_start\n" : ''; - $out .= $selector->show($sel); + $out .= $selector->show(strval($sel)); $out .= $form_end ? "\n$form_end" : ''; return $out; -- Gitblit v1.9.1