From 29c54229cfbc104930e7743cecc212f53aed8a15 Mon Sep 17 00:00:00 2001 From: alecpl <alec@alec.pl> Date: Mon, 28 Feb 2011 12:33:26 -0500 Subject: [PATCH] - Fix parsing links with non-printable characters inside (#1487805) --- program/steps/mail/sendmail.inc | 254 ++++++++++++++++++++++++++++++-------------------- 1 files changed, 153 insertions(+), 101 deletions(-) diff --git a/program/steps/mail/sendmail.inc b/program/steps/mail/sendmail.inc index d6143d8..118e9ed 100644 --- a/program/steps/mail/sendmail.inc +++ b/program/steps/mail/sendmail.inc @@ -4,8 +4,8 @@ +-----------------------------------------------------------------------+ | program/steps/mail/sendmail.inc | | | - | This file is part of the RoundCube Webmail client | - | Copyright (C) 2005-2010, RoundCube Dev. - Switzerland | + | This file is part of the Roundcube Webmail client | + | Copyright (C) 2005-2010, The Roundcube Dev Team | | Licensed under the GNU GPL | | | | PURPOSE: | @@ -24,7 +24,7 @@ $OUTPUT->reset(); $OUTPUT->framed = TRUE; -$savedraft = !empty($_POST['_draft']) ? TRUE : FALSE; +$savedraft = !empty($_POST['_draft']) ? true : false; /****** checks ********/ @@ -60,8 +60,7 @@ function rcmail_encrypt_header($what) { global $CONFIG, $RCMAIL; - if (!$CONFIG['http_received_header_encrypt']) - { + if (!$CONFIG['http_received_header_encrypt']) { return $what; } return $RCMAIL->encrypt($what); @@ -69,29 +68,20 @@ // get identity record function rcmail_get_identity($id) - { +{ global $USER, $OUTPUT; - - if ($sql_arr = $USER->get_identity($id)) - { + + if ($sql_arr = $USER->get_identity($id)) { $out = $sql_arr; $out['mailto'] = $sql_arr['email']; - - // Special chars as defined by RFC 822 need to in quoted string (or escaped). - if (preg_match('/[\(\)\<\>\\\.\[\]@,;:"]/', $sql_arr['name'])) - $name = '"' . addcslashes($sql_arr['name'], '"') . '"'; - else - $name = $sql_arr['name']; - - $out['string'] = rcube_charset_convert($name, RCMAIL_CHARSET, $OUTPUT->get_charset()); - if ($sql_arr['email']) - $out['string'] .= ' <' . $sql_arr['email'] . '>'; + $out['string'] = format_email_recipient($sql_arr['email'], + rcube_charset_convert($sql_arr['name'], RCMAIL_CHARSET, $OUTPUT->get_charset())); return $out; - } - - return FALSE; } + + return FALSE; +} /** * go from this: @@ -110,7 +100,7 @@ // remove any null-byte characters before parsing $body = preg_replace('/\x00/', '', $body); - + $searchstr = 'program/js/tiny_mce/plugins/emotions/img/'; $offset = 0; @@ -146,7 +136,7 @@ } // parse email address input (and count addresses) -function rcmail_email_input_format($mailto, $count=false) +function rcmail_email_input_format($mailto, $count=false, $check=true) { global $EMAIL_FORMAT_ERROR, $RECIPIENT_COUNT; @@ -163,9 +153,11 @@ $item = trim($item); // address in brackets without name (do nothing) if (preg_match('/^<\S+@\S+>$/', $item)) { + $item = rcube_idn_to_ascii($item); $result[] = $item; // address without brackets and without name (add brackets) } else if (preg_match('/^\S+@\S+$/', $item)) { + $item = rcube_idn_to_ascii($item); $result[] = '<'.$item.'>'; // address with name (handle name) } else if (preg_match('/\S+@\S+>*$/', $item, $matches)) { @@ -174,8 +166,9 @@ $name = trim($name); if ($name && ($name[0] != '"' || $name[strlen($name)-1] != '"') && preg_match('/[\(\)\<\>\\\.\[\]@,;:"]/', $name)) { - $name = '"'.addcslashes($name, '"').'"'; + $name = '"'.addcslashes($name, '"').'"'; } + $address = rcube_idn_to_ascii($address); if (!preg_match('/^<\S+@\S+>$/', $address)) $address = '<'.$address.'>'; @@ -187,7 +180,7 @@ // check address format $item = trim($item, '<>'); - if ($item && !check_email($item)) { + if ($item && $check && !check_email($item)) { $EMAIL_FORMAT_ERROR = $item; return; } @@ -200,12 +193,13 @@ return implode(', ', $result); } + /****** compose message ********/ if (strlen($_POST['_draft_saveid']) > 3) $olddraftmessageid = get_input_value('_draft_saveid', RCUBE_INPUT_POST); -$message_id = sprintf('<%s@%s>', md5(uniqid('rcmail'.mt_rand(),true)), $RCMAIL->config->mail_domain($_SESSION['imap_host'])); +$message_id = rcmail_gen_message_id(); // set default charset $input_charset = $OUTPUT->get_charset(); @@ -219,7 +213,7 @@ $mailbcc = rcmail_email_input_format(get_input_value('_bcc', RCUBE_INPUT_POST, TRUE, $message_charset), true); if ($EMAIL_FORMAT_ERROR) { - $OUTPUT->show_message('emailformaterror', 'error', array('email' => $EMAIL_FORMAT_ERROR)); + $OUTPUT->show_message('emailformaterror', 'error', array('email' => $EMAIL_FORMAT_ERROR)); $OUTPUT->send('iframe'); } @@ -230,18 +224,30 @@ else if (empty($mailto)) $mailto = 'undisclosed-recipients:;'; -// get sender name and address +// Get sender name and address... $from = get_input_value('_from', RCUBE_INPUT_POST, true, $message_charset); -$identity_arr = rcmail_get_identity($from); +// ... from identity... +if (is_numeric($from)) { + if (is_array($identity_arr = rcmail_get_identity($from))) { + if ($identity_arr['mailto']) + $from = $identity_arr['mailto']; + if ($identity_arr['string']) + $from_string = $identity_arr['string']; + } + else { + $from = null; + } +} +// ... if there is no identity record, this might be a custom from +else if ($from_string = rcmail_email_input_format($from)) { + if (preg_match('/(\S+@\S+)/', $from_string, $m)) + $from = trim($m[1], '<>'); + else + $from = null; +} -if (!$identity_arr && ($from = rcmail_email_input_format($from))) { - if (preg_match('/(\S+@\S+)/', $from, $m)) - $identity_arr['mailto'] = $m[1]; -} else - $from = $identity_arr['mailto']; - -if (empty($identity_arr['string'])) - $identity_arr['string'] = $from; +if (!$from_string && $from) + $from_string = $from; // compose headers array $headers = array(); @@ -249,7 +255,7 @@ // if configured, the Received headers goes to top, for good measure if ($CONFIG['http_received_header']) { - $nldlm = $RCMAIL->config->header_delimiter() . "\t"; + $nldlm = "\r\n\t"; // FROM/VIA $http_header = 'from '; if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) { @@ -287,17 +293,17 @@ $headers['Received'] = $http_header; } -$headers['Date'] = date('r'); -$headers['From'] = rcube_charset_convert($identity_arr['string'], RCMAIL_CHARSET, $message_charset); +$headers['Date'] = rcmail_user_date(); +$headers['From'] = rcube_charset_convert($from_string, RCMAIL_CHARSET, $message_charset); $headers['To'] = $mailto; // additional recipients -if (!empty($mailcc)) +if (!empty($mailcc)) { $headers['Cc'] = $mailcc; - -if (!empty($mailbcc)) +} +if (!empty($mailbcc)) { $headers['Bcc'] = $mailbcc; - +} if (!empty($identity_arr['bcc'])) { $headers['Bcc'] = ($headers['Bcc'] ? $headers['Bcc'].', ' : '') . $identity_arr['bcc']; $RECIPIENT_COUNT ++; @@ -313,52 +319,63 @@ // add subject $headers['Subject'] = trim(get_input_value('_subject', RCUBE_INPUT_POST, TRUE, $message_charset)); -if (!empty($identity_arr['organization'])) +if (!empty($identity_arr['organization'])) { $headers['Organization'] = $identity_arr['organization']; - -if (!empty($_POST['_replyto'])) +} +if (!empty($_POST['_replyto'])) { $headers['Reply-To'] = rcmail_email_input_format(get_input_value('_replyto', RCUBE_INPUT_POST, TRUE, $message_charset)); -else if (!empty($identity_arr['reply-to'])) - $headers['Reply-To'] = $identity_arr['reply-to']; - -if (!empty($_SESSION['compose']['reply_msgid'])) +} +else if (!empty($identity_arr['reply-to'])) { + $headers['Reply-To'] = rcmail_email_input_format($identity_arr['reply-to'], false, true); +} +if (!empty($headers['Reply-To'])) { + $headers['Mail-Reply-To'] = $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($_SESSION['compose']['reply_msgid'])) { $headers['In-Reply-To'] = $_SESSION['compose']['reply_msgid']; - +} + // remember reply/forward UIDs in special headers -if (!empty($_SESSION['compose']['reply_uid']) && $savedraft) +if (!empty($_SESSION['compose']['reply_uid']) && $savedraft) { $headers['X-Draft-Info'] = array('type' => 'reply', 'uid' => $_SESSION['compose']['reply_uid']); -else if (!empty($_SESSION['compose']['forward_uid']) && $savedraft) +} +else if (!empty($_SESSION['compose']['forward_uid']) && $savedraft) { $headers['X-Draft-Info'] = array('type' => 'forward', 'uid' => $_SESSION['compose']['forward_uid']); +} -if (!empty($_SESSION['compose']['references'])) +if (!empty($_SESSION['compose']['references'])) { $headers['References'] = $_SESSION['compose']['references']; +} -if (!empty($_POST['_priority'])) - { +if (!empty($_POST['_priority'])) { $priority = intval($_POST['_priority']); $a_priorities = array(1=>'highest', 2=>'high', 4=>'low', 5=>'lowest'); - if ($str_priority = $a_priorities[$priority]) + if ($str_priority = $a_priorities[$priority]) { $headers['X-Priority'] = sprintf("%d (%s)", $priority, ucfirst($str_priority)); } +} -if (!empty($_POST['_receipt'])) - { - $headers['Return-Receipt-To'] = $identity_arr['string']; - $headers['Disposition-Notification-To'] = $identity_arr['string']; - } +if (!empty($_POST['_receipt'])) { + $headers['Return-Receipt-To'] = $from_string; + $headers['Disposition-Notification-To'] = $from_string; +} // additional headers $headers['Message-ID'] = $message_id; $headers['X-Sender'] = $from; -if (is_array($headers['X-Draft-Info'])) +if (is_array($headers['X-Draft-Info'])) { $headers['X-Draft-Info'] = rcmail_draftinfo_encode($headers['X-Draft-Info'] + array('folder' => $_SESSION['compose']['mailbox'])); - -if (!empty($CONFIG['useragent'])) +} +if (!empty($CONFIG['useragent'])) { $headers['User-Agent'] = $CONFIG['useragent']; +} // exec hook for header checking and manipulation -$data = $RCMAIL->plugins->exec_hook('outgoing_message_headers', array('headers' => $headers)); +$data = $RCMAIL->plugins->exec_hook('message_outgoing_headers', array('headers' => $headers)); // sending aborted by plugin if ($data['abort'] && !$savedraft) { @@ -382,13 +399,28 @@ // add inline css for blockquotes $bstyle = 'padding-left:5px; border-left:#1010ff 2px solid; margin-left:5px; width:100%'; $message_body = preg_replace('/<blockquote>/', - '<blockquote type="cite" style="'.$bstyle.'">', $message_body); + '<blockquote type="cite" style="'.$bstyle.'">', $message_body); + + // append doctype and html/body wrappers + $message_body = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN">' . + "\r\n<html><body>\r\n" . $message_body; } + // generic footer for all messages - if (!empty($CONFIG['generic_message_footer'])) { + if ($isHtml && !empty($CONFIG['generic_message_footer_html'])) { + $footer = file_get_contents(realpath($CONFIG['generic_message_footer_html'])); + $footer = rcube_charset_convert($footer, RCMAIL_CHARSET, $message_charset); + } + else if (!empty($CONFIG['generic_message_footer'])) { $footer = file_get_contents(realpath($CONFIG['generic_message_footer'])); $footer = rcube_charset_convert($footer, RCMAIL_CHARSET, $message_charset); + if ($isHtml) + $footer = '<pre>'.$footer.'</pre>'; } + if ($footer) + $message_body .= "\r\n" . $footer; + if ($isHtml) + $message_body .= "\r\n</body></html>\r\n"; } // set line length for body wrapping @@ -398,7 +430,7 @@ @set_time_limit(0); // create PEAR::Mail_mime instance -$MAIL_MIME = new Mail_mime($RCMAIL->config->header_delimiter()); +$MAIL_MIME = new Mail_mime("\r\n"); // Check if we have enough memory to handle the message in it // It's faster than using files, so we'll do this if we only can @@ -419,18 +451,30 @@ // the HTML part and the plain-text part if ($isHtml) { - $plugin = $RCMAIL->plugins->exec_hook('outgoing_message_body', array('body' => $message_body, 'type' => 'html', 'message' => $MAIL_MIME)); - $MAIL_MIME->setHTMLBody($plugin['body'] . ($footer ? "\r\n<pre>".$footer.'</pre>' : '')); + $plugin = $RCMAIL->plugins->exec_hook('message_outgoing_body', + array('body' => $message_body, 'type' => 'html', 'message' => $MAIL_MIME)); + + $MAIL_MIME->setHTMLBody($plugin['body']); + + // replace emoticons + $plugin['body'] = rcmail_replace_emoticons($plugin['body']); // add a plain text version of the e-mail as an alternative part. $h2t = new html2text($plugin['body'], false, true, 0); - $plainTextPart = rc_wordwrap($h2t->get_text(), $LINE_LENGTH, "\r\n") . ($footer ? "\r\n".$footer : ''); + $plainTextPart = rc_wordwrap($h2t->get_text(), $LINE_LENGTH, "\r\n"); $plainTextPart = wordwrap($plainTextPart, 998, "\r\n", true); - if (!strlen($plainTextPart)) { - // empty message body breaks attachment handling in drafts - $plainTextPart = "\r\n"; + if (!$plainTextPart) { + // empty message body breaks attachment handling in drafts + $plainTextPart = "\r\n"; } - $plugin = $RCMAIL->plugins->exec_hook('outgoing_message_body', array('body' => $plainTextPart, 'type' => 'alternative', 'message' => $MAIL_MIME)); + else { + // make sure all line endings are CRLF (#1486712) + $plainTextPart = preg_replace('/\r?\n/', "\r\n", $plainTextPart); + } + + $plugin = $RCMAIL->plugins->exec_hook('message_outgoing_body', + array('body' => $plainTextPart, 'type' => 'alternative', 'message' => $MAIL_MIME)); + $MAIL_MIME->setTXTBody($plugin['body']); // look for "emoticon" images from TinyMCE and change their src paths to @@ -438,22 +482,24 @@ $message_body = rcmail_fix_emoticon_paths($MAIL_MIME); } else { - if ($footer) - $message_body .= "\r\n" . $footer; - - // compose format=flowed content if enabled and not a reply message - if (empty($_SESSION['compose']['reply_msgid']) && ($flowed = $RCMAIL->config->get('send_format_flowed', true))) - $message_body = rcube_message::format_flowed($message_body, $LINE_LENGTH); + $plugin = $RCMAIL->plugins->exec_hook('message_outgoing_body', + array('body' => $message_body, 'type' => 'plain', 'message' => $MAIL_MIME)); + + $message_body = $plugin['body']; + + // compose format=flowed content if enabled + if ($flowed = $RCMAIL->config->get('send_format_flowed', true)) + $message_body = rcube_message::format_flowed($message_body, min($LINE_LENGTH+2, 79)); else $message_body = rc_wordwrap($message_body, $LINE_LENGTH, "\r\n"); - + $message_body = wordwrap($message_body, 998, "\r\n", true); if (!strlen($message_body)) { // empty message body breaks attachment handling in drafts $message_body = "\r\n"; } - $plugin = $RCMAIL->plugins->exec_hook('outgoing_message_body', array('body' => $message_body, 'type' => 'plain', 'message' => $MAIL_MIME)); - $MAIL_MIME->setTXTBody($plugin['body'], false, true); + + $MAIL_MIME->setTXTBody($message_body, false, true); } // add stored attachments, if any @@ -461,9 +507,9 @@ { foreach ($_SESSION['compose']['attachments'] as $id => $attachment) { // This hook retrieves the attachment contents from the file storage backend - $attachment = $RCMAIL->plugins->exec_hook('get_attachment', $attachment); + $attachment = $RCMAIL->plugins->exec_hook('attachment_get', $attachment); - $dispurl = '/\ssrc\s*=\s*[\'"]*\S+display-attachment\S+file=rcmfile' . preg_quote($attachment['id']) . '[\s\'"]\s*/'; + $dispurl = '/\ssrc\s*=\s*[\'"]*\S+display-attachment\S+file=rcmfile' . preg_quote($attachment['id']) . '[\s\'"]*/'; $message_body = $MAIL_MIME->getHTMLBody(); if ($isHtml && (preg_match($dispurl, $message_body) > 0)) { $message_body = preg_replace($dispurl, ' src="'.$attachment['name'].'" ', $message_body); @@ -480,14 +526,15 @@ // .eml attachments send inline $MAIL_MIME->addAttachment($file, - $ctype, + $ctype, $attachment['name'], ($attachment['data'] ? false : true), ($ctype == 'message/rfc822' ? '8bit' : 'base64'), ($ctype == 'message/rfc822' ? 'inline' : 'attachment'), - $message_charset, '', '', + '', '', '', $CONFIG['mime_param_folding'] ? 'quoted-printable' : NULL, - $CONFIG['mime_param_folding'] == 2 ? 'quoted-printable' : NULL + $CONFIG['mime_param_folding'] == 2 ? 'quoted-printable' : NULL, + '', RCMAIL_CHARSET ); } } @@ -508,27 +555,32 @@ $MAIL_MIME->setParam('text_charset', $message_charset . ($flowed ? ";\r\n format=flowed" : '')); // encoding subject header with mb_encode provides better results with asian characters -if (function_exists('mb_encode_mimeheader')) -{ +if (function_exists('mb_encode_mimeheader')) { mb_internal_encoding($message_charset); $headers['Subject'] = mb_encode_mimeheader($headers['Subject'], - $message_charset, 'Q', $RCMAIL->config->header_delimiter(), 8); + $message_charset, 'Q', "\r\n", 8); mb_internal_encoding(RCMAIL_CHARSET); } // pass headers to message object $MAIL_MIME->headers($headers); -// Begin SMTP Delivery Block +// Begin SMTP Delivery Block if (!$savedraft) { - // check for 'From' address (identity may be incomplete) - if ($identity_arr && !$identity_arr['mailto']) { + // check 'From' address (identity may be incomplete) + if (empty($from)) { $OUTPUT->show_message('nofromaddress', 'error'); - $OUTPUT->send('iframe'); + $OUTPUT->send('iframe'); } - $sent = rcmail_deliver_message($MAIL_MIME, $from, $mailto, $smtp_error, $mailbody_file); + // Handle Delivery Status Notification request + if (!empty($_POST['_dsn'])) { + $smtp_opts['dsn'] = true; + } + + $sent = rcmail_deliver_message($MAIL_MIME, $from, $mailto, + $smtp_error, $mailbody_file, $smtp_opts); // return to compose page if sending failed if (!$sent) @@ -675,4 +727,4 @@ $OUTPUT->send('iframe'); } -?> + -- Gitblit v1.9.1