Skip to content

Commit

Permalink
Merge pull request #199 from Slamdunk/test_headers
Browse files Browse the repository at this point in the history
Parse all email type headers
Closes #96 #135
  • Loading branch information
Slamdunk committed Sep 28, 2017
2 parents eb13a9d + 3875874 commit aac8f58
Show file tree
Hide file tree
Showing 10 changed files with 183 additions and 28 deletions.
50 changes: 45 additions & 5 deletions src/Message.php
Expand Up @@ -53,7 +53,7 @@ public function getId(): string
*
* @return EmailAddress
*/
public function getFrom()
public function getFrom(): EmailAddress
{
return $this->getHeaders()->get('from');
}
Expand All @@ -63,7 +63,7 @@ public function getFrom()
*
* @return EmailAddress[] Empty array in case message has no To: recipients
*/
public function getTo()
public function getTo(): array
{
return $this->getHeaders()->get('to') ?: [];
}
Expand All @@ -73,11 +73,51 @@ public function getTo()
*
* @return EmailAddress[] Empty array in case message has no CC: recipients
*/
public function getCc()
public function getCc(): array
{
return $this->getHeaders()->get('cc') ?: [];
}

/**
* Get Bcc recipients
*
* @return EmailAddress[] Empty array in case message has no BCC: recipients
*/
public function getBcc(): array
{
return $this->getHeaders()->get('bcc') ?: [];
}

/**
* Get Reply-To recipients
*
* @return EmailAddress[] Empty array in case message has no Reply-To: recipients
*/
public function getReplyTo(): array
{
return $this->getHeaders()->get('reply_to') ?: [];
}

/**
* Get Sender
*
* @return EmailAddress[] Empty array in case message has no Sender: recipients
*/
public function getSender(): array
{
return $this->getHeaders()->get('sender') ?: [];
}

/**
* Get Return-Path
*
* @return EmailAddress[] Empty array in case message has no Return-Path: recipients
*/
public function getReturnPath(): array
{
return $this->getHeaders()->get('return_path') ?: [];
}

/**
* Get message number (from headers)
*
Expand All @@ -91,9 +131,9 @@ public function getNumber(): int
/**
* Get date (from headers)
*
* @return \DateTime
* @return \DateTimeImmutable
*/
public function getDate()
public function getDate(): \DateTimeImmutable
{
return $this->getHeaders()->get('date');
}
Expand Down
12 changes: 3 additions & 9 deletions src/Message/EmailAddress.php
Expand Up @@ -7,7 +7,7 @@
/**
* An e-mail address
*/
class EmailAddress
final class EmailAddress
{
private $mailbox;
private $hostname;
Expand Down Expand Up @@ -37,10 +37,9 @@ public function getAddress()
*/
public function getFullAddress(): string
{
$address = sprintf('%s@%s', $this->mailbox, $this->hostname);
if ($this->name) {
$address = sprintf('%s <%s@%s>', $this->name, $this->mailbox, $this->hostname);
} else {
$address = sprintf('%s@%s', $this->mailbox, $this->hostname);
$address = sprintf('"%s" <%s>', addcslashes($this->name, '"'), $address);
}

return $address;
Expand All @@ -60,9 +59,4 @@ public function getName()
{
return $this->name;
}

public function __toString()
{
return $this->getAddress();
}
}
27 changes: 16 additions & 11 deletions src/Message/Headers.php
Expand Up @@ -45,35 +45,40 @@ private function parseHeader(string $key, $value)
return (int) $value;
case 'date':
$value = $this->decode($value);
$value = preg_replace('/([^\(]*)\(.*\)/', '$1', $value);
$value = str_replace(',', '', $value);
$value = preg_replace('/ +\(.*\)/', '', $value);
if (0 === preg_match('/\d\d:\d\d:\d\d.* [\+\-]?\d\d:?\d\d/', $value)) {
$value .= ' +0000';
}

return new \DateTime($value);
return new \DateTimeImmutable($value);
case 'from':
return $this->decodeEmailAddress(current($value));
case 'to':
case 'cc':
case 'bcc':
case 'reply_to':
case 'sender':
case 'return_path':
$emails = [];
foreach ($value as $address) {
$emails[] = $this->decodeEmailAddress($address);
if (isset($address->mailbox)) {
$emails[] = $this->decodeEmailAddress($address);
}
}

return $emails;
case 'subject':
return $this->decode($value);
default:
return $value;
}

return $value;
}

