Passed
Push — master ( 014c18...b2d007 )
by Richard
08:38 queued 11s
created

PHPMailer::isQmail()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 6
dl 0
loc 10
rs 10
c 0
b 0
f 0
cc 2
nc 2
nop 0
1
<?php
2
/**
3
 * PHPMailer - PHP email creation and transport class.
4
 * PHP Version 5
5
 * @package PHPMailer
6
 * @link https://github.com/PHPMailer/PHPMailer/ The PHPMailer GitHub project
7
 * @author Marcus Bointon (Synchro/coolbru) <[email protected]>
8
 * @author Jim Jagielski (jimjag) <[email protected]>
9
 * @author Andy Prevost (codeworxtech) <[email protected]>
10
 * @author Brent R. Matzelle (original founder)
11
 * @copyright 2012 - 2014 Marcus Bointon
12
 * @copyright 2010 - 2012 Jim Jagielski
13
 * @copyright 2004 - 2009 Andy Prevost
14
 * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
15
 * @note This program is distributed in the hope that it will be useful - WITHOUT
16
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
17
 * FITNESS FOR A PARTICULAR PURPOSE.
18
 */
19
20
/**
21
 * PHPMailer - PHP email creation and transport class.
22
 * @package PHPMailer
23
 * @author Marcus Bointon (Synchro/coolbru) <[email protected]>
24
 * @author Jim Jagielski (jimjag) <[email protected]>
25
 * @author Andy Prevost (codeworxtech) <[email protected]>
26
 * @author Brent R. Matzelle (original founder)
27
 */
