Completed
Push — master ( 8147a6...fa04e0 )
by Marcus
10:06 queued 07:38
created

class.phpmailer.php (1 issue)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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.21';
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
     *   string  $to            email address of the recipient
444
     *   string  $cc            cc email addresses
445
     *   string  $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 56
    /**
648
     * The maximum line length allowed by RFC 2822 section 2.1.1
649 56
     * @var integer
650
     */
651
    const MAX_LINE_LENGTH = 998;
652 56
653
    /**
654
     * Constructor.
655
     * @param boolean $exceptions Should we throw external exceptions?
656
     */
657 56
    public function __construct($exceptions = null)
658
    {
659
        if ($exceptions !== null) {
660 56
            $this->exceptions = (boolean)$exceptions;
661 56
        }
662
    }
663
664
    /**
665
     * Destructor.
666
     */
667
    public function __destruct()
668
    {
669
        //Close any open SMTP connection nicely
670
        $this->smtpClose();
671
    }
672
673
    /**
674
     * Call mail() in a safe_mode-aware fashion.
675
     * Also, unless sendmail_path points to sendmail (or something that
676 2
     * claims to be sendmail), don't pass params (not a perfect fix,
677
     * but it will do)
678
     * @param string $to To
679 2
     * @param string $subject Subject
680
     * @param string $body Message Body
681
     * @param string $header Additional Header(s)
682 2
     * @param string $params Params
683
     * @access private
684
     * @return boolean
685
     */
686 2
    private function mailPassthru($to, $subject, $body, $header, $params)
687
    {
688
        //Check overloading of mail function to avoid double-encoding
689 2
        if (ini_get('mbstring.func_overload') & 1) {
690
            $subject = $this->secureHeader($subject);
691 2
        } else {
692
            $subject = $this->encodeHeader($this->secureHeader($subject));
693
        }
694
695
        //Can't use additional_parameters in safe_mode, calling mail() with null params breaks
696
        //@link http://php.net/manual/en/function.mail.php
697
        if (ini_get('safe_mode') or !$this->UseSendmailOptions or is_null($params)) {
698
            $result = @mail($to, $subject, $body, $header);
699
        } else {
700
            $result = @mail($to, $subject, $body, $header, $params);
701 4
        }
702
        return $result;
703 4
    }
704
    /**
705
     * Output debugging info via user-defined method.
706
     * Only generates output if SMTP debug output is enabled (@see SMTP::$do_debug).
707 4
     * @see PHPMailer::$Debugoutput
708
     * @see PHPMailer::$SMTPDebug
709
     * @param string $str
710
     */
711 4
    protected function edebug($str)
712 4
    {
713
        if ($this->SMTPDebug <= 0) {
714
            return;
715
        }
716 4
        //Avoid clash with built-in function names
717
        if (!in_array($this->Debugoutput, array('error_log', 'html', 'echo')) and is_callable($this->Debugoutput)) {
718
            call_user_func($this->Debugoutput, $str, $this->SMTPDebug);
719
            return;
720
        }
721
        switch ($this->Debugoutput) {
722
            case 'error_log':
723
                //Don't output, just log
724
                error_log($str);
725 4
                break;
726 4
            case 'html':
727
                //Cleans up output a bit for a better looking, HTML-safe output
728 4
                echo htmlentities(
729 4
                    preg_replace('/[\r\n]+/', '', $str),
730 4
                    ENT_QUOTES,
731 4
                    'UTF-8'
732 4
                )
733 4
                . "<br>\n";
734 4
                break;
735 4
            case 'echo':
736
            default:
737
                //Normalize line breaks
738
                $str = preg_replace('/\r\n?/ms', "\n", $str);
739
                echo gmdate('Y-m-d H:i:s') . "\t" . str_replace(
740
                    "\n",
741
                    "\n                   \t                  ",
742 16
                    trim($str)
743
                ) . "\n";
744 16
        }
745 13
    }
746 13
747 3
    /**
748
     * Sets message type to HTML or plain.
749 16
     * @param boolean $isHtml True for HTML mode.
750
     * @return void
751
     */
752
    public function isHTML($isHtml = true)
753
    {
754
        if ($isHtml) {
755 1
            $this->ContentType = 'text/html';
756
        } else {
757 1
            $this->ContentType = 'text/plain';
758 1
        }
759
    }
760
761
    /**
762
     * Send messages using SMTP.
763
     * @return void
764 3
     */
765
    public function isSMTP()
766 3
    {
767 3
        $this->Mailer = 'smtp';
768
    }
769
770
    /**
771
     * Send messages using PHP's mail() function.
772
     * @return void
773 2
     */
774
    public function isMail()
775 2
    {
776
        $this->Mailer = 'mail';
777 2
    }
778
779
    /**
780 2
     * Send messages using $Sendmail.
781
     * @return void
782 2
     */
783 2
    public function isSendmail()
784
    {
785
        $ini_sendmail_path = ini_get('sendmail_path');
786
787
        if (!stristr($ini_sendmail_path, 'sendmail')) {
788
            $this->Sendmail = '/usr/sbin/sendmail';
789 1
        } else {
790
            $this->Sendmail = $ini_sendmail_path;
791 1
        }
792
        $this->Mailer = 'sendmail';
793 1
    }
794 1
795 1
    /**
796
     * Send messages using qmail.
797
     * @return void
798 1
     */
799 1
    public function isQmail()
800
    {
801
        $ini_sendmail_path = ini_get('sendmail_path');
802
803
        if (!stristr($ini_sendmail_path, 'qmail')) {
804
            $this->Sendmail = '/var/qmail/bin/qmail-inject';
805
        } else {
806
            $this->Sendmail = $ini_sendmail_path;
807 56
        }
808
        $this->Mailer = 'qmail';
809 56
    }
810
811
    /**
812
     * Add a "To" address.
813
     * @param string $address The email address to send to
814
     * @param string $name
815
     * @return boolean true on success, false if address already used or invalid in some way
816
     */
817
    public function addAddress($address, $name = '')
818
    {
819 56
        return $this->addOrEnqueueAnAddress('to', $address, $name);
820
    }
821 56
822
    /**
823
     * Add a "CC" address.
824
     * @note: This function works with the SMTP mailer on win32, not with the "mail" mailer.
825
     * @param string $address The email address to send to
826
     * @param string $name
827
     * @return boolean true on success, false if address already used or invalid in some way
828
     */
829
    public function addCC($address, $name = '')
830
    {
831 3
        return $this->addOrEnqueueAnAddress('cc', $address, $name);
832
    }
833 3
834
    /**
835
     * Add a "BCC" address.
836
     * @note: This function works with the SMTP mailer on win32, not with the "mail" mailer.
837
     * @param string $address The email address to send to
838
     * @param string $name
839
     * @return boolean true on success, false if address already used or invalid in some way
840
     */
841
    public function addBCC($address, $name = '')
842 56
    {
843
        return $this->addOrEnqueueAnAddress('bcc', $address, $name);
844 56
    }
845
846
    /**
847
     * Add a "Reply-To" address.
848
     * @param string $address The email address to reply to
849
     * @param string $name
850
     * @return boolean true on success, false if address already used or invalid in some way
851
     */
852
    public function addReplyTo($address, $name = '')
853
    {
854
        return $this->addOrEnqueueAnAddress('Reply-To', $address, $name);
855
    }
856
857
    /**
858
     * Add an address to one of the recipient arrays or to the ReplyTo array. Because PHPMailer
859 56
     * can't validate addresses with an IDN without knowing the PHPMailer::$CharSet (that can still
860
     * be modified after calling this function), addition of such addresses is delayed until send().
861 56
     * Addresses that have been added already return false, but do not throw exceptions.
862 56
     * @param string $kind One of 'to', 'cc', 'bcc', or 'ReplyTo'
863 56
     * @param string $address The email address to send, resp. to reply to
864
     * @param string $name
865 1
     * @throws phpmailerException
866 1
     * @return boolean true on success, false if address already used or invalid in some way
867 1
     * @access protected
868 1
     */
869
    protected function addOrEnqueueAnAddress($kind, $address, $name)
870
    {
871 1
        $address = trim($address);
872
        $name = trim(preg_replace('/[\r\n]+/', '', $name)); //Strip breaks and trim
873 56
        if (($pos = strrpos($address, '@')) === false) {
874
            // At-sign is misssing.
875 56
            $error_message = $this->lang('invalid_address') . " (addAnAddress $kind): $address";
876 2
            $this->setError($error_message);
877 2
            $this->edebug($error_message);
878 2
            if ($this->exceptions) {
879 2
                throw new phpmailerException($error_message);
880
            }
881 1
            return false;
882 2
        }
883 2
        $params = array($kind, $address, $name);
884 2
        // Enqueue addresses with IDN until we know the PHPMailer::$CharSet.
885
        if ($this->has8bitChars(substr($address, ++$pos)) and $this->idnSupported()) {
886
            if ($kind != 'Reply-To') {
887 1
                if (!array_key_exists($address, $this->RecipientsQueue)) {
888
                    $this->RecipientsQueue[$address] = $params;
889
                    return true;
890 56
                }
891
            } else {
892
                if (!array_key_exists($address, $this->ReplyToQueue)) {
893
                    $this->ReplyToQueue[$address] = $params;
894
                    return true;
895
                }
896
            }
897
            return false;
898
        }
899
        // Immediately add standard addresses without IDN.
900
        return call_user_func_array(array($this, 'addAnAddress'), $params);
901
    }
902
903 56
    /**
904
     * Add an address to one of the recipient arrays or to the ReplyTo array.
905 56
     * Addresses that have been added already return false, but do not throw exceptions.
906
     * @param string $kind One of 'to', 'cc', 'bcc', or 'ReplyTo'
907
     * @param string $address The email address to send, resp. to reply to
908
     * @param string $name
909
     * @throws phpmailerException
910
     * @return boolean true on success, false if address already used or invalid in some way
911
     * @access protected
912
     */
913
    protected function addAnAddress($kind, $address, $name = '')
914 56
    {
915 2
        if (!in_array($kind, array('to', 'cc', 'bcc', 'Reply-To'))) {
916 2
            $error_message = $this->lang('Invalid recipient kind: ') . $kind;
917 2
            $this->setError($error_message);
918 2
            $this->edebug($error_message);
919
            if ($this->exceptions) {
920
                throw new phpmailerException($error_message);
921 2
            }
922
            return false;
923 56
        }
924 56
        if (!$this->validateAddress($address)) {
925 56
            $error_message = $this->lang('invalid_address') . " (addAnAddress $kind): $address";
926 56
            $this->setError($error_message);
927 56
            $this->edebug($error_message);
928
            if ($this->exceptions) {
929 2
                throw new phpmailerException($error_message);
930 56
            }
931 56
            return false;
932 56
        }
933
        if ($kind != 'Reply-To') {
934
            if (!array_key_exists(strtolower($address), $this->all_recipients)) {
935 2
                array_push($this->$kind, array($address, $name));
936
                $this->all_recipients[strtolower($address)] = true;
937
                return true;
938
            }
939
        } else {
940
            if (!array_key_exists(strtolower($address), $this->ReplyTo)) {
941
                $this->ReplyTo[strtolower($address)] = array($address, $name);
942
                return true;
943
            }
944
        }
945
        return false;
946
    }
947
948 1
    /**
949
     * Parse and validate a string containing one or more RFC822-style comma-separated email addresses
950 1
     * of the form "display name <address>" into an array of name/address pairs.
951 1
     * Uses the imap_rfc822_parse_adrlist function if the IMAP extension is available.
952
     * Note that quotes in the name part are removed.
953 1
     * @param string $addrstr The address list string
954 1
     * @param bool $useimap Whether to use the IMAP extension to parse the list
955 1
     * @return array
956 1
     * @link http://www.andrew.cmu.edu/user/agreen1/testing/mrbs/web/Mail/RFC822.php A more careful implementation
957 1
     */
958 1
    public function parseAddresses($addrstr, $useimap = true)
959 1
    {
960 1
        $addresses = array();
961 1
        if ($useimap and function_exists('imap_rfc822_parse_adrlist')) {
962 1
            //Use this built-in parser if it's available
963 1
            $list = imap_rfc822_parse_adrlist($addrstr, '');
964 1
            foreach ($list as $address) {
965
                if ($address->host != '.SYNTAX-ERROR.') {
966 1
                    if ($this->validateAddress($address->mailbox . '@' . $address->host)) {
967 1
                        $addresses[] = array(
968 1
                            'name' => (property_exists($address, 'personal') ? $address->personal : ''),
969
                            'address' => $address->mailbox . '@' . $address->host
970 1
                        );
971
                    }
972 1
                }
973 1
            }
974 1
        } else {
975
            //Use this simpler parser
976 1
            $list = explode(',', $addrstr);
977 1
            foreach ($list as $address) {
978 1
                $address = trim($address);
979 1
                //Is there a separate name part?
980 1
                if (strpos($address, '<') === false) {
981 1
                    //No separate name, just use the whole thing
982 1
                    if ($this->validateAddress($address)) {
983 1
                        $addresses[] = array(
984
                            'name' => '',
985 1
                            'address' => $address
986 1
                        );
987
                    }
988 1
                } else {
989
                    list($name, $email) = explode('<', $address);
990 1
                    $email = trim(str_replace('>', '', $email));
991
                    if ($this->validateAddress($email)) {
992
                        $addresses[] = array(
993
                            'name' => trim(str_replace(array('"', "'"), '', $name)),
994
                            'address' => $email
995
                        );
996
                    }
997
                }
998
            }
999
        }
1000
        return $addresses;
1001 1
    }
1002
1003 1
    /**
1004 1
     * Set the From and FromName properties.
1005
     * @param string $address
1006 1
     * @param string $name
1007 1
     * @param boolean $auto Whether to also set the Sender address, defaults to true
1008 1
     * @throws phpmailerException
1009 1
     * @return boolean
1010 1
     */
1011 1
    public function setFrom($address, $name = '', $auto = true)
1012 1
    {
1013
        $address = trim($address);
1014
        $name = trim(preg_replace('/[\r\n]+/', '', $name)); //Strip breaks and trim
1015 1
        // Don't validate now addresses with IDN. Will be done in send().
1016
        if (($pos = strrpos($address, '@')) === false or
1017 1
            (!$this->has8bitChars(substr($address, ++$pos)) or !$this->idnSupported()) and
1018 1
            !$this->validateAddress($address)) {
1019 1
            $error_message = $this->lang('invalid_address') . " (setFrom) $address";
1020 1
            $this->setError($error_message);
1021 1
            $this->edebug($error_message);
1022 1
            if ($this->exceptions) {
1023 1
                throw new phpmailerException($error_message);
1024 1
            }
1025
            return false;
1026
        }
1027
        $this->From = $address;
1028
        $this->FromName = $name;
1029
        if ($auto) {
1030
            if (empty($this->Sender)) {
1031
                $this->Sender = $address;
1032
            }
1033
        }
1034 1
        return true;
1035
    }
1036 1
1037
    /**
1038
     * Return the Message-ID header of the last email.
1039
     * Technically this is the value from the last time the headers were created,
1040
     * but it's also the message ID of the last sent message except in
1041
     * pathological cases.
1042
     * @return string
1043
     */
1044
    public function getLastMessageID()
1045
    {
1046
        return $this->lastMessageID;
1047
    }
1048
1049
    /**
1050
     * Check that a string looks like an email address.
1051
     * @param string $address The email address to check
1052
     * @param string|callable $patternselect A selector for the validation pattern to use :
1053
     * * `auto` Pick best pattern automatically;
1054
     * * `pcre8` Use the squiloople.com pattern, requires PCRE > 8.0, PHP >= 5.3.2, 5.2.14;
1055
     * * `pcre` Use old PCRE implementation;
1056
     * * `php` Use PHP built-in FILTER_VALIDATE_EMAIL;
1057
     * * `html5` Use the pattern given by the HTML5 spec for 'email' type form input elements.
1058 56
     * * `noregex` Don't use a regex: super fast, really dumb.
1059
     * Alternatively you may pass in a callable to inject your own validator, for example:
1060 56
     * PHPMailer::validateAddress('[email protected]', function($address) {
1061 56
     *     return (strpos($address, '@') !== false);
1062 56
     * });
1063 56
     * You can also set the PHPMailer::$validator static to a callable, allowing built-in methods to use your validator.
1064 1
     * @return boolean
1065
     * @static
1066
     * @access public
1067 56
     */
1068 1
    public static function validateAddress($address, $patternselect = null)
