From a8b004e8d8f040d868e4b19da9527c177be9959d Mon Sep 17 00:00:00 2001
From: Aleksander Machniak <alec@alec.pl>
Date: Mon, 04 Nov 2013 05:19:56 -0500
Subject: [PATCH] Improve identity selection based on From: header (#1489378)

---
 program/steps/mail/func.inc |  585 ++++++++++++++++++++++++++++-----------------------------
 1 files changed, 289 insertions(+), 296 deletions(-)

diff --git a/program/steps/mail/func.inc b/program/steps/mail/func.inc
index 7049376..78a977b 100644
--- a/program/steps/mail/func.inc
+++ b/program/steps/mail/func.inc
@@ -89,13 +89,15 @@
   }
 
   $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('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('reply_all_mode', (int) $RCMAIL->config->get('reply_all_mode'));
   $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);
@@ -119,10 +121,47 @@
   if (!$OUTPUT->ajax_call)
     $OUTPUT->add_label('checkingmail', 'deletemessage', 'movemessagetotrash',
       'movingmessage', 'copyingmessage', 'deletingmessage', 'markingmessage',
-      'copy', 'move', 'quota');
+      'copy', 'move', 'quota', 'replyall', 'replylist', 'importwait');
 
-  $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);
 }
+
+// register UI objects
+$OUTPUT->add_handlers(array(
+  'mailboxlist'         => 'rcmail_mailbox_list',
+  'messages'            => 'rcmail_message_list',
+  'messagecountdisplay' => 'rcmail_messagecount_display',
+  'quotadisplay'        => 'rcmail_quota_display',
+  'mailboxname'         => 'rcmail_mailbox_name_display',
+  'messageheaders'      => 'rcmail_message_headers',
+  'messagefullheaders'  => 'rcmail_message_full_headers',
+  'messagebody'         => 'rcmail_message_body',
+  'messagecontentframe' => 'rcmail_messagecontent_frame',
+  'messageimportform'   => 'rcmail_message_import_form',
+  'searchfilter'        => 'rcmail_search_filter',
+  'searchform'          => array($OUTPUT, 'search_form'),
+));
+
+// register action aliases
+$RCMAIL->register_action_map(array(
+    'refresh' => 'check_recent.inc',
+    'preview' => 'show.inc',
+    'print'   => 'show.inc',
+    'move'    => 'move_del.inc',
+    'delete'  => 'move_del.inc',
+    'send'    => 'sendmail.inc',
+    'expunge' => 'folders.inc',
+    'purge'   => 'folders.inc',
+    'remove-attachment'  => 'attachments.inc',
+    'display-attachment' => 'attachments.inc',
+    'upload'             => 'attachments.inc',
+    'group-expand'       => 'autocomplete.inc',
+));
+
+
 
 /**
  * Returns 'to' if current folder is configured Sent or Drafts
@@ -139,7 +178,9 @@
   $sent_mbox   = $RCMAIL->config->get('sent_mbox');
   $drafts_mbox = $RCMAIL->config->get('drafts_mbox');
 
-  if (strpos($mbox.$delim, $sent_mbox.$delim) === 0 || strpos($mbox.$delim, $drafts_mbox.$delim) === 0) {
+  if ((strpos($mbox.$delim, $sent_mbox.$delim) === 0 || strpos($mbox.$delim, $drafts_mbox.$delim) === 0)
+    && strtoupper($mbox) != 'INBOX'
+  ) {
     return 'to';
   }
 
@@ -220,7 +261,7 @@
   if (!in_array('threads', $a_show_cols))
     array_unshift($a_show_cols, 'threads');
 
-  $skin_path = $_SESSION['skin_path'] = $CONFIG['skin_path'];
+  $_SESSION['skin_path'] = $CONFIG['skin_path'];
 
   // set client env
   $OUTPUT->add_gui_object('messagelist', $attrib['id']);
@@ -232,15 +273,13 @@
 
   $OUTPUT->include_script('list.js');
 
-  $thead = '';
-  foreach (rcmail_message_list_head($attrib, $a_show_cols) as $cell)
-    $thead .= html::tag('td', array('class' => $cell['className'], 'id' => $cell['id']), $cell['html']);
+  $table = new html_table($attrib);
+  if (!$attrib['noheader']) {
+    foreach (rcmail_message_list_head($attrib, $a_show_cols) as $cell)
+      $table->add_header(array('class' => $cell['className'], 'id' => $cell['id']), $cell['html']);
+  }
 
-  return html::tag('table',
-    $attrib,
-    html::tag('thead', null, html::tag('tr', null, $thead)) .
-      html::tag('tbody', null, ''),
-        array('style', 'class', 'id', 'cellpadding', 'cellspacing', 'border', 'summary'));
+  return $table->show();
 }
 
 
@@ -287,7 +326,7 @@
   $thead = $head_replace ? rcmail_message_list_head($_SESSION['list_attrib'], $a_show_cols) : NULL;
 
   // get name of smart From/To column in folder context
-  if (($f = array_search('fromto', $a_show_cols)) !== false) {
+  if (array_search('fromto', $a_show_cols) !== false) {
     $smart_col = rcmail_message_list_smart_column_name();
   }
 
@@ -303,7 +342,7 @@
   }
 
   // loop through message headers
-  foreach ($a_headers as $n => $header) {
+  foreach ($a_headers as $header) {
     if (empty($header))
       continue;
 
@@ -377,7 +416,6 @@
   global $RCMAIL;
 
   $skin_path = $_SESSION['skin_path'];
-  $image_tag = html::img(array('src' => "%s%s", 'alt' => "%s"));
 
   // check to see if we have some settings for sorting
   $sort_col   = $_SESSION['sort_col'];
@@ -413,7 +451,7 @@
   $cells = array();
 
   // get name of smart From/To column in folder context
-  if (($f = array_search('fromto', $a_show_cols)) !== false) {
+  if (array_search('fromto', $a_show_cols) !== false) {
     $smart_col = rcmail_message_list_smart_column_name();
   }
 
@@ -604,6 +642,8 @@
             $message->set_safe(true);
           }
         }
+
+        $RCMAIL->plugins->exec_hook('message_check_safe', array('message' => $message));
         break;
 
       case 2: // always
@@ -628,39 +668,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
@@ -673,9 +680,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(
@@ -702,7 +706,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');
@@ -739,8 +743,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';
   }
@@ -765,8 +772,13 @@
   unset($data['body']);
 
   // plaintext postprocessing
-  if ($part->ctype_secondary == 'plain')
-    $body = rcmail_plain_body($body, $part->ctype_parameters['format'] == 'flowed');
+  if ($part->ctype_secondary == 'plain') {
+    if ($part->ctype_secondary == 'plain' && $part->ctype_parameters['format'] == 'flowed') {
+      $body = rcube_mime::unfold_flowed($body);
+    }
+
+    $body = rcmail_plain_body($body);
+  }
 
   // allow post-processing of the message body
   $data = $RCMAIL->plugins->exec_hook('message_part_after',
@@ -780,16 +792,16 @@
  * Handle links and citation marks in plain text message
  *
  * @param string  Plain text string
- * @param boolean Text uses format=flowed
  *
  * @return string Formatted HTML string
  */
