From 4ec08675be98973ba7d1c89e482b924d3447a561 Mon Sep 17 00:00:00 2001 From: fredtempez Date: Mon, 18 Apr 2022 17:52:14 +0200 Subject: [PATCH] php mailer v6.6.0 --- CHANGES.md | 1 + core/class/phpmailer/Exception.class.php | 5 +- core/class/phpmailer/PHPMailer.class.php | 812 ++++++++++++++------- core/class/phpmailer/SMTP.class.php | 235 ++++-- core/class/phpmailer/phpmailer.lang-fr.php | 38 + core/core.php | 7 +- 6 files changed, 737 insertions(+), 361 deletions(-) create mode 100644 core/class/phpmailer/phpmailer.lang-fr.php diff --git a/CHANGES.md b/CHANGES.md index 02884df8..0f77e99a 100755 --- a/CHANGES.md +++ b/CHANGES.md @@ -23,6 +23,7 @@ - Nettoyage du code. ### Mise à jour : - TableDND, script JQUERY de tri de tables utilisé par la galerie, passe en version 1.0.5 +- PHPMailer 6.6.0 ## Version 11.3.07 ### Correction : diff --git a/core/class/phpmailer/Exception.class.php b/core/class/phpmailer/Exception.class.php index b1e552f5..52eaf951 100644 --- a/core/class/phpmailer/Exception.class.php +++ b/core/class/phpmailer/Exception.class.php @@ -1,4 +1,5 @@ * @author Andy Prevost (codeworxtech) * @author Brent R. Matzelle (original founder) - * @copyright 2012 - 2017 Marcus Bointon + * @copyright 2012 - 2020 Marcus Bointon * @copyright 2010 - 2012 Jim Jagielski * @copyright 2004 - 2009 Andy Prevost * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License @@ -34,6 +35,6 @@ class Exception extends \Exception */ public function errorMessage() { - return '' . htmlspecialchars($this->getMessage()) . "
\n"; + return '' . htmlspecialchars($this->getMessage(), ENT_COMPAT | ENT_HTML401) . "
\n"; } } diff --git a/core/class/phpmailer/PHPMailer.class.php b/core/class/phpmailer/PHPMailer.class.php index 51dff921..718216b5 100644 --- a/core/class/phpmailer/PHPMailer.class.php +++ b/core/class/phpmailer/PHPMailer.class.php @@ -1,4 +1,5 @@ * @author Andy Prevost (codeworxtech) * @author Brent R. Matzelle (original founder) - * @copyright 2012 - 2019 Marcus Bointon + * @copyright 2012 - 2020 Marcus Bointon * @copyright 2010 - 2012 Jim Jagielski * @copyright 2004 - 2009 Andy Prevost * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License @@ -102,14 +103,14 @@ class PHPMailer * * @var string */ - public $From = 'root@localhost'; + public $From = ''; /** * The From name of the message. * * @var string */ - public $FromName = 'Root User'; + public $FromName = ''; /** * The envelope sender of the message. @@ -357,9 +358,9 @@ class PHPMailer public $AuthType = ''; /** - * An instance of the PHPMailer OAuth class. + * An implementation of the PHPMailer OAuthTokenProvider interface. * - * @var OAuth + * @var OAuthTokenProvider */ protected $oauth; @@ -388,11 +389,11 @@ class PHPMailer * SMTP class debug output mode. * Debug output level. * Options: - * * SMTP::DEBUG_OFF: No output - * * SMTP::DEBUG_CLIENT: Client messages - * * SMTP::DEBUG_SERVER: Client and server messages - * * SMTP::DEBUG_CONNECTION: As SERVER plus connection status - * * SMTP::DEBUG_LOWLEVEL: Noisy, low-level data output, rarely needed + * @see SMTP::DEBUG_OFF: No output + * @see SMTP::DEBUG_CLIENT: Client messages + * @see SMTP::DEBUG_SERVER: Client and server messages + * @see SMTP::DEBUG_CONNECTION: As SERVER plus connection status + * @see SMTP::DEBUG_LOWLEVEL: Noisy, low-level data output, rarely needed * * @see SMTP::$do_debug * @@ -427,9 +428,11 @@ class PHPMailer public $Debugoutput = 'echo'; /** - * Whether to keep SMTP connection open after each message. - * If this is set to true then to close the connection - * requires an explicit call to smtpClose(). + * Whether to keep the SMTP connection open after each message. + * If this is set to true then the connection will remain open after a send, + * and closing the connection will require an explicit call to smtpClose(). + * It's a good idea to use this if you are sending multiple messages as it reduces overhead. + * See the mailing list example for how to use it. * * @var bool */ @@ -441,6 +444,8 @@ class PHPMailer * Only supported in `mail` and `sendmail` transports, not in SMTP. * * @var bool + * + * @deprecated 6.0.0 PHPMailer isn't a mailing list manager! */ public $SingleTo = false; @@ -684,7 +689,7 @@ class PHPMailer protected $boundary = []; /** - * The array of available languages. + * The array of available text strings for the current language. * * @var array */ @@ -745,7 +750,7 @@ class PHPMailer * * @var string */ - const VERSION = '6.1.5'; + const VERSION = '6.6.0'; /** * Error severity: message only, continue processing. @@ -859,18 +864,25 @@ class PHPMailer $subject = $this->encodeHeader($this->secureHeader($subject)); } //Calling mail() with null params breaks + $this->edebug('Sending with mail()'); + $this->edebug('Sendmail path: ' . ini_get('sendmail_path')); + $this->edebug("Envelope sender: {$this->Sender}"); + $this->edebug("To: {$to}"); + $this->edebug("Subject: {$subject}"); + $this->edebug("Headers: {$header}"); if (!$this->UseSendmailOptions || null === $params) { $result = @mail($to, $subject, $body, $header); } else { + $this->edebug("Additional params: {$params}"); $result = @mail($to, $subject, $body, $header, $params); } - + $this->edebug('Result: ' . ($result ? 'true' : 'false')); return $result; } /** - * Output debugging info via user-defined method. - * Only generates output if SMTP debug output is enabled (@see SMTP::$do_debug). + * Output debugging info via a user-defined method. + * Only generates output if debug output is enabled. * * @see PHPMailer::$Debugoutput * @see PHPMailer::$SMTPDebug @@ -897,6 +909,7 @@ class PHPMailer switch ($this->Debugoutput) { case 'error_log': //Don't output, just log + /** @noinspection ForgottenDebugOutputInspection */ error_log($str); break; case 'html': @@ -1066,7 +1079,7 @@ class PHPMailer $name = trim(preg_replace('/[\r\n]+/', '', $name)); //Strip breaks and trim $pos = strrpos($address, '@'); if (false === $pos) { - // At-sign is missing. + //At-sign is missing. $error_message = sprintf( '%s (%s): %s', $this->lang('invalid_address'), @@ -1082,7 +1095,7 @@ class PHPMailer return false; } $params = [$kind, $address, $name]; - // Enqueue addresses with IDN until we know the PHPMailer::$CharSet. + //Enqueue addresses with IDN until we know the PHPMailer::$CharSet. if (static::idnSupported() && $this->has8bitChars(substr($address, ++$pos))) { if ('Reply-To' !== $kind) { if (!array_key_exists($address, $this->RecipientsQueue)) { @@ -1099,7 +1112,7 @@ class PHPMailer return false; } - // Immediately add standard addresses without IDN. + //Immediately add standard addresses without IDN. return call_user_func_array([$this, 'addAnAddress'], $params); } @@ -1172,19 +1185,39 @@ class PHPMailer * * @param string $addrstr The address list string * @param bool $useimap Whether to use the IMAP extension to parse the list + * @param string $charset The charset to use when decoding the address list string. * * @return array */ - public static function parseAddresses($addrstr, $useimap = true) + public static function parseAddresses($addrstr, $useimap = true, $charset = self::CHARSET_ISO88591) { $addresses = []; if ($useimap && 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. + imap_errors(); foreach ($list as $address) { - if (('.SYNTAX-ERROR.' !== $address->host) && static::validateAddress( - $address->mailbox . '@' . $address->host - )) { + if ( + '.SYNTAX-ERROR.' !== $address->host && + static::validateAddress($address->mailbox . '@' . $address->host) + ) { + //Decode the name part if it's present and 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) + ) { + $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); + } + $addresses[] = [ 'name' => (property_exists($address, 'personal') ? $address->personal : ''), 'address' => $address->mailbox . '@' . $address->host, @@ -1208,9 +1241,22 @@ class PHPMailer } 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[] = [ - 'name' => trim(str_replace(['"', "'"], '', $name)), + //Remove any surrounding quotes and spaces from the name + 'name' => trim($name, '\'" '), 'address' => $email, ]; } @@ -1236,9 +1282,10 @@ class PHPMailer { $address = trim($address); $name = trim(preg_replace('/[\r\n]+/', '', $name)); //Strip breaks and trim - // Don't validate now addresses with IDN. Will be done in send(). + //Don't validate now addresses with IDN. Will be done in send(). $pos = strrpos($address, '@'); - if ((false === $pos) + if ( + (false === $pos) || ((!$this->has8bitChars(substr($address, ++$pos)) || !static::idnSupported()) && !static::validateAddress($address)) ) { @@ -1306,8 +1353,9 @@ class PHPMailer if (null === $patternselect) { $patternselect = static::$validator; } - if (is_callable($patternselect)) { - return $patternselect($address); + //Don't allow strings as callables, see SECURITY.md and CVE-2021-3603 + if (is_callable($patternselect) && !is_string($patternselect)) { + return call_user_func($patternselect, $address); } //Reject line breaks in addresses; it's valid RFC5322, but not RFC5321 if (strpos($address, "\n") !== false || strpos($address, "\r") !== false) { @@ -1348,7 +1396,7 @@ class PHPMailer /* * This is the pattern used in the HTML5 spec for validation of 'email' type form input elements. * - * @see http://www.whatwg.org/specs/web-apps/current-work/#e-mail-state-(type=email) + * @see https://html.spec.whatwg.org/#e-mail-state-(type=email) */ return (bool) preg_match( '/^[a-zA-Z0-9.!#$%&\'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}' . @@ -1388,23 +1436,33 @@ class PHPMailer */ public function punyencodeAddress($address) { - // Verify we have required functions, CharSet, and at-sign. + //Verify we have required functions, CharSet, and at-sign. $pos = strrpos($address, '@'); - if (!empty($this->CharSet) && + if ( + !empty($this->CharSet) && false !== $pos && static::idnSupported() ) { $domain = substr($address, ++$pos); - // Verify CharSet string is a valid one, and domain properly encoded in this CharSet. + //Verify CharSet string is a valid one, and domain properly encoded in this CharSet. if ($this->has8bitChars($domain) && @mb_check_encoding($domain, $this->CharSet)) { - $domain = mb_convert_encoding($domain, 'UTF-8', $this->CharSet); + //Convert the domain from whatever charset it's in to UTF-8 + $domain = mb_convert_encoding($domain, self::CHARSET_UTF8, $this->CharSet); //Ignore IDE complaints about this line - method signature changed in PHP 5.4 $errorcode = 0; if (defined('INTL_IDNA_VARIANT_UTS46')) { - $punycode = idn_to_ascii($domain, $errorcode, INTL_IDNA_VARIANT_UTS46); + //Use the current punycode standard (appeared in PHP 7.2) + $punycode = idn_to_ascii( + $domain, + \IDNA_DEFAULT | \IDNA_USE_STD3_RULES | \IDNA_CHECK_BIDI | + \IDNA_CHECK_CONTEXTJ | \IDNA_NONTRANSITIONAL_TO_ASCII, + \INTL_IDNA_VARIANT_UTS46 + ); } elseif (defined('INTL_IDNA_VARIANT_2003')) { - $punycode = idn_to_ascii($domain, $errorcode, INTL_IDNA_VARIANT_2003); + //Fall back to this old, deprecated/removed encoding + $punycode = idn_to_ascii($domain, $errorcode, \INTL_IDNA_VARIANT_2003); } else { + //Fall back to a default we don't know about $punycode = idn_to_ascii($domain, $errorcode); } if (false !== $punycode) { @@ -1452,8 +1510,9 @@ class PHPMailer */ public function preSend() { - if ('smtp' === $this->Mailer - || ('mail' === $this->Mailer && stripos(PHP_OS, 'WIN') === 0) + if ( + 'smtp' === $this->Mailer + || ('mail' === $this->Mailer && (\PHP_VERSION_ID >= 80000 || stripos(PHP_OS, 'WIN') === 0)) ) { //SMTP mandates RFC-compliant line endings //and it's also used with mail() on Windows @@ -1463,25 +1522,21 @@ class PHPMailer static::setLE(PHP_EOL); } //Check for buggy PHP versions that add a header with an incorrect line break - if ('mail' === $this->Mailer - && ((PHP_VERSION_ID >= 70000 && PHP_VERSION_ID < 70017) - || (PHP_VERSION_ID >= 70100 && PHP_VERSION_ID < 70103)) + if ( + 'mail' === $this->Mailer + && ((\PHP_VERSION_ID >= 70000 && \PHP_VERSION_ID < 70017) + || (\PHP_VERSION_ID >= 70100 && \PHP_VERSION_ID < 70103)) && ini_get('mail.add_x_header') === '1' && stripos(PHP_OS, 'WIN') === 0 ) { - trigger_error( - 'Your version of PHP is affected by a bug that may result in corrupted messages.' . - ' To fix it, switch to sending using SMTP, disable the mail.add_x_header option in' . - ' your php.ini, switch to MacOS or Linux, or upgrade your PHP to version 7.0.17+ or 7.1.3+.', - E_USER_WARNING - ); + trigger_error($this->lang('buggy_php'), E_USER_WARNING); } try { - $this->error_count = 0; // Reset errors + $this->error_count = 0; //Reset errors $this->mailHeader = ''; - // Dequeue recipient and Reply-To addresses with IDN + //Dequeue recipient and Reply-To addresses with IDN foreach (array_merge($this->RecipientsQueue, $this->ReplyToQueue) as $params) { $params[1] = $this->punyencodeAddress($params[1]); call_user_func_array([$this, 'addAnAddress'], $params); @@ -1490,7 +1545,7 @@ class PHPMailer throw new Exception($this->lang('provide_address'), self::STOP_CRITICAL); } - // Validate From, Sender, and ConfirmReadingTo addresses + //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)) { @@ -1514,29 +1569,29 @@ class PHPMailer } } - // Set whether the message is multipart/alternative + //Set whether the message is multipart/alternative if ($this->alternativeExists()) { $this->ContentType = static::CONTENT_TYPE_MULTIPART_ALTERNATIVE; } $this->setMessageType(); - // Refuse to send an empty message unless we are specifically allowing it + //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); } //Trim subject consistently $this->Subject = trim($this->Subject); - // Create body before headers in case body makes changes to headers (e.g. altering transfer encoding) + //Create body before headers in case body makes changes to headers (e.g. altering transfer encoding) $this->MIMEHeader = ''; $this->MIMEBody = $this->createBody(); - // createBody may have added some headers, so retain them + //createBody may have added some headers, so retain them $tempheaders = $this->MIMEHeader; $this->MIMEHeader = $this->createHeader(); $this->MIMEHeader .= $tempheaders; - // To capture the complete message when using mail(), create - // an extra header list which createHeader() doesn't fold in + //To capture the complete message when using mail(), create + //an extra header list which createHeader() doesn't fold in if ('mail' === $this->Mailer) { if (count($this->to) > 0) { $this->mailHeader .= $this->addrAppend('To', $this->to); @@ -1549,8 +1604,9 @@ class PHPMailer ); } - // Sign with DKIM if enabled - if (!empty($this->DKIM_domain) + //Sign with DKIM if enabled + if ( + !empty($this->DKIM_domain) && !empty($this->DKIM_selector) && (!empty($this->DKIM_private_string) || (!empty($this->DKIM_private) @@ -1589,7 +1645,7 @@ class PHPMailer public function postSend() { try { - // Choose the mailer and send through it + //Choose the mailer and send through it switch ($this->Mailer) { case 'sendmail': case 'qmail': @@ -1607,6 +1663,9 @@ class PHPMailer return $this->mailSend($this->MIMEHeader, $this->MIMEBody); } } catch (Exception $exc) { + if ($this->Mailer === 'smtp' && $this->SMTPKeepAlive == true) { + $this->smtp->reset(); + } $this->setError($exc->getMessage()); $this->edebug($exc->getMessage()); if ($this->exceptions) { @@ -1631,22 +1690,47 @@ class PHPMailer */ protected function sendmailSend($header, $body) { + if ($this->Mailer === 'qmail') { + $this->edebug('Sending with qmail'); + } else { + $this->edebug('Sending with sendmail'); + } $header = static::stripTrailingWSP($header) . static::$LE . static::$LE; + //This sets the SMTP envelope sender which gets turned into a return-path header by the receiver + //A space after `-f` is optional, but there is a long history of its presence + //causing problems, so we don't use one + //Exim docs: http://www.exim.org/exim-html-current/doc/html/spec_html/ch-the_exim_command_line.html + //Sendmail docs: http://www.sendmail.org/~ca/email/man/sendmail.html + //Qmail docs: http://www.qmail.org/man/man8/qmail-inject.html + //Example problem: https://www.drupal.org/node/1057954 - // CVE-2016-10033, CVE-2016-10045: Don't pass -f if characters will be escaped. - if (!empty($this->Sender) && self::isShellSafe($this->Sender)) { - if ('qmail' === $this->Mailer) { + //PHP 5.6 workaround + $sendmail_from_value = ini_get('sendmail_from'); + if (empty($this->Sender) && !empty($sendmail_from_value)) { + //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. + 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 ('qmail' === $this->Mailer) { - $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'; } $sendmail = sprintf($sendmailFmt, escapeshellcmd($this->Sendmail), $this->Sender); + $this->edebug('Sendmail path: ' . $this->Sendmail); + $this->edebug('Sendmail command: ' . $sendmail); + $this->edebug('Envelope sender: ' . $this->Sender); + $this->edebug("Headers: {$header}"); if ($this->SingleTo) { foreach ($this->SingleToArray as $toAddr) { @@ -1654,13 +1738,15 @@ class PHPMailer if (!$mail) { throw new Exception($this->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), - [$toAddr], + [[$addrinfo['address'], $addrinfo['name']]], $this->cc, $this->bcc, $this->Subject, @@ -1668,6 +1754,7 @@ class PHPMailer $this->From, [] ); + $this->edebug("Result: " . ($result === 0 ? 'true' : 'false')); if (0 !== $result) { throw new Exception($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL); } @@ -1690,6 +1777,7 @@ class PHPMailer $this->From, [] ); + $this->edebug("Result: " . ($result === 0 ? 'true' : 'false')); if (0 !== $result) { throw new Exception($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL); } @@ -1710,8 +1798,15 @@ class PHPMailer */ protected static function isShellSafe($string) { - // Future-proof - if (escapeshellcmd($string) !== $string + //It's not possible to use shell commands safely (which includes the mail() function) without escapeshellarg, + //but some hosting providers disable it, creating a security problem that we don't want to have to deal with, + //so we don't. + if (!function_exists('escapeshellarg') || !function_exists('escapeshellcmd')) { + return false; + } + + if ( + escapeshellcmd($string) !== $string || !in_array(escapeshellarg($string), ["'$string'", "\"$string\""]) ) { return false; @@ -1722,9 +1817,9 @@ class PHPMailer for ($i = 0; $i < $length; ++$i) { $c = $string[$i]; - // All other characters have a special meaning in at least one common shell, including = and +. - // Full stop (.) has a special meaning in cmd.exe, but its impact should be negligible here. - // Note that this does permit non-Latin alphanumeric characters based on the current locale. + //All other characters have a special meaning in at least one common shell, including = and +. + //Full stop (.) has a special meaning in cmd.exe, but its impact should be negligible here. + //Note that this does permit non-Latin alphanumeric characters based on the current locale. if (!ctype_alnum($c) && strpos('@_-.', $c) === false) { return false; } @@ -1744,7 +1839,28 @@ class PHPMailer */ protected static function isPermittedPath($path) { - return !preg_match('#^[a-z]+://#i', $path); + //Matches scheme definition from https://tools.ietf.org/html/rfc3986#section-3.1 + return !preg_match('#^[a-z][a-z\d+.-]*://#i', $path); + } + + /** + * Check whether a file path is safe, accessible, and readable. + * + * @param string $path A relative or absolute path to a file + * + * @return bool + */ + protected static function fileIsAccessible($path) + { + if (!static::isPermittedPath($path)) { + return false; + } + $readable = file_exists($path); + //If not a UNC path (expected to start with \\), check read permission, see #2069 + if (strpos($path, '\\\\') !== 0) { + $readable = $readable && is_readable($path); + } + return $readable; } /** @@ -1777,11 +1893,18 @@ class PHPMailer //Sendmail docs: http://www.sendmail.org/~ca/email/man/sendmail.html //Qmail docs: http://www.qmail.org/man/man8/qmail-inject.html //Example problem: https://www.drupal.org/node/1057954 - // CVE-2016-10033, CVE-2016-10045: Don't pass -f if characters will be escaped. - if (!empty($this->Sender) && static::validateAddress($this->Sender) && self::isShellSafe($this->Sender)) { - $params = sprintf('-f%s', $this->Sender); + //CVE-2016-10033, CVE-2016-10045: Don't pass -f if characters will be escaped. + + //PHP 5.6 workaround + $sendmail_from_value = ini_get('sendmail_from'); + if (empty($this->Sender) && !empty($sendmail_from_value)) { + //PHP config has a sender address we can use + $this->Sender = ini_get('sendmail_from'); } if (!empty($this->Sender) && static::validateAddress($this->Sender)) { + if (self::isShellSafe($this->Sender)) { + $params = sprintf('-f%s', $this->Sender); + } $old_from = ini_get('sendmail_from'); ini_set('sendmail_from', $this->Sender); } @@ -1789,7 +1912,17 @@ class PHPMailer if ($this->SingleTo && count($toArr) > 1) { foreach ($toArr as $toAddr) { $result = $this->mailPassthru($toAddr, $this->Subject, $body, $header, $params); - $this->doCallback($result, [$toAddr], $this->cc, $this->bcc, $this->Subject, $body, $this->From, []); + $addrinfo = static::parseAddresses($toAddr, true, $this->CharSet); + $this->doCallback( + $result, + [[$addrinfo['address'], $addrinfo['name']]], + $this->cc, + $this->bcc, + $this->Subject, + $body, + $this->From, + [] + ); } } else { $result = $this->mailPassthru($to, $this->Subject, $body, $header, $params); @@ -1867,7 +2000,7 @@ class PHPMailer } $callbacks = []; - // Attempt to send to all recipients + //Attempt to send to all recipients foreach ([$this->to, $this->cc, $this->bcc] as $togroup) { foreach ($togroup as $to) { if (!$this->smtp->recipient($to[0], $this->dsn)) { @@ -1878,11 +2011,11 @@ class PHPMailer $isSent = true; } - $callbacks[] = ['issent'=>$isSent, 'to'=>$to[0]]; + $callbacks[] = ['issent' => $isSent, 'to' => $to[0], 'name' => $to[1]]; } } - // Only send the DATA command if we have viable recipients + //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); } @@ -1899,7 +2032,7 @@ class PHPMailer foreach ($callbacks as $cb) { $this->doCallback( $cb['issent'], - [$cb['to']], + [[$cb['to'], $cb['name']]], [], [], $this->Subject, @@ -1944,7 +2077,7 @@ class PHPMailer $options = $this->SMTPOptions; } - // Already connected? + //Already connected? if ($this->smtp->connected()) { return true; } @@ -1958,20 +2091,22 @@ class PHPMailer foreach ($hosts as $hostentry) { $hostinfo = []; - if (!preg_match( - '/^(?:(ssl|tls):\/\/)?(.+?)(?::(\d+))?$/', - trim($hostentry), - $hostinfo - )) { + if ( + !preg_match( + '/^(?:(ssl|tls):\/\/)?(.+?)(?::(\d+))?$/', + trim($hostentry), + $hostinfo + ) + ) { $this->edebug($this->lang('invalid_hostentry') . ' ' . trim($hostentry)); - // Not a valid host entry + //Not a valid host entry continue; } - // $hostinfo[1]: optional ssl or tls prefix - // $hostinfo[2]: the hostname - // $hostinfo[3]: optional port number - // The host string prefix can temporarily override the current setting for SMTPSecure - // If it's not specified, the default value is used + //$hostinfo[1]: optional ssl or tls prefix + //$hostinfo[2]: the hostname + //$hostinfo[3]: optional port number + //The host string prefix can temporarily override the current setting for SMTPSecure + //If it's not specified, the default value is used //Check the host name is a valid name or IP address before trying to use it if (!static::isValidHost($hostinfo[2])) { @@ -1983,11 +2118,11 @@ class PHPMailer $tls = (static::ENCRYPTION_STARTTLS === $this->SMTPSecure); if ('ssl' === $hostinfo[1] || ('' === $hostinfo[1] && static::ENCRYPTION_SMTPS === $this->SMTPSecure)) { $prefix = 'ssl://'; - $tls = false; // Can't have SSL and TLS at the same time + $tls = false; //Can't have SSL and TLS at the same time $secure = static::ENCRYPTION_SMTPS; } elseif ('tls' === $hostinfo[1]) { $tls = true; - // tls doesn't use a prefix + //TLS doesn't use a prefix $secure = static::ENCRYPTION_STARTTLS; } //Do we need the OpenSSL extension? @@ -2000,7 +2135,12 @@ class PHPMailer } $host = $hostinfo[2]; $port = $this->Port; - if (array_key_exists(3, $hostinfo) && is_numeric($hostinfo[3]) && $hostinfo[3] > 0 && $hostinfo[3] < 65536) { + if ( + array_key_exists(3, $hostinfo) && + is_numeric($hostinfo[3]) && + $hostinfo[3] > 0 && + $hostinfo[3] < 65536 + ) { $port = (int) $hostinfo[3]; } if ($this->smtp->connect($prefix . $host, $port, $this->Timeout, $options)) { @@ -2012,26 +2152,29 @@ class PHPMailer } $this->smtp->hello($hello); //Automatically enable TLS encryption if: - // * it's not disabled - // * we have openssl extension - // * we are not already using SSL - // * the server offers STARTTLS + //* it's not disabled + //* we have openssl extension + //* we are not already using SSL + //* the server offers STARTTLS if ($this->SMTPAutoTLS && $sslext && 'ssl' !== $secure && $this->smtp->getServerExt('STARTTLS')) { $tls = true; } if ($tls) { if (!$this->smtp->startTLS()) { - throw new Exception($this->lang('connect_host')); + $message = $this->getSmtpErrorMessage('connect_host'); + throw new Exception($message); } - // We must resend EHLO after TLS negotiation + //We must resend EHLO after TLS negotiation $this->smtp->hello($hello); } - if ($this->SMTPAuth && !$this->smtp->authenticate( - $this->Username, - $this->Password, - $this->AuthType, - $this->oauth - )) { + if ( + $this->SMTPAuth && !$this->smtp->authenticate( + $this->Username, + $this->Password, + $this->AuthType, + $this->oauth + ) + ) { throw new Exception($this->lang('authenticate')); } @@ -2039,16 +2182,20 @@ class PHPMailer } catch (Exception $exc) { $lastexception = $exc; $this->edebug($exc->getMessage()); - // We must have connected, but then failed TLS or Auth, so close connection nicely + //We must have connected, but then failed TLS or Auth, so close connection nicely $this->smtp->quit(); } } } - // If we get here, all connection attempts have failed, so close connection hard + //If we get here, all connection attempts have failed, so close connection hard $this->smtp->close(); - // As we've caught all exceptions, just report whatever the last one was + //As we've caught all exceptions, just report whatever the last one was if ($this->exceptions && null !== $lastexception) { throw $lastexception; + } elseif ($this->exceptions) { + // no exception was thrown, likely $this->smtp->connect() failed + $message = $this->getSmtpErrorMessage('connect_host'); + throw new Exception($message); } return false; @@ -2067,17 +2214,19 @@ class PHPMailer /** * Set the language for error messages. - * Returns false if it cannot load the language file. * The default language is English. * * @param string $langcode ISO 639-1 2-character language code (e.g. French is "fr") + * Optionally, the language code can be enhanced with a 4-character + * script annotation and/or a 2-character country annotation. * @param string $lang_path Path to the language file directory, with trailing separator (slash) + * Do not set this from user input! * - * @return bool + * @return bool Returns true if the requested language was loaded, false otherwise. */ public function setLanguage($langcode = 'en', $lang_path = '') { - // Backwards compatibility for renamed language codes + //Backwards compatibility for renamed language codes $renamed_langcodes = [ 'br' => 'pt_br', 'cz' => 'cs', @@ -2089,58 +2238,109 @@ class PHPMailer 'am' => 'hy', ]; - if (isset($renamed_langcodes[$langcode])) { + if (array_key_exists($langcode, $renamed_langcodes)) { $langcode = $renamed_langcodes[$langcode]; } - // Define full set of translatable strings in English + //Define full set of translatable strings in English $PHPMAILER_LANG = [ 'authenticate' => 'SMTP Error: Could not authenticate.', + 'buggy_php' => 'Your version of PHP is affected by a bug that may result in corrupted messages.' . + ' To fix it, switch to sending using SMTP, disable the mail.add_x_header option in' . + ' your php.ini, switch to MacOS or Linux, or upgrade your PHP to version 7.0.17+ or 7.1.3+.', 'connect_host' => 'SMTP Error: Could not connect to SMTP host.', 'data_not_accepted' => 'SMTP Error: data not accepted.', 'empty_message' => 'Message body empty', 'encoding' => 'Unknown encoding: ', 'execute' => 'Could not execute: ', + 'extension_missing' => 'Extension missing: ', 'file_access' => 'Could not access file: ', 'file_open' => 'File Error: Could not open file: ', 'from_failed' => 'The following From address failed: ', 'instantiate' => 'Could not instantiate mail function.', 'invalid_address' => 'Invalid address: ', + 'invalid_header' => 'Invalid header name or value', 'invalid_hostentry' => 'Invalid hostentry: ', 'invalid_host' => 'Invalid host: ', 'mailer_not_supported' => ' mailer is not supported.', 'provide_address' => 'You must provide at least one recipient email address.', 'recipients_failed' => 'SMTP Error: The following recipients failed: ', 'signing' => 'Signing Error: ', + 'smtp_code' => 'SMTP code: ', + 'smtp_code_ex' => 'Additional SMTP info: ', 'smtp_connect_failed' => 'SMTP connect() failed.', + 'smtp_detail' => 'Detail: ', 'smtp_error' => 'SMTP server error: ', 'variable_set' => 'Cannot set or reset variable: ', - 'extension_missing' => 'Extension missing: ', ]; if (empty($lang_path)) { - // Calculate an absolute path so it can work if CWD is not here + //Calculate an absolute path so it can work if CWD is not here $lang_path = dirname(__DIR__) . DIRECTORY_SEPARATOR . 'language' . DIRECTORY_SEPARATOR; } + //Validate $langcode - if (!preg_match('/^[a-z]{2}(?:_[a-zA-Z]{2})?$/', $langcode)) { + $foundlang = true; + $langcode = strtolower($langcode); + if ( + !preg_match('/^(?P[a-z]{2})(?P