diff options
| author | 2025-10-01 21:03:22 +0200 | |
|---|---|---|
| committer | 2025-10-01 21:03:22 +0200 | |
| commit | 8e57e28a9a1b37987d6ce9c6406df00a401bcf72 (patch) | |
| tree | 0d4d243d5811c847ace80fcce5f60c966ba778e1 /lib/phpmailer | |
| parent | df4b76f74b4b3c25a73cb4c72690f428b6b09535 (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.md | 2 | ||||
| -rw-r--r-- | lib/phpmailer/phpmailer/VERSION | 2 | ||||
| -rw-r--r-- | lib/phpmailer/phpmailer/src/PHPMailer.php | 374 | ||||
| -rw-r--r-- | lib/phpmailer/phpmailer/src/SMTP.php | 53 |
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 |