-function rcmail_plain_body($body, $flowed=false)
+function rcmail_plain_body($body)
 {
   global $RCMAIL;
 
   // make links and email-addresses clickable
-  $replacer = new rcmail_string_replacer;
+  $attribs  = array('link_attribs' => array('rel' => 'noreferrer', 'target' => '_blank'));
+  $replacer = new rcmail_string_replacer($attribs);
 
   // search for patterns like links and e-mail addresses and replace with tokens
   $body = $replacer->replace($body);
@@ -808,53 +820,17 @@
       if ($q > $quote_level) {
         $body[$n] = $replacer->get_replacement($replacer->add(
           str_repeat('<blockquote>', $q - $quote_level))) . $body[$n];
+        $last = $n;
       }
       else if ($q < $quote_level) {
         $body[$n] = $replacer->get_replacement($replacer->add(
           str_repeat('</blockquote>', $quote_level - $q))) . $body[$n];
-      }
-      else if ($flowed) {
-        // previous line is flowed
-        if (isset($body[$last]) && $body[$n]
-          && $body[$last][strlen($body[$last])-1] == ' ') {
-          // merge lines
-          $body[$last] .= $body[$n];
-          unset($body[$n]);
-        }
-        else {
-          $last = $n;
-        }
+        $last = $n;
       }
     }
     else {
       $q = 0;
-      if ($flowed) {
-        // sig separator - line is fixed
-        if ($body[$n] == '-- ') {
-          $last = $last_sig = $n;
-        }
-        else {
-          // remove space-stuffing
-          if ($body[$n][0] == ' ')
-            $body[$n] = substr($body[$n], 1);
-
-          // previous line is flowed?
-          if (isset($body[$last]) && $body[$n]
-            && $last !== $last_sig
-            && $body[$last][strlen($body[$last])-1] == ' '
-          ) {
-            $body[$last] .= $body[$n];
-            unset($body[$n]);
-          }
-          else {
-            $last = $n;
-          }
-        }
-        if ($quote_level > 0)
-          $body[$last] = $replacer->get_replacement($replacer->add(
-            str_repeat('</blockquote>', $quote_level))) . $body[$last];
-      }
-      else if ($quote_level > 0)
+      if ($quote_level > 0)
         $body[$n] = $replacer->get_replacement($replacer->add(
           str_repeat('</blockquote>', $quote_level))) . $body[$n];
     }
