Issues (4069)

Security Analysis    not enabled

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

include/phpmailer/class.phpmailer.php (6 issues)

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.13';
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.
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
     * @var string
205
     */
206
    public $MessageID = '';
207
208
    /**
209
     * The message Date to be used in the Date header.
210
     * If empty, the current date will be added.
211
     * @var string
212
     */
213
    public $MessageDate = '';
214
215
    /**
216
     * SMTP hosts.
217
     * Either a single hostname or multiple semicolon-delimited hostnames.
218
     * You can also specify a different port
219
     * for each host by using this format: [hostname:port]
220
     * (e.g. "smtp1.example.com:25;smtp2.example.com").
221
     * You can also specify encryption type, for example:
222
     * (e.g. "tls://smtp1.example.com:587;ssl://smtp2.example.com:465").
223
     * Hosts will be tried in order.
224
     * @var string
225
     */
226
    public $Host = 'localhost';
227
228
    /**
229
     * The default SMTP server port.
230
     * @var integer
231
     * @TODO Why is this needed when the SMTP class takes care of it?
232
     */
233
    public $Port = 25;
234
235
    /**
236
     * The SMTP HELO of the message.
237
     * Default is $Hostname. If $Hostname is empty, PHPMailer attempts to find
238
     * one with the same method described above for $Hostname.
239
     * @var string
240
     * @see PHPMailer::$Hostname
241
     */
242
    public $Helo = '';
243
244
    /**
245
     * What kind of encryption to use on the SMTP connection.
246
     * Options: '', 'ssl' or 'tls'
247
     * @var string
248
     */
249
    public $SMTPSecure = '';
250
251
    /**
252
     * Whether to enable TLS encryption automatically if a server supports it,
253
     * even if `SMTPSecure` is not set to 'tls'.
254
     * Be aware that in PHP >= 5.6 this requires that the server's certificates are valid.
255
     * @var boolean
256
     */
257
    public $SMTPAutoTLS = false;
258
259
    /**
260
     * Whether to use SMTP authentication.
261
     * Uses the Username and Password properties.
262
     * @var boolean
263
     * @see PHPMailer::$Username
264
     * @see PHPMailer::$Password
265
     */
266
    public $SMTPAuth = false;
267
268
    /**
269
     * Options array passed to stream_context_create when connecting via SMTP.
270
     * @var array
271
     */
272
    public $SMTPOptions = array();
273
274
    /**
275
     * SMTP username.
276
     * @var string
277
     */
278
    public $Username = '';
279
280
    /**
281
     * SMTP password.
282
     * @var string
283
     */
284
    public $Password = '';
285
286
    /**
287
     * SMTP auth type.
288
     * Options are LOGIN (default), PLAIN, NTLM, CRAM-MD5
289
     * @var string
290
     */
291
    public $AuthType = '';
292
293
    /**
294
     * SMTP realm.
295
     * Used for NTLM auth
296
     * @var string
297
     */
298
    public $Realm = '';
299
300
    /**
301
     * SMTP workstation.
302
     * Used for NTLM auth
303
     * @var string
304
     */
305
    public $Workstation = '';
306
307
    /**
308
     * The SMTP server timeout in seconds.
309
     * Default of 5 minutes (300sec) is from RFC2821 section 4.5.3.2
310
     * @var integer
311
     */
312
    public $Timeout = 300;
313
314
    /**
315
     * SMTP class debug output mode.
316
     * Debug output level.
317
     * Options:
318
     * * `0` No output
319
     * * `1` Commands
320
     * * `2` Data and commands
321
     * * `3` As 2 plus connection status
322
     * * `4` Low-level data output
323
     * @var integer
324
     * @see SMTP::$do_debug
325
     */
326
    public $SMTPDebug = 0;
327
328
    /**
329
     * How to handle debug output.
330
     * Options:
331
     * * `echo` Output plain-text as-is, appropriate for CLI
332
     * * `html` Output escaped, line breaks converted to `<br>`, appropriate for browser output
333
     * * `error_log` Output to error log as configured in php.ini
334
     *
335
     * Alternatively, you can provide a callable expecting two params: a message string and the debug level:
336
     * <code>
337
     * $mail->Debugoutput = function($str, $level) {echo "debug level $level; message: $str";};
338
     * </code>
339
     * @var string|callable
340
     * @see SMTP::$Debugoutput
341
     */
342
    public $Debugoutput = 'echo';
343
344
    /**
345
     * Whether to keep SMTP connection open after each message.
346
     * If this is set to true then to close the connection
347
     * requires an explicit call to smtpClose().
348
     * @var boolean
349
     */
350
    public $SMTPKeepAlive = false;
351
352
    /**
353
     * Whether to split multiple to addresses into multiple messages
354
     * or send them all in one message.
355
     * @var boolean
356
     */
357
    public $SingleTo = false;
358
359
    /**
360
     * Storage for addresses when SingleTo is enabled.
361
     * @var array
362
     * @TODO This should really not be public
363
     */
364
    public $SingleToArray = array();
365
366
    /**
367
     * Whether to generate VERP addresses on send.
368
     * Only applicable when sending via SMTP.
369
     * @link https://en.wikipedia.org/wiki/Variable_envelope_return_path
370
     * @link http://www.postfix.org/VERP_README.html Postfix VERP info
371
     * @var boolean
372
     */
373
    public $do_verp = false;
374
375
    /**
376
     * Whether to allow sending messages with an empty body.
377
     * @var boolean
378
     */
379
    public $AllowEmpty = false;
380
381
    /**
382
     * The default line ending.
383
     * @note The default remains "\n". We force CRLF where we know
384
     *        it must be used via self::CRLF.
385
     * @var string
386
     */
387
    public $LE = "\n";
388
389
    /**
390
     * DKIM selector.
391
     * @var string
392
     */
393
    public $DKIM_selector = '';
394
395
    /**
396
     * DKIM Identity.
397
     * Usually the email address used as the source of the email
398
     * @var string
399
     */
400
    public $DKIM_identity = '';
401
402
    /**
403
     * DKIM passphrase.
404
     * Used if your key is encrypted.
405
     * @var string
406
     */
407
    public $DKIM_passphrase = '';
408
409
    /**
410
     * DKIM signing domain name.
411
     * @example 'example.com'
412
     * @var string
413
     */
414
    public $DKIM_domain = '';
415
416
    /**
417
     * DKIM private key file path.
418
     * @var string
419
     */
420
    public $DKIM_private = '';
421
422
    /**
423
     * Callback Action function name.
424
     *
425
     * The function that handles the result of the send email action.
426
     * It is called out by send() for each email sent.
427
     *
428
     * Value can be any php callable: http://www.php.net/is_callable
429
     *
430
     * Parameters:
431
     *   boolean $result        result of the send action
432
     *   string  $to            email address of the recipient
433
     *   string  $cc            cc email addresses
434
     *   string  $bcc           bcc email addresses
435
     *   string  $subject       the subject
436
     *   string  $body          the email body
437
     *   string  $from          email address of sender
438
     * @var string
439
     */
440
    public $action_function = '';
441
442
    /**
443
     * What to put in the X-Mailer header.
444
     * Options: An empty string for PHPMailer default, whitespace for none, or a string to use
445
     * @var string
446
     */
447
    public $XMailer = '';
448
449
    /**
450
     * Only For XOAUTH - Google
451
     * Options: An empty string for PHPMailer default, Enter the email used to get access token
452
     * @var string
453
     */
454
//    public $UserEmail = '';
455
//    public $RefreshToken = '';
456
//    public $ClientId = '';
457
//    public $ClientSecret = '';
458
459
460
    /**
461
     * An instance of the SMTP sender class.
462
     * @var SMTP
463
     * @access protected
464
     */
465
    protected $smtp = null;
466
467
    /**
468
     * The array of 'to' addresses.
469
     * @var array
470
     * @access protected
471
     */
472
    protected $to = array();
473
474
    /**
475
     * The array of 'cc' addresses.
476
     * @var array
477
     * @access protected
478
     */
479
    protected $cc = array();
480
481
    /**
482
     * The array of 'bcc' addresses.
483
     * @var array
484
     * @access protected
485
     */
486
    protected $bcc = array();
487
488
    /**
489
     * The array of reply-to names and addresses.
490
     * @var array
491
     * @access protected
492
     */
493
    protected $ReplyTo = array();
494
495
    /**
496
     * An array of all kinds of addresses.
497
     * Includes all of $to, $cc, $bcc
498
     * @var array
499
     * @access protected
500
     */
501
    protected $all_recipients = array();
502
503
    /**
504
     * The array of attachments.
505
     * @var array
506
     * @access protected
507
     */
508
    protected $attachment = array();
509
510
    /**
511
     * The array of custom headers.
512
     * @var array
513
     * @access protected
514
     */
515
    protected $CustomHeader = array();
516
517
    /**
518
     * The most recent Message-ID (including angular brackets).
519
     * @var string
520
     * @access protected
521
     */
522
    protected $lastMessageID = '';
523
524
    /**
525
     * The message's MIME type.
526
     * @var string
527
     * @access protected
528
     */
529
    protected $message_type = '';
530
531
    /**
532
     * The array of MIME boundary strings.
533
     * @var array
534
     * @access protected
535
     */
536
    protected $boundary = array();
537
538
    /**
539
     * The array of available languages.
540
     * @var array
541
     * @access protected
542
     */
543
    protected $language = array();
544
545
    /**
546
     * The number of errors encountered.
547
     * @var integer
548
     * @access protected
549
     */
550
    protected $error_count = 0;
551
552
    /**
553
     * The S/MIME certificate file path.
554
     * @var string
555
     * @access protected
556
     */
557
    protected $sign_cert_file = '';
558
559
    /**
560
     * The S/MIME key file path.
561
     * @var string
562
     * @access protected
563
     */
564
    protected $sign_key_file = '';
565
566
    /**
567
     * The optional S/MIME extra certificates ("CA Chain") file path.
568
     * @var string
569
     * @access protected
570
     */
571
    protected $sign_extracerts_file = '';
572
573
    /**
574
     * The S/MIME password for the key.
575
     * Used only if the key is encrypted.
576
     * @var string
577
     * @access protected
578
     */
579
    protected $sign_key_pass = '';
580
581
    /**
582
     * Whether to throw exceptions for errors.
583
     * @var boolean
584
     * @access protected
585
     */
586
    protected $exceptions = false;
587
588
    /**
589
     * Unique ID used for message ID and boundaries.
590
     * @var string
591
     * @access protected
592
     */
593
    protected $uniqueid = '';
594
595
    /**
596
     * Error severity: message only, continue processing.
597
     */
598
    const STOP_MESSAGE = 0;
599
600
    /**
601
     * Error severity: message, likely ok to continue processing.
602
     */
603
    const STOP_CONTINUE = 1;
604
605
    /**
606
     * Error severity: message, plus full stop, critical error reached.
607
     */
608
    const STOP_CRITICAL = 2;
609
610
    /**
611
     * SMTP RFC standard line ending.
612
     */
613
    const CRLF = "\r\n";
614
615
    /**
616
     * The maximum line length allowed by RFC 2822 section 2.1.1
617
     * @var integer
618
     */
619
    const MAX_LINE_LENGTH = 998;
620
621
    /**
622
     * Constructor.
623
     * @param boolean $exceptions Should we throw external exceptions?
624
     */
625 6
    public function __construct($exceptions = false)
626
    {
627 6
        $this->exceptions = (boolean)$exceptions;
628 6
    }
629
630
    /**
631
     * Destructor.
632
     */
633 6
    public function __destruct()
634
    {
635
        //Close any open SMTP connection nicely
636 6
        if ($this->Mailer == 'smtp') {
637 6
            $this->smtpClose();
638
        }
639 6
    }
640
641
    /**
642
     * Call mail() in a safe_mode-aware fashion.
643
     * Also, unless sendmail_path points to sendmail (or something that
644
     * claims to be sendmail), don't pass params (not a perfect fix,
645
     * but it will do)
646
     * @param string $to To
647
     * @param string $subject Subject
648
     * @param string $body Message Body
649
     * @param string $header Additional Header(s)
650
     * @param string $params Params
651
     * @access private
652
     * @return boolean
653
     */
654
    private function mailPassthru($to, $subject, $body, $header, $params)
655
    {
656
        //Check overloading of mail function to avoid double-encoding
657
        if (ini_get('mbstring.func_overload') & 1) {
658
            $subject = $this->secureHeader($subject);
659
        } else {
660
            $subject = $this->encodeHeader($this->secureHeader($subject));
661
        }
662
        if (ini_get('safe_mode') || !($this->UseSendmailOptions)) {
663
            $result = @mail($to, $subject, $body, $header);
664
        } else {
665
            $result = @mail($to, $subject, $body, $header, $params);
666
        }
667
        return $result;
668
    }
669
670
    /**
671
     * Output debugging info via user-defined method.
672
     * Only generates output if SMTP debug output is enabled (@see SMTP::$do_debug).
673
     * @see PHPMailer::$Debugoutput
674
     * @see PHPMailer::$SMTPDebug
675
     * @param string $str
676
     */
677 3
    protected function edebug($str)
678
    {
679 3
        if ($this->SMTPDebug <= 0) {
680 3
            return;
681
        }
682
        //Avoid clash with built-in function names
683
        if (!in_array($this->Debugoutput, array('error_log', 'html', 'echo')) and is_callable($this->Debugoutput)) {
684
            call_user_func($this->Debugoutput, $str, $this->SMTPDebug);
685
            return;
686
        }
687
        switch ($this->Debugoutput) {
688
            case 'error_log':
689
                //Don't output, just log
690
                error_log($str);
691
                break;
692
            case 'html':
693
                //Cleans up output a bit for a better looking, HTML-safe output
694
                echo htmlentities(
695
                    preg_replace('/[\r\n]+/', '', $str),
696
                    ENT_QUOTES,
697
                    'UTF-8'
698
                )
699
                . "<br>\n";
700
                break;
701
            case 'echo':
702
            default:
703
                //Normalize line breaks
704
                $str = preg_replace('/(\r\n|\r|\n)/ms', "\n", $str);
705
                echo gmdate('Y-m-d H:i:s') . "\t" . str_replace(
706
                    "\n",
707
                    "\n                   \t                  ",
708
                    trim($str)
709
                ) . "\n";
710
        }
711
    }
712
713
    /**
714
     * Sets message type to HTML or plain.
715
     * @param boolean $isHtml True for HTML mode.
716
     * @return void
717
     */
718 6
    public function isHTML($isHtml = true)
719
    {
720 6
        if ($isHtml) {
721 2
            $this->ContentType = 'text/html';
722
        } else {
723 6
            $this->ContentType = 'text/plain';
724
        }
725 6
    }
726
727
    /**
728
     * Send messages using SMTP.
729
     * @return void
730
     */
731
    public function isSMTP()
732
    {
733
        $this->Mailer = 'smtp';
734
    }
735
736
    /**
737
     * Send messages using PHP's mail() function.
738
     * @return void
739
     */
740
    public function isMail()
741
    {
742
        $this->Mailer = 'mail';
743
    }
744
745
    /**
746
     * Send messages using $Sendmail.
747
     * @return void
748
     */
749
    public function isSendmail()
750
    {
751
        $ini_sendmail_path = ini_get('sendmail_path');
752
753
        if (!stristr($ini_sendmail_path, 'sendmail')) {
754
            $this->Sendmail = '/usr/sbin/sendmail';
755
        } else {
756
            $this->Sendmail = $ini_sendmail_path;
757
        }
758
        $this->Mailer = 'sendmail';
759
    }
760
761
    /**
762
     * Send messages using qmail.
763
     * @return void
764
     */
765
    public function isQmail()
766
    {
767
        $ini_sendmail_path = ini_get('sendmail_path');
768
769
        if (!stristr($ini_sendmail_path, 'qmail')) {
770
            $this->Sendmail = '/var/qmail/bin/qmail-inject';
771
        } else {
772
            $this->Sendmail = $ini_sendmail_path;
773
        }
774
        $this->Mailer = 'qmail';
775
    }
776
777
    /**
778
     * Add a "To" address.
779
     * @param string $address
780
     * @param string $name
781
     * @return boolean true on success, false if address already used
782
     */
783 3
    public function addAddress($address, $name = '')
784
    {
785 3
        return $this->addAnAddress('to', $address, $name);
786
    }
787
788
    /**
789
     * Add a "CC" address.
790
     * @note: This function works with the SMTP mailer on win32, not with the "mail" mailer.
791
     * @param string $address
792
     * @param string $name
793
     * @return boolean true on success, false if address already used
794
     */
795
    public function addCC($address, $name = '')
796
    {
797
        return $this->addAnAddress('cc', $address, $name);
798
    }
799
800
    /**
801
     * Add a "BCC" address.
802
     * @note: This function works with the SMTP mailer on win32, not with the "mail" mailer.
803
     * @param string $address
804
     * @param string $name
805
     * @return boolean true on success, false if address already used
806
     */
