Merge pull request #3202 from SirLouen/patch/3201

Adding Name Encoding Improvement without MbString Extension
This commit is contained in:
Marcus Bointon 2025-09-29 18:26:33 +02:00 committed by GitHub
commit 22a8dce04e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 276 additions and 233 deletions

View File

@ -35,3 +35,4 @@ $PHPMAILER_LANG['smtp_detail'] = 'Detalle: ';
$PHPMAILER_LANG['smtp_error'] = 'Error del servidor SMTP: '; $PHPMAILER_LANG['smtp_error'] = 'Error del servidor SMTP: ';
$PHPMAILER_LANG['variable_set'] = 'No se pudo configurar la variable: '; $PHPMAILER_LANG['variable_set'] = 'No se pudo configurar la variable: ';
$PHPMAILER_LANG['imap_recommended'] = 'No se recomienda usar el analizador de direcciones simplificado. Instala la extensión IMAP de PHP para un análisis RFC822 más completo.'; $PHPMAILER_LANG['imap_recommended'] = 'No se recomienda usar el analizador de direcciones simplificado. Instala la extensión IMAP de PHP para un análisis RFC822 más completo.';
$PHPMAILER_LANG['deprecated_argument'] = 'El argumento $deprecatedArg ha quedado obsoleto';

View File

