diff options
-rw-r--r-- | CHANGELOG | 1 | ||||
-rw-r--r-- | program/include/rcube_imap.php | 82 | ||||
-rw-r--r-- | tests/maildecode.php | 45 |
3 files changed, 112 insertions, 16 deletions
@@ -1,6 +1,7 @@ CHANGELOG Roundcube Webmail =========================== +- Fix handling of comments inside an email address spec. (#1487673) - Fix randomly disappearing folders list in IE (#1487704) - Fix list column add/removal in IE (#1487703) - Fix login redirect issues (#1487686) diff --git a/program/include/rcube_imap.php b/program/include/rcube_imap.php index fa7998338..cab232da4 100644 --- a/program/include/rcube_imap.php +++ b/program/include/rcube_imap.php @@ -4696,10 +4696,13 @@ class rcube_imap private function _parse_address_list($str, $decode=true) { // remove any newlines and carriage returns before - $a = rcube_explode_quoted_string('[,;]', preg_replace( "/[\r\n]/", " ", $str)); + $str = preg_replace('/\r?\n(\s|\t)?/', ' ', $str); + + // extract list items, remove comments + $str = self::explode_header_string(',;', $str, true); $result = array(); - foreach ($a as $key => $val) { + foreach ($str as $key => $val) { $name = ''; $address = ''; $val = trim($val); @@ -4741,6 +4744,81 @@ class rcube_imap /** + * Explodes header (e.g. address-list) string into array of strings + * using specified separator characters with proper handling + * of quoted-strings and comments (RFC2822) + * + * @param string $separator String containing separator characters + * @param string $str Header string + * @param bool $remove_comments Enable to remove comments + * + * @return array Header items + */ + static function explode_header_string($separator, $str, $remove_comments=false) + { + $length = strlen($str); + $result = array(); + $quoted = false; + $comment = 0; + $out = ''; + + for ($i=0; $i<$length; $i++) { + // we're inside a quoted string + if ($quoted) { + if ($str[$i] == '"') { + $quoted = false; + } + else if ($str[$i] == '\\') { + if ($comment <= 0) { + $out .= '\\'; + } + $i++; + } + } + // we're inside a comment string + else if ($comment > 0) { + if ($str[$i] == ')') { + $comment--; + } + else if ($str[$i] == '(') { + $comment++; + } + else if ($str[$i] == '\\') { + $i++; + } + continue; + } + // separator, add to result array + else if (strpos($separator, $str[$i]) !== false) { + if ($out) { + $result[] = $out; + } + $out = ''; + continue; + } + // start of quoted string + else if ($str[$i] == '"') { + $quoted = true; + } + // start of comment + else if ($remove_comments && $str[$i] == '(') { + $comment++; + } + + if ($comment <= 0) { + $out .= $str[$i]; + } + } + + if ($out && $comment <= 0) { + $result[] = $out; + } + + return $result; + } + + + /** * This is our own debug handler for the IMAP connection * @access public */ diff --git a/tests/maildecode.php b/tests/maildecode.php index cfd7eda2f..8d359a5b6 100644 --- a/tests/maildecode.php +++ b/tests/maildecode.php @@ -35,28 +35,45 @@ class rcube_test_maildecode extends UnitTestCase 8 => '"Test<Test" <test@domain.tld>', 9 => '=?ISO-8859-1?B?VGVzdAo=?= <test@domain.tld>', 10 => '=?ISO-8859-1?B?VGVzdAo=?=<test@domain.tld>', // #1487068 + // comments in address (#1487673) + 11 => 'Test (comment) <test@domain.tld>', + 12 => '"Test" (comment) <test@domain.tld>', + 13 => '"Test (comment)" (comment) <test@domain.tld>', + 14 => '(comment) <test@domain.tld>', + 15 => 'Test <test@(comment)domain.tld>', + 16 => 'Test Test ((comment)) <test@domain.tld>', + 17 => 'test@domain.tld (comment)', + 18 => '"Test,Test" <test@domain.tld>', ); $results = array( - 0 => array('', 'test@domain.tld'), - 1 => array('', 'test@domain.tld'), - 2 => array('Test', 'test@domain.tld'), - 3 => array('Test Test', 'test@domain.tld'), - 4 => array('Test Test', 'test@domain.tld'), - 5 => array('Test Test', 'test@domain.tld'), - 6 => array('Test Test', 'test@domain.tld'), - 7 => array('Test " Test', 'test@domain.tld'), - 8 => array('Test<Test', 'test@domain.tld'), - 9 => array('Test', 'test@domain.tld'), - 10 => array('Test', 'test@domain.tld'), + 0 => array(1, '', 'test@domain.tld'), + 1 => array(1, '', 'test@domain.tld'), + 2 => array(1, 'Test', 'test@domain.tld'), + 3 => array(1, 'Test Test', 'test@domain.tld'), + 4 => array(1, 'Test Test', 'test@domain.tld'), + 5 => array(1, 'Test Test', 'test@domain.tld'), + 6 => array(1, 'Test Test', 'test@domain.tld'), + 7 => array(1, 'Test " Test', 'test@domain.tld'), + 8 => array(1, 'Test<Test', 'test@domain.tld'), + 9 => array(1, 'Test', 'test@domain.tld'), + 10 => array(1, 'Test', 'test@domain.tld'), + 11 => array(1, 'Test', 'test@domain.tld'), + 12 => array(1, 'Test', 'test@domain.tld'), + 13 => array(1, 'Test (comment)', 'test@domain.tld'), + 14 => array(1, '', 'test@domain.tld'), + 15 => array(1, 'Test', 'test@domain.tld'), + 16 => array(1, 'Test Test', 'test@domain.tld'), + 17 => array(1, '', 'test@domain.tld'), + 18 => array(1, 'Test,Test', 'test@domain.tld'), ); foreach ($headers as $idx => $header) { $res = $this->app->imap->decode_address_list($header); - $this->assertEqual(1, count($res), "Rows number in result for header: " . $header); - $this->assertEqual($results[$idx][0], $res[1]['name'], "Name part decoding for header: " . $header); - $this->assertEqual($results[$idx][1], $res[1]['mailto'], "Name part decoding for header: " . $header); + $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); } } |