807
    public function addBCC($address, $name = '')
808
    {
809
        return $this->addAnAddress('bcc', $address, $name);
810
    }
811
812
    /**
813
     * Add a "Reply-to" address.
814
     * @param string $address
815
     * @param string $name
816
     * @return boolean
817
     */
818 1
    public function addReplyTo($address, $name = '')
819
    {
820 1
        return $this->addAnAddress('Reply-To', $address, $name);
821
    }
822
823
    /**
824
     * Add an address to one of the recipient arrays.
825
     * Addresses that have been added already return false, but do not throw exceptions
826
     * @param string $kind One of 'to', 'cc', 'bcc', 'ReplyTo'
827
     * @param string $address The email address to send to
828
     * @param string $name
829
     * @throws phpmailerException
830
     * @return boolean true on success, false if address already used or invalid in some way
831
     * @access protected
832
     */
833 3
    protected function addAnAddress($kind, $address, $name = '')
834
    {
835 3
        if (!preg_match('/^(to|cc|bcc|Reply-To)$/', $kind)) {
836
            $this->setError($this->lang('Invalid recipient array') . ': ' . $kind);
837
            $this->edebug($this->lang('Invalid recipient array') . ': ' . $kind);
838
            if ($this->exceptions) {
839
                throw new phpmailerException('Invalid recipient array: ' . $kind);
840
            }
841
            return false;
842
        }
843 3
        $address = trim($address);
844 3
        $name = trim(preg_replace('/[\r\n]+/', '', $name)); //Strip breaks and trim
845 3
        if (!$this->validateAddress($address)) {
846 3
            $this->setError($this->lang('invalid_address') . ': ' . $address);
847 3
            $this->edebug($this->lang('invalid_address') . ': ' . $address);
848 3
            if ($this->exceptions) {
849
                throw new phpmailerException($this->lang('invalid_address') . ': ' . $address);
850
            }
851 3
            return false;
852
        }
853
        if ($kind != 'Reply-To') {
854
            if (!isset($this->all_recipients[strtolower($address)])) {
855
                array_push($this->$kind, array($address, $name));
856
                $this->all_recipients[strtolower($address)] = true;
857
                return true;
858
            }
859
        } else {
860
            if (!array_key_exists(strtolower($address), $this->ReplyTo)) {
861
                $this->ReplyTo[strtolower($address)] = array($address, $name);
862
                return true;
863
            }
864
        }
865
        return false;
866
    }
867
868
    /**
869
     * Parse and validate a string containing one or more RFC822-style comma-separated email addresses
870
     * of the form "display name <address>" into an array of name/address pairs.
871
     * Uses the imap_rfc822_parse_adrlist function if the IMAP extension is available.
872
     * Note that quotes in the name part are removed.
873
     * @param string $addrstr The address list string
874
     * @param bool $useimap Whether to use the IMAP extension to parse the list
875
     * @return array
876
     * @link http://www.andrew.cmu.edu/user/agreen1/testing/mrbs/web/Mail/RFC822.php A more careful implementation
877
     */
878
    public function parseAddresses($addrstr, $useimap = true)
879
    {
880
        $addresses = array();
881
        if ($useimap and function_exists('imap_rfc822_parse_adrlist')) {
882
            //Use this built-in parser if it's available
883
            $list = imap_rfc822_parse_adrlist($addrstr, '');
884
            foreach ($list as $address) {
885
                if ($address->host != '.SYNTAX-ERROR.') {
886
                    if ($this->validateAddress($address->mailbox . '@' . $address->host)) {
887
                        $addresses[] = array(
888
                            'name' => (property_exists($address, 'personal') ? $address->personal : ''),
889
                            'address' => $address->mailbox . '@' . $address->host
890
                        );
891
                    }
892
                }
893
            }
894
        } else {
895
            //Use this simpler parser
896
            $list = explode(',', $addrstr);
897
            foreach ($list as $address) {
898
                $address = trim($address);
899
                //Is there a separate name part?
900
                if (strpos($address, '<') === false) {
901
                    //No separate name, just use the whole thing
902
                    if ($this->validateAddress($address)) {
903
                        $addresses[] = array(
904
                            'name' => '',
905
                            'address' => $address
906
                        );
907
                    }
908
                } else {
909
                    list($name, $email) = explode('<', $address);
910
                    $email = trim(str_replace('>', '', $email));
911
                    if ($this->validateAddress($email)) {
912
                        $addresses[] = array(
913
                            'name' => trim(str_replace(array('"', "'"), '', $name)),
914
                            'address' => $email
915
                        );
916
                    }
917
                }
918
            }
919
        }
920
        return $addresses;
921
    }
922
923
    /**
924
     * Set the From and FromName properties.
925
     * @param string $address
926
     * @param string $name
927
     * @param boolean $auto Whether to also set the Sender address, defaults to true
928
     * @throws phpmailerException
929
     * @return boolean
930
     */
931
    public function setFrom($address, $name = '', $auto = true)
932
    {
933
        $address = trim($address);
934
        $name = trim(preg_replace('/[\r\n]+/', '', $name)); //Strip breaks and trim
935
        if (!$this->validateAddress($address)) {
936
            $this->setError($this->lang('invalid_address') . ': ' . $address);
937
            $this->edebug($this->lang('invalid_address') . ': ' . $address);
938
            if ($this->exceptions) {
939
                throw new phpmailerException($this->lang('invalid_address') . ': ' . $address);
940
            }
941
            return false;
942
        }
943
        $this->From = $address;
944
        $this->FromName = $name;
945
        if ($auto) {
946
            if (empty($this->Sender)) {
947
                $this->Sender = $address;
948
            }
949
        }
950
        return true;
951
    }
952
953
    /**
954
     * Return the Message-ID header of the last email.
955
     * Technically this is the value from the last time the headers were created,
956
     * but it's also the message ID of the last sent message except in
957
     * pathological cases.
958
     * @return string
959
     */
960
    public function getLastMessageID()
961
    {
962
        return $this->lastMessageID;
963
    }
964
965
    /**
966
     * Check that a string looks like an email address.
967
     * @param string $address The email address to check
968
     * @param string $patternselect A selector for the validation pattern to use :
969
     * * `auto` Pick strictest one automatically;
970
     * * `pcre8` Use the squiloople.com pattern, requires PCRE > 8.0, PHP >= 5.3.2, 5.2.14;
971
     * * `pcre` Use old PCRE implementation;
972
     * * `php` Use PHP built-in FILTER_VALIDATE_EMAIL; same as pcre8 but does not allow 'dotless' domains;
973
     * * `html5` Use the pattern given by the HTML5 spec for 'email' type form input elements.
974
     * * `noregex` Don't use a regex: super fast, really dumb.
975
     * @return boolean
976
     * @static
977
     * @access public
978
     */
979 3
    public static function validateAddress($address, $patternselect = 'auto')
980
    {
981 3
        if (!$patternselect or $patternselect == 'auto') {
982
            //Check this constant first so it works when extension_loaded() is disabled by safe mode
983
            //Constant was added in PHP 5.2.4
984 3
            if (defined('PCRE_VERSION')) {
985
                //This pattern can get stuck in a recursive loop in PCRE <= 8.0.2
986 3
                if (version_compare(PCRE_VERSION, '8.0.3') >= 0) {
987 3
                    $patternselect = 'pcre8';
988
                } else {
989 3
                    $patternselect = 'pcre';
990
                }
991
            } elseif (function_exists('extension_loaded') and extension_loaded('pcre')) {
992
                //Fall back to older PCRE
993
                $patternselect = 'pcre';
994
            } else {
995
                //Filter_var appeared in PHP 5.2.0 and does not require the PCRE extension
996
                if (version_compare(PHP_VERSION, '5.2.0') >= 0) {
997
                    $patternselect = 'php';
998
                } else {
999
                    $patternselect = 'noregex';
1000
                }
1001
            }
1002
        }
1003
        switch ($patternselect) {
1004 3
            case 'pcre8':
1005
                /**
1006
                 * Uses the same RFC5322 regex on which FILTER_VALIDATE_EMAIL is based, but allows dotless domains.
1007
                 * @link http://squiloople.com/2009/12/20/email-address-validation/
1008
                 * @copyright 2009-2010 Michael Rushton
1009
                 * Feel free to use and redistribute this code. But please keep this copyright notice.
1010
                 */
1011 3
                return (boolean)preg_match(
1012
                    '/^(?!(?>(?1)"?(?>\\\[ -~]|[^"])"?(?1)){255,})(?!(?>(?1)"?(?>\\\[ -~]|[^"])"?(?1)){65,}@)' .
1013
                    '((?>(?>(?>((?>(?>(?>\x0D\x0A)?[\t ])+|(?>[\t ]*\x0D\x0A)?[\t ]+)?)(\((?>(?2)' .
1014
                    '(?>[\x01-\x08\x0B\x0C\x0E-\'*-\[\]-\x7F]|\\\[\x00-\x7F]|(?3)))*(?2)\)))+(?2))|(?2))?)' .
1015
                    '([!#-\'*+\/-9=?^-~-]+|"(?>(?2)(?>[\x01-\x08\x0B\x0C\x0E-!#-\[\]-\x7F]|\\\[\x00-\x7F]))*' .
1016
                    '(?2)")(?>(?1)\.(?1)(?4))*(?1)@(?!(?1)[a-z0-9-]{64,})(?1)(?>([a-z0-9](?>[a-z0-9-]*[a-z0-9])?)' .
1017
                    '(?>(?1)\.(?!(?1)[a-z0-9-]{64,})(?1)(?5)){0,126}|\[(?:(?>IPv6:(?>([a-f0-9]{1,4})(?>:(?6)){7}' .
1018
                    '|(?!(?:.*[a-f0-9][:\]]){8,})((?6)(?>:(?6)){0,6})?::(?7)?))|(?>(?>IPv6:(?>(?6)(?>:(?6)){5}:' .
1019
                    '|(?!(?:.*[a-f0-9]:){6,})(?8)?::(?>((?6)(?>:(?6)){0,4}):)?))?(25[0-5]|2[0-4][0-9]|1[0-9]{2}' .
1020 3
                    '|[1-9]?[0-9])(?>\.(?9)){3}))\])(?1)$/isD',
1021
                    $address
1022
                );
1023
            case 'pcre':
1024
                //An older regex that doesn't need a recent PCRE
1025
                return (boolean)preg_match(
1026
                    '/^(?!(?>"?(?>\\\[ -~]|[^"])"?){255,})(?!(?>"?(?>\\\[ -~]|[^"])"?){65,}@)(?>' .
1027
                    '[!#-\'*+\/-9=?^-~-]+|"(?>(?>[\x01-\x08\x0B\x0C\x0E-!#-\[\]-\x7F]|\\\[\x00-\xFF]))*")' .
1028
                    '(?>\.(?>[!#-\'*+\/-9=?^-~-]+|"(?>(?>[\x01-\x08\x0B\x0C\x0E-!#-\[\]-\x7F]|\\\[\x00-\xFF]))*"))*' .
1029
                    '@(?>(?![a-z0-9-]{64,})(?>[a-z0-9](?>[a-z0-9-]*[a-z0-9])?)(?>\.(?![a-z0-9-]{64,})' .
1030
                    '(?>[a-z0-9](?>[a-z0-9-]*[a-z0-9])?)){0,126}|\[(?:(?>IPv6:(?>(?>[a-f0-9]{1,4})(?>:' .
1031
                    '[a-f0-9]{1,4}){7}|(?!(?:.*[a-f0-9][:\]]){8,})(?>[a-f0-9]{1,4}(?>:[a-f0-9]{1,4}){0,6})?' .
1032
                    '::(?>[a-f0-9]{1,4}(?>:[a-f0-9]{1,4}){0,6})?))|(?>(?>IPv6:(?>[a-f0-9]{1,4}(?>:' .
1033
                    '[a-f0-9]{1,4}){5}:|(?!(?:.*[a-f0-9]:){6,})(?>[a-f0-9]{1,4}(?>:[a-f0-9]{1,4}){0,4})?' .
1034
                    '::(?>(?:[a-f0-9]{1,4}(?>:[a-f0-9]{1,4}){0,4}):)?))?(?>25[0-5]|2[0-4][0-9]|1[0-9]{2}' .
1035
                    '|[1-9]?[0-9])(?>\.(?>25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3}))\])$/isD',
1036
                    $address
1037
                );
1038
            case 'html5':
1039
                /**
1040
                 * This is the pattern used in the HTML5 spec for validation of 'email' type form input elements.
1041
                 * @link http://www.whatwg.org/specs/web-apps/current-work/#e-mail-state-(type=email)
1042
                 */
1043
                return (boolean)preg_match(
1044
                    '/^[a-zA-Z0-9.!#$%&\'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}' .
1045
                    '[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/sD',
1046
                    $address
1047
                );
1048
            case 'noregex':
1049
                //No PCRE! Do something _very_ approximate!
1050
                //Check the address is 3 chars or longer and contains an @ that's not the first or last char
1051
                return (strlen($address) >= 3
1052
                    and strpos($address, '@') >= 1
1053
                    and strpos($address, '@') != strlen($address) - 1);
1054
            case 'php':
1055
            default:
1056
                return (boolean)filter_var($address, FILTER_VALIDATE_EMAIL);
1057
        }
1058
    }
1059
1060
    /**
1061
     * Create a message and send it.
1062
     * Uses the sending method specified by $Mailer.
1063
     * @throws phpmailerException
1064
     * @return boolean false on error - See the ErrorInfo property for details of the error.
1065
     */
1066 1
    public function send()
1067
    {
1068
        try {
1069 1
            if (!$this->preSend()) {
1070 1
                return false;
1071
            }
1072
            return $this->postSend();
1073
        } catch (phpmailerException $exc) {
1074
            $this->mailHeader = '';
1075
            $this->setError($exc->getMessage());
1076
            if ($this->exceptions) {
1077
                throw $exc;
1078
            }
1079
            return false;
1080
        }
1081
    }
1082
1083
    /**
1084
     * Prepare a message for sending.
1085
     * @throws phpmailerException
1086
     * @return boolean
1087
     */
1088 1
    public function preSend()
1089
    {
1090
        try {
1091 1
            $this->mailHeader = '';
1092 1
            if ((count($this->to) + count($this->cc) + count($this->bcc)) < 1) {
1093 1
                throw new phpmailerException($this->lang('provide_address'), self::STOP_CRITICAL);
1094
            }
1095
1096
            // Set whether the message is multipart/alternative
1097
            if (!empty($this->AltBody)) {
1098
                $this->ContentType = 'multipart/alternative';
1099
            }
1100
1101
            $this->error_count = 0; // Reset errors
1102
            $this->setMessageType();
1103
            // Refuse to send an empty message unless we are specifically allowing it
1104
            if (!$this->AllowEmpty and empty($this->Body)) {
1105
                throw new phpmailerException($this->lang('empty_message'), self::STOP_CRITICAL);
1106
            }
1107
1108
            // Create body before headers in case body makes changes to headers (e.g. altering transfer encoding)
1109
            $this->MIMEHeader = '';
1110
            $this->MIMEBody = $this->createBody();
1111
            // createBody may have added some headers, so retain them
1112
            $tempheaders = $this->MIMEHeader;
1113
            $this->MIMEHeader = $this->createHeader();
1114
            $this->MIMEHeader .= $tempheaders;
1115
1116
            // To capture the complete message when using mail(), create
1117
            // an extra header list which createHeader() doesn't fold in
1118
            if ($this->Mailer == 'mail') {
1119
                if (count($this->to) > 0) {
1120
                    $this->mailHeader .= $this->addrAppend('To', $this->to);
1121
                } else {
1122
                    $this->mailHeader .= $this->headerLine('To', 'undisclosed-recipients:;');
1123
                }
1124
                $this->mailHeader .= $this->headerLine(
1125
                    'Subject',
1126
                    $this->encodeHeader($this->secureHeader(trim($this->Subject)))
1127
                );
1128
            }
1129
1130
            // Sign with DKIM if enabled
1131
            if (!empty($this->DKIM_domain)
1132
                && !empty($this->DKIM_private)
1133
                && !empty($this->DKIM_selector)
1134
                && file_exists($this->DKIM_private)) {
1135
                $header_dkim = $this->DKIM_Add(
1136
                    $this->MIMEHeader . $this->mailHeader,
1137
                    $this->encodeHeader($this->secureHeader($this->Subject)),
1138
                    $this->MIMEBody
1139
                );
1140
                $this->MIMEHeader = rtrim($this->MIMEHeader, "\r\n ") . self::CRLF .
1141
                    str_replace("\r\n", "\n", $header_dkim) . self::CRLF;
1142
            }
1143
            return true;
1144 1
        } catch (phpmailerException $exc) {
1145 1
            $this->setError($exc->getMessage());
1146 1
            if ($this->exceptions) {
1147
                throw $exc;
1148
            }
1149 1
            return false;
1150
        }
1151
    }
