From fa28c46052e23797d9501276155ae7cf70dc365e Mon Sep 17 00:00:00 2001 From: SirLouen Date: Fri, 9 Jan 2026 21:48:22 +0100 Subject: [PATCH] Update PHPMailer version to 7.0.2 --- src/wp-includes/PHPMailer/PHPMailer.php | 131 ++++++++++++++++++------ src/wp-includes/PHPMailer/POP3.php | 3 +- src/wp-includes/PHPMailer/SMTP.php | 39 ++++++- 3 files changed, 134 insertions(+), 39 deletions(-) diff --git a/src/wp-includes/PHPMailer/PHPMailer.php b/src/wp-includes/PHPMailer/PHPMailer.php index ff3b84bb269c1..2bb3578c7e0d9 100644 --- a/src/wp-includes/PHPMailer/PHPMailer.php +++ b/src/wp-includes/PHPMailer/PHPMailer.php @@ -768,7 +768,7 @@ class PHPMailer * * @var string */ - const VERSION = '7.0.0'; + const VERSION = '7.0.2'; /** * Error severity: message only, continue processing. @@ -876,6 +876,7 @@ public function __destruct() private function mailPassthru($to, $subject, $body, $header, $params) { //Check overloading of mail function to avoid double-encoding + // phpcs:ignore PHPCompatibility.IniDirectives.RemovedIniDirectives.mbstring_func_overloadDeprecatedRemoved if ((int)ini_get('mbstring.func_overload') & 1) { $subject = $this->secureHeader($subject); } else { @@ -987,6 +988,54 @@ public function isMail() $this->Mailer = 'mail'; } + /** + * Extract sendmail path and parse to deal with known parameters. + * + * @param string $sendmailPath The sendmail path as set in php.ini + * + * @return string The sendmail path without the known parameters + */ + private function parseSendmailPath($sendmailPath) + { + $sendmailPath = trim((string)$sendmailPath); + if ($sendmailPath === '') { + return $sendmailPath; + } + + $parts = preg_split('/\s+/', $sendmailPath); + if (empty($parts)) { + return $sendmailPath; + } + + $command = array_shift($parts); + $remainder = []; + + // Parse only -t, -i, -oi and -f parameters. + for ($i = 0; $i < count($parts); ++$i) { + $part = $parts[$i]; + if (preg_match('/^-(i|oi|t)$/', $part, $matches)) { + continue; + } + if (preg_match('/^-f(.*)$/', $part, $matches)) { + $address = $matches[1]; + if ($address === '' && isset($parts[$i + 1]) && strpos($parts[$i + 1], '-') !== 0) { + $address = $parts[++$i]; + } + $this->Sender = $address; + continue; + } + + $remainder[] = $part; + } + + // The params that are not parsed are added back to the command. + if (!empty($remainder)) { + $command .= ' ' . implode(' ', $remainder); + } + + return $command; + } + /** * Send messages using $Sendmail. */ @@ -995,10 +1044,9 @@ public function isSendmail() $ini_sendmail_path = ini_get('sendmail_path'); if (false === stripos($ini_sendmail_path, 'sendmail')) { - $this->Sendmail = '/usr/sbin/sendmail'; - } else { - $this->Sendmail = $ini_sendmail_path; + $ini_sendmail_path = '/usr/sbin/sendmail'; } + $this->Sendmail = $this->parseSendmailPath($ini_sendmail_path); $this->Mailer = 'sendmail'; } @@ -1010,10 +1058,9 @@ public function isQmail() $ini_sendmail_path = ini_get('sendmail_path'); if (false === stripos($ini_sendmail_path, 'qmail')) { - $this->Sendmail = '/var/qmail/bin/qmail-inject'; - } else { - $this->Sendmail = $ini_sendmail_path; + $ini_sendmail_path = '/var/qmail/bin/qmail-inject'; } + $this->Sendmail = $this->parseSendmailPath($ini_sendmail_path); $this->Mailer = 'qmail'; } @@ -1242,7 +1289,9 @@ protected function addAnAddress($kind, $address, $name = '') * @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 null $useimap Deprecated argument since 6.11.0. + * @param null $useimap Unused. Argument has been deprecated in PHPMailer 6.11.0. + * Previously this argument determined whether to use + * the IMAP extension to parse the list and accepted a boolean value. * @param string $charset The charset to use when decoding the address list string. * * @return array @@ -1250,13 +1299,15 @@ protected function addAnAddress($kind, $address, $name = '') public static function parseAddresses($addrstr, $useimap = null, $charset = self::CHARSET_ISO88591) { if ($useimap !== null) { - trigger_error(self::lang('deprecated_argument'), E_USER_DEPRECATED); + trigger_error(self::lang('deprecated_argument') . '$useimap', E_USER_DEPRECATED); } $addresses = []; if (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, ''); // Clear any potential IMAP errors to get rid of notices being thrown at end of script. + // phpcs:ignore PHPCompatibility.FunctionUse.RemovedFunctions.imap_errorsRemoved -- wrapped in function_exists() imap_errors(); foreach ($list as $address) { if ( @@ -1583,9 +1634,11 @@ public function punyencodeAddress($address) ); } elseif (defined('INTL_IDNA_VARIANT_2003')) { //Fall back to this old, deprecated/removed encoding + // phpcs:ignore PHPCompatibility.Constants.RemovedConstants.intl_idna_variant_2003DeprecatedRemoved $punycode = idn_to_ascii($domain, $errorcode, \INTL_IDNA_VARIANT_2003); } else { //Fall back to a default we don't know about + // phpcs:ignore PHPCompatibility.ParameterValues.NewIDNVariantDefault.NotSet $punycode = idn_to_ascii($domain, $errorcode); } if (false !== $punycode) { @@ -1853,25 +1906,27 @@ protected function sendmailSend($header, $body) //PHP config has a sender address we can use $this->Sender = ini_get('sendmail_from'); } - //CVE-2016-10033, CVE-2016-10045: Don't pass -f if characters will be escaped. + + $sendmailArgs = []; + + // CVE-2016-10033, CVE-2016-10045: Don't pass -f if characters will be escaped. + // Also don't add the -f automatically unless it has been set either via Sender + // or sendmail_path. Otherwise it can introduce new problems. + // @see http://github.com/PHPMailer/PHPMailer/issues/2298 if (!empty($this->Sender) && static::validateAddress($this->Sender) && self::isShellSafe($this->Sender)) { - if ($this->Mailer === 'qmail') { - $sendmailFmt = '%s -f%s'; - } else { - $sendmailFmt = '%s -oi -f%s -t'; - } - } elseif ($this->Mailer === 'qmail') { - $sendmailFmt = '%s'; - } else { - //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 - //), and - //it has historically worked this way. - $sendmailFmt = '%s -oi -t'; + $sendmailArgs[] = '-f' . $this->Sender; + } + + // Qmail doesn't accept all the sendmail parameters + // @see https://github.com/PHPMailer/PHPMailer/issues/3189 + if ($this->Mailer !== 'qmail') { + $sendmailArgs[] = '-i'; + $sendmailArgs[] = '-t'; } - $sendmail = sprintf($sendmailFmt, escapeshellcmd($this->Sendmail), $this->Sender); + $resultArgs = (empty($sendmailArgs) ? '' : ' ' . implode(' ', $sendmailArgs)); + + $sendmail = trim(escapeshellcmd($this->Sendmail) . $resultArgs); $this->edebug('Sendmail path: ' . $this->Sendmail); $this->edebug('Sendmail command: ' . $sendmail); $this->edebug('Envelope sender: ' . $this->Sender); @@ -2055,7 +2110,8 @@ protected function mailSend($header, $body) $this->Sender = ini_get('sendmail_from'); } if (!empty($this->Sender) && static::validateAddress($this->Sender)) { - if (self::isShellSafe($this->Sender)) { + $phpmailer_path = ini_get('sendmail_path'); + if (self::isShellSafe($this->Sender) && strpos($phpmailer_path, ' -f') === false) { $params = sprintf('-f%s', $this->Sender); } $old_from = ini_get('sendmail_from'); @@ -2482,7 +2538,7 @@ public static function setLanguage($langcode = 'en', $lang_path = '') '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', + 'deprecated_argument' => 'Deprecated Argument: ', ]; if (empty($lang_path)) { //Calculate an absolute path so it can work if CWD is not here @@ -2956,6 +3012,7 @@ protected function generateId() $bytes = ''; if (function_exists('random_bytes')) { try { + // phpcs:ignore PHPCompatibility.FunctionUse.NewFunctions.random_bytesFound -- Wrapped in function_exists. $bytes = random_bytes($len); } catch (\Exception $e) { //Do nothing @@ -4590,10 +4647,10 @@ public function getCustomHeaders() * Converts data-uri images into embedded attachments. * If you don't want to apply these transformations to your HTML, just set Body and AltBody directly. * - * @param string $message HTML message string - * @param string $basedir Absolute path to a base directory to prepend to relative paths to images - * @param bool|callable $advanced Whether to use the internal HTML to text converter - * or your own custom converter + * @param string $message HTML message string + * @param string $basedir Absolute path to a base directory to prepend to relative paths to images + * @param bool|callable $advanced Whether to use the internal HTML to text converter + * or your own custom converter * @return string The transformed message body * * @throws Exception @@ -4602,6 +4659,12 @@ public function getCustomHeaders() */ public function msgHTML($message, $basedir = '', $advanced = false) { + $cid_domain = 'phpmailer.0'; + if (filter_var($this->From, FILTER_VALIDATE_EMAIL)) { + //prepend with a character to create valid RFC822 string in order to validate + $cid_domain = substr($this->From, strrpos($this->From, '@') + 1); + } + preg_match_all('/(? 1 && '/' !== substr($basedir, -1)) { @@ -4623,7 +4686,7 @@ public function msgHTML($message, $basedir = '', $advanced = false) } //Hash the decoded data, not the URL, so that the same data-URI image used in multiple places //will only be embedded once, even if it used a different encoding - $cid = substr(hash('sha256', $data), 0, 32) . '@phpmailer.0'; //RFC2392 S 2 + $cid = substr(hash('sha256', $data), 0, 32) . '@' . $cid_domain; //RFC2392 S 2 if (!$this->cidExists($cid)) { $this->addStringEmbeddedImage( @@ -4657,7 +4720,7 @@ public function msgHTML($message, $basedir = '', $advanced = false) $directory = ''; } //RFC2392 S 2 - $cid = substr(hash('sha256', $url), 0, 32) . '@phpmailer.0'; + $cid = substr(hash('sha256', $url), 0, 32) . '@' . $cid_domain; if (strlen($basedir) > 1 && '/' !== substr($basedir, -1)) { $basedir .= '/'; } @@ -5105,12 +5168,14 @@ public function DKIM_Sign($signHeader) } if (openssl_sign($signHeader, $signature, $privKey, 'sha256WithRSAEncryption')) { if (\PHP_MAJOR_VERSION < 8) { + // phpcs:ignore PHPCompatibility.FunctionUse.RemovedFunctions.openssl_pkey_freeDeprecated openssl_pkey_free($privKey); } return base64_encode($signature); } if (\PHP_MAJOR_VERSION < 8) { + // phpcs:ignore PHPCompatibility.FunctionUse.RemovedFunctions.openssl_pkey_freeDeprecated openssl_pkey_free($privKey); } diff --git a/src/wp-includes/PHPMailer/POP3.php b/src/wp-includes/PHPMailer/POP3.php index c881a1c3fe9f3..186fe9fe47ab7 100644 --- a/src/wp-includes/PHPMailer/POP3.php +++ b/src/wp-includes/PHPMailer/POP3.php @@ -45,8 +45,9 @@ class POP3 * The POP3 PHPMailer Version number. * * @var string + * @deprecated This constant will be removed in PHPMailer 8.0. Use `PHPMailer::VERSION` instead. */ - const VERSION = '7.0.0'; + const VERSION = '7.0.2'; /** * Default POP3 port number. diff --git a/src/wp-includes/PHPMailer/SMTP.php b/src/wp-includes/PHPMailer/SMTP.php index a36741bfb9bba..559b52c45e8f8 100644 --- a/src/wp-includes/PHPMailer/SMTP.php +++ b/src/wp-includes/PHPMailer/SMTP.php @@ -34,8 +34,9 @@ class SMTP * The PHPMailer SMTP version number. * * @var string + * @deprecated This constant will be removed in PHPMailer 8.0. Use `PHPMailer::VERSION` instead. */ - const VERSION = '7.0.0'; + const VERSION = '7.0.2'; /** * SMTP line break constant. @@ -494,7 +495,9 @@ public function startTLS() //PHP 5.6.7 dropped inclusion of TLS 1.1 and 1.2 in STREAM_CRYPTO_METHOD_TLS_CLIENT //so add them back in manually if we can if (defined('STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT')) { + // phpcs:ignore PHPCompatibility.Constants.NewConstants.stream_crypto_method_tlsv1_2_clientFound $crypto_method |= STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT; + // phpcs:ignore PHPCompatibility.Constants.NewConstants.stream_crypto_method_tlsv1_1_clientFound $crypto_method |= STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT; } @@ -633,7 +636,13 @@ public function authenticate( if (null === $OAuth) { return false; } - $oauth = $OAuth->getOauth64(); + try { + $oauth = $OAuth->getOauth64(); + } catch (\Exception $e) { + // We catch all exceptions and convert them to PHPMailer exceptions to be able to + // handle them correctly later + throw new Exception("SMTP authentication error", 0, $e); + } /* * 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: @@ -761,6 +770,25 @@ public function close() } } + private function iterateLines($s) + { + $start = 0; + $length = strlen($s); + + for ($i = 0; $i < $length; $i++) { + $c = $s[$i]; + if ($c === "\n" || $c === "\r") { + yield substr($s, $start, $i - $start); + if ($c === "\r" && $i + 1 < $length && $s[$i + 1] === "\n") { + $i++; + } + $start = $i + 1; + } + } + + yield substr($s, $start); + } + /** * Send an SMTP DATA command. * Issues a data command and sends the msg_data to the server, @@ -789,15 +817,16 @@ public function data($msg_data) * NOTE: this does not count towards line-length limit. */ - //Normalize line breaks before exploding - $lines = explode("\n", str_replace(["\r\n", "\r"], "\n", $msg_data)); + //Iterate over lines with normalized line breaks + $lines = $this->iterateLines($msg_data); /* To distinguish between a complete RFC822 message and a plain message body, we check if the first field * of the first line (':' separated) does not contain a space then it _should_ be a header, and we will * process all lines before a blank line as headers. */ - $field = substr($lines[0], 0, strpos($lines[0], ':')); + $first_line = $lines->current(); + $field = substr($first_line, 0, strpos($first_line, ':')); $in_headers = false; if (!empty($field) && strpos($field, ' ') === false) { $in_headers = true;