@@ -921,27 +897,11 @@
 
 
 /**
- * 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)
-  {
-  global $OUTPUT, $MESSAGE, $PRINT_MODE, $RCMAIL;
+{
+  global $MESSAGE, $PRINT_MODE, $RCMAIL;
   static $sa_attrib;
 
   // keep header table attrib
@@ -979,7 +939,7 @@
       $value = $headers[$hkey];
     else if ($headers['others'][$hkey])
       $value = $headers['others'][$hkey];
-    else
+    else if (!$attrib['valueof'])
       continue;
 
     if (in_array($hkey, $exclude_headers))
@@ -1126,14 +1086,14 @@
       $header_attrib[$regs[1]] = $value;
 
   if (!empty($MESSAGE->parts)) {
-    foreach ($MESSAGE->parts as $i => $part) {
+    foreach ($MESSAGE->parts as $part) {
       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;
@@ -1230,16 +1190,15 @@
           $show_link = array(
             'href' => $MESSAGE->get_part_url($attach_prop->mime_id, false),
             'onclick' => sprintf(
-              'return %s.command(\'load-attachment\',{part:\'%s\', mimetype:\'%s\'},this)',
+              'return %s.command(\'load-attachment\',\'%s\',this)',
               JS_OBJECT_NAME,
-              $attach_prop->mime_id,
-              $mimetype)
+              $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',
+                'src'   => $MESSAGE->get_part_url($attach_prop->mime_id, 'image') . '&_thumb=1',
                 'title' => $attach_prop->filename,
                 'alt'   => $attach_prop->filename,
                 'style' => sprintf('max-width:%dpx; max-height:%dpx', $thumbnail_size, $thumbnail_size),
@@ -1259,7 +1218,7 @@
             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),
+                'src'   => $MESSAGE->get_part_url($attach_prop->mime_id, 'image'),
                 'title' => $attach_prop->filename,
                 'alt'   => $attach_prop->filename,
               )))
@@ -1317,20 +1276,6 @@
   ) {
     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;
 }
 
 
@@ -1433,18 +1378,15 @@
 
 
 /**
- * parse link attributes and set correct target
+ * parse link (a, link, area) attributes and set correct target
  */
 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 = $matches[1];
+  $tag    = strtolower($matches[1]);
   $attrib = parse_attrib_string($matches[2]);
-  $end = '>';
+  $end    = '>';
 
   // Remove non-printable characters in URL (#1487805)
   if ($attrib['href'])
