Skip to content

Commit

Permalink
Enforce RFC822 parsing if enabled #462
Browse files Browse the repository at this point in the history
  • Loading branch information
Webklex committed Jan 19, 2025
1 parent d21e00c commit dae4548
Show file tree
Hide file tree
Showing 7 changed files with 145 additions and 9 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ Updates should follow the [Keep a CHANGELOG](http://keepachangelog.com/) princip
- Address parsing improved and extended to include more cases
- Boundary parsing fixed and improved to support more formats #544
- Decode partially encoded address names #511
- Enforce RFC822 parsing if enabled #462

### Added
- Security configuration options added
Expand Down
18 changes: 18 additions & 0 deletions src/Address.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,24 @@ public function __construct(object $object) {
if (property_exists($object, "host")){ $this->host = $object->host ?? ''; }
if (property_exists($object, "mail")){ $this->mail = $object->mail ?? ''; }
if (property_exists($object, "full")){ $this->full = $object->full ?? ''; }
$this->boot();
}

/**
* Boot the address
*/
private function boot(): void {
if($this->mail === "" && $this->mailbox !== "" && $this->host !== ""){
$this->mail = $this->mailbox . "@" . $this->host;
}elseif($this->mail === "" && $this->mailbox !== ""){
$this->mail = $this->mailbox;
}

if($this->full === "" && $this->mail !== "" && $this->personal !== ""){
$this->full = $this->personal . " <" . $this->mail . ">";
}elseif($this->full === "" && $this->mail !== ""){
$this->full = $this->mail;
}
}


Expand Down
52 changes: 48 additions & 4 deletions src/Header.php
Original file line number Diff line number Diff line change
Expand Up @@ -408,6 +408,24 @@ private function decodeAddresses($values): array {
"mailbox" => $mailbox,
"host" => $host,
];
}elseif (preg_match(
'/^((?P<name>.+)<)(?P<email>[^<]+?)>$/',
$split_address,
$matches
)) {
$name = trim(rtrim($matches["name"]));
if(str_starts_with($name, "\"") && str_ends_with($name, "\"")) {
$name = substr($name, 1, -1);
}elseif(str_starts_with($name, "'") && str_ends_with($name, "'")) {
$name = substr($name, 1, -1);
}
$email = trim(rtrim($matches["email"]));
list($mailbox, $host) = array_pad(explode("@", $email), 2, null);
$addresses[] = (object)[
"personal" => $name,
"mailbox" => $mailbox,
"host" => $host,
];
}
}
}
Expand Down Expand Up @@ -438,7 +456,6 @@ private function parseAddresses($list): array {

if (is_array($list) === false) {
if(is_string($list)) {
// $list = "<[email protected]>"
if (preg_match(
'/^(?:(?P<name>.+)\s)?(?(name)<|<?)(?P<email>[^\s]+?)(?(name)>|>?)$/',
$list,
Expand All @@ -460,6 +477,32 @@ private function parseAddresses($list): array {
"host" => $host,
]
];
}elseif (preg_match(
'/^((?P<name>.+)<)(?P<email>[^<]+?)>$/',
$list,
$matches
)) {
$name = trim(rtrim($matches["name"]));
$email = trim(rtrim($matches["email"]));
if(str_starts_with($name, "\"") && str_ends_with($name, "\"")) {
$name = substr($name, 1, -1);
}elseif(str_starts_with($name, "'") && str_ends_with($name, "'")) {
$name = substr($name, 1, -1);
}
list($mailbox, $host) = array_pad(explode("@", $email), 2, null);
if($mailbox === ">") { // Fix trailing ">" in malformed mailboxes
$mailbox = "";
}
if($name === "" && $mailbox === "" && $host === "") {
return $addresses;
}
$list = [
(object)[
"personal" => $name,
"mailbox" => $mailbox,
"host" => $host,
]
];
}else{
return $addresses;
}
Expand Down Expand Up @@ -501,14 +544,15 @@ private function parseAddresses($list): array {

if ($address->host == ".SYNTAX-ERROR.") {
$address->host = "";
}elseif ($address->host == "UNKNOWN") {
$address->host = "";
}
if ($address->mailbox == "UNEXPECTED_DATA_AFTER_ADDRESS") {
$address->mailbox = "";
}elseif ($address->mailbox == "MISSING_MAILBOX_TERMINATOR") {
$address->mailbox = "";
}

$address->mail = ($address->mailbox && $address->host) ? $address->mailbox . '@' . $address->host : false;
$address->full = ($address->personal) ? $address->personal . ' <' . $address->mail . '>' : $address->mail;

$addresses[] = new Address($address);
}

Expand Down
24 changes: 22 additions & 2 deletions tests/fixtures/EmailAddressTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,16 @@

namespace Tests\fixtures;

use Webklex\PHPIMAP\Exceptions\AuthFailedException;
use Webklex\PHPIMAP\Exceptions\ConnectionFailedException;
use Webklex\PHPIMAP\Exceptions\ImapBadRequestException;
use Webklex\PHPIMAP\Exceptions\ImapServerErrorException;
use Webklex\PHPIMAP\Exceptions\InvalidMessageDateException;
use Webklex\PHPIMAP\Exceptions\MaskNotFoundException;
use Webklex\PHPIMAP\Exceptions\MessageContentFetchingException;
use Webklex\PHPIMAP\Exceptions\ResponseException;
use Webklex\PHPIMAP\Exceptions\RuntimeException;

/**
* Class EmailAddressTest
*
Expand All @@ -23,6 +33,16 @@ class EmailAddressTest extends FixtureTestCase {
* Test the fixture email_address.eml
*
* @return void
* @throws \ReflectionException
* @throws AuthFailedException
* @throws ConnectionFailedException
* @throws ImapBadRequestException
* @throws ImapServerErrorException
* @throws InvalidMessageDateException
* @throws MaskNotFoundException
* @throws MessageContentFetchingException
* @throws ResponseException
* @throws RuntimeException
*/
public function testFixture() : void {
$message = $this->getFixture("email_address.eml");
Expand All @@ -32,8 +52,8 @@ public function testFixture() : void {
self::assertEquals("Hi\r\nHow are you?", $message->getTextBody());
self::assertFalse($message->hasHTMLBody());
self::assertFalse($message->date->first());
self::assertEquals("no_host@UNKNOWN", (string)$message->from);
self::assertEquals("no_host", (string)$message->from);
self::assertEquals("", $message->to);
self::assertEquals("This one: is \"right\" <[email protected]>, No-address@UNKNOWN", $message->cc);
self::assertEquals("This one: is \"right\" <[email protected]>, No-address", (string)$message->cc);
}
}
6 changes: 3 additions & 3 deletions tests/fixtures/ReferencesTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ public function testFixture() : void {

self::assertEquals("[email protected]", $message->in_reply_to);
self::assertEquals("", $message->from->first()->personal);
self::assertEquals("UNKNOWN", $message->from->first()->host);
self::assertEquals("no_host@UNKNOWN", $message->from->first()->mail);
self::assertEquals("", $message->from->first()->host);
self::assertEquals("no_host", $message->from->first()->mail);
self::assertFalse($message->to->first());

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

self::assertEquals([
'This one: is "right" <[email protected]>',
'No-address@UNKNOWN'
'No-address'
], $message->cc->map(function($address){
/** @var \Webklex\PHPIMAP\Address $address */
return $address->full;
Expand Down
48 changes: 48 additions & 0 deletions tests/issues/Issue462Test.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<?php
/*
* File: Issue462Test.php
* Category: -
* Author: M.Goldenbaum
* Created: 23.06.23 20:41
* Updated: -
*
* Description:
* -
*/

namespace Tests\issues;

use Tests\fixtures\FixtureTestCase;
use Webklex\PHPIMAP\Config;
use Webklex\PHPIMAP\Exceptions\AuthFailedException;
use Webklex\PHPIMAP\Exceptions\ConnectionFailedException;
use Webklex\PHPIMAP\Exceptions\ImapBadRequestException;
use Webklex\PHPIMAP\Exceptions\ImapServerErrorException;
use Webklex\PHPIMAP\Exceptions\InvalidMessageDateException;
use Webklex\PHPIMAP\Exceptions\MaskNotFoundException;
use Webklex\PHPIMAP\Exceptions\MessageContentFetchingException;
use Webklex\PHPIMAP\Exceptions\ResponseException;
use Webklex\PHPIMAP\Exceptions\RuntimeException;

class Issue462Test extends FixtureTestCase {

/**
* @throws RuntimeException
* @throws MessageContentFetchingException
* @throws ResponseException
* @throws ImapBadRequestException
* @throws InvalidMessageDateException
* @throws ConnectionFailedException
* @throws \ReflectionException
* @throws ImapServerErrorException
* @throws AuthFailedException
* @throws MaskNotFoundException
*/
public function testIssueEmail() {
$config = Config::make();
$config->set('options.rfc822', false);
$message = $this->getFixture("issue-462.eml", $config);
self::assertSame("Undeliverable: Some subject", (string)$message->subject);
self::assertSame("postmaster@ <sending_domain.tld postmaster@sending_domain.tld>", (string)$message->from->first());
}
}
5 changes: 5 additions & 0 deletions tests/messages/issue-462.eml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
From: "postmaster@" <sending_domain.tld postmaster@sending_domain.tld>
To: receipent@receipent_domain.tld
Subject: Undeliverable: Some subject

Test message

0 comments on commit dae4548

Please sign in to comment.