"Fossies" - the Fresh Open Source Software Archive

Member "PHPMailer-6.4.1/src/PHPMailer.php" (29 Apr 2021, 170896 Bytes) of package /linux/www/PHPMailer-6.4.1.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) PHP source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "PHPMailer.php" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 6.4.0_vs_6.4.1.

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