@@ -1456,12 +1398,36 @@
     $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';
@@ -1469,6 +1435,11 @@
   }
   else if (!empty($attrib['href']) && $attrib['href'][0] != '#') {
     $attrib['target'] = '_blank';
+  }
+
+  // Better security by adding rel="noreferrer" (#1484686)
+  if (($tag == 'a' || $tag == 'area') && $attrib['href'] && $attrib['href'][0] != '#') {
+    $attrib['rel'] = 'noreferrer';
   }
 
   // allowed attributes for a|link|area tags
@@ -1494,7 +1465,8 @@
   $c = count($a_parts);
   $j = 0;
   $out = '';
-  $allvalues = array();
+  $allvalues  = array();
+  $show_email = $RCMAIL->config->get('message_show_email');
 
   if ($addicon && !isset($_SESSION['writeable_abook'])) {
     $_SESSION['writeable_abook'] = $RCMAIL->get_address_sources(true) ? true : false;
@@ -1505,6 +1477,12 @@
     $name   = $part['name'];
     $mailto = $part['mailto'];
     $string = $part['string'];
+    $valid  = check_email($mailto, false);
+
+    // phishing email prevention (#1488981), e.g. "valid@email.addr <phishing@email.addr>"
+    if (!$show_email && $valid && $name && $name != $mailto && strpos($name, '@')) {
+      $name = '';
+    }
 
     // IDNA ASCII to Unicode
     if ($name == $mailto)
@@ -1514,19 +1492,25 @@
     $mailto = rcube_idn_to_utf8($mailto);
 
     if ($PRINT_MODE) {
-      $out .= ($out ? ', ' : '') . sprintf('%s &lt;%s&gt;', Q($name), $mailto);
-      // for printing we display all addresses
-      continue;
+      $address = sprintf('%s &lt;%s&gt;', Q($name), Q($mailto));
     }
-    else if (check_email($part['mailto'], false)) {
+    else if ($valid) {
       if ($linked) {
-        $address = html::a(array(
-            'href' => 'mailto:'.$mailto,
-            'onclick' => sprintf("return %s.command('compose','%s',this)", JS_OBJECT_NAME, JQ($mailto)),
-            'title' => $mailto,
-            'class' => "rcmContactAddress",
-          ),
-        Q($name ? $name : $mailto));
+        $attrs = array(
+           'href' => 'mailto:' . $mailto,
+           'onclick' => sprintf("return %s.command('compose','%s',this)", JS_OBJECT_NAME, JQ(format_email_recipient($mailto, $name))),
+           'class' => "rcmContactAddress",
+        );
+
+        if ($show_email && $name && $mailto) {
+          $content = Q($name ? sprintf('%s <%s>', $name, $mailto) : $mailto);
+        }
+        else {
+          $content = Q($name ? $name : $mailto);
+          $attrs['title'] = $mailto;
+        }
+
+        $address = html::a($attrs, $content);
       }
       else {
         $address = html::span(array('title' => $mailto, 'class' => "rcmContactAddress"),
@@ -1551,7 +1535,7 @@
       if ($name)
         $address .= Q($name);
       if ($mailto)
-        $address .= (strlen($address) ? ' ' : '') . sprintf('&lt;%s&gt;', Q($mailto));
+        $address = trim($address . ' ' . Q($name ? sprintf('<%s>', $mailto) : $mailto));
     }
 
     $address = html::span('adr', $address);
@@ -1572,6 +1556,15 @@
   }
 
   if ($moreadrs) {
+    if ($PRINT_MODE) {
+      $out .= ' ' . html::a(array(
+        'href' => '#more',
+        'class' => 'morelink',
+        'onclick' => '$(this).hide().next().show()',
+      ), Q(rcube_label(array('name' => 'andnmore', 'vars' => array('nr' => $moreadrs))))) .
+      html::span(array('style' => 'display:none'), join(', ', $allvalues));
+    }
+    else {
       $out .= ' ' . html::a(array(
           'href' => '#more',
           'class' => 'morelink',
@@ -1581,6 +1574,7 @@
             JQ($title))
         ),
         Q(rcube_label(array('name' => 'andnmore', 'vars' => array('nr' => $moreadrs)))));
+    }
   }
 
   return $out;
@@ -1590,11 +1584,11 @@
 /**
  * Wrap text to a given number of characters per line
  * but respect the mail quotation of replies messages (>).
- * Finally add another quotation level by prpending the lines
+ * Finally add another quotation level by prepending the lines
  * with >
  *
  * @param string Text to wrap
- * @param int The line width
+ * @param int    The line width
  * @return string The wrapped text
  */
 function rcmail_wrap_and_quote($text, $length = 72)
@@ -1610,7 +1604,7 @@
       $line = '>' . rtrim($line);
     else if (mb_strlen($line) > $max) {
       $newline = '';
-      foreach(explode("\n", rc_wordwrap($line, $length - 2)) as $l) {
+      foreach (explode("\n", rc_wordwrap($line, $length - 2)) as $l) {
         if (strlen($l))
           $newline .= '> ' . $l . "\n";
         else
@@ -1650,49 +1644,6 @@
   }
 
   return $info;
-}
-
-
-function rcmail_message_part_controls($attrib)
-{
-  global $MESSAGE, $RCMAIL;
-
-  $part = asciiwords(get_input_value('_part', RCUBE_INPUT_GPC));
-  if (!is_object($MESSAGE) || !is_array($MESSAGE->parts) || !($_GET['_uid'] && $_GET['_part']) || !$MESSAGE->mime_parts[$part])
-    return '';
-
-  $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');
-  }
-
-  if (!empty($filename)) {
-    $table->add('title', Q(rcube_label('filename')));
-    $table->add('header', Q($filename));
-    $table->add('download-link', html::a(array('href' => './?'.str_replace('_frame=', '_download=', $_SERVER['QUERY_STRING'])), Q(rcube_label('download'))));
-  }
-
-  $table->add('title', Q(rcube_label('filesize')));
-  $table->add('header', Q($RCMAIL->message_part_size($part)));
-
-  return $table->show($attrib);
-}
-
-
-
-function rcmail_message_part_frame($attrib)
-{
-  global $MESSAGE;
-
-  $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' ? '_embed=' : '_preload='), $_SERVER['QUERY_STRING']);
-
-  return html::iframe($attrib);
 }
 
 
@@ -1757,6 +1708,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" .
@@ -1778,10 +1732,9 @@
     $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)
-    {
+    if ($sent) {
       $RCMAIL->storage->set_flag($message->uid, 'MDNSENT');
       return true;
     }
@@ -1807,7 +1760,7 @@
         $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_recipients[] = strtolower($addr['mailto']);
                 $a_names[]      = $addr['name'];
             }
         }
