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 |   94 ++++++++++++++++++++++++++++++++---------------
 1 files changed, 64 insertions(+), 30 deletions(-)

diff --git a/program/steps/mail/func.inc b/program/steps/mail/func.inc
index 340292a..78a977b 100644
--- a/program/steps/mail/func.inc
+++ b/program/steps/mail/func.inc
@@ -97,6 +97,7 @@
   $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);
@@ -1383,9 +1384,6 @@
 {
   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    = strtolower($matches[1]);
   $attrib = parse_attrib_string($matches[2]);
   $end    = '>';
@@ -1400,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';
@@ -1476,7 +1498,7 @@
       if ($linked) {
         $attrs = array(
            'href' => 'mailto:' . $mailto,
-           'onclick' => sprintf("return %s.command('compose','%s',this)", JS_OBJECT_NAME, JQ($mailto)),
+           'onclick' => sprintf("return %s.command('compose','%s',this)", JS_OBJECT_NAME, JQ(format_email_recipient($mailto, $name))),
            'class' => "rcmContactAddress",
         );
 
@@ -1754,28 +1776,34 @@
         }
     }
 
-    $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
+        // use replied/forwarded message recipients
         else if (($found = array_search(strtolower($ident['email_ascii']), $a_recipients)) !== false) {
-            if ($found_idx === null) {
-                $found_idx = $idx;
+            // 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']) {
@@ -1785,22 +1813,27 @@
         }
     }
 
-    // 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) {
             // 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 = $ident['email_ascii'];
+            $ident1 = strtolower($ident['email_ascii']);
             $ident2 = str_replace('@', '=', $ident1);
+            $ident1 = '<' . $ident1 . '>';
+            $ident2 = '-' . $ident2 . '@';
 
-            foreach ((array)$return_path as $path) {
-                if (stripos($path, $ident1) !== false || stripos($path, $ident2)) {
+            foreach ($return_path as $path) {
+                if ($path == $ident1 || stripos($path, $ident2)) {
                     $from_idx = $idx;
                     break 2;
                 }
@@ -1814,7 +1847,8 @@
 
     $selected = $plugin['selected'];
 
-    return $identities[$selected !== null ? $selected : $default_identity];
+    // default identity is always first on the list
+    return $identities[$selected !== null ? $selected : 0];
 }
 
 // Fixes some content-type names

--
Gitblit v1.9.1