private function decodeEmailAddress(\stdClass $value): EmailAddress
{
if(!isset($value->mailbox)){
$mailbox = 'undisclosed';
} else {
$mailbox = $value->mailbox;
}
return new EmailAddress(
$mailbox,
$value->mailbox,
isset($value->host) ? $value->host : null,
isset($value->personal) ? $this->decode($value->personal) : null
);
Expand Down
1 change: 1 addition & 0 deletions tests/MailboxTest.php
Expand Up @@ -8,6 +8,7 @@
use Ddeboer\Imap\Mailbox;

/**
* @covers \Ddeboer\Imap\Exception\Exception
* @covers \Ddeboer\Imap\Mailbox
*/
class MailboxTest extends AbstractTest
Expand Down
83 changes: 81 additions & 2 deletions tests/MessageTest.php
Expand Up @@ -12,11 +12,13 @@
* @covers \Ddeboer\Imap\Connection::expunge
* @covers \Ddeboer\Imap\Mailbox::expunge
* @covers \Ddeboer\Imap\Message
* @covers \Ddeboer\Imap\Message\Transcoder
* @covers \Ddeboer\Imap\MessageIterator
* @covers \Ddeboer\Imap\Message\Attachment
* @covers \Ddeboer\Imap\Message\EmailAddress
* @covers \Ddeboer\Imap\Message\Headers
* @covers \Ddeboer\Imap\Message\Part
* @covers \Ddeboer\Imap\Message\Transcoder
* @covers \Ddeboer\Imap\Parameters
*/
class MessageTest extends AbstractTest
{
Expand Down Expand Up @@ -175,8 +177,10 @@ public function testEmailAddress()
$cc = $message->getCc();
$this->assertCount(2, $cc);
$this->assertInstanceOf(EmailAddress::class, $cc[0]);
$this->assertEquals('This one is right', $cc[0]->getName());
$this->assertEquals('This one: is "right"', $cc[0]->getName());
$this->assertEquals('dong.com', $cc[0]->getHostname());
$this->assertEquals('ding@dong.com', $cc[0]->getAddress());
$this->assertEquals('"This one: is \\"right\\"" <ding@dong.com>', $cc[0]->getFullAddress());

$this->assertInstanceOf(EmailAddress::class, $cc[1]);
$this->assertEquals('No-address', $cc[1]->getMailbox());
Expand Down Expand Up @@ -234,4 +238,79 @@ public function getAttachmentFixture(): array
['attachment_encoded_filename'],
];
}

/**
* @dataProvider provideUndisclosedRecipientsCases
*/
public function testUndiscloredRecipients(string $fixture)
{
$this->mailbox->addMessage($this->getFixture($fixture));

$message = $this->mailbox->getMessage(1);

$this->assertCount(1, $message->getTo());
}

public function provideUndisclosedRecipientsCases(): array
{
return [
['undisclosed-recipients/minus'],
['undisclosed-recipients/space'],
];
}

public function testAdditionalAddresses()
{
$this->mailbox->addMessage($this->getFixture('bcc'));

$message = $this->mailbox->getMessage(1);

$types = [
'Bcc',
'Reply-To',
'Sender',
// 'Return-Path', // Can't get Dovecot return the Return-Path
];
foreach ($types as $type) {
$method = 'get' . str_replace('-', '', $type);
$emails = $message->{$method}();

$this->assertCount(1, $emails, $type);

$email = current($emails);

$this->assertSame(sprintf('%s@here.com', strtolower($type)), $email->getAddress(), $type);
}
}

/**
* @dataProvider provideDateCases
*/
public function testDates(string $output, string $dateRawHeader)
{
$template = $this->getFixture('date-template');
$message = str_replace('%date_raw_header%', $dateRawHeader, $template);
$this->mailbox->addMessage($message);

$message = $this->mailbox->getMessage(1);
$date = $message->getDate();

$this->assertInstanceOf(\DateTimeImmutable::class, $date);
$this->assertSame($output, $date->format(\DATE_ISO8601), sprintf('RAW: %s', $dateRawHeader));
}

/**
* @see https://gist.github.com/mikesart/b33762363153e2b8c7c7
*/
public function provideDateCases(): array
{
return [
['2017-09-28T09:24:01+0000', 'Thu, 28 Sep 2017 09:24:01 +0000 (UTC)'],
['2014-06-13T17:18:44+0200', '=?ISO-8859-2?Q?Fri,_13_Jun_2014_17:18:44_+020?=' . "\r\n" . ' =?ISO-8859-2?Q?0_(St=F8edn=ED_Evropa_(letn=ED_=E8as))?='],
['2008-02-13T02:15:46+0000', '13 Feb 08 02:15:46'],
['2008-04-03T12:36:15-0700', '03 Apr 2008 12:36:15 PDT'],
['2004-08-12T23:38:38-0700', 'Thu, 12 Aug 2004 11:38:38 PM -0700 (PDT)'],
['2006-01-04T21:47:28+0000', 'WED 04, JAN 2006 21:47:28'],
];
}
}
12 changes: 12 additions & 0 deletions tests/fixtures/bcc.eml
@@ -0,0 +1,12 @@
Return-Path: <return-path@here.com>
Subject: test
MIME-Version: 1.0
Content-Type: text/plain
Date: Wed, 27 Sep 2017 12:48:51 +0200
From: from@there.com
To: to@here.com
Bcc: bcc@here.com
Reply-To: reply-to@here.com
Sender: sender@here.com

Hi!
8 changes: 8 additions & 0 deletions tests/fixtures/date-template.eml
@@ -0,0 +1,8 @@
Subject: test
MIME-Version: 1.0
Content-Type: text/plain
Date: %date_raw_header%
From: from@there.com
To: to@here.com

Hi!
2 changes: 1 addition & 1 deletion tests/fixtures/email_address.eml
@@ -1,3 +1,3 @@
From: no_host
Cc: This one is right <ding@dong.com>, No-address
Cc: "This one: is \"right\"" <ding@dong.com>, No-address

8 changes: 8 additions & 0 deletions tests/fixtures/undisclosed-recipients/minus.eml
@@ -0,0 +1,8 @@
Subject: test
MIME-Version: 1.0
Content-Type: text/plain
Date: Wed, 27 Sep 2017 12:48:51 +0200
From: from@there.com
To: undisclosed-recipients:;

Hi!
8 changes: 8 additions & 0 deletions tests/fixtures/undisclosed-recipients/space.eml
@@ -0,0 +1,8 @@
Subject: test
MIME-Version: 1.0
Content-Type: text/plain
Date: Wed, 27 Sep 2017 12:48:51 +0200
From: from@there.com
To: Undisclosed recipients:;

Hi!

0 comments on commit aac8f58

Please sign in to comment.