Subversion Repositories cheapmusic

Rev

Go to most recent revision | Details | Last modification | View Log | RSS feed

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