@@ -1816,35 +1769,41 @@
             $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_recipients[] = strtolower($addr['mailto']);
                     $a_names[]      = $addr['name'];
                 }
             }
         }
     }
 
-    $from_idx         = null;
-    $found_idx        = null;
-    $default_identity = 0; // default identity is always first on the list
+    // decode From: address
+    $from = rcube_mime::decode_address_list($MESSAGE->headers->from, null, true, $MESSAGE->headers->charset);
+    $from = array_shift($from);
+    $from['mailto'] = strtolower($from['mailto']);
+
+    $from_idx   = null;
+    $found_idx  = array('to' => null, 'from' => null);
+    $check_from = in_array($compose_mode, array('draft', 'edit', 'reply'));
 
     // Select identity
     foreach ($identities as $idx => $ident) {
-        // use From header
-        if (in_array($compose_mode, array('draft', 'edit'))) {
-            if ($MESSAGE->headers->from == $ident['ident']) {
+        // use From: header when in edit/draft or reply-to-self
+        if ($check_from && $from['mailto'] == strtolower($ident['email_ascii'])) {
+            // remember first matching identity address
+            if ($found_idx['from'] === null) {
+                $found_idx['from'] = $idx;
+            }
+            // match identity name
+            if ($from['name'] && $ident['name'] && $from['name'] == $ident['name']) {
                 $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;
+        // use replied/forwarded message recipients
+        else if (($found = array_search(strtolower($ident['email_ascii']), $a_recipients)) !== false) {
+            // remember first matching identity address
+            if ($found_idx['to'] === null) {
+                $found_idx['to'] = $idx;
             }
             // match identity name
             if ($a_names[$found] && $ident['name'] && $a_names[$found] == $ident['name']) {
@@ -1854,42 +1813,42 @@
         }
     }
 
-    // If matching by name+address doesn't found any matches, get first found address (identity)
+    // If matching by name+address didn't find any matches,
+    // get first found identity (address) if any
     if ($from_idx === null) {
-        $from_idx = $found_idx;
+        $from_idx = $found_idx['from'] !== null ? $found_idx['from'] : $found_idx['to'];
     }
 
     // Try Return-Path
     if ($from_idx === null && ($return_path = $MESSAGE->headers->others['return-path'])) {
+        $return_path = array_map('strtolower', (array) $return_path);
+
         foreach ($identities as $idx => $ident) {
-            if (strpos($return_path, str_replace('@', '=', $ident['email_ascii']).'@') !== false) {
-                $from_idx = $idx;
-                break;
+            // Return-Path header contains an email address, but on some mailing list
+            // it can be e.g. <pear-dev-return-55250-local=domain.tld@lists.php.net>
+            // where local@domain.tld is the address we're looking for (#1489241)
+            $ident1 = strtolower($ident['email_ascii']);
+            $ident2 = str_replace('@', '=', $ident1);
+            $ident1 = '<' . $ident1 . '>';
+            $ident2 = '-' . $ident2 . '@';
+
+            foreach ($return_path as $path) {
+                if ($path == $ident1 || stripos($path, $ident2)) {
+                    $from_idx = $idx;
+                    break 2;
+                }
             }
         }
     }
 
-    // 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;
-            }
-        }
-    }
+    // See identity_select plugin for example usage of this hook
+    $plugin = rcmail::get_instance()->plugins->exec_hook('identity_select',
+        array('message' => $MESSAGE, 'identities' => $identities, 'selected' => $from_idx));
 
-    // 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;
-            }
-        }
-    }
+    $selected = $plugin['selected'];
 