1069
    {
1070 56
        if (is_null($patternselect)) {
1071
            $patternselect = self::$validator;
1072
        }
1073 4
        if (is_callable($patternselect)) {
1074
            return call_user_func($patternselect, $address);
1075 4
        }
1076 4
        //Reject line breaks in addresses; it's valid RFC5322, but not RFC5321
1077 4
        if (strpos($address, "\n") !== false or strpos($address, "\r") !== false) {
1078
            return false;
1079
        }
1080 4
        if (!$patternselect or $patternselect == 'auto') {
1081
            //Check this constant first so it works when extension_loaded() is disabled by safe mode
1082
            //Constant was added in PHP 5.2.4
1083
            if (defined('PCRE_VERSION')) {
1084
                //This pattern can get stuck in a recursive loop in PCRE <= 8.0.2
1085
                if (version_compare(PCRE_VERSION, '8.0.3') >= 0) {
1086
                    $patternselect = 'pcre8';
1087
                } else {
1088
                    $patternselect = 'pcre';
1089
                }
1090
            } elseif (function_exists('extension_loaded') and extension_loaded('pcre')) {
1091 4
                //Fall back to older PCRE
1092
                $patternselect = 'pcre';
1093 56
            } else {
1094
                //Filter_var appeared in PHP 5.2.0 and does not require the PCRE extension
1095
                if (version_compare(PHP_VERSION, '5.2.0') >= 0) {
1096
                    $patternselect = 'php';
1097
                } else {
1098
                    $patternselect = 'noregex';
1099
                }
1100 4
            }
1101
        }
1102 4
        switch ($patternselect) {
1103 4
            case 'pcre8':
1104 4
                /**
1105 4
                 * Uses the same RFC5322 regex on which FILTER_VALIDATE_EMAIL is based, but allows dotless domains.
1106 4
                 * @link http://squiloople.com/2009/12/20/email-address-validation/
1107 4
                 * @copyright 2009-2010 Michael Rushton
1108 4
                 * Feel free to use and redistribute this code. But please keep this copyright notice.
1109 4
                 */
1110
                return (boolean)preg_match(
1111 4
                    '/^(?!(?>(?1)"?(?>\\\[ -~]|[^"])"?(?1)){255,})(?!(?>(?1)"?(?>\\\[ -~]|[^"])"?(?1)){65,}@)' .
1112 54
                    '((?>(?>(?>((?>(?>(?>\x0D\x0A)?[\t ])+|(?>[\t ]*\x0D\x0A)?[\t ]+)?)(\((?>(?2)' .
1113
                    '(?>[\x01-\x08\x0B\x0C\x0E-\'*-\[\]-\x7F]|\\\[\x00-\x7F]|(?3)))*(?2)\)))+(?2))|(?2))?)' .
1114 1
                    '([!#-\'*+\/-9=?^-~-]+|"(?>(?2)(?>[\x01-\x08\x0B\x0C\x0E-!#-\[\]-\x7F]|\\\[\x00-\x7F]))*' .
1115
                    '(?2)")(?>(?1)\.(?1)(?4))*(?1)@(?!(?1)[a-z0-9-]{64,})(?1)(?>([a-z0-9](?>[a-z0-9-]*[a-z0-9])?)' .
1116 1
                    '(?>(?1)\.(?!(?1)[a-z0-9-]{64,})(?1)(?5)){0,126}|\[(?:(?>IPv6:(?>([a-f0-9]{1,4})(?>:(?6)){7}' .
1117 1
                    '|(?!(?:.*[a-f0-9][:\]]){8,})((?6)(?>:(?6)){0,6})?::(?7)?))|(?>(?>IPv6:(?>(?6)(?>:(?6)){5}:' .
1118 1
                    '|(?!(?:.*[a-f0-9]:){6,})(?8)?::(?>((?6)(?>:(?6)){0,4}):)?))?(25[0-5]|2[0-4][0-9]|1[0-9]{2}' .
1119 1
                    '|[1-9]?[0-9])(?>\.(?9)){3}))\])(?1)$/isD',
1120 1
                    $address
1121 1
                );
1122 1
            case 'pcre':
1123 1
                //An older regex that doesn't need a recent PCRE
1124 1
                return (boolean)preg_match(
1125
                    '/^(?!(?>"?(?>\\\[ -~]|[^"])"?){255,})(?!(?>"?(?>\\\[ -~]|[^"])"?){65,}@)(?>' .
1126 1
                    '[!#-\'*+\/-9=?^-~-]+|"(?>(?>[\x01-\x08\x0B\x0C\x0E-!#-\[\]-\x7F]|\\\[\x00-\xFF]))*")' .
1127 54
                    '(?>\.(?>[!#-\'*+\/-9=?^-~-]+|"(?>(?>[\x01-\x08\x0B\x0C\x0E-!#-\[\]-\x7F]|\\\[\x00-\xFF]))*"))*' .
1128
                    '@(?>(?![a-z0-9-]{64,})(?>[a-z0-9](?>[a-z0-9-]*[a-z0-9])?)(?>\.(?![a-z0-9-]{64,})' .
1129
                    '(?>[a-z0-9](?>[a-z0-9-]*[a-z0-9])?)){0,126}|\[(?:(?>IPv6:(?>(?>[a-f0-9]{1,4})(?>:' .
1130
                    '[a-f0-9]{1,4}){7}|(?!(?:.*[a-f0-9][:\]]){8,})(?>[a-f0-9]{1,4}(?>:[a-f0-9]{1,4}){0,6})?' .
1131
                    '::(?>[a-f0-9]{1,4}(?>:[a-f0-9]{1,4}){0,6})?))|(?>(?>IPv6:(?>[a-f0-9]{1,4}(?>:' .
1132
                    '[a-f0-9]{1,4}){5}:|(?!(?:.*[a-f0-9]:){6,})(?>[a-f0-9]{1,4}(?>:[a-f0-9]{1,4}){0,4})?' .
1133
                    '::(?>(?:[a-f0-9]{1,4}(?>:[a-f0-9]{1,4}){0,4}):)?))?(?>25[0-5]|2[0-4][0-9]|1[0-9]{2}' .
1134
                    '|[1-9]?[0-9])(?>\.(?>25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3}))\])$/isD',
1135
                    $address
1136
                );
1137 54
            case 'html5':
1138
                /**
1139
                 * This is the pattern used in the HTML5 spec for validation of 'email' type form input elements.
1140 1
                 * @link http://www.whatwg.org/specs/web-apps/current-work/#e-mail-state-(type=email)
1141 1
                 */
1142 1
                return (boolean)preg_match(
1143 54
                    '/^[a-zA-Z0-9.!#$%&\'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}' .
1144 54
                    '[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/sD',
1145 54
                    $address
1146
                );
1147
            case 'noregex':
1148
                //No PCRE! Do something _very_ approximate!
1149
                //Check the address is 3 chars or longer and contains an @ that's not the first or last char
1150
                return (strlen($address) >= 3
1151
                    and strpos($address, '@') >= 1
1152
                    and strpos($address, '@') != strlen($address) - 1);
1153
            case 'php':
1154 41
            default:
1155
                return (boolean)filter_var($address, FILTER_VALIDATE_EMAIL);
1156
        }
1157 41
    }
1158
1159
    /**
1160
     * Tells whether IDNs (Internationalized Domain Names) are supported or not. This requires the
1161
     * "intl" and "mbstring" PHP extensions.
1162
     * @return bool "true" if required functions for IDN support are present
1163
     */
1164
    public function idnSupported()
1165
    {
1166
        // @TODO: Write our own "idn_to_ascii" function for PHP <= 5.2.
1167
        return function_exists('idn_to_ascii') and function_exists('mb_convert_encoding');
1168
    }
1169
1170
    /**
1171 41
     * Converts IDN in given email address to its ASCII form, also known as punycode, if possible.
1172
     * Important: Address must be passed in same encoding as currently set in PHPMailer::$CharSet.
1173
     * This function silently returns unmodified address if:
1174 41
     * - No conversion is necessary (i.e. domain name is not an IDN, or is already in ASCII form)
1175 41
     * - Conversion to punycode is impossible (e.g. required PHP functions are not available)
1176 41
     *   or fails for any reason (e.g. domain has characters not allowed in an IDN)
1177 41
     * @see PHPMailer::$CharSet
1178
     * @param string $address The email address to convert
1179 41
     * @return string The encoded address in ASCII form
1180 3
     */
1181 3
    public function punyencodeAddress($address)
1182 3
    {
1183 3
        // Verify we have required functions, CharSet, and at-sign.
1184 3
        if ($this->idnSupported() and
1185
            !empty($this->CharSet) and
1186
            ($pos = strrpos($address, '@')) !== false) {
1187 41
            $domain = substr($address, ++$pos);
1188 41
            // Verify CharSet string is a valid one, and domain properly encoded in this CharSet.
1189
            if ($this->has8bitChars($domain) and @mb_check_encoding($domain, $this->CharSet)) {
1190
                $domain = mb_convert_encoding($domain, 'UTF-8', $this->CharSet);
1191
                if (($punycode = defined('INTL_IDNA_VARIANT_UTS46') ?
1192
                    idn_to_ascii($domain, 0, INTL_IDNA_VARIANT_UTS46) :
1193
                    idn_to_ascii($domain)) !== false) {
1194
                    return substr($address, 0, $pos) . $punycode;
1195
                }
1196
            }
1197 36
        }
1198
        return $address;
1199
    }
1200 36
1201 3
    /**
1202
     * Create a message and send it.
1203 36
     * Uses the sending method specified by $Mailer.
1204
     * @throws phpmailerException
1205
     * @return boolean false on error - See the ErrorInfo property for details of the error.
1206
     */
1207
    public function send()
1208
    {
1209
        try {
1210
            if (!$this->preSend()) {
1211
                return false;
1212
            }
1213
            return $this->postSend();
1214
        } catch (phpmailerException $exc) {
1215
            $this->mailHeader = '';
1216
            $this->setError($exc->getMessage());
1217
            if ($this->exceptions) {
1218
                throw $exc;
1219 41
            }
1220
            return false;
1221
        }
1222 41
    }
1223 41
1224
    /**
1225
     * Prepare a message for sending.
1226 41
     * @throws phpmailerException
1227 2
     * @return boolean
1228 2
     */
1229 41
    public function preSend()
1230 41
    {
1231 1
        try {
1232
            $this->error_count = 0; // Reset errors
1233
            $this->mailHeader = '';
1234
1235 41
            // Dequeue recipient and Reply-To addresses with IDN
1236 41
            foreach (array_merge($this->RecipientsQueue, $this->ReplyToQueue) as $params) {
1237 41
                $params[1] = $this->punyencodeAddress($params[1]);
1238 40
                call_user_func_array(array($this, 'addAnAddress'), $params);
1239
            }
1240 41
            if ((count($this->to) + count($this->cc) + count($this->bcc)) < 1) {
1241 41
                throw new phpmailerException($this->lang('provide_address'), self::STOP_CRITICAL);
1242 1
            }
1243 1
1244 1
            // Validate From, Sender, and ConfirmReadingTo addresses
1245 1
            foreach (array('From', 'Sender', 'ConfirmReadingTo') as $address_kind) {
1246
                $this->$address_kind = trim($this->$address_kind);
1247
                if (empty($this->$address_kind)) {
1248 1
                    continue;
1249
                }
1250 41
                $this->$address_kind = $this->punyencodeAddress($this->$address_kind);
1251
                if (!$this->validateAddress($this->$address_kind)) {
1252
                    $error_message = $this->lang('invalid_address') . ' (punyEncode) ' . $this->$address_kind;
1253 41
                    $this->setError($error_message);
1254 7
                    $this->edebug($error_message);
1255 7
                    if ($this->exceptions) {
1256
                        throw new phpmailerException($error_message);
1257 41
                    }
1258
                    return false;
1259 41
                }
1260 1
            }
1261
1262
            // Set whether the message is multipart/alternative
1263
            if ($this->alternativeExists()) {
1264 41
                $this->ContentType = 'multipart/alternative';
1265 41
            }
1266
1267 41
            $this->setMessageType();
1268 41
            // Refuse to send an empty message unless we are specifically allowing it
1269 41
            if (!$this->AllowEmpty and empty($this->Body)) {
1270
                throw new phpmailerException($this->lang('empty_message'), self::STOP_CRITICAL);
1271
            }
1272
1273 41
            // Create body before headers in case body makes changes to headers (e.g. altering transfer encoding)
1274 2
            $this->MIMEHeader = '';
1275 2
            $this->MIMEBody = $this->createBody();
1276 2
            // createBody may have added some headers, so retain them
1277
            $tempheaders = $this->MIMEHeader;
1278
            $this->MIMEHeader = $this->createHeader();
1279 2
            $this->MIMEHeader .= $tempheaders;
1280 2
1281 2
            // To capture the complete message when using mail(), create
1282 2
            // an extra header list which createHeader() doesn't fold in
1283 2
            if ($this->Mailer == 'mail') {
1284
                if (count($this->to) > 0) {
1285
                    $this->mailHeader .= $this->addrAppend('To', $this->to);
1286 41
                } else {
1287 41
                    $this->mailHeader .= $this->headerLine('To', 'undisclosed-recipients:;');
1288 41
                }
1289 41
                $this->mailHeader .= $this->headerLine(
1290 1
                    'Subject',
1291 1
                    $this->encodeHeader($this->secureHeader(trim($this->Subject)))
1292 1
                );
1293 1
            }
1294 1
1295 1
            // Sign with DKIM if enabled
1296 1
            if (!empty($this->DKIM_domain)
1297 1
                && !empty($this->DKIM_selector)
1298 41
                && (!empty($this->DKIM_private_string)
1299 2
                   || (!empty($this->DKIM_private) && file_exists($this->DKIM_private))
1300 2
                )
1301 2
            ) {
1302
                $header_dkim = $this->DKIM_Add(
1303
                    $this->MIMEHeader . $this->mailHeader,
1304 2
                    $this->encodeHeader($this->secureHeader($this->Subject)),
1305
                    $this->MIMEBody
1306
                );
1307
                $this->MIMEHeader = rtrim($this->MIMEHeader, "\r\n ") . self::CRLF .
1308
                    str_replace("\r\n", "\n", $header_dkim) . self::CRLF;
1309
            }
1310
            return true;
1311
        } catch (phpmailerException $exc) {
1312
            $this->setError($exc->getMessage());
1313
            if ($this->exceptions) {
1314 36
                throw $exc;
1315
            }
1316
            return false;
1317
        }
1318 36
    }
1319 36
1320 36
    /**
1321 1
     * Actually send a message.
1322 35
     * Send the email via the selected mechanism
1323 33
     * @throws phpmailerException
1324 2
     * @return boolean
1325 2
     */
1326
    public function postSend()
1327
    {
1328
        try {
1329
            // Choose the mailer and send through it
1330
            switch ($this->Mailer) {
1331
                case 'sendmail':
1332
                case 'qmail':
1333
                    return $this->sendmailSend($this->MIMEHeader, $this->MIMEBody);
1334
                case 'smtp':
1335
                    return $this->smtpSend($this->MIMEHeader, $this->MIMEBody);
1336
                case 'mail':
1337
                    return $this->mailSend($this->MIMEHeader, $this->MIMEBody);
1338
                default:
1339
                    $sendMethod = $this->Mailer.'Send';
1340
                    if (method_exists($this, $sendMethod)) {
1341
                        return $this->$sendMethod($this->MIMEHeader, $this->MIMEBody);
1342
                    }
1343
1344
                    return $this->mailSend($this->MIMEHeader, $this->MIMEBody);
1345
            }
1346
        } catch (phpmailerException $exc) {
1347
            $this->setError($exc->getMessage());
1348
            $this->edebug($exc->getMessage());
1349
            if ($this->exceptions) {
1350
                throw $exc;
1351
            }
1352
        }
1353 1
        return false;
1354
    }
1355 1
1356 1
    /**
1357
     * Send mail using the $Sendmail program.
1358
     * @param string $header The message headers
1359 1
     * @param string $body The message body
1360
     * @see PHPMailer::$Sendmail
1361 1
     * @throws phpmailerException
1362
     * @access protected
1363
     * @return boolean
1364
     */
1365
    protected function sendmailSend($header, $body)
1366
    {
1367
        // CVE-2016-10033, CVE-2016-10045: Don't pass -f if characters will be escaped.
1368 1
        if (!empty($this->Sender) and self::isShellSafe($this->Sender)) {
1369
            if ($this->Mailer == 'qmail') {
1370
                $sendmailFmt = '%s -f%s';
1371
            } else {
1372
                $sendmailFmt = '%s -oi -f%s -t';
1373
            }
1374
        } else {
1375
            if ($this->Mailer == 'qmail') {
1376
                $sendmailFmt = '%s';
1377
            } else {
1378
                $sendmailFmt = '%s -oi -t';
1379
            }
1380
        }
1381
1382
        // TODO: If possible, this should be changed to escapeshellarg.  Needs thorough testing.
1383
        $sendmail = sprintf($sendmailFmt, escapeshellcmd($this->Sendmail), $this->Sender);
1384
1385
        if ($this->SingleTo) {
1386
            foreach ($this->SingleToArray as $toAddr) {
1387
                if (!@$mail = popen($sendmail, 'w')) {
1388
                    throw new phpmailerException($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL);
1389
                }
1390
                fputs($mail, 'To: ' . $toAddr . "\n");
1391 1
                fputs($mail, $header);
1392
                fputs($mail, $body);
1393
                $result = pclose($mail);
1394 1
                $this->doCallback(
1395 1
                    ($result == 0),
1396 1
                    array($toAddr),
1397 1
                    $this->cc,
1398 1
                    $this->bcc,
1399 1
                    $this->Subject,
1400 1
                    $body,
1401 1
                    $this->From
1402 1
                );
1403 1
                if ($result != 0) {
1404 1
                    throw new phpmailerException($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL);
1405 1
                }
1406 1
            }
1407
        } else {
1408
            if (!@$mail = popen($sendmail, 'w')) {
1409
                throw new phpmailerException($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL);
1410 1
            }
1411
            fputs($mail, $header);
1412
            fputs($mail, $body);
1413
            $result = pclose($mail);
1414
            $this->doCallback(
1415
                ($result == 0),
1416
                $this->to,
1417
                $this->cc,
1418
                $this->bcc,
1419
                $this->Subject,
1420
                $body,
1421
                $this->From
1422 2
            );
1423
            if ($result != 0) {
1424 2
                throw new phpmailerException($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL);
1425 2
            }
1426 2
        }
1427 2
        return true;
1428 2
    }
1429
1430 2
    /**
1431
     * Fix CVE-2016-10033 and CVE-2016-10045 by disallowing potentially unsafe shell characters.
1432 2
     *
1433 2
     * Note that escapeshellarg and escapeshellcmd are inadequate for our purposes, especially on Windows.
1434 2
     * @param string $string The string to be validated
1435 2
     * @see https://github.com/PHPMailer/PHPMailer/issues/924 CVE-2016-10045 bug report
1436 2
     * @access protected
1437 2
     * @return boolean
1438 2
     */
1439 2
    protected static function isShellSafe($string)
1440 2
    {
1441
        // Future-proof
1442
        if (escapeshellcmd($string) !== $string
1443
            or !in_array(escapeshellarg($string), array("'$string'", "\"$string\""))
1444
        ) {
1445
            return false;
1446 2
        }
1447 2
1448
        $length = strlen($string);
1449 2
1450 2
        for ($i = 0; $i < $length; $i++) {
1451 2
            $c = $string[$i];
1452 2
1453
            // All other characters have a special meaning in at least one common shell, including = and +.
1454
            // Full stop (.) has a special meaning in cmd.exe, but its impact should be negligible here.
1455 2
            // Note that this does permit non-Latin alphanumeric characters based on the current locale.
1456
            if (!ctype_alnum($c) && strpos('@_-.', $c) === false) {
1457
                return false;
1458
            }
1459
        }
1460
1461
        return true;
1462
    }