1152
1153
    /**
1154
     * Actually send a message.
1155
     * Send the email via the selected mechanism
1156
     * @throws phpmailerException
1157
     * @return boolean
1158
     */
1159
    public function postSend()
1160
    {
1161
        try {
1162
            // Choose the mailer and send through it
1163
            switch ($this->Mailer) {
1164
                case 'sendmail':
1165
                case 'qmail':
1166
                    return $this->sendmailSend($this->MIMEHeader, $this->MIMEBody);
1167
                case 'smtp':
1168
                    return $this->smtpSend($this->MIMEHeader, $this->MIMEBody);
1169
                case 'mail':
1170
                    return $this->mailSend($this->MIMEHeader, $this->MIMEBody);
1171
                default:
1172
                    $sendMethod = $this->Mailer.'Send';
1173
                    if (method_exists($this, $sendMethod)) {
1174
                        return $this->$sendMethod($this->MIMEHeader, $this->MIMEBody);
1175
                    }
1176
1177
                    return $this->mailSend($this->MIMEHeader, $this->MIMEBody);
1178
            }
1179
        } catch (phpmailerException $exc) {
1180
            $this->setError($exc->getMessage());
1181
            $this->edebug($exc->getMessage());
1182
            if ($this->exceptions) {
1183
                throw $exc;
1184
            }
1185
        }
1186
        return false;
1187
    }
1188
1189
    /**
1190
     * Send mail using the $Sendmail program.
1191
     * @param string $header The message headers
1192
     * @param string $body The message body
1193
     * @see PHPMailer::$Sendmail
1194
     * @throws phpmailerException
1195
     * @access protected
1196
     * @return boolean
1197
     */
1198
    protected function sendmailSend($header, $body)
1199
    {
1200
        if ($this->Sender != '') {
1201
            if ($this->Mailer == 'qmail') {
1202
                $sendmail = sprintf('%s -f%s', escapeshellcmd($this->Sendmail), escapeshellarg($this->Sender));
1203
            } else {
1204
                $sendmail = sprintf('%s -oi -f%s -t', escapeshellcmd($this->Sendmail), escapeshellarg($this->Sender));
1205
            }
1206
        } else {
1207
            if ($this->Mailer == 'qmail') {
1208
                $sendmail = sprintf('%s', escapeshellcmd($this->Sendmail));
1209
            } else {
1210
                $sendmail = sprintf('%s -oi -t', escapeshellcmd($this->Sendmail));
1211
            }
1212
        }
1213
        if ($this->SingleTo) {
1214
            foreach ($this->SingleToArray as $toAddr) {
1215
                if (!@$mail = popen($sendmail, 'w')) {
1216
                    throw new phpmailerException($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL);
1217
                }
1218
                fputs($mail, 'To: ' . $toAddr . "\n");
1219
                fputs($mail, $header);
1220
                fputs($mail, $body);
1221
                $result = pclose($mail);
1222
                $this->doCallback(
1223
                    ($result == 0),
1224
                    array($toAddr),
1225
                    $this->cc,
1226
                    $this->bcc,
1227
                    $this->Subject,
1228
                    $body,
1229
                    $this->From
1230
                );
1231
                if ($result != 0) {
1232
                    throw new phpmailerException($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL);
1233
                }
1234
            }
1235
        } else {
1236
            if (!@$mail = popen($sendmail, 'w')) {
1237
                throw new phpmailerException($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL);
1238
            }
1239
            fputs($mail, $header);
1240
            fputs($mail, $body);
1241
            $result = pclose($mail);
1242
            $this->doCallback(($result == 0), $this->to, $this->cc, $this->bcc, $this->Subject, $body, $this->From);
1243
            if ($result != 0) {
1244
                throw new phpmailerException($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL);
1245
            }
1246
        }
1247
        return true;
1248
    }
1249
1250
    /**
1251
     * Send mail using the PHP mail() function.
1252
     * @param string $header The message headers
1253
     * @param string $body The message body
1254
     * @link http://www.php.net/manual/en/book.mail.php
1255
     * @throws phpmailerException
1256
     * @access protected
1257
     * @return boolean
1258
     */
1259
    protected function mailSend($header, $body)
1260
    {
1261
        $toArr = array();
1262
        foreach ($this->to as $toaddr) {
1263
            $toArr[] = $this->addrFormat($toaddr);
1264
        }
1265
        $to = implode(', ', $toArr);
1266
1267
        if (empty($this->Sender)) {
1268
            $params = ' ';
1269
        } else {
1270
            $params = sprintf('-f%s', $this->Sender);
1271
        }
1272
        if ($this->Sender != '' and !ini_get('safe_mode')) {
1273
            $old_from = ini_get('sendmail_from');
1274
            ini_set('sendmail_from', $this->Sender);
1275
        }
1276
        $result = false;
1277
        if ($this->SingleTo && count($toArr) > 1) {
1278
            foreach ($toArr as $toAddr) {
1279
                $result = $this->mailPassthru($toAddr, $this->Subject, $body, $header, $params);
1280
                $this->doCallback($result, array($toAddr), $this->cc, $this->bcc, $this->Subject, $body, $this->From);
1281
            }
1282
        } else {
1283
            $result = $this->mailPassthru($to, $this->Subject, $body, $header, $params);
1284
            $this->doCallback($result, $this->to, $this->cc, $this->bcc, $this->Subject, $body, $this->From);
1285
        }
1286
        if (isset($old_from)) {
1287
            ini_set('sendmail_from', $old_from);
1288
        }
1289
        if (!$result) {
1290
            throw new phpmailerException($this->lang('instantiate'), self::STOP_CRITICAL);
1291
        }
1292
        return true;
1293
    }
1294
1295
    /**
1296
     * Get an instance to use for SMTP operations.
1297
     * Override this function to load your own SMTP implementation
1298
     * @return SMTP
1299
     */
1300
    public function getSMTPInstance()
1301
    {
1302
        if (!is_object($this->smtp)) {
1303
            $this->smtp = new SMTP;
1304
        }
1305
        return $this->smtp;
1306
    }
1307
1308
    /**
1309
     * Send mail via SMTP.
1310
     * Returns false if there is a bad MAIL FROM, RCPT, or DATA input.
1311
     * Uses the PHPMailerSMTP class by default.
1312
     * @see PHPMailer::getSMTPInstance() to use a different class.
1313
     * @param string $header The message headers
1314
     * @param string $body The message body
1315
     * @throws phpmailerException
1316
     * @uses SMTP
1317
     * @access protected
1318
     * @return boolean
1319
     */
1320
    protected function smtpSend($header, $body)
1321
    {
1322
        $bad_rcpt = array();
1323
        if (!$this->smtpConnect($this->SMTPOptions)) {
1324
            throw new phpmailerException($this->lang('smtp_connect_failed'), self::STOP_CRITICAL);
1325
        }
1326
        if ('' == $this->Sender) {
1327
            $smtp_from = $this->From;
1328
        } else {
1329
            $smtp_from = $this->Sender;
1330
        }
1331
        if (!$this->smtp->mail($smtp_from)) {
1332
            $this->setError($this->lang('from_failed') . $smtp_from . ' : ' . implode(',', $this->smtp->getError()));
1333
            throw new phpmailerException($this->ErrorInfo, self::STOP_CRITICAL);
1334
        }
1335
1336
        // Attempt to send to all recipients
1337
        foreach (array($this->to, $this->cc, $this->bcc) as $togroup) {
1338
            foreach ($togroup as $to) {
1339
                if (!$this->smtp->recipient($to[0])) {
1340
                    $error = $this->smtp->getError();
1341
                    $bad_rcpt[] = array('to' => $to[0], 'error' => $error['detail']);
1342
                    $isSent = false;
1343
                } else {
1344
                    $isSent = true;
1345
                }
1346
                $this->doCallback($isSent, array($to[0]), array(), array(), $this->Subject, $body, $this->From);
1347
            }
1348
        }
1349
1350
        // Only send the DATA command if we have viable recipients
1351
        if ((count($this->all_recipients) > count($bad_rcpt)) and !$this->smtp->data($header . $body)) {
1352
            throw new phpmailerException($this->lang('data_not_accepted'), self::STOP_CRITICAL);
1353
        }
1354
        if ($this->SMTPKeepAlive) {
1355
            $this->smtp->reset();
1356
        } else {
1357
            $this->smtp->quit();
1358
            $this->smtp->close();
1359
        }
1360
        //Create error message for any bad addresses
1361
        if (count($bad_rcpt) > 0) {
1362
            $errstr = '';
1363
            foreach ($bad_rcpt as $bad) {
1364
                $errstr .= $bad['to'] . ': ' . $bad['error'];
1365
            }
1366
            throw new phpmailerException(
1367
                $this->lang('recipients_failed') . $errstr,
1368
                self::STOP_CONTINUE
1369
            );
1370
        }
1371
        return true;
1372
    }
1373
1374
    /**
1375
     * Initiate a connection to an SMTP server.
1376
     * Returns false if the operation failed.
1377
     * @param array $options An array of options compatible with stream_context_create()
1378
     * @uses SMTP
1379
     * @access public
1380
     * @throws phpmailerException
1381
     * @return boolean
1382
     */
1383
    public function smtpConnect($options = array())
1384
    {
1385
        if (is_null($this->smtp)) {
1386
            $this->smtp = $this->getSMTPInstance();
1387
        }
1388
1389
        // Already connected?
1390
        if ($this->smtp->connected()) {
1391
            return true;
1392
        }
1393
1394
        $this->smtp->setTimeout($this->Timeout);
1395
        $this->smtp->setDebugLevel($this->SMTPDebug);
1396
        $this->smtp->setDebugOutput($this->Debugoutput);
1397
        $this->smtp->setVerp($this->do_verp);
1398
        $hosts = explode(';', $this->Host);
1399
        $lastexception = null;
1400
1401
        foreach ($hosts as $hostentry) {
1402
            $hostinfo = array();
1403
            if (!preg_match('/^((ssl|tls):\/\/)*([a-zA-Z0-9\.-]*):?([0-9]*)$/', trim($hostentry), $hostinfo)) {
1404
                // Not a valid host entry
1405
                continue;
1406
            }
1407
            // $hostinfo[2]: optional ssl or tls prefix
1408
            // $hostinfo[3]: the hostname
1409
            // $hostinfo[4]: optional port number
1410
            // The host string prefix can temporarily override the current setting for SMTPSecure
1411
            // If it's not specified, the default value is used
1412
            $prefix = '';
1413
            $secure = $this->SMTPSecure;
1414
            $tls = ($this->SMTPSecure == 'tls');
1415
            if ('ssl' == $hostinfo[2] or ('' == $hostinfo[2] and 'ssl' == $this->SMTPSecure)) {
1416
                $prefix = 'ssl://';
1417
                $tls = false; // Can't have SSL and TLS at the same time
1418
                $secure = 'ssl';
1419
            } elseif ($hostinfo[2] == 'tls') {
1420
                $tls = true;
1421
                // tls doesn't use a prefix
1422
                $secure = 'tls';
1423
            }
1424
            //Do we need the OpenSSL extension?
1425
            $sslext = defined('OPENSSL_ALGO_SHA1');
1426
            if ('tls' === $secure or 'ssl' === $secure) {
1427
                //Check for an OpenSSL constant rather than using extension_loaded, which is sometimes disabled
1428
                if (!$sslext) {
1429
                    throw new phpmailerException($this->lang('extension_missing').'openssl', self::STOP_CRITICAL);
1430
                }
1431
            }
1432
            $host = $hostinfo[3];
1433
            $port = $this->Port;
1434
            $tport = (integer)$hostinfo[4];
1435
            if ($tport > 0 and $tport < 65536) {
1436
                $port = $tport;
1437
            }
1438
            if ($this->smtp->connect($prefix . $host, $port, $this->Timeout, $options)) {
1439
                try {
1440
                    if ($this->Helo) {
1441
                        $hello = $this->Helo;
1442
                    } else {
1443
                        $hello = $this->serverHostname();
1444
                    }
1445
                    $this->smtp->hello($hello);
1446
                    //Automatically enable TLS encryption if:
1447
                    // * it's not disabled
1448
                    // * we have openssl extension
1449
                    // * we are not already using SSL
1450
                    // * the server offers STARTTLS
1451
                    if ($this->SMTPAutoTLS and $sslext and $secure != 'ssl' and $this->smtp->getServerExt('STARTTLS')) {
1452
                        $tls = true;
1453
                    }
1454
                    if ($tls) {
1455
                        if (!$this->smtp->startTLS()) {
1456
                            throw new phpmailerException($this->lang('connect_host'));
1457
                        }
1458
                        // We must resend HELO after tls negotiation
1459
                        $this->smtp->hello($hello);
1460
                    }
1461
                    if ($this->SMTPAuth) {
1462
                        if (!$this->smtp->authenticate(
1463
                            $this->Username,
1464
                            $this->Password,
1465
                            $this->AuthType,
1466
                            $this->Realm,
1467
                            $this->Workstation
1468
                        )
1469
                        ) {
1470
                            throw new phpmailerException($this->lang('authenticate'));
1471
                        }
1472
                    }
1473
                    return true;
1474
                } catch (phpmailerException $exc) {
1475
                    $lastexception = $exc;
1476
                    $this->edebug($exc->getMessage());
1477
                    // We must have connected, but then failed TLS or Auth, so close connection nicely
1478
                    $this->smtp->quit();
1479
                }
1480
            }
1481
        }
1482
        // If we get here, all connection attempts have failed, so close connection hard
1483
        $this->smtp->close();
1484
        // As we've caught all exceptions, just report whatever the last one was
1485
        if ($this->exceptions and !is_null($lastexception)) {
1486
            throw $lastexception;
1487
        }
1488
        return false;
1489
    }
1490
1491
    /**
1492
     * Close the active SMTP session if one exists.
1493
     * @return void
1494
     */
1495 6
    public function smtpClose()
1496
    {
1497 6
        if ($this->smtp !== null) {
1498
            if ($this->smtp->connected()) {
1499
                $this->smtp->quit();
1500
                $this->smtp->close();
1501
            }
1502
        }
1503 6
    }
1504
1505
    /**
1506
     * Set the language for error messages.
1507
     * Returns false if it cannot load the language file.
1508
     * The default language is English.
1509
     * @param string $langcode ISO 639-1 2-character language code (e.g. French is "fr")
1510
     * @param string $lang_path Path to the language file directory, with trailing separator (slash)
1511
     * @return boolean
1512
     * @access public
1513
     */
1514 6
    public function setLanguage($langcode = 'en', $lang_path = '')
1515
    {
1516
        // Define full set of translatable strings in English
1517
        $PHPMAILER_LANG = array(
1518 6
            'authenticate' => 'SMTP Error: Could not authenticate.',
1519
            'connect_host' => 'SMTP Error: Could not connect to SMTP host.',
1520
            'data_not_accepted' => 'SMTP Error: data not accepted.',
1521
            'empty_message' => 'Message body empty',
1522
            'encoding' => 'Unknown encoding: ',
1523
            'execute' => 'Could not execute: ',
1524
            'file_access' => 'Could not access file: ',
1525
            'file_open' => 'File Error: Could not open file: ',
1526
            'from_failed' => 'The following From address failed: ',
1527
            'instantiate' => 'Could not instantiate mail function.',
1528
            'invalid_address' => 'Invalid address',
1529
            'mailer_not_supported' => ' mailer is not supported.',
1530
            'provide_address' => 'You must provide at least one recipient email address.',
1531
            'recipients_failed' => 'SMTP Error: The following recipients failed: ',
1532
            'signing' => 'Signing Error: ',
1533
            'smtp_connect_failed' => 'SMTP connect() failed.',
1534
            'smtp_error' => 'SMTP server error: ',
1535
            'variable_set' => 'Cannot set or reset variable: ',
1536
            'extension_missing' => 'Extension missing: '
1537
        );
1538 6
        if (empty($lang_path)) {
1539
            // Calculate an absolute path so it can work if CWD is not here
1540
            $lang_path = dirname(__FILE__). DIRECTORY_SEPARATOR . 'language'. DIRECTORY_SEPARATOR;
1541
        }
1542 6
        $foundlang = true;
1543 6
        $lang_file = $lang_path . 'phpmailer.lang-' . $langcode . '.php';
1544
        // There is no English translation file
1545 6
        if ($langcode != 'en') {
1546
            // Make sure language file path is readable
1547
            if (!is_readable($lang_file)) {
1548
                $foundlang = false;
1549
            } else {
1550
                // Overwrite language-specific strings.
1551
                // This way we'll never have missing translation keys.
1552
                $foundlang = include $lang_file;
1553
            }
1554
        }
1555 6
        $this->language = $PHPMAILER_LANG;
1556 6
        return (boolean)$foundlang; // Returns false if language not found
1557
    }
1558
1559
    /**
1560
     * Get the array of strings for the current language.
1561
     * @return array
1562
     */
1563
    public function getTranslations()
