Skip to content

Commit dae4548

Browse files
committed
Enforce RFC822 parsing if enabled Webklex#462
1 parent d21e00c commit dae4548

File tree

7 files changed

+145
-9
lines changed

7 files changed

+145
-9
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ Updates should follow the [Keep a CHANGELOG](http://keepachangelog.com/) princip
1010
- Address parsing improved and extended to include more cases
1111
- Boundary parsing fixed and improved to support more formats #544
1212
- Decode partially encoded address names #511
13+
- Enforce RFC822 parsing if enabled #462
1314

1415
### Added
1516
- Security configuration options added

src/Address.php

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,24 @@ public function __construct(object $object) {
4343
if (property_exists($object, "host")){ $this->host = $object->host ?? ''; }
4444
if (property_exists($object, "mail")){ $this->mail = $object->mail ?? ''; }
4545
if (property_exists($object, "full")){ $this->full = $object->full ?? ''; }
46+
$this->boot();
47+
}
48+
49+
/**
50+
* Boot the address
51+
*/
52+
private function boot(): void {
53+
if($this->mail === "" && $this->mailbox !== "" && $this->host !== ""){
54+
$this->mail = $this->mailbox . "@" . $this->host;
55+
}elseif($this->mail === "" && $this->mailbox !== ""){
56+
$this->mail = $this->mailbox;
57+
}
58+
59+
if($this->full === "" && $this->mail !== "" && $this->personal !== ""){
60+
$this->full = $this->personal . " <" . $this->mail . ">";
61+
}elseif($this->full === "" && $this->mail !== ""){
62+
$this->full = $this->mail;
63+
}
4664
}
4765

4866

src/Header.php

Lines changed: 48 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -408,6 +408,24 @@ private function decodeAddresses($values): array {
408408
"mailbox" => $mailbox,
409409
"host" => $host,
410410
];
411+
}elseif (preg_match(
412+
'/^((?P<name>.+)<)(?P<email>[^<]+?)>$/',
413+
$split_address,
414+
$matches
415+
)) {
416+
$name = trim(rtrim($matches["name"]));
417+
if(str_starts_with($name, "\"") && str_ends_with($name, "\"")) {
418+
$name = substr($name, 1, -1);
419+
}elseif(str_starts_with($name, "'") && str_ends_with($name, "'")) {
420+
$name = substr($name, 1, -1);
421+
}
422+
$email = trim(rtrim($matches["email"]));
423+
list($mailbox, $host) = array_pad(explode("@", $email), 2, null);
424+
$addresses[] = (object)[
425+
"personal" => $name,
426+
"mailbox" => $mailbox,
427+
"host" => $host,
428+
];
411429
}
412430
}
413431
}
@@ -438,7 +456,6 @@ private function parseAddresses($list): array {
438456

439457
if (is_array($list) === false) {
440458
if(is_string($list)) {
441-
// $list = "<[email protected]>"
442459
if (preg_match(
443460
'/^(?:(?P<name>.+)\s)?(?(name)<|<?)(?P<email>[^\s]+?)(?(name)>|>?)$/',
444461
$list,
@@ -460,6 +477,32 @@ private function parseAddresses($list): array {
460477
"host" => $host,
461478
]
462479
];
480+
}elseif (preg_match(
481+
'/^((?P<name>.+)<)(?P<email>[^<]+?)>$/',
482+
$list,
483+
$matches
484+
)) {
485+
$name = trim(rtrim($matches["name"]));
486+
$email = trim(rtrim($matches["email"]));
487+
if(str_starts_with($name, "\"") && str_ends_with($name, "\"")) {
488+
$name = substr($name, 1, -1);
489+
}elseif(str_starts_with($name, "'") && str_ends_with($name, "'")) {
490+
$name = substr($name, 1, -1);
491+
}
492+
list($mailbox, $host) = array_pad(explode("@", $email), 2, null);
493+
if($mailbox === ">") { // Fix trailing ">" in malformed mailboxes
494+
$mailbox = "";
495+
}
496+
if($name === "" && $mailbox === "" && $host === "") {
497+
return $addresses;
498+
}
499+
$list = [
500+
(object)[
501+
"personal" => $name,
502+
"mailbox" => $mailbox,
503+
"host" => $host,
504+
]
505+
];
463506
}else{
464507
return $addresses;
465508
}
@@ -501,14 +544,15 @@ private function parseAddresses($list): array {
501544

502545
if ($address->host == ".SYNTAX-ERROR.") {
503546
$address->host = "";
547+
}elseif ($address->host == "UNKNOWN") {
548+
$address->host = "";
504549
}
505550
if ($address->mailbox == "UNEXPECTED_DATA_AFTER_ADDRESS") {
506551
$address->mailbox = "";
552+
}elseif ($address->mailbox == "MISSING_MAILBOX_TERMINATOR") {
553+
$address->mailbox = "";
507554
}
508555

509-
$address->mail = ($address->mailbox && $address->host) ? $address->mailbox . '@' . $address->host : false;
510-
$address->full = ($address->personal) ? $address->personal . ' <' . $address->mail . '>' : $address->mail;
511-
512556
$addresses[] = new Address($address);
513557
}
514558

tests/fixtures/EmailAddressTest.php

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,16 @@
1212

1313
namespace Tests\fixtures;
1414

15+
use Webklex\PHPIMAP\Exceptions\AuthFailedException;
16+
use Webklex\PHPIMAP\Exceptions\ConnectionFailedException;
17+
use Webklex\PHPIMAP\Exceptions\ImapBadRequestException;
18+
use Webklex\PHPIMAP\Exceptions\ImapServerErrorException;
19+
use Webklex\PHPIMAP\Exceptions\InvalidMessageDateException;
20+
use Webklex\PHPIMAP\Exceptions\MaskNotFoundException;
21+
use Webklex\PHPIMAP\Exceptions\MessageContentFetchingException;
22+
use Webklex\PHPIMAP\Exceptions\ResponseException;
23+
use Webklex\PHPIMAP\Exceptions\RuntimeException;
24+
1525
/**
1626
* Class EmailAddressTest
1727
*
@@ -23,6 +33,16 @@ class EmailAddressTest extends FixtureTestCase {
2333
* Test the fixture email_address.eml
2434
*
2535
* @return void
36+
* @throws \ReflectionException
37+
* @throws AuthFailedException
38+
* @throws ConnectionFailedException
39+
* @throws ImapBadRequestException
40+
* @throws ImapServerErrorException
41+
* @throws InvalidMessageDateException
42+
* @throws MaskNotFoundException
43+
* @throws MessageContentFetchingException
44+
* @throws ResponseException
45+
* @throws RuntimeException
2646
*/
2747
public function testFixture() : void {
2848
$message = $this->getFixture("email_address.eml");
@@ -32,8 +52,8 @@ public function testFixture() : void {
3252
self::assertEquals("Hi\r\nHow are you?", $message->getTextBody());
3353
self::assertFalse($message->hasHTMLBody());
3454
self::assertFalse($message->date->first());
35-
self::assertEquals("no_host@UNKNOWN", (string)$message->from);
55+
self::assertEquals("no_host", (string)$message->from);
3656
self::assertEquals("", $message->to);
37-
self::assertEquals("This one: is \"right\" <[email protected]>, No-address@UNKNOWN", $message->cc);
57+
self::assertEquals("This one: is \"right\" <[email protected]>, No-address", (string)$message->cc);
3858
}
3959
}

tests/fixtures/ReferencesTest.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,8 @@ public function testFixture() : void {
3434

3535
self::assertEquals("[email protected]", $message->in_reply_to);
3636
self::assertEquals("", $message->from->first()->personal);
37-
self::assertEquals("UNKNOWN", $message->from->first()->host);
38-
self::assertEquals("no_host@UNKNOWN", $message->from->first()->mail);
37+
self::assertEquals("", $message->from->first()->host);
38+
self::assertEquals("no_host", $message->from->first()->mail);
3939
self::assertFalse($message->to->first());
4040

4141
self::assertEquals([
@@ -45,7 +45,7 @@ public function testFixture() : void {
4545

4646
self::assertEquals([
4747
'This one: is "right" <[email protected]>',
48-
'No-address@UNKNOWN'
48+
'No-address'
4949
], $message->cc->map(function($address){
5050
/** @var \Webklex\PHPIMAP\Address $address */
5151
return $address->full;

tests/issues/Issue462Test.php

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
<?php
2+
/*
3+
* File: Issue462Test.php
4+
* Category: -
5+
* Author: M.Goldenbaum
6+
* Created: 23.06.23 20:41
7+
* Updated: -
8+
*
9+
* Description:
10+
* -
11+
*/
12+
13+
namespace Tests\issues;
14+
15+
use Tests\fixtures\FixtureTestCase;
16+
use Webklex\PHPIMAP\Config;
17+
use Webklex\PHPIMAP\Exceptions\AuthFailedException;
18+
use Webklex\PHPIMAP\Exceptions\ConnectionFailedException;
19+
use Webklex\PHPIMAP\Exceptions\ImapBadRequestException;
20+
use Webklex\PHPIMAP\Exceptions\ImapServerErrorException;
21+
use Webklex\PHPIMAP\Exceptions\InvalidMessageDateException;
22+
use Webklex\PHPIMAP\Exceptions\MaskNotFoundException;
23+
use Webklex\PHPIMAP\Exceptions\MessageContentFetchingException;
24+
use Webklex\PHPIMAP\Exceptions\ResponseException;
25+
use Webklex\PHPIMAP\Exceptions\RuntimeException;
26+
27+
class Issue462Test extends FixtureTestCase {
28+
29+
/**
30+
* @throws RuntimeException
31+
* @throws MessageContentFetchingException
32+
* @throws ResponseException
33+
* @throws ImapBadRequestException
34+
* @throws InvalidMessageDateException
35+
* @throws ConnectionFailedException
36+
* @throws \ReflectionException
37+
* @throws ImapServerErrorException
38+
* @throws AuthFailedException
39+
* @throws MaskNotFoundException
40+
*/
41+
public function testIssueEmail() {
42+
$config = Config::make();
43+
$config->set('options.rfc822', false);
44+
$message = $this->getFixture("issue-462.eml", $config);
45+
self::assertSame("Undeliverable: Some subject", (string)$message->subject);
46+
self::assertSame("postmaster@ <sending_domain.tld postmaster@sending_domain.tld>", (string)$message->from->first());
47+
}
48+
}

tests/messages/issue-462.eml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
From: "postmaster@" <sending_domain.tld postmaster@sending_domain.tld>
2+
To: receipent@receipent_domain.tld
3+
Subject: Undeliverable: Some subject
4+
5+
Test message

0 commit comments

Comments
 (0)