Make WordPress Core

Ticket #37210: 37210v2.diff

File 37210v2.diff, 34.9 KB (added by MattyRob, 7 years ago)
  • src/wp-includes/class-phpmailer.php

     
    3131     * The PHPMailer Version number.
    3232     * @var string
    3333     */
    34     public $Version = '5.2.14';
     34    public $Version = '5.2.19';
    3535
    3636    /**
    3737     * Email priority.
     
    201201    /**
    202202     * An ID to be used in the Message-ID header.
    203203     * If empty, a unique id will be generated.
     204     * You can set your own, but it must be in the format "<id@domain>",
     205     * as defined in RFC5322 section 3.6.4 or it will be ignored.
     206     * @see https://tools.ietf.org/html/rfc5322#section-3.6.4
    204207     * @var string
    205208     */
    206209    public $MessageID = '';
     
    285288
    286289    /**
    287290     * SMTP auth type.
    288      * Options are LOGIN (default), PLAIN, NTLM, CRAM-MD5
     291     * Options are CRAM-MD5, LOGIN, PLAIN, NTLM, XOAUTH2, attempted in that order if not specified
    289292     * @var string
    290293     */
    291294    public $AuthType = '';
     
    352355    /**
    353356     * Whether to split multiple to addresses into multiple messages
    354357     * or send them all in one message.
     358     * Only supported in `mail` and `sendmail` transports, not in SMTP.
    355359     * @var boolean
    356360     */
    357361    public $SingleTo = false;
     
    394398
    395399    /**
    396400     * DKIM Identity.
    397      * Usually the email address used as the source of the email
     401     * Usually the email address used as the source of the email.
    398402     * @var string
    399403     */
    400404    public $DKIM_identity = '';
     
    420424    public $DKIM_private = '';
    421425
    422426    /**
     427     * DKIM private key string.
     428     * If set, takes precedence over `$DKIM_private`.
     429     * @var string
     430     */
     431    public $DKIM_private_string = '';
     432
     433    /**
    423434     * Callback Action function name.
    424435     *
    425436     * The function that handles the result of the send email action.
     
    447458    public $XMailer = '';
    448459
    449460    /**
     461     * Which validator to use by default when validating email addresses.
     462     * May be a callable to inject your own validator, but there are several built-in validators.
     463     * @see PHPMailer::validateAddress()
     464     * @var string|callable
     465     * @static
     466     */
     467    public static $validator = 'auto';
     468
     469    /**
    450470     * An instance of the SMTP sender class.
    451471     * @var SMTP
    452472     * @access protected
     
    634654     * Constructor.
    635655     * @param boolean $exceptions Should we throw external exceptions?
    636656     */
    637     public function __construct($exceptions = false)
     657    public function __construct($exceptions = null)
    638658    {
    639         $this->exceptions = (boolean)$exceptions;
     659        if ($exceptions !== null) {
     660            $this->exceptions = (boolean)$exceptions;
     661        }
    640662    }
    641663
    642664    /**
     
    645667    public function __destruct()
    646668    {
    647669        //Close any open SMTP connection nicely
    648         if ($this->Mailer == 'smtp') {
    649             $this->smtpClose();
    650         }
     670        $this->smtpClose();
    651671    }
    652672
    653673    /**
     
    671691        } else {
    672692            $subject = $this->encodeHeader($this->secureHeader($subject));
    673693        }
    674         if (ini_get('safe_mode') || !($this->UseSendmailOptions)) {
     694
     695        //Can't use additional_parameters in safe_mode, calling mail() with null params breaks
     696        //@link http://php.net/manual/en/function.mail.php
     697        if (ini_get('safe_mode') or !$this->UseSendmailOptions or is_null($params)) {
    675698            $result = @mail($to, $subject, $body, $header);
    676699        } else {
    677700            $result = @mail($to, $subject, $body, $header, $params);
     
    678701        }
    679702        return $result;
    680703    }
    681 
    682704    /**
    683705     * Output debugging info via user-defined method.
    684706     * Only generates output if SMTP debug output is enabled (@see SMTP::$do_debug).
     
    713735            case 'echo':
    714736            default:
    715737                //Normalize line breaks
    716                 $str = preg_replace('/(\r\n|\r|\n)/ms', "\n", $str);
     738                $str = preg_replace('/\r\n?/ms', "\n", $str);
    717739                echo gmdate('Y-m-d H:i:s') . "\t" . str_replace(
    718740                    "\n",
    719741                    "\n                   \t                  ",
     
    850872        $name = trim(preg_replace('/[\r\n]+/', '', $name)); //Strip breaks and trim
    851873        if (($pos = strrpos($address, '@')) === false) {
    852874            // At-sign is misssing.
    853             $error_message = $this->lang('invalid_address') . $address;
     875            $error_message = $this->lang('invalid_address') . " (addAnAddress $kind): $address";
    854876            $this->setError($error_message);
    855877            $this->edebug($error_message);
    856878            if ($this->exceptions) {
     
    900922            return false;
    901923        }
    902924        if (!$this->validateAddress($address)) {
    903             $error_message = $this->lang('invalid_address') . $address;
     925            $error_message = $this->lang('invalid_address') . " (addAnAddress $kind): $address";
    904926            $this->setError($error_message);
    905927            $this->edebug($error_message);
    906928            if ($this->exceptions) {
     
    924946    }
    925947
    926948    /**
     949     * Parse and validate a string containing one or more RFC822-style comma-separated email addresses
     950     * of the form "display name <address>" into an array of name/address pairs.
     951     * Uses the imap_rfc822_parse_adrlist function if the IMAP extension is available.
     952     * Note that quotes in the name part are removed.
     953     * @param string $addrstr The address list string
     954     * @param bool $useimap Whether to use the IMAP extension to parse the list
     955     * @return array
     956     * @link http://www.andrew.cmu.edu/user/agreen1/testing/mrbs/web/Mail/RFC822.php A more careful implementation
     957     */
     958    public function parseAddresses($addrstr, $useimap = true)
     959    {
     960        $addresses = array();
     961        if ($useimap and function_exists('imap_rfc822_parse_adrlist')) {
     962            //Use this built-in parser if it's available
     963            $list = imap_rfc822_parse_adrlist($addrstr, '');
     964            foreach ($list as $address) {
     965                if ($address->host != '.SYNTAX-ERROR.') {
     966                    if ($this->validateAddress($address->mailbox . '@' . $address->host)) {
     967                        $addresses[] = array(
     968                            'name' => (property_exists($address, 'personal') ? $address->personal : ''),
     969                            'address' => $address->mailbox . '@' . $address->host
     970                        );
     971                    }
     972                }
     973            }
     974        } else {
     975            //Use this simpler parser
     976            $list = explode(',', $addrstr);
     977            foreach ($list as $address) {
     978                $address = trim($address);
     979                //Is there a separate name part?
     980                if (strpos($address, '<') === false) {
     981                    //No separate name, just use the whole thing
     982                    if ($this->validateAddress($address)) {
     983                        $addresses[] = array(
     984                            'name' => '',
     985                            'address' => $address
     986                        );
     987                    }
     988                } else {
     989                    list($name, $email) = explode('<', $address);
     990                    $email = trim(str_replace('>', '', $email));
     991                    if ($this->validateAddress($email)) {
     992                        $addresses[] = array(
     993                            'name' => trim(str_replace(array('"', "'"), '', $name)),
     994                            'address' => $email
     995                        );
     996                    }
     997                }
     998            }
     999        }
     1000        return $addresses;
     1001    }
     1002
     1003    /**
    9271004     * Set the From and FromName properties.
    9281005     * @param string $address
    9291006     * @param string $name
     
    9391016        if (($pos = strrpos($address, '@')) === false or
    9401017            (!$this->has8bitChars(substr($address, ++$pos)) or !$this->idnSupported()) and
    9411018            !$this->validateAddress($address)) {
    942             $error_message = $this->lang('invalid_address') . $address;
     1019            $error_message = $this->lang('invalid_address') . " (setFrom) $address";
    9431020            $this->setError($error_message);
    9441021            $this->edebug($error_message);
    9451022            if ($this->exceptions) {
     
    9721049    /**
    9731050     * Check that a string looks like an email address.
    9741051     * @param string $address The email address to check
    975      * @param string $patternselect A selector for the validation pattern to use :
     1052     * @param string|callable $patternselect A selector for the validation pattern to use :
    9761053     * * `auto` Pick best pattern automatically;
    9771054     * * `pcre8` Use the squiloople.com pattern, requires PCRE > 8.0, PHP >= 5.3.2, 5.2.14;
    9781055     * * `pcre` Use old PCRE implementation;
     
    9791056     * * `php` Use PHP built-in FILTER_VALIDATE_EMAIL;
    9801057     * * `html5` Use the pattern given by the HTML5 spec for 'email' type form input elements.
    9811058     * * `noregex` Don't use a regex: super fast, really dumb.
     1059     * Alternatively you may pass in a callable to inject your own validator, for example:
     1060     * PHPMailer::validateAddress('user@example.com', function($address) {
     1061     *     return (strpos($address, '@') !== false);
     1062     * });
     1063     * You can also set the PHPMailer::$validator static to a callable, allowing built-in methods to use your validator.
    9821064     * @return boolean
    9831065     * @static
    9841066     * @access public
    9851067     */
    986     public static function validateAddress($address, $patternselect = 'auto')
     1068    public static function validateAddress($address, $patternselect = null)
    9871069    {
     1070        if (is_null($patternselect)) {
     1071            $patternselect = self::$validator;
     1072        }
     1073        if (is_callable($patternselect)) {
     1074            return call_user_func($patternselect, $address);
     1075        }
    9881076        //Reject line breaks in addresses; it's valid RFC5322, but not RFC5321
    9891077        if (strpos($address, "\n") !== false or strpos($address, "\r") !== false) {
    9901078            return false;
     
    11611249                }
    11621250                $this->$address_kind = $this->punyencodeAddress($this->$address_kind);
    11631251                if (!$this->validateAddress($this->$address_kind)) {
    1164                     $error_message = $this->lang('invalid_address') . $this->$address_kind;
     1252                    $error_message = $this->lang('invalid_address') . ' (punyEncode) ' . $this->$address_kind;
    11651253                    $this->setError($error_message);
    11661254                    $this->edebug($error_message);
    11671255                    if ($this->exceptions) {
     
    11721260            }
    11731261
    11741262            // Set whether the message is multipart/alternative
    1175             if (!empty($this->AltBody)) {
     1263            if ($this->alternativeExists()) {
    11761264                $this->ContentType = 'multipart/alternative';
    11771265            }
    11781266
     
    12061294
    12071295            // Sign with DKIM if enabled
    12081296            if (!empty($this->DKIM_domain)
    1209                 && !empty($this->DKIM_private)
    12101297                && !empty($this->DKIM_selector)
    1211                 && file_exists($this->DKIM_private)) {
     1298                && (!empty($this->DKIM_private_string)
     1299                   || (!empty($this->DKIM_private) && file_exists($this->DKIM_private))
     1300                )
     1301            ) {
    12121302                $header_dkim = $this->DKIM_Add(
    12131303                    $this->MIMEHeader . $this->mailHeader,
    12141304                    $this->encodeHeader($this->secureHeader($this->Subject)),
     
    12741364     */
    12751365    protected function sendmailSend($header, $body)
    12761366    {
    1277         if ($this->Sender != '') {
     1367        if (!empty($this->Sender)) {
    12781368            if ($this->Mailer == 'qmail') {
    12791369                $sendmail = sprintf('%s -f%s', escapeshellcmd($this->Sendmail), escapeshellarg($this->Sender));
    12801370            } else {
     
    13491439        }
    13501440        $to = implode(', ', $toArr);
    13511441
    1352         if (empty($this->Sender)) {
    1353             $params = ' ';
    1354         } else {
    1355             $params = sprintf('-f%s', $this->Sender);
     1442        $params = null;
     1443        //This sets the SMTP envelope sender which gets turned into a return-path header by the receiver
     1444        if (!empty($this->Sender) and $this->validateAddress($this->Sender)) {
     1445            $params = sprintf('-f%s', escapeshellarg($this->Sender));
    13561446        }
    1357         if ($this->Sender != '' and !ini_get('safe_mode')) {
     1447        if (!empty($this->Sender) and !ini_get('safe_mode') and $this->validateAddress($this->Sender)) {
    13581448            $old_from = ini_get('sendmail_from');
    13591449            ini_set('sendmail_from', $this->Sender);
    13601450        }
    13611451        $result = false;
    1362         if ($this->SingleTo && count($toArr) > 1) {
     1452        if ($this->SingleTo and count($toArr) > 1) {
    13631453            foreach ($toArr as $toAddr) {
    13641454                $result = $this->mailPassthru($toAddr, $this->Subject, $body, $header, $params);
    13651455                $this->doCallback($result, array($toAddr), $this->cc, $this->bcc, $this->Subject, $body, $this->From);
     
    13851475    public function getSMTPInstance()
    13861476    {
    13871477        if (!is_object($this->smtp)) {
    1388                         require_once( 'class-smtp.php' );
     1478                require_once( 'class-smtp.php' );
    13891479            $this->smtp = new SMTP;
    13901480        }
    13911481        return $this->smtp;
     
    14091499        if (!$this->smtpConnect($this->SMTPOptions)) {
    14101500            throw new phpmailerException($this->lang('smtp_connect_failed'), self::STOP_CRITICAL);
    14111501        }
    1412         if ('' == $this->Sender) {
     1502        if (!empty($this->Sender) and $this->validateAddress($this->Sender)) {
     1503            $smtp_from = $this->Sender;
     1504        } else {
    14131505            $smtp_from = $this->From;
    1414         } else {
    1415             $smtp_from = $this->Sender;
    14161506        }
    14171507        if (!$this->smtp->mail($smtp_from)) {
    14181508            $this->setError($this->lang('from_failed') . $smtp_from . ' : ' . implode(',', $this->smtp->getError()));
     
    14661556     * @throws phpmailerException
    14671557     * @return boolean
    14681558     */
    1469     public function smtpConnect($options = array())
     1559    public function smtpConnect($options = null)
    14701560    {
    14711561        if (is_null($this->smtp)) {
    14721562            $this->smtp = $this->getSMTPInstance();
    14731563        }
    14741564
     1565        //If no options are provided, use whatever is set in the instance
     1566        if (is_null($options)) {
     1567            $options = $this->SMTPOptions;
     1568        }
     1569
    14751570        // Already connected?
    14761571        if ($this->smtp->connected()) {
    14771572            return true;
     
    15411636                        if (!$this->smtp->startTLS()) {
    15421637                            throw new phpmailerException($this->lang('connect_host'));
    15431638                        }
    1544                         // We must resend HELO after tls negotiation
     1639                        // We must resend EHLO after TLS negotiation
    15451640                        $this->smtp->hello($hello);
    15461641                    }
    15471642                    if ($this->SMTPAuth) {
     
    15801675     */
    15811676    public function smtpClose()
    15821677    {
    1583         if ($this->smtp !== null) {
     1678        if (is_a($this->smtp, 'SMTP')) {
    15841679            if ($this->smtp->connected()) {
    15851680                $this->smtp->quit();
    15861681                $this->smtp->close();
     
    15991694     */
    16001695    public function setLanguage($langcode = 'en', $lang_path = '')
    16011696    {
     1697        // Backwards compatibility for renamed language codes
     1698        $renamed_langcodes = array(
     1699            'br' => 'pt_br',
     1700            'cz' => 'cs',
     1701            'dk' => 'da',
     1702            'no' => 'nb',
     1703            'se' => 'sv',
     1704        );
     1705
     1706        if (isset($renamed_langcodes[$langcode])) {
     1707            $langcode = $renamed_langcodes[$langcode];
     1708        }
     1709
    16021710        // Define full set of translatable strings in English
    16031711        $PHPMAILER_LANG = array(
    16041712            'authenticate' => 'SMTP Error: Could not authenticate.',
     
    16251733            // Calculate an absolute path so it can work if CWD is not here
    16261734            $lang_path = dirname(__FILE__). DIRECTORY_SEPARATOR . 'language'. DIRECTORY_SEPARATOR;
    16271735        }
     1736        //Validate $langcode
     1737        if (!preg_match('/^[a-z]{2}(?:_[a-zA-Z]{2})?$/', $langcode)) {
     1738            $langcode = 'en';
     1739        }
    16281740        $foundlang = true;
    16291741        $lang_file = $lang_path . 'phpmailer.lang-' . $langcode . '.php';
    16301742        // There is no English translation file
     
    19182030            $result .= $this->headerLine('Subject', $this->encodeHeader($this->secureHeader($this->Subject)));
    19192031        }
    19202032
    1921         if ($this->MessageID != '') {
     2033        // Only allow a custom message ID if it conforms to RFC 5322 section 3.6.4
     2034        // https://tools.ietf.org/html/rfc5322#section-3.6.4
     2035        if ('' != $this->MessageID and preg_match('/^<.*@.*>$/', $this->MessageID)) {
    19222036            $this->lastMessageID = $this->MessageID;
    19232037        } else {
    19242038            $this->lastMessageID = sprintf('<%s@%s>', $this->uniqueid, $this->serverHostname());
     
    20202134     */
    20212135    public function getSentMIMEMessage()
    20222136    {
    2023         return $this->MIMEHeader . $this->mailHeader . self::CRLF . $this->MIMEBody;
     2137        return rtrim($this->MIMEHeader . $this->mailHeader, "\n\r") . self::CRLF . self::CRLF . $this->MIMEBody;
    20242138    }
    20252139
    20262140    /**
     2141     * Create unique ID
     2142     * @return string
     2143     */
     2144    protected function generateId() {
     2145        return md5(uniqid(time()));
     2146    }
     2147
     2148    /**
    20272149     * Assemble the message body.
    20282150     * Returns an empty string on failure.
    20292151     * @access public
     
    20342156    {
    20352157        $body = '';
    20362158        //Create unique IDs and preset boundaries
    2037         $this->uniqueid = md5(uniqid(time()));
     2159        $this->uniqueid = $this->generateId();
    20382160        $this->boundary[1] = 'b1_' . $this->uniqueid;
    20392161        $this->boundary[2] = 'b2_' . $this->uniqueid;
    20402162        $this->boundary[3] = 'b3_' . $this->uniqueid;
     
    20502172        //Can we do a 7-bit downgrade?
    20512173        if ($bodyEncoding == '8bit' and !$this->has8bitChars($this->Body)) {
    20522174            $bodyEncoding = '7bit';
     2175            //All ISO 8859, Windows codepage and UTF-8 charsets are ascii compatible up to 7-bit
    20532176            $bodyCharSet = 'us-ascii';
    20542177        }
    2055         //If lines are too long, change to quoted-printable transfer encoding
    2056         if (self::hasLineLongerThanMax($this->Body)) {
    2057             $this->Encoding = 'quoted-printable';
     2178        //If lines are too long, and we're not already using an encoding that will shorten them,
     2179        //change to quoted-printable transfer encoding for the body part only
     2180        if ('base64' != $this->Encoding and self::hasLineLongerThanMax($this->Body)) {
    20582181            $bodyEncoding = 'quoted-printable';
    20592182        }
    20602183
     
    20632186        //Can we do a 7-bit downgrade?
    20642187        if ($altBodyEncoding == '8bit' and !$this->has8bitChars($this->AltBody)) {
    20652188            $altBodyEncoding = '7bit';
     2189            //All ISO 8859, Windows codepage and UTF-8 charsets are ascii compatible up to 7-bit
    20662190            $altBodyCharSet = 'us-ascii';
    20672191        }
    2068         //If lines are too long, change to quoted-printable transfer encoding
    2069         if (self::hasLineLongerThanMax($this->AltBody)) {
     2192        //If lines are too long, and we're not already using an encoding that will shorten them,
     2193        //change to quoted-printable transfer encoding for the alt body part only
     2194        if ('base64' != $altBodyEncoding and self::hasLineLongerThanMax($this->AltBody)) {
    20702195            $altBodyEncoding = 'quoted-printable';
    20712196        }
    20722197        //Use this as a preamble in all multipart message types
     
    21692294                $body .= $this->attachAll('attachment', $this->boundary[1]);
    21702295                break;
    21712296            default:
    2172                 // catch case 'plain' and case ''
    2173                 $body .= $this->encodeString($this->Body, $bodyEncoding);
     2297                // Catch case 'plain' and case '', applies to simple `text/plain` and `text/html` body content types
     2298                //Reset the `Encoding` property in case we changed it for line length reasons
     2299                $this->Encoding = $bodyEncoding;
     2300                $body .= $this->encodeString($this->Body, $this->Encoding);
    21742301                break;
    21752302        }
    21762303
     
    22762403
    22772404    /**
    22782405     * Set the message type.
    2279      * PHPMailer only supports some preset message types,
    2280      * not arbitrary MIME structures.
     2406     * PHPMailer only supports some preset message types, not arbitrary MIME structures.
    22812407     * @access protected
    22822408     * @return void
    22832409     */
     
    22952421        }
    22962422        $this->message_type = implode('_', $type);
    22972423        if ($this->message_type == '') {
     2424            //The 'plain' message_type refers to the message having a single body element, not that it is plain-text
    22982425            $this->message_type = 'plain';
    22992426        }
    23002427    }
     
    32093336    }
    32103337
    32113338    /**
    3212      * Create a message from an HTML string.
    3213      * Automatically makes modifications for inline images and backgrounds
    3214      * and creates a plain-text version by converting the HTML.
    3215      * Overwrites any existing values in $this->Body and $this->AltBody
     3339     * Create a message body from an HTML string.
     3340     * Automatically inlines images and creates a plain-text version by converting the HTML,
     3341     * overwriting any existing values in Body and AltBody.
     3342     * $basedir is used when handling relative image paths, e.g. <img src="images/a.png">
     3343     * will look for an image file in $basedir/images/a.png and convert it to inline.
     3344     * If you don't want to apply these transformations to your HTML, just set Body and AltBody yourself.
    32163345     * @access public
    32173346     * @param string $message HTML message string
    3218      * @param string $basedir baseline directory for path
     3347     * @param string $basedir base directory for relative paths to images
    32193348     * @param boolean|callable $advanced Whether to use the internal HTML to text converter
    32203349     *    or your own custom converter @see PHPMailer::html2text()
    3221      * @return string $message
     3350     * @return string $message The transformed message Body
    32223351     */
    32233352    public function msgHTML($message, $basedir = '', $advanced = false)
    32243353    {
     
    32413370                            $message
    32423371                        );
    32433372                    }
    3244                 } elseif (substr($url, 0, 4) !== 'cid:' && !preg_match('#^[A-z]+://#', $url)) {
     3373                } elseif (substr($url, 0, 4) !== 'cid:' && !preg_match('#^[a-z][a-z0-9+.-]*://#i', $url)) {
    32453374                    // Do not change urls for absolute images (thanks to corvuscorax)
    32463375                    // Do not change urls that are already inline images
    32473376                    $filename = basename($url);
     
    32773406        // Convert all message body line breaks to CRLF, makes quoted-printable encoding work much better
    32783407        $this->Body = $this->normalizeBreaks($message);
    32793408        $this->AltBody = $this->normalizeBreaks($this->html2text($message, $advanced));
    3280         if (empty($this->AltBody)) {
     3409        if (!$this->alternativeExists()) {
    32813410            $this->AltBody = 'To view this email message, open it in a program that understands HTML!' .
    32823411                self::CRLF . self::CRLF;
    32833412        }
     
    32883417     * Convert an HTML string into plain text.
    32893418     * This is used by msgHTML().
    32903419     * Note - older versions of this function used a bundled advanced converter
    3291      * which was been removed for license reasons in #232
     3420     * which was been removed for license reasons in #232.
    32923421     * Example usage:
    32933422     * <code>
    32943423     * // Use default conversion
     
    35883717     * @access public
    35893718     * @param string $signHeader
    35903719     * @throws phpmailerException
    3591      * @return string
     3720     * @return string The DKIM signature value
    35923721     */
    35933722    public function DKIM_Sign($signHeader)
    35943723    {
     
    35983727            }
    35993728            return '';
    36003729        }
    3601         $privKeyStr = file_get_contents($this->DKIM_private);
    3602         if ($this->DKIM_passphrase != '') {
     3730        $privKeyStr = !empty($this->DKIM_private_string) ? $this->DKIM_private_string : file_get_contents($this->DKIM_private);
     3731        if ('' != $this->DKIM_passphrase) {
    36033732            $privKey = openssl_pkey_get_private($privKeyStr, $this->DKIM_passphrase);
    36043733        } else {
    3605             $privKey = $privKeyStr;
     3734            $privKey = openssl_pkey_get_private($privKeyStr);
    36063735        }
    3607         if (openssl_sign($signHeader, $signature, $privKey)) {
    3608             return base64_encode($signature);
     3736        //Workaround for missing digest algorithms in old PHP & OpenSSL versions
     3737        //@link http://stackoverflow.com/a/11117338/333340
     3738        if (version_compare(PHP_VERSION, '5.3.0') >= 0 and
     3739            in_array('sha256WithRSAEncryption', openssl_get_md_methods(true))) {
     3740            if (openssl_sign($signHeader, $signature, $privKey, 'sha256WithRSAEncryption')) {
     3741                openssl_pkey_free($privKey);
     3742                return base64_encode($signature);
     3743            }
     3744        } else {
     3745            $pinfo = openssl_pkey_get_details($privKey);
     3746            $hash = hash('sha256', $signHeader);
     3747            //'Magic' constant for SHA256 from RFC3447
     3748            //@link https://tools.ietf.org/html/rfc3447#page-43
     3749            $t = '3031300d060960864801650304020105000420' . $hash;
     3750            $pslen = $pinfo['bits'] / 8 - (strlen($t) / 2 + 3);
     3751            $eb = pack('H*', '0001' . str_repeat('FF', $pslen) . '00' . $t);
     3752
     3753            if (openssl_private_encrypt($eb, $signature, $privKey, OPENSSL_NO_PADDING)) {
     3754                openssl_pkey_free($privKey);
     3755                return base64_encode($signature);
     3756            }
    36093757        }
     3758        openssl_pkey_free($privKey);
    36103759        return '';
    36113760    }
    36123761
     
    36233772        foreach ($lines as $key => $line) {
    36243773            list($heading, $value) = explode(':', $line, 2);
    36253774            $heading = strtolower($heading);
    3626             $value = preg_replace('/\s+/', ' ', $value); // Compress useless spaces
     3775            $value = preg_replace('/\s{2,}/', ' ', $value); // Compress useless spaces
    36273776            $lines[$key] = $heading . ':' . trim($value); // Don't forget to remove WSP around the value
    36283777        }
    36293778        $signHeader = implode("\r\n", $lines);
     
    36613810     */
    36623811    public function DKIM_Add($headers_line, $subject, $body)
    36633812    {
    3664         $DKIMsignatureType = 'rsa-sha1'; // Signature & hash algorithms
     3813        $DKIMsignatureType = 'rsa-sha256'; // Signature & hash algorithms
    36653814        $DKIMcanonicalization = 'relaxed/simple'; // Canonicalization of header/body
    36663815        $DKIMquery = 'dns/txt'; // Query method
    36673816        $DKIMtime = time(); // Signature Timestamp = seconds since 00:00:00 - Jan 1, 1970 (UTC time zone)
     
    36693818        $headers = explode($this->LE, $headers_line);
    36703819        $from_header = '';
    36713820        $to_header = '';
     3821        $date_header = '';
    36723822        $current = '';
    36733823        foreach ($headers as $header) {
    36743824            if (strpos($header, 'From:') === 0) {
     
    36773827            } elseif (strpos($header, 'To:') === 0) {
    36783828                $to_header = $header;
    36793829                $current = 'to_header';
     3830            } elseif (strpos($header, 'Date:') === 0) {
     3831                $date_header = $header;
     3832                $current = 'date_header';
    36803833            } else {
    36813834                if (!empty($$current) && strpos($header, ' =?') === 0) {
    36823835                    $$current .= $header;
     
    36873840        }
    36883841        $from = str_replace('|', '=7C', $this->DKIM_QP($from_header));
    36893842        $to = str_replace('|', '=7C', $this->DKIM_QP($to_header));
     3843        $date = str_replace('|', '=7C', $this->DKIM_QP($date_header));
    36903844        $subject = str_replace(
    36913845            '|',
    36923846            '=7C',
     
    36943848        ); // Copied header fields (dkim-quoted-printable)
    36953849        $body = $this->DKIM_BodyC($body);
    36963850        $DKIMlen = strlen($body); // Length of body
    3697         $DKIMb64 = base64_encode(pack('H*', sha1($body))); // Base64 of packed binary SHA-1 hash of body
     3851        $DKIMb64 = base64_encode(pack('H*', hash('sha256', $body))); // Base64 of packed binary SHA-256 hash of body
    36983852        if ('' == $this->DKIM_identity) {
    36993853            $ident = '';
    37003854        } else {
     
    37073861            $this->DKIM_selector .
    37083862            ";\r\n" .
    37093863            "\tt=" . $DKIMtime . '; c=' . $DKIMcanonicalization . ";\r\n" .
    3710             "\th=From:To:Subject;\r\n" .
     3864            "\th=From:To:Date:Subject;\r\n" .
    37113865            "\td=" . $this->DKIM_domain . ';' . $ident . "\r\n" .
    37123866            "\tz=$from\r\n" .
    37133867            "\t|$to\r\n" .
     3868            "\t|$date\r\n" .
    37143869            "\t|$subject;\r\n" .
    37153870            "\tbh=" . $DKIMb64 . ";\r\n" .
    37163871            "\tb=";
     
    37173872        $toSign = $this->DKIM_HeaderC(
    37183873            $from_header . "\r\n" .
    37193874            $to_header . "\r\n" .
     3875            $date_header . "\r\n" .
    37203876            $subject_header . "\r\n" .
    37213877            $dkimhdrs
    37223878        );
  • src/wp-includes/class-smtp.php

     
    3030     * The PHPMailer SMTP version number.
    3131     * @var string
    3232     */
    33     const VERSION = '5.2.14';
     33    const VERSION = '5.2.19';
    3434
    3535    /**
    3636     * SMTP line break constant.
     
    8181     * @deprecated Use the `VERSION` constant instead
    8282     * @see SMTP::VERSION
    8383     */
    84     public $Version = '5.2.14';
     84    public $Version = '5.2.19';
    8585
    8686    /**
    8787     * SMTP server port number.
     
    150150     */
    151151    public $Timelimit = 300;
    152152
     153        /**
     154         * @var array patterns to extract smtp transaction id from smtp reply
     155         * Only first capture group will be use, use non-capturing group to deal with it
     156         * Extend this class to override this property to fulfil your needs.
     157         */
     158        protected $smtp_transaction_id_patterns = array(
     159                'exim' => '/[0-9]{3} OK id=(.*)/',
     160                'sendmail' => '/[0-9]{3} 2.0.0 (.*) Message/',
     161                'postfix' => '/[0-9]{3} 2.0.0 Ok: queued as (.*)/'
     162        );
     163
    153164    /**
    154165     * The socket for the server connection.
    155166     * @var resource
     
    206217        }
    207218        //Avoid clash with built-in function names
    208219        if (!in_array($this->Debugoutput, array('error_log', 'html', 'echo')) and is_callable($this->Debugoutput)) {
    209             call_user_func($this->Debugoutput, $str, $this->do_debug);
     220            call_user_func($this->Debugoutput, $str, $level);
    210221            return;
    211222        }
    212223        switch ($this->Debugoutput) {
     
    272283        $errstr = '';
    273284        if ($streamok) {
    274285            $socket_context = stream_context_create($options);
    275             //Suppress errors; connection failures are handled at a higher level
    276             $this->smtp_conn = @stream_socket_client(
     286            set_error_handler(array($this, 'errorHandler'));
     287            $this->smtp_conn = stream_socket_client(
    277288                $host . ":" . $port,
    278289                $errno,
    279290                $errstr,
     
    281292                STREAM_CLIENT_CONNECT,
    282293                $socket_context
    283294            );
     295            restore_error_handler();
    284296        } else {
    285297            //Fall back to fsockopen which should work in more places, but is missing some features
    286298            $this->edebug(
     
    287299                "Connection: stream_socket_client not available, falling back to fsockopen",
    288300                self::DEBUG_CONNECTION
    289301            );
     302            set_error_handler(array($this, 'errorHandler'));
    290303            $this->smtp_conn = fsockopen(
    291304                $host,
    292305                $port,
     
    294307                $errstr,
    295308                $timeout
    296309            );
     310            restore_error_handler();
    297311        }
    298312        // Verify we connected properly
    299313        if (!is_resource($this->smtp_conn)) {
     
    336350        if (!$this->sendCommand('STARTTLS', 'STARTTLS', 220)) {
    337351            return false;
    338352        }
     353
     354        //Allow the best TLS version(s) we can
     355        $crypto_method = STREAM_CRYPTO_METHOD_TLS_CLIENT;
     356
     357        //PHP 5.6.7 dropped inclusion of TLS 1.1 and 1.2 in STREAM_CRYPTO_METHOD_TLS_CLIENT
     358        //so add them back in manually if we can
     359        if (defined('STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT')) {
     360            $crypto_method |= STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT;
     361            $crypto_method |= STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT;
     362        }
     363
    339364        // Begin encrypted connection
    340365        if (!stream_socket_enable_crypto(
    341366            $this->smtp_conn,
    342367            true,
    343             STREAM_CRYPTO_METHOD_TLS_CLIENT
     368            $crypto_method
    344369        )) {
    345370            return false;
    346371        }
     
    389414            );
    390415
    391416            if (empty($authtype)) {
    392                 foreach (array('LOGIN', 'CRAM-MD5', 'PLAIN') as $method) {
     417                foreach (array('CRAM-MD5', 'LOGIN', 'PLAIN', 'XOAUTH2') as $method) {
    393418                    if (in_array($method, $this->server_caps['AUTH'])) {
    394419                        $authtype = $method;
    395420                        break;
     
    437462                    return false;
    438463                }
    439464                break;
     465            case 'XOAUTH2':
     466                //If the OAuth Instance is not set. Can be a case when PHPMailer is used
     467                //instead of PHPMailerOAuth
     468                if (is_null($OAuth)) {
     469                    return false;
     470                }
     471                $oauth = $OAuth->getOauth64();
     472
     473                // Start authentication
     474                if (!$this->sendCommand('AUTH', 'AUTH XOAUTH2 ' . $oauth, 235)) {
     475                    return false;
     476                }
     477                break;
    440478            case 'CRAM-MD5':
    441479                // Start authentication
    442480                if (!$this->sendCommand('AUTH CRAM-MD5', 'AUTH CRAM-MD5', 334)) {
     
    673711    protected function parseHelloFields($type)
    674712    {
    675713        $this->server_caps = array();
    676         $lines = explode("\n", $this->last_reply);
     714        $lines = explode("\n", $this->helo_rply);
    677715
    678716        foreach ($lines as $n => $s) {
    679717            //First 4 chars contain response code followed by - or space
     
    11151153    {
    11161154        return $this->Timeout;
    11171155    }
     1156
     1157    /**
     1158     * Reports an error number and string.
     1159     * @param integer $errno The error number returned by PHP.
     1160     * @param string $errmsg The error message returned by PHP.
     1161     */
     1162    protected function errorHandler($errno, $errmsg)
     1163    {
     1164        $notice = 'Connection: Failed to connect to server.';
     1165        $this->setError(
     1166            $notice,
     1167            $errno,
     1168            $errmsg
     1169        );
     1170        $this->edebug(
     1171            $notice . ' Error number ' . $errno . '. "Error notice: ' . $errmsg,
     1172            self::DEBUG_CONNECTION
     1173        );
     1174    }
     1175
     1176        /**
     1177         * Will return the ID of the last smtp transaction based on a list of patterns provided
     1178         * in SMTP::$smtp_transaction_id_patterns.
     1179         * If no reply has been received yet, it will return null.
     1180         * If no pattern has been matched, it will return false.
     1181         * @return bool|null|string
     1182         */
     1183        public function getLastTransactionID()
     1184        {
     1185                $reply = $this->getLastReply();
     1186
     1187                if (empty($reply)) {
     1188                        return null;
     1189                }
     1190
     1191                foreach($this->smtp_transaction_id_patterns as $smtp_transaction_id_pattern) {
     1192                        if(preg_match($smtp_transaction_id_pattern, $reply, $matches)) {
     1193                                return $matches[1];
     1194                        }
     1195                }
     1196
     1197                return false;
     1198    }
    11181199}