1564
    {
1565
        return $this->language;
1566
    }
1567
1568
    /**
1569
     * Create recipient headers.
1570
     * @access public
1571
     * @param string $type
1572
     * @param array $addr An array of recipient,
1573
     * where each recipient is a 2-element indexed array with element 0 containing an address
1574
     * and element 1 containing a name, like:
1575
     * array(array('[email protected]', 'Joe User'), array('[email protected]', 'Zoe User'))
1576
     * @return string
1577
     */
1578
    public function addrAppend($type, $addr)
1579
    {
1580
        $addresses = array();
1581
        foreach ($addr as $address) {
1582
            $addresses[] = $this->addrFormat($address);
1583
        }
1584
        return $type . ': ' . implode(', ', $addresses) . $this->LE;
1585
    }
1586
1587
    /**
1588
     * Format an address for use in a message header.
1589
     * @access public
1590
     * @param array $addr A 2-element indexed array, element 0 containing an address, element 1 containing a name
1591
     *      like array('[email protected]', 'Joe User')
1592
     * @return string
1593
     */
1594
    public function addrFormat($addr)
1595
    {
1596
        if (empty($addr[1])) { // No name provided
1597
            return $this->secureHeader($addr[0]);
1598
        } else {
1599
            return $this->encodeHeader($this->secureHeader($addr[1]), 'phrase') . ' <' . $this->secureHeader(
1600
                $addr[0]
1601
            ) . '>';
1602
        }
1603
    }
1604
1605
    /**
1606
     * Word-wrap message.
1607
     * For use with mailers that do not automatically perform wrapping
1608
     * and for quoted-printable encoded messages.
1609
     * Original written by philippe.
1610
     * @param string $message The message to wrap
1611
     * @param integer $length The line length to wrap to
1612
     * @param boolean $qp_mode Whether to run in Quoted-Printable mode
1613
     * @access public
1614
     * @return string
1615
     */
1616
    public function wrapText($message, $length, $qp_mode = false)
1617
    {
1618
        if ($qp_mode) {
1619
            $soft_break = sprintf(' =%s', $this->LE);
1620
        } else {
1621
            $soft_break = $this->LE;
1622
        }
1623
        // If utf-8 encoding is used, we will need to make sure we don't
1624
        // split multibyte characters when we wrap
1625
        $is_utf8 = (strtolower($this->CharSet) == 'utf-8');
1626
        $lelen = strlen($this->LE);
1627
        $crlflen = strlen(self::CRLF);
1628
1629
        $message = $this->fixEOL($message);
1630
        //Remove a trailing line break
1631
        if (substr($message, -$lelen) == $this->LE) {
1632
            $message = substr($message, 0, -$lelen);
1633
        }
1634
1635
        //Split message into lines
1636
        $lines = explode($this->LE, $message);
1637
        //Message will be rebuilt in here
1638
        $message = '';
1639
        foreach ($lines as $line) {
1640
            $words = explode(' ', $line);
1641
            $buf = '';
1642
            $firstword = true;
1643
            foreach ($words as $word) {
1644
                if ($qp_mode and (strlen($word) > $length)) {
1645
                    $space_left = $length - strlen($buf) - $crlflen;
1646
                    if (!$firstword) {
1647
                        if ($space_left > 20) {
1648
                            $len = $space_left;
1649
                            if ($is_utf8) {
1650
                                $len = $this->utf8CharBoundary($word, $len);
1651
                            } elseif (substr($word, $len - 1, 1) == '=') {
1652
                                $len--;
1653
                            } elseif (substr($word, $len - 2, 1) == '=') {
1654
                                $len -= 2;
1655
                            }
1656
                            $part = substr($word, 0, $len);
1657
                            $word = substr($word, $len);
1658
                            $buf .= ' ' . $part;
1659
                            $message .= $buf . sprintf('=%s', self::CRLF);
1660
                        } else {
1661
                            $message .= $buf . $soft_break;
1662
                        }
1663
                        $buf = '';
1664
                    }
1665
                    while (strlen($word) > 0) {
1666
                        if ($length <= 0) {
1667
                            break;
1668
                        }
1669
                        $len = $length;
1670
                        if ($is_utf8) {
1671
                            $len = $this->utf8CharBoundary($word, $len);
1672
                        } elseif (substr($word, $len - 1, 1) == '=') {
1673
                            $len--;
1674
                        } elseif (substr($word, $len - 2, 1) == '=') {
1675
                            $len -= 2;
1676
                        }
1677
                        $part = substr($word, 0, $len);
1678
                        $word = substr($word, $len);
1679
1680
                        if (strlen($word) > 0) {
1681
                            $message .= $part . sprintf('=%s', self::CRLF);
1682
                        } else {
1683
                            $buf = $part;
1684
                        }
1685
                    }
1686
                } else {
1687
                    $buf_o = $buf;
1688
                    if (!$firstword) {
1689
                        $buf .= ' ';
1690
                    }
1691
                    $buf .= $word;
1692
1693
                    if (strlen($buf) > $length and $buf_o != '') {
1694
                        $message .= $buf_o . $soft_break;
1695
                        $buf = $word;
1696
                    }
1697
                }
1698
                $firstword = false;
1699
            }
1700
            $message .= $buf . self::CRLF;
1701
        }
1702
1703
        return $message;
1704
    }
1705
1706
    /**
1707
     * Find the last character boundary prior to $maxLength in a utf-8
1708
     * quoted-printable encoded string.
1709
     * Original written by Colin Brown.
1710
     * @access public
1711
     * @param string $encodedText utf-8 QP text
1712
     * @param integer $maxLength Find the last character boundary prior to this length
1713
     * @return integer
1714
     */
1715
    public function utf8CharBoundary($encodedText, $maxLength)
1716
    {
1717
        $foundSplitPos = false;
1718
        $lookBack = 3;
1719
        while (!$foundSplitPos) {
1720
            $lastChunk = substr($encodedText, $maxLength - $lookBack, $lookBack);
1721
            $encodedCharPos = strpos($lastChunk, '=');
1722
            if (false !== $encodedCharPos) {
1723
                // Found start of encoded character byte within $lookBack block.
1724
                // Check the encoded byte value (the 2 chars after the '=')
1725
                $hex = substr($encodedText, $maxLength - $lookBack + $encodedCharPos + 1, 2);
1726
                $dec = hexdec($hex);
1727
                if ($dec < 128) {
1728
                    // Single byte character.
1729
                    // If the encoded char was found at pos 0, it will fit
1730
                    // otherwise reduce maxLength to start of the encoded char
1731
                    if ($encodedCharPos > 0) {
1732
                        $maxLength = $maxLength - ($lookBack - $encodedCharPos);
1733
                    }
1734
                    $foundSplitPos = true;
1735
                } elseif ($dec >= 192) {
1736
                    // First byte of a multi byte character
1737
                    // Reduce maxLength to split at start of character
1738
                    $maxLength = $maxLength - ($lookBack - $encodedCharPos);
1739
                    $foundSplitPos = true;
1740
                } elseif ($dec < 192) {
1741
                    // Middle byte of a multi byte character, look further back
1742
                    $lookBack += 3;
1743
                }
1744
            } else {
1745
                // No encoded character found
1746
                $foundSplitPos = true;
1747
            }
1748
        }
1749
        return $maxLength;
1750
    }
1751
1752
    /**
1753
     * Apply word wrapping to the message body.
1754
     * Wraps the message body to the number of chars set in the WordWrap property.
1755
     * You should only do this to plain-text bodies as wrapping HTML tags may break them.
1756
     * This is called automatically by createBody(), so you don't need to call it yourself.
1757
     * @access public
1758
     * @return void
1759
     */
1760
    public function setWordWrap()
1761
    {
1762
        if ($this->WordWrap < 1) {
1763
            return;
1764
        }
1765
1766
        switch ($this->message_type) {
1767
            case 'alt':
1768
            case 'alt_inline':
1769
            case 'alt_attach':
1770
            case 'alt_inline_attach':
1771
                $this->AltBody = $this->wrapText($this->AltBody, $this->WordWrap);
1772
                break;
1773
            default:
1774
                $this->Body = $this->wrapText($this->Body, $this->WordWrap);
1775
                break;
1776
        }
1777
    }
1778
1779
    /**
1780
     * Assemble message headers.
1781
     * @access public
1782
     * @return string The assembled headers
1783
     */
1784
    public function createHeader()
1785
    {
1786
        $result = '';
1787
1788
        if ($this->MessageDate == '') {
1789
            $this->MessageDate = self::rfcDate();
1790
        }
1791
        $result .= $this->headerLine('Date', $this->MessageDate);
1792
1793
1794
        // To be created automatically by mail()
1795
        if ($this->SingleTo) {
1796
            if ($this->Mailer != 'mail') {
1797
                foreach ($this->to as $toaddr) {
1798
                    $this->SingleToArray[] = $this->addrFormat($toaddr);
1799
                }
1800
            }
1801
        } else {
1802
            if (count($this->to) > 0) {
1803
                if ($this->Mailer != 'mail') {
1804
                    $result .= $this->addrAppend('To', $this->to);
1805
                }
1806
            } elseif (count($this->cc) == 0) {
1807
                $result .= $this->headerLine('To', 'undisclosed-recipients:;');
1808
            }
1809
        }
1810
1811
        $result .= $this->addrAppend('From', array(array(trim($this->From), $this->FromName)));
1812
1813
        // sendmail and mail() extract Cc from the header before sending
1814
        if (count($this->cc) > 0) {
1815
            $result .= $this->addrAppend('Cc', $this->cc);
1816
        }
1817
1818
        // sendmail and mail() extract Bcc from the header before sending
1819
        if ((
1820
                $this->Mailer == 'sendmail' or $this->Mailer == 'qmail' or $this->Mailer == 'mail'
1821
            )
1822
            and count($this->bcc) > 0
1823
        ) {
1824
            $result .= $this->addrAppend('Bcc', $this->bcc);
1825
        }
1826
1827
        if (count($this->ReplyTo) > 0) {
1828
            $result .= $this->addrAppend('Reply-To', $this->ReplyTo);
1829
        }
1830
1831
        // mail() sets the subject itself
1832
        if ($this->Mailer != 'mail') {
1833
            $result .= $this->headerLine('Subject', $this->encodeHeader($this->secureHeader($this->Subject)));
1834
        }
1835
1836
        if ($this->MessageID != '') {
1837
            $this->lastMessageID = $this->MessageID;
1838
        } else {
1839
            $this->lastMessageID = sprintf('<%s@%s>', $this->uniqueid, $this->serverHostname());
1840
        }
1841
        $result .= $this->headerLine('Message-ID', $this->lastMessageID);
1842
        if (!is_null($this->Priority)) {
1843
            $result .= $this->headerLine('X-Priority', $this->Priority);
1844
        }
1845
        if ($this->XMailer == '') {
1846
            $result .= $this->headerLine(
1847
                'X-Mailer',
1848
                'PHPMailer ' . $this->Version . ' (https://github.com/PHPMailer/PHPMailer)'
1849
            );
1850
        } else {
1851
            $myXmailer = trim($this->XMailer);
1852
            if ($myXmailer) {
1853
                $result .= $this->headerLine('X-Mailer', $myXmailer);
1854
            }
1855
        }
1856
1857
        if ($this->ConfirmReadingTo != '') {
1858
            $result .= $this->headerLine('Disposition-Notification-To', '<' . trim($this->ConfirmReadingTo) . '>');
1859
        }
1860
1861
        // Add custom headers
1862
        foreach ($this->CustomHeader as $header) {
1863
            $result .= $this->headerLine(
1864
                trim($header[0]),
1865
                $this->encodeHeader(trim($header[1]))
1866
            );
1867
        }
1868
        if (!$this->sign_key_file) {
1869
            $result .= $this->headerLine('MIME-Version', '1.0');
1870
            $result .= $this->getMailMIME();
1871
        }
1872
1873
        return $result;
1874
    }
1875
1876
    /**
1877
     * Get the message MIME type headers.
1878
     * @access public
1879
     * @return string
1880
     */
1881
    public function getMailMIME()
1882
    {
1883
        $result = '';
1884
        $ismultipart = true;
1885
        switch ($this->message_type) {
1886
            case 'inline':
1887
                $result .= $this->headerLine('Content-Type', 'multipart/related;');
1888
                $result .= $this->textLine("\tboundary=\"" . $this->boundary[1] . '"');
1889
                break;
1890
            case 'attach':
1891
            case 'inline_attach':
1892
            case 'alt_attach':
1893
            case 'alt_inline_attach':
1894
                $result .= $this->headerLine('Content-Type', 'multipart/mixed;');
1895
                $result .= $this->textLine("\tboundary=\"" . $this->boundary[1] . '"');
1896
                break;
1897
            case 'alt':
1898
            case 'alt_inline':
1899
                $result .= $this->headerLine('Content-Type', 'multipart/alternative;');
1900
                $result .= $this->textLine("\tboundary=\"" . $this->boundary[1] . '"');
1901
                break;
1902
            default:
1903
                // Catches case 'plain': and case '':
1904
                $result .= $this->textLine('Content-Type: ' . $this->ContentType . '; charset=' . $this->CharSet);
1905
                $ismultipart = false;
1906
                break;
1907
        }
1908
        // RFC1341 part 5 says 7bit is assumed if not specified
1909
        if ($this->Encoding != '7bit') {
1910
            // RFC 2045 section 6.4 says multipart MIME parts may only use 7bit, 8bit or binary CTE
1911
            if ($ismultipart) {
1912
                if ($this->Encoding == '8bit') {
1913
                    $result .= $this->headerLine('Content-Transfer-Encoding', '8bit');
1914
                }
1915
                // The only remaining alternatives are quoted-printable and base64, which are both 7bit compatible
1916
            } else {
1917
                $result .= $this->headerLine('Content-Transfer-Encoding', $this->Encoding);
1918
            }
1919
        }
1920
1921
        if ($this->Mailer != 'mail') {
1922
            $result .= $this->LE;
1923
        }
1924
1925
        return $result;
1926
    }
1927
1928
    /**
1929
     * Returns the whole MIME message.
1930
     * Includes complete headers and body.
1931
     * Only valid post preSend().
1932
     * @see PHPMailer::preSend()
1933
     * @access public
1934
     * @return string
1935
     */
1936
    public function getSentMIMEMessage()
1937
    {
1938
        return $this->MIMEHeader . $this->mailHeader . self::CRLF . $this->MIMEBody;
1939
    }
1940
1941
    /**
1942
     * Assemble the message body.
1943
     * Returns an empty string on failure.
1944
     * @access public
1945
     * @throws phpmailerException
1946
     * @return string The assembled message body
1947
     */
1948
    public function createBody()
