From eafd5b1aa4e67c4de18fc09493540b55dc647220 Mon Sep 17 00:00:00 2001 From: Aleksander Machniak Date: Thu, 3 Oct 2013 17:36:31 +0200 Subject: Improved mailto: link arguments handling (#1489363) --- program/steps/mail/compose.inc | 77 ++++++++++++++++++++++++++++++----------- program/steps/mail/func.inc | 41 ++++++++++++++++------ program/steps/mail/sendmail.inc | 8 ++--- 3 files changed, 92 insertions(+), 34 deletions(-) (limited to 'program/steps/mail') diff --git a/program/steps/mail/compose.inc b/program/steps/mail/compose.inc index b62f9bf5a..dc2452506 100644 --- a/program/steps/mail/compose.inc +++ b/program/steps/mail/compose.inc @@ -54,30 +54,12 @@ if (!is_array($COMPOSE)) $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'])) { @@ -260,6 +242,14 @@ if (!empty($msg_uid) && empty($COMPOSE['as_attachment'])) } 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(); @@ -414,6 +404,53 @@ $MESSAGE_BODY = rcmail_prepare_message_body(); /****** 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) { global $MESSAGE; diff --git a/program/steps/mail/func.inc b/program/steps/mail/func.inc index 340292aa0..48afecb60 100644 --- a/program/steps/mail/func.inc +++ b/program/steps/mail/func.inc @@ -1383,9 +1383,6 @@ function rcmail_alter_html_link($matches) { global $RCMAIL; - // Support unicode/punycode in top-level domain part - $EMAIL_PATTERN = '([a-z0-9][a-z0-9\-\.\+\_]*@[^&@"\'.][^@&"\']*\\.([^\\x00-\\x40\\x5b-\\x60\\x7b-\\x7f]{2,}|xn--[a-z0-9]{2,}))'; - $tag = strtolower($matches[1]); $attrib = parse_attrib_string($matches[2]); $end = '>'; @@ -1400,12 +1397,36 @@ function rcmail_alter_html_link($matches) $attrib['href'] = $RCMAIL->url(array('task' => 'utils', 'action' => 'modcss', 'u' => $tempurl, 'c' => $GLOBALS['rcmail_html_container_id'])); $end = ' />'; } - else if (preg_match('/^mailto:'.$EMAIL_PATTERN.'(\?[^"\'>]+)?/i', $attrib['href'], $mailto)) { - $attrib['href'] = $mailto[0]; - $attrib['onclick'] = sprintf( - "return %s.command('compose','%s',this)", - JS_OBJECT_NAME, - JQ($mailto[1].$mailto[3])); + else if (preg_match('/^mailto:(.+)/i', $attrib['href'], $mailto)) { + list($mailto, $url) = explode('?', html_entity_decode($mailto[1], ENT_QUOTES, 'UTF-8'), 2); + + $url = urldecode($url); + $mailto = urldecode($mailto); + $addresses = rcube_mime::decode_address_list($mailto, null, true); + $mailto = array(); + + // do sanity checks on recipients + foreach ($addresses as $idx => $addr) { + if (rcube_utils::check_email($addr['mailto'], false)) { + $addresses[$idx] = $addr['mailto']; + $mailto[] = $addr['string']; + } + else { + unset($addresses[$idx]); + } + } + + if (!empty($addresses)) { + $attrib['href'] = 'mailto:' . implode(',', $addresses); + $attrib['onclick'] = sprintf( + "return %s.command('compose','%s',this)", + JS_OBJECT_NAME, + JQ(implode(',', $mailto) . ($url ? "?$url" : ''))); + } + else { + $attrib['href'] = '#NOP'; + $attrib['onclick'] = ''; + } } else if (empty($attrib['href']) && !$attrib['name']) { $attrib['href'] = './#NOP'; @@ -1476,7 +1497,7 @@ function rcmail_address_string($input, $max=null, $linked=false, $addicon=null, if ($linked) { $attrs = array( 'href' => 'mailto:' . $mailto, - 'onclick' => sprintf("return %s.command('compose','%s',this)", JS_OBJECT_NAME, JQ($mailto)), + 'onclick' => sprintf("return %s.command('compose','%s',this)", JS_OBJECT_NAME, JQ(format_email_recipient($mailto, $name))), 'class' => "rcmContactAddress", ); diff --git a/program/steps/mail/sendmail.inc b/program/steps/mail/sendmail.inc index dee8d2136..ccb8978be 100644 --- a/program/steps/mail/sendmail.inc +++ b/program/steps/mail/sendmail.inc @@ -414,9 +414,6 @@ if (!empty($headers['Reply-To'])) { if (!empty($_POST['_followupto'])) { $headers['Mail-Followup-To'] = rcmail_email_input_format(get_input_value('_followupto', RCUBE_INPUT_POST, TRUE, $message_charset)); } -if (!empty($COMPOSE['reply_msgid'])) { - $headers['In-Reply-To'] = $COMPOSE['reply_msgid']; -} // remember reply/forward UIDs in special headers if (!empty($COMPOSE['reply_uid']) && $savedraft) { @@ -426,6 +423,9 @@ else if (!empty($COMPOSE['forward_uid']) && $savedraft) { $headers['X-Draft-Info'] = array('type' => 'forward', 'uid' => $COMPOSE['forward_uid']); } +if (!empty($COMPOSE['reply_msgid'])) { + $headers['In-Reply-To'] = $COMPOSE['reply_msgid']; +} if (!empty($COMPOSE['references'])) { $headers['References'] = $COMPOSE['references']; } @@ -759,7 +759,7 @@ if ($store_target) { if (PEAR::isError($msg)) raise_error(array('code' => 650, 'type' => 'php', - 'file' => __FILE__, 'line' => __LINE__, + 'file' => __FILE__, 'line' => __LINE__, 'message' => "Could not create message: ".$msg->getMessage()), TRUE, FALSE); else { -- cgit v1.2.3