From ec603f7da6e0dcae398169efe03b52d199427d11 Mon Sep 17 00:00:00 2001 From: alecpl Date: Wed, 18 Feb 2009 19:15:36 +0000 Subject: - Fix html body washing on reply/forward + fix attachments handling (#1485676) --- CHANGELOG | 4 + program/steps/mail/compose.inc | 81 ++++++++++++------ program/steps/mail/func.inc | 187 +++++++++++++++++++++++++---------------- program/steps/mail/show.inc | 18 +--- 4 files changed, 174 insertions(+), 116 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 825fbb2f0..0f817752b 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,10 @@ CHANGELOG RoundCube Webmail --------------------------- +2009/02/18 (alec) +---------- +- Fix html body washing on reply/forward + fix attachments handling (#1485676) + 2009/02/13 (alec) ---------- - Fix multiple recipients input parsing (#1485733) diff --git a/program/steps/mail/compose.inc b/program/steps/mail/compose.inc index 7dfa967cd..1b83b1ed4 100644 --- a/program/steps/mail/compose.inc +++ b/program/steps/mail/compose.inc @@ -381,16 +381,6 @@ function rcmail_compose_body($attrib) // load draft message body else if ($compose_mode == RCUBE_COMPOSE_DRAFT) $body = rcmail_create_draft_body($body, $isHtml); - - if ($isHtml) { - // replace cid with href in inline images links - foreach ((array)$_SESSION['compose']['attachments'] as $pid => $attachment) { - if ($attachment['content_id']) { - $body = str_replace('cid:'. $attachment['content_id'], - $OUTPUT->app->comm_path.'&_action=display-attachment&_file=rcmfile'.$pid, $body); - } - } - } } else if (!empty($_SESSION['compose']['param']['_body'])) { @@ -506,13 +496,19 @@ function rcmail_create_reply_body($body, $bodyIsHtml) } else { + // save inline images to files + $cid_map = rcmail_write_inline_attachments($MESSAGE); + // set is_safe flag (we need this for html body washing) + rcmail_check_safe($MESSAGE); + // clean up html tags + $body = rcmail_wash_html($body, array('safe' => $MESSAGE->is_safe), $cid_map); + + // build reply (quote content) $prefix = sprintf("On %s, %s wrote:
\n", $MESSAGE->headers->date, htmlspecialchars(Q($MESSAGE->get_header('from'), 'replace'), ENT_COMPAT, $OUTPUT->get_charset())); $prefix .= '
'; $suffix = "

"; - - rcmail_write_inline_attachments($MESSAGE); } return $prefix.$body.$suffix; @@ -523,6 +519,10 @@ function rcmail_create_forward_body($body, $bodyIsHtml) { global $IMAP, $MESSAGE, $OUTPUT; + // add attachments + if (!isset($_SESSION['compose']['forward_attachments']) && is_array($MESSAGE->mime_parts)) + $cid_map = rcmail_write_compose_attachments($MESSAGE, $bodyIsHtml); + if (!$bodyIsHtml) { $prefix = "\n\n\n-------- Original Message --------\n"; @@ -536,6 +536,11 @@ function rcmail_create_forward_body($body, $bodyIsHtml) } else { + // set is_safe flag (we need this for html body washing) + rcmail_check_safe($MESSAGE); + // clean up html tags + $body = rcmail_wash_html($body, array('safe' => $MESSAGE->is_safe), $cid_map); + $prefix = sprintf( "

-------- Original Message --------" . "" . @@ -554,10 +559,6 @@ function rcmail_create_forward_body($body, $bodyIsHtml) $prefix .= "

"; } - - // add attachments - if (!isset($_SESSION['compose']['forward_attachments']) && is_array($MESSAGE->mime_parts)) - rcmail_write_compose_attachments($MESSAGE, $bodyIsHtml); return $prefix.$body; } @@ -565,7 +566,7 @@ function rcmail_create_forward_body($body, $bodyIsHtml) function rcmail_create_draft_body($body, $bodyIsHtml) { - global $MESSAGE; + global $MESSAGE, $OUTPUT; /** * add attachments @@ -574,39 +575,67 @@ function rcmail_create_draft_body($body, $bodyIsHtml) if (!isset($_SESSION['compose']['forward_attachments']) && is_array($MESSAGE->mime_parts) && count($MESSAGE->mime_parts) > 0) - rcmail_write_compose_attachments($MESSAGE, $bodyIsHtml); + { + $cid_map = rcmail_write_compose_attachments($MESSAGE, $bodyIsHtml); + // replace cid with href in inline images links + if ($cid_map) + $body = str_replace(array_keys($cid_map), array_values($cid_map), $body); + } + return $body; } function rcmail_write_compose_attachments(&$message, $bodyIsHtml) { + global $OUTPUT; + + $cid_map = array(); + $id = 0; + foreach ((array)$message->mime_parts as $pid => $part) { if (($part->ctype_primary != 'message' || !$bodyIsHtml) && ($part->disposition=='attachment' || $part->disposition=='inline' || $part->headers['content-id'] || (empty($part->disposition) && $part->filename))) { - if ($attachment = rcmail_save_attachment($message, $pid)) - $_SESSION['compose']['attachments'][] = $attachment; + if ($attachment = rcmail_save_attachment($message, $pid)) { + $_SESSION['compose']['attachments'][$id] = $attachment; + if ($bodyIsHtml && $part->filename && $part->content_id) { + $cid_map['cid:'.$part->content_id] = + $OUTPUT->app->comm_path.'&_action=display-attachment&_file=rcmfile'.$id; + } + $id++; + } } } $_SESSION['compose']['forward_attachments'] = true; + + return $cid_map; } function rcmail_write_inline_attachments(&$message) { - foreach ((array)$message->mime_parts as $pid => $part) - { - if ($part->content_id && $part->filename) - { - if ($attachment = rcmail_save_attachment($message, $pid)) - $_SESSION['compose']['attachments'][] = $attachment; + global $OUTPUT; + + $cid_map = array(); + $id = 0; + + foreach ((array)$message->mime_parts as $pid => $part) { + if ($part->content_id && $part->filename) { + if ($attachment = rcmail_save_attachment($message, $pid)) { + $_SESSION['compose']['attachments'][$id] = $attachment; + $cid_map['cid:'.$part->content_id] = + $OUTPUT->app->comm_path.'&_action=display-attachment&_file=rcmfile'.$id; + $id++; + } } } + + return $cid_map; } function rcmail_save_attachment(&$message, $pid) diff --git a/program/steps/mail/func.inc b/program/steps/mail/func.inc index f54d4facd..2e85a4097 100644 --- a/program/steps/mail/func.inc +++ b/program/steps/mail/func.inc @@ -19,7 +19,6 @@ */ -require_once('lib/enriched.inc'); require_once('include/rcube_smtp.inc'); $EMAIL_ADDRESS_PATTERN = '/([a-z0-9][a-z0-9\-\.\+\_]*@[a-z0-9]([a-z0-9\-][.]?)*[a-z0-9]\\.[a-z]{2,5})/i'; @@ -611,22 +610,128 @@ function rcmail_get_mailbox_name_text() return rcmail_localize_foldername($RCMAIL->imap->get_mailbox_name()); } +/** + * Sets message is_safe flag according to 'show_images' option value + * + * @param object rcube_message Message + */ +function rcmail_check_safe(&$message) +{ + global $RCMAIL; + + $show_images = $RCMAIL->config->get('show_images'); + if (!$message->is_safe + && !empty($show_images) + && $message->has_html_part()) + { + switch($show_images) { + case '1': // known senders only + $CONTACTS = new rcube_contacts($DB, $_SESSION['user_id']); + if ($CONTACTS->search('email', $message->sender['mailto'], true, false)->count) { + $message->set_safe(true); + } + break; + case '2': // always + $message->set_safe(true); + break; + } + } +} + +/** + * Cleans up the given message HTML Body (for displaying) + * + * @param string HTML + * @param array Display parameters + * @param array CID map replaces (inline images) + * @return string Clean HTML + */ +function rcmail_wash_html($html, $p = array(), $cid_replaces) +{ + global $REMOTE_OBJECTS; + + $p += array('safe' => false, 'inline_html' => true); + + // special replacements (not properly handled by washtml class) + $html_search = array( + '/(<\/nobr>)(\s+)()/i', // space(s) between + '/(<[\/]*st1:[^>]+>)/i', // Microsoft's Smart Tags + '/<\/?rte_text>/i', // Rich Text Editor tags (#1485647) + '/.*<\/title>/i', // PHP bug #32547 workaround: remove title tag + '/<html[^>]*>/im', // malformed html: remove html tags (#1485139) + '/<\/html>/i', // malformed html: remove html tags (#1485139) + '/^[\xFE\xFF\xBB\xBF\x00]+((?:<\!doctype|\<html))/im', // remove byte-order mark (only outlook?) + ); + $html_replace = array( + '\\1'.'   '.'\\3', + '', + '', + '', + '', + '', + '\\1', + ); + $html = preg_replace($html_search, $html_replace, $html); + + // charset was converted to UTF-8 in rcube_imap::get_message_part() -> change charset specification in HTML accordingly + $charset_pattern = '/(\s+content=[\'"]?\w+\/\w+;\s*charset)=([a-z0-9-_]+)/i'; + if (preg_match($charset_pattern, $html)) { + $html = preg_replace($charset_pattern, '\\1='.RCMAIL_CHARSET, $html); + } + else { + // add head for malformed messages, washtml cannot work without that + if (!preg_match('/<head[^>]*>(.*)<\/head>/Uims', $html)) + $html = '<head></head>'. $html; + $html = substr_replace($html, '<meta http-equiv="content-type" content="text/html; charset='.RCMAIL_CHARSET.'" />', intval(stripos($html, '<head>')+6), 0); + } + + // turn relative into absolute urls + $html = rcmail_resolve_base($html); + + // clean HTML with washhtml by Frederic Motte + $wash_opts = array( + 'show_washed' => false, + 'allow_remote' => $p['safe'], + 'blocked_src' => "./program/blocked.gif", + 'charset' => RCMAIL_CHARSET, + 'cid_map' => $cid_replaces, + 'html_elements' => array('body'), + ); + + if (!$p['inline_html']) { + $wash_opts['html_elements'] = array('html','head','title','body'); + } + if ($p['safe']) { + $wash_opts['html_elements'][] = 'link'; + $wash_opts['html_attribs'] = array('rel','type'); + } + + $washer = new washtml($wash_opts); + $washer->add_callback('form', 'rcmail_washtml_callback'); + + if ($p['safe']) { // allow CSS styles, will be sanitized by rcmail_washtml_callback() + $washer->add_callback('style', 'rcmail_washtml_callback'); + } + + $html = $washer->wash($html); + $REMOTE_OBJECTS = $washer->extlinks; + + return $html; +} + /** * Convert the given message part to proper HTML * which can be displayed the message view * * @param object rcube_message_part Message part - * @param bool True if external objects (ie. images ) are allowed - * @param bool True if part should be converted to plaintext + * @param array Display parameters array * @return string Formatted HTML string */ function rcmail_print_body($part, $p = array()) { - global $REMOTE_OBJECTS; - $p += array('safe' => false, 'plain' => false, 'inline_html' => true); - + // convert html to text/plain if ($part->ctype_secondary == 'html' && $p['plain']) { $txt = new html2text($part->body, false, true); @@ -635,77 +740,12 @@ function rcmail_print_body($part, $p = array()) } // text/html else if ($part->ctype_secondary == 'html') { - $html = $part->body; - - // special replacements (not properly handled by washtml class) - $html_search = array( - '/(<\/nobr>)(\s+)(<nobr>)/i', // space(s) between <NOBR> - '/(<[\/]*st1:[^>]+>)/i', // Microsoft's Smart Tags <ST1> - '/<\/?rte_text>/i', // Rich Text Editor tags (#1485647) - '/<title>.*<\/title>/i', // PHP bug #32547 workaround: remove title tag - '/<html[^>]*>/im', // malformed html: remove html tags (#1485139) - '/<\/html>/i', // malformed html: remove html tags (#1485139) - '/^[\xFE\xFF\xBB\xBF\x00]+((?:<\!doctype|\<html))/im', // remove byte-order mark (only outlook?) - ); - $html_replace = array( - '\\1'.'   '.'\\3', - '', - '', - '', - '', - '', - '\\1', - ); - $html = preg_replace($html_search, $html_replace, $html); - - // charset was converted to UTF-8 in rcube_imap::get_message_part() -> change charset specification in HTML accordingly - $charset_pattern = '/(\s+content=[\'"]?\w+\/\w+;\s*charset)=([a-z0-9-_]+)/i'; - if (preg_match($charset_pattern, $html)) { - $html = preg_replace($charset_pattern, '\\1='.RCMAIL_CHARSET, $html); - } - else { - // add head for malformed messages, washtml cannot work without that - if (!preg_match('/<head[^>]*>(.*)<\/head>/Uims', $html)) - $html = '<head></head>'. $html; - $html = substr_replace($html, '<meta http-equiv="content-type" content="text/html; charset='.RCMAIL_CHARSET.'" />', intval(stripos($html, '<head>')+6), 0); - } - - // turn relative into absolute urls - $html = rcmail_resolve_base($html); - - // clean HTML with washhtml by Frederic Motte - $wash_opts = array( - 'show_washed' => false, - 'allow_remote' => $p['safe'], - 'blocked_src' => "./program/blocked.gif", - 'charset' => RCMAIL_CHARSET, - 'cid_map' => $part->replaces, - 'html_elements' => array('body'), - ); - - if (!$p['inline_html']) { - $wash_opts['html_elements'] = array('html','head','title','body'); - } - if ($p['safe']) { - $wash_opts['html_elements'][] = 'link'; - $wash_opts['html_attribs'] = array('rel','type'); - } - - $washer = new washtml($wash_opts); - $washer->add_callback('form', 'rcmail_washtml_callback'); - - if ($p['safe']) { // allow CSS styles, will be sanitized by rcmail_washtml_callback() - $washer->add_callback('style', 'rcmail_washtml_callback'); - } - - $body = $washer->wash($html); - $REMOTE_OBJECTS = $washer->extlinks; - - return $body; + return rcmail_wash_html($part->body, $p, $part->replaces); } // text/enriched else if ($part->ctype_secondary=='enriched') { $part->ctype_secondary = 'html'; + require_once('lib/enriched.inc'); return Q(enriched_to_html($part->body), 'show'); } else @@ -757,6 +797,7 @@ function rcmail_print_body($part, $p = array()) return html::tag('pre', array(), $body); } + /** * add a string to the replacement array and return a replacement string */ diff --git a/program/steps/mail/show.inc b/program/steps/mail/show.inc index 7ae0ae000..fd31fa91c 100644 --- a/program/steps/mail/show.inc +++ b/program/steps/mail/show.inc @@ -43,23 +43,7 @@ if ($_GET['_uid']) { $mbox_name = $IMAP->get_mailbox_name(); // show images? - $show_images = $RCMAIL->config->get('show_images'); - if(!$MESSAGE->is_safe - && !empty($show_images) - && $MESSAGE->has_html_part()) - { - switch($show_images) { - case '1': // known senders only - $CONTACTS = new rcube_contacts($DB, $_SESSION['user_id']); - if ($CONTACTS->search('email', $MESSAGE->sender['mailto'], true, false)->count) { - $MESSAGE->set_safe(true); - } - break; - case '2': // always - $MESSAGE->set_safe(true); - break; - } - } + rcmail_check_safe($MESSAGE); // calculate Etag for this request $etag = md5($MESSAGE->uid.$mbox_name.session_id() -- cgit v1.2.3