1463 35
1464
    /**
1465 35
     * Send mail using the PHP mail() function.
1466 35
     * @param string $header The message headers
1467 35
     * @param string $body The message body
1468 35
     * @link http://www.php.net/manual/en/book.mail.php
1469
     * @throws phpmailerException
1470
     * @access protected
1471
     * @return boolean
1472
     */
1473
    protected function mailSend($header, $body)
1474
    {
1475
        $toArr = array();
1476
        foreach ($this->to as $toaddr) {
1477
            $toArr[] = $this->addrFormat($toaddr);
1478
        }
1479
        $to = implode(', ', $toArr);
1480
1481
        $params = null;
1482
        //This sets the SMTP envelope sender which gets turned into a return-path header by the receiver
1483 33
        if (!empty($this->Sender) and $this->validateAddress($this->Sender)) {
1484
            // CVE-2016-10033, CVE-2016-10045: Don't pass -f if characters will be escaped.
1485 33
            if (self::isShellSafe($this->Sender)) {
1486 33
                $params = sprintf('-f%s', $this->Sender);
1487
            }
1488
        }
1489 33
        if (!empty($this->Sender) and !ini_get('safe_mode') and $this->validateAddress($this->Sender)) {
1490
            $old_from = ini_get('sendmail_from');
1491
            ini_set('sendmail_from', $this->Sender);
1492 33
        }
1493
        $result = false;
1494 33
        if ($this->SingleTo and count($toArr) > 1) {
1495
            foreach ($toArr as $toAddr) {
1496
                $result = $this->mailPassthru($toAddr, $this->Subject, $body, $header, $params);
1497
                $this->doCallback($result, array($toAddr), $this->cc, $this->bcc, $this->Subject, $body, $this->From);
1498
            }
1499
        } else {
1500 33
            $result = $this->mailPassthru($to, $this->Subject, $body, $header, $params);
1501 33
            $this->doCallback($result, $this->to, $this->cc, $this->bcc, $this->Subject, $body, $this->From);
1502 33
        }
1503
        if (isset($old_from)) {
1504
            ini_set('sendmail_from', $old_from);
1505
        }
1506
        if (!$result) {
1507 33
            throw new phpmailerException($this->lang('instantiate'), self::STOP_CRITICAL);
1508
        }
1509 33
        return true;
1510 33
    }
1511 33
1512
    /**
1513
     * Get an instance to use for SMTP operations.
1514 33
     * Override this function to load your own SMTP implementation
1515
     * @return SMTP
1516
     */
1517 33
    public function getSMTPInstance()
1518 1
    {
1519 1
        if (!is_object($this->smtp)) {
1520 32
            $this->smtp = new SMTP;
1521 32
        }
1522
        return $this->smtp;
1523
    }
1524 33
1525
    /**
1526
     * Send mail via SMTP.
1527
     * Returns false if there is a bad MAIL FROM, RCPT, or DATA input.
1528
     * Uses the PHPMailerSMTP class by default.
1529
     * @see PHPMailer::getSMTPInstance() to use a different class.
1530
     * @param string $header The message headers
1531
     * @param string $body The message body
1532
     * @throws phpmailerException
1533
     * @uses SMTP
1534 33
     * @access protected
1535
     * @return boolean
1536
     */
1537
    protected function smtpSend($header, $body)
1538
    {
1539
        $bad_rcpt = array();
1540
        if (!$this->smtpConnect($this->SMTPOptions)) {
1541
            throw new phpmailerException($this->lang('smtp_connect_failed'), self::STOP_CRITICAL);
1542
        }
1543
        if (!empty($this->Sender) and $this->validateAddress($this->Sender)) {
1544
            $smtp_from = $this->Sender;
1545
        } else {
1546 35
            $smtp_from = $this->From;
1547
        }
1548 35
        if (!$this->smtp->mail($smtp_from)) {
1549 35
            $this->setError($this->lang('from_failed') . $smtp_from . ' : ' . implode(',', $this->smtp->getError()));
1550 35
            throw new phpmailerException($this->ErrorInfo, self::STOP_CRITICAL);
1551
        }
1552
1553 35
        // Attempt to send to all recipients
1554 2
        foreach (array($this->to, $this->cc, $this->bcc) as $togroup) {
1555 2
            foreach ($togroup as $to) {
1556
                if (!$this->smtp->recipient($to[0])) {
1557
                    $error = $this->smtp->getError();
1558 35
                    $bad_rcpt[] = array('to' => $to[0], 'error' => $error['detail']);
1559 1
                    $isSent = false;
1560
                } else {
1561
                    $isSent = true;
1562 35
                }
1563 35
                $this->doCallback($isSent, array($to[0]), array(), array(), $this->Subject, $body, $this->From);
1564 35
            }
1565 35
        }
1566 35
1567 35
        // Only send the DATA command if we have viable recipients
1568
        if ((count($this->all_recipients) > count($bad_rcpt)) and !$this->smtp->data($header . $body)) {
1569 35
            throw new phpmailerException($this->lang('data_not_accepted'), self::STOP_CRITICAL);
1570 35
        }
1571 35
        if ($this->SMTPKeepAlive) {
1572
            $this->smtp->reset();
1573
        } else {
1574
            $this->smtp->quit();
1575
            $this->smtp->close();
1576
        }
1577
        //Create error message for any bad addresses
1578
        if (count($bad_rcpt) > 0) {
1579
            $errstr = '';
1580 35
            foreach ($bad_rcpt as $bad) {
1581 35
                $errstr .= $bad['to'] . ': ' . $bad['error'];
1582 35
            }
1583 35
            throw new phpmailerException(
1584 1
                $this->lang('recipients_failed') . $errstr,
1585 1
                self::STOP_CONTINUE
1586 1
            );
1587 35
        }
1588 1
        return true;
1589
    }
1590 1
1591 1
    /**
1592
     * Initiate a connection to an SMTP server.
1593 35
     * Returns false if the operation failed.
1594 35
     * @param array $options An array of options compatible with stream_context_create()
1595
     * @uses SMTP
1596 1
     * @access public
1597
     * @throws phpmailerException
1598
     * @return boolean
1599 1
     */
1600 35
    public function smtpConnect($options = null)
1601 35
    {
1602 35
        if (is_null($this->smtp)) {
1603 35
            $this->smtp = $this->getSMTPInstance();
1604 1
        }
1605 1
1606 35
        //If no options are provided, use whatever is set in the instance
1607
        if (is_null($options)) {
1608 35
            $options = $this->SMTPOptions;
1609 35
        }
1610 35
1611
        // Already connected?
1612
        if ($this->smtp->connected()) {
1613 35
            return true;
1614
        }
1615
1616
        $this->smtp->setTimeout($this->Timeout);
1617
        $this->smtp->setDebugLevel($this->SMTPDebug);
1618
        $this->smtp->setDebugOutput($this->Debugoutput);
1619 35
        $this->smtp->setVerp($this->do_verp);
1620
        $hosts = explode(';', $this->Host);
1621
        $lastexception = null;
1622 35
1623
        foreach ($hosts as $hostentry) {
1624
            $hostinfo = array();
1625
            if (!preg_match('/^((ssl|tls):\/\/)*([a-zA-Z0-9\.-]*):?([0-9]*)$/', trim($hostentry), $hostinfo)) {
1626
                // Not a valid host entry
1627
                continue;
1628
            }
1629 35
            // $hostinfo[2]: optional ssl or tls prefix
1630
            // $hostinfo[3]: the hostname
1631
            // $hostinfo[4]: optional port number
1632
            // The host string prefix can temporarily override the current setting for SMTPSecure
1633
            // If it's not specified, the default value is used
1634
            $prefix = '';
1635
            $secure = $this->SMTPSecure;
1636
            $tls = ($this->SMTPSecure == 'tls');
1637
            if ('ssl' == $hostinfo[2] or ('' == $hostinfo[2] and 'ssl' == $this->SMTPSecure)) {
1638
                $prefix = 'ssl://';
1639
                $tls = false; // Can't have SSL and TLS at the same time
1640
                $secure = 'ssl';
1641 35
            } elseif ($hostinfo[2] == 'tls') {
1642
                $tls = true;
1643
                // tls doesn't use a prefix
1644
                $secure = 'tls';
1645
            }
1646
            //Do we need the OpenSSL extension?
1647
            $sslext = defined('OPENSSL_ALGO_SHA1');
1648
            if ('tls' === $secure or 'ssl' === $secure) {
1649 1
                //Check for an OpenSSL constant rather than using extension_loaded, which is sometimes disabled
1650
                if (!$sslext) {
1651 1
                    throw new phpmailerException($this->lang('extension_missing').'openssl', self::STOP_CRITICAL);
1652
                }
1653 1
            }
1654
            $host = $hostinfo[3];
1655
            $port = $this->Port;
1656 1
            $tport = (integer)$hostinfo[4];
1657
            if ($tport > 0 and $tport < 65536) {
1658
                $port = $tport;
1659
            }
1660
            if ($this->smtp->connect($prefix . $host, $port, $this->Timeout, $options)) {
1661
                try {
1662
                    if ($this->Helo) {
1663 56
                        $hello = $this->Helo;
1664
                    } else {
1665 56
                        $hello = $this->serverHostname();
1666 35
                    }
1667 3
                    $this->smtp->hello($hello);
1668 3
                    //Automatically enable TLS encryption if:
1669 3
                    // * it's not disabled
1670 35
                    // * we have openssl extension
1671 56
                    // * we are not already using SSL
1672
                    // * the server offers STARTTLS
1673
                    if ($this->SMTPAutoTLS and $sslext and $secure != 'ssl' and $this->smtp->getServerExt('STARTTLS')) {
1674
                        $tls = true;
1675
                    }
1676
                    if ($tls) {
1677
                        if (!$this->smtp->startTLS()) {
1678
                            throw new phpmailerException($this->lang('connect_host'));
1679
                        }
1680
                        // We must resend EHLO after TLS negotiation
1681
                        $this->smtp->hello($hello);
1682 8
                    }
1683
                    if ($this->SMTPAuth) {
1684
                        if (!$this->smtp->authenticate(
1685
                            $this->Username,
1686 8
                            $this->Password,
1687 8
                            $this->AuthType,
1688 8
                            $this->Realm,
1689 8
                            $this->Workstation
1690 8
                        )
1691 8
                        ) {
1692 8
                            throw new phpmailerException($this->lang('authenticate'));
1693 8
                        }
1694 8
                    }
1695 8
                    return true;
1696 8
                } catch (phpmailerException $exc) {
1697 8
                    $lastexception = $exc;
1698 8
                    $this->edebug($exc->getMessage());
1699 8
                    // We must have connected, but then failed TLS or Auth, so close connection nicely
1700 8
                    $this->smtp->quit();
1701 8
                }
1702 8
            }
1703 8
        }
1704
        // If we get here, all connection attempts have failed, so close connection hard
1705 8
        $this->smtp->close();
1706 8
        // As we've caught all exceptions, just report whatever the last one was
1707
        if ($this->exceptions and !is_null($lastexception)) {
1708 8
            throw $lastexception;
1709 8
        }
1710 8
        return false;
1711 8
    }
1712
1713 8
    /**
1714
     * Close the active SMTP session if one exists.
1715 1
     * @return void
1716
     */
1717
    public function smtpClose()
1718
    {
1719
        if (is_a($this->smtp, 'SMTP')) {
1720 1
            if ($this->smtp->connected()) {
1721
                $this->smtp->quit();
1722 1
                $this->smtp->close();
1723 8
            }
1724 8
        }
1725
    }
1726
1727
    /**
1728
     * Set the language for error messages.
1729
     * Returns false if it cannot load the language file.
1730
     * The default language is English.
1731
     * @param string $langcode ISO 639-1 2-character language code (e.g. French is "fr")
1732
     * @param string $lang_path Path to the language file directory, with trailing separator (slash)
1733
     * @return boolean
1734
     * @access public
1735
     */
1736
    public function setLanguage($langcode = 'en', $lang_path = '')
1737
    {
1738
        // Backwards compatibility for renamed language codes
1739
        $renamed_langcodes = array(
1740
            'br' => 'pt_br',
1741
            'cz' => 'cs',
1742
            'dk' => 'da',
1743
            'no' => 'nb',
1744
            'se' => 'sv',
1745
        );
1746 42
1747
        if (isset($renamed_langcodes[$langcode])) {
1748 42
            $langcode = $renamed_langcodes[$langcode];
1749 42
        }
1750 42
1751 42
        // Define full set of translatable strings in English
1752 42
        $PHPMAILER_LANG = array(
1753
            'authenticate' => 'SMTP Error: Could not authenticate.',
1754
            'connect_host' => 'SMTP Error: Could not connect to SMTP host.',
1755
            'data_not_accepted' => 'SMTP Error: data not accepted.',
1756
            'empty_message' => 'Message body empty',
1757
            'encoding' => 'Unknown encoding: ',
1758
            'execute' => 'Could not execute: ',
1759
            'file_access' => 'Could not access file: ',
1760
            'file_open' => 'File Error: Could not open file: ',
1761
            'from_failed' => 'The following From address failed: ',
1762 42
            'instantiate' => 'Could not instantiate mail function.',
1763
            'invalid_address' => 'Invalid address: ',
1764 42
            'mailer_not_supported' => ' mailer is not supported.',
1765 3
            'provide_address' => 'You must provide at least one recipient email address.',
1766
            'recipients_failed' => 'SMTP Error: The following recipients failed: ',
1767 42
            'signing' => 'Signing Error: ',
1768 42
            'smtp_connect_failed' => 'SMTP connect() failed.',
1769 42
            'smtp_error' => 'SMTP server error: ',
1770
            'variable_set' => 'Cannot set or reset variable: ',
1771
            'extension_missing' => 'Extension missing: '
1772
        );
1773
        if (empty($lang_path)) {
1774
            // Calculate an absolute path so it can work if CWD is not here
1775
            $lang_path = dirname(__FILE__). DIRECTORY_SEPARATOR . 'language'. DIRECTORY_SEPARATOR;
1776
        }
1777
        //Validate $langcode
1778
        if (!preg_match('/^[a-z]{2}(?:_[a-zA-Z]{2})?$/', $langcode)) {
1779
            $langcode = 'en';
1780
        }
1781
        $foundlang = true;
1782
        $lang_file = $lang_path . 'phpmailer.lang-' . $langcode . '.php';
1783
        // There is no English translation file
1784 4
        if ($langcode != 'en') {
1785
            // Make sure language file path is readable
1786 4
            if (!is_readable($lang_file)) {
1787
                $foundlang = false;
1788
            } else {
1789 4
                // Overwrite language-specific strings.
1790
                // This way we'll never have missing translation keys.
1791
                $foundlang = include $lang_file;
1792
            }
1793 4
        }
1794 4
        $this->language = $PHPMAILER_LANG;
1795 4
        return (boolean)$foundlang; // Returns false if language not found
1796
    }
1797 4
1798
    /**
1799 4
     * Get the array of strings for the current language.
1800 2
     * @return array
1801 2
     */
1802
    public function getTranslations()
1803
    {
1804 4
        return $this->language;
1805
    }
1806 4
1807 4
    /**
1808 4
     * Create recipient headers.
1809 4
     * @access public
1810 4
     * @param string $type
1811 4
     * @param array $addr An array of recipient,
1812 4
     * where each recipient is a 2-element indexed array with element 0 containing an address
1813
     * and element 1 containing a name, like:
1814
     * array(array('[email protected]', 'Joe User'), array('[email protected]', 'Zoe User'))
1815
     * @return string
1816
     */
1817
    public function addrAppend($type, $addr)
1818
    {
1819
        $addresses = array();
1820
        foreach ($addr as $address) {
1821
            $addresses[] = $this->addrFormat($address);
1822
        }
1823
        return $type . ': ' . implode(', ', $addresses) . $this->LE;
1824
    }
1825
1826
    /**
1827
     * Format an address for use in a message header.
1828
     * @access public
1829
     * @param array $addr A 2-element indexed array, element 0 containing an address, element 1 containing a name
1830
     *      like array('[email protected]', 'Joe User')
1831
     * @return string
1832
     */
1833
    public function addrFormat($addr)
1834
    {
1835
        if (empty($addr[1])) { // No name provided
1836
            return $this->secureHeader($addr[0]);
1837
        } else {
1838
            return $this->encodeHeader($this->secureHeader($addr[1]), 'phrase') . ' <' . $this->secureHeader(
1839
                $addr[0]
1840
            ) . '>';
1841
        }
1842
    }
1843
1844
    /**
1845
     * Word-wrap message.
1846
     * For use with mailers that do not automatically perform wrapping
1847
     * and for quoted-printable encoded messages.
1848
     * Original written by philippe.
1849
     * @param string $message The message to wrap
1850
     * @param integer $length The line length to wrap to
1851
     * @param boolean $qp_mode Whether to run in Quoted-Printable mode
1852
     * @access public
1853
     * @return string
1854
     */
1855 4
    public function wrapText($message, $length, $qp_mode = false)
