commit a29b8fb3146e318ba3fd9a084859f2e39553c084 Author: Elan Ruusamäe Date: Wed Feb 10 09:35:53 2016 +0200 test and fix for #64 backport for 2.4 Conflicts: src/Header/HeaderWrap.php diff --git a/src/Header/HeaderWrap.php b/src/Header/HeaderWrap.php index df532edc..e0be2f56 100644 --- a/src/Header/HeaderWrap.php +++ b/src/Header/HeaderWrap.php @@ -116,7 +116,21 @@ abstract class HeaderWrap */ public static function canBeEncoded($value) { - $encoded = iconv_mime_encode('x-test', $value, array('scheme' => 'Q')); + // avoid any wrapping by specifying line length long enough + // "test" -> 4 + // "x-test: =?ISO-8859-1?B?dGVzdA==?=" -> 33 + // 8 +2 +3 +3 -> 16 + $charset = 'UTF-8'; + $line_length = strlen($value) * 4 + strlen($charset) + 16; + + $preferences = array( + 'scheme' => 'Q', + 'input-charset' => $charset, + 'output-charset' => $charset, + 'line-length' => $line_length, + ); + + $encoded = iconv_mime_encode('x-test', $value, $preferences); return (false !== $encoded); } commit 755b22727a126608611b6a58c289ad6744db21a9 Merge: 6034313f 393b43c9 Author: Elan Ruusamäe Date: Mon Feb 15 11:51:30 2016 +0200 Merge tag 'release-2.4.9' into develop-2.4 zend-mail 2.4.9 Conflicts: composer.json commit 222ace2631198f7498ae7c3799caaf6fc3a7936a Merge: 755b2272 c1c73d7f Author: Elan Ruusamäe Date: Fri Jan 13 17:39:32 2017 +0200 Merge tag 'release-2.4.11' into develop-2.4 zend-mail 2.4.11 Added ----- - Nothing. Deprecated ---------- - Nothing. Removed ------- - Nothing. Fixed ----- - Fixes the [ZF2016-04 advisory](https://framework.zend.com/security/advisory/ZF2016-04) ("Potential remote code execution in zend-mail via Sendmail adapter"). commit 8493b2a0610c59fbeeaf0ffc44340810d4d84d93 Author: Etienne CHAMPETIER Date: Fri May 29 14:32:09 2015 +0200 Headers: fix bad sprintf call Signed-off-by: Etienne CHAMPETIER diff --git a/src/Headers.php b/src/Headers.php index 3ceb10be..b416f52a 100644 --- a/src/Headers.php +++ b/src/Headers.php @@ -220,6 +220,7 @@ class Headers implements Countable, Iterator if (!is_string($headerFieldNameOrLine)) { throw new Exception\InvalidArgumentException(sprintf( '%s expects its first argument to be a string; received "%s"', + __METHOD__, (is_object($headerFieldNameOrLine) ? get_class($headerFieldNameOrLine) : gettype($headerFieldNameOrLine)) commit 66eeb12567335f3a23aea7538a82e9a144464427 Author: Denis Sokolov Date: Wed Jun 10 16:44:27 2015 +0300 Handle simple comments in address lists diff --git a/src/Header/AbstractAddressList.php b/src/Header/AbstractAddressList.php index e7db7240..b13f9ebd 100644 --- a/src/Header/AbstractAddressList.php +++ b/src/Header/AbstractAddressList.php @@ -63,6 +63,7 @@ abstract class AbstractAddressList implements HeaderInterface $values, function (&$value) { $value = trim($value); + $value = self::stripComments($value); } ); @@ -155,4 +156,19 @@ abstract class AbstractAddressList implements HeaderInterface $value = $this->getFieldValue(HeaderInterface::FORMAT_ENCODED); return (empty($value)) ? '' : sprintf('%s: %s', $name, $value); } + + // Supposed to be private, protected as a workaround for PHP bug 68194 + protected static function stripComments($value) + { + return preg_replace( + '/\\( + ( + \\\\.| + [^\\\\)] + )+ + \\)/x', + '', + $value + ); + } } commit 5064f95148c1d996b5b25bc556830da9a8458e6a Author: Denis Sokolov Date: Thu Jun 11 13:27:04 2015 +0300 Handle groups in address lists diff --git a/src/Header/AbstractAddressList.php b/src/Header/AbstractAddressList.php index b13f9ebd..e0b4e78c 100644 --- a/src/Header/AbstractAddressList.php +++ b/src/Header/AbstractAddressList.php @@ -58,6 +58,7 @@ abstract class AbstractAddressList implements HeaderInterface } // split value on "," $fieldValue = str_replace(Headers::FOLDING, ' ', $fieldValue); + $fieldValue = preg_replace('/[^:]+:([^;]*);/', '$1,', $fieldValue); $values = str_getcsv($fieldValue, ','); array_walk( $values, @@ -66,6 +67,7 @@ abstract class AbstractAddressList implements HeaderInterface $value = self::stripComments($value); } ); + $values = array_filter($values); $addressList = $header->getAddressList(); foreach ($values as $address) { commit ebdc224fb6847c39e9691631eef23b3b1c3eb6a0 Author: Stefano Torresi Date: Wed Jun 3 17:33:40 2015 +0200 fixes zendframework/zf2#7555 diff --git a/src/Header/Sender.php b/src/Header/Sender.php index 2efc23bf..e7bcac63 100644 --- a/src/Header/Sender.php +++ b/src/Header/Sender.php @@ -39,25 +39,23 @@ class Sender implements HeaderInterface // check to ensure proper header type for this factory if (strtolower($name) !== 'sender') { - throw new Exception\InvalidArgumentException('Invalid header line for Sender string'); + throw new Exception\InvalidArgumentException('Invalid header name for Sender string'); } - $header = new static(); - $senderName = ''; - $senderEmail = ''; + $header = new static(); + $hasMatches = preg_match('/^(?:(?P.+)\s)?(?(name)<|[^\s]+?)(?(name)>|>?)$/', $value, $matches); - // Check for address, and set if found - if (preg_match('/^(?P.*?)<(?P[^>]+)>$/', $value, $matches)) { - $senderName = trim($matches['name']); - if (empty($senderName)) { - $senderName = null; - } - $senderEmail = $matches['email']; - } else { - $senderEmail = $value; + if ($hasMatches !== 1) { + throw new Exception\InvalidArgumentException('Invalid header value for Sender string'); + } + + $senderName = trim($matches['name']); + + if (empty($senderName)) { + $senderName = null; } - $header->setAddress($senderEmail, $senderName); + $header->setAddress($matches['email'], $senderName); return $header; } commit c012507f8ae08eb92edb022c290d84dd9966fb46 Author: Stefano Torresi Date: Mon Oct 26 20:26:11 2015 +0100 comment the regex diff --git a/src/Header/Sender.php b/src/Header/Sender.php index e7bcac63..9f532949 100644 --- a/src/Header/Sender.php +++ b/src/Header/Sender.php @@ -43,6 +43,12 @@ class Sender implements HeaderInterface } $header = new static(); + + /** + * matches the header value so that the email must be enclosed by < > when a name is present + * 'name' and 'email' capture groups correspond respectively to 'display-name' and 'addr-spec' in the ABNF + * @see https://tools.ietf.org/html/rfc5322#section-3.4 + */ $hasMatches = preg_match('/^(?:(?P.+)\s)?(?(name)<|[^\s]+?)(?(name)>|>?)$/', $value, $matches); if ($hasMatches !== 1) { commit bfe40077aeed5c2cf401d23d0508d63b5c44d8c6 Author: Elan Ruusamäe Date: Sun Jan 31 00:03:58 2016 +0200 add format param to toArray this allows exporting headers in raw or encoded form diff --git a/src/Headers.php b/src/Headers.php index b416f52a..c05cde1b 100644 --- a/src/Headers.php +++ b/src/Headers.php @@ -430,10 +430,11 @@ class Headers implements Countable, Iterator /** * Return the headers container as an array * - * @todo determine how to produce single line headers, if they are supported + * @param bool $format Return the values in Mime::Encoded or in Raw format * @return array + * @todo determine how to produce single line headers, if they are supported */ - public function toArray() + public function toArray($format = Header\HeaderInterface::FORMAT_RAW) { $headers = array(); /* @var $header Header\HeaderInterface */ @@ -443,9 +444,9 @@ class Headers implements Countable, Iterator if (!isset($headers[$name])) { $headers[$name] = array(); } - $headers[$name][] = $header->getFieldValue(); + $headers[$name][] = $header->getFieldValue($format); } else { - $headers[$header->getFieldName()] = $header->getFieldValue(); + $headers[$header->getFieldName()] = $header->getFieldValue($format); } } return $headers; commit 8892c77410b08d2f0df90d4813904c10d741bd20 Author: Daniel Król Date: Wed Dec 2 17:24:47 2015 +0100 Inverse decode-split order, allow special char containing labels diff --git a/src/Header/AbstractAddressList.php b/src/Header/AbstractAddressList.php index e0b4e78c..64e5f8f9 100644 --- a/src/Header/AbstractAddressList.php +++ b/src/Header/AbstractAddressList.php @@ -42,31 +42,44 @@ abstract class AbstractAddressList implements HeaderInterface public static function fromString($headerLine) { list($fieldName, $fieldValue) = GenericHeader::splitHeaderLine($headerLine); - $decodedValue = HeaderWrap::mimeDecodeValue($fieldValue); - $wasEncoded = ($decodedValue !== $fieldValue); - $fieldValue = $decodedValue; - if (strtolower($fieldName) !== static::$type) { throw new Exception\InvalidArgumentException(sprintf( - 'Invalid header line for "%s" string', - __CLASS__ - )); - } - $header = new static(); - if ($wasEncoded) { - $header->setEncoding('UTF-8'); + 'Invalid header line for "%s" string', + __CLASS__ + )); } + // split value on "," $fieldValue = str_replace(Headers::FOLDING, ' ', $fieldValue); $fieldValue = preg_replace('/[^:]+:([^;]*);/', '$1,', $fieldValue); - $values = str_getcsv($fieldValue, ','); + $values = str_getcsv($fieldValue, ','); + + $wasEncoded = false; array_walk( $values, - function (&$value) { - $value = trim($value); + function (&$value) use (&$wasEncoded) { + $decodedValue = HeaderWrap::mimeDecodeValue($value); + $wasEncoded = $wasEncoded || ($decodedValue !== $value); + $value = trim($decodedValue); $value = self::stripComments($value); + $value = preg_replace( + [ + '#(?setEncoding('UTF-8'); + } + $values = array_filter($values); $addressList = $header->getAddressList(); commit 9b54b914885a3c07fa7522fe1bedf7fb4482392b Author: Daniel Król Date: Wed Dec 2 17:01:45 2015 +0100 Use RFC compliant EOL to allow new lines in message body http://www.ietf.org/rfc/rfc2821.txt diff --git a/src/Message.php b/src/Message.php index e16c2438..f83c2961 100644 --- a/src/Message.php +++ b/src/Message.php @@ -551,7 +551,7 @@ class Message $message = new static(); $headers = null; $content = null; - Mime\Decode::splitMessage($rawMessage, $headers, $content); + Mime\Decode::splitMessage($rawMessage, $headers, $content, Headers::EOL); if ($headers->has('mime-version')) { // todo - restore body to mime\message } commit 788375fa8b156424a6b9afe40dac23ba6f00b916 Author: Marvin Feldmann Date: Fri Apr 15 16:53:17 2016 +0200 Allow DNS Hostnames diff --git a/src/Address.php b/src/Address.php index e97878d5..e4c89d20 100644 --- a/src/Address.php +++ b/src/Address.php @@ -27,7 +27,7 @@ class Address implements Address\AddressInterface */ public function __construct($email, $name = null) { - $emailAddressValidator = new EmailAddressValidator(Hostname::ALLOW_LOCAL); + $emailAddressValidator = new EmailAddressValidator(Hostname::ALLOW_DNS | Hostname::ALLOW_LOCAL); if (! is_string($email) || empty($email)) { throw new Exception\InvalidArgumentException('Email must be a valid email address'); } commit e37c5e15185143eaaff7f2c4c2a64f605926c978 Author: Marvin Feldmann Date: Tue Apr 19 16:24:38 2016 +0200 Return email address(es) with ACE diff --git a/src/Header/AbstractAddressList.php b/src/Header/AbstractAddressList.php index 64e5f8f9..db40486a 100644 --- a/src/Header/AbstractAddressList.php +++ b/src/Header/AbstractAddressList.php @@ -94,6 +94,19 @@ abstract class AbstractAddressList implements HeaderInterface return $this->fieldName; } + /** + * Safely convert UTF-8 encoded domain name to ASCII + * @param string $domainName the UTF-8 encoded email + * @return string + */ + protected function idnToAscii($domainName) + { + if (extension_loaded('intl')) { + return (idn_to_ascii($domainName) ?: $domainName); + } + return $domainName; + } + public function getFieldValue($format = HeaderInterface::FORMAT_RAW) { $emails = array(); @@ -103,22 +116,29 @@ abstract class AbstractAddressList implements HeaderInterface $email = $address->getEmail(); $name = $address->getName(); - if (empty($name)) { - $emails[] = $email; - continue; - } - - if (false !== strstr($name, ',')) { + if (!empty($name) && false !== strstr($name, ',')) { $name = sprintf('"%s"', $name); } if ($format === HeaderInterface::FORMAT_ENCODED && 'ASCII' !== $encoding ) { - $name = HeaderWrap::mimeEncodeValue($name, $encoding); + if (!empty($name)) { + $name = HeaderWrap::mimeEncodeValue($name, $encoding); + } + + if (preg_match('/^(.+)@([^@]+)$/', $email, $matches)) { + $localPart = $matches[1]; + $hostname = $this->idnToAscii($matches[2]); + $email = sprintf('%s@%s', $localPart, $hostname); + } } - $emails[] = sprintf('%s <%s>', $name, $email); + if (empty($name)) { + $emails[] = $email; + } else { + $emails[] = sprintf('%s <%s>', $name, $email); + } } // Ensure the values are valid before sending them. commit e00ac0101b964063c70e282575a5b1bc4022f227 Author: Elan Ruusamäe Date: Sun May 14 15:09:56 2017 +0300 restore php 5.3 compat in HeaderValue. #129 Error in HeaderValue (version 2.4.11 and 2.4.10) for PHP 5.3.26 diff --git a/src/Header/HeaderValue.php b/src/Header/HeaderValue.php index 884cdcf7..5104a512 100644 --- a/src/Header/HeaderValue.php +++ b/src/Header/HeaderValue.php @@ -87,7 +87,7 @@ final class HeaderValue $lf = ord($value[$i + 1]); $sp = ord($value[$i + 2]); - if ($lf !== 10 || ! in_array($sp, [9, 32], true)) { + if ($lf !== 10 || ! in_array($sp, array(9, 32), true)) { return false; } commit 6bbdb6b5b8f549fa9f87cc5a6adbb558dfefeb99 Merge: 786a1418 e00ac010 Author: Elan Ruusamäe Date: Sun May 14 15:16:24 2017 +0300 Merge branch 'fix-129' into develop-2.4 fixing: https://github.com/zendframework/zend-mail/issues/129 commit ddf2e8ec39394774e753b9a44793e845134a3524 Author: Matthew Weier O'Phinney Date: Thu May 5 12:56:11 2016 -0500 Merge branch 'hotfix/86' Close #86 diff --git a/src/Headers.php b/src/Headers.php index c05cde1b..eac2bf9f 100644 --- a/src/Headers.php +++ b/src/Headers.php @@ -228,7 +228,11 @@ class Headers implements Countable, Iterator } if ($fieldValue === null) { - $this->addHeader(Header\GenericHeader::fromString($headerFieldNameOrLine)); + $headers = $this->loadHeader($headerFieldNameOrLine); + $headers = is_array($headers) ? $headers : [$headers]; + foreach ($headers as $header) { + $this->addHeader($header); + } } elseif (is_array($fieldValue)) { foreach ($fieldValue as $i) { $this->addHeader(Header\GenericMultiHeader::fromString($headerFieldNameOrLine . ':' . $i)); @@ -466,6 +470,19 @@ class Headers implements Countable, Iterator } /** + * Create Header object from header line + * + * @param string $headerLine + * @return Header\HeaderInterface|Header\HeaderInterface[] + */ + public function loadHeader($headerLine) + { + list($name, ) = Header\GenericHeader::splitHeaderLine($headerLine); + $class = $this->getPluginClassLoader()->load($name) ?: Header\GenericHeader::class; + return $class::fromString($headerLine); + } + + /** * @param $index * @return mixed */ commit c3ac503689ffd00ac51e43c97c311e8753ecd32c Author: Elan Ruusamäe Date: Tue Jun 13 00:54:18 2017 +0300 fix MessageId having double brackets this got broken from pull/86 when header lazyloading was omitted diff --git a/src/Header/MessageId.php b/src/Header/MessageId.php index 850757ea..03c52a75 100644 --- a/src/Header/MessageId.php +++ b/src/Header/MessageId.php @@ -27,7 +27,7 @@ class MessageId implements HeaderInterface } $header = new static(); - $header->setId($value); + $header->setId(trim($value, '<>')); return $header; } commit 63ac228e6f883ed8e26a7b8a02130348e2332edf Author: Elan Ruusamäe Date: Wed Jun 28 00:19:11 2017 +0300 skip lazy-load for array as well refs: - a4a93ef0 - ddf2e8ec this should be sent upstream, but upstream doesn't seem to respond even to trivial pull-requests. diff --git a/src/Headers.php b/src/Headers.php index eac2bf9f..d4f9a9e7 100644 --- a/src/Headers.php +++ b/src/Headers.php @@ -207,9 +207,6 @@ class Headers implements Countable, Iterator /** * Add a raw header line, either in name => value, or as a single string 'name: value' * - * This method allows for lazy-loading in that the parsing and instantiation of HeaderInterface object - * will be delayed until they are retrieved by either get() or current() - * * @throws Exception\InvalidArgumentException * @param string $headerFieldNameOrLine * @param string $fieldValue optional @@ -238,7 +235,9 @@ class Headers implements Countable, Iterator $this->addHeader(Header\GenericMultiHeader::fromString($headerFieldNameOrLine . ':' . $i)); } } else { - $this->addHeader(Header\GenericHeader::fromString($headerFieldNameOrLine . ':' . $fieldValue)); + $class = $this->getPluginClassLoader()->load($headerFieldNameOrLine) ?: Header\GenericHeader::class; + $header = $class::fromString($headerFieldNameOrLine . ':' . $fieldValue); + $this->addHeader($header); } return $this;