1949
    {
1950
        $body = '';
1951
        //Create unique IDs and preset boundaries
1952
        $this->uniqueid = md5(uniqid(time()));
1953
        $this->boundary[1] = 'b1_' . $this->uniqueid;
1954
        $this->boundary[2] = 'b2_' . $this->uniqueid;
1955
        $this->boundary[3] = 'b3_' . $this->uniqueid;
1956
1957
        if ($this->sign_key_file) {
1958
            $body .= $this->getMailMIME() . $this->LE;
1959
        }
1960
1961
        $this->setWordWrap();
1962
1963
        $bodyEncoding = $this->Encoding;
1964
        $bodyCharSet = $this->CharSet;
1965
        //Can we do a 7-bit downgrade?
1966
        if ($bodyEncoding == '8bit' and !$this->has8bitChars($this->Body)) {
1967
            $bodyEncoding = '7bit';
1968
            $bodyCharSet = 'us-ascii';
1969
        }
1970
        //If lines are too long, and we're not already using an encoding that will shorten them,
1971
        //change to quoted-printable transfer encoding
1972
        if ('base64' != $this->Encoding and self::hasLineLongerThanMax($this->Body)) {
1973
            $this->Encoding = 'quoted-printable';
1974
            $bodyEncoding = 'quoted-printable';
1975
        }
1976
1977
        $altBodyEncoding = $this->Encoding;
1978
        $altBodyCharSet = $this->CharSet;
1979
        //Can we do a 7-bit downgrade?
1980
        if ($altBodyEncoding == '8bit' and !$this->has8bitChars($this->AltBody)) {
1981
            $altBodyEncoding = '7bit';
1982
            $altBodyCharSet = 'us-ascii';
1983
        }
1984
        //If lines are too long, change to quoted-printable transfer encoding
1985
        if (self::hasLineLongerThanMax($this->AltBody)) {
1986
            $altBodyEncoding = 'quoted-printable';
1987
        }
1988
        //Use this as a preamble in all multipart message types
1989
        $mimepre = "This is a multi-part message in MIME format." . $this->LE . $this->LE;
1990
        switch ($this->message_type) {
1991
            case 'inline':
1992
                $body .= $mimepre;
1993
                $body .= $this->getBoundary($this->boundary[1], $bodyCharSet, '', $bodyEncoding);
1994
                $body .= $this->encodeString($this->Body, $bodyEncoding);
1995
                $body .= $this->LE . $this->LE;
1996
                $body .= $this->attachAll('inline', $this->boundary[1]);
1997
                break;
1998
            case 'attach':
1999
                $body .= $mimepre;
2000
                $body .= $this->getBoundary($this->boundary[1], $bodyCharSet, '', $bodyEncoding);
2001
                $body .= $this->encodeString($this->Body, $bodyEncoding);
2002
                $body .= $this->LE . $this->LE;
2003
                $body .= $this->attachAll('attachment', $this->boundary[1]);
2004
                break;
2005
            case 'inline_attach':
2006
                $body .= $mimepre;
2007
                $body .= $this->textLine('--' . $this->boundary[1]);
2008
                $body .= $this->headerLine('Content-Type', 'multipart/related;');
2009
                $body .= $this->textLine("\tboundary=\"" . $this->boundary[2] . '"');
2010
                $body .= $this->LE;
2011
                $body .= $this->getBoundary($this->boundary[2], $bodyCharSet, '', $bodyEncoding);
2012
                $body .= $this->encodeString($this->Body, $bodyEncoding);
2013
                $body .= $this->LE . $this->LE;
2014
                $body .= $this->attachAll('inline', $this->boundary[2]);
2015
                $body .= $this->LE;
2016
                $body .= $this->attachAll('attachment', $this->boundary[1]);
2017
                break;
2018
            case 'alt':
2019
                $body .= $mimepre;
2020
                $body .= $this->getBoundary($this->boundary[1], $altBodyCharSet, 'text/plain', $altBodyEncoding);
2021
                $body .= $this->encodeString($this->AltBody, $altBodyEncoding);
2022
                $body .= $this->LE . $this->LE;
2023
                $body .= $this->getBoundary($this->boundary[1], $bodyCharSet, 'text/html', $bodyEncoding);
2024
                $body .= $this->encodeString($this->Body, $bodyEncoding);
2025
                $body .= $this->LE . $this->LE;
2026
                if (!empty($this->Ical)) {
2027
                    $body .= $this->getBoundary($this->boundary[1], '', 'text/calendar; method=REQUEST', '');
2028
                    $body .= $this->encodeString($this->Ical, $this->Encoding);
2029
                    $body .= $this->LE . $this->LE;
2030
                }
2031
                $body .= $this->endBoundary($this->boundary[1]);
2032
                break;
2033
            case 'alt_inline':
2034
                $body .= $mimepre;
2035
                $body .= $this->getBoundary($this->boundary[1], $altBodyCharSet, 'text/plain', $altBodyEncoding);
2036
                $body .= $this->encodeString($this->AltBody, $altBodyEncoding);
2037
                $body .= $this->LE . $this->LE;
2038
                $body .= $this->textLine('--' . $this->boundary[1]);
2039
                $body .= $this->headerLine('Content-Type', 'multipart/related;');
2040
                $body .= $this->textLine("\tboundary=\"" . $this->boundary[2] . '"');
2041
                $body .= $this->LE;
2042
                $body .= $this->getBoundary($this->boundary[2], $bodyCharSet, 'text/html', $bodyEncoding);
2043
                $body .= $this->encodeString($this->Body, $bodyEncoding);
2044
                $body .= $this->LE . $this->LE;
2045
                $body .= $this->attachAll('inline', $this->boundary[2]);
2046
                $body .= $this->LE;
2047
                $body .= $this->endBoundary($this->boundary[1]);
2048
                break;
2049
            case 'alt_attach':
2050
                $body .= $mimepre;
2051
                $body .= $this->textLine('--' . $this->boundary[1]);
2052
                $body .= $this->headerLine('Content-Type', 'multipart/alternative;');
2053
                $body .= $this->textLine("\tboundary=\"" . $this->boundary[2] . '"');
2054
                $body .= $this->LE;
2055
                $body .= $this->getBoundary($this->boundary[2], $altBodyCharSet, 'text/plain', $altBodyEncoding);
2056
                $body .= $this->encodeString($this->AltBody, $altBodyEncoding);
2057
                $body .= $this->LE . $this->LE;
2058
                $body .= $this->getBoundary($this->boundary[2], $bodyCharSet, 'text/html', $bodyEncoding);
2059
                $body .= $this->encodeString($this->Body, $bodyEncoding);
2060
                $body .= $this->LE . $this->LE;
2061
                $body .= $this->endBoundary($this->boundary[2]);
2062
                $body .= $this->LE;
2063
                $body .= $this->attachAll('attachment', $this->boundary[1]);
2064
                break;
2065
            case 'alt_inline_attach':
2066
                $body .= $mimepre;
2067
                $body .= $this->textLine('--' . $this->boundary[1]);
2068
                $body .= $this->headerLine('Content-Type', 'multipart/alternative;');
2069
                $body .= $this->textLine("\tboundary=\"" . $this->boundary[2] . '"');
2070
                $body .= $this->LE;
2071
                $body .= $this->getBoundary($this->boundary[2], $altBodyCharSet, 'text/plain', $altBodyEncoding);
2072
                $body .= $this->encodeString($this->AltBody, $altBodyEncoding);
2073
                $body .= $this->LE . $this->LE;
2074
                $body .= $this->textLine('--' . $this->boundary[2]);
2075
                $body .= $this->headerLine('Content-Type', 'multipart/related;');
2076
                $body .= $this->textLine("\tboundary=\"" . $this->boundary[3] . '"');
2077
                $body .= $this->LE;
2078
                $body .= $this->getBoundary($this->boundary[3], $bodyCharSet, 'text/html', $bodyEncoding);
2079
                $body .= $this->encodeString($this->Body, $bodyEncoding);
2080
                $body .= $this->LE . $this->LE;
2081
                $body .= $this->attachAll('inline', $this->boundary[3]);
2082
                $body .= $this->LE;
2083
                $body .= $this->endBoundary($this->boundary[2]);
2084
                $body .= $this->LE;
2085
                $body .= $this->attachAll('attachment', $this->boundary[1]);
2086
                break;
2087
            default:
2088
                // catch case 'plain' and case ''
2089
                $body .= $this->encodeString($this->Body, $bodyEncoding);
2090
                break;
2091
        }
2092
2093
        if ($this->isError()) {
2094
            $body = '';
2095
        } elseif ($this->sign_key_file) {
2096
            try {
2097
                if (!defined('PKCS7_TEXT')) {
2098
                    throw new phpmailerException($this->lang('extension_missing') . 'openssl');
2099
                }
2100
                // @TODO would be nice to use php://temp streams here, but need to wrap for PHP < 5.1
2101
                $file = tempnam(sys_get_temp_dir(), 'mail');
2102
                if (false === file_put_contents($file, $body)) {
2103
                    throw new phpmailerException($this->lang('signing') . ' Could not write temp file');
2104
                }
2105
                $signed = tempnam(sys_get_temp_dir(), 'signed');
2106
                //Workaround for PHP bug https://bugs.php.net/bug.php?id=69197
2107
                if (empty($this->sign_extracerts_file)) {
2108
                    $sign = @openssl_pkcs7_sign(
2109
                        $file,
2110
                        $signed,
2111
                        'file://' . realpath($this->sign_cert_file),
2112
                        array('file://' . realpath($this->sign_key_file), $this->sign_key_pass),
2113
                        null
2114
                    );
2115
                } else {
2116
                    $sign = @openssl_pkcs7_sign(
2117
                        $file,
2118
                        $signed,
2119
                        'file://' . realpath($this->sign_cert_file),
2120
                        array('file://' . realpath($this->sign_key_file), $this->sign_key_pass),
2121
                        null,
2122
                        PKCS7_DETACHED,
2123
                        $this->sign_extracerts_file
2124
                    );
2125
                }
2126
                if ($sign) {
2127
                    @unlink($file);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

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

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

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
2128
                    $body = file_get_contents($signed);
2129
                    @unlink($signed);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

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

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

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
2130
                    //The message returned by openssl contains both headers and body, so need to split them up
2131
                    $parts = explode("\n\n", $body, 2);
2132
                    $this->MIMEHeader .= $parts[0] . $this->LE . $this->LE;
2133
                    $body = $parts[1];
2134
                } else {
2135
                    @unlink($file);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

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

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

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
2136
                    @unlink($signed);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

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

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

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
2137
                    throw new phpmailerException($this->lang('signing') . openssl_error_string());
2138
                }
2139
            } catch (phpmailerException $exc) {
2140
                $body = '';
2141
                if ($this->exceptions) {
2142
                    throw $exc;
2143
                }
2144
            }
2145
        }
2146
        return $body;
2147
    }
2148
2149
    /**
2150
     * Return the start of a message boundary.
2151
     * @access protected
2152
     * @param string $boundary
2153
     * @param string $charSet
2154
     * @param string $contentType
2155
     * @param string $encoding
2156
     * @return string
2157
     */
2158
    protected function getBoundary($boundary, $charSet, $contentType, $encoding)
2159
    {
2160
        $result = '';
2161
        if ($charSet == '') {
2162
            $charSet = $this->CharSet;
2163
        }
2164
        if ($contentType == '') {
2165
            $contentType = $this->ContentType;
2166
        }
2167
        if ($encoding == '') {
2168
            $encoding = $this->Encoding;
2169
        }
2170
        $result .= $this->textLine('--' . $boundary);
2171
        $result .= sprintf('Content-Type: %s; charset=%s', $contentType, $charSet);
2172
        $result .= $this->LE;
2173
        // RFC1341 part 5 says 7bit is assumed if not specified
2174
        if ($encoding != '7bit') {
2175
            $result .= $this->headerLine('Content-Transfer-Encoding', $encoding);
2176
        }
2177
        $result .= $this->LE;
2178
2179
        return $result;
2180
    }
2181
2182
    /**
2183
     * Return the end of a message boundary.
2184
     * @access protected
2185
     * @param string $boundary
2186
     * @return string
2187
     */
2188
    protected function endBoundary($boundary)
2189
    {
2190
        return $this->LE . '--' . $boundary . '--' . $this->LE;
2191
    }
2192
2193
    /**
2194
     * Set the message type.
2195
     * PHPMailer only supports some preset message types,
2196
     * not arbitrary MIME structures.
2197
     * @access protected
2198
     * @return void
2199
     */
2200
    protected function setMessageType()
2201
    {
2202
        $type = array();
2203
        if ($this->alternativeExists()) {
2204
            $type[] = 'alt';
2205
        }
2206
        if ($this->inlineImageExists()) {
2207
            $type[] = 'inline';
2208
        }
2209
        if ($this->attachmentExists()) {
2210
            $type[] = 'attach';
2211
        }
2212
        $this->message_type = implode('_', $type);
2213
        if ($this->message_type == '') {
2214
            $this->message_type = 'plain';
2215
        }
2216
    }
2217
2218
    /**
2219
     * Format a header line.
2220
     * @access public
2221
     * @param string $name
2222
     * @param string $value
2223
     * @return string
2224
     */
2225
    public function headerLine($name, $value)
2226
    {
2227
        return $name . ': ' . $value . $this->LE;
2228
    }
2229
2230
    /**
2231
     * Return a formatted mail line.
2232
     * @access public
2233
     * @param string $value
2234
     * @return string
2235
     */
2236
    public function textLine($value)
2237
    {
2238
        return $value . $this->LE;
2239
    }
2240
2241
    /**
2242
     * Add an attachment from a path on the filesystem.
2243
     * Returns false if the file could not be found or read.
2244
     * @param string $path Path to the attachment.
2245
     * @param string $name Overrides the attachment name.
2246
     * @param string $encoding File encoding (see $Encoding).
2247
     * @param string $type File extension (MIME) type.
2248
     * @param string $disposition Disposition to use
2249
     * @throws phpmailerException
2250
     * @return boolean
2251
     */
2252
    public function addAttachment($path, $name = '', $encoding = 'base64', $type = '', $disposition = 'attachment')
2253
    {
2254
        try {
2255
            if (!@is_file($path)) {
2256
                throw new phpmailerException($this->lang('file_access') . $path, self::STOP_CONTINUE);
2257
            }
2258
2259
            // If a MIME type is not specified, try to work it out from the file name
2260
            if ($type == '') {
2261
                $type = self::filenameToType($path);
2262
            }
2263
2264
            $filename = basename($path);
2265
            if ($name == '') {
2266
                $name = $filename;
2267
            }
2268
2269
            $this->attachment[] = array(
2270
                0 => $path,
2271
                1 => $filename,
2272
                2 => $name,
2273
                3 => $encoding,
2274
                4 => $type,
2275
                5 => false, // isStringAttachment
2276
                6 => $disposition,
2277
                7 => 0
2278
            );
2279
2280
        } catch (phpmailerException $exc) {
2281
            $this->setError($exc->getMessage());
2282
            $this->edebug($exc->getMessage());
2283
            if ($this->exceptions) {
2284
                throw $exc;
2285
            }
2286
            return false;
2287
        }
2288
        return true;
2289
    }
2290
2291
    /**
2292
     * Return the array of attachments.
2293
     * @return array
2294
     */
2295
    public function getAttachments()
2296
    {
2297
        return $this->attachment;
2298
    }
2299
2300
    /**
2301
     * Attach all file, string, and binary attachments to the message.
2302
     * Returns an empty string on failure.
2303
     * @access protected
2304
     * @param string $disposition_type
2305
     * @param string $boundary
2306
     * @return string
2307
     */
2308
    protected function attachAll($disposition_type, $boundary)
2309
    {
2310
        // Return text of body
2311
        $mime = array();
2312
        $cidUniq = array();
2313
        $incl = array();
2314
2315
        // Add all attachments
2316
        foreach ($this->attachment as $attachment) {
2317
            // Check if it is a valid disposition_filter
2318
            if ($attachment[6] == $disposition_type) {
2319
                // Check for string attachment
2320
                $string = '';
2321
                $path = '';
2322
                $bString = $attachment[5];
2323
                if ($bString) {
2324
                    $string = $attachment[0];
2325
                } else {
2326
                    $path = $attachment[0];
2327
                }
2328
2329
                $inclhash = md5(serialize($attachment));
2330
                if (in_array($inclhash, $incl)) {
2331
                    continue;
2332
                }
2333
                $incl[] = $inclhash;
2334
                $name = $attachment[2];
2335
                $encoding = $attachment[3];
2336
                $type = $attachment[4];
2337
                $disposition = $attachment[6];
2338
                $cid = $attachment[7];
2339
                if ($disposition == 'inline' && isset($cidUniq[$cid])) {
2340
                    continue;
2341
                }
2342
                $cidUniq[$cid] = true;
2343
2344
                $mime[] = sprintf('--%s%s', $boundary, $this->LE);
2345
                //Only include a filename property if we have one
2346
                if (!empty($name)) {
2347
                    $mime[] = sprintf(
2348
                        'Content-Type: %s; name="%s"%s',
2349
                        $type,
2350
                        $this->encodeHeader($this->secureHeader($name)),
2351
                        $this->LE
2352
                    );
2353
                } else {
2354
                    $mime[] = sprintf(
2355
                        'Content-Type: %s%s',
2356
                        $type,
2357
                        $this->LE
2358
                    );
2359
                }
2360
                // RFC1341 part 5 says 7bit is assumed if not specified
2361
                if ($encoding != '7bit') {
2362
                    $mime[] = sprintf('Content-Transfer-Encoding: %s%s', $encoding, $this->LE);
2363
                }
2364
2365
                if ($disposition == 'inline') {
2366
                    $mime[] = sprintf('Content-ID: <%s>%s', $cid, $this->LE);
2367
                }
2368
2369
                // If a filename contains any of these chars, it should be quoted,
2370
                // but not otherwise: RFC2183 & RFC2045 5.1
2371
                // Fixes a warning in IETF's msglint MIME checker
2372
                // Allow for bypassing the Content-Disposition header totally
2373
                if (!(empty($disposition))) {
2374
                    $encoded_name = $this->encodeHeader($this->secureHeader($name));
2375
                    if (preg_match('/[ \(\)<>@,;:\\"\/\[\]\?=]/', $encoded_name)) {
2376
                        $mime[] = sprintf(
2377
                            'Content-Disposition: %s; filename="%s"%s',
2378
                            $disposition,
2379
                            $encoded_name,
2380
                            $this->LE . $this->LE
2381
                        );
2382
                    } else {
2383
                        if (!empty($encoded_name)) {
2384
                            $mime[] = sprintf(
2385
                                'Content-Disposition: %s; filename=%s%s',
2386
                                $disposition,
2387
                                $encoded_name,
2388
                                $this->LE . $this->LE
2389
                            );
2390
                        } else {
2391
                            $mime[] = sprintf(
2392
                                'Content-Disposition: %s%s',
2393
                                $disposition,
2394
                                $this->LE . $this->LE
2395
                            );
2396
                        }
2397
                    }
2398
                } else {
2399
                    $mime[] = $this->LE;
2400
                }
2401
2402
                // Encode as string attachment
2403
                if ($bString) {
2404
                    $mime[] = $this->encodeString($string, $encoding);
2405
                    if ($this->isError()) {
2406
                        return '';
2407
                    }
2408
                    $mime[] = $this->LE . $this->LE;
2409
                } else {
2410
                    $mime[] = $this->encodeFile($path, $encoding);
2411
                    if ($this->isError()) {
2412
                        return '';
2413
                    }
2414
                    $mime[] = $this->LE . $this->LE;
2415
                }
2416
            }
2417
        }
2418
2419
        $mime[] = sprintf('--%s--%s', $boundary, $this->LE);
2420
2421
        return implode('', $mime);
2422
    }
