Merge branch 'master' into patch/3199

This commit is contained in:
Marcus Bointon 2025-09-24 18:35:25 +01:00 committed by GitHub
commit a49806c893
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
22 changed files with 456 additions and 228 deletions

View File

@ -12,3 +12,9 @@ updates:
open-pull-requests-limit: 5
commit-message:
prefix: "GH Actions:"
groups:
action-runners:
applies-to: version-updates
update-types:
- "minor"
- "patch"

View File

@ -14,13 +14,14 @@ jobs:
if: github.repository == 'PHPMailer/PHPMailer'
steps:
- name: Checkout sources
uses: actions/checkout@v4
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
fetch-depth: 1
persist-credentials: false
- name: Build Docs
uses: ./.github/actions/build-docs
- name: Publish Docs to gh-pages
uses: JamesIves/github-pages-deploy-action@v4
uses: JamesIves/github-pages-deploy-action@6c2d9db40f9296374acc17b90404b6e8864128c8 # v4.7.3
with:
branch: gh-pages
folder: docs

View File

@ -7,8 +7,7 @@ on:
push:
branches: [ "master" ]
# Declare default permissions as read only.
permissions: read-all
permissions: {}
jobs:
analysis:
@ -17,23 +16,20 @@ jobs:
name: Scorecards analysis
runs-on: ubuntu-latest
permissions:
# Needed to upload the results to code-scanning dashboard.
# Required when publishing results (badge / API / code scanning)
security-events: write
# Used to receive a badge. (Upcoming feature)
id-token: write
# Needs for private repositories.
contents: read
actions: read
steps:
- name: "Checkout code"
uses: actions/checkout@v4
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
persist-credentials: false
- name: "Run analysis"
uses: ossf/scorecard-action@62b2cac7ed8198b15735ed49ab1e5cf35480ba46
uses: ossf/scorecard-action@05b42c624433fc40578a4040d5cf5e36ddca8cde # v2.4.2
with:
results_file: results.sarif
results_format: sarif
@ -52,7 +48,7 @@ jobs:
# Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF
# format to the repository Actions tab.
- name: "Upload artifact"
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: SARIF file
path: results.sarif
@ -60,6 +56,6 @@ jobs:
# Upload the results to GitHub's code scanning dashboard.
- name: "Upload to code-scanning"
uses: github/codeql-action/upload-sarif@v3
uses: github/codeql-action/upload-sarif@192325c86100d080feab897ff886c34abd4c83a3 # v3.30.3
with:
sarif_file: results.sarif

View File

@ -6,8 +6,7 @@ on:
# Allow manually triggering the workflow.
workflow_dispatch:
permissions:
contents: read # to fetch code (actions/checkout)
permissions: {}
jobs:
@ -15,12 +14,17 @@ jobs:
runs-on: ubuntu-22.04
name: Coding standards
permissions:
contents: read # to fetch code (actions/checkout)
steps:
- name: Check out code
uses: actions/checkout@v4
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
persist-credentials: false
- name: Set up PHP
uses: shivammathur/setup-php@v2
uses: shivammathur/setup-php@bf6b4fbd49ca58e4608c9c89fba0b8d90bd2a39f # 2.35.5
with:
php-version: 'latest'
coverage: none
@ -29,7 +33,7 @@ jobs:
# Install dependencies and handle caching in one go.
# @link https://github.com/marketplace/actions/install-php-dependencies-with-composer
- name: Install Composer dependencies
uses: "ramsey/composer-install@v3"
uses: "ramsey/composer-install@3cf229dc2919194e9e36783941438d17239e8520" # 3.1.1
with:
# Bust the cache at least once a month - output format: YYYY-MM.
custom-cache-suffix: $(date -u "+%Y-%m")
@ -55,12 +59,17 @@ jobs:
name: "Lint: PHP ${{ matrix.php }}"
continue-on-error: ${{ matrix.experimental }}
permissions:
contents: read # to fetch code (actions/checkout)
steps:
- name: Checkout code
uses: actions/checkout@v4
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
persist-credentials: false
- name: Install PHP
uses: shivammathur/setup-php@v2
uses: shivammathur/setup-php@bf6b4fbd49ca58e4608c9c89fba0b8d90bd2a39f # 2.35.5
with:
php-version: ${{ matrix.php }}
ini-values: error_reporting=-1, display_errors=On, display_startup_errors=On
@ -70,7 +79,7 @@ jobs:
# Install dependencies and handle caching in one go.
# @link https://github.com/marketplace/actions/install-php-dependencies-with-composer
- name: Install Composer dependencies
uses: "ramsey/composer-install@v3"
uses: "ramsey/composer-install@3cf229dc2919194e9e36783941438d17239e8520" # 3.1.1
with:
# Bust the cache at least once a month - output format: YYYY-MM.
custom-cache-suffix: $(date -u "+%Y-%m")
@ -125,9 +134,14 @@ jobs:
continue-on-error: ${{ matrix.experimental }}
permissions:
contents: read # to fetch code (actions/checkout)
steps:
- name: Check out code
uses: actions/checkout@v4
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
persist-credentials: false
# About the "extensions":
#
@ -157,7 +171,7 @@ jobs:
fi
- name: Set up PHP
uses: shivammathur/setup-php@v2
uses: shivammathur/setup-php@bf6b4fbd49ca58e4608c9c89fba0b8d90bd2a39f # 2.35.5
with:
php-version: ${{ matrix.php }}
coverage: ${{ matrix.coverage && 'xdebug' || 'none' }}
@ -168,7 +182,7 @@ jobs:
# @link https://github.com/marketplace/actions/install-php-dependencies-with-composer
- name: Install PHP packages - normal
if: ${{ matrix.php != '8.5' }}
uses: "ramsey/composer-install@v3"
uses: "ramsey/composer-install@3cf229dc2919194e9e36783941438d17239e8520" # 3.1.1
with:
composer-options: ${{ steps.set_extensions.outputs.COMPOSER_OPTIONS }}
# Bust the cache at least once a month - output format: YYYY-MM.
@ -176,7 +190,7 @@ jobs:
- name: Install PHP packages - ignore-platform-reqs
if: ${{ matrix.php == '8.5' }}
uses: "ramsey/composer-install@v3"
uses: "ramsey/composer-install@3cf229dc2919194e9e36783941438d17239e8520" # 3.1.1
with:
composer-options: --ignore-platform-reqs ${{ steps.set_extensions.outputs.COMPOSER_OPTIONS }}
# Bust the cache at least once a month - output format: YYYY-MM.
@ -185,7 +199,7 @@ jobs:
# Install postfix and automatically retry if the install failed, which happens reguarly.
# @link https://github.com/marketplace/actions/retry-step
- name: Install postfix
uses: nick-invision/retry@v3
uses: nick-invision/retry@ce71cc2ab81d554ebbe88c79ab5975992d79ba08 # v3.0.2
with:
timeout_minutes: 2
max_attempts: 3
@ -214,7 +228,7 @@ jobs:
- name: Send coverage report to Codecov
if: ${{ success() && matrix.coverage == true }}
uses: codecov/codecov-action@v4
uses: codecov/codecov-action@5a1091511ad55cbe89839c7260b706298ca349f7 # v5.5.1
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
with:

