Aleksander Machniak
2013-03-15 0e32d7032b54838607e071588fd30ff8b13fc7fe
program/steps/mail/func.inc
@@ -5,7 +5,7 @@
 | program/steps/mail/func.inc                                           |
 |                                                                       |
 | This file is part of the Roundcube Webmail client                     |
 | Copyright (C) 2005-2010, The Roundcube Dev Team                       |
 | Copyright (C) 2005-2012, The Roundcube Dev Team                       |
 |                                                                       |
 | Licensed under the GNU General Public License version 3 or            |
 | any later version with exceptions for skins & plugins.                |
@@ -68,6 +68,9 @@
// set main env variables, labels and page title
if (empty($RCMAIL->action) || $RCMAIL->action == 'list') {
  // connect to storage server and trigger error on failure
  $RCMAIL->storage_connect();
  $mbox_name = $RCMAIL->storage->get_folder();
  if (empty($RCMAIL->action)) {
@@ -86,28 +89,24 @@
  }
  $threading = (bool) $RCMAIL->storage->get_threading();
  $delimiter = $RCMAIL->storage->get_hierarchy_delimiter();
  // set current mailbox and some other vars in client environment
  $OUTPUT->set_env('mailbox', $mbox_name);
  $OUTPUT->set_env('pagesize', $RCMAIL->storage->get_pagesize());
  $OUTPUT->set_env('quota', $RCMAIL->storage->get_capability('QUOTA'));
  $OUTPUT->set_env('delimiter', $RCMAIL->storage->get_hierarchy_delimiter());
  $OUTPUT->set_env('delimiter', $delimiter);
  $OUTPUT->set_env('threading', $threading);
  $OUTPUT->set_env('threads', $threading || $RCMAIL->storage->get_capability('THREAD'));
  $OUTPUT->set_env('preview_pane_mark_read', $RCMAIL->config->get('preview_pane_mark_read', 0));
  if ($RCMAIL->storage->get_capability('QUOTA')) {
    $OUTPUT->set_env('quota', true);
  }
  if ($CONFIG['delete_junk'])
    $OUTPUT->set_env('delete_junk', true);
  if ($CONFIG['flag_for_deletion'])
    $OUTPUT->set_env('flag_for_deletion', true);
  if ($CONFIG['read_when_deleted'])
    $OUTPUT->set_env('read_when_deleted', true);
  if ($CONFIG['skip_deleted'])
    $OUTPUT->set_env('skip_deleted', true);
  if ($CONFIG['display_next'])
    $OUTPUT->set_env('display_next', true);
  if ($CONFIG['forward_attachment'])
    $OUTPUT->set_env('forward_attachment', true);
  foreach (array('delete_junk','flag_for_deletion','read_when_deleted','skip_deleted','display_next','message_extwin','compose_extwin','forward_attachment') as $prop) {
    if ($CONFIG[$prop])
      $OUTPUT->set_env($prop, true);
  }
  if ($CONFIG['trash_mbox'])
    $OUTPUT->set_env('trash_mailbox', $CONFIG['trash_mbox']);
  if ($CONFIG['drafts_mbox'])
@@ -123,7 +122,10 @@
      'movingmessage', 'copyingmessage', 'deletingmessage', 'markingmessage',
      'copy', 'move', 'quota');
  $OUTPUT->set_pagetitle(rcmail_localize_foldername($RCMAIL->storage->mod_folder($mbox_name)));
  $pagetitle = $RCMAIL->localize_foldername($RCMAIL->storage->mod_folder($mbox_name), true);
  $pagetitle = str_replace($delimiter, " \xC2\xBB ", $pagetitle);
  $OUTPUT->set_pagetitle($pagetitle);
}
/**
@@ -317,7 +319,7 @@
      $col_name = $col == 'fromto' ? $smart_col : $col;
      if (in_array($col_name, array('from', 'to', 'cc', 'replyto')))
        $cont = Q(rcmail_address_string($header->$col_name, 3, false, null, $header->charset), 'show');
        $cont = rcmail_address_string($header->$col_name, 3, false, null, $header->charset);
      else if ($col == 'subject') {
        $cont = trim(rcube_mime::decode_header($header->$col, $header->charset));
        if (!$cont) $cont = rcube_label('nosubject');
@@ -376,6 +378,8 @@
 */