2423
2424
    /**
2425
     * Encode a file attachment in requested format.
2426
     * Returns an empty string on failure.
2427
     * @param string $path The full path to the file
2428
     * @param string $encoding The encoding to use; one of 'base64', '7bit', '8bit', 'binary', 'quoted-printable'
2429
     * @throws phpmailerException
2430
     * @see EncodeFile(encodeFile
2431
     * @access protected
2432
     * @return string
2433
     */
2434
    protected function encodeFile($path, $encoding = 'base64')
2435
    {
2436
        try {
2437
            if (!is_readable($path)) {
2438
                throw new phpmailerException($this->lang('file_open') . $path, self::STOP_CONTINUE);
2439
            }
2440
            $magic_quotes = get_magic_quotes_runtime();
2441
            if ($magic_quotes) {
2442
                if (version_compare(PHP_VERSION, '5.3.0', '<')) {
2443
                    set_magic_quotes_runtime(false);
2444
                } else {
2445
                    //Doesn't exist in PHP 5.4, but we don't need to check because
2446
                    //get_magic_quotes_runtime always returns false in 5.4+
2447
                    //so it will never get here
2448
                    ini_set('magic_quotes_runtime', false);
2449
                }
2450
            }
2451
            $file_buffer = file_get_contents($path);
2452
            $file_buffer = $this->encodeString($file_buffer, $encoding);
2453
            if ($magic_quotes) {
2454
                if (version_compare(PHP_VERSION, '5.3.0', '<')) {
2455
                    set_magic_quotes_runtime($magic_quotes);
2456
                } else {
2457
                    ini_set('magic_quotes_runtime', $magic_quotes);
2458
                }
2459
            }
2460
            return $file_buffer;
2461
        } catch (Exception $exc) {
2462
            $this->setError($exc->getMessage());
2463
            return '';
2464
        }
2465
    }
2466
2467
    /**
2468
     * Encode a string in requested format.
2469
     * Returns an empty string on failure.
2470
     * @param string $str The text to encode
2471
     * @param string $encoding The encoding to use; one of 'base64', '7bit', '8bit', 'binary', 'quoted-printable'
2472
     * @access public
2473
     * @return string
2474
     */
2475
    public function encodeString($str, $encoding = 'base64')
2476
    {
2477
        $encoded = '';
2478
        switch (strtolower($encoding)) {
2479
            case 'base64':
2480
                $encoded = chunk_split(base64_encode($str), 76, $this->LE);
2481
                break;
2482
            case '7bit':
2483
            case '8bit':
2484
                $encoded = $this->fixEOL($str);
2485
                // Make sure it ends with a line break
2486
                if (substr($encoded, -(strlen($this->LE))) != $this->LE) {
2487
                    $encoded .= $this->LE;
2488
                }
2489
                break;
2490
            case 'binary':
2491
                $encoded = $str;
2492
                break;
2493
            case 'quoted-printable':
2494
                $encoded = $this->encodeQP($str);
2495
                break;
2496
            default:
2497
                $this->setError($this->lang('encoding') . $encoding);
2498
                break;
2499
        }
2500
        return $encoded;
2501
    }
2502
2503
    /**
2504
     * Encode a header string optimally.
2505
     * Picks shortest of Q, B, quoted-printable or none.
2506
     * @access public
2507
     * @param string $str
2508
     * @param string $position
2509
     * @return string
2510
     */
2511
    public function encodeHeader($str, $position = 'text')
2512
    {
2513
        $matchcount = 0;
2514
        switch (strtolower($position)) {
2515
            case 'phrase':
2516
                if (!preg_match('/[\200-\377]/', $str)) {
2517
                    // Can't use addslashes as we don't know the value of magic_quotes_sybase
2518
                    $encoded = addcslashes($str, "\0..\37\177\\\"");
2519
                    if (($str == $encoded) && !preg_match('/[^A-Za-z0-9!#$%&\'*+\/=?^_`{|}~ -]/', $str)) {
2520
                        return ($encoded);
2521
                    } else {
2522
                        return ("\"$encoded\"");
2523
                    }
2524
                }
2525
                $matchcount = preg_match_all('/[^\040\041\043-\133\135-\176]/', $str, $matches);
2526
                break;
2527
            /** @noinspection PhpMissingBreakStatementInspection */
2528
            case 'comment':
2529
                $matchcount = preg_match_all('/[()"]/', $str, $matches);
2530
                // Intentional fall-through
2531
            case 'text':
2532
            default:
2533
                $matchcount += preg_match_all('/[\000-\010\013\014\016-\037\177-\377]/', $str, $matches);
2534
                break;
2535
        }
2536
2537
        //There are no chars that need encoding
2538
        if ($matchcount == 0) {
2539
            return ($str);
2540
        }
2541
2542
        $maxlen = 75 - 7 - strlen($this->CharSet);
2543
        // Try to select the encoding which should produce the shortest output
2544
        if ($matchcount > strlen($str) / 3) {
2545
            // More than a third of the content will need encoding, so B encoding will be most efficient
2546
            $encoding = 'B';
2547
            if (function_exists('mb_strlen') && $this->hasMultiBytes($str)) {
2548
                // Use a custom function which correctly encodes and wraps long
2549
                // multibyte strings without breaking lines within a character
2550
                $encoded = $this->base64EncodeWrapMB($str, "\n");
2551
            } else {
2552
                $encoded = base64_encode($str);
2553
                $maxlen -= $maxlen % 4;
2554
                $encoded = trim(chunk_split($encoded, $maxlen, "\n"));
2555
            }
2556
        } else {
2557
            $encoding = 'Q';
2558
            $encoded = $this->encodeQ($str, $position);
2559
            $encoded = $this->wrapText($encoded, $maxlen, true);
2560
            $encoded = str_replace('=' . self::CRLF, "\n", trim($encoded));
2561
        }
2562
2563
        $encoded = preg_replace('/^(.*)$/m', ' =?' . $this->CharSet . "?$encoding?\\1?=", $encoded);
2564
        $encoded = trim(str_replace("\n", $this->LE, $encoded));
2565
2566
        return $encoded;
2567
    }
2568
2569
    /**
2570
     * Check if a string contains multi-byte characters.
2571
     * @access public
2572
     * @param string $str multi-byte text to wrap encode
2573
     * @return boolean
2574
     */
2575
    public function hasMultiBytes($str)
2576
    {
2577
        if (function_exists('mb_strlen')) {
2578
            return (strlen($str) > mb_strlen($str, $this->CharSet));
2579
        } else { // Assume no multibytes (we can't handle without mbstring functions anyway)
2580
            return false;
2581
        }
2582
    }
2583
2584
    /**
2585
     * Does a string contain any 8-bit chars (in any charset)?
2586
     * @param string $text
2587
     * @return boolean
2588
     */
2589
    public function has8bitChars($text)
2590
    {
2591
        return (boolean)preg_match('/[\x80-\xFF]/', $text);
2592
    }
2593
2594
    /**
2595
     * Encode and wrap long multibyte strings for mail headers
2596
     * without breaking lines within a character.
2597
     * Adapted from a function by paravoid
2598
     * @link http://www.php.net/manual/en/function.mb-encode-mimeheader.php#60283
2599
     * @access public
2600
     * @param string $str multi-byte text to wrap encode
2601
     * @param string $linebreak string to use as linefeed/end-of-line
2602
     * @return string
2603
     */
2604
    public function base64EncodeWrapMB($str, $linebreak = null)
2605
    {
2606
        $start = '=?' . $this->CharSet . '?B?';
2607
        $end = '?=';
2608
        $encoded = '';
2609
        if ($linebreak === null) {
2610
            $linebreak = $this->LE;
2611
        }
2612
2613
        $mb_length = mb_strlen($str, $this->CharSet);
2614
        // Each line must have length <= 75, including $start and $end
2615
        $length = 75 - strlen($start) - strlen($end);
2616
        // Average multi-byte ratio
2617
        $ratio = $mb_length / strlen($str);
2618
        // Base64 has a 4:3 ratio
2619
        $avgLength = floor($length * $ratio * .75);
2620
2621
        for ($i = 0; $i < $mb_length; $i += $offset) {
2622
            $lookBack = 0;
2623
            do {
2624
                $offset = $avgLength - $lookBack;
2625
                $chunk = mb_substr($str, $i, $offset, $this->CharSet);
2626
                $chunk = base64_encode($chunk);
2627
                $lookBack++;
2628
            } while (strlen($chunk) > $length);
2629
            $encoded .= $chunk . $linebreak;
2630
        }
2631
2632
        // Chomp the last linefeed
2633
        $encoded = substr($encoded, 0, -strlen($linebreak));
2634
        return $encoded;
2635
    }
2636
2637
    /**
2638
     * Encode a string in quoted-printable format.
2639
     * According to RFC2045 section 6.7.
2640
     * @access public
2641
     * @param string $string The text to encode
2642
     * @param integer $line_max Number of chars allowed on a line before wrapping
2643
     * @return string
2644
     * @link http://www.php.net/manual/en/function.quoted-printable-decode.php#89417 Adapted from this comment
2645
     */
2646
    public function encodeQP($string, $line_max = 76)
2647
    {
2648
        // Use native function if it's available (>= PHP5.3)
2649
        if (function_exists('quoted_printable_encode')) {
2650
            return quoted_printable_encode($string);
2651
        }
2652
        // Fall back to a pure PHP implementation
2653
        $string = str_replace(
2654
            array('%20', '%0D%0A.', '%0D%0A', '%'),
2655
            array(' ', "\r\n=2E", "\r\n", '='),
2656
            rawurlencode($string)
2657
        );
2658
        return preg_replace('/[^\r\n]{' . ($line_max - 3) . '}[^=\r\n]{2}/', "$0=\r\n", $string);
2659
    }
2660
2661
    /**
2662
     * Backward compatibility wrapper for an old QP encoding function that was removed.
2663
     * @see PHPMailer::encodeQP()
2664
     * @access public
2665
     * @param string $string
2666
     * @param integer $line_max
2667
     * @param boolean $space_conv
2668
     * @return string
2669
     * @deprecated Use encodeQP instead.
2670
     */
2671
    public function encodeQPphp(
2672
        $string,
2673
        $line_max = 76,
2674
        /** @noinspection PhpUnusedParameterInspection */ $space_conv = false
2675
    ) {
2676
        return $this->encodeQP($string, $line_max);
2677
    }
2678
2679
    /**
2680
     * Encode a string using Q encoding.
2681
     * @link http://tools.ietf.org/html/rfc2047
2682
     * @param string $str the text to encode
2683
     * @param string $position Where the text is going to be used, see the RFC for what that means
2684
     * @access public
2685
     * @return string
2686
     */
2687
    public function encodeQ($str, $position = 'text')
2688
    {
2689
        // There should not be any EOL in the string
2690
        $pattern = '';
2691
        $encoded = str_replace(array("\r", "\n"), '', $str);
2692
        switch (strtolower($position)) {
2693
            case 'phrase':
2694
                // RFC 2047 section 5.3
2695
                $pattern = '^A-Za-z0-9!*+\/ -';
2696
                break;
2697
            /** @noinspection PhpMissingBreakStatementInspection */
2698
            case 'comment':
2699
                // RFC 2047 section 5.2
2700
                $pattern = '\(\)"';
2701
                // intentional fall-through
2702
                // for this reason we build the $pattern without including delimiters and []
2703
            case 'text':
2704
            default:
2705
                // RFC 2047 section 5.1
2706
                // Replace every high ascii, control, =, ? and _ characters
2707
                $pattern = '\000-\011\013\014\016-\037\075\077\137\177-\377' . $pattern;
2708
                break;
2709
        }
2710
        $matches = array();
2711
        if (preg_match_all("/[{$pattern}]/", $encoded, $matches)) {
2712
            // If the string contains an '=', make sure it's the first thing we replace
2713
            // so as to avoid double-encoding
2714
            $eqkey = array_search('=', $matches[0]);
2715
            if (false !== $eqkey) {
2716
                unset($matches[0][$eqkey]);
2717
                array_unshift($matches[0], '=');
2718
            }
2719
            foreach (array_unique($matches[0]) as $char) {
2720
                $encoded = str_replace($char, '=' . sprintf('%02X', ord($char)), $encoded);
2721
            }
2722
        }
2723
        // Replace every spaces to _ (more readable than =20)
2724
        return str_replace(' ', '_', $encoded);
2725
    }
2726
2727
2728
    /**
2729
     * Add a string or binary attachment (non-filesystem).
2730
     * This method can be used to attach ascii or binary data,
2731
     * such as a BLOB record from a database.
2732
     * @param string $string String attachment data.
2733
     * @param string $filename Name of the attachment.
2734
     * @param string $encoding File encoding (see $Encoding).
2735
     * @param string $type File extension (MIME) type.
2736
     * @param string $disposition Disposition to use
2737
     * @return void
2738
     */
2739
    public function addStringAttachment(
2740
        $string,
2741
        $filename,
2742
        $encoding = 'base64',
2743
        $type = '',
2744
        $disposition = 'attachment'
2745
    ) {
2746
        // If a MIME type is not specified, try to work it out from the file name
2747
        if ($type == '') {
2748
            $type = self::filenameToType($filename);
2749
        }
2750
        // Append to $attachment array
2751
        $this->attachment[] = array(
2752
            0 => $string,
2753
            1 => $filename,
2754
            2 => basename($filename),
2755
            3 => $encoding,
2756
            4 => $type,
2757
            5 => true, // isStringAttachment
2758
            6 => $disposition,
2759
            7 => 0
2760
        );
2761
    }
2762
2763
    /**
2764
     * Add an embedded (inline) attachment from a file.
2765
     * This can include images, sounds, and just about any other document type.
2766
     * These differ from 'regular' attachments in that they are intended to be
2767
     * displayed inline with the message, not just attached for download.
2768
     * This is used in HTML messages that embed the images
2769
     * the HTML refers to using the $cid value.
2770
     * @param string $path Path to the attachment.
2771
     * @param string $cid Content ID of the attachment; Use this to reference
2772
     *        the content when using an embedded image in HTML.
2773
     * @param string $name Overrides the attachment name.
2774
     * @param string $encoding File encoding (see $Encoding).
2775
     * @param string $type File MIME type.
2776
     * @param string $disposition Disposition to use
2777
     * @return boolean True on successfully adding an attachment
2778
     */
2779
    public function addEmbeddedImage($path, $cid, $name = '', $encoding = 'base64', $type = '', $disposition = 'inline')
2780
    {
2781
        if (!@is_file($path)) {
2782
            $this->setError($this->lang('file_access') . $path);
2783
            return false;
2784
        }
2785
2786
        // If a MIME type is not specified, try to work it out from the file name
2787
        if ($type == '') {
2788
            $type = self::filenameToType($path);
2789
        }
2790
2791
        $filename = basename($path);
2792
        if ($name == '') {
2793
            $name = $filename;
2794
        }
2795
2796
        // Append to $attachment array
2797
        $this->attachment[] = array(
2798
            0 => $path,
2799
            1 => $filename,
2800
            2 => $name,
2801
            3 => $encoding,
2802
            4 => $type,
2803
            5 => false, // isStringAttachment
2804
            6 => $disposition,
2805
            7 => $cid
2806
        );
2807
        return true;
2808
    }
2809
2810
    /**
2811
     * Add an embedded stringified attachment.
2812
     * This can include images, sounds, and just about any other document type.
2813
     * Be sure to set the $type to an image type for images:
2814
     * JPEG images use 'image/jpeg', GIF uses 'image/gif', PNG uses 'image/png'.
2815
     * @param string $string The attachment binary data.
2816
     * @param string $cid Content ID of the attachment; Use this to reference
2817
     *        the content when using an embedded image in HTML.
2818
     * @param string $name
2819
     * @param string $encoding File encoding (see $Encoding).
2820
     * @param string $type MIME type.
2821
     * @param string $disposition Disposition to use
2822
     * @return boolean True on successfully adding an attachment
2823
     */
