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