aboutsummaryrefslogtreecommitdiff
path: root/lib/phpmailer
diff options
context:
space:
mode:
authorGravatar dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> 2025-10-01 21:03:22 +0200
committerGravatar GitHub <noreply@github.com> 2025-10-01 21:03:22 +0200
commit8e57e28a9a1b37987d6ce9c6406df00a401bcf72 (patch)
tree0d4d243d5811c847ace80fcce5f60c966ba778e1 /lib/phpmailer
parentdf4b76f74b4b3c25a73cb4c72690f428b6b09535 (diff)
Update phpmailer/phpmailer requirement from 6.10.0 to 6.11.1 in /lib (#8048)
* Update phpmailer/phpmailer requirement from 6.10.0 to 6.11.1 in /lib Updates the requirements on [phpmailer/phpmailer](https://github.com/PHPMailer/PHPMailer) to permit the latest version. - [Release notes](https://github.com/PHPMailer/PHPMailer/releases) - [Changelog](https://github.com/PHPMailer/PHPMailer/blob/master/changelog.md) - [Commits](https://github.com/PHPMailer/PHPMailer/compare/v6.10.0...v6.11.1) --- updated-dependencies: - dependency-name: phpmailer/phpmailer dependency-version: 6.11.1 dependency-type: direct:production ... Signed-off-by: dependabot[bot] <support@github.com> * composer update --no-autoloader --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>
Diffstat (limited to 'lib/phpmailer')
-rw-r--r--lib/phpmailer/phpmailer/README.md2
-rw-r--r--lib/phpmailer/phpmailer/VERSION2
-rw-r--r--lib/phpmailer/phpmailer/src/PHPMailer.php374
-rw-r--r--lib/phpmailer/phpmailer/src/SMTP.php53
4 files changed, 285 insertions, 146 deletions
diff --git a/lib/phpmailer/phpmailer/README.md b/lib/phpmailer/phpmailer/README.md
index 862a4e1a4..51c97517f 100644
--- a/lib/phpmailer/phpmailer/README.md
+++ b/lib/phpmailer/phpmailer/README.md
@@ -48,7 +48,7 @@ This software is distributed under the [LGPL 2.1](https://www.gnu.org/licenses/o
PHPMailer is available on [Packagist](https://packagist.org/packages/phpmailer/phpmailer) (using semantic versioning), and installation via [Composer](https://getcomposer.org) is the recommended way to install PHPMailer. Just add this line to your `composer.json` file:
```json
-"phpmailer/phpmailer": "^6.10.0"
+"phpmailer/phpmailer": "^6.11.1"
```
or run
diff --git a/lib/phpmailer/phpmailer/VERSION b/lib/phpmailer/phpmailer/VERSION
index cf79bf90e..fac714a32 100644
--- a/lib/phpmailer/phpmailer/VERSION
+++ b/lib/phpmailer/phpmailer/VERSION
@@ -1 +1 @@
-6.10.0
+6.11.1
diff --git a/lib/phpmailer/phpmailer/src/PHPMailer.php b/lib/phpmailer/phpmailer/src/PHPMailer.php
index 2444bcf35..0a8711f48 100644
--- a/lib/phpmailer/phpmailer/src/PHPMailer.php
+++ b/lib/phpmailer/phpmailer/src/PHPMailer.php
@@ -561,9 +561,9 @@ class PHPMailer
* string $body the email body
* string $from email address of sender
* string $extra extra information of possible use
- * "smtp_transaction_id' => last smtp transaction id
+ * 'smtp_transaction_id' => last smtp transaction id
*
- * @var string
+ * @var callable|callable-string
*/
public $action_function = '';
@@ -711,7 +711,7 @@ class PHPMailer
*
* @var array
*/
- protected $language = [];
+ protected static $language = [];
/**
* The number of errors encountered.
@@ -768,7 +768,7 @@ class PHPMailer
*
* @var string
*/
- const VERSION = '6.10.0';
+ const VERSION = '6.11.1';
/**
* Error severity: message only, continue processing.
@@ -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
);
@@ -1220,12 +1220,16 @@ class PHPMailer
return true;
}
- } elseif (!array_key_exists(strtolower($address), $this->ReplyTo)) {
- $this->ReplyTo[strtolower($address)] = [$address, $name];
+ } else {
+ foreach ($this->ReplyTo as $replyTo) {
+ if (0 === strcasecmp($replyTo[0], $address)) {
+ return false;
+ }
+ }
+ $this->ReplyTo[] = [$address, $name];
return true;
}
-
return false;
}
@@ -1238,15 +1242,18 @@ class PHPMailer
* @see https://www.andrew.cmu.edu/user/agreen1/testing/mrbs/web/Mail/RFC822.php A more careful implementation
*
* @param string $addrstr The address list string
- * @param bool $useimap Whether to use the IMAP extension to parse the list
+ * @param null $useimap Deprecated argument since 6.11.0.
* @param string $charset The charset to use when decoding the address list string.
*
* @return array
*/
- public static function parseAddresses($addrstr, $useimap = true, $charset = self::CHARSET_ISO88591)
+ public static function parseAddresses($addrstr, $useimap = null, $charset = self::CHARSET_ISO88591)
{
+ if ($useimap !== null) {
+ trigger_error(self::lang('deprecated_argument'), E_USER_DEPRECATED);
+ }
$addresses = [];
- if ($useimap && function_exists('imap_rfc822_parse_adrlist')) {
+ if (function_exists('imap_rfc822_parse_adrlist')) {
//Use this built-in parser if it's available
$list = imap_rfc822_parse_adrlist($addrstr, '');
// Clear any potential IMAP errors to get rid of notices being thrown at end of script.
@@ -1256,20 +1263,13 @@ class PHPMailer
'.SYNTAX-ERROR.' !== $address->host &&
static::validateAddress($address->mailbox . '@' . $address->host)
) {
- //Decode the name part if it's present and encoded
+ //Decode the name part if it's present and maybe encoded
if (
- property_exists($address, 'personal') &&
- //Check for a Mbstring constant rather than using extension_loaded, which is sometimes disabled
- defined('MB_CASE_UPPER') &&
- preg_match('/^=\?.*\?=$/s', $address->personal)
+ property_exists($address, 'personal')
+ && is_string($address->personal)
+ && $address->personal !== ''
) {
- $origCharset = mb_internal_encoding();
- mb_internal_encoding($charset);
- //Undo any RFC2047-encoded spaces-as-underscores
- $address->personal = str_replace('_', '=20', $address->personal);
- //Decode the name
- $address->personal = mb_decode_mimeheader($address->personal);
- mb_internal_encoding($origCharset);
+ $address->personal = static::decodeHeader($address->personal, $charset);
}
$addresses[] = [
@@ -1280,40 +1280,51 @@ 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 = static::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);
+
+ $addresses = [];
+ $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 {
+ $parsed = static::parseEmailString($address);
+ $email = $parsed['email'];
+ if (static::validateAddress($email)) {
+ $name = static::decodeHeader($parsed['name'], $charset);
+ $addresses[] = [
+ //Remove any surrounding quotes and spaces from the name
+ 'name' => trim($name, '\'" '),
+ 'address' => $email,
+ ];
}
}
}
@@ -1322,6 +1333,42 @@ class PHPMailer
}
/**
+ * 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.
*
* @param string $address
@@ -1334,6 +1381,10 @@ class PHPMailer
*/
public function setFrom($address, $name = '', $auto = true)
{
+ if (is_null($name)) {
+ //Helps avoid a deprecation warning in the preg_replace() below
+ $name = '';
+ }
$address = trim((string)$address);
$name = trim(preg_replace('/[\r\n]+/', '', $name)); //Strip breaks and trim
//Don't validate now addresses with IDN. Will be done in send().
@@ -1345,7 +1396,7 @@ class PHPMailer
) {
$error_message = sprintf(
'%s (From): %s',
- $this->lang('invalid_address'),
+ self::lang('invalid_address'),
$address
);
$this->setError($error_message);
@@ -1601,7 +1652,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 {
@@ -1631,7 +1682,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
@@ -1648,7 +1699,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}
);
@@ -1670,7 +1721,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
@@ -1809,8 +1860,10 @@ class PHPMailer
} else {
$sendmailFmt = '%s -oi -f%s -t';
}
+ } elseif ($this->Mailer === 'qmail') {
+ $sendmailFmt = '%s';
} else {
- //allow sendmail to choose a default envelope sender. It may
+ //Allow sendmail to choose a default envelope sender. It may
//seem preferable to force it to use the From header as with
//SMTP, but that introduces new problems (see
//<https://github.com/PHPMailer/PHPMailer/issues/2298>), and
@@ -1828,33 +1881,35 @@ 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");
fwrite($mail, $header);
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,
- []
- );
+ $addrinfo = static::parseAddresses($toAddr, null, $this->CharSet);
+ 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);
@@ -1871,7 +1926,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);
}
}
@@ -2010,17 +2065,19 @@ class PHPMailer
if ($this->SingleTo && count($toArr) > 1) {
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,
- []
- );
+ $addrinfo = static::parseAddresses($toAddr, null, $this->CharSet);
+ 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);
@@ -2030,7 +2087,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;
@@ -2116,12 +2173,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) {
@@ -2133,7 +2190,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);
}
@@ -2155,7 +2212,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();
@@ -2186,7 +2243,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;
@@ -2240,7 +2297,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;
}
@@ -2252,7 +2309,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 = '';
@@ -2272,7 +2329,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];
@@ -2324,7 +2381,7 @@ class PHPMailer
$this->oauth
)
) {
- throw new Exception($this->lang('authenticate'));
+ throw new Exception(self::lang('authenticate'));
}
return true;
@@ -2374,7 +2431,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 = [
@@ -2423,6 +2480,9 @@ 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.',
+ 'deprecated_argument' => 'Argument $useimap is deprecated',
];
if (empty($lang_path)) {
//Calculate an absolute path so it can work if CWD is not here
@@ -2489,7 +2549,7 @@ class PHPMailer
}
}
}
- $this->language = $PHPMAILER_LANG;
+ self::$language = $PHPMAILER_LANG;
return $foundlang; //Returns false if language not found
}
@@ -2501,11 +2561,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;
}
/**
@@ -2928,10 +2988,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;
@@ -2963,6 +3019,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) {
@@ -3144,12 +3206,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');
@@ -3187,7 +3249,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 = '';
@@ -3332,7 +3394,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
@@ -3345,7 +3407,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[] = [
@@ -3506,11 +3568,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);
@@ -3563,9 +3625,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;
}
@@ -3672,6 +3734,42 @@ class PHPMailer
}
/**
+ * Decode an RFC2047-encoded header value
+ * Attempts multiple strategies so it works even when the mbstring extension is disabled.
+ *
+ * @param string $value The header value to decode
+ * @param string $charset The target charset to convert to, defaults to ISO-8859-1 for BC
+ *
+ * @return string The decoded header value
+ */
+ public static function decodeHeader($value, $charset = self::CHARSET_ISO88591)
+ {
+ if (!is_string($value) || $value === '') {
+ return '';
+ }
+ // Detect the presence of any RFC2047 encoded-words
+ $hasEncodedWord = (bool) preg_match('/=\?.*\?=/s', $value);
+ if ($hasEncodedWord && defined('MB_CASE_UPPER')) {
+ $origCharset = mb_internal_encoding();
+ // Always decode to UTF-8 to provide a consistent, modern output encoding.
+ mb_internal_encoding($charset);
+ if (PHP_VERSION_ID < 80300) {
+ // Undo any RFC2047-encoded spaces-as-underscores.
+ $value = str_replace('_', '=20', $value);
+ } else {
+ // PHP 8.3+ already interprets underscores as spaces. Remove additional
+ // linear whitespace between adjacent encoded words to avoid double spacing.
+ $value = preg_replace('/(\?=)\s+(=\?)/', '$1$2', $value);
+ }
+ // Decode the header value
+ $value = mb_decode_mimeheader($value);
+ mb_internal_encoding($origCharset);
+ }
+
+ return $value;
+ }
+
+ /**
* Check if a string contains multi-byte characters.
*
* @param string $str multi-byte text to wrap encode
@@ -3840,7 +3938,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
@@ -3899,7 +3997,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
@@ -3908,7 +4006,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);
@@ -3974,7 +4072,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
@@ -4231,7 +4329,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;
@@ -4255,15 +4353,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'];
}
}
}
@@ -4388,21 +4486,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
@@ -4417,7 +4515,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'];
@@ -4461,7 +4559,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;
@@ -4854,7 +4952,7 @@ class PHPMailer
return true;
}
- $this->setError($this->lang('variable_set') . $name);
+ $this->setError(self::lang('variable_set') . $name);
return false;
}
@@ -4992,7 +5090,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/lib/phpmailer/phpmailer/src/SMTP.php b/lib/phpmailer/phpmailer/src/SMTP.php
index 7226ee93b..3772c94ad 100644
--- a/lib/phpmailer/phpmailer/src/SMTP.php
+++ b/lib/phpmailer/phpmailer/src/SMTP.php
@@ -35,7 +35,7 @@ class SMTP
*
* @var string
*/
- const VERSION = '6.10.0';
+ const VERSION = '6.11.1';
/**
* SMTP line break constant.
@@ -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/',
];
/**
@@ -633,10 +634,41 @@ class SMTP
return false;
}
$oauth = $OAuth->getOauth64();
-
- //Start authentication
- if (!$this->sendCommand('AUTH', 'AUTH XOAUTH2 ' . $oauth, 235)) {
- return false;
+ /*
+ * An SMTP command line can have a maximum length of 512 bytes, including the command name,
+ * so the base64-encoded OAUTH token has a maximum length of:
+ * 512 - 13 (AUTH XOAUTH2) - 2 (CRLF) = 497 bytes
+ * If the token is longer than that, the command and the token must be sent separately as described in
+ * https://www.rfc-editor.org/rfc/rfc4954#section-4
+ */
+ if ($oauth === '') {
+ //Sending an empty auth token is legitimate, but it must be encoded as '='
+ //to indicate it's not a 2-part command
+ if (!$this->sendCommand('AUTH', 'AUTH XOAUTH2 =', 235)) {
+ return false;
+ }
+ } elseif (strlen($oauth) <= 497) {
+ //Authenticate using a token in the initial-response part
+ if (!$this->sendCommand('AUTH', 'AUTH XOAUTH2 ' . $oauth, 235)) {
+ return false;
+ }
+ } else {
+ //The token is too long, so we need to send it in two parts.
+ //Send the auth command without a token and expect a 334
+ if (!$this->sendCommand('AUTH', 'AUTH XOAUTH2', 334)) {
+ return false;
+ }
+ //Send the token
+ if (!$this->sendCommand('OAuth TOKEN', $oauth, [235, 334])) {
+ return false;
+ }
+ //If the server answers with 334, send an empty line and wait for a 235
+ if (
+ substr($this->last_reply, 0, 3) === '334'
+ && $this->sendCommand('AUTH End', '', 235)
+ ) {
+ return false;
+ }
}
break;
default:
@@ -1309,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