-    return $identities[$from_idx !== null ? $from_idx : $default_identity];
+    // default identity is always first on the list
+    return $identities[$selected !== null ? $selected : 0];
 }
 
 // Fixes some content-type names
@@ -1899,12 +1858,42 @@
   // application/pdf.A520491B_3BF7_494D_8855_7FAC2C6C0608
   if (preg_match('/^application\/pdf.+/', $name))
     $name = 'application/pdf';
-
-  // treat image/pjpeg as image/jpeg
+  // treat image/pjpeg (image/pjpg, image/jpg) as image/jpeg (#1489097)
   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)
@@ -1916,13 +1905,15 @@
 
   $attrib['onchange'] = JS_OBJECT_NAME.'.filter_mailbox(this.value)';
 
-  /*
-    RFC3501 (6.4.4): 'ALL', 'RECENT', 
-    'ANSWERED', 'DELETED', 'FLAGGED', 'SEEN',
-    'UNANSWERED', 'UNDELETED', 'UNFLAGGED', 'UNSEEN',
-    'NEW', // = (RECENT UNSEEN)
-    'OLD' // = NOT RECENT
-  */
+  // Content-Type values of messages with attachments
+  // the same as in app.js:add_message_row()
+  $ctypes = array('application/', 'multipart/m', 'multipart/signed', 'multipart/report');
+
+  // Build search string of "with attachment" filter
+  $attachment = str_repeat(' OR', count($ctypes)-1);
+  foreach ($ctypes as $type) {
+    $attachment .= ' HEADER Content-Type ' . rcube_imap_generic::escape($type);
+  }
 
   $select_filter = new html_select($attrib);
   $select_filter->add(rcube_label('all'), 'ALL');
