ru-se.com

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs

PHPMailer.php (172583B)


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