1856 4
    {
1857 4
        if ($qp_mode) {
1858 4
            $soft_break = sprintf(' =%s', $this->LE);
1859 4
        } else {
1860
            $soft_break = $this->LE;
1861 4
        }
1862 4
        // If utf-8 encoding is used, we will need to make sure we don't
1863 4
        // split multibyte characters when we wrap
1864 4
        $is_utf8 = (strtolower($this->CharSet) == 'utf-8');
1865
        $lelen = strlen($this->LE);
1866 4
        $crlflen = strlen(self::CRLF);
1867 4
1868 4
        $message = $this->fixEOL($message);
1869 4
        //Remove a trailing line break
1870
        if (substr($message, -$lelen) == $this->LE) {
1871 4
            $message = substr($message, 0, -$lelen);
1872
        }
1873
1874
        //Split message into lines
1875
        $lines = explode($this->LE, $message);
1876
        //Message will be rebuilt in here
1877
        $message = '';
1878
        foreach ($lines as $line) {
1879
            $words = explode(' ', $line);
1880
            $buf = '';
1881
            $firstword = true;
1882
            foreach ($words as $word) {
1883
                if ($qp_mode and (strlen($word) > $length)) {
1884
                    $space_left = $length - strlen($buf) - $crlflen;
1885
                    if (!$firstword) {
1886
                        if ($space_left > 20) {
1887
                            $len = $space_left;
1888
                            if ($is_utf8) {
1889
                                $len = $this->utf8CharBoundary($word, $len);
1890
                            } elseif (substr($word, $len - 1, 1) == '=') {
1891
                                $len--;
1892
                            } elseif (substr($word, $len - 2, 1) == '=') {
1893
                                $len -= 2;
1894
                            }
1895
                            $part = substr($word, 0, $len);
1896
                            $word = substr($word, $len);
1897
                            $buf .= ' ' . $part;
1898
                            $message .= $buf . sprintf('=%s', self::CRLF);
1899
                        } else {
1900
                            $message .= $buf . $soft_break;
1901
                        }
1902
                        $buf = '';
1903
                    }
1904
                    while (strlen($word) > 0) {
1905
                        if ($length <= 0) {
1906
                            break;
1907
                        }
1908
                        $len = $length;
1909
                        if ($is_utf8) {
1910
                            $len = $this->utf8CharBoundary($word, $len);
1911
                        } elseif (substr($word, $len - 1, 1) == '=') {
1912
                            $len--;
1913
                        } elseif (substr($word, $len - 2, 1) == '=') {
1914
                            $len -= 2;
1915
                        }
1916
                        $part = substr($word, 0, $len);
1917
                        $word = substr($word, $len);
1918
1919
                        if (strlen($word) > 0) {
1920
                            $message .= $part . sprintf('=%s', self::CRLF);
1921
                        } else {
1922
                            $buf = $part;
1923
                        }
1924
                    }
1925
                } else {
1926
                    $buf_o = $buf;
1927
                    if (!$firstword) {
1928 41
                        $buf .= ' ';
1929
                    }
1930 41
                    $buf .= $word;
1931 38
1932
                    if (strlen($buf) > $length and $buf_o != '') {
1933
                        $message .= $buf_o . $soft_break;
1934 3
                        $buf = $word;
1935 3
                    }
1936 3
                }
1937 3
                $firstword = false;
1938 3
            }
1939 1
            $message .= $buf . self::CRLF;
1940 1
        }
1941 2
1942 2
        return $message;
1943 2
    }
1944 3
1945 3
    /**
1946
     * Find the last character boundary prior to $maxLength in a utf-8
1947
     * quoted-printable encoded string.
1948
     * Original written by Colin Brown.
1949
     * @access public
1950
     * @param string $encodedText utf-8 QP text
1951
     * @param integer $maxLength Find the last character boundary prior to this length
1952 42
     * @return integer
1953
     */
1954 42
    public function utf8CharBoundary($encodedText, $maxLength)
1955
    {
1956 42
        $foundSplitPos = false;
1957 42
        $lookBack = 3;
1958 42
        while (!$foundSplitPos) {
1959 42
            $lastChunk = substr($encodedText, $maxLength - $lookBack, $lookBack);
1960
            $encodedCharPos = strpos($lastChunk, '=');
1961
            if (false !== $encodedCharPos) {
1962 42
                // Found start of encoded character byte within $lookBack block.
1963
                // Check the encoded byte value (the 2 chars after the '=')
1964
                $hex = substr($encodedText, $maxLength - $lookBack + $encodedCharPos + 1, 2);
1965
                $dec = hexdec($hex);
1966
                if ($dec < 128) {
1967
                    // Single byte character.
1968
                    // If the encoded char was found at pos 0, it will fit
1969 42
                    // otherwise reduce maxLength to start of the encoded char
1970 41
                    if ($encodedCharPos > 0) {
1971 39
                        $maxLength = $maxLength - ($lookBack - $encodedCharPos);
1972 39
                    }
1973 42
                    $foundSplitPos = true;
1974 1
                } elseif ($dec >= 192) {
1975 1
                    // First byte of a multi byte character
1976
                    // Reduce maxLength to split at start of character
1977
                    $maxLength = $maxLength - ($lookBack - $encodedCharPos);
1978 42
                    $foundSplitPos = true;
1979
                } elseif ($dec < 192) {
1980
                    // Middle byte of a multi byte character, look further back
1981 42
                    $lookBack += 3;
1982 39
                }
1983 39
            } else {
1984
                // No encoded character found
1985
                $foundSplitPos = true;
1986
            }
1987 42
        }
1988 40
        return $maxLength;
1989 42
    }
1990 42
1991
    /**
1992
     * Apply word wrapping to the message body.
1993
     * Wraps the message body to the number of chars set in the WordWrap property.
1994 42
     * You should only do this to plain-text bodies as wrapping HTML tags may break them.
1995 42
     * This is called automatically by createBody(), so you don't need to call it yourself.
1996 42
     * @access public
1997
     * @return void
1998
     */
1999 42
    public function setWordWrap()
2000 40
    {
2001 40
        if ($this->WordWrap < 1) {
2002
            return;
2003 42
        }
2004 1
2005 1
        switch ($this->message_type) {
2006 42
            case 'alt':
2007
            case 'alt_inline':
2008 42
            case 'alt_attach':
2009 42
            case 'alt_inline_attach':
2010 42
                $this->AltBody = $this->wrapText($this->AltBody, $this->WordWrap);
2011 42
                break;
2012 42
            default:
2013 42
                $this->Body = $this->wrapText($this->Body, $this->WordWrap);
2014 42
                break;
2015 42
        }
2016 42
    }
2017 42
2018
    /**
2019
     * Assemble message headers.
2020
     * @access public
2021
     * @return string The assembled headers
2022
     */
2023
    public function createHeader()
2024 42
    {
2025 1
        $result = '';
2026 1
2027
        if ($this->MessageDate == '') {
2028
            $this->MessageDate = self::rfcDate();
2029 42
        }
2030
        $result .= $this->headerLine('Date', $this->MessageDate);
2031
2032
        // To be created automatically by mail()
2033
        if ($this->SingleTo) {
2034 42
            if ($this->Mailer != 'mail') {
2035 42
                foreach ($this->to as $toaddr) {
2036 40
                    $this->SingleToArray[] = $this->addrFormat($toaddr);
2037 40
                }
2038 40
            }
2039
        } else {
2040 42
            if (count($this->to) > 0) {
2041
                if ($this->Mailer != 'mail') {
2042
                    $result .= $this->addrAppend('To', $this->to);
2043
                }
2044
            } elseif (count($this->cc) == 0) {
2045
                $result .= $this->headerLine('To', 'undisclosed-recipients:;');
2046
            }
2047
        }
2048 42
2049
        $result .= $this->addrAppend('From', array(array(trim($this->From), $this->FromName)));
2050 42
2051 42
        // sendmail and mail() extract Cc from the header before sending
2052 42
        if (count($this->cc) > 0) {
2053 42
            $result .= $this->addrAppend('Cc', $this->cc);
2054 3
        }
2055 3
2056 3
        // sendmail and mail() extract Bcc from the header before sending
2057 39
        if ((
2058 39
                $this->Mailer == 'sendmail' or $this->Mailer == 'qmail' or $this->Mailer == 'mail'
2059 39
            )
2060 39
            and count($this->bcc) > 0
2061 6
        ) {
2062 6
            $result .= $this->addrAppend('Bcc', $this->bcc);
2063 6
        }
2064 33
2065 33
        if (count($this->ReplyTo) > 0) {
2066 6
            $result .= $this->addrAppend('Reply-To', $this->ReplyTo);
2067 6
        }
2068 6
2069 27
        // mail() sets the subject itself
2070
        if ($this->Mailer != 'mail') {
2071 27
            $result .= $this->headerLine('Subject', $this->encodeHeader($this->secureHeader($this->Subject)));
2072 27
        }
2073 27
2074 42
        // Only allow a custom message ID if it conforms to RFC 5322 section 3.6.4
2075
        // https://tools.ietf.org/html/rfc5322#section-3.6.4
2076 42
        if ('' != $this->MessageID and preg_match('/^<.*@.*>$/', $this->MessageID)) {
2077
            $this->lastMessageID = $this->MessageID;
2078 24
        } else {
2079 15
            $this->lastMessageID = sprintf('<%s@%s>', $this->uniqueid, $this->serverHostname());
2080 15
        }
2081 15
        $result .= $this->headerLine('Message-ID', $this->lastMessageID);
2082
        if (!is_null($this->Priority)) {
2083 15
            $result .= $this->headerLine('X-Priority', $this->Priority);
2084 9
        }
2085
        if ($this->XMailer == '') {
2086 24
            $result .= $this->headerLine(
2087
                'X-Mailer',
2088 42
                'PHPMailer ' . $this->Version . ' (https://github.com/PHPMailer/PHPMailer)'
2089 40
            );
2090 40
        } else {
2091
            $myXmailer = trim($this->XMailer);
2092 42
            if ($myXmailer) {
2093
                $result .= $this->headerLine('X-Mailer', $myXmailer);
2094
            }
2095
        }
2096
2097
        if ($this->ConfirmReadingTo != '') {
2098
            $result .= $this->headerLine('Disposition-Notification-To', '<' . $this->ConfirmReadingTo . '>');
2099
        }
2100
2101
        // Add custom headers
2102
        foreach ($this->CustomHeader as $header) {
2103 9
            $result .= $this->headerLine(
2104
                trim($header[0]),
2105 9
                $this->encodeHeader(trim($header[1]))
2106
            );
2107
        }
2108
        if (!$this->sign_key_file) {
2109
            $result .= $this->headerLine('MIME-Version', '1.0');
2110
            $result .= $this->getMailMIME();
2111
        }
2112
2113
        return $result;
2114
    }
2115 41
2116
    /**
2117 41
     * Get the message MIME type headers.
2118
     * @access public
2119 41
     * @return string
2120 41
     */
2121 41
    public function getMailMIME()
2122 41
    {
2123
        $result = '';
2124 41
        $ismultipart = true;
2125 2
        switch ($this->message_type) {
2126 2
            case 'inline':
2127
                $result .= $this->headerLine('Content-Type', 'multipart/related;');
2128 41
                $result .= $this->textLine("\tboundary=\"" . $this->boundary[1] . '"');
2129
                break;
2130 41
            case 'attach':
2131 41
            case 'inline_attach':
2132
            case 'alt_attach':
2133 41
            case 'alt_inline_attach':
2134 33
                $result .= $this->headerLine('Content-Type', 'multipart/mixed;');
2135
                $result .= $this->textLine("\tboundary=\"" . $this->boundary[1] . '"');
2136 33
                break;
2137 33
            case 'alt':
2138
            case 'alt_inline':
2139
                $result .= $this->headerLine('Content-Type', 'multipart/alternative;');
2140 41
                $result .= $this->textLine("\tboundary=\"" . $this->boundary[1] . '"');
2141 2
                break;
2142 2
            default:
2143
                // Catches case 'plain': and case '':
2144 41
                $result .= $this->textLine('Content-Type: ' . $this->ContentType . '; charset=' . $this->CharSet);
2145 41
                $ismultipart = false;
2146
                break;
2147 41
        }
2148 37
        // RFC1341 part 5 says 7bit is assumed if not specified
2149
        if ($this->Encoding != '7bit') {
2150 37
            // RFC 2045 section 6.4 says multipart MIME parts may only use 7bit, 8bit or binary CTE
2151 37
            if ($ismultipart) {
2152
                if ($this->Encoding == '8bit') {
2153
                    $result .= $this->headerLine('Content-Transfer-Encoding', '8bit');
2154 41
                }
2155 1
                // The only remaining alternatives are quoted-printable and base64, which are both 7bit compatible
2156 1
            } else {
2157
                $result .= $this->headerLine('Content-Transfer-Encoding', $this->Encoding);
2158 41
            }
2159 41
        }
2160 41
2161 3
        if ($this->Mailer != 'mail') {
2162 3
            $result .= $this->LE;
2163 3
        }
2164 3
2165 3
        return $result;
2166 3
    }
2167 38
2168 4
    /**
2169 4
     * Returns the whole MIME message.
2170 4
     * Includes complete headers and body.
2171 4
     * Only valid post preSend().
2172 4
     * @see PHPMailer::preSend()
2173 4
     * @access public
2174 34
     * @return string
2175 1
     */
2176 1
    public function getSentMIMEMessage()
2177 1
    {
2178 1
        return rtrim($this->MIMEHeader . $this->mailHeader, "\n\r") . self::CRLF . self::CRLF . $this->MIMEBody;
2179 1
    }
2180 1
2181 1
    /**
2182 1
     * Create unique ID
2183 1
     * @return string
2184 1
     */
2185 1
    protected function generateId() {
2186 1
        return md5(uniqid(time()));
2187 33
    }
2188 4
2189 4
    /**
2190 4
     * Assemble the message body.
2191 4
     * Returns an empty string on failure.
2192 4
     * @access public
2193 4
     * @throws phpmailerException
2194 4
     * @return string The assembled message body
2195 4
     */
2196 1
    public function createBody()
