diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index d727b17c..8a7997b3 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -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"
diff --git a/.github/workflows/docs.yaml b/.github/workflows/docs.yaml
index f30a48e9..070bec95 100644
--- a/.github/workflows/docs.yaml
+++ b/.github/workflows/docs.yaml
@@ -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
diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml
index 06fa35ac..189de394 100644
--- a/.github/workflows/scorecards.yml
+++ b/.github/workflows/scorecards.yml
@@ -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
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index ec6e7ddb..30ba6233 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -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:
diff --git a/changelog.md b/changelog.md
index 272c73be..dd585eb9 100644
--- a/changelog.md
+++ b/changelog.md
@@ -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.
diff --git a/composer.json b/composer.json
index 82da669a..e4dd7ddd 100644
--- a/composer.json
+++ b/composer.json
@@ -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": {
diff --git a/language/phpmailer.lang-es.php b/language/phpmailer.lang-es.php
index 4e74bfb7..35ce5b8e 100644
--- a/language/phpmailer.lang-es.php
+++ b/language/phpmailer.lang-es.php
@@ -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.';
diff --git a/phpcs.xml.dist b/phpcs.xml.dist
index 426618c7..e2d1e5c6 100644
--- a/phpcs.xml.dist
+++ b/phpcs.xml.dist
@@ -36,7 +36,24 @@
+
+
+
*/language/phpmailer\.lang*\.php$
+
+
+
+ */test/Fixtures/LocalizationTest/phpmailer.lang-yz\.php
+
+
+ */test/Fixtures/LocalizationTest/phpmailer.lang-yz\.php
+
+
diff --git a/src/PHPMailer.php b/src/PHPMailer.php
index 49fce31e..0596d166 100644
--- a/src/PHPMailer.php
+++ b/src/PHPMailer.php
@@ -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
" 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 '';
diff --git a/src/SMTP.php b/src/SMTP.php
index e6170f71..7c359cf7 100644
--- a/src/SMTP.php
+++ b/src/SMTP.php
@@ -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
diff --git a/test/Fixtures/HasLineLongerThanMaxTest/cert.pem b/test/Fixtures/HasLineLongerThanMaxTest/cert.pem
new file mode 100644
index 00000000..e7c90b30
--- /dev/null
+++ b/test/Fixtures/HasLineLongerThanMaxTest/cert.pem
@@ -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-----
diff --git a/test/Fixtures/HasLineLongerThanMaxTest/key.pem b/test/Fixtures/HasLineLongerThanMaxTest/key.pem
new file mode 100644
index 00000000..6d7ec066
--- /dev/null
+++ b/test/Fixtures/HasLineLongerThanMaxTest/key.pem
@@ -0,0 +1,52 @@
+-----BEGIN PRIVATE KEY-----
+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
+ujKpHkhiv0V4kA8yZpxQDcXp84JE
+-----END PRIVATE KEY-----
diff --git a/test/Fixtures/LocalizationTest/phpmailer.lang-yy.php b/test/Fixtures/LocalizationTest/phpmailer.lang-yy.php
index eb485dbf..d3af6be6 100644
--- a/test/Fixtures/LocalizationTest/phpmailer.lang-yy.php
+++ b/test/Fixtures/LocalizationTest/phpmailer.lang-yy.php
@@ -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";
diff --git a/test/Fixtures/LocalizationTest/phpmailer.lang-yz.php b/test/Fixtures/LocalizationTest/phpmailer.lang-yz.php
new file mode 100644
index 00000000..2b0f8aa3
--- /dev/null
+++ b/test/Fixtures/LocalizationTest/phpmailer.lang-yz.php
@@ -0,0 +1,14 @@
+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');
diff --git a/test/PHPMailer/FileIsAccessibleTest.php b/test/PHPMailer/FileIsAccessibleTest.php
index 8989643a..45f0837d 100644
--- a/test/PHPMailer/FileIsAccessibleTest.php
+++ b/test/PHPMailer/FileIsAccessibleTest.php
@@ -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'));
diff --git a/test/PHPMailer/HasLineLongerThanMaxTest.php b/test/PHPMailer/HasLineLongerThanMaxTest.php
index af6118d6..7fffee6d 100644
--- a/test/PHPMailer/HasLineLongerThanMaxTest.php
+++ b/test/PHPMailer/HasLineLongerThanMaxTest.php
@@ -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.
*
diff --git a/test/PHPMailer/IsPermittedPathTest.php b/test/PHPMailer/IsPermittedPathTest.php
index 93823a44..f3f17537 100644
--- a/test/PHPMailer/IsPermittedPathTest.php
+++ b/test/PHPMailer/IsPermittedPathTest.php
@@ -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);
}
diff --git a/test/PHPMailer/LocalizationTest.php b/test/PHPMailer/LocalizationTest.php
index c67ce578..4af752a3 100644
--- a/test/PHPMailer/LocalizationTest.php
+++ b/test/PHPMailer/LocalizationTest.php
@@ -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);
}
diff --git a/test/PHPMailer/PHPMailerTest.php b/test/PHPMailer/PHPMailerTest.php
index 3c835480..cee80dd3 100644
--- a/test/PHPMailer/PHPMailerTest.php
+++ b/test/PHPMailer/PHPMailerTest.php
@@ -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());
diff --git a/test/PHPMailer/ParseAddressesTest.php b/test/PHPMailer/ParseAddressesTest.php
index f6188b90..ee9064f1 100644
--- a/test/PHPMailer/ParseAddressesTest.php
+++ b/test/PHPMailer/ParseAddressesTest.php
@@ -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 , Jill User ',
+ 'expected' => [
+ ['name' => 'Joe User', 'address' => 'joe@example.com'],
+ ['name' => 'Jill User', 'address' => 'jill@example.net'],
+ ],
+ ],
+ ];
+ }
+
/**
* Verify the expectations.
*
diff --git a/test/TestCase.php b/test/TestCase.php
index 40fbc8e5..c9ec7a26 100644
--- a/test/TestCase.php
+++ b/test/TestCase.php
@@ -119,6 +119,7 @@ abstract class TestCase extends PolyfillTestCase
private $PHPMailerStaticProps = [
'LE' => PHPMailer::CRLF,
'validator' => 'php',
+ 'language' => [],
];
/**