@@ -1933,6 +1924,7 @@
     $select_filter->add(rcube_label('deleted'), 'DELETED');
     $select_filter->add(rcube_label('undeleted'), 'UNDELETED');
   }
+  $select_filter->add(rcube_label('withattachment'), $attachment);
   $select_filter->add(rcube_label('priority').': '.rcube_label('highest'), 'HEADER X-PRIORITY 1');
   $select_filter->add(rcube_label('priority').': '.rcube_label('high'), 'HEADER X-PRIORITY 2');
   $select_filter->add(rcube_label('priority').': '.rcube_label('normal'), 'NOT HEADER X-PRIORITY 1 NOT HEADER X-PRIORITY 2 NOT HEADER X-PRIORITY 4 NOT HEADER X-PRIORITY 5');
@@ -1962,35 +1954,36 @@
   $RCMAIL->output->send('messageerror');
 }
 
-// register UI objects
-$OUTPUT->add_handlers(array(
-  'mailboxlist' => 'rcmail_mailbox_list',
-  'messages' => 'rcmail_message_list',
-  'messagecountdisplay' => 'rcmail_messagecount_display',
-  'quotadisplay' => 'rcmail_quota_display',
-  'mailboxname' => 'rcmail_mailbox_name_display',
-  'messageheaders' => 'rcmail_message_headers',
-  'messagefullheaders' => 'rcmail_message_full_headers',
-  'messagebody' => 'rcmail_message_body',
-  'messagecontentframe' => 'rcmail_messagecontent_frame',
-  'messagepartframe' => 'rcmail_message_part_frame',
-  'messagepartcontrols' => 'rcmail_message_part_controls',
-  'searchfilter' => 'rcmail_search_filter',
-  'searchform' => array($OUTPUT, 'search_form'),
-));
+function rcmail_message_import_form($attrib = array())
+{
+  global $OUTPUT;
 
-// register action aliases
-$RCMAIL->register_action_map(array(
-    'refresh' => 'check_recent.inc',
-    'preview' => 'show.inc',
-    'print'   => 'show.inc',
-    'moveto'  => 'move_del.inc',
-    'delete'  => 'move_del.inc',
-    'send'    => 'sendmail.inc',
-    'expunge' => 'folders.inc',
-    'purge'   => 'folders.inc',
-    'remove-attachment'  => 'attachments.inc',
-    'display-attachment' => 'attachments.inc',
-    'upload'             => 'attachments.inc',
-    'group-expand'       => 'autocomplete.inc',
-));
+  // set defaults
+  $attrib += array('id' => 'rcmImportform', 'buttons' => 'yes');
+
+  // Get filesize, enable upload progress bar
+  $max_filesize = rcube_upload_init();
+
+  $button = new html_inputfield(array('type' => 'button'));
+  $fileinput = new html_inputfield(array(
+      'type' => 'file',
+      'name' => '_file[]',
+      'multiple' => 'multiple',
+      'accept' => ".eml, .mbox, message/rfc822, text/*",
+  ));
+
+  $out = html::div($attrib,
+    $OUTPUT->form_tag(array('id' => $attrib['id'].'Frm', 'method' => 'post', 'enctype' => 'multipart/form-data'),
+      html::tag('input', array('type' => 'hidden', 'name' => '_unlock', 'value' => '')) .
+      html::div(null, $fileinput->show()) .
+      html::div('hint', rcube_label(array('name' => 'maxuploadsize', 'vars' => array('size' => $max_filesize)))) .
+      (get_boolean($attrib['buttons']) ? html::div('buttons',
+        $button->show(rcube_label('close'), array('class' => 'button', 'onclick' => "$('#$attrib[id]').hide()")) . ' ' .
+        $button->show(rcube_label('upload'), array('class' => 'button mainaction', 'onclick' => JS_OBJECT_NAME . ".command('import-messages', this.form)"))
+      ) : '')
+    )
+  );
+
+  $OUTPUT->add_gui_object('importform', $attrib['id'].'Frm');
+  return $out;
+}

--
Gitblit v1.9.1