2197 1
    {
2198 1
        $body = '';
2199 1
        //Create unique IDs and preset boundaries
2200 4
        $this->uniqueid = $this->generateId();
2201 4
        $this->boundary[1] = 'b1_' . $this->uniqueid;
2202 29
        $this->boundary[2] = 'b2_' . $this->uniqueid;
2203 2
        $this->boundary[3] = 'b3_' . $this->uniqueid;
2204 2
2205 2
        if ($this->sign_key_file) {
2206 2
            $body .= $this->getMailMIME() . $this->LE;
2207 2
        }
2208 2
2209 2
        $this->setWordWrap();
2210 2
2211 2
        $bodyEncoding = $this->Encoding;
2212 2
        $bodyCharSet = $this->CharSet;
2213 2
        //Can we do a 7-bit downgrade?
2214 2
        if ($bodyEncoding == '8bit' and !$this->has8bitChars($this->Body)) {
2215 2
            $bodyEncoding = '7bit';
2216 2
            //All ISO 8859, Windows codepage and UTF-8 charsets are ascii compatible up to 7-bit
2217 2
            $bodyCharSet = 'us-ascii';
2218 27
        }
2219 1
        //If lines are too long, and we're not already using an encoding that will shorten them,
2220 1
        //change to quoted-printable transfer encoding for the body part only
2221 1
        if ('base64' != $this->Encoding and self::hasLineLongerThanMax($this->Body)) {
2222 1
            $bodyEncoding = 'quoted-printable';
2223 1
        }
2224 1
2225 1
        $altBodyEncoding = $this->Encoding;
2226 1
        $altBodyCharSet = $this->CharSet;
2227 1
        //Can we do a 7-bit downgrade?
2228 1
        if ($altBodyEncoding == '8bit' and !$this->has8bitChars($this->AltBody)) {
2229 1
            $altBodyEncoding = '7bit';
2230 1
            //All ISO 8859, Windows codepage and UTF-8 charsets are ascii compatible up to 7-bit
2231 1
            $altBodyCharSet = 'us-ascii';
2232 1
        }
2233 1
        //If lines are too long, and we're not already using an encoding that will shorten them,
2234 26
        //change to quoted-printable transfer encoding for the alt body part only
2235
        if ('base64' != $altBodyEncoding and self::hasLineLongerThanMax($this->AltBody)) {
2236
            $altBodyEncoding = 'quoted-printable';
2237
        }
2238
        //Use this as a preamble in all multipart message types
2239
        $mimepre = "This is a multi-part message in MIME format." . $this->LE . $this->LE;
2240
        switch ($this->message_type) {
2241
            case 'inline':
2242
                $body .= $mimepre;
2243
                $body .= $this->getBoundary($this->boundary[1], $bodyCharSet, '', $bodyEncoding);
2244
                $body .= $this->encodeString($this->Body, $bodyEncoding);
2245
                $body .= $this->LE . $this->LE;
2246
                $body .= $this->attachAll('inline', $this->boundary[1]);
2247
                break;
2248
            case 'attach':
2249
                $body .= $mimepre;
2250
                $body .= $this->getBoundary($this->boundary[1], $bodyCharSet, '', $bodyEncoding);
2251
                $body .= $this->encodeString($this->Body, $bodyEncoding);
2252
                $body .= $this->LE . $this->LE;
2253
                $body .= $this->attachAll('attachment', $this->boundary[1]);
2254
                break;
2255
            case 'inline_attach':
2256 26
                $body .= $mimepre;
2257
                $body .= $this->textLine('--' . $this->boundary[1]);
2258
                $body .= $this->headerLine('Content-Type', 'multipart/related;');
2259 26
                $body .= $this->textLine("\tboundary=\"" . $this->boundary[2] . '"');
2260 26
                $body .= $this->LE;
2261 26
                $body .= $this->getBoundary($this->boundary[2], $bodyCharSet, '', $bodyEncoding);
2262 41
                $body .= $this->encodeString($this->Body, $bodyEncoding);
2263
                $body .= $this->LE . $this->LE;
2264 41
                $body .= $this->attachAll('inline', $this->boundary[2]);
2265
                $body .= $this->LE;
2266 41
                $body .= $this->attachAll('attachment', $this->boundary[1]);
2267
                break;
2268 2
            case 'alt':
2269
                $body .= $mimepre;
2270
                $body .= $this->getBoundary($this->boundary[1], $altBodyCharSet, 'text/plain', $altBodyEncoding);
2271
                $body .= $this->encodeString($this->AltBody, $altBodyEncoding);
2272 2
                $body .= $this->LE . $this->LE;
2273 2
                $body .= $this->getBoundary($this->boundary[1], $bodyCharSet, 'text/html', $bodyEncoding);
2274
                $body .= $this->encodeString($this->Body, $bodyEncoding);
2275
                $body .= $this->LE . $this->LE;
2276 2
                if (!empty($this->Ical)) {
2277
                    $body .= $this->getBoundary($this->boundary[1], '', 'text/calendar; method=REQUEST', '');
2278 2
                    $body .= $this->encodeString($this->Ical, $this->Encoding);
2279 1
                    $body .= $this->LE . $this->LE;
2280 1
                }
2281 1
                $body .= $this->endBoundary($this->boundary[1]);
2282 1
                break;
2283 1
            case 'alt_inline':
2284
                $body .= $mimepre;
2285 1
                $body .= $this->getBoundary($this->boundary[1], $altBodyCharSet, 'text/plain', $altBodyEncoding);
2286 1
                $body .= $this->encodeString($this->AltBody, $altBodyEncoding);
2287 1
                $body .= $this->LE . $this->LE;
2288 1
                $body .= $this->textLine('--' . $this->boundary[1]);
2289 1
                $body .= $this->headerLine('Content-Type', 'multipart/related;');
2290 1
                $body .= $this->textLine("\tboundary=\"" . $this->boundary[2] . '"');
2291 1
                $body .= $this->LE;
2292 1
                $body .= $this->getBoundary($this->boundary[2], $bodyCharSet, 'text/html', $bodyEncoding);
2293 1
                $body .= $this->encodeString($this->Body, $bodyEncoding);
2294 1
                $body .= $this->LE . $this->LE;
2295 1
                $body .= $this->attachAll('inline', $this->boundary[2]);
2296
                $body .= $this->LE;
2297 2
                $body .= $this->endBoundary($this->boundary[1]);
2298 2
                break;
2299 2
            case 'alt_attach':
2300 2
                $body .= $mimepre;
2301
                $body .= $this->textLine('--' . $this->boundary[1]);
2302 2
                $body .= $this->headerLine('Content-Type', 'multipart/alternative;');
2303 2
                $body .= $this->textLine("\tboundary=\"" . $this->boundary[2] . '"');
2304 2
                $body .= $this->LE;
2305 2
                $body .= $this->getBoundary($this->boundary[2], $altBodyCharSet, 'text/plain', $altBodyEncoding);
2306
                $body .= $this->encodeString($this->AltBody, $altBodyEncoding);
2307
                $body .= $this->LE . $this->LE;
2308
                $body .= $this->getBoundary($this->boundary[2], $bodyCharSet, 'text/html', $bodyEncoding);
2309
                $body .= $this->encodeString($this->Body, $bodyEncoding);
2310 2
                $body .= $this->LE . $this->LE;
2311
                $body .= $this->endBoundary($this->boundary[2]);
2312
                $body .= $this->LE;
2313
                $body .= $this->attachAll('attachment', $this->boundary[1]);
2314
                break;
2315
            case 'alt_inline_attach':
2316 2
                $body .= $mimepre;
2317 41
                $body .= $this->textLine('--' . $this->boundary[1]);
2318
                $body .= $this->headerLine('Content-Type', 'multipart/alternative;');
2319
                $body .= $this->textLine("\tboundary=\"" . $this->boundary[2] . '"');
2320
                $body .= $this->LE;
2321
                $body .= $this->getBoundary($this->boundary[2], $altBodyCharSet, 'text/plain', $altBodyEncoding);
2322
                $body .= $this->encodeString($this->AltBody, $altBodyEncoding);
2323
                $body .= $this->LE . $this->LE;
2324
                $body .= $this->textLine('--' . $this->boundary[2]);
2325
                $body .= $this->headerLine('Content-Type', 'multipart/related;');
2326
                $body .= $this->textLine("\tboundary=\"" . $this->boundary[3] . '"');
2327
                $body .= $this->LE;
2328
                $body .= $this->getBoundary($this->boundary[3], $bodyCharSet, 'text/html', $bodyEncoding);
2329 15
                $body .= $this->encodeString($this->Body, $bodyEncoding);
2330
                $body .= $this->LE . $this->LE;
2331 15
                $body .= $this->attachAll('inline', $this->boundary[3]);
2332 15
                $body .= $this->LE;
2333 1
                $body .= $this->endBoundary($this->boundary[2]);
2334 1
                $body .= $this->LE;
2335 15
                $body .= $this->attachAll('attachment', $this->boundary[1]);
2336 8
                break;
2337 8
            default:
2338 15
                // Catch case 'plain' and case '', applies to simple `text/plain` and `text/html` body content types
2339 1
                //Reset the `Encoding` property in case we changed it for line length reasons
2340 1
                $this->Encoding = $bodyEncoding;
2341 15
                $body .= $this->encodeString($this->Body, $this->Encoding);
2342 15
                break;
2343 15
        }
2344
2345 15
        if ($this->isError()) {
2346 5
            $body = '';
2347 5
        } elseif ($this->sign_key_file) {
2348 15
            try {
2349
                if (!defined('PKCS7_TEXT')) {
2350 15
                    throw new phpmailerException($this->lang('extension_missing') . 'openssl');
2351
                }
2352
                // @TODO would be nice to use php://temp streams here, but need to wrap for PHP < 5.1
2353
                $file = tempnam(sys_get_temp_dir(), 'mail');
2354
                if (false === file_put_contents($file, $body)) {
2355
                    throw new phpmailerException($this->lang('signing') . ' Could not write temp file');
2356
                }
2357
                $signed = tempnam(sys_get_temp_dir(), 'signed');
2358
                //Workaround for PHP bug https://bugs.php.net/bug.php?id=69197
2359 7
                if (empty($this->sign_extracerts_file)) {
2360
                    $sign = @openssl_pkcs7_sign(
2361 7
                        $file,
2362
                        $signed,
2363
                        'file://' . realpath($this->sign_cert_file),
2364
                        array('file://' . realpath($this->sign_key_file), $this->sign_key_pass),
2365
                        null
2366
                    );
2367
                } else {
2368
                    $sign = @openssl_pkcs7_sign(
2369
                        $file,
2370 41
                        $signed,
2371
                        'file://' . realpath($this->sign_cert_file),
2372 41
                        array('file://' . realpath($this->sign_key_file), $this->sign_key_pass),
2373 41
                        null,
2374 7
                        PKCS7_DETACHED,
2375 7
                        $this->sign_extracerts_file
2376 41
                    );
2377 6
                }
2378 6
                if ($sign) {
2379 41
                    @unlink($file);
2380 6
                    $body = file_get_contents($signed);
2381 6
                    @unlink($signed);
2382 41
                    //The message returned by openssl contains both headers and body, so need to split them up
2383 41
                    $parts = explode("\n\n", $body, 2);
2384
                    $this->MIMEHeader .= $parts[0] . $this->LE . $this->LE;
2385 26
                    $body = $parts[1];
2386 26
                } else {
2387 41
                    @unlink($file);
2388
                    @unlink($signed);
2389
                    throw new phpmailerException($this->lang('signing') . openssl_error_string());
2390
                }
2391
            } catch (phpmailerException $exc) {
2392
                $body = '';
2393
                if ($this->exceptions) {
2394
                    throw $exc;
2395
                }
2396 42
            }
2397
        }
2398 42
        return $body;
2399
    }
2400
2401
    /**
2402
     * Return the start of a message boundary.
2403
     * @access protected
2404
     * @param string $boundary
2405
     * @param string $charSet
2406
     * @param string $contentType
2407 42
     * @param string $encoding
2408
     * @return string
2409 42
     */
2410
    protected function getBoundary($boundary, $charSet, $contentType, $encoding)
2411
    {
2412
        $result = '';
2413
        if ($charSet == '') {
2414
            $charSet = $this->CharSet;
2415
        }
2416
        if ($contentType == '') {
2417
            $contentType = $this->ContentType;
2418
        }
2419
        if ($encoding == '') {
2420
            $encoding = $this->Encoding;
2421
        }
2422
        $result .= $this->textLine('--' . $boundary);
2423 5
        $result .= sprintf('Content-Type: %s; charset=%s', $contentType, $charSet);
2424
        $result .= $this->LE;
2425
        // RFC1341 part 5 says 7bit is assumed if not specified
2426 5
        if ($encoding != '7bit') {
2427 1
            $result .= $this->headerLine('Content-Transfer-Encoding', $encoding);
2428
        }
2429
        $result .= $this->LE;
2430
2431 5
        return $result;
2432 5
    }
2433 5
2434
    /**
2435 5
     * Return the end of a message boundary.
2436 5
     * @access protected
2437 1
     * @param string $boundary
2438 1
     * @return string
2439
     */
2440 5
    protected function endBoundary($boundary)
2441 5
    {
2442 5
        return $this->LE . '--' . $boundary . '--' . $this->LE;
2443 5
    }
2444 5
2445 5
    /**
2446 5
     * Set the message type.
2447 5
     * PHPMailer only supports some preset message types, not arbitrary MIME structures.
2448
     * @access protected
2449 5
     * @return void
2450
     */
2451 5
    protected function setMessageType()
2452 1
    {
2453 1
        $type = array();
2454 1
        if ($this->alternativeExists()) {
2455
            $type[] = 'alt';
2456
        }
2457 1
        if ($this->inlineImageExists()) {
2458
            $type[] = 'inline';
2459 5
        }
2460
        if ($this->attachmentExists()) {
2461
            $type[] = 'attach';
2462
        }
2463
        $this->message_type = implode('_', $type);
2464
        if ($this->message_type == '') {
2465
            //The 'plain' message_type refers to the message having a single body element, not that it is plain-text
2466 40
            $this->message_type = 'plain';
2467
        }
2468 40
    }
2469
2470
    /**
2471
     * Format a header line.
2472
     * @access public
2473
     * @param string $name
2474
     * @param string $value
2475
     * @return string
2476
     */
2477
    public function headerLine($name, $value)
2478
    {
2479 11
        return $name . ': ' . $value . $this->LE;
2480
    }
2481
2482 11
    /**
2483 11
     * Return a formatted mail line.
2484 11
     * @access public
2485
     * @param string $value
2486
     * @return string
2487 11
     */
2488
    public function textLine($value)
2489 11
    {
2490
        return $value . $this->LE;
2491 11
    }
2492 11
2493 11
    /**
2494 11
     * Add an attachment from a path on the filesystem.
2495 2
     * Returns false if the file could not be found or read.
2496 2
     * @param string $path Path to the attachment.
2497 9
     * @param string $name Overrides the attachment name.
2498
     * @param string $encoding File encoding (see $Encoding).
2499
     * @param string $type File extension (MIME) type.
2500 11
     * @param string $disposition Disposition to use
2501 11
     * @throws phpmailerException
2502 1
     * @return boolean
2503
     */
2504 11
    public function addAttachment($path, $name = '', $encoding = 'base64', $type = '', $disposition = 'attachment')
2505 11
    {
2506 11
        try {
2507 11
            if (!@is_file($path)) {
2508 11
                throw new phpmailerException($this->lang('file_access') . $path, self::STOP_CONTINUE);
2509 11
            }
2510 11
2511
            // If a MIME type is not specified, try to work it out from the file name
2512
            if ($type == '') {
2513 11
                $type = self::filenameToType($path);
2514
            }
2515 11
2516
            $filename = basename($path);
2517 11
            if ($name == '') {
2518 10
                $name = $filename;
2519 10
            }
2520 10
2521 10
            $this->attachment[] = array(
2522 10
                0 => $path,
2523 10
                1 => $filename,
2524 10
                2 => $name,
2525 1
                3 => $encoding,
2526 1
                4 => $type,
2527 1
                5 => false, // isStringAttachment
2528 1
                6 => $disposition,
2529 1
                7 => 0
2530
            );
2531
0 ignored issues
show
Blank line found at end of control structure
Loading history...
2532 11
        } catch (phpmailerException $exc) {
2533 11
            $this->setError($exc->getMessage());
2534 11
            $this->edebug($exc->getMessage());
2535
            if ($this->exceptions) {
2536 11
                throw $exc;
2537 6
            }
2538 6
            return false;
2539
        }
2540
        return true;
2541
    }
2542
2543
    /**
2544 11
     * Return the array of attachments.
2545 11
     * @return array
2546 11
     */
2547
    public function getAttachments()
2548
    {
2549
        return $this->attachment;
2550
    }
2551
2552
    /**
2553
     * Attach all file, string, and binary attachments to the message.
2554 11
     * Returns an empty string on failure.
2555 10
     * @access protected
2556 10
     * @param string $disposition_type
2557 10
     * @param string $boundary
2558 10
     * @return string
2559 10
     */
2560 10
    protected function attachAll($disposition_type, $boundary)
2561 10
    {
2562 1
        // Return text of body
2563 1
        $mime = array();
2564 1
        $cidUniq = array();
2565 1
        $incl = array();
2566 1
2567
        // Add all attachments
2568
        foreach ($this->attachment as $attachment) {
2569 11
            // Check if it is a valid disposition_filter
2570
            if ($attachment[6] == $disposition_type) {
2571
                // Check for string attachment
2572
                $string = '';
2573
                $path = '';
2574 11
                $bString = $attachment[5];
2575 2
                if ($bString) {
2576 2
                    $string = $attachment[0];
2577
                } else {
2578
                    $path = $attachment[0];
2579 2
                }
2580 2
2581 9
                $inclhash = md5(serialize($attachment));
2582 9
                if (in_array($inclhash, $incl)) {
2583
                    continue;
2584
                }
2585 9
                $incl[] = $inclhash;
2586
                $name = $attachment[2];
2587 11
                $encoding = $attachment[3];
2588 11
                $type = $attachment[4];
2589
                $disposition = $attachment[6];
2590 11
                $cid = $attachment[7];
2591
                if ($disposition == 'inline' && array_key_exists($cid, $cidUniq)) {
2592 11
                    continue;
2593
                }
2594
                $cidUniq[$cid] = true;
2595
2596
                $mime[] = sprintf('--%s%s', $boundary, $this->LE);
2597
                //Only include a filename property if we have one
2598
                if (!empty($name)) {
2599
                    $mime[] = sprintf(
2600
                        'Content-Type: %s; name="%s"%s',
2601
                        $type,
2602
                        $this->encodeHeader($this->secureHeader($name)),
2603
                        $this->LE
2604 9
                    );
2605
                } else {
2606
                    $mime[] = sprintf(
2607 9
                        'Content-Type: %s%s',
2608
                        $type,
2609
                        $this->LE
2610 9
                    );
2611 9
                }
2612
                // RFC1341 part 5 says 7bit is assumed if not specified
2613
                if ($encoding != '7bit') {
2614
                    $mime[] = sprintf('Content-Transfer-Encoding: %s%s', $encoding, $this->LE);
2615
                }
2616
2617
                if ($disposition == 'inline') {
2618
                    $mime[] = sprintf('Content-ID: <%s>%s', $cid, $this->LE);
2619
                }
2620
2621 9
                // If a filename contains any of these chars, it should be quoted,
2622 9
                // but not otherwise: RFC2183 & RFC2045 5.1
2623 9
                // Fixes a warning in IETF's msglint MIME checker
2624
                // Allow for bypassing the Content-Disposition header totally
2625
                if (!(empty($disposition))) {
2626
                    $encoded_name = $this->encodeHeader($this->secureHeader($name));
2627
                    if (preg_match('/[ \(\)<>@,;:\\"\/\[\]\?=]/', $encoded_name)) {
2628
                        $mime[] = sprintf(
2629
                            'Content-Disposition: %s; filename="%s"%s',
2630 9
                            $disposition,
2631
                            $encoded_name,
2632
                            $this->LE . $this->LE
2633
                        );
2634
                    } else {
2635
                        if (!empty($encoded_name)) {
2636
                            $mime[] = sprintf(
2637
                                'Content-Disposition: %s; filename=%s%s',
2638
                                $disposition,
2639
                                $encoded_name,
2640
                                $this->LE . $this->LE
2641
                            );
2642
                        } else {
2643
                            $mime[] = sprintf(
2644
                                'Content-Disposition: %s%s',
2645 41
                                $disposition,
2646
                                $this->LE . $this->LE
2647 41
                            );
2648 41
                        }
2649 41
                    }
2650 12
                } else {
2651 12
                    $mime[] = $this->LE;
2652 40
                }
2653 40
2654 37
                // Encode as string attachment
2655
                if ($bString) {
2656 37
                    $mime[] = $this->encodeString($string, $encoding);
2657 6
                    if ($this->isError()) {
2658 6
                        return '';
2659 37
                    }
2660 3
                    $mime[] = $this->LE . $this->LE;
2661
                } else {
2662
                    $mime[] = $this->encodeFile($path, $encoding);
2663 3
                    if ($this->isError()) {
2664 3
                        return '';
2665 3
                    }
2666
                    $mime[] = $this->LE . $this->LE;
2667
                }
2668
            }
2669 41
        }
2670 41
2671
        $mime[] = sprintf('--%s--%s', $boundary, $this->LE);
2672
2673
        return implode('', $mime);
2674
    }
2675
2676
    /**
2677
     * Encode a file attachment in requested format.
2678
     * Returns an empty string on failure.
2679
     * @param string $path The full path to the file
2680
     * @param string $encoding The encoding to use; one of 'base64', '7bit', '8bit', 'binary', 'quoted-printable'
2681 42
     * @throws phpmailerException
2682
     * @access protected
2683 42
     * @return string
2684 42
     */
2685 42
    protected function encodeFile($path, $encoding = 'base64')
2686 42
    {
2687
        try {
2688 42
            if (!is_readable($path)) {
2689 42
                throw new phpmailerException($this->lang('file_open') . $path, self::STOP_CONTINUE);
2690 42
            }
2691
            $magic_quotes = get_magic_quotes_runtime();
2692 2
            if ($magic_quotes) {
2693
                if (version_compare(PHP_VERSION, '5.3.0', '<')) {
2694
                    set_magic_quotes_runtime(false);
2695
                } else {
2696
                    //Doesn't exist in PHP 5.4, but we don't need to check because
2697
                    //get_magic_quotes_runtime always returns false in 5.4+
2698 42
                    //so it will never get here
2699
                    ini_set('magic_quotes_runtime', false);
2700
                }
2701 42
            }
2702 42
            $file_buffer = file_get_contents($path);
2703 42
            $file_buffer = $this->encodeString($file_buffer, $encoding);
2704 42
            if ($magic_quotes) {
2705 42
                if (version_compare(PHP_VERSION, '5.3.0', '<')) {
2706
                    set_magic_quotes_runtime($magic_quotes);
2707
                } else {
2708 42
                    ini_set('magic_quotes_runtime', $magic_quotes);
2709 42
                }
2710
            }
2711
            return $file_buffer;
2712
        } catch (Exception $exc) {
2713
            $this->setError($exc->getMessage());
2714
            return '';
2715
        }
2716
    }
