diff --git a/wp-includes/class-phpmailer.php b/wp-includes/class-phpmailer.php index 7f5e353578..8772db2842 100644 --- a/wp-includes/class-phpmailer.php +++ b/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/wp-includes/class-smtp.php b/wp-includes/class-smtp.php index f9942a5985..8b11c73f22 100644 --- a/wp-includes/class-smtp.php +++ b/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; } } diff --git a/wp-includes/version.php b/wp-includes/version.php index f4ec3204ea..8cedcbada9 100644 --- a/wp-includes/version.php +++ b/wp-includes/version.php @@ -13,7 +13,7 @@ * * @global string $wp_version */ -$wp_version = '5.3-alpha-46096'; +$wp_version = '5.3-alpha-46097'; /** * Holds the WordPress DB revision, increments when changes are made to the WordPress DB schema.