function rcmail_message_list_head($attrib, $a_show_cols)
{
  global $RCMAIL;
  $skin_path = $_SESSION['skin_path'];
  $image_tag = html::img(array('src' => "%s%s", 'alt' => "%s"));
@@ -383,8 +387,18 @@
  $sort_col   = $_SESSION['sort_col'];
  $sort_order = $_SESSION['sort_order'];
  $dont_override  = (array)$RCMAIL->config->get('dont_override');
  $disabled_sort  = in_array('message_sort_col', $dont_override);
  $disabled_order = in_array('message_sort_order', $dont_override);
  $RCMAIL->output->set_env('disabled_sort_col', $disabled_sort);
  $RCMAIL->output->set_env('disabled_sort_order', $disabled_order);
  // define sortable columns
  $a_sort_cols = array('subject', 'date', 'from', 'to', 'fromto', 'size', 'cc');
  if ($disabled_sort)
    $a_sort_cols = $sort_col && !$disabled_order ? array($sort_col) : array();
  else
    $a_sort_cols = array('subject', 'date', 'from', 'to', 'fromto', 'size', 'cc');
  if (!empty($attrib['optionsmenuicon'])) {
    $onclick = 'return ' . JS_OBJECT_NAME . ".command('menu-open', 'messagelistmenu')";
@@ -434,7 +448,7 @@
    else if ($col_name[0] != '<')
      $col_name = '<span class="' . $col .'">' . $col_name . '</span>';
    $sort_class = $col == $sort_col ? " sorted$sort_order" : '';
    $sort_class = $col == $sort_col && !$disabled_order ? " sorted$sort_order" : '';
    $class_name = $col.$sort_class;
    // put it all together
@@ -461,7 +475,7 @@
    $OUTPUT->set_env('contentframe', $attrib['id']);
  $OUTPUT->set_env('blankpage', $attrib['src'] ? $OUTPUT->abs_url($attrib['src']) : 'program/resources/blank.gif');
  return html::iframe($attrib);
  return $OUTPUT->frame($attrib, true);
}
@@ -618,39 +632,6 @@
  $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>
    '/<title[^>]*>[^<]*<\/title>/i',   // PHP bug #32547 workaround: remove title tag
    '/^(\0\0\xFE\xFF|\xFF\xFE\0\0|\xFE\xFF|\xFF\xFE|\xEF\xBB\xBF)/',   // byte-order mark (only outlook?)
    '/<html\s[^>]+>/i',         // washtml/DOMDocument cannot handle xml namespaces
  );
  $html_replace = array(
    '\\1'.' &nbsp; '.'\\3',
    '',
    '',
    '<html>',
  );
  $html = preg_replace($html_search, $html_replace, trim($html));
  // PCRE errors handling (#1486856), should we use something like for every preg_* use?
  if ($html === null && ($preg_error = preg_last_error()) != PREG_NO_ERROR) {
    $errstr = "Could not clean up HTML message! PCRE Error: $preg_error.";
    if ($preg_error == PREG_BACKTRACK_LIMIT_ERROR)
      $errstr .= " Consider raising pcre.backtrack_limit!";
    if ($preg_error == PREG_RECURSION_LIMIT_ERROR)
      $errstr .= " Consider raising pcre.recursion_limit!";
    raise_error(array('code' => 620, 'type' => 'php',
        'line' => __LINE__, 'file' => __FILE__,
        'message' => $errstr), true, false);
    return '';
  }
  // fix (unknown/malformed) HTML tags before "wash"
  $html = preg_replace_callback('/(<[\/]*)([^\s>]+)/', 'rcmail_html_tag_callback', $html);
  // charset was converted to UTF-8 in rcube_storage::get_message_part(),
  // change/add charset specification in HTML accordingly,
  // washtml cannot work without that
