aboutsummaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorGravatar Brooke <bandonrandon@gmail.com> 2020-05-18 00:42:04 -0700
committerGravatar GitHub <noreply@github.com> 2020-05-18 09:42:04 +0200
commitfa56f90223ce44c34b2919567d163ca4f2e81f65 (patch)
tree14eb7427ef32953a82c1fa3d32bc5245dce3f626 /lib
parent83b5944dcbb09db0accd528be4906b88590d924f (diff)
Update/php mailer (#2980)
* Update PHPMailer to 6.1.5 This PR update the bundled version of PHPMAiler
Diffstat (limited to 'lib')
-rw-r--r--lib/PHPMailer/PHPMailer/Exception.php2
-rw-r--r--lib/PHPMailer/PHPMailer/PHPMailer.php1332
-rw-r--r--lib/PHPMailer/PHPMailer/SMTP.php135
3 files changed, 916 insertions, 553 deletions
diff --git a/lib/PHPMailer/PHPMailer/Exception.php b/lib/PHPMailer/PHPMailer/Exception.php
index 9a05dec3c..b1e552f50 100644
--- a/lib/PHPMailer/PHPMailer/Exception.php
+++ b/lib/PHPMailer/PHPMailer/Exception.php
@@ -23,7 +23,7 @@ namespace PHPMailer\PHPMailer;
/**
* PHPMailer exception handler.
*
- * @author Marcus Bointon <phpmailer@synchromedia.co.uk>
+ * @author Marcus Bointon <phpmailer@synchromedia.co.uk>
*/
class Exception extends \Exception
{
diff --git a/lib/PHPMailer/PHPMailer/PHPMailer.php b/lib/PHPMailer/PHPMailer/PHPMailer.php
index 52104924d..fddad40ac 100644
--- a/lib/PHPMailer/PHPMailer/PHPMailer.php
+++ b/lib/PHPMailer/PHPMailer/PHPMailer.php
@@ -3,13 +3,13 @@
* PHPMailer - PHP email creation and transport class.
* PHP Version 5.5.
*
- * @see https://github.com/PHPMailer/PHPMailer/ The PHPMailer GitHub project
+ * @see https://github.com/PHPMailer/PHPMailer/ The PHPMailer GitHub project
*
* @author Marcus Bointon (Synchro/coolbru) <phpmailer@synchromedia.co.uk>
* @author Jim Jagielski (jimjag) <jimjag@gmail.com>
* @author Andy Prevost (codeworxtech) <codeworxtech@users.sourceforge.net>
* @author Brent R. Matzelle (original founder)
- * @copyright 2012 - 2017 Marcus Bointon
+ * @copyright 2012 - 2019 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
@@ -23,13 +23,14 @@ namespace PHPMailer\PHPMailer;
/**
* PHPMailer - PHP email creation and transport class.
*
- * @author Marcus Bointon (Synchro/coolbru) <phpmailer@synchromedia.co.uk>
- * @author Jim Jagielski (jimjag) <jimjag@gmail.com>
- * @author Andy Prevost (codeworxtech) <codeworxtech@users.sourceforge.net>
- * @author Brent R. Matzelle (original founder)
+ * @author Marcus Bointon (Synchro/coolbru) <phpmailer@synchromedia.co.uk>
+ * @author Jim Jagielski (jimjag) <jimjag@gmail.com>
+ * @author Andy Prevost (codeworxtech) <codeworxtech@users.sourceforge.net>
+ * @author Brent R. Matzelle (original founder)
*/
class PHPMailer
{
+ const CHARSET_ASCII = 'us-ascii';
const CHARSET_ISO88591 = 'iso-8859-1';
const CHARSET_UTF8 = 'utf-8';
@@ -46,12 +47,24 @@ class PHPMailer
const ENCODING_BINARY = 'binary';
const ENCODING_QUOTED_PRINTABLE = 'quoted-printable';
+ const ENCRYPTION_STARTTLS = 'tls';
+ const ENCRYPTION_SMTPS = 'ssl';
+
+ const ICAL_METHOD_REQUEST = 'REQUEST';
+ const ICAL_METHOD_PUBLISH = 'PUBLISH';
+ const ICAL_METHOD_REPLY = 'REPLY';
+ const ICAL_METHOD_ADD = 'ADD';
+ const ICAL_METHOD_CANCEL = 'CANCEL';
+ const ICAL_METHOD_REFRESH = 'REFRESH';
+ const ICAL_METHOD_COUNTER = 'COUNTER';
+ const ICAL_METHOD_DECLINECOUNTER = 'DECLINECOUNTER';
+
/**
* Email priority.
* Options: null (default), 1 = High, 3 = Normal, 5 = low.
* When null, the header is not set at all.
*
- * @var int
+ * @var int|null
*/
public $Priority;
@@ -146,6 +159,22 @@ class PHPMailer
public $Ical = '';
/**
+ * Value-array of "method" in Contenttype header "text/calendar"
+ *
+ * @var string[]
+ */
+ protected static $IcalMethods = [
+ self::ICAL_METHOD_REQUEST,
+ self::ICAL_METHOD_PUBLISH,
+ self::ICAL_METHOD_REPLY,
+ self::ICAL_METHOD_ADD,
+ self::ICAL_METHOD_CANCEL,
+ self::ICAL_METHOD_REFRESH,
+ self::ICAL_METHOD_COUNTER,
+ self::ICAL_METHOD_DECLINECOUNTER,
+ ];
+
+ /**
* The complete compiled MIME message body.
*
* @var string
@@ -212,6 +241,8 @@ class PHPMailer
* $_SERVER['SERVER_NAME'], gethostname(), php_uname('n'), or the value
* 'localhost.localdomain'.
*
+ * @see PHPMailer::$Helo
+ *
* @var string
*/
public $Hostname = '';
@@ -258,7 +289,7 @@ class PHPMailer
public $Port = 25;
/**
- * The SMTP HELO of the message.
+ * The SMTP HELO/EHLO name used for the SMTP connection.
* Default is $Hostname. If $Hostname is empty, PHPMailer attempts to find
* one with the same method described above for $Hostname.
*
@@ -270,7 +301,7 @@ class PHPMailer
/**
* What kind of encryption to use on the SMTP connection.
- * Options: '', 'ssl' or 'tls'.
+ * Options: '', static::ENCRYPTION_STARTTLS, or static::ENCRYPTION_SMTPS.
*
* @var string
*/
@@ -341,14 +372,27 @@ class PHPMailer
public $Timeout = 300;
/**
+ * Comma separated list of DSN notifications
+ * 'NEVER' under no circumstances a DSN must be returned to the sender.
+ * If you use NEVER all other notifications will be ignored.
+ * 'SUCCESS' will notify you when your mail has arrived at its destination.
+ * 'FAILURE' will arrive if an error occurred during delivery.
+ * 'DELAY' will notify you if there is an unusual delay in delivery, but the actual
+ * delivery's outcome (success or failure) is not yet decided.
+ *
+ * @see https://tools.ietf.org/html/rfc3461 See section 4.1 for more information about NOTIFY
+ */
+ public $dsn = '';
+
+ /**
* SMTP class debug output mode.
* Debug output level.
* Options:
- * * `0` No output
- * * `1` Commands
- * * `2` Data and commands
- * * `3` As 2 plus connection status
- * * `4` Low-level data output.
+ * * 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::$do_debug
*
@@ -514,9 +558,9 @@ class PHPMailer
/**
* What to put in the X-Mailer header.
- * Options: An empty string for PHPMailer default, whitespace for none, or a string to use.
+ * Options: An empty string for PHPMailer default, whitespace/null for none, or a string to use.
*
- * @var string
+ * @var string|null
*/
public $XMailer = '';
@@ -701,7 +745,7 @@ class PHPMailer
*
* @var string
*/
- const VERSION = '6.0.7';
+ const VERSION = '6.1.5';
/**
* Error severity: message only, continue processing.
@@ -725,11 +769,32 @@ class PHPMailer
const STOP_CRITICAL = 2;
/**
- * SMTP RFC standard line ending.
+ * The SMTP standard CRLF line break.
+ * If you want to change line break format, change static::$LE, not this.
+ */
+ const CRLF = "\r\n";
+
+ /**
+ * "Folding White Space" a white space string used for line folding.
+ */
+ const FWS = ' ';
+
+ /**
+ * SMTP RFC standard line ending; Carriage Return, Line Feed.
*
* @var string
*/
- protected static $LE = "\r\n";
+ protected static $LE = self::CRLF;
+
+ /**
+ * The maximum line length supported by mail().
+ *
+ * Background: mail() will sometimes corrupt messages
+ * with headers headers longer than 65 chars, see #818.
+ *
+ * @var int
+ */
+ const MAIL_MAX_LINE_LENGTH = 63;
/**
* The maximum line length allowed by RFC 2822 section 2.1.1.
@@ -794,7 +859,7 @@ class PHPMailer
$subject = $this->encodeHeader($this->secureHeader($subject));
}
//Calling mail() with null params breaks
- if (!$this->UseSendmailOptions or null === $params) {
+ if (!$this->UseSendmailOptions || null === $params) {
$result = @mail($to, $subject, $body, $header);
} else {
$result = @mail($to, $subject, $body, $header, $params);
@@ -824,7 +889,7 @@ class PHPMailer
return;
}
//Avoid clash with built-in function names
- if (!in_array($this->Debugoutput, ['error_log', 'html', 'echo']) and is_callable($this->Debugoutput)) {
+ if (is_callable($this->Debugoutput) && !in_array($this->Debugoutput, ['error_log', 'html', 'echo'])) {
call_user_func($this->Debugoutput, $str, $this->SMTPDebug);
return;
@@ -845,12 +910,12 @@ class PHPMailer
case 'echo':
default:
//Normalize line breaks
- $str = preg_replace('/\r\n|\r/ms', "\n", $str);
+ $str = preg_replace('/\r\n|\r/m', "\n", $str);
echo gmdate('Y-m-d H:i:s'),
"\t",
//Trim trailing space
trim(
- //Indent for readability, except for trailing break
+ //Indent for readability, except for trailing break
str_replace(
"\n",
"\n \t ",
@@ -927,6 +992,8 @@ class PHPMailer
* @param string $address The email address to send to
* @param string $name
*
+ * @throws Exception
+ *
* @return bool true on success, false if address already used or invalid in some way
*/
public function addAddress($address, $name = '')
@@ -940,6 +1007,8 @@ class PHPMailer
* @param string $address The email address to send to
* @param string $name
*
+ * @throws Exception
+ *
* @return bool true on success, false if address already used or invalid in some way
*/
public function addCC($address, $name = '')
@@ -953,6 +1022,8 @@ class PHPMailer
* @param string $address The email address to send to
* @param string $name
*
+ * @throws Exception
+ *
* @return bool true on success, false if address already used or invalid in some way
*/
public function addBCC($address, $name = '')
@@ -966,6 +1037,8 @@ class PHPMailer
* @param string $address The email address to reply to
* @param string $name
*
+ * @throws Exception
+ *
* @return bool true on success, false if address already used or invalid in some way
*/
public function addReplyTo($address, $name = '')
@@ -994,10 +1067,12 @@ class PHPMailer
$pos = strrpos($address, '@');
if (false === $pos) {
// At-sign is missing.
- $error_message = sprintf('%s (%s): %s',
+ $error_message = sprintf(
+ '%s (%s): %s',
$this->lang('invalid_address'),
$kind,
- $address);
+ $address
+ );
$this->setError($error_message);
$this->edebug($error_message);
if ($this->exceptions) {
@@ -1008,19 +1083,17 @@ class PHPMailer
}
$params = [$kind, $address, $name];
// Enqueue addresses with IDN until we know the PHPMailer::$CharSet.
- if ($this->has8bitChars(substr($address, ++$pos)) and static::idnSupported()) {
- if ('Reply-To' != $kind) {
+ if (static::idnSupported() && $this->has8bitChars(substr($address, ++$pos))) {
+ if ('Reply-To' !== $kind) {
if (!array_key_exists($address, $this->RecipientsQueue)) {
$this->RecipientsQueue[$address] = $params;
return true;
}
- } else {
- if (!array_key_exists($address, $this->ReplyToQueue)) {
- $this->ReplyToQueue[$address] = $params;
+ } elseif (!array_key_exists($address, $this->ReplyToQueue)) {
+ $this->ReplyToQueue[$address] = $params;
- return true;
- }
+ return true;
}
return false;
@@ -1045,9 +1118,11 @@ class PHPMailer
protected function addAnAddress($kind, $address, $name = '')
{
if (!in_array($kind, ['to', 'cc', 'bcc', 'Reply-To'])) {
- $error_message = sprintf('%s: %s',
+ $error_message = sprintf(
+ '%s: %s',
$this->lang('Invalid recipient kind'),
- $kind);
+ $kind
+ );
$this->setError($error_message);
$this->edebug($error_message);
if ($this->exceptions) {
@@ -1057,10 +1132,12 @@ class PHPMailer
return false;
}
if (!static::validateAddress($address)) {
- $error_message = sprintf('%s (%s): %s',
+ $error_message = sprintf(
+ '%s (%s): %s',
$this->lang('invalid_address'),
$kind,
- $address);
+ $address
+ );
$this->setError($error_message);
$this->edebug($error_message);
if ($this->exceptions) {
@@ -1069,19 +1146,17 @@ class PHPMailer
return false;
}
- if ('Reply-To' != $kind) {
+ if ('Reply-To' !== $kind) {
if (!array_key_exists(strtolower($address), $this->all_recipients)) {
$this->{$kind}[] = [$address, $name];
$this->all_recipients[strtolower($address)] = true;
return true;
}
- } else {
- if (!array_key_exists(strtolower($address), $this->ReplyTo)) {
- $this->ReplyTo[strtolower($address)] = [$address, $name];
+ } elseif (!array_key_exists(strtolower($address), $this->ReplyTo)) {
+ $this->ReplyTo[strtolower($address)] = [$address, $name];
- return true;
- }
+ return true;
}
return false;
@@ -1093,7 +1168,7 @@ class PHPMailer
* Uses the imap_rfc822_parse_adrlist function if the IMAP extension is available.
* Note that quotes in the name part are removed.
*
- * @see http://www.andrew.cmu.edu/user/agreen1/testing/mrbs/web/Mail/RFC822.php A more careful implementation
+ * @see http://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
@@ -1103,17 +1178,17 @@ class PHPMailer
public static function parseAddresses($addrstr, $useimap = true)
{
$addresses = [];
- if ($useimap and function_exists('imap_rfc822_parse_adrlist')) {
+ if ($useimap && function_exists('imap_rfc822_parse_adrlist')) {
//Use this built-in parser if it's available
$list = imap_rfc822_parse_adrlist($addrstr, '');
foreach ($list as $address) {
- if ('.SYNTAX-ERROR.' != $address->host) {
- if (static::validateAddress($address->mailbox . '@' . $address->host)) {
- $addresses[] = [
- 'name' => (property_exists($address, 'personal') ? $address->personal : ''),
- 'address' => $address->mailbox . '@' . $address->host,
- ];
- }
+ if (('.SYNTAX-ERROR.' !== $address->host) && static::validateAddress(
+ $address->mailbox . '@' . $address->host
+ )) {
+ $addresses[] = [
+ 'name' => (property_exists($address, 'personal') ? $address->personal : ''),
+ 'address' => $address->mailbox . '@' . $address->host,
+ ];
}
}
} else {
@@ -1163,12 +1238,15 @@ class PHPMailer
$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, '@');
- if (false === $pos or
- (!$this->has8bitChars(substr($address, ++$pos)) or !static::idnSupported()) and
- !static::validateAddress($address)) {
- $error_message = sprintf('%s (From): %s',
+ if ((false === $pos)
+ || ((!$this->has8bitChars(substr($address, ++$pos)) || !static::idnSupported())
+ && !static::validateAddress($address))
+ ) {
+ $error_message = sprintf(
+ '%s (From): %s',
$this->lang('invalid_address'),
- $address);
+ $address
+ );
$this->setError($error_message);
$this->edebug($error_message);
if ($this->exceptions) {
@@ -1179,10 +1257,8 @@ class PHPMailer
}
$this->From = $address;
$this->FromName = $name;
- if ($auto) {
- if (empty($this->Sender)) {
- $this->Sender = $address;
- }
+ if ($auto && empty($this->Sender)) {
+ $this->Sender = $address;
}
return true;
@@ -1231,10 +1307,10 @@ class PHPMailer
$patternselect = static::$validator;
}
if (is_callable($patternselect)) {
- return call_user_func($patternselect, $address);
+ return $patternselect($address);
}
//Reject line breaks in addresses; it's valid RFC5322, but not RFC5321
- if (strpos($address, "\n") !== false or strpos($address, "\r") !== false) {
+ if (strpos($address, "\n") !== false || strpos($address, "\r") !== false) {
return false;
}
switch ($patternselect) {
@@ -1281,7 +1357,7 @@ class PHPMailer
);
case 'php':
default:
- return (bool) filter_var($address, FILTER_VALIDATE_EMAIL);
+ return filter_var($address, FILTER_VALIDATE_EMAIL) !== false;
}
}
@@ -1293,7 +1369,7 @@ class PHPMailer
*/
public static function idnSupported()
{
- return function_exists('idn_to_ascii') and function_exists('mb_convert_encoding');
+ return function_exists('idn_to_ascii') && function_exists('mb_convert_encoding');
}
/**
@@ -1304,7 +1380,7 @@ class PHPMailer
* - Conversion to punycode is impossible (e.g. required PHP functions are not available)
* or fails for any reason (e.g. domain contains characters not allowed in an IDN).
*
- * @see PHPMailer::$CharSet
+ * @see PHPMailer::$CharSet
*
* @param string $address The email address to convert
*
@@ -1314,17 +1390,23 @@ class PHPMailer
{
// Verify we have required functions, CharSet, and at-sign.
$pos = strrpos($address, '@');
- if (static::idnSupported() and
- !empty($this->CharSet) and
- false !== $pos
+ 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.
- if ($this->has8bitChars($domain) and @mb_check_encoding($domain, $this->CharSet)) {
+ if ($this->has8bitChars($domain) && @mb_check_encoding($domain, $this->CharSet)) {
$domain = mb_convert_encoding($domain, 'UTF-8', $this->CharSet);
//Ignore IDE complaints about this line - method signature changed in PHP 5.4
$errorcode = 0;
- $punycode = idn_to_ascii($domain, $errorcode, INTL_IDNA_VARIANT_UTS46);
+ if (defined('INTL_IDNA_VARIANT_UTS46')) {
+ $punycode = idn_to_ascii($domain, $errorcode, INTL_IDNA_VARIANT_UTS46);
+ } elseif (defined('INTL_IDNA_VARIANT_2003')) {
+ $punycode = idn_to_ascii($domain, $errorcode, INTL_IDNA_VARIANT_2003);
+ } else {
+ $punycode = idn_to_ascii($domain, $errorcode);
+ }
if (false !== $punycode) {
return substr($address, 0, $pos) . $punycode;
}
@@ -1370,24 +1452,22 @@ class PHPMailer
*/
public function preSend()
{
- if ('smtp' == $this->Mailer or
- ('mail' == $this->Mailer and stripos(PHP_OS, 'WIN') === 0)
+ if ('smtp' === $this->Mailer
+ || ('mail' === $this->Mailer && stripos(PHP_OS, 'WIN') === 0)
) {
//SMTP mandates RFC-compliant line endings
//and it's also used with mail() on Windows
- static::setLE("\r\n");
+ static::setLE(self::CRLF);
} else {
//Maintain backward compatibility with legacy Linux command line mailers
static::setLE(PHP_EOL);
}
//Check for buggy PHP versions that add a header with an incorrect line break
- if (ini_get('mail.add_x_header') == 1
- and 'mail' == $this->Mailer
- and stripos(PHP_OS, 'WIN') === 0
- and ((version_compare(PHP_VERSION, '7.0.0', '>=')
- and version_compare(PHP_VERSION, '7.0.17', '<'))
- or (version_compare(PHP_VERSION, '7.1.0', '>=')
- and version_compare(PHP_VERSION, '7.1.3', '<')))
+ 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.' .
@@ -1418,10 +1498,12 @@ class PHPMailer
}
$this->$address_kind = $this->punyencodeAddress($this->$address_kind);
if (!static::validateAddress($this->$address_kind)) {
- $error_message = sprintf('%s (%s): %s',
+ $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);
if ($this->exceptions) {
@@ -1439,7 +1521,7 @@ class PHPMailer
$this->setMessageType();
// Refuse to send an empty message unless we are specifically allowing it
- if (!$this->AllowEmpty and empty($this->Body)) {
+ if (!$this->AllowEmpty && empty($this->Body)) {
throw new Exception($this->lang('empty_message'), self::STOP_CRITICAL);
}
@@ -1455,7 +1537,7 @@ class PHPMailer
// To capture the complete message when using mail(), create
// an extra header list which createHeader() doesn't fold in
- if ('mail' == $this->Mailer) {
+ if ('mail' === $this->Mailer) {
if (count($this->to) > 0) {
$this->mailHeader .= $this->addrAppend('To', $this->to);
} else {
@@ -1469,11 +1551,11 @@ class PHPMailer
// Sign with DKIM if enabled
if (!empty($this->DKIM_domain)
- and !empty($this->DKIM_selector)
- and (!empty($this->DKIM_private_string)
- or (!empty($this->DKIM_private)
- and static::isPermittedPath($this->DKIM_private)
- and file_exists($this->DKIM_private)
+ && !empty($this->DKIM_selector)
+ && (!empty($this->DKIM_private_string)
+ || (!empty($this->DKIM_private)
+ && static::isPermittedPath($this->DKIM_private)
+ && file_exists($this->DKIM_private)
)
)
) {
@@ -1482,7 +1564,7 @@ class PHPMailer
$this->encodeHeader($this->secureHeader($this->Subject)),
$this->MIMEBody
);
- $this->MIMEHeader = rtrim($this->MIMEHeader, "\r\n ") . static::$LE .
+ $this->MIMEHeader = static::stripTrailingWSP($this->MIMEHeader) . static::$LE .
static::normalizeBreaks($header_dkim) . static::$LE;
}
@@ -1538,7 +1620,7 @@ class PHPMailer
/**
* Send mail using the $Sendmail program.
*
- * @see PHPMailer::$Sendmail
+ * @see PHPMailer::$Sendmail
*
* @param string $header The message headers
* @param string $body The message body
@@ -1549,19 +1631,19 @@ class PHPMailer
*/
protected function sendmailSend($header, $body)
{
+ $header = static::stripTrailingWSP($header) . static::$LE . static::$LE;
+
// CVE-2016-10033, CVE-2016-10045: Don't pass -f if characters will be escaped.
- if (!empty($this->Sender) and self::isShellSafe($this->Sender)) {
- if ('qmail' == $this->Mailer) {
+ if (!empty($this->Sender) && self::isShellSafe($this->Sender)) {
+ if ('qmail' === $this->Mailer) {
$sendmailFmt = '%s -f%s';
} else {
$sendmailFmt = '%s -oi -f%s -t';
}
+ } elseif ('qmail' === $this->Mailer) {
+ $sendmailFmt = '%s';
} else {
- if ('qmail' == $this->Mailer) {
- $sendmailFmt = '%s';
- } else {
- $sendmailFmt = '%s -oi -t';
- }
+ $sendmailFmt = '%s -oi -t';
}
$sendmail = sprintf($sendmailFmt, escapeshellcmd($this->Sendmail), $this->Sender);
@@ -1577,7 +1659,7 @@ class PHPMailer
fwrite($mail, $body);
$result = pclose($mail);
$this->doCallback(
- ($result == 0),
+ ($result === 0),
[$toAddr],
$this->cc,
$this->bcc,
@@ -1599,7 +1681,7 @@ class PHPMailer
fwrite($mail, $body);
$result = pclose($mail);
$this->doCallback(
- ($result == 0),
+ ($result === 0),
$this->to,
$this->cc,
$this->bcc,
@@ -1630,7 +1712,7 @@ class PHPMailer
{
// Future-proof
if (escapeshellcmd($string) !== $string
- or !in_array(escapeshellarg($string), ["'$string'", "\"$string\""])
+ || !in_array(escapeshellarg($string), ["'$string'", "\"$string\""])
) {
return false;
}
@@ -1668,7 +1750,7 @@ class PHPMailer
/**
* Send mail using the PHP mail() function.
*
- * @see http://www.php.net/manual/en/book.mail.php
+ * @see http://www.php.net/manual/en/book.mail.php
*
* @param string $header The message headers
* @param string $body The message body
@@ -1679,6 +1761,8 @@ class PHPMailer
*/
protected function mailSend($header, $body)
{
+ $header = static::stripTrailingWSP($header) . static::$LE . static::$LE;
+
$toArr = [];
foreach ($this->to as $toaddr) {
$toArr[] = $this->addrFormat($toaddr);
@@ -1687,24 +1771,22 @@ class PHPMailer
$params = null;
//This sets the SMTP envelope sender which gets turned into a return-path header by the receiver
- if (!empty($this->Sender) and static::validateAddress($this->Sender)) {
- //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 (self::isShellSafe($this->Sender)) {
- $params = sprintf('-f%s', $this->Sender);
- }
+ //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) && static::validateAddress($this->Sender) && self::isShellSafe($this->Sender)) {
+ $params = sprintf('-f%s', $this->Sender);
}
- if (!empty($this->Sender) and static::validateAddress($this->Sender)) {
+ if (!empty($this->Sender) && static::validateAddress($this->Sender)) {
$old_from = ini_get('sendmail_from');
ini_set('sendmail_from', $this->Sender);
}
$result = false;
- if ($this->SingleTo and count($toArr) > 1) {
+ 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, []);
@@ -1742,8 +1824,6 @@ class PHPMailer
/**
* Provide an instance to use for SMTP operations.
*
- * @param SMTP $smtp
- *
* @return SMTP
*/
public function setSMTPInstance(SMTP $smtp)
@@ -1770,12 +1850,13 @@ class PHPMailer
*/
protected function smtpSend($header, $body)
{
+ $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);
}
//Sender already validated in preSend()
- if ('' == $this->Sender) {
+ if ('' === $this->Sender) {
$smtp_from = $this->From;
} else {
$smtp_from = $this->Sender;
@@ -1789,7 +1870,7 @@ class PHPMailer
// 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])) {
+ if (!$this->smtp->recipient($to[0], $this->dsn)) {
$error = $this->smtp->getError();
$bad_rcpt[] = ['to' => $to[0], 'error' => $error['detail']];
$isSent = false;
@@ -1802,7 +1883,7 @@ class PHPMailer
}
// Only send the DATA command if we have viable recipients
- if ((count($this->all_recipients) > count($bad_rcpt)) and !$this->smtp->data($header . $body)) {
+ if ((count($this->all_recipients) > count($bad_rcpt)) && !$this->smtp->data($header . $body)) {
throw new Exception($this->lang('data_not_accepted'), self::STOP_CRITICAL);
}
@@ -1834,10 +1915,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($this->lang('recipients_failed') . $errstr, self::STOP_CONTINUE);
}
return true;
@@ -1881,50 +1959,49 @@ class PHPMailer
foreach ($hosts as $hostentry) {
$hostinfo = [];
if (!preg_match(
- '/^((ssl|tls):\/\/)*([a-zA-Z0-9\.-]*|\[[a-fA-F0-9:]+\]):?([0-9]*)$/',
+ '/^(?:(ssl|tls):\/\/)?(.+?)(?::(\d+))?$/',
trim($hostentry),
$hostinfo
)) {
- static::edebug($this->lang('connect_host') . ' ' . $hostentry);
+ $this->edebug($this->lang('invalid_hostentry') . ' ' . trim($hostentry));
// Not a valid host entry
continue;
}
- // $hostinfo[2]: optional ssl or tls prefix
- // $hostinfo[3]: the hostname
- // $hostinfo[4]: optional port number
+ // $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[3])) {
- static::edebug($this->lang('connect_host') . ' ' . $hostentry);
+ if (!static::isValidHost($hostinfo[2])) {
+ $this->edebug($this->lang('invalid_host') . ' ' . $hostinfo[2]);
continue;
}
$prefix = '';
$secure = $this->SMTPSecure;
- $tls = ('tls' == $this->SMTPSecure);
- if ('ssl' == $hostinfo[2] or ('' == $hostinfo[2] and 'ssl' == $this->SMTPSecure)) {
+ $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
- $secure = 'ssl';
- } elseif ('tls' == $hostinfo[2]) {
+ $secure = static::ENCRYPTION_SMTPS;
+ } elseif ('tls' === $hostinfo[1]) {
$tls = true;
// tls doesn't use a prefix
- $secure = 'tls';
+ $secure = static::ENCRYPTION_STARTTLS;
}
//Do we need the OpenSSL extension?
$sslext = defined('OPENSSL_ALGO_SHA256');
- if ('tls' === $secure or 'ssl' === $secure) {
+ 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);
}
}
- $host = $hostinfo[3];
+ $host = $hostinfo[2];
$port = $this->Port;
- $tport = (int) $hostinfo[4];
- if ($tport > 0 and $tport < 65536) {
- $port = $tport;
+ 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)) {
try {
@@ -1939,7 +2016,7 @@ class PHPMailer
// * we have openssl extension
// * we are not already using SSL
// * the server offers STARTTLS
- if ($this->SMTPAutoTLS and $sslext and 'ssl' != $secure and $this->smtp->getServerExt('STARTTLS')) {
+ if ($this->SMTPAutoTLS && $sslext && 'ssl' !== $secure && $this->smtp->getServerExt('STARTTLS')) {
$tls = true;
}
if ($tls) {
@@ -1949,16 +2026,13 @@ class PHPMailer
// We must resend EHLO after TLS negotiation
$this->smtp->hello($hello);
}
- if ($this->SMTPAuth) {
- if (!$this->smtp->authenticate(
- $this->Username,
- $this->Password,
- $this->AuthType,
- $this->oauth
- )
- ) {
- throw new Exception($this->lang('authenticate'));
- }
+ if ($this->SMTPAuth && !$this->smtp->authenticate(
+ $this->Username,
+ $this->Password,
+ $this->AuthType,
+ $this->oauth
+ )) {
+ throw new Exception($this->lang('authenticate'));
}
return true;
@@ -1973,7 +2047,7 @@ class PHPMailer
// 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
- if ($this->exceptions and null !== $lastexception) {
+ if ($this->exceptions && null !== $lastexception) {
throw $lastexception;
}
@@ -1985,11 +2059,9 @@ class PHPMailer
*/
public function smtpClose()
{
- if (null !== $this->smtp) {
- if ($this->smtp->connected()) {
- $this->smtp->quit();
- $this->smtp->close();
- }
+ if ((null !== $this->smtp) && $this->smtp->connected()) {
+ $this->smtp->quit();
+ $this->smtp->close();
}
}
@@ -2033,6 +2105,8 @@ class PHPMailer
'from_failed' => 'The following From address failed: ',
'instantiate' => 'Could not instantiate mail function.',
'invalid_address' => 'Invalid address: ',
+ '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: ',
@@ -2053,7 +2127,7 @@ class PHPMailer
$foundlang = true;
$lang_file = $lang_path . 'phpmailer.lang-' . $langcode . '.php';
// There is no English translation file
- if ('en' != $langcode) {
+ if ('en' !== $langcode) {
// Make sure language file path is readable
if (!static::isPermittedPath($lang_file) || !file_exists($lang_file)) {
$foundlang = false;
@@ -2113,9 +2187,8 @@ class PHPMailer
return $this->secureHeader($addr[0]);
}
- return $this->encodeHeader($this->secureHeader($addr[1]), 'phrase') . ' <' . $this->secureHeader(
- $addr[0]
- ) . '>';
+ return $this->encodeHeader($this->secureHeader($addr[1]), 'phrase') .
+ ' <' . $this->secureHeader($addr[0]) . '>';
}
/**
@@ -2145,7 +2218,7 @@ class PHPMailer
$message = static::normalizeBreaks($message);
//Remove a trailing line break
- if (substr($message, -$lelen) == static::$LE) {
+ if (substr($message, -$lelen) === static::$LE) {
$message = substr($message, 0, -$lelen);
}
@@ -2158,16 +2231,16 @@ class PHPMailer
$buf = '';
$firstword = true;
foreach ($words as $word) {
- if ($qp_mode and (strlen($word) > $length)) {
+ if ($qp_mode && (strlen($word) > $length)) {
$space_left = $length - strlen($buf) - $crlflen;
if (!$firstword) {
if ($space_left > 20) {
$len = $space_left;
if ($is_utf8) {
$len = $this->utf8CharBoundary($word, $len);
- } elseif ('=' == substr($word, $len - 1, 1)) {
+ } elseif ('=' === substr($word, $len - 1, 1)) {
--$len;
- } elseif ('=' == substr($word, $len - 2, 1)) {
+ } elseif ('=' === substr($word, $len - 2, 1)) {
$len -= 2;
}
$part = substr($word, 0, $len);
@@ -2179,22 +2252,22 @@ class PHPMailer
}
$buf = '';
}
- while (strlen($word) > 0) {
+ while ($word !== '') {
if ($length <= 0) {
break;
}
$len = $length;
if ($is_utf8) {
$len = $this->utf8CharBoundary($word, $len);
- } elseif ('=' == substr($word, $len - 1, 1)) {
+ } elseif ('=' === substr($word, $len - 1, 1)) {
--$len;
- } elseif ('=' == substr($word, $len - 2, 1)) {
+ } elseif ('=' === substr($word, $len - 2, 1)) {
$len -= 2;
}
$part = substr($word, 0, $len);
- $word = substr($word, $len);
+ $word = (string) substr($word, $len);
- if (strlen($word) > 0) {
+ if ($word !== '') {
$message .= $part . sprintf('=%s', static::$LE);
} else {
$buf = $part;
@@ -2207,7 +2280,7 @@ class PHPMailer
}
$buf .= $word;
- if (strlen($buf) > $length and '' != $buf_o) {
+ if ('' !== $buf_o && strlen($buf) > $length) {
$message .= $buf_o . $soft_break;
$buf = $word;
}
@@ -2302,23 +2375,21 @@ class PHPMailer
{
$result = '';
- $result .= $this->headerLine('Date', '' == $this->MessageDate ? self::rfcDate() : $this->MessageDate);
+ $result .= $this->headerLine('Date', '' === $this->MessageDate ? self::rfcDate() : $this->MessageDate);
// To be created automatically by mail()
if ($this->SingleTo) {
- if ('mail' != $this->Mailer) {
+ if ('mail' !== $this->Mailer) {
foreach ($this->to as $toaddr) {
$this->SingleToArray[] = $this->addrFormat($toaddr);
}
}
- } else {
- if (count($this->to) > 0) {
- if ('mail' != $this->Mailer) {
- $result .= $this->addrAppend('To', $this->to);
- }
- } elseif (count($this->cc) == 0) {
- $result .= $this->headerLine('To', 'undisclosed-recipients:;');
+ } elseif (count($this->to) > 0) {
+ if ('mail' !== $this->Mailer) {
+ $result .= $this->addrAppend('To', $this->to);
}
+ } elseif (count($this->cc) === 0) {
+ $result .= $this->headerLine('To', 'undisclosed-recipients:;');
}
$result .= $this->addrAppend('From', [[trim($this->From), $this->FromName]]);
@@ -2330,9 +2401,9 @@ class PHPMailer
// sendmail and mail() extract Bcc from the header before sending
if ((
- 'sendmail' == $this->Mailer or 'qmail' == $this->Mailer or 'mail' == $this->Mailer
+ 'sendmail' === $this->Mailer || 'qmail' === $this->Mailer || 'mail' === $this->Mailer
)
- and count($this->bcc) > 0
+ && count($this->bcc) > 0
) {
$result .= $this->addrAppend('Bcc', $this->bcc);
}
@@ -2342,13 +2413,13 @@ class PHPMailer
}
// mail() sets the subject itself
- if ('mail' != $this->Mailer) {
+ if ('mail' !== $this->Mailer) {
$result .= $this->headerLine('Subject', $this->encodeHeader($this->secureHeader($this->Subject)));
}
// Only allow a custom message ID if it conforms to RFC 5322 section 3.6.4
// https://tools.ietf.org/html/rfc5322#section-3.6.4
- if ('' != $this->MessageID and preg_match('/^<.*@.*>$/', $this->MessageID)) {
+ if ('' !== $this->MessageID && preg_match('/^<.*@.*>$/', $this->MessageID)) {
$this->lastMessageID = $this->MessageID;
} else {
$this->lastMessageID = sprintf('<%s@%s>', $this->uniqueid, $this->serverHostname());
@@ -2357,7 +2428,7 @@ class PHPMailer
if (null !== $this->Priority) {
$result .= $this->headerLine('X-Priority', $this->Priority);
}
- if ('' == $this->XMailer) {
+ if ('' === $this->XMailer) {
$result .= $this->headerLine(
'X-Mailer',
'PHPMailer ' . self::VERSION . ' (https://github.com/PHPMailer/PHPMailer)'
@@ -2369,7 +2440,7 @@ class PHPMailer
}
}
- if ('' != $this->ConfirmReadingTo) {
+ if ('' !== $this->ConfirmReadingTo) {
$result .= $this->headerLine('Disposition-Notification-To', '<' . $this->ConfirmReadingTo . '>');
}
@@ -2400,19 +2471,19 @@ class PHPMailer
switch ($this->message_type) {
case 'inline':
$result .= $this->headerLine('Content-Type', static::CONTENT_TYPE_MULTIPART_RELATED . ';');
- $result .= $this->textLine("\tboundary=\"" . $this->boundary[1] . '"');
+ $result .= $this->textLine(' boundary="' . $this->boundary[1] . '"');
break;
case 'attach':
case 'inline_attach':
case 'alt_attach':
case 'alt_inline_attach':
$result .= $this->headerLine('Content-Type', static::CONTENT_TYPE_MULTIPART_MIXED . ';');
- $result .= $this->textLine("\tboundary=\"" . $this->boundary[1] . '"');
+ $result .= $this->textLine(' boundary="' . $this->boundary[1] . '"');
break;
case 'alt':
case 'alt_inline':
$result .= $this->headerLine('Content-Type', static::CONTENT_TYPE_MULTIPART_ALTERNATIVE . ';');
- $result .= $this->textLine("\tboundary=\"" . $this->boundary[1] . '"');
+ $result .= $this->textLine(' boundary="' . $this->boundary[1] . '"');
break;
default:
// Catches case 'plain': and case '':
@@ -2421,10 +2492,10 @@ class PHPMailer
break;
}
// RFC1341 part 5 says 7bit is assumed if not specified
- if (static::ENCODING_7BIT != $this->Encoding) {
+ if (static::ENCODING_7BIT !== $this->Encoding) {
// RFC 2045 section 6.4 says multipart MIME parts may only use 7bit, 8bit or binary CTE
if ($ismultipart) {
- if (static::ENCODING_8BIT == $this->Encoding) {
+ if (static::ENCODING_8BIT === $this->Encoding) {
$result .= $this->headerLine('Content-Transfer-Encoding', static::ENCODING_8BIT);
}
// The only remaining alternatives are quoted-printable and base64, which are both 7bit compatible
@@ -2433,8 +2504,8 @@ class PHPMailer
}
}
- if ('mail' != $this->Mailer) {
- $result .= static::$LE;
+ if ('mail' !== $this->Mailer) {
+// $result .= static::$LE;
}
return $result;
@@ -2451,7 +2522,8 @@ class PHPMailer
*/
public function getSentMIMEMessage()
{
- return rtrim($this->MIMEHeader . $this->mailHeader, "\n\r") . static::$LE . static::$LE . $this->MIMEBody;
+ return static::stripTrailingWSP($this->MIMEHeader . $this->mailHeader) .
+ static::$LE . static::$LE . $this->MIMEBody;
}
/**
@@ -2462,11 +2534,19 @@ class PHPMailer
protected function generateId()
{
$len = 32; //32 bytes = 256 bits
+ $bytes = '';
if (function_exists('random_bytes')) {
- $bytes = random_bytes($len);
+ try {
+ $bytes = random_bytes($len);
+ } catch (\Exception $e) {
+ //Do nothing
+ }
} elseif (function_exists('openssl_random_pseudo_bytes')) {
+ /** @noinspection CryptographicallySecureRandomnessInspection */
$bytes = openssl_random_pseudo_bytes($len);
- } else {
+ }
+ if ($bytes === '') {
+ //We failed to produce a proper random string, so make do.
//Use a hash to force the length to the same as the other methods
$bytes = hash('sha256', uniqid((string) mt_rand(), true), true);
}
@@ -2501,32 +2581,32 @@ class PHPMailer
$bodyEncoding = $this->Encoding;
$bodyCharSet = $this->CharSet;
//Can we do a 7-bit downgrade?
- if (static::ENCODING_8BIT == $bodyEncoding and !$this->has8bitChars($this->Body)) {
+ if (static::ENCODING_8BIT === $bodyEncoding && !$this->has8bitChars($this->Body)) {
$bodyEncoding = static::ENCODING_7BIT;
//All ISO 8859, Windows codepage and UTF-8 charsets are ascii compatible up to 7-bit
- $bodyCharSet = 'us-ascii';
+ $bodyCharSet = static::CHARSET_ASCII;
}
//If lines are too long, and we're not already using an encoding that will shorten them,
//change to quoted-printable transfer encoding for the body part only
- if (static::ENCODING_BASE64 != $this->Encoding and static::hasLineLongerThanMax($this->Body)) {
+ if (static::ENCODING_BASE64 !== $this->Encoding && static::hasLineLongerThanMax($this->Body)) {
$bodyEncoding = static::ENCODING_QUOTED_PRINTABLE;
}
$altBodyEncoding = $this->Encoding;
$altBodyCharSet = $this->CharSet;
//Can we do a 7-bit downgrade?
- if (static::ENCODING_8BIT == $altBodyEncoding and !$this->has8bitChars($this->AltBody)) {
+ if (static::ENCODING_8BIT === $altBodyEncoding && !$this->has8bitChars($this->AltBody)) {
$altBodyEncoding = static::ENCODING_7BIT;
//All ISO 8859, Windows codepage and UTF-8 charsets are ascii compatible up to 7-bit
- $altBodyCharSet = 'us-ascii';
+ $altBodyCharSet = static::CHARSET_ASCII;
}
//If lines are too long, and we're not already using an encoding that will shorten them,
//change to quoted-printable transfer encoding for the alt body part only
- if (static::ENCODING_BASE64 != $altBodyEncoding and static::hasLineLongerThanMax($this->AltBody)) {
+ if (static::ENCODING_BASE64 !== $altBodyEncoding && static::hasLineLongerThanMax($this->AltBody)) {
$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;
+ $mimepre = 'This is a multi-part message in MIME format.' . static::$LE . static::$LE;
switch ($this->message_type) {
case 'inline':
$body .= $mimepre;
@@ -2546,7 +2626,8 @@ class PHPMailer
$body .= $mimepre;
$body .= $this->textLine('--' . $this->boundary[1]);
$body .= $this->headerLine('Content-Type', static::CONTENT_TYPE_MULTIPART_RELATED . ';');
- $body .= $this->textLine("\tboundary=\"" . $this->boundary[2] . '"');
+ $body .= $this->textLine(' boundary="' . $this->boundary[2] . '";');
+ $body .= $this->textLine(' type="' . static::CONTENT_TYPE_TEXT_HTML . '"');
$body .= static::$LE;
$body .= $this->getBoundary($this->boundary[2], $bodyCharSet, '', $bodyEncoding);
$body .= $this->encodeString($this->Body, $bodyEncoding);
@@ -2557,14 +2638,36 @@ class PHPMailer
break;
case 'alt':
$body .= $mimepre;
- $body .= $this->getBoundary($this->boundary[1], $altBodyCharSet, static::CONTENT_TYPE_PLAINTEXT, $altBodyEncoding);
+ $body .= $this->getBoundary(
+ $this->boundary[1],
+ $altBodyCharSet,
+ static::CONTENT_TYPE_PLAINTEXT,
+ $altBodyEncoding
+ );
$body .= $this->encodeString($this->AltBody, $altBodyEncoding);
$body .= static::$LE;
- $body .= $this->getBoundary($this->boundary[1], $bodyCharSet, static::CONTENT_TYPE_TEXT_HTML, $bodyEncoding);
+ $body .= $this->getBoundary(
+ $this->boundary[1],
+ $bodyCharSet,
+ static::CONTENT_TYPE_TEXT_HTML,
+ $bodyEncoding
+ );
$body .= $this->encodeString($this->Body, $bodyEncoding);
$body .= static::$LE;
if (!empty($this->Ical)) {
- $body .= $this->getBoundary($this->boundary[1], '', static::CONTENT_TYPE_TEXT_CALENDAR . '; method=REQUEST', '');
+ $method = static::ICAL_METHOD_REQUEST;
+ foreach (static::$IcalMethods as $imethod) {
+ if (stripos($this->Ical, 'METHOD:' . $imethod) !== false) {
+ $method = $imethod;
+ break;
+ }
+ }
+ $body .= $this->getBoundary(
+ $this->boundary[1],
+ '',
+ static::CONTENT_TYPE_TEXT_CALENDAR . '; method=' . $method,
+ ''
+ );
$body .= $this->encodeString($this->Ical, $this->Encoding);
$body .= static::$LE;
}
@@ -2572,14 +2675,25 @@ class PHPMailer
break;
case 'alt_inline':
$body .= $mimepre;
- $body .= $this->getBoundary($this->boundary[1], $altBodyCharSet, static::CONTENT_TYPE_PLAINTEXT, $altBodyEncoding);
+ $body .= $this->getBoundary(
+ $this->boundary[1],
+ $altBodyCharSet,
+ static::CONTENT_TYPE_PLAINTEXT,
+ $altBodyEncoding
+ );
$body .= $this->encodeString($this->AltBody, $altBodyEncoding);
$body .= static::$LE;
$body .= $this->textLine('--' . $this->boundary[1]);
$body .= $this->headerLine('Content-Type', static::CONTENT_TYPE_MULTIPART_RELATED . ';');
- $body .= $this->textLine("\tboundary=\"" . $this->boundary[2] . '"');
+ $body .= $this->textLine(' boundary="' . $this->boundary[2] . '";');
+ $body .= $this->textLine(' type="' . static::CONTENT_TYPE_TEXT_HTML . '"');
$body .= static::$LE;
- $body .= $this->getBoundary($this->boundary[2], $bodyCharSet, static::CONTENT_TYPE_TEXT_HTML, $bodyEncoding);
+ $body .= $this->getBoundary(
+ $this->boundary[2],
+ $bodyCharSet,
+ static::CONTENT_TYPE_TEXT_HTML,
+ $bodyEncoding
+ );
$body .= $this->encodeString($this->Body, $bodyEncoding);
$body .= static::$LE;
$body .= $this->attachAll('inline', $this->boundary[2]);
@@ -2590,16 +2704,38 @@ class PHPMailer
$body .= $mimepre;
$body .= $this->textLine('--' . $this->boundary[1]);
$body .= $this->headerLine('Content-Type', static::CONTENT_TYPE_MULTIPART_ALTERNATIVE . ';');
- $body .= $this->textLine("\tboundary=\"" . $this->boundary[2] . '"');
+ $body .= $this->textLine(' boundary="' . $this->boundary[2] . '"');
$body .= static::$LE;
- $body .= $this->getBoundary($this->boundary[2], $altBodyCharSet, static::CONTENT_TYPE_PLAINTEXT, $altBodyEncoding);
+ $body .= $this->getBoundary(
+ $this->boundary[2],
+ $altBodyCharSet,
+ static::CONTENT_TYPE_PLAINTEXT,
+ $altBodyEncoding
+ );
$body .= $this->encodeString($this->AltBody, $altBodyEncoding);
$body .= static::$LE;
- $body .= $this->getBoundary($this->boundary[2], $bodyCharSet, static::CONTENT_TYPE_TEXT_HTML, $bodyEncoding);
+ $body .= $this->getBoundary(
+ $this->boundary[2],
+ $bodyCharSet,
+ static::CONTENT_TYPE_TEXT_HTML,
+ $bodyEncoding
+ );
$body .= $this->encodeString($this->Body, $bodyEncoding);
$body .= static::$LE;
if (!empty($this->Ical)) {
- $body .= $this->getBoundary($this->boundary[2], '', static::CONTENT_TYPE_TEXT_CALENDAR . '; method=REQUEST', '');
+ $method = static::ICAL_METHOD_REQUEST;
+ foreach (static::$IcalMethods as $imethod) {
+ if (stripos($this->Ical, 'METHOD:' . $imethod) !== false) {
+ $method = $imethod;
+ break;
+ }
+ }
+ $body .= $this->getBoundary(
+ $this->boundary[2],
+ '',
+ static::CONTENT_TYPE_TEXT_CALENDAR . '; method=' . $method,
+ ''
+ );
$body .= $this->encodeString($this->Ical, $this->Encoding);
}
$body .= $this->endBoundary($this->boundary[2]);
@@ -2610,16 +2746,27 @@ class PHPMailer
$body .= $mimepre;
$body .= $this->textLine('--' . $this->boundary[1]);
$body .= $this->headerLine('Content-Type', static::CONTENT_TYPE_MULTIPART_ALTERNATIVE . ';');
- $body .= $this->textLine("\tboundary=\"" . $this->boundary[2] . '"');
+ $body .= $this->textLine(' boundary="' . $this->boundary[2] . '"');
$body .= static::$LE;
- $body .= $this->getBoundary($this->boundary[2], $altBodyCharSet, static::CONTENT_TYPE_PLAINTEXT, $altBodyEncoding);
+ $body .= $this->getBoundary(
+ $this->boundary[2],
+ $altBodyCharSet,
+ static::CONTENT_TYPE_PLAINTEXT,
+ $altBodyEncoding
+ );
$body .= $this->encodeString($this->AltBody, $altBodyEncoding);
$body .= static::$LE;
$body .= $this->textLine('--' . $this->boundary[2]);
$body .= $this->headerLine('Content-Type', static::CONTENT_TYPE_MULTIPART_RELATED . ';');
- $body .= $this->textLine("\tboundary=\"" . $this->boundary[3] . '"');
+ $body .= $this->textLine(' boundary="' . $this->boundary[3] . '";');
+ $body .= $this->textLine(' type="' . static::CONTENT_TYPE_TEXT_HTML . '"');
$body .= static::$LE;
- $body .= $this->getBoundary($this->boundary[3], $bodyCharSet, static::CONTENT_TYPE_TEXT_HTML, $bodyEncoding);
+ $body .= $this->getBoundary(
+ $this->boundary[3],
+ $bodyCharSet,
+ static::CONTENT_TYPE_TEXT_HTML,
+ $bodyEncoding
+ );
$body .= $this->encodeString($this->Body, $bodyEncoding);
$body .= static::$LE;
$body .= $this->attachAll('inline', $this->boundary[3]);
@@ -2646,12 +2793,11 @@ class PHPMailer
if (!defined('PKCS7_TEXT')) {
throw new Exception($this->lang('extension_missing') . 'openssl');
}
- // @TODO would be nice to use php://temp streams here
- $file = tempnam(sys_get_temp_dir(), 'mail');
- if (false === file_put_contents($file, $body)) {
- throw new Exception($this->lang('signing') . ' Could not write temp file');
- }
- $signed = tempnam(sys_get_temp_dir(), 'signed');
+
+ $file = tempnam(sys_get_temp_dir(), 'srcsign');
+ $signed = tempnam(sys_get_temp_dir(), 'mailsign');
+ file_put_contents($file, $body);
+
//Workaround for PHP bug https://bugs.php.net/bug.php?id=69197
if (empty($this->sign_extracerts_file)) {
$sign = @openssl_pkcs7_sign(
@@ -2672,6 +2818,7 @@ class PHPMailer
$this->sign_extracerts_file
);
}
+
@unlink($file);
if ($sign) {
$body = file_get_contents($signed);
@@ -2708,20 +2855,20 @@ class PHPMailer
protected function getBoundary($boundary, $charSet, $contentType, $encoding)
{
$result = '';
- if ('' == $charSet) {
+ if ('' === $charSet) {
$charSet = $this->CharSet;
}
- if ('' == $contentType) {
+ if ('' === $contentType) {
$contentType = $this->ContentType;
}
- if ('' == $encoding) {
+ if ('' === $encoding) {
$encoding = $this->Encoding;
}
$result .= $this->textLine('--' . $boundary);
$result .= sprintf('Content-Type: %s; charset=%s', $contentType, $charSet);
$result .= static::$LE;
// RFC1341 part 5 says 7bit is assumed if not specified
- if (static::ENCODING_7BIT != $encoding) {
+ if (static::ENCODING_7BIT !== $encoding) {
$result .= $this->headerLine('Content-Transfer-Encoding', $encoding);
}
$result .= static::$LE;
@@ -2758,7 +2905,7 @@ class PHPMailer
$type[] = 'attach';
}
$this->message_type = implode('_', $type);
- if ('' == $this->message_type) {
+ if ('' === $this->message_type) {
//The 'plain' message_type refers to the message having a single body element, not that it is plain-text
$this->message_type = 'plain';
}
@@ -2806,23 +2953,32 @@ class PHPMailer
*
* @return bool
*/
- public function addAttachment($path, $name = '', $encoding = self::ENCODING_BASE64, $type = '', $disposition = 'attachment')
- {
+ public function addAttachment(
+ $path,
+ $name = '',
+ $encoding = self::ENCODING_BASE64,
+ $type = '',
+ $disposition = 'attachment'
+ ) {
try {
- if (!static::isPermittedPath($path) || !@is_file($path)) {
+ if (!static::isPermittedPath($path) || !@is_file($path) || !is_readable($path)) {
throw new Exception($this->lang('file_access') . $path, self::STOP_CONTINUE);
}
// If a MIME type is not specified, try to work it out from the file name
- if ('' == $type) {
+ if ('' === $type) {
$type = static::filenameToType($path);
}
- $filename = basename($path);
- if ('' == $name) {
+ $filename = (string) static::mb_pathinfo($path, PATHINFO_BASENAME);
+ if ('' === $name) {
$name = $filename;
}
+ if (!$this->validateEncoding($encoding)) {
+ throw new Exception($this->lang('encoding') . $encoding);
+ }
+
$this->attachment[] = [
0 => $path,
1 => $filename,
@@ -2863,6 +3019,8 @@ class PHPMailer
* @param string $disposition_type
* @param string $boundary
*
+ * @throws Exception
+ *
* @return string
*/
protected function attachAll($disposition_type, $boundary)
@@ -2875,7 +3033,7 @@ class PHPMailer
// Add all attachments
foreach ($this->attachment as $attachment) {
// Check if it is a valid disposition_filter
- if ($attachment[6] == $disposition_type) {
+ if ($attachment[6] === $disposition_type) {
// Check for string attachment
$string = '';
$path = '';
@@ -2887,7 +3045,7 @@ class PHPMailer
}
$inclhash = hash('sha256', serialize($attachment));
- if (in_array($inclhash, $incl)) {
+ if (in_array($inclhash, $incl, true)) {
continue;
}
$incl[] = $inclhash;
@@ -2896,7 +3054,7 @@ class PHPMailer
$type = $attachment[4];
$disposition = $attachment[6];
$cid = $attachment[7];
- if ('inline' == $disposition and array_key_exists($cid, $cidUniq)) {
+ if ('inline' === $disposition && array_key_exists($cid, $cidUniq)) {
continue;
}
$cidUniq[$cid] = true;
@@ -2918,42 +3076,41 @@ class PHPMailer
);
}
// RFC1341 part 5 says 7bit is assumed if not specified
- if (static::ENCODING_7BIT != $encoding) {
+ if (static::ENCODING_7BIT !== $encoding) {
$mime[] = sprintf('Content-Transfer-Encoding: %s%s', $encoding, static::$LE);
}
- if (!empty($cid)) {
- $mime[] = sprintf('Content-ID: <%s>%s', $cid, static::$LE);
+ //Only set Content-IDs on inline attachments
+ if ((string) $cid !== '' && $disposition === 'inline') {
+ $mime[] = 'Content-ID: <' . $this->encodeHeader($this->secureHeader($cid)) . '>' . static::$LE;
}
// If a filename contains any of these chars, it should be quoted,
// but not otherwise: RFC2183 & RFC2045 5.1
// Fixes a warning in IETF's msglint MIME checker
// Allow for bypassing the Content-Disposition header totally
- if (!(empty($disposition))) {
+ if (!empty($disposition)) {
$encoded_name = $this->encodeHeader($this->secureHeader($name));
- if (preg_match('/[ \(\)<>@,;:\\"\/\[\]\?=]/', $encoded_name)) {
+ if (preg_match('/[ ()<>@,;:"\/\[\]?=]/', $encoded_name)) {
$mime[] = sprintf(
'Content-Disposition: %s; filename="%s"%s',
$disposition,
$encoded_name,
static::$LE . static::$LE
);
+ } elseif (!empty($encoded_name)) {
+ $mime[] = sprintf(
+ 'Content-Disposition: %s; filename=%s%s',
+ $disposition,
+ $encoded_name,
+ static::$LE . static::$LE
+ );
} else {
- if (!empty($encoded_name)) {
- $mime[] = sprintf(
- 'Content-Disposition: %s; filename=%s%s',
- $disposition,
- $encoded_name,
- static::$LE . static::$LE
- );
- } else {
- $mime[] = sprintf(
- 'Content-Disposition: %s%s',
- $disposition,
- static::$LE . static::$LE
- );
- }
+ $mime[] = sprintf(
+ 'Content-Disposition: %s%s',
+ $disposition,
+ static::$LE . static::$LE
+ );
}
} else {
$mime[] = static::$LE;
@@ -2984,14 +3141,12 @@ class PHPMailer
* @param string $path The full path to the file
* @param string $encoding The encoding to use; one of 'base64', '7bit', '8bit', 'binary', 'quoted-printable'
*
- * @throws Exception
- *
* @return string
*/
protected function encodeFile($path, $encoding = self::ENCODING_BASE64)
{
try {
- if (!static::isPermittedPath($path) || !file_exists($path)) {
+ if (!static::isPermittedPath($path) || !file_exists($path) || !is_readable($path)) {
throw new Exception($this->lang('file_open') . $path, self::STOP_CONTINUE);
}
$file_buffer = file_get_contents($path);
@@ -3003,7 +3158,10 @@ class PHPMailer
return $file_buffer;
} catch (Exception $exc) {
$this->setError($exc->getMessage());
-
+ $this->edebug($exc->getMessage());
+ if ($this->exceptions) {
+ throw $exc;
+ }
return '';
}
}
@@ -3015,6 +3173,8 @@ class PHPMailer
* @param string $str The text to encode
* @param string $encoding The encoding to use; one of 'base64', '7bit', '8bit', 'binary', 'quoted-printable'
*
+ * @throws Exception
+ *
* @return string
*/
public function encodeString($str, $encoding = self::ENCODING_BASE64)
@@ -3032,7 +3192,7 @@ class PHPMailer
case static::ENCODING_8BIT:
$encoded = static::normalizeBreaks($str);
// Make sure it ends with a line break
- if (substr($encoded, -(strlen(static::$LE))) != static::$LE) {
+ if (substr($encoded, -(strlen(static::$LE))) !== static::$LE) {
$encoded .= static::$LE;
}
break;
@@ -3044,6 +3204,9 @@ class PHPMailer
break;
default:
$this->setError($this->lang('encoding') . $encoding);
+ if ($this->exceptions) {
+ throw new Exception($this->lang('encoding') . $encoding);
+ }
break;
}
@@ -3068,7 +3231,7 @@ class PHPMailer
if (!preg_match('/[\200-\377]/', $str)) {
// Can't use addslashes as we don't know the value of magic_quotes_sybase
$encoded = addcslashes($str, "\0..\37\177\\\"");
- if (($str == $encoded) and !preg_match('/[^A-Za-z0-9!#$%&\'*+\/=?^_`{|}~ -]/', $str)) {
+ if (($str === $encoded) && !preg_match('/[^A-Za-z0-9!#$%&\'*+\/=?^_`{|}~ -]/', $str)) {
return $encoded;
}
@@ -3086,51 +3249,57 @@ class PHPMailer
break;
}
- //RFCs specify a maximum line length of 78 chars, however mail() will sometimes
- //corrupt messages with headers longer than 65 chars. See #818
- $lengthsub = 'mail' == $this->Mailer ? 13 : 0;
- $maxlen = static::STD_LINE_LENGTH - $lengthsub;
- // Try to select the encoding which should produce the shortest output
+ if ($this->has8bitChars($str)) {
+ $charset = $this->CharSet;
+ } else {
+ $charset = static::CHARSET_ASCII;
+ }
+
+ // Q/B encoding adds 8 chars and the charset ("` =?<charset>?[QB]?<content>?=`").
+ $overhead = 8 + strlen($charset);
+
+ if ('mail' === $this->Mailer) {
+ $maxlen = static::MAIL_MAX_LINE_LENGTH - $overhead;
+ } else {
+ $maxlen = static::MAX_LINE_LENGTH - $overhead;
+ }
+
+ // Select the encoding that produces the shortest output and/or prevents corruption.
if ($matchcount > strlen($str) / 3) {
- // More than a third of the content will need encoding, so B encoding will be most efficient
+ // More than 1/3 of the content needs encoding, use B-encode.
$encoding = 'B';
- //This calculation is:
- // max line length
- // - shorten to avoid mail() corruption
- // - Q/B encoding char overhead ("` =?<charset>?[QB]?<content>?=`")
- // - charset name length
- $maxlen = static::STD_LINE_LENGTH - $lengthsub - 8 - strlen($this->CharSet);
- if ($this->hasMultiBytes($str)) {
- // Use a custom function which correctly encodes and wraps long
- // multibyte strings without breaking lines within a character
- $encoded = $this->base64EncodeWrapMB($str, "\n");
- } else {
- $encoded = base64_encode($str);
- $maxlen -= $maxlen % 4;
- $encoded = trim(chunk_split($encoded, $maxlen, "\n"));
- }
- $encoded = preg_replace('/^(.*)$/m', ' =?' . $this->CharSet . "?$encoding?\\1?=", $encoded);
} elseif ($matchcount > 0) {
- //1 or more chars need encoding, use Q-encode
+ // Less than 1/3 of the content needs encoding, use Q-encode.
$encoding = 'Q';
- //Recalc max line length for Q encoding - see comments on B encode
- $maxlen = static::STD_LINE_LENGTH - $lengthsub - 8 - strlen($this->CharSet);
- $encoded = $this->encodeQ($str, $position);
- $encoded = $this->wrapText($encoded, $maxlen, true);
- $encoded = str_replace('=' . static::$LE, "\n", trim($encoded));
- $encoded = preg_replace('/^(.*)$/m', ' =?' . $this->CharSet . "?$encoding?\\1?=", $encoded);
} elseif (strlen($str) > $maxlen) {
- //No chars need encoding, but line is too long, so fold it
- $encoded = trim($this->wrapText($str, $maxlen, false));
- if ($str == $encoded) {
- //Wrapping nicely didn't work, wrap hard instead
- $encoded = trim(chunk_split($str, static::STD_LINE_LENGTH, static::$LE));
- }
- $encoded = str_replace(static::$LE, "\n", trim($encoded));
- $encoded = preg_replace('/^(.*)$/m', ' \\1', $encoded);
+ // No encoding needed, but value exceeds max line length, use Q-encode to prevent corruption.
+ $encoding = 'Q';
} else {
- //No reformatting needed
- return $str;
+ // No reformatting needed
+ $encoding = false;
+ }
+
+ switch ($encoding) {
+ case 'B':
+ if ($this->hasMultiBytes($str)) {
+ // Use a custom function which correctly encodes and wraps long
+ // multibyte strings without breaking lines within a character
+ $encoded = $this->base64EncodeWrapMB($str, "\n");
+ } else {
+ $encoded = base64_encode($str);
+ $maxlen -= $maxlen % 4;
+ $encoded = trim(chunk_split($encoded, $maxlen, "\n"));
+ }
+ $encoded = preg_replace('/^(.*)$/m', ' =?' . $charset . "?$encoding?\\1?=", $encoded);
+ break;
+ case 'Q':
+ $encoded = $this->encodeQ($str, $position);
+ $encoded = $this->wrapText($encoded, $maxlen, true);
+ $encoded = str_replace('=' . static::$LE, "\n", trim($encoded));
+ $encoded = preg_replace('/^(.*)$/m', ' =?' . $charset . "?$encoding?\\1?=", $encoded);
+ break;
+ default:
+ return $str;
}
return trim(static::normalizeBreaks($encoded));
@@ -3194,6 +3363,7 @@ class PHPMailer
// Base64 has a 4:3 ratio
$avgLength = floor($length * $ratio * .75);
+ $offset = 0;
for ($i = 0; $i < $mb_length; $i += $offset) {
$lookBack = 0;
do {
@@ -3254,7 +3424,6 @@ class PHPMailer
default:
// RFC 2047 section 5.1
// Replace every high ascii, control, =, ? and _ characters
- /** @noinspection SuspiciousAssignmentsInspection */
$pattern = '\000-\011\013\014\016-\037\075\077\137\177-\377' . $pattern;
break;
}
@@ -3262,7 +3431,7 @@ class PHPMailer
if (preg_match_all("/[{$pattern}]/", $encoded, $matches)) {
// If the string contains an '=', make sure it's the first thing we replace
// so as to avoid double-encoding
- $eqkey = array_search('=', $matches[0]);
+ $eqkey = array_search('=', $matches[0], true);
if (false !== $eqkey) {
unset($matches[0][$eqkey]);
array_unshift($matches[0], '=');
@@ -3286,6 +3455,10 @@ class PHPMailer
* @param string $encoding File encoding (see $Encoding)
* @param string $type File extension (MIME) type
* @param string $disposition Disposition to use
+ *
+ * @throws Exception
+ *
+ * @return bool True on successfully adding an attachment
*/
public function addStringAttachment(
$string,
@@ -3294,21 +3467,38 @@ class PHPMailer
$type = '',
$disposition = 'attachment'
) {
- // If a MIME type is not specified, try to work it out from the file name
- if ('' == $type) {
- $type = static::filenameToType($filename);
- }
- // Append to $attachment array
- $this->attachment[] = [
- 0 => $string,
- 1 => $filename,
- 2 => basename($filename),
- 3 => $encoding,
- 4 => $type,
- 5 => true, // isStringAttachment
- 6 => $disposition,
- 7 => 0,
- ];
+ try {
+ // If a MIME type is not specified, try to work it out from the file name
+ if ('' === $type) {
+ $type = static::filenameToType($filename);
+ }
+
+ if (!$this->validateEncoding($encoding)) {
+ throw new Exception($this->lang('encoding') . $encoding);
+ }
+
+ // Append to $attachment array
+ $this->attachment[] = [
+ 0 => $string,
+ 1 => $filename,
+ 2 => static::mb_pathinfo($filename, PATHINFO_BASENAME),
+ 3 => $encoding,
+ 4 => $type,
+ 5 => true, // isStringAttachment
+ 6 => $disposition,
+ 7 => 0,
+ ];
+ } catch (Exception $exc) {
+ $this->setError($exc->getMessage());
+ $this->edebug($exc->getMessage());
+ if ($this->exceptions) {
+ throw $exc;
+ }
+
+ return false;
+ }
+
+ return true;
}
/**
@@ -3328,37 +3518,57 @@ class PHPMailer
* @param string $type File MIME type
* @param string $disposition Disposition to use
*
+ * @throws Exception
+ *
* @return bool True on successfully adding an attachment
*/
- public function addEmbeddedImage($path, $cid, $name = '', $encoding = self::ENCODING_BASE64, $type = '', $disposition = 'inline')
- {
- if (!static::isPermittedPath($path) || !@is_file($path)) {
- $this->setError($this->lang('file_access') . $path);
+ public function addEmbeddedImage(
+ $path,
+ $cid,
+ $name = '',
+ $encoding = self::ENCODING_BASE64,
+ $type = '',
+ $disposition = 'inline'
+ ) {
+ try {
+ if (!static::isPermittedPath($path) || !@is_file($path) || !is_readable($path)) {
+ throw new Exception($this->lang('file_access') . $path, self::STOP_CONTINUE);
+ }
- return false;
- }
+ // If a MIME type is not specified, try to work it out from the file name
+ if ('' === $type) {
+ $type = static::filenameToType($path);
+ }
- // If a MIME type is not specified, try to work it out from the file name
- if ('' == $type) {
- $type = static::filenameToType($path);
- }
+ if (!$this->validateEncoding($encoding)) {
+ throw new Exception($this->lang('encoding') . $encoding);
+ }
- $filename = basename($path);
- if ('' == $name) {
- $name = $filename;
- }
+ $filename = (string) static::mb_pathinfo($path, PATHINFO_BASENAME);
+ if ('' === $name) {
+ $name = $filename;
+ }
- // Append to $attachment array
- $this->attachment[] = [
- 0 => $path,
- 1 => $filename,
- 2 => $name,
- 3 => $encoding,
- 4 => $type,
- 5 => false, // isStringAttachment
- 6 => $disposition,
- 7 => $cid,
- ];
+ // Append to $attachment array
+ $this->attachment[] = [
+ 0 => $path,
+ 1 => $filename,
+ 2 => $name,
+ 3 => $encoding,
+ 4 => $type,
+ 5 => false, // isStringAttachment
+ 6 => $disposition,
+ 7 => $cid,
+ ];
+ } catch (Exception $exc) {
+ $this->setError($exc->getMessage());
+ $this->edebug($exc->getMessage());
+ if ($this->exceptions) {
+ throw $exc;
+ }
+
+ return false;
+ }
return true;
}
@@ -3378,6 +3588,8 @@ class PHPMailer
* @param string $type MIME type - will be used in preference to any automatically derived type
* @param string $disposition Disposition to use
*
+ * @throws Exception
+ *
* @return bool True on successfully adding an attachment
*/
public function addStringEmbeddedImage(
@@ -3388,27 +3600,63 @@ class PHPMailer
$type = '',
$disposition = 'inline'
) {
- // If a MIME type is not specified, try to work it out from the name
- if ('' == $type and !empty($name)) {
- $type = static::filenameToType($name);
- }
-
- // Append to $attachment array
- $this->attachment[] = [
- 0 => $string,
- 1 => $name,
- 2 => $name,
- 3 => $encoding,
- 4 => $type,
- 5 => true, // isStringAttachment
- 6 => $disposition,
- 7 => $cid,
- ];
+ try {
+ // If a MIME type is not specified, try to work it out from the name
+ if ('' === $type && !empty($name)) {
+ $type = static::filenameToType($name);
+ }
+
+ if (!$this->validateEncoding($encoding)) {
+ throw new Exception($this->lang('encoding') . $encoding);
+ }
+
+ // Append to $attachment array
+ $this->attachment[] = [
+ 0 => $string,
+ 1 => $name,
+ 2 => $name,
+ 3 => $encoding,
+ 4 => $type,
+ 5 => true, // isStringAttachment
+ 6 => $disposition,
+ 7 => $cid,
+ ];
+ } catch (Exception $exc) {
+ $this->setError($exc->getMessage());
+ $this->edebug($exc->getMessage());
+ if ($this->exceptions) {
+ throw $exc;
+ }
+
+ return false;
+ }
return true;
}
/**
+ * Validate encodings.
+ *
+ * @param string $encoding
+ *
+ * @return bool
+ */
+ protected function validateEncoding($encoding)
+ {
+ return in_array(
+ $encoding,
+ [
+ self::ENCODING_7BIT,
+ self::ENCODING_QUOTED_PRINTABLE,
+ self::ENCODING_BASE64,
+ self::ENCODING_8BIT,
+ self::ENCODING_BINARY,
+ ],
+ true
+ );
+ }
+
+ /**
* Check if an embedded attachment is present with this cid.
*
* @param string $cid
@@ -3418,7 +3666,7 @@ class PHPMailer
protected function cidExists($cid)
{
foreach ($this->attachment as $attachment) {
- if ('inline' == $attachment[6] and $cid == $attachment[7]) {
+ if ('inline' === $attachment[6] && $cid === $attachment[7]) {
return true;
}
}
@@ -3434,7 +3682,7 @@ class PHPMailer
public function inlineImageExists()
{
foreach ($this->attachment as $attachment) {
- if ('inline' == $attachment[6]) {
+ if ('inline' === $attachment[6]) {
return true;
}
}
@@ -3450,7 +3698,7 @@ class PHPMailer
public function attachmentExists()
{
foreach ($this->attachment as $attachment) {
- if ('attachment' == $attachment[6]) {
+ if ('attachment' === $attachment[6]) {
return true;
}
}
@@ -3477,8 +3725,8 @@ class PHPMailer
{
$this->RecipientsQueue = array_filter(
$this->RecipientsQueue,
- function ($params) use ($kind) {
- return $params[0] != $kind;
+ static function ($params) use ($kind) {
+ return $params[0] !== $kind;
}
);
}
@@ -3564,7 +3812,7 @@ class PHPMailer
protected function setError($msg)
{
++$this->error_count;
- if ('smtp' == $this->Mailer and null !== $this->smtp) {
+ if ('smtp' === $this->Mailer && null !== $this->smtp) {
$lasterror = $this->smtp->getError();
if (!empty($lasterror['error'])) {
$msg .= $this->lang('smtp_error') . $lasterror['error'];
@@ -3607,9 +3855,9 @@ class PHPMailer
$result = '';
if (!empty($this->Hostname)) {
$result = $this->Hostname;
- } elseif (isset($_SERVER) and array_key_exists('SERVER_NAME', $_SERVER)) {
+ } elseif (isset($_SERVER) && array_key_exists('SERVER_NAME', $_SERVER)) {
$result = $_SERVER['SERVER_NAME'];
- } elseif (function_exists('gethostname') and gethostname() !== false) {
+ } elseif (function_exists('gethostname') && gethostname() !== false) {
$result = gethostname();
} elseif (php_uname('n') !== false) {
$result = php_uname('n');
@@ -3633,22 +3881,23 @@ class PHPMailer
{
//Simple syntax limits
if (empty($host)
- or !is_string($host)
- or strlen($host) > 256
+ || !is_string($host)
+ || strlen($host) > 256
+ || !preg_match('/^([a-zA-Z\d.-]*|\[[a-fA-F\d:]+])$/', $host)
) {
return false;
}
//Looks like a bracketed IPv6 address
- if (trim($host, '[]') != $host) {
- return (bool) filter_var(trim($host, '[]'), FILTER_VALIDATE_IP, FILTER_FLAG_IPV6);
+ if (strlen($host) > 2 && substr($host, 0, 1) === '[' && substr($host, -1, 1) === ']') {
+ return filter_var(substr($host, 1, -1), FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) !== false;
}
//If removing all the dots results in a numeric string, it must be an IPv4 address.
//Need to check this first because otherwise things like `999.0.0.0` are considered valid host names
if (is_numeric(str_replace('.', '', $host))) {
//Is it a valid IPv4 address?
- return (bool) filter_var($host, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4);
+ return filter_var($host, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) !== false;
}
- if (filter_var('http://' . $host, FILTER_VALIDATE_URL)) {
+ if (filter_var('http://' . $host, FILTER_VALIDATE_URL) !== false) {
//Is it a syntactically valid hostname?
return true;
}
@@ -3666,11 +3915,11 @@ class PHPMailer
protected function lang($key)
{
if (count($this->language) < 1) {
- $this->setLanguage('en'); // set the default language
+ $this->setLanguage(); // set the default language
}
if (array_key_exists($key, $this->language)) {
- if ('smtp_connect_failed' == $key) {
+ 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.
@@ -3701,15 +3950,28 @@ class PHPMailer
*
* @param string $name Custom header name
* @param string|null $value Header value
+ *
+ * @throws Exception
*/
public function addCustomHeader($name, $value = null)
{
- if (null === $value) {
+ if (null === $value && strpos($name, ':') !== false) {
// Value passed in as name:value
- $this->CustomHeader[] = explode(':', $name, 2);
- } else {
- $this->CustomHeader[] = [$name, $value];
+ list($name, $value) = explode(':', $name, 2);
}
+ $name = trim($name);
+ $value = trim($value);
+ //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('Invalid header name or value');
+ }
+
+ return false;
+ }
+ $this->CustomHeader[] = [$name, $value];
+
+ return true;
}
/**
@@ -3736,36 +3998,45 @@ class PHPMailer
* @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 @see PHPMailer::html2text()
+ * or your own custom converter @return string $message The transformed message Body
+ *
+ * @throws Exception
*
- * @return string $message The transformed message Body
+ * @see PHPMailer::html2text()
*/
public function msgHTML($message, $basedir = '', $advanced = false)
{
- preg_match_all('/(src|background)=["\'](.*)["\']/Ui', $message, $images);
+ preg_match_all('/(?<!-)(src|background)=["\'](.*)["\']/Ui', $message, $images);
if (array_key_exists(2, $images)) {
- if (strlen($basedir) > 1 && '/' != substr($basedir, -1)) {
+ if (strlen($basedir) > 1 && '/' !== substr($basedir, -1)) {
// Ensure $basedir has a trailing /
$basedir .= '/';
}
foreach ($images[2] as $imgindex => $url) {
// Convert data URIs into embedded images
//e.g. "data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="
+ $match = [];
if (preg_match('#^data:(image/(?:jpe?g|gif|png));?(base64)?,(.+)#', $url, $match)) {
- if (count($match) == 4 and static::ENCODING_BASE64 == $match[2]) {
+ if (count($match) === 4 && static::ENCODING_BASE64 === $match[2]) {
$data = base64_decode($match[3]);
- } elseif ('' == $match[2]) {
+ } elseif ('' === $match[2]) {
$data = rawurldecode($match[3]);
} else {
//Not recognised so leave it alone
continue;
}
- //Hash the decoded data, not the URL so that the same data-URI image used in multiple places
+ //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 = hash('sha256', $data) . '@phpmailer.0'; // RFC2392 S 2
+ $cid = substr(hash('sha256', $data), 0, 32) . '@phpmailer.0'; // RFC2392 S 2
if (!$this->cidExists($cid)) {
- $this->addStringEmbeddedImage($data, $cid, 'embed' . $imgindex, static::ENCODING_BASE64, $match[1]);
+ $this->addStringEmbeddedImage(
+ $data,
+ $cid,
+ 'embed' . $imgindex,
+ static::ENCODING_BASE64,
+ $match[1]
+ );
}
$message = str_replace(
$images[0][$imgindex],
@@ -3777,22 +4048,23 @@ class PHPMailer
if (// Only process relative URLs if a basedir is provided (i.e. no absolute local paths)
!empty($basedir)
// Ignore URLs containing parent dir traversal (..)
- and (strpos($url, '..') === false)
+ && (strpos($url, '..') === false)
// Do not change urls that are already inline images
- and 0 !== strpos($url, 'cid:')
+ && 0 !== strpos($url, 'cid:')
// Do not change absolute URLs, including anonymous protocol
- and !preg_match('#^[a-z][a-z0-9+.-]*:?//#i', $url)
+ && !preg_match('#^[a-z][a-z0-9+.-]*:?//#i', $url)
) {
- $filename = basename($url);
+ $filename = static::mb_pathinfo($url, PATHINFO_BASENAME);
$directory = dirname($url);
- if ('.' == $directory) {
+ if ('.' === $directory) {
$directory = '';
}
- $cid = hash('sha256', $url) . '@phpmailer.0'; // RFC2392 S 2
- if (strlen($basedir) > 1 and '/' != substr($basedir, -1)) {
+ // RFC2392 S 2
+ $cid = substr(hash('sha256', $url), 0, 32) . '@phpmailer.0';
+ if (strlen($basedir) > 1 && '/' !== substr($basedir, -1)) {
$basedir .= '/';
}
- if (strlen($directory) > 1 and '/' != substr($directory, -1)) {
+ if (strlen($directory) > 1 && '/' !== substr($directory, -1)) {
$directory .= '/';
}
if ($this->addEmbeddedImage(
@@ -3812,7 +4084,7 @@ class PHPMailer
}
}
}
- $this->isHTML(true);
+ $this->isHTML();
// Convert all message body line breaks to LE, makes quoted-printable encoding work much better
$this->Body = static::normalizeBreaks($message);
$this->AltBody = static::normalizeBreaks($this->html2text($message, $advanced));
@@ -3850,7 +4122,7 @@ class PHPMailer
public function html2text($html, $advanced = false)
{
if (is_callable($advanced)) {
- return call_user_func($advanced, $html);
+ return $advanced($html);
}
return html_entity_decode(
@@ -4014,7 +4286,7 @@ class PHPMailer
* Multi-byte-safe pathinfo replacement.
* Drop-in replacement for pathinfo(), but multibyte- and cross-platform-safe.
*
- * @see http://www.php.net/manual/en/function.pathinfo.php#107461
+ * @see http://www.php.net/manual/en/function.pathinfo.php#107461
*
* @param string $path A filename or path, does not need to exist as a file
* @param int|string $options Either a PATHINFO_* constant,
@@ -4026,7 +4298,7 @@ class PHPMailer
{
$ret = ['dirname' => '', 'basename' => '', 'extension' => '', 'filename' => ''];
$pathinfo = [];
- if (preg_match('#^(.*?)[\\\\/]*(([^/\\\\]*?)(\.([^\.\\\\/]+?)|))[\\\\/\.]*$#im', $path, $pathinfo)) {
+ if (preg_match('#^(.*?)[\\\\/]*(([^/\\\\]*?)(\.([^.\\\\/]+?)|))[\\\\/.]*$#m', $path, $pathinfo)) {
if (array_key_exists(1, $pathinfo)) {
$ret['dirname'] = $pathinfo[1];
}
@@ -4063,9 +4335,9 @@ class PHPMailer
* You should avoid this function - it's more verbose, less efficient, more error-prone and
* harder to debug than setting properties directly.
* Usage Example:
- * `$mail->set('SMTPSecure', 'tls');`
+ * `$mail->set('SMTPSecure', static::ENCRYPTION_STARTTLS);`
* is the same as:
- * `$mail->SMTPSecure = 'tls';`.
+ * `$mail->SMTPSecure = static::ENCRYPTION_STARTTLS;`.
*
* @param string $name The property name to set
* @param mixed $value The value to set the property to
@@ -4112,7 +4384,7 @@ class PHPMailer
$breaktype = static::$LE;
}
// Normalise to \n
- $text = str_replace(["\r\n", "\r"], "\n", $text);
+ $text = str_replace([self::CRLF, "\r"], "\n", $text);
// Now convert LE as needed
if ("\n" !== $breaktype) {
$text = str_replace("\n", $breaktype, $text);
@@ -4122,6 +4394,18 @@ class PHPMailer
}
/**
+ * Remove trailing breaks from a string.
+ *
+ * @param string $text
+ *
+ * @return string The text to remove breaks from
+ */
+ public static function stripTrailingWSP($text)
+ {
+ return rtrim($text, " \r\n\t");
+ }
+
+ /**
* Return the current line break format string.
*
* @return string
@@ -4170,7 +4454,7 @@ class PHPMailer
$len = strlen($txt);
for ($i = 0; $i < $len; ++$i) {
$ord = ord($txt[$i]);
- if (((0x21 <= $ord) and ($ord <= 0x3A)) or $ord == 0x3C or ((0x3E <= $ord) and ($ord <= 0x7E))) {
+ if (((0x21 <= $ord) && ($ord <= 0x3A)) || $ord === 0x3C || ((0x3E <= $ord) && ($ord <= 0x7E))) {
$line .= $txt[$i];
} else {
$line .= '=' . sprintf('%02X', $ord);
@@ -4201,7 +4485,7 @@ class PHPMailer
$privKeyStr = !empty($this->DKIM_private_string) ?
$this->DKIM_private_string :
file_get_contents($this->DKIM_private);
- if ('' != $this->DKIM_passphrase) {
+ if ('' !== $this->DKIM_passphrase) {
$privKey = openssl_pkey_get_private($privKeyStr, $this->DKIM_passphrase);
} else {
$privKey = openssl_pkey_get_private($privKeyStr);
@@ -4221,7 +4505,7 @@ class PHPMailer
* Uses the 'relaxed' algorithm from RFC6376 section 3.4.2.
* Canonicalized headers should *always* use CRLF, regardless of mailer setting.
*
- * @see https://tools.ietf.org/html/rfc6376#section-3.4.2
+ * @see https://tools.ietf.org/html/rfc6376#section-3.4.2
*
* @param string $signHeader Header
*
@@ -4229,13 +4513,15 @@ class PHPMailer
*/
public function DKIM_HeaderC($signHeader)
{
- //Unfold all header continuation lines
- //Also collapses folded whitespace.
+ //Normalize breaks to CRLF (regardless of the mailer)
+ $signHeader = static::normalizeBreaks($signHeader, self::CRLF);
+ //Unfold header lines
//Note PCRE \s is too broad a definition of whitespace; RFC5322 defines it as `[ \t]`
//@see https://tools.ietf.org/html/rfc5322#section-2.2
//That means this may break if you do something daft like put vertical tabs in your headers.
$signHeader = preg_replace('/\r\n[ \t]+/', ' ', $signHeader);
- $lines = explode("\r\n", $signHeader);
+ //Break headers out into an array
+ $lines = explode(self::CRLF, $signHeader);
foreach ($lines as $key => $line) {
//If the header is missing a :, skip it as it's invalid
//This is likely to happen because the explode() above will also split
@@ -4246,16 +4532,16 @@ class PHPMailer
list($heading, $value) = explode(':', $line, 2);
//Lower-case header name
$heading = strtolower($heading);
- //Collapse white space within the value
- $value = preg_replace('/[ \t]{2,}/', ' ', $value);
+ //Collapse white space within the value, also convert WSP to space
+ $value = preg_replace('/[ \t]+/', ' ', $value);
//RFC6376 is slightly unclear here - it says to delete space at the *end* of each value
//But then says to delete space before and after the colon.
//Net result is the same as trimming both ends of the value.
- //by elimination, the same applies to the field name
+ //By elimination, the same applies to the field name
$lines[$key] = trim($heading, " \t") . ':' . trim($value, " \t");
}
- return implode("\r\n", $lines);
+ return implode(self::CRLF, $lines);
}
/**
@@ -4263,7 +4549,7 @@ class PHPMailer
* Uses the 'simple' algorithm from RFC6376 section 3.4.3.
* Canonicalized bodies should *always* use CRLF, regardless of mailer setting.
*
- * @see https://tools.ietf.org/html/rfc6376#section-3.4.3
+ * @see https://tools.ietf.org/html/rfc6376#section-3.4.3
*
* @param string $body Message Body
*
@@ -4272,13 +4558,13 @@ class PHPMailer
public function DKIM_BodyC($body)
{
if (empty($body)) {
- return "\r\n";
+ return self::CRLF;
}
// Normalize line endings to CRLF
- $body = static::normalizeBreaks($body, "\r\n");
+ $body = static::normalizeBreaks($body, self::CRLF);
//Reduce multiple trailing line breaks to a single one
- return rtrim($body, "\r\n") . "\r\n";
+ return static::stripTrailingWSP($body) . self::CRLF;
}
/**
@@ -4288,109 +4574,143 @@ class PHPMailer
* @param string $subject Subject
* @param string $body Body
*
+ * @throws Exception
+ *
* @return string
*/
public function DKIM_Add($headers_line, $subject, $body)
{
$DKIMsignatureType = 'rsa-sha256'; // Signature & hash algorithms
- $DKIMcanonicalization = 'relaxed/simple'; // Canonicalization of header/body
+ $DKIMcanonicalization = 'relaxed/simple'; // Canonicalization methods of header & body
$DKIMquery = 'dns/txt'; // Query method
- $DKIMtime = time(); // Signature Timestamp = seconds since 00:00:00 - Jan 1, 1970 (UTC time zone)
- $subject_header = "Subject: $subject";
- $headers = explode(static::$LE, $headers_line);
- $from_header = '';
- $to_header = '';
- $date_header = '';
- $current = '';
- $copiedHeaderFields = '';
- $foundExtraHeaders = [];
- $extraHeaderKeys = '';
- $extraHeaderValues = '';
- $extraCopyHeaderFields = '';
- foreach ($headers as $header) {
- if (strpos($header, 'From:') === 0) {
- $from_header = $header;
- $current = 'from_header';
- } elseif (strpos($header, 'To:') === 0) {
- $to_header = $header;
- $current = 'to_header';
- } elseif (strpos($header, 'Date:') === 0) {
- $date_header = $header;
- $current = 'date_header';
- } elseif (!empty($this->DKIM_extraHeaders)) {
- foreach ($this->DKIM_extraHeaders as $extraHeader) {
- if (strpos($header, $extraHeader . ':') === 0) {
- $headerValue = $header;
- foreach ($this->CustomHeader as $customHeader) {
- if ($customHeader[0] === $extraHeader) {
- $headerValue = trim($customHeader[0]) .
- ': ' .
- $this->encodeHeader(trim($customHeader[1]));
- break;
- }
+ $DKIMtime = time();
+ //Always sign these headers without being asked
+ //Recommended list from https://tools.ietf.org/html/rfc6376#section-5.4.1
+ $autoSignHeaders = [
+ 'from',
+ 'to',
+ 'cc',
+ 'date',
+ 'subject',
+ 'reply-to',
+ 'message-id',
+ 'content-type',
+ 'mime-version',
+ 'x-mailer',
+ ];
+ if (stripos($headers_line, 'Subject') === false) {
+ $headers_line .= 'Subject: ' . $subject . static::$LE;
+ }
+ $headerLines = explode(static::$LE, $headers_line);
+ $currentHeaderLabel = '';
+ $currentHeaderValue = '';
+ $parsedHeaders = [];
+ $headerLineIndex = 0;
+ $headerLineCount = count($headerLines);
+ foreach ($headerLines as $headerLine) {
+ $matches = [];
+ if (preg_match('/^([^ \t]*?)(?::[ \t]*)(.*)$/', $headerLine, $matches)) {
+ if ($currentHeaderLabel !== '') {
+ //We were previously in another header; This is the start of a new header, so save the previous one
+ $parsedHeaders[] = ['label' => $currentHeaderLabel, 'value' => $currentHeaderValue];
+ }
+ $currentHeaderLabel = $matches[1];
+ $currentHeaderValue = $matches[2];
+ } elseif (preg_match('/^[ \t]+(.*)$/', $headerLine, $matches)) {
+ //This is a folded continuation of the current header, so unfold it
+ $currentHeaderValue .= ' ' . $matches[1];
+ }
+ ++$headerLineIndex;
+ if ($headerLineIndex >= $headerLineCount) {
+ //This was the last line, so finish off this header
+ $parsedHeaders[] = ['label' => $currentHeaderLabel, 'value' => $currentHeaderValue];
+ }
+ }
+ $copiedHeaders = [];
+ $headersToSignKeys = [];
+ $headersToSign = [];
+ foreach ($parsedHeaders as $header) {
+ //Is this header one that must be included in the DKIM signature?
+ if (in_array(strtolower($header['label']), $autoSignHeaders, true)) {
+ $headersToSignKeys[] = $header['label'];
+ $headersToSign[] = $header['label'] . ': ' . $header['value'];
+ if ($this->DKIM_copyHeaderFields) {
+ $copiedHeaders[] = $header['label'] . ':' . //Note no space after this, as per RFC
+ str_replace('|', '=7C', $this->DKIM_QP($header['value']));
+ }
+ continue;
+ }
+ //Is this an extra custom header we've been asked to sign?
+ if (in_array($header['label'], $this->DKIM_extraHeaders, true)) {
+ //Find its value in custom headers
+ foreach ($this->CustomHeader as $customHeader) {
+ if ($customHeader[0] === $header['label']) {
+ $headersToSignKeys[] = $header['label'];
+ $headersToSign[] = $header['label'] . ': ' . $header['value'];
+ if ($this->DKIM_copyHeaderFields) {
+ $copiedHeaders[] = $header['label'] . ':' . //Note no space after this, as per RFC
+ str_replace('|', '=7C', $this->DKIM_QP($header['value']));
}
- $foundExtraHeaders[$extraHeader] = $headerValue;
- $current = '';
- break;
+ //Skip straight to the next header
+ continue 2;
}
}
- } else {
- if (!empty($$current) and strpos($header, ' =?') === 0) {
- $$current .= $header;
- } else {
- $current = '';
- }
}
}
- foreach ($foundExtraHeaders as $key => $value) {
- $extraHeaderKeys .= ':' . $key;
- $extraHeaderValues .= $value . "\r\n";
- if ($this->DKIM_copyHeaderFields) {
- $extraCopyHeaderFields .= "\t|" . str_replace('|', '=7C', $this->DKIM_QP($value)) . ";\r\n";
+ $copiedHeaderFields = '';
+ if ($this->DKIM_copyHeaderFields && count($copiedHeaders) > 0) {
+ //Assemble a DKIM 'z' tag
+ $copiedHeaderFields = ' z=';
+ $first = true;
+ foreach ($copiedHeaders as $copiedHeader) {
+ if (!$first) {
+ $copiedHeaderFields .= static::$LE . ' |';
+ }
+ //Fold long values
+ if (strlen($copiedHeader) > self::STD_LINE_LENGTH - 3) {
+ $copiedHeaderFields .= substr(
+ chunk_split($copiedHeader, self::STD_LINE_LENGTH - 3, static::$LE . self::FWS),
+ 0,
+ -strlen(static::$LE . self::FWS)
+ );
+ } else {
+ $copiedHeaderFields .= $copiedHeader;
+ }
+ $first = false;
}
+ $copiedHeaderFields .= ';' . static::$LE;
}
- if ($this->DKIM_copyHeaderFields) {
- $from = str_replace('|', '=7C', $this->DKIM_QP($from_header));
- $to = str_replace('|', '=7C', $this->DKIM_QP($to_header));
- $date = str_replace('|', '=7C', $this->DKIM_QP($date_header));
- $subject = str_replace('|', '=7C', $this->DKIM_QP($subject_header));
- $copiedHeaderFields = "\tz=$from\r\n" .
- "\t|$to\r\n" .
- "\t|$date\r\n" .
- "\t|$subject;\r\n" .
- $extraCopyHeaderFields;
- }
+ $headerKeys = ' h=' . implode(':', $headersToSignKeys) . ';' . static::$LE;
+ $headerValues = implode(static::$LE, $headersToSign);
$body = $this->DKIM_BodyC($body);
- $DKIMlen = strlen($body); // Length of body
$DKIMb64 = base64_encode(pack('H*', hash('sha256', $body))); // Base64 of packed binary SHA-256 hash of body
- if ('' == $this->DKIM_identity) {
- $ident = '';
- } else {
- $ident = ' i=' . $this->DKIM_identity . ';';
- }
- $dkimhdrs = 'DKIM-Signature: v=1; a=' .
- $DKIMsignatureType . '; q=' .
- $DKIMquery . '; l=' .
- $DKIMlen . '; s=' .
- $this->DKIM_selector .
- ";\r\n" .
- "\tt=" . $DKIMtime . '; c=' . $DKIMcanonicalization . ";\r\n" .
- "\th=From:To:Date:Subject" . $extraHeaderKeys . ";\r\n" .
- "\td=" . $this->DKIM_domain . ';' . $ident . "\r\n" .
+ $ident = '';
+ if ('' !== $this->DKIM_identity) {
+ $ident = ' i=' . $this->DKIM_identity . ';' . static::$LE;
+ }
+ //The DKIM-Signature header is included in the signature *except for* the value of the `b` tag
+ //which is appended after calculating the signature
+ //https://tools.ietf.org/html/rfc6376#section-3.5
+ $dkimSignatureHeader = 'DKIM-Signature: v=1;' .
+ ' d=' . $this->DKIM_domain . ';' .
+ ' s=' . $this->DKIM_selector . ';' . static::$LE .
+ ' a=' . $DKIMsignatureType . ';' .
+ ' q=' . $DKIMquery . ';' .
+ ' t=' . $DKIMtime . ';' .
+ ' c=' . $DKIMcanonicalization . ';' . static::$LE .
+ $headerKeys .
+ $ident .
$copiedHeaderFields .
- "\tbh=" . $DKIMb64 . ";\r\n" .
- "\tb=";
- $toSign = $this->DKIM_HeaderC(
- $from_header . "\r\n" .
- $to_header . "\r\n" .
- $date_header . "\r\n" .
- $subject_header . "\r\n" .
- $extraHeaderValues .
- $dkimhdrs
+ ' bh=' . $DKIMb64 . ';' . static::$LE .
+ ' b=';
+ //Canonicalize the set of headers
+ $canonicalizedHeaders = $this->DKIM_HeaderC(
+ $headerValues . static::$LE . $dkimSignatureHeader
);
- $signed = $this->DKIM_Sign($toSign);
+ $signature = $this->DKIM_Sign($canonicalizedHeaders);
+ $signature = trim(chunk_split($signature, self::STD_LINE_LENGTH - 3, static::$LE . self::FWS));
- return static::normalizeBreaks($dkimhdrs . $signed) . static::$LE;
+ return static::normalizeBreaks($dkimSignatureHeader . $signature);
}
/**
@@ -4475,7 +4795,7 @@ class PHPMailer
*/
protected function doCallback($isSent, $to, $cc, $bcc, $subject, $body, $from, $extra)
{
- if (!empty($this->action_function) and is_callable($this->action_function)) {
+ if (!empty($this->action_function) && is_callable($this->action_function)) {
call_user_func($this->action_function, $isSent, $to, $cc, $bcc, $subject, $body, $from, $extra);
}
}
@@ -4492,8 +4812,6 @@ class PHPMailer
/**
* Set an OAuth instance.
- *
- * @param OAuth $oauth
*/
public function setOAuth(OAuth $oauth)
{
diff --git a/lib/PHPMailer/PHPMailer/SMTP.php b/lib/PHPMailer/PHPMailer/SMTP.php
index da85442bf..1e38ba7da 100644
--- a/lib/PHPMailer/PHPMailer/SMTP.php
+++ b/lib/PHPMailer/PHPMailer/SMTP.php
@@ -9,7 +9,7 @@
* @author Jim Jagielski (jimjag) <jimjag@gmail.com>
* @author Andy Prevost (codeworxtech) <codeworxtech@users.sourceforge.net>
* @author Brent R. Matzelle (original founder)
- * @copyright 2012 - 2017 Marcus Bointon
+ * @copyright 2012 - 2019 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
@@ -24,8 +24,8 @@ namespace PHPMailer\PHPMailer;
* PHPMailer RFC821 SMTP email transport class.
* Implements RFC 821 SMTP commands and provides some utility methods for sending mail to an SMTP server.
*
- * @author Chris Ryan
- * @author Marcus Bointon <phpmailer@synchromedia.co.uk>
+ * @author Chris Ryan
+ * @author Marcus Bointon <phpmailer@synchromedia.co.uk>
*/
class SMTP
{
@@ -34,7 +34,7 @@ class SMTP
*
* @var string
*/
- const VERSION = '6.0.7';
+ const VERSION = '6.1.5';
/**
* SMTP line break constant.
@@ -51,34 +51,57 @@ class SMTP
const DEFAULT_PORT = 25;
/**
- * The maximum line length allowed by RFC 2822 section 2.1.1.
+ * The maximum line length allowed by RFC 5321 section 4.5.3.1.6,
+ * *excluding* a trailing CRLF break.
+ *
+ * @see https://tools.ietf.org/html/rfc5321#section-4.5.3.1.6
*
* @var int
*/
const MAX_LINE_LENGTH = 998;
/**
+ * The maximum line length allowed for replies in RFC 5321 section 4.5.3.1.5,
+ * *including* a trailing CRLF line break.
+ *
+ * @see https://tools.ietf.org/html/rfc5321#section-4.5.3.1.5
+ *
+ * @var int
+ */
+ const MAX_REPLY_LENGTH = 512;
+
+ /**
* Debug level for no output.
+ *
+ * @var int
*/
const DEBUG_OFF = 0;
/**
* Debug level to show client -> server messages.
+ *
+ * @var int
*/
const DEBUG_CLIENT = 1;
/**
* Debug level to show client -> server and server -> client messages.
+ *
+ * @var int
*/
const DEBUG_SERVER = 2;
/**
* Debug level to show connection status, client -> server and server -> client messages.
+ *
+ * @var int
*/
const DEBUG_CONNECTION = 3;
/**
* Debug level to show all messages.
+ *
+ * @var int
*/
const DEBUG_LOWLEVEL = 4;
@@ -197,7 +220,7 @@ class SMTP
*
* @var string|null
*/
- protected $helo_rply = null;
+ protected $helo_rply;
/**
* The set of SMTP extensions sent in reply to EHLO command.
@@ -209,7 +232,7 @@ class SMTP
*
* @var array|null
*/
- protected $server_caps = null;
+ protected $server_caps;
/**
* The most recent reply received from the server.
@@ -239,7 +262,7 @@ class SMTP
return;
}
//Avoid clash with built-in function names
- if (!in_array($this->Debugoutput, ['error_log', 'html', 'echo']) and is_callable($this->Debugoutput)) {
+ if (is_callable($this->Debugoutput) && !in_array($this->Debugoutput, ['error_log', 'html', 'echo'])) {
call_user_func($this->Debugoutput, $str, $level);
return;
@@ -260,12 +283,12 @@ class SMTP
case 'echo':
default:
//Normalize line breaks
- $str = preg_replace('/\r\n|\r/ms', "\n", $str);
+ $str = preg_replace('/\r\n|\r/m', "\n", $str);
echo gmdate('Y-m-d H:i:s'),
"\t",
//Trim trailing space
trim(
- //Indent for readability, except for trailing break
+ //Indent for readability, except for trailing break
str_replace(
"\n",
"\n \t ",
@@ -348,7 +371,7 @@ class SMTP
'Failed to connect to server',
'',
(string) $errno,
- (string) $errstr
+ $errstr
);
$this->edebug(
'SMTP ERROR: ' . $this->error['error']
@@ -361,10 +384,10 @@ class SMTP
$this->edebug('Connection: opened', self::DEBUG_CONNECTION);
// SMTP server can take longer to respond, give longer timeout for first read
// Windows does not have support for this timeout function
- if (substr(PHP_OS, 0, 3) != 'WIN') {
- $max = ini_get('max_execution_time');
+ if (strpos(PHP_OS, 'WIN') !== 0) {
+ $max = (int) ini_get('max_execution_time');
// Don't bother if unlimited
- if (0 != $max and $timeout > $max) {
+ if (0 !== $max && $timeout > $max) {
@set_time_limit($timeout);
}
stream_set_timeout($this->smtp_conn, $timeout, 0);
@@ -444,14 +467,14 @@ class SMTP
return false;
}
- $this->edebug('Auth method requested: ' . ($authtype ? $authtype : 'UNSPECIFIED'), self::DEBUG_LOWLEVEL);
+ $this->edebug('Auth method requested: ' . ($authtype ?: 'UNSPECIFIED'), self::DEBUG_LOWLEVEL);
$this->edebug(
'Auth methods available on the server: ' . implode(',', $this->server_caps['AUTH']),
self::DEBUG_LOWLEVEL
);
//If we have requested a specific auth type, check the server supports it before trying others
- if (null !== $authtype and !in_array($authtype, $this->server_caps['AUTH'])) {
+ if (null !== $authtype && !in_array($authtype, $this->server_caps['AUTH'], true)) {
$this->edebug('Requested auth method not available: ' . $authtype, self::DEBUG_LOWLEVEL);
$authtype = null;
}
@@ -460,7 +483,7 @@ class SMTP
//If no auth mechanism is specified, attempt to use these, in this order
//Try CRAM-MD5 first as it's more secure than the others
foreach (['CRAM-MD5', 'LOGIN', 'PLAIN', 'XOAUTH2'] as $method) {
- if (in_array($method, $this->server_caps['AUTH'])) {
+ if (in_array($method, $this->server_caps['AUTH'], true)) {
$authtype = $method;
break;
}
@@ -470,10 +493,10 @@ class SMTP
return false;
}
- self::edebug('Auth method selected: ' . $authtype, self::DEBUG_LOWLEVEL);
+ $this->edebug('Auth method selected: ' . $authtype, self::DEBUG_LOWLEVEL);
}
- if (!in_array($authtype, $this->server_caps['AUTH'])) {
+ if (!in_array($authtype, $this->server_caps['AUTH'], true)) {
$this->setError("The requested authentication method \"$authtype\" is not supported by the server");
return false;
@@ -663,13 +686,13 @@ class SMTP
$field = substr($lines[0], 0, strpos($lines[0], ':'));
$in_headers = false;
- if (!empty($field) and strpos($field, ' ') === false) {
+ if (!empty($field) && strpos($field, ' ') === false) {
$in_headers = true;
}
foreach ($lines as $line) {
$lines_out = [];
- if ($in_headers and $line == '') {
+ if ($in_headers && $line === '') {
$in_headers = false;
}
//Break this line up into several smaller lines if it's too long
@@ -700,7 +723,7 @@ class SMTP
//Send the lines to the server
foreach ($lines_out as $line_out) {
//RFC2821 section 4.5.2
- if (!empty($line_out) and $line_out[0] == '.') {
+ if (!empty($line_out) && $line_out[0] === '.') {
$line_out = '.' . $line_out;
}
$this->client_send($line_out . static::LE, 'DATA');
@@ -710,7 +733,7 @@ class SMTP
//Message data has been sent, complete the command
//Increase timelimit for end of DATA command
$savetimelimit = $this->Timelimit;
- $this->Timelimit = $this->Timelimit * 2;
+ $this->Timelimit *= 2;
$result = $this->sendCommand('DATA END', '.', 250);
$this->recordLastTransactionID();
//Restore timelimit
@@ -745,7 +768,7 @@ class SMTP
*
* @return bool
*
- * @see hello()
+ * @see hello()
*/
protected function sendHello($hello, $host)
{
@@ -838,7 +861,7 @@ class SMTP
{
$noerror = $this->sendCommand('QUIT', 'QUIT', 221);
$err = $this->error; //Save any error
- if ($noerror or $close_on_error) {
+ if ($noerror || $close_on_error) {
$this->close();
$this->error = $err; //Restore any error from the quit command
}
@@ -853,14 +876,35 @@ class SMTP
* Implements from RFC 821: RCPT <SP> TO:<forward-path> <CRLF>.
*
* @param string $address The address the message is being sent to
+ * @param string $dsn Comma separated list of DSN notifications. NEVER, SUCCESS, FAILURE
+ * or DELAY. If you specify NEVER all other notifications are ignored.
*
* @return bool
*/
- public function recipient($address)
+ public function recipient($address, $dsn = '')
{
+ if (empty($dsn)) {
+ $rcpt = 'RCPT TO:<' . $address . '>';
+ } else {
+ $dsn = strtoupper($dsn);
+ $notify = [];
+
+ if (strpos($dsn, 'NEVER') !== false) {
+ $notify[] = 'NEVER';
+ } else {
+ foreach (['SUCCESS', 'FAILURE', 'DELAY'] as $value) {
+ if (strpos($dsn, $value) !== false) {
+ $notify[] = $value;
+ }
+ }
+ }
+
+ $rcpt = 'RCPT TO:<' . $address . '> NOTIFY=' . implode(',', $notify);
+ }
+
return $this->sendCommand(
'RCPT TO',
- 'RCPT TO:<' . $address . '>',
+ $rcpt,
[250, 251]
);
}
@@ -894,7 +938,7 @@ class SMTP
return false;
}
//Reject line breaks in all commands
- if (strpos($commandstring, "\n") !== false or strpos($commandstring, "\r") !== false) {
+ if ((strpos($commandstring, "\n") !== false) || (strpos($commandstring, "\r") !== false)) {
$this->setError("Command '$command' contained line breaks");
return false;
@@ -904,8 +948,8 @@ class SMTP
$this->last_reply = $this->get_lines();
// Fetch SMTP code and possible error code explanation
$matches = [];
- if (preg_match('/^([0-9]{3})[ -](?:([0-9]\\.[0-9]\\.[0-9]) )?/', $this->last_reply, $matches)) {
- $code = $matches[1];
+ if (preg_match('/^([\d]{3})[ -](?:([\d]\\.[\d]\\.[\d]{1,2}) )?/', $this->last_reply, $matches)) {
+ $code = (int) $matches[1];
$code_ex = (count($matches) > 2 ? $matches[2] : null);
// Cut off error code from each response line
$detail = preg_replace(
@@ -916,14 +960,14 @@ class SMTP
);
} else {
// Fall back to simple parsing if regex fails
- $code = substr($this->last_reply, 0, 3);
+ $code = (int) substr($this->last_reply, 0, 3);
$code_ex = null;
$detail = substr($this->last_reply, 4);
}
$this->edebug('SERVER -> CLIENT: ' . $this->last_reply, self::DEBUG_SERVER);
- if (!in_array($code, (array) $expect)) {
+ if (!in_array($code, (array) $expect, true)) {
$this->setError(
"$command command failed",
$detail,
@@ -1014,9 +1058,9 @@ class SMTP
{
//If SMTP transcripts are left enabled, or debug output is posted online
//it can leak credentials, so hide credentials in all but lowest level
- if (self::DEBUG_LOWLEVEL > $this->do_debug and
+ if (self::DEBUG_LOWLEVEL > $this->do_debug &&
in_array($command, ['User & Password', 'Username', 'Password'], true)) {
- $this->edebug('CLIENT -> SERVER: <credentials hidden>', self::DEBUG_CLIENT);
+ $this->edebug('CLIENT -> SERVER: [credentials hidden]', self::DEBUG_CLIENT);
} else {
$this->edebug('CLIENT -> SERVER: ' . $data, self::DEBUG_CLIENT);
}
@@ -1062,7 +1106,7 @@ class SMTP
*
* @param string $name Name of SMTP extension or 'HELO'|'EHLO'
*
- * @return mixed
+ * @return string|bool|null
*/
public function getServerExt($name)
{
@@ -1073,10 +1117,10 @@ class SMTP
}
if (!array_key_exists($name, $this->server_caps)) {
- if ('HELO' == $name) {
+ if ('HELO' === $name) {
return $this->server_caps['EHLO'];
}
- if ('EHLO' == $name || array_key_exists('EHLO', $this->server_caps)) {
+ if ('EHLO' === $name || array_key_exists('EHLO', $this->server_caps)) {
return false;
}
$this->setError('HELO handshake was used; No information about server extensions available');
@@ -1120,7 +1164,7 @@ class SMTP
}
$selR = [$this->smtp_conn];
$selW = null;
- while (is_resource($this->smtp_conn) and !feof($this->smtp_conn)) {
+ while (is_resource($this->smtp_conn) && !feof($this->smtp_conn)) {
//Must pass vars in here as params are by reference
if (!stream_select($selR, $selW, $selW, $this->Timelimit)) {
$this->edebug(
@@ -1130,13 +1174,13 @@ class SMTP
break;
}
//Deliberate noise suppression - errors are handled afterwards
- $str = @fgets($this->smtp_conn, 515);
+ $str = @fgets($this->smtp_conn, self::MAX_REPLY_LENGTH);
$this->edebug('SMTP INBOUND: "' . trim($str) . '"', self::DEBUG_LOWLEVEL);
$data .= $str;
// If response is only 3 chars (not valid, but RFC5321 S4.2 says it must be handled),
- // or 4th character is a space, we are done reading, break the loop,
- // string array access is a micro-optimisation over strlen
- if (!isset($str[3]) or (isset($str[3]) and $str[3] == ' ')) {
+ // or 4th character is a space or a line break char, we are done reading, break the loop.
+ // String array access is a significant micro-optimisation over strlen
+ if (!isset($str[3]) || $str[3] === ' ' || $str[3] === "\r" || $str[3] === "\n") {
break;
}
// Timed-out? Log and break
@@ -1149,7 +1193,7 @@ class SMTP
break;
}
// Now check if reads took too long
- if ($endtime and time() > $endtime) {
+ if ($endtime && time() > $endtime) {
$this->edebug(
'SMTP -> get_lines(): timelimit reached (' .
$this->Timelimit . ' sec)',
@@ -1289,7 +1333,7 @@ class SMTP
* If no reply has been received yet, it will return null.
* If no pattern was matched, it will return false.
*
- * @return bool|null|string
+ * @return bool|string|null
*/
protected function recordLastTransactionID()
{
@@ -1300,6 +1344,7 @@ class SMTP
} else {
$this->last_smtp_transaction_id = false;
foreach ($this->smtp_transaction_id_patterns as $smtp_transaction_id_pattern) {
+ $matches = [];
if (preg_match($smtp_transaction_id_pattern, $reply, $matches)) {
$this->last_smtp_transaction_id = trim($matches[1]);
break;
@@ -1315,7 +1360,7 @@ class SMTP
* If no reply has been received yet, it will return null.
* If no pattern was matched, it will return false.
*
- * @return bool|null|string
+ * @return bool|string|null
*
* @see recordLastTransactionID()
*/