alecpl
2009-02-18 ec603f7da6e0dcae398169efe03b52d199427d11
- Fix html body washing on reply/forward + fix attachments handling (#1485676)


4 files modified
290 ■■■■■ changed files
CHANGELOG 4 ●●●● patch | view | raw | blame | history
program/steps/mail/compose.inc 81 ●●●●● patch | view | raw | blame | history
program/steps/mail/func.inc 187 ●●●●● patch | view | raw | blame | history
program/steps/mail/show.inc 18 ●●●●● patch | view | raw | blame | history
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)
program/steps/mail/compose.inc
@@ -381,16 +381,6 @@
    // 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 @@
  }
  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:<br />\n",
      $MESSAGE->headers->date,
      htmlspecialchars(Q($MESSAGE->get_header('from'), 'replace'), ENT_COMPAT, $OUTPUT->get_charset()));
    $prefix .= '<blockquote type="cite" style="padding-left:5px; border-left:#1010ff 2px solid; margin-left:5px; width:100%">';
    $suffix = "</blockquote><p></p>";
    rcmail_write_inline_attachments($MESSAGE);
  }
  return $prefix.$body.$suffix;
@@ -522,6 +518,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)
  {
@@ -536,6 +536,11 @@
  }
  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(
      "<br><br>-------- Original Message --------" .
        "<table border=\"0\" cellpadding=\"0\" cellspacing=\"0\"><tbody>" .
@@ -554,10 +559,6 @@
    $prefix .= "</tbody></table><br>";
  }
  // 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_draft_body($body, $bodyIsHtml)
{
  global $MESSAGE;
  global $MESSAGE, $OUTPUT;
  
  /**
   * add attachments
@@ -574,39 +575,67 @@
  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)
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 @@
    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+)(<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'.' &nbsp; '.'\\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 @@
  }
  // 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'.' &nbsp; '.'\\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 @@
  return html::tag('pre', array(), $body);
}
/**
 * add a string to the replacement array and return a replacement string
 */
program/steps/mail/show.inc
@@ -43,23 +43,7 @@
  $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()