@@ -663,9 +644,6 @@
  if (!$rcount) {
    $html = '<head>' . $meta . '</head>' . $html;
  }
  // turn relative into absolute urls
  $html = rcmail_resolve_base($html);
  // clean HTML with washhtml by Frederic Motte
  $wash_opts = array(
@@ -692,7 +670,7 @@
    $wash_opts['html_attribs'] = $p['html_attribs'];
  // initialize HTML washer
  $washer = new washtml($wash_opts);
  $washer = new rcube_washtml($wash_opts);
  if (!$p['skip_washer_form_callback'])
    $washer->add_callback('form', 'rcmail_washtml_callback');
@@ -729,8 +707,11 @@
        + $p + array('safe' => false, 'plain' => false, 'inline_html' => true));
  // convert html to text/plain
  if ($data['type'] == 'html' && $data['plain']) {
    $txt = new html2text($data['body'], false, true);
  if ($data['plain'] && ($data['type'] == 'html' || $data['type'] == 'enriched')) {
    if ($data['type'] == 'enriched') {
      $data['body'] = rcube_enriched::to_html($data['body']);
    }
    $txt = new rcube_html2text($data['body'], false, true);
    $body = $txt->get_text();
    $part->ctype_secondary = 'plain';
  }
@@ -741,9 +722,9 @@
  }
  // text/enriched
  else if ($data['type'] == 'enriched') {
    $body = rcube_enriched::to_html($data['body']);
    $body = rcmail_wash_html($body, $data, $part->replaces);
    $part->ctype_secondary = 'html';
    require_once(INSTALL_PATH . 'program/lib/enriched.inc');
    $body = Q(enriched_to_html($data['body']), 'show');
  }
  else {
    // assert plaintext
@@ -779,11 +760,10 @@
  global $RCMAIL;
  // make links and email-addresses clickable
  $replacer = new rcube_string_replacer;
  $replacer = new rcmail_string_replacer;
  // search for patterns like links and e-mail addresses
  $body = preg_replace_callback($replacer->link_pattern, array($replacer, 'link_callback'), $body);
  $body = preg_replace_callback($replacer->mailto_pattern, array($replacer, 'mailto_callback'), $body);
  // search for patterns like links and e-mail addresses and replace with tokens
  $body = $replacer->replace($body);
  // split body into single lines
  $body = preg_split('/\r?\n/', $body);
@@ -792,8 +772,8 @@
  // find/mark quoted lines...
  for ($n=0, $cnt=count($body); $n < $cnt; $n++) {
    if ($body[$n][0] == '>' && preg_match('/^(>+\s*)+/', $body[$n], $regs)) {
      $q = strlen(preg_replace('/\s/', '', $regs[0]));
    if ($body[$n][0] == '>' && preg_match('/^(>+ {0,1})+/', $body[$n], $regs)) {
      $q        = substr_count($regs[0], '>');
      $body[$n] = substr($body[$n], strlen($regs[0]));
      if ($q > $quote_level) {
@@ -912,31 +892,15 @@
/**
 * Callback function for HTML tags fixing
 */
function rcmail_html_tag_callback($matches)
{
  $tagname = $matches[2];
  $tagname = preg_replace(array(
    '/:.*$/',         // Microsoft's Smart Tags <st1:xxxx>
    '/[^a-z0-9_\[\]\!-]/i',   // forbidden characters
    ), '', $tagname);
  return $matches[1].$tagname;
}
/**
 * return table with message headers
 */
function rcmail_message_headers($attrib, $headers=NULL)
function rcmail_message_headers($attrib, $headers=null)
  {
  global $OUTPUT, $MESSAGE, $PRINT_MODE, $RCMAIL;
  static $sa_attrib;
  // keep header table attrib
  if (is_array($attrib) && !$sa_attrib)
  if (is_array($attrib) && !$sa_attrib && !$attrib['valueof'])
    $sa_attrib = $attrib;
  else if (!is_array($attrib) && is_array($sa_attrib))
    $attrib = $sa_attrib;
@@ -945,16 +909,27 @@
    return FALSE;
  // get associative array of headers object
  if (!$headers)
    $headers = is_object($MESSAGE->headers) ? get_object_vars($MESSAGE->headers) : $MESSAGE->headers;
  if (!$headers) {
    $headers_obj = $MESSAGE->headers;
    $headers = get_object_vars($MESSAGE->headers);
  }
  else if (is_object($headers)) {
    $headers_obj = $headers;
    $headers = get_object_vars($headers_obj);
  }
  else {
    $headers_obj = rcube_message_header::from_array($headers);
  }
  // show these headers
  $standard_headers = array('subject', 'from', 'to', 'cc', 'bcc', 'replyto',
  $standard_headers = array('subject', 'from', 'sender', 'to', 'cc', 'bcc', 'replyto',
    'mail-reply-to', 'mail-followup-to', 'date', 'priority');
  $exclude_headers = $attrib['exclude'] ? explode(',', $attrib['exclude']) : array();
  $output_headers = array();
  foreach ($standard_headers as $hkey) {
    $ishtml = false;
    if ($headers[$hkey])
      $value = $headers[$hkey];
    else if ($headers['others'][$hkey])
@@ -964,6 +939,8 @@
    if (in_array($hkey, $exclude_headers))
      continue;
    $header_title = rcube_label(preg_replace('/(^mail-|-)/', '', $hkey));
    if ($hkey == 'date') {
      if ($PRINT_MODE)
@@ -979,37 +956,54 @@
        continue;
    }
    else if ($hkey == 'replyto') {
      if ($headers['replyto'] != $headers['from'])
        $header_value = rcmail_address_string($value, null, true, $attrib['addicon'], $headers['charset']);
      if ($headers['replyto'] != $headers['from']) {
        $header_value = rcmail_address_string($value, $attrib['max'], true, $attrib['addicon'], $headers['charset'], $header_title);
        $ishtml = true;
      }
      else
        continue;
    }
    else if ($hkey == 'mail-reply-to') {
      if ($headers['mail-replyto'] != $headers['reply-to']
        && $headers['reply-to'] != $headers['from']
      )
        $header_value = rcmail_address_string($value, null, true, $attrib['addicon'], $headers['charset']);
      ) {
        $header_value = rcmail_address_string($value, $attrib['max'], true, $attrib['addicon'], $headers['charset'], $header_title);
        $ishtml = true;
      }
      else
        continue;
    }
    else if ($hkey == 'sender') {
      if ($headers['sender'] != $headers['from']) {
        $header_value = rcmail_address_string($value, $attrib['max'], true, $attrib['addicon'], $headers['charset'], $header_title);
        $ishtml = true;
      }
      else
        continue;
    }
    else if ($hkey == 'mail-followup-to') {
      $header_value = rcmail_address_string($value, null, true, $attrib['addicon'], $headers['charset']);
      $header_value = rcmail_address_string($value, $attrib['max'], true, $attrib['addicon'], $headers['charset'], $header_title);
      $ishtml = true;
    }
    else if (in_array($hkey, array('from', 'to', 'cc', 'bcc')))
      $header_value = rcmail_address_string($value, null, true, $attrib['addicon'], $headers['charset']);
    else if (in_array($hkey, array('from', 'to', 'cc', 'bcc'))) {
      $header_value = rcmail_address_string($value, $attrib['max'], true, $attrib['addicon'], $headers['charset'], $header_title);
      $ishtml = true;
    }
    else if ($hkey == 'subject' && empty($value))
      $header_value = rcube_label('nosubject');
    else
      $header_value = trim(rcube_mime::decode_header($value, $headers['charset']));
    $output_headers[$hkey] = array(
        'title' => rcube_label(preg_replace('/(^mail-|-)/', '', $hkey)),
        'value' => $header_value, 'raw' => $value
        'title' => $header_title,
        'value' => $header_value,
        'raw' => $value,
        'html' => $ishtml,
    );
  }
  $plugin = $RCMAIL->plugins->exec_hook('message_headers_output',
    array('output' => $output_headers, 'headers' => $MESSAGE->headers, 'exclude' => $exclude_headers));
    array('output' => $output_headers, 'headers' => $headers_obj, 'exclude' => $exclude_headers));
  // single header value is requested
  if (!empty($attrib['valueof']))
@@ -1020,7 +1014,7 @@
  foreach ($plugin['output'] as $hkey => $row) {
    $table->add(array('class' => 'header-title'), Q($row['title']));
    $table->add(array('class' => 'header '.$hkey), Q($row['value'], ($hkey == 'subject' ? 'strict' : 'show')));
    $table->add(array('class' => 'header '.$hkey), $row['html'] ? $row['value'] : Q($row['value'], ($hkey == 'subject' ? 'strict' : 'show')));
  }
  return $table->show($attrib);
@@ -1038,10 +1032,10 @@
    '4' => 'low',
    '5' => 'lowest',
  );
  if ($value && $labels_map[$value])
    return rcube_label($labels_map[$value]);
  return '';
}
@@ -1053,7 +1047,7 @@
  global $OUTPUT;
  $html = html::div(array('id' => "all-headers", 'class' => "all", 'style' => 'display:none'), html::div(array('id' => 'headers-source'), ''));
  $html .= html::div(array('class' => "more-headers show-headers", 'onclick' => "return ".JS_OBJECT_NAME.".command('show-headers','',this)"), '');
  $html .= html::div(array('class' => "more-headers show-headers", 'onclick' => "return ".JS_OBJECT_NAME.".command('show-headers','',this)", 'title' => rcube_label('togglefullheaders')), '');
  $OUTPUT->add_gui_object('all_headers_row', 'all-headers');
  $OUTPUT->add_gui_object('all_headers_box', 'headers-source');
@@ -1088,12 +1082,13 @@
  if (!empty($MESSAGE->parts)) {
    foreach ($MESSAGE->parts as $i => $part) {
      if ($part->type == 'headers')
        $out .= rcmail_message_headers(sizeof($header_attrib) ? $header_attrib : NULL, $part->headers);
      if ($part->type == 'headers') {
        $out .= html::div('message-partheaders', rcmail_message_headers(sizeof($header_attrib) ? $header_attrib : null, $part->headers));
      }
      else if ($part->type == 'content') {
        // unsapported
        // unsupported (e.g. encrypted)
        if ($part->realtype) {
          if ($part->realtype == 'multipart/encrypted') {
          if ($part->realtype == 'multipart/encrypted' || $part->realtype == 'application/pkcs7-mime') {
            $out .= html::span('part-notice', rcube_label('encryptedmessage'));
          }
          continue;
@@ -1116,6 +1111,15 @@
        // fetch part if not available
        if (!isset($part->body))
          $part->body = $MESSAGE->get_part_content($part->mime_id);
        // extract headers from message/rfc822 parts
        if ($part->mimetype == 'message/rfc822') {
          $msgpart = rcube_mime::parse_message($part->body);
          if (!empty($msgpart->headers)) {
            $part = $msgpart;
            $out .= html::div('message-partheaders', rcmail_message_headers(sizeof($header_attrib) ? $header_attrib : null, $part->headers));
          }
        }
        // message is cached but not exists (#1485443), or other error
        if ($part->body === false) {
@@ -1164,7 +1168,10 @@
  }
  // list images after mail body
  if ($CONFIG['inline_images'] && !empty($MESSAGE->attachments)) {
  if ($RCMAIL->config->get('inline_images', true) && !empty($MESSAGE->attachments)) {
    $thumbnail_size = $RCMAIL->config->get('image_thumbnail_size', 240);
    $client_mimetypes = (array)$RCMAIL->config->get('client_mimetypes');
    foreach ($MESSAGE->attachments as $attach_prop) {
      // skip inline images
      if ($attach_prop->content_id && $attach_prop->disposition == 'inline') {
@@ -1172,13 +1179,46 @@
      }
      // Content-Type: image/*...
      if (rcmail_part_image_type($attach_prop)) {
        $out .= html::tag('hr') . html::p(array('align' => "center"),
          html::img(array(
            'src' => $MESSAGE->get_part_url($attach_prop->mime_id, true),
            'title' => $attach_prop->filename,
            'alt' => $attach_prop->filename,
          )));
      if ($mimetype = rcmail_part_image_type($attach_prop)) {
        // display thumbnails
        if ($thumbnail_size) {
          $show_link = array(
            'href' => $MESSAGE->get_part_url($attach_prop->mime_id, false),
            'onclick' => sprintf(
              'return %s.command(\'load-attachment\',\'%s\',this)',
              JS_OBJECT_NAME,
              $attach_prop->mime_id)
          );
          $out .= html::p('image-attachment',
             html::a($show_link + array('class' => 'image-link', 'style' => sprintf('width:%dpx', $thumbnail_size)),
               html::img(array(
                'class' => 'image-thumbnail',
                'src'   => $MESSAGE->get_part_url($attach_prop->mime_id, true) . '&_thumb=1',
                'title' => $attach_prop->filename,
                'alt'   => $attach_prop->filename,
                'style' => sprintf('max-width:%dpx; max-height:%dpx', $thumbnail_size, $thumbnail_size),
              ))
            ) .
            html::span('image-filename', Q($attach_prop->filename)) .
            html::span('image-filesize', Q($RCMAIL->message_part_size($attach_prop))) .
            html::span('attachment-links',
              (in_array($mimetype, $client_mimetypes) ? html::a($show_link, rcube_label('showattachment')) . '&nbsp;' : '') .
              html::a($show_link['href'] . '&_download=1', rcube_label('download'))
            ) .
            html::br(array('style' => 'clear:both'))
          );
        }
        else {
          $out .= html::tag('fieldset', 'image-attachment',
            html::tag('legend', 'image-filename', Q($attach_prop->filename)) .
            html::p(array('align' => "center"),
              html::img(array(
                'src'   => $MESSAGE->get_part_url($attach_prop->mime_id, true),
                'title' => $attach_prop->filename,
                'alt'   => $attach_prop->filename,
              )))
          );
        }
      }
    }
  }
@@ -1204,7 +1244,7 @@
  // Content-Type: image/*...
  if (preg_match($mime_regex, $part->mimetype)) {
    return $part->mimetype;
    return rcmail_fix_mimetype($part->mimetype);
  }
  // Many clients use application/octet-stream, we'll detect mimetype
@@ -1225,26 +1265,12 @@
  if ($part->filename
    && preg_match('/^application\/octet-stream$/i', $part->mimetype)
    && preg_match('/\.([^.])$/i', $part->filename, $m)
    && preg_match('/\.([^.]+)$/i', $part->filename, $m)
    && ($extension = strtolower($m[1]))
    && isset($types[$extension])
  ) {
    return $types[$extension];
  }
}
/**
 * Convert all relative URLs according to a <base> in HTML
 */
function rcmail_resolve_base($body)
{
  // check for <base href=...>
  if (preg_match('!(<base.*href=["\']?)([hftps]{3,5}://[a-z0-9/.%-]+)!i', $body, $regs)) {
    $replacer = new rcube_base_replacer($regs[2]);
    $body     = $replacer->replace($body);
  }
  return $body;
}
@@ -1272,7 +1298,7 @@
  // modify HTML links to open a new window if clicked
  $GLOBALS['rcmail_html_container_id'] = $container_id;
  $body = preg_replace_callback('/<(a|link)\s+([^>]+)>/Ui', 'rcmail_alter_html_link', $body);
  $body = preg_replace_callback('/<(a|link|area)\s+([^>]+)>/Ui', 'rcmail_alter_html_link', $body);
  unset($GLOBALS['rcmail_html_container_id']);
  $body = preg_replace(array(
@@ -1385,14 +1411,18 @@
    $attrib['target'] = '_blank';
  }
  return "<$tag" . html::attrib_string($attrib, array('href','name','target','onclick','id','class','style','title','rel','type','media')) . $end;
  // allowed attributes for a|link|area tags
  $allow = array('href','name','target','onclick','id','class','style','title',
    'rel','type','media','alt','coords','nohref','hreflang','shape');
  return "<$tag" . html::attrib_string($attrib, $allow) . $end;
}
/**
 * decode address string and re-format it as HTML links
 */
function rcmail_address_string($input, $max=null, $linked=false, $addicon=null, $default_charset=null)
function rcmail_address_string($input, $max=null, $linked=false, $addicon=null, $default_charset=null, $title=null)
{
  global $RCMAIL, $PRINT_MODE, $CONFIG;
@@ -1404,6 +1434,7 @@
  $c = count($a_parts);
  $j = 0;
  $out = '';
  $allvalues = array();
  if ($addicon && !isset($_SESSION['writeable_abook'])) {
    $_SESSION['writeable_abook'] = $RCMAIL->get_address_sources(true) ? true : false;
@@ -1411,10 +1442,14 @@
  foreach ($a_parts as $part) {
    $j++;
    $name   = $part['name'];
    $mailto = $part['mailto'];
    $string = $part['string'];
    // phishing email prevention (#1488981), e.g. "valid@email.addr <phishing@email.addr>"
    if ($name && $name != $mailto && strpos($name, '@')) {
      $name = '';
    }
    // IDNA ASCII to Unicode
    if ($name == $mailto)
@@ -1424,7 +1459,9 @@
    $mailto = rcube_idn_to_utf8($mailto);
    if ($PRINT_MODE) {
      $out .= sprintf('%s &lt;%s&gt;', Q($name), $mailto);
      $out .= ($out ? ', ' : '') . sprintf('%s &lt;%s&gt;', Q($name), $mailto);
      // for printing we display all addresses
      continue;
    }
    else if (check_email($part['mailto'], false)) {
      if ($linked) {
@@ -1442,33 +1479,53 @@
      }
      if ($addicon && $_SESSION['writeable_abook']) {
        $address = html::span(null, $address . html::a(array(
        $address .= html::a(array(
            'href' => "#add",
            'onclick' => sprintf("return %s.command('add-contact','%s',this)", JS_OBJECT_NAME, $string),
            'onclick' => sprintf("return %s.command('add-contact','%s',this)", JS_OBJECT_NAME, JQ($string)),
            'title' => rcube_label('addtoaddressbook'),
            'class' => 'rcmaddcontact',
          ),
          html::img(array(
            'src' => $CONFIG['skin_path'] . $addicon,
            'alt' => "Add contact",
          ))));
          )));
      }
      $out .= $address;
    }
    else {
      $address = '';
      if ($name)
        $out .= Q($name);
        $address .= Q($name);
      if ($mailto)
        $out .= (strlen($out) ? ' ' : '') . sprintf('&lt;%s&gt;', Q($mailto));
        $address .= (strlen($address) ? ' ' : '') . sprintf('&lt;%s&gt;', Q($mailto));
    }
    if ($c>$j)
      $out .= ','.($max ? '&nbsp;' : ' ');
    $address = html::span('adr', $address);
    $allvalues[] = $address;
    if ($max && $j==$max && $c>$j) {
      $out .= '...';
      break;
    if (!$moreadrs)
      $out .= ($out ? ', ' : '') . $address;
    if ($max && $j == $max && $c > $j) {
      if ($linked) {
        $moreadrs = $c - $j;
      }
      else {
        $out .= '...';
        break;
      }
    }
  }
  if ($moreadrs) {
      $out .= ' ' . html::a(array(
          'href' => '#more',
          'class' => 'morelink',
          'onclick' => sprintf("return %s.show_popup_dialog('%s','%s')",
            JS_OBJECT_NAME,
            JQ(join(', ', $allvalues)),
            JQ($title))
        ),
        Q(rcube_label(array('name' => 'andnmore', 'vars' => array('nr' => $moreadrs)))));
  }
  return $out;
@@ -1513,7 +1570,7 @@
    $out .= $line . "\n";
  }
  return $out;
  return rtrim($out, "\n");
}
@@ -1552,10 +1609,7 @@
  $part  = $MESSAGE->mime_parts[$part];
  $table = new html_table(array('cols' => 3));
  $filename = $part->filename;
  if (empty($filename) && $attach_prop->mimetype == 'text/html') {
    $filename = rcube_label('htmlmessage');
  }
  $filename = rcmail_attachment_name($part);
  if (!empty($filename)) {
    $table->add('title', Q(rcube_label('filename')));
@@ -1570,7 +1624,6 @@
}
function rcmail_message_part_frame($attrib)
{
  global $MESSAGE;
@@ -1578,7 +1631,7 @@
  $part = $MESSAGE->mime_parts[asciiwords(get_input_value('_part', RCUBE_INPUT_GPC))];
  $ctype_primary = strtolower($part->ctype_primary);
  $attrib['src'] = './?' . str_replace('_frame=', ($ctype_primary=='text' ? '_show=' : '_preload='), $_SERVER['QUERY_STRING']);
  $attrib['src'] = './?' . str_replace('_frame=', ($ctype_primary=='text' ? '_embed=' : '_preload='), $_SERVER['QUERY_STRING']);
  return html::iframe($attrib);
}
@@ -1616,11 +1669,11 @@
  if ($message->headers->mdn_to && empty($message->headers->flags['MDNSENT']) &&
    ($RCMAIL->storage->check_permflag('MDNSENT') || $RCMAIL->storage->check_permflag('*')))
  {
    $identity = $RCMAIL->user->get_identity();
    $sender = format_email_recipient($identity['email'], $identity['name']);
    $identity  = rcmail_identity_select($message);
    $sender    = format_email_recipient($identity['email'], $identity['name']);
    $recipient = array_shift(rcube_mime::decode_address_list(
      $message->headers->mdn_to, 1, true, $message->headers->charset));
    $mailto = $recipient['mailto'];
    $mailto    = $recipient['mailto'];
    $compose = new Mail_mime("\r\n");
@@ -1645,6 +1698,9 @@
    if ($agent = $RCMAIL->config->get('useragent'))
      $headers['User-Agent'] = $agent;
    if ($RCMAIL->config->get('mdn_use_from'))
      $options['mdn_use_from'] = true;
    $body = rcube_label("yourmessage") . "\r\n\r\n" .
      "\t" . rcube_label("to") . ': ' . rcube_mime::decode_mime_string($message->headers->to, $message->headers->charset) . "\r\n" .
      "\t" . rcube_label("subject") . ': ' . $message->subject . "\r\n" .
@@ -1666,7 +1722,7 @@
    $compose->setTXTBody(rc_wordwrap($body, 75, "\r\n"));
    $compose->addAttachment($report, 'message/disposition-notification', 'MDNPart2.txt', false, '7bit', 'inline');
    $sent = rcmail_deliver_message($compose, $identity['email'], $mailto, $smtp_error, $body_file);
    $sent = rcmail_deliver_message($compose, $identity['email'], $mailto, $smtp_error, $body_file, $options);
    if ($sent)
    {
@@ -1678,6 +1734,107 @@
  return false;
}
/**
 * Detect recipient identity from specified message
 */
function rcmail_identity_select($MESSAGE, $identities = null, $compose_mode = 'reply')
{
    $a_recipients = array();
    $a_names      = array();
    if ($identities === null) {
        $identities = rcmail::get_instance()->user->list_identities(null, true);
    }
    // extract all recipients of the reply-message
    if (is_object($MESSAGE->headers) && in_array($compose_mode, array('reply', 'forward'))) {
        $a_to = rcube_mime::decode_address_list($MESSAGE->headers->to, null, true, $MESSAGE->headers->charset);
        foreach ($a_to as $addr) {
            if (!empty($addr['mailto'])) {
                $a_recipients[] = format_email($addr['mailto']);
                $a_names[]      = $addr['name'];
            }
        }
        if (!empty($MESSAGE->headers->cc)) {
            $a_cc = rcube_mime::decode_address_list($MESSAGE->headers->cc, null, true, $MESSAGE->headers->charset);
            foreach ($a_cc as $addr) {
                if (!empty($addr['mailto'])) {
                    $a_recipients[] = format_email($addr['mailto']);
                    $a_names[]      = $addr['name'];
                }
            }
        }
    }
    $from_idx         = null;
    $found_idx        = null;
    $default_identity = 0; // default identity is always first on the list
    // Select identity
    foreach ($identities as $idx => $ident) {
        // use From header
        if (in_array($compose_mode, array('draft', 'edit'))) {
            if ($MESSAGE->headers->from == $ident['ident']) {
                $from_idx = $idx;
                break;
            }
        }
        // reply to yourself
        else if ($compose_mode == 'reply' && $MESSAGE->headers->from == $ident['ident']) {
            $from_idx = $idx;
            break;
        }
        // use replied message recipients
        else if (($found = array_search($ident['email_ascii'], $a_recipients)) !== false) {
            if ($found_idx === null) {
                $found_idx = $idx;
            }
            // match identity name
            if ($a_names[$found] && $ident['name'] && $a_names[$found] == $ident['name']) {
                $from_idx = $idx;
                break;
            }
        }
    }
    // If matching by name+address doesn't found any matches, get first found address (identity)
    if ($from_idx === null) {
        $from_idx = $found_idx;
    }
    // Try Return-Path
    if ($from_idx === null && ($return_path = $MESSAGE->headers->others['return-path'])) {
        foreach ($identities as $idx => $ident) {
            if (strpos($return_path, str_replace('@', '=', $ident['email_ascii']).'@') !== false) {
                $from_idx = $idx;
                break;
            }
        }
    }
    // Fallback using Delivered-To
    if ($from_idx === null && ($delivered_to = $MESSAGE->headers->others['delivered-to'])) {
        foreach ($identities as $idx => $ident) {
            if (in_array($ident['email_ascii'], (array)$delivered_to)) {
                $from_idx = $idx;
                break;
            }
        }
    }
    // Fallback using Envelope-To
    if ($from_idx === null && ($envelope_to = $MESSAGE->headers->others['envelope-to'])) {
        foreach ($identities as $idx => $ident) {
            if (in_array($ident['email_ascii'], (array)$envelope_to)) {
                $from_idx = $idx;
                break;
            }
        }
    }
    return $identities[$from_idx !== null ? $from_idx : $default_identity];
}
// Fixes some content-type names
function rcmail_fix_mimetype($name)
@@ -1687,7 +1844,42 @@
  if (preg_match('/^application\/pdf.+/', $name))
    $name = 'application/pdf';
  // treat image/pjpeg as image/jpeg
  else if (preg_match('/^image\/p?jpe?g$/', $name))
    $name = 'image/jpeg';
  return $name;
}
// return attachment filename, handle empty filename case
function rcmail_attachment_name($attachment, $display = false)
{
    $filename = $attachment->filename;
    if ($filename === null || $filename === '') {
        if ($attachment->mimetype == 'text/html') {
            $filename = rcube_label('htmlmessage');
        }
        else {
            $ext      = (array) rcube_mime::get_mime_extensions($attachment->mimetype);
            $ext      = array_shift($ext);
            $filename = rcube_label('messagepart') . ' ' . $attachment->mime_id;
            if ($ext) {
                $filename .= '.' . $ext;
            }
        }
    }
    $filename = preg_replace('[\r\n]', '', $filename);
    // Display smart names for some known mimetypes
    if ($display) {
        if (preg_match('/application\/(pgp|pkcs7)-signature/i', $attachment->mimetype)) {
            $filename = rcube_label('digitalsig');
        }
    }
    return $filename;
}
function rcmail_search_filter($attrib)
@@ -1764,6 +1956,7 @@
// register action aliases
$RCMAIL->register_action_map(array(
    'refresh' => 'check_recent.inc',
    'preview' => 'show.inc',
    'print'   => 'show.inc',
    'moveto'  => 'move_del.inc',