View File

@ -4,6 +4,12 @@
* Add support for [RFC4954](https://www.rfc-editor.org/rfc/rfc4954#section-4) two-part authentication for large XOAUTH2 tokens.
* Also support empty tokens.
* Avoid bogus static analyser deprecation warnings in `setFrom`.
* Make language loading entirely static, thanks to @SirLouen.
* Emit warnings when `parseAddresses()` is used without IMAP extension.
* Fix PHP 8.5 linting issue.
* Don't use `-t` switch when calling qmail.
* Checking for interrupted system calls now works in languages other than English.
* Add support for extracting gmail transaction IDs after sending.
## Version 6.10.0 (April 24th, 2025)
* Add support for [RFC 6530 SMTPUTF8](https://www.rfc-editor.org/rfc/rfc6530), permitting use of UTF-8 Unicode characters everywhere, thanks to @arnt and ICANN. See `SMTPUTF8.md` for details.

View File

@ -56,7 +56,8 @@
"league/oauth2-google": "Needed for Google XOAUTH2 authentication",
"psr/log": "For optional PSR-3 debug logging",
"thenetworg/oauth2-azure": "Needed for Microsoft XOAUTH2 authentication",
"symfony/polyfill-mbstring": "To support UTF-8 if the Mbstring PHP extension is not enabled (^1.2)"
"symfony/polyfill-mbstring": "To support UTF-8 if the Mbstring PHP extension is not enabled (^1.2)",
"ext-imap": "Needed to support advanced email address parsing according to RFC822"
},
"autoload": {
"psr-4": {

View File

@ -9,7 +9,7 @@
*/
$PHPMAILER_LANG['authenticate'] = 'Error SMTP: Imposible autentificar.';
$PHPMAILER_LANG['buggy_php'] = 'Tu versión de PHP está afectada por un bug que puede resultar en mensajes corruptos. Para arreglarlo, cambia a enviar usando SMTP, deshabilita la opción mail.add_x_header en tu php.ini, cambia a MacOS o Linux, o actualiza tu PHP a la versión 7.0.17+ o 7.1.3+.';
$PHPMAILER_LANG['buggy_php'] = 'Tu versión de PHP ha sido afectada por un bug que puede resultar en mensajes corruptos. Para arreglarlo, cambia a enviar usando SMTP, deshabilita la opción mail.add_x_header en tu php.ini, cambia a MacOS o Linux, o actualiza tu PHP a la versión 7.0.17+ o 7.1.3+.';
$PHPMAILER_LANG['connect_host'] = 'Error SMTP: Imposible conectar al servidor SMTP.';
$PHPMAILER_LANG['data_not_accepted'] = 'Error SMTP: Datos no aceptados.';
$PHPMAILER_LANG['empty_message'] = 'El cuerpo del mensaje está vacío.';
@ -18,7 +18,7 @@ $PHPMAILER_LANG['execute'] = 'Imposible ejecutar: ';
$PHPMAILER_LANG['extension_missing'] = 'Extensión faltante: ';
$PHPMAILER_LANG['file_access'] = 'Imposible acceder al archivo: ';
$PHPMAILER_LANG['file_open'] = 'Error de Archivo: Imposible abrir el archivo: ';
$PHPMAILER_LANG['from_failed'] = 'La(s) siguiente(s) direcciones de remitente fallaron: ';
$PHPMAILER_LANG['from_failed'] = 'La siguiente dirección de remitente falló: ';
$PHPMAILER_LANG['instantiate'] = 'Imposible crear una instancia de la función Mail.';
$PHPMAILER_LANG['invalid_address'] = 'Imposible enviar: dirección de email inválido: ';
$PHPMAILER_LANG['invalid_header'] = 'Nombre o valor de encabezado no válido';
@ -34,3 +34,4 @@ $PHPMAILER_LANG['smtp_connect_failed'] = 'SMTP Connect() falló.';
$PHPMAILER_LANG['smtp_detail'] = 'Detalle: ';
$PHPMAILER_LANG['smtp_error'] = 'Error del servidor SMTP: ';
$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.';

View File

@ -36,7 +36,24 @@
<exclude name="PHPCompatibility.ParameterValues.NewIDNVariantDefault.NotSet"/>
</rule>
<!--
#############################################################################
SELECTIVE EXCLUSIONS
Exclude specific files for specific sniffs and/or exclude sub-groups in sniffs.
#############################################################################
-->
<rule ref="Generic.Files.LineLength.TooLong">
<exclude-pattern>*/language/phpmailer\.lang*\.php$</exclude-pattern>
</rule>
<!-- Excludes related to linting ignore comment for one specific test file. -->
<rule ref="PSR12.Files.OpenTag.NotAlone">
<exclude-pattern>*/test/Fixtures/LocalizationTest/phpmailer.lang-yz\.php</exclude-pattern>
</rule>
<rule ref="PSR12.Files.FileHeader.SpacingAfterBlock">
<exclude-pattern>*/test/Fixtures/LocalizationTest/phpmailer.lang-yz\.php</exclude-pattern>
</rule>
</ruleset>

View File

@ -711,7 +711,7 @@ class PHPMailer
*
* @var array
*/
protected $language = [];
protected static $language = [];
/**
* The number of errors encountered.
@ -1102,7 +1102,7 @@ class PHPMailer
//At-sign is missing.
$error_message = sprintf(
'%s (%s): %s',
$this->lang('invalid_address'),
self::lang('invalid_address'),
$kind,
$address
);
@ -1187,7 +1187,7 @@ class PHPMailer
if (!in_array($kind, ['to', 'cc', 'bcc', 'Reply-To'])) {
$error_message = sprintf(
'%s: %s',
$this->lang('Invalid recipient kind'),
self::lang('Invalid recipient kind'),
$kind
);
$this->setError($error_message);
@ -1201,7 +1201,7 @@ class PHPMailer
if (!static::validateAddress($address)) {
$error_message = sprintf(
'%s (%s): %s',
$this->lang('invalid_address'),
self::lang('invalid_address'),
$kind,
$address
);
@ -1280,40 +1280,61 @@ class PHPMailer
}
} else {
//Use this simpler parser
$list = explode(',', $addrstr);
foreach ($list as $address) {
$address = trim($address);
//Is there a separate name part?
if (strpos($address, '<') === false) {
//No separate name, just use the whole thing
if (static::validateAddress($address)) {
$addresses[] = [
'name' => '',
'address' => $address,
];
}
} else {
list($name, $email) = explode('<', $address);
$email = trim(str_replace('>', '', $email));
$name = trim($name);
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);
}
$addresses[] = [
//Remove any surrounding quotes and spaces from the name
'name' => trim($name, '\'" '),
'address' => $email,
];
$addresses = self::parseSimplerAddresses($addrstr, $charset);
}
return $addresses;
}
/**
* Parse a string containing one or more RFC822-style comma-separated email addresses
* with the form "display name <address>" into an array of name/address pairs.
* Uses a simpler parser that does not require the IMAP extension but doesnt support
* the full RFC822 spec. For full RFC822 support, use the PHP IMAP extension.
*
* @param string $addrstr The address list string
* @param string $charset The charset to use when decoding the address list string.
*
* @return array
*/
protected static function parseSimplerAddresses($addrstr, $charset)
{
// Emit a runtime notice to recommend using the IMAP extension for full RFC822 parsing
trigger_error(self::lang('imap_recommended'), E_USER_NOTICE);
$list = explode(',', $addrstr);
foreach ($list as $address) {
$address = trim($address);
//Is there a separate name part?
if (strpos($address, '<') === false) {
//No separate name, just use the whole thing
if (static::validateAddress($address)) {
$addresses[] = [
'name' => '',
'address' => $address,
];
}
} else {
list($name, $email) = explode('<', $address);
$email = trim(str_replace('>', '', $email));
$name = trim($name);
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);
}
$addresses[] = [
//Remove any surrounding quotes and spaces from the name
'name' => trim($name, '\'" '),
'address' => $email,
];
}
}
}
@ -1349,7 +1370,7 @@ class PHPMailer
) {
$error_message = sprintf(
'%s (From): %s',
$this->lang('invalid_address'),
self::lang('invalid_address'),
$address
);
$this->setError($error_message);
@ -1605,7 +1626,7 @@ class PHPMailer
&& ini_get('mail.add_x_header') === '1'
&& stripos(PHP_OS, 'WIN') === 0
) {
trigger_error($this->lang('buggy_php'), E_USER_WARNING);
trigger_error(self::lang('buggy_php'), E_USER_WARNING);
}
try {
@ -1635,7 +1656,7 @@ class PHPMailer
call_user_func_array([$this, 'addAnAddress'], $params);
}
if (count($this->to) + count($this->cc) + count($this->bcc) < 1) {
throw new Exception($this->lang('provide_address'), self::STOP_CRITICAL);
throw new Exception(self::lang('provide_address'), self::STOP_CRITICAL);
}
//Validate From, Sender, and ConfirmReadingTo addresses
@ -1652,7 +1673,7 @@ class PHPMailer
if (!static::validateAddress($this->{$address_kind})) {
$error_message = sprintf(
'%s (%s): %s',
$this->lang('invalid_address'),
self::lang('invalid_address'),
$address_kind,
$this->{$address_kind}
);
@ -1674,7 +1695,7 @@ class PHPMailer
$this->setMessageType();
//Refuse to send an empty message unless we are specifically allowing it
if (!$this->AllowEmpty && empty($this->Body)) {
throw new Exception($this->lang('empty_message'), self::STOP_CRITICAL);
throw new Exception(self::lang('empty_message'), self::STOP_CRITICAL);
}
//Trim subject consistently
@ -1834,7 +1855,7 @@ class PHPMailer
foreach ($this->SingleToArray as $toAddr) {
$mail = @popen($sendmail, 'w');
if (!$mail) {
throw new Exception($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL);
throw new Exception(self::lang('execute') . $this->Sendmail, self::STOP_CRITICAL);
}
$this->edebug("To: {$toAddr}");
fwrite($mail, 'To: ' . $toAddr . "\n");
@ -1842,25 +1863,27 @@ class PHPMailer
fwrite($mail, $body);
$result = pclose($mail);
$addrinfo = static::parseAddresses($toAddr, true, $this->CharSet);
$this->doCallback(
($result === 0),
[[$addrinfo['address'], $addrinfo['name']]],
$this->cc,
$this->bcc,
$this->Subject,
$body,
$this->From,
[]
);
foreach ($addrinfo as $addr) {
$this->doCallback(
($result === 0),
[[$addr['address'], $addr['name']]],
$this->cc,
$this->bcc,
$this->Subject,
$body,
$this->From,
[]
);
}
$this->edebug("Result: " . ($result === 0 ? 'true' : 'false'));
if (0 !== $result) {
throw new Exception($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL);
throw new Exception(self::lang('execute') . $this->Sendmail, self::STOP_CRITICAL);
}
}
} else {
$mail = @popen($sendmail, 'w');
if (!$mail) {
throw new Exception($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL);
throw new Exception(self::lang('execute') . $this->Sendmail, self::STOP_CRITICAL);
}
fwrite($mail, $header);
fwrite($mail, $body);
@ -1877,7 +1900,7 @@ class PHPMailer
);
$this->edebug("Result: " . ($result === 0 ? 'true' : 'false'));
if (0 !== $result) {
throw new Exception($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL);
throw new Exception(self::lang('execute') . $this->Sendmail, self::STOP_CRITICAL);
}
}
@ -2017,16 +2040,18 @@ class PHPMailer
foreach ($toArr as $toAddr) {
$result = $this->mailPassthru($toAddr, $this->Subject, $body, $header, $params);
$addrinfo = static::parseAddresses($toAddr, true, $this->CharSet);
$this->doCallback(
$result,
[[$addrinfo['address'], $addrinfo['name']]],
$this->cc,
$this->bcc,
$this->Subject,
$body,
$this->From,
[]
);
foreach ($addrinfo as $addr) {
$this->doCallback(
$result,
[[$addr['address'], $addr['name']]],
$this->cc,
$this->bcc,
$this->Subject,
$body,
$this->From,
[]
);
}
}
} else {
$result = $this->mailPassthru($to, $this->Subject, $body, $header, $params);
@ -2036,7 +2061,7 @@ class PHPMailer
ini_set('sendmail_from', $old_from);
}
if (!$result) {
throw new Exception($this->lang('instantiate'), self::STOP_CRITICAL);
throw new Exception(self::lang('instantiate'), self::STOP_CRITICAL);
}
return true;
@ -2122,12 +2147,12 @@ class PHPMailer
$header = static::stripTrailingWSP($header) . static::$LE . static::$LE;
$bad_rcpt = [];
if (!$this->smtpConnect($this->SMTPOptions)) {
throw new Exception($this->lang('smtp_connect_failed'), self::STOP_CRITICAL);
throw new Exception(self::lang('smtp_connect_failed'), self::STOP_CRITICAL);
}
//If we have recipient addresses that need Unicode support,
//but the server doesn't support it, stop here
if ($this->UseSMTPUTF8 && !$this->smtp->getServerExt('SMTPUTF8')) {
throw new Exception($this->lang('no_smtputf8'), self::STOP_CRITICAL);
throw new Exception(self::lang('no_smtputf8'), self::STOP_CRITICAL);
}
//Sender already validated in preSend()
if ('' === $this->Sender) {
@ -2139,7 +2164,7 @@ class PHPMailer
$this->smtp->xclient($this->SMTPXClient);
}
if (!$this->smtp->mail($smtp_from)) {
$this->setError($this->lang('from_failed') . $smtp_from . ' : ' . implode(',', $this->smtp->getError()));
$this->setError(self::lang('from_failed') . $smtp_from . ' : ' . implode(',', $this->smtp->getError()));
throw new Exception($this->ErrorInfo, self::STOP_CRITICAL);
}
@ -2161,7 +2186,7 @@ class PHPMailer
//Only send the DATA command if we have viable recipients
if ((count($this->all_recipients) > count($bad_rcpt)) && !$this->smtp->data($header . $body)) {
throw new Exception($this->lang('data_not_accepted'), self::STOP_CRITICAL);
throw new Exception(self::lang('data_not_accepted'), self::STOP_CRITICAL);
}
$smtp_transaction_id = $this->smtp->getLastTransactionID();
@ -2192,7 +2217,7 @@ class PHPMailer
foreach ($bad_rcpt as $bad) {
$errstr .= $bad['to'] . ': ' . $bad['error'];
}
throw new Exception($this->lang('recipients_failed') . $errstr, self::STOP_CONTINUE);
throw new Exception(self::lang('recipients_failed') . $errstr, self::STOP_CONTINUE);
}
return true;
@ -2246,7 +2271,7 @@ class PHPMailer
$hostinfo
)
) {
$this->edebug($this->lang('invalid_hostentry') . ' ' . trim($hostentry));
$this->edebug(self::lang('invalid_hostentry') . ' ' . trim($hostentry));
//Not a valid host entry
continue;
}
@ -2258,7 +2283,7 @@ class PHPMailer
//Check the host name is a valid name or IP address before trying to use it
if (!static::isValidHost($hostinfo[2])) {
$this->edebug($this->lang('invalid_host') . ' ' . $hostinfo[2]);
$this->edebug(self::lang('invalid_host') . ' ' . $hostinfo[2]);
continue;
}
$prefix = '';
@ -2278,7 +2303,7 @@ class PHPMailer
if (static::ENCRYPTION_STARTTLS === $secure || static::ENCRYPTION_SMTPS === $secure) {
//Check for an OpenSSL constant rather than using extension_loaded, which is sometimes disabled
if (!$sslext) {
throw new Exception($this->lang('extension_missing') . 'openssl', self::STOP_CRITICAL);
throw new Exception(self::lang('extension_missing') . 'openssl', self::STOP_CRITICAL);
}
}
$host = $hostinfo[2];
@ -2330,7 +2355,7 @@ class PHPMailer
$this->oauth
)
) {
throw new Exception($this->lang('authenticate'));
throw new Exception(self::lang('authenticate'));
}
return true;
@ -2380,7 +2405,7 @@ class PHPMailer
*
* @return bool Returns true if the requested language was loaded, false otherwise.
*/
public function setLanguage($langcode = 'en', $lang_path = '')
public static function setLanguage($langcode = 'en', $lang_path = '')
{
//Backwards compatibility for renamed language codes
$renamed_langcodes = [
@ -2429,6 +2454,8 @@ class PHPMailer
'smtp_error' => 'SMTP server error: ',
'variable_set' => 'Cannot set or reset variable: ',
'no_smtputf8' => 'Server does not support SMTPUTF8 needed to send to Unicode addresses',
'imap_recommended' => 'Using simplified address parser is not recommended. ' .
'Install the PHP IMAP extension for full RFC822 parsing.',
];
if (empty($lang_path)) {
//Calculate an absolute path so it can work if CWD is not here
@ -2495,7 +2522,7 @@ class PHPMailer
}
}
}
$this->language = $PHPMAILER_LANG;
self::$language = $PHPMAILER_LANG;
return $foundlang; //Returns false if language not found
}
@ -2507,11 +2534,11 @@ class PHPMailer
*/
public function getTranslations()
{
if (empty($this->language)) {
$this->setLanguage(); // Set the default language.
if (empty(self::$language)) {
self::setLanguage(); // Set the default language.
}
return $this->language;
return self::$language;
}
/**
@ -2934,10 +2961,6 @@ class PHPMailer
//Create unique IDs and preset boundaries
$this->setBoundaries();
if ($this->sign_key_file) {
$body .= $this->getMailMIME() . static::$LE;
}
$this->setWordWrap();
$bodyEncoding = $this->Encoding;
@ -2969,6 +2992,12 @@ class PHPMailer
if (static::ENCODING_BASE64 !== $altBodyEncoding && static::hasLineLongerThanMax($this->AltBody)) {
$altBodyEncoding = static::ENCODING_QUOTED_PRINTABLE;
}
if ($this->sign_key_file) {
$this->Encoding = $bodyEncoding;
$body .= $this->getMailMIME() . static::$LE;
}
//Use this as a preamble in all multipart message types
$mimepre = '';
switch ($this->message_type) {
@ -3150,12 +3179,12 @@ class PHPMailer
if ($this->isError()) {
$body = '';
if ($this->exceptions) {
throw new Exception($this->lang('empty_message'), self::STOP_CRITICAL);
throw new Exception(self::lang('empty_message'), self::STOP_CRITICAL);
}
} elseif ($this->sign_key_file) {
try {
if (!defined('PKCS7_TEXT')) {
throw new Exception($this->lang('extension_missing') . 'openssl');
throw new Exception(self::lang('extension_missing') . 'openssl');
}
$file = tempnam(sys_get_temp_dir(), 'srcsign');
@ -3193,7 +3222,7 @@ class PHPMailer
$body = $parts[1];
} else {
@unlink($signed);
throw new Exception($this->lang('signing') . openssl_error_string());
throw new Exception(self::lang('signing') . openssl_error_string());
}
} catch (Exception $exc) {
$body = '';
@ -3338,7 +3367,7 @@ class PHPMailer
) {
try {
if (!static::fileIsAccessible($path)) {
throw new Exception($this->lang('file_access') . $path, self::STOP_CONTINUE);
throw new Exception(self::lang('file_access') . $path, self::STOP_CONTINUE);
}
//If a MIME type is not specified, try to work it out from the file name
@ -3351,7 +3380,7 @@ class PHPMailer
$name = $filename;
}
if (!$this->validateEncoding($encoding)) {
throw new Exception($this->lang('encoding') . $encoding);
throw new Exception(self::lang('encoding') . $encoding);
}
$this->attachment[] = [
@ -3512,11 +3541,11 @@ class PHPMailer
{
try {
if (!static::fileIsAccessible($path)) {
throw new Exception($this->lang('file_open') . $path, self::STOP_CONTINUE);
throw new Exception(self::lang('file_open') . $path, self::STOP_CONTINUE);
}
$file_buffer = file_get_contents($path);
if (false === $file_buffer) {
throw new Exception($this->lang('file_open') . $path, self::STOP_CONTINUE);
throw new Exception(self::lang('file_open') . $path, self::STOP_CONTINUE);
}
$file_buffer = $this->encodeString($file_buffer, $encoding);
@ -3569,9 +3598,9 @@ class PHPMailer
$encoded = $this->encodeQP($str);
break;
default:
$this->setError($this->lang('encoding') . $encoding);
$this->setError(self::lang('encoding') . $encoding);
if ($this->exceptions) {
throw new Exception($this->lang('encoding') . $encoding);
throw new Exception(self::lang('encoding') . $encoding);
}
break;
}
@ -3846,7 +3875,7 @@ class PHPMailer
}
if (!$this->validateEncoding($encoding)) {
throw new Exception($this->lang('encoding') . $encoding);
throw new Exception(self::lang('encoding') . $encoding);
}
//Append to $attachment array
@ -3905,7 +3934,7 @@ class PHPMailer
) {
try {
if (!static::fileIsAccessible($path)) {
throw new Exception($this->lang('file_access') . $path, self::STOP_CONTINUE);
throw new Exception(self::lang('file_access') . $path, self::STOP_CONTINUE);
}
//If a MIME type is not specified, try to work it out from the file name
@ -3914,7 +3943,7 @@ class PHPMailer
}
if (!$this->validateEncoding($encoding)) {
throw new Exception($this->lang('encoding') . $encoding);
throw new Exception(self::lang('encoding') . $encoding);
}
$filename = (string) static::mb_pathinfo($path, PATHINFO_BASENAME);
@ -3980,7 +4009,7 @@ class PHPMailer
}
if (!$this->validateEncoding($encoding)) {
throw new Exception($this->lang('encoding') . $encoding);
throw new Exception(self::lang('encoding') . $encoding);
}
//Append to $attachment array
@ -4237,7 +4266,7 @@ class PHPMailer
}
if (strpbrk($name . $value, "\r\n") !== false) {
if ($this->exceptions) {
throw new Exception($this->lang('invalid_header'));
throw new Exception(self::lang('invalid_header'));
}
return false;
@ -4261,15 +4290,15 @@ class PHPMailer
if ('smtp' === $this->Mailer && null !== $this->smtp) {
$lasterror = $this->smtp->getError();
if (!empty($lasterror['error'])) {
$msg .= ' ' . $this->lang('smtp_error') . $lasterror['error'];
$msg .= ' ' . self::lang('smtp_error') . $lasterror['error'];
if (!empty($lasterror['detail'])) {
$msg .= ' ' . $this->lang('smtp_detail') . $lasterror['detail'];
$msg .= ' ' . self::lang('smtp_detail') . $lasterror['detail'];
}
if (!empty($lasterror['smtp_code'])) {
$msg .= ' ' . $this->lang('smtp_code') . $lasterror['smtp_code'];
$msg .= ' ' . self::lang('smtp_code') . $lasterror['smtp_code'];
}
if (!empty($lasterror['smtp_code_ex'])) {
$msg .= ' ' . $this->lang('smtp_code_ex') . $lasterror['smtp_code_ex'];
$msg .= ' ' . self::lang('smtp_code_ex') . $lasterror['smtp_code_ex'];
}
}
}
@ -4394,21 +4423,21 @@ class PHPMailer
*
* @return string
*/
protected function lang($key)
protected static function lang($key)
{
if (count($this->language) < 1) {
$this->setLanguage(); //Set the default language
if (count(self::$language) < 1) {
self::setLanguage(); //Set the default language
}
if (array_key_exists($key, $this->language)) {
if (array_key_exists($key, self::$language)) {
if ('smtp_connect_failed' === $key) {
//Include a link to troubleshooting docs on SMTP connection failure.
//This is by far the biggest cause of support questions
//but it's usually not PHPMailer's fault.
return $this->language[$key] . ' https://github.com/PHPMailer/PHPMailer/wiki/Troubleshooting';
return self::$language[$key] . ' https://github.com/PHPMailer/PHPMailer/wiki/Troubleshooting';
}
return $this->language[$key];
return self::$language[$key];
}
//Return the key as a fallback
@ -4423,7 +4452,7 @@ class PHPMailer
*/
private function getSmtpErrorMessage($base_key)
{
$message = $this->lang($base_key);
$message = self::lang($base_key);
$error = $this->smtp->getError();
if (!empty($error['error'])) {
$message .= ' ' . $error['error'];
@ -4467,7 +4496,7 @@ class PHPMailer
//Ensure name is not empty, and that neither name nor value contain line breaks
if (empty($name) || strpbrk($name . $value, "\r\n") !== false) {
if ($this->exceptions) {
throw new Exception($this->lang('invalid_header'));
throw new Exception(self::lang('invalid_header'));
}
return false;
@ -4860,7 +4889,7 @@ class PHPMailer
return true;
}
$this->setError($this->lang('variable_set') . $name);
$this->setError(self::lang('variable_set') . $name);
return false;
}
@ -4998,7 +5027,7 @@ class PHPMailer
{
if (!defined('PKCS7_TEXT')) {
if ($this->exceptions) {
throw new Exception($this->lang('extension_missing') . 'openssl');
throw new Exception(self::lang('extension_missing') . 'openssl');
}
return '';

View File

@ -205,6 +205,7 @@ class SMTP
'Haraka' => '/[\d]{3} Message Queued \((.*)\)/',
'ZoneMTA' => '/[\d]{3} Message queued as (.*)/',
'Mailjet' => '/[\d]{3} OK queued as (.*)/',
'Gsmtp' => '/[\d]{3} 2\.0\.0 OK (.*) - gsmtp/',
];
/**
@ -1340,7 +1341,16 @@ class SMTP
//stream_select returns false when the `select` system call is interrupted
//by an incoming signal, try the select again
if (stripos($message, 'interrupted system call') !== false) {
if (
stripos($message, 'interrupted system call') !== false ||
(
// on applications with a different locale than english, the message above is not found because
// it's translated. So we also check for the SOCKET_EINTR constant which is defined under
// Windows and UNIX-like platforms (if available on the platform).
defined('SOCKET_EINTR') &&
stripos($message, 'stream_select(): Unable to select [' . SOCKET_EINTR . ']') !== false
)
) {
$this->edebug(
'SMTP -> get_lines(): retrying stream_select',
self::DEBUG_LOWLEVEL

View File

@ -0,0 +1,31 @@
-----BEGIN CERTIFICATE-----
MIIFazCCA1OgAwIBAgIUPVCD/ME/tR7lrcNY0eLMignHqywwDQYJKoZIhvcNAQEL
BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM
GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yNTA5MTExOTI4MDdaFw0zNTA5
MDkxOTI4MDdaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw
HwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggIiMA0GCSqGSIb3DQEB
AQUAA4ICDwAwggIKAoICAQDHDtSUM73CqVfUt5hhBIbz56ENE746CqgqCqYpKypQ
frCDbcaRWagg9JOy4k9BChB4/B8wZilF9vsmfFoIa0H+LmQWQLN1pVx2tuSWI9rw
CdmTm6cXFZCxleOQMFxzmzV53gK9y2YRxAYL/hm6mcWp6Rblv0SqyxBz+GPJLLrr
cVRIgkktEia7ENA56DWpLoi49xYUwnDN3o+PwtrPGEzwsH/25zhEyS1LlcfRM3pY
W9UGX8HtU1LB94dWoVWNvISFvjicCWhVsuNw1Z0tIko499iEQG+zezbmh++n9a2G
bkCaI6dFZL5pHakmKOTYKyZ1sprE4799KDSTd8hlPfHboC4ClWqIiI6ou3kEpJln
sdsNZP5vPHrDgjuW/oE+zsQjmkaJiWaZphpthyYkR32Xu7HPvtQT4MHfkrs/SFE0
43ml8CqrGSa+IjSjI+HXMwsf0mRmEtK7PcqVLhdSWAGjMNPjJ+er/O+PX3ZAWrbl
GzJfYU5LAk1ES/8uKpB+TjAXDL8xyM0+aP0axEeU57SyTNqbVfimrA250KZ+Q3hk
dpTWlTEjCXhxGHXdiJJwFPyanCNstFuKgNHTbmdRTMKIQ+Wmu5EgUSH2GcRZg9oO
t2veQP3EIc9dIxzijUFETWuBqzi80D6rKJJ1KowJE0rdh7owI/SCHNOYgjSN5a0G
XQIDAQABo1MwUTAdBgNVHQ4EFgQUzSwRCSiJnYQsy99FkcsdWzHJjIwwHwYDVR0j
BBgwFoAUzSwRCSiJnYQsy99FkcsdWzHJjIwwDwYDVR0TAQH/BAUwAwEB/zANBgkq
hkiG9w0BAQsFAAOCAgEAVZApbeRypzpwv2d8B/kPcIRcq5Kot0HhTDr9CNvGqU0G
TwQrVyIVAzi0uX+Ki7flj3+bo1br9xR/ocKbnTbEA3ofCxEbf0KGEjiwvB7tAg22
UeFBxdAZG2IJcwwmY779IHKmjmFgrWGbXTirrN2a3i5TYU/nrTp7yY3GFQFujt5q
hQXBnkEvubS3n9ImdA0ByWCgmYiS08v8HGgsgGs9xVe1idkDkD+5I1imCADvUh7I
0ZksoB/XpdHRaqTRF0h6G2EUXznOG7x04uG4tiHkim1W4IkBBVTLxp6iul9n8GAe
QoZadHGaPIeytwl7A986Qo78WIltxZC+SBjJeQJG7/qHt/MvB8dBXZ49zg1SmHeV
ZtBWdtC0LBGcLoImm9m7DCyA9xMqSKSoOqmzXTlWcKQnPi3MeI5dqfWzuvk9LyLg
71hXXF4EnTgZpHw1ZWJBI47jEfsH2G0c7X46HPYjD4XcDCChNG81d0xQpZMbq5J7
jy6PSbE/iGghEOuiF1NpQsrAnlf0UAzA27bUPyX0NFOmQmAejc8b6NqIljSQE/Xm
MOZlE4RpIa63EzzDo1fas8hhUhtz3loYysHN+nmw4N0BRjVenxatJXiunIbsDkzJ
VeBTrddQlqszd0qOlGCpJM0uhHuVDPntHKmFLo7O32aH2TgvYakpp54Xd+4xIqM=
-----END CERTIFICATE-----

View File

@ -0,0 +1,52 @@
-----BEGIN PRIVATE KEY-----
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
ujKpHkhiv0V4kA8yZpxQDcXp84JE
-----END PRIVATE KEY-----

View File

@ -12,6 +12,5 @@ echo $composer;
$PHPMAILER_LANG['extension_missing'] = 'Confirming that test fixture was loaded correctly (yy).';
$PHPMAILER_LANG['empty_message'] = $composer;
$PHPMAILER_LANG['encoding'] = `ls -l`;
$PHPMAILER_LANG['execute'] = exec('some harmful command');
$PHPMAILER_LANG['signing'] = "Double quoted but not interpolated $composer";

View File

@ -0,0 +1,14 @@
<?php // lint < 8.5.
/**
* Test fixture.
*
* Used in the `PHPMailer\LocalizationTest` to test that arbitrary code in translation files is disregarded.
*
* Note: this test fixture uses a syntax (backticks) which has been deprecated in PHP 8.5 and
* is slated for removal in PHP 9.0.
* For that reason, the file is excluded from the linting check on PHP 8.5 and above.
*/
$PHPMAILER_LANG['extension_missing'] = 'Confirming that test fixture was loaded correctly (yz).';
$PHPMAILER_LANG['encoding'] = `ls -l`;

View File

@ -36,7 +36,7 @@ final class OAuthTest extends TestCase
$PHPMailer = new PHPMailer(true);
$reflection = new \ReflectionClass($PHPMailer);
$property = $reflection->getProperty('oauth');
$property->setAccessible(true);
(\PHP_VERSION_ID < 80100) && $property->setAccessible(true);
$property->setValue($PHPMailer, true);
self::assertTrue($PHPMailer->getOAuth(), 'Initial value of oauth property is not true');

View File

@ -39,9 +39,9 @@ final class FileIsAccessibleTest extends TestCase
public function testFileIsAccessible($input, $expected)
{
$reflMethod = new ReflectionMethod(PHPMailer::class, 'fileIsAccessible');
$reflMethod->setAccessible(true);
(\PHP_VERSION_ID < 80100) && $reflMethod->setAccessible(true);
$result = $reflMethod->invoke(null, $input);
$reflMethod->setAccessible(false);
(\PHP_VERSION_ID < 80100) && $reflMethod->setAccessible(false);
self::assertSame($expected, $result);
}
@ -91,9 +91,9 @@ final class FileIsAccessibleTest extends TestCase
chmod($file, octdec('0'));
$reflMethod = new ReflectionMethod(PHPMailer::class, 'fileIsAccessible');
$reflMethod->setAccessible(true);
(\PHP_VERSION_ID < 80100) && $reflMethod->setAccessible(true);
$result = $reflMethod->invoke(null, $file);
$reflMethod->setAccessible(false);
(\PHP_VERSION_ID < 80100) && $reflMethod->setAccessible(false);
// Reset to the default for git files before running assertions.
chmod($file, octdec('644'));

View File

@ -56,6 +56,46 @@ final class HasLineLongerThanMaxTest extends PreSendTestCase
);
}
/**
* Test constructing a SMIME signed message that contains lines that are too long for RFC compliance.
*
* @covers \PHPMailer\PHPMailer\PHPMailer::hasLineLongerThanMax
*/
public function testLongBodySmime()
{
$oklen = str_repeat(str_repeat('0', PHPMailer::MAX_LINE_LENGTH) . PHPMailer::getLE(), 2);
// Use +2 to ensure line length is over limit - LE may only be 1 char.
$badlen = str_repeat(str_repeat('1', PHPMailer::MAX_LINE_LENGTH + 2) . PHPMailer::getLE(), 2);
$this->Mail->Body = 'This message contains lines that are too long.' .
PHPMailer::getLE() . $oklen . $badlen . $oklen;
self::assertTrue(
PHPMailer::hasLineLongerThanMax($this->Mail->Body),
'Test content does not contain long lines!'
);
$this->Mail->isHTML();
$this->buildBody();
#$this->Mail->AltBody = $this->Mail->Body;
$this->Mail->Encoding = '8bit';
$this->Mail->sign(
__DIR__ . '/../Fixtures/HasLineLongerThanMaxTest/cert.pem',
__DIR__ . '/../Fixtures/HasLineLongerThanMaxTest/key.pem',
null
);
$this->Mail->preSend();
$message = $this->Mail->getSentMIMEMessage();
self::assertFalse(
PHPMailer::hasLineLongerThanMax($message),
'Long line not corrected (Max: ' . (PHPMailer::MAX_LINE_LENGTH + strlen(PHPMailer::getLE())) . ' chars)'
);
self::assertStringContainsString(
'Content-Transfer-Encoding: quoted-printable',
$message,
'Long line did not cause transfer encoding switch.'
);
}
/**
* Test constructing a message that does NOT contain lines that are too long for RFC compliance.
*

View File

@ -35,9 +35,9 @@ final class IsPermittedPathTest extends TestCase
public function testIsPermittedPath($input, $expected)
{
$reflMethod = new ReflectionMethod(PHPMailer::class, 'isPermittedPath');
$reflMethod->setAccessible(true);
(\PHP_VERSION_ID < 80100) && $reflMethod->setAccessible(true);
$result = $reflMethod->invoke(null, $input);
$reflMethod->setAccessible(false);
(\PHP_VERSION_ID < 80100) && $reflMethod->setAccessible(false);
self::assertSame($expected, $result);
}

View File

@ -15,6 +15,7 @@ namespace PHPMailer\Test\PHPMailer;
use ReflectionMethod;
use PHPMailer\Test\TestCase;
use PHPMailer\PHPMailer\PHPMailer;
/**
* Test localized error message functionality.
@ -305,13 +306,6 @@ final class LocalizationTest extends TestCase
'The "empty_message" translation is not as expected'
);
self::assertArrayHasKey('encoding', $lang, 'The "encoding" translation key was not found');
self::assertSame(
'Unknown encoding: ',
$lang['encoding'],
'The "encoding" translation is not as expected'
);
self::assertArrayHasKey('execute', $lang, 'The "execute" translation key was not found');
self::assertSame(
'Could not execute: ',
@ -327,6 +321,37 @@ final class LocalizationTest extends TestCase
);
}
/**
* Test that arbitrary code in a language file does not get executed.
*/
public function testSetLanguageDoesNotExecuteCodeWithBackticksInLangFile()
{
$result = $this->Mail->setLanguage(
'yz', // Unassigned lang code.
dirname(__DIR__) . '/Fixtures/LocalizationTest/'
);
$lang = $this->Mail->getTranslations();
self::assertTrue($result, 'Setting the language failed. Translations set to: ' . var_export($lang, true));
self::assertIsArray($lang, 'Translations is not an array');
// Verify that the fixture file was loaded.
self::assertArrayHasKey('extension_missing', $lang, 'The "extension_missing" translation key was not found');
self::assertSame(
'Confirming that test fixture was loaded correctly (yz).',
$lang['extension_missing'],
'The "extension_missing" translation is not as expected'
);
// Verify that arbitrary code in a translation file does not get processed.
self::assertArrayHasKey('encoding', $lang, 'The "encoding" translation key was not found');
self::assertSame(
'Unknown encoding: ',
$lang['encoding'],
'The "encoding" translation is not as expected'
);
}
/**
* Test that text strings passed in from a language file for arbitrary keys do not get processed.
*/
@ -419,13 +444,13 @@ final class LocalizationTest extends TestCase
public function testLang($input, $expected, $langCode = null)
{
if (isset($langCode)) {
$this->Mail->setLanguage($langCode);
PHPMailer::setLanguage($langCode);
}
$reflMethod = new ReflectionMethod($this->Mail, 'lang');
$reflMethod->setAccessible(true);
$result = $reflMethod->invoke($this->Mail, $input);
$reflMethod->setAccessible(false);
$reflMethod = new ReflectionMethod(PHPMailer::class, 'lang');
(\PHP_VERSION_ID < 80100) && $reflMethod->setAccessible(true);
$result = $reflMethod->invoke(null, $input);
(\PHP_VERSION_ID < 80100) && $reflMethod->setAccessible(false);
self::assertSame($expected, $result);
}

View File

@ -253,7 +253,7 @@ EOT;
$PHPMailer = new PHPMailer();
$reflection = new \ReflectionClass($PHPMailer);
$property = $reflection->getProperty('message_type');
$property->setAccessible(true);
(\PHP_VERSION_ID < 80100) && $property->setAccessible(true);
$property->setValue($PHPMailer, 'inline');
self::assertIsString($PHPMailer->createBody());

View File

@ -14,6 +14,7 @@
namespace PHPMailer\Test\PHPMailer;
use PHPMailer\PHPMailer\PHPMailer;
use ReflectionMethod;
use Yoast\PHPUnitPolyfills\TestCases\TestCase;
/**
@ -28,36 +29,6 @@ use Yoast\PHPUnitPolyfills\TestCases\TestCase;
*/
final class ParseAddressesTest extends TestCase
{
/**
* Test RFC822 address splitting using the PHPMailer native implementation
* with the Mbstring extension available.
*
* @requires extension mbstring
*
* @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 testAddressSplittingNative($addrstr, $expected, $charset = null)
{
if (isset($charset)) {
$parsed = PHPMailer::parseAddresses($addrstr, false, $charset);
} else {
$parsed = PHPMailer::parseAddresses($addrstr, false);
}
$expectedOutput = $expected['default'];
if (empty($expected['native+mbstring']) === false) {
$expectedOutput = $expected['native+mbstring'];
} elseif (empty($expected['native']) === false) {
$expectedOutput = $expected['native'];
}
$this->verifyExpectations($parsed, $expectedOutput);
}
/**
* Test RFC822 address splitting using the IMAP implementation
* with the Mbstring extension available.
@ -89,38 +60,6 @@ final class ParseAddressesTest extends TestCase
$this->verifyExpectations($parsed, $expectedOutput);
}
/**
* Test RFC822 address splitting using the PHPMailer native implementation
* without the Mbstring extension.
*
* @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 testAddressSplittingNativeNoMbstring($addrstr, $expected, $charset = null)
{
if (extension_loaded('mbstring')) {
self::markTestSkipped('Test requires MbString *not* to be available');
}
if (isset($charset)) {
$parsed = PHPMailer::parseAddresses($addrstr, false, $charset);
} else {
$parsed = PHPMailer::parseAddresses($addrstr, false);
}
$expectedOutput = $expected['default'];
if (empty($expected['native--mbstring']) === false) {
$expectedOutput = $expected['native--mbstring'];
} elseif (empty($expected['native']) === false) {
$expectedOutput = $expected['native'];
}
$this->verifyExpectations($parsed, $expectedOutput);
}
/**
* Test RFC822 address splitting using the IMAP implementation
* without the Mbstring extension.
@ -155,6 +94,52 @@ final class ParseAddressesTest extends TestCase
$this->verifyExpectations($parsed, $expectedOutput);
}
/**
* Test RFC822 address splitting using the native implementation
*
* @dataProvider dataAddressSplittingNative
*
* @param string $addrstr The address list string.
* @param array $expected The expected function output.
* @param string $charset Optional.The charset to use.
*/
public function testAddressSplittingNative($addrstr, $expected, $charset = PHPMailer::CHARSET_ISO88591)
{
error_reporting(E_ALL & ~E_USER_NOTICE);
$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);
}
/**
* Data provider for testAddressSplittingNative.
*
* @return array
* addrstr: string,
* expected: array{name: string, address: string}[]
* charset: string
*/
public function dataAddressSplittingNative()
{
return [
'Valid address: single address without name' => [
'addrstr' => 'joe@example.com',
'expected' => [
['name' => '', 'address' => 'joe@example.com'],
],
],
'Valid address: two addresses with names' => [
'addrstr' => 'Joe User <joe@example.com>, Jill User <jill@example.net>',
'expected' => [
['name' => 'Joe User', 'address' => 'joe@example.com'],
['name' => 'Jill User', 'address' => 'jill@example.net'],
],
],
];
}
/**
* Verify the expectations.
*

View File

@ -119,6 +119,7 @@ abstract class TestCase extends PolyfillTestCase
private $PHPMailerStaticProps = [
'LE' => PHPMailer::CRLF,
'validator' => 'php',
'language' => [],
];
/**