From 6d0ada30d7847a509db10d819020ac653597d073 Mon Sep 17 00:00:00 2001
From: alecpl <alec@alec.pl>
Date: Tue, 09 Aug 2011 05:46:54 -0400
Subject: [PATCH] - Fix handling of email addresses with quoted local part (#1487939)

---
 program/include/rcube_smtp.php  |    5 +++--
 tests/maildecode.php            |    5 ++++-
 CHANGELOG                       |    1 +
 program/include/rcube_imap.php  |    7 +++++--
 program/steps/mail/sendmail.inc |   13 ++++++++-----
 program/lib/Mail/mimePart.php   |   10 +++++++---
 6 files changed, 28 insertions(+), 13 deletions(-)

diff --git a/CHANGELOG b/CHANGELOG
index 28c5c28..39b7991 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,6 +1,7 @@
 CHANGELOG Roundcube Webmail
 ===========================
 
+- Fix handling of email addresses with quoted local part (#1487939)
 - Fix EOL character in vCard exports (#1487873)
 - Added optional "multithreading" autocomplete feature
 - Plugin API: Added 'config_get' hook
diff --git a/program/include/rcube_imap.php b/program/include/rcube_imap.php
index 3ba0589..5fbf3ad 100644
--- a/program/include/rcube_imap.php
+++ b/program/include/rcube_imap.php
@@ -4760,12 +4760,15 @@
         $str = self::explode_header_string(',;', $str, true);
         $result = array();
 
+        // simplified regexp, supporting quoted local part
+        $email_rx = '(\S+|("\s*(?:[^"\f\n\r\t\v\b\s]+\s*)+"))@\S+';
+
         foreach ($str as $key => $val) {
             $name    = '';
             $address = '';
             $val     = trim($val);
 
-            if (preg_match('/(.*)<(\S+@\S+)>$/', $val, $m)) {
+            if (preg_match('/(.*)<('.$email_rx.')>$/', $val, $m)) {
                 $address = $m[2];
                 $name    = trim($m[1]);
             }
@@ -4779,7 +4782,7 @@
 
             // dequote and/or decode name
             if ($name) {
-                if ($name[0] == '"') {
+                if ($name[0] == '"' && $name[strlen($name)-1] == '"') {
                     $name = substr($name, 1, -1);
                     $name = stripslashes($name);
                 }
diff --git a/program/include/rcube_smtp.php b/program/include/rcube_smtp.php
index 120336c..73c30d2 100644
--- a/program/include/rcube_smtp.php
+++ b/program/include/rcube_smtp.php
@@ -439,14 +439,14 @@
     // if we're passed an array, assume addresses are valid and implode them before parsing.
     if (is_array($recipients))
       $recipients = implode(', ', $recipients);
-    
+
     $addresses = array();
     $recipients = rcube_explode_quoted_string(',', $recipients);
 
     reset($recipients);
     while (list($k, $recipient) = each($recipients))
     {
-      $a = explode(" ", $recipient);
+      $a = rcube_explode_quoted_string(' ', $recipient);
       while (list($k2, $word) = each($a))
       {
         if (strpos($word, "@") > 0 && $word[strlen($word)-1] != '"')
@@ -457,6 +457,7 @@
         }
       }
     }
+
     return $addresses;
   }
 
diff --git a/program/lib/Mail/mimePart.php b/program/lib/Mail/mimePart.php
index 60b3601..5674792 100644
--- a/program/lib/Mail/mimePart.php
+++ b/program/lib/Mail/mimePart.php
@@ -131,6 +131,7 @@
     */
     var $_eol = "\r\n";
 
+
     /**
     * Constructor.
     *
@@ -800,6 +801,9 @@
 
         // Structured header (make sure addr-spec inside is not encoded)
         if (!empty($separator)) {
+            // Simple e-mail address regexp
+            $email_regexp = '(\S+|("\s*(?:[^"\f\n\r\t\v\b\s]+\s*)+"))@\S+';
+
             $parts = Mail_mimePart::_explodeQuotedString($separator, $value);
             $value = '';
 
@@ -817,12 +821,12 @@
                 }
 
                 // let's find phrase (name) and/or addr-spec
-                if (preg_match('/^<\S+@\S+>$/', $part)) {
+                if (preg_match('/^<' . $email_regexp . '>$/', $part)) {
                     $value .= $part;
-                } else if (preg_match('/^\S+@\S+$/', $part)) {
+                } else if (preg_match('/^' . $email_regexp . '$/', $part)) {
                     // address without brackets and without name
                     $value .= $part;
-                } else if (preg_match('/<*\S+@\S+>*$/', $part, $matches)) {
+                } else if (preg_match('/<*' . $email_regexp . '>*$/', $part, $matches)) {
                     // address with name (handle name)
                     $address = $matches[0];
                     $word = str_replace($address, '', $part);
diff --git a/program/steps/mail/sendmail.inc b/program/steps/mail/sendmail.inc
index 91d71a7..0b6f49f 100644
--- a/program/steps/mail/sendmail.inc
+++ b/program/steps/mail/sendmail.inc
@@ -143,7 +143,10 @@
 {
   global $EMAIL_FORMAT_ERROR, $RECIPIENT_COUNT;
 
-  $regexp = array('/[,;]\s*[\r\n]+/', '/[\r\n]+/', '/[,;]\s*$/m', '/;/', '/(\S{1})(<\S+@\S+>)/U');
+  // simplified email regexp, supporting quoted local part
+  $email_regexp = '(\S+|("\s*(?:[^"\f\n\r\t\v\b\s]+\s*)+"))@\S+';
+
+  $regexp  = array('/[,;]\s*[\r\n]+/', '/[\r\n]+/', '/[,;]\s*$/m', '/;/', '/(\S{1})(<'.$email_regexp.'>)/U');
   $replace = array(', ', ', ', '', ',', '\\1 \\2');
 
   // replace new lines and strip ending ', ', make address input more valid
@@ -155,15 +158,15 @@
   foreach($items as $item) {
     $item = trim($item);
     // address in brackets without name (do nothing)
-    if (preg_match('/^<\S+@\S+>$/', $item)) {
+    if (preg_match('/^<'.$email_regexp.'>$/', $item)) {
       $item = rcube_idn_to_ascii($item);
       $result[] = $item;
     // address without brackets and without name (add brackets)
-    } else if (preg_match('/^\S+@\S+$/', $item)) {
+    } else if (preg_match('/^'.$email_regexp.'$/', $item)) {
       $item = rcube_idn_to_ascii($item);
       $result[] = '<'.$item.'>';
     // address with name (handle name)
-    } else if (preg_match('/\S+@\S+>*$/', $item, $matches)) {
+    } else if (preg_match('/'.$email_regexp.'>*$/', $item, $matches)) {
       $address = $matches[0];
       $name = str_replace($address, '', $item);
       $name = trim($name);
@@ -172,7 +175,7 @@
             $name = '"'.addcslashes($name, '"').'"';
       }
       $address = rcube_idn_to_ascii($address);
-      if (!preg_match('/^<\S+@\S+>$/', $address))
+      if (!preg_match('/^<'.$email_regexp.'>$/', $address))
         $address = '<'.$address.'>';
 
       $result[] = $name.' '.$address;
diff --git a/tests/maildecode.php b/tests/maildecode.php
index 8d359a5..ef3d182 100644
--- a/tests/maildecode.php
+++ b/tests/maildecode.php
@@ -44,6 +44,8 @@
         16 => 'Test Test ((comment)) <test@domain.tld>',
         17 => 'test@domain.tld (comment)',
         18 => '"Test,Test" <test@domain.tld>',
+        // 1487939
+        19 => 'Test <"test test"@domain.tld>',
     );
 
     $results = array(
@@ -66,6 +68,7 @@
         16 => array(1, 'Test Test', 'test@domain.tld'),
         17 => array(1, '', 'test@domain.tld'),
         18 => array(1, 'Test,Test', 'test@domain.tld'),
+        19 => array(1, 'Test', '"test test"@domain.tld'),
     );
 
     foreach ($headers as $idx => $header) {
@@ -73,7 +76,7 @@
 
       $this->assertEqual($results[$idx][0], count($res), "Rows number in result for header: " . $header);
       $this->assertEqual($results[$idx][1], $res[1]['name'], "Name part decoding for header: " . $header);
-      $this->assertEqual($results[$idx][2], $res[1]['mailto'], "Name part decoding for header: " . $header);
+      $this->assertEqual($results[$idx][2], $res[1]['mailto'], "Email part decoding for header: " . $header);
     }
   }
 

--
Gitblit v1.9.1