2824
    public function addStringEmbeddedImage(
2825
        $string,
2826
        $cid,
2827
        $name = '',
2828
        $encoding = 'base64',
2829
        $type = '',
2830
        $disposition = 'inline'
2831
    ) {
2832
        // If a MIME type is not specified, try to work it out from the name
2833
        if ($type == '' and !empty($name)) {
2834
            $type = self::filenameToType($name);
2835
        }
2836
2837
        // Append to $attachment array
2838
        $this->attachment[] = array(
2839
            0 => $string,
2840
            1 => $name,
2841
            2 => $name,
2842
            3 => $encoding,
2843
            4 => $type,
2844
            5 => true, // isStringAttachment
2845
            6 => $disposition,
2846
            7 => $cid
2847
        );
2848
        return true;
2849
    }
2850
2851
    /**
2852
     * Check if an inline attachment is present.
2853
     * @access public
2854
     * @return boolean
2855
     */
2856
    public function inlineImageExists()
2857
    {
2858
        foreach ($this->attachment as $attachment) {
2859
            if ($attachment[6] == 'inline') {
2860
                return true;
2861
            }
2862
        }
2863
        return false;
2864
    }
2865
2866
    /**
2867
     * Check if an attachment (non-inline) is present.
2868
     * @return boolean
2869
     */
2870
    public function attachmentExists()
2871
    {
2872
        foreach ($this->attachment as $attachment) {
2873
            if ($attachment[6] == 'attachment') {
2874
                return true;
2875
            }
2876
        }
2877
        return false;
2878
    }
2879
2880
    /**
2881
     * Check if this message has an alternative body set.
2882
     * @return boolean
2883
     */
2884
    public function alternativeExists()
2885
    {
2886
        return !empty($this->AltBody);
2887
    }
2888
2889
    /**
2890
     * Clear all To recipients.
2891
     * @return void
2892
     */
2893
    public function clearAddresses()
2894
    {
2895
        foreach ($this->to as $to) {
2896
            unset($this->all_recipients[strtolower($to[0])]);
2897
        }
2898
        $this->to = array();
2899
    }
2900
2901
    /**
2902
     * Clear all CC recipients.
2903
     * @return void
2904
     */
2905
    public function clearCCs()
2906
    {
2907
        foreach ($this->cc as $cc) {
2908
            unset($this->all_recipients[strtolower($cc[0])]);
2909
        }
2910
        $this->cc = array();
2911
    }
2912
2913
    /**
2914
     * Clear all BCC recipients.
2915
     * @return void
2916
     */
2917
    public function clearBCCs()
2918
    {
2919
        foreach ($this->bcc as $bcc) {
2920
            unset($this->all_recipients[strtolower($bcc[0])]);
2921
        }
2922
        $this->bcc = array();
2923
    }
2924
2925
    /**
2926
     * Clear all ReplyTo recipients.
2927
     * @return void
2928
     */
2929
    public function clearReplyTos()
2930
    {
2931
        $this->ReplyTo = array();
2932
    }
2933
2934
    /**
2935
     * Clear all recipient types.
2936
     * @return void
2937
     */
2938
    public function clearAllRecipients()
2939
    {
2940
        $this->to = array();
2941
        $this->cc = array();
2942
        $this->bcc = array();
2943
        $this->all_recipients = array();
2944
    }
2945
2946
    /**
2947
     * Clear all filesystem, string, and binary attachments.
2948
     * @return void
2949
     */
2950
    public function clearAttachments()
2951
    {
2952
        $this->attachment = array();
2953
    }
2954
2955
    /**
2956
     * Clear all custom headers.
2957
     * @return void
2958
     */
2959
    public function clearCustomHeaders()
2960
    {
2961
        $this->CustomHeader = array();
2962
    }
2963
2964
    /**
2965
     * Add an error message to the error container.
2966
     * @access protected
2967
     * @param string $msg
2968
     * @return void
2969
     */
2970 3
    protected function setError($msg)
2971
    {
2972 3
        $this->error_count++;
2973 3
        if ($this->Mailer == 'smtp' and !is_null($this->smtp)) {
2974
            $lasterror = $this->smtp->getError();
2975
            if (!empty($lasterror['error'])) {
2976
                $msg .= $this->lang('smtp_error') . $lasterror['error'];
2977
                if (!empty($lasterror['detail'])) {
2978
                    $msg .= ' Detail: '. $lasterror['detail'];
2979
                }
2980
                if (!empty($lasterror['smtp_code'])) {
2981
                    $msg .= ' SMTP code: ' . $lasterror['smtp_code'];
2982
                }
2983
                if (!empty($lasterror['smtp_code_ex'])) {
2984
                    $msg .= ' Additional SMTP info: ' . $lasterror['smtp_code_ex'];
2985
                }
2986
            }
2987
        }
2988 3
        $this->ErrorInfo = $msg;
2989 3
    }
2990
2991
    /**
2992
     * Return an RFC 822 formatted date.
2993
     * @access public
2994
     * @return string
2995
     * @static
2996
     */
2997
    public static function rfcDate()
2998
    {
2999
        // Set the time zone to whatever the default is to avoid 500 errors
3000
        // Will default to UTC if it's not set properly in php.ini
3001
        date_default_timezone_set(@date_default_timezone_get());
3002
        return date('D, j M Y H:i:s O');
3003
    }
3004
3005
    /**
3006
     * Get the server hostname.
3007
     * Returns 'localhost.localdomain' if unknown.
3008
     * @access protected
3009
     * @return string
3010
     */
3011
    protected function serverHostname()
3012
    {
3013
        $result = 'localhost.localdomain';
3014
        if (!empty($this->Hostname)) {
3015
            $result = $this->Hostname;
3016
        } elseif (isset($_SERVER) and array_key_exists('SERVER_NAME', $_SERVER) and !empty($_SERVER['SERVER_NAME'])) {
3017
            $result = $_SERVER['SERVER_NAME'];
3018
        } elseif (function_exists('gethostname') && gethostname() !== false) {
3019
            $result = gethostname();
3020
        } elseif (php_uname('n') !== false) {
3021
            $result = php_uname('n');
3022
        }
3023
        return $result;
3024
    }
3025
3026
    /**
3027
     * Get an error message in the current language.
3028
     * @access protected
3029
     * @param string $key
3030
     * @return string
3031
     */
3032 3
    protected function lang($key)
3033
    {
3034 3
        if (count($this->language) < 1) {
3035
            $this->setLanguage('en'); // set the default language
3036
        }
3037
3038 3
        if (array_key_exists($key, $this->language)) {
3039 3
            if ($key == 'smtp_connect_failed') {
3040
                //Include a link to troubleshooting docs on SMTP connection failure
3041
                //this is by far the biggest cause of support questions
3042
                //but it's usually not PHPMailer's fault.
3043
                return $this->language[$key] . ' https://github.com/PHPMailer/PHPMailer/wiki/Troubleshooting';
3044
            }
3045 3
            return $this->language[$key];
3046
        } else {
3047
            //Return the key as a fallback
3048
            return $key;
3049
        }
3050
    }
3051
3052
    /**
3053
     * Check if an error occurred.
3054
     * @access public
3055
     * @return boolean True if an error did occur.
3056
     */
3057
    public function isError()
3058
    {
3059
        return ($this->error_count > 0);
3060
    }
3061
3062
    /**
3063
     * Ensure consistent line endings in a string.
3064
     * Changes every end of line from CRLF, CR or LF to $this->LE.
3065
     * @access public
3066
     * @param string $str String to fixEOL
3067
     * @return string
3068
     */
3069
    public function fixEOL($str)
3070
    {
3071
        // Normalise to \n
3072
        $nstr = str_replace(array("\r\n", "\r"), "\n", $str);
3073
        // Now convert LE as needed
3074
        if ($this->LE !== "\n") {
3075
            $nstr = str_replace("\n", $this->LE, $nstr);
3076
        }
3077
        return $nstr;
3078
    }
3079
3080
    /**
3081
     * Add a custom header.
3082
     * $name value can be overloaded to contain
3083
     * both header name and value (name:value)
3084
     * @access public
3085
     * @param string $name Custom header name
3086
     * @param string $value Header value
3087
     * @return void
3088
     */
3089
    public function addCustomHeader($name, $value = null)
3090
    {
3091
        if ($value === null) {
3092
            // Value passed in as name:value
3093
            $this->CustomHeader[] = explode(':', $name, 2);
3094
        } else {
3095
            $this->CustomHeader[] = array($name, $value);
3096
        }
3097
    }
3098
3099
    /**
3100
     * Returns all custom headers
3101
     *
3102
     * @return array
3103
     */
3104
    public function getCustomHeaders()
3105
    {
3106
        return $this->CustomHeader;
3107
    }
3108
3109
    /**
3110
     * Create a message from an HTML string.
3111
     * Automatically makes modifications for inline images and backgrounds
3112
     * and creates a plain-text version by converting the HTML.
3113
     * Overwrites any existing values in $this->Body and $this->AltBody
3114
     * @access public
3115
     * @param string $message HTML message string
3116
     * @param string $basedir baseline directory for path
3117
     * @param boolean|callable $advanced Whether to use the internal HTML to text converter
3118
     *    or your own custom converter @see html2text()
3119
     * @return string $message
3120
     */
3121
    public function msgHTML($message, $basedir = '', $advanced = false)
3122
    {
3123
        preg_match_all('/(src|background)=["\'](.*)["\']/Ui', $message, $images);
3124
        if (isset($images[2])) {
3125
            foreach ($images[2] as $imgindex => $url) {
3126
                // Convert data URIs into embedded images
3127
                if (preg_match('#^data:(image[^;,]*)(;base64)?,#', $url, $match)) {
3128
                    $data = substr($url, strpos($url, ','));
3129
                    if ($match[2]) {
3130
                        $data = base64_decode($data);
3131
                    } else {
3132
                        $data = rawurldecode($data);
3133
                    }
3134
                    $cid = md5($url) . '@phpmailer.0'; // RFC2392 S 2
3135
                    if ($this->addStringEmbeddedImage($data, $cid, 'embed' . $imgindex, 'base64', $match[1])) {
3136
                        $message = str_replace(
3137
                            $images[0][$imgindex],
3138
                            $images[1][$imgindex] . '="cid:' . $cid . '"',
3139
                            $message
3140
                        );
3141
                    }
3142
                } elseif (substr($url, 0, 4) !== 'cid:' && !preg_match('#^[A-z]+://#', $url)) {
3143
                    // Do not change urls for absolute images (thanks to corvuscorax)
3144
					// Do not change urls that are already inline images
3145
                    $filename = basename($url);
3146
                    $directory = dirname($url);
3147
                    if ($directory == '.') {
3148
                        $directory = '';
3149
                    }
3150
                    $cid = md5($url) . '@phpmailer.0'; // RFC2392 S 2
3151
                    if (strlen($basedir) > 1 && substr($basedir, -1) != '/') {
3152
                        $basedir .= '/';
3153
                    }
3154
                    if (strlen($directory) > 1 && substr($directory, -1) != '/') {
3155
                        $directory .= '/';
3156
                    }
3157
                    if ($this->addEmbeddedImage(
3158
                        $basedir . $directory . $filename,
3159
                        $cid,
3160
                        $filename,
3161
                        'base64',
3162
                        self::_mime_types((string)self::mb_pathinfo($filename, PATHINFO_EXTENSION))
3163
                    )
3164
                    ) {
3165
                        $message = preg_replace(
3166
                            '/' . $images[1][$imgindex] . '=["\']' . preg_quote($url, '/') . '["\']/Ui',
3167
                            $images[1][$imgindex] . '="cid:' . $cid . '"',
3168
                            $message
3169
                        );
3170
                    }
3171
                }
3172
            }
3173
        }
3174
        $this->isHTML(true);
3175
        // Convert all message body line breaks to CRLF, makes quoted-printable encoding work much better
3176
        $this->Body = $this->normalizeBreaks($message);
3177
        $this->AltBody = $this->normalizeBreaks($this->html2text($message, $advanced));
3178
        if (empty($this->AltBody)) {
3179
            $this->AltBody = 'To view this email message, open it in a program that understands HTML!' .
3180
                self::CRLF . self::CRLF;
3181
        }
3182
        return $this->Body;
3183
    }
3184
3185
    /**
3186
     * Convert an HTML string into plain text.
3187
     * This is used by msgHTML().
3188
     * Note - older versions of this function used a bundled advanced converter
3189
     * which was been removed for license reasons in #232
3190
     * Example usage:
3191
     * <code>
3192
     * // Use default conversion
3193
     * $plain = $mail->html2text($html);
3194
     * // Use your own custom converter
3195
     * $plain = $mail->html2text($html, function($html) {
3196
     *     $converter = new MyHtml2text($html);
3197
     *     return $converter->get_text();
3198
     * });
3199
     * </code>
3200
     * @param string $html The HTML text to convert
3201
     * @param boolean|callable $advanced Any boolean value to use the internal converter,
3202
     *   or provide your own callable for custom conversion.
3203
     * @return string
3204
     */
3205
    public function html2text($html, $advanced = false)
3206
    {
3207
        if (is_callable($advanced)) {
3208
            return call_user_func($advanced, $html);
3209
        }
3210
        return html_entity_decode(
3211
            trim(strip_tags(preg_replace('/<(head|title|style|script)[^>]*>.*?<\/\\1>/si', '', $html))),
3212
            ENT_QUOTES,
3213
            $this->CharSet
0 ignored issues
show
The call to html_entity_decode() has too many arguments starting with $this->CharSet.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
3214
        );
3215
    }
3216
3217
    /**
3218
     * Get the MIME type for a file extension.
3219
     * @param string $ext File extension
3220
     * @access public
3221
     * @return string MIME type of file.
3222
     * @static
3223
     */
3224
    public static function _mime_types($ext = '')
3225
    {
3226
        $mimes = array(
3227
            'xl'    => 'application/excel',
3228
            'js'    => 'application/javascript',
3229
            'hqx'   => 'application/mac-binhex40',
3230
            'cpt'   => 'application/mac-compactpro',
3231
            'bin'   => 'application/macbinary',
3232
            'doc'   => 'application/msword',
3233
            'word'  => 'application/msword',
3234
            'xlsx'  => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
3235
            'xltx'  => 'application/vnd.openxmlformats-officedocument.spreadsheetml.template',
3236
            'potx'  => 'application/vnd.openxmlformats-officedocument.presentationml.template',
3237
            'ppsx'  => 'application/vnd.openxmlformats-officedocument.presentationml.slideshow',
3238
            'pptx'  => 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
3239
            'sldx'  => 'application/vnd.openxmlformats-officedocument.presentationml.slide',
3240
            'docx'  => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
3241
            'dotx'  => 'application/vnd.openxmlformats-officedocument.wordprocessingml.template',
3242
            'xlam'  => 'application/vnd.ms-excel.addin.macroEnabled.12',
3243
            'xlsb'  => 'application/vnd.ms-excel.sheet.binary.macroEnabled.12',
3244
            'class' => 'application/octet-stream',
3245
            'dll'   => 'application/octet-stream',
3246
            'dms'   => 'application/octet-stream',
3247
            'exe'   => 'application/octet-stream',
3248
            'lha'   => 'application/octet-stream',
3249
            'lzh'   => 'application/octet-stream',
3250
            'psd'   => 'application/octet-stream',
3251
            'sea'   => 'application/octet-stream',
3252
            'so'    => 'application/octet-stream',
3253
            'oda'   => 'application/oda',
3254
            'pdf'   => 'application/pdf',
3255
            'ai'    => 'application/postscript',
3256
            'eps'   => 'application/postscript',
3257
            'ps'    => 'application/postscript',
3258
            'smi'   => 'application/smil',
3259
            'smil'  => 'application/smil',
3260
            'mif'   => 'application/vnd.mif',
3261
            'xls'   => 'application/vnd.ms-excel',
3262
            'ppt'   => 'application/vnd.ms-powerpoint',
3263
            'wbxml' => 'application/vnd.wap.wbxml',
3264
            'wmlc'  => 'application/vnd.wap.wmlc',
3265
            'dcr'   => 'application/x-director',
3266
            'dir'   => 'application/x-director',
3267
            'dxr'   => 'application/x-director',
3268
            'dvi'   => 'application/x-dvi',
3269
            'gtar'  => 'application/x-gtar',
3270
            'php3'  => 'application/x-httpd-php',
3271
            'php4'  => 'application/x-httpd-php',
3272
            'php'   => 'application/x-httpd-php',
3273
            'phtml' => 'application/x-httpd-php',
3274
            'phps'  => 'application/x-httpd-php-source',
3275
            'swf'   => 'application/x-shockwave-flash',
3276
            'sit'   => 'application/x-stuffit',
3277
            'tar'   => 'application/x-tar',
3278
            'tgz'   => 'application/x-tar',
3279
            'xht'   => 'application/xhtml+xml',
3280
            'xhtml' => 'application/xhtml+xml',
3281
            'zip'   => 'application/zip',
3282
            'mid'   => 'audio/midi',
3283
            'midi'  => 'audio/midi',
3284
            'mp2'   => 'audio/mpeg',
3285
            'mp3'   => 'audio/mpeg',
3286
            'mpga'  => 'audio/mpeg',
3287
            'aif'   => 'audio/x-aiff',
3288
            'aifc'  => 'audio/x-aiff',
3289
            'aiff'  => 'audio/x-aiff',
3290
            'ram'   => 'audio/x-pn-realaudio',
3291
            'rm'    => 'audio/x-pn-realaudio',
3292
            'rpm'   => 'audio/x-pn-realaudio-plugin',
3293
            'ra'    => 'audio/x-realaudio',
3294
            'wav'   => 'audio/x-wav',
3295
            'bmp'   => 'image/bmp',
3296
            'gif'   => 'image/gif',
3297
            'jpeg'  => 'image/jpeg',
3298
            'jpe'   => 'image/jpeg',
3299
            'jpg'   => 'image/jpeg',
3300
            'png'   => 'image/png',
3301
            'tiff'  => 'image/tiff',
3302
            'tif'   => 'image/tiff',
3303
            'eml'   => 'message/rfc822',
3304
            'css'   => 'text/css',
3305
            'html'  => 'text/html',
3306
            'htm'   => 'text/html',
3307
            'shtml' => 'text/html',
3308
            'log'   => 'text/plain',
3309
            'text'  => 'text/plain',
3310
            'txt'   => 'text/plain',
3311
            'rtx'   => 'text/richtext',
3312
            'rtf'   => 'text/rtf',
3313
            'vcf'   => 'text/vcard',
3314
            'vcard' => 'text/vcard',
3315
            'xml'   => 'text/xml',
3316
            'xsl'   => 'text/xml',
3317
            'mpeg'  => 'video/mpeg',
3318
            'mpe'   => 'video/mpeg',
3319
            'mpg'   => 'video/mpeg',
3320
            'mov'   => 'video/quicktime',
3321
            'qt'    => 'video/quicktime',
3322
            'rv'    => 'video/vnd.rn-realvideo',
3323
            'avi'   => 'video/x-msvideo',
3324
            'movie' => 'video/x-sgi-movie'
3325
        );
3326
        if (array_key_exists(strtolower($ext), $mimes)) {
3327
            return $mimes[strtolower($ext)];
3328
        }
3329
        return 'application/octet-stream';
3330
    }
