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

Code
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