Skip to content
This repository has been archived by the owner on Jan 30, 2020. It is now read-only.

Commit

Permalink
Merge branch 'hotfix/147-to-split-semicolon'
Browse files Browse the repository at this point in the history
Close #147
  • Loading branch information
weierophinney committed Jun 6, 2018
2 parents 91f3972 + 83f9dde commit 8aac5f7
Show file tree
Hide file tree
Showing 5 changed files with 130 additions and 6 deletions.
7 changes: 6 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ All notable changes to this project will be documented in this file, in reverse
- [#213](https://github.com/zendframework/zend-mail/pull/213) re-adds support for PHP 5.6 and 7.0; ZF policy is never
to bump the major version of a PHP requirement unless the package is bumping major version.

### Changed

- Nothing.

### Deprecated

- Nothing.
Expand All @@ -19,7 +23,8 @@ All notable changes to this project will be documented in this file, in reverse

### Fixed

- Nothing.
- [#147](https://github.com/zendframework/zend-mail/pull/147) fixes how address lists are parsed, expanding the functionality to allow either
`,` or `;` delimiters (or both in combination).

## 2.9.0 - 2017-03-01

Expand Down
13 changes: 9 additions & 4 deletions src/AddressList.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,9 @@ public function add($emailOrAddress, $name = null)
{
if (is_string($emailOrAddress)) {
$emailOrAddress = $this->createAddress($emailOrAddress, $name);
} elseif (! $emailOrAddress instanceof Address\AddressInterface) {
}

if (! $emailOrAddress instanceof Address\AddressInterface) {
throw new Exception\InvalidArgumentException(sprintf(
'%s expects an email address or %s\Address object as its first argument; received "%s"',
__METHOD__,
Expand Down Expand Up @@ -65,14 +67,17 @@ public function addMany(array $addresses)
foreach ($addresses as $key => $value) {
if (is_int($key) || is_numeric($key)) {
$this->add($value);
} elseif (is_string($key)) {
$this->add($key, $value);
} else {
continue;
}

if (! is_string($key)) {
throw new Exception\RuntimeException(sprintf(
'Invalid key type in provided addresses array ("%s")',
(is_object($key) ? get_class($key) : var_export($key, 1))
));
}

$this->add($key, $value);
}
return $this;
}
Expand Down
3 changes: 2 additions & 1 deletion src/Header/AbstractAddressList.php
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ public static function fromString($headerLine)
// split value on ","
$fieldValue = str_replace(Headers::FOLDING, ' ', $fieldValue);
$fieldValue = preg_replace('/[^:]+:([^;]*);/', '$1,', $fieldValue);
$values = str_getcsv($fieldValue, ',');
$values = AddressListParser::parse($fieldValue);

$wasEncoded = false;
array_walk(
Expand Down Expand Up @@ -80,6 +80,7 @@ function (&$value) use (&$wasEncoded) {

$values = array_filter($values);

/** @var AddressList $addressList */
$addressList = $header->getAddressList();
foreach ($values as $address) {
$addressList->addFromString($address);
Expand Down
86 changes: 86 additions & 0 deletions src/Header/AddressListParser.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
<?php
/**
* @see https://github.com/zendframework/zend-mail for the canonical source repository
* @copyright Copyright (c) 2018 Zend Technologies USA Inc. (https://www.zend.com)
* @license https://github.com/zendframework/zend-mail/blob/master/LICENSE.md New BSD License
*/

namespace Zend\Mail\Header;

use function in_array;

class AddressListParser
{
const CHAR_QUOTES = ['\'', '"'];
const CHAR_DELIMS = [',', ';'];
const CHAR_ESCAPE = '\\';

/**
* @param string $value
* @return array
*/
public static function parse($value)
{
$values = [];
$length = strlen($value);
$currentValue = '';
$inEscape = false;
$inQuote = false;
$currentQuoteDelim = null;

for ($i = 0; $i < $length; $i += 1) {
$char = $value[$i];

// If we are in an escape sequence, append the character and continue.
if ($inEscape) {
$currentValue .= $char;
$inEscape = false;
continue;
}

// If we are not in a quoted string, and have a delimiter, append
// the current value to the list, and reset the current value.
if (in_array($char, self::CHAR_DELIMS, true) && ! $inQuote) {
$values [] = $currentValue;
$currentValue = '';
continue;
}

// Append the character to the current value
$currentValue .= $char;

// Escape sequence discovered.
if (self::CHAR_ESCAPE === $char) {
$inEscape = true;
continue;
}

// If the character is not a quote character, we are done
// processing it.
if (! in_array($char, self::CHAR_QUOTES)) {
continue;
}

// If the character matches a previously matched quote delimiter,
// we reset our quote status and the currently opened quote
// delimiter.
if ($char === $currentQuoteDelim) {
$inQuote = false;
$currentQuoteDelim = null;
continue;
}

// Otherwise, we're starting a quoted string.
$inQuote = true;
$currentQuoteDelim = $char;
}

// If we reached the end of the string and still have a current value,
// append it to the list (no delimiter was reached).
if ('' !== $currentValue) {
$values [] = $currentValue;
}

return $values;
}
}
27 changes: 27 additions & 0 deletions test/AddressListTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
use PHPUnit\Framework\TestCase;
use Zend\Mail\Address;
use Zend\Mail\AddressList;
use Zend\Mail\Exception\InvalidArgumentException;
use Zend\Mail\Header;

/**
* @group Zend_Mail
Expand Down Expand Up @@ -105,4 +107,29 @@ public function testDoesNotStoreDuplicatesAndFirstWins()
$address = $this->list->get('zf-devteam@zend.com');
$this->assertNull($address->getName());
}

/**
* Microsoft Outlook sends emails with semicolon separated To addresses.
*
* @see https://blogs.msdn.microsoft.com/oldnewthing/20150119-00/?p=44883
*/
public function testSemicolonSeparator()
{
$header = 'Some User <some.user@example.com>; uzer2.surname@example.org;'
. ' asda.fasd@example.net, root@example.org';

// In previous versions, this throws: 'The input exceeds the allowed
// length'; hence the try/catch block, to allow finding the root cause.
try {
$to = Header\To::fromString('To:' . $header);
} catch (InvalidArgumentException $e) {
$this->fail('Header\To::fromString should not throw');
}
$addressList = $to->getAddressList();

$this->assertEquals('Some User', $addressList->get('some.user@example.com')->getName());
$this->assertTrue($addressList->has('uzer2.surname@example.org'));
$this->assertTrue($addressList->has('asda.fasd@example.net'));
$this->assertTrue($addressList->has('root@example.org'));
}
}

0 comments on commit 8aac5f7

Please sign in to comment.