3331
3332
    /**
3333
     * Map a file name to a MIME type.
3334
     * Defaults to 'application/octet-stream', i.e.. arbitrary binary data.
3335
     * @param string $filename A file name or full path, does not need to exist as a file
3336
     * @return string
3337
     * @static
3338
     */
3339
    public static function filenameToType($filename)
3340
    {
3341
        // In case the path is a URL, strip any query string before getting extension
3342
        $qpos = strpos($filename, '?');
3343
        if (false !== $qpos) {
3344
            $filename = substr($filename, 0, $qpos);
3345
        }
3346
        $pathinfo = self::mb_pathinfo($filename);
3347
        return self::_mime_types($pathinfo['extension']);
3348
    }
3349
3350
    /**
3351
     * Multi-byte-safe pathinfo replacement.
3352
     * Drop-in replacement for pathinfo(), but multibyte-safe, cross-platform-safe, old-version-safe.
3353
     * Works similarly to the one in PHP >= 5.2.0
3354
     * @link http://www.php.net/manual/en/function.pathinfo.php#107461
3355
     * @param string $path A filename or path, does not need to exist as a file
3356
     * @param integer|string $options Either a PATHINFO_* constant,
3357
     *      or a string name to return only the specified piece, allows 'filename' to work on PHP < 5.2
3358
     * @return string|array
3359
     * @static
3360
     */
3361
    public static function mb_pathinfo($path, $options = null)
3362
    {
3363
        $ret = array('dirname' => '', 'basename' => '', 'extension' => '', 'filename' => '');
3364
        $pathinfo = array();
3365
        if (preg_match('%^(.*?)[\\\\/]*(([^/\\\\]*?)(\.([^\.\\\\/]+?)|))[\\\\/\.]*$%im', $path, $pathinfo)) {
3366
            if (array_key_exists(1, $pathinfo)) {
3367
                $ret['dirname'] = $pathinfo[1];
3368
            }
3369
            if (array_key_exists(2, $pathinfo)) {
3370
                $ret['basename'] = $pathinfo[2];
3371
            }
3372
            if (array_key_exists(5, $pathinfo)) {
3373
                $ret['extension'] = $pathinfo[5];
3374
            }
3375
            if (array_key_exists(3, $pathinfo)) {
3376
                $ret['filename'] = $pathinfo[3];
3377
            }
3378
        }
3379
        switch ($options) {
3380
            case PATHINFO_DIRNAME:
3381
            case 'dirname':
3382
                return $ret['dirname'];
3383
            case PATHINFO_BASENAME:
3384
            case 'basename':
3385
                return $ret['basename'];
3386
            case PATHINFO_EXTENSION:
3387
            case 'extension':
3388
                return $ret['extension'];
3389
            case PATHINFO_FILENAME:
3390
            case 'filename':
3391
                return $ret['filename'];
3392
            default:
3393
                return $ret;
3394
        }
3395
    }
3396
3397
    /**
3398
     * Set or reset instance properties.
3399
     * You should avoid this function - it's more verbose, less efficient, more error-prone and
3400
     * harder to debug than setting properties directly.
3401
     * Usage Example:
3402
     * `$mail->set('SMTPSecure', 'tls');`
3403
     *   is the same as:
3404
     * `$mail->SMTPSecure = 'tls';`
3405
     * @access public
3406
     * @param string $name The property name to set
3407
     * @param mixed $value The value to set the property to
3408
     * @return boolean
3409
     * @TODO Should this not be using the __set() magic function?
3410
     */
3411
    public function set($name, $value = '')
3412
    {
3413
        if (property_exists($this, $name)) {
3414
            $this->$name = $value;
3415
            return true;
3416
        } else {
3417
            $this->setError($this->lang('variable_set') . $name);
3418
            return false;
3419
        }
3420
    }
3421
3422
    /**
3423
     * Strip newlines to prevent header injection.
3424
     * @access public
3425
     * @param string $str
3426
     * @return string
3427
     */
3428
    public function secureHeader($str)
3429
    {
3430
        return trim(str_replace(array("\r", "\n"), '', $str));
3431
    }
3432
3433
    /**
3434
     * Normalize line breaks in a string.
3435
     * Converts UNIX LF, Mac CR and Windows CRLF line breaks into a single line break format.
3436
     * Defaults to CRLF (for message bodies) and preserves consecutive breaks.
3437
     * @param string $text
3438
     * @param string $breaktype What kind of line break to use, defaults to CRLF
3439
     * @return string
3440
     * @access public
3441
     * @static
3442
     */
3443
    public static function normalizeBreaks($text, $breaktype = "\r\n")
3444
    {
3445
        return preg_replace('/(\r\n|\r|\n)/ms', $breaktype, $text);
3446
    }
3447
3448
3449
    /**
3450
     * Set the public and private key files and password for S/MIME signing.
3451
     * @access public
3452
     * @param string $cert_filename
3453
     * @param string $key_filename
3454
     * @param string $key_pass Password for private key
3455
     * @param string $extracerts_filename Optional path to chain certificate
3456
     */
3457
    public function sign($cert_filename, $key_filename, $key_pass, $extracerts_filename = '')
3458
    {
3459
        $this->sign_cert_file = $cert_filename;
3460
        $this->sign_key_file = $key_filename;
3461
        $this->sign_key_pass = $key_pass;
3462
        $this->sign_extracerts_file = $extracerts_filename;
3463
    }
3464
3465
    /**
3466
     * Quoted-Printable-encode a DKIM header.
3467
     * @access public
3468
     * @param string $txt
3469
     * @return string
3470
     */
3471
    public function DKIM_QP($txt)
3472
    {
3473
        $line = '';
3474
        for ($i = 0; $i < strlen($txt); $i++) {
3475
            $ord = ord($txt[$i]);
3476
            if (((0x21 <= $ord) && ($ord <= 0x3A)) || $ord == 0x3C || ((0x3E <= $ord) && ($ord <= 0x7E))) {
3477
                $line .= $txt[$i];
3478
            } else {
3479
                $line .= '=' . sprintf('%02X', $ord);
3480
            }
3481
        }
3482
        return $line;
3483
    }
3484
3485
    /**
3486
     * Generate a DKIM signature.
3487
     * @access public
3488
     * @param string $signHeader
3489
     * @throws phpmailerException
3490
     * @return string
3491
     */
3492
    public function DKIM_Sign($signHeader)
3493
    {
3494
        if (!defined('PKCS7_TEXT')) {
3495
            if ($this->exceptions) {
3496
                throw new phpmailerException($this->lang('extension_missing') . 'openssl');
3497
            }
3498
            return '';
3499
        }
3500
        $privKeyStr = file_get_contents($this->DKIM_private);
3501
        if ($this->DKIM_passphrase != '') {
3502
            $privKey = openssl_pkey_get_private($privKeyStr, $this->DKIM_passphrase);
3503
        } else {
3504
            $privKey = $privKeyStr;
3505
        }
3506
        if (openssl_sign($signHeader, $signature, $privKey)) {
3507
            return base64_encode($signature);
3508
        }
3509
        return '';
3510
    }
3511
3512
    /**
3513
     * Generate a DKIM canonicalization header.
3514
     * @access public
3515
     * @param string $signHeader Header
3516
     * @return string
3517
     */
3518
    public function DKIM_HeaderC($signHeader)
3519
    {
3520
        $signHeader = preg_replace('/\r\n\s+/', ' ', $signHeader);
3521
        $lines = explode("\r\n", $signHeader);
3522
        foreach ($lines as $key => $line) {
3523
            list($heading, $value) = explode(':', $line, 2);
3524
            $heading = strtolower($heading);
3525
            $value = preg_replace('/\s+/', ' ', $value); // Compress useless spaces
3526
            $lines[$key] = $heading . ':' . trim($value); // Don't forget to remove WSP around the value
3527
        }
3528
        $signHeader = implode("\r\n", $lines);
3529
        return $signHeader;
3530
    }
3531
3532
    /**
3533
     * Generate a DKIM canonicalization body.
3534
     * @access public
3535
     * @param string $body Message Body
3536
     * @return string
3537
     */
3538
    public function DKIM_BodyC($body)
3539
    {
3540
        if ($body == '') {
3541
            return "\r\n";
3542
        }
3543
        // stabilize line endings
3544
        $body = str_replace("\r\n", "\n", $body);
3545
        $body = str_replace("\n", "\r\n", $body);
3546
        // END stabilize line endings
3547
        while (substr($body, strlen($body) - 4, 4) == "\r\n\r\n") {
3548
            $body = substr($body, 0, strlen($body) - 2);
3549
        }
3550
        return $body;
3551
    }
3552
3553
    /**
3554
     * Create the DKIM header and body in a new message header.
3555
     * @access public
3556
     * @param string $headers_line Header lines
3557
     * @param string $subject Subject
3558
     * @param string $body Body
3559
     * @return string
3560
     */
3561
    public function DKIM_Add($headers_line, $subject, $body)
3562
    {
3563
        $DKIMsignatureType = 'rsa-sha1'; // Signature & hash algorithms
3564
        $DKIMcanonicalization = 'relaxed/simple'; // Canonicalization of header/body
3565
        $DKIMquery = 'dns/txt'; // Query method
3566
        $DKIMtime = time(); // Signature Timestamp = seconds since 00:00:00 - Jan 1, 1970 (UTC time zone)
3567
        $subject_header = "Subject: $subject";
3568
        $headers = explode($this->LE, $headers_line);
3569
        $from_header = '';
3570
        $to_header = '';
3571
        $current = '';
3572
        foreach ($headers as $header) {
3573
            if (strpos($header, 'From:') === 0) {
3574
                $from_header = $header;
3575
                $current = 'from_header';
3576
            } elseif (strpos($header, 'To:') === 0) {
3577
                $to_header = $header;
3578
                $current = 'to_header';
3579
            } else {
3580
                if (!empty($$current) && strpos($header, ' =?') === 0) {
3581
                    $$current .= $header;
3582
                } else {
3583
                    $current = '';
3584
                }
3585
            }
3586
        }
3587
        $from = str_replace('|', '=7C', $this->DKIM_QP($from_header));
3588
        $to = str_replace('|', '=7C', $this->DKIM_QP($to_header));
3589
        $subject = str_replace(
3590
            '|',
3591
            '=7C',
3592
            $this->DKIM_QP($subject_header)
3593
        ); // Copied header fields (dkim-quoted-printable)
3594
        $body = $this->DKIM_BodyC($body);
3595
        $DKIMlen = strlen($body); // Length of body
3596
        $DKIMb64 = base64_encode(pack('H*', sha1($body))); // Base64 of packed binary SHA-1 hash of body
3597
        if ('' == $this->DKIM_identity) {
3598
            $ident = '';
3599
        } else {
3600
            $ident = ' i=' . $this->DKIM_identity . ';';
3601
        }
3602
        $dkimhdrs = 'DKIM-Signature: v=1; a=' .
3603
            $DKIMsignatureType . '; q=' .
3604
            $DKIMquery . '; l=' .
3605
            $DKIMlen . '; s=' .
3606
            $this->DKIM_selector .
3607
            ";\r\n" .
3608
            "\tt=" . $DKIMtime . '; c=' . $DKIMcanonicalization . ";\r\n" .
3609
            "\th=From:To:Subject;\r\n" .
3610
            "\td=" . $this->DKIM_domain . ';' . $ident . "\r\n" .
3611
            "\tz=$from\r\n" .
3612
            "\t|$to\r\n" .
3613
            "\t|$subject;\r\n" .
3614
            "\tbh=" . $DKIMb64 . ";\r\n" .
3615
            "\tb=";
3616
        $toSign = $this->DKIM_HeaderC(
3617
            $from_header . "\r\n" .
3618
            $to_header . "\r\n" .
3619
            $subject_header . "\r\n" .
3620
            $dkimhdrs
3621
        );
3622
        $signed = $this->DKIM_Sign($toSign);
3623
        return $dkimhdrs . $signed . "\r\n";
3624
    }
3625
3626
    /**
3627
     * Detect if a string contains a line longer than the maximum line length allowed.
3628
     * @param string $str
3629
     * @return boolean
3630
     * @static
3631
     */
3632
    public static function hasLineLongerThanMax($str)
3633
    {
3634
        //+2 to include CRLF line break for a 1000 total
3635
        return (boolean)preg_match('/^(.{'.(self::MAX_LINE_LENGTH + 2).',})/m', $str);
3636
    }
3637
3638
    /**
3639
     * Allows for public read access to 'to' property.
3640
     * @access public
3641
     * @return array
3642
     */
3643
    public function getToAddresses()
3644
    {
3645
        return $this->to;
3646
    }
3647
3648
    /**
3649
     * Allows for public read access to 'cc' property.
3650
     * @access public
3651
     * @return array
3652
     */
3653
    public function getCcAddresses()
3654
    {
3655
        return $this->cc;
3656
    }
3657
3658
    /**
3659
     * Allows for public read access to 'bcc' property.
3660
     * @access public
3661
     * @return array
3662
     */
3663
    public function getBccAddresses()
3664
    {
3665
        return $this->bcc;
3666
    }
3667
3668
    /**
3669
     * Allows for public read access to 'ReplyTo' property.
3670
     * @access public
3671
     * @return array
3672
     */
3673
    public function getReplyToAddresses()
3674
    {
3675
        return $this->ReplyTo;
3676
    }
3677
3678
    /**
3679
     * Allows for public read access to 'all_recipients' property.
3680
     * @access public
3681
     * @return array
3682
     */
3683
    public function getAllRecipientAddresses()
3684
    {
3685
        return $this->all_recipients;
3686
    }
3687
3688
    /**
3689
     * Perform a callback.
3690
     * @param boolean $isSent
3691
     * @param array $to
3692
     * @param array $cc
3693
     * @param array $bcc
3694
     * @param string $subject
3695
     * @param string $body
3696
     * @param string $from
3697
     */
3698
    protected function doCallback($isSent, $to, $cc, $bcc, $subject, $body, $from)
3699
    {
3700
        if (!empty($this->action_function) && is_callable($this->action_function)) {
3701
            $params = array($isSent, $to, $cc, $bcc, $subject, $body, $from);
3702
            call_user_func_array($this->action_function, $params);
3703
        }
3704
    }
3705
}
3706
3707
/**
3708
 * PHPMailer exception handler
3709
 * @package PHPMailer
3710
 */
3711
class phpmailerException extends Exception
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class should be in its own file to aid autoloaders.

Having each class in a dedicated file usually plays nice with PSR autoloaders and is therefore a well established practice. If you use other autoloaders, you might not want to follow this rule.

Loading history...
3712
{
3713
    /**
3714
     * Prettify error message output
3715
     * @return string
3716
     */
3717
    public function errorMessage()
3718
    {
3719
        $errorMsg = '<strong>' . $this->getMessage() . "</strong><br />\n";
3720
        return $errorMsg;
3721
    }
3722
}
3723