Commit 0a07391e71 for wordpress.org
commit 0a07391e71e550212c70252ed9f99dcf61a98a5c
Author: SergeyBiryukov <sergeybiryukov@git.wordpress.org>
Date: Thu Nov 12 23:20:05 2026 +0000
External Libraries: Upgrade PHPMailer to version 7.2.0.
Changes in the latest version:
- **XOAUTH:** authentication with a long token could report success when it had actually failed.
- **S/MIME:** signed messages no longer contain bare LF line breaks, which some SMTP servers reject.
- **Long address headers:** these are now folded to stay within the RFC 2822 line length limit.
- **`QUIT`:** no longer sent while a `DATA` command is still in progress.
- **XCLIENT:**
- Attribute values are now xtext-encoded when they're sent, which prevents attribute injection.
- The expected reply is now 220, not 250, and `EHLO` is sent again afterwards.
- Invalid attributes are rejected.
- **`DSNConfigurator`:** `smtps://` DSNs now use `ENCRYPTION_SMTPS` instead of `ENCRYPTION_STARTTLS`.
- **Deprecations:** `PHPMailer::parseAddresses()` and the `pcre` and `pcre8` patterns for `validateAddress()` are deprecated, and will be removed in PHPMailer 8.0. The deprecation is only in the docblocks; no notice is triggered.
WordPress core doesn't use XCLIENT, `DSNConfigurator`, `parseAddresses()` or `validateAddress()`. Plugins that call these directly may be affected.
References:
* [https://github.com/PHPMailer/PHPMailer/releases/tag/v7.2.0 PHPMailer 7.2.0 release notes]
* [https://github.com/PHPMailer/PHPMailer/compare/v7.1.1...v7.2.0 Full list of changes in PHPMailer 7.2.0]
Follow-up to r54937, r55557, r56484, r57137, r59246, r59481, r60623, r60813, r60888, r61249, r61468, r62975.
Props desrosj.
Fixes #66649.
Built from https://develop.svn.wordpress.org/trunk@65457
git-svn-id: http://core.svn.wordpress.org/trunk@64585 1a063a9b-81f0-0310-95a4-ce76da25c4cd
diff --git a/wp-includes/PHPMailer/DSNConfigurator.php b/wp-includes/PHPMailer/DSNConfigurator.php
index 7058c1f05e..9c855fa729 100644
--- a/wp-includes/PHPMailer/DSNConfigurator.php
+++ b/wp-includes/PHPMailer/DSNConfigurator.php
@@ -22,7 +22,10 @@
namespace PHPMailer\PHPMailer;
/**
- * Configure PHPMailer with DSN string.
+ * Configure PHPMailer with a DSN string.
+ * Be aware that a DSN string can configure *all* PHPMailer properties,
+ * including those that have security consequences, so do not use
+ * user-provided DSN strings without strict validation of all parameters.
*
* @see https://en.wikipedia.org/wiki/Data_source_name
*
@@ -140,7 +143,7 @@ class DSNConfigurator
$isSMTPS = 'smtps' === $config['scheme'];
if ($isSMTPS) {
- $mailer->SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS;
+ $mailer->SMTPSecure = PHPMailer::ENCRYPTION_SMTPS;
}
$mailer->Host = $config['host'];
diff --git a/wp-includes/PHPMailer/PHPMailer.php b/wp-includes/PHPMailer/PHPMailer.php
index 4900cbc43a..5fc3208f58 100644
--- a/wp-includes/PHPMailer/PHPMailer.php
+++ b/wp-includes/PHPMailer/PHPMailer.php
@@ -769,7 +769,7 @@ class PHPMailer
*
* @var string
*/
- const VERSION = '7.1.1';
+ const VERSION = '7.2.0';
/**
* Error severity: message only, continue processing.
@@ -1288,6 +1288,7 @@ class PHPMailer
* the deprecated $useimap argument is truthy.
* Note that quotes in the name part are removed.
*
+ * @deprecated This method will be removed in the next major release of 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
@@ -1300,11 +1301,11 @@ class PHPMailer
*/
public static function parseAddresses($addrstr, $useimap = null, $charset = self::CHARSET_ISO88591)
{
- if ($useimap == true) {
+ if ($useimap) {
trigger_error(self::lang('deprecated_argument') . '$useimap', E_USER_DEPRECATED);
}
$addresses = [];
- if ($useimap == true && function_exists('imap_rfc822_parse_adrlist')) {
+ if ($useimap !== false && function_exists('imap_rfc822_parse_adrlist')) {
//Use this built-in parser if it's available
// phpcs:ignore PHPCompatibility.FunctionUse.RemovedFunctions.imap_rfc822_parse_adrlistRemoved -- wrapped in function_exists()
$list = imap_rfc822_parse_adrlist($addrstr, '');
@@ -1486,8 +1487,8 @@ class PHPMailer
* Check that a string looks like an email address.
* Validation patterns supported:
* * `auto` Pick best pattern automatically;
- * * `pcre8` Use the squiloople.com pattern, requires PCRE > 8.0;
- * * `pcre` Use old PCRE implementation;
+ * * `pcre8` (deprecated, will convert to PHP validator) Use the squiloople.com pattern, requires PCRE > 8.0
+ * * `pcre` (deprecated, will convert to PHP validator) Use old PCRE implementation, kept for BC;
* * `php` Use PHP built-in FILTER_VALIDATE_EMAIL;
* * `html5` Use the pattern given by the HTML5 spec for 'email' type form input elements.
* * `eai` Use a pattern similar to the HTML5 spec for 'email' and to firefox, extended to support EAI (RFC6530).
@@ -1503,7 +1504,8 @@ class PHPMailer
* You can also set the PHPMailer::$validator static to a callable, allowing built-in methods to use your validator.
*
* @param string $address The email address to check
- * @param string|callable $patternselect Which pattern to use
+ * @param string|callable $patternselect Which pattern to use;
+ * the `pcre` and `pcre8` patterns are deprecated
*
* @return bool
*/
@@ -1521,7 +1523,7 @@ class PHPMailer
return false;
}
switch ($patternselect) {
- case 'pcre': //Kept for BC
+ case 'pcre': //Deprecated, kept for BC
case 'pcre8':
/*
* A more complex and more permissive version of the RFC5322 regex on which FILTER_VALIDATE_EMAIL
@@ -2191,12 +2193,17 @@ class PHPMailer
*/
public function setSMTPXclientAttribute($name, $value)
{
- if (!in_array($name, SMTP::$xclient_allowed_attributes)) {
+ if (!in_array($name, SMTP::$xclient_allowed_attributes, true)) {
return false;
}
if (isset($this->SMTPXClient[$name]) && $value === null) {
unset($this->SMTPXClient[$name]);
} elseif ($value !== null) {
+ if (!is_string($value)) {
+ //Reject invalid XCLIENT attributes
+ return false;
+ }
+ //Values are stored raw and xtext-encoded at send time
$this->SMTPXClient[$name] = $value;
}
@@ -2247,7 +2254,17 @@ class PHPMailer
$smtp_from = $this->Sender;
}
if (count($this->SMTPXClient)) {
- $this->smtp->xclient($this->SMTPXClient);
+ if (!$this->smtp->xclient($this->SMTPXClient)) {
+ throw new Exception($this->smtp->getError()['error'], self::STOP_CRITICAL);
+ }
+ //XCLIENT success resets the session to the greeting protocol stage,
+ //so the client must send EHLO again before starting a transaction.
+ if ($this->Helo) {
+ $hello = $this->Helo;
+ } else {
+ $hello = $this->serverHostname();
+ }
+ $this->smtp->hello($hello);
}
if (!$this->smtp->mail($smtp_from)) {
$this->setError(self::lang('from_failed') . $smtp_from . ' : ' . implode(',', $this->smtp->getError()));
@@ -2646,7 +2663,22 @@ class PHPMailer
$addresses[] = $this->addrFormat($address);
}
- return $type . ': ' . implode(', ', $addresses) . static::$LE;
+ //Fold between addresses so that no line exceeds the RFC 2822 line length limit
+ $header = $type . ': ' . array_shift($addresses);
+ $lineLength = strlen($header);
+ foreach ($addresses as $address) {
+ //The address goes after ', ' and must still leave room for the ',' that ends
+ //the line if the next address gets folded
+ if ($lineLength + strlen(', ') + strlen($address) + strlen(',') > static::MAX_LINE_LENGTH) {
+ $header .= ',' . static::$LE . ' ' . $address;
+ $lineLength = strlen(' ') + strlen($address);
+ } else {
+ $header .= ', ' . $address;
+ $lineLength += strlen(', ') + strlen($address);
+ }
+ }
+
+ return $header . static::$LE;
}
/**
@@ -3319,8 +3351,9 @@ class PHPMailer
@unlink($signed);
//The message returned by openssl contains both headers and body, so need to split them up
$parts = explode("\n\n", $body, 2);
- $this->MIMEHeader .= $parts[0] . static::$LE . static::$LE;
- $body = $parts[1];
+ //openssl emits bare LF breaks, which SMTP servers may reject
+ $this->MIMEHeader .= static::normalizeBreaks($parts[0]) . static::$LE . static::$LE;
+ $body = static::normalizeBreaks($parts[1]);
} else {
@unlink($signed);
throw new Exception(self::lang('signing') . openssl_error_string());
diff --git a/wp-includes/PHPMailer/POP3.php b/wp-includes/PHPMailer/POP3.php
index 0ba9678373..823ec8be3d 100644
--- a/wp-includes/PHPMailer/POP3.php
+++ b/wp-includes/PHPMailer/POP3.php
@@ -47,7 +47,7 @@ class POP3
* @var string
* @deprecated This constant will be removed in PHPMailer 8.0. Use `PHPMailer::VERSION` instead.
*/
- const VERSION = '7.1.1';
+ const VERSION = '7.2.0';
/**
* Default POP3 port number.
diff --git a/wp-includes/PHPMailer/SMTP.php b/wp-includes/PHPMailer/SMTP.php
index f0957b80a9..605d136b15 100644
--- a/wp-includes/PHPMailer/SMTP.php
+++ b/wp-includes/PHPMailer/SMTP.php
@@ -36,7 +36,7 @@ class SMTP
* @var string
* @deprecated This constant will be removed in PHPMailer 8.0. Use `PHPMailer::VERSION` instead.
*/
- const VERSION = '7.1.1';
+ const VERSION = '7.2.0';
/**
* SMTP line break constant.
@@ -212,6 +212,7 @@ class SMTP
/**
* Allowed SMTP XCLIENT attributes.
* Must be allowed by the SMTP server. EHLO response is not checked.
+ * This is a static rather than a constant because PHP 5.5 doesn't allow constant arrays.
*
* @see https://www.postfix.org/XCLIENT_README.html
*
@@ -275,6 +276,14 @@ class SMTP
*/
protected $last_reply = '';
+ /**
+ * The last command sent to the server.
+ * Used to avoid sending QUIT while a DATA command is still in progress.
+ *
+ * @var string
+ */
+ protected $state = '';
+
/**
* Output debugging info via a user-selected method.
*
@@ -671,10 +680,11 @@ class SMTP
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 the server answers with 334, send an empty line
+ //a reply other than a 235 means the authentication failed, so we don't need to send anything else
if (
- substr($this->last_reply, 0, 3) === '334'
- && $this->sendCommand('AUTH End', '', 235)
+ strpos($this->last_reply, '334') === 0
+ && !$this->sendCommand('AUTH End', '', 235)
) {
return false;
}
@@ -762,6 +772,7 @@ class SMTP
{
$this->server_caps = null;
$this->helo_rply = null;
+ $this->state = '';
if (is_resource($this->smtp_conn)) {
//Close the connection and cleanup
fclose($this->smtp_conn);
@@ -1020,7 +1031,12 @@ class SMTP
*/
public function quit($close_on_error = true)
{
- $noerror = $this->sendCommand('QUIT', 'QUIT', 221);
+ if ($this->state === 'DATA') {
+ //The server would take QUIT as part of an unfinished message, so just close the connection
+ $noerror = true;
+ } else {
+ $noerror = $this->sendCommand('QUIT', 'QUIT', 221);
+ }
$err = $this->error; //Save any error
if ($noerror || $close_on_error) {
$this->close();
@@ -1070,8 +1086,39 @@ class SMTP
);
}
+ /**
+ * Encode a string using xtext encoding as defined in RFC 1891 & 3461,
+ * as required for XCLIENT attribute values: any octet outside
+ * printable US-ASCII (33-126), including '+', whitespace, and control
+ * characters, is encoded as '+' followed by two uppercase hexadecimal
+ * digits. Values encoded this way cannot inject additional attributes.
+ *
+ * @param string $value The string to encode
+ *
+ * @return string The xtext-encoded string
+ */
+ public static function xtext($value)
+ {
+ return preg_replace_callback(
+ '/[^\x21-\x7E]|\+/',
+ static function ($matches) {
+ return sprintf('+%02X', ord($matches[0]));
+ },
+ $value
+ );
+ }
+
/**
* Send SMTP XCLIENT command to server and check its return code.
+ * On success the server resets the session to the initial greeting
+ * protocol stage and responds with 220, so a new EHLO is required
+ * before any MAIL command.
+ *
+ * Attribute values are xtext-encoded (RFC 1891 & 3461) before being
+ * sent.
+ *
+ * @see https://www.postfix.org/XCLIENT_README.html
+ * @see https://www.rfc-editor.org/info/rfc3461/#section-4
*
* @return bool True on success
*/
@@ -1079,14 +1126,20 @@ class SMTP
{
$xclient_options = "";
foreach ($vars as $key => $value) {
- if (in_array($key, SMTP::$xclient_allowed_attributes)) {
- $xclient_options .= " {$key}={$value}";
+ if (!in_array($key, SMTP::$xclient_allowed_attributes)) {
+ $this->setError("Invalid XCLIENT attribute name: $key");
+ return false;
+ }
+ if (!is_string($value)) {
+ $this->setError("XCLIENT attribute value for $key must be a string");
+ return false;
}
+ $xclient_options .= " {$key}=" . static::xtext($value);
}
if (!$xclient_options) {
return true;
}
- return $this->sendCommand('XCLIENT', 'XCLIENT' . $xclient_options, 250);
+ return $this->sendCommand('XCLIENT', 'XCLIENT' . $xclient_options, 220);
}
/**
@@ -1123,6 +1176,7 @@ class SMTP
return false;
}
+ $this->state = $command;
$this->client_send($commandstring . static::LE, $command);
$this->last_reply = $this->get_lines();
diff --git a/wp-includes/version.php b/wp-includes/version.php
index e4708922bb..5c7f5d9a29 100644
--- a/wp-includes/version.php
+++ b/wp-includes/version.php
@@ -16,7 +16,7 @@
*
* @global string $wp_version
*/
-$wp_version = '7.2-beta5-65445';
+$wp_version = '7.2-beta5-65457';
/**
* Holds the WordPress DB revision, increments when changes are made to the WordPress DB schema.