@ -1237,16 +1237,19 @@ class PHPMailer
* *
* @see https://www.andrew.cmu.edu/user/agreen1/testing/mrbs/web/Mail/RFC822.php A more careful implementation * @see https://www.andrew.cmu.edu/user/agreen1/testing/mrbs/web/Mail/RFC822.php A more careful implementation
* *
* @param string $addrstr The address list string * @param string $addrstr The address list string
* @param bool $useimap Whether to use the IMAP extension to parse the list * @param string|null $deprecatedArg Deprecated argument since 6.11.0.
* @param string $charset The charset to use when decoding the address list string. * @param string $charset The charset to use when decoding the address list string.
* *
* @return array * @return array
*/ */
public static function parseAddresses($addrstr, $useimap = true, $charset = self::CHARSET_ISO88591) public static function parseAddresses($addrstr, $deprecatedArg = null, $charset = self::CHARSET_ISO88591)
{ {
if ($deprecatedArg !== null) {
trigger_error(self::lang('deprecated_argument'), E_USER_DEPRECATED);
}
$addresses = []; $addresses = [];
if ($useimap && function_exists('imap_rfc822_parse_adrlist')) { if (function_exists('imap_rfc822_parse_adrlist')) {
//Use this built-in parser if it's available //Use this built-in parser if it's available
$list = imap_rfc822_parse_adrlist($addrstr, ''); $list = imap_rfc822_parse_adrlist($addrstr, '');
// Clear any potential IMAP errors to get rid of notices being thrown at end of script. // Clear any potential IMAP errors to get rid of notices being thrown at end of script.
@ -1256,20 +1259,13 @@ class PHPMailer
'.SYNTAX-ERROR.' !== $address->host && '.SYNTAX-ERROR.' !== $address->host &&
static::validateAddress($address->mailbox . '@' . $address->host) static::validateAddress($address->mailbox . '@' . $address->host)
) { ) {
//Decode the name part if it's present and encoded //Decode the name part if it's present and maybe encoded
if ( if (
property_exists($address, 'personal') && property_exists($address, 'personal')
//Check for a Mbstring constant rather than using extension_loaded, which is sometimes disabled && is_string($address->personal)
defined('MB_CASE_UPPER') && && $address->personal !== ''
preg_match('/^=\?.*\?=$/s', $address->personal)
) { ) {
$origCharset = mb_internal_encoding(); $address->personal = static::decodeHeader($address->personal, $charset);
mb_internal_encoding($charset);
//Undo any RFC2047-encoded spaces-as-underscores
$address->personal = str_replace('_', '=20', $address->personal);
//Decode the name
$address->personal = mb_decode_mimeheader($address->personal);
mb_internal_encoding($origCharset);
} }
$addresses[] = [ $addresses[] = [
@ -1280,7 +1276,7 @@ class PHPMailer
} }
} else { } else {
//Use this simpler parser //Use this simpler parser
$addresses = self::parseSimplerAddresses($addrstr, $charset); $addresses = static::parseSimplerAddresses($addrstr, $charset);
} }
return $addresses; return $addresses;
@ -1302,6 +1298,7 @@ class PHPMailer
// Emit a runtime notice to recommend using the IMAP extension for full RFC822 parsing // Emit a runtime notice to recommend using the IMAP extension for full RFC822 parsing
trigger_error(self::lang('imap_recommended'), E_USER_NOTICE); trigger_error(self::lang('imap_recommended'), E_USER_NOTICE);
$addresses = [];
$list = explode(',', $addrstr); $list = explode(',', $addrstr);
foreach ($list as $address) { foreach ($list as $address) {
$address = trim($address); $address = trim($address);
@ -1315,21 +1312,10 @@ class PHPMailer
]; ];
} }
} else { } else {
list($name, $email) = explode('<', $address); $parsed = static::parseEmailString($address);
$email = trim(str_replace('>', '', $email)); $email = $parsed['email'];
$name = trim($name);
if (static::validateAddress($email)) { if (static::validateAddress($email)) {
//Check for a Mbstring constant rather than using extension_loaded, which is sometimes disabled $name = static::decodeHeader($parsed['name'], $charset);
//If this name is encoded, decode it
if (defined('MB_CASE_UPPER') && preg_match('/^=\?.*\?=$/s', $name)) {
$origCharset = mb_internal_encoding();
mb_internal_encoding($charset);
//Undo any RFC2047-encoded spaces-as-underscores
$name = str_replace('_', '=20', $name);
//Decode the name
$name = mb_decode_mimeheader($name);
mb_internal_encoding($origCharset);
}
$addresses[] = [ $addresses[] = [
//Remove any surrounding quotes and spaces from the name //Remove any surrounding quotes and spaces from the name
'name' => trim($name, '\'" '), 'name' => trim($name, '\'" '),
@ -1342,6 +1328,42 @@ class PHPMailer
return $addresses; return $addresses;
} }
/**
* Parse a string containing an email address with an optional name
* and divide it into a name and email address.
*
* @param string $input The email with name.
*
* @return array{name: string, email: string}
*/
private static function parseEmailString($input)
{
$input = trim((string)$input);
if ($input === '') {
return ['name' => '', 'email' => ''];
}
$pattern = '/^\s*(?:(?:"([^"]*)"|\'([^\']*)\'|([^<]*?))\s*)?<\s*([^>]+)\s*>\s*$/';
if (preg_match($pattern, $input, $matches)) {
$name = '';
// Double quotes including special scenarios.
if (isset($matches[1]) && $matches[1] !== '') {
$name = $matches[1];
// Single quotes including special scenarios.
} elseif (isset($matches[2]) && $matches[2] !== '') {
$name = $matches[2];
// Simplest scenario, name and email are in the format "Name <email>".
} elseif (isset($matches[3])) {
$name = trim($matches[3]);
}
return ['name' => $name, 'email' => trim($matches[4])];
}
return ['name' => '', 'email' => $input];
}
/** /**
* Set the From and FromName properties. * Set the From and FromName properties.
* *
@ -1862,7 +1884,7 @@ class PHPMailer
fwrite($mail, $header); fwrite($mail, $header);
fwrite($mail, $body); fwrite($mail, $body);
$result = pclose($mail); $result = pclose($mail);
$addrinfo = static::parseAddresses($toAddr, true, $this->CharSet); $addrinfo = static::parseAddresses($toAddr, null, $this->CharSet);
foreach ($addrinfo as $addr) { foreach ($addrinfo as $addr) {
$this->doCallback( $this->doCallback(
($result === 0), ($result === 0),
@ -2039,7 +2061,7 @@ class PHPMailer
if ($this->SingleTo && count($toArr) > 1) { if ($this->SingleTo && count($toArr) > 1) {
foreach ($toArr as $toAddr) { foreach ($toArr as $toAddr) {
$result = $this->mailPassthru($toAddr, $this->Subject, $body, $header, $params); $result = $this->mailPassthru($toAddr, $this->Subject, $body, $header, $params);
$addrinfo = static::parseAddresses($toAddr, true, $this->CharSet); $addrinfo = static::parseAddresses($toAddr, null, $this->CharSet);
foreach ($addrinfo as $addr) { foreach ($addrinfo as $addr) {
$this->doCallback( $this->doCallback(
$result, $result,
@ -2456,6 +2478,7 @@ class PHPMailer
'no_smtputf8' => 'Server does not support SMTPUTF8 needed to send to Unicode addresses', 'no_smtputf8' => 'Server does not support SMTPUTF8 needed to send to Unicode addresses',
'imap_recommended' => 'Using simplified address parser is not recommended. ' . 'imap_recommended' => 'Using simplified address parser is not recommended. ' .
'Install the PHP IMAP extension for full RFC822 parsing.', 'Install the PHP IMAP extension for full RFC822 parsing.',
'deprecated_argument' => 'Argument $deprecatedArg is deprecated',
]; ];
if (empty($lang_path)) { if (empty($lang_path)) {
//Calculate an absolute path so it can work if CWD is not here //Calculate an absolute path so it can work if CWD is not here
@ -3706,6 +3729,42 @@ class PHPMailer
return trim(static::normalizeBreaks($encoded)); return trim(static::normalizeBreaks($encoded));
} }
/**
* Decode an RFC2047-encoded header value
* Attempts multiple strategies so it works even when the mbstring extension is disabled.
*
* @param string $value The header value to decode
* @param string $charset The target charset to convert to, defaults to ISO-8859-1 for BC
*
* @return string The decoded header value
*/
public static function decodeHeader($value, $charset = self::CHARSET_ISO88591)
{
if (!is_string($value) || $value === '') {
return '';
}
// Detect the presence of any RFC2047 encoded-words
$hasEncodedWord = (bool) preg_match('/=\?.*\?=/s', $value);
if ($hasEncodedWord && defined('MB_CASE_UPPER')) {
$origCharset = mb_internal_encoding();
// Always decode to UTF-8 to provide a consistent, modern output encoding.
mb_internal_encoding($charset);
if (PHP_VERSION_ID < 80300) {
// Undo any RFC2047-encoded spaces-as-underscores.
$value = str_replace('_', '=20', $value);
} else {
// PHP 8.3+ already interprets underscores as spaces. Remove additional
// linear whitespace between adjacent encoded words to avoid double spacing.
$value = preg_replace('/(\?=)\s+(=\?)/', '$1$2', $value);
}
// Decode the header value
$value = mb_decode_mimeheader($value);
mb_internal_encoding($origCharset);
}
return $value;
}
/** /**
* Check if a string contains multi-byte characters. * Check if a string contains multi-byte characters.
* *

View File

@ -278,6 +278,8 @@ EOT;
/** /**
* Send a message containing ISO-8859-1 text. * Send a message containing ISO-8859-1 text.
*
* @requires extension mbstring
*/ */
public function testHtmlIso8859() public function testHtmlIso8859()
{ {

View File

@ -19,85 +19,32 @@ use Yoast\PHPUnitPolyfills\TestCases\TestCase;
/** /**
* Test RFC822 address splitting. * Test RFC822 address splitting.
*
* @todo Additional tests need to be added to verify the correct handling of inputs which
* include a different encoding than UTF8 or even mixed encoding. For more information
* on what these test cases should look like and should test, please see
* {@link https://github.com/PHPMailer/PHPMailer/pull/2449} for context.
*
* @covers \PHPMailer\PHPMailer\PHPMailer::parseAddresses
*/ */
final class ParseAddressesTest extends TestCase final class ParseAddressesTest extends TestCase
{ {
/** /**
* Test RFC822 address splitting using the IMAP implementation * Verify the expectations.
* with the Mbstring extension available.
* *
* @requires extension imap * Abstracted out as the same verification needs to be done for every test, just with different data.
* @requires extension mbstring
* *
* @dataProvider dataAddressSplitting * @param string $actual The actual function output.
*
* @param string $addrstr The address list string.
* @param array $expected The expected function output. * @param array $expected The expected function output.
* @param string $charset Optional. The charset to use.
*/ */
public function testAddressSplittingImap($addrstr, $expected, $charset = null) protected function verifyExpectations($actual, $expected)
{ {
if (isset($charset)) { self::assertIsArray($actual, 'parseAddresses() did not return an array');
$parsed = PHPMailer::parseAddresses($addrstr, true, $charset); self::assertSame(
} else { $expected,
$parsed = PHPMailer::parseAddresses($addrstr, true); $actual,
} 'The return value from parseAddresses() did not match the expected output'
);
$expectedOutput = $expected['default'];
if (empty($expected['imap+mbstring']) === false) {
$expectedOutput = $expected['imap+mbstring'];
} elseif (empty($expected['imap']) === false) {
$expectedOutput = $expected['imap'];
}
$this->verifyExpectations($parsed, $expectedOutput);
}
/**
* Test RFC822 address splitting using the IMAP implementation
* without the Mbstring extension.
*
* @requires extension imap
*
* @dataProvider dataAddressSplitting
*
* @param string $addrstr The address list string.
* @param array $expected The expected function output.
* @param string $charset Optional. The charset to use.
*/
public function testAddressSplittingImapNoMbstring($addrstr, $expected, $charset = null)
{
if (extension_loaded('mbstring')) {
self::markTestSkipped('Test requires MbString *not* to be available');
}
if (isset($charset)) {
$parsed = PHPMailer::parseAddresses($addrstr, true, $charset);
} else {
$parsed = PHPMailer::parseAddresses($addrstr, true);
}
$expectedOutput = $expected['default'];
if (empty($expected['imap--mbstring']) === false) {
$expectedOutput = $expected['imap--mbstring'];
} elseif (empty($expected['imap']) === false) {
$expectedOutput = $expected['imap'];
}
$this->verifyExpectations($parsed, $expectedOutput);
} }
/** /**
* Test RFC822 address splitting using the native implementation * Test RFC822 address splitting using the native implementation
* *
* @dataProvider dataAddressSplittingNative * @dataProvider dataAddressSplittingNative
* @covers \PHPMailer\PHPMailer\PHPMailer::parseSimplerAddresses
* *
* @param string $addrstr The address list string. * @param string $addrstr The address list string.
* @param array $expected The expected function output. * @param array $expected The expected function output.
@ -105,12 +52,21 @@ final class ParseAddressesTest extends TestCase
*/ */
public function testAddressSplittingNative($addrstr, $expected, $charset = PHPMailer::CHARSET_ISO88591) public function testAddressSplittingNative($addrstr, $expected, $charset = PHPMailer::CHARSET_ISO88591)
{ {
error_reporting(E_ALL & ~E_USER_NOTICE); set_error_handler(static function ($errno, $errstr) {
$reflMethod = new ReflectionMethod(PHPMailer::class, 'parseSimplerAddresses'); throw new \Exception($errstr, $errno);
(\PHP_VERSION_ID < 80100) && $reflMethod->setAccessible(true); }, E_USER_NOTICE);
$parsed = $reflMethod->invoke(null, $addrstr, $charset);
(\PHP_VERSION_ID < 80100) && $reflMethod->setAccessible(false); try {
$this->verifyExpectations($parsed, $expected); $this->expectException(\Exception::class);
$reflMethod = new ReflectionMethod(PHPMailer::class, 'parseSimplerAddresses');
(\PHP_VERSION_ID < 80100) && $reflMethod->setAccessible(true);
$parsed = $reflMethod->invoke(null, $addrstr, $charset);
(\PHP_VERSION_ID < 80100) && $reflMethod->setAccessible(false);
$this->verifyExpectations($parsed, $expected);
} finally {
restore_error_handler();
}
} }
/** /**
@ -140,37 +96,79 @@ final class ParseAddressesTest extends TestCase
]; ];
} }
/** /**
* Verify the expectations. * Test if email addresses are parsed and split into a name and address.
* *
* Abstracted out as the same verification needs to be done for every test, just with different data. * @dataProvider dataParseEmailString
* * @covers \PHPMailer\PHPMailer\PHPMailer::parseEmailString
* @param string $actual The actual function output. * @param mixed $addrstr
* @param array $expected The expected function output. * @param mixed $expected
*/ */
protected function verifyExpectations($actual, $expected) public function testParseEmailString($addrstr, $expected)
{ {
self::assertIsArray($actual, 'parseAddresses() did not return an array'); $reflMethod = new ReflectionMethod(PHPMailer::class, 'parseEmailString');
self::assertSame( (\PHP_VERSION_ID < 80100) && $reflMethod->setAccessible(true);
$expected, $parsed = $reflMethod->invoke(null, $addrstr);
$actual, (\PHP_VERSION_ID < 80100) && $reflMethod->setAccessible(false);
'The return value from parseAddresses() did not match the expected output' $this->assertEquals($parsed, $expected);
); }
/**
* Data provider for testParseEmailString.
*
* @return array The array is expected to have an `addrstr` and an `expected` key.
*/
public function dataParseEmailString()
{
return [
'Valid address: simple address' => [
'addrstr' => 'Joe User <joe@example.com>',
'expected' => ['name' => 'Joe User', 'email' => 'joe@example.com'],
],
'Valid address: simple address with double quotes' => [
'addrstr' => '"Joe User" <joe@example.com>',
'expected' => ['name' => 'Joe User', 'email' => 'joe@example.com'],
],
'Valid address: simple address with single quotes' => [
'addrstr' => '\'Joe User\' <joe@example.com>',
'expected' => ['name' => 'Joe User', 'email' => 'joe@example.com'],
],
'Valid address: complex address with single quotes' => [
'addrstr' => '\'Joe<User\' <joe@example.com>',
'expected' => ['name' => 'Joe<User', 'email' => 'joe@example.com'],
],
'Valid address: complex address with triangle bracket' => [
'addrstr' => '"test<stage" <test@example.com>',
'expected' => ['name' => 'test<stage', 'email' => 'test@example.com'],
],
];
}
/**
* Test RFC822 address splitting using the PHPMailer implementation
*
* @dataProvider dataAddressSplitting
* @covers \PHPMailer\PHPMailer\PHPMailer::parseAddresses
*
* @requires extension imap
* @requires extension mbstring
*
* @param string $addrstr The address list string.
* @param array $expected The expected function output.
* @param string $charset Optional. The charset to use.
*/
public function testAddressSplitting($addrstr, $expected)
{
$parsed = PHPMailer::parseAddresses($addrstr, null, PHPMailer::CHARSET_UTF8);
$this->verifyExpectations($parsed, $expected);
} }
/** /**
* Data provider. * Data provider.
* *
* @return array The array is expected to have an `addrstr` and an `expected` key. * @return array The array is expected to have an `addrstr` and an `expected` key.
* The `expected` key should - as a minimum - have a `default` key. * The `expected` key should - as a minimum.
* Optionally, the following extra keys are supported:
* - `native` Expected output from the native implementation with or without Mbstring.
* - `native+mbstring` Expected output from the native implementation with Mbstring.
* - `native--mbstring` Expected output from the native implementation without Mbstring.
* - `imap` Expected output from the IMAP implementation with or without Mbstring.
* - `imap+mbstring` Expected output from the IMAP implementation with Mbstring.
* - `imap--mbstring` Expected output from the IMAP implementation without Mbstring.
* Also optionally, an additional `charset` key can be passed,
*/ */
public function dataAddressSplitting() public function dataAddressSplitting()
{ {
@ -179,54 +177,39 @@ final class ParseAddressesTest extends TestCase
'Valid address: single address without name' => [ 'Valid address: single address without name' => [
'addrstr' => 'joe@example.com', 'addrstr' => 'joe@example.com',
'expected' => [ 'expected' => [
'default' => [
['name' => '', 'address' => 'joe@example.com'], ['name' => '', 'address' => 'joe@example.com'],
],
], ],
], ],
'Valid address: single address with name' => [ 'Valid address: single address with name' => [
'addrstr' => 'Joe User <joe@example.com>', 'addrstr' => 'Joe User <joe@example.com>',
'expected' => [ 'expected' => [
'default' => [ ['name' => 'Joe User', 'address' => 'joe@example.com'],
['name' => 'Joe User', 'address' => 'joe@example.com'],
],
], ],
], ],
'Valid address: single RFC2047 address folded onto multiple lines' => [ 'Valid address: single RFC2047 address folded onto multiple lines' => [
'addrstr' => "=?iso-8859-1?B?QWJjZGVmZ2ggSWprbG3DsSDmnIPorbDlrqTpoJDntITn?=\r\n" . 'addrstr' => "=?ISO-8859-1?Q?J=F6rg?=\r\n" .
' =?iso-8859-1?B?s7vntbE=?= <xyz@example.com>', ' =?ISO-8859-1?Q?_M=FCller?= <xyz@example.com>',
'expected' => [ 'expected' => [
'default' => [ ['name' => 'Jörg Müller', 'address' => 'xyz@example.com'],
['name' => 'Abcdefgh Ijklmñ 會議室預約系統', 'address' => 'xyz@example.com'],
],
], ],
], ],
'Valid address: single RFC2047 address with space encoded as _' => [ 'Valid address: single RFC2047 address with space encoded as _' => [
'addrstr' => '=?iso-8859-1?Q?Abcdefgh_ijklm=C3=B1?= <xyz@example.com>', 'addrstr' => '=?iso-8859-1?Q?Abcdefgh_ijklm=F1?= <xyz@example.com>',
'expected' => [ 'expected' => [
'default' => [ ['name' => 'Abcdefgh ijklmñ', 'address' => 'xyz@example.com'],
['name' => 'Abcdefgh ijklmñ', 'address' => 'xyz@example.com'],
],
], ],
], ],
'Valid address: single address, quotes within name' => [ 'Valid address: single address, quotes within name' => [
'addrstr' => 'Tim "The Book" O\'Reilly <foo@example.com>', 'addrstr' => 'Tim "The Book" O\'Reilly <foo@example.com>',
'expected' => [ 'expected' => [
'default' => [ ['name' => 'Tim The Book O\'Reilly', 'address' => 'foo@example.com'],
['name' => 'Tim "The Book" O\'Reilly', 'address' => 'foo@example.com'],
],
'imap' => [
['name' => 'Tim The Book O\'Reilly', 'address' => 'foo@example.com'],
],
], ],
], ],
'Valid address: two addresses with names' => [ 'Valid address: two addresses with names' => [
'addrstr' => 'Joe User <joe@example.com>, Jill User <jill@example.net>', 'addrstr' => 'Joe User <joe@example.com>, Jill User <jill@example.net>',
'expected' => [ 'expected' => [
'default' => [ ['name' => 'Joe User', 'address' => 'joe@example.com'],
['name' => 'Joe User', 'address' => 'joe@example.com'], ['name' => 'Jill User', 'address' => 'jill@example.net'],
['name' => 'Jill User', 'address' => 'jill@example.net'],
],
], ],
], ],
'Valid address: two addresses with names, one without' => [ 'Valid address: two addresses with names, one without' => [
@ -234,106 +217,104 @@ final class ParseAddressesTest extends TestCase
. 'Jill User <jill@example.net>,' . 'Jill User <jill@example.net>,'
. 'frank@example.com,', . 'frank@example.com,',
'expected' => [ 'expected' => [
'default' => [ ['name' => 'Joe User', 'address' => 'joe@example.com'],
['name' => 'Joe User', 'address' => 'joe@example.com'], ['name' => 'Jill User', 'address' => 'jill@example.net'],
['name' => 'Jill User', 'address' => 'jill@example.net'], ['name' => '', 'address' => 'frank@example.com'],
['name' => '', 'address' => 'frank@example.com'],
],
], ],
], ],
'Valid address: multiple address, various formats, including one utf8-encoded name' => [ 'Valid address: multiple address, various formats, including one utf8-encoded names' => [
'addrstr' => 'joe@example.com, <me@example.com>, Joe Doe <doe@example.com>,' . 'addrstr' => 'joe@example.com, <me@example.com>, Joe Doe <doe@example.com>,' .
' "John O\'Groats" <johnog@example.net>,' . ' "John O\'Groats" <johnog@example.net>,' .
' =?utf-8?B?0J3QsNC30LLQsNC90LjQtSDRgtC10YHRgtCw?= <encoded@example.org>', ' =?utf-8?B?0J3QsNC30LLQsNC90LjQtSDRgtC10YHRgtCw?= <encoded@example.org>,' .
' =?UTF-8?Q?Welcome_to_our_caf=C3=A9!?= =?ISO-8859-1?Q?_Willkommen_in_unserem_Caf=E9!?=' .
' =?KOI8-R?Q?_=F0=D2=C9=D7=C5=D4_=D7_=CE=C1=DB=C5_=CB=C1=C6=C5!?= <encoded3@example.org>',
'expected' => [ 'expected' => [
'default' => [ ['name' => '', 'address' => 'joe@example.com'],
[ ['name' => '', 'address' => 'me@example.com'],
'name' => '', ['name' => 'Joe Doe', 'address' => 'doe@example.com'],
'address' => 'joe@example.com', ['name' => "John O'Groats", 'address' => 'johnog@example.net'],
], ['name' => 'Название теста', 'address' => 'encoded@example.org'],
[ [
'name' => '', 'name' => 'Welcome to our café! Willkommen in unserem Café! Привет в наше кафе!',
'address' => 'me@example.com', 'address' => 'encoded3@example.org'
],
[
'name' => 'Joe Doe',
'address' => 'doe@example.com',
],
[
'name' => "John O'Groats",
'address' => 'johnog@example.net',
],
[
'name' => 'Название теста',
'address' => 'encoded@example.org',
],
], ],
'native--mbstring' => [ ]
[
'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' => '=?utf-8?B?0J3QsNC30LLQsNC90LjQtSDRgtC10YHRgtCw?=',
'address' => 'encoded@example.org',
],
],
'imap--mbstring' => [
[
'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' => '=?utf-8?B?0J3QsNC30LLQsNC90LjQtSDRgtC10YHRgtCw?=',
'address' => 'encoded@example.org',
],
],
],
'charset' => PHPMailer::CHARSET_UTF8,
], ],
// Test cases with invalid addresses. // Test cases with invalid addresses.
'Invalid address: single address, incomplete email' => [ 'Invalid address: single address, incomplete email' => [
'addrstr' => 'Jill User <doug@>', 'addrstr' => 'Jill User <doug@>',
'expected' => [ 'expected' => [],
'default' => [],
],
], ],
'Invalid address: single address, invalid characters in email' => [ 'Invalid address: single address, invalid characters in email' => [
'addrstr' => 'Joe User <{^c\@**Dog^}@cartoon.com>', 'addrstr' => 'Joe User <{^c\@**Dog^}@cartoon.com>',
'expected' => [ 'expected' => [],
'default' => [],
],
], ],
'Invalid address: multiple addresses, invalid periods' => [ 'Invalid address: multiple addresses, invalid periods' => [
'addrstr' => 'Joe User <joe@example.com.>, Jill User <jill.@example.net>', 'addrstr' => 'Joe User <joe@example.com.>, Jill User <jill.@example.net>',
'expected' => [ 'expected' => [],
'default' => [], ],
], ];
}
/**
* Test decodeHeader using the PHPMailer
* with the Mbstring extension available.
*
* @dataProvider dataDecodeHeader
* @covers \PHPMailer\PHPMailer\PHPMailer::decodeHeader
*
* @requires extension mbstring
*
* @param string $addrstr The header string.
* @param array $expected The expected function output.
*/
public function testDecodeHeader($str, $expected)
{
$parsed = PHPMailer::decodeHeader($str, PHPMailer::CHARSET_UTF8);
$this->assertEquals($parsed, $expected);
}
/**
* Data provider for decodeHeader.
*
* @return array The array is expected to have an `addrstr` and an `expected` key.
* The `expected` key should - as a minimum - have a single value.
*/
public function dataDecodeHeader()
{
return [
'UTF-8 B-encoded' => [
'name' => '=?utf-8?B?0J3QsNC30LLQsNC90LjQtSDRgtC10YHRgtCw?=',
'expected' => 'Название теста',
],
'UTF-8 Q-encoded' => [
'name' => '=?UTF-8?Q?=D0=9D=D0=B0=D0=B7=D0=B2=D0=B0=D0=BD=D0=B8?=' .
' =?UTF-8?Q?=D0=B5_=D1=82=D0=B5=D1=81=D1=82=D0=B0?=',
'expected' => 'Название теста',
],
'UTF-8 Q-encoded with multiple wrong labels and space encoded as _' => [
'name' => '=?UTF-8?Q?Welcome_to_our_caf=C3=A9!?= =?ISO-8859-1?Q?_Willkommen_in_unserem_Caf=E9!?=' .
' =?KOI8-R?Q?_=F0=D2=C9=D7=C5=D4_=D7_=CE=C1=DB=C5_=CB=C1=C6=C5!?=',
'expected' => 'Welcome to our café! Willkommen in unserem Café! Привет в наше кафе!',
],
'ISO-8859-1 Q-encoded' => [
'name' => '=?ISO-8859-1?Q?Willkommen_in_unserem_Caf=E9!?=',
'expected' => 'Willkommen in unserem Café!',
],
'Valid but wrongly labeled UTF-8 as ISO-8859-1' => [
'name' => '=?iso-8859-1?B?5pyD6K2w5a6k?=',
'expected' => "æ\xC2\x9C\xC2\x83議室",
],
'SMTPUTF8 encoded' => [
'name' => '=?UTF-8?B?SGVsbG8g8J+MjSDkuJbnlYwgY2Fmw6k=?=',
'expected' => 'Hello 🌍 世界 café',
],
'Multiple lines' => [
'name' => '=?UTF-8?B?0YLQtdGB0YIg0YLQtdGB0YIg0YLQtdGB0YIg0YLQtdGB0YIg0YLQtdGB0YIg?='
. "\n =?UTF-8?B?0YLQtdGB0YIg0YLQtdGB0YI=?=",
'expected' => 'тест тест тест тест тест тест тест',
], ],
]; ];
} }