2717
2718
    /**
2719
     * Encode a string in requested format.
2720
     * Returns an empty string on failure.
2721
     * @param string $str The text to encode
2722
     * @param string $encoding The encoding to use; one of 'base64', '7bit', '8bit', 'binary', 'quoted-printable'
2723
     * @access public
2724
     * @return string
2725
     */
2726
    public function encodeString($str, $encoding = 'base64')
2727
    {
2728
        $encoded = '';
2729
        switch (strtolower($encoding)) {
2730
            case 'base64':
2731
                $encoded = chunk_split(base64_encode($str), 76, $this->LE);
2732
                break;
2733
            case '7bit':
2734
            case '8bit':
2735
                $encoded = $this->fixEOL($str);
2736
                // Make sure it ends with a line break
2737
                if (substr($encoded, -(strlen($this->LE))) != $this->LE) {
2738
                    $encoded .= $this->LE;
2739
                }
2740
                break;
2741
            case 'binary':
2742
                $encoded = $str;
2743
                break;
2744
            case 'quoted-printable':
2745
                $encoded = $this->encodeQP($str);
2746
                break;
2747
            default:
2748
                $this->setError($this->lang('encoding') . $encoding);
2749
                break;
2750
        }
2751
        return $encoded;
2752
    }
2753
2754
    /**
2755
     * Encode a header string optimally.
2756
     * Picks shortest of Q, B, quoted-printable or none.
2757
     * @access public
2758
     * @param string $str
2759 56
     * @param string $position
2760
     * @return string
2761 56
     */
2762
    public function encodeHeader($str, $position = 'text')
2763
    {
2764
        $matchcount = 0;
2765
        switch (strtolower($position)) {
2766
            case 'phrase':
2767
                if (!preg_match('/[\200-\377]/', $str)) {
2768
                    // Can't use addslashes as we don't know the value of magic_quotes_sybase
2769
                    $encoded = addcslashes($str, "\0..\37\177\\\"");
2770
                    if (($str == $encoded) && !preg_match('/[^A-Za-z0-9!#$%&\'*+\/=?^_`{|}~ -]/', $str)) {
2771
                        return ($encoded);
2772
                    } else {
2773
                        return ("\"$encoded\"");
2774
                    }
2775
                }
2776
                $matchcount = preg_match_all('/[^\040\041\043-\133\135-\176]/', $str, $matches);
2777
                break;
2778
            /** @noinspection PhpMissingBreakStatementInspection */
2779
            case 'comment':
2780
                $matchcount = preg_match_all('/[()"]/', $str, $matches);
2781
                // Intentional fall-through
2782
            case 'text':
2783
            default:
2784
                $matchcount += preg_match_all('/[\000-\010\013\014\016-\037\177-\377]/', $str, $matches);
2785
                break;
2786
        }
2787
2788
        //There are no chars that need encoding
2789
        if ($matchcount == 0) {
2790
            return ($str);
2791
        }
2792
2793
        $maxlen = 75 - 7 - strlen($this->CharSet);
2794
        // Try to select the encoding which should produce the shortest output
2795
        if ($matchcount > strlen($str) / 3) {
2796
            // More than a third of the content will need encoding, so B encoding will be most efficient
2797
            $encoding = 'B';
2798
            if (function_exists('mb_strlen') && $this->hasMultiBytes($str)) {
2799
                // Use a custom function which correctly encodes and wraps long
2800
                // multibyte strings without breaking lines within a character
2801
                $encoded = $this->base64EncodeWrapMB($str, "\n");
2802
            } else {
2803
                $encoded = base64_encode($str);
2804
                $maxlen -= $maxlen % 4;
2805
                $encoded = trim(chunk_split($encoded, $maxlen, "\n"));
2806
            }
2807
        } else {
2808
            $encoding = 'Q';
2809
            $encoded = $this->encodeQ($str, $position);
2810
            $encoded = $this->wrapText($encoded, $maxlen, true);
2811
            $encoded = str_replace('=' . self::CRLF, "\n", trim($encoded));
2812
        }
2813
2814
        $encoded = preg_replace('/^(.*)$/m', ' =?' . $this->CharSet . "?$encoding?\\1?=", $encoded);
2815
        $encoded = trim(str_replace("\n", $this->LE, $encoded));
2816 3
2817
        return $encoded;
2818
    }
2819 3
2820 3
    /**
2821
     * Check if a string contains multi-byte characters.
2822
     * @access public
2823
     * @param string $str multi-byte text to wrap encode
2824
     * @return boolean
2825
     */
2826
    public function hasMultiBytes($str)
2827
    {
2828
        if (function_exists('mb_strlen')) {
2829
            return (strlen($str) > mb_strlen($str, $this->CharSet));
2830
        } else { // Assume no multibytes (we can't handle without mbstring functions anyway)
2831
            return false;
2832
        }
2833
    }
2834
2835
    /**
2836
     * Does a string contain any 8-bit chars (in any charset)?
2837
     * @param string $text
2838
     * @return boolean
2839
     */
2840
    public function has8bitChars($text)
2841
    {
2842
        return (boolean)preg_match('/[\x80-\xFF]/', $text);
2843
    }
2844
2845
    /**
2846
     * Encode and wrap long multibyte strings for mail headers
2847
     * without breaking lines within a character.
2848
     * Adapted from a function by paravoid
2849
     * @link http://www.php.net/manual/en/function.mb-encode-mimeheader.php#60283
2850
     * @access public
2851
     * @param string $str multi-byte text to wrap encode
2852
     * @param string $linebreak string to use as linefeed/end-of-line
2853
     * @return string
2854
     */
2855
    public function base64EncodeWrapMB($str, $linebreak = null)
2856
    {
2857 1
        $start = '=?' . $this->CharSet . '?B?';
2858
        $end = '?=';
2859
        $encoded = '';
2860 1
        if ($linebreak === null) {
2861 1
            $linebreak = $this->LE;
2862 1
        }
2863 1
2864
        $mb_length = mb_strlen($str, $this->CharSet);
2865 1
        // Each line must have length <= 75, including $start and $end
2866 1
        $length = 75 - strlen($start) - strlen($end);
2867
        // Average multi-byte ratio
2868 1
        $ratio = $mb_length / strlen($str);
2869
        // Base64 has a 4:3 ratio
2870 1
        $avgLength = floor($length * $ratio * .75);
2871
2872
        for ($i = 0; $i < $mb_length; $i += $offset) {
2873 1
            $lookBack = 0;
2874 1
            do {
2875
                $offset = $avgLength - $lookBack;
2876
                $chunk = mb_substr($str, $i, $offset, $this->CharSet);
2877 1
                $chunk = base64_encode($chunk);
2878 1
                $lookBack++;
2879 1
            } while (strlen($chunk) > $length);
2880 1
            $encoded .= $chunk . $linebreak;
2881 1
        }
2882
2883
        // Chomp the last linefeed
2884 1
        $encoded = substr($encoded, 0, -strlen($linebreak));
2885 1
        return $encoded;
2886 1
    }
2887 1
2888 1
    /**
2889 1
     * Encode a string in quoted-printable format.
2890 1
     * According to RFC2045 section 6.7.
2891 1
     * @access public
2892 1
     * @param string $string The text to encode
2893
     * @param integer $line_max Number of chars allowed on a line before wrapping
2894 1
     * @return string
2895
     * @link http://www.php.net/manual/en/function.quoted-printable-decode.php#89417 Adapted from this comment
2896
     */
2897
    public function encodeQP($string, $line_max = 76)
2898
    {
2899
        // Use native function if it's available (>= PHP5.3)
2900
        if (function_exists('quoted_printable_encode')) {
2901
            return quoted_printable_encode($string);
2902
        }
2903
        // Fall back to a pure PHP implementation
2904
        $string = str_replace(
2905
            array('%20', '%0D%0A.', '%0D%0A', '%'),
2906
            array(' ', "\r\n=2E", "\r\n", '='),
2907
            rawurlencode($string)
2908 1
        );
2909
        return preg_replace('/[^\r\n]{' . ($line_max - 3) . '}[^=\r\n]{2}/', "$0=\r\n", $string);
2910
    }
2911
2912
    /**
2913
     * Backward compatibility wrapper for an old QP encoding function that was removed.
2914
     * @see PHPMailer::encodeQP()
2915
     * @access public
2916 1
     * @param string $string
2917 1
     * @param integer $line_max
2918 1
     * @param boolean $space_conv
2919
     * @return string
2920 1
     * @deprecated Use encodeQP instead.
2921 1
     */
2922 1
    public function encodeQPphp(
2923 1
        $string,
2924 1
        $line_max = 76,
2925 1
        /** @noinspection PhpUnusedParameterInspection */ $space_conv = false
2926 1
    ) {
2927 1
        return $this->encodeQP($string, $line_max);
2928
    }
2929 1
2930 1
    /**
2931
     * Encode a string using Q encoding.
2932
     * @link http://tools.ietf.org/html/rfc2047
2933
     * @param string $str the text to encode
2934
     * @param string $position Where the text is going to be used, see the RFC for what that means
2935
     * @access public
2936
     * @return string
2937
     */
2938
    public function encodeQ($str, $position = 'text')
2939
    {
2940
        // There should not be any EOL in the string
2941
        $pattern = '';
2942
        $encoded = str_replace(array("\r", "\n"), '', $str);
2943
        switch (strtolower($position)) {
2944
            case 'phrase':
2945
                // RFC 2047 section 5.3
2946
                $pattern = '^A-Za-z0-9!*+\/ -';
2947
                break;
2948 5
            /** @noinspection PhpMissingBreakStatementInspection */
2949
            case 'comment':
2950 5
                // RFC 2047 section 5.2
2951 1
                $pattern = '\(\)"';
2952 1
                // intentional fall-through
2953
                // for this reason we build the $pattern without including delimiters and []
2954
            case 'text':
2955
            default:
2956 5
                // RFC 2047 section 5.1
2957 1
                // Replace every high ascii, control, =, ? and _ characters
2958 1
                $pattern = '\000-\011\013\014\016-\037\075\077\137\177-\377' . $pattern;
2959
                break;
2960 5
        }
2961 5
        $matches = array();
2962 1
        if (preg_match_all("/[{$pattern}]/", $encoded, $matches)) {
2963 1
            // If the string contains an '=', make sure it's the first thing we replace
2964
            // so as to avoid double-encoding
2965
            $eqkey = array_search('=', $matches[0]);
2966 5
            if (false !== $eqkey) {
2967 5
                unset($matches[0][$eqkey]);
2968 5
                array_unshift($matches[0], '=');
2969 5
            }
2970 5
            foreach (array_unique($matches[0]) as $char) {
2971 5
                $encoded = str_replace($char, '=' . sprintf('%02X', ord($char)), $encoded);
2972 5
            }
2973 5
        }
2974
        // Replace every spaces to _ (more readable than =20)
2975 5
        return str_replace(' ', '_', $encoded);
2976 5
    }
2977
2978
    /**
2979
     * Add a string or binary attachment (non-filesystem).
2980
     * This method can be used to attach ascii or binary data,
2981
     * such as a BLOB record from a database.
2982
     * @param string $string String attachment data.
2983
     * @param string $filename Name of the attachment.
2984
     * @param string $encoding File encoding (see $Encoding).
2985
     * @param string $type File extension (MIME) type.
2986
     * @param string $disposition Disposition to use
2987
     * @return void
2988
     */
2989
    public function addStringAttachment(
2990
        $string,
2991
        $filename,
2992
        $encoding = 'base64',
2993 1
        $type = '',
2994
        $disposition = 'attachment'
2995
    ) {
2996
        // If a MIME type is not specified, try to work it out from the file name
2997
        if ($type == '') {
2998
            $type = self::filenameToType($filename);
2999
        }
3000
        // Append to $attachment array
3001
        $this->attachment[] = array(
3002 1
            0 => $string,
3003
            1 => $filename,
3004
            2 => basename($filename),
3005
            3 => $encoding,
3006
            4 => $type,
3007 1
            5 => true, // isStringAttachment
3008 1
            6 => $disposition,
3009 1
            7 => 0
3010 1
        );
3011 1
    }
3012 1
3013 1
    /**
3014 1
     * Add an embedded (inline) attachment from a file.
3015
     * This can include images, sounds, and just about any other document type.
3016 1
     * These differ from 'regular' attachments in that they are intended to be
3017 1
     * displayed inline with the message, not just attached for download.
3018
     * This is used in HTML messages that embed the images
3019
     * the HTML refers to using the $cid value.
3020
     * @param string $path Path to the attachment.
3021
     * @param string $cid Content ID of the attachment; Use this to reference
3022
     *        the content when using an embedded image in HTML.
3023
     * @param string $name Overrides the attachment name.
3024
     * @param string $encoding File encoding (see $Encoding).
3025 41
     * @param string $type File MIME type.
3026
     * @param string $disposition Disposition to use
3027 41
     * @return boolean True on successfully adding an attachment
3028 11
     */
3029 6
    public function addEmbeddedImage($path, $cid, $name = '', $encoding = 'base64', $type = '', $disposition = 'inline')
3030
    {
3031 35
        if (!@is_file($path)) {
3032 35
            $this->setError($this->lang('file_access') . $path);
3033
            return false;
3034
        }
3035
3036
        // If a MIME type is not specified, try to work it out from the file name
3037
        if ($type == '') {
3038
            $type = self::filenameToType($path);
3039 41
        }
3040
3041 41
        $filename = basename($path);
3042 11
        if ($name == '') {
3043 6
            $name = $filename;
3044
        }
3045 36
3046 35
        // Append to $attachment array
3047
        $this->attachment[] = array(
3048
            0 => $path,
3049
            1 => $filename,
3050
            2 => $name,
3051
            3 => $encoding,
3052
            4 => $type,
3053 41
            5 => false, // isStringAttachment
3054
            6 => $disposition,
3055 41
            7 => $cid
3056
        );
3057
        return true;
3058
    }
3059
3060
    /**
3061
     * Add an embedded stringified attachment.
3062
     * This can include images, sounds, and just about any other document type.
3063
     * Be sure to set the $type to an image type for images:
3064 3
     * JPEG images use 'image/jpeg', GIF uses 'image/gif', PNG uses 'image/png'.
3065
     * @param string $string The attachment binary data.
3066 3
     * @param string $cid Content ID of the attachment; Use this to reference
3067 3
     *        the content when using an embedded image in HTML.
3068 1
     * @param string $name
3069 1
     * @param string $encoding File encoding (see $Encoding).
3070 1
     * @param string $type MIME type.
3071 3
     * @param string $disposition Disposition to use
3072 3
     * @return boolean True on successfully adding an attachment
3073
     */
3074
    public function addStringEmbeddedImage(
3075
        $string,
3076
        $cid,
3077
        $name = '',
3078 1
        $encoding = 'base64',
3079
        $type = '',
3080 1
        $disposition = 'inline'
3081 1
    ) {
3082 1
        // If a MIME type is not specified, try to work it out from the name
3083 1
        if ($type == '' and !empty($name)) {
3084 1
            $type = self::filenameToType($name);
3085 1
        }
3086
3087
        // Append to $attachment array
3088
        $this->attachment[] = array(
3089
            0 => $string,
3090
            1 => $name,
3091 1
            2 => $name,
3092
            3 => $encoding,
3093 1
            4 => $type,
3094 1
            5 => true, // isStringAttachment
3095 1
            6 => $disposition,
3096 1
            7 => $cid
3097 1
        );
3098 1
        return true;
3099
    }
3100
3101
    /**
3102
     * Check if an inline attachment is present.
3103
     * @access public
3104 2
     * @return boolean
3105
     */
3106 2
    public function inlineImageExists()
3107 1
    {
3108 2
        foreach ($this->attachment as $attachment) {
3109 2
            if ($attachment[6] == 'inline') {
3110 2
                return true;
3111 2
            }
3112
        }
3113
        return false;
3114
    }
3115
3116
    /**
3117 3
     * Check if an attachment (non-inline) is present.
3118
     * @return boolean
3119 3
     */
3120 3
    public function attachmentExists()
3121 3
    {
3122
        foreach ($this->attachment as $attachment) {
3123
            if ($attachment[6] == 'attachment') {
3124
                return true;
3125
            }
3126
        }
3127 5
        return false;
3128
    }
3129 5
3130 5
    /**
3131 5
     * Check if this message has an alternative body set.
3132 5
     * @return boolean
3133 5
     */
3134 5
    public function alternativeExists()
3135
    {
3136
        return !empty($this->AltBody);
3137
    }
3138
3139
    /**
3140 1
     * Clear queued addresses of given kind.
3141
     * @access protected
3142 1
     * @param string $kind 'to', 'cc', or 'bcc'
3143 1
     * @return void
3144
     */
3145
    public function clearQueuedAddresses($kind)
3146
    {
3147
        $RecipientsQueue = $this->RecipientsQueue;
3148
        foreach ($RecipientsQueue as $address => $params) {
3149 2
            if ($params[0] == $kind) {
3150
                unset($this->RecipientsQueue[$address]);
3151 2
            }
3152 2
        }
3153
    }
3154
3155
    /**
3156
     * Clear all To recipients.
3157
     * @return void
3158
     */
3159
    public function clearAddresses()
3160 8
    {
3161
        foreach ($this->to as $to) {
3162 8
            unset($this->all_recipients[strtolower($to[0])]);
3163 8
        }
3164 1
        $this->to = array();
3165 1
        $this->clearQueuedAddresses('to');
3166
    }
3167
3168
    /**
3169
     * Clear all CC recipients.
3170
     * @return void
3171
     */
3172
    public function clearCCs()
3173
    {
3174
        foreach ($this->cc as $cc) {
3175
            unset($this->all_recipients[strtolower($cc[0])]);
3176
        }
3177 1
        $this->cc = array();
3178 8
        $this->clearQueuedAddresses('cc');
3179 8
    }
3180
3181
    /**
3182
     * Clear all BCC recipients.
3183
     * @return void
3184
     */
3185
    public function clearBCCs()
3186
    {
3187 42
        foreach ($this->bcc as $bcc) {
3188
            unset($this->all_recipients[strtolower($bcc[0])]);
3189
        }
3190
        $this->bcc = array();
3191 42
        $this->clearQueuedAddresses('bcc');
3192 42
    }
3193
3194
    /**
3195
     * Clear all ReplyTo recipients.
3196
     * @return void
3197
     */
3198
    public function clearReplyTos()
3199
    {
3200
        $this->ReplyTo = array();
3201 42
        $this->ReplyToQueue = array();
3202
    }
3203 42
3204 42
    /**
3205
     * Clear all recipient types.
3206 42
     * @return void
3207
     */
3208 42
    public function clearAllRecipients()
3209 42
    {
3210 42
        $this->to = array();
3211
        $this->cc = array();
3212
        $this->bcc = array();
3213 42
        $this->all_recipients = array();
3214
        $this->RecipientsQueue = array();
3215
    }
3216
3217
    /**
3218
     * Clear all filesystem, string, and binary attachments.
3219
     * @return void
3220
     */
3221
    public function clearAttachments()
3222 8
    {
3223
        $this->attachment = array();
3224 8
    }
3225 7
3226 7
    /**
3227
     * Clear all custom headers.
3228 8
     * @return void
3229 8
     */
3230
    public function clearCustomHeaders()
3231
    {
3232
        $this->CustomHeader = array();
3233
    }
3234
3235 8
    /**
3236
     * Add an error message to the error container.
3237
     * @access protected
3238
     * @param string $msg
3239
     * @return void
3240
     */
3241
    protected function setError($msg)
3242
    {
3243
        $this->error_count++;
3244
        if ($this->Mailer == 'smtp' and !is_null($this->smtp)) {
3245
            $lasterror = $this->smtp->getError();
3246
            if (!empty($lasterror['error'])) {
3247 41
                $msg .= $this->lang('smtp_error') . $lasterror['error'];
3248
                if (!empty($lasterror['detail'])) {
3249 41
                    $msg .= ' Detail: '. $lasterror['detail'];
3250
                }
3251
                if (!empty($lasterror['smtp_code'])) {
3252
                    $msg .= ' SMTP code: ' . $lasterror['smtp_code'];
3253
                }
3254
                if (!empty($lasterror['smtp_code_ex'])) {
3255
                    $msg .= ' Additional SMTP info: ' . $lasterror['smtp_code_ex'];
3256
                }
3257
            }
3258
        }
3259 38
        $this->ErrorInfo = $msg;
3260
    }
3261
3262 38
    /**
3263
     * Return an RFC 822 formatted date.
3264 38
     * @access public
3265
     * @return string
3266
     * @static
3267 38
     */
3268
    public static function rfcDate()
3269
    {
3270
        // Set the time zone to whatever the default is to avoid 500 errors
3271
        // Will default to UTC if it's not set properly in php.ini
3272
        date_default_timezone_set(@date_default_timezone_get());
3273
        return date('D, j M Y H:i:s O');
3274
    }
3275
3276
    /**
3277
     * Get the server hostname.
3278
     * Returns 'localhost.localdomain' if unknown.
3279 2
     * @access protected
3280
     * @return string
3281 2
     */
3282
    protected function serverHostname()
3283 2
    {
3284 2
        $result = 'localhost.localdomain';
3285 1
        if (!empty($this->Hostname)) {
3286
            $result = $this->Hostname;
3287 2
        } elseif (isset($_SERVER) and array_key_exists('SERVER_NAME', $_SERVER) and !empty($_SERVER['SERVER_NAME'])) {
3288
            $result = $_SERVER['SERVER_NAME'];
3289
        } elseif (function_exists('gethostname') && gethostname() !== false) {
3290
            $result = gethostname();
3291
        } elseif (php_uname('n') !== false) {
3292
            $result = php_uname('n');
3293 1
        }
3294
        return $result;
3295 1
    }
3296
3297
    /**
3298
     * Get an error message in the current language.
3299
     * @access protected
3300
     * @param string $key
3301
     * @return string
3302
     */
3303
    protected function lang($key)
3304
    {
3305
        if (count($this->language) < 1) {
3306
            $this->setLanguage('en'); // set the default language
3307
        }
3308
3309
        if (array_key_exists($key, $this->language)) {
3310 2
            if ($key == 'smtp_connect_failed') {
3311
                //Include a link to troubleshooting docs on SMTP connection failure
3312 2
                //this is by far the biggest cause of support questions
3313 2
                //but it's usually not PHPMailer's fault.
3314 2
                return $this->language[$key] . ' https://github.com/PHPMailer/PHPMailer/wiki/Troubleshooting';
3315
            }
3316 2
            return $this->language[$key];
3317
        } else {
3318
            //Return the key as a fallback
3319
            return $key;
3320
        }
3321
    }
3322
3323
    /**
3324
     * Check if an error occurred.
3325
     * @access public
3326
     * @return boolean True if an error did occur.
3327
     */
3328
    public function isError()
3329
    {
3330
        return ($this->error_count > 0);
3331 2
    }
3332
3333
    /**
3334 2
     * Ensure consistent line endings in a string.
3335 2
     * Changes every end of line from CRLF, CR or LF to $this->LE.
3336 2
     * @access public
3337
     * @param string $str String to fixEOL
3338
     * @return string
3339 2
     */
3340 2
    public function fixEOL($str)
3341 2
    {
3342 2
        // Normalise to \n
3343 2
        $nstr = str_replace(array("\r\n", "\r"), "\n", $str);
3344 2
        // Now convert LE as needed
3345 2
        if ($this->LE !== "\n") {
3346 2
            $nstr = str_replace("\n", $this->LE, $nstr);
3347 2
        }
3348 2
        return $nstr;
3349 2
    }
3350 2
3351 2
    /**
3352 2
     * Add a custom header.
3353 2
     * $name value can be overloaded to contain
3354 2
     * both header name and value (name:value)
3355 2
     * @access public
3356 2
     * @param string $name Custom header name
3357
     * @param string $value Header value
3358 2
     * @return void
3359 2
     */
3360 2
    public function addCustomHeader($name, $value = null)
3361 2
    {
3362 2
        if ($value === null) {
3363 2
            // Value passed in as name:value
3364
            $this->CustomHeader[] = explode(':', $name, 2);
3365 2
        } else {
3366 2
            $this->CustomHeader[] = array($name, $value);
3367 2
        }
3368
    }
3369
3370
    /**
3371 2
     * Returns all custom headers.
3372
     * @return array
3373
     */
3374
    public function getCustomHeaders()
3375
    {
3376
        return $this->CustomHeader;
3377
    }
3378
3379
    /**
3380
     * Create a message body from an HTML string.
3381
     * Automatically inlines images and creates a plain-text version by converting the HTML,
3382
     * overwriting any existing values in Body and AltBody.
3383
     * $basedir is used when handling relative image paths, e.g. <img src="images/a.png">
3384
     * will look for an image file in $basedir/images/a.png and convert it to inline.
3385
     * If you don't want to apply these transformations to your HTML, just set Body and AltBody yourself.
3386
     * @access public
3387
     * @param string $message HTML message string
3388
     * @param string $basedir base directory for relative paths to images
3389
     * @param boolean|callable $advanced Whether to use the internal HTML to text converter
3390
     *    or your own custom converter @see PHPMailer::html2text()
3391
     * @return string $message The transformed message Body
3392
     */
3393
    public function msgHTML($message, $basedir = '', $advanced = false)
3394 2
    {
3395
        preg_match_all('/(src|background)=["\'](.*)["\']/Ui', $message, $images);
3396 2
        if (array_key_exists(2, $images)) {
3397 1
            foreach ($images[2] as $imgindex => $url) {
3398
                // Convert data URIs into embedded images
3399 2
                if (preg_match('#^data:(image[^;,]*)(;base64)?,#', $url, $match)) {
3400 2
                    $data = substr($url, strpos($url, ','));
3401 2
                    if ($match[2]) {
3402 2
                        $data = base64_decode($data);
3403 2
                    } else {
3404
                        $data = rawurldecode($data);
3405
                    }
3406
                    $cid = md5($url) . '@phpmailer.0'; // RFC2392 S 2
3407
                    if ($this->addStringEmbeddedImage($data, $cid, 'embed' . $imgindex, 'base64', $match[1])) {
3408
                        $message = str_replace(
3409
                            $images[0][$imgindex],
3410
                            $images[1][$imgindex] . '="cid:' . $cid . '"',
3411
                            $message
3412
                        );
3413 10
                    }
3414
                } elseif (substr($url, 0, 4) !== 'cid:' && !preg_match('#^[a-z][a-z0-9+.-]*://#i', $url)) {
3415
                    // Do not change urls for absolute images (thanks to corvuscorax)
3416 10
                    // Do not change urls that are already inline images
3417 10
                    $filename = basename($url);
3418 10
                    $directory = dirname($url);
3419 10
                    if ($directory == '.') {
3420 10
                        $directory = '';
3421 10
                    }
3422 10
                    $cid = md5($url) . '@phpmailer.0'; // RFC2392 S 2
3423 10
                    if (strlen($basedir) > 1 && substr($basedir, -1) != '/') {
3424 10
                        $basedir .= '/';
3425 10
                    }
3426 10
                    if (strlen($directory) > 1 && substr($directory, -1) != '/') {
3427 10
                        $directory .= '/';
3428 10
                    }
3429 10
                    if ($this->addEmbeddedImage(
3430 10
                        $basedir . $directory . $filename,
3431 10
                        $cid,
3432 10
                        $filename,
3433 10
                        'base64',
3434 10
                        self::_mime_types((string)self::mb_pathinfo($filename, PATHINFO_EXTENSION))
3435 10
                    )
3436 10
                    ) {
3437 10
                        $message = preg_replace(
3438 10
                            '/' . $images[1][$imgindex] . '=["\']' . preg_quote($url, '/') . '["\']/Ui',
3439 10
                            $images[1][$imgindex] . '="cid:' . $cid . '"',
3440 10
                            $message
3441 10
                        );
3442 10
                    }
3443 10
                }
3444 10
            }
3445 10
        }
3446 10
        $this->isHTML(true);
3447 10
        // Convert all message body line breaks to CRLF, makes quoted-printable encoding work much better
3448 10
        $this->Body = $this->normalizeBreaks($message);
3449 10
        $this->AltBody = $this->normalizeBreaks($this->html2text($message, $advanced));
3450 10
        if (!$this->alternativeExists()) {
3451 10
            $this->AltBody = 'To view this email message, open it in a program that understands HTML!' .
3452 10
                self::CRLF . self::CRLF;
3453 10
        }
3454 10
        return $this->Body;
3455 10
    }
3456 10
3457 10
    /**
3458 10
     * Convert an HTML string into plain text.
3459 10
     * This is used by msgHTML().
3460 10
     * Note - older versions of this function used a bundled advanced converter
3461 10
     * which was been removed for license reasons in #232.
3462 10
     * Example usage:
3463 10
     * <code>
3464 10
     * // Use default conversion
3465 10
     * $plain = $mail->html2text($html);
3466 10
     * // Use your own custom converter
3467 10
     * $plain = $mail->html2text($html, function($html) {
3468 10
     *     $converter = new MyHtml2text($html);
3469 10
     *     return $converter->get_text();
3470 10
     * });
3471 10
     * </code>
3472 10
     * @param string $html The HTML text to convert
3473 10
     * @param boolean|callable $advanced Any boolean value to use the internal converter,
3474 10
     *   or provide your own callable for custom conversion.
3475 10
     * @return string
3476 10
     */
3477 10
    public function html2text($html, $advanced = false)
3478 10
    {
3479 10
        if (is_callable($advanced)) {
3480 10
            return call_user_func($advanced, $html);
3481 10
        }
3482 10
        return html_entity_decode(
3483 10
            trim(strip_tags(preg_replace('/<(head|title|style|script)[^>]*>.*?<\/\\1>/si', '', $html))),
3484 10
            ENT_QUOTES,
3485 10
            $this->CharSet
3486 10
        );
3487 10
    }
3488 10
3489 10
    /**
3490 10
     * Get the MIME type for a file extension.
3491 10
     * @param string $ext File extension
3492 10
     * @access public
3493 10
     * @return string MIME type of file.
3494 10
     * @static
3495 10
     */
3496 10
    public static function _mime_types($ext = '')
3497 10
    {
3498 10
        $mimes = array(
3499 10
            'xl'    => 'application/excel',
3500 10
            'js'    => 'application/javascript',
3501 10
            'hqx'   => 'application/mac-binhex40',
3502 10
            'cpt'   => 'application/mac-compactpro',
3503 10
            'bin'   => 'application/macbinary',
3504 10
            'doc'   => 'application/msword',
3505 10
            'word'  => 'application/msword',
3506 10
            'xlsx'  => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
3507 10
            'xltx'  => 'application/vnd.openxmlformats-officedocument.spreadsheetml.template',
3508 10
            'potx'  => 'application/vnd.openxmlformats-officedocument.presentationml.template',
3509 10
            'ppsx'  => 'application/vnd.openxmlformats-officedocument.presentationml.slideshow',
3510 10
            'pptx'  => 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
3511 10
            'sldx'  => 'application/vnd.openxmlformats-officedocument.presentationml.slide',
3512 10
            'docx'  => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
3513
            'dotx'  => 'application/vnd.openxmlformats-officedocument.wordprocessingml.template',
3514 10
            'xlam'  => 'application/vnd.ms-excel.addin.macroEnabled.12',
3515 10
            'xlsb'  => 'application/vnd.ms-excel.sheet.binary.macroEnabled.12',
3516 10
            'class' => 'application/octet-stream',
3517
            'dll'   => 'application/octet-stream',
3518
            'dms'   => 'application/octet-stream',
3519
            'exe'   => 'application/octet-stream',
3520
            'lha'   => 'application/octet-stream',
3521
            'lzh'   => 'application/octet-stream',
3522
            'psd'   => 'application/octet-stream',
3523
            'sea'   => 'application/octet-stream',
3524
            'so'    => 'application/octet-stream',
3525
            'oda'   => 'application/oda',
3526
            'pdf'   => 'application/pdf',
3527
            'ai'    => 'application/postscript',
3528 7
            'eps'   => 'application/postscript',
3529
            'ps'    => 'application/postscript',
3530
            'smi'   => 'application/smil',
3531 7
            'smil'  => 'application/smil',
3532 7
            'mif'   => 'application/vnd.mif',
3533
            'xls'   => 'application/vnd.ms-excel',
3534
            'ppt'   => 'application/vnd.ms-powerpoint',
3535 7
            'wbxml' => 'application/vnd.wap.wbxml',
3536 7
            'wmlc'  => 'application/vnd.wap.wmlc',
3537
            'dcr'   => 'application/x-director',
3538
            'dir'   => 'application/x-director',
3539
            'dxr'   => 'application/x-director',
3540
            'dvi'   => 'application/x-dvi',
3541
            'gtar'  => 'application/x-gtar',
3542
            'php3'  => 'application/x-httpd-php',
3543
            'php4'  => 'application/x-httpd-php',
3544
            'php'   => 'application/x-httpd-php',
3545
            'phtml' => 'application/x-httpd-php',
3546
            'phps'  => 'application/x-httpd-php-source',
3547
            'swf'   => 'application/x-shockwave-flash',
3548
            'sit'   => 'application/x-stuffit',
3549
            'tar'   => 'application/x-tar',
3550 10
            'tgz'   => 'application/x-tar',
3551
            'xht'   => 'application/xhtml+xml',
3552 10
            'xhtml' => 'application/xhtml+xml',
3553 10
            'zip'   => 'application/zip',
3554 10
            'mid'   => 'audio/midi',
3555 10
            'midi'  => 'audio/midi',
3556 10
            'mp2'   => 'audio/mpeg',
3557 10
            'mp3'   => 'audio/mpeg',
3558 10
            'mpga'  => 'audio/mpeg',
3559 10
            'aif'   => 'audio/x-aiff',
3560 10
            'aifc'  => 'audio/x-aiff',
3561 10
            'aiff'  => 'audio/x-aiff',
3562 10
            'ram'   => 'audio/x-pn-realaudio',
3563 10
            'rm'    => 'audio/x-pn-realaudio',
3564 10
            'rpm'   => 'audio/x-pn-realaudio-plugin',
3565 10
            'ra'    => 'audio/x-realaudio',
3566 10
            'wav'   => 'audio/x-wav',
3567 10
            'bmp'   => 'image/bmp',
3568
            'gif'   => 'image/gif',
3569 10
            'jpeg'  => 'image/jpeg',
3570 10
            'jpe'   => 'image/jpeg',
3571 1
            'jpg'   => 'image/jpeg',
3572 10
            'png'   => 'image/png',
3573 10
            'tiff'  => 'image/tiff',
3574
            'tif'   => 'image/tiff',
3575 10
            'eml'   => 'message/rfc822',
3576 10
            'css'   => 'text/css',
3577 2
            'html'  => 'text/html',
3578 8
            'htm'   => 'text/html',
3579 8
            'shtml' => 'text/html',
3580 1
            'log'   => 'text/plain',
3581 8
            'text'  => 'text/plain',
3582 8
            'txt'   => 'text/plain',
3583 8
            'rtx'   => 'text/richtext',
3584
            'rtf'   => 'text/rtf',
3585
            'vcf'   => 'text/vcard',
3586
            'vcard' => 'text/vcard',
3587
            'xml'   => 'text/xml',
3588
            'xsl'   => 'text/xml',
3589
            'mpeg'  => 'video/mpeg',
3590
            'mpe'   => 'video/mpeg',
3591
            'mpg'   => 'video/mpeg',
3592
            'mov'   => 'video/quicktime',
3593
            'qt'    => 'video/quicktime',
3594
            'rv'    => 'video/vnd.rn-realvideo',
3595
            'avi'   => 'video/x-msvideo',
3596
            'movie' => 'video/x-sgi-movie'
3597
        );
3598
        if (array_key_exists(strtolower($ext), $mimes)) {
3599
            return $mimes[strtolower($ext)];
3600 1
        }
3601
        return 'application/octet-stream';
3602 1
    }
3603 1
3604 1
    /**
3605
     * Map a file name to a MIME type.
3606 1
     * Defaults to 'application/octet-stream', i.e.. arbitrary binary data.
3607 1
     * @param string $filename A file name or full path, does not need to exist as a file
3608
     * @return string
3609
     * @static
3610
     */
3611
    public static function filenameToType($filename)
3612
    {
3613
        // In case the path is a URL, strip any query string before getting extension
3614
        $qpos = strpos($filename, '?');
3615
        if (false !== $qpos) {
3616
            $filename = substr($filename, 0, $qpos);
3617 42
        }
3618
        $pathinfo = self::mb_pathinfo($filename);
3619 42
        return self::_mime_types($pathinfo['extension']);
3620
    }
3621
3622
    /**
3623
     * Multi-byte-safe pathinfo replacement.
3624
     * Drop-in replacement for pathinfo(), but multibyte-safe, cross-platform-safe, old-version-safe.
3625
     * Works similarly to the one in PHP >= 5.2.0
3626
     * @link http://www.php.net/manual/en/function.pathinfo.php#107461
3627
     * @param string $path A filename or path, does not need to exist as a file
3628
     * @param integer|string $options Either a PATHINFO_* constant,
3629
     *      or a string name to return only the specified piece, allows 'filename' to work on PHP < 5.2
3630
     * @return string|array
3631
     * @static
3632 3
     */
3633
    public static function mb_pathinfo($path, $options = null)
3634 3
    {
3635
        $ret = array('dirname' => '', 'basename' => '', 'extension' => '', 'filename' => '');
3636
        $pathinfo = array();
3637
        if (preg_match('%^(.*?)[\\\\/]*(([^/\\\\]*?)(\.([^\.\\\\/]+?)|))[\\\\/\.]*$%im', $path, $pathinfo)) {
3638
            if (array_key_exists(1, $pathinfo)) {
3639
                $ret['dirname'] = $pathinfo[1];
3640
            }
3641
            if (array_key_exists(2, $pathinfo)) {
3642
                $ret['basename'] = $pathinfo[2];
3643
            }
3644
            if (array_key_exists(5, $pathinfo)) {
3645 2
                $ret['extension'] = $pathinfo[5];
3646
            }
3647 2
            if (array_key_exists(3, $pathinfo)) {
3648 2
                $ret['filename'] = $pathinfo[3];
3649 2
            }
3650 2
        }
3651 2
        switch ($options) {
3652
            case PATHINFO_DIRNAME:
3653
            case 'dirname':
3654
                return $ret['dirname'];
3655
            case PATHINFO_BASENAME:
3656
            case 'basename':
3657
                return $ret['basename'];
3658
            case PATHINFO_EXTENSION:
3659 1
            case 'extension':
3660
                return $ret['extension'];
3661 1
            case PATHINFO_FILENAME:
3662 1
            case 'filename':
3663 1
                return $ret['filename'];
3664 1
            default:
3665 1
                return $ret;
3666 1
        }
3667 1
    }
3668
3669 1
    /**
3670 1
     * Set or reset instance properties.
3671
     * You should avoid this function - it's more verbose, less efficient, more error-prone and
3672
     * harder to debug than setting properties directly.
3673
     * Usage Example:
3674
     * `$mail->set('SMTPSecure', 'tls');`
3675
     *   is the same as:
3676
     * `$mail->SMTPSecure = 'tls';`
3677
     * @access public
3678
     * @param string $name The property name to set
3679
     * @param mixed $value The value to set the property to
3680 1
     * @return boolean
3681
     * @TODO Should this not be using the __set() magic function?
3682 1
     */
3683
    public function set($name, $value = '')
3684
    {
3685
        if (property_exists($this, $name)) {
3686
            $this->$name = $value;
3687
            return true;
3688 1
        } else {
3689 1
            $this->setError($this->lang('variable_set') . $name);
3690
            return false;
3691
        }
3692 1
    }
3693
3694 1
    /**
3695 1
     * Strip newlines to prevent header injection.
3696 1
     * @access public
3697
     * @param string $str
3698
     * @return string
3699
     */
3700
    public function secureHeader($str)
3701
    {
3702
        return trim(str_replace(array("\r", "\n"), '', $str));
3703
    }
3704
3705
    /**
3706
     * Normalize line breaks in a string.
3707
     * Converts UNIX LF, Mac CR and Windows CRLF line breaks into a single line break format.
3708 1
     * Defaults to CRLF (for message bodies) and preserves consecutive breaks.
3709
     * @param string $text
3710 1
     * @param string $breaktype What kind of line break to use, defaults to CRLF
3711 1
     * @return string
3712 1
     * @access public
3713 1
     * @static
3714 1
     */
3715 1
    public static function normalizeBreaks($text, $breaktype = "\r\n")
3716 1
    {
3717 1
        return preg_replace('/(\r\n|\r|\n)/ms', $breaktype, $text);
3718 1
    }
3719 1
3720
    /**
3721
     * Set the public and private key files and password for S/MIME signing.
3722
     * @access public
3723
     * @param string $cert_filename
3724
     * @param string $key_filename
3725
     * @param string $key_pass Password for private key
3726
     * @param string $extracerts_filename Optional path to chain certificate
3727
     */
3728 1
    public function sign($cert_filename, $key_filename, $key_pass, $extracerts_filename = '')
3729
    {
3730 1
        $this->sign_cert_file = $cert_filename;
3731
        $this->sign_key_file = $key_filename;
3732
        $this->sign_key_pass = $key_pass;
3733
        $this->sign_extracerts_file = $extracerts_filename;
3734 1
    }
3735 1
3736
    /**
3737 1
     * Quoted-Printable-encode a DKIM header.
3738 1
     * @access public
3739 1
     * @param string $txt
3740 1
     * @return string
3741
     */
3742
    public function DKIM_QP($txt)
3743
    {
3744
        $line = '';
3745
        for ($i = 0; $i < strlen($txt); $i++) {
3746
            $ord = ord($txt[$i]);
3747
            if (((0x21 <= $ord) && ($ord <= 0x3A)) || $ord == 0x3C || ((0x3E <= $ord) && ($ord <= 0x7E))) {
3748
                $line .= $txt[$i];
3749
            } else {
3750
                $line .= '=' . sprintf('%02X', $ord);
3751 1
            }
3752
        }
3753 1
        return $line;
3754 1
    }
3755 1
3756 1
    /**
3757 1
     * Generate a DKIM signature.
3758 1
     * @access public
3759 1
     * @param string $signHeader
3760 1
     * @throws phpmailerException
3761 1
     * @return string The DKIM signature value
3762 1
     */
3763 1
    public function DKIM_Sign($signHeader)
3764 1
    {
3765 1
        if (!defined('PKCS7_TEXT')) {
3766 1
            if ($this->exceptions) {
3767 1
                throw new phpmailerException($this->lang('extension_missing') . 'openssl');
3768 1
            }
3769 1
            return '';
3770 1
        }
3771 1
        $privKeyStr = !empty($this->DKIM_private_string) ? $this->DKIM_private_string : file_get_contents($this->DKIM_private);
3772 1
        if ('' != $this->DKIM_passphrase) {
3773 1
            $privKey = openssl_pkey_get_private($privKeyStr, $this->DKIM_passphrase);
3774 1
        } else {
3775
            $privKey = openssl_pkey_get_private($privKeyStr);
3776
        }
3777 1
        //Workaround for missing digest algorithms in old PHP & OpenSSL versions
3778
        //@link http://stackoverflow.com/a/11117338/333340
3779
        if (version_compare(PHP_VERSION, '5.3.0') >= 0 and
3780 1
            in_array('sha256WithRSAEncryption', openssl_get_md_methods(true))) {
3781 1
            if (openssl_sign($signHeader, $signature, $privKey, 'sha256WithRSAEncryption')) {
3782 1
                openssl_pkey_free($privKey);
3783 1
                return base64_encode($signature);
3784 1
            }
3785 1
        } else {
3786 1
            $pinfo = openssl_pkey_get_details($privKey);
3787 1
            $hash = hash('sha256', $signHeader);
3788 1
            //'Magic' constant for SHA256 from RFC3447
3789 1
            //@link https://tools.ietf.org/html/rfc3447#page-43
3790 1
            $t = '3031300d060960864801650304020105000420' . $hash;
3791 1
            $pslen = $pinfo['bits'] / 8 - (strlen($t) / 2 + 3);
3792 1
            $eb = pack('H*', '0001' . str_repeat('FF', $pslen) . '00' . $t);
3793 1
3794 1
            if (openssl_private_encrypt($eb, $signature, $privKey, OPENSSL_NO_PADDING)) {
3795
                openssl_pkey_free($privKey);
3796
                return base64_encode($signature);
3797
            }
3798 1
        }
3799 1
        openssl_pkey_free($privKey);
3800 1
        return '';
3801 1
    }
3802 1
3803 1
    /**
3804 1
     * Generate a DKIM canonicalization header.
3805 1
     * @access public
3806 1
     * @param string $signHeader Header
3807 1
     * @return string
3808 1
     */
3809 1
    public function DKIM_HeaderC($signHeader)
3810 1
    {
3811 1
        $signHeader = preg_replace('/\r\n\s+/', ' ', $signHeader);
3812 1
        $lines = explode("\r\n", $signHeader);
3813 1
        foreach ($lines as $key => $line) {
3814 1
            list($heading, $value) = explode(':', $line, 2);
3815 1
            $heading = strtolower($heading);
3816 1
            $value = preg_replace('/\s{2,}/', ' ', $value); // Compress useless spaces
3817
            $lines[$key] = $heading . ':' . trim($value); // Don't forget to remove WSP around the value
3818 1
        }
3819 1
        $signHeader = implode("\r\n", $lines);
3820 1
        return $signHeader;
3821
    }
3822
3823
    /**
3824
     * Generate a DKIM canonicalization body.
3825
     * @access public
3826
     * @param string $body Message Body
3827
     * @return string
3828
     */
3829 40
    public function DKIM_BodyC($body)
3830
    {
3831
        if ($body == '') {
3832 40
            return "\r\n";
3833
        }
3834
        // stabilize line endings
3835
        $body = str_replace("\r\n", "\n", $body);
3836
        $body = str_replace("\n", "\r\n", $body);
3837
        // END stabilize line endings
3838
        while (substr($body, strlen($body) - 4, 4) == "\r\n\r\n") {
3839
            $body = substr($body, 0, strlen($body) - 2);
3840
        }
3841 2
        return $body;
3842
    }
3843 2
3844
    /**
3845
     * Create the DKIM header and body in a new message header.
3846
     * @access public
3847
     * @param string $headers_line Header lines
3848
     * @param string $subject Subject
3849
     * @param string $body Body
3850
     * @return string
3851
     */
3852 1
    public function DKIM_Add($headers_line, $subject, $body)
3853
    {
3854 1
        $DKIMsignatureType = 'rsa-sha256'; // Signature & hash algorithms
3855
        $DKIMcanonicalization = 'relaxed/simple'; // Canonicalization of header/body
3856
        $DKIMquery = 'dns/txt'; // Query method
3857
        $DKIMtime = time(); // Signature Timestamp = seconds since 00:00:00 - Jan 1, 1970 (UTC time zone)
3858
        $subject_header = "Subject: $subject";
3859
        $headers = explode($this->LE, $headers_line);
3860
        $from_header = '';
3861
        $to_header = '';
3862
        $date_header = '';
3863 1
        $current = '';
3864
        foreach ($headers as $header) {
3865 1
            if (strpos($header, 'From:') === 0) {
3866
                $from_header = $header;
3867
                $current = 'from_header';
3868
            } elseif (strpos($header, 'To:') === 0) {
3869
                $to_header = $header;
3870
                $current = 'to_header';
3871
            } elseif (strpos($header, 'Date:') === 0) {
3872
                $date_header = $header;
3873
                $current = 'date_header';
3874 2
            } else {
3875
                if (!empty($$current) && strpos($header, ' =?') === 0) {
3876 2
                    $$current .= $header;
3877
                } else {
3878
                    $current = '';
3879
                }
3880
            }
3881
        }
3882
        $from = str_replace('|', '=7C', $this->DKIM_QP($from_header));
3883
        $to = str_replace('|', '=7C', $this->DKIM_QP($to_header));
3884
        $date = str_replace('|', '=7C', $this->DKIM_QP($date_header));
3885
        $subject = str_replace(
3886
            '|',
3887
            '=7C',
3888
            $this->DKIM_QP($subject_header)
3889
        ); // Copied header fields (dkim-quoted-printable)
3890
        $body = $this->DKIM_BodyC($body);
3891
        $DKIMlen = strlen($body); // Length of body
3892
        $DKIMb64 = base64_encode(pack('H*', hash('sha256', $body))); // Base64 of packed binary SHA-256 hash of body
3893
        if ('' == $this->DKIM_identity) {
3894
            $ident = '';
3895
        } else {
3896
            $ident = ' i=' . $this->DKIM_identity . ';';
3897
        }
3898
        $dkimhdrs = 'DKIM-Signature: v=1; a=' .
3899
            $DKIMsignatureType . '; q=' .
3900 36
            $DKIMquery . '; l=' .
3901
            $DKIMlen . '; s=' .
3902 36
            $this->DKIM_selector .
3903
            ";\r\n" .
3904
            "\tt=" . $DKIMtime . '; c=' . $DKIMcanonicalization . ";\r\n" .
3905
            "\th=From:To:Date:Subject;\r\n" .
3906 36
            "\td=" . $this->DKIM_domain . ';' . $ident . "\r\n" .
3907
            "\tz=$from\r\n" .
3908
            "\t|$to\r\n" .
3909
            "\t|$date\r\n" .
3910
            "\t|$subject;\r\n" .
3911
            "\tbh=" . $DKIMb64 . ";\r\n" .
3912
            "\tb=";
3913
        $toSign = $this->DKIM_HeaderC(
3914
            $from_header . "\r\n" .
3915
            $to_header . "\r\n" .
3916
            $date_header . "\r\n" .
3917
            $subject_header . "\r\n" .
3918
            $dkimhdrs
3919
        );
3920
        $signed = $this->DKIM_Sign($toSign);
3921
        return $dkimhdrs . $signed . "\r\n";
3922
    }
3923
3924
    /**
3925
     * Detect if a string contains a line longer than the maximum line length allowed.
3926
     * @param string $str
3927
     * @return boolean
3928
     * @static
3929
     */
3930
    public static function hasLineLongerThanMax($str)
3931
    {
3932
        //+2 to include CRLF line break for a 1000 total
3933
        return (boolean)preg_match('/^(.{'.(self::MAX_LINE_LENGTH + 2).',})/m', $str);
3934
    }
3935
3936
    /**
3937
     * Allows for public read access to 'to' property.
3938
     * @note: Before the send() call, queued addresses (i.e. with IDN) are not yet included.
3939
     * @access public
3940
     * @return array
3941
     */
3942
    public function getToAddresses()
3943
    {
3944
        return $this->to;
3945
    }
3946
3947
    /**
3948
     * Allows for public read access to 'cc' property.
3949
     * @note: Before the send() call, queued addresses (i.e. with IDN) are not yet included.
3950
     * @access public
3951
     * @return array
3952
     */
3953
    public function getCcAddresses()
3954
    {
3955
        return $this->cc;
3956
    }
3957
3958
    /**
3959
     * Allows for public read access to 'bcc' property.
3960
     * @note: Before the send() call, queued addresses (i.e. with IDN) are not yet included.
3961
     * @access public
3962
     * @return array
3963
     */
3964
    public function getBccAddresses()
3965
    {
3966
        return $this->bcc;
3967
    }
3968
3969
    /**
3970
     * Allows for public read access to 'ReplyTo' property.
3971
     * @note: Before the send() call, queued addresses (i.e. with IDN) are not yet included.
3972
     * @access public
3973
     * @return array
3974
     */
3975
    public function getReplyToAddresses()
3976
    {
3977
        return $this->ReplyTo;
3978
    }
3979
3980
    /**
3981
     * Allows for public read access to 'all_recipients' property.
3982
     * @note: Before the send() call, queued addresses (i.e. with IDN) are not yet included.
3983
     * @access public
3984
     * @return array
3985
     */
3986
    public function getAllRecipientAddresses()
3987
    {
3988
        return $this->all_recipients;
3989
    }
3990
3991
    /**
3992
     * Perform a callback.
3993
     * @param boolean $isSent
3994
     * @param array $to
3995
     * @param array $cc
3996
     * @param array $bcc
3997
     * @param string $subject
3998
     * @param string $body
3999
     * @param string $from
4000
     */
4001
    protected function doCallback($isSent, $to, $cc, $bcc, $subject, $body, $from)
4002
    {
4003
        if (!empty($this->action_function) && is_callable($this->action_function)) {
4004
            $params = array($isSent, $to, $cc, $bcc, $subject, $body, $from);
4005
            call_user_func_array($this->action_function, $params);
4006
        }
4007
    }
4008
}
4009
4010
/**
4011
 * PHPMailer exception handler
4012
 * @package PHPMailer
4013
 */
4014
class phpmailerException extends Exception
4015
{
4016
    /**
4017
     * Prettify error message output
4018
     * @return string
4019
     */
4020
    public function errorMessage()
4021
    {
4022
        $errorMsg = '<strong>' . $this->getMessage() . "</strong><br />\n";
4023
        return $errorMsg;
4024
    }
4025
}
4026