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