diff --git a/src/Header/Consumer/AddressConsumer.php b/src/Header/Consumer/AddressConsumer.php index 5c6b9c21..71b6e422 100644 --- a/src/Header/Consumer/AddressConsumer.php +++ b/src/Header/Consumer/AddressConsumer.php @@ -9,6 +9,7 @@ use ZBateson\MailMimeParser\Header\IHeaderPart; use ZBateson\MailMimeParser\Header\Part\Token; use ZBateson\MailMimeParser\Header\Part\AddressGroupPart; +use ZBateson\MailMimeParser\Header\Part\AddressPart; /** * Parses a single part of an address header. @@ -45,21 +46,21 @@ protected function getSubConsumers() { return [ $this->consumerService->getAddressGroupConsumer(), + $this->consumerService->getAddressEmailConsumer(), $this->consumerService->getCommentConsumer(), $this->consumerService->getQuotedStringConsumer(), ]; } /** - * Overridden to return patterns matching the beginning part of an address - * in a name/address part ("<" and ">" chars), end tokens ("," and ";"), and + * Overridden to return patterns matching end tokens ("," and ";"), and * whitespace. * * @return string[] the patterns */ public function getTokenSeparators() { - return [ '<', '>', ',', ';', '\s+' ]; + return [ ',', ';', '\s+' ]; } /** @@ -129,18 +130,22 @@ private function processSinglePart(IHeaderPart $part, &$strName, &$strValue) protected function processParts(array $parts) { $strName = ''; - $strValue = ''; + $strEmail = ''; foreach ($parts as $part) { if ($part instanceof AddressGroupPart) { return [ $this->partFactory->newAddressGroupPart( $part->getAddresses(), - $strValue + $strEmail ) ]; + } elseif ($part instanceof AddressPart) { + $strName = $strEmail; + $strEmail = $part->getEmail(); + break; } - $this->processSinglePart($part, $strName, $strValue); + $strEmail .= $part->getValue(); } - return [ $this->partFactory->newAddressPart($strName, $strValue) ]; + return [ $this->partFactory->newAddressPart($strName, $strEmail) ]; } } diff --git a/src/Header/Consumer/AddressEmailConsumer.php b/src/Header/Consumer/AddressEmailConsumer.php new file mode 100644 index 00000000..b89eb459 --- /dev/null +++ b/src/Header/Consumer/AddressEmailConsumer.php @@ -0,0 +1,85 @@ +. + * + * The address portion found within the '<' and '>' chars may contain comments + * and quoted portions. + * + * @author Zaahid Bateson + */ +class AddressEmailConsumer extends AbstractConsumer +{ + /** + * Returns the following as sub-consumers: + * - {@see AddressGroupConsumer} + * - {@see CommentConsumer} + * - {@see QuotedStringConsumer} + * + * @return AbstractConsumer[] the sub-consumers + */ + protected function getSubConsumers() + { + return [ + $this->consumerService->getCommentConsumer(), + $this->consumerService->getQuotedStringConsumer(), + ]; + } + + /** + * Overridden to return patterns matching the beginning/end part of an + * address in a name/address part ("<" and ">" chars). + * + * @return string[] the patterns + */ + public function getTokenSeparators() + { + return [ '<', '>' ]; + } + + /** + * Returns true for the '>' char. + * + * @param string $token + * @return boolean false + */ + protected function isEndToken($token) + { + return ($token === '>'); + } + + /** + * Returns true for the '<' char. + * + * @param string $token + * @return boolean false + */ + protected function isStartToken($token) + { + return ($token === '<'); + } + + /** + * Returns a single AddressPart with its 'email' portion set, so an + * AddressConsumer can identify it and create an AddressPart with both a + * name and email set. + * + * @param \ZBateson\MailMimeParser\Header\IHeaderPart[] $parts + * @return \ZBateson\MailMimeParser\Header\IHeaderPart[]|array + */ + protected function processParts(array $parts) + { + $strEmail = ''; + foreach ($parts as $p) { + $strEmail .= $p->getValue(); + } + return [ $this->partFactory->newAddressPart('', $strEmail) ]; + } +} diff --git a/src/Header/Consumer/ConsumerService.php b/src/Header/Consumer/ConsumerService.php index 2fae1f10..77887396 100644 --- a/src/Header/Consumer/ConsumerService.php +++ b/src/Header/Consumer/ConsumerService.php @@ -81,6 +81,16 @@ public function getAddressGroupConsumer() return AddressGroupConsumer::getInstance($this, $this->partFactory); } + /** + * Returns the AddressEmailConsumer singleton instance. + * + * @return AddressEmailConsumer + */ + public function getAddressEmailConsumer() + { + return AddressEmailConsumer::getInstance($this, $this->partFactory); + } + /** * Returns the CommentConsumer singleton instance. * diff --git a/tests/MailMimeParser/Header/Consumer/AddressBaseConsumerTest.php b/tests/MailMimeParser/Header/Consumer/AddressBaseConsumerTest.php index 3181190a..2f919a60 100644 --- a/tests/MailMimeParser/Header/Consumer/AddressBaseConsumerTest.php +++ b/tests/MailMimeParser/Header/Consumer/AddressBaseConsumerTest.php @@ -61,6 +61,20 @@ public function testConsumeAddresses() $this->assertEquals('Brute', $ret[2]->getName()); $this->assertEquals('brute@isThatHisName.com', $ret[2]->getEmail()); } + + public function testConsumeNamesAndAddressesWithFunnyChars() + { + $emails = '"Popeye the Sailor" , "Olive" , Brute , NotCute '; + $ret = $this->addressBaseConsumer->__invoke($emails); + $this->assertNotEmpty($ret); + $this->assertCount(4, $ret); + + $this->assertEquals('Popeye@TheSailorMan.com', $ret[0]->getEmail()); + $this->assertEquals('Olive@Oil.com:', $ret[1]->getEmail()); + $this->assertEquals('Brute', $ret[2]->getName()); + $this->assertEquals('brute@isThatHisName.com,', $ret[2]->getEmail()); + $this->assertEquals('notcute@address.com;', $ret[3]->getEmail()); + } public function testConsumeAddressAndGroup() { diff --git a/tests/MailMimeParser/Header/Consumer/AddressEmailConsumerTest.php b/tests/MailMimeParser/Header/Consumer/AddressEmailConsumerTest.php new file mode 100644 index 00000000..f9862417 --- /dev/null +++ b/tests/MailMimeParser/Header/Consumer/AddressEmailConsumerTest.php @@ -0,0 +1,91 @@ +getMockBuilder('ZBateson\MbWrapper\MbWrapper') + ->setMethods(['__toString']) + ->getMock(); + $pf = $this->getMockBuilder('ZBateson\MailMimeParser\Header\Part\HeaderPartFactory') + ->setConstructorArgs([$charsetConverter]) + ->setMethods(['__toString']) + ->getMock(); + $mlpf = $this->getMockBuilder('ZBateson\MailMimeParser\Header\Part\MimeLiteralPartFactory') + ->setConstructorArgs([$charsetConverter]) + ->setMethods(['__toString']) + ->getMock(); + $cs = $this->getMockBuilder('ZBateson\MailMimeParser\Header\Consumer\ConsumerService') + ->setConstructorArgs([$pf, $mlpf]) + ->setMethods(['__toString']) + ->getMock(); + $this->addressConsumer = new AddressEmailConsumer($cs, $pf); + } + + public function testConsumeEmail() + { + $email = 'Max.Payne@AddressUnknown.com'; + $ret = $this->addressConsumer->__invoke($email); + $this->assertNotEmpty($ret); + $this->assertCount(1, $ret); + + $address = $ret[0]; + $this->assertInstanceOf('\ZBateson\MailMimeParser\Header\Part\AddressPart', $address); + $this->assertEquals('', $address->getName()); + $this->assertEquals($email, $address->getEmail()); + } + + public function testConsumeEmailWithComments() + { + // can't remember any longer if this is how it should be handled + // need to review RFC + $email = 'Max(imum).Payne (comment)@AddressUnknown.com'; + $ret = $this->addressConsumer->__invoke($email); + $this->assertNotEmpty($ret); + $this->assertCount(1, $ret); + + $address = $ret[0]; + $this->assertInstanceOf('\ZBateson\MailMimeParser\Header\Part\AddressPart', $address); + $this->assertEquals('Max.Payne@AddressUnknown.com', $address->getEmail()); + } + + public function testConsumeEmailWithQuotes() + { + // can't remember any longer if this is how it should be handled + // need to review RFC + $email = 'Max"(imum).Payne (comment)"@AddressUnknown.com'; + $ret = $this->addressConsumer->__invoke($email); + $this->assertNotEmpty($ret); + $this->assertCount(1, $ret); + + $address = $ret[0]; + $this->assertInstanceOf('\ZBateson\MailMimeParser\Header\Part\AddressPart', $address); + $this->assertEquals('Max(imum).Payne(comment)@AddressUnknown.com', $address->getEmail()); + } + + public function testNotConsumeAddressGroup() + { + $email = 'Senate: Caesar@Dictator.com,Cicero@Philosophy.com, Marc Antony '; + $ret = $this->addressConsumer->__invoke($email); + $this->assertNotEmpty($ret); + $this->assertCount(1, $ret); + + $address = $ret[0]; + $this->assertInstanceOf('\ZBateson\MailMimeParser\Header\Part\AddressPart', $address); + $this->assertEquals('Senate:Caesar@Dictator.com,Cicero@Philosophy.com,MarcAntonygetEmail()); + } +} diff --git a/tests/MailMimeParser/Header/Consumer/ConsumerServiceTest.php b/tests/MailMimeParser/Header/Consumer/ConsumerServiceTest.php index 722e5537..8b7953ce 100644 --- a/tests/MailMimeParser/Header/Consumer/ConsumerServiceTest.php +++ b/tests/MailMimeParser/Header/Consumer/ConsumerServiceTest.php @@ -45,6 +45,13 @@ public function testGetAddressConsumer() $this->assertNotNull($consumer); $this->assertInstanceOf('\ZBateson\MailMimeParser\Header\Consumer\AddressConsumer', $consumer); } + + public function testGetAddressEmailConsumer() + { + $consumer = $this->consumerService->getAddressEmailConsumer(); + $this->assertNotNull($consumer); + $this->assertInstanceOf('\ZBateson\MailMimeParser\Header\Consumer\AddressEmailConsumer', $consumer); + } public function testGetAddressGroupConsumer() {