WordPress.org

Make WordPress Core

Ticket #41750: 41750.6.patch

File 41750.6.patch, 406.7 KB (added by ayeshrajans, 20 months ago)

https://travis-ci.org/Ayesh/wordpress-develop/builds/647882845

  • new file src/wp-includes/PHPMailer/Exception.php

    diff --git a/src/wp-includes/PHPMailer/Exception.php b/src/wp-includes/PHPMailer/Exception.php
    new file mode 100644
    index 0000000000..b1e552f50b
    - +  
     1<?php
     2/**
     3 * PHPMailer Exception class.
     4 * PHP Version 5.5.
     5 *
     6 * @see       https://github.com/PHPMailer/PHPMailer/ The PHPMailer GitHub project
     7 *
     8 * @author    Marcus Bointon (Synchro/coolbru) <phpmailer@synchromedia.co.uk>
     9 * @author    Jim Jagielski (jimjag) <jimjag@gmail.com>
     10 * @author    Andy Prevost (codeworxtech) <codeworxtech@users.sourceforge.net>
     11 * @author    Brent R. Matzelle (original founder)
     12 * @copyright 2012 - 2017 Marcus Bointon
     13 * @copyright 2010 - 2012 Jim Jagielski
     14 * @copyright 2004 - 2009 Andy Prevost
     15 * @license   http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
     16 * @note      This program is distributed in the hope that it will be useful - WITHOUT
     17 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
     18 * FITNESS FOR A PARTICULAR PURPOSE.
     19 */
     20
     21namespace PHPMailer\PHPMailer;
     22
     23/**
     24 * PHPMailer exception handler.
     25 *
     26 * @author Marcus Bointon <phpmailer@synchromedia.co.uk>
     27 */
     28class Exception extends \Exception
     29{
     30    /**
     31     * Prettify error message output.
     32     *
     33     * @return string
     34     */
     35    public function errorMessage()
     36    {
     37        return '<strong>' . htmlspecialchars($this->getMessage()) . "</strong><br />\n";
     38    }
     39}
  • new file src/wp-includes/PHPMailer/PHPMailer.php

    diff --git a/src/wp-includes/PHPMailer/PHPMailer.php b/src/wp-includes/PHPMailer/PHPMailer.php
    new file mode 100644
    index 0000000000..127f2b79bc
    - +  
     1<?php
     2/**
     3 * PHPMailer - PHP email creation and transport class.
     4 * PHP Version 5.5.
     5 *
     6 * @see https://github.com/PHPMailer/PHPMailer/ The PHPMailer GitHub project
     7 *
     8 * @author    Marcus Bointon (Synchro/coolbru) <phpmailer@synchromedia.co.uk>
     9 * @author    Jim Jagielski (jimjag) <jimjag@gmail.com>
     10 * @author    Andy Prevost (codeworxtech) <codeworxtech@users.sourceforge.net>
     11 * @author    Brent R. Matzelle (original founder)
     12 * @copyright 2012 - 2019 Marcus Bointon
     13 * @copyright 2010 - 2012 Jim Jagielski
     14 * @copyright 2004 - 2009 Andy Prevost
     15 * @license   http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
     16 * @note      This program is distributed in the hope that it will be useful - WITHOUT
     17 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
     18 * FITNESS FOR A PARTICULAR PURPOSE.
     19 */
     20
     21namespace PHPMailer\PHPMailer;
     22
     23/**
     24 * PHPMailer - PHP email creation and transport class.
     25 *
     26 * @author Marcus Bointon (Synchro/coolbru) <phpmailer@synchromedia.co.uk>
     27 * @author Jim Jagielski (jimjag) <jimjag@gmail.com>
     28 * @author Andy Prevost (codeworxtech) <codeworxtech@users.sourceforge.net>
     29 * @author Brent R. Matzelle (original founder)
     30 */
     31class PHPMailer
     32{
     33    const CHARSET_ASCII = 'us-ascii';
     34    const CHARSET_ISO88591 = 'iso-8859-1';
     35    const CHARSET_UTF8 = 'utf-8';
     36
     37    const CONTENT_TYPE_PLAINTEXT = 'text/plain';
     38    const CONTENT_TYPE_TEXT_CALENDAR = 'text/calendar';
     39    const CONTENT_TYPE_TEXT_HTML = 'text/html';
     40    const CONTENT_TYPE_MULTIPART_ALTERNATIVE = 'multipart/alternative';
     41    const CONTENT_TYPE_MULTIPART_MIXED = 'multipart/mixed';
     42    const CONTENT_TYPE_MULTIPART_RELATED = 'multipart/related';
     43
     44    const ENCODING_7BIT = '7bit';
     45    const ENCODING_8BIT = '8bit';
     46    const ENCODING_BASE64 = 'base64';
     47    const ENCODING_BINARY = 'binary';
     48    const ENCODING_QUOTED_PRINTABLE = 'quoted-printable';
     49
     50    const ENCRYPTION_STARTTLS = 'tls';
     51    const ENCRYPTION_SMTPS = 'ssl';
     52
     53    const ICAL_METHOD_REQUEST = 'REQUEST';
     54    const ICAL_METHOD_PUBLISH = 'PUBLISH';
     55    const ICAL_METHOD_REPLY = 'REPLY';
     56    const ICAL_METHOD_ADD = 'ADD';
     57    const ICAL_METHOD_CANCEL = 'CANCEL';
     58    const ICAL_METHOD_REFRESH = 'REFRESH';
     59    const ICAL_METHOD_COUNTER = 'COUNTER';
     60    const ICAL_METHOD_DECLINECOUNTER = 'DECLINECOUNTER';
     61
     62    /**
     63     * Email priority.
     64     * Options: null (default), 1 = High, 3 = Normal, 5 = low.
     65     * When null, the header is not set at all.
     66     *
     67     * @var int
     68     */
     69    public $Priority;
     70
     71    /**
     72     * The character set of the message.
     73     *
     74     * @var string
     75     */
     76    public $CharSet = self::CHARSET_ISO88591;
     77
     78    /**
     79     * The MIME Content-type of the message.
     80     *
     81     * @var string
     82     */
     83    public $ContentType = self::CONTENT_TYPE_PLAINTEXT;
     84
     85    /**
     86     * The message encoding.
     87     * Options: "8bit", "7bit", "binary", "base64", and "quoted-printable".
     88     *
     89     * @var string
     90     */
     91    public $Encoding = self::ENCODING_8BIT;
     92
     93    /**
     94     * Holds the most recent mailer error message.
     95     *
     96     * @var string
     97     */
     98    public $ErrorInfo = '';
     99
     100    /**
     101     * The From email address for the message.
     102     *
     103     * @var string
     104     */
     105    public $From = 'root@localhost';
     106
     107    /**
     108     * The From name of the message.
     109     *
     110     * @var string
     111     */
     112    public $FromName = 'Root User';
     113
     114    /**
     115     * The envelope sender of the message.
     116     * This will usually be turned into a Return-Path header by the receiver,
     117     * and is the address that bounces will be sent to.
     118     * If not empty, will be passed via `-f` to sendmail or as the 'MAIL FROM' value over SMTP.
     119     *
     120     * @var string
     121     */
     122    public $Sender = '';
     123
     124    /**
     125     * The Subject of the message.
     126     *
     127     * @var string
     128     */
     129    public $Subject = '';
     130
     131    /**
     132     * An HTML or plain text message body.
     133     * If HTML then call isHTML(true).
     134     *
     135     * @var string
     136     */
     137    public $Body = '';
     138
     139    /**
     140     * The plain-text message body.
     141     * This body can be read by mail clients that do not have HTML email
     142     * capability such as mutt & Eudora.
     143     * Clients that can read HTML will view the normal Body.
     144     *
     145     * @var string
     146     */
     147    public $AltBody = '';
     148
     149    /**
     150     * An iCal message part body.
     151     * Only supported in simple alt or alt_inline message types
     152     * To generate iCal event structures, use classes like EasyPeasyICS or iCalcreator.
     153     *
     154     * @see http://sprain.ch/blog/downloads/php-class-easypeasyics-create-ical-files-with-php/
     155     * @see http://kigkonsult.se/iCalcreator/
     156     *
     157     * @var string
     158     */
     159    public $Ical = '';
     160
     161    /**
     162     * Value-array of "method" in Contenttype header "text/calendar"
     163     *
     164     * @var string[]
     165     */
     166    protected static $IcalMethods = [
     167        self::ICAL_METHOD_REQUEST,
     168        self::ICAL_METHOD_PUBLISH,
     169        self::ICAL_METHOD_REPLY,
     170        self::ICAL_METHOD_ADD,
     171        self::ICAL_METHOD_CANCEL,
     172        self::ICAL_METHOD_REFRESH,
     173        self::ICAL_METHOD_COUNTER,
     174        self::ICAL_METHOD_DECLINECOUNTER,
     175    ];
     176
     177    /**
     178     * The complete compiled MIME message body.
     179     *
     180     * @var string
     181     */
     182    protected $MIMEBody = '';
     183
     184    /**
     185     * The complete compiled MIME message headers.
     186     *
     187     * @var string
     188     */
     189    protected $MIMEHeader = '';
     190
     191    /**
     192     * Extra headers that createHeader() doesn't fold in.
     193     *
     194     * @var string
     195     */
     196    protected $mailHeader = '';
     197
     198    /**
     199     * Word-wrap the message body to this number of chars.
     200     * Set to 0 to not wrap. A useful value here is 78, for RFC2822 section 2.1.1 compliance.
     201     *
     202     * @see static::STD_LINE_LENGTH
     203     *
     204     * @var int
     205     */
     206    public $WordWrap = 0;
     207
     208    /**
     209     * Which method to use to send mail.
     210     * Options: "mail", "sendmail", or "smtp".
     211     *
     212     * @var string
     213     */
     214    public $Mailer = 'mail';
     215
     216    /**
     217     * The path to the sendmail program.
     218     *
     219     * @var string
     220     */
     221    public $Sendmail = '/usr/sbin/sendmail';
     222
     223    /**
     224     * Whether mail() uses a fully sendmail-compatible MTA.
     225     * One which supports sendmail's "-oi -f" options.
     226     *
     227     * @var bool
     228     */
     229    public $UseSendmailOptions = true;
     230
     231    /**
     232     * The email address that a reading confirmation should be sent to, also known as read receipt.
     233     *
     234     * @var string
     235     */
     236    public $ConfirmReadingTo = '';
     237
     238    /**
     239     * The hostname to use in the Message-ID header and as default HELO string.
     240     * If empty, PHPMailer attempts to find one with, in order,
     241     * $_SERVER['SERVER_NAME'], gethostname(), php_uname('n'), or the value
     242     * 'localhost.localdomain'.
     243     *
     244     * @see PHPMailer::$Helo
     245     *
     246     * @var string
     247     */
     248    public $Hostname = '';
     249
     250    /**
     251     * An ID to be used in the Message-ID header.
     252     * If empty, a unique id will be generated.
     253     * You can set your own, but it must be in the format "<id@domain>",
     254     * as defined in RFC5322 section 3.6.4 or it will be ignored.
     255     *
     256     * @see https://tools.ietf.org/html/rfc5322#section-3.6.4
     257     *
     258     * @var string
     259     */
     260    public $MessageID = '';
     261
     262    /**
     263     * The message Date to be used in the Date header.
     264     * If empty, the current date will be added.
     265     *
     266     * @var string
     267     */
     268    public $MessageDate = '';
     269
     270    /**
     271     * SMTP hosts.
     272     * Either a single hostname or multiple semicolon-delimited hostnames.
     273     * You can also specify a different port
     274     * for each host by using this format: [hostname:port]
     275     * (e.g. "smtp1.example.com:25;smtp2.example.com").
     276     * You can also specify encryption type, for example:
     277     * (e.g. "tls://smtp1.example.com:587;ssl://smtp2.example.com:465").
     278     * Hosts will be tried in order.
     279     *
     280     * @var string
     281     */
     282    public $Host = 'localhost';
     283
     284    /**
     285     * The default SMTP server port.
     286     *
     287     * @var int
     288     */
     289    public $Port = 25;
     290
     291    /**
     292     * The SMTP HELO/EHLO name used for the SMTP connection.
     293     * Default is $Hostname. If $Hostname is empty, PHPMailer attempts to find
     294     * one with the same method described above for $Hostname.
     295     *
     296     * @see PHPMailer::$Hostname
     297     *
     298     * @var string
     299     */
     300    public $Helo = '';
     301
     302    /**
     303     * What kind of encryption to use on the SMTP connection.
     304     * Options: '', static::ENCRYPTION_STARTTLS, or static::ENCRYPTION_SMTPS.
     305     *
     306     * @var string
     307     */
     308    public $SMTPSecure = '';
     309
     310    /**
     311     * Whether to enable TLS encryption automatically if a server supports it,
     312     * even if `SMTPSecure` is not set to 'tls'.
     313     * Be aware that in PHP >= 5.6 this requires that the server's certificates are valid.
     314     *
     315     * @var bool
     316     */
     317    public $SMTPAutoTLS = true;
     318
     319    /**
     320     * Whether to use SMTP authentication.
     321     * Uses the Username and Password properties.
     322     *
     323     * @see PHPMailer::$Username
     324     * @see PHPMailer::$Password
     325     *
     326     * @var bool
     327     */
     328    public $SMTPAuth = false;
     329
     330    /**
     331     * Options array passed to stream_context_create when connecting via SMTP.
     332     *
     333     * @var array
     334     */
     335    public $SMTPOptions = [];
     336
     337    /**
     338     * SMTP username.
     339     *
     340     * @var string
     341     */
     342    public $Username = '';
     343
     344    /**
     345     * SMTP password.
     346     *
     347     * @var string
     348     */
     349    public $Password = '';
     350
     351    /**
     352     * SMTP auth type.
     353     * Options are CRAM-MD5, LOGIN, PLAIN, XOAUTH2, attempted in that order if not specified.
     354     *
     355     * @var string
     356     */
     357    public $AuthType = '';
     358
     359    /**
     360     * An instance of the PHPMailer OAuth class.
     361     *
     362     * @var OAuth
     363     */
     364    protected $oauth;
     365
     366    /**
     367     * The SMTP server timeout in seconds.
     368     * Default of 5 minutes (300sec) is from RFC2821 section 4.5.3.2.
     369     *
     370     * @var int
     371     */
     372    public $Timeout = 300;
     373
     374    /**
     375     * Comma separated list of DSN notifications
     376     * 'NEVER' under no circumstances a DSN must be returned to the sender.
     377     *         If you use NEVER all other notifications will be ignored.
     378     * 'SUCCESS' will notify you when your mail has arrived at its destination.
     379     * 'FAILURE' will arrive if an error occurred during delivery.
     380     * 'DELAY'   will notify you if there is an unusual delay in delivery, but the actual
     381     *           delivery's outcome (success or failure) is not yet decided.
     382     *
     383     * @see https://tools.ietf.org/html/rfc3461 See section 4.1 for more information about NOTIFY
     384     */
     385    public $dsn = '';
     386
     387    /**
     388     * SMTP class debug output mode.
     389     * Debug output level.
     390     * Options:
     391     * * SMTP::DEBUG_OFF: No output
     392     * * SMTP::DEBUG_CLIENT: Client messages
     393     * * SMTP::DEBUG_SERVER: Client and server messages
     394     * * SMTP::DEBUG_CONNECTION: As SERVER plus connection status
     395     * * SMTP::DEBUG_LOWLEVEL: Noisy, low-level data output, rarely needed
     396     *
     397     * @see SMTP::$do_debug
     398     *
     399     * @var int
     400     */
     401    public $SMTPDebug = 0;
     402
     403    /**
     404     * How to handle debug output.
     405     * Options:
     406     * * `echo` Output plain-text as-is, appropriate for CLI
     407     * * `html` Output escaped, line breaks converted to `<br>`, appropriate for browser output
     408     * * `error_log` Output to error log as configured in php.ini
     409     * By default PHPMailer will use `echo` if run from a `cli` or `cli-server` SAPI, `html` otherwise.
     410     * Alternatively, you can provide a callable expecting two params: a message string and the debug level:
     411     *
     412     * ```php
     413     * $mail->Debugoutput = function($str, $level) {echo "debug level $level; message: $str";};
     414     * ```
     415     *
     416     * Alternatively, you can pass in an instance of a PSR-3 compatible logger, though only `debug`
     417     * level output is used:
     418     *
     419     * ```php
     420     * $mail->Debugoutput = new myPsr3Logger;
     421     * ```
     422     *
     423     * @see SMTP::$Debugoutput
     424     *
     425     * @var string|callable|\Psr\Log\LoggerInterface
     426     */
     427    public $Debugoutput = 'echo';
     428
     429    /**
     430     * Whether to keep SMTP connection open after each message.
     431     * If this is set to true then to close the connection
     432     * requires an explicit call to smtpClose().
     433     *
     434     * @var bool
     435     */
     436    public $SMTPKeepAlive = false;
     437
     438    /**
     439     * Whether to split multiple to addresses into multiple messages
     440     * or send them all in one message.
     441     * Only supported in `mail` and `sendmail` transports, not in SMTP.
     442     *
     443     * @var bool
     444     */
     445    public $SingleTo = false;
     446
     447    /**
     448     * Storage for addresses when SingleTo is enabled.
     449     *
     450     * @var array
     451     */
     452    protected $SingleToArray = [];
     453
     454    /**
     455     * Whether to generate VERP addresses on send.
     456     * Only applicable when sending via SMTP.
     457     *
     458     * @see https://en.wikipedia.org/wiki/Variable_envelope_return_path
     459     * @see http://www.postfix.org/VERP_README.html Postfix VERP info
     460     *
     461     * @var bool
     462     */
     463    public $do_verp = false;
     464
     465    /**
     466     * Whether to allow sending messages with an empty body.
     467     *
     468     * @var bool
     469     */
     470    public $AllowEmpty = false;
     471
     472    /**
     473     * DKIM selector.
     474     *
     475     * @var string
     476     */
     477    public $DKIM_selector = '';
     478
     479    /**
     480     * DKIM Identity.
     481     * Usually the email address used as the source of the email.
     482     *
     483     * @var string
     484     */
     485    public $DKIM_identity = '';
     486
     487    /**
     488     * DKIM passphrase.
     489     * Used if your key is encrypted.
     490     *
     491     * @var string
     492     */
     493    public $DKIM_passphrase = '';
     494
     495    /**
     496     * DKIM signing domain name.
     497     *
     498     * @example 'example.com'
     499     *
     500     * @var string
     501     */
     502    public $DKIM_domain = '';
     503
     504    /**
     505     * DKIM Copy header field values for diagnostic use.
     506     *
     507     * @var bool
     508     */
     509    public $DKIM_copyHeaderFields = true;
     510
     511    /**
     512     * DKIM Extra signing headers.
     513     *
     514     * @example ['List-Unsubscribe', 'List-Help']
     515     *
     516     * @var array
     517     */
     518    public $DKIM_extraHeaders = [];
     519
     520    /**
     521     * DKIM private key file path.
     522     *
     523     * @var string
     524     */
     525    public $DKIM_private = '';
     526
     527    /**
     528     * DKIM private key string.
     529     *
     530     * If set, takes precedence over `$DKIM_private`.
     531     *
     532     * @var string
     533     */
     534    public $DKIM_private_string = '';
     535
     536    /**
     537     * Callback Action function name.
     538     *
     539     * The function that handles the result of the send email action.
     540     * It is called out by send() for each email sent.
     541     *
     542     * Value can be any php callable: http://www.php.net/is_callable
     543     *
     544     * Parameters:
     545     *   bool $result        result of the send action
     546     *   array   $to            email addresses of the recipients
     547     *   array   $cc            cc email addresses
     548     *   array   $bcc           bcc email addresses
     549     *   string  $subject       the subject
     550     *   string  $body          the email body
     551     *   string  $from          email address of sender
     552     *   string  $extra         extra information of possible use
     553     *                          "smtp_transaction_id' => last smtp transaction id
     554     *
     555     * @var string
     556     */
     557    public $action_function = '';
     558
     559    /**
     560     * What to put in the X-Mailer header.
     561     * Options: An empty string for PHPMailer default, whitespace/null for none, or a string to use.
     562     *
     563     * @var string|null
     564     */
     565    public $XMailer = '';
     566
     567    /**
     568     * Which validator to use by default when validating email addresses.
     569     * May be a callable to inject your own validator, but there are several built-in validators.
     570     * The default validator uses PHP's FILTER_VALIDATE_EMAIL filter_var option.
     571     *
     572     * @see PHPMailer::validateAddress()
     573     *
     574     * @var string|callable
     575     */
     576    public static $validator = 'php';
     577
     578    /**
     579     * An instance of the SMTP sender class.
     580     *
     581     * @var SMTP
     582     */
     583    protected $smtp;
     584
     585    /**
     586     * The array of 'to' names and addresses.
     587     *
     588     * @var array
     589     */
     590    protected $to = [];
     591
     592    /**
     593     * The array of 'cc' names and addresses.
     594     *
     595     * @var array
     596     */
     597    protected $cc = [];
     598
     599    /**
     600     * The array of 'bcc' names and addresses.
     601     *
     602     * @var array
     603     */
     604    protected $bcc = [];
     605
     606    /**
     607     * The array of reply-to names and addresses.
     608     *
     609     * @var array
     610     */
     611    protected $ReplyTo = [];
     612
     613    /**
     614     * An array of all kinds of addresses.
     615     * Includes all of $to, $cc, $bcc.
     616     *
     617     * @see PHPMailer::$to
     618     * @see PHPMailer::$cc
     619     * @see PHPMailer::$bcc
     620     *
     621     * @var array
     622     */
     623    protected $all_recipients = [];
     624
     625    /**
     626     * An array of names and addresses queued for validation.
     627     * In send(), valid and non duplicate entries are moved to $all_recipients
     628     * and one of $to, $cc, or $bcc.
     629     * This array is used only for addresses with IDN.
     630     *
     631     * @see PHPMailer::$to
     632     * @see PHPMailer::$cc
     633     * @see PHPMailer::$bcc
     634     * @see PHPMailer::$all_recipients
     635     *
     636     * @var array
     637     */
     638    protected $RecipientsQueue = [];
     639
     640    /**
     641     * An array of reply-to names and addresses queued for validation.
     642     * In send(), valid and non duplicate entries are moved to $ReplyTo.
     643     * This array is used only for addresses with IDN.
     644     *
     645     * @see PHPMailer::$ReplyTo
     646     *
     647     * @var array
     648     */
     649    protected $ReplyToQueue = [];
     650
     651    /**
     652     * The array of attachments.
     653     *
     654     * @var array
     655     */
     656    protected $attachment = [];
     657
     658    /**
     659     * The array of custom headers.
     660     *
     661     * @var array
     662     */
     663    protected $CustomHeader = [];
     664
     665    /**
     666     * The most recent Message-ID (including angular brackets).
     667     *
     668     * @var string
     669     */
     670    protected $lastMessageID = '';
     671
     672    /**
     673     * The message's MIME type.
     674     *
     675     * @var string
     676     */
     677    protected $message_type = '';
     678
     679    /**
     680     * The array of MIME boundary strings.
     681     *
     682     * @var array
     683     */
     684    protected $boundary = [];
     685
     686    /**
     687     * The array of available languages.
     688     *
     689     * @var array
     690     */
     691    protected $language = [];
     692
     693    /**
     694     * The number of errors encountered.
     695     *
     696     * @var int
     697     */
     698    protected $error_count = 0;
     699
     700    /**
     701     * The S/MIME certificate file path.
     702     *
     703     * @var string
     704     */
     705    protected $sign_cert_file = '';
     706
     707    /**
     708     * The S/MIME key file path.
     709     *
     710     * @var string
     711     */
     712    protected $sign_key_file = '';
     713
     714    /**
     715     * The optional S/MIME extra certificates ("CA Chain") file path.
     716     *
     717     * @var string
     718     */
     719    protected $sign_extracerts_file = '';
     720
     721    /**
     722     * The S/MIME password for the key.
     723     * Used only if the key is encrypted.
     724     *
     725     * @var string
     726     */
     727    protected $sign_key_pass = '';
     728
     729    /**
     730     * Whether to throw exceptions for errors.
     731     *
     732     * @var bool
     733     */
     734    protected $exceptions = false;
     735
     736    /**
     737     * Unique ID used for message ID and boundaries.
     738     *
     739     * @var string
     740     */
     741    protected $uniqueid = '';
     742
     743    /**
     744     * The PHPMailer Version number.
     745     *
     746     * @var string
     747     */
     748    const VERSION = '6.1.4';
     749
     750    /**
     751     * Error severity: message only, continue processing.
     752     *
     753     * @var int
     754     */
     755    const STOP_MESSAGE = 0;
     756
     757    /**
     758     * Error severity: message, likely ok to continue processing.
     759     *
     760     * @var int
     761     */
     762    const STOP_CONTINUE = 1;
     763
     764    /**
     765     * Error severity: message, plus full stop, critical error reached.
     766     *
     767     * @var int
     768     */
     769    const STOP_CRITICAL = 2;
     770
     771    /**
     772     * SMTP RFC standard line ending.
     773     *
     774     * @var string
     775     */
     776    protected static $LE = "\r\n";
     777
     778    /**
     779     * The maximum line length supported by mail().
     780     *
     781     * Background: mail() will sometimes corrupt messages
     782     * with headers headers longer than 65 chars, see #818.
     783     *
     784     * @var int
     785     */
     786    const MAIL_MAX_LINE_LENGTH = 63;
     787
     788    /**
     789     * The maximum line length allowed by RFC 2822 section 2.1.1.
     790     *
     791     * @var int
     792     */
     793    const MAX_LINE_LENGTH = 998;
     794
     795    /**
     796     * The lower maximum line length allowed by RFC 2822 section 2.1.1.
     797     * This length does NOT include the line break
     798     * 76 means that lines will be 77 or 78 chars depending on whether
     799     * the line break format is LF or CRLF; both are valid.
     800     *
     801     * @var int
     802     */
     803    const STD_LINE_LENGTH = 76;
     804
     805    /**
     806     * Constructor.
     807     *
     808     * @param bool $exceptions Should we throw external exceptions?
     809     */
     810    public function __construct($exceptions = null)
     811    {
     812        if (null !== $exceptions) {
     813            $this->exceptions = (bool) $exceptions;
     814        }
     815        //Pick an appropriate debug output format automatically
     816        $this->Debugoutput = (strpos(PHP_SAPI, 'cli') !== false ? 'echo' : 'html');
     817    }
     818
     819    /**
     820     * Destructor.
     821     */
     822    public function __destruct()
     823    {
     824        //Close any open SMTP connection nicely
     825        $this->smtpClose();
     826    }
     827
     828    /**
     829     * Call mail() in a safe_mode-aware fashion.
     830     * Also, unless sendmail_path points to sendmail (or something that
     831     * claims to be sendmail), don't pass params (not a perfect fix,
     832     * but it will do).
     833     *
     834     * @param string      $to      To
     835     * @param string      $subject Subject
     836     * @param string      $body    Message Body
     837     * @param string      $header  Additional Header(s)
     838     * @param string|null $params  Params
     839     *
     840     * @return bool
     841     */
     842    private function mailPassthru($to, $subject, $body, $header, $params)
     843    {
     844        //Check overloading of mail function to avoid double-encoding
     845        if (ini_get('mbstring.func_overload') & 1) {
     846            $subject = $this->secureHeader($subject);
     847        } else {
     848            $subject = $this->encodeHeader($this->secureHeader($subject));
     849        }
     850        //Calling mail() with null params breaks
     851        if (!$this->UseSendmailOptions || null === $params) {
     852            $result = @mail($to, $subject, $body, $header);
     853        } else {
     854            $result = @mail($to, $subject, $body, $header, $params);
     855        }
     856
     857        return $result;
     858    }
     859
     860    /**
     861     * Output debugging info via user-defined method.
     862     * Only generates output if SMTP debug output is enabled (@see SMTP::$do_debug).
     863     *
     864     * @see PHPMailer::$Debugoutput
     865     * @see PHPMailer::$SMTPDebug
     866     *
     867     * @param string $str
     868     */
     869    protected function edebug($str)
     870    {
     871        if ($this->SMTPDebug <= 0) {
     872            return;
     873        }
     874        //Is this a PSR-3 logger?
     875        if ($this->Debugoutput instanceof \Psr\Log\LoggerInterface) {
     876            $this->Debugoutput->debug($str);
     877
     878            return;
     879        }
     880        //Avoid clash with built-in function names
     881        if (is_callable($this->Debugoutput) && !in_array($this->Debugoutput, ['error_log', 'html', 'echo'])) {
     882            call_user_func($this->Debugoutput, $str, $this->SMTPDebug);
     883
     884            return;
     885        }
     886        switch ($this->Debugoutput) {
     887            case 'error_log':
     888                //Don't output, just log
     889                error_log($str);
     890                break;
     891            case 'html':
     892                //Cleans up output a bit for a better looking, HTML-safe output
     893                echo htmlentities(
     894                    preg_replace('/[\r\n]+/', '', $str),
     895                    ENT_QUOTES,
     896                    'UTF-8'
     897                ), "<br>\n";
     898                break;
     899            case 'echo':
     900            default:
     901                //Normalize line breaks
     902                $str = preg_replace('/\r\n|\r/m', "\n", $str);
     903                echo gmdate('Y-m-d H:i:s'),
     904                "\t",
     905                    //Trim trailing space
     906                trim(
     907                    //Indent for readability, except for trailing break
     908                    str_replace(
     909                        "\n",
     910                        "\n                   \t                  ",
     911                        trim($str)
     912                    )
     913                ),
     914                "\n";
     915        }
     916    }
     917
     918    /**
     919     * Sets message type to HTML or plain.
     920     *
     921     * @param bool $isHtml True for HTML mode
     922     */
     923    public function isHTML($isHtml = true)
     924    {
     925        if ($isHtml) {
     926            $this->ContentType = static::CONTENT_TYPE_TEXT_HTML;
     927        } else {
     928            $this->ContentType = static::CONTENT_TYPE_PLAINTEXT;
     929        }
     930    }
     931
     932    /**
     933     * Send messages using SMTP.
     934     */
     935    public function isSMTP()
     936    {
     937        $this->Mailer = 'smtp';
     938    }
     939
     940    /**
     941     * Send messages using PHP's mail() function.
     942     */
     943    public function isMail()
     944    {
     945        $this->Mailer = 'mail';
     946    }
     947
     948    /**
     949     * Send messages using $Sendmail.
     950     */
     951    public function isSendmail()
     952    {
     953        $ini_sendmail_path = ini_get('sendmail_path');
     954
     955        if (false === stripos($ini_sendmail_path, 'sendmail')) {
     956            $this->Sendmail = '/usr/sbin/sendmail';
     957        } else {
     958            $this->Sendmail = $ini_sendmail_path;
     959        }
     960        $this->Mailer = 'sendmail';
     961    }
     962
     963    /**
     964     * Send messages using qmail.
     965     */
     966    public function isQmail()
     967    {
     968        $ini_sendmail_path = ini_get('sendmail_path');
     969
     970        if (false === stripos($ini_sendmail_path, 'qmail')) {
     971            $this->Sendmail = '/var/qmail/bin/qmail-inject';
     972        } else {
     973            $this->Sendmail = $ini_sendmail_path;
     974        }
     975        $this->Mailer = 'qmail';
     976    }
     977
     978    /**
     979     * Add a "To" address.
     980     *
     981     * @param string $address The email address to send to
     982     * @param string $name
     983     *
     984     * @throws Exception
     985     *
     986     * @return bool true on success, false if address already used or invalid in some way
     987     */
     988    public function addAddress($address, $name = '')
     989    {
     990        return $this->addOrEnqueueAnAddress('to', $address, $name);
     991    }
     992
     993    /**
     994     * Add a "CC" address.
     995     *
     996     * @param string $address The email address to send to
     997     * @param string $name
     998     *
     999     * @throws Exception
     1000     *
     1001     * @return bool true on success, false if address already used or invalid in some way
     1002     */
     1003    public function addCC($address, $name = '')
     1004    {
     1005        return $this->addOrEnqueueAnAddress('cc', $address, $name);
     1006    }
     1007
     1008    /**
     1009     * Add a "BCC" address.
     1010     *
     1011     * @param string $address The email address to send to
     1012     * @param string $name
     1013     *
     1014     * @throws Exception
     1015     *
     1016     * @return bool true on success, false if address already used or invalid in some way
     1017     */
     1018    public function addBCC($address, $name = '')
     1019    {
     1020        return $this->addOrEnqueueAnAddress('bcc', $address, $name);
     1021    }
     1022
     1023    /**
     1024     * Add a "Reply-To" address.
     1025     *
     1026     * @param string $address The email address to reply to
     1027     * @param string $name
     1028     *
     1029     * @throws Exception
     1030     *
     1031     * @return bool true on success, false if address already used or invalid in some way
     1032     */
     1033    public function addReplyTo($address, $name = '')
     1034    {
     1035        return $this->addOrEnqueueAnAddress('Reply-To', $address, $name);
     1036    }
     1037
     1038    /**
     1039     * Add an address to one of the recipient arrays or to the ReplyTo array. Because PHPMailer
     1040     * can't validate addresses with an IDN without knowing the PHPMailer::$CharSet (that can still
     1041     * be modified after calling this function), addition of such addresses is delayed until send().
     1042     * Addresses that have been added already return false, but do not throw exceptions.
     1043     *
     1044     * @param string $kind    One of 'to', 'cc', 'bcc', or 'ReplyTo'
     1045     * @param string $address The email address to send, resp. to reply to
     1046     * @param string $name
     1047     *
     1048     * @throws Exception
     1049     *
     1050     * @return bool true on success, false if address already used or invalid in some way
     1051     */
     1052    protected function addOrEnqueueAnAddress($kind, $address, $name)
     1053    {
     1054        $address = trim($address);
     1055        $name = trim(preg_replace('/[\r\n]+/', '', $name)); //Strip breaks and trim
     1056        $pos = strrpos($address, '@');
     1057        if (false === $pos) {
     1058            // At-sign is missing.
     1059            $error_message = sprintf(
     1060                '%s (%s): %s',
     1061                $this->lang('invalid_address'),
     1062                $kind,
     1063                $address
     1064            );
     1065            $this->setError($error_message);
     1066            $this->edebug($error_message);
     1067            if ($this->exceptions) {
     1068                throw new Exception($error_message);
     1069            }
     1070
     1071            return false;
     1072        }
     1073        $params = [$kind, $address, $name];
     1074        // Enqueue addresses with IDN until we know the PHPMailer::$CharSet.
     1075        if (static::idnSupported() && $this->has8bitChars(substr($address, ++$pos))) {
     1076            if ('Reply-To' !== $kind) {
     1077                if (!array_key_exists($address, $this->RecipientsQueue)) {
     1078                    $this->RecipientsQueue[$address] = $params;
     1079
     1080                    return true;
     1081                }
     1082            } elseif (!array_key_exists($address, $this->ReplyToQueue)) {
     1083                $this->ReplyToQueue[$address] = $params;
     1084
     1085                return true;
     1086            }
     1087
     1088            return false;
     1089        }
     1090
     1091        // Immediately add standard addresses without IDN.
     1092        return call_user_func_array([$this, 'addAnAddress'], $params);
     1093    }
     1094
     1095    /**
     1096     * Add an address to one of the recipient arrays or to the ReplyTo array.
     1097     * Addresses that have been added already return false, but do not throw exceptions.
     1098     *
     1099     * @param string $kind    One of 'to', 'cc', 'bcc', or 'ReplyTo'
     1100     * @param string $address The email address to send, resp. to reply to
     1101     * @param string $name
     1102     *
     1103     * @throws Exception
     1104     *
     1105     * @return bool true on success, false if address already used or invalid in some way
     1106     */
     1107    protected function addAnAddress($kind, $address, $name = '')
     1108    {
     1109        if (!in_array($kind, ['to', 'cc', 'bcc', 'Reply-To'])) {
     1110            $error_message = sprintf(
     1111                '%s: %s',
     1112                $this->lang('Invalid recipient kind'),
     1113                $kind
     1114            );
     1115            $this->setError($error_message);
     1116            $this->edebug($error_message);
     1117            if ($this->exceptions) {
     1118                throw new Exception($error_message);
     1119            }
     1120
     1121            return false;
     1122        }
     1123        if (!static::validateAddress($address)) {
     1124            $error_message = sprintf(
     1125                '%s (%s): %s',
     1126                $this->lang('invalid_address'),
     1127                $kind,
     1128                $address
     1129            );
     1130            $this->setError($error_message);
     1131            $this->edebug($error_message);
     1132            if ($this->exceptions) {
     1133                throw new Exception($error_message);
     1134            }
     1135
     1136            return false;
     1137        }
     1138        if ('Reply-To' !== $kind) {
     1139            if (!array_key_exists(strtolower($address), $this->all_recipients)) {
     1140                $this->{$kind}[] = [$address, $name];
     1141                $this->all_recipients[strtolower($address)] = true;
     1142
     1143                return true;
     1144            }
     1145        } elseif (!array_key_exists(strtolower($address), $this->ReplyTo)) {
     1146            $this->ReplyTo[strtolower($address)] = [$address, $name];
     1147
     1148            return true;
     1149        }
     1150
     1151        return false;
     1152    }
     1153
     1154    /**
     1155     * Parse and validate a string containing one or more RFC822-style comma-separated email addresses
     1156     * of the form "display name <address>" into an array of name/address pairs.
     1157     * Uses the imap_rfc822_parse_adrlist function if the IMAP extension is available.
     1158     * Note that quotes in the name part are removed.
     1159     *
     1160     * @see http://www.andrew.cmu.edu/user/agreen1/testing/mrbs/web/Mail/RFC822.php A more careful implementation
     1161     *
     1162     * @param string $addrstr The address list string
     1163     * @param bool   $useimap Whether to use the IMAP extension to parse the list
     1164     *
     1165     * @return array
     1166     */
     1167    public static function parseAddresses($addrstr, $useimap = true)
     1168    {
     1169        $addresses = [];
     1170        if ($useimap && function_exists('imap_rfc822_parse_adrlist')) {
     1171            //Use this built-in parser if it's available
     1172            $list = imap_rfc822_parse_adrlist($addrstr, '');
     1173            foreach ($list as $address) {
     1174                if (('.SYNTAX-ERROR.' !== $address->host) && static::validateAddress(
     1175                    $address->mailbox . '@' . $address->host
     1176                )) {
     1177                    $addresses[] = [
     1178                        'name' => (property_exists($address, 'personal') ? $address->personal : ''),
     1179                        'address' => $address->mailbox . '@' . $address->host,
     1180                    ];
     1181                }
     1182            }
     1183        } else {
     1184            //Use this simpler parser
     1185            $list = explode(',', $addrstr);
     1186            foreach ($list as $address) {
     1187                $address = trim($address);
     1188                //Is there a separate name part?
     1189                if (strpos($address, '<') === false) {
     1190                    //No separate name, just use the whole thing
     1191                    if (static::validateAddress($address)) {
     1192                        $addresses[] = [
     1193                            'name' => '',
     1194                            'address' => $address,
     1195                        ];
     1196                    }
     1197                } else {
     1198                    list($name, $email) = explode('<', $address);
     1199                    $email = trim(str_replace('>', '', $email));
     1200                    if (static::validateAddress($email)) {
     1201                        $addresses[] = [
     1202                            'name' => trim(str_replace(['"', "'"], '', $name)),
     1203                            'address' => $email,
     1204                        ];
     1205                    }
     1206                }
     1207            }
     1208        }
     1209
     1210        return $addresses;
     1211    }
     1212
     1213    /**
     1214     * Set the From and FromName properties.
     1215     *
     1216     * @param string $address
     1217     * @param string $name
     1218     * @param bool   $auto    Whether to also set the Sender address, defaults to true
     1219     *
     1220     * @throws Exception
     1221     *
     1222     * @return bool
     1223     */
     1224    public function setFrom($address, $name = '', $auto = true)
     1225    {
     1226        $address = trim($address);
     1227        $name = trim(preg_replace('/[\r\n]+/', '', $name)); //Strip breaks and trim
     1228        // Don't validate now addresses with IDN. Will be done in send().
     1229        $pos = strrpos($address, '@');
     1230        if ((false === $pos)
     1231            || ((!$this->has8bitChars(substr($address, ++$pos)) || !static::idnSupported())
     1232            && !static::validateAddress($address))
     1233        ) {
     1234            $error_message = sprintf(
     1235                '%s (From): %s',
     1236                $this->lang('invalid_address'),
     1237                $address
     1238            );
     1239            $this->setError($error_message);
     1240            $this->edebug($error_message);
     1241            if ($this->exceptions) {
     1242                throw new Exception($error_message);
     1243            }
     1244
     1245            return false;
     1246        }
     1247        $this->From = $address;
     1248        $this->FromName = $name;
     1249        if ($auto && empty($this->Sender)) {
     1250            $this->Sender = $address;
     1251        }
     1252
     1253        return true;
     1254    }
     1255
     1256    /**
     1257     * Return the Message-ID header of the last email.
     1258     * Technically this is the value from the last time the headers were created,
     1259     * but it's also the message ID of the last sent message except in
     1260     * pathological cases.
     1261     *
     1262     * @return string
     1263     */
     1264    public function getLastMessageID()
     1265    {
     1266        return $this->lastMessageID;
     1267    }
     1268
     1269    /**
     1270     * Check that a string looks like an email address.
     1271     * Validation patterns supported:
     1272     * * `auto` Pick best pattern automatically;
     1273     * * `pcre8` Use the squiloople.com pattern, requires PCRE > 8.0;
     1274     * * `pcre` Use old PCRE implementation;
     1275     * * `php` Use PHP built-in FILTER_VALIDATE_EMAIL;
     1276     * * `html5` Use the pattern given by the HTML5 spec for 'email' type form input elements.
     1277     * * `noregex` Don't use a regex: super fast, really dumb.
     1278     * Alternatively you may pass in a callable to inject your own validator, for example:
     1279     *
     1280     * ```php
     1281     * PHPMailer::validateAddress('user@example.com', function($address) {
     1282     *     return (strpos($address, '@') !== false);
     1283     * });
     1284     * ```
     1285     *
     1286     * You can also set the PHPMailer::$validator static to a callable, allowing built-in methods to use your validator.
     1287     *
     1288     * @param string          $address       The email address to check
     1289     * @param string|callable $patternselect Which pattern to use
     1290     *
     1291     * @return bool
     1292     */
     1293    public static function validateAddress($address, $patternselect = null)
     1294    {
     1295        if (null === $patternselect) {
     1296            $patternselect = static::$validator;
     1297        }
     1298        if (is_callable($patternselect)) {
     1299            return $patternselect($address);
     1300        }
     1301        //Reject line breaks in addresses; it's valid RFC5322, but not RFC5321
     1302        if (strpos($address, "\n") !== false || strpos($address, "\r") !== false) {
     1303            return false;
     1304        }
     1305        switch ($patternselect) {
     1306            case 'pcre': //Kept for BC
     1307            case 'pcre8':
     1308                /*
     1309                 * A more complex and more permissive version of the RFC5322 regex on which FILTER_VALIDATE_EMAIL
     1310                 * is based.
     1311                 * In addition to the addresses allowed by filter_var, also permits:
     1312                 *  * dotless domains: `a@b`
     1313                 *  * comments: `1234 @ local(blah) .machine .example`
     1314                 *  * quoted elements: `'"test blah"@example.org'`
     1315                 *  * numeric TLDs: `a@b.123`
     1316                 *  * unbracketed IPv4 literals: `a@192.168.0.1`
     1317                 *  * IPv6 literals: 'first.last@[IPv6:a1::]'
     1318                 * Not all of these will necessarily work for sending!
     1319                 *
     1320                 * @see       http://squiloople.com/2009/12/20/email-address-validation/
     1321                 * @copyright 2009-2010 Michael Rushton
     1322                 * Feel free to use and redistribute this code. But please keep this copyright notice.
     1323                 */
     1324                return (bool) preg_match(
     1325                    '/^(?!(?>(?1)"?(?>\\\[ -~]|[^"])"?(?1)){255,})(?!(?>(?1)"?(?>\\\[ -~]|[^"])"?(?1)){65,}@)' .
     1326                    '((?>(?>(?>((?>(?>(?>\x0D\x0A)?[\t ])+|(?>[\t ]*\x0D\x0A)?[\t ]+)?)(\((?>(?2)' .
     1327                    '(?>[\x01-\x08\x0B\x0C\x0E-\'*-\[\]-\x7F]|\\\[\x00-\x7F]|(?3)))*(?2)\)))+(?2))|(?2))?)' .
     1328                    '([!#-\'*+\/-9=?^-~-]+|"(?>(?2)(?>[\x01-\x08\x0B\x0C\x0E-!#-\[\]-\x7F]|\\\[\x00-\x7F]))*' .
     1329                    '(?2)")(?>(?1)\.(?1)(?4))*(?1)@(?!(?1)[a-z0-9-]{64,})(?1)(?>([a-z0-9](?>[a-z0-9-]*[a-z0-9])?)' .
     1330                    '(?>(?1)\.(?!(?1)[a-z0-9-]{64,})(?1)(?5)){0,126}|\[(?:(?>IPv6:(?>([a-f0-9]{1,4})(?>:(?6)){7}' .
     1331                    '|(?!(?:.*[a-f0-9][:\]]){8,})((?6)(?>:(?6)){0,6})?::(?7)?))|(?>(?>IPv6:(?>(?6)(?>:(?6)){5}:' .
     1332                    '|(?!(?:.*[a-f0-9]:){6,})(?8)?::(?>((?6)(?>:(?6)){0,4}):)?))?(25[0-5]|2[0-4][0-9]|1[0-9]{2}' .
     1333                    '|[1-9]?[0-9])(?>\.(?9)){3}))\])(?1)$/isD',
     1334                    $address
     1335                );
     1336            case 'html5':
     1337                /*
     1338                 * This is the pattern used in the HTML5 spec for validation of 'email' type form input elements.
     1339                 *
     1340                 * @see http://www.whatwg.org/specs/web-apps/current-work/#e-mail-state-(type=email)
     1341                 */
     1342                return (bool) preg_match(
     1343                    '/^[a-zA-Z0-9.!#$%&\'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}' .
     1344                    '[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/sD',
     1345                    $address
     1346                );
     1347            case 'php':
     1348            default:
     1349                return filter_var($address, FILTER_VALIDATE_EMAIL) !== false;
     1350        }
     1351    }
     1352
     1353    /**
     1354     * Tells whether IDNs (Internationalized Domain Names) are supported or not. This requires the
     1355     * `intl` and `mbstring` PHP extensions.
     1356     *
     1357     * @return bool `true` if required functions for IDN support are present
     1358     */
     1359    public static function idnSupported()
     1360    {
     1361        return function_exists('idn_to_ascii') && function_exists('mb_convert_encoding');
     1362    }
     1363
     1364    /**
     1365     * Converts IDN in given email address to its ASCII form, also known as punycode, if possible.
     1366     * Important: Address must be passed in same encoding as currently set in PHPMailer::$CharSet.
     1367     * This function silently returns unmodified address if:
     1368     * - No conversion is necessary (i.e. domain name is not an IDN, or is already in ASCII form)
     1369     * - Conversion to punycode is impossible (e.g. required PHP functions are not available)
     1370     *   or fails for any reason (e.g. domain contains characters not allowed in an IDN).
     1371     *
     1372     * @see PHPMailer::$CharSet
     1373     *
     1374     * @param string $address The email address to convert
     1375     *
     1376     * @return string The encoded address in ASCII form
     1377     */
     1378    public function punyencodeAddress($address)
     1379    {
     1380        // Verify we have required functions, CharSet, and at-sign.
     1381        $pos = strrpos($address, '@');
     1382        if (!empty($this->CharSet) &&
     1383            false !== $pos &&
     1384            static::idnSupported()
     1385        ) {
     1386            $domain = substr($address, ++$pos);
     1387            // Verify CharSet string is a valid one, and domain properly encoded in this CharSet.
     1388            if ($this->has8bitChars($domain) && @mb_check_encoding($domain, $this->CharSet)) {
     1389                $domain = mb_convert_encoding($domain, 'UTF-8', $this->CharSet);
     1390                //Ignore IDE complaints about this line - method signature changed in PHP 5.4
     1391                $errorcode = 0;
     1392                if (defined('INTL_IDNA_VARIANT_UTS46')) {
     1393                    $punycode = idn_to_ascii($domain, $errorcode, INTL_IDNA_VARIANT_UTS46);
     1394                } elseif (defined('INTL_IDNA_VARIANT_2003')) {
     1395                    $punycode = idn_to_ascii($domain, $errorcode, INTL_IDNA_VARIANT_2003);
     1396                } else {
     1397                    $punycode = idn_to_ascii($domain, $errorcode);
     1398                }
     1399                if (false !== $punycode) {
     1400                    return substr($address, 0, $pos) . $punycode;
     1401                }
     1402            }
     1403        }
     1404
     1405        return $address;
     1406    }
     1407
     1408    /**
     1409     * Create a message and send it.
     1410     * Uses the sending method specified by $Mailer.
     1411     *
     1412     * @throws Exception
     1413     *
     1414     * @return bool false on error - See the ErrorInfo property for details of the error
     1415     */
     1416    public function send()
     1417    {
     1418        try {
     1419            if (!$this->preSend()) {
     1420                return false;
     1421            }
     1422
     1423            return $this->postSend();
     1424        } catch (Exception $exc) {
     1425            $this->mailHeader = '';
     1426            $this->setError($exc->getMessage());
     1427            if ($this->exceptions) {
     1428                throw $exc;
     1429            }
     1430
     1431            return false;
     1432        }
     1433    }
     1434
     1435    /**
     1436     * Prepare a message for sending.
     1437     *
     1438     * @throws Exception
     1439     *
     1440     * @return bool
     1441     */
     1442    public function preSend()
     1443    {
     1444        if ('smtp' === $this->Mailer
     1445            || ('mail' === $this->Mailer && stripos(PHP_OS, 'WIN') === 0)
     1446        ) {
     1447            //SMTP mandates RFC-compliant line endings
     1448            //and it's also used with mail() on Windows
     1449            static::setLE("\r\n");
     1450        } else {
     1451            //Maintain backward compatibility with legacy Linux command line mailers
     1452            static::setLE(PHP_EOL);
     1453        }
     1454        //Check for buggy PHP versions that add a header with an incorrect line break
     1455        if ('mail' === $this->Mailer
     1456            && ((PHP_VERSION_ID >= 70000 && PHP_VERSION_ID < 70017)
     1457                || (PHP_VERSION_ID >= 70100 && PHP_VERSION_ID < 70103))
     1458            && ini_get('mail.add_x_header') === '1'
     1459            && stripos(PHP_OS, 'WIN') === 0
     1460        ) {
     1461            trigger_error(
     1462                'Your version of PHP is affected by a bug that may result in corrupted messages.' .
     1463                ' To fix it, switch to sending using SMTP, disable the mail.add_x_header option in' .
     1464                ' your php.ini, switch to MacOS or Linux, or upgrade your PHP to version 7.0.17+ or 7.1.3+.',
     1465                E_USER_WARNING
     1466            );
     1467        }
     1468
     1469        try {
     1470            $this->error_count = 0; // Reset errors
     1471            $this->mailHeader = '';
     1472
     1473            // Dequeue recipient and Reply-To addresses with IDN
     1474            foreach (array_merge($this->RecipientsQueue, $this->ReplyToQueue) as $params) {
     1475                $params[1] = $this->punyencodeAddress($params[1]);
     1476                call_user_func_array([$this, 'addAnAddress'], $params);
     1477            }
     1478            if (count($this->to) + count($this->cc) + count($this->bcc) < 1) {
     1479                throw new Exception($this->lang('provide_address'), self::STOP_CRITICAL);
     1480            }
     1481
     1482            // Validate From, Sender, and ConfirmReadingTo addresses
     1483            foreach (['From', 'Sender', 'ConfirmReadingTo'] as $address_kind) {
     1484                $this->$address_kind = trim($this->$address_kind);
     1485                if (empty($this->$address_kind)) {
     1486                    continue;
     1487                }
     1488                $this->$address_kind = $this->punyencodeAddress($this->$address_kind);
     1489                if (!static::validateAddress($this->$address_kind)) {
     1490                    $error_message = sprintf(
     1491                        '%s (%s): %s',
     1492                        $this->lang('invalid_address'),
     1493                        $address_kind,
     1494                        $this->$address_kind
     1495                    );
     1496                    $this->setError($error_message);
     1497                    $this->edebug($error_message);
     1498                    if ($this->exceptions) {
     1499                        throw new Exception($error_message);
     1500                    }
     1501
     1502                    return false;
     1503                }
     1504            }
     1505
     1506            // Set whether the message is multipart/alternative
     1507            if ($this->alternativeExists()) {
     1508                $this->ContentType = static::CONTENT_TYPE_MULTIPART_ALTERNATIVE;
     1509            }
     1510
     1511            $this->setMessageType();
     1512            // Refuse to send an empty message unless we are specifically allowing it
     1513            if (!$this->AllowEmpty && empty($this->Body)) {
     1514                throw new Exception($this->lang('empty_message'), self::STOP_CRITICAL);
     1515            }
     1516
     1517            //Trim subject consistently
     1518            $this->Subject = trim($this->Subject);
     1519            // Create body before headers in case body makes changes to headers (e.g. altering transfer encoding)
     1520            $this->MIMEHeader = '';
     1521            $this->MIMEBody = $this->createBody();
     1522            // createBody may have added some headers, so retain them
     1523            $tempheaders = $this->MIMEHeader;
     1524            $this->MIMEHeader = $this->createHeader();
     1525            $this->MIMEHeader .= $tempheaders;
     1526
     1527            // To capture the complete message when using mail(), create
     1528            // an extra header list which createHeader() doesn't fold in
     1529            if ('mail' === $this->Mailer) {
     1530                if (count($this->to) > 0) {
     1531                    $this->mailHeader .= $this->addrAppend('To', $this->to);
     1532                } else {
     1533                    $this->mailHeader .= $this->headerLine('To', 'undisclosed-recipients:;');
     1534                }
     1535                $this->mailHeader .= $this->headerLine(
     1536                    'Subject',
     1537                    $this->encodeHeader($this->secureHeader($this->Subject))
     1538                );
     1539            }
     1540
     1541            // Sign with DKIM if enabled
     1542            if (!empty($this->DKIM_domain)
     1543                && !empty($this->DKIM_selector)
     1544                && (!empty($this->DKIM_private_string)
     1545                    || (!empty($this->DKIM_private)
     1546                        && static::isPermittedPath($this->DKIM_private)
     1547                        && file_exists($this->DKIM_private)
     1548                    )
     1549                )
     1550            ) {
     1551                $header_dkim = $this->DKIM_Add(
     1552                    $this->MIMEHeader . $this->mailHeader,
     1553                    $this->encodeHeader($this->secureHeader($this->Subject)),
     1554                    $this->MIMEBody
     1555                );
     1556                $this->MIMEHeader = rtrim($this->MIMEHeader, "\r\n ") . static::$LE .
     1557                    static::normalizeBreaks($header_dkim) . static::$LE;
     1558            }
     1559
     1560            return true;
     1561        } catch (Exception $exc) {
     1562            $this->setError($exc->getMessage());
     1563            if ($this->exceptions) {
     1564                throw $exc;
     1565            }
     1566
     1567            return false;
     1568        }
     1569    }
     1570
     1571    /**
     1572     * Actually send a message via the selected mechanism.
     1573     *
     1574     * @throws Exception
     1575     *
     1576     * @return bool
     1577     */
     1578    public function postSend()
     1579    {
     1580        try {
     1581            // Choose the mailer and send through it
     1582            switch ($this->Mailer) {
     1583                case 'sendmail':
     1584                case 'qmail':
     1585                    return $this->sendmailSend($this->MIMEHeader, $this->MIMEBody);
     1586                case 'smtp':
     1587                    return $this->smtpSend($this->MIMEHeader, $this->MIMEBody);
     1588                case 'mail':
     1589                    return $this->mailSend($this->MIMEHeader, $this->MIMEBody);
     1590                default:
     1591                    $sendMethod = $this->Mailer . 'Send';
     1592                    if (method_exists($this, $sendMethod)) {
     1593                        return $this->$sendMethod($this->MIMEHeader, $this->MIMEBody);
     1594                    }
     1595
     1596                    return $this->mailSend($this->MIMEHeader, $this->MIMEBody);
     1597            }
     1598        } catch (Exception $exc) {
     1599            $this->setError($exc->getMessage());
     1600            $this->edebug($exc->getMessage());
     1601            if ($this->exceptions) {
     1602                throw $exc;
     1603            }
     1604        }
     1605
     1606        return false;
     1607    }
     1608
     1609    /**
     1610     * Send mail using the $Sendmail program.
     1611     *
     1612     * @see PHPMailer::$Sendmail
     1613     *
     1614     * @param string $header The message headers
     1615     * @param string $body   The message body
     1616     *
     1617     * @throws Exception
     1618     *
     1619     * @return bool
     1620     */
     1621    protected function sendmailSend($header, $body)
     1622    {
     1623        $header = rtrim($header, "\r\n ") . static::$LE . static::$LE;
     1624
     1625        // CVE-2016-10033, CVE-2016-10045: Don't pass -f if characters will be escaped.
     1626        if (!empty($this->Sender) && self::isShellSafe($this->Sender)) {
     1627            if ('qmail' === $this->Mailer) {
     1628                $sendmailFmt = '%s -f%s';
     1629            } else {
     1630                $sendmailFmt = '%s -oi -f%s -t';
     1631            }
     1632        } elseif ('qmail' === $this->Mailer) {
     1633            $sendmailFmt = '%s';
     1634        } else {
     1635            $sendmailFmt = '%s -oi -t';
     1636        }
     1637
     1638        $sendmail = sprintf($sendmailFmt, escapeshellcmd($this->Sendmail), $this->Sender);
     1639
     1640        if ($this->SingleTo) {
     1641            foreach ($this->SingleToArray as $toAddr) {
     1642                $mail = @popen($sendmail, 'w');
     1643                if (!$mail) {
     1644                    throw new Exception($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL);
     1645                }
     1646                fwrite($mail, 'To: ' . $toAddr . "\n");
     1647                fwrite($mail, $header);
     1648                fwrite($mail, $body);
     1649                $result = pclose($mail);
     1650                $this->doCallback(
     1651                    ($result === 0),
     1652                    [$toAddr],
     1653                    $this->cc,
     1654                    $this->bcc,
     1655                    $this->Subject,
     1656                    $body,
     1657                    $this->From,
     1658                    []
     1659                );
     1660                if (0 !== $result) {
     1661                    throw new Exception($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL);
     1662                }
     1663            }
     1664        } else {
     1665            $mail = @popen($sendmail, 'w');
     1666            if (!$mail) {
     1667                throw new Exception($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL);
     1668            }
     1669            fwrite($mail, $header);
     1670            fwrite($mail, $body);
     1671            $result = pclose($mail);
     1672            $this->doCallback(
     1673                ($result === 0),
     1674                $this->to,
     1675                $this->cc,
     1676                $this->bcc,
     1677                $this->Subject,
     1678                $body,
     1679                $this->From,
     1680                []
     1681            );
     1682            if (0 !== $result) {
     1683                throw new Exception($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL);
     1684            }
     1685        }
     1686
     1687        return true;
     1688    }
     1689
     1690    /**
     1691     * Fix CVE-2016-10033 and CVE-2016-10045 by disallowing potentially unsafe shell characters.
     1692     * Note that escapeshellarg and escapeshellcmd are inadequate for our purposes, especially on Windows.
     1693     *
     1694     * @see https://github.com/PHPMailer/PHPMailer/issues/924 CVE-2016-10045 bug report
     1695     *
     1696     * @param string $string The string to be validated
     1697     *
     1698     * @return bool
     1699     */
     1700    protected static function isShellSafe($string)
     1701    {
     1702        // Future-proof
     1703        if (escapeshellcmd($string) !== $string
     1704            || !in_array(escapeshellarg($string), ["'$string'", "\"$string\""])
     1705        ) {
     1706            return false;
     1707        }
     1708
     1709        $length = strlen($string);
     1710
     1711        for ($i = 0; $i < $length; ++$i) {
     1712            $c = $string[$i];
     1713
     1714            // All other characters have a special meaning in at least one common shell, including = and +.
     1715            // Full stop (.) has a special meaning in cmd.exe, but its impact should be negligible here.
     1716            // Note that this does permit non-Latin alphanumeric characters based on the current locale.
     1717            if (!ctype_alnum($c) && strpos('@_-.', $c) === false) {
     1718                return false;
     1719            }
     1720        }
     1721
     1722        return true;
     1723    }
     1724
     1725    /**
     1726     * Check whether a file path is of a permitted type.
     1727     * Used to reject URLs and phar files from functions that access local file paths,
     1728     * such as addAttachment.
     1729     *
     1730     * @param string $path A relative or absolute path to a file
     1731     *
     1732     * @return bool
     1733     */
     1734    protected static function isPermittedPath($path)
     1735    {
     1736        return !preg_match('#^[a-z]+://#i', $path);
     1737    }
     1738
     1739    /**
     1740     * Send mail using the PHP mail() function.
     1741     *
     1742     * @see http://www.php.net/manual/en/book.mail.php
     1743     *
     1744     * @param string $header The message headers
     1745     * @param string $body   The message body
     1746     *
     1747     * @throws Exception
     1748     *
     1749     * @return bool
     1750     */
     1751    protected function mailSend($header, $body)
     1752    {
     1753        $header = rtrim($header, "\r\n ") . static::$LE . static::$LE;
     1754
     1755        $toArr = [];
     1756        foreach ($this->to as $toaddr) {
     1757            $toArr[] = $this->addrFormat($toaddr);
     1758        }
     1759        $to = implode(', ', $toArr);
     1760
     1761        $params = null;
     1762        //This sets the SMTP envelope sender which gets turned into a return-path header by the receiver
     1763        //A space after `-f` is optional, but there is a long history of its presence
     1764        //causing problems, so we don't use one
     1765        //Exim docs: http://www.exim.org/exim-html-current/doc/html/spec_html/ch-the_exim_command_line.html
     1766        //Sendmail docs: http://www.sendmail.org/~ca/email/man/sendmail.html
     1767        //Qmail docs: http://www.qmail.org/man/man8/qmail-inject.html
     1768        //Example problem: https://www.drupal.org/node/1057954
     1769        // CVE-2016-10033, CVE-2016-10045: Don't pass -f if characters will be escaped.
     1770        if (!empty($this->Sender) && static::validateAddress($this->Sender) && self::isShellSafe($this->Sender)) {
     1771            $params = sprintf('-f%s', $this->Sender);
     1772        }
     1773        if (!empty($this->Sender) && static::validateAddress($this->Sender)) {
     1774            $old_from = ini_get('sendmail_from');
     1775            ini_set('sendmail_from', $this->Sender);
     1776        }
     1777        $result = false;
     1778        if ($this->SingleTo && count($toArr) > 1) {
     1779            foreach ($toArr as $toAddr) {
     1780                $result = $this->mailPassthru($toAddr, $this->Subject, $body, $header, $params);
     1781                $this->doCallback($result, [$toAddr], $this->cc, $this->bcc, $this->Subject, $body, $this->From, []);
     1782            }
     1783        } else {
     1784            $result = $this->mailPassthru($to, $this->Subject, $body, $header, $params);
     1785            $this->doCallback($result, $this->to, $this->cc, $this->bcc, $this->Subject, $body, $this->From, []);
     1786        }
     1787        if (isset($old_from)) {
     1788            ini_set('sendmail_from', $old_from);
     1789        }
     1790        if (!$result) {
     1791            throw new Exception($this->lang('instantiate'), self::STOP_CRITICAL);
     1792        }
     1793
     1794        return true;
     1795    }
     1796
     1797    /**
     1798     * Get an instance to use for SMTP operations.
     1799     * Override this function to load your own SMTP implementation,
     1800     * or set one with setSMTPInstance.
     1801     *
     1802     * @return SMTP
     1803     */
     1804    public function getSMTPInstance()
     1805    {
     1806        if (!is_object($this->smtp)) {
     1807            $this->smtp = new SMTP();
     1808        }
     1809
     1810        return $this->smtp;
     1811    }
     1812
     1813    /**
     1814     * Provide an instance to use for SMTP operations.
     1815     *
     1816     * @return SMTP
     1817     */
     1818    public function setSMTPInstance(SMTP $smtp)
     1819    {
     1820        $this->smtp = $smtp;
     1821
     1822        return $this->smtp;
     1823    }
     1824
     1825    /**
     1826     * Send mail via SMTP.
     1827     * Returns false if there is a bad MAIL FROM, RCPT, or DATA input.
     1828     *
     1829     * @see PHPMailer::setSMTPInstance() to use a different class.
     1830     *
     1831     * @uses \PHPMailer\PHPMailer\SMTP
     1832     *
     1833     * @param string $header The message headers
     1834     * @param string $body   The message body
     1835     *
     1836     * @throws Exception
     1837     *
     1838     * @return bool
     1839     */
     1840    protected function smtpSend($header, $body)
     1841    {
     1842        $header = rtrim($header, "\r\n ") . static::$LE . static::$LE;
     1843        $bad_rcpt = [];
     1844        if (!$this->smtpConnect($this->SMTPOptions)) {
     1845            throw new Exception($this->lang('smtp_connect_failed'), self::STOP_CRITICAL);
     1846        }
     1847        //Sender already validated in preSend()
     1848        if ('' === $this->Sender) {
     1849            $smtp_from = $this->From;
     1850        } else {
     1851            $smtp_from = $this->Sender;
     1852        }
     1853        if (!$this->smtp->mail($smtp_from)) {
     1854            $this->setError($this->lang('from_failed') . $smtp_from . ' : ' . implode(',', $this->smtp->getError()));
     1855            throw new Exception($this->ErrorInfo, self::STOP_CRITICAL);
     1856        }
     1857
     1858        $callbacks = [];
     1859        // Attempt to send to all recipients
     1860        foreach ([$this->to, $this->cc, $this->bcc] as $togroup) {
     1861            foreach ($togroup as $to) {
     1862                if (!$this->smtp->recipient($to[0], $this->dsn)) {
     1863                    $error = $this->smtp->getError();
     1864                    $bad_rcpt[] = ['to' => $to[0], 'error' => $error['detail']];
     1865                    $isSent = false;
     1866                } else {
     1867                    $isSent = true;
     1868                }
     1869
     1870                $callbacks[] = ['issent'=>$isSent, 'to'=>$to[0]];
     1871            }
     1872        }
     1873
     1874        // Only send the DATA command if we have viable recipients
     1875        if ((count($this->all_recipients) > count($bad_rcpt)) && !$this->smtp->data($header . $body)) {
     1876            throw new Exception($this->lang('data_not_accepted'), self::STOP_CRITICAL);
     1877        }
     1878
     1879        $smtp_transaction_id = $this->smtp->getLastTransactionID();
     1880
     1881        if ($this->SMTPKeepAlive) {
     1882            $this->smtp->reset();
     1883        } else {
     1884            $this->smtp->quit();
     1885            $this->smtp->close();
     1886        }
     1887
     1888        foreach ($callbacks as $cb) {
     1889            $this->doCallback(
     1890                $cb['issent'],
     1891                [$cb['to']],
     1892                [],
     1893                [],
     1894                $this->Subject,
     1895                $body,
     1896                $this->From,
     1897                ['smtp_transaction_id' => $smtp_transaction_id]
     1898            );
     1899        }
     1900
     1901        //Create error message for any bad addresses
     1902        if (count($bad_rcpt) > 0) {
     1903            $errstr = '';
     1904            foreach ($bad_rcpt as $bad) {
     1905                $errstr .= $bad['to'] . ': ' . $bad['error'];
     1906            }
     1907            throw new Exception($this->lang('recipients_failed') . $errstr, self::STOP_CONTINUE);
     1908        }
     1909
     1910        return true;
     1911    }
     1912
     1913    /**
     1914     * Initiate a connection to an SMTP server.
     1915     * Returns false if the operation failed.
     1916     *
     1917     * @param array $options An array of options compatible with stream_context_create()
     1918     *
     1919     * @throws Exception
     1920     *
     1921     * @uses \PHPMailer\PHPMailer\SMTP
     1922     *
     1923     * @return bool
     1924     */
     1925    public function smtpConnect($options = null)
     1926    {
     1927        if (null === $this->smtp) {
     1928            $this->smtp = $this->getSMTPInstance();
     1929        }
     1930
     1931        //If no options are provided, use whatever is set in the instance
     1932        if (null === $options) {
     1933            $options = $this->SMTPOptions;
     1934        }
     1935
     1936        // Already connected?
     1937        if ($this->smtp->connected()) {
     1938            return true;
     1939        }
     1940
     1941        $this->smtp->setTimeout($this->Timeout);
     1942        $this->smtp->setDebugLevel($this->SMTPDebug);
     1943        $this->smtp->setDebugOutput($this->Debugoutput);
     1944        $this->smtp->setVerp($this->do_verp);
     1945        $hosts = explode(';', $this->Host);
     1946        $lastexception = null;
     1947
     1948        foreach ($hosts as $hostentry) {
     1949            $hostinfo = [];
     1950            if (!preg_match(
     1951                '/^(?:(ssl|tls):\/\/)?(.+?)(?::(\d+))?$/',
     1952                trim($hostentry),
     1953                $hostinfo
     1954            )) {
     1955                $this->edebug($this->lang('invalid_hostentry') . ' ' . trim($hostentry));
     1956                // Not a valid host entry
     1957                continue;
     1958            }
     1959            // $hostinfo[1]: optional ssl or tls prefix
     1960            // $hostinfo[2]: the hostname
     1961            // $hostinfo[3]: optional port number
     1962            // The host string prefix can temporarily override the current setting for SMTPSecure
     1963            // If it's not specified, the default value is used
     1964
     1965            //Check the host name is a valid name or IP address before trying to use it
     1966            if (!static::isValidHost($hostinfo[2])) {
     1967                $this->edebug($this->lang('invalid_host') . ' ' . $hostinfo[2]);
     1968                continue;
     1969            }
     1970            $prefix = '';
     1971            $secure = $this->SMTPSecure;
     1972            $tls = (static::ENCRYPTION_STARTTLS === $this->SMTPSecure);
     1973            if ('ssl' === $hostinfo[1] || ('' === $hostinfo[1] && static::ENCRYPTION_SMTPS === $this->SMTPSecure)) {
     1974                $prefix = 'ssl://';
     1975                $tls = false; // Can't have SSL and TLS at the same time
     1976                $secure = static::ENCRYPTION_SMTPS;
     1977            } elseif ('tls' === $hostinfo[1]) {
     1978                $tls = true;
     1979                // tls doesn't use a prefix
     1980                $secure = static::ENCRYPTION_STARTTLS;
     1981            }
     1982            //Do we need the OpenSSL extension?
     1983            $sslext = defined('OPENSSL_ALGO_SHA256');
     1984            if (static::ENCRYPTION_STARTTLS === $secure || static::ENCRYPTION_SMTPS === $secure) {
     1985                //Check for an OpenSSL constant rather than using extension_loaded, which is sometimes disabled
     1986                if (!$sslext) {
     1987                    throw new Exception($this->lang('extension_missing') . 'openssl', self::STOP_CRITICAL);
     1988                }
     1989            }
     1990            $host = $hostinfo[2];
     1991            $port = $this->Port;
     1992            if (array_key_exists(3, $hostinfo) && is_numeric($hostinfo[3]) && $hostinfo[3] > 0 && $hostinfo[3] < 65536) {
     1993                $port = (int) $hostinfo[3];
     1994            }
     1995            if ($this->smtp->connect($prefix . $host, $port, $this->Timeout, $options)) {
     1996                try {
     1997                    if ($this->Helo) {
     1998                        $hello = $this->Helo;
     1999                    } else {
     2000                        $hello = $this->serverHostname();
     2001                    }
     2002                    $this->smtp->hello($hello);
     2003                    //Automatically enable TLS encryption if:
     2004                    // * it's not disabled
     2005                    // * we have openssl extension
     2006                    // * we are not already using SSL
     2007                    // * the server offers STARTTLS
     2008                    if ($this->SMTPAutoTLS && $sslext && 'ssl' !== $secure && $this->smtp->getServerExt('STARTTLS')) {
     2009                        $tls = true;
     2010                    }
     2011                    if ($tls) {
     2012                        if (!$this->smtp->startTLS()) {
     2013                            throw new Exception($this->lang('connect_host'));
     2014                        }
     2015                        // We must resend EHLO after TLS negotiation
     2016                        $this->smtp->hello($hello);
     2017                    }
     2018                    if ($this->SMTPAuth && !$this->smtp->authenticate(
     2019                        $this->Username,
     2020                        $this->Password,
     2021                        $this->AuthType,
     2022                        $this->oauth
     2023                    )) {
     2024                        throw new Exception($this->lang('authenticate'));
     2025                    }
     2026
     2027                    return true;
     2028                } catch (Exception $exc) {
     2029                    $lastexception = $exc;
     2030                    $this->edebug($exc->getMessage());
     2031                    // We must have connected, but then failed TLS or Auth, so close connection nicely
     2032                    $this->smtp->quit();
     2033                }
     2034            }
     2035        }
     2036        // If we get here, all connection attempts have failed, so close connection hard
     2037        $this->smtp->close();
     2038        // As we've caught all exceptions, just report whatever the last one was
     2039        if ($this->exceptions && null !== $lastexception) {
     2040            throw $lastexception;
     2041        }
     2042
     2043        return false;
     2044    }
     2045
     2046    /**
     2047     * Close the active SMTP session if one exists.
     2048     */
     2049    public function smtpClose()
     2050    {
     2051        if ((null !== $this->smtp) && $this->smtp->connected()) {
     2052            $this->smtp->quit();
     2053            $this->smtp->close();
     2054        }
     2055    }
     2056
     2057    /**
     2058     * Set the language for error messages.
     2059     * Returns false if it cannot load the language file.
     2060     * The default language is English.
     2061     *
     2062     * @param string $langcode  ISO 639-1 2-character language code (e.g. French is "fr")
     2063     * @param string $lang_path Path to the language file directory, with trailing separator (slash)
     2064     *
     2065     * @return bool
     2066     */
     2067    public function setLanguage($langcode = 'en', $lang_path = '')
     2068    {
     2069        // Backwards compatibility for renamed language codes
     2070        $renamed_langcodes = [
     2071            'br' => 'pt_br',
     2072            'cz' => 'cs',
     2073            'dk' => 'da',
     2074            'no' => 'nb',
     2075            'se' => 'sv',
     2076            'rs' => 'sr',
     2077            'tg' => 'tl',
     2078        ];
     2079
     2080        if (isset($renamed_langcodes[$langcode])) {
     2081            $langcode = $renamed_langcodes[$langcode];
     2082        }
     2083
     2084        // Define full set of translatable strings in English
     2085        $PHPMAILER_LANG = [
     2086            'authenticate' => 'SMTP Error: Could not authenticate.',
     2087            'connect_host' => 'SMTP Error: Could not connect to SMTP host.',
     2088            'data_not_accepted' => 'SMTP Error: data not accepted.',
     2089            'empty_message' => 'Message body empty',
     2090            'encoding' => 'Unknown encoding: ',
     2091            'execute' => 'Could not execute: ',
     2092            'file_access' => 'Could not access file: ',
     2093            'file_open' => 'File Error: Could not open file: ',
     2094            'from_failed' => 'The following From address failed: ',
     2095            'instantiate' => 'Could not instantiate mail function.',
     2096            'invalid_address' => 'Invalid address: ',
     2097            'invalid_hostentry' => 'Invalid hostentry: ',
     2098            'invalid_host' => 'Invalid host: ',
     2099            'mailer_not_supported' => ' mailer is not supported.',
     2100            'provide_address' => 'You must provide at least one recipient email address.',
     2101            'recipients_failed' => 'SMTP Error: The following recipients failed: ',
     2102            'signing' => 'Signing Error: ',
     2103            'smtp_connect_failed' => 'SMTP connect() failed.',
     2104            'smtp_error' => 'SMTP server error: ',
     2105            'variable_set' => 'Cannot set or reset variable: ',
     2106            'extension_missing' => 'Extension missing: ',
     2107        ];
     2108        if (empty($lang_path)) {
     2109            // Calculate an absolute path so it can work if CWD is not here
     2110            $lang_path = dirname(__DIR__) . DIRECTORY_SEPARATOR . 'language' . DIRECTORY_SEPARATOR;
     2111        }
     2112        //Validate $langcode
     2113        if (!preg_match('/^[a-z]{2}(?:_[a-zA-Z]{2})?$/', $langcode)) {
     2114            $langcode = 'en';
     2115        }
     2116        $foundlang = true;
     2117        $lang_file = $lang_path . 'phpmailer.lang-' . $langcode . '.php';
     2118        // There is no English translation file
     2119        if ('en' !== $langcode) {
     2120            // Make sure language file path is readable
     2121            if (!static::isPermittedPath($lang_file) || !file_exists($lang_file)) {
     2122                $foundlang = false;
     2123            } else {
     2124                // Overwrite language-specific strings.
     2125                // This way we'll never have missing translation keys.
     2126                $foundlang = include $lang_file;
     2127            }
     2128        }
     2129        $this->language = $PHPMAILER_LANG;
     2130
     2131        return (bool) $foundlang; // Returns false if language not found
     2132    }
     2133
     2134    /**
     2135     * Get the array of strings for the current language.
     2136     *
     2137     * @return array
     2138     */
     2139    public function getTranslations()
     2140    {
     2141        return $this->language;
     2142    }
     2143
     2144    /**
     2145     * Create recipient headers.
     2146     *
     2147     * @param string $type
     2148     * @param array  $addr An array of recipients,
     2149     *                     where each recipient is a 2-element indexed array with element 0 containing an address
     2150     *                     and element 1 containing a name, like:
     2151     *                     [['joe@example.com', 'Joe User'], ['zoe@example.com', 'Zoe User']]
     2152     *
     2153     * @return string
     2154     */
     2155    public function addrAppend($type, $addr)
     2156    {
     2157        $addresses = [];
     2158        foreach ($addr as $address) {
     2159            $addresses[] = $this->addrFormat($address);
     2160        }
     2161
     2162        return $type . ': ' . implode(', ', $addresses) . static::$LE;
     2163    }
     2164
     2165    /**
     2166     * Format an address for use in a message header.
     2167     *
     2168     * @param array $addr A 2-element indexed array, element 0 containing an address, element 1 containing a name like
     2169     *                    ['joe@example.com', 'Joe User']
     2170     *
     2171     * @return string
     2172     */
     2173    public function addrFormat($addr)
     2174    {
     2175        if (empty($addr[1])) { // No name provided
     2176            return $this->secureHeader($addr[0]);
     2177        }
     2178
     2179        return $this->encodeHeader($this->secureHeader($addr[1]), 'phrase') .
     2180            ' <' . $this->secureHeader($addr[0]) . '>';
     2181    }
     2182
     2183    /**
     2184     * Word-wrap message.
     2185     * For use with mailers that do not automatically perform wrapping
     2186     * and for quoted-printable encoded messages.
     2187     * Original written by philippe.
     2188     *
     2189     * @param string $message The message to wrap
     2190     * @param int    $length  The line length to wrap to
     2191     * @param bool   $qp_mode Whether to run in Quoted-Printable mode
     2192     *
     2193     * @return string
     2194     */
     2195    public function wrapText($message, $length, $qp_mode = false)
     2196    {
     2197        if ($qp_mode) {
     2198            $soft_break = sprintf(' =%s', static::$LE);
     2199        } else {
     2200            $soft_break = static::$LE;
     2201        }
     2202        // If utf-8 encoding is used, we will need to make sure we don't
     2203        // split multibyte characters when we wrap
     2204        $is_utf8 = static::CHARSET_UTF8 === strtolower($this->CharSet);
     2205        $lelen = strlen(static::$LE);
     2206        $crlflen = strlen(static::$LE);
     2207
     2208        $message = static::normalizeBreaks($message);
     2209        //Remove a trailing line break
     2210        if (substr($message, -$lelen) === static::$LE) {
     2211            $message = substr($message, 0, -$lelen);
     2212        }
     2213
     2214        //Split message into lines
     2215        $lines = explode(static::$LE, $message);
     2216        //Message will be rebuilt in here
     2217        $message = '';
     2218        foreach ($lines as $line) {
     2219            $words = explode(' ', $line);
     2220            $buf = '';
     2221            $firstword = true;
     2222            foreach ($words as $word) {
     2223                if ($qp_mode && (strlen($word) > $length)) {
     2224                    $space_left = $length - strlen($buf) - $crlflen;
     2225                    if (!$firstword) {
     2226                        if ($space_left > 20) {
     2227                            $len = $space_left;
     2228                            if ($is_utf8) {
     2229                                $len = $this->utf8CharBoundary($word, $len);
     2230                            } elseif ('=' === substr($word, $len - 1, 1)) {
     2231                                --$len;
     2232                            } elseif ('=' === substr($word, $len - 2, 1)) {
     2233                                $len -= 2;
     2234                            }
     2235                            $part = substr($word, 0, $len);
     2236                            $word = substr($word, $len);
     2237                            $buf .= ' ' . $part;
     2238                            $message .= $buf . sprintf('=%s', static::$LE);
     2239                        } else {
     2240                            $message .= $buf . $soft_break;
     2241                        }
     2242                        $buf = '';
     2243                    }
     2244                    while ($word !== '') {
     2245                        if ($length <= 0) {
     2246                            break;
     2247                        }
     2248                        $len = $length;
     2249                        if ($is_utf8) {
     2250                            $len = $this->utf8CharBoundary($word, $len);
     2251                        } elseif ('=' === substr($word, $len - 1, 1)) {
     2252                            --$len;
     2253                        } elseif ('=' === substr($word, $len - 2, 1)) {
     2254                            $len -= 2;
     2255                        }
     2256                        $part = substr($word, 0, $len);
     2257                        $word = (string) substr($word, $len);
     2258
     2259                        if ($word !== '') {
     2260                            $message .= $part . sprintf('=%s', static::$LE);
     2261                        } else {
     2262                            $buf = $part;
     2263                        }
     2264                    }
     2265                } else {
     2266                    $buf_o = $buf;
     2267                    if (!$firstword) {
     2268                        $buf .= ' ';
     2269                    }
     2270                    $buf .= $word;
     2271
     2272                    if ('' !== $buf_o && strlen($buf) > $length) {
     2273                        $message .= $buf_o . $soft_break;
     2274                        $buf = $word;
     2275                    }
     2276                }
     2277                $firstword = false;
     2278            }
     2279            $message .= $buf . static::$LE;
     2280        }
     2281
     2282        return $message;
     2283    }
     2284
     2285    /**
     2286     * Find the last character boundary prior to $maxLength in a utf-8
     2287     * quoted-printable encoded string.
     2288     * Original written by Colin Brown.
     2289     *
     2290     * @param string $encodedText utf-8 QP text
     2291     * @param int    $maxLength   Find the last character boundary prior to this length
     2292     *
     2293     * @return int
     2294     */
     2295    public function utf8CharBoundary($encodedText, $maxLength)
     2296    {
     2297        $foundSplitPos = false;
     2298        $lookBack = 3;
     2299        while (!$foundSplitPos) {
     2300            $lastChunk = substr($encodedText, $maxLength - $lookBack, $lookBack);
     2301            $encodedCharPos = strpos($lastChunk, '=');
     2302            if (false !== $encodedCharPos) {
     2303                // Found start of encoded character byte within $lookBack block.
     2304                // Check the encoded byte value (the 2 chars after the '=')
     2305                $hex = substr($encodedText, $maxLength - $lookBack + $encodedCharPos + 1, 2);
     2306                $dec = hexdec($hex);
     2307                if ($dec < 128) {
     2308                    // Single byte character.
     2309                    // If the encoded char was found at pos 0, it will fit
     2310                    // otherwise reduce maxLength to start of the encoded char
     2311                    if ($encodedCharPos > 0) {
     2312                        $maxLength -= $lookBack - $encodedCharPos;
     2313                    }
     2314                    $foundSplitPos = true;
     2315                } elseif ($dec >= 192) {
     2316                    // First byte of a multi byte character
     2317                    // Reduce maxLength to split at start of character
     2318                    $maxLength -= $lookBack - $encodedCharPos;
     2319                    $foundSplitPos = true;
     2320                } elseif ($dec < 192) {
     2321                    // Middle byte of a multi byte character, look further back
     2322                    $lookBack += 3;
     2323                }
     2324            } else {
     2325                // No encoded character found
     2326                $foundSplitPos = true;
     2327            }
     2328        }
     2329
     2330        return $maxLength;
     2331    }
     2332
     2333    /**
     2334     * Apply word wrapping to the message body.
     2335     * Wraps the message body to the number of chars set in the WordWrap property.
     2336     * You should only do this to plain-text bodies as wrapping HTML tags may break them.
     2337     * This is called automatically by createBody(), so you don't need to call it yourself.
     2338     */
     2339    public function setWordWrap()
     2340    {
     2341        if ($this->WordWrap < 1) {
     2342            return;
     2343        }
     2344
     2345        switch ($this->message_type) {
     2346            case 'alt':
     2347            case 'alt_inline':
     2348            case 'alt_attach':
     2349            case 'alt_inline_attach':
     2350                $this->AltBody = $this->wrapText($this->AltBody, $this->WordWrap);
     2351                break;
     2352            default:
     2353                $this->Body = $this->wrapText($this->Body, $this->WordWrap);
     2354                break;
     2355        }
     2356    }
     2357
     2358    /**
     2359     * Assemble message headers.
     2360     *
     2361     * @return string The assembled headers
     2362     */
     2363    public function createHeader()
     2364    {
     2365        $result = '';
     2366
     2367        $result .= $this->headerLine('Date', '' === $this->MessageDate ? self::rfcDate() : $this->MessageDate);
     2368
     2369        // To be created automatically by mail()
     2370        if ($this->SingleTo) {
     2371            if ('mail' !== $this->Mailer) {
     2372                foreach ($this->to as $toaddr) {
     2373                    $this->SingleToArray[] = $this->addrFormat($toaddr);
     2374                }
     2375            }
     2376        } elseif (count($this->to) > 0) {
     2377            if ('mail' !== $this->Mailer) {
     2378                $result .= $this->addrAppend('To', $this->to);
     2379            }
     2380        } elseif (count($this->cc) === 0) {
     2381            $result .= $this->headerLine('To', 'undisclosed-recipients:;');
     2382        }
     2383
     2384        $result .= $this->addrAppend('From', [[trim($this->From), $this->FromName]]);
     2385
     2386        // sendmail and mail() extract Cc from the header before sending
     2387        if (count($this->cc) > 0) {
     2388            $result .= $this->addrAppend('Cc', $this->cc);
     2389        }
     2390
     2391        // sendmail and mail() extract Bcc from the header before sending
     2392        if ((
     2393                'sendmail' === $this->Mailer || 'qmail' === $this->Mailer || 'mail' === $this->Mailer
     2394            )
     2395            && count($this->bcc) > 0
     2396        ) {
     2397            $result .= $this->addrAppend('Bcc', $this->bcc);
     2398        }
     2399
     2400        if (count($this->ReplyTo) > 0) {
     2401            $result .= $this->addrAppend('Reply-To', $this->ReplyTo);
     2402        }
     2403
     2404        // mail() sets the subject itself
     2405        if ('mail' !== $this->Mailer) {
     2406            $result .= $this->headerLine('Subject', $this->encodeHeader($this->secureHeader($this->Subject)));
     2407        }
     2408
     2409        // Only allow a custom message ID if it conforms to RFC 5322 section 3.6.4
     2410        // https://tools.ietf.org/html/rfc5322#section-3.6.4
     2411        if ('' !== $this->MessageID && preg_match('/^<.*@.*>$/', $this->MessageID)) {
     2412            $this->lastMessageID = $this->MessageID;
     2413        } else {
     2414            $this->lastMessageID = sprintf('<%s@%s>', $this->uniqueid, $this->serverHostname());
     2415        }
     2416        $result .= $this->headerLine('Message-ID', $this->lastMessageID);
     2417        if (null !== $this->Priority) {
     2418            $result .= $this->headerLine('X-Priority', $this->Priority);
     2419        }
     2420        if ('' === $this->XMailer) {
     2421            $result .= $this->headerLine(
     2422                'X-Mailer',
     2423                'PHPMailer ' . self::VERSION . ' (https://github.com/PHPMailer/PHPMailer)'
     2424            );
     2425        } else {
     2426            $myXmailer = trim($this->XMailer);
     2427            if ($myXmailer) {
     2428                $result .= $this->headerLine('X-Mailer', $myXmailer);
     2429            }
     2430        }
     2431
     2432        if ('' !== $this->ConfirmReadingTo) {
     2433            $result .= $this->headerLine('Disposition-Notification-To', '<' . $this->ConfirmReadingTo . '>');
     2434        }
     2435
     2436        // Add custom headers
     2437        foreach ($this->CustomHeader as $header) {
     2438            $result .= $this->headerLine(
     2439                trim($header[0]),
     2440                $this->encodeHeader(trim($header[1]))
     2441            );
     2442        }
     2443        if (!$this->sign_key_file) {
     2444            $result .= $this->headerLine('MIME-Version', '1.0');
     2445            $result .= $this->getMailMIME();
     2446        }
     2447
     2448        return $result;
     2449    }
     2450
     2451    /**
     2452     * Get the message MIME type headers.
     2453     *
     2454     * @return string
     2455     */
     2456    public function getMailMIME()
     2457    {
     2458        $result = '';
     2459        $ismultipart = true;
     2460        switch ($this->message_type) {
     2461            case 'inline':
     2462                $result .= $this->headerLine('Content-Type', static::CONTENT_TYPE_MULTIPART_RELATED . ';');
     2463                $result .= $this->textLine(' boundary="' . $this->boundary[1] . '"');
     2464                break;
     2465            case 'attach':
     2466            case 'inline_attach':
     2467            case 'alt_attach':
     2468            case 'alt_inline_attach':
     2469                $result .= $this->headerLine('Content-Type', static::CONTENT_TYPE_MULTIPART_MIXED . ';');
     2470                $result .= $this->textLine(' boundary="' . $this->boundary[1] . '"');
     2471                break;
     2472            case 'alt':
     2473            case 'alt_inline':
     2474                $result .= $this->headerLine('Content-Type', static::CONTENT_TYPE_MULTIPART_ALTERNATIVE . ';');
     2475                $result .= $this->textLine(' boundary="' . $this->boundary[1] . '"');
     2476                break;
     2477            default:
     2478                // Catches case 'plain': and case '':
     2479                $result .= $this->textLine('Content-Type: ' . $this->ContentType . '; charset=' . $this->CharSet);
     2480                $ismultipart = false;
     2481                break;
     2482        }
     2483        // RFC1341 part 5 says 7bit is assumed if not specified
     2484        if (static::ENCODING_7BIT !== $this->Encoding) {
     2485            // RFC 2045 section 6.4 says multipart MIME parts may only use 7bit, 8bit or binary CTE
     2486            if ($ismultipart) {
     2487                if (static::ENCODING_8BIT === $this->Encoding) {
     2488                    $result .= $this->headerLine('Content-Transfer-Encoding', static::ENCODING_8BIT);
     2489                }
     2490                // The only remaining alternatives are quoted-printable and base64, which are both 7bit compatible
     2491            } else {
     2492                $result .= $this->headerLine('Content-Transfer-Encoding', $this->Encoding);
     2493            }
     2494        }
     2495
     2496        if ('mail' !== $this->Mailer) {
     2497//            $result .= static::$LE;
     2498        }
     2499
     2500        return $result;
     2501    }
     2502
     2503    /**
     2504     * Returns the whole MIME message.
     2505     * Includes complete headers and body.
     2506     * Only valid post preSend().
     2507     *
     2508     * @see PHPMailer::preSend()
     2509     *
     2510     * @return string
     2511     */
     2512    public function getSentMIMEMessage()
     2513    {
     2514        return rtrim($this->MIMEHeader . $this->mailHeader, "\n\r") . static::$LE . static::$LE . $this->MIMEBody;
     2515    }
     2516
     2517    /**
     2518     * Create a unique ID to use for boundaries.
     2519     *
     2520     * @return string
     2521     */
     2522    protected function generateId()
     2523    {
     2524        $len = 32; //32 bytes = 256 bits
     2525        $bytes = '';
     2526        if (function_exists('random_bytes')) {
     2527            try {
     2528                $bytes = random_bytes($len);
     2529            } catch (\Exception $e) {
     2530                //Do nothing
     2531            }
     2532        } elseif (function_exists('openssl_random_pseudo_bytes')) {
     2533            /** @noinspection CryptographicallySecureRandomnessInspection */
     2534            $bytes = openssl_random_pseudo_bytes($len);
     2535        }
     2536        if ($bytes === '') {
     2537            //We failed to produce a proper random string, so make do.
     2538            //Use a hash to force the length to the same as the other methods
     2539            $bytes = hash('sha256', uniqid((string) mt_rand(), true), true);
     2540        }
     2541
     2542        //We don't care about messing up base64 format here, just want a random string
     2543        return str_replace(['=', '+', '/'], '', base64_encode(hash('sha256', $bytes, true)));
     2544    }
     2545
     2546    /**
     2547     * Assemble the message body.
     2548     * Returns an empty string on failure.
     2549     *
     2550     * @throws Exception
     2551     *
     2552     * @return string The assembled message body
     2553     */
     2554    public function createBody()
     2555    {
     2556        $body = '';
     2557        //Create unique IDs and preset boundaries
     2558        $this->uniqueid = $this->generateId();
     2559        $this->boundary[1] = 'b1_' . $this->uniqueid;
     2560        $this->boundary[2] = 'b2_' . $this->uniqueid;
     2561        $this->boundary[3] = 'b3_' . $this->uniqueid;
     2562
     2563        if ($this->sign_key_file) {
     2564            $body .= $this->getMailMIME() . static::$LE;
     2565        }
     2566
     2567        $this->setWordWrap();
     2568
     2569        $bodyEncoding = $this->Encoding;
     2570        $bodyCharSet = $this->CharSet;
     2571        //Can we do a 7-bit downgrade?
     2572        if (static::ENCODING_8BIT === $bodyEncoding && !$this->has8bitChars($this->Body)) {
     2573            $bodyEncoding = static::ENCODING_7BIT;
     2574            //All ISO 8859, Windows codepage and UTF-8 charsets are ascii compatible up to 7-bit
     2575            $bodyCharSet = static::CHARSET_ASCII;
     2576        }
     2577        //If lines are too long, and we're not already using an encoding that will shorten them,
     2578        //change to quoted-printable transfer encoding for the body part only
     2579        if (static::ENCODING_BASE64 !== $this->Encoding && static::hasLineLongerThanMax($this->Body)) {
     2580            $bodyEncoding = static::ENCODING_QUOTED_PRINTABLE;
     2581        }
     2582
     2583        $altBodyEncoding = $this->Encoding;
     2584        $altBodyCharSet = $this->CharSet;
     2585        //Can we do a 7-bit downgrade?
     2586        if (static::ENCODING_8BIT === $altBodyEncoding && !$this->has8bitChars($this->AltBody)) {
     2587            $altBodyEncoding = static::ENCODING_7BIT;
     2588            //All ISO 8859, Windows codepage and UTF-8 charsets are ascii compatible up to 7-bit
     2589            $altBodyCharSet = static::CHARSET_ASCII;
     2590        }
     2591        //If lines are too long, and we're not already using an encoding that will shorten them,
     2592        //change to quoted-printable transfer encoding for the alt body part only
     2593        if (static::ENCODING_BASE64 !== $altBodyEncoding && static::hasLineLongerThanMax($this->AltBody)) {
     2594            $altBodyEncoding = static::ENCODING_QUOTED_PRINTABLE;
     2595        }
     2596        //Use this as a preamble in all multipart message types
     2597        $mimepre = 'This is a multi-part message in MIME format.' . static::$LE;
     2598        switch ($this->message_type) {
     2599            case 'inline':
     2600                $body .= $mimepre;
     2601                $body .= $this->getBoundary($this->boundary[1], $bodyCharSet, '', $bodyEncoding);
     2602                $body .= $this->encodeString($this->Body, $bodyEncoding);
     2603                $body .= static::$LE;
     2604                $body .= $this->attachAll('inline', $this->boundary[1]);
     2605                break;
     2606            case 'attach':
     2607                $body .= $mimepre;
     2608                $body .= $this->getBoundary($this->boundary[1], $bodyCharSet, '', $bodyEncoding);
     2609                $body .= $this->encodeString($this->Body, $bodyEncoding);
     2610                $body .= static::$LE;
     2611                $body .= $this->attachAll('attachment', $this->boundary[1]);
     2612                break;
     2613            case 'inline_attach':
     2614                $body .= $mimepre;
     2615                $body .= $this->textLine('--' . $this->boundary[1]);
     2616                $body .= $this->headerLine('Content-Type', static::CONTENT_TYPE_MULTIPART_RELATED . ';');
     2617                $body .= $this->textLine(' boundary="' . $this->boundary[2] . '";');
     2618                $body .= $this->textLine(' type="' . static::CONTENT_TYPE_TEXT_HTML . '"');
     2619                $body .= static::$LE;
     2620                $body .= $this->getBoundary($this->boundary[2], $bodyCharSet, '', $bodyEncoding);
     2621                $body .= $this->encodeString($this->Body, $bodyEncoding);
     2622                $body .= static::$LE;
     2623                $body .= $this->attachAll('inline', $this->boundary[2]);
     2624                $body .= static::$LE;
     2625                $body .= $this->attachAll('attachment', $this->boundary[1]);
     2626                break;
     2627            case 'alt':
     2628                $body .= $mimepre;
     2629                $body .= $this->getBoundary(
     2630                    $this->boundary[1],
     2631                    $altBodyCharSet,
     2632                    static::CONTENT_TYPE_PLAINTEXT,
     2633                    $altBodyEncoding
     2634                );
     2635                $body .= $this->encodeString($this->AltBody, $altBodyEncoding);
     2636                $body .= static::$LE;
     2637                $body .= $this->getBoundary(
     2638                    $this->boundary[1],
     2639                    $bodyCharSet,
     2640                    static::CONTENT_TYPE_TEXT_HTML,
     2641                    $bodyEncoding
     2642                );
     2643                $body .= $this->encodeString($this->Body, $bodyEncoding);
     2644                $body .= static::$LE;
     2645                if (!empty($this->Ical)) {
     2646                    $method = static::ICAL_METHOD_REQUEST;
     2647                    foreach (static::$IcalMethods as $imethod) {
     2648                        if (stripos($this->Ical, 'METHOD:' . $imethod) !== false) {
     2649                            $method = $imethod;
     2650                            break;
     2651                        }
     2652                    }
     2653                    $body .= $this->getBoundary(
     2654                        $this->boundary[1],
     2655                        '',
     2656                        static::CONTENT_TYPE_TEXT_CALENDAR . '; method=' . $method,
     2657                        ''
     2658                    );
     2659                    $body .= $this->encodeString($this->Ical, $this->Encoding);
     2660                    $body .= static::$LE;
     2661                }
     2662                $body .= $this->endBoundary($this->boundary[1]);
     2663                break;
     2664            case 'alt_inline':
     2665                $body .= $mimepre;
     2666                $body .= $this->getBoundary(
     2667                    $this->boundary[1],
     2668                    $altBodyCharSet,
     2669                    static::CONTENT_TYPE_PLAINTEXT,
     2670                    $altBodyEncoding
     2671                );
     2672                $body .= $this->encodeString($this->AltBody, $altBodyEncoding);
     2673                $body .= static::$LE;
     2674                $body .= $this->textLine('--' . $this->boundary[1]);
     2675                $body .= $this->headerLine('Content-Type', static::CONTENT_TYPE_MULTIPART_RELATED . ';');
     2676                $body .= $this->textLine(' boundary="' . $this->boundary[2] . '";');
     2677                $body .= $this->textLine(' type="' . static::CONTENT_TYPE_TEXT_HTML . '"');
     2678                $body .= static::$LE;
     2679                $body .= $this->getBoundary(
     2680                    $this->boundary[2],
     2681                    $bodyCharSet,
     2682                    static::CONTENT_TYPE_TEXT_HTML,
     2683                    $bodyEncoding
     2684                );
     2685                $body .= $this->encodeString($this->Body, $bodyEncoding);
     2686                $body .= static::$LE;
     2687                $body .= $this->attachAll('inline', $this->boundary[2]);
     2688                $body .= static::$LE;
     2689                $body .= $this->endBoundary($this->boundary[1]);
     2690                break;
     2691            case 'alt_attach':
     2692                $body .= $mimepre;
     2693                $body .= $this->textLine('--' . $this->boundary[1]);
     2694                $body .= $this->headerLine('Content-Type', static::CONTENT_TYPE_MULTIPART_ALTERNATIVE . ';');
     2695                $body .= $this->textLine(' boundary="' . $this->boundary[2] . '"');
     2696                $body .= static::$LE;
     2697                $body .= $this->getBoundary(
     2698                    $this->boundary[2],
     2699                    $altBodyCharSet,
     2700                    static::CONTENT_TYPE_PLAINTEXT,
     2701                    $altBodyEncoding
     2702                );
     2703                $body .= $this->encodeString($this->AltBody, $altBodyEncoding);
     2704                $body .= static::$LE;
     2705                $body .= $this->getBoundary(
     2706                    $this->boundary[2],
     2707                    $bodyCharSet,
     2708                    static::CONTENT_TYPE_TEXT_HTML,
     2709                    $bodyEncoding
     2710                );
     2711                $body .= $this->encodeString($this->Body, $bodyEncoding);
     2712                $body .= static::$LE;
     2713                if (!empty($this->Ical)) {
     2714                    $method = static::ICAL_METHOD_REQUEST;
     2715                    foreach (static::$IcalMethods as $imethod) {
     2716                        if (stripos($this->Ical, 'METHOD:' . $imethod) !== false) {
     2717                            $method = $imethod;
     2718                            break;
     2719                        }
     2720                    }
     2721                    $body .= $this->getBoundary(
     2722                        $this->boundary[2],
     2723                        '',
     2724                        static::CONTENT_TYPE_TEXT_CALENDAR . '; method=' . $method,
     2725                        ''
     2726                    );
     2727                    $body .= $this->encodeString($this->Ical, $this->Encoding);
     2728                }
     2729                $body .= $this->endBoundary($this->boundary[2]);
     2730                $body .= static::$LE;
     2731                $body .= $this->attachAll('attachment', $this->boundary[1]);
     2732                break;
     2733            case 'alt_inline_attach':
     2734                $body .= $mimepre;
     2735                $body .= $this->textLine('--' . $this->boundary[1]);
     2736                $body .= $this->headerLine('Content-Type', static::CONTENT_TYPE_MULTIPART_ALTERNATIVE . ';');
     2737                $body .= $this->textLine(' boundary="' . $this->boundary[2] . '"');
     2738                $body .= static::$LE;
     2739                $body .= $this->getBoundary(
     2740                    $this->boundary[2],
     2741                    $altBodyCharSet,
     2742                    static::CONTENT_TYPE_PLAINTEXT,
     2743                    $altBodyEncoding
     2744                );
     2745                $body .= $this->encodeString($this->AltBody, $altBodyEncoding);
     2746                $body .= static::$LE;
     2747                $body .= $this->textLine('--' . $this->boundary[2]);
     2748                $body .= $this->headerLine('Content-Type', static::CONTENT_TYPE_MULTIPART_RELATED . ';');
     2749                $body .= $this->textLine(' boundary="' . $this->boundary[3] . '";');
     2750                $body .= $this->textLine(' type="' . static::CONTENT_TYPE_TEXT_HTML . '"');
     2751                $body .= static::$LE;
     2752                $body .= $this->getBoundary(
     2753                    $this->boundary[3],
     2754                    $bodyCharSet,
     2755                    static::CONTENT_TYPE_TEXT_HTML,
     2756                    $bodyEncoding
     2757                );
     2758                $body .= $this->encodeString($this->Body, $bodyEncoding);
     2759                $body .= static::$LE;
     2760                $body .= $this->attachAll('inline', $this->boundary[3]);
     2761                $body .= static::$LE;
     2762                $body .= $this->endBoundary($this->boundary[2]);
     2763                $body .= static::$LE;
     2764                $body .= $this->attachAll('attachment', $this->boundary[1]);
     2765                break;
     2766            default:
     2767                // Catch case 'plain' and case '', applies to simple `text/plain` and `text/html` body content types
     2768                //Reset the `Encoding` property in case we changed it for line length reasons
     2769                $this->Encoding = $bodyEncoding;
     2770                $body .= $this->encodeString($this->Body, $this->Encoding);
     2771                break;
     2772        }
     2773
     2774        if ($this->isError()) {
     2775            $body = '';
     2776            if ($this->exceptions) {
     2777                throw new Exception($this->lang('empty_message'), self::STOP_CRITICAL);
     2778            }
     2779        } elseif ($this->sign_key_file) {
     2780            try {
     2781                if (!defined('PKCS7_TEXT')) {
     2782                    throw new Exception($this->lang('extension_missing') . 'openssl');
     2783                }
     2784
     2785                $file = tempnam(sys_get_temp_dir(), 'srcsign');
     2786                $signed = tempnam(sys_get_temp_dir(), 'mailsign');
     2787                file_put_contents($file, $body);
     2788
     2789                //Workaround for PHP bug https://bugs.php.net/bug.php?id=69197
     2790                if (empty($this->sign_extracerts_file)) {
     2791                    $sign = @openssl_pkcs7_sign(
     2792                        $file,
     2793                        $signed,
     2794                        'file://' . realpath($this->sign_cert_file),
     2795                        ['file://' . realpath($this->sign_key_file), $this->sign_key_pass],
     2796                        []
     2797                    );
     2798                } else {
     2799                    $sign = @openssl_pkcs7_sign(
     2800                        $file,
     2801                        $signed,
     2802                        'file://' . realpath($this->sign_cert_file),
     2803                        ['file://' . realpath($this->sign_key_file), $this->sign_key_pass],
     2804                        [],
     2805                        PKCS7_DETACHED,
     2806                        $this->sign_extracerts_file
     2807                    );
     2808                }
     2809
     2810                @unlink($file);
     2811                if ($sign) {
     2812                    $body = file_get_contents($signed);
     2813                    @unlink($signed);
     2814                    //The message returned by openssl contains both headers and body, so need to split them up
     2815                    $parts = explode("\n\n", $body, 2);
     2816                    $this->MIMEHeader .= $parts[0] . static::$LE . static::$LE;
     2817                    $body = $parts[1];
     2818                } else {
     2819                    @unlink($signed);
     2820                    throw new Exception($this->lang('signing') . openssl_error_string());
     2821                }
     2822            } catch (Exception $exc) {
     2823                $body = '';
     2824                if ($this->exceptions) {
     2825                    throw $exc;
     2826                }
     2827            }
     2828        }
     2829
     2830        return $body;
     2831    }
     2832
     2833    /**
     2834     * Return the start of a message boundary.
     2835     *
     2836     * @param string $boundary
     2837     * @param string $charSet
     2838     * @param string $contentType
     2839     * @param string $encoding
     2840     *
     2841     * @return string
     2842     */
     2843    protected function getBoundary($boundary, $charSet, $contentType, $encoding)
     2844    {
     2845        $result = '';
     2846        if ('' === $charSet) {
     2847            $charSet = $this->CharSet;
     2848        }
     2849        if ('' === $contentType) {
     2850            $contentType = $this->ContentType;
     2851        }
     2852        if ('' === $encoding) {
     2853            $encoding = $this->Encoding;
     2854        }
     2855        $result .= $this->textLine('--' . $boundary);
     2856        $result .= sprintf('Content-Type: %s; charset=%s', $contentType, $charSet);
     2857        $result .= static::$LE;
     2858        // RFC1341 part 5 says 7bit is assumed if not specified
     2859        if (static::ENCODING_7BIT !== $encoding) {
     2860            $result .= $this->headerLine('Content-Transfer-Encoding', $encoding);
     2861        }
     2862        $result .= static::$LE;
     2863
     2864        return $result;
     2865    }
     2866
     2867    /**
     2868     * Return the end of a message boundary.
     2869     *
     2870     * @param string $boundary
     2871     *
     2872     * @return string
     2873     */
     2874    protected function endBoundary($boundary)
     2875    {
     2876        return static::$LE . '--' . $boundary . '--' . static::$LE;
     2877    }
     2878
     2879    /**
     2880     * Set the message type.
     2881     * PHPMailer only supports some preset message types, not arbitrary MIME structures.
     2882     */
     2883    protected function setMessageType()
     2884    {
     2885        $type = [];
     2886        if ($this->alternativeExists()) {
     2887            $type[] = 'alt';
     2888        }
     2889        if ($this->inlineImageExists()) {
     2890            $type[] = 'inline';
     2891        }
     2892        if ($this->attachmentExists()) {
     2893            $type[] = 'attach';
     2894        }
     2895        $this->message_type = implode('_', $type);
     2896        if ('' === $this->message_type) {
     2897            //The 'plain' message_type refers to the message having a single body element, not that it is plain-text
     2898            $this->message_type = 'plain';
     2899        }
     2900    }
     2901
     2902    /**
     2903     * Format a header line.
     2904     *
     2905     * @param string     $name
     2906     * @param string|int $value
     2907     *
     2908     * @return string
     2909     */
     2910    public function headerLine($name, $value)
     2911    {
     2912        return $name . ': ' . $value . static::$LE;
     2913    }
     2914
     2915    /**
     2916     * Return a formatted mail line.
     2917     *
     2918     * @param string $value
     2919     *
     2920     * @return string
     2921     */
     2922    public function textLine($value)
     2923    {
     2924        return $value . static::$LE;
     2925    }
     2926
     2927    /**
     2928     * Add an attachment from a path on the filesystem.
     2929     * Never use a user-supplied path to a file!
     2930     * Returns false if the file could not be found or read.
     2931     * Explicitly *does not* support passing URLs; PHPMailer is not an HTTP client.
     2932     * If you need to do that, fetch the resource yourself and pass it in via a local file or string.
     2933     *
     2934     * @param string $path        Path to the attachment
     2935     * @param string $name        Overrides the attachment name
     2936     * @param string $encoding    File encoding (see $Encoding)
     2937     * @param string $type        File extension (MIME) type
     2938     * @param string $disposition Disposition to use
     2939     *
     2940     * @throws Exception
     2941     *
     2942     * @return bool
     2943     */
     2944    public function addAttachment(
     2945        $path,
     2946        $name = '',
     2947        $encoding = self::ENCODING_BASE64,
     2948        $type = '',
     2949        $disposition = 'attachment'
     2950    ) {
     2951        try {
     2952            if (!static::isPermittedPath($path) || !@is_file($path)) {
     2953                throw new Exception($this->lang('file_access') . $path, self::STOP_CONTINUE);
     2954            }
     2955
     2956            // If a MIME type is not specified, try to work it out from the file name
     2957            if ('' === $type) {
     2958                $type = static::filenameToType($path);
     2959            }
     2960
     2961            $filename = (string) static::mb_pathinfo($path, PATHINFO_BASENAME);
     2962            if ('' === $name) {
     2963                $name = $filename;
     2964            }
     2965
     2966            if (!$this->validateEncoding($encoding)) {
     2967                throw new Exception($this->lang('encoding') . $encoding);
     2968            }
     2969
     2970            $this->attachment[] = [
     2971                0 => $path,
     2972                1 => $filename,
     2973                2 => $name,
     2974                3 => $encoding,
     2975                4 => $type,
     2976                5 => false, // isStringAttachment
     2977                6 => $disposition,
     2978                7 => $name,
     2979            ];
     2980        } catch (Exception $exc) {
     2981            $this->setError($exc->getMessage());
     2982            $this->edebug($exc->getMessage());
     2983            if ($this->exceptions) {
     2984                throw $exc;
     2985            }
     2986
     2987            return false;
     2988        }
     2989
     2990        return true;
     2991    }
     2992
     2993    /**
     2994     * Return the array of attachments.
     2995     *
     2996     * @return array
     2997     */
     2998    public function getAttachments()
     2999    {
     3000        return $this->attachment;
     3001    }
     3002
     3003    /**
     3004     * Attach all file, string, and binary attachments to the message.
     3005     * Returns an empty string on failure.
     3006     *
     3007     * @param string $disposition_type
     3008     * @param string $boundary
     3009     *
     3010     * @throws Exception
     3011     *
     3012     * @return string
     3013     */
     3014    protected function attachAll($disposition_type, $boundary)
     3015    {
     3016        // Return text of body
     3017        $mime = [];
     3018        $cidUniq = [];
     3019        $incl = [];
     3020
     3021        // Add all attachments
     3022        foreach ($this->attachment as $attachment) {
     3023            // Check if it is a valid disposition_filter
     3024            if ($attachment[6] === $disposition_type) {
     3025                // Check for string attachment
     3026                $string = '';
     3027                $path = '';
     3028                $bString = $attachment[5];
     3029                if ($bString) {
     3030                    $string = $attachment[0];
     3031                } else {
     3032                    $path = $attachment[0];
     3033                }
     3034
     3035                $inclhash = hash('sha256', serialize($attachment));
     3036                if (in_array($inclhash, $incl, true)) {
     3037                    continue;
     3038                }
     3039                $incl[] = $inclhash;
     3040                $name = $attachment[2];
     3041                $encoding = $attachment[3];
     3042                $type = $attachment[4];
     3043                $disposition = $attachment[6];
     3044                $cid = $attachment[7];
     3045                if ('inline' === $disposition && array_key_exists($cid, $cidUniq)) {
     3046                    continue;
     3047                }
     3048                $cidUniq[$cid] = true;
     3049
     3050                $mime[] = sprintf('--%s%s', $boundary, static::$LE);
     3051                //Only include a filename property if we have one
     3052                if (!empty($name)) {
     3053                    $mime[] = sprintf(
     3054                        'Content-Type: %s; name="%s"%s',
     3055                        $type,
     3056                        $this->encodeHeader($this->secureHeader($name)),
     3057                        static::$LE
     3058                    );
     3059                } else {
     3060                    $mime[] = sprintf(
     3061                        'Content-Type: %s%s',
     3062                        $type,
     3063                        static::$LE
     3064                    );
     3065                }
     3066                // RFC1341 part 5 says 7bit is assumed if not specified
     3067                if (static::ENCODING_7BIT !== $encoding) {
     3068                    $mime[] = sprintf('Content-Transfer-Encoding: %s%s', $encoding, static::$LE);
     3069                }
     3070
     3071                //Only set Content-IDs on inline attachments
     3072                if ((string) $cid !== '' && $disposition === 'inline') {
     3073                    $mime[] = 'Content-ID: <' . $this->encodeHeader($this->secureHeader($cid)) . '>' . static::$LE;
     3074                }
     3075
     3076                // If a filename contains any of these chars, it should be quoted,
     3077                // but not otherwise: RFC2183 & RFC2045 5.1
     3078                // Fixes a warning in IETF's msglint MIME checker
     3079                // Allow for bypassing the Content-Disposition header totally
     3080                if (!empty($disposition)) {
     3081                    $encoded_name = $this->encodeHeader($this->secureHeader($name));
     3082                    if (preg_match('/[ ()<>@,;:"\/\[\]?=]/', $encoded_name)) {
     3083                        $mime[] = sprintf(
     3084                            'Content-Disposition: %s; filename="%s"%s',
     3085                            $disposition,
     3086                            $encoded_name,
     3087                            static::$LE . static::$LE
     3088                        );
     3089                    } elseif (!empty($encoded_name)) {
     3090                        $mime[] = sprintf(
     3091                            'Content-Disposition: %s; filename=%s%s',
     3092                            $disposition,
     3093                            $encoded_name,
     3094                            static::$LE . static::$LE
     3095                        );
     3096                    } else {
     3097                        $mime[] = sprintf(
     3098                            'Content-Disposition: %s%s',
     3099                            $disposition,
     3100                            static::$LE . static::$LE
     3101                        );
     3102                    }
     3103                } else {
     3104                    $mime[] = static::$LE;
     3105                }
     3106
     3107                // Encode as string attachment
     3108                if ($bString) {
     3109                    $mime[] = $this->encodeString($string, $encoding);
     3110                } else {
     3111                    $mime[] = $this->encodeFile($path, $encoding);
     3112                }
     3113                if ($this->isError()) {
     3114                    return '';
     3115                }
     3116                $mime[] = static::$LE;
     3117            }
     3118        }
     3119
     3120        $mime[] = sprintf('--%s--%s', $boundary, static::$LE);
     3121
     3122        return implode('', $mime);
     3123    }
     3124
     3125    /**
     3126     * Encode a file attachment in requested format.
     3127     * Returns an empty string on failure.
     3128     *
     3129     * @param string $path     The full path to the file
     3130     * @param string $encoding The encoding to use; one of 'base64', '7bit', '8bit', 'binary', 'quoted-printable'
     3131     *
     3132     * @return string
     3133     */
     3134    protected function encodeFile($path, $encoding = self::ENCODING_BASE64)
     3135    {
     3136        try {
     3137            if (!static::isPermittedPath($path) || !file_exists($path)) {
     3138                throw new Exception($this->lang('file_open') . $path, self::STOP_CONTINUE);
     3139            }
     3140            $file_buffer = file_get_contents($path);
     3141            if (false === $file_buffer) {
     3142                throw new Exception($this->lang('file_open') . $path, self::STOP_CONTINUE);
     3143            }
     3144            $file_buffer = $this->encodeString($file_buffer, $encoding);
     3145
     3146            return $file_buffer;
     3147        } catch (Exception $exc) {
     3148            $this->setError($exc->getMessage());
     3149
     3150            return '';
     3151        }
     3152    }
     3153
     3154    /**
     3155     * Encode a string in requested format.
     3156     * Returns an empty string on failure.
     3157     *
     3158     * @param string $str      The text to encode
     3159     * @param string $encoding The encoding to use; one of 'base64', '7bit', '8bit', 'binary', 'quoted-printable'
     3160     *
     3161     * @throws Exception
     3162     *
     3163     * @return string
     3164     */
     3165    public function encodeString($str, $encoding = self::ENCODING_BASE64)
     3166    {
     3167        $encoded = '';
     3168        switch (strtolower($encoding)) {
     3169            case static::ENCODING_BASE64:
     3170                $encoded = chunk_split(
     3171                    base64_encode($str),
     3172                    static::STD_LINE_LENGTH,
     3173                    static::$LE
     3174                );
     3175                break;
     3176            case static::ENCODING_7BIT:
     3177            case static::ENCODING_8BIT:
     3178                $encoded = static::normalizeBreaks($str);
     3179                // Make sure it ends with a line break
     3180                if (substr($encoded, -(strlen(static::$LE))) !== static::$LE) {
     3181                    $encoded .= static::$LE;
     3182                }
     3183                break;
     3184            case static::ENCODING_BINARY:
     3185                $encoded = $str;
     3186                break;
     3187            case static::ENCODING_QUOTED_PRINTABLE:
     3188                $encoded = $this->encodeQP($str);
     3189                break;
     3190            default:
     3191                $this->setError($this->lang('encoding') . $encoding);
     3192                if ($this->exceptions) {
     3193                    throw new Exception($this->lang('encoding') . $encoding);
     3194                }
     3195                break;
     3196        }
     3197
     3198        return $encoded;
     3199    }
     3200
     3201    /**
     3202     * Encode a header value (not including its label) optimally.
     3203     * Picks shortest of Q, B, or none. Result includes folding if needed.
     3204     * See RFC822 definitions for phrase, comment and text positions.
     3205     *
     3206     * @param string $str      The header value to encode
     3207     * @param string $position What context the string will be used in
     3208     *
     3209     * @return string
     3210     */
     3211    public function encodeHeader($str, $position = 'text')
     3212    {
     3213        $matchcount = 0;
     3214        switch (strtolower($position)) {
     3215            case 'phrase':
     3216                if (!preg_match('/[\200-\377]/', $str)) {
     3217                    // Can't use addslashes as we don't know the value of magic_quotes_sybase
     3218                    $encoded = addcslashes($str, "\0..\37\177\\\"");
     3219                    if (($str === $encoded) && !preg_match('/[^A-Za-z0-9!#$%&\'*+\/=?^_`{|}~ -]/', $str)) {
     3220                        return $encoded;
     3221                    }
     3222
     3223                    return "\"$encoded\"";
     3224                }
     3225                $matchcount = preg_match_all('/[^\040\041\043-\133\135-\176]/', $str, $matches);
     3226                break;
     3227            /* @noinspection PhpMissingBreakStatementInspection */
     3228            case 'comment':
     3229                $matchcount = preg_match_all('/[()"]/', $str, $matches);
     3230            //fallthrough
     3231            case 'text':
     3232            default:
     3233                $matchcount += preg_match_all('/[\000-\010\013\014\016-\037\177-\377]/', $str, $matches);
     3234                break;
     3235        }
     3236
     3237        if ($this->has8bitChars($str)) {
     3238            $charset = $this->CharSet;
     3239        } else {
     3240            $charset = static::CHARSET_ASCII;
     3241        }
     3242
     3243        // Q/B encoding adds 8 chars and the charset ("` =?<charset>?[QB]?<content>?=`").
     3244        $overhead = 8 + strlen($charset);
     3245
     3246        if ('mail' === $this->Mailer) {
     3247            $maxlen = static::MAIL_MAX_LINE_LENGTH - $overhead;
     3248        } else {
     3249            $maxlen = static::MAX_LINE_LENGTH - $overhead;
     3250        }
     3251
     3252        // Select the encoding that produces the shortest output and/or prevents corruption.
     3253        if ($matchcount > strlen($str) / 3) {
     3254            // More than 1/3 of the content needs encoding, use B-encode.
     3255            $encoding = 'B';
     3256        } elseif ($matchcount > 0) {
     3257            // Less than 1/3 of the content needs encoding, use Q-encode.
     3258            $encoding = 'Q';
     3259        } elseif (strlen($str) > $maxlen) {
     3260            // No encoding needed, but value exceeds max line length, use Q-encode to prevent corruption.
     3261            $encoding = 'Q';
     3262        } else {
     3263            // No reformatting needed
     3264            $encoding = false;
     3265        }
     3266
     3267        switch ($encoding) {
     3268            case 'B':
     3269                if ($this->hasMultiBytes($str)) {
     3270                    // Use a custom function which correctly encodes and wraps long
     3271                    // multibyte strings without breaking lines within a character
     3272                    $encoded = $this->base64EncodeWrapMB($str, "\n");
     3273                } else {
     3274                    $encoded = base64_encode($str);
     3275                    $maxlen -= $maxlen % 4;
     3276                    $encoded = trim(chunk_split($encoded, $maxlen, "\n"));
     3277                }
     3278                $encoded = preg_replace('/^(.*)$/m', ' =?' . $charset . "?$encoding?\\1?=", $encoded);
     3279                break;
     3280            case 'Q':
     3281                $encoded = $this->encodeQ($str, $position);
     3282                $encoded = $this->wrapText($encoded, $maxlen, true);
     3283                $encoded = str_replace('=' . static::$LE, "\n", trim($encoded));
     3284                $encoded = preg_replace('/^(.*)$/m', ' =?' . $charset . "?$encoding?\\1?=", $encoded);
     3285                break;
     3286            default:
     3287                return $str;
     3288        }
     3289
     3290        return trim(static::normalizeBreaks($encoded));
     3291    }
     3292
     3293    /**
     3294     * Check if a string contains multi-byte characters.
     3295     *
     3296     * @param string $str multi-byte text to wrap encode
     3297     *
     3298     * @return bool
     3299     */
     3300    public function hasMultiBytes($str)
     3301    {
     3302        if (function_exists('mb_strlen')) {
     3303            return strlen($str) > mb_strlen($str, $this->CharSet);
     3304        }
     3305
     3306        // Assume no multibytes (we can't handle without mbstring functions anyway)
     3307        return false;
     3308    }
     3309
     3310    /**
     3311     * Does a string contain any 8-bit chars (in any charset)?
     3312     *
     3313     * @param string $text
     3314     *
     3315     * @return bool
     3316     */
     3317    public function has8bitChars($text)
     3318    {
     3319        return (bool) preg_match('/[\x80-\xFF]/', $text);
     3320    }
     3321
     3322    /**
     3323     * Encode and wrap long multibyte strings for mail headers
     3324     * without breaking lines within a character.
     3325     * Adapted from a function by paravoid.
     3326     *
     3327     * @see http://www.php.net/manual/en/function.mb-encode-mimeheader.php#60283
     3328     *
     3329     * @param string $str       multi-byte text to wrap encode
     3330     * @param string $linebreak string to use as linefeed/end-of-line
     3331     *
     3332     * @return string
     3333     */
     3334    public function base64EncodeWrapMB($str, $linebreak = null)
     3335    {
     3336        $start = '=?' . $this->CharSet . '?B?';
     3337        $end = '?=';
     3338        $encoded = '';
     3339        if (null === $linebreak) {
     3340            $linebreak = static::$LE;
     3341        }
     3342
     3343        $mb_length = mb_strlen($str, $this->CharSet);
     3344        // Each line must have length <= 75, including $start and $end
     3345        $length = 75 - strlen($start) - strlen($end);
     3346        // Average multi-byte ratio
     3347        $ratio = $mb_length / strlen($str);
     3348        // Base64 has a 4:3 ratio
     3349        $avgLength = floor($length * $ratio * .75);
     3350
     3351        $offset = 0;
     3352        for ($i = 0; $i < $mb_length; $i += $offset) {
     3353            $lookBack = 0;
     3354            do {
     3355                $offset = $avgLength - $lookBack;
     3356                $chunk = mb_substr($str, $i, $offset, $this->CharSet);
     3357                $chunk = base64_encode($chunk);
     3358                ++$lookBack;
     3359            } while (strlen($chunk) > $length);
     3360            $encoded .= $chunk . $linebreak;
     3361        }
     3362
     3363        // Chomp the last linefeed
     3364        return substr($encoded, 0, -strlen($linebreak));
     3365    }
     3366
     3367    /**
     3368     * Encode a string in quoted-printable format.
     3369     * According to RFC2045 section 6.7.
     3370     *
     3371     * @param string $string The text to encode
     3372     *
     3373     * @return string
     3374     */
     3375    public function encodeQP($string)
     3376    {
     3377        return static::normalizeBreaks(quoted_printable_encode($string));
     3378    }
     3379
     3380    /**
     3381     * Encode a string using Q encoding.
     3382     *
     3383     * @see http://tools.ietf.org/html/rfc2047#section-4.2
     3384     *
     3385     * @param string $str      the text to encode
     3386     * @param string $position Where the text is going to be used, see the RFC for what that means
     3387     *
     3388     * @return string
     3389     */
     3390    public function encodeQ($str, $position = 'text')
     3391    {
     3392        // There should not be any EOL in the string
     3393        $pattern = '';
     3394        $encoded = str_replace(["\r", "\n"], '', $str);
     3395        switch (strtolower($position)) {
     3396            case 'phrase':
     3397                // RFC 2047 section 5.3
     3398                $pattern = '^A-Za-z0-9!*+\/ -';
     3399                break;
     3400            /*
     3401             * RFC 2047 section 5.2.
     3402             * Build $pattern without including delimiters and []
     3403             */
     3404            /* @noinspection PhpMissingBreakStatementInspection */
     3405            case 'comment':
     3406                $pattern = '\(\)"';
     3407            /* Intentional fall through */
     3408            case 'text':
     3409            default:
     3410                // RFC 2047 section 5.1
     3411                // Replace every high ascii, control, =, ? and _ characters
     3412                $pattern = '\000-\011\013\014\016-\037\075\077\137\177-\377' . $pattern;
     3413                break;
     3414        }
     3415        $matches = [];
     3416        if (preg_match_all("/[{$pattern}]/", $encoded, $matches)) {
     3417            // If the string contains an '=', make sure it's the first thing we replace
     3418            // so as to avoid double-encoding
     3419            $eqkey = array_search('=', $matches[0], true);
     3420            if (false !== $eqkey) {
     3421                unset($matches[0][$eqkey]);
     3422                array_unshift($matches[0], '=');
     3423            }
     3424            foreach (array_unique($matches[0]) as $char) {
     3425                $encoded = str_replace($char, '=' . sprintf('%02X', ord($char)), $encoded);
     3426            }
     3427        }
     3428        // Replace spaces with _ (more readable than =20)
     3429        // RFC 2047 section 4.2(2)
     3430        return str_replace(' ', '_', $encoded);
     3431    }
     3432
     3433    /**
     3434     * Add a string or binary attachment (non-filesystem).
     3435     * This method can be used to attach ascii or binary data,
     3436     * such as a BLOB record from a database.
     3437     *
     3438     * @param string $string      String attachment data
     3439     * @param string $filename    Name of the attachment
     3440     * @param string $encoding    File encoding (see $Encoding)
     3441     * @param string $type        File extension (MIME) type
     3442     * @param string $disposition Disposition to use
     3443     *
     3444     * @throws Exception
     3445     *
     3446     * @return bool True on successfully adding an attachment
     3447     */
     3448    public function addStringAttachment(
     3449        $string,
     3450        $filename,
     3451        $encoding = self::ENCODING_BASE64,
     3452        $type = '',
     3453        $disposition = 'attachment'
     3454    ) {
     3455        try {
     3456            // If a MIME type is not specified, try to work it out from the file name
     3457            if ('' === $type) {
     3458                $type = static::filenameToType($filename);
     3459            }
     3460
     3461            if (!$this->validateEncoding($encoding)) {
     3462                throw new Exception($this->lang('encoding') . $encoding);
     3463            }
     3464
     3465            // Append to $attachment array
     3466            $this->attachment[] = [
     3467                0 => $string,
     3468                1 => $filename,
     3469                2 => static::mb_pathinfo($filename, PATHINFO_BASENAME),
     3470                3 => $encoding,
     3471                4 => $type,
     3472                5 => true, // isStringAttachment
     3473                6 => $disposition,
     3474                7 => 0,
     3475            ];
     3476        } catch (Exception $exc) {
     3477            $this->setError($exc->getMessage());
     3478            $this->edebug($exc->getMessage());
     3479            if ($this->exceptions) {
     3480                throw $exc;
     3481            }
     3482
     3483            return false;
     3484        }
     3485
     3486        return true;
     3487    }
     3488
     3489    /**
     3490     * Add an embedded (inline) attachment from a file.
     3491     * This can include images, sounds, and just about any other document type.
     3492     * These differ from 'regular' attachments in that they are intended to be
     3493     * displayed inline with the message, not just attached for download.
     3494     * This is used in HTML messages that embed the images
     3495     * the HTML refers to using the $cid value.
     3496     * Never use a user-supplied path to a file!
     3497     *
     3498     * @param string $path        Path to the attachment
     3499     * @param string $cid         Content ID of the attachment; Use this to reference
     3500     *                            the content when using an embedded image in HTML
     3501     * @param string $name        Overrides the attachment name
     3502     * @param string $encoding    File encoding (see $Encoding)
     3503     * @param string $type        File MIME type
     3504     * @param string $disposition Disposition to use
     3505     *
     3506     * @throws Exception
     3507     *
     3508     * @return bool True on successfully adding an attachment
     3509     */
     3510    public function addEmbeddedImage(
     3511        $path,
     3512        $cid,
     3513        $name = '',
     3514        $encoding = self::ENCODING_BASE64,
     3515        $type = '',
     3516        $disposition = 'inline'
     3517    ) {
     3518        try {
     3519            if (!static::isPermittedPath($path) || !@is_file($path)) {
     3520                throw new Exception($this->lang('file_access') . $path, self::STOP_CONTINUE);
     3521            }
     3522
     3523            // If a MIME type is not specified, try to work it out from the file name
     3524            if ('' === $type) {
     3525                $type = static::filenameToType($path);
     3526            }
     3527
     3528            if (!$this->validateEncoding($encoding)) {
     3529                throw new Exception($this->lang('encoding') . $encoding);
     3530            }
     3531
     3532            $filename = (string) static::mb_pathinfo($path, PATHINFO_BASENAME);
     3533            if ('' === $name) {
     3534                $name = $filename;
     3535            }
     3536
     3537            // Append to $attachment array
     3538            $this->attachment[] = [
     3539                0 => $path,
     3540                1 => $filename,
     3541                2 => $name,
     3542                3 => $encoding,
     3543                4 => $type,
     3544                5 => false, // isStringAttachment
     3545                6 => $disposition,
     3546                7 => $cid,
     3547            ];
     3548        } catch (Exception $exc) {
     3549            $this->setError($exc->getMessage());
     3550            $this->edebug($exc->getMessage());
     3551            if ($this->exceptions) {
     3552                throw $exc;
     3553            }
     3554
     3555            return false;
     3556        }
     3557
     3558        return true;
     3559    }
     3560
     3561    /**
     3562     * Add an embedded stringified attachment.
     3563     * This can include images, sounds, and just about any other document type.
     3564     * If your filename doesn't contain an extension, be sure to set the $type to an appropriate MIME type.
     3565     *
     3566     * @param string $string      The attachment binary data
     3567     * @param string $cid         Content ID of the attachment; Use this to reference
     3568     *                            the content when using an embedded image in HTML
     3569     * @param string $name        A filename for the attachment. If this contains an extension,
     3570     *                            PHPMailer will attempt to set a MIME type for the attachment.
     3571     *                            For example 'file.jpg' would get an 'image/jpeg' MIME type.
     3572     * @param string $encoding    File encoding (see $Encoding), defaults to 'base64'
     3573     * @param string $type        MIME type - will be used in preference to any automatically derived type
     3574     * @param string $disposition Disposition to use
     3575     *
     3576     * @throws Exception
     3577     *
     3578     * @return bool True on successfully adding an attachment
     3579     */
     3580    public function addStringEmbeddedImage(
     3581        $string,
     3582        $cid,
     3583        $name = '',
     3584        $encoding = self::ENCODING_BASE64,
     3585        $type = '',
     3586        $disposition = 'inline'
     3587    ) {
     3588        try {
     3589            // If a MIME type is not specified, try to work it out from the name
     3590            if ('' === $type && !empty($name)) {
     3591                $type = static::filenameToType($name);
     3592            }
     3593
     3594            if (!$this->validateEncoding($encoding)) {
     3595                throw new Exception($this->lang('encoding') . $encoding);
     3596            }
     3597
     3598            // Append to $attachment array
     3599            $this->attachment[] = [
     3600                0 => $string,
     3601                1 => $name,
     3602                2 => $name,
     3603                3 => $encoding,
     3604                4 => $type,
     3605                5 => true, // isStringAttachment
     3606                6 => $disposition,
     3607                7 => $cid,
     3608            ];
     3609        } catch (Exception $exc) {
     3610            $this->setError($exc->getMessage());
     3611            $this->edebug($exc->getMessage());
     3612            if ($this->exceptions) {
     3613                throw $exc;
     3614            }
     3615
     3616            return false;
     3617        }
     3618
     3619        return true;
     3620    }
     3621
     3622    /**
     3623     * Validate encodings.
     3624     *
     3625     * @param string $encoding
     3626     *
     3627     * @return bool
     3628     */
     3629    protected function validateEncoding($encoding)
     3630    {
     3631        return in_array(
     3632            $encoding,
     3633            [
     3634                self::ENCODING_7BIT,
     3635                self::ENCODING_QUOTED_PRINTABLE,
     3636                self::ENCODING_BASE64,
     3637                self::ENCODING_8BIT,
     3638                self::ENCODING_BINARY,
     3639            ],
     3640            true
     3641        );
     3642    }
     3643
     3644    /**
     3645     * Check if an embedded attachment is present with this cid.
     3646     *
     3647     * @param string $cid
     3648     *
     3649     * @return bool
     3650     */
     3651    protected function cidExists($cid)
     3652    {
     3653        foreach ($this->attachment as $attachment) {
     3654            if ('inline' === $attachment[6] && $cid === $attachment[7]) {
     3655                return true;
     3656            }
     3657        }
     3658
     3659        return false;
     3660    }
     3661
     3662    /**
     3663     * Check if an inline attachment is present.
     3664     *
     3665     * @return bool
     3666     */
     3667    public function inlineImageExists()
     3668    {
     3669        foreach ($this->attachment as $attachment) {
     3670            if ('inline' === $attachment[6]) {
     3671                return true;
     3672            }
     3673        }
     3674
     3675        return false;
     3676    }
     3677
     3678    /**
     3679     * Check if an attachment (non-inline) is present.
     3680     *
     3681     * @return bool
     3682     */
     3683    public function attachmentExists()
     3684    {
     3685        foreach ($this->attachment as $attachment) {
     3686            if ('attachment' === $attachment[6]) {
     3687                return true;
     3688            }
     3689        }
     3690
     3691        return false;
     3692    }
     3693
     3694    /**
     3695     * Check if this message has an alternative body set.
     3696     *
     3697     * @return bool
     3698     */
     3699    public function alternativeExists()
     3700    {
     3701        return !empty($this->AltBody);
     3702    }
     3703
     3704    /**
     3705     * Clear queued addresses of given kind.
     3706     *
     3707     * @param string $kind 'to', 'cc', or 'bcc'
     3708     */
     3709    public function clearQueuedAddresses($kind)
     3710    {
     3711        $this->RecipientsQueue = array_filter(
     3712            $this->RecipientsQueue,
     3713            static function ($params) use ($kind) {
     3714                return $params[0] !== $kind;
     3715            }
     3716        );
     3717    }
     3718
     3719    /**
     3720     * Clear all To recipients.
     3721     */
     3722    public function clearAddresses()
     3723    {
     3724        foreach ($this->to as $to) {
     3725            unset($this->all_recipients[strtolower($to[0])]);
     3726        }
     3727        $this->to = [];
     3728        $this->clearQueuedAddresses('to');
     3729    }
     3730
     3731    /**
     3732     * Clear all CC recipients.
     3733     */
     3734    public function clearCCs()
     3735    {
     3736        foreach ($this->cc as $cc) {
     3737            unset($this->all_recipients[strtolower($cc[0])]);
     3738        }
     3739        $this->cc = [];
     3740        $this->clearQueuedAddresses('cc');
     3741    }
     3742
     3743    /**
     3744     * Clear all BCC recipients.
     3745     */
     3746    public function clearBCCs()
     3747    {
     3748        foreach ($this->bcc as $bcc) {
     3749            unset($this->all_recipients[strtolower($bcc[0])]);
     3750        }
     3751        $this->bcc = [];
     3752        $this->clearQueuedAddresses('bcc');
     3753    }
     3754
     3755    /**
     3756     * Clear all ReplyTo recipients.
     3757     */
     3758    public function clearReplyTos()
     3759    {
     3760        $this->ReplyTo = [];
     3761        $this->ReplyToQueue = [];
     3762    }
     3763
     3764    /**
     3765     * Clear all recipient types.
     3766     */
     3767    public function clearAllRecipients()
     3768    {
     3769        $this->to = [];
     3770        $this->cc = [];
     3771        $this->bcc = [];
     3772        $this->all_recipients = [];
     3773        $this->RecipientsQueue = [];
     3774    }
     3775
     3776    /**
     3777     * Clear all filesystem, string, and binary attachments.
     3778     */
     3779    public function clearAttachments()
     3780    {
     3781        $this->attachment = [];
     3782    }
     3783
     3784    /**
     3785     * Clear all custom headers.
     3786     */
     3787    public function clearCustomHeaders()
     3788    {
     3789        $this->CustomHeader = [];
     3790    }
     3791
     3792    /**
     3793     * Add an error message to the error container.
     3794     *
     3795     * @param string $msg
     3796     */
     3797    protected function setError($msg)
     3798    {
     3799        ++$this->error_count;
     3800        if ('smtp' === $this->Mailer && null !== $this->smtp) {
     3801            $lasterror = $this->smtp->getError();
     3802            if (!empty($lasterror['error'])) {
     3803                $msg .= $this->lang('smtp_error') . $lasterror['error'];
     3804                if (!empty($lasterror['detail'])) {
     3805                    $msg .= ' Detail: ' . $lasterror['detail'];
     3806                }
     3807                if (!empty($lasterror['smtp_code'])) {
     3808                    $msg .= ' SMTP code: ' . $lasterror['smtp_code'];
     3809                }
     3810                if (!empty($lasterror['smtp_code_ex'])) {
     3811                    $msg .= ' Additional SMTP info: ' . $lasterror['smtp_code_ex'];
     3812                }
     3813            }
     3814        }
     3815        $this->ErrorInfo = $msg;
     3816    }
     3817
     3818    /**
     3819     * Return an RFC 822 formatted date.
     3820     *
     3821     * @return string
     3822     */
     3823    public static function rfcDate()
     3824    {
     3825        // Set the time zone to whatever the default is to avoid 500 errors
     3826        // Will default to UTC if it's not set properly in php.ini
     3827        date_default_timezone_set(@date_default_timezone_get());
     3828
     3829        return date('D, j M Y H:i:s O');
     3830    }
     3831
     3832    /**
     3833     * Get the server hostname.
     3834     * Returns 'localhost.localdomain' if unknown.
     3835     *
     3836     * @return string
     3837     */
     3838    protected function serverHostname()
     3839    {
     3840        $result = '';
     3841        if (!empty($this->Hostname)) {
     3842            $result = $this->Hostname;
     3843        } elseif (isset($_SERVER) && array_key_exists('SERVER_NAME', $_SERVER)) {
     3844            $result = $_SERVER['SERVER_NAME'];
     3845        } elseif (function_exists('gethostname') && gethostname() !== false) {
     3846            $result = gethostname();
     3847        } elseif (php_uname('n') !== false) {
     3848            $result = php_uname('n');
     3849        }
     3850        if (!static::isValidHost($result)) {
     3851            return 'localhost.localdomain';
     3852        }
     3853
     3854        return $result;
     3855    }
     3856
     3857    /**
     3858     * Validate whether a string contains a valid value to use as a hostname or IP address.
     3859     * IPv6 addresses must include [], e.g. `[::1]`, not just `::1`.
     3860     *
     3861     * @param string $host The host name or IP address to check
     3862     *
     3863     * @return bool
     3864     */
     3865    public static function isValidHost($host)
     3866    {
     3867        //Simple syntax limits
     3868        if (empty($host)
     3869            || !is_string($host)
     3870            || strlen($host) > 256
     3871            || !preg_match('/^([a-zA-Z\d.-]*|\[[a-fA-F\d:]+])$/', $host)
     3872        ) {
     3873            return false;
     3874        }
     3875        //Looks like a bracketed IPv6 address
     3876        if (strlen($host) > 2 && substr($host, 0, 1) === '[' && substr($host, -1, 1) === ']') {
     3877            return filter_var(substr($host, 1, -1), FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) !== false;
     3878        }
     3879        //If removing all the dots results in a numeric string, it must be an IPv4 address.
     3880        //Need to check this first because otherwise things like `999.0.0.0` are considered valid host names
     3881        if (is_numeric(str_replace('.', '', $host))) {
     3882            //Is it a valid IPv4 address?
     3883            return filter_var($host, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) !== false;
     3884        }
     3885        if (filter_var('http://' . $host, FILTER_VALIDATE_URL) !== false) {
     3886            //Is it a syntactically valid hostname?
     3887            return true;
     3888        }
     3889
     3890        return false;
     3891    }
     3892
     3893    /**
     3894     * Get an error message in the current language.
     3895     *
     3896     * @param string $key
     3897     *
     3898     * @return string
     3899     */
     3900    protected function lang($key)
     3901    {
     3902        if (count($this->language) < 1) {
     3903            $this->setLanguage(); // set the default language
     3904        }
     3905
     3906        if (array_key_exists($key, $this->language)) {
     3907            if ('smtp_connect_failed' === $key) {
     3908                //Include a link to troubleshooting docs on SMTP connection failure
     3909                //this is by far the biggest cause of support questions
     3910                //but it's usually not PHPMailer's fault.
     3911                return $this->language[$key] . ' https://github.com/PHPMailer/PHPMailer/wiki/Troubleshooting';
     3912            }
     3913
     3914            return $this->language[$key];
     3915        }
     3916
     3917        //Return the key as a fallback
     3918        return $key;
     3919    }
     3920
     3921    /**
     3922     * Check if an error occurred.
     3923     *
     3924     * @return bool True if an error did occur
     3925     */
     3926    public function isError()
     3927    {
     3928        return $this->error_count > 0;
     3929    }
     3930
     3931    /**
     3932     * Add a custom header.
     3933     * $name value can be overloaded to contain
     3934     * both header name and value (name:value).
     3935     *
     3936     * @param string      $name  Custom header name
     3937     * @param string|null $value Header value
     3938     */
     3939    public function addCustomHeader($name, $value = null)
     3940    {
     3941        if (null === $value) {
     3942            // Value passed in as name:value
     3943            $this->CustomHeader[] = explode(':', $name, 2);
     3944        } else {
     3945            $this->CustomHeader[] = [$name, $value];
     3946        }
     3947    }
     3948
     3949    /**
     3950     * Returns all custom headers.
     3951     *
     3952     * @return array
     3953     */
     3954    public function getCustomHeaders()
     3955    {
     3956        return $this->CustomHeader;
     3957    }
     3958
     3959    /**
     3960     * Create a message body from an HTML string.
     3961     * Automatically inlines images and creates a plain-text version by converting the HTML,
     3962     * overwriting any existing values in Body and AltBody.
     3963     * Do not source $message content from user input!
     3964     * $basedir is prepended when handling relative URLs, e.g. <img src="/images/a.png"> and must not be empty
     3965     * will look for an image file in $basedir/images/a.png and convert it to inline.
     3966     * If you don't provide a $basedir, relative paths will be left untouched (and thus probably break in email)
     3967     * Converts data-uri images into embedded attachments.
     3968     * If you don't want to apply these transformations to your HTML, just set Body and AltBody directly.
     3969     *
     3970     * @param string        $message  HTML message string
     3971     * @param string        $basedir  Absolute path to a base directory to prepend to relative paths to images
     3972     * @param bool|callable $advanced Whether to use the internal HTML to text converter
     3973     *                                or your own custom converter @return string $message The transformed message Body
     3974     *
     3975     * @throws Exception
     3976     *
     3977     * @see PHPMailer::html2text()
     3978     */
     3979    public function msgHTML($message, $basedir = '', $advanced = false)
     3980    {
     3981        preg_match_all('/(?<!-)(src|background)=["\'](.*)["\']/Ui', $message, $images);
     3982        if (array_key_exists(2, $images)) {
     3983            if (strlen($basedir) > 1 && '/' !== substr($basedir, -1)) {
     3984                // Ensure $basedir has a trailing /
     3985                $basedir .= '/';
     3986            }
     3987            foreach ($images[2] as $imgindex => $url) {
     3988                // Convert data URIs into embedded images
     3989                //e.g. "data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="
     3990                if (preg_match('#^data:(image/(?:jpe?g|gif|png));?(base64)?,(.+)#', $url, $match)) {
     3991                    if (count($match) === 4 && static::ENCODING_BASE64 === $match[2]) {
     3992                        $data = base64_decode($match[3]);
     3993                    } elseif ('' === $match[2]) {
     3994                        $data = rawurldecode($match[3]);
     3995                    } else {
     3996                        //Not recognised so leave it alone
     3997                        continue;
     3998                    }
     3999                    //Hash the decoded data, not the URL, so that the same data-URI image used in multiple places
     4000                    //will only be embedded once, even if it used a different encoding
     4001                    $cid = substr(hash('sha256', $data), 0, 32) . '@phpmailer.0'; // RFC2392 S 2
     4002
     4003                    if (!$this->cidExists($cid)) {
     4004                        $this->addStringEmbeddedImage(
     4005                            $data,
     4006                            $cid,
     4007                            'embed' . $imgindex,
     4008                            static::ENCODING_BASE64,
     4009                            $match[1]
     4010                        );
     4011                    }
     4012                    $message = str_replace(
     4013                        $images[0][$imgindex],
     4014                        $images[1][$imgindex] . '="cid:' . $cid . '"',
     4015                        $message
     4016                    );
     4017                    continue;
     4018                }
     4019                if (// Only process relative URLs if a basedir is provided (i.e. no absolute local paths)
     4020                    !empty($basedir)
     4021                    // Ignore URLs containing parent dir traversal (..)
     4022                    && (strpos($url, '..') === false)
     4023                    // Do not change urls that are already inline images
     4024                    && 0 !== strpos($url, 'cid:')
     4025                    // Do not change absolute URLs, including anonymous protocol
     4026                    && !preg_match('#^[a-z][a-z0-9+.-]*:?//#i', $url)
     4027                ) {
     4028                    $filename = static::mb_pathinfo($url, PATHINFO_BASENAME);
     4029                    $directory = dirname($url);
     4030                    if ('.' === $directory) {
     4031                        $directory = '';
     4032                    }
     4033                    // RFC2392 S 2
     4034                    $cid = substr(hash('sha256', $url), 0, 32) . '@phpmailer.0';
     4035                    if (strlen($basedir) > 1 && '/' !== substr($basedir, -1)) {
     4036                        $basedir .= '/';
     4037                    }
     4038                    if (strlen($directory) > 1 && '/' !== substr($directory, -1)) {
     4039                        $directory .= '/';
     4040                    }
     4041                    if ($this->addEmbeddedImage(
     4042                        $basedir . $directory . $filename,
     4043                        $cid,
     4044                        $filename,
     4045                        static::ENCODING_BASE64,
     4046                        static::_mime_types((string) static::mb_pathinfo($filename, PATHINFO_EXTENSION))
     4047                    )
     4048                    ) {
     4049                        $message = preg_replace(
     4050                            '/' . $images[1][$imgindex] . '=["\']' . preg_quote($url, '/') . '["\']/Ui',
     4051                            $images[1][$imgindex] . '="cid:' . $cid . '"',
     4052                            $message
     4053                        );
     4054                    }
     4055                }
     4056            }
     4057        }
     4058        $this->isHTML();
     4059        // Convert all message body line breaks to LE, makes quoted-printable encoding work much better
     4060        $this->Body = static::normalizeBreaks($message);
     4061        $this->AltBody = static::normalizeBreaks($this->html2text($message, $advanced));
     4062        if (!$this->alternativeExists()) {
     4063            $this->AltBody = 'This is an HTML-only message. To view it, activate HTML in your email application.'
     4064                . static::$LE;
     4065        }
     4066
     4067        return $this->Body;
     4068    }
     4069
     4070    /**
     4071     * Convert an HTML string into plain text.
     4072     * This is used by msgHTML().
     4073     * Note - older versions of this function used a bundled advanced converter
     4074     * which was removed for license reasons in #232.
     4075     * Example usage:
     4076     *
     4077     * ```php
     4078     * // Use default conversion
     4079     * $plain = $mail->html2text($html);
     4080     * // Use your own custom converter
     4081     * $plain = $mail->html2text($html, function($html) {
     4082     *     $converter = new MyHtml2text($html);
     4083     *     return $converter->get_text();
     4084     * });
     4085     * ```
     4086     *
     4087     * @param string        $html     The HTML text to convert
     4088     * @param bool|callable $advanced Any boolean value to use the internal converter,
     4089     *                                or provide your own callable for custom conversion
     4090     *
     4091     * @return string
     4092     */
     4093    public function html2text($html, $advanced = false)
     4094    {
     4095        if (is_callable($advanced)) {
     4096            return $advanced($html);
     4097        }
     4098
     4099        return html_entity_decode(
     4100            trim(strip_tags(preg_replace('/<(head|title|style|script)[^>]*>.*?<\/\\1>/si', '', $html))),
     4101            ENT_QUOTES,
     4102            $this->CharSet
     4103        );
     4104    }
     4105
     4106    /**
     4107     * Get the MIME type for a file extension.
     4108     *
     4109     * @param string $ext File extension
     4110     *
     4111     * @return string MIME type of file
     4112     */
     4113    public static function _mime_types($ext = '')
     4114    {
     4115        $mimes = [
     4116            'xl' => 'application/excel',
     4117            'js' => 'application/javascript',
     4118            'hqx' => 'application/mac-binhex40',
     4119            'cpt' => 'application/mac-compactpro',
     4120            'bin' => 'application/macbinary',
     4121            'doc' => 'application/msword',
     4122            'word' => 'application/msword',
     4123            'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
     4124            'xltx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.template',
     4125            'potx' => 'application/vnd.openxmlformats-officedocument.presentationml.template',
     4126            'ppsx' => 'application/vnd.openxmlformats-officedocument.presentationml.slideshow',
     4127            'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
     4128            'sldx' => 'application/vnd.openxmlformats-officedocument.presentationml.slide',
     4129            'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
     4130            'dotx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.template',
     4131            'xlam' => 'application/vnd.ms-excel.addin.macroEnabled.12',
     4132            'xlsb' => 'application/vnd.ms-excel.sheet.binary.macroEnabled.12',
     4133            'class' => 'application/octet-stream',
     4134            'dll' => 'application/octet-stream',
     4135            'dms' => 'application/octet-stream',
     4136            'exe' => 'application/octet-stream',
     4137            'lha' => 'application/octet-stream',
     4138            'lzh' => 'application/octet-stream',
     4139            'psd' => 'application/octet-stream',
     4140            'sea' => 'application/octet-stream',
     4141            'so' => 'application/octet-stream',
     4142            'oda' => 'application/oda',
     4143            'pdf' => 'application/pdf',
     4144            'ai' => 'application/postscript',
     4145            'eps' => 'application/postscript',
     4146            'ps' => 'application/postscript',
     4147            'smi' => 'application/smil',
     4148            'smil' => 'application/smil',
     4149            'mif' => 'application/vnd.mif',
     4150            'xls' => 'application/vnd.ms-excel',
     4151            'ppt' => 'application/vnd.ms-powerpoint',
     4152            'wbxml' => 'application/vnd.wap.wbxml',
     4153            'wmlc' => 'application/vnd.wap.wmlc',
     4154            'dcr' => 'application/x-director',
     4155            'dir' => 'application/x-director',
     4156            'dxr' => 'application/x-director',
     4157            'dvi' => 'application/x-dvi',
     4158            'gtar' => 'application/x-gtar',
     4159            'php3' => 'application/x-httpd-php',
     4160            'php4' => 'application/x-httpd-php',
     4161            'php' => 'application/x-httpd-php',
     4162            'phtml' => 'application/x-httpd-php',
     4163            'phps' => 'application/x-httpd-php-source',
     4164            'swf' => 'application/x-shockwave-flash',
     4165            'sit' => 'application/x-stuffit',
     4166            'tar' => 'application/x-tar',
     4167            'tgz' => 'application/x-tar',
     4168            'xht' => 'application/xhtml+xml',
     4169            'xhtml' => 'application/xhtml+xml',
     4170            'zip' => 'application/zip',
     4171            'mid' => 'audio/midi',
     4172            'midi' => 'audio/midi',
     4173            'mp2' => 'audio/mpeg',
     4174            'mp3' => 'audio/mpeg',
     4175            'm4a' => 'audio/mp4',
     4176            'mpga' => 'audio/mpeg',
     4177            'aif' => 'audio/x-aiff',
     4178            'aifc' => 'audio/x-aiff',
     4179            'aiff' => 'audio/x-aiff',
     4180            'ram' => 'audio/x-pn-realaudio',
     4181            'rm' => 'audio/x-pn-realaudio',
     4182            'rpm' => 'audio/x-pn-realaudio-plugin',
     4183            'ra' => 'audio/x-realaudio',
     4184            'wav' => 'audio/x-wav',
     4185            'mka' => 'audio/x-matroska',
     4186            'bmp' => 'image/bmp',
     4187            'gif' => 'image/gif',
     4188            'jpeg' => 'image/jpeg',
     4189            'jpe' => 'image/jpeg',
     4190            'jpg' => 'image/jpeg',
     4191            'png' => 'image/png',
     4192            'tiff' => 'image/tiff',
     4193            'tif' => 'image/tiff',
     4194            'webp' => 'image/webp',
     4195            'heif' => 'image/heif',
     4196            'heifs' => 'image/heif-sequence',
     4197            'heic' => 'image/heic',
     4198            'heics' => 'image/heic-sequence',
     4199            'eml' => 'message/rfc822',
     4200            'css' => 'text/css',
     4201            'html' => 'text/html',
     4202            'htm' => 'text/html',
     4203            'shtml' => 'text/html',
     4204            'log' => 'text/plain',
     4205            'text' => 'text/plain',
     4206            'txt' => 'text/plain',
     4207            'rtx' => 'text/richtext',
     4208            'rtf' => 'text/rtf',
     4209            'vcf' => 'text/vcard',
     4210            'vcard' => 'text/vcard',
     4211            'ics' => 'text/calendar',
     4212            'xml' => 'text/xml',
     4213            'xsl' => 'text/xml',
     4214            'wmv' => 'video/x-ms-wmv',
     4215            'mpeg' => 'video/mpeg',
     4216            'mpe' => 'video/mpeg',
     4217            'mpg' => 'video/mpeg',
     4218            'mp4' => 'video/mp4',
     4219            'm4v' => 'video/mp4',
     4220            'mov' => 'video/quicktime',
     4221            'qt' => 'video/quicktime',
     4222            'rv' => 'video/vnd.rn-realvideo',
     4223            'avi' => 'video/x-msvideo',
     4224            'movie' => 'video/x-sgi-movie',
     4225            'webm' => 'video/webm',
     4226            'mkv' => 'video/x-matroska',
     4227        ];
     4228        $ext = strtolower($ext);
     4229        if (array_key_exists($ext, $mimes)) {
     4230            return $mimes[$ext];
     4231        }
     4232
     4233        return 'application/octet-stream';
     4234    }
     4235
     4236    /**
     4237     * Map a file name to a MIME type.
     4238     * Defaults to 'application/octet-stream', i.e.. arbitrary binary data.
     4239     *
     4240     * @param string $filename A file name or full path, does not need to exist as a file
     4241     *
     4242     * @return string
     4243     */
     4244    public static function filenameToType($filename)
     4245    {
     4246        // In case the path is a URL, strip any query string before getting extension
     4247        $qpos = strpos($filename, '?');
     4248        if (false !== $qpos) {
     4249            $filename = substr($filename, 0, $qpos);
     4250        }
     4251        $ext = static::mb_pathinfo($filename, PATHINFO_EXTENSION);
     4252
     4253        return static::_mime_types($ext);
     4254    }
     4255
     4256    /**
     4257     * Multi-byte-safe pathinfo replacement.
     4258     * Drop-in replacement for pathinfo(), but multibyte- and cross-platform-safe.
     4259     *
     4260     * @see http://www.php.net/manual/en/function.pathinfo.php#107461
     4261     *
     4262     * @param string     $path    A filename or path, does not need to exist as a file
     4263     * @param int|string $options Either a PATHINFO_* constant,
     4264     *                            or a string name to return only the specified piece
     4265     *
     4266     * @return string|array
     4267     */
     4268    public static function mb_pathinfo($path, $options = null)
     4269    {
     4270        $ret = ['dirname' => '', 'basename' => '', 'extension' => '', 'filename' => ''];
     4271        $pathinfo = [];
     4272        if (preg_match('#^(.*?)[\\\\/]*(([^/\\\\]*?)(\.([^.\\\\/]+?)|))[\\\\/.]*$#m', $path, $pathinfo)) {
     4273            if (array_key_exists(1, $pathinfo)) {
     4274                $ret['dirname'] = $pathinfo[1];
     4275            }
     4276            if (array_key_exists(2, $pathinfo)) {
     4277                $ret['basename'] = $pathinfo[2];
     4278            }
     4279            if (array_key_exists(5, $pathinfo)) {
     4280                $ret['extension'] = $pathinfo[5];
     4281            }
     4282            if (array_key_exists(3, $pathinfo)) {
     4283                $ret['filename'] = $pathinfo[3];
     4284            }
     4285        }
     4286        switch ($options) {
     4287            case PATHINFO_DIRNAME:
     4288            case 'dirname':
     4289                return $ret['dirname'];
     4290            case PATHINFO_BASENAME:
     4291            case 'basename':
     4292                return $ret['basename'];
     4293            case PATHINFO_EXTENSION:
     4294            case 'extension':
     4295                return $ret['extension'];
     4296            case PATHINFO_FILENAME:
     4297            case 'filename':
     4298                return $ret['filename'];
     4299            default:
     4300                return $ret;
     4301        }
     4302    }
     4303
     4304    /**
     4305     * Set or reset instance properties.
     4306     * You should avoid this function - it's more verbose, less efficient, more error-prone and
     4307     * harder to debug than setting properties directly.
     4308     * Usage Example:
     4309     * `$mail->set('SMTPSecure', static::ENCRYPTION_STARTTLS);`
     4310     *   is the same as:
     4311     * `$mail->SMTPSecure = static::ENCRYPTION_STARTTLS;`.
     4312     *
     4313     * @param string $name  The property name to set
     4314     * @param mixed  $value The value to set the property to
     4315     *
     4316     * @return bool
     4317     */
     4318    public function set($name, $value = '')
     4319    {
     4320        if (property_exists($this, $name)) {
     4321            $this->$name = $value;
     4322
     4323            return true;
     4324        }
     4325        $this->setError($this->lang('variable_set') . $name);
     4326
     4327        return false;
     4328    }
     4329
     4330    /**
     4331     * Strip newlines to prevent header injection.
     4332     *
     4333     * @param string $str
     4334     *
     4335     * @return string
     4336     */
     4337    public function secureHeader($str)
     4338    {
     4339        return trim(str_replace(["\r", "\n"], '', $str));
     4340    }
     4341
     4342    /**
     4343     * Normalize line breaks in a string.
     4344     * Converts UNIX LF, Mac CR and Windows CRLF line breaks into a single line break format.
     4345     * Defaults to CRLF (for message bodies) and preserves consecutive breaks.
     4346     *
     4347     * @param string $text
     4348     * @param string $breaktype What kind of line break to use; defaults to static::$LE
     4349     *
     4350     * @return string
     4351     */
     4352    public static function normalizeBreaks($text, $breaktype = null)
     4353    {
     4354        if (null === $breaktype) {
     4355            $breaktype = static::$LE;
     4356        }
     4357        // Normalise to \n
     4358        $text = str_replace(["\r\n", "\r"], "\n", $text);
     4359        // Now convert LE as needed
     4360        if ("\n" !== $breaktype) {
     4361            $text = str_replace("\n", $breaktype, $text);
     4362        }
     4363
     4364        return $text;
     4365    }
     4366
     4367    /**
     4368     * Return the current line break format string.
     4369     *
     4370     * @return string
     4371     */
     4372    public static function getLE()
     4373    {
     4374        return static::$LE;
     4375    }
     4376
     4377    /**
     4378     * Set the line break format string, e.g. "\r\n".
     4379     *
     4380     * @param string $le
     4381     */
     4382    protected static function setLE($le)
     4383    {
     4384        static::$LE = $le;
     4385    }
     4386
     4387    /**
     4388     * Set the public and private key files and password for S/MIME signing.
     4389     *
     4390     * @param string $cert_filename
     4391     * @param string $key_filename
     4392     * @param string $key_pass            Password for private key
     4393     * @param string $extracerts_filename Optional path to chain certificate
     4394     */
     4395    public function sign($cert_filename, $key_filename, $key_pass, $extracerts_filename = '')
     4396    {
     4397        $this->sign_cert_file = $cert_filename;
     4398        $this->sign_key_file = $key_filename;
     4399        $this->sign_key_pass = $key_pass;
     4400        $this->sign_extracerts_file = $extracerts_filename;
     4401    }
     4402
     4403    /**
     4404     * Quoted-Printable-encode a DKIM header.
     4405     *
     4406     * @param string $txt
     4407     *
     4408     * @return string
     4409     */
     4410    public function DKIM_QP($txt)
     4411    {
     4412        $line = '';
     4413        $len = strlen($txt);
     4414        for ($i = 0; $i < $len; ++$i) {
     4415            $ord = ord($txt[$i]);
     4416            if (((0x21 <= $ord) && ($ord <= 0x3A)) || $ord === 0x3C || ((0x3E <= $ord) && ($ord <= 0x7E))) {
     4417                $line .= $txt[$i];
     4418            } else {
     4419                $line .= '=' . sprintf('%02X', $ord);
     4420            }
     4421        }
     4422
     4423        return $line;
     4424    }
     4425
     4426    /**
     4427     * Generate a DKIM signature.
     4428     *
     4429     * @param string $signHeader
     4430     *
     4431     * @throws Exception
     4432     *
     4433     * @return string The DKIM signature value
     4434     */
     4435    public function DKIM_Sign($signHeader)
     4436    {
     4437        if (!defined('PKCS7_TEXT')) {
     4438            if ($this->exceptions) {
     4439                throw new Exception($this->lang('extension_missing') . 'openssl');
     4440            }
     4441
     4442            return '';
     4443        }
     4444        $privKeyStr = !empty($this->DKIM_private_string) ?
     4445            $this->DKIM_private_string :
     4446            file_get_contents($this->DKIM_private);
     4447        if ('' !== $this->DKIM_passphrase) {
     4448            $privKey = openssl_pkey_get_private($privKeyStr, $this->DKIM_passphrase);
     4449        } else {
     4450            $privKey = openssl_pkey_get_private($privKeyStr);
     4451        }
     4452        if (openssl_sign($signHeader, $signature, $privKey, 'sha256WithRSAEncryption')) {
     4453            openssl_pkey_free($privKey);
     4454
     4455            return base64_encode($signature);
     4456        }
     4457        openssl_pkey_free($privKey);
     4458
     4459        return '';
     4460    }
     4461
     4462    /**
     4463     * Generate a DKIM canonicalization header.
     4464     * Uses the 'relaxed' algorithm from RFC6376 section 3.4.2.
     4465     * Canonicalized headers should *always* use CRLF, regardless of mailer setting.
     4466     *
     4467     * @see https://tools.ietf.org/html/rfc6376#section-3.4.2
     4468     *
     4469     * @param string $signHeader Header
     4470     *
     4471     * @return string
     4472     */
     4473    public function DKIM_HeaderC($signHeader)
     4474    {
     4475        //Note PCRE \s is too broad a definition of whitespace; RFC5322 defines it as `[ \t]`
     4476        //@see https://tools.ietf.org/html/rfc5322#section-2.2
     4477        //That means this may break if you do something daft like put vertical tabs in your headers.
     4478        //Unfold header lines
     4479        $signHeader = preg_replace('/\r\n[ \t]+/', ' ', $signHeader);
     4480        //Break headers out into an array
     4481        $lines = explode("\r\n", $signHeader);
     4482        foreach ($lines as $key => $line) {
     4483            //If the header is missing a :, skip it as it's invalid
     4484            //This is likely to happen because the explode() above will also split
     4485            //on the trailing LE, leaving an empty line
     4486            if (strpos($line, ':') === false) {
     4487                continue;
     4488            }
     4489            list($heading, $value) = explode(':', $line, 2);
     4490            //Lower-case header name
     4491            $heading = strtolower($heading);
     4492            //Collapse white space within the value, also convert WSP to space
     4493            $value = preg_replace('/[ \t]+/', ' ', $value);
     4494            //RFC6376 is slightly unclear here - it says to delete space at the *end* of each value
     4495            //But then says to delete space before and after the colon.
     4496            //Net result is the same as trimming both ends of the value.
     4497            //By elimination, the same applies to the field name
     4498            $lines[$key] = trim($heading, " \t") . ':' . trim($value, " \t");
     4499        }
     4500
     4501        return implode("\r\n", $lines);
     4502    }
     4503
     4504    /**
     4505     * Generate a DKIM canonicalization body.
     4506     * Uses the 'simple' algorithm from RFC6376 section 3.4.3.
     4507     * Canonicalized bodies should *always* use CRLF, regardless of mailer setting.
     4508     *
     4509     * @see https://tools.ietf.org/html/rfc6376#section-3.4.3
     4510     *
     4511     * @param string $body Message Body
     4512     *
     4513     * @return string
     4514     */
     4515    public function DKIM_BodyC($body)
     4516    {
     4517        if (empty($body)) {
     4518            return "\r\n";
     4519        }
     4520        // Normalize line endings to CRLF
     4521        $body = static::normalizeBreaks($body, "\r\n");
     4522
     4523        //Reduce multiple trailing line breaks to a single one
     4524        return rtrim($body, "\r\n") . "\r\n";
     4525    }
     4526
     4527    /**
     4528     * Create the DKIM header and body in a new message header.
     4529     *
     4530     * @param string $headers_line Header lines
     4531     * @param string $subject      Subject
     4532     * @param string $body         Body
     4533     *
     4534     * @throws Exception
     4535     *
     4536     * @return string
     4537     */
     4538    public function DKIM_Add($headers_line, $subject, $body)
     4539    {
     4540        $DKIMsignatureType = 'rsa-sha256'; // Signature & hash algorithms
     4541        $DKIMcanonicalization = 'relaxed/simple'; // Canonicalization methods of header & body
     4542        $DKIMquery = 'dns/txt'; // Query method
     4543        $DKIMtime = time();
     4544        //Always sign these headers without being asked
     4545        $autoSignHeaders = [
     4546            'From',
     4547            'To',
     4548            'CC',
     4549            'Date',
     4550            'Subject',
     4551            'Reply-To',
     4552            'Message-ID',
     4553            'Content-Type',
     4554            'Mime-Version',
     4555            'X-Mailer',
     4556        ];
     4557        if (stripos($headers_line, 'Subject') === false) {
     4558            $headers_line .= 'Subject: ' . $subject . static::$LE;
     4559        }
     4560        $headerLines = explode(static::$LE, $headers_line);
     4561        $currentHeaderLabel = '';
     4562        $currentHeaderValue = '';
     4563        $parsedHeaders = [];
     4564        $headerLineIndex = 0;
     4565        $headerLineCount = count($headerLines);
     4566        foreach ($headerLines as $headerLine) {
     4567            $matches = [];
     4568            if (preg_match('/^([^ \t]*?)(?::[ \t]*)(.*)$/', $headerLine, $matches)) {
     4569                if ($currentHeaderLabel !== '') {
     4570                    //We were previously in another header; This is the start of a new header, so save the previous one
     4571                    $parsedHeaders[] = ['label' => $currentHeaderLabel, 'value' => $currentHeaderValue];
     4572                }
     4573                $currentHeaderLabel = $matches[1];
     4574                $currentHeaderValue = $matches[2];
     4575            } elseif (preg_match('/^[ \t]+(.*)$/', $headerLine, $matches)) {
     4576                //This is a folded continuation of the current header, so unfold it
     4577                $currentHeaderValue .= ' ' . $matches[1];
     4578            }
     4579            ++$headerLineIndex;
     4580            if ($headerLineIndex >= $headerLineCount) {
     4581                //This was the last line, so finish off this header
     4582                $parsedHeaders[] = ['label' => $currentHeaderLabel, 'value' => $currentHeaderValue];
     4583            }
     4584        }
     4585        $copiedHeaders = [];
     4586        $headersToSignKeys = [];
     4587        $headersToSign = [];
     4588        foreach ($parsedHeaders as $header) {
     4589            //Is this header one that must be included in the DKIM signature?
     4590            if (in_array($header['label'], $autoSignHeaders, true)) {
     4591                $headersToSignKeys[] = $header['label'];
     4592                $headersToSign[] = $header['label'] . ': ' . $header['value'];
     4593                if ($this->DKIM_copyHeaderFields) {
     4594                    $copiedHeaders[] = $header['label'] . ':' . //Note no space after this, as per RFC
     4595                        str_replace('|', '=7C', $this->DKIM_QP($header['value']));
     4596                }
     4597                continue;
     4598            }
     4599            //Is this an extra custom header we've been asked to sign?
     4600            if (in_array($header['label'], $this->DKIM_extraHeaders, true)) {
     4601                //Find its value in custom headers
     4602                foreach ($this->CustomHeader as $customHeader) {
     4603                    if ($customHeader[0] === $header['label']) {
     4604                        $headersToSignKeys[] = $header['label'];
     4605                        $headersToSign[] = $header['label'] . ': ' . $header['value'];
     4606                        if ($this->DKIM_copyHeaderFields) {
     4607                            $copiedHeaders[] = $header['label'] . ':' . //Note no space after this, as per RFC
     4608                                str_replace('|', '=7C', $this->DKIM_QP($header['value']));
     4609                        }
     4610                        //Skip straight to the next header
     4611                        continue 2;
     4612                    }
     4613                }
     4614            }
     4615        }
     4616        $copiedHeaderFields = '';
     4617        if ($this->DKIM_copyHeaderFields && count($copiedHeaders) > 0) {
     4618            //Assemble a DKIM 'z' tag
     4619            $copiedHeaderFields = ' z=';
     4620            $first = true;
     4621            foreach ($copiedHeaders as $copiedHeader) {
     4622                if (!$first) {
     4623                    $copiedHeaderFields .= static::$LE . ' |';
     4624                }
     4625                //Fold long values
     4626                if (strlen($copiedHeader) > self::STD_LINE_LENGTH - 3) {
     4627                    $copiedHeaderFields .= substr(
     4628                        chunk_split($copiedHeader, self::STD_LINE_LENGTH - 3, static::$LE . ' '),
     4629                        0,
     4630                        -strlen(static::$LE . ' ')
     4631                    );
     4632                } else {
     4633                    $copiedHeaderFields .= $copiedHeader;
     4634                }
     4635                $first = false;
     4636            }
     4637            $copiedHeaderFields .= ';' . static::$LE;
     4638        }
     4639        $headerKeys = ' h=' . implode(':', $headersToSignKeys) . ';' . static::$LE;
     4640        $headerValues = implode(static::$LE, $headersToSign);
     4641        $body = $this->DKIM_BodyC($body);
     4642        $DKIMlen = strlen($body); // Length of body
     4643        $DKIMb64 = base64_encode(pack('H*', hash('sha256', $body))); // Base64 of packed binary SHA-256 hash of body
     4644        $ident = '';
     4645        if ('' !== $this->DKIM_identity) {
     4646            $ident = ' i=' . $this->DKIM_identity . ';' . static::$LE;
     4647        }
     4648        //The DKIM-Signature header is included in the signature *except for* the value of the `b` tag
     4649        //which is appended after calculating the signature
     4650        //https://tools.ietf.org/html/rfc6376#section-3.5
     4651        $dkimSignatureHeader = 'DKIM-Signature: v=1;' .
     4652            ' d=' . $this->DKIM_domain . ';' .
     4653            ' s=' . $this->DKIM_selector . ';' . static::$LE .
     4654            ' a=' . $DKIMsignatureType . ';' .
     4655            ' q=' . $DKIMquery . ';' .
     4656            ' l=' . $DKIMlen . ';' .
     4657            ' t=' . $DKIMtime . ';' .
     4658            ' c=' . $DKIMcanonicalization . ';' . static::$LE .
     4659            $headerKeys .
     4660            $ident .
     4661            $copiedHeaderFields .
     4662            ' bh=' . $DKIMb64 . ';' . static::$LE .
     4663            ' b=';
     4664        //Canonicalize the set of headers
     4665        $canonicalizedHeaders = $this->DKIM_HeaderC(
     4666            $headerValues . static::$LE . $dkimSignatureHeader
     4667        );
     4668        $signature = $this->DKIM_Sign($canonicalizedHeaders);
     4669        $signature = trim(chunk_split($signature, self::STD_LINE_LENGTH - 3, static::$LE . ' '));
     4670
     4671        return static::normalizeBreaks($dkimSignatureHeader . $signature) . static::$LE;
     4672    }
     4673
     4674    /**
     4675     * Detect if a string contains a line longer than the maximum line length
     4676     * allowed by RFC 2822 section 2.1.1.
     4677     *
     4678     * @param string $str
     4679     *
     4680     * @return bool
     4681     */
     4682    public static function hasLineLongerThanMax($str)
     4683    {
     4684        return (bool) preg_match('/^(.{' . (self::MAX_LINE_LENGTH + strlen(static::$LE)) . ',})/m', $str);
     4685    }
     4686
     4687    /**
     4688     * Allows for public read access to 'to' property.
     4689     * Before the send() call, queued addresses (i.e. with IDN) are not yet included.
     4690     *
     4691     * @return array
     4692     */
     4693    public function getToAddresses()
     4694    {
     4695        return $this->to;
     4696    }
     4697
     4698    /**
     4699     * Allows for public read access to 'cc' property.
     4700     * Before the send() call, queued addresses (i.e. with IDN) are not yet included.
     4701     *
     4702     * @return array
     4703     */
     4704    public function getCcAddresses()
     4705    {
     4706        return $this->cc;
     4707    }
     4708
     4709    /**
     4710     * Allows for public read access to 'bcc' property.
     4711     * Before the send() call, queued addresses (i.e. with IDN) are not yet included.
     4712     *
     4713     * @return array
     4714     */
     4715    public function getBccAddresses()
     4716    {
     4717        return $this->bcc;
     4718    }
     4719
     4720    /**
     4721     * Allows for public read access to 'ReplyTo' property.
     4722     * Before the send() call, queued addresses (i.e. with IDN) are not yet included.
     4723     *
     4724     * @return array
     4725     */
     4726    public function getReplyToAddresses()
     4727    {
     4728        return $this->ReplyTo;
     4729    }
     4730
     4731    /**
     4732     * Allows for public read access to 'all_recipients' property.
     4733     * Before the send() call, queued addresses (i.e. with IDN) are not yet included.
     4734     *
     4735     * @return array
     4736     */
     4737    public function getAllRecipientAddresses()
     4738    {
     4739        return $this->all_recipients;
     4740    }
     4741
     4742    /**
     4743     * Perform a callback.
     4744     *
     4745     * @param bool   $isSent
     4746     * @param array  $to
     4747     * @param array  $cc
     4748     * @param array  $bcc
     4749     * @param string $subject
     4750     * @param string $body
     4751     * @param string $from
     4752     * @param array  $extra
     4753     */
     4754    protected function doCallback($isSent, $to, $cc, $bcc, $subject, $body, $from, $extra)
     4755    {
     4756        if (!empty($this->action_function) && is_callable($this->action_function)) {
     4757            call_user_func($this->action_function, $isSent, $to, $cc, $bcc, $subject, $body, $from, $extra);
     4758        }
     4759    }
     4760
     4761    /**
     4762     * Get the OAuth instance.
     4763     *
     4764     * @return OAuth
     4765     */
     4766    public function getOAuth()
     4767    {
     4768        return $this->oauth;
     4769    }
     4770
     4771    /**
     4772     * Set an OAuth instance.
     4773     */
     4774    public function setOAuth(OAuth $oauth)
     4775    {
     4776        $this->oauth = $oauth;
     4777    }
     4778}
  • new file src/wp-includes/PHPMailer/SMTP.php

    diff --git a/src/wp-includes/PHPMailer/SMTP.php b/src/wp-includes/PHPMailer/SMTP.php
    new file mode 100644
    index 0000000000..c693f4d467
    - +  
     1<?php
     2/**
     3 * PHPMailer RFC821 SMTP email transport class.
     4 * PHP Version 5.5.
     5 *
     6 * @see       https://github.com/PHPMailer/PHPMailer/ The PHPMailer GitHub project
     7 *
     8 * @author    Marcus Bointon (Synchro/coolbru) <phpmailer@synchromedia.co.uk>
     9 * @author    Jim Jagielski (jimjag) <jimjag@gmail.com>
     10 * @author    Andy Prevost (codeworxtech) <codeworxtech@users.sourceforge.net>
     11 * @author    Brent R. Matzelle (original founder)
     12 * @copyright 2012 - 2019 Marcus Bointon
     13 * @copyright 2010 - 2012 Jim Jagielski
     14 * @copyright 2004 - 2009 Andy Prevost
     15 * @license   http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
     16 * @note      This program is distributed in the hope that it will be useful - WITHOUT
     17 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
     18 * FITNESS FOR A PARTICULAR PURPOSE.
     19 */
     20
     21namespace PHPMailer\PHPMailer;
     22
     23/**
     24 * PHPMailer RFC821 SMTP email transport class.
     25 * Implements RFC 821 SMTP commands and provides some utility methods for sending mail to an SMTP server.
     26 *
     27 * @author Chris Ryan
     28 * @author Marcus Bointon <phpmailer@synchromedia.co.uk>
     29 */
     30class SMTP
     31{
     32    /**
     33     * The PHPMailer SMTP version number.
     34     *
     35     * @var string
     36     */
     37    const VERSION = '6.1.4';
     38
     39    /**
     40     * SMTP line break constant.
     41     *
     42     * @var string
     43     */
     44    const LE = "\r\n";
     45
     46    /**
     47     * The SMTP port to use if one is not specified.
     48     *
     49     * @var int
     50     */
     51    const DEFAULT_PORT = 25;
     52
     53    /**
     54     * The maximum line length allowed by RFC 5321 section 4.5.3.1.6,
     55     * *excluding* a trailing CRLF break.
     56     *
     57     * @see https://tools.ietf.org/html/rfc5321#section-4.5.3.1.6
     58     *
     59     * @var int
     60     */
     61    const MAX_LINE_LENGTH = 998;
     62
     63    /**
     64     * The maximum line length allowed for replies in RFC 5321 section 4.5.3.1.5,
     65     * *including* a trailing CRLF line break.
     66     *
     67     * @see https://tools.ietf.org/html/rfc5321#section-4.5.3.1.5
     68     *
     69     * @var int
     70     */
     71    const MAX_REPLY_LENGTH = 512;
     72
     73    /**
     74     * Debug level for no output.
     75     *
     76     * @var int
     77     */
     78    const DEBUG_OFF = 0;
     79
     80    /**
     81     * Debug level to show client -> server messages.
     82     *
     83     * @var int
     84     */
     85    const DEBUG_CLIENT = 1;
     86
     87    /**
     88     * Debug level to show client -> server and server -> client messages.
     89     *
     90     * @var int
     91     */
     92    const DEBUG_SERVER = 2;
     93
     94    /**
     95     * Debug level to show connection status, client -> server and server -> client messages.
     96     *
     97     * @var int
     98     */
     99    const DEBUG_CONNECTION = 3;
     100
     101    /**
     102     * Debug level to show all messages.
     103     *
     104     * @var int
     105     */
     106    const DEBUG_LOWLEVEL = 4;
     107
     108    /**
     109     * Debug output level.
     110     * Options:
     111     * * self::DEBUG_OFF (`0`) No debug output, default
     112     * * self::DEBUG_CLIENT (`1`) Client commands
     113     * * self::DEBUG_SERVER (`2`) Client commands and server responses
     114     * * self::DEBUG_CONNECTION (`3`) As DEBUG_SERVER plus connection status
     115     * * self::DEBUG_LOWLEVEL (`4`) Low-level data output, all messages.
     116     *
     117     * @var int
     118     */
     119    public $do_debug = self::DEBUG_OFF;
     120
     121    /**
     122     * How to handle debug output.
     123     * Options:
     124     * * `echo` Output plain-text as-is, appropriate for CLI
     125     * * `html` Output escaped, line breaks converted to `<br>`, appropriate for browser output
     126     * * `error_log` Output to error log as configured in php.ini
     127     * Alternatively, you can provide a callable expecting two params: a message string and the debug level:
     128     *
     129     * ```php
     130     * $smtp->Debugoutput = function($str, $level) {echo "debug level $level; message: $str";};
     131     * ```
     132     *
     133     * Alternatively, you can pass in an instance of a PSR-3 compatible logger, though only `debug`
     134     * level output is used:
     135     *
     136     * ```php
     137     * $mail->Debugoutput = new myPsr3Logger;
     138     * ```
     139     *
     140     * @var string|callable|\Psr\Log\LoggerInterface
     141     */
     142    public $Debugoutput = 'echo';
     143
     144    /**
     145     * Whether to use VERP.
     146     *
     147     * @see http://en.wikipedia.org/wiki/Variable_envelope_return_path
     148     * @see http://www.postfix.org/VERP_README.html Info on VERP
     149     *
     150     * @var bool
     151     */
     152    public $do_verp = false;
     153
     154    /**
     155     * The timeout value for connection, in seconds.
     156     * Default of 5 minutes (300sec) is from RFC2821 section 4.5.3.2.
     157     * This needs to be quite high to function correctly with hosts using greetdelay as an anti-spam measure.
     158     *
     159     * @see http://tools.ietf.org/html/rfc2821#section-4.5.3.2
     160     *
     161     * @var int
     162     */
     163    public $Timeout = 300;
     164
     165    /**
     166     * How long to wait for commands to complete, in seconds.
     167     * Default of 5 minutes (300sec) is from RFC2821 section 4.5.3.2.
     168     *
     169     * @var int
     170     */
     171    public $Timelimit = 300;
     172
     173    /**
     174     * Patterns to extract an SMTP transaction id from reply to a DATA command.
     175     * The first capture group in each regex will be used as the ID.
     176     * MS ESMTP returns the message ID, which may not be correct for internal tracking.
     177     *
     178     * @var string[]
     179     */
     180    protected $smtp_transaction_id_patterns = [
     181        'exim' => '/[\d]{3} OK id=(.*)/',
     182        'sendmail' => '/[\d]{3} 2.0.0 (.*) Message/',
     183        'postfix' => '/[\d]{3} 2.0.0 Ok: queued as (.*)/',
     184        'Microsoft_ESMTP' => '/[0-9]{3} 2.[\d].0 (.*)@(?:.*) Queued mail for delivery/',
     185        'Amazon_SES' => '/[\d]{3} Ok (.*)/',
     186        'SendGrid' => '/[\d]{3} Ok: queued as (.*)/',
     187        'CampaignMonitor' => '/[\d]{3} 2.0.0 OK:([a-zA-Z\d]{48})/',
     188    ];
     189
     190    /**
     191     * The last transaction ID issued in response to a DATA command,
     192     * if one was detected.
     193     *
     194     * @var string|bool|null
     195     */
     196    protected $last_smtp_transaction_id;
     197
     198    /**
     199     * The socket for the server connection.
     200     *
     201     * @var ?resource
     202     */
     203    protected $smtp_conn;
     204
     205    /**
     206     * Error information, if any, for the last SMTP command.
     207     *
     208     * @var array
     209     */
     210    protected $error = [
     211        'error' => '',
     212        'detail' => '',
     213        'smtp_code' => '',
     214        'smtp_code_ex' => '',
     215    ];
     216
     217    /**
     218     * The reply the server sent to us for HELO.
     219     * If null, no HELO string has yet been received.
     220     *
     221     * @var string|null
     222     */
     223    protected $helo_rply;
     224
     225    /**
     226     * The set of SMTP extensions sent in reply to EHLO command.
     227     * Indexes of the array are extension names.
     228     * Value at index 'HELO' or 'EHLO' (according to command that was sent)
     229     * represents the server name. In case of HELO it is the only element of the array.
     230     * Other values can be boolean TRUE or an array containing extension options.
     231     * If null, no HELO/EHLO string has yet been received.
     232     *
     233     * @var array|null
     234     */
     235    protected $server_caps;
     236
     237    /**
     238     * The most recent reply received from the server.
     239     *
     240     * @var string
     241     */
     242    protected $last_reply = '';
     243
     244    /**
     245     * Output debugging info via a user-selected method.
     246     *
     247     * @param string $str   Debug string to output
     248     * @param int    $level The debug level of this message; see DEBUG_* constants
     249     *
     250     * @see SMTP::$Debugoutput
     251     * @see SMTP::$do_debug
     252     */
     253    protected function edebug($str, $level = 0)
     254    {
     255        if ($level > $this->do_debug) {
     256            return;
     257        }
     258        //Is this a PSR-3 logger?
     259        if ($this->Debugoutput instanceof \Psr\Log\LoggerInterface) {
     260            $this->Debugoutput->debug($str);
     261
     262            return;
     263        }
     264        //Avoid clash with built-in function names
     265        if (is_callable($this->Debugoutput) && !in_array($this->Debugoutput, ['error_log', 'html', 'echo'])) {
     266            call_user_func($this->Debugoutput, $str, $level);
     267
     268            return;
     269        }
     270        switch ($this->Debugoutput) {
     271            case 'error_log':
     272                //Don't output, just log
     273                error_log($str);
     274                break;
     275            case 'html':
     276                //Cleans up output a bit for a better looking, HTML-safe output
     277                echo gmdate('Y-m-d H:i:s'), ' ', htmlentities(
     278                    preg_replace('/[\r\n]+/', '', $str),
     279                    ENT_QUOTES,
     280                    'UTF-8'
     281                ), "<br>\n";
     282                break;
     283            case 'echo':
     284            default:
     285                //Normalize line breaks
     286                $str = preg_replace('/\r\n|\r/m', "\n", $str);
     287                echo gmdate('Y-m-d H:i:s'),
     288                "\t",
     289                    //Trim trailing space
     290                trim(
     291                    //Indent for readability, except for trailing break
     292                    str_replace(
     293                        "\n",
     294                        "\n                   \t                  ",
     295                        trim($str)
     296                    )
     297                ),
     298                "\n";
     299        }
     300    }
     301
     302    /**
     303     * Connect to an SMTP server.
     304     *
     305     * @param string $host    SMTP server IP or host name
     306     * @param int    $port    The port number to connect to
     307     * @param int    $timeout How long to wait for the connection to open
     308     * @param array  $options An array of options for stream_context_create()
     309     *
     310     * @return bool
     311     */
     312    public function connect($host, $port = null, $timeout = 30, $options = [])
     313    {
     314        static $streamok;
     315        //This is enabled by default since 5.0.0 but some providers disable it
     316        //Check this once and cache the result
     317        if (null === $streamok) {
     318            $streamok = function_exists('stream_socket_client');
     319        }
     320        // Clear errors to avoid confusion
     321        $this->setError('');
     322        // Make sure we are __not__ connected
     323        if ($this->connected()) {
     324            // Already connected, generate error
     325            $this->setError('Already connected to a server');
     326
     327            return false;
     328        }
     329        if (empty($port)) {
     330            $port = self::DEFAULT_PORT;
     331        }
     332        // Connect to the SMTP server
     333        $this->edebug(
     334            "Connection: opening to $host:$port, timeout=$timeout, options=" .
     335            (count($options) > 0 ? var_export($options, true) : 'array()'),
     336            self::DEBUG_CONNECTION
     337        );
     338        $errno = 0;
     339        $errstr = '';
     340        if ($streamok) {
     341            $socket_context = stream_context_create($options);
     342            set_error_handler([$this, 'errorHandler']);
     343            $this->smtp_conn = stream_socket_client(
     344                $host . ':' . $port,
     345                $errno,
     346                $errstr,
     347                $timeout,
     348                STREAM_CLIENT_CONNECT,
     349                $socket_context
     350            );
     351            restore_error_handler();
     352        } else {
     353            //Fall back to fsockopen which should work in more places, but is missing some features
     354            $this->edebug(
     355                'Connection: stream_socket_client not available, falling back to fsockopen',
     356                self::DEBUG_CONNECTION
     357            );
     358            set_error_handler([$this, 'errorHandler']);
     359            $this->smtp_conn = fsockopen(
     360                $host,
     361                $port,
     362                $errno,
     363                $errstr,
     364                $timeout
     365            );
     366            restore_error_handler();
     367        }
     368        // Verify we connected properly
     369        if (!is_resource($this->smtp_conn)) {
     370            $this->setError(
     371                'Failed to connect to server',
     372                '',
     373                (string) $errno,
     374                $errstr
     375            );
     376            $this->edebug(
     377                'SMTP ERROR: ' . $this->error['error']
     378                . ": $errstr ($errno)",
     379                self::DEBUG_CLIENT
     380            );
     381
     382            return false;
     383        }
     384        $this->edebug('Connection: opened', self::DEBUG_CONNECTION);
     385        // SMTP server can take longer to respond, give longer timeout for first read
     386        // Windows does not have support for this timeout function
     387        if (strpos(PHP_OS, 'WIN') !== 0) {
     388            $max = (int) ini_get('max_execution_time');
     389            // Don't bother if unlimited
     390            if (0 !== $max && $timeout > $max) {
     391                @set_time_limit($timeout);
     392            }
     393            stream_set_timeout($this->smtp_conn, $timeout, 0);
     394        }
     395        // Get any announcement
     396        $announce = $this->get_lines();
     397        $this->edebug('SERVER -> CLIENT: ' . $announce, self::DEBUG_SERVER);
     398
     399        return true;
     400    }
     401
     402    /**
     403     * Initiate a TLS (encrypted) session.
     404     *
     405     * @return bool
     406     */
     407    public function startTLS()
     408    {
     409        if (!$this->sendCommand('STARTTLS', 'STARTTLS', 220)) {
     410            return false;
     411        }
     412
     413        //Allow the best TLS version(s) we can
     414        $crypto_method = STREAM_CRYPTO_METHOD_TLS_CLIENT;
     415
     416        //PHP 5.6.7 dropped inclusion of TLS 1.1 and 1.2 in STREAM_CRYPTO_METHOD_TLS_CLIENT
     417        //so add them back in manually if we can
     418        if (defined('STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT')) {
     419            $crypto_method |= STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT;
     420            $crypto_method |= STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT;
     421        }
     422
     423        // Begin encrypted connection
     424        set_error_handler([$this, 'errorHandler']);
     425        $crypto_ok = stream_socket_enable_crypto(
     426            $this->smtp_conn,
     427            true,
     428            $crypto_method
     429        );
     430        restore_error_handler();
     431
     432        return (bool) $crypto_ok;
     433    }
     434
     435    /**
     436     * Perform SMTP authentication.
     437     * Must be run after hello().
     438     *
     439     * @see    hello()
     440     *
     441     * @param string $username The user name
     442     * @param string $password The password
     443     * @param string $authtype The auth type (CRAM-MD5, PLAIN, LOGIN, XOAUTH2)
     444     * @param OAuth  $OAuth    An optional OAuth instance for XOAUTH2 authentication
     445     *
     446     * @return bool True if successfully authenticated
     447     */
     448    public function authenticate(
     449        $username,
     450        $password,
     451        $authtype = null,
     452        $OAuth = null
     453    ) {
     454        if (!$this->server_caps) {
     455            $this->setError('Authentication is not allowed before HELO/EHLO');
     456
     457            return false;
     458        }
     459
     460        if (array_key_exists('EHLO', $this->server_caps)) {
     461            // SMTP extensions are available; try to find a proper authentication method
     462            if (!array_key_exists('AUTH', $this->server_caps)) {
     463                $this->setError('Authentication is not allowed at this stage');
     464                // 'at this stage' means that auth may be allowed after the stage changes
     465                // e.g. after STARTTLS
     466
     467                return false;
     468            }
     469
     470            $this->edebug('Auth method requested: ' . ($authtype ?: 'UNSPECIFIED'), self::DEBUG_LOWLEVEL);
     471            $this->edebug(
     472                'Auth methods available on the server: ' . implode(',', $this->server_caps['AUTH']),
     473                self::DEBUG_LOWLEVEL
     474            );
     475
     476            //If we have requested a specific auth type, check the server supports it before trying others
     477            if (null !== $authtype && !in_array($authtype, $this->server_caps['AUTH'], true)) {
     478                $this->edebug('Requested auth method not available: ' . $authtype, self::DEBUG_LOWLEVEL);
     479                $authtype = null;
     480            }
     481
     482            if (empty($authtype)) {
     483                //If no auth mechanism is specified, attempt to use these, in this order
     484                //Try CRAM-MD5 first as it's more secure than the others
     485                foreach (['CRAM-MD5', 'LOGIN', 'PLAIN', 'XOAUTH2'] as $method) {
     486                    if (in_array($method, $this->server_caps['AUTH'], true)) {
     487                        $authtype = $method;
     488                        break;
     489                    }
     490                }
     491                if (empty($authtype)) {
     492                    $this->setError('No supported authentication methods found');
     493
     494                    return false;
     495                }
     496                $this->edebug('Auth method selected: ' . $authtype, self::DEBUG_LOWLEVEL);
     497            }
     498
     499            if (!in_array($authtype, $this->server_caps['AUTH'], true)) {
     500                $this->setError("The requested authentication method \"$authtype\" is not supported by the server");
     501
     502                return false;
     503            }
     504        } elseif (empty($authtype)) {
     505            $authtype = 'LOGIN';
     506        }
     507        switch ($authtype) {
     508            case 'PLAIN':
     509                // Start authentication
     510                if (!$this->sendCommand('AUTH', 'AUTH PLAIN', 334)) {
     511                    return false;
     512                }
     513                // Send encoded username and password
     514                if (!$this->sendCommand(
     515                    'User & Password',
     516                    base64_encode("\0" . $username . "\0" . $password),
     517                    235
     518                )
     519                ) {
     520                    return false;
     521                }
     522                break;
     523            case 'LOGIN':
     524                // Start authentication
     525                if (!$this->sendCommand('AUTH', 'AUTH LOGIN', 334)) {
     526                    return false;
     527                }
     528                if (!$this->sendCommand('Username', base64_encode($username), 334)) {
     529                    return false;
     530                }
     531                if (!$this->sendCommand('Password', base64_encode($password), 235)) {
     532                    return false;
     533                }
     534                break;
     535            case 'CRAM-MD5':
     536                // Start authentication
     537                if (!$this->sendCommand('AUTH CRAM-MD5', 'AUTH CRAM-MD5', 334)) {
     538                    return false;
     539                }
     540                // Get the challenge
     541                $challenge = base64_decode(substr($this->last_reply, 4));
     542
     543                // Build the response
     544                $response = $username . ' ' . $this->hmac($challenge, $password);
     545
     546                // send encoded credentials
     547                return $this->sendCommand('Username', base64_encode($response), 235);
     548            case 'XOAUTH2':
     549                //The OAuth instance must be set up prior to requesting auth.
     550                if (null === $OAuth) {
     551                    return false;
     552                }
     553                $oauth = $OAuth->getOauth64();
     554
     555                // Start authentication
     556                if (!$this->sendCommand('AUTH', 'AUTH XOAUTH2 ' . $oauth, 235)) {
     557                    return false;
     558                }
     559                break;
     560            default:
     561                $this->setError("Authentication method \"$authtype\" is not supported");
     562
     563                return false;
     564        }
     565
     566        return true;
     567    }
     568
     569    /**
     570     * Calculate an MD5 HMAC hash.
     571     * Works like hash_hmac('md5', $data, $key)
     572     * in case that function is not available.
     573     *
     574     * @param string $data The data to hash
     575     * @param string $key  The key to hash with
     576     *
     577     * @return string
     578     */
     579    protected function hmac($data, $key)
     580    {
     581        if (function_exists('hash_hmac')) {
     582            return hash_hmac('md5', $data, $key);
     583        }
     584
     585        // The following borrowed from
     586        // http://php.net/manual/en/function.mhash.php#27225
     587
     588        // RFC 2104 HMAC implementation for php.
     589        // Creates an md5 HMAC.
     590        // Eliminates the need to install mhash to compute a HMAC
     591        // by Lance Rushing
     592
     593        $bytelen = 64; // byte length for md5
     594        if (strlen($key) > $bytelen) {
     595            $key = pack('H*', md5($key));
     596        }
     597        $key = str_pad($key, $bytelen, chr(0x00));
     598        $ipad = str_pad('', $bytelen, chr(0x36));
     599        $opad = str_pad('', $bytelen, chr(0x5c));
     600        $k_ipad = $key ^ $ipad;
     601        $k_opad = $key ^ $opad;
     602
     603        return md5($k_opad . pack('H*', md5($k_ipad . $data)));
     604    }
     605
     606    /**
     607     * Check connection state.
     608     *
     609     * @return bool True if connected
     610     */
     611    public function connected()
     612    {
     613        if (is_resource($this->smtp_conn)) {
     614            $sock_status = stream_get_meta_data($this->smtp_conn);
     615            if ($sock_status['eof']) {
     616                // The socket is valid but we are not connected
     617                $this->edebug(
     618                    'SMTP NOTICE: EOF caught while checking if connected',
     619                    self::DEBUG_CLIENT
     620                );
     621                $this->close();
     622
     623                return false;
     624            }
     625
     626            return true; // everything looks good
     627        }
     628
     629        return false;
     630    }
     631
     632    /**
     633     * Close the socket and clean up the state of the class.
     634     * Don't use this function without first trying to use QUIT.
     635     *
     636     * @see quit()
     637     */
     638    public function close()
     639    {
     640        $this->setError('');
     641        $this->server_caps = null;
     642        $this->helo_rply = null;
     643        if (is_resource($this->smtp_conn)) {
     644            // close the connection and cleanup
     645            fclose($this->smtp_conn);
     646            $this->smtp_conn = null; //Makes for cleaner serialization
     647            $this->edebug('Connection: closed', self::DEBUG_CONNECTION);
     648        }
     649    }
     650
     651    /**
     652     * Send an SMTP DATA command.
     653     * Issues a data command and sends the msg_data to the server,
     654     * finializing the mail transaction. $msg_data is the message
     655     * that is to be send with the headers. Each header needs to be
     656     * on a single line followed by a <CRLF> with the message headers
     657     * and the message body being separated by an additional <CRLF>.
     658     * Implements RFC 821: DATA <CRLF>.
     659     *
     660     * @param string $msg_data Message data to send
     661     *
     662     * @return bool
     663     */
     664    public function data($msg_data)
     665    {
     666        //This will use the standard timelimit
     667        if (!$this->sendCommand('DATA', 'DATA', 354)) {
     668            return false;
     669        }
     670
     671        /* The server is ready to accept data!
     672         * According to rfc821 we should not send more than 1000 characters on a single line (including the LE)
     673         * so we will break the data up into lines by \r and/or \n then if needed we will break each of those into
     674         * smaller lines to fit within the limit.
     675         * We will also look for lines that start with a '.' and prepend an additional '.'.
     676         * NOTE: this does not count towards line-length limit.
     677         */
     678
     679        // Normalize line breaks before exploding
     680        $lines = explode("\n", str_replace(["\r\n", "\r"], "\n", $msg_data));
     681
     682        /* To distinguish between a complete RFC822 message and a plain message body, we check if the first field
     683         * of the first line (':' separated) does not contain a space then it _should_ be a header and we will
     684         * process all lines before a blank line as headers.
     685         */
     686
     687        $field = substr($lines[0], 0, strpos($lines[0], ':'));
     688        $in_headers = false;
     689        if (!empty($field) && strpos($field, ' ') === false) {
     690            $in_headers = true;
     691        }
     692
     693        foreach ($lines as $line) {
     694            $lines_out = [];
     695            if ($in_headers && $line === '') {
     696                $in_headers = false;
     697            }
     698            //Break this line up into several smaller lines if it's too long
     699            //Micro-optimisation: isset($str[$len]) is faster than (strlen($str) > $len),
     700            while (isset($line[self::MAX_LINE_LENGTH])) {
     701                //Working backwards, try to find a space within the last MAX_LINE_LENGTH chars of the line to break on
     702                //so as to avoid breaking in the middle of a word
     703                $pos = strrpos(substr($line, 0, self::MAX_LINE_LENGTH), ' ');
     704                //Deliberately matches both false and 0
     705                if (!$pos) {
     706                    //No nice break found, add a hard break
     707                    $pos = self::MAX_LINE_LENGTH - 1;
     708                    $lines_out[] = substr($line, 0, $pos);
     709                    $line = substr($line, $pos);
     710                } else {
     711                    //Break at the found point
     712                    $lines_out[] = substr($line, 0, $pos);
     713                    //Move along by the amount we dealt with
     714                    $line = substr($line, $pos + 1);
     715                }
     716                //If processing headers add a LWSP-char to the front of new line RFC822 section 3.1.1
     717                if ($in_headers) {
     718                    $line = "\t" . $line;
     719                }
     720            }
     721            $lines_out[] = $line;
     722
     723            //Send the lines to the server
     724            foreach ($lines_out as $line_out) {
     725                //RFC2821 section 4.5.2
     726                if (!empty($line_out) && $line_out[0] === '.') {
     727                    $line_out = '.' . $line_out;
     728                }
     729                $this->client_send($line_out . static::LE, 'DATA');
     730            }
     731        }
     732
     733        //Message data has been sent, complete the command
     734        //Increase timelimit for end of DATA command
     735        $savetimelimit = $this->Timelimit;
     736        $this->Timelimit *= 2;
     737        $result = $this->sendCommand('DATA END', '.', 250);
     738        $this->recordLastTransactionID();
     739        //Restore timelimit
     740        $this->Timelimit = $savetimelimit;
     741
     742        return $result;
     743    }
     744
     745    /**
     746     * Send an SMTP HELO or EHLO command.
     747     * Used to identify the sending server to the receiving server.
     748     * This makes sure that client and server are in a known state.
     749     * Implements RFC 821: HELO <SP> <domain> <CRLF>
     750     * and RFC 2821 EHLO.
     751     *
     752     * @param string $host The host name or IP to connect to
     753     *
     754     * @return bool
     755     */
     756    public function hello($host = '')
     757    {
     758        //Try extended hello first (RFC 2821)
     759        return $this->sendHello('EHLO', $host) or $this->sendHello('HELO', $host);
     760    }
     761
     762    /**
     763     * Send an SMTP HELO or EHLO command.
     764     * Low-level implementation used by hello().
     765     *
     766     * @param string $hello The HELO string
     767     * @param string $host  The hostname to say we are
     768     *
     769     * @return bool
     770     *
     771     * @see hello()
     772     */
     773    protected function sendHello($hello, $host)
     774    {
     775        $noerror = $this->sendCommand($hello, $hello . ' ' . $host, 250);
     776        $this->helo_rply = $this->last_reply;
     777        if ($noerror) {
     778            $this->parseHelloFields($hello);
     779        } else {
     780            $this->server_caps = null;
     781        }
     782
     783        return $noerror;
     784    }
     785
     786    /**
     787     * Parse a reply to HELO/EHLO command to discover server extensions.
     788     * In case of HELO, the only parameter that can be discovered is a server name.
     789     *
     790     * @param string $type `HELO` or `EHLO`
     791     */
     792    protected function parseHelloFields($type)
     793    {
     794        $this->server_caps = [];
     795        $lines = explode("\n", $this->helo_rply);
     796
     797        foreach ($lines as $n => $s) {
     798            //First 4 chars contain response code followed by - or space
     799            $s = trim(substr($s, 4));
     800            if (empty($s)) {
     801                continue;
     802            }
     803            $fields = explode(' ', $s);
     804            if (!empty($fields)) {
     805                if (!$n) {
     806                    $name = $type;
     807                    $fields = $fields[0];
     808                } else {
     809                    $name = array_shift($fields);
     810                    switch ($name) {
     811                        case 'SIZE':
     812                            $fields = ($fields ? $fields[0] : 0);
     813                            break;
     814                        case 'AUTH':
     815                            if (!is_array($fields)) {
     816                                $fields = [];
     817                            }
     818                            break;
     819                        default:
     820                            $fields = true;
     821                    }
     822                }
     823                $this->server_caps[$name] = $fields;
     824            }
     825        }
     826    }
     827
     828    /**
     829     * Send an SMTP MAIL command.
     830     * Starts a mail transaction from the email address specified in
     831     * $from. Returns true if successful or false otherwise. If True
     832     * the mail transaction is started and then one or more recipient
     833     * commands may be called followed by a data command.
     834     * Implements RFC 821: MAIL <SP> FROM:<reverse-path> <CRLF>.
     835     *
     836     * @param string $from Source address of this message
     837     *
     838     * @return bool
     839     */
     840    public function mail($from)
     841    {
     842        $useVerp = ($this->do_verp ? ' XVERP' : '');
     843
     844        return $this->sendCommand(
     845            'MAIL FROM',
     846            'MAIL FROM:<' . $from . '>' . $useVerp,
     847            250
     848        );
     849    }
     850
     851    /**
     852     * Send an SMTP QUIT command.
     853     * Closes the socket if there is no error or the $close_on_error argument is true.
     854     * Implements from RFC 821: QUIT <CRLF>.
     855     *
     856     * @param bool $close_on_error Should the connection close if an error occurs?
     857     *
     858     * @return bool
     859     */
     860    public function quit($close_on_error = true)
     861    {
     862        $noerror = $this->sendCommand('QUIT', 'QUIT', 221);
     863        $err = $this->error; //Save any error
     864        if ($noerror || $close_on_error) {
     865            $this->close();
     866            $this->error = $err; //Restore any error from the quit command
     867        }
     868
     869        return $noerror;
     870    }
     871
     872    /**
     873     * Send an SMTP RCPT command.
     874     * Sets the TO argument to $toaddr.
     875     * Returns true if the recipient was accepted false if it was rejected.
     876     * Implements from RFC 821: RCPT <SP> TO:<forward-path> <CRLF>.
     877     *
     878     * @param string $address The address the message is being sent to
     879     * @param string $dsn     Comma separated list of DSN notifications. NEVER, SUCCESS, FAILURE
     880     *                        or DELAY. If you specify NEVER all other notifications are ignored.
     881     *
     882     * @return bool
     883     */
     884    public function recipient($address, $dsn = '')
     885    {
     886        if (empty($dsn)) {
     887            $rcpt = 'RCPT TO:<' . $address . '>';
     888        } else {
     889            $dsn = strtoupper($dsn);
     890            $notify = [];
     891
     892            if (strpos($dsn, 'NEVER') !== false) {
     893                $notify[] = 'NEVER';
     894            } else {
     895                foreach (['SUCCESS', 'FAILURE', 'DELAY'] as $value) {
     896                    if (strpos($dsn, $value) !== false) {
     897                        $notify[] = $value;
     898                    }
     899                }
     900            }
     901
     902            $rcpt = 'RCPT TO:<' . $address . '> NOTIFY=' . implode(',', $notify);
     903        }
     904
     905        return $this->sendCommand(
     906            'RCPT TO',
     907            $rcpt,
     908            [250, 251]
     909        );
     910    }
     911
     912    /**
     913     * Send an SMTP RSET command.
     914     * Abort any transaction that is currently in progress.
     915     * Implements RFC 821: RSET <CRLF>.
     916     *
     917     * @return bool True on success
     918     */
     919    public function reset()
     920    {
     921        return $this->sendCommand('RSET', 'RSET', 250);
     922    }
     923
     924    /**
     925     * Send a command to an SMTP server and check its return code.
     926     *
     927     * @param string    $command       The command name - not sent to the server
     928     * @param string    $commandstring The actual command to send
     929     * @param int|array $expect        One or more expected integer success codes
     930     *
     931     * @return bool True on success
     932     */
     933    protected function sendCommand($command, $commandstring, $expect)
     934    {
     935        if (!$this->connected()) {
     936            $this->setError("Called $command without being connected");
     937
     938            return false;
     939        }
     940        //Reject line breaks in all commands
     941        if ((strpos($commandstring, "\n") !== false) || (strpos($commandstring, "\r") !== false)) {
     942            $this->setError("Command '$command' contained line breaks");
     943
     944            return false;
     945        }
     946        $this->client_send($commandstring . static::LE, $command);
     947
     948        $this->last_reply = $this->get_lines();
     949        // Fetch SMTP code and possible error code explanation
     950        $matches = [];
     951        if (preg_match('/^([\d]{3})[ -](?:([\d]\\.[\d]\\.[\d]{1,2}) )?/', $this->last_reply, $matches)) {
     952            $code = (int) $matches[1];
     953            $code_ex = (count($matches) > 2 ? $matches[2] : null);
     954            // Cut off error code from each response line
     955            $detail = preg_replace(
     956                "/{$code}[ -]" .
     957                ($code_ex ? str_replace('.', '\\.', $code_ex) . ' ' : '') . '/m',
     958                '',
     959                $this->last_reply
     960            );
     961        } else {
     962            // Fall back to simple parsing if regex fails
     963            $code = (int) substr($this->last_reply, 0, 3);
     964            $code_ex = null;
     965            $detail = substr($this->last_reply, 4);
     966        }
     967
     968        $this->edebug('SERVER -> CLIENT: ' . $this->last_reply, self::DEBUG_SERVER);
     969
     970        if (!in_array($code, (array) $expect, true)) {
     971            $this->setError(
     972                "$command command failed",
     973                $detail,
     974                $code,
     975                $code_ex
     976            );
     977            $this->edebug(
     978                'SMTP ERROR: ' . $this->error['error'] . ': ' . $this->last_reply,
     979                self::DEBUG_CLIENT
     980            );
     981
     982            return false;
     983        }
     984
     985        $this->setError('');
     986
     987        return true;
     988    }
     989
     990    /**
     991     * Send an SMTP SAML command.
     992     * Starts a mail transaction from the email address specified in $from.
     993     * Returns true if successful or false otherwise. If True
     994     * the mail transaction is started and then one or more recipient
     995     * commands may be called followed by a data command. This command
     996     * will send the message to the users terminal if they are logged
     997     * in and send them an email.
     998     * Implements RFC 821: SAML <SP> FROM:<reverse-path> <CRLF>.
     999     *
     1000     * @param string $from The address the message is from
     1001     *
     1002     * @return bool
     1003     */
     1004    public function sendAndMail($from)
     1005    {
     1006        return $this->sendCommand('SAML', "SAML FROM:$from", 250);
     1007    }
     1008
     1009    /**
     1010     * Send an SMTP VRFY command.
     1011     *
     1012     * @param string $name The name to verify
     1013     *
     1014     * @return bool
     1015     */
     1016    public function verify($name)
     1017    {
     1018        return $this->sendCommand('VRFY', "VRFY $name", [250, 251]);
     1019    }
     1020
     1021    /**
     1022     * Send an SMTP NOOP command.
     1023     * Used to keep keep-alives alive, doesn't actually do anything.
     1024     *
     1025     * @return bool
     1026     */
     1027    public function noop()
     1028    {
     1029        return $this->sendCommand('NOOP', 'NOOP', 250);
     1030    }
     1031
     1032    /**
     1033     * Send an SMTP TURN command.
     1034     * This is an optional command for SMTP that this class does not support.
     1035     * This method is here to make the RFC821 Definition complete for this class
     1036     * and _may_ be implemented in future.
     1037     * Implements from RFC 821: TURN <CRLF>.
     1038     *
     1039     * @return bool
     1040     */
     1041    public function turn()
     1042    {
     1043        $this->setError('The SMTP TURN command is not implemented');
     1044        $this->edebug('SMTP NOTICE: ' . $this->error['error'], self::DEBUG_CLIENT);
     1045
     1046        return false;
     1047    }
     1048
     1049    /**
     1050     * Send raw data to the server.
     1051     *
     1052     * @param string $data    The data to send
     1053     * @param string $command Optionally, the command this is part of, used only for controlling debug output
     1054     *
     1055     * @return int|bool The number of bytes sent to the server or false on error
     1056     */
     1057    public function client_send($data, $command = '')
     1058    {
     1059        //If SMTP transcripts are left enabled, or debug output is posted online
     1060        //it can leak credentials, so hide credentials in all but lowest level
     1061        if (self::DEBUG_LOWLEVEL > $this->do_debug &&
     1062            in_array($command, ['User & Password', 'Username', 'Password'], true)) {
     1063            $this->edebug('CLIENT -> SERVER: [credentials hidden]', self::DEBUG_CLIENT);
     1064        } else {
     1065            $this->edebug('CLIENT -> SERVER: ' . $data, self::DEBUG_CLIENT);
     1066        }
     1067        set_error_handler([$this, 'errorHandler']);
     1068        $result = fwrite($this->smtp_conn, $data);
     1069        restore_error_handler();
     1070
     1071        return $result;
     1072    }
     1073
     1074    /**
     1075     * Get the latest error.
     1076     *
     1077     * @return array
     1078     */
     1079    public function getError()
     1080    {
     1081        return $this->error;
     1082    }
     1083
     1084    /**
     1085     * Get SMTP extensions available on the server.
     1086     *
     1087     * @return array|null
     1088     */
     1089    public function getServerExtList()
     1090    {
     1091        return $this->server_caps;
     1092    }
     1093
     1094    /**
     1095     * Get metadata about the SMTP server from its HELO/EHLO response.
     1096     * The method works in three ways, dependent on argument value and current state:
     1097     *   1. HELO/EHLO has not been sent - returns null and populates $this->error.
     1098     *   2. HELO has been sent -
     1099     *     $name == 'HELO': returns server name
     1100     *     $name == 'EHLO': returns boolean false
     1101     *     $name == any other string: returns null and populates $this->error
     1102     *   3. EHLO has been sent -
     1103     *     $name == 'HELO'|'EHLO': returns the server name
     1104     *     $name == any other string: if extension $name exists, returns True
     1105     *       or its options (e.g. AUTH mechanisms supported). Otherwise returns False.
     1106     *
     1107     * @param string $name Name of SMTP extension or 'HELO'|'EHLO'
     1108     *
     1109     * @return string|bool|null
     1110     */
     1111    public function getServerExt($name)
     1112    {
     1113        if (!$this->server_caps) {
     1114            $this->setError('No HELO/EHLO was sent');
     1115
     1116            return;
     1117        }
     1118
     1119        if (!array_key_exists($name, $this->server_caps)) {
     1120            if ('HELO' === $name) {
     1121                return $this->server_caps['EHLO'];
     1122            }
     1123            if ('EHLO' === $name || array_key_exists('EHLO', $this->server_caps)) {
     1124                return false;
     1125            }
     1126            $this->setError('HELO handshake was used; No information about server extensions available');
     1127
     1128            return;
     1129        }
     1130
     1131        return $this->server_caps[$name];
     1132    }
     1133
     1134    /**
     1135     * Get the last reply from the server.
     1136     *
     1137     * @return string
     1138     */
     1139    public function getLastReply()
     1140    {
     1141        return $this->last_reply;
     1142    }
     1143
     1144    /**
     1145     * Read the SMTP server's response.
     1146     * Either before eof or socket timeout occurs on the operation.
     1147     * With SMTP we can tell if we have more lines to read if the
     1148     * 4th character is '-' symbol. If it is a space then we don't
     1149     * need to read anything else.
     1150     *
     1151     * @return string
     1152     */
     1153    protected function get_lines()
     1154    {
     1155        // If the connection is bad, give up straight away
     1156        if (!is_resource($this->smtp_conn)) {
     1157            return '';
     1158        }
     1159        $data = '';
     1160        $endtime = 0;
     1161        stream_set_timeout($this->smtp_conn, $this->Timeout);
     1162        if ($this->Timelimit > 0) {
     1163            $endtime = time() + $this->Timelimit;
     1164        }
     1165        $selR = [$this->smtp_conn];
     1166        $selW = null;
     1167        while (is_resource($this->smtp_conn) && !feof($this->smtp_conn)) {
     1168            //Must pass vars in here as params are by reference
     1169            if (!stream_select($selR, $selW, $selW, $this->Timelimit)) {
     1170                $this->edebug(
     1171                    'SMTP -> get_lines(): timed-out (' . $this->Timeout . ' sec)',
     1172                    self::DEBUG_LOWLEVEL
     1173                );
     1174                break;
     1175            }
     1176            //Deliberate noise suppression - errors are handled afterwards
     1177            $str = @fgets($this->smtp_conn, self::MAX_REPLY_LENGTH);
     1178            $this->edebug('SMTP INBOUND: "' . trim($str) . '"', self::DEBUG_LOWLEVEL);
     1179            $data .= $str;
     1180            // If response is only 3 chars (not valid, but RFC5321 S4.2 says it must be handled),
     1181            // or 4th character is a space or a line break char, we are done reading, break the loop.
     1182            // String array access is a significant micro-optimisation over strlen
     1183            if (!isset($str[3]) || $str[3] === ' ' || $str[3] === "\r" || $str[3] === "\n") {
     1184                break;
     1185            }
     1186            // Timed-out? Log and break
     1187            $info = stream_get_meta_data($this->smtp_conn);
     1188            if ($info['timed_out']) {
     1189                $this->edebug(
     1190                    'SMTP -> get_lines(): timed-out (' . $this->Timeout . ' sec)',
     1191                    self::DEBUG_LOWLEVEL
     1192                );
     1193                break;
     1194            }
     1195            // Now check if reads took too long
     1196            if ($endtime && time() > $endtime) {
     1197                $this->edebug(
     1198                    'SMTP -> get_lines(): timelimit reached (' .
     1199                    $this->Timelimit . ' sec)',
     1200                    self::DEBUG_LOWLEVEL
     1201                );
     1202                break;
     1203            }
     1204        }
     1205
     1206        return $data;
     1207    }
     1208
     1209    /**
     1210     * Enable or disable VERP address generation.
     1211     *
     1212     * @param bool $enabled
     1213     */
     1214    public function setVerp($enabled = false)
     1215    {
     1216        $this->do_verp = $enabled;
     1217    }
     1218
     1219    /**
     1220     * Get VERP address generation mode.
     1221     *
     1222     * @return bool
     1223     */
     1224    public function getVerp()
     1225    {
     1226        return $this->do_verp;
     1227    }
     1228
     1229    /**
     1230     * Set error messages and codes.
     1231     *
     1232     * @param string $message      The error message
     1233     * @param string $detail       Further detail on the error
     1234     * @param string $smtp_code    An associated SMTP error code
     1235     * @param string $smtp_code_ex Extended SMTP code
     1236     */
     1237    protected function setError($message, $detail = '', $smtp_code = '', $smtp_code_ex = '')
     1238    {
     1239        $this->error = [
     1240            'error' => $message,
     1241            'detail' => $detail,
     1242            'smtp_code' => $smtp_code,
     1243            'smtp_code_ex' => $smtp_code_ex,
     1244        ];
     1245    }
     1246
     1247    /**
     1248     * Set debug output method.
     1249     *
     1250     * @param string|callable $method The name of the mechanism to use for debugging output, or a callable to handle it
     1251     */
     1252    public function setDebugOutput($method = 'echo')
     1253    {
     1254        $this->Debugoutput = $method;
     1255    }
     1256
     1257    /**
     1258     * Get debug output method.
     1259     *
     1260     * @return string
     1261     */
     1262    public function getDebugOutput()
     1263    {
     1264        return $this->Debugoutput;
     1265    }
     1266
     1267    /**
     1268     * Set debug output level.
     1269     *
     1270     * @param int $level
     1271     */
     1272    public function setDebugLevel($level = 0)
     1273    {
     1274        $this->do_debug = $level;
     1275    }
     1276
     1277    /**
     1278     * Get debug output level.
     1279     *
     1280     * @return int
     1281     */
     1282    public function getDebugLevel()
     1283    {
     1284        return $this->do_debug;
     1285    }
     1286
     1287    /**
     1288     * Set SMTP timeout.
     1289     *
     1290     * @param int $timeout The timeout duration in seconds
     1291     */
     1292    public function setTimeout($timeout = 0)
     1293    {
     1294        $this->Timeout = $timeout;
     1295    }
     1296
     1297    /**
     1298     * Get SMTP timeout.
     1299     *
     1300     * @return int
     1301     */
     1302    public function getTimeout()
     1303    {
     1304        return $this->Timeout;
     1305    }
     1306
     1307    /**
     1308     * Reports an error number and string.
     1309     *
     1310     * @param int    $errno   The error number returned by PHP
     1311     * @param string $errmsg  The error message returned by PHP
     1312     * @param string $errfile The file the error occurred in
     1313     * @param int    $errline The line number the error occurred on
     1314     */
     1315    protected function errorHandler($errno, $errmsg, $errfile = '', $errline = 0)
     1316    {
     1317        $notice = 'Connection failed.';
     1318        $this->setError(
     1319            $notice,
     1320            $errmsg,
     1321            (string) $errno
     1322        );
     1323        $this->edebug(
     1324            "$notice Error #$errno: $errmsg [$errfile line $errline]",
     1325            self::DEBUG_CONNECTION
     1326        );
     1327    }
     1328
     1329    /**
     1330     * Extract and return the ID of the last SMTP transaction based on
     1331     * a list of patterns provided in SMTP::$smtp_transaction_id_patterns.
     1332     * Relies on the host providing the ID in response to a DATA command.
     1333     * If no reply has been received yet, it will return null.
     1334     * If no pattern was matched, it will return false.
     1335     *
     1336     * @return bool|string|null
     1337     */
     1338    protected function recordLastTransactionID()
     1339    {
     1340        $reply = $this->getLastReply();
     1341
     1342        if (empty($reply)) {
     1343            $this->last_smtp_transaction_id = null;
     1344        } else {
     1345            $this->last_smtp_transaction_id = false;
     1346            foreach ($this->smtp_transaction_id_patterns as $smtp_transaction_id_pattern) {
     1347                if (preg_match($smtp_transaction_id_pattern, $reply, $matches)) {
     1348                    $this->last_smtp_transaction_id = trim($matches[1]);
     1349                    break;
     1350                }
     1351            }
     1352        }
     1353
     1354        return $this->last_smtp_transaction_id;
     1355    }
     1356
     1357    /**
     1358     * Get the queue/transaction ID of the last SMTP transaction
     1359     * If no reply has been received yet, it will return null.
     1360     * If no pattern was matched, it will return false.
     1361     *
     1362     * @return bool|string|null
     1363     *
     1364     * @see recordLastTransactionID()
     1365     */
     1366    public function getLastTransactionID()
     1367    {
     1368        return $this->last_smtp_transaction_id;
     1369    }
     1370}
  • src/wp-includes/class-phpmailer.php

    diff --git a/src/wp-includes/class-phpmailer.php b/src/wp-includes/class-phpmailer.php
    index 30754a4e6c..625f816e83 100644
    a b  
    11<?php
    2 /**
    3  * PHPMailer - PHP email creation and transport class.
    4  * PHP Version 5
    5  * @package PHPMailer
    6  * @link https://github.com/PHPMailer/PHPMailer/ The PHPMailer GitHub project
    7  * @author Marcus Bointon (Synchro/coolbru) <phpmailer@synchromedia.co.uk>
    8  * @author Jim Jagielski (jimjag) <jimjag@gmail.com>
    9  * @author Andy Prevost (codeworxtech) <codeworxtech@users.sourceforge.net>
    10  * @author Brent R. Matzelle (original founder)
    11  * @copyright 2012 - 2014 Marcus Bointon
    12  * @copyright 2010 - 2012 Jim Jagielski
    13  * @copyright 2004 - 2009 Andy Prevost
    14  * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
    15  * @note This program is distributed in the hope that it will be useful - WITHOUT
    16  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
    17  * FITNESS FOR A PARTICULAR PURPOSE.
    18  */
    19 
    20 /**
    21  * PHPMailer - PHP email creation and transport class.
    22  * @package PHPMailer
    23  * @author Marcus Bointon (Synchro/coolbru) <phpmailer@synchromedia.co.uk>
    24  * @author Jim Jagielski (jimjag) <jimjag@gmail.com>
    25  * @author Andy Prevost (codeworxtech) <codeworxtech@users.sourceforge.net>
    26  * @author Brent R. Matzelle (original founder)
    27  */
    28 class PHPMailer
    29 {
    30     /**
    31      * The PHPMailer Version number.
    32      * @var string
    33      */
    34     public $Version = '5.2.27';
    35 
    36     /**
    37      * Email priority.
    38      * Options: null (default), 1 = High, 3 = Normal, 5 = low.
    39      * When null, the header is not set at all.
    40      * @var integer
    41      */
    42     public $Priority = null;
    43 
    44     /**
    45      * The character set of the message.
    46      * @var string
    47      */
    48     public $CharSet = 'iso-8859-1';
    49 
    50     /**
    51      * The MIME Content-type of the message.
    52      * @var string
    53      */
    54     public $ContentType = 'text/plain';
    55 
    56     /**
    57      * The message encoding.
    58      * Options: "8bit", "7bit", "binary", "base64", and "quoted-printable".
    59      * @var string
    60      */
    61     public $Encoding = '8bit';
    62 
    63     /**
    64      * Holds the most recent mailer error message.
    65      * @var string
    66      */
    67     public $ErrorInfo = '';
    68 
    69     /**
    70      * The From email address for the message.
    71      * @var string
    72      */
    73     public $From = 'root@localhost';
    74 
    75     /**
    76      * The From name of the message.
    77      * @var string
    78      */
    79     public $FromName = 'Root User';
    80 
    81     /**
    82      * The Sender email (Return-Path) of the message.
    83      * If not empty, will be sent via -f to sendmail or as 'MAIL FROM' in smtp mode.
    84      * @var string
    85      */
    86