diff --git a/src/wp-includes/class-phpmailer.php b/src/wp-includes/class-phpmailer.php
index 7f5e353578..8772db2842 100644
--- a/src/wp-includes/class-phpmailer.php
+++ b/src/wp-includes/class-phpmailer.php
@@ -31,7 +31,7 @@ class PHPMailer
* The PHPMailer Version number.
* @var string
*/
- public $Version = '5.2.22';
+ public $Version = '5.2.27';
/**
* Email priority.
@@ -440,9 +440,9 @@ class PHPMailer
*
* Parameters:
* boolean $result result of the send action
- * string $to email address of the recipient
- * string $cc cc email addresses
- * string $bcc bcc email addresses
+ * array $to email addresses of the recipients
+ * array $cc cc email addresses
+ * array $bcc bcc email addresses
* string $subject the subject
* string $body the email body
* string $from email address of sender
@@ -659,6 +659,8 @@ class PHPMailer
if ($exceptions !== null) {
$this->exceptions = (boolean)$exceptions;
}
+ //Pick an appropriate debug output format automatically
+ $this->Debugoutput = (strpos(PHP_SAPI, 'cli') !== false ? 'echo' : 'html');
}
/**
@@ -1294,9 +1296,12 @@ class PHPMailer
// Sign with DKIM if enabled
if (!empty($this->DKIM_domain)
- && !empty($this->DKIM_selector)
- && (!empty($this->DKIM_private_string)
- || (!empty($this->DKIM_private) && file_exists($this->DKIM_private))
+ and !empty($this->DKIM_selector)
+ and (!empty($this->DKIM_private_string)
+ or (!empty($this->DKIM_private)
+ and self::isPermittedPath($this->DKIM_private)
+ and file_exists($this->DKIM_private)
+ )
)
) {
$header_dkim = $this->DKIM_Add(
@@ -1461,6 +1466,18 @@ class PHPMailer
return true;
}
+ /**
+ * Check whether a file path is of a permitted type.
+ * Used to reject URLs and phar files from functions that access local file paths,
+ * such as addAttachment.
+ * @param string $path A relative or absolute path to a file.
+ * @return bool
+ */
+ protected static function isPermittedPath($path)
+ {
+ return !preg_match('#^[a-z]+://#i', $path);
+ }
+
/**
* Send mail using the PHP mail() function.
* @param string $header The message headers
@@ -1623,8 +1640,13 @@ class PHPMailer
foreach ($hosts as $hostentry) {
$hostinfo = array();
- if (!preg_match('/^((ssl|tls):\/\/)*([a-zA-Z0-9\.-]*):?([0-9]*)$/', trim($hostentry), $hostinfo)) {
+ if (!preg_match(
+ '/^((ssl|tls):\/\/)*([a-zA-Z0-9\.-]*|\[[a-fA-F0-9:]+\]):?([0-9]*)$/',
+ trim($hostentry),
+ $hostinfo
+ )) {
// Not a valid host entry
+ $this->edebug('Ignoring invalid host: ' . $hostentry);
continue;
}
// $hostinfo[2]: optional ssl or tls prefix
@@ -1743,6 +1765,7 @@ class PHPMailer
'dk' => 'da',
'no' => 'nb',
'se' => 'sv',
+ 'sr' => 'rs'
);
if (isset($renamed_langcodes[$langcode])) {
@@ -1784,7 +1807,7 @@ class PHPMailer
// There is no English translation file
if ($langcode != 'en') {
// Make sure language file path is readable
- if (!is_readable($lang_file)) {
+ if (!self::isPermittedPath($lang_file) or !is_readable($lang_file)) {
$foundlang = false;
} else {
// Overwrite language-specific strings.
@@ -2025,10 +2048,7 @@ class PHPMailer
{
$result = '';
- if ($this->MessageDate == '') {
- $this->MessageDate = self::rfcDate();
- }
- $result .= $this->headerLine('Date', $this->MessageDate);
+ $result .= $this->headerLine('Date', $this->MessageDate == '' ? self::rfcDate() : $this->MessageDate);
// To be created automatically by mail()
if ($this->SingleTo) {
@@ -2495,6 +2515,8 @@ class PHPMailer
* Add an attachment from a path on the filesystem.
* Never use a user-supplied path to a file!
* Returns false if the file could not be found or read.
+ * Explicitly *does not* support passing URLs; PHPMailer is not an HTTP client.
+ * If you need to do that, fetch the resource yourself and pass it in via a local file or string.
* @param string $path Path to the attachment.
* @param string $name Overrides the attachment name.
* @param string $encoding File encoding (see $Encoding).
@@ -2506,7 +2528,7 @@ class PHPMailer
public function addAttachment($path, $name = '', $encoding = 'base64', $type = '', $disposition = 'attachment')
{
try {
- if (!@is_file($path)) {
+ if (!self::isPermittedPath($path) or !@is_file($path)) {
throw new phpmailerException($this->lang('file_access') . $path, self::STOP_CONTINUE);
}
@@ -2687,7 +2709,7 @@ class PHPMailer
protected function encodeFile($path, $encoding = 'base64')
{
try {
- if (!is_readable($path)) {
+ if (!self::isPermittedPath($path) or !file_exists($path)) {
throw new phpmailerException($this->lang('file_open') . $path, self::STOP_CONTINUE);
}
$magic_quotes = get_magic_quotes_runtime();
@@ -3031,7 +3053,7 @@ class PHPMailer
*/
public function addEmbeddedImage($path, $cid, $name = '', $encoding = 'base64', $type = '', $disposition = 'inline')
{
- if (!@is_file($path)) {
+ if (!self::isPermittedPath($path) or !@is_file($path)) {
$this->setError($this->lang('file_access') . $path);
return false;
}
@@ -4034,7 +4056,7 @@ class phpmailerException extends Exception
*/
public function errorMessage()
{
- $errorMsg = '' . $this->getMessage() . "
\n";
+ $errorMsg = '' . htmlspecialchars($this->getMessage()) . "
\n";
return $errorMsg;
}
}
diff --git a/src/wp-includes/class-smtp.php b/src/wp-includes/class-smtp.php
index f9942a5985..8b11c73f22 100644
--- a/src/wp-includes/class-smtp.php
+++ b/src/wp-includes/class-smtp.php
@@ -30,7 +30,7 @@ class SMTP
* The PHPMailer SMTP version number.
* @var string
*/
- const VERSION = '5.2.22';
+ const VERSION = '5.2.27';
/**
* SMTP line break constant.
@@ -81,7 +81,7 @@ class SMTP
* @deprecated Use the `VERSION` constant instead
* @see SMTP::VERSION
*/
- public $Version = '5.2.22';
+ public $Version = '5.2.27';
/**
* SMTP server port number.
@@ -150,16 +150,21 @@ class SMTP
*/
public $Timelimit = 300;
- /**
- * @var array patterns to extract smtp transaction id from smtp reply
- * Only first capture group will be use, use non-capturing group to deal with it
- * Extend this class to override this property to fulfil your needs.
- */
- protected $smtp_transaction_id_patterns = array(
- 'exim' => '/[0-9]{3} OK id=(.*)/',
- 'sendmail' => '/[0-9]{3} 2.0.0 (.*) Message/',
- 'postfix' => '/[0-9]{3} 2.0.0 Ok: queued as (.*)/'
- );
+ /**
+ * @var array Patterns to extract an SMTP transaction id from reply to a DATA command.
+ * The first capture group in each regex will be used as the ID.
+ */
+ protected $smtp_transaction_id_patterns = array(
+ 'exim' => '/[0-9]{3} OK id=(.*)/',
+ 'sendmail' => '/[0-9]{3} 2.0.0 (.*) Message/',
+ 'postfix' => '/[0-9]{3} 2.0.0 Ok: queued as (.*)/'
+ );
+
+ /**
+ * @var string The last transaction ID issued in response to a DATA command,
+ * if one was detected
+ */
+ protected $last_smtp_transaction_id;
/**
* The socket for the server connection.
@@ -227,12 +232,11 @@ class SMTP
break;
case 'html':
//Cleans up output a bit for a better looking, HTML-safe output
- echo htmlentities(
+ echo gmdate('Y-m-d H:i:s') . ' ' . htmlentities(
preg_replace('/[\r\n]+/', '', $str),
ENT_QUOTES,
'UTF-8'
- )
- . "
\n";
+ ) . "
\n";
break;
case 'echo':
default:
@@ -242,7 +246,7 @@ class SMTP
"\n",
"\n \t ",
trim($str)
- )."\n";
+ ) . "\n";
}
}
@@ -276,7 +280,8 @@ class SMTP
}
// Connect to the SMTP server
$this->edebug(
- "Connection: opening to $host:$port, timeout=$timeout, options=".var_export($options, true),
+ "Connection: opening to $host:$port, timeout=$timeout, options=" .
+ var_export($options, true),
self::DEBUG_CONNECTION
);
$errno = 0;
@@ -362,14 +367,14 @@ class SMTP
}
// Begin encrypted connection
- if (!stream_socket_enable_crypto(
+ set_error_handler(array($this, 'errorHandler'));
+ $crypto_ok = stream_socket_enable_crypto(
$this->smtp_conn,
true,
$crypto_method
- )) {
- return false;
- }
- return true;
+ );
+ restore_error_handler();
+ return $crypto_ok;
}
/**
@@ -398,8 +403,7 @@ class SMTP
}
if (array_key_exists('EHLO', $this->server_caps)) {
- // SMTP extensions are available. Let's try to find a proper authentication method
-
+ // SMTP extensions are available; try to find a proper authentication method
if (!array_key_exists('AUTH', $this->server_caps)) {
$this->setError('Authentication is not allowed at this stage');
// 'at this stage' means that auth may be allowed after the stage changes
@@ -424,7 +428,7 @@ class SMTP
$this->setError('No supported authentication methods found');
return false;
}
- self::edebug('Auth method selected: '.$authtype, self::DEBUG_LOWLEVEL);
+ self::edebug('Auth method selected: ' . $authtype, self::DEBUG_LOWLEVEL);
}
if (!in_array($authtype, $this->server_caps['AUTH'])) {
@@ -487,7 +491,7 @@ class SMTP
* Works like hash_hmac('md5', $data, $key)
* in case that function is not available
* @param string $data The data to hash
- * @param string $key The key to hash with
+ * @param string $key The key to hash with
* @access protected
* @return string
*/
@@ -564,10 +568,10 @@ class SMTP
/**
* Send an SMTP DATA command.
* Issues a data command and sends the msg_data to the server,
- * finalizing the mail transaction. $msg_data is the message
+ * finializing the mail transaction. $msg_data is the message
* that is to be send with the headers. Each header needs to be
* on a single line followed by a with the message headers
- * and the message body being separated by an additional .
+ * and the message body being separated by and additional .
* Implements rfc 821: DATA
* @param string $msg_data Message data to send
* @access public
@@ -647,6 +651,7 @@ class SMTP
$savetimelimit = $this->Timelimit;
$this->Timelimit = $this->Timelimit * 2;
$result = $this->sendCommand('DATA END', '.', 250);
+ $this->recordLastTransactionID();
//Restore timelimit
$this->Timelimit = $savetimelimit;
return $result;
@@ -830,7 +835,8 @@ class SMTP
$code_ex = (count($matches) > 2 ? $matches[2] : null);
// Cut off error code from each response line
$detail = preg_replace(
- "/{$code}[ -]".($code_ex ? str_replace('.', '\\.', $code_ex).' ' : '')."/m",
+ "/{$code}[ -]" .
+ ($code_ex ? str_replace('.', '\\.', $code_ex) . ' ' : '') . "/m",
'',
$this->last_reply
);
@@ -926,7 +932,10 @@ class SMTP
public function client_send($data)
{
$this->edebug("CLIENT -> SERVER: $data", self::DEBUG_CLIENT);
- return fwrite($this->smtp_conn, $data);
+ set_error_handler(array($this, 'errorHandler'));
+ $result = fwrite($this->smtp_conn, $data);
+ restore_error_handler();
+ return $result;
}
/**
@@ -1026,8 +1035,10 @@ class SMTP
$this->edebug("SMTP -> get_lines(): \$data is \"$data\"", self::DEBUG_LOWLEVEL);
$this->edebug("SMTP -> get_lines(): \$str is \"$str\"", self::DEBUG_LOWLEVEL);
$data .= $str;
- // If 4th character is a space, we are done reading, break the loop, micro-optimisation over strlen
- if ((isset($str[3]) and $str[3] == ' ')) {
+ // 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] == ' ')) {
break;
}
// Timed-out? Log and break
@@ -1042,7 +1053,7 @@ class SMTP
// Now check if reads took too long
if ($endtime and time() > $endtime) {
$this->edebug(
- 'SMTP -> get_lines(): timelimit reached ('.
+ 'SMTP -> get_lines(): timelimit reached (' .
$this->Timelimit . ' sec)',
self::DEBUG_LOWLEVEL
);
@@ -1145,42 +1156,58 @@ class SMTP
* Reports an error number and string.
* @param integer $errno The error number returned by PHP.
* @param string $errmsg The error message returned by PHP.
+ * @param string $errfile The file the error occurred in
+ * @param integer $errline The line number the error occurred on
*/
- protected function errorHandler($errno, $errmsg)
+ protected function errorHandler($errno, $errmsg, $errfile = '', $errline = 0)
{
- $notice = 'Connection: Failed to connect to server.';
+ $notice = 'Connection failed.';
$this->setError(
$notice,
$errno,
$errmsg
);
$this->edebug(
- $notice . ' Error number ' . $errno . '. "Error notice: ' . $errmsg,
+ $notice . ' Error #' . $errno . ': ' . $errmsg . " [$errfile line $errline]",
self::DEBUG_CONNECTION
);
}
- /**
- * Will return the ID of the last smtp transaction based on a list of patterns provided
- * in SMTP::$smtp_transaction_id_patterns.
- * If no reply has been received yet, it will return null.
- * If no pattern has been matched, it will return false.
- * @return bool|null|string
- */
- public function getLastTransactionID()
- {
- $reply = $this->getLastReply();
+ /**
+ * Extract and return the ID of the last SMTP transaction based on
+ * a list of patterns provided in SMTP::$smtp_transaction_id_patterns.
+ * Relies on the host providing the ID in response to a DATA command.
+ * If no reply has been received yet, it will return null.
+ * If no pattern was matched, it will return false.
+ * @return bool|null|string
+ */
+ protected function recordLastTransactionID()
+ {
+ $reply = $this->getLastReply();
- if (empty($reply)) {
- return null;
- }
+ if (empty($reply)) {
+ $this->last_smtp_transaction_id = null;
+ } else {
+ $this->last_smtp_transaction_id = false;
+ foreach ($this->smtp_transaction_id_patterns as $smtp_transaction_id_pattern) {
+ if (preg_match($smtp_transaction_id_pattern, $reply, $matches)) {
+ $this->last_smtp_transaction_id = $matches[1];
+ }
+ }
+ }
- foreach($this->smtp_transaction_id_patterns as $smtp_transaction_id_pattern) {
- if(preg_match($smtp_transaction_id_pattern, $reply, $matches)) {
- return $matches[1];
- }
- }
+ return $this->last_smtp_transaction_id;
+ }
- return false;
+ /**
+ * Get the queue/transaction ID of the last SMTP transaction
+ * If no reply has been received yet, it will return null.
+ * If no pattern was matched, it will return false.
+ * @return bool|null|string
+ * @see recordLastTransactionID()
+ */
+ public function getLastTransactionID()
+ {
+ return $this->last_smtp_transaction_id;
}
}