28
class PHPMailer
29
{
30
    /**
31
     * The PHPMailer Version number.
32
     * @var string
33
     */
34
    public $Version = '5.2.28';
35
36
    /**
37
     * Email priority.
38
     * Options: null (default), 1 = High, 3 = Normal, 5 = low.
39
     * When null, the header is not set at all.
40
     * @var integer
41
     */
42
    public $Priority = null;
43
44
    /**
45
     * The character set of the message.
46
     * @var string
47
     */
48
    public $CharSet = 'iso-8859-1';
49
50
    /**
51
     * The MIME Content-type of the message.
52
     * @var string
53
     */
54
    public $ContentType = 'text/plain';
55
56
    /**
57
     * The message encoding.
58
     * Options: "8bit", "7bit", "binary", "base64", and "quoted-printable".
59
     * @var string
60
     */
61
    public $Encoding = '8bit';
62
63
    /**
64
     * Holds the most recent mailer error message.
65
     * @var string
66
     */
67
    public $ErrorInfo = '';
68
69
    /**
70
     * The From email address for the message.
71
     * @var string
72
     */
73
    public $From = 'root@localhost';
74
75
    /**
76
     * The From name of the message.
77
     * @var string
78
     */
79
    public $FromName = 'Root User';
80
81
    /**
82
     * The Sender email (Return-Path) of the message.
83
     * If not empty, will be sent via -f to sendmail or as 'MAIL FROM' in smtp mode.
84
     * @var string
85
     */
86
    public $Sender = '';
87
88
    /**
89
     * The Return-Path of the message.
90
     * If empty, it will be set to either From or Sender.
91
     * @var string
92
     * @deprecated Email senders should never set a return-path header;
93
     * it's the receiver's job (RFC5321 section 4.4), so this no longer does anything.
94
     * @link https://tools.ietf.org/html/rfc5321#section-4.4 RFC5321 reference
95
     */
96
    public $ReturnPath = '';
97
98
    /**
99
     * The Subject of the message.
100
     * @var string
101
     */
102
    public $Subject = '';
103
104
    /**
105
     * An HTML or plain text message body.
106
     * If HTML then call isHTML(true).
107
     * @var string
108
     */
109
    public $Body = '';
110
111
    /**
112
     * The plain-text message body.
113
     * This body can be read by mail clients that do not have HTML email
114
     * capability such as mutt & Eudora.
115
     * Clients that can read HTML will view the normal Body.
116
     * @var string
117
     */
118
    public $AltBody = '';
119
120
    /**
121
     * An iCal message part body.
122
     * Only supported in simple alt or alt_inline message types
123
     * To generate iCal events, use the bundled extras/EasyPeasyICS.php class or iCalcreator
124
     * @link http://sprain.ch/blog/downloads/php-class-easypeasyics-create-ical-files-with-php/
125
     * @link http://kigkonsult.se/iCalcreator/
126
     * @var string
127
     */
128
    public $Ical = '';
129
130
    /**
131
     * The complete compiled MIME message body.
132
     * @access protected
133
     * @var string
134
     */
135
    protected $MIMEBody = '';
136
137
    /**
138
     * The complete compiled MIME message headers.
139
     * @var string
140
     * @access protected
141
     */
142
    protected $MIMEHeader = '';
143
144
    /**
145
     * Extra headers that createHeader() doesn't fold in.
146
     * @var string
147
     * @access protected
148
     */
149
    protected $mailHeader = '';
150
151
    /**
152
     * Word-wrap the message body to this number of chars.
153
     * Set to 0 to not wrap. A useful value here is 78, for RFC2822 section 2.1.1 compliance.
154
     * @var integer
155
     */
156
    public $WordWrap = 0;
157
158
    /**
159
     * Which method to use to send mail.
160
     * Options: "mail", "sendmail", or "smtp".
161
     * @var string
162
     */
163
    public $Mailer = 'mail';
164
165
    /**
166
     * The path to the sendmail program.
167
     * @var string
168
     */
169
    public $Sendmail = '/usr/sbin/sendmail';
170
171
    /**
172
     * Whether mail() uses a fully sendmail-compatible MTA.
173
     * One which supports sendmail's "-oi -f" options.
174
     * @var boolean
175
     */
176
    public $UseSendmailOptions = true;
177
178
    /**
179
     * Path to PHPMailer plugins.
180
     * Useful if the SMTP class is not in the PHP include path.
181
     * @var string
182
     * @deprecated Should not be needed now there is an autoloader.
183
     */
184
    public $PluginDir = '';
185
186
    /**
187
     * The email address that a reading confirmation should be sent to, also known as read receipt.
188
     * @var string
189
     */
190
    public $ConfirmReadingTo = '';
191
192
    /**
193
     * The hostname to use in the Message-ID header and as default HELO string.
194
     * If empty, PHPMailer attempts to find one with, in order,
195
     * $_SERVER['SERVER_NAME'], gethostname(), php_uname('n'), or the value
196
     * 'localhost.localdomain'.
197
     * @var string
198
     */
199
    public $Hostname = '';
200
201
    /**
202
     * An ID to be used in the Message-ID header.
203
     * If empty, a unique id will be generated.
204
     * You can set your own, but it must be in the format "<id@domain>",
205
     * as defined in RFC5322 section 3.6.4 or it will be ignored.
206
     * @see https://tools.ietf.org/html/rfc5322#section-3.6.4
207
     * @var string
208
     */
209
    public $MessageID = '';
210
211
    /**
212
     * The message Date to be used in the Date header.
213
     * If empty, the current date will be added.
214
     * @var string
215
     */
216
    public $MessageDate = '';
217
218
    /**
219
     * SMTP hosts.
220
     * Either a single hostname or multiple semicolon-delimited hostnames.
221
     * You can also specify a different port
222
     * for each host by using this format: [hostname:port]
223
     * (e.g. "smtp1.example.com:25;smtp2.example.com").
224
     * You can also specify encryption type, for example:
225
     * (e.g. "tls://smtp1.example.com:587;ssl://smtp2.example.com:465").
226
     * Hosts will be tried in order.
227
     * @var string
228
     */
229
    public $Host = 'localhost';
230
231
    /**
232
     * The default SMTP server port.
233
     * @var integer
234
     * @TODO Why is this needed when the SMTP class takes care of it?
235
     */
236
    public $Port = 25;
237
238
    /**
239
     * The SMTP HELO of the message.
240
     * Default is $Hostname. If $Hostname is empty, PHPMailer attempts to find
241
     * one with the same method described above for $Hostname.
242
     * @var string
243
     * @see PHPMailer::$Hostname
244
     */
245
    public $Helo = '';
246
247
    /**
248
     * What kind of encryption to use on the SMTP connection.
249
     * Options: '', 'ssl' or 'tls'
250
     * @var string
251
     */
252
    public $SMTPSecure = '';
253
254
    /**
255
     * Whether to enable TLS encryption automatically if a server supports it,
256
     * even if `SMTPSecure` is not set to 'tls'.
257
     * Be aware that in PHP >= 5.6 this requires that the server's certificates are valid.
258
     * @var boolean
259
     */
260
    public $SMTPAutoTLS = true;
261
262
    /**
263
     * Whether to use SMTP authentication.
264
     * Uses the Username and Password properties.
265
     * @var boolean
266
     * @see PHPMailer::$Username
267
     * @see PHPMailer::$Password
268
     */
269
    public $SMTPAuth = false;
270
271
    /**
272
     * Options array passed to stream_context_create when connecting via SMTP.
273
     * @var array
274
     */
275
    public $SMTPOptions = array();
276
277
    /**
278
     * SMTP username.
279
     * @var string
280
     */
281
    public $Username = '';
282
283
    /**
284
     * SMTP password.
285
     * @var string
286
     */
287
    public $Password = '';
288
289
    /**
290
     * SMTP auth type.
291
     * Options are CRAM-MD5, LOGIN, PLAIN, NTLM, XOAUTH2, attempted in that order if not specified
292
     * @var string
293
     */
294
    public $AuthType = '';
295
296
    /**
297
     * SMTP realm.
298
     * Used for NTLM auth
299
     * @var string
300
     */
301
    public $Realm = '';
302
303
    /**
304
     * SMTP workstation.
305
     * Used for NTLM auth
306
     * @var string
307
     */
308
    public $Workstation = '';
309
310
    /**
311
     * The SMTP server timeout in seconds.
312
     * Default of 5 minutes (300sec) is from RFC2821 section 4.5.3.2
313
     * @var integer
314
     */
315
    public $Timeout = 300;
316
317
    /**
318
     * SMTP class debug output mode.
319
     * Debug output level.
320
     * Options:
321
     * * `0` No output
322
     * * `1` Commands
323
     * * `2` Data and commands
324
     * * `3` As 2 plus connection status
325
     * * `4` Low-level data output
326
     * @var integer
327
     * @see SMTP::$do_debug
328
     */
329
    public $SMTPDebug = 0;
330
331
    /**
332
     * How to handle debug output.
333
     * Options:
334
     * * `echo` Output plain-text as-is, appropriate for CLI
335
     * * `html` Output escaped, line breaks converted to `<br>`, appropriate for browser output
336
     * * `error_log` Output to error log as configured in php.ini
337
     *
338
     * Alternatively, you can provide a callable expecting two params: a message string and the debug level:
339
     * <code>
340
     * $mail->Debugoutput = function($str, $level) {echo "debug level $level; message: $str";};
341
     * </code>
342
     * @var string|callable
343
     * @see SMTP::$Debugoutput
344
     */
345
    public $Debugoutput = 'echo';
346
347
    /**
348
     * Whether to keep SMTP connection open after each message.
349
     * If this is set to true then to close the connection
350
     * requires an explicit call to smtpClose().
351
     * @var boolean
352
     */
353
    public $SMTPKeepAlive = false;
354
355
    /**
356
     * Whether to split multiple to addresses into multiple messages
357
     * or send them all in one message.
358
     * Only supported in `mail` and `sendmail` transports, not in SMTP.
359
     * @var boolean
360
     */
361
    public $SingleTo = false;
362
363
    /**
364
     * Storage for addresses when SingleTo is enabled.
365
     * @var array
366
     * @TODO This should really not be public
367
     */
368
    public $SingleToArray = array();
369
370
    /**
371
     * Whether to generate VERP addresses on send.
372
     * Only applicable when sending via SMTP.
373
     * @link https://en.wikipedia.org/wiki/Variable_envelope_return_path
374
     * @link http://www.postfix.org/VERP_README.html Postfix VERP info
375
     * @var boolean
376
     */
377
    public $do_verp = false;
378
379
    /**
380
     * Whether to allow sending messages with an empty body.
381
     * @var boolean
382
     */
383
    public $AllowEmpty = false;
384
385
    /**
386
     * The default line ending.
387
     * @note The default remains "\n". We force CRLF where we know
388
     *        it must be used via self::CRLF.
389
     * @var string
390
     */
391
    public $LE = "\n";
392
393
    /**
394
     * DKIM selector.
395
     * @var string
396
     */
397
    public $DKIM_selector = '';
398
399
    /**
400
     * DKIM Identity.
401
     * Usually the email address used as the source of the email.
402
     * @var string
403
     */
404
    public $DKIM_identity = '';
405
406
    /**
407
     * DKIM passphrase.
408
     * Used if your key is encrypted.
409
     * @var string
410
     */
411
    public $DKIM_passphrase = '';
412
413
    /**
414
     * DKIM signing domain name.
415
     * @example 'example.com'
416
     * @var string
417
     */
418
    public $DKIM_domain = '';
419
420
    /**
421
     * DKIM private key file path.
422
     * @var string
423
     */
424
    public $DKIM_private = '';
425
426
    /**
427
     * DKIM private key string.
428
     * If set, takes precedence over `$DKIM_private`.
429
     * @var string
430
     */
431
    public $DKIM_private_string = '';
432
433
    /**
434
     * Callback Action function name.
435
     *
436
     * The function that handles the result of the send email action.
437
     * It is called out by send() for each email sent.
438
     *
439
     * Value can be any php callable: http://www.php.net/is_callable
440
     *
441
     * Parameters:
442
     *   boolean $result        result of the send action
443
     *   array   $to            email addresses of the recipients
444
     *   array   $cc            cc email addresses
445
     *   array   $bcc           bcc email addresses
446
     *   string  $subject       the subject
447
     *   string  $body          the email body
448
     *   string  $from          email address of sender
449
     * @var string
450
     */
451
    public $action_function = '';
452
453
    /**
454
     * What to put in the X-Mailer header.
455
     * Options: An empty string for PHPMailer default, whitespace for none, or a string to use
456
     * @var string
457
     */
458
    public $XMailer = '';
459
460
    /**
461
     * Which validator to use by default when validating email addresses.
462
     * May be a callable to inject your own validator, but there are several built-in validators.
463
     * @see PHPMailer::validateAddress()
464
     * @var string|callable
465
     * @static
466
     */
467
    public static $validator = 'auto';
468
469
    /**
470
     * An instance of the SMTP sender class.
471
     * @var SMTP
472
     * @access protected
473
     */
474
    protected $smtp = null;
475
476
    /**
477
     * The array of 'to' names and addresses.
478
     * @var array
479
     * @access protected
480
     */
481
    protected $to = array();
482
483
    /**
484
     * The array of 'cc' names and addresses.
485
     * @var array
486
     * @access protected
487
     */
488
    protected $cc = array();
489
490
    /**
491
     * The array of 'bcc' names and addresses.
492
     * @var array
493
     * @access protected
494
     */
495
    protected $bcc = array();
496
497
    /**
498
     * The array of reply-to names and addresses.
499
     * @var array
500
     * @access protected
501
     */
502
    protected $ReplyTo = array();
503
504
    /**
505
     * An array of all kinds of addresses.
506
     * Includes all of $to, $cc, $bcc
507
     * @var array
508
     * @access protected
509
     * @see PHPMailer::$to @see PHPMailer::$cc @see PHPMailer::$bcc
510
     */
511
    protected $all_recipients = array();
512
513
    /**
514
     * An array of names and addresses queued for validation.
515
     * In send(), valid and non duplicate entries are moved to $all_recipients
516
     * and one of $to, $cc, or $bcc.
517
     * This array is used only for addresses with IDN.
518
     * @var array
519
     * @access protected
520
     * @see PHPMailer::$to @see PHPMailer::$cc @see PHPMailer::$bcc
521
     * @see PHPMailer::$all_recipients
522
     */
523
    protected $RecipientsQueue = array();
524
525
    /**
526
     * An array of reply-to names and addresses queued for validation.
527
     * In send(), valid and non duplicate entries are moved to $ReplyTo.
528
     * This array is used only for addresses with IDN.
529
     * @var array
530
     * @access protected
531
     * @see PHPMailer::$ReplyTo
532
     */
533
    protected $ReplyToQueue = array();
534
535
    /**
536
     * The array of attachments.
537
     * @var array
538
     * @access protected
539
     */
540
    protected $attachment = array();
541
542
    /**
543
     * The array of custom headers.
544
     * @var array
545
     * @access protected
546
     */
547
    protected $CustomHeader = array();
548
549
    /**
550
     * The most recent Message-ID (including angular brackets).
551
     * @var string
552
     * @access protected
553
     */
554
    protected $lastMessageID = '';
555
556
    /**
557
     * The message's MIME type.
558
     * @var string
559
     * @access protected
560
     */
561
    protected $message_type = '';
562
563
    /**
564
     * The array of MIME boundary strings.
565
     * @var array
566
     * @access protected
567
     */
568
    protected $boundary = array();
569
570
    /**
571
     * The array of available languages.
572
     * @var array
573
     * @access protected
574
     */
575
    protected $language = array();
576
577
    /**
578
     * The number of errors encountered.
579
     * @var integer
580
     * @access protected
581
     */
582
    protected $error_count = 0;
583
584
    /**
585
     * The S/MIME certificate file path.
586
     * @var string
587
     * @access protected
588
     */
589
    protected $sign_cert_file = '';
590
591
    /**
592
     * The S/MIME key file path.
593
     * @var string
594
     * @access protected
595
     */
596
    protected $sign_key_file = '';
597
598
    /**
599
     * The optional S/MIME extra certificates ("CA Chain") file path.
600
     * @var string
601
     * @access protected
602
     */
603
    protected $sign_extracerts_file = '';
604
605
    /**
606
     * The S/MIME password for the key.
607
     * Used only if the key is encrypted.
608
     * @var string
609
     * @access protected
610
     */
611
    protected $sign_key_pass = '';
612
613
    /**
614
     * Whether to throw exceptions for errors.
615
     * @var boolean
616
     * @access protected
617
     */
618
    protected $exceptions = false;
619
620
    /**
621
     * Unique ID used for message ID and boundaries.
622
     * @var string
623
     * @access protected
624
     */
625
    protected $uniqueid = '';
626
627
    /**
628
     * Error severity: message only, continue processing.
629
     */
630
    const STOP_MESSAGE = 0;
631
632
    /**
633
     * Error severity: message, likely ok to continue processing.
634
     */
635
    const STOP_CONTINUE = 1;
636
637
    /**
638
     * Error severity: message, plus full stop, critical error reached.
639
     */
640
    const STOP_CRITICAL = 2;
641
642
    /**
643
     * SMTP RFC standard line ending.
644
     */
645
    const CRLF = "\r\n";
646
647
    /**
648
     * The maximum line length allowed by RFC 2822 section 2.1.1
649
     * @var integer
650
     */
651
    const MAX_LINE_LENGTH = 998;
652
653
    /**
654
     * Constructor.
655
     * @param boolean $exceptions Should we throw external exceptions?
656
     */
657
    public function __construct($exceptions = null)
658
    {
659
        if ($exceptions !== null) {
660
            $this->exceptions = (boolean)$exceptions;
661
        }
662
        //Pick an appropriate debug output format automatically
663
        $this->Debugoutput = (strpos(PHP_SAPI, 'cli') !== false ? 'echo' : 'html');
664
    }
665
666
    /**
667
     * Destructor.
668
     */
669
    public function __destruct()
670
    {
671
        //Close any open SMTP connection nicely
672
        $this->smtpClose();
673
    }
674
675
    /**
676
     * Call mail() in a safe_mode-aware fashion.
677
     * Also, unless sendmail_path points to sendmail (or something that
678
     * claims to be sendmail), don't pass params (not a perfect fix,
679
     * but it will do)
680
     * @param string $to To
681
     * @param string $subject Subject
682
     * @param string $body Message Body
683
     * @param string $header Additional Header(s)
684
     * @param string $params Params
685
     * @access private
686
     * @return boolean
687
     */
688
    private function mailPassthru($to, $subject, $body, $header, $params)
689
    {
690
        //Check overloading of mail function to avoid double-encoding
691
        if (ini_get('mbstring.func_overload') & 1) {
692
            $subject = $this->secureHeader($subject);
693
        } else {
694
            $subject = $this->encodeHeader($this->secureHeader($subject));
695
        }
696
697
        //Can't use additional_parameters in safe_mode, calling mail() with null params breaks
698
        //@link http://php.net/manual/en/function.mail.php
699
        if (ini_get('safe_mode') or !$this->UseSendmailOptions or is_null($params)) {
700
            $result = @mail($to, $subject, $body, $header);
701
        } else {
702
            $result = @mail($to, $subject, $body, $header, $params);
703
        }
704
        return $result;
705
    }
706
    /**
707
     * Output debugging info via user-defined method.
708
     * Only generates output if SMTP debug output is enabled (@see SMTP::$do_debug).
709
     * @see PHPMailer::$Debugoutput
710
     * @see PHPMailer::$SMTPDebug
711
     * @param string $str
712
     */
713
    protected function edebug($str)
714
    {
715
        if ($this->SMTPDebug <= 0) {
716
            return;
717
        }
718
        //Avoid clash with built-in function names
719
        if (!in_array($this->Debugoutput, array('error_log', 'html', 'echo')) and is_callable($this->Debugoutput)) {
720
            call_user_func($this->Debugoutput, $str, $this->SMTPDebug);
721
            return;
722
        }
723
        switch ($this->Debugoutput) {
724
            case 'error_log':
725
                //Don't output, just log
726
                error_log($str);
727
                break;
728
            case 'html':
729
                //Cleans up output a bit for a better looking, HTML-safe output
730
                echo htmlentities(
731
                    preg_replace('/[\r\n]+/', '', $str),
732
                    ENT_QUOTES,
733
                    'UTF-8'
734
                )
735
                . "<br>\n";
736
                break;
737
            case 'echo':
738
            default:
739
                //Normalize line breaks
740
                $str = preg_replace('/\r\n?/ms', "\n", $str);
741
                echo gmdate('Y-m-d H:i:s') . "\t" . str_replace(
742
                    "\n",
743
                    "\n                   \t                  ",
744
                    trim($str)
745
                ) . "\n";
746
        }
747
    }
748
749
    /**
750
     * Sets message type to HTML or plain.
751
     * @param boolean $isHtml True for HTML mode.
752
     * @return void
753
     */
754
    public function isHTML($isHtml = true)
755
    {
756
        if ($isHtml) {
757
            $this->ContentType = 'text/html';
758
        } else {
759
            $this->ContentType = 'text/plain';
760
        }
761
    }
762
763
    /**
764
     * Send messages using SMTP.
765
     * @return void
766
     */
767
    public function isSMTP()
768
    {
769
        $this->Mailer = 'smtp';
770
    }
771
772
    /**
773
     * Send messages using PHP's mail() function.
774
     * @return void
775
     */
776
    public function isMail()
777
    {
778
        $this->Mailer = 'mail';
779
    }
780
781
    /**
782
     * Send messages using $Sendmail.
783
     * @return void
784
     */
785
    public function isSendmail()
786
    {
787
        $ini_sendmail_path = ini_get('sendmail_path');
788
789
        if (!stristr($ini_sendmail_path, 'sendmail')) {
790
            $this->Sendmail = '/usr/sbin/sendmail';
791
        } else {
792
            $this->Sendmail = $ini_sendmail_path;
793
        }
794
        $this->Mailer = 'sendmail';
795
    }
796
797
    /**
798
     * Send messages using qmail.
799
     * @return void
800
     */
801
    public function isQmail()
802
    {
803
        $ini_sendmail_path = ini_get('sendmail_path');
804
805
        if (!stristr($ini_sendmail_path, 'qmail')) {
806
            $this->Sendmail = '/var/qmail/bin/qmail-inject';
807
        } else {
808
            $this->Sendmail = $ini_sendmail_path;
809
        }
810
        $this->Mailer = 'qmail';
811
    }
812
813
    /**
814
     * Add a "To" address.
815
     * @param string $address The email address to send to
816
     * @param string $name
817
     * @return boolean true on success, false if address already used or invalid in some way
818
     */
819
    public function addAddress($address, $name = '')
820
    {
821
        return $this->addOrEnqueueAnAddress('to', $address, $name);
822
    }
823
824
    /**
825
     * Add a "CC" address.
826
     * @note: This function works with the SMTP mailer on win32, not with the "mail" mailer.
827
     * @param string $address The email address to send to
828
     * @param string $name
829
     * @return boolean true on success, false if address already used or invalid in some way
830
     */
831
    public function addCC($address, $name = '')
832
    {
833
        return $this->addOrEnqueueAnAddress('cc', $address, $name);
834
    }
835
836
    /**
837
     * Add a "BCC" address.
838
     * @note: This function works with the SMTP mailer on win32, not with the "mail" mailer.
839
     * @param string $address The email address to send to
840
     * @param string $name
841
     * @return boolean true on success, false if address already used or invalid in some way
842
     */
843
    public function addBCC($address, $name = '')
844
    {
845
        return $this->addOrEnqueueAnAddress('bcc', $address, $name);
846
    }
847
848
    /**
849
     * Add a "Reply-To" address.
850
     * @param string $address The email address to reply to
851
     * @param string $name
852
     * @return boolean true on success, false if address already used or invalid in some way
853
     */
854
    public function addReplyTo($address, $name = '')
855
    {
856
        return $this->addOrEnqueueAnAddress('Reply-To', $address, $name);
857
    }
858
859
    /**
860
     * Add an address to one of the recipient arrays or to the ReplyTo array. Because PHPMailer
861
     * can't validate addresses with an IDN without knowing the PHPMailer::$CharSet (that can still
862
     * be modified after calling this function), addition of such addresses is delayed until send().
863
     * Addresses that have been added already return false, but do not throw exceptions.
864
     * @param string $kind One of 'to', 'cc', 'bcc', or 'ReplyTo'
865
     * @param string $address The email address to send, resp. to reply to
866
     * @param string $name
867
     * @throws phpmailerException
868
     * @return boolean true on success, false if address already used or invalid in some way
869
     * @access protected
870
     */
871
    protected function addOrEnqueueAnAddress($kind, $address, $name)
872
    {
873
        $address = trim($address);
874
        $name = trim(preg_replace('/[\r\n]+/', '', $name)); //Strip breaks and trim
875
        if (($pos = strrpos($address, '@')) === false) {
876
            // At-sign is misssing.
877
            $error_message = $this->lang('invalid_address') . " (addAnAddress $kind): $address";
878
            $this->setError($error_message);
879
            $this->edebug($error_message);
880
            if ($this->exceptions) {
881
                throw new phpmailerException($error_message);
882
            }
883
            return false;
884
        }
885
        $params = array($kind, $address, $name);
886
        // Enqueue addresses with IDN until we know the PHPMailer::$CharSet.
887
        if ($this->has8bitChars(substr($address, ++$pos)) and $this->idnSupported()) {
888
            if ($kind != 'Reply-To') {
889
                if (!array_key_exists($address, $this->RecipientsQueue)) {
890
                    $this->RecipientsQueue[$address] = $params;
891
                    return true;
892
                }
893
            } else {
894
                if (!array_key_exists($address, $this->ReplyToQueue)) {
895
                    $this->ReplyToQueue[$address] = $params;
896
                    return true;
897
                }
898
            }
899
            return false;
900
        }
901
        // Immediately add standard addresses without IDN.
902
        return call_user_func_array(array($this, 'addAnAddress'), $params);
903
    }
904
905
    /**
906
     * Add an address to one of the recipient arrays or to the ReplyTo array.
907
     * Addresses that have been added already return false, but do not throw exceptions.
908
     * @param string $kind One of 'to', 'cc', 'bcc', or 'ReplyTo'
909
     * @param string $address The email address to send, resp. to reply to
910
     * @param string $name
911
     * @throws phpmailerException
912
     * @return boolean true on success, false if address already used or invalid in some way
913
     * @access protected
914
     */
915
    protected function addAnAddress($kind, $address, $name = '')
916
    {
917
        if (!in_array($kind, array('to', 'cc', 'bcc', 'Reply-To'))) {
918
            $error_message = $this->lang('Invalid recipient kind: ') . $kind;
919
            $this->setError($error_message);
920
            $this->edebug($error_message);
921
            if ($this->exceptions) {
922
                throw new phpmailerException($error_message);
923
            }
924
            return false;
925
        }
926
        if (!$this->validateAddress($address)) {
927
            $error_message = $this->lang('invalid_address') . " (addAnAddress $kind): $address";
928
            $this->setError($error_message);
929
            $this->edebug($error_message);
930
            if ($this->exceptions) {
931
                throw new phpmailerException($error_message);
932
            }
933
            return false;
934
        }
935
        if ($kind != 'Reply-To') {
936
            if (!array_key_exists(strtolower($address), $this->all_recipients)) {
937
                array_push($this->$kind, array($address, $name));
938
                $this->all_recipients[strtolower($address)] = true;
939
                return true;
940
            }
941
        } else {
942
            if (!array_key_exists(strtolower($address), $this->ReplyTo)) {
943
                $this->ReplyTo[strtolower($address)] = array($address, $name);
944
                return true;
945
            }
946
        }
947
        return false;
948
    }
949
950
    /**
951
     * Parse and validate a string containing one or more RFC822-style comma-separated email addresses
952
     * of the form "display name <address>" into an array of name/address pairs.
953
     * Uses the imap_rfc822_parse_adrlist function if the IMAP extension is available.
954
     * Note that quotes in the name part are removed.
955
     * @param string $addrstr The address list string
956
     * @param bool $useimap Whether to use the IMAP extension to parse the list
957
     * @return array
958
     * @link http://www.andrew.cmu.edu/user/agreen1/testing/mrbs/web/Mail/RFC822.php A more careful implementation
959
     */
960
    public function parseAddresses($addrstr, $useimap = true)
961
    {
962
        $addresses = array();
963
        if ($useimap and function_exists('imap_rfc822_parse_adrlist')) {
964
            //Use this built-in parser if it's available
965
            $list = imap_rfc822_parse_adrlist($addrstr, '');
966
            foreach ($list as $address) {
967
                if ($address->host != '.SYNTAX-ERROR.') {
968
                    if ($this->validateAddress($address->mailbox . '@' . $address->host)) {
969
                        $addresses[] = array(
970
                            'name' => (property_exists($address, 'personal') ? $address->personal : ''),
971
                            'address' => $address->mailbox . '@' . $address->host
972
                        );
973
                    }
974
                }
975
            }
976
        } else {
977
            //Use this simpler parser
978
            $list = explode(',', $addrstr);
979
            foreach ($list as $address) {
980
                $address = trim($address);
981
                //Is there a separate name part?
982
                if (strpos($address, '<') === false) {
983
                    //No separate name, just use the whole thing
984
                    if ($this->validateAddress($address)) {
985
                        $addresses[] = array(
986
                            'name' => '',
987
                            'address' => $address
988
                        );
989
                    }
990
                } else {
991
                    list($name, $email) = explode('<', $address);
992
                    $email = trim(str_replace('>', '', $email));
993
                    if ($this->validateAddress($email)) {
994
                        $addresses[] = array(
995
                            'name' => trim(str_replace(array('"', "'"), '', $name)),
996
                            'address' => $email
997
                        );
998
                    }
999
                }
1000
            }
1001
        }
1002
        return $addresses;
1003
    }
1004
1005
    /**
1006
     * Set the From and FromName properties.
1007
     * @param string $address
1008
     * @param string $name
1009
     * @param boolean $auto Whether to also set the Sender address, defaults to true
1010
     * @throws phpmailerException
1011
     * @return boolean
1012
     */
1013
    public function setFrom($address, $name = '', $auto = true)
1014
    {
1015
        $address = trim($address);
1016
        $name = trim(preg_replace('/[\r\n]+/', '', $name)); //Strip breaks and trim
1017
        // Don't validate now addresses with IDN. Will be done in send().
1018
        if (($pos = strrpos($address, '@')) === false or
1019
            (!$this->has8bitChars(substr($address, ++$pos)) or !$this->idnSupported()) and
1020
            !$this->validateAddress($address)) {
1021
            $error_message = $this->lang('invalid_address') . " (setFrom) $address";
1022
            $this->setError($error_message);
1023
            $this->edebug($error_message);
1024
            if ($this->exceptions) {
1025
                throw new phpmailerException($error_message);
1026
            }
1027
            return false;
1028
        }
1029
        $this->From = $address;
1030
        $this->FromName = $name;
1031
        if ($auto) {
1032
            if (empty($this->Sender)) {
1033
                $this->Sender = $address;
1034
            }
1035
        }
1036
        return true;
1037
    }
1038
1039
    /**
1040
     * Return the Message-ID header of the last email.
1041
     * Technically this is the value from the last time the headers were created,
1042
     * but it's also the message ID of the last sent message except in
1043
     * pathological cases.
1044
     * @return string
1045
     */
1046
    public function getLastMessageID()
1047
    {
1048
        return $this->lastMessageID;
1049
    }
1050
1051
    /**
1052
     * Check that a string looks like an email address.
1053
     * @param string $address The email address to check
1054
     * @param string|callable $patternselect A selector for the validation pattern to use :
1055
     * * `auto` Pick best pattern automatically;
1056
     * * `pcre8` Use the squiloople.com pattern, requires PCRE > 8.0, PHP >= 5.3.2, 5.2.14;
1057
     * * `pcre` Use old PCRE implementation;
1058
     * * `php` Use PHP built-in FILTER_VALIDATE_EMAIL;
1059
     * * `html5` Use the pattern given by the HTML5 spec for 'email' type form input elements.
1060
     * * `noregex` Don't use a regex: super fast, really dumb.
1061
     * Alternatively you may pass in a callable to inject your own validator, for example:
1062
     * PHPMailer::validateAddress('[email protected]', function($address) {
1063
     *     return (strpos($address, '@') !== false);
1064
     * });
1065
     * You can also set the PHPMailer::$validator static to a callable, allowing built-in methods to use your validator.
1066
     * @return boolean
1067
     * @static
1068
     * @access public
1069
     */
1070
    public static function validateAddress($address, $patternselect = null)
1071
    {
1072
        if (is_null($patternselect)) {
1073
            $patternselect = self::$validator;
1074
        }
1075
        if (is_callable($patternselect)) {
1076
            return call_user_func($patternselect, $address);
1077
        }
1078
        //Reject line breaks in addresses; it's valid RFC5322, but not RFC5321
1079
        if (strpos($address, "\n") !== false or strpos($address, "\r") !== false) {
1080
            return false;
1081
        }
1082
        if (!$patternselect or $patternselect == 'auto') {
1083
            //Check this constant first so it works when extension_loaded() is disabled by safe mode
1084
            //Constant was added in PHP 5.2.4
1085
            if (defined('PCRE_VERSION')) {
1086
                //This pattern can get stuck in a recursive loop in PCRE <= 8.0.2
1087
                if (version_compare(PCRE_VERSION, '8.0.3') >= 0) {
1088
                    $patternselect = 'pcre8';
1089
                } else {
1090
                    $patternselect = 'pcre';
1091
                }
1092
            } elseif (function_exists('extension_loaded') and extension_loaded('pcre')) {
1093
                //Fall back to older PCRE
1094
                $patternselect = 'pcre';
1095
            } else {
1096
                //Filter_var appeared in PHP 5.2.0 and does not require the PCRE extension
1097
                if (version_compare(PHP_VERSION, '5.2.0') >= 0) {
1098
                    $patternselect = 'php';
1099
                } else {
1100
                    $patternselect = 'noregex';
1101
                }
1102
            }
1103
        }
1104
        switch ($patternselect) {
1105
            case 'pcre8':
1106
                /**
1107
                 * Uses the same RFC5322 regex on which FILTER_VALIDATE_EMAIL is based, but allows dotless domains.
1108
                 * @link http://squiloople.com/2009/12/20/email-address-validation/
1109
                 * @copyright 2009-2010 Michael Rushton
1110
                 * Feel free to use and redistribute this code. But please keep this copyright notice.
1111
                 */
1112
                return (boolean)preg_match(
1113
                    '/^(?!(?>(?1)"?(?>\\\[ -~]|[^"])"?(?1)){255,})(?!(?>(?1)"?(?>\\\[ -~]|[^"])"?(?1)){65,}@)' .
1114
                    '((?>(?>(?>((?>(?>(?>\x0D\x0A)?[\t ])+|(?>[\t ]*\x0D\x0A)?[\t ]+)?)(\((?>(?2)' .
1115
                    '(?>[\x01-\x08\x0B\x0C\x0E-\'*-\[\]-\x7F]|\\\[\x00-\x7F]|(?3)))*(?2)\)))+(?2))|(?2))?)' .
1116
                    '([!#-\'*+\/-9=?^-~-]+|"(?>(?2)(?>[\x01-\x08\x0B\x0C\x0E-!#-\[\]-\x7F]|\\\[\x00-\x7F]))*' .
1117
                    '(?2)")(?>(?1)\.(?1)(?4))*(?1)@(?!(?1)[a-z0-9-]{64,})(?1)(?>([a-z0-9](?>[a-z0-9-]*[a-z0-9])?)' .
1118
                    '(?>(?1)\.(?!(?1)[a-z0-9-]{64,})(?1)(?5)){0,126}|\[(?:(?>IPv6:(?>([a-f0-9]{1,4})(?>:(?6)){7}' .
1119
                    '|(?!(?:.*[a-f0-9][:\]]){8,})((?6)(?>:(?6)){0,6})?::(?7)?))|(?>(?>IPv6:(?>(?6)(?>:(?6)){5}:' .
1120
                    '|(?!(?:.*[a-f0-9]:){6,})(?8)?::(?>((?6)(?>:(?6)){0,4}):)?))?(25[0-5]|2[0-4][0-9]|1[0-9]{2}' .
1121
                    '|[1-9]?[0-9])(?>\.(?9)){3}))\])(?1)$/isD',
1122
                    $address
1123
                );
1124
            case 'pcre':
1125
                //An older regex that doesn't need a recent PCRE
1126
                return (boolean)preg_match(
1127
                    '/^(?!(?>"?(?>\\\[ -~]|[^"])"?){255,})(?!(?>"?(?>\\\[ -~]|[^"])"?){65,}@)(?>' .
1128
                    '[!#-\'*+\/-9=?^-~-]+|"(?>(?>[\x01-\x08\x0B\x0C\x0E-!#-\[\]-\x7F]|\\\[\x00-\xFF]))*")' .
1129
                    '(?>\.(?>[!#-\'*+\/-9=?^-~-]+|"(?>(?>[\x01-\x08\x0B\x0C\x0E-!#-\[\]-\x7F]|\\\[\x00-\xFF]))*"))*' .
1130
                    '@(?>(?![a-z0-9-]{64,})(?>[a-z0-9](?>[a-z0-9-]*[a-z0-9])?)(?>\.(?![a-z0-9-]{64,})' .
1131
                    '(?>[a-z0-9](?>[a-z0-9-]*[a-z0-9])?)){0,126}|\[(?:(?>IPv6:(?>(?>[a-f0-9]{1,4})(?>:' .
1132
                    '[a-f0-9]{1,4}){7}|(?!(?:.*[a-f0-9][:\]]){8,})(?>[a-f0-9]{1,4}(?>:[a-f0-9]{1,4}){0,6})?' .
1133
                    '::(?>[a-f0-9]{1,4}(?>:[a-f0-9]{1,4}){0,6})?))|(?>(?>IPv6:(?>[a-f0-9]{1,4}(?>:' .
1134
                    '[a-f0-9]{1,4}){5}:|(?!(?:.*[a-f0-9]:){6,})(?>[a-f0-9]{1,4}(?>:[a-f0-9]{1,4}){0,4})?' .
1135
                    '::(?>(?:[a-f0-9]{1,4}(?>:[a-f0-9]{1,4}){0,4}):)?))?(?>25[0-5]|2[0-4][0-9]|1[0-9]{2}' .
1136
                    '|[1-9]?[0-9])(?>\.(?>25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3}))\])$/isD',
1137
                    $address
1138
                );
1139
            case 'html5':
1140
                /**
1141
                 * This is the pattern used in the HTML5 spec for validation of 'email' type form input elements.
1142
                 * @link http://www.whatwg.org/specs/web-apps/current-work/#e-mail-state-(type=email)
1143
                 */
1144
                return (boolean)preg_match(
1145
                    '/^[a-zA-Z0-9.!#$%&\'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}' .
1146
                    '[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/sD',
1147
                    $address
1148
                );
1149
            case 'noregex':
1150
                //No PCRE! Do something _very_ approximate!
1151
                //Check the address is 3 chars or longer and contains an @ that's not the first or last char
1152
                return (strlen($address) >= 3
1153
                    and strpos($address, '@') >= 1
1154
                    and strpos($address, '@') != strlen($address) - 1);
1155
            case 'php':
1156
            default:
1157
                return (boolean)filter_var($address, FILTER_VALIDATE_EMAIL);
1158
        }
1159
    }
1160
1161
    /**
1162
     * Tells whether IDNs (Internationalized Domain Names) are supported or not. This requires the
1163
     * "intl" and "mbstring" PHP extensions.
1164
     * @return bool "true" if required functions for IDN support are present
1165
     */
1166
    public function idnSupported()
1167
    {
1168
        // @TODO: Write our own "idn_to_ascii" function for PHP <= 5.2.
1169
        return function_exists('idn_to_ascii') and function_exists('mb_convert_encoding');
1170
    }
1171
1172
    /**
1173
     * Converts IDN in given email address to its ASCII form, also known as punycode, if possible.
1174
     * Important: Address must be passed in same encoding as currently set in PHPMailer::$CharSet.
1175
     * This function silently returns unmodified address if:
1176
     * - No conversion is necessary (i.e. domain name is not an IDN, or is already in ASCII form)
1177
     * - Conversion to punycode is impossible (e.g. required PHP functions are not available)
1178
     *   or fails for any reason (e.g. domain has characters not allowed in an IDN)
1179
     * @see PHPMailer::$CharSet
1180
     * @param string $address The email address to convert
1181
     * @return string The encoded address in ASCII form
1182
     */
1183
    public function punyencodeAddress($address)
1184
    {
1185
        // Verify we have required functions, CharSet, and at-sign.
1186
        if ($this->idnSupported() and
1187
            !empty($this->CharSet) and
1188
            ($pos = strrpos($address, '@')) !== false) {
1189
            $domain = substr($address, ++$pos);
1190
            // Verify CharSet string is a valid one, and domain properly encoded in this CharSet.
1191
            if ($this->has8bitChars($domain) and @mb_check_encoding($domain, $this->CharSet)) {
1192
                $domain = mb_convert_encoding($domain, 'UTF-8', $this->CharSet);
1193
                if (($punycode = defined('INTL_IDNA_VARIANT_UTS46') ?
0 ignored issues
show
introduced by
The condition $punycode = defined('INT...scii($domain) !== false is always true.
Loading history...
1194
                    idn_to_ascii($domain, 0, INTL_IDNA_VARIANT_UTS46) :
1195
                    idn_to_ascii($domain)) !== false) {
1196
                    return substr($address, 0, $pos) . $punycode;
1197
                }
1198
            }
1199
        }
1200
        return $address;
1201
    }
1202
1203
    /**
1204
     * Create a message and send it.
1205
     * Uses the sending method specified by $Mailer.
1206
     * @throws phpmailerException
1207
     * @return boolean false on error - See the ErrorInfo property for details of the error.
1208
     */
1209
    public function send()
1210
    {
1211
        try {
1212
            if (!$this->preSend()) {
1213
                return false;
1214
            }
1215
            return $this->postSend();
1216
        } catch (phpmailerException $exc) {
1217
            $this->mailHeader = '';
1218
            $this->setError($exc->getMessage());
1219
            if ($this->exceptions) {
1220
                throw $exc;
1221
            }
1222
            return false;
1223
        }
1224
    }
1225
1226
    /**
1227
     * Prepare a message for sending.
1228
     * @throws phpmailerException
1229
     * @return boolean
1230
     */
1231
    public function preSend()
1232
    {
1233
        try {
1234
            $this->error_count = 0; // Reset errors
1235
            $this->mailHeader = '';
1236
1237
            // Dequeue recipient and Reply-To addresses with IDN
1238
            foreach (array_merge($this->RecipientsQueue, $this->ReplyToQueue) as $params) {
1239
                $params[1] = $this->punyencodeAddress($params[1]);
1240
                call_user_func_array(array($this, 'addAnAddress'), $params);
1241
            }
1242
            if ((count($this->to) + count($this->cc) + count($this->bcc)) < 1) {
1243
                throw new phpmailerException($this->lang('provide_address'), self::STOP_CRITICAL);
1244
            }
1245
1246
            // Validate From, Sender, and ConfirmReadingTo addresses
1247
            foreach (array('From', 'Sender', 'ConfirmReadingTo') as $address_kind) {
1248
                $this->$address_kind = trim($this->$address_kind);
1249
                if (empty($this->$address_kind)) {
1250
                    continue;
1251
                }
1252
                $this->$address_kind = $this->punyencodeAddress($this->$address_kind);
1253
                if (!$this->validateAddress($this->$address_kind)) {
1254
                    $error_message = $this->lang('invalid_address') . ' (punyEncode) ' . $this->$address_kind;
1255
                    $this->setError($error_message);
1256
                    $this->edebug($error_message);
1257
                    if ($this->exceptions) {
1258
                        throw new phpmailerException($error_message);
1259
                    }
1260
                    return false;
1261
                }
1262
            }
1263
1264
            // Set whether the message is multipart/alternative
1265
            if ($this->alternativeExists()) {
1266
                $this->ContentType = 'multipart/alternative';
1267
            }
1268
1269
            $this->setMessageType();
1270
            // Refuse to send an empty message unless we are specifically allowing it
1271
            if (!$this->AllowEmpty and empty($this->Body)) {
1272
                throw new phpmailerException($this->lang('empty_message'), self::STOP_CRITICAL);
1273
            }
1274
1275
            // Create body before headers in case body makes changes to headers (e.g. altering transfer encoding)
1276
            $this->MIMEHeader = '';
1277
            $this->MIMEBody = $this->createBody();
1278
            // createBody may have added some headers, so retain them
1279
            $tempheaders = $this->MIMEHeader;
1280
            $this->MIMEHeader = $this->createHeader();
1281
            $this->MIMEHeader .= $tempheaders;
1282
1283
            // To capture the complete message when using mail(), create
1284
            // an extra header list which createHeader() doesn't fold in
1285
            if ($this->Mailer == 'mail') {
1286
                if (count($this->to) > 0) {
1287
                    $this->mailHeader .= $this->addrAppend('To', $this->to);
1288
                } else {
1289
                    $this->mailHeader .= $this->headerLine('To', 'undisclosed-recipients:;');
1290
                }
1291
                $this->mailHeader .= $this->headerLine(
1292
                    'Subject',
1293
                    $this->encodeHeader($this->secureHeader(trim($this->Subject)))
1294
                );
1295
            }
1296
1297
            // Sign with DKIM if enabled
1298
            if (!empty($this->DKIM_domain)
1299
                and !empty($this->DKIM_selector)
1300
                and (!empty($this->DKIM_private_string)
1301
                    or (!empty($this->DKIM_private)
1302
                        and self::isPermittedPath($this->DKIM_private)
1303
                        and file_exists($this->DKIM_private)
1304
                    )
1305
                )
1306
            ) {
1307
                $header_dkim = $this->DKIM_Add(
1308
                    $this->MIMEHeader . $this->mailHeader,
1309
                    $this->encodeHeader($this->secureHeader($this->Subject)),
1310
                    $this->MIMEBody
1311
                );
1312
                $this->MIMEHeader = rtrim($this->MIMEHeader, "\r\n ") . self::CRLF .
1313
                    str_replace("\r\n", "\n", $header_dkim) . self::CRLF;
1314
            }
1315
            return true;
1316
        } catch (phpmailerException $exc) {
1317
            $this->setError($exc->getMessage());
1318
            if ($this->exceptions) {
1319
                throw $exc;
1320
            }
1321
            return false;
1322
        }
1323
    }
1324
1325
    /**
1326
     * Actually send a message.
1327
     * Send the email via the selected mechanism
1328
     * @throws phpmailerException
1329
     * @return boolean
1330
     */
1331
    public function postSend()
1332
    {
1333
        try {
1334
            // Choose the mailer and send through it
1335
            switch ($this->Mailer) {
1336
                case 'sendmail':
1337
                case 'qmail':
1338
                    return $this->sendmailSend($this->MIMEHeader, $this->MIMEBody);
1339
                case 'smtp':
1340
                    return $this->smtpSend($this->MIMEHeader, $this->MIMEBody);
1341
                case 'mail':
1342
                    return $this->mailSend($this->MIMEHeader, $this->MIMEBody);
1343
                default:
1344
                    $sendMethod = $this->Mailer.'Send';
1345
                    if (method_exists($this, $sendMethod)) {
1346
                        return $this->$sendMethod($this->MIMEHeader, $this->MIMEBody);
1347
                    }
1348
1349
                    return $this->mailSend($this->MIMEHeader, $this->MIMEBody);
1350
            }
1351
        } catch (phpmailerException $exc) {
1352
            $this->setError($exc->getMessage());
1353
            $this->edebug($exc->getMessage());
1354
            if ($this->exceptions) {
1355
                throw $exc;
1356
            }
1357
        }
1358
        return false;
1359
    }
1360
1361
    /**
1362
     * Send mail using the $Sendmail program.
1363
     * @param string $header The message headers
1364
     * @param string $body The message body
1365
     * @see PHPMailer::$Sendmail
1366
     * @throws phpmailerException
1367
     * @access protected
1368
     * @return boolean
1369
     */
1370
    protected function sendmailSend($header, $body)
1371
    {
1372
        // CVE-2016-10033, CVE-2016-10045: Don't pass -f if characters will be escaped.
1373
        if (!empty($this->Sender) and self::isShellSafe($this->Sender)) {
1374
            if ($this->Mailer == 'qmail') {
1375
                $sendmailFmt = '%s -f%s';
1376
            } else {
1377
                $sendmailFmt = '%s -oi -f%s -t';
1378
            }
1379
        } else {
1380
            if ($this->Mailer == 'qmail') {
1381
                $sendmailFmt = '%s';
1382
            } else {
1383
                $sendmailFmt = '%s -oi -t';
1384
            }
1385
        }
1386
1387
        // TODO: If possible, this should be changed to escapeshellarg.  Needs thorough testing.
1388
        $sendmail = sprintf($sendmailFmt, escapeshellcmd($this->Sendmail), $this->Sender);
1389
1390
        if ($this->SingleTo) {
1391
            foreach ($this->SingleToArray as $toAddr) {
1392
                if (!@$mail = popen($sendmail, 'w')) {
1393
                    throw new phpmailerException($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL);
1394
                }
1395
                fputs($mail, 'To: ' . $toAddr . "\n");
0 ignored issues
show
Bug introduced by
It seems like $mail can also be of type false; however, parameter $handle of fputs() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1395
                fputs(/** @scrutinizer ignore-type */ $mail, 'To: ' . $toAddr . "\n");
Loading history...
1396
                fputs($mail, $header);
1397
                fputs($mail, $body);
1398
                $result = pclose($mail);
0 ignored issues
show
Bug introduced by
It seems like $mail can also be of type false; however, parameter $handle of pclose() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1398
                $result = pclose(/** @scrutinizer ignore-type */ $mail);
Loading history...
1399
                $this->doCallback(
1400
                    ($result == 0),
1401
                    array($toAddr),
1402
                    $this->cc,
1403
                    $this->bcc,
1404
                    $this->Subject,
1405
                    $body,
1406
                    $this->From
1407
                );
1408
                if ($result != 0) {
1409
                    throw new phpmailerException($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL);
1410
                }
1411
            }
1412
        } else {
1413
            if (!@$mail = popen($sendmail, 'w')) {
1414
                throw new phpmailerException($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL);
1415
            }
1416
            fputs($mail, $header);
1417
            fputs($mail, $body);
1418
            $result = pclose($mail);
1419
            $this->doCallback(
1420
                ($result == 0),
1421
                $this->to,
1422
                $this->cc,
1423
                $this->bcc,
1424
                $this->Subject,
1425
                $body,
1426
                $this->From
1427
            );
1428
            if ($result != 0) {
1429
                throw new phpmailerException($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL);
1430
            }
1431
        }
1432
        return true;
1433
    }
1434
1435
    /**
1436
     * Fix CVE-2016-10033 and CVE-2016-10045 by disallowing potentially unsafe shell characters.
1437
     *
1438
     * Note that escapeshellarg and escapeshellcmd are inadequate for our purposes, especially on Windows.
1439
     * @param string $string The string to be validated
1440
     * @see https://github.com/PHPMailer/PHPMailer/issues/924 CVE-2016-10045 bug report
1441
     * @access protected
1442
     * @return boolean
1443
     */
1444
    protected static function isShellSafe($string)
1445
    {
1446
        // Future-proof
1447
        if (escapeshellcmd($string) !== $string
1448
            or !in_array(escapeshellarg($string), array("'$string'", "\"$string\""))
1449
        ) {
1450
            return false;
1451
        }
1452
1453
        $length = strlen($string);
1454
1455
        for ($i = 0; $i < $length; $i++) {
1456
            $c = $string[$i];
1457
1458
            // All other characters have a special meaning in at least one common shell, including = and +.
1459
            // Full stop (.) has a special meaning in cmd.exe, but its impact should be negligible here.
1460
            // Note that this does permit non-Latin alphanumeric characters based on the current locale.
1461
            if (!ctype_alnum($c) && strpos('@_-.', $c) === false) {
1462
                return false;
1463
            }
1464
        }
1465
1466
        return true;
1467
    }
1468
1469
    /**
1470
     * Check whether a file path is of a permitted type.
1471
     * Used to reject URLs and phar files from functions that access local file paths,
1472
     * such as addAttachment.
1473
     * @param string $path A relative or absolute path to a file.
1474
     * @return bool
1475
     */
1476
    protected static function isPermittedPath($path)
1477
    {
1478
        return !preg_match('#^[a-z]+://#i', $path);
1479
    }
1480
1481
    /**
1482
     * Send mail using the PHP mail() function.
1483
     * @param string $header The message headers
1484
     * @param string $body The message body
1485
     * @link http://www.php.net/manual/en/book.mail.php
1486
     * @throws phpmailerException
1487
     * @access protected
1488
     * @return boolean
1489
     */
1490
    protected function mailSend($header, $body)
1491
    {
1492
        $toArr = array();
1493
        foreach ($this->to as $toaddr) {
1494
            $toArr[] = $this->addrFormat($toaddr);
1495
        }
1496
        $to = implode(', ', $toArr);
1497
1498
        $params = null;
1499
        //This sets the SMTP envelope sender which gets turned into a return-path header by the receiver
1500
        if (!empty($this->Sender) and $this->validateAddress($this->Sender)) {
1501
            // CVE-2016-10033, CVE-2016-10045: Don't pass -f if characters will be escaped.
1502
            if (self::isShellSafe($this->Sender)) {
1503
                $params = sprintf('-f%s', $this->Sender);
1504
            }
1505
        }
1506
        if (!empty($this->Sender) and !ini_get('safe_mode') and $this->validateAddress($this->Sender)) {
1507
            $old_from = ini_get('sendmail_from');
1508
            ini_set('sendmail_from', $this->Sender);
1509
        }
1510
        $result = false;
1511
        if ($this->SingleTo and count($toArr) > 1) {
1512
            foreach ($toArr as $toAddr) {
1513
                $result = $this->mailPassthru($toAddr, $this->Subject, $body, $header, $params);
1514
                $this->doCallback($result, array($toAddr), $this->cc, $this->bcc, $this->Subject, $body, $this->From);
1515
            }
1516
        } else {
1517
            $result = $this->mailPassthru($to, $this->Subject, $body, $header, $params);
1518
            $this->doCallback($result, $this->to, $this->cc, $this->bcc, $this->Subject, $body, $this->From);
1519
        }
1520
        if (isset($old_from)) {
1521
            ini_set('sendmail_from', $old_from);
1522
        }
1523
        if (!$result) {
1524
            throw new phpmailerException($this->lang('instantiate'), self::STOP_CRITICAL);
1525
        }
1526
        return true;
1527
    }
1528
1529
    /**
1530
     * Get an instance to use for SMTP operations.
1531
     * Override this function to load your own SMTP implementation
1532
     * @return SMTP
1533
     */
1534
    public function getSMTPInstance()
1535
    {
1536
        if (!is_object($this->smtp)) {
1537
            $this->smtp = new SMTP;
1538
        }
1539
        return $this->smtp;
1540
    }
1541
1542
    /**
1543
     * Send mail via SMTP.
1544
     * Returns false if there is a bad MAIL FROM, RCPT, or DATA input.
1545
     * Uses the PHPMailerSMTP class by default.
1546
     * @see PHPMailer::getSMTPInstance() to use a different class.
1547
     * @param string $header The message headers
1548
     * @param string $body The message body
1549
     * @throws phpmailerException
1550
     * @uses SMTP
1551
     * @access protected
1552
     * @return boolean
1553
     */
1554
    protected function smtpSend($header, $body)
1555
    {
1556
        $bad_rcpt = array();
1557
        if (!$this->smtpConnect($this->SMTPOptions)) {
1558
            throw new phpmailerException($this->lang('smtp_connect_failed'), self::STOP_CRITICAL);
1559
        }
1560
        if (!empty($this->Sender) and $this->validateAddress($this->Sender)) {
1561
            $smtp_from = $this->Sender;
1562
        } else {
1563
            $smtp_from = $this->From;
1564
        }
1565
        if (!$this->smtp->mail($smtp_from)) {
1566
            $this->setError($this->lang('from_failed') . $smtp_from . ' : ' . implode(',', $this->smtp->getError()));
1567
            throw new phpmailerException($this->ErrorInfo, self::STOP_CRITICAL);
1568
        }
1569
1570
        // Attempt to send to all recipients
1571
        foreach (array($this->to, $this->cc, $this->bcc) as $togroup) {
1572
            foreach ($togroup as $to) {
1573
                if (!$this->smtp->recipient($to[0])) {
1574
                    $error = $this->smtp->getError();
1575
                    $bad_rcpt[] = array('to' => $to[0], 'error' => $error['detail']);
1576
                    $isSent = false;
1577
                } else {
1578
                    $isSent = true;
1579
                }
1580
                $this->doCallback($isSent, array($to[0]), array(), array(), $this->Subject, $body, $this->From);
1581
            }
1582
        }
1583
1584
        // Only send the DATA command if we have viable recipients
1585
        if ((count($this->all_recipients) > count($bad_rcpt)) and !$this->smtp->data($header . $body)) {
1586
            throw new phpmailerException($this->lang('data_not_accepted'), self::STOP_CRITICAL);
1587
        }
1588
        if ($this->SMTPKeepAlive) {
1589
            $this->smtp->reset();
1590
        } else {
1591
            $this->smtp->quit();
1592
            $this->smtp->close();
1593
        }
1594
        //Create error message for any bad addresses
1595
        if (count($bad_rcpt) > 0) {
1596
            $errstr = '';
1597
            foreach ($bad_rcpt as $bad) {
1598
                $errstr .= $bad['to'] . ': ' . $bad['error'];
1599
            }
1600
            throw new phpmailerException(
1601
                $this->lang('recipients_failed') . $errstr,
1602
                self::STOP_CONTINUE
1603
            );
1604
        }
1605
        return true;
1606
    }
1607
1608
    /**
1609
     * Initiate a connection to an SMTP server.
1610
     * Returns false if the operation failed.
1611
     * @param array $options An array of options compatible with stream_context_create()
1612
     * @uses SMTP
1613
     * @access public
1614
     * @throws phpmailerException
1615
     * @return boolean
1616
     */
1617
    public function smtpConnect($options = null)
1618
    {
1619
        if (is_null($this->smtp)) {
1620
            $this->smtp = $this->getSMTPInstance();
1621
        }
1622
1623
        //If no options are provided, use whatever is set in the instance
1624
        if (is_null($options)) {
1625
            $options = $this->SMTPOptions;
1626
        }
1627
1628
        // Already connected?
1629
        if ($this->smtp->connected()) {
1630
            return true;
1631
        }
1632
1633
        $this->smtp->setTimeout($this->Timeout);
1634
        $this->smtp->setDebugLevel($this->SMTPDebug);
1635
        $this->smtp->setDebugOutput($this->Debugoutput);
1636
        $this->smtp->setVerp($this->do_verp);
1637
        $hosts = explode(';', $this->Host);
1638
        $lastexception = null;
1639
1640
        foreach ($hosts as $hostentry) {
1641
            $hostinfo = array();
1642
            if (!preg_match(
1643
                '/^((ssl|tls):\/\/)*([a-zA-Z0-9\.-]*|\[[a-fA-F0-9:]+\]):?([0-9]*)$/',
1644
                trim($hostentry),
1645
                $hostinfo
1646
            )) {
1647
                // Not a valid host entry
1648
                $this->edebug('Ignoring invalid host: ' . $hostentry);
1649
                continue;
1650
            }
1651
            // $hostinfo[2]: optional ssl or tls prefix
1652
            // $hostinfo[3]: the hostname
1653
            // $hostinfo[4]: optional port number
1654
            // The host string prefix can temporarily override the current setting for SMTPSecure
1655
            // If it's not specified, the default value is used
1656
            $prefix = '';
1657
            $secure = $this->SMTPSecure;
1658
            $tls = ($this->SMTPSecure == 'tls');
1659
            if ('ssl' == $hostinfo[2] or ('' == $hostinfo[2] and 'ssl' == $this->SMTPSecure)) {
1660
                $prefix = 'ssl://';
1661
                $tls = false; // Can't have SSL and TLS at the same time
1662
                $secure = 'ssl';
1663
            } elseif ($hostinfo[2] == 'tls') {
1664
                $tls = true;
1665
                // tls doesn't use a prefix
1666
                $secure = 'tls';
1667
            }
1668
            //Do we need the OpenSSL extension?
1669
            $sslext = defined('OPENSSL_ALGO_SHA1');
1670
            if ('tls' === $secure or 'ssl' === $secure) {
1671
                //Check for an OpenSSL constant rather than using extension_loaded, which is sometimes disabled
1672
                if (!$sslext) {
1673
                    throw new phpmailerException($this->lang('extension_missing').'openssl', self::STOP_CRITICAL);
1674
                }
1675
            }
1676
            $host = $hostinfo[3];
1677
            $port = $this->Port;
1678
            $tport = (integer)$hostinfo[4];
1679
            if ($tport > 0 and $tport < 65536) {
1680
                $port = $tport;
1681
            }
1682
            if ($this->smtp->connect($prefix . $host, $port, $this->Timeout, $options)) {
1683
                try {
1684
                    if ($this->Helo) {
1685
                        $hello = $this->Helo;
1686
                    } else {
1687
                        $hello = $this->serverHostname();
1688
                    }
1689
                    $this->smtp->hello($hello);
1690
                    //Automatically enable TLS encryption if:
1691
                    // * it's not disabled
1692
                    // * we have openssl extension
1693
                    // * we are not already using SSL
1694
                    // * the server offers STARTTLS
1695
                    if ($this->SMTPAutoTLS and $sslext and $secure != 'ssl' and $this->smtp->getServerExt('STARTTLS')) {
1696
                        $tls = true;
1697
                    }
1698
                    if ($tls) {
1699
                        if (!$this->smtp->startTLS()) {
1700
                            throw new phpmailerException($this->lang('connect_host'));
1701
                        }
1702
                        // We must resend EHLO after TLS negotiation
1703
                        $this->smtp->hello($hello);
1704
                    }
1705
                    if ($this->SMTPAuth) {
1706
                        if (!$this->smtp->authenticate(
1707
                            $this->Username,
1708
                            $this->Password,
1709
                            $this->AuthType,
1710
                            $this->Realm,
1711
                            $this->Workstation
1712
                        )
1713
                        ) {
1714
                            throw new phpmailerException($this->lang('authenticate'));
1715
                        }
1716
                    }
1717
                    return true;
1718
                } catch (phpmailerException $exc) {
1719
                    $lastexception = $exc;
1720
                    $this->edebug($exc->getMessage());
1721
                    // We must have connected, but then failed TLS or Auth, so close connection nicely
1722
                    $this->smtp->quit();
1723
                }
1724
            }
1725
        }
1726
        // If we get here, all connection attempts have failed, so close connection hard
1727
        $this->smtp->close();
1728
        // As we've caught all exceptions, just report whatever the last one was
1729
        if ($this->exceptions and !is_null($lastexception)) {
1730
            throw $lastexception;
1731
        }
1732
        return false;
1733
    }
1734
1735
    /**
1736
     * Close the active SMTP session if one exists.
1737
     * @return void
1738
     */
1739
    public function smtpClose()
1740
    {
1741
        if (is_a($this->smtp, 'SMTP')) {
1742
            if ($this->smtp->connected()) {
1743
                $this->smtp->quit();
1744
                $this->smtp->close();
1745
            }
1746
        }
1747
    }
1748
1749
    /**
1750
     * Set the language for error messages.
1751
     * Returns false if it cannot load the language file.
1752
     * The default language is English.
1753
     * @param string $langcode ISO 639-1 2-character language code (e.g. French is "fr")
1754
     * @param string $lang_path Path to the language file directory, with trailing separator (slash)
1755
     * @return boolean
1756
     * @access public
1757
     */
1758
    public function setLanguage($langcode = 'en', $lang_path = '')
1759
    {
1760
        // Backwards compatibility for renamed language codes
1761
        $renamed_langcodes = array(
1762
            'br' => 'pt_br',
1763
            'cz' => 'cs',
1764
            'dk' => 'da',
1765
            'no' => 'nb',
1766
            'se' => 'sv',
1767
            'sr' => 'rs'
1768
        );
1769
1770
        if (isset($renamed_langcodes[$langcode])) {
1771
            $langcode = $renamed_langcodes[$langcode];
1772
        }
1773
1774
        // Define full set of translatable strings in English
1775
        $PHPMAILER_LANG = array(
1776
            'authenticate' => 'SMTP Error: Could not authenticate.',
1777
            'connect_host' => 'SMTP Error: Could not connect to SMTP host.',
1778
            'data_not_accepted' => 'SMTP Error: data not accepted.',
1779
            'empty_message' => 'Message body empty',
1780
            'encoding' => 'Unknown encoding: ',
1781
            'execute' => 'Could not execute: ',
1782
            'file_access' => 'Could not access file: ',
1783
            'file_open' => 'File Error: Could not open file: ',
1784
            'from_failed' => 'The following From address failed: ',
1785
            'instantiate' => 'Could not instantiate mail function.',
1786
            'invalid_address' => 'Invalid address: ',
1787
            'mailer_not_supported' => ' mailer is not supported.',
1788
            'provide_address' => 'You must provide at least one recipient email address.',
1789
            'recipients_failed' => 'SMTP Error: The following recipients failed: ',
1790
            'signing' => 'Signing Error: ',
1791
            'smtp_connect_failed' => 'SMTP connect() failed.',
1792
            'smtp_error' => 'SMTP server error: ',
1793
            'variable_set' => 'Cannot set or reset variable: ',
1794
            'extension_missing' => 'Extension missing: '
1795
        );
1796
        if (empty($lang_path)) {
1797
            // Calculate an absolute path so it can work if CWD is not here
1798
            $lang_path = dirname(__FILE__). DIRECTORY_SEPARATOR . 'language'. DIRECTORY_SEPARATOR;
1799
        }
1800
        //Validate $langcode
1801
        if (!preg_match('/^[a-z]{2}(?:_[a-zA-Z]{2})?$/', $langcode)) {
1802
            $langcode = 'en';
1803
        }
1804
        $foundlang = true;
1805
        $lang_file = $lang_path . 'phpmailer.lang-' . $langcode . '.php';
1806
        // There is no English translation file
1807
        if ($langcode != 'en') {
1808
            // Make sure language file path is readable
1809
            if (!self::isPermittedPath($lang_file) or !is_readable($lang_file)) {
1810
                $foundlang = false;
1811
            } else {
1812
                // Overwrite language-specific strings.
1813
                // This way we'll never have missing translation keys.
1814
                $foundlang = include $lang_file;
1815
            }
1816
        }
1817
        $this->language = $PHPMAILER_LANG;
1818
        return (boolean)$foundlang; // Returns false if language not found
1819
    }
1820
1821
    /**
1822
     * Get the array of strings for the current language.
1823
     * @return array
1824
     */
1825
    public function getTranslations()
1826
    {
1827
        return $this->language;
1828
    }
1829
1830
    /**
1831
     * Create recipient headers.
1832
     * @access public
1833
     * @param string $type
1834
     * @param array $addr An array of recipient,
1835
     * where each recipient is a 2-element indexed array with element 0 containing an address
1836
     * and element 1 containing a name, like:
1837
     * array(array('[email protected]', 'Joe User'), array('[email protected]', 'Zoe User'))
1838
     * @return string
1839
     */
1840
    public function addrAppend($type, $addr)
1841
    {
1842
        $addresses = array();
1843
        foreach ($addr as $address) {
1844
            $addresses[] = $this->addrFormat($address);
1845
        }
1846
        return $type . ': ' . implode(', ', $addresses) . $this->LE;
1847
    }
1848
1849
    /**
1850
     * Format an address for use in a message header.
1851
     * @access public
1852
     * @param array $addr A 2-element indexed array, element 0 containing an address, element 1 containing a name
1853
     *      like array('[email protected]', 'Joe User')
1854
     * @return string
1855
     */
1856
    public function addrFormat($addr)
1857
    {
1858
        if (empty($addr[1])) { // No name provided
1859
            return $this->secureHeader($addr[0]);
1860
        } else {
1861
            return $this->encodeHeader($this->secureHeader($addr[1]), 'phrase') . ' <' . $this->secureHeader(
1862
                $addr[0]
1863
            ) . '>';
1864
        }
1865
    }
1866
1867
    /**
1868
     * Word-wrap message.
1869
     * For use with mailers that do not automatically perform wrapping
1870
     * and for quoted-printable encoded messages.
1871
     * Original written by philippe.
1872
     * @param string $message The message to wrap
1873
     * @param integer $length The line length to wrap to
1874
     * @param boolean $qp_mode Whether to run in Quoted-Printable mode
1875
     * @access public
1876
     * @return string
1877
     */
1878
    public function wrapText($message, $length, $qp_mode = false)
1879
    {
1880
        if ($qp_mode) {
1881
            $soft_break = sprintf(' =%s', $this->LE);
1882
        } else {
1883
            $soft_break = $this->LE;
1884
        }
1885
        // If utf-8 encoding is used, we will need to make sure we don't
1886
        // split multibyte characters when we wrap
1887
        $is_utf8 = (strtolower($this->CharSet) == 'utf-8');
1888
        $lelen = strlen($this->LE);
1889
        $crlflen = strlen(self::CRLF);
1890
1891
        $message = $this->fixEOL($message);
1892
        //Remove a trailing line break
1893
        if (substr($message, -$lelen) == $this->LE) {
1894
            $message = substr($message, 0, -$lelen);
1895
        }
1896
1897
        //Split message into lines
1898
        $lines = explode($this->LE, $message);
1899
        //Message will be rebuilt in here
1900
        $message = '';
1901
        foreach ($lines as $line) {
1902
            $words = explode(' ', $line);
1903
            $buf = '';
1904
            $firstword = true;
1905
            foreach ($words as $word) {
1906
                if ($qp_mode and (strlen($word) > $length)) {
1907
                    $space_left = $length - strlen($buf) - $crlflen;
1908
                    if (!$firstword) {
1909
                        if ($space_left > 20) {
1910
                            $len = $space_left;
1911
                            if ($is_utf8) {
1912
                                $len = $this->utf8CharBoundary($word, $len);
1913
                            } elseif (substr($word, $len - 1, 1) == '=') {
1914
                                $len--;
1915
                            } elseif (substr($word, $len - 2, 1) == '=') {
1916
                                $len -= 2;
1917
                            }
1918
                            $part = substr($word, 0, $len);
1919
                            $word = substr($word, $len);
1920
                            $buf .= ' ' . $part;
1921
                            $message .= $buf . sprintf('=%s', self::CRLF);
1922
                        } else {
1923
                            $message .= $buf . $soft_break;
1924
                        }
1925
                        $buf = '';
1926
                    }
1927
                    while (strlen($word) > 0) {
1928
                        if ($length <= 0) {
1929
                            break;
1930
                        }
1931
                        $len = $length;
1932
                        if ($is_utf8) {
1933
                            $len = $this->utf8CharBoundary($word, $len);
1934
                        } elseif (substr($word, $len - 1, 1) == '=') {
1935
                            $len--;
1936
                        } elseif (substr($word, $len - 2, 1) == '=') {
1937
                            $len -= 2;
1938
                        }
1939
                        $part = substr($word, 0, $len);
1940
                        $word = substr($word, $len);
1941
1942
                        if (strlen($word) > 0) {
1943
                            $message .= $part . sprintf('=%s', self::CRLF);
1944
                        } else {
1945
                            $buf = $part;
1946
                        }
1947
                    }
1948
                } else {
1949
                    $buf_o = $buf;
1950
                    if (!$firstword) {
1951
                        $buf .= ' ';
1952
                    }
1953
                    $buf .= $word;
1954
1955
                    if (strlen($buf) > $length and $buf_o != '') {
1956
                        $message .= $buf_o . $soft_break;
1957
                        $buf = $word;
1958
                    }
1959
                }
1960
                $firstword = false;
1961
            }
1962
            $message .= $buf . self::CRLF;
1963
        }
1964
1965
        return $message;
1966
    }
1967
1968
    /**
1969
     * Find the last character boundary prior to $maxLength in a utf-8
1970
     * quoted-printable encoded string.
1971
     * Original written by Colin Brown.
1972
     * @access public
1973
     * @param string $encodedText utf-8 QP text
1974
     * @param integer $maxLength Find the last character boundary prior to this length
1975
     * @return integer
1976
     */
1977
    public function utf8CharBoundary($encodedText, $maxLength)
1978
    {
1979
        $foundSplitPos = false;
1980
        $lookBack = 3;
1981
        while (!$foundSplitPos) {
1982
            $lastChunk = substr($encodedText, $maxLength - $lookBack, $lookBack);
1983
            $encodedCharPos = strpos($lastChunk, '=');
1984
            if (false !== $encodedCharPos) {
1985
                // Found start of encoded character byte within $lookBack block.
1986
                // Check the encoded byte value (the 2 chars after the '=')
1987
                $hex = substr($encodedText, $maxLength - $lookBack + $encodedCharPos + 1, 2);
1988
                $dec = hexdec($hex);
1989
                if ($dec < 128) {
1990
                    // Single byte character.
1991
                    // If the encoded char was found at pos 0, it will fit
1992
                    // otherwise reduce maxLength to start of the encoded char
1993
                    if ($encodedCharPos > 0) {
1994
                        $maxLength = $maxLength - ($lookBack - $encodedCharPos);
1995
                    }
1996
                    $foundSplitPos = true;
1997
                } elseif ($dec >= 192) {
1998
                    // First byte of a multi byte character
1999
                    // Reduce maxLength to split at start of character
2000
                    $maxLength = $maxLength - ($lookBack - $encodedCharPos);
2001
                    $foundSplitPos = true;
2002
                } elseif ($dec < 192) {
2003
                    // Middle byte of a multi byte character, look further back
2004
                    $lookBack += 3;
2005
                }
2006
            } else {
2007
                // No encoded character found
2008
                $foundSplitPos = true;
2009
            }
2010
        }
2011
        return $maxLength;
2012
    }
2013
2014
    /**
2015
     * Apply word wrapping to the message body.
2016
     * Wraps the message body to the number of chars set in the WordWrap property.
2017
     * You should only do this to plain-text bodies as wrapping HTML tags may break them.
2018
     * This is called automatically by createBody(), so you don't need to call it yourself.
2019
     * @access public
2020
     * @return void
2021
     */
2022
    public function setWordWrap()
2023
    {
2024
        if ($this->WordWrap < 1) {
2025
            return;
2026
        }
2027
2028
        switch ($this->message_type) {
2029
            case 'alt':
2030
            case 'alt_inline':
2031
            case 'alt_attach':
2032
            case 'alt_inline_attach':
2033
                $this->AltBody = $this->wrapText($this->AltBody, $this->WordWrap);
2034
                break;
2035
            default:
2036
                $this->Body = $this->wrapText($this->Body, $this->WordWrap);
2037
                break;
2038
        }
2039
    }
2040
2041
    /**
2042
     * Assemble message headers.
2043
     * @access public
2044
     * @return string The assembled headers
2045
     */
2046
    public function createHeader()
2047
    {
2048
        $result = '';
2049
2050
        $result .= $this->headerLine('Date', $this->MessageDate == '' ? self::rfcDate() : $this->MessageDate);
2051
2052
        // To be created automatically by mail()
2053
        if ($this->SingleTo) {
2054
            if ($this->Mailer != 'mail') {
2055
                foreach ($this->to as $toaddr) {
2056
                    $this->SingleToArray[] = $this->addrFormat($toaddr);
2057
                }
2058
            }
2059
        } else {
2060
            if (count($this->to) > 0) {
2061
                if ($this->Mailer != 'mail') {
2062
                    $result .= $this->addrAppend('To', $this->to);
2063
                }
2064
            } elseif (count($this->cc) == 0) {
2065
                $result .= $this->headerLine('To', 'undisclosed-recipients:;');
2066
            }
2067
        }
2068
2069
        $result .= $this->addrAppend('From', array(array(trim($this->From), $this->FromName)));
2070
2071
        // sendmail and mail() extract Cc from the header before sending
2072
        if (count($this->cc) > 0) {
2073
            $result .= $this->addrAppend('Cc', $this->cc);
2074
        }
2075
2076
        // sendmail and mail() extract Bcc from the header before sending
2077
        if ((
2078
                $this->Mailer == 'sendmail' or $this->Mailer == 'qmail' or $this->Mailer == 'mail'
2079
            )
2080
            and count($this->bcc) > 0
2081
        ) {
2082
            $result .= $this->addrAppend('Bcc', $this->bcc);
2083
        }
2084
2085
        if (count($this->ReplyTo) > 0) {
2086
            $result .= $this->addrAppend('Reply-To', $this->ReplyTo);
2087
        }
2088
2089
        // mail() sets the subject itself
2090
        if ($this->Mailer != 'mail') {
2091
            $result .= $this->headerLine('Subject', $this->encodeHeader($this->secureHeader($this->Subject)));
2092
        }
2093
2094
        // Only allow a custom message ID if it conforms to RFC 5322 section 3.6.4
2095
        // https://tools.ietf.org/html/rfc5322#section-3.6.4
2096
        if ('' != $this->MessageID and preg_match('/^<.*@.*>$/', $this->MessageID)) {
2097
            $this->lastMessageID = $this->MessageID;
2098
        } else {
2099
            $this->lastMessageID = sprintf('<%s@%s>', $this->uniqueid, $this->serverHostname());
2100
        }
2101
        $result .= $this->headerLine('Message-ID', $this->lastMessageID);
2102
        if (!is_null($this->Priority)) {
0 ignored issues
show
introduced by
The condition is_null($this->Priority) is always false.
Loading history...
2103
            $result .= $this->headerLine('X-Priority', $this->Priority);
2104
        }
2105
        if ($this->XMailer == '') {
2106
            $result .= $this->headerLine(
2107
                'X-Mailer',
2108
                'PHPMailer ' . $this->Version . ' (https://github.com/PHPMailer/PHPMailer)'
2109
            );
2110
        } else {
2111
            $myXmailer = trim($this->XMailer);
2112
            if ($myXmailer) {
2113
                $result .= $this->headerLine('X-Mailer', $myXmailer);
2114
            }
2115
        }
2116
2117
        if ($this->ConfirmReadingTo != '') {
2118
            $result .= $this->headerLine('Disposition-Notification-To', '<' . $this->ConfirmReadingTo . '>');
2119
        }
2120
2121
        // Add custom headers
2122
        foreach ($this->CustomHeader as $header) {
2123
            $result .= $this->headerLine(
2124
                trim($header[0]),
2125
                $this->encodeHeader(trim($header[1]))
2126
            );
2127
        }
2128
        if (!$this->sign_key_file) {
2129
            $result .= $this->headerLine('MIME-Version', '1.0');
2130
            $result .= $this->getMailMIME();
2131
        }
2132
2133
        return $result;
2134
    }
2135
2136
    /**
2137
     * Get the message MIME type headers.
2138
     * @access public
2139
     * @return string
2140
     */
2141
    public function getMailMIME()
2142
    {
2143
        $result = '';
2144
        $ismultipart = true;
2145
        switch ($this->message_type) {
2146
            case 'inline':
2147
                $result .= $this->headerLine('Content-Type', 'multipart/related;');
2148
                $result .= $this->textLine("\tboundary=\"" . $this->boundary[1] . '"');
2149
                break;
2150
            case 'attach':
2151
            case 'inline_attach':
2152
            case 'alt_attach':
2153
            case 'alt_inline_attach':
2154
                $result .= $this->headerLine('Content-Type', 'multipart/mixed;');
2155
                $result .= $this->textLine("\tboundary=\"" . $this->boundary[1] . '"');
2156
                break;
2157
            case 'alt':
2158
            case 'alt_inline':
2159
                $result .= $this->headerLine('Content-Type', 'multipart/alternative;');
2160
                $result .= $this->textLine("\tboundary=\"" . $this->boundary[1] . '"');
2161
                break;
2162
            default:
2163
                // Catches case 'plain': and case '':
2164
                $result .= $this->textLine('Content-Type: ' . $this->ContentType . '; charset=' . $this->CharSet);
2165
                $ismultipart = false;
2166
                break;
2167
        }
2168
        // RFC1341 part 5 says 7bit is assumed if not specified
2169
        if ($this->Encoding != '7bit') {
2170
            // RFC 2045 section 6.4 says multipart MIME parts may only use 7bit, 8bit or binary CTE
2171
            if ($ismultipart) {
2172
                if ($this->Encoding == '8bit') {
2173
                    $result .= $this->headerLine('Content-Transfer-Encoding', '8bit');
2174
                }
2175
                // The only remaining alternatives are quoted-printable and base64, which are both 7bit compatible
2176
            } else {
2177
                $result .= $this->headerLine('Content-Transfer-Encoding', $this->Encoding);
2178
            }
2179
        }
2180
2181
        if ($this->Mailer != 'mail') {
2182
            $result .= $this->LE;
2183
        }
2184
2185
        return $result;
2186
    }
2187
2188
    /**
2189
     * Returns the whole MIME message.
2190
     * Includes complete headers and body.
2191
     * Only valid post preSend().
2192
     * @see PHPMailer::preSend()
2193
     * @access public
2194
     * @return string
2195
     */
2196
    public function getSentMIMEMessage()
2197
    {
2198
        return rtrim($this->MIMEHeader . $this->mailHeader, "\n\r") . self::CRLF . self::CRLF . $this->MIMEBody;
2199
    }
2200
2201
    /**
2202
     * Create unique ID
2203
     * @return string
2204
     */
2205
    protected function generateId() {
2206
        return md5(uniqid(time()));
2207
    }
2208
2209
    /**
2210
     * Assemble the message body.
2211
     * Returns an empty string on failure.
2212
     * @access public
2213
     * @throws phpmailerException
2214
     * @return string The assembled message body
2215
     */
2216
    public function createBody()
2217
    {
2218
        $body = '';
2219
        //Create unique IDs and preset boundaries
2220
        $this->uniqueid = $this->generateId();
2221
        $this->boundary[1] = 'b1_' . $this->uniqueid;
2222
        $this->boundary[2] = 'b2_' . $this->uniqueid;
2223
        $this->boundary[3] = 'b3_' . $this->uniqueid;
2224
2225
        if ($this->sign_key_file) {
2226
            $body .= $this->getMailMIME() . $this->LE;
2227
        }
2228
2229
        $this->setWordWrap();
2230
2231
        $bodyEncoding = $this->Encoding;
2232
        $bodyCharSet = $this->CharSet;
2233
        //Can we do a 7-bit downgrade?
2234
        if ($bodyEncoding == '8bit' and !$this->has8bitChars($this->Body)) {
2235
            $bodyEncoding = '7bit';
2236
            //All ISO 8859, Windows codepage and UTF-8 charsets are ascii compatible up to 7-bit
2237
            $bodyCharSet = 'us-ascii';
2238
        }
2239
        //If lines are too long, and we're not already using an encoding that will shorten them,
2240
        //change to quoted-printable transfer encoding for the body part only
2241
        if ('base64' != $this->Encoding and self::hasLineLongerThanMax($this->Body)) {
2242
            $bodyEncoding = 'quoted-printable';
2243
        }
2244
2245
        $altBodyEncoding = $this->Encoding;
2246
        $altBodyCharSet = $this->CharSet;
2247
        //Can we do a 7-bit downgrade?
2248
        if ($altBodyEncoding == '8bit' and !$this->has8bitChars($this->AltBody)) {
2249
            $altBodyEncoding = '7bit';
2250
            //All ISO 8859, Windows codepage and UTF-8 charsets are ascii compatible up to 7-bit
2251
            $altBodyCharSet = 'us-ascii';
2252
        }
2253
        //If lines are too long, and we're not already using an encoding that will shorten them,
2254
        //change to quoted-printable transfer encoding for the alt body part only
2255
        if ('base64' != $altBodyEncoding and self::hasLineLongerThanMax($this->AltBody)) {
2256
            $altBodyEncoding = 'quoted-printable';
2257
        }
2258
        //Use this as a preamble in all multipart message types
2259
        $mimepre = "This is a multi-part message in MIME format." . $this->LE . $this->LE;
2260
        switch ($this->message_type) {
2261
            case 'inline':
2262
                $body .= $mimepre;
2263
                $body .= $this->getBoundary($this->boundary[1], $bodyCharSet, '', $bodyEncoding);
2264
                $body .= $this->encodeString($this->Body, $bodyEncoding);
2265
                $body .= $this->LE . $this->LE;
2266
                $body .= $this->attachAll('inline', $this->boundary[1]);
2267
                break;
2268
            case 'attach':
2269
                $body .= $mimepre;
2270
                $body .= $this->getBoundary($this->boundary[1], $bodyCharSet, '', $bodyEncoding);
2271
                $body .= $this->encodeString($this->Body, $bodyEncoding);
2272
                $body .= $this->LE . $this->LE;
2273
                $body .= $this->attachAll('attachment', $this->boundary[1]);
2274
                break;
2275
            case 'inline_attach':
2276
                $body .= $mimepre;
2277
                $body .= $this->textLine('--' . $this->boundary[1]);
2278
                $body .= $this->headerLine('Content-Type', 'multipart/related;');
2279
                $body .= $this->textLine("\tboundary=\"" . $this->boundary[2] . '"');
2280
                $body .= $this->LE;
2281
                $body .= $this->getBoundary($this->boundary[2], $bodyCharSet, '', $bodyEncoding);
2282
                $body .= $this->encodeString($this->Body, $bodyEncoding);
2283
                $body .= $this->LE . $this->LE;
2284
                $body .= $this->attachAll('inline', $this->boundary[2]);
2285
                $body .= $this->LE;
2286
                $body .= $this->attachAll('attachment', $this->boundary[1]);
2287
                break;
2288
            case 'alt':
2289
                $body .= $mimepre;
2290
                $body .= $this->getBoundary($this->boundary[1], $altBodyCharSet, 'text/plain', $altBodyEncoding);
2291
                $body .= $this->encodeString($this->AltBody, $altBodyEncoding);
2292
                $body .= $this->LE . $this->LE;
2293
                $body .= $this->getBoundary($this->boundary[1], $bodyCharSet, 'text/html', $bodyEncoding);
2294
                $body .= $this->encodeString($this->Body, $bodyEncoding);
2295
                $body .= $this->LE . $this->LE;
2296
                if (!empty($this->Ical)) {
2297
                    $body .= $this->getBoundary($this->boundary[1], '', 'text/calendar; method=REQUEST', '');
2298
                    $body .= $this->encodeString($this->Ical, $this->Encoding);
2299
                    $body .= $this->LE . $this->LE;
2300
                }
2301
                $body .= $this->endBoundary($this->boundary[1]);
2302
                break;
2303
            case 'alt_inline':
2304
                $body .= $mimepre;
2305
                $body .= $this->getBoundary($this->boundary[1], $altBodyCharSet, 'text/plain', $altBodyEncoding);
2306
                $body .= $this->encodeString($this->AltBody, $altBodyEncoding);
2307
                $body .= $this->LE . $this->LE;
2308
                $body .= $this->textLine('--' . $this->boundary[1]);
2309
                $body .= $this->headerLine('Content-Type', 'multipart/related;');
2310
                $body .= $this->textLine("\tboundary=\"" . $this->boundary[2] . '"');
2311
                $body .= $this->LE;
2312
                $body .= $this->getBoundary($this->boundary[2], $bodyCharSet, 'text/html', $bodyEncoding);
2313
                $body .= $this->encodeString($this->Body, $bodyEncoding);
2314
                $body .= $this->LE . $this->LE;
2315
                $body .= $this->attachAll('inline', $this->boundary[2]);
2316
                $body .= $this->LE;
2317
                $body .= $this->endBoundary($this->boundary[1]);
2318
                break;
2319
            case 'alt_attach':
2320
                $body .= $mimepre;
2321
                $body .= $this->textLine('--' . $this->boundary[1]);
2322
                $body .= $this->headerLine('Content-Type', 'multipart/alternative;');
2323
                $body .= $this->textLine("\tboundary=\"" . $this->boundary[2] . '"');
2324
                $body .= $this->LE;
2325
                $body .= $this->getBoundary($this->boundary[2], $altBodyCharSet, 'text/plain', $altBodyEncoding);
2326
                $body .= $this->encodeString($this->AltBody, $altBodyEncoding);
2327
                $body .= $this->LE . $this->LE;
2328
                $body .= $this->getBoundary($this->boundary[2], $bodyCharSet, 'text/html', $bodyEncoding);
2329
                $body .= $this->encodeString($this->Body, $bodyEncoding);
2330
                $body .= $this->LE . $this->LE;
2331
                $body .= $this->endBoundary($this->boundary[2]);
2332
                $body .= $this->LE;
2333
                $body .= $this->attachAll('attachment', $this->boundary[1]);
2334
                break;
2335
            case 'alt_inline_attach':
2336
                $body .= $mimepre;
2337
                $body .= $this->textLine('--' . $this->boundary[1]);
2338
                $body .= $this->headerLine('Content-Type', 'multipart/alternative;');
2339
                $body .= $this->textLine("\tboundary=\"" . $this->boundary[2] . '"');
2340
                $body .= $this->LE;
2341
                $body .= $this->getBoundary($this->boundary[2], $altBodyCharSet, 'text/plain', $altBodyEncoding);
2342
                $body .= $this->encodeString($this->AltBody, $altBodyEncoding);
2343
                $body .= $this->LE . $this->LE;
2344
                $body .= $this->textLine('--' . $this->boundary[2]);
2345
                $body .= $this->headerLine('Content-Type', 'multipart/related;');
2346
                $body .= $this->textLine("\tboundary=\"" . $this->boundary[3] . '"');
2347
                $body .= $this->LE;
2348
                $body .= $this->getBoundary($this->boundary[3], $bodyCharSet, 'text/html', $bodyEncoding);
2349
                $body .= $this->encodeString($this->Body, $bodyEncoding);
2350
                $body .= $this->LE . $this->LE;
2351
                $body .= $this->attachAll('inline', $this->boundary[3]);
2352
                $body .= $this->LE;
2353
                $body .= $this->endBoundary($this->boundary[2]);
2354
                $body .= $this->LE;
2355
                $body .= $this->attachAll('attachment', $this->boundary[1]);
2356
                break;
2357
            default:
2358
                // Catch case 'plain' and case '', applies to simple `text/plain` and `text/html` body content types
2359
                //Reset the `Encoding` property in case we changed it for line length reasons
2360
                $this->Encoding = $bodyEncoding;
2361
                $body .= $this->encodeString($this->Body, $this->Encoding);
2362
                break;
2363
        }
2364
2365
        if ($this->isError()) {
2366
            $body = '';
2367
        } elseif ($this->sign_key_file) {
2368
            try {
2369
                if (!defined('PKCS7_TEXT')) {
2370
                    throw new phpmailerException($this->lang('extension_missing') . 'openssl');
2371
                }
2372
                // @TODO would be nice to use php://temp streams here, but need to wrap for PHP < 5.1
2373
                $file = tempnam(sys_get_temp_dir(), 'mail');
2374
                if (false === file_put_contents($file, $body)) {
2375
                    throw new phpmailerException($this->lang('signing') . ' Could not write temp file');
2376
                }
2377
                $signed = tempnam(sys_get_temp_dir(), 'signed');
2378
                //Workaround for PHP bug https://bugs.php.net/bug.php?id=69197
2379
                if (empty($this->sign_extracerts_file)) {
2380
                    $sign = @openssl_pkcs7_sign(
2381
                        $file,
2382
                        $signed,
2383
                        'file://' . realpath($this->sign_cert_file),
2384
                        array('file://' . realpath($this->sign_key_file), $this->sign_key_pass),
2385
                        null
0 ignored issues
show
Bug introduced by
null of type null is incompatible with the type array expected by parameter $headers of openssl_pkcs7_sign(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

2385
                        /** @scrutinizer ignore-type */ null
Loading history...
2386
                    );
2387
                } else {
2388
                    $sign = @openssl_pkcs7_sign(
2389
                        $file,
2390
                        $signed,
2391
                        'file://' . realpath($this->sign_cert_file),
2392
                        array('file://' . realpath($this->sign_key_file), $this->sign_key_pass),
2393
                        null,
2394
                        PKCS7_DETACHED,
2395
                        $this->sign_extracerts_file
2396
                    );
2397
                }
2398
                if ($sign) {
2399
                    @unlink($file);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for unlink(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unhandled  annotation

2399
                    /** @scrutinizer ignore-unhandled */ @unlink($file);

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
2400
                    $body = file_get_contents($signed);
2401
                    @unlink($signed);
2402
                    //The message returned by openssl contains both headers and body, so need to split them up
2403
                    $parts = explode("\n\n", $body, 2);
2404
                    $this->MIMEHeader .= $parts[0] . $this->LE . $this->LE;
2405
                    $body = $parts[1];
2406
                } else {
2407
                    @unlink($file);
2408
                    @unlink($signed);
2409
                    throw new phpmailerException($this->lang('signing') . openssl_error_string());
2410
                }
2411
            } catch (phpmailerException $exc) {
2412
                $body = '';
2413
                if ($this->exceptions) {
2414
                    throw $exc;
2415
                }
2416
            }
2417
        }
2418
        return $body;
2419
    }
2420
2421
    /**
2422
     * Return the start of a message boundary.
2423
     * @access protected
2424
     * @param string $boundary
2425
     * @param string $charSet
2426
     * @param string $contentType
2427
     * @param string $encoding
2428
     * @return string
2429
     */
2430
    protected function getBoundary($boundary, $charSet, $contentType, $encoding)
2431
    {
2432
        $result = '';
2433
        if ($charSet == '') {
2434
            $charSet = $this->CharSet;
2435
        }
2436
        if ($contentType == '') {
2437
            $contentType = $this->ContentType;
2438
        }
2439
        if ($encoding == '') {
2440
            $encoding = $this->Encoding;
2441
        }
2442
        $result .= $this->textLine('--' . $boundary);
2443
        $result .= sprintf('Content-Type: %s; charset=%s', $contentType, $charSet);
2444
        $result .= $this->LE;
2445
        // RFC1341 part 5 says 7bit is assumed if not specified
2446
        if ($encoding != '7bit') {
2447
            $result .= $this->headerLine('Content-Transfer-Encoding', $encoding);
2448
        }
2449
        $result .= $this->LE;
2450
2451
        return $result;
2452
    }
2453
2454
    /**
2455
     * Return the end of a message boundary.
2456
     * @access protected
2457
     * @param string $boundary
2458
     * @return string
2459
     */
2460
    protected function endBoundary($boundary)
2461
    {
2462
        return $this->LE . '--' . $boundary . '--' . $this->LE;
2463
    }
2464
2465
    /**
2466
     * Set the message type.
2467
     * PHPMailer only supports some preset message types, not arbitrary MIME structures.
2468
     * @access protected
2469
     * @return void
2470
     */
2471
    protected function setMessageType()
2472
    {
2473
        $type = array();
2474
        if ($this->alternativeExists()) {
2475
            $type[] = 'alt';
2476
        }
2477
        if ($this->inlineImageExists()) {
2478
            $type[] = 'inline';
2479
        }
2480
        if ($this->attachmentExists()) {
2481
            $type[] = 'attach';
2482
        }
2483
        $this->message_type = implode('_', $type);
2484
        if ($this->message_type == '') {
2485
            //The 'plain' message_type refers to the message having a single body element, not that it is plain-text
2486
            $this->message_type = 'plain';
2487
        }
2488
    }
2489
2490
    /**
2491
     * Format a header line.
2492
     * @access public
2493
     * @param string $name
2494
     * @param string $value
2495
     * @return string
2496
     */
2497
    public function headerLine($name, $value)
2498
    {
2499
        return $name . ': ' . $value . $this->LE;
2500
    }
2501
2502
    /**
2503
     * Return a formatted mail line.
2504
     * @access public
2505
     * @param string $value
2506
     * @return string
2507
     */
2508
    public function textLine($value)
2509
    {
2510
        return $value . $this->LE;
2511
    }
2512
2513
    /**
2514
     * Add an attachment from a path on the filesystem.
2515
     * Never use a user-supplied path to a file!
2516
     * Returns false if the file could not be found or read.
2517
     * Explicitly *does not* support passing URLs; PHPMailer is not an HTTP client.
2518
     * If you need to do that, fetch the resource yourself and pass it in via a local file or string.
2519
     * @param string $path Path to the attachment.
2520
     * @param string $name Overrides the attachment name.
2521
     * @param string $encoding File encoding (see $Encoding).
2522
     * @param string $type File extension (MIME) type.
2523
     * @param string $disposition Disposition to use
2524
     * @throws phpmailerException
2525
     * @return boolean
2526
     */
2527
    public function addAttachment($path, $name = '', $encoding = 'base64', $type = '', $disposition = 'attachment')
2528
    {
2529
        try {
2530
            if (!self::isPermittedPath($path) or !@is_file($path)) {
2531
                throw new phpmailerException($this->lang('file_access') . $path, self::STOP_CONTINUE);
2532
            }
2533
2534
            // If a MIME type is not specified, try to work it out from the file name
2535
            if ($type == '') {
2536
                $type = self::filenameToType($path);
2537
            }
2538
2539
            $filename = basename($path);
2540
            if ($name == '') {
2541
                $name = $filename;
2542
            }
2543
2544
            $this->attachment[] = array(
2545
                0 => $path,
2546
                1 => $filename,
2547
                2 => $name,
2548
                3 => $encoding,
2549
                4 => $type,
2550
                5 => false, // isStringAttachment
2551
                6 => $disposition,
2552
                7 => 0
2553
            );
2554
2555
        } catch (phpmailerException $exc) {
2556
            $this->setError($exc->getMessage());
2557
            $this->edebug($exc->getMessage());
2558
            if ($this->exceptions) {
2559
                throw $exc;
2560
            }
2561
            return false;
2562
        }
2563
        return true;
2564
    }
2565
2566
    /**
2567
     * Return the array of attachments.
2568
     * @return array
2569
     */
2570
    public function getAttachments()
2571
    {
2572
        return $this->attachment;
2573
    }
2574
2575
    /**
2576
     * Attach all file, string, and binary attachments to the message.
2577
     * Returns an empty string on failure.
2578
     * @access protected
2579
     * @param string $disposition_type
2580
     * @param string $boundary
2581
     * @return string
2582
     */
2583
    protected function attachAll($disposition_type, $boundary)
2584
    {
2585
        // Return text of body
2586
        $mime = array();
2587
        $cidUniq = array();
2588
        $incl = array();
2589
2590
        // Add all attachments
2591
        foreach ($this->attachment as $attachment) {
2592
            // Check if it is a valid disposition_filter
2593
            if ($attachment[6] == $disposition_type) {
2594
                // Check for string attachment
2595
                $string = '';
2596
                $path = '';
2597
                $bString = $attachment[5];
2598
                if ($bString) {
2599
                    $string = $attachment[0];
2600
                } else {
2601
                    $path = $attachment[0];
2602
                }
2603
2604
                $inclhash = md5(serialize($attachment));
2605
                if (in_array($inclhash, $incl)) {
2606
                    continue;
2607
                }
2608
                $incl[] = $inclhash;
2609
                $name = $attachment[2];
2610
                $encoding = $attachment[3];
2611
                $type = $attachment[4];
2612
                $disposition = $attachment[6];
2613
                $cid = $attachment[7];
2614
                if ($disposition == 'inline' && array_key_exists($cid, $cidUniq)) {
2615
                    continue;
2616
                }
2617
                $cidUniq[$cid] = true;
2618
2619
                $mime[] = sprintf('--%s%s', $boundary, $this->LE);
2620
                //Only include a filename property if we have one
2621
                if (!empty($name)) {
2622
                    $mime[] = sprintf(
2623
                        'Content-Type: %s; name="%s"%s',
2624
                        $type,
2625
                        $this->encodeHeader($this->secureHeader($name)),
2626
                        $this->LE
2627
                    );
2628
                } else {
2629
                    $mime[] = sprintf(
2630
                        'Content-Type: %s%s',
2631
                        $type,
2632
                        $this->LE
2633
                    );
2634
                }
2635
                // RFC1341 part 5 says 7bit is assumed if not specified
2636
                if ($encoding != '7bit') {
2637
                    $mime[] = sprintf('Content-Transfer-Encoding: %s%s', $encoding, $this->LE);
2638
                }
2639
2640
                if ($disposition == 'inline') {
2641
                    $mime[] = sprintf('Content-ID: <%s>%s', $cid, $this->LE);
2642
                }
2643
2644
                // If a filename contains any of these chars, it should be quoted,
2645
                // but not otherwise: RFC2183 & RFC2045 5.1
2646
                // Fixes a warning in IETF's msglint MIME checker
2647
                // Allow for bypassing the Content-Disposition header totally
2648
                if (!(empty($disposition))) {
2649
                    $encoded_name = $this->encodeHeader($this->secureHeader($name));
2650
                    if (preg_match('/[ \(\)<>@,;:\\"\/\[\]\?=]/', $encoded_name)) {
2651
                        $mime[] = sprintf(
2652
                            'Content-Disposition: %s; filename="%s"%s',
2653
                            $disposition,
2654
                            $encoded_name,
2655
                            $this->LE . $this->LE
2656
                        );
2657
                    } else {
2658
                        if (!empty($encoded_name)) {
2659
                            $mime[] = sprintf(
2660
                                'Content-Disposition: %s; filename=%s%s',
2661
                                $disposition,
2662
                                $encoded_name,
2663
                                $this->LE . $this->LE
2664
                            );
2665
                        } else {
2666
                            $mime[] = sprintf(
2667
                                'Content-Disposition: %s%s',
2668
                                $disposition,
2669
                                $this->LE . $this->LE
2670
                            );
2671
                        }
2672
                    }
2673
                } else {
2674
                    $mime[] = $this->LE;
2675
                }
2676
2677
                // Encode as string attachment
2678
                if ($bString) {
2679
                    $mime[] = $this->encodeString($string, $encoding);
2680
                    if ($this->isError()) {
2681
                        return '';
2682
                    }
2683
                    $mime[] = $this->LE . $this->LE;
2684
                } else {
2685
                    $mime[] = $this->encodeFile($path, $encoding);
2686
                    if ($this->isError()) {
2687
                        return '';
2688
                    }
2689
                    $mime[] = $this->LE . $this->LE;
2690
                }
2691
            }
2692
        }
2693
2694
        $mime[] = sprintf('--%s--%s', $boundary, $this->LE);
2695
2696
        return implode('', $mime);
2697
    }
2698
2699
    /**
2700
     * Encode a file attachment in requested format.
2701
     * Returns an empty string on failure.
2702
     * @param string $path The full path to the file
2703
     * @param string $encoding The encoding to use; one of 'base64', '7bit', '8bit', 'binary', 'quoted-printable'
2704
     * @throws phpmailerException
2705
     * @access protected
2706
     * @return string
2707
     */
2708
    protected function encodeFile($path, $encoding = 'base64')
2709
    {
2710
        try {
2711
            if (!self::isPermittedPath($path) or !file_exists($path)) {
2712
                throw new phpmailerException($this->lang('file_open') . $path, self::STOP_CONTINUE);
2713
            }
2714
            $magic_quotes = false;
2715
            if( version_compare(PHP_VERSION, '7.4.0', '<') ) {
2716
                $magic_quotes = get_magic_quotes_runtime();
2717
            }
2718
            if ($magic_quotes) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $magic_quotes of type false|integer is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
2719
                if (version_compare(PHP_VERSION, '5.3.0', '<')) {
2720
                    set_magic_quotes_runtime(false);
0 ignored issues
show
Deprecated Code introduced by
The function set_magic_quotes_runtime() has been deprecated: 5.3.0 ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

2720
                    /** @scrutinizer ignore-deprecated */ set_magic_quotes_runtime(false);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
2721
                } else {
2722
                    //Doesn't exist in PHP 5.4, but we don't need to check because
2723
                    //get_magic_quotes_runtime always returns false in 5.4+
2724
                    //so it will never get here
2725
                    ini_set('magic_quotes_runtime', false);
0 ignored issues
show
Bug introduced by
false of type false is incompatible with the type string expected by parameter $newvalue of ini_set(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

2725
                    ini_set('magic_quotes_runtime', /** @scrutinizer ignore-type */ false);
Loading history...
2726
                }
2727
            }
2728
            $file_buffer = file_get_contents($path);
2729
            $file_buffer = $this->encodeString($file_buffer, $encoding);
2730
            if ($magic_quotes) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $magic_quotes of type false|integer is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
2731
                if (version_compare(PHP_VERSION, '5.3.0', '<')) {
2732
                    set_magic_quotes_runtime($magic_quotes);
0 ignored issues
show
Bug introduced by
$magic_quotes of type integer is incompatible with the type boolean expected by parameter $new_setting of set_magic_quotes_runtime(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

2732
                    set_magic_quotes_runtime(/** @scrutinizer ignore-type */ $magic_quotes);
Loading history...
Deprecated Code introduced by
The function set_magic_quotes_runtime() has been deprecated: 5.3.0 ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

2732
                    /** @scrutinizer ignore-deprecated */ set_magic_quotes_runtime($magic_quotes);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
2733
                } else {
2734
                    ini_set('magic_quotes_runtime', $magic_quotes);
2735
                }
2736
            }
2737
            return $file_buffer;
2738
        } catch (Exception $exc) {
2739
            $this->setError($exc->getMessage());
2740
            return '';
2741
        }
2742
    }
2743
2744
    /**
2745
     * Encode a string in requested format.
2746
     * Returns an empty string on failure.
2747
     * @param string $str The text to encode
2748
     * @param string $encoding The encoding to use; one of 'base64', '7bit', '8bit', 'binary', 'quoted-printable'
2749
     * @access public
2750
     * @return string
2751
     */
2752
    public function encodeString($str, $encoding = 'base64')
2753
    {
2754
        $encoded = '';
2755
        switch (strtolower($encoding)) {
2756
            case 'base64':
2757
                $encoded = chunk_split(base64_encode($str), 76, $this->LE);
2758
                break;
2759
            case '7bit':
2760
            case '8bit':
2761
                $encoded = $this->fixEOL($str);
2762
                // Make sure it ends with a line break
2763
                if (substr($encoded, -(strlen($this->LE))) != $this->LE) {
2764
                    $encoded .= $this->LE;
2765
                }
2766
                break;
2767
            case 'binary':
2768
                $encoded = $str;
2769
                break;
2770
            case 'quoted-printable':
2771
                $encoded = $this->encodeQP($str);
2772
                break;
2773
            default:
2774
                $this->setError($this->lang('encoding') . $encoding);
2775
                break;
2776
        }
2777
        return $encoded;
2778
    }
2779
2780
    /**
2781
     * Encode a header string optimally.
2782
     * Picks shortest of Q, B, quoted-printable or none.
2783
     * @access public
2784
     * @param string $str
2785
     * @param string $position
2786
     * @return string
2787
     */
2788
    public function encodeHeader($str, $position = 'text')
2789
    {
2790
        $matchcount = 0;
2791
        switch (strtolower($position)) {
2792
            case 'phrase':
2793
                if (!preg_match('/[\200-\377]/', $str)) {
2794
                    // Can't use addslashes as we don't know the value of magic_quotes_sybase
2795
                    $encoded = addcslashes($str, "\0..\37\177\\\"");
2796
                    if (($str == $encoded) && !preg_match('/[^A-Za-z0-9!#$%&\'*+\/=?^_`{|}~ -]/', $str)) {
2797
                        return ($encoded);
2798
                    } else {
2799
                        return ("\"$encoded\"");
2800
                    }
2801
                }
2802
                $matchcount = preg_match_all('/[^\040\041\043-\133\135-\176]/', $str, $matches);
2803
                break;
2804
            /** @noinspection PhpMissingBreakStatementInspection */
2805
            case 'comment':
2806
                $matchcount = preg_match_all('/[()"]/', $str, $matches);
2807
                // Intentional fall-through
2808
            case 'text':
2809
            default:
2810
                $matchcount += preg_match_all('/[\000-\010\013\014\016-\037\177-\377]/', $str, $matches);
2811
                break;
2812
        }
2813
2814
        //There are no chars that need encoding
2815
        if ($matchcount == 0) {
2816
            return ($str);
2817
        }
2818
2819
        $maxlen = 75 - 7 - strlen($this->CharSet);
2820
        // Try to select the encoding which should produce the shortest output
2821
        if ($matchcount > strlen($str) / 3) {
2822
            // More than a third of the content will need encoding, so B encoding will be most efficient
2823
            $encoding = 'B';
2824
            if (function_exists('mb_strlen') && $this->hasMultiBytes($str)) {
2825
                // Use a custom function which correctly encodes and wraps long
2826
                // multibyte strings without breaking lines within a character
2827
                $encoded = $this->base64EncodeWrapMB($str, "\n");
2828
            } else {
2829
                $encoded = base64_encode($str);
2830
                $maxlen -= $maxlen % 4;
2831
                $encoded = trim(chunk_split($encoded, $maxlen, "\n"));
2832
            }
2833
        } else {
2834
            $encoding = 'Q';
2835
            $encoded = $this->encodeQ($str, $position);
2836
            $encoded = $this->wrapText($encoded, $maxlen, true);
2837
            $encoded = str_replace('=' . self::CRLF, "\n", trim($encoded));
2838
        }
2839
2840
        $encoded = preg_replace('/^(.*)$/m', ' =?' . $this->CharSet . "?$encoding?\\1?=", $encoded);
2841
        $encoded = trim(str_replace("\n", $this->LE, $encoded));
2842
2843
        return $encoded;
2844
    }
2845
2846
    /**
2847
     * Check if a string contains multi-byte characters.
2848
     * @access public
2849
     * @param string $str multi-byte text to wrap encode
2850
     * @return boolean
2851
     */
2852
    public function hasMultiBytes($str)
2853
    {
2854
        if (function_exists('mb_strlen')) {
2855
            return (strlen($str) > mb_strlen($str, $this->CharSet));
2856
        } else { // Assume no multibytes (we can't handle without mbstring functions anyway)
2857
            return false;
2858
        }
2859
    }
2860
2861
    /**
2862
     * Does a string contain any 8-bit chars (in any charset)?
2863
     * @param string $text
2864
     * @return boolean
2865
     */
2866
    public function has8bitChars($text)
2867
    {
2868
        return (boolean)preg_match('/[\x80-\xFF]/', $text);
2869
    }
2870
2871
    /**
2872
     * Encode and wrap long multibyte strings for mail headers
2873
     * without breaking lines within a character.
2874
     * Adapted from a function by paravoid
2875
     * @link http://www.php.net/manual/en/function.mb-encode-mimeheader.php#60283
2876
     * @access public
2877
     * @param string $str multi-byte text to wrap encode
2878
     * @param string $linebreak string to use as linefeed/end-of-line
2879
     * @return string
2880
     */
2881
    public function base64EncodeWrapMB($str, $linebreak = null)
2882
    {
2883
        $start = '=?' . $this->CharSet . '?B?';
2884
        $end = '?=';
2885
        $encoded = '';
2886
        if ($linebreak === null) {
2887
            $linebreak = $this->LE;
2888
        }
2889
2890
        $mb_length = mb_strlen($str, $this->CharSet);
2891
        // Each line must have length <= 75, including $start and $end
2892
        $length = 75 - strlen($start) - strlen($end);
2893
        // Average multi-byte ratio
2894
        $ratio = $mb_length / strlen($str);
2895
        // Base64 has a 4:3 ratio
2896
        $avgLength = floor($length * $ratio * .75);
2897
2898
        for ($i = 0; $i < $mb_length; $i += $offset) {
2899
            $lookBack = 0;
2900
            do {
2901
                $offset = $avgLength - $lookBack;
2902
                $chunk = mb_substr($str, $i, $offset, $this->CharSet);
0 ignored issues
show
Bug introduced by
$offset of type double is incompatible with the type integer expected by parameter $length of mb_substr(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

2902
                $chunk = mb_substr($str, $i, /** @scrutinizer ignore-type */ $offset, $this->CharSet);
Loading history...
2903
                $chunk = base64_encode($chunk);
2904
                $lookBack++;
2905
            } while (strlen($chunk) > $length);
2906
            $encoded .= $chunk . $linebreak;
2907
        }
2908
2909
        // Chomp the last linefeed
2910
        $encoded = substr($encoded, 0, -strlen($linebreak));
2911
        return $encoded;
2912
    }
2913
2914
    /**
2915
     * Encode a string in quoted-printable format.
2916
     * According to RFC2045 section 6.7.
2917
     * @access public
2918
     * @param string $string The text to encode
2919
     * @param integer $line_max Number of chars allowed on a line before wrapping
2920
     * @return string
2921
     * @link http://www.php.net/manual/en/function.quoted-printable-decode.php#89417 Adapted from this comment
2922
     */
2923
    public function encodeQP($string, $line_max = 76)
2924
    {
2925
        // Use native function if it's available (>= PHP5.3)
2926
        if (function_exists('quoted_printable_encode')) {
2927
            return quoted_printable_encode($string);
2928
        }
2929
        // Fall back to a pure PHP implementation
2930
        $string = str_replace(
2931
            array('%20', '%0D%0A.', '%0D%0A', '%'),
2932
            array(' ', "\r\n=2E", "\r\n", '='),
2933
            rawurlencode($string)
2934
        );
2935
        return preg_replace('/[^\r\n]{' . ($line_max - 3) . '}[^=\r\n]{2}/', "$0=\r\n", $string);
2936
    }
2937
2938
    /**
2939
     * Backward compatibility wrapper for an old QP encoding function that was removed.
2940
     * @see PHPMailer::encodeQP()
2941
     * @access public
2942
     * @param string $string
2943
     * @param integer $line_max
2944
     * @param boolean $space_conv
2945
     * @return string
2946
     * @deprecated Use encodeQP instead.
2947
     */
2948
    public function encodeQPphp(
2949
        $string,
2950
        $line_max = 76,
2951
        /** @noinspection PhpUnusedParameterInspection */ $space_conv = false
2952
    ) {
2953
        return $this->encodeQP($string, $line_max);
2954
    }
2955
2956
    /**
2957
     * Encode a string using Q encoding.
2958
     * @link http://tools.ietf.org/html/rfc2047
2959
     * @param string $str the text to encode
2960
     * @param string $position Where the text is going to be used, see the RFC for what that means
2961
     * @access public
2962
     * @return string
2963
     */
2964
    public function encodeQ($str, $position = 'text')
2965
    {
2966
        // There should not be any EOL in the string
2967
        $pattern = '';
2968
        $encoded = str_replace(array("\r", "\n"), '', $str);
2969
        switch (strtolower($position)) {
2970
            case 'phrase':
2971
                // RFC 2047 section 5.3
2972
                $pattern = '^A-Za-z0-9!*+\/ -';
2973
                break;
2974
            /** @noinspection PhpMissingBreakStatementInspection */
2975
            case 'comment':
2976
                // RFC 2047 section 5.2
2977
                $pattern = '\(\)"';
2978
                // intentional fall-through
2979
                // for this reason we build the $pattern without including delimiters and []
2980
            case 'text':
2981
            default:
2982
                // RFC 2047 section 5.1
2983
                // Replace every high ascii, control, =, ? and _ characters
2984
                $pattern = '\000-\011\013\014\016-\037\075\077\137\177-\377' . $pattern;
2985
                break;
2986
        }
2987
        $matches = array();
2988
        if (preg_match_all("/[{$pattern}]/", $encoded, $matches)) {
2989
            // If the string contains an '=', make sure it's the first thing we replace
2990
            // so as to avoid double-encoding
2991
            $eqkey = array_search('=', $matches[0]);
2992
            if (false !== $eqkey) {
2993
                unset($matches[0][$eqkey]);
2994
                array_unshift($matches[0], '=');
2995
            }
2996
            foreach (array_unique($matches[0]) as $char) {
2997
                $encoded = str_replace($char, '=' . sprintf('%02X', ord($char)), $encoded);
2998
            }
2999
        }
3000
        // Replace every spaces to _ (more readable than =20)
3001
        return str_replace(' ', '_', $encoded);
3002
    }
3003
3004
    /**
3005
     * Add a string or binary attachment (non-filesystem).
3006
     * This method can be used to attach ascii or binary data,
3007
     * such as a BLOB record from a database.
3008
     * @param string $string String attachment data.
3009
     * @param string $filename Name of the attachment.
3010
     * @param string $encoding File encoding (see $Encoding).
3011
     * @param string $type File extension (MIME) type.
3012
     * @param string $disposition Disposition to use
3013
     * @return void
3014
     */
3015
    public function addStringAttachment(
3016
        $string,
3017
        $filename,
3018
        $encoding = 'base64',
3019
        $type = '',
3020
        $disposition = 'attachment'
3021
    ) {
3022
        // If a MIME type is not specified, try to work it out from the file name
3023
        if ($type == '') {
3024
            $type = self::filenameToType($filename);
3025
        }
3026
        // Append to $attachment array
3027
        $this->attachment[] = array(
3028
            0 => $string,
3029
            1 => $filename,
3030
            2 => basename($filename),
3031
            3 => $encoding,
3032
            4 => $type,
3033
            5 => true, // isStringAttachment
3034
            6 => $disposition,
3035
            7 => 0
3036
        );
3037
    }
3038
3039
    /**
3040
     * Add an embedded (inline) attachment from a file.
3041
     * This can include images, sounds, and just about any other document type.
3042
     * These differ from 'regular' attachments in that they are intended to be
3043
     * displayed inline with the message, not just attached for download.
3044
     * This is used in HTML messages that embed the images
3045
     * the HTML refers to using the $cid value.
3046
     * Never use a user-supplied path to a file!
3047
     * @param string $path Path to the attachment.
3048
     * @param string $cid Content ID of the attachment; Use this to reference
3049
     *        the content when using an embedded image in HTML.
3050
     * @param string $name Overrides the attachment name.
3051
     * @param string $encoding File encoding (see $Encoding).
3052
     * @param string $type File MIME type.
3053
     * @param string $disposition Disposition to use
3054
     * @return boolean True on successfully adding an attachment
3055
     */
3056
    public function addEmbeddedImage($path, $cid, $name = '', $encoding = 'base64', $type = '', $disposition = 'inline')
3057
    {
3058
        if (!self::isPermittedPath($path) or !@is_file($path)) {
3059
            $this->setError($this->lang('file_access') . $path);
3060
            return false;
3061
        }
3062
3063
        // If a MIME type is not specified, try to work it out from the file name
3064
        if ($type == '') {
3065
            $type = self::filenameToType($path);
3066
        }
3067
3068
        $filename = basename($path);
3069
        if ($name == '') {
3070
            $name = $filename;
3071
        }
3072
3073
        // Append to $attachment array
3074
        $this->attachment[] = array(
3075
            0 => $path,
3076
            1 => $filename,
3077
            2 => $name,
3078
            3 => $encoding,
3079
            4 => $type,
3080
            5 => false, // isStringAttachment
3081
            6 => $disposition,
3082
            7 => $cid
3083
        );
3084
        return true;
3085
    }
3086
3087
    /**
3088
     * Add an embedded stringified attachment.
3089
     * This can include images, sounds, and just about any other document type.
3090
     * Be sure to set the $type to an image type for images:
3091
     * JPEG images use 'image/jpeg', GIF uses 'image/gif', PNG uses 'image/png'.
3092
     * @param string $string The attachment binary data.
3093
     * @param string $cid Content ID of the attachment; Use this to reference
3094
     *        the content when using an embedded image in HTML.
3095
     * @param string $name
3096
     * @param string $encoding File encoding (see $Encoding).
3097
     * @param string $type MIME type.
3098
     * @param string $disposition Disposition to use
3099
     * @return boolean True on successfully adding an attachment
3100
     */
3101
    public function addStringEmbeddedImage(
3102
        $string,
3103
        $cid,
3104
        $name = '',
3105
        $encoding = 'base64',
3106
        $type = '',
3107
        $disposition = 'inline'
3108
    ) {
3109
        // If a MIME type is not specified, try to work it out from the name
3110
        if ($type == '' and !empty($name)) {
3111
            $type = self::filenameToType($name);
3112
        }
3113
3114
        // Append to $attachment array
3115
        $this->attachment[] = array(
3116
            0 => $string,
3117
            1 => $name,
3118
            2 => $name,
3119
            3 => $encoding,
3120
            4 => $type,
3121
            5 => true, // isStringAttachment
3122
            6 => $disposition,
3123
            7 => $cid
3124
        );
3125
        return true;
3126
    }
3127
3128
    /**
3129
     * Check if an inline attachment is present.
3130
     * @access public
3131
     * @return boolean
3132
     */
3133
    public function inlineImageExists()
3134
    {
3135
        foreach ($this->attachment as $attachment) {
3136
            if ($attachment[6] == 'inline') {
3137
                return true;
3138
            }
3139
        }
3140
        return false;
3141
    }
3142
3143
    /**
3144
     * Check if an attachment (non-inline) is present.
3145
     * @return boolean
3146
     */
3147
    public function attachmentExists()
3148
    {
3149
        foreach ($this->attachment as $attachment) {
3150
            if ($attachment[6] == 'attachment') {
3151
                return true;
3152
            }
3153
        }
3154
        return false;
3155
    }
3156
3157
    /**
3158
     * Check if this message has an alternative body set.
3159
     * @return boolean
3160
     */
3161
    public function alternativeExists()
3162
    {
3163
        return !empty($this->AltBody);
3164
    }
3165
3166
    /**
3167
     * Clear queued addresses of given kind.
3168
     * @access protected
3169
     * @param string $kind 'to', 'cc', or 'bcc'
3170
     * @return void
3171
     */
3172
    public function clearQueuedAddresses($kind)
3173
    {
3174
        $RecipientsQueue = $this->RecipientsQueue;
3175
        foreach ($RecipientsQueue as $address => $params) {
3176
            if ($params[0] == $kind) {
3177
                unset($this->RecipientsQueue[$address]);
3178
            }
3179
        }
3180
    }
3181
3182
    /**
3183
     * Clear all To recipients.
3184
     * @return void
3185
     */
3186
    public function clearAddresses()
3187
    {
3188
        foreach ($this->to as $to) {
3189
            unset($this->all_recipients[strtolower($to[0])]);
3190
        }
3191
        $this->to = array();
3192
        $this->clearQueuedAddresses('to');
3193
    }
3194
3195
    /**
3196
     * Clear all CC recipients.
3197
     * @return void
3198
     */
3199
    public function clearCCs()
3200
    {
3201
        foreach ($this->cc as $cc) {
3202
            unset($this->all_recipients[strtolower($cc[0])]);
3203
        }
3204
        $this->cc = array();
3205
        $this->clearQueuedAddresses('cc');
3206
    }
3207
3208
    /**
3209
     * Clear all BCC recipients.
3210
     * @return void
3211
     */
3212
    public function clearBCCs()
3213
    {
3214
        foreach ($this->bcc as $bcc) {
3215
            unset($this->all_recipients[strtolower($bcc[0])]);
3216
        }
3217
        $this->bcc = array();
3218
        $this->clearQueuedAddresses('bcc');
3219
    }
3220
3221
    /**
3222
     * Clear all ReplyTo recipients.
3223
     * @return void
3224
     */
3225
    public function clearReplyTos()
3226
    {
3227
        $this->ReplyTo = array();
3228
        $this->ReplyToQueue = array();
3229
    }
3230
3231
    /**
3232
     * Clear all recipient types.
3233
     * @return void
3234
     */
3235
    public function clearAllRecipients()
3236
    {
3237
        $this->to = array();
3238
        $this->cc = array();
3239
        $this->bcc = array();
3240
        $this->all_recipients = array();
3241
        $this->RecipientsQueue = array();
3242
    }
3243
3244
    /**
3245
     * Clear all filesystem, string, and binary attachments.
3246
     * @return void
3247
     */
3248
    public function clearAttachments()
3249
    {
3250
        $this->attachment = array();
3251
    }
3252
3253
    /**
3254
     * Clear all custom headers.
3255
     * @return void
3256
     */
3257
    public function clearCustomHeaders()
3258
    {
3259
        $this->CustomHeader = array();
3260
    }
3261
3262
    /**
3263
     * Add an error message to the error container.
3264
     * @access protected
3265
     * @param string $msg
3266
     * @return void
3267
     */
3268
    protected function setError($msg)
3269
    {
3270
        $this->error_count++;
3271
        if ($this->Mailer == 'smtp' and !is_null($this->smtp)) {
3272
            $lasterror = $this->smtp->getError();
3273
            if (!empty($lasterror['error'])) {
3274
                $msg .= $this->lang('smtp_error') . $lasterror['error'];
3275
                if (!empty($lasterror['detail'])) {
3276
                    $msg .= ' Detail: '. $lasterror['detail'];
3277
                }
3278
                if (!empty($lasterror['smtp_code'])) {
3279
                    $msg .= ' SMTP code: ' . $lasterror['smtp_code'];
3280
                }
3281
                if (!empty($lasterror['smtp_code_ex'])) {
3282
                    $msg .= ' Additional SMTP info: ' . $lasterror['smtp_code_ex'];
3283
                }
3284
            }
3285
        }
3286
        $this->ErrorInfo = $msg;
3287
    }
3288
3289
    /**
3290
     * Return an RFC 822 formatted date.
3291
     * @access public
3292
     * @return string
3293
     * @static
3294
     */
3295
    public static function rfcDate()
3296
    {
3297
        // Set the time zone to whatever the default is to avoid 500 errors
3298
        // Will default to UTC if it's not set properly in php.ini
3299
        date_default_timezone_set(@date_default_timezone_get());
3300
        return date('D, j M Y H:i:s O');
3301
    }
3302
3303
    /**
3304
     * Get the server hostname.
3305
     * Returns 'localhost.localdomain' if unknown.
3306
     * @access protected
3307
     * @return string
3308
     */
3309
    protected function serverHostname()
3310
    {
3311
        $result = 'localhost.localdomain';
3312
        if (!empty($this->Hostname)) {
3313
            $result = $this->Hostname;
3314
        } elseif (isset($_SERVER) and array_key_exists('SERVER_NAME', $_SERVER) and !empty($_SERVER['SERVER_NAME'])) {
3315
            $result = $_SERVER['SERVER_NAME'];
3316
        } elseif (function_exists('gethostname') && gethostname() !== false) {
3317
            $result = gethostname();
3318
        } elseif (php_uname('n') !== false) {
3319
            $result = php_uname('n');
3320
        }
3321
        return $result;
3322
    }
3323
3324
    /**
3325
     * Get an error message in the current language.
3326
     * @access protected
3327
     * @param string $key
3328
     * @return string
3329
     */
3330
    protected function lang($key)
3331
    {
3332
        if (count($this->language) < 1) {
3333
            $this->setLanguage('en'); // set the default language
3334
        }
3335
3336
        if (array_key_exists($key, $this->language)) {
3337
            if ($key == 'smtp_connect_failed') {
3338
                //Include a link to troubleshooting docs on SMTP connection failure
3339
                //this is by far the biggest cause of support questions
3340
                //but it's usually not PHPMailer's fault.
3341
                return $this->language[$key] . ' https://github.com/PHPMailer/PHPMailer/wiki/Troubleshooting';
3342
            }
3343
            return $this->language[$key];
3344
        } else {
3345
            //Return the key as a fallback
3346
            return $key;
3347
        }
3348
    }
3349
3350
    /**
3351
     * Check if an error occurred.
3352
     * @access public
3353
     * @return boolean True if an error did occur.
3354
     */
3355
    public function isError()
3356
    {
3357
        return ($this->error_count > 0);
3358
    }
3359
3360
    /**
3361
     * Ensure consistent line endings in a string.
3362
     * Changes every end of line from CRLF, CR or LF to $this->LE.
3363
     * @access public
3364
     * @param string $str String to fixEOL
3365
     * @return string
3366
     */
3367
    public function fixEOL($str)
3368
    {
3369
        // Normalise to \n
3370
        $nstr = str_replace(array("\r\n", "\r"), "\n", $str);
3371
        // Now convert LE as needed
3372
        if ($this->LE !== "\n") {
3373
            $nstr = str_replace("\n", $this->LE, $nstr);
3374
        }
3375
        return $nstr;
3376
    }
3377
3378
    /**
3379
     * Add a custom header.
3380
     * $name value can be overloaded to contain
3381
     * both header name and value (name:value)
3382
     * @access public
3383
     * @param string $name Custom header name
3384
     * @param string $value Header value
3385
     * @return void
3386
     */
3387
    public function addCustomHeader($name, $value = null)
3388
    {
3389
        if ($value === null) {
3390
            // Value passed in as name:value
3391
            $this->CustomHeader[] = explode(':', $name, 2);
3392
        } else {
3393
            $this->CustomHeader[] = array($name, $value);
3394
        }
3395
    }
3396
3397
    /**
3398
     * Returns all custom headers.
3399
     * @return array
3400
     */
3401
    public function getCustomHeaders()
3402
    {
3403
        return $this->CustomHeader;
3404
    }
3405
3406
    /**
3407
     * Create a message body from an HTML string.
3408
     * Automatically inlines images and creates a plain-text version by converting the HTML,
3409
     * overwriting any existing values in Body and AltBody.
3410
     * Do not source $message content from user input!
3411
     * $basedir is prepended when handling relative URLs, e.g. <img src="/images/a.png"> and must not be empty
3412
     * will look for an image file in $basedir/images/a.png and convert it to inline.
3413
     * If you don't provide a $basedir, relative paths will be left untouched (and thus probably break in email)
3414
     * If you don't want to apply these transformations to your HTML, just set Body and AltBody directly.
3415
     * @access public
3416
     * @param string $message HTML message string
3417
     * @param string $basedir Absolute path to a base directory to prepend to relative paths to images
3418
     * @param boolean|callable $advanced Whether to use the internal HTML to text converter
3419
     *    or your own custom converter @see PHPMailer::html2text()
3420
     * @return string $message The transformed message Body
3421
     */
3422
    public function msgHTML($message, $basedir = '', $advanced = false)
3423
    {
3424
        preg_match_all('/(src|background)=["\'](.*)["\']/Ui', $message, $images);
3425
        if (array_key_exists(2, $images)) {
3426
            if (strlen($basedir) > 1 && substr($basedir, -1) != '/') {
3427
                // Ensure $basedir has a trailing /
3428
                $basedir .= '/';
3429
            }
3430
            foreach ($images[2] as $imgindex => $url) {
3431
                // Convert data URIs into embedded images
3432
                if (preg_match('#^data:(image[^;,]*)(;base64)?,#', $url, $match)) {
3433
                    $data = substr($url, strpos($url, ','));
3434
                    if ($match[2]) {
3435
                        $data = base64_decode($data);
3436
                    } else {
3437
                        $data = rawurldecode($data);
3438
                    }
3439
                    $cid = md5($url) . '@phpmailer.0'; // RFC2392 S 2
3440
                    if ($this->addStringEmbeddedImage($data, $cid, 'embed' . $imgindex, 'base64', $match[1])) {
3441
                        $message = str_replace(
3442
                            $images[0][$imgindex],
3443
                            $images[1][$imgindex] . '="cid:' . $cid . '"',
3444
                            $message
3445
                        );
3446
                    }
3447
                    continue;
3448
                }
3449
                if (
3450
                    // Only process relative URLs if a basedir is provided (i.e. no absolute local paths)
3451
                    !empty($basedir)
3452
                    // Ignore URLs containing parent dir traversal (..)
3453
                    && (strpos($url, '..') === false)
3454
                    // Do not change urls that are already inline images
3455
                    && substr($url, 0, 4) !== 'cid:'
3456
                    // Do not change absolute URLs, including anonymous protocol
3457
                    && !preg_match('#^[a-z][a-z0-9+.-]*:?//#i', $url)
3458
                ) {
3459
                    $filename = basename($url);
3460
                    $directory = dirname($url);
3461
                    if ($directory == '.') {
3462
                        $directory = '';
3463
                    }
3464
                    $cid = md5($url) . '@phpmailer.0'; // RFC2392 S 2
3465
                    if (strlen($directory) > 1 && substr($directory, -1) != '/') {
3466
                        $directory .= '/';
3467
                    }
3468
                    if ($this->addEmbeddedImage(
3469
                        $basedir . $directory . $filename,
3470
                        $cid,
3471
                        $filename,
3472
                        'base64',
3473
                        self::_mime_types((string)self::mb_pathinfo($filename, PATHINFO_EXTENSION))
3474
                    )
3475
                    ) {
3476
                        $message = preg_replace(
3477
                            '/' . $images[1][$imgindex] . '=["\']' . preg_quote($url, '/') . '["\']/Ui',
3478
                            $images[1][$imgindex] . '="cid:' . $cid . '"',
3479
                            $message
3480
                        );
3481
                    }
3482
                }
3483
            }
3484
        }
3485
        $this->isHTML(true);
3486
        // Convert all message body line breaks to CRLF, makes quoted-printable encoding work much better
3487
        $this->Body = $this->normalizeBreaks($message);
3488
        $this->AltBody = $this->normalizeBreaks($this->html2text($message, $advanced));
3489
        if (!$this->alternativeExists()) {
3490
            $this->AltBody = 'To view this email message, open it in a program that understands HTML!' .
3491
                self::CRLF . self::CRLF;
3492
        }
3493
        return $this->Body;
3494
    }
3495
3496
    /**
3497
     * Convert an HTML string into plain text.
3498
     * This is used by msgHTML().
3499
     * Note - older versions of this function used a bundled advanced converter
3500
     * which was been removed for license reasons in #232.
3501
     * Example usage:
3502
     * <code>
3503
     * // Use default conversion
3504
     * $plain = $mail->html2text($html);
3505
     * // Use your own custom converter
3506
     * $plain = $mail->html2text($html, function($html) {
3507
     *     $converter = new MyHtml2text($html);
3508
     *     return $converter->get_text();
3509
     * });
3510
     * </code>
3511
     * @param string $html The HTML text to convert
3512
     * @param boolean|callable $advanced Any boolean value to use the internal converter,
3513
     *   or provide your own callable for custom conversion.
3514
     * @return string
3515
     */
3516
    public function html2text($html, $advanced = false)
3517
    {
3518
        if (is_callable($advanced)) {
3519
            return call_user_func($advanced, $html);
0 ignored issues
show
Bug introduced by
It seems like $advanced can also be of type boolean; however, parameter $function of call_user_func() does only seem to accept callable, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

3519
            return call_user_func(/** @scrutinizer ignore-type */ $advanced, $html);
Loading history...
3520
        }
3521
        return html_entity_decode(
3522
            trim(strip_tags(preg_replace('/<(head|title|style|script)[^>]*>.*?<\/\\1>/si', '', $html))),
3523
            ENT_QUOTES,
3524
            $this->CharSet
3525
        );
3526
    }
3527
3528
    /**
3529
     * Get the MIME type for a file extension.
3530
     * @param string $ext File extension
3531
     * @access public
3532
     * @return string MIME type of file.
3533
     * @static
3534
     */
3535
    public static function _mime_types($ext = '')
3536
    {
3537
        $mimes = array(
3538
            'xl'    => 'application/excel',
3539
            'js'    => 'application/javascript',
3540
            'hqx'   => 'application/mac-binhex40',
3541
            'cpt'   => 'application/mac-compactpro',
3542
            'bin'   => 'application/macbinary',
3543
            'doc'   => 'application/msword',
3544
            'word'  => 'application/msword',
3545
            'xlsx'  => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
3546
            'xltx'  => 'application/vnd.openxmlformats-officedocument.spreadsheetml.template',
3547
            'potx'  => 'application/vnd.openxmlformats-officedocument.presentationml.template',
3548
            'ppsx'  => 'application/vnd.openxmlformats-officedocument.presentationml.slideshow',
3549
            'pptx'  => 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
3550
            'sldx'  => 'application/vnd.openxmlformats-officedocument.presentationml.slide',
3551
            'docx'  => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
3552
            'dotx'  => 'application/vnd.openxmlformats-officedocument.wordprocessingml.template',
3553
            'xlam'  => 'application/vnd.ms-excel.addin.macroEnabled.12',
3554
            'xlsb'  => 'application/vnd.ms-excel.sheet.binary.macroEnabled.12',
3555
            'class' => 'application/octet-stream',
3556
            'dll'   => 'application/octet-stream',
3557
            'dms'   => 'application/octet-stream',
3558
            'exe'   => 'application/octet-stream',
3559
            'lha'   => 'application/octet-stream',
3560
            'lzh'   => 'application/octet-stream',
3561
            'psd'   => 'application/octet-stream',
3562
            'sea'   => 'application/octet-stream',
3563
            'so'    => 'application/octet-stream',
3564
            'oda'   => 'application/oda',
3565
            'pdf'   => 'application/pdf',
3566
            'ai'    => 'application/postscript',
3567
            'eps'   => 'application/postscript',
3568
            'ps'    => 'application/postscript',
3569
            'smi'   => 'application/smil',
3570
            'smil'  => 'application/smil',
3571
            'mif'   => 'application/vnd.mif',
3572
            'xls'   => 'application/vnd.ms-excel',
3573
            'ppt'   => 'application/vnd.ms-powerpoint',
3574
            'wbxml' => 'application/vnd.wap.wbxml',
3575
            'wmlc'  => 'application/vnd.wap.wmlc',
3576
            'dcr'   => 'application/x-director',
3577
            'dir'   => 'application/x-director',
3578
            'dxr'   => 'application/x-director',
3579
            'dvi'   => 'application/x-dvi',
3580
            'gtar'  => 'application/x-gtar',
3581
            'php3'  => 'application/x-httpd-php',
3582
            'php4'  => 'application/x-httpd-php',
3583
            'php'   => 'application/x-httpd-php',
3584
            'phtml' => 'application/x-httpd-php',
3585
            'phps'  => 'application/x-httpd-php-source',
3586
            'swf'   => 'application/x-shockwave-flash',
3587
            'sit'   => 'application/x-stuffit',
3588
            'tar'   => 'application/x-tar',
3589
            'tgz'   => 'application/x-tar',
3590
            'xht'   => 'application/xhtml+xml',
3591
            'xhtml' => 'application/xhtml+xml',
3592
            'zip'   => 'application/zip',
3593
            'mid'   => 'audio/midi',
3594
            'midi'  => 'audio/midi',
3595
            'mp2'   => 'audio/mpeg',
3596
            'mp3'   => 'audio/mpeg',
3597
            'mpga'  => 'audio/mpeg',
3598
            'aif'   => 'audio/x-aiff',
3599
            'aifc'  => 'audio/x-aiff',
3600
            'aiff'  => 'audio/x-aiff',
3601
            'ram'   => 'audio/x-pn-realaudio',
3602
            'rm'    => 'audio/x-pn-realaudio',
3603
            'rpm'   => 'audio/x-pn-realaudio-plugin',
3604
            'ra'    => 'audio/x-realaudio',
3605
            'wav'   => 'audio/x-wav',
3606
            'bmp'   => 'image/bmp',
3607
            'gif'   => 'image/gif',
3608
            'jpeg'  => 'image/jpeg',
3609
            'jpe'   => 'image/jpeg',
3610
            'jpg'   => 'image/jpeg',
3611
            'png'   => 'image/png',
3612
            'tiff'  => 'image/tiff',
3613
            'tif'   => 'image/tiff',
3614
            'eml'   => 'message/rfc822',
3615
            'css'   => 'text/css',
3616
            'html'  => 'text/html',
3617
            'htm'   => 'text/html',
3618
            'shtml' => 'text/html',
3619
            'log'   => 'text/plain',
3620
            'text'  => 'text/plain',
3621
            'txt'   => 'text/plain',
3622
            'rtx'   => 'text/richtext',
3623
            'rtf'   => 'text/rtf',
3624
            'vcf'   => 'text/vcard',
3625
            'vcard' => 'text/vcard',
3626
            'xml'   => 'text/xml',
3627
            'xsl'   => 'text/xml',
3628
            'mpeg'  => 'video/mpeg',
3629
            'mpe'   => 'video/mpeg',
3630
            'mpg'   => 'video/mpeg',
3631
            'mov'   => 'video/quicktime',
3632
            'qt'    => 'video/quicktime',
3633
            'rv'    => 'video/vnd.rn-realvideo',
3634
            'avi'   => 'video/x-msvideo',
3635
            'movie' => 'video/x-sgi-movie'
3636
        );
3637
        if (array_key_exists(strtolower($ext), $mimes)) {
3638
            return $mimes[strtolower($ext)];
3639
        }
3640
        return 'application/octet-stream';
3641
    }
3642
3643
    /**
3644
     * Map a file name to a MIME type.
3645
     * Defaults to 'application/octet-stream', i.e.. arbitrary binary data.
3646
     * @param string $filename A file name or full path, does not need to exist as a file
3647
     * @return string
3648
     * @static
3649
     */
3650
    public static function filenameToType($filename)
3651
    {
3652
        // In case the path is a URL, strip any query string before getting extension
3653
        $qpos = strpos($filename, '?');
3654
        if (false !== $qpos) {
3655
            $filename = substr($filename, 0, $qpos);
3656
        }
3657
        $pathinfo = self::mb_pathinfo($filename);
3658
        return self::_mime_types($pathinfo['extension']);
3659
    }
3660
3661
    /**
3662
     * Multi-byte-safe pathinfo replacement.
3663
     * Drop-in replacement for pathinfo(), but multibyte-safe, cross-platform-safe, old-version-safe.
3664
     * Works similarly to the one in PHP >= 5.2.0
3665
     * @link http://www.php.net/manual/en/function.pathinfo.php#107461
3666
     * @param string $path A filename or path, does not need to exist as a file
3667
     * @param integer|string $options Either a PATHINFO_* constant,
3668
     *      or a string name to return only the specified piece, allows 'filename' to work on PHP < 5.2
3669
     * @return string|array
3670
     * @static
3671
     */
3672
    public static function mb_pathinfo($path, $options = null)
3673
    {
3674
        $ret = array('dirname' => '', 'basename' => '', 'extension' => '', 'filename' => '');
3675
        $pathinfo = array();
3676
        if (preg_match('%^(.*?)[\\\\/]*(([^/\\\\]*?)(\.([^\.\\\\/]+?)|))[\\\\/\.]*$%im', $path, $pathinfo)) {
3677
            if (array_key_exists(1, $pathinfo)) {
3678
                $ret['dirname'] = $pathinfo[1];
3679
            }
3680
            if (array_key_exists(2, $pathinfo)) {
3681
                $ret['basename'] = $pathinfo[2];
3682
            }
3683
            if (array_key_exists(5, $pathinfo)) {
3684
                $ret['extension'] = $pathinfo[5];
3685
            }
3686
            if (array_key_exists(3, $pathinfo)) {
3687
                $ret['filename'] = $pathinfo[3];
3688
            }
3689
        }
3690
        switch ($options) {
3691
            case PATHINFO_DIRNAME:
3692
            case 'dirname':
3693
                return $ret['dirname'];
3694
            case PATHINFO_BASENAME:
3695
            case 'basename':
3696
                return $ret['basename'];
3697
            case PATHINFO_EXTENSION:
3698
            case 'extension':
3699
                return $ret['extension'];
3700
            case PATHINFO_FILENAME:
3701
            case 'filename':
3702
                return $ret['filename'];
3703
            default:
3704
                return $ret;
3705
        }
3706
    }
3707
3708
    /**
3709
     * Set or reset instance properties.
3710
     * You should avoid this function - it's more verbose, less efficient, more error-prone and
3711
     * harder to debug than setting properties directly.
3712
     * Usage Example:
3713
     * `$mail->set('SMTPSecure', 'tls');`
3714
     *   is the same as:
3715
     * `$mail->SMTPSecure = 'tls';`
3716
     * @access public
3717
     * @param string $name The property name to set
3718
     * @param mixed $value The value to set the property to
3719
     * @return boolean
3720
     * @TODO Should this not be using the __set() magic function?
3721
     */
3722
    public function set($name, $value = '')
3723
    {
3724
        if (property_exists($this, $name)) {
3725
            $this->$name = $value;
3726
            return true;
3727
        } else {
3728
            $this->setError($this->lang('variable_set') . $name);
3729
            return false;
3730
        }
3731
    }
3732
3733
    /**
3734
     * Strip newlines to prevent header injection.
3735
     * @access public
3736
     * @param string $str
3737
     * @return string
3738
     */
3739
    public function secureHeader($str)
3740
    {
3741
        return trim(str_replace(array("\r", "\n"), '', $str));
3742
    }
3743
3744
    /**
3745
     * Normalize line breaks in a string.
3746
     * Converts UNIX LF, Mac CR and Windows CRLF line breaks into a single line break format.
3747
     * Defaults to CRLF (for message bodies) and preserves consecutive breaks.
3748
     * @param string $text
3749
     * @param string $breaktype What kind of line break to use, defaults to CRLF
3750
     * @return string
3751
     * @access public
3752
     * @static
3753
     */
3754
    public static function normalizeBreaks($text, $breaktype = "\r\n")
3755
    {
3756
        return preg_replace('/(\r\n|\r|\n)/ms', $breaktype, $text);
3757
    }
3758
3759
    /**
3760
     * Set the public and private key files and password for S/MIME signing.
3761
     * @access public
3762
     * @param string $cert_filename
3763
     * @param string $key_filename
3764
     * @param string $key_pass Password for private key
3765
     * @param string $extracerts_filename Optional path to chain certificate
3766
     */
3767
    public function sign($cert_filename, $key_filename, $key_pass, $extracerts_filename = '')
3768
    {
3769
        $this->sign_cert_file = $cert_filename;
3770
        $this->sign_key_file = $key_filename;
3771
        $this->sign_key_pass = $key_pass;
3772
        $this->sign_extracerts_file = $extracerts_filename;
3773
    }
3774
3775
    /**
3776
     * Quoted-Printable-encode a DKIM header.
3777
     * @access public
3778
     * @param string $txt
3779
     * @return string
3780
     */
3781
    public function DKIM_QP($txt)
3782
    {
3783
        $line = '';
3784
        for ($i = 0; $i < strlen($txt); $i++) {
3785
            $ord = ord($txt[$i]);
3786
            if (((0x21 <= $ord) && ($ord <= 0x3A)) || $ord == 0x3C || ((0x3E <= $ord) && ($ord <= 0x7E))) {
3787
                $line .= $txt[$i];
3788
            } else {
3789
                $line .= '=' . sprintf('%02X', $ord);
3790
            }
3791
        }
3792
        return $line;
3793
    }
3794
3795
    /**
3796
     * Generate a DKIM signature.
3797
     * @access public
3798
     * @param string $signHeader
3799
     * @throws phpmailerException
3800
     * @return string The DKIM signature value
3801
     */
3802
    public function DKIM_Sign($signHeader)
3803
    {
3804
        if (!defined('PKCS7_TEXT')) {
3805
            if ($this->exceptions) {
3806
                throw new phpmailerException($this->lang('extension_missing') . 'openssl');
3807
            }
3808
            return '';
3809
        }
3810
        $privKeyStr = !empty($this->DKIM_private_string) ? $this->DKIM_private_string : file_get_contents($this->DKIM_private);
3811
        if ('' != $this->DKIM_passphrase) {
3812
            $privKey = openssl_pkey_get_private($privKeyStr, $this->DKIM_passphrase);
3813
        } else {
3814
            $privKey = openssl_pkey_get_private($privKeyStr);
3815
        }
3816
        //Workaround for missing digest algorithms in old PHP & OpenSSL versions
3817
        //@link http://stackoverflow.com/a/11117338/333340
3818
        if (version_compare(PHP_VERSION, '5.3.0') >= 0 and
3819
            in_array('sha256WithRSAEncryption', openssl_get_md_methods(true))) {
3820
            if (openssl_sign($signHeader, $signature, $privKey, 'sha256WithRSAEncryption')) {
0 ignored issues
show
Bug introduced by
'sha256WithRSAEncryption' of type string is incompatible with the type integer expected by parameter $signature_alg of openssl_sign(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

3820
            if (openssl_sign($signHeader, $signature, $privKey, /** @scrutinizer ignore-type */ 'sha256WithRSAEncryption')) {
Loading history...
3821
                openssl_pkey_free($privKey);
0 ignored issues
show
Bug introduced by
It seems like $privKey can also be of type false; however, parameter $key of openssl_pkey_free() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

3821
                openssl_pkey_free(/** @scrutinizer ignore-type */ $privKey);
Loading history...
3822
                return base64_encode($signature);
3823
            }
3824
        } else {
3825
            $pinfo = openssl_pkey_get_details($privKey);
0 ignored issues
show
Bug introduced by
It seems like $privKey can also be of type false; however, parameter $key of openssl_pkey_get_details() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

3825
            $pinfo = openssl_pkey_get_details(/** @scrutinizer ignore-type */ $privKey);
Loading history...
3826
            $hash = hash('sha256', $signHeader);
3827
            //'Magic' constant for SHA256 from RFC3447
3828
            //@link https://tools.ietf.org/html/rfc3447#page-43
3829
            $t = '3031300d060960864801650304020105000420' . $hash;
3830
            $pslen = $pinfo['bits'] / 8 - (strlen($t) / 2 + 3);
3831
            $eb = pack('H*', '0001' . str_repeat('FF', $pslen) . '00' . $t);
3832
3833
            if (openssl_private_encrypt($eb, $signature, $privKey, OPENSSL_NO_PADDING)) {
3834
                openssl_pkey_free($privKey);
3835
                return base64_encode($signature);
3836
            }
3837
        }
3838
        openssl_pkey_free($privKey);
3839
        return '';
3840
    }
3841
3842
    /**
3843
     * Generate a DKIM canonicalization header.
3844
     * @access public
3845
     * @param string $signHeader Header
3846
     * @return string
3847
     */
3848
    public function DKIM_HeaderC($signHeader)
3849
    {
3850
        $signHeader = preg_replace('/\r\n\s+/', ' ', $signHeader);
3851
        $lines = explode("\r\n", $signHeader);
3852
        foreach ($lines as $key => $line) {
3853
            list($heading, $value) = explode(':', $line, 2);
3854
            $heading = strtolower($heading);
3855
            $value = preg_replace('/\s{2,}/', ' ', $value); // Compress useless spaces
3856
            $lines[$key] = $heading . ':' . trim($value); // Don't forget to remove WSP around the value
3857
        }
3858
        $signHeader = implode("\r\n", $lines);
3859
        return $signHeader;
3860
    }
3861
3862
    /**
3863
     * Generate a DKIM canonicalization body.
3864
     * @access public
3865
     * @param string $body Message Body
3866
     * @return string
3867
     */
3868
    public function DKIM_BodyC($body)
3869
    {
3870
        if ($body == '') {
3871
            return "\r\n";
3872
        }
3873
        // stabilize line endings
3874
        $body = str_replace("\r\n", "\n", $body);
3875
        $body = str_replace("\n", "\r\n", $body);
3876
        // END stabilize line endings
3877
        while (substr($body, strlen($body) - 4, 4) == "\r\n\r\n") {
3878
            $body = substr($body, 0, strlen($body) - 2);
3879
        }
3880
        return $body;
3881
    }
3882
3883
    /**
3884
     * Create the DKIM header and body in a new message header.
3885
     * @access public
3886
     * @param string $headers_line Header lines
3887
     * @param string $subject Subject
3888
     * @param string $body Body
3889
     * @return string
3890
     */
3891
    public function DKIM_Add($headers_line, $subject, $body)
3892
    {
3893
        $DKIMsignatureType = 'rsa-sha256'; // Signature & hash algorithms
3894
        $DKIMcanonicalization = 'relaxed/simple'; // Canonicalization of header/body
3895
        $DKIMquery = 'dns/txt'; // Query method
3896
        $DKIMtime = time(); // Signature Timestamp = seconds since 00:00:00 - Jan 1, 1970 (UTC time zone)
3897
        $subject_header = "Subject: $subject";
3898
        $headers = explode($this->LE, $headers_line);
3899
        $from_header = '';
3900
        $to_header = '';
3901
        $date_header = '';
3902
        $current = '';
3903
        foreach ($headers as $header) {
3904
            if (strpos($header, 'From:') === 0) {
3905
                $from_header = $header;
3906
                $current = 'from_header';
3907
            } elseif (strpos($header, 'To:') === 0) {
3908
                $to_header = $header;
3909
                $current = 'to_header';
3910
            } elseif (strpos($header, 'Date:') === 0) {
3911
                $date_header = $header;
3912
                $current = 'date_header';
3913
            } else {
3914
                if (!empty($$current) && strpos($header, ' =?') === 0) {
3915
                    $$current .= $header;
3916
                } else {
3917
                    $current = '';
3918
                }
3919
            }
3920
        }
3921
        $from = str_replace('|', '=7C', $this->DKIM_QP($from_header));
3922
        $to = str_replace('|', '=7C', $this->DKIM_QP($to_header));
3923
        $date = str_replace('|', '=7C', $this->DKIM_QP($date_header));
3924
        $subject = str_replace(
3925
            '|',
3926
            '=7C',
3927
            $this->DKIM_QP($subject_header)
3928
        ); // Copied header fields (dkim-quoted-printable)
3929
        $body = $this->DKIM_BodyC($body);
3930
        $DKIMlen = strlen($body); // Length of body
3931
        $DKIMb64 = base64_encode(pack('H*', hash('sha256', $body))); // Base64 of packed binary SHA-256 hash of body
3932
        if ('' == $this->DKIM_identity) {
3933
            $ident = '';
3934
        } else {
3935
            $ident = ' i=' . $this->DKIM_identity . ';';
3936
        }
3937
        $dkimhdrs = 'DKIM-Signature: v=1; a=' .
3938
            $DKIMsignatureType . '; q=' .
3939
            $DKIMquery . '; l=' .
3940
            $DKIMlen . '; s=' .
3941
            $this->DKIM_selector .
3942
            ";\r\n" .
3943
            "\tt=" . $DKIMtime . '; c=' . $DKIMcanonicalization . ";\r\n" .
3944
            "\th=From:To:Date:Subject;\r\n" .
3945
            "\td=" . $this->DKIM_domain . ';' . $ident . "\r\n" .
3946
            "\tz=$from\r\n" .
3947
            "\t|$to\r\n" .
3948
            "\t|$date\r\n" .
3949
            "\t|$subject;\r\n" .
3950
            "\tbh=" . $DKIMb64 . ";\r\n" .
3951
            "\tb=";
3952
        $toSign = $this->DKIM_HeaderC(
3953
            $from_header . "\r\n" .
3954
            $to_header . "\r\n" .
3955
            $date_header . "\r\n" .
3956
            $subject_header . "\r\n" .
3957
            $dkimhdrs
3958
        );
3959
        $signed = $this->DKIM_Sign($toSign);
3960
        return $dkimhdrs . $signed . "\r\n";
3961
    }
3962
3963
    /**
3964
     * Detect if a string contains a line longer than the maximum line length allowed.
3965
     * @param string $str
3966
     * @return boolean
3967
     * @static
3968
     */
3969
    public static function hasLineLongerThanMax($str)
3970
    {
3971
        //+2 to include CRLF line break for a 1000 total
3972
        return (boolean)preg_match('/^(.{'.(self::MAX_LINE_LENGTH + 2).',})/m', $str);
3973
    }
3974
3975
    /**
3976
     * Allows for public read access to 'to' property.
3977
     * @note: Before the send() call, queued addresses (i.e. with IDN) are not yet included.
3978
     * @access public
3979
     * @return array
3980
     */
3981
    public function getToAddresses()
3982
    {
3983
        return $this->to;
3984
    }
3985
3986
    /**
3987
     * Allows for public read access to 'cc' property.
3988
     * @note: Before the send() call, queued addresses (i.e. with IDN) are not yet included.
3989
     * @access public
3990
     * @return array
3991
     */
3992
    public function getCcAddresses()
3993
    {
3994
        return $this->cc;
3995
    }
3996
3997
    /**
3998
     * Allows for public read access to 'bcc' property.
3999
     * @note: Before the send() call, queued addresses (i.e. with IDN) are not yet included.
4000
     * @access public
4001
     * @return array
4002
     */
4003
    public function getBccAddresses()
4004
    {
4005
        return $this->bcc;
4006
    }
4007
4008
    /**
4009
     * Allows for public read access to 'ReplyTo' property.
4010
     * @note: Before the send() call, queued addresses (i.e. with IDN) are not yet included.
4011
     * @access public
4012
     * @return array
4013
     */
4014
    public function getReplyToAddresses()
4015
    {
4016
        return $this->ReplyTo;
4017
    }
4018
4019
    /**
4020
     * Allows for public read access to 'all_recipients' property.
4021
     * @note: Before the send() call, queued addresses (i.e. with IDN) are not yet included.
4022
     * @access public
4023
     * @return array
4024
     */
4025
    public function getAllRecipientAddresses()
4026
    {
4027
        return $this->all_recipients;
4028
    }
4029
4030
    /**
4031
     * Perform a callback.
4032
     * @param boolean $isSent
4033
     * @param array $to
4034
     * @param array $cc
4035
     * @param array $bcc
4036
     * @param string $subject
4037
     * @param string $body
4038
     * @param string $from
4039
     */
4040
    protected function doCallback($isSent, $to, $cc, $bcc, $subject, $body, $from)
4041
    {
4042
        if (!empty($this->action_function) && is_callable($this->action_function)) {
4043
            $params = array($isSent, $to, $cc, $bcc, $subject, $body, $from);
4044
            call_user_func_array($this->action_function, $params);
4045
        }
4046
    }
4047
}
4048
4049
/**
4050
 * PHPMailer exception handler
4051
 * @package PHPMailer
4052
 */
4053
class phpmailerException extends Exception
4054
{
4055
    /**
4056
     * Prettify error message output
4057
     * @return string
4058
     */
4059
    public function errorMessage()
4060
    {
4061
        $errorMsg = '<strong>' . htmlspecialchars($this->getMessage()) . "</strong><br />\n";
4062
        return $errorMsg;
4063
    }
4064
}
4065