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

Commit

Permalink
Allow comma or semicolon separation in a list
Browse files Browse the repository at this point in the history
This patch adds a new class, `AddressListParser`, with a single static
method `parse()`. It loops through each character of the value to
identify non-escaped, non-quoted delimiters, allowing either `,` or `;`
to be used.

`AbstractAddressList::fromString()` now uses the above method instead of
`str_getcsv()` to extract the list of addresses.

The patch also updates the test provided in zendframework#147 to test for the
existence of all addresses in the test string.
  • Loading branch information
weierophinney committed Jun 6, 2018
1 parent 5c2a409 commit f30279e
Show file tree
Hide file tree
Showing 3 changed files with 90 additions and 1 deletion.
2 changes: 1 addition & 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
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;
}
}
3 changes: 3 additions & 0 deletions test/AddressListTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -128,5 +128,8 @@ public function testSemicolonSeparator()
$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 f30279e

Please sign in to comment.