From e2eb2304feff92f0f89f30a49bf1d77026775f23 Mon Sep 17 00:00:00 2001 From: Marcus Bointon Date: Thu, 18 Feb 2021 11:46:07 +0100 Subject: [PATCH] Decode encoded names in the address parser, see #2266 --- changelog.md | 1 + src/PHPMailer.php | 13 +++++++- test/PHPMailerTest.php | 71 +++++++++++++++++++++++++++++++++++++----- test/fakefunctions.php | 35 --------------------- 4 files changed, 77 insertions(+), 43 deletions(-) diff --git a/changelog.md b/changelog.md index 9d4d2c11..ba0ac9e2 100644 --- a/changelog.md +++ b/changelog.md @@ -5,6 +5,7 @@ * Switch to Github Actions for CI * Generate debug output for `mail()` and `sendmail` transports – enable using the same mechanism as for SMTP: set `SMTPDebug` > 0 * Make the `mail()` transport set the envelope sender the same way as SMTP does, i.e. use whatever `From` is set to, only falling back to the `sendmail_from` php.ini setting if `From` is unset. This avoids errors from the `mail()` function if `Sender` is not set explicitly and php.ini is not configured. This is a minor functionality change, so bumps the minor version number. +* Extend `parseAddresses` to decode encoded names, improve tests ## Version 6.2.0 * PHP 8.0 compatibility, many thanks to @jrf_nl! diff --git a/src/PHPMailer.php b/src/PHPMailer.php index 52d49442..55eab8fb 100644 --- a/src/PHPMailer.php +++ b/src/PHPMailer.php @@ -1196,6 +1196,11 @@ class PHPMailer $address->mailbox . '@' . $address->host ) ) { + //Decode the name part if it's present and encoded + if (property_exists($address, 'personal') && preg_match('/^=\?.*\?=$/', $address->personal)) { + $address->personal = mb_decode_mimeheader($address->personal); + } + $addresses[] = [ 'name' => (property_exists($address, 'personal') ? $address->personal : ''), 'address' => $address->mailbox . '@' . $address->host, @@ -1219,9 +1224,15 @@ class PHPMailer } else { list($name, $email) = explode('<', $address); $email = trim(str_replace('>', '', $email)); + $name = trim($name); if (static::validateAddress($email)) { + //If this name is encoded, decode it + if (preg_match('/^=\?.*\?=$/', $name)) { + $name = mb_decode_mimeheader($name); + } $addresses[] = [ - 'name' => trim(str_replace(['"', "'"], '', $name)), + //Remove any surrounding quotes and spaces from the name + 'name' => trim($name, '\'" '), 'address' => $email, ]; } diff --git a/test/PHPMailerTest.php b/test/PHPMailerTest.php index 056d138e..8b4f052c 100644 --- a/test/PHPMailerTest.php +++ b/test/PHPMailerTest.php @@ -3029,24 +3029,81 @@ EOT; } /** + * Test RFC822 address list parsing using PHPMailer's parser. * @test */ public function imapParsedAddressList_parseAddress_returnsAddressArray() { + $addressLine = 'joe@example.com, , Joe Doe , "John O\'Groats" ,' . + ' =?utf-8?B?0J3QsNC30LLQsNC90LjQtSDRgtC10YHRgtCw?= '; + + //Test using PHPMailer's own parser $expected = [ [ - 'name' => 'joe', + 'name' => '', 'address' => 'joe@example.com', ], [ - 'name' => 'me', - 'address' => 'me@home.com', + 'name' => '', + 'address' => 'me@example.com', + ], + [ + 'name' => 'Joe Doe', + 'address' => 'doe@example.com', + ], + [ + 'name' => "John O'Groats", + 'address' => 'johnog@example.net', + ], + [ + 'name' => 'Название теста', + 'address' => 'encoded@example.org', ], ]; - if (file_exists($this->INCLUDE_DIR . '/test/fakefunctions.php')) { - include $this->INCLUDE_DIR . '/test/fakefunctions.php'; - $addresses = PHPMailer::parseAddresses('joe@example.com, me@home.com'); - $this->assertEquals(asort($expected), asort($addresses)); + $parsed = PHPMailer::parseAddresses($addressLine, false); + $this->assertSameSize($expected, $parsed); + for ($i = 0; $i < count($expected); $i++) { + $this->assertSame($expected[$i], $parsed[$i]); + } + } + + /** + * Test RFC822 address list parsing using the IMAP extension's parser. + * @test + */ + public function imapParsedAddressList_parseAddress_returnsAddressArray_usingImap() + { + if (!extension_loaded('imap')) { + $this->markTestSkipped("imap extension missing, can't run this test"); + } + $addressLine = 'joe@example.com, , Joe Doe , "John O\'Groats" ,' . + ' =?utf-8?B?0J3QsNC30LLQsNC90LjQtSDRgtC10YHRgtCw?= '; + $expected = [ + [ + 'name' => '', + 'address' => 'joe@example.com', + ], + [ + 'name' => '', + 'address' => 'me@example.com', + ], + [ + 'name' => 'Joe Doe', + 'address' => 'doe@example.com', + ], + [ + 'name' => "John O'Groats", + 'address' => 'johnog@example.net', + ], + [ + 'name' => 'Название теста', + 'address' => 'encoded@example.org', + ], + ]; + $parsed = PHPMailer::parseAddresses($addressLine, true); + $this->assertSameSize($expected, $parsed); + for ($i = 0; $i < count($expected); $i++) { + $this->assertSame($expected[$i], $parsed[$i]); } } diff --git a/test/fakefunctions.php b/test/fakefunctions.php index 7ff33b9b..134da600 100644 --- a/test/fakefunctions.php +++ b/test/fakefunctions.php @@ -13,38 +13,3 @@ if (!function_exists('mb_convert_encoding')) { return true; } } - -if (!function_exists('imap_rfc822_parse_adrlist')) { - function imap_rfc822_parse_adrlist($addressList) - { - $addresses = explode(',', $addressList); - $fakedAddresses = []; - foreach ($addresses as $address) { - $fakedAddresses[] = new FakeAddress($address); - } - - return $fakedAddresses; - } - - if (!class_exists(FakeAddress::class)) { - class FakeAddress - { - public $host = 'example.com'; - public $mailbox = 'joe'; - public $personal = 'joe example'; - - /** - * FakeAddress constructor. - * - * @param string $addressString - */ - public function __construct($addressString) - { - $addressParts = explode('@', $addressString); - $this->mailbox = trim($addressParts[0]); - $this->host = trim($addressParts[1]); - $this->personal = explode('.', $addressParts[1])[0]; - } - } - } -}