Adding #3235 and reorganizing tests

This commit is contained in:
SirLouen 2025-09-26 01:07:01 +02:00
parent bda1fee442
commit 95ff455f9d
No known key found for this signature in database
GPG Key ID: 87796BFBFE09911B
3 changed files with 136 additions and 65 deletions

View File

@ -1298,6 +1298,7 @@ class PHPMailer
// Emit a runtime notice to recommend using the IMAP extension for full RFC822 parsing
trigger_error(self::lang('imap_recommended'), E_USER_NOTICE);
$addresses = [];
$list = explode(',', $addrstr);
foreach ($list as $address) {
$address = trim($address);
@ -1311,21 +1312,10 @@ class PHPMailer
];
}
} else {
list($name, $email) = explode('<', $address);
$email = trim(str_replace('>', '', $email));
$name = trim($name);
$parsed = self::parseEmailString($address);
$email = $parsed['email'];
if (static::validateAddress($email)) {
//Check for a Mbstring constant rather than using extension_loaded, which is sometimes disabled
//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);
}
$name = static::decodeHeader($parsed['name'], $charset);
$addresses[] = [
//Remove any surrounding quotes and spaces from the name
'name' => trim($name, '\'" '),
@ -1338,6 +1328,42 @@ class PHPMailer
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.
*
@ -3727,14 +3753,8 @@ class PHPMailer
// Decode the header value
$value = mb_decode_mimeheader($value);
mb_internal_encoding($origCharset);
} elseif ($hasEncodedWord && function_exists('iconv_mime_decode')) {
// Use iconv as a fallback when mbstring is not available
$mode = defined('ICONV_MIME_DECODE_CONTINUE_ON_ERROR') ? ICONV_MIME_DECODE_CONTINUE_ON_ERROR : 0;
$decoded = @iconv_mime_decode($value, $mode, $charset);
if ($decoded !== false) {
$value = $decoded;
}
}
return $value;
}

View File

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

View File

@ -19,52 +19,32 @@ use Yoast\PHPUnitPolyfills\TestCases\TestCase;
/**
* 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
{
/**
* Test RFC822 address splitting using the PHPMailer native implementation
* Verify the expectations.
*
* @dataProvider dataAddressSplitting
* Abstracted out as the same verification needs to be done for every test, just with different data.
*
* @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);
}
/**
* Test decodeHeader using the PHPMailer
* with the Mbstring extension available.
*
* @dataProvider dataDecodeHeader
*
* @param string $addrstr The header string.
* @param string $actual The actual function output.
* @param array $expected The expected function output.
*/
public function testDecodeHeader($str, $expected)
protected function verifyExpectations($actual, $expected)
{
$parsed = PHPMailer::decodeHeader($str, PHPMailer::CHARSET_UTF8);
$this->assertEquals($parsed, $expected);
self::assertIsArray($actual, 'parseAddresses() did not return an array');
self::assertSame(
$expected,
$actual,
'The return value from parseAddresses() did not match the expected output'
);
}
/**
* Test RFC822 address splitting using the native implementation
*
* @dataProvider dataAddressSplittingNative
* @covers \PHPMailer\PHPMailer\PHPMailer::parseSimplerAddresses
*
* @param string $addrstr The address list string.
* @param array $expected The expected function output.
@ -72,6 +52,7 @@ final class ParseAddressesTest extends TestCase
*/
public function testAddressSplittingNative($addrstr, $expected, $charset = PHPMailer::CHARSET_ISO88591)
{
xdebug_break();
error_reporting(E_ALL & ~E_USER_NOTICE);
$reflMethod = new ReflectionMethod(PHPMailer::class, 'parseSimplerAddresses');
(\PHP_VERSION_ID < 80100) && $reflMethod->setAccessible(true);
@ -107,22 +88,71 @@ 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.
*
* @param string $actual The actual function output.
* @param array $expected The expected function output.
* @dataProvider dataParseEmailString
* @covers \PHPMailer\PHPMailer\PHPMailer::parseEmailString
* @param mixed $addrstr
* @param mixed $expected
*/
protected function verifyExpectations($actual, $expected)
public function testParseEmailString($addrstr, $expected)
{
self::assertIsArray($actual, 'parseAddresses() did not return an array');
self::assertSame(
$expected,
$actual,
'The return value from parseAddresses() did not match the expected output'
);
$reflMethod = new ReflectionMethod(PHPMailer::class, 'parseEmailString');
(\PHP_VERSION_ID < 80100) && $reflMethod->setAccessible(true);
$parsed = $reflMethod->invoke(null, $addrstr);
(\PHP_VERSION_ID < 80100) && $reflMethod->setAccessible(false);
$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 native implementation
*
* @dataProvider dataAddressSplitting
* @covers \PHPMailer\PHPMailer\PHPMailer::parseAddresses
*
* @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);
}
/**
@ -218,6 +248,25 @@ final class ParseAddressesTest extends TestCase
];
}
/**
* 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.
*