diff -r 7b1b88e27a20 -r 48c4eec2b7e6 wp/wp-includes/PHPMailer/PHPMailer.php --- a/wp/wp-includes/PHPMailer/PHPMailer.php Thu Sep 29 08:06:27 2022 +0200 +++ b/wp/wp-includes/PHPMailer/PHPMailer.php Fri Sep 05 18:40:08 2025 +0200 @@ -350,14 +350,21 @@ public $Password = ''; /** - * SMTP auth type. - * Options are CRAM-MD5, LOGIN, PLAIN, XOAUTH2, attempted in that order if not specified. + * SMTP authentication type. Options are CRAM-MD5, LOGIN, PLAIN, XOAUTH2. + * If not specified, the first one from that list that the server supports will be selected. * * @var string */ public $AuthType = ''; /** + * SMTP SMTPXClient command attibutes + * + * @var array + */ + protected $SMTPXClient = []; + + /** * An implementation of the PHPMailer OAuthTokenProvider interface. * * @var OAuthTokenProvider @@ -750,7 +757,7 @@ * * @var string */ - const VERSION = '6.6.0'; + const VERSION = '6.9.1'; /** * Error severity: message only, continue processing. @@ -795,7 +802,7 @@ * The maximum line length supported by mail(). * * Background: mail() will sometimes corrupt messages - * with headers headers longer than 65 chars, see #818. + * with headers longer than 65 chars, see #818. * * @var int */ @@ -858,7 +865,7 @@ private function mailPassthru($to, $subject, $body, $header, $params) { //Check overloading of mail function to avoid double-encoding - if (ini_get('mbstring.func_overload') & 1) { // phpcs:ignore PHPCompatibility.IniDirectives.RemovedIniDirectives.mbstring_func_overloadDeprecated + if ((int)ini_get('mbstring.func_overload') & 1) { // phpcs:ignore PHPCompatibility.IniDirectives.RemovedIniDirectives.mbstring_func_overloadDeprecated $subject = $this->secureHeader($subject); } else { $subject = $this->encodeHeader($this->secureHeader($subject)); @@ -1066,8 +1073,8 @@ * Addresses that have been added already return false, but do not throw exceptions. * * @param string $kind One of 'to', 'cc', 'bcc', or 'ReplyTo' - * @param string $address The email address to send, resp. to reply to - * @param string $name + * @param string $address The email address + * @param string $name An optional username associated with the address * * @throws Exception * @@ -1075,9 +1082,11 @@ */ protected function addOrEnqueueAnAddress($kind, $address, $name) { - $address = trim($address); - $name = trim(preg_replace('/[\r\n]+/', '', $name)); //Strip breaks and trim - $pos = strrpos($address, '@'); + $pos = false; + if ($address !== null) { + $address = trim($address); + $pos = strrpos($address, '@'); + } if (false === $pos) { //At-sign is missing. $error_message = sprintf( @@ -1094,8 +1103,14 @@ return false; } + if ($name !== null && is_string($name)) { + $name = trim(preg_replace('/[\r\n]+/', '', $name)); //Strip breaks and trim + } else { + $name = ''; + } $params = [$kind, $address, $name]; //Enqueue addresses with IDN until we know the PHPMailer::$CharSet. + //Domain is assumed to be whatever is after the last @ symbol in the address if (static::idnSupported() && $this->has8bitChars(substr($address, ++$pos))) { if ('Reply-To' !== $kind) { if (!array_key_exists($address, $this->RecipientsQueue)) { @@ -1117,6 +1132,22 @@ } /** + * Set the boundaries to use for delimiting MIME parts. + * If you override this, ensure you set all 3 boundaries to unique values. + * The default boundaries include a "=_" sequence which cannot occur in quoted-printable bodies, + * as suggested by https://www.rfc-editor.org/rfc/rfc2045#section-6.7 + * + * @return void + */ + public function setBoundaries() + { + $this->uniqueid = $this->generateId(); + $this->boundary[1] = 'b1=_' . $this->uniqueid; + $this->boundary[2] = 'b2=_' . $this->uniqueid; + $this->boundary[3] = 'b3=_' . $this->uniqueid; + } + + /** * Add an address to one of the recipient arrays or to the ReplyTo array. * Addresses that have been added already return false, but do not throw exceptions. * @@ -1280,7 +1311,7 @@ */ public function setFrom($address, $name = '', $auto = true) { - $address = trim($address); + $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(). $pos = strrpos($address, '@'); @@ -1549,17 +1580,21 @@ //Validate From, Sender, and ConfirmReadingTo addresses foreach (['From', 'Sender', 'ConfirmReadingTo'] as $address_kind) { - $this->$address_kind = trim($this->$address_kind); - if (empty($this->$address_kind)) { + if ($this->{$address_kind} === null) { + $this->{$address_kind} = ''; continue; } - $this->$address_kind = $this->punyencodeAddress($this->$address_kind); - if (!static::validateAddress($this->$address_kind)) { + $this->{$address_kind} = trim($this->{$address_kind}); + if (empty($this->{$address_kind})) { + continue; + } + $this->{$address_kind} = $this->punyencodeAddress($this->{$address_kind}); + if (!static::validateAddress($this->{$address_kind})) { $error_message = sprintf( '%s (%s): %s', $this->lang('invalid_address'), $address_kind, - $this->$address_kind + $this->{$address_kind} ); $this->setError($error_message); $this->edebug($error_message); @@ -1659,17 +1694,17 @@ default: $sendMethod = $this->Mailer . 'Send'; if (method_exists($this, $sendMethod)) { - return $this->$sendMethod($this->MIMEHeader, $this->MIMEBody); + return $this->{$sendMethod}($this->MIMEHeader, $this->MIMEBody); } return $this->mailSend($this->MIMEHeader, $this->MIMEBody); } } catch (Exception $exc) { - if ($this->Mailer === 'smtp' && $this->SMTPKeepAlive == true) { + $this->setError($exc->getMessage()); + $this->edebug($exc->getMessage()); + if ($this->Mailer === 'smtp' && $this->SMTPKeepAlive == true && $this->smtp->connected()) { $this->smtp->reset(); } - $this->setError($exc->getMessage()); - $this->edebug($exc->getMessage()); if ($this->exceptions) { throw $exc; } @@ -1857,7 +1892,7 @@ if (!static::isPermittedPath($path)) { return false; } - $readable = file_exists($path); + $readable = is_file($path); //If not a UNC path (expected to start with \\), check read permission, see #2069 if (strpos($path, '\\\\') !== 0) { $readable = $readable && is_readable($path); @@ -1885,7 +1920,14 @@ foreach ($this->to as $toaddr) { $toArr[] = $this->addrFormat($toaddr); } - $to = implode(', ', $toArr); + $to = trim(implode(', ', $toArr)); + + //If there are no To-addresses (e.g. when sending only to BCC-addresses) + //the following should be added to get a correct DKIM-signature. + //Compare with $this->preSend() + if ($to === '') { + $to = 'undisclosed-recipients:;'; + } $params = null; //This sets the SMTP envelope sender which gets turned into a return-path header by the receiver @@ -1969,6 +2011,38 @@ } /** + * Provide SMTP XCLIENT attributes + * + * @param string $name Attribute name + * @param ?string $value Attribute value + * + * @return bool + */ + public function setSMTPXclientAttribute($name, $value) + { + if (!in_array($name, SMTP::$xclient_allowed_attributes)) { + return false; + } + if (isset($this->SMTPXClient[$name]) && $value === null) { + unset($this->SMTPXClient[$name]); + } elseif ($value !== null) { + $this->SMTPXClient[$name] = $value; + } + + return true; + } + + /** + * Get SMTP XCLIENT attributes + * + * @return array + */ + public function getSMTPXclientAttributes() + { + return $this->SMTPXClient; + } + + /** * Send mail via SMTP. * Returns false if there is a bad MAIL FROM, RCPT, or DATA input. * @@ -1996,6 +2070,9 @@ } else { $smtp_from = $this->Sender; } + if (count($this->SMTPXClient)) { + $this->smtp->xclient($this->SMTPXClient); + } if (!$this->smtp->mail($smtp_from)) { $this->setError($this->lang('from_failed') . $smtp_from . ' : ' . implode(',', $this->smtp->getError())); throw new Exception($this->ErrorInfo, self::STOP_CRITICAL); @@ -2088,6 +2165,9 @@ $this->smtp->setDebugLevel($this->SMTPDebug); $this->smtp->setDebugOutput($this->Debugoutput); $this->smtp->setVerp($this->do_verp); + if ($this->Host === null) { + $this->Host = 'localhost'; + } $hosts = explode(';', $this->Host); $lastexception = null; @@ -2155,10 +2235,17 @@ $this->smtp->hello($hello); //Automatically enable TLS encryption if: //* it's not disabled + //* we are not connecting to localhost //* we have openssl extension //* we are not already using SSL //* the server offers STARTTLS - if ($this->SMTPAutoTLS && $sslext && 'ssl' !== $secure && $this->smtp->getServerExt('STARTTLS')) { + if ( + $this->SMTPAutoTLS && + $this->Host !== 'localhost' && + $sslext && + $secure !== 'ssl' && + $this->smtp->getServerExt('STARTTLS') + ) { $tls = true; } if ($tls) { @@ -2194,7 +2281,8 @@ //As we've caught all exceptions, just report whatever the last one was if ($this->exceptions && null !== $lastexception) { throw $lastexception; - } elseif ($this->exceptions) { + } + if ($this->exceptions) { // no exception was thrown, likely $this->smtp->connect() failed $message = $this->getSmtpErrorMessage('connect_host'); throw new Exception($message); @@ -2390,7 +2478,7 @@ */ public function addrFormat($addr) { - if (empty($addr[1])) { //No name provided + if (!isset($addr[1]) || ($addr[1] === '')) { //No name provided return $this->secureHeader($addr[0]); } @@ -2777,10 +2865,7 @@ { $body = ''; //Create unique IDs and preset boundaries - $this->uniqueid = $this->generateId(); - $this->boundary[1] = 'b1_' . $this->uniqueid; - $this->boundary[2] = 'b2_' . $this->uniqueid; - $this->boundary[3] = 'b3_' . $this->uniqueid; + $this->setBoundaries(); if ($this->sign_key_file) { $body .= $this->getMailMIME() . static::$LE; @@ -2816,7 +2901,7 @@ $altBodyEncoding = static::ENCODING_QUOTED_PRINTABLE; } //Use this as a preamble in all multipart message types - $mimepre = 'This is a multi-part message in MIME format.' . static::$LE . static::$LE; + $mimepre = ''; switch ($this->message_type) { case 'inline': $body .= $mimepre; @@ -3053,6 +3138,18 @@ } /** + * Get the boundaries that this message will use + * @return array + */ + public function getBoundaries() + { + if (empty($this->boundary)) { + $this->setBoundaries(); + } + return $this->boundary; + } + + /** * Return the start of a message boundary. * * @param string $boundary @@ -3707,20 +3804,21 @@ * These differ from 'regular' attachments in that they are intended to be * displayed inline with the message, not just attached for download. * This is used in HTML messages that embed the images - * the HTML refers to using the $cid value. + * the HTML refers to using the `$cid` value in `img` tags, for example ``. * Never use a user-supplied path to a file! * * @param string $path Path to the attachment * @param string $cid Content ID of the attachment; Use this to reference * the content when using an embedded image in HTML - * @param string $name Overrides the attachment name - * @param string $encoding File encoding (see $Encoding) - * @param string $type File MIME type - * @param string $disposition Disposition to use - * - * @throws Exception + * @param string $name Overrides the attachment filename + * @param string $encoding File encoding (see $Encoding) defaults to `base64` + * @param string $type File MIME type (by default mapped from the `$path` filename's extension) + * @param string $disposition Disposition to use: `inline` (default) or `attachment` + * (unlikely you want this – {@see `addAttachment()`} instead) * * @return bool True on successfully adding an attachment + * @throws Exception + * */ public function addEmbeddedImage( $path, @@ -4005,6 +4103,79 @@ } /** + * Clear a specific custom header by name or name and value. + * $name value can be overloaded to contain + * both header name and value (name:value). + * + * @param string $name Custom header name + * @param string|null $value Header value + * + * @return bool True if a header was replaced successfully + */ + public function clearCustomHeader($name, $value = null) + { + if (null === $value && strpos($name, ':') !== false) { + //Value passed in as name:value + list($name, $value) = explode(':', $name, 2); + } + $name = trim($name); + $value = (null === $value) ? null : trim($value); + + foreach ($this->CustomHeader as $k => $pair) { + if ($pair[0] == $name) { + // We remove the header if the value is not provided or it matches. + if (null === $value || $pair[1] == $value) { + unset($this->CustomHeader[$k]); + } + } + } + + return true; + } + + /** + * Replace a custom header. + * $name value can be overloaded to contain + * both header name and value (name:value). + * + * @param string $name Custom header name + * @param string|null $value Header value + * + * @return bool True if a header was replaced successfully + * @throws Exception + */ + public function replaceCustomHeader($name, $value = null) + { + if (null === $value && strpos($name, ':') !== false) { + //Value passed in as name:value + list($name, $value) = explode(':', $name, 2); + } + $name = trim($name); + $value = (null === $value) ? '' : trim($value); + + $replaced = false; + foreach ($this->CustomHeader as $k => $pair) { + if ($pair[0] == $name) { + if ($replaced) { + unset($this->CustomHeader[$k]); + continue; + } + if (strpbrk($name . $value, "\r\n") !== false) { + if ($this->exceptions) { + throw new Exception($this->lang('invalid_header')); + } + + return false; + } + $this->CustomHeader[$k] = [$name, $value]; + $replaced = true; + } + } + + return true; + } + + /** * Add an error message to the error container. * * @param string $msg @@ -4098,12 +4269,8 @@ //Is it a valid IPv4 address? return filter_var($host, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) !== false; } - if (filter_var('http://' . $host, FILTER_VALIDATE_URL) !== false) { - //Is it a syntactically valid hostname? - return true; - } - - return false; + //Is it a syntactically valid hostname (when embeded in a URL)? + return filter_var('http://' . $host, FILTER_VALIDATE_URL) !== false; } /** @@ -4172,6 +4339,7 @@ * @param string $name Custom header name * @param string|null $value Header value * + * @return bool True if a header was set successfully * @throws Exception */ public function addCustomHeader($name, $value = null) @@ -4466,6 +4634,7 @@ 'ics' => 'text/calendar', 'xml' => 'text/xml', 'xsl' => 'text/xml', + 'csv' => 'text/csv', 'wmv' => 'video/x-ms-wmv', 'mpeg' => 'video/mpeg', 'mpe' => 'video/mpeg', @@ -4573,7 +4742,7 @@ public function set($name, $value = '') { if (property_exists($this, $name)) { - $this->$name = $value; + $this->{$name} = $value; return true; } @@ -4620,15 +4789,27 @@ } /** - * Remove trailing breaks from a string. + * Remove trailing whitespace from a string. + * + * @param string $text + * + * @return string The text to remove whitespace from + */ + public static function stripTrailingWSP($text) + { + return rtrim($text, " \r\n\t"); + } + + /** + * Strip trailing line breaks from a string. * * @param string $text * * @return string The text to remove breaks from */ - public static function stripTrailingWSP($text) + public static function stripTrailingBreaks($text) { - return rtrim($text, " \r\n\t"); + return rtrim($text, "\r\n"); } /** @@ -4794,7 +4975,7 @@ $body = static::normalizeBreaks($body, self::CRLF); //Reduce multiple trailing line breaks to a single one - return static::stripTrailingWSP($body) . self::CRLF; + return static::stripTrailingBreaks($body) . self::CRLF; } /**