Issues (4967)

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.

src/wp-includes/class-phpmailer.php (8 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.22';
35
36
    /**
37
     * Email priority.
38
     * Options: null (default), 1 = High, 3 = Normal, 5 = low.
39
     * When null, the header is not set at all.
40
     * @var integer
41
     */
42
    public $Priority = null;
43
44
    /**
45
     * The character set of the message.
46
     * @var string
47
     */
48
    public $CharSet = 'iso-8859-1';
49
50
    /**
51
     * The MIME Content-type of the message.
52
     * @var string
53
     */
54
    public $ContentType = 'text/plain';
55
56
    /**
57
     * The message encoding.
58
     * Options: "8bit", "7bit", "binary", "base64", and "quoted-printable".
59
     * @var string
60
     */
61
    public $Encoding = '8bit';
62
63
    /**
64
     * Holds the most recent mailer error message.
65
     * @var string
66
     */
67
    public $ErrorInfo = '';
68
69
    /**
70
     * The From email address for the message.
71
     * @var string
72
     */
73
    public $From = 'root@localhost';
74
75
    /**
76
     * The From name of the message.
77
     * @var string
78
     */
79
    public $FromName = 'Root User';
80
81
    /**
82
     * The Sender email (Return-Path) of the message.
83
     * If not empty, will be sent via -f to sendmail or as 'MAIL FROM' in smtp mode.
84
     * @var string
85
     */
86
    public $Sender = '';
87
88
    /**
89
     * The Return-Path of the message.
90
     * If empty, it will be set to either From or Sender.
91
     * @var string
92
     * @deprecated Email senders should never set a return-path header;
93
     * it's the receiver's job (RFC5321 section 4.4), so this no longer does anything.
94
     * @link https://tools.ietf.org/html/rfc5321#section-4.4 RFC5321 reference
95
     */
96
    public $ReturnPath = '';
97
98
    /**
99
     * The Subject of the message.
100
     * @var string
101
     */
102
    public $Subject = '';
103
104
    /**
105
     * An HTML or plain text message body.
106
     * If HTML then call isHTML(true).
107
     * @var string
108
     */
109
    public $Body = '';
110
111
    /**
112
     * The plain-text message body.
113
     * This body can be read by mail clients that do not have HTML email
114
     * capability such as mutt & Eudora.
115
     * Clients that can read HTML will view the normal Body.
116
     * @var string
117
     */
118
    public $AltBody = '';
119
120
    /**
121
     * An iCal message part body.
122
     * Only supported in simple alt or alt_inline message types
123
     * To generate iCal events, use the bundled extras/EasyPeasyICS.php class or iCalcreator
124
     * @link http://sprain.ch/blog/downloads/php-class-easypeasyics-create-ical-files-with-php/
125
     * @link http://kigkonsult.se/iCalcreator/
126
     * @var string
127
     */
128
    public $Ical = '';
129
130
    /**
131
     * The complete compiled MIME message body.
132
     * @access protected
133
     * @var string
134
     */
135
    protected $MIMEBody = '';
136
137
    /**
138
     * The complete compiled MIME message headers.
139
     * @var string
140
     * @access protected
141
     */
142
    protected $MIMEHeader = '';
143
144
    /**
145
     * Extra headers that createHeader() doesn't fold in.
146
     * @var string
147
     * @access protected
148
     */
149
    protected $mailHeader = '';
150
151
    /**
152
     * Word-wrap the message body to this number of chars.
153
     * Set to 0 to not wrap. A useful value here is 78, for RFC2822 section 2.1.1 compliance.
154
     * @var integer
155
     */
156
    public $WordWrap = 0;
157
158
    /**
159
     * Which method to use to send mail.
160
     * Options: "mail", "sendmail", or "smtp".
161
     * @var string
162
     */
163
    public $Mailer = 'mail';
164
165
    /**
166
     * The path to the sendmail program.
167
     * @var string
168
     */
169
    public $Sendmail = '/usr/sbin/sendmail';
170
171
    /**
172
     * Whether mail() uses a fully sendmail-compatible MTA.
173
     * One which supports sendmail's "-oi -f" options.
174
     * @var boolean
175
     */
176
    public $UseSendmailOptions = true;
177
178
    /**
179
     * Path to PHPMailer plugins.
180
     * Useful if the SMTP class is not in the PHP include path.
181
     * @var string
182
     * @deprecated Should not be needed now there is an autoloader.
183
     */
184
    public $PluginDir = '';
185
186
    /**
187
     * The email address that a reading confirmation should be sent to, also known as read receipt.
188
     * @var string
189
     */
190
    public $ConfirmReadingTo = '';
191
192
    /**
193
     * The hostname to use in the Message-ID header and as default HELO string.
194
     * If empty, PHPMailer attempts to find one with, in order,
195
     * $_SERVER['SERVER_NAME'], gethostname(), php_uname('n'), or the value
196
     * 'localhost.localdomain'.
197
     * @var string
198
     */
199
    public $Hostname = '';
200
201
    /**
202
     * An ID to be used in the Message-ID header.
203
     * If empty, a unique id will be generated.
204
     * You can set your own, but it must be in the format "<id@domain>",
205
     * as defined in RFC5322 section 3.6.4 or it will be ignored.
206
     * @see https://tools.ietf.org/html/rfc5322#section-3.6.4
207
     * @var string
208
     */
209
    public $MessageID = '';
210
211
    /**
212
     * The message Date to be used in the Date header.
213
     * If empty, the current date will be added.
214
     * @var string
215
     */
216
    public $MessageDate = '';
217
218
    /**
219
     * SMTP hosts.
220
     * Either a single hostname or multiple semicolon-delimited hostnames.
221
     * You can also specify a different port
222
     * for each host by using this format: [hostname:port]
223
     * (e.g. "smtp1.example.com:25;smtp2.example.com").
224
     * You can also specify encryption type, for example:
225
     * (e.g. "tls://smtp1.example.com:587;ssl://smtp2.example.com:465").
226
     * Hosts will be tried in order.
227
     * @var string
228
     */
229
    public $Host = 'localhost';
230
231
    /**
232
     * The default SMTP server port.
233
     * @var integer
234
     * @TODO Why is this needed when the SMTP class takes care of it?
235
     */
236
    public $Port = 25;
237
238
    /**
239
     * The SMTP HELO of the message.
240
     * Default is $Hostname. If $Hostname is empty, PHPMailer attempts to find
241
     * one with the same method described above for $Hostname.
242
     * @var string
243
     * @see PHPMailer::$Hostname
244
     */
245
    public $Helo = '';
246
247
    /**
248
     * What kind of encryption to use on the SMTP connection.
249
     * Options: '', 'ssl' or 'tls'
250
     * @var string
251
     */
252
    public $SMTPSecure = '';
253
254
    /**
255
     * Whether to enable TLS encryption automatically if a server supports it,
256
     * even if `SMTPSecure` is not set to 'tls'.
257
     * Be aware that in PHP >= 5.6 this requires that the server's certificates are valid.
258
     * @var boolean
259
     */
260
    public $SMTPAutoTLS = true;
261
262
    /**
263
     * Whether to use SMTP authentication.
264
     * Uses the Username and Password properties.
265
     * @var boolean
266
     * @see PHPMailer::$Username
267
     * @see PHPMailer::$Password
268
     */
269
    public $SMTPAuth = false;
270
271
    /**
272
     * Options array passed to stream_context_create when connecting via SMTP.
273
     * @var array
274
     */
275
    public $SMTPOptions = array();
276
277
    /**
278
     * SMTP username.
279
     * @var string
280
     */
281
    public $Username = '';
282
283
    /**
284
     * SMTP password.
285
     * @var string
286
     */
287
    public $Password = '';
288
289
    /**
290
     * SMTP auth type.
291
     * Options are CRAM-MD5, LOGIN, PLAIN, attempted in that order if not specified
292
     * @var string
293
     */
294
    public $AuthType = '';
295
296
    /**
297
     * SMTP realm.
298
     * Used for NTLM auth
299
     * @var string
300
     */
301
    public $Realm = '';
302
303
    /**
304
     * SMTP workstation.
305
     * Used for NTLM auth
306
     * @var string
307
     */
308
    public $Workstation = '';
309
310
    /**
311
     * The SMTP server timeout in seconds.
312
     * Default of 5 minutes (300sec) is from RFC2821 section 4.5.3.2
313
     * @var integer
314
     */
315
    public $Timeout = 300;
316
317
    /**
318
     * SMTP class debug output mode.
319
     * Debug output level.
320
     * Options:
321
     * * `0` No output
322
     * * `1` Commands
323
     * * `2` Data and commands
324
     * * `3` As 2 plus connection status
325
     * * `4` Low-level data output
326
     * @var integer
327
     * @see SMTP::$do_debug
328
     */
329
    public $SMTPDebug = 0;
330
331
    /**
332
     * How to handle debug output.
333
     * Options:
334
     * * `echo` Output plain-text as-is, appropriate for CLI
335
     * * `html` Output escaped, line breaks converted to `<br>`, appropriate for browser output
336
     * * `error_log` Output to error log as configured in php.ini
337
     *
338
     * Alternatively, you can provide a callable expecting two params: a message string and the debug level:
339
     * <code>
340
     * $mail->Debugoutput = function($str, $level) {echo "debug level $level; message: $str";};
341
     * </code>
342
     * @var string|callable
343
     * @see SMTP::$Debugoutput
344
     */
345
    public $Debugoutput = 'echo';
346
347
    /**
348
     * Whether to keep SMTP connection open after each message.
349
     * If this is set to true then to close the connection
350
     * requires an explicit call to smtpClose().
351
     * @var boolean
352
     */
353
    public $SMTPKeepAlive = false;
354
355
    /**
356
     * Whether to split multiple to addresses into multiple messages
357
     * or send them all in one message.
358
     * Only supported in `mail` and `sendmail` transports, not in SMTP.
359
     * @var boolean
360
     */
361
    public $SingleTo = false;
362
363
    /**
364
     * Storage for addresses when SingleTo is enabled.
365
     * @var array
366
     * @TODO This should really not be public
367
     */
368
    public $SingleToArray = array();
369
370
    /**
371
     * Whether to generate VERP addresses on send.
372
     * Only applicable when sending via SMTP.
373
     * @link https://en.wikipedia.org/wiki/Variable_envelope_return_path
374
     * @link http://www.postfix.org/VERP_README.html Postfix VERP info
375
     * @var boolean
376
     */
377
    public $do_verp = false;
378
379
    /**
380
     * Whether to allow sending messages with an empty body.
381
     * @var boolean
382
     */
383
    public $AllowEmpty = false;
384
385
    /**
386
     * The default line ending.
387
     * @note The default remains "\n". We force CRLF where we know
388
     *        it must be used via self::CRLF.
389
     * @var string
390
     */
391
    public $LE = "\n";
392
393
    /**
394
     * DKIM selector.
395
     * @var string
396
     */
397
    public $DKIM_selector = '';
398
399
    /**
400
     * DKIM Identity.
401
     * Usually the email address used as the source of the email.
402
     * @var string
403
     */
404
    public $DKIM_identity = '';
405
406
    /**
407
     * DKIM passphrase.
408
     * Used if your key is encrypted.
409
     * @var string
410
     */
411
    public $DKIM_passphrase = '';
412
413
    /**
414
     * DKIM signing domain name.
415
     * @example 'example.com'
416
     * @var string
417
     */
418
    public $DKIM_domain = '';
419
420
    /**
421
     * DKIM private key file path.
422
     * @var string
423
     */
424
    public $DKIM_private = '';
425
426
    /**
427
     * DKIM private key string.
428
     * If set, takes precedence over `$DKIM_private`.
429
     * @var string
430
     */
431
    public $DKIM_private_string = '';
432
433
    /**
434
     * Callback Action function name.
435
     *
436
     * The function that handles the result of the send email action.
437
     * It is called out by send() for each email sent.
438
     *
439
     * Value can be any php callable: http://www.php.net/is_callable
440
     *
441
     * Parameters:
442
     *   boolean $result        result of the send action
443
     *   string  $to            email address of the recipient
444
     *   string  $cc            cc email addresses
445
     *   string  $bcc           bcc email addresses
446
     *   string  $subject       the subject
447
     *   string  $body          the email body
448
     *   string  $from          email address of sender
449
     * @var string
450
     */
451
    public $action_function = '';
452
453
    /**
454
     * What to put in the X-Mailer header.
455
     * Options: An empty string for PHPMailer default, whitespace for none, or a string to use
456
     * @var string
457
     */
458
    public $XMailer = '';
459
460
    /**
461
     * Which validator to use by default when validating email addresses.
462
     * May be a callable to inject your own validator, but there are several built-in validators.
463
     * @see PHPMailer::validateAddress()
464
     * @var string|callable
465
     * @static
466
     */
467
    public static $validator = 'auto';
468
469
    /**
470
     * An instance of the SMTP sender class.
471
     * @var SMTP
472
     * @access protected
473
     */
474
    protected $smtp = null;
475
476
    /**
477
     * The array of 'to' names and addresses.
478
     * @var array
479
     * @access protected
480
     */
481
    protected $to = array();
482
483
    /**
484
     * The array of 'cc' names and addresses.
485
     * @var array
486
     * @access protected
487
     */
488
    protected $cc = array();
489
490
    /**
491
     * The array of 'bcc' names and addresses.
492
     * @var array
493
     * @access protected
494
     */
495
    protected $bcc = array();
496
497
    /**
498
     * The array of reply-to names and addresses.
499
     * @var array
500
     * @access protected
501
     */
502
    protected $ReplyTo = array();
503
504
    /**
505
     * An array of all kinds of addresses.
506
     * Includes all of $to, $cc, $bcc
507
     * @var array
508
     * @access protected
509
     * @see PHPMailer::$to @see PHPMailer::$cc @see PHPMailer::$bcc
510
     */
511
    protected $all_recipients = array();
512
513
    /**
514
     * An array of names and addresses queued for validation.
515
     * In send(), valid and non duplicate entries are moved to $all_recipients
516
     * and one of $to, $cc, or $bcc.
517
     * This array is used only for addresses with IDN.
518
     * @var array
519
     * @access protected
520
     * @see PHPMailer::$to @see PHPMailer::$cc @see PHPMailer::$bcc
521
     * @see PHPMailer::$all_recipients
522
     */
523
    protected $RecipientsQueue = array();
524
525
    /**
526
     * An array of reply-to names and addresses queued for validation.
527
     * In send(), valid and non duplicate entries are moved to $ReplyTo.
528
     * This array is used only for addresses with IDN.
529
     * @var array
530
     * @access protected
531
     * @see PHPMailer::$ReplyTo
532
     */
533
    protected $ReplyToQueue = array();
534
535
    /**
536
     * The array of attachments.
537
     * @var array
538
     * @access protected
539
     */
540
    protected $attachment = array();
541
542
    /**
543
     * The array of custom headers.
544
     * @var array
545
     * @access protected
546
     */
547
    protected $CustomHeader = array();
548
549
    /**
550
     * The most recent Message-ID (including angular brackets).
551
     * @var string
552
     * @access protected
553
     */
554
    protected $lastMessageID = '';
555
556
    /**
557
     * The message's MIME type.
558
     * @var string
559
     * @access protected
560
     */
561
    protected $message_type = '';
562
563
    /**
564
     * The array of MIME boundary strings.
565
     * @var array
566
     * @access protected
567
     */
568
    protected $boundary = array();
569
570
    /**
571
     * The array of available languages.
572
     * @var array
573
     * @access protected
574
     */
575
    protected $language = array();
576
577
    /**
578
     * The number of errors encountered.
579
     * @var integer
580
     * @access protected
581
     */
582
    protected $error_count = 0;
583
584
    /**
585
     * The S/MIME certificate file path.
586
     * @var string
587
     * @access protected
588
     */
589
    protected $sign_cert_file = '';
590
591
    /**
592
     * The S/MIME key file path.
593
     * @var string
594
     * @access protected
595
     */
596
    protected $sign_key_file = '';
597
598
    /**
599
     * The optional S/MIME extra certificates ("CA Chain") file path.
600
     * @var string
601
     * @access protected
602
     */
603
    protected $sign_extracerts_file = '';
604
605
    /**
606
     * The S/MIME password for the key.
607
     * Used only if the key is encrypted.
608
     * @var string
609
     * @access protected
610
     */
611
    protected $sign_key_pass = '';
612
613
    /**
614
     * Whether to throw exceptions for errors.
615
     * @var boolean
616
     * @access protected
617
     */
618
    protected $exceptions = false;
619
620
    /**
621
     * Unique ID used for message ID and boundaries.
622
     * @var string
623
     * @access protected
624
     */
625
    protected $uniqueid = '';
626
627
    /**
628
     * Error severity: message only, continue processing.
629
     */
630
    const STOP_MESSAGE = 0;
631
632
    /**
633
     * Error severity: message, likely ok to continue processing.
634
     */
635
    const STOP_CONTINUE = 1;
636
637
    /**
638
     * Error severity: message, plus full stop, critical error reached.
639
     */
640
    const STOP_CRITICAL = 2;
641
642
    /**
643
     * SMTP RFC standard line ending.
644
     */
645
    const CRLF = "\r\n";
646
647
    /**
648
     * The maximum line length allowed by RFC 2822 section 2.1.1
649
     * @var integer
650
     */
651
    const MAX_LINE_LENGTH = 998;
652
653
    /**
654
     * Constructor.
655
     * @param boolean $exceptions Should we throw external exceptions?
656
     */
657
    public function __construct($exceptions = null)
658
    {
659
        if ($exceptions !== null) {
660
            $this->exceptions = (boolean)$exceptions;
661
        }
662
    }
663
664
    /**
665
     * Destructor.
666
     */
667
    public function __destruct()
668
    {
669
        //Close any open SMTP connection nicely
670
        $this->smtpClose();
671
    }
672
673
    /**
674
     * Call mail() in a safe_mode-aware fashion.
675
     * Also, unless sendmail_path points to sendmail (or something that
676
     * claims to be sendmail), don't pass params (not a perfect fix,
677
     * but it will do)
678
     * @param string $to To
679
     * @param string $subject Subject
680
     * @param string $body Message Body
681
     * @param string $header Additional Header(s)
682
     * @param string $params Params
683
     * @access private
684
     * @return boolean
685
     */
686
    private function mailPassthru($to, $subject, $body, $header, $params)
687
    {
688
        //Check overloading of mail function to avoid double-encoding
689
        if (ini_get('mbstring.func_overload') & 1) {
690
            $subject = $this->secureHeader($subject);
691
        } else {
692
            $subject = $this->encodeHeader($this->secureHeader($subject));
693
        }
694
695
        //Can't use additional_parameters in safe_mode, calling mail() with null params breaks
696
        //@link http://php.net/manual/en/function.mail.php
697
        if (ini_get('safe_mode') or !$this->UseSendmailOptions or is_null($params)) {
698
            $result = @mail($to, $subject, $body, $header);
699
        } else {
700
            $result = @mail($to, $subject, $body, $header, $params);
701
        }
702
        return $result;
703
    }
704
    /**
705
     * Output debugging info via user-defined method.
706
     * Only generates output if SMTP debug output is enabled (@see SMTP::$do_debug).
707
     * @see PHPMailer::$Debugoutput
708
     * @see PHPMailer::$SMTPDebug
709
     * @param string $str
710
     */
711 View Code Duplication
    protected function edebug($str)
712
    {
713
        if ($this->SMTPDebug <= 0) {
714
            return;
715
        }
716
        //Avoid clash with built-in function names
717
        if (!in_array($this->Debugoutput, array('error_log', 'html', 'echo')) and is_callable($this->Debugoutput)) {
718
            call_user_func($this->Debugoutput, $str, $this->SMTPDebug);
719
            return;
720
        }
721
        switch ($this->Debugoutput) {
722
            case 'error_log':
723
                //Don't output, just log
724
                error_log($str);
725
                break;
726
            case 'html':
727
                //Cleans up output a bit for a better looking, HTML-safe output
728
                echo htmlentities(
729
                    preg_replace('/[\r\n]+/', '', $str),
730
                    ENT_QUOTES,
731
                    'UTF-8'
732
                )
733
                . "<br>\n";
734
                break;
735
            case 'echo':
736
            default:
737
                //Normalize line breaks
738
                $str = preg_replace('/\r\n?/ms', "\n", $str);
739
                echo gmdate('Y-m-d H:i:s') . "\t" . str_replace(
740
                    "\n",
741
                    "\n                   \t                  ",
742
                    trim($str)
743
                ) . "\n";
744
        }
745
    }
746
747
    /**
748
     * Sets message type to HTML or plain.
749
     * @param boolean $isHtml True for HTML mode.
750
     * @return void
751
     */
752
    public function isHTML($isHtml = true)
753
    {
754
        if ($isHtml) {
755
            $this->ContentType = 'text/html';
756
        } else {
757
            $this->ContentType = 'text/plain';
758
        }
759
    }
760
761
    /**
762
     * Send messages using SMTP.
763
     * @return void
764
     */
765
    public function isSMTP()
766
    {
767
        $this->Mailer = 'smtp';
768
    }
769
770
    /**
771
     * Send messages using PHP's mail() function.
772
     * @return void
773
     */
774
    public function isMail()
775
    {
776
        $this->Mailer = 'mail';
777
    }
778
779
    /**
780
     * Send messages using $Sendmail.
781
     * @return void
782
     */
783 View Code Duplication
    public function isSendmail()
784
    {
785
        $ini_sendmail_path = ini_get('sendmail_path');
786
787
        if (!stristr($ini_sendmail_path, 'sendmail')) {
788
            $this->Sendmail = '/usr/sbin/sendmail';
789
        } else {
790
            $this->Sendmail = $ini_sendmail_path;
791
        }
792
        $this->Mailer = 'sendmail';
793
    }
794
795
    /**
796
     * Send messages using qmail.
797
     * @return void
798
     */
799 View Code Duplication
    public function isQmail()
800
    {
801
        $ini_sendmail_path = ini_get('sendmail_path');
802
803
        if (!stristr($ini_sendmail_path, 'qmail')) {
804
            $this->Sendmail = '/var/qmail/bin/qmail-inject';
805
        } else {
806
            $this->Sendmail = $ini_sendmail_path;
807
        }
808
        $this->Mailer = 'qmail';
809
    }
810
811
    /**
812
     * Add a "To" address.
813
     * @param string $address The email address to send to
814
     * @param string $name
815
     * @return boolean true on success, false if address already used or invalid in some way
816
     */
817
    public function addAddress($address, $name = '')
818
    {
819
        return $this->addOrEnqueueAnAddress('to', $address, $name);
820
    }
821
822
    /**
823
     * Add a "CC" address.
824
     * @note: This function works with the SMTP mailer on win32, not with the "mail" mailer.
825
     * @param string $address The email address to send to
826
     * @param string $name
827
     * @return boolean true on success, false if address already used or invalid in some way
828
     */
829
    public function addCC($address, $name = '')
830
    {
831
        return $this->addOrEnqueueAnAddress('cc', $address, $name);
832
    }
833
834
    /**
835
     * Add a "BCC" address.
836
     * @note: This function works with the SMTP mailer on win32, not with the "mail" mailer.
837
     * @param string $address The email address to send to
838
     * @param string $name
839
     * @return boolean true on success, false if address already used or invalid in some way
840
     */
841
    public function addBCC($address, $name = '')
842
    {
843
        return $this->addOrEnqueueAnAddress('bcc', $address, $name);
844
    }
845
846
    /**
847
     * Add a "Reply-To" address.
848
     * @param string $address The email address to reply to
849
     * @param string $name
850
     * @return boolean true on success, false if address already used or invalid in some way
851
     */
852
    public function addReplyTo($address, $name = '')
853
    {
854
        return $this->addOrEnqueueAnAddress('Reply-To', $address, $name);
855
    }
856
857
    /**
858
     * Add an address to one of the recipient arrays or to the ReplyTo array. Because PHPMailer
859
     * can't validate addresses with an IDN without knowing the PHPMailer::$CharSet (that can still
860
     * be modified after calling this function), addition of such addresses is delayed until send().
861
     * Addresses that have been added already return false, but do not throw exceptions.
862
     * @param string $kind One of 'to', 'cc', 'bcc', or 'ReplyTo'
863
     * @param string $address The email address to send, resp. to reply to
864
     * @param string $name
865
     * @throws phpmailerException
866
     * @return boolean true on success, false if address already used or invalid in some way
867
     * @access protected
868
     */
869
    protected function addOrEnqueueAnAddress($kind, $address, $name)
870
    {
871
        $address = trim($address);
872
        $name = trim(preg_replace('/[\r\n]+/', '', $name)); //Strip breaks and trim
873
        if (($pos = strrpos($address, '@')) === false) {
874
            // At-sign is misssing.
875
            $error_message = $this->lang('invalid_address') . " (addAnAddress $kind): $address";
876
            $this->setError($error_message);
877
            $this->edebug($error_message);
878
            if ($this->exceptions) {
879
                throw new phpmailerException($error_message);
880
            }
881
            return false;
882
        }
883
        $params = array($kind, $address, $name);
884
        // Enqueue addresses with IDN until we know the PHPMailer::$CharSet.
885
        if ($this->has8bitChars(substr($address, ++$pos)) and $this->idnSupported()) {
886
            if ($kind != 'Reply-To') {
887
                if (!array_key_exists($address, $this->RecipientsQueue)) {
888
                    $this->RecipientsQueue[$address] = $params;
889
                    return true;
890
                }
891
            } else {
892
                if (!array_key_exists($address, $this->ReplyToQueue)) {
893
                    $this->ReplyToQueue[$address] = $params;
894
                    return true;
895
                }
896
            }
897
            return false;
898
        }
899
        // Immediately add standard addresses without IDN.
900
        return call_user_func_array(array($this, 'addAnAddress'), $params);
901
    }
902
903
    /**
904
     * Add an address to one of the recipient arrays or to the ReplyTo array.
905
     * Addresses that have been added already return false, but do not throw exceptions.
906
     * @param string $kind One of 'to', 'cc', 'bcc', or 'ReplyTo'
907
     * @param string $address The email address to send, resp. to reply to
908
     * @param string $name
909
     * @throws phpmailerException
910
     * @return boolean true on success, false if address already used or invalid in some way
911
     * @access protected
912
     */
913
    protected function addAnAddress($kind, $address, $name = '')
914
    {
915
        if (!in_array($kind, array('to', 'cc', 'bcc', 'Reply-To'))) {
916
            $error_message = $this->lang('Invalid recipient kind: ') . $kind;
917
            $this->setError($error_message);
918
            $this->edebug($error_message);
919
            if ($this->exceptions) {
920
                throw new phpmailerException($error_message);
921
            }
922
            return false;
923
        }
924
        if (!$this->validateAddress($address)) {
925
            $error_message = $this->lang('invalid_address') . " (addAnAddress $kind): $address";
926
            $this->setError($error_message);
927
            $this->edebug($error_message);
928
            if ($this->exceptions) {
929
                throw new phpmailerException($error_message);
930
            }
931
            return false;
932
        }
933
        if ($kind != 'Reply-To') {
934 View Code Duplication
            if (!array_key_exists(strtolower($address), $this->all_recipients)) {
935
                array_push($this->$kind, array($address, $name));
936
                $this->all_recipients[strtolower($address)] = true;
937
                return true;
938
            }
939
        } else {
940 View Code Duplication
            if (!array_key_exists(strtolower($address), $this->ReplyTo)) {
941
                $this->ReplyTo[strtolower($address)] = array($address, $name);
942
                return true;
943
            }
944
        }
945
        return false;
946
    }
947
948
    /**
949
     * Parse and validate a string containing one or more RFC822-style comma-separated email addresses
950
     * of the form "display name <address>" into an array of name/address pairs.
951
     * Uses the imap_rfc822_parse_adrlist function if the IMAP extension is available.
952
     * Note that quotes in the name part are removed.
953
     * @param string $addrstr The address list string
954
     * @param bool $useimap Whether to use the IMAP extension to parse the list
955
     * @return array
956
     * @link http://www.andrew.cmu.edu/user/agreen1/testing/mrbs/web/Mail/RFC822.php A more careful implementation
957
     */
958
    public function parseAddresses($addrstr, $useimap = true)
959
    {
960
        $addresses = array();
961
        if ($useimap and function_exists('imap_rfc822_parse_adrlist')) {
962
            //Use this built-in parser if it's available
963
            $list = imap_rfc822_parse_adrlist($addrstr, '');
964
            foreach ($list as $address) {
965
                if ($address->host != '.SYNTAX-ERROR.') {
966
                    if ($this->validateAddress($address->mailbox . '@' . $address->host)) {
967
                        $addresses[] = array(
968
                            'name' => (property_exists($address, 'personal') ? $address->personal : ''),
969
                            'address' => $address->mailbox . '@' . $address->host
970
                        );
971
                    }
972
                }
973
            }
974
        } else {
975
            //Use this simpler parser
976
            $list = explode(',', $addrstr);
977
            foreach ($list as $address) {
978
                $address = trim($address);
979
                //Is there a separate name part?
980
                if (strpos($address, '<') === false) {
981
                    //No separate name, just use the whole thing
982
                    if ($this->validateAddress($address)) {
983
                        $addresses[] = array(
984
                            'name' => '',
985
                            'address' => $address
986
                        );
987
                    }
988
                } else {
989
                    list($name, $email) = explode('<', $address);
990
                    $email = trim(str_replace('>', '', $email));
991
                    if ($this->validateAddress($email)) {
992
                        $addresses[] = array(
993
                            'name' => trim(str_replace(array('"', "'"), '', $name)),
994
                            'address' => $email
995
                        );
996
                    }
997
                }
998
            }
999
        }
1000
        return $addresses;
1001
    }
1002
1003
    /**
1004
     * Set the From and FromName properties.
1005
     * @param string $address
1006
     * @param string $name
1007
     * @param boolean $auto Whether to also set the Sender address, defaults to true
1008
     * @throws phpmailerException
1009
     * @return boolean
1010
     */
1011
    public function setFrom($address, $name = '', $auto = true)
1012
    {
1013
        $address = trim($address);
1014
        $name = trim(preg_replace('/[\r\n]+/', '', $name)); //Strip breaks and trim
1015
        // Don't validate now addresses with IDN. Will be done in send().
1016
        if (($pos = strrpos($address, '@')) === false or
1017
            (!$this->has8bitChars(substr($address, ++$pos)) or !$this->idnSupported()) and
1018
            !$this->validateAddress($address)) {
1019
            $error_message = $this->lang('invalid_address') . " (setFrom) $address";
1020
            $this->setError($error_message);
1021
            $this->edebug($error_message);
1022
            if ($this->exceptions) {
1023
                throw new phpmailerException($error_message);
1024
            }
1025
            return false;
1026
        }
1027
        $this->From = $address;
1028
        $this->FromName = $name;
1029
        if ($auto) {
1030
            if (empty($this->Sender)) {
1031
                $this->Sender = $address;
1032
            }
1033
        }
1034
        return true;
1035
    }
1036
1037
    /**
1038
     * Return the Message-ID header of the last email.
1039
     * Technically this is the value from the last time the headers were created,
1040
     * but it's also the message ID of the last sent message except in
1041
     * pathological cases.
1042
     * @return string
1043
     */
1044
    public function getLastMessageID()
1045
    {
1046
        return $this->lastMessageID;
1047
    }
1048
1049
    /**
1050
     * Check that a string looks like an email address.
1051
     * @param string $address The email address to check
1052
     * @param string|callable $patternselect A selector for the validation pattern to use :
0 ignored issues
show
Should the type for parameter $patternselect not be callable|null? Also, consider making the array more specific, something like array<String>, or String[].

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive. In addition it looks for parameters that have the generic type array and suggests a stricter type like array<String>.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
1053
     * * `auto` Pick best pattern automatically;
1054
     * * `pcre8` Use the squiloople.com pattern, requires PCRE > 8.0, PHP >= 5.3.2, 5.2.14;
1055
     * * `pcre` Use old PCRE implementation;
1056
     * * `php` Use PHP built-in FILTER_VALIDATE_EMAIL;
1057
     * * `html5` Use the pattern given by the HTML5 spec for 'email' type form input elements.
1058
     * * `noregex` Don't use a regex: super fast, really dumb.
1059
     * Alternatively you may pass in a callable to inject your own validator, for example:
1060
     * PHPMailer::validateAddress('[email protected]', function($address) {
1061
     *     return (strpos($address, '@') !== false);
1062
     * });
1063
     * You can also set the PHPMailer::$validator static to a callable, allowing built-in methods to use your validator.
1064
     * @return boolean
1065
     * @static
1066
     * @access public
1067
     */
1068
    public static function validateAddress($address, $patternselect = null)
1069
    {
1070
        if (is_null($patternselect)) {
1071
            $patternselect = self::$validator;
1072
        }
1073
        if (is_callable($patternselect)) {
1074
            return call_user_func($patternselect, $address);
1075
        }
1076
        //Reject line breaks in addresses; it's valid RFC5322, but not RFC5321
1077
        if (strpos($address, "\n") !== false or strpos($address, "\r") !== false) {
1078
            return false;
1079
        }
1080
        if (!$patternselect or $patternselect == 'auto') {
1081
            //Check this constant first so it works when extension_loaded() is disabled by safe mode
1082
            //Constant was added in PHP 5.2.4
1083
            if (defined('PCRE_VERSION')) {
1084
                //This pattern can get stuck in a recursive loop in PCRE <= 8.0.2
1085
                if (version_compare(PCRE_VERSION, '8.0.3') >= 0) {
1086
                    $patternselect = 'pcre8';
1087
                } else {
1088
                    $patternselect = 'pcre';
1089
                }
1090
            } elseif (function_exists('extension_loaded') and extension_loaded('pcre')) {
1091
                //Fall back to older PCRE
1092
                $patternselect = 'pcre';
1093
            } else {
1094
                //Filter_var appeared in PHP 5.2.0 and does not require the PCRE extension
1095
                if (version_compare(PHP_VERSION, '5.2.0') >= 0) {
1096
                    $patternselect = 'php';
1097
                } else {
1098
                    $patternselect = 'noregex';
1099
                }
1100
            }
1101
        }
1102
        switch ($patternselect) {
1103 View Code Duplication
            case 'pcre8':
1104
                /**
1105
                 * Uses the same RFC5322 regex on which FILTER_VALIDATE_EMAIL is based, but allows dotless domains.
1106
                 * @link http://squiloople.com/2009/12/20/email-address-validation/
1107
                 * @copyright 2009-2010 Michael Rushton
1108
                 * Feel free to use and redistribute this code. But please keep this copyright notice.
1109
                 */
1110
                return (boolean)preg_match(
1111
                    '/^(?!(?>(?1)"?(?>\\\[ -~]|[^"])"?(?1)){255,})(?!(?>(?1)"?(?>\\\[ -~]|[^"])"?(?1)){65,}@)' .
1112
                    '((?>(?>(?>((?>(?>(?>\x0D\x0A)?[\t ])+|(?>[\t ]*\x0D\x0A)?[\t ]+)?)(\((?>(?2)' .
1113
                    '(?>[\x01-\x08\x0B\x0C\x0E-\'*-\[\]-\x7F]|\\\[\x00-\x7F]|(?3)))*(?2)\)))+(?2))|(?2))?)' .
1114
                    '([!#-\'*+\/-9=?^-~-]+|"(?>(?2)(?>[\x01-\x08\x0B\x0C\x0E-!#-\[\]-\x7F]|\\\[\x00-\x7F]))*' .
1115
                    '(?2)")(?>(?1)\.(?1)(?4))*(?1)@(?!(?1)[a-z0-9-]{64,})(?1)(?>([a-z0-9](?>[a-z0-9-]*[a-z0-9])?)' .
1116
                    '(?>(?1)\.(?!(?1)[a-z0-9-]{64,})(?1)(?5)){0,126}|\[(?:(?>IPv6:(?>([a-f0-9]{1,4})(?>:(?6)){7}' .
1117
                    '|(?!(?:.*[a-f0-9][:\]]){8,})((?6)(?>:(?6)){0,6})?::(?7)?))|(?>(?>IPv6:(?>(?6)(?>:(?6)){5}:' .
1118
                    '|(?!(?:.*[a-f0-9]:){6,})(?8)?::(?>((?6)(?>:(?6)){0,4}):)?))?(25[0-5]|2[0-4][0-9]|1[0-9]{2}' .
1119
                    '|[1-9]?[0-9])(?>\.(?9)){3}))\])(?1)$/isD',
1120
                    $address
1121
                );
1122 View Code Duplication
            case 'pcre':
1123
                //An older regex that doesn't need a recent PCRE
1124
                return (boolean)preg_match(
1125
                    '/^(?!(?>"?(?>\\\[ -~]|[^"])"?){255,})(?!(?>"?(?>\\\[ -~]|[^"])"?){65,}@)(?>' .
1126
                    '[!#-\'*+\/-9=?^-~-]+|"(?>(?>[\x01-\x08\x0B\x0C\x0E-!#-\[\]-\x7F]|\\\[\x00-\xFF]))*")' .
1127
                    '(?>\.(?>[!#-\'*+\/-9=?^-~-]+|"(?>(?>[\x01-\x08\x0B\x0C\x0E-!#-\[\]-\x7F]|\\\[\x00-\xFF]))*"))*' .
1128
                    '@(?>(?![a-z0-9-]{64,})(?>[a-z0-9](?>[a-z0-9-]*[a-z0-9])?)(?>\.(?![a-z0-9-]{64,})' .
1129
                    '(?>[a-z0-9](?>[a-z0-9-]*[a-z0-9])?)){0,126}|\[(?:(?>IPv6:(?>(?>[a-f0-9]{1,4})(?>:' .
1130
                    '[a-f0-9]{1,4}){7}|(?!(?:.*[a-f0-9][:\]]){8,})(?>[a-f0-9]{1,4}(?>:[a-f0-9]{1,4}){0,6})?' .
1131
                    '::(?>[a-f0-9]{1,4}(?>:[a-f0-9]{1,4}){0,6})?))|(?>(?>IPv6:(?>[a-f0-9]{1,4}(?>:' .
1132
                    '[a-f0-9]{1,4}){5}:|(?!(?:.*[a-f0-9]:){6,})(?>[a-f0-9]{1,4}(?>:[a-f0-9]{1,4}){0,4})?' .
1133
                    '::(?>(?:[a-f0-9]{1,4}(?>:[a-f0-9]{1,4}){0,4}):)?))?(?>25[0-5]|2[0-4][0-9]|1[0-9]{2}' .
1134
                    '|[1-9]?[0-9])(?>\.(?>25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3}))\])$/isD',
1135
                    $address
1136
                );
1137
            case 'html5':
1138
                /**
1139
                 * This is the pattern used in the HTML5 spec for validation of 'email' type form input elements.
1140
                 * @link http://www.whatwg.org/specs/web-apps/current-work/#e-mail-state-(type=email)
1141
                 */
1142
                return (boolean)preg_match(
1143
                    '/^[a-zA-Z0-9.!#$%&\'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}' .
1144
                    '[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/sD',
1145
                    $address
1146
                );
1147
            case 'noregex':
1148
                //No PCRE! Do something _very_ approximate!
1149
                //Check the address is 3 chars or longer and contains an @ that's not the first or last char
1150
                return (strlen($address) >= 3
1151
                    and strpos($address, '@') >= 1
1152
                    and strpos($address, '@') != strlen($address) - 1);
1153
            case 'php':
1154
            default:
1155
                return (boolean)filter_var($address, FILTER_VALIDATE_EMAIL);
1156
        }
1157
    }
1158
1159
    /**
1160
     * Tells whether IDNs (Internationalized Domain Names) are supported or not. This requires the
1161
     * "intl" and "mbstring" PHP extensions.
1162
     * @return bool "true" if required functions for IDN support are present
1163
     */
1164
    public function idnSupported()
1165
    {
1166
        // @TODO: Write our own "idn_to_ascii" function for PHP <= 5.2.
1167
        return function_exists('idn_to_ascii') and function_exists('mb_convert_encoding');
1168
    }
1169
1170
    /**
1171
     * Converts IDN in given email address to its ASCII form, also known as punycode, if possible.
1172
     * Important: Address must be passed in same encoding as currently set in PHPMailer::$CharSet.
1173
     * This function silently returns unmodified address if:
1174
     * - No conversion is necessary (i.e. domain name is not an IDN, or is already in ASCII form)
1175
     * - Conversion to punycode is impossible (e.g. required PHP functions are not available)
1176
     *   or fails for any reason (e.g. domain has characters not allowed in an IDN)
1177
     * @see PHPMailer::$CharSet
1178
     * @param string $address The email address to convert
1179
     * @return string The encoded address in ASCII form
1180
     */
1181
    public function punyencodeAddress($address)
1182
    {
1183
        // Verify we have required functions, CharSet, and at-sign.
1184
        if ($this->idnSupported() and
1185
            !empty($this->CharSet) and
1186
            ($pos = strrpos($address, '@')) !== false) {
1187
            $domain = substr($address, ++$pos);
1188
            // Verify CharSet string is a valid one, and domain properly encoded in this CharSet.
1189
            if ($this->has8bitChars($domain) and @mb_check_encoding($domain, $this->CharSet)) {
1190
                $domain = mb_convert_encoding($domain, 'UTF-8', $this->CharSet);
1191
                if (($punycode = defined('INTL_IDNA_VARIANT_UTS46') ?
1192
                    idn_to_ascii($domain, 0, INTL_IDNA_VARIANT_UTS46) :
1193
                    idn_to_ascii($domain)) !== false) {
1194
                    return substr($address, 0, $pos) . $punycode;
1195
                }
1196
            }
1197
        }
1198
        return $address;
1199
    }
1200
1201
    /**
1202
     * Create a message and send it.
1203
     * Uses the sending method specified by $Mailer.
1204
     * @throws phpmailerException
1205
     * @return boolean false on error - See the ErrorInfo property for details of the error.
1206
     */
1207
    public function send()
1208
    {
1209
        try {
1210
            if (!$this->preSend()) {
1211
                return false;
1212
            }
1213
            return $this->postSend();
1214
        } catch (phpmailerException $exc) {
1215
            $this->mailHeader = '';
1216
            $this->setError($exc->getMessage());
1217
            if ($this->exceptions) {
1218
                throw $exc;
1219
            }
1220
            return false;
1221
        }
1222
    }
1223
1224
    /**
1225
     * Prepare a message for sending.
1226
     * @throws phpmailerException
1227
     * @return boolean
1228
     */
1229
    public function preSend()
1230
    {
1231
        try {
1232
            $this->error_count = 0; // Reset errors
1233
            $this->mailHeader = '';
1234
1235
            // Dequeue recipient and Reply-To addresses with IDN
1236
            foreach (array_merge($this->RecipientsQueue, $this->ReplyToQueue) as $params) {
1237
                $params[1] = $this->punyencodeAddress($params[1]);
1238
                call_user_func_array(array($this, 'addAnAddress'), $params);
1239
            }
1240
            if ((count($this->to) + count($this->cc) + count($this->bcc)) < 1) {
1241
                throw new phpmailerException($this->lang('provide_address'), self::STOP_CRITICAL);
1242
            }
1243
1244
            // Validate From, Sender, and ConfirmReadingTo addresses
1245
            foreach (array('From', 'Sender', 'ConfirmReadingTo') as $address_kind) {
1246
                $this->$address_kind = trim($this->$address_kind);
1247
                if (empty($this->$address_kind)) {
1248
                    continue;
1249
                }
1250
                $this->$address_kind = $this->punyencodeAddress($this->$address_kind);
1251
                if (!$this->validateAddress($this->$address_kind)) {
1252
                    $error_message = $this->lang('invalid_address') . ' (punyEncode) ' . $this->$address_kind;
1253
                    $this->setError($error_message);
1254
                    $this->edebug($error_message);
1255
                    if ($this->exceptions) {
1256
                        throw new phpmailerException($error_message);
1257
                    }
1258
                    return false;
1259
                }
1260
            }
1261
1262
            // Set whether the message is multipart/alternative
1263
            if ($this->alternativeExists()) {
1264
                $this->ContentType = 'multipart/alternative';
1265
            }
1266
1267
            $this->setMessageType();
1268
            // Refuse to send an empty message unless we are specifically allowing it
1269
            if (!$this->AllowEmpty and empty($this->Body)) {
1270
                throw new phpmailerException($this->lang('empty_message'), self::STOP_CRITICAL);
1271
            }
1272
1273
            // Create body before headers in case body makes changes to headers (e.g. altering transfer encoding)
1274
            $this->MIMEHeader = '';
1275
            $this->MIMEBody = $this->createBody();
1276
            // createBody may have added some headers, so retain them
1277
            $tempheaders = $this->MIMEHeader;
1278
            $this->MIMEHeader = $this->createHeader();
1279
            $this->MIMEHeader .= $tempheaders;
1280
1281
            // To capture the complete message when using mail(), create
1282
            // an extra header list which createHeader() doesn't fold in
1283
            if ($this->Mailer == 'mail') {
1284
                if (count($this->to) > 0) {
1285
                    $this->mailHeader .= $this->addrAppend('To', $this->to);
1286
                } else {
1287
                    $this->mailHeader .= $this->headerLine('To', 'undisclosed-recipients:;');
1288
                }
1289
                $this->mailHeader .= $this->headerLine(
1290
                    'Subject',
1291
                    $this->encodeHeader($this->secureHeader(trim($this->Subject)))
1292
                );
1293
            }
1294
1295
            // Sign with DKIM if enabled
1296
            if (!empty($this->DKIM_domain)
1297
                && !empty($this->DKIM_selector)
1298
                && (!empty($this->DKIM_private_string)
1299
                   || (!empty($this->DKIM_private) && file_exists($this->DKIM_private))
1300
                )
1301
            ) {
1302
                $header_dkim = $this->DKIM_Add(
1303
                    $this->MIMEHeader . $this->mailHeader,
1304
                    $this->encodeHeader($this->secureHeader($this->Subject)),
1305
                    $this->MIMEBody
1306
                );
1307
                $this->MIMEHeader = rtrim($this->MIMEHeader, "\r\n ") . self::CRLF .
1308
                    str_replace("\r\n", "\n", $header_dkim) . self::CRLF;
1309
            }
1310
            return true;
1311
        } catch (phpmailerException $exc) {
1312
            $this->setError($exc->getMessage());
1313
            if ($this->exceptions) {
1314
                throw $exc;
1315
            }
1316
            return false;
1317
        }
1318
    }
1319
1320
    /**
1321
     * Actually send a message.
1322
     * Send the email via the selected mechanism
1323
     * @throws phpmailerException
1324
     * @return boolean
1325
     */
1326
    public function postSend()
1327
    {
1328
        try {
1329
            // Choose the mailer and send through it
1330
            switch ($this->Mailer) {
1331
                case 'sendmail':
1332
                case 'qmail':
1333
                    return $this->sendmailSend($this->MIMEHeader, $this->MIMEBody);
1334
                case 'smtp':
1335
                    return $this->smtpSend($this->MIMEHeader, $this->MIMEBody);
1336
                case 'mail':
1337
                    return $this->mailSend($this->MIMEHeader, $this->MIMEBody);
1338
                default:
1339
                    $sendMethod = $this->Mailer.'Send';
1340
                    if (method_exists($this, $sendMethod)) {
1341
                        return $this->$sendMethod($this->MIMEHeader, $this->MIMEBody);
1342
                    }
1343
1344
                    return $this->mailSend($this->MIMEHeader, $this->MIMEBody);
1345
            }
1346
        } catch (phpmailerException $exc) {
1347
            $this->setError($exc->getMessage());
1348
            $this->edebug($exc->getMessage());
1349
            if ($this->exceptions) {
1350
                throw $exc;
1351
            }
1352
        }
1353
        return false;
1354
    }
1355
1356
    /**
1357
     * Send mail using the $Sendmail program.
1358
     * @param string $header The message headers
1359
     * @param string $body The message body
1360
     * @see PHPMailer::$Sendmail
1361
     * @throws phpmailerException
1362
     * @access protected
1363
     * @return boolean
1364
     */
1365
    protected function sendmailSend($header, $body)
1366
    {
1367
        // CVE-2016-10033, CVE-2016-10045: Don't pass -f if characters will be escaped.
1368
        if (!empty($this->Sender) and self::isShellSafe($this->Sender)) {
1369
            if ($this->Mailer == 'qmail') {
1370
                $sendmailFmt = '%s -f%s';
1371
            } else {
1372
                $sendmailFmt = '%s -oi -f%s -t';
1373
            }
1374
        } else {
1375
            if ($this->Mailer == 'qmail') {
1376
                $sendmailFmt = '%s';
1377
            } else {
1378
                $sendmailFmt = '%s -oi -t';
1379
            }
1380
        }
1381
1382
        // TODO: If possible, this should be changed to escapeshellarg.  Needs thorough testing.
1383
        $sendmail = sprintf($sendmailFmt, escapeshellcmd($this->Sendmail), $this->Sender);
1384
1385
        if ($this->SingleTo) {
1386
            foreach ($this->SingleToArray as $toAddr) {
1387 View Code Duplication
                if (!@$mail = popen($sendmail, 'w')) {
1388
                    throw new phpmailerException($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL);
1389
                }
1390
                fputs($mail, 'To: ' . $toAddr . "\n");
1391
                fputs($mail, $header);
1392
                fputs($mail, $body);
1393
                $result = pclose($mail);
1394
                $this->doCallback(
1395
                    ($result == 0),
1396
                    array($toAddr),
1397
                    $this->cc,
1398
                    $this->bcc,
1399
                    $this->Subject,
1400
                    $body,
1401
                    $this->From
1402
                );
1403
                if ($result != 0) {
1404
                    throw new phpmailerException($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL);
1405
                }
1406
            }
1407
        } else {
1408 View Code Duplication
            if (!@$mail = popen($sendmail, 'w')) {
1409
                throw new phpmailerException($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL);
1410
            }
1411
            fputs($mail, $header);
1412
            fputs($mail, $body);
1413
            $result = pclose($mail);
1414
            $this->doCallback(
1415
                ($result == 0),
1416
                $this->to,
1417
                $this->cc,
1418
                $this->bcc,
1419
                $this->Subject,
1420
                $body,
1421
                $this->From
1422
            );
1423
            if ($result != 0) {
1424
                throw new phpmailerException($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL);
1425
            }
1426
        }
1427
        return true;
1428
    }
1429
1430
    /**
1431
     * Fix CVE-2016-10033 and CVE-2016-10045 by disallowing potentially unsafe shell characters.
1432
     *
1433
     * Note that escapeshellarg and escapeshellcmd are inadequate for our purposes, especially on Windows.
1434
     * @param string $string The string to be validated
1435
     * @see https://github.com/PHPMailer/PHPMailer/issues/924 CVE-2016-10045 bug report
1436
     * @access protected
1437
     * @return boolean
1438
     */
1439
    protected static function isShellSafe($string)
1440
    {
1441
        // Future-proof
1442
        if (escapeshellcmd($string) !== $string
1443
            or !in_array(escapeshellarg($string), array("'$string'", "\"$string\""))
1444
        ) {
1445
            return false;
1446
        }
1447
1448
        $length = strlen($string);
1449
1450
        for ($i = 0; $i < $length; $i++) {
1451
            $c = $string[$i];
1452
1453
            // All other characters have a special meaning in at least one common shell, including = and +.
1454
            // Full stop (.) has a special meaning in cmd.exe, but its impact should be negligible here.
1455
            // Note that this does permit non-Latin alphanumeric characters based on the current locale.
1456
            if (!ctype_alnum($c) && strpos('@_-.', $c) === false) {
1457
                return false;
1458
            }
1459
        }
1460
1461
        return true;
1462
    }
1463
1464
    /**
1465
     * Send mail using the PHP mail() function.
1466
     * @param string $header The message headers
1467
     * @param string $body The message body
1468
     * @link http://www.php.net/manual/en/book.mail.php
1469
     * @throws phpmailerException
1470
     * @access protected
1471
     * @return boolean
1472
     */
1473
    protected function mailSend($header, $body)
1474
    {
1475
        $toArr = array();
1476
        foreach ($this->to as $toaddr) {
1477
            $toArr[] = $this->addrFormat($toaddr);
1478
        }
1479
        $to = implode(', ', $toArr);
1480
1481
        $params = null;
1482
        //This sets the SMTP envelope sender which gets turned into a return-path header by the receiver
1483
        if (!empty($this->Sender) and $this->validateAddress($this->Sender)) {
1484
            // CVE-2016-10033, CVE-2016-10045: Don't pass -f if characters will be escaped.
1485
            if (self::isShellSafe($this->Sender)) {
1486
                $params = sprintf('-f%s', $this->Sender);
1487
            }
1488
        }
1489
        if (!empty($this->Sender) and !ini_get('safe_mode') and $this->validateAddress($this->Sender)) {
1490
            $old_from = ini_get('sendmail_from');
1491
            ini_set('sendmail_from', $this->Sender);
1492
        }
1493
        $result = false;
1494
        if ($this->SingleTo and count($toArr) > 1) {
1495
            foreach ($toArr as $toAddr) {
1496
                $result = $this->mailPassthru($toAddr, $this->Subject, $body, $header, $params);
1497
                $this->doCallback($result, array($toAddr), $this->cc, $this->bcc, $this->Subject, $body, $this->From);
1498
            }
1499
        } else {
1500
            $result = $this->mailPassthru($to, $this->Subject, $body, $header, $params);
1501
            $this->doCallback($result, $this->to, $this->cc, $this->bcc, $this->Subject, $body, $this->From);
1502
        }
1503
        if (isset($old_from)) {
1504
            ini_set('sendmail_from', $old_from);
1505
        }
1506
        if (!$result) {
1507
            throw new phpmailerException($this->lang('instantiate'), self::STOP_CRITICAL);
1508
        }
1509
        return true;
1510
    }
1511
1512
    /**
1513
     * Get an instance to use for SMTP operations.
1514
     * Override this function to load your own SMTP implementation
1515
     * @return SMTP
1516
     */
1517
    public function getSMTPInstance()
1518
    {
1519
        if (!is_object($this->smtp)) {
1520
			require_once( 'class-smtp.php' );
1521
            $this->smtp = new SMTP;
1522
        }
1523
        return $this->smtp;
1524
    }
1525
1526
    /**
1527
     * Send mail via SMTP.
1528
     * Returns false if there is a bad MAIL FROM, RCPT, or DATA input.
1529
     * Uses the PHPMailerSMTP class by default.
1530
     * @see PHPMailer::getSMTPInstance() to use a different class.
1531
     * @param string $header The message headers
1532
     * @param string $body The message body
1533
     * @throws phpmailerException
1534
     * @uses SMTP
1535
     * @access protected
1536
     * @return boolean
1537
     */
1538
    protected function smtpSend($header, $body)
1539
    {
1540
        $bad_rcpt = array();
1541
        if (!$this->smtpConnect($this->SMTPOptions)) {
1542
            throw new phpmailerException($this->lang('smtp_connect_failed'), self::STOP_CRITICAL);
1543
        }
1544
        if (!empty($this->Sender) and $this->validateAddress($this->Sender)) {
1545
            $smtp_from = $this->Sender;
1546
        } else {
1547
            $smtp_from = $this->From;
1548
        }
1549
        if (!$this->smtp->mail($smtp_from)) {
1550
            $this->setError($this->lang('from_failed') . $smtp_from . ' : ' . implode(',', $this->smtp->getError()));
1551
            throw new phpmailerException($this->ErrorInfo, self::STOP_CRITICAL);
1552
        }
1553
1554
        // Attempt to send to all recipients
1555
        foreach (array($this->to, $this->cc, $this->bcc) as $togroup) {
1556
            foreach ($togroup as $to) {
1557
                if (!$this->smtp->recipient($to[0])) {
1558
                    $error = $this->smtp->getError();
1559
                    $bad_rcpt[] = array('to' => $to[0], 'error' => $error['detail']);
1560
                    $isSent = false;
1561
                } else {
1562
                    $isSent = true;
1563
                }
1564
                $this->doCallback($isSent, array($to[0]), array(), array(), $this->Subject, $body, $this->From);
1565
            }
1566
        }
1567
1568
        // Only send the DATA command if we have viable recipients
1569
        if ((count($this->all_recipients) > count($bad_rcpt)) and !$this->smtp->data($header . $body)) {
1570
            throw new phpmailerException($this->lang('data_not_accepted'), self::STOP_CRITICAL);
1571
        }
1572
        if ($this->SMTPKeepAlive) {
1573
            $this->smtp->reset();
1574
        } else {
1575
            $this->smtp->quit();
1576
            $this->smtp->close();
1577
        }
1578
        //Create error message for any bad addresses
1579
        if (count($bad_rcpt) > 0) {
1580
            $errstr = '';
1581
            foreach ($bad_rcpt as $bad) {
1582
                $errstr .= $bad['to'] . ': ' . $bad['error'];
1583
            }
1584
            throw new phpmailerException(
1585
                $this->lang('recipients_failed') . $errstr,
1586
                self::STOP_CONTINUE
1587
            );
1588
        }
1589
        return true;
1590
    }
1591
1592
    /**
1593
     * Initiate a connection to an SMTP server.
1594
     * Returns false if the operation failed.
1595
     * @param array $options An array of options compatible with stream_context_create()
0 ignored issues
show
Should the type for parameter $options not be array|null? Also, consider making the array more specific, something like array<String>, or String[].

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive. In addition it looks for parameters that have the generic type array and suggests a stricter type like array<String>.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
1596
     * @uses SMTP
1597
     * @access public
1598
     * @throws phpmailerException
1599
     * @return boolean
1600
     */
1601
    public function smtpConnect($options = null)
1602
    {
1603
        if (is_null($this->smtp)) {
1604
            $this->smtp = $this->getSMTPInstance();
1605
        }
1606
1607
        //If no options are provided, use whatever is set in the instance
1608
        if (is_null($options)) {
1609
            $options = $this->SMTPOptions;
1610
        }
1611
1612
        // Already connected?
1613
        if ($this->smtp->connected()) {
1614
            return true;
1615
        }
1616
1617
        $this->smtp->setTimeout($this->Timeout);
1618
        $this->smtp->setDebugLevel($this->SMTPDebug);
1619
        $this->smtp->setDebugOutput($this->Debugoutput);
1620
        $this->smtp->setVerp($this->do_verp);
1621
        $hosts = explode(';', $this->Host);
1622
        $lastexception = null;
1623
1624
        foreach ($hosts as $hostentry) {
1625
            $hostinfo = array();
1626
            if (!preg_match('/^((ssl|tls):\/\/)*([a-zA-Z0-9\.-]*):?([0-9]*)$/', trim($hostentry), $hostinfo)) {
1627
                // Not a valid host entry
1628
                continue;
1629
            }
1630
            // $hostinfo[2]: optional ssl or tls prefix
1631
            // $hostinfo[3]: the hostname
1632
            // $hostinfo[4]: optional port number
1633
            // The host string prefix can temporarily override the current setting for SMTPSecure
1634
            // If it's not specified, the default value is used
1635
            $prefix = '';
1636
            $secure = $this->SMTPSecure;
1637
            $tls = ($this->SMTPSecure == 'tls');
1638
            if ('ssl' == $hostinfo[2] or ('' == $hostinfo[2] and 'ssl' == $this->SMTPSecure)) {
1639
                $prefix = 'ssl://';
1640
                $tls = false; // Can't have SSL and TLS at the same time
1641
                $secure = 'ssl';
1642
            } elseif ($hostinfo[2] == 'tls') {
1643
                $tls = true;
1644
                // tls doesn't use a prefix
1645
                $secure = 'tls';
1646
            }
1647
            //Do we need the OpenSSL extension?
1648
            $sslext = defined('OPENSSL_ALGO_SHA1');
1649
            if ('tls' === $secure or 'ssl' === $secure) {
1650
                //Check for an OpenSSL constant rather than using extension_loaded, which is sometimes disabled
1651
                if (!$sslext) {
1652
                    throw new phpmailerException($this->lang('extension_missing').'openssl', self::STOP_CRITICAL);
1653
                }
1654
            }
1655
            $host = $hostinfo[3];
1656
            $port = $this->Port;
1657
            $tport = (integer)$hostinfo[4];
1658
            if ($tport > 0 and $tport < 65536) {
1659
                $port = $tport;
1660
            }
1661
            if ($this->smtp->connect($prefix . $host, $port, $this->Timeout, $options)) {
1662
                try {
1663
                    if ($this->Helo) {
1664
                        $hello = $this->Helo;
1665
                    } else {
1666
                        $hello = $this->serverHostname();
1667
                    }
1668
                    $this->smtp->hello($hello);
1669
                    //Automatically enable TLS encryption if:
1670
                    // * it's not disabled
1671
                    // * we have openssl extension
1672
                    // * we are not already using SSL
1673
                    // * the server offers STARTTLS
1674
                    if ($this->SMTPAutoTLS and $sslext and $secure != 'ssl' and $this->smtp->getServerExt('STARTTLS')) {
1675
                        $tls = true;
1676
                    }
1677
                    if ($tls) {
1678
                        if (!$this->smtp->startTLS()) {
1679
                            throw new phpmailerException($this->lang('connect_host'));
1680
                        }
1681
                        // We must resend EHLO after TLS negotiation
1682
                        $this->smtp->hello($hello);
1683
                    }
1684
                    if ($this->SMTPAuth) {
1685
                        if (!$this->smtp->authenticate(
1686
                            $this->Username,
1687
                            $this->Password,
1688
                            $this->AuthType,
1689
                            $this->Realm,
1690
                            $this->Workstation
1691
                        )
1692
                        ) {
1693
                            throw new phpmailerException($this->lang('authenticate'));
1694
                        }
1695
                    }
1696
                    return true;
1697
                } catch (phpmailerException $exc) {
1698
                    $lastexception = $exc;
1699
                    $this->edebug($exc->getMessage());
1700
                    // We must have connected, but then failed TLS or Auth, so close connection nicely
1701
                    $this->smtp->quit();
1702
                }
1703
            }
1704
        }
1705
        // If we get here, all connection attempts have failed, so close connection hard
1706
        $this->smtp->close();
1707
        // As we've caught all exceptions, just report whatever the last one was
1708
        if ($this->exceptions and !is_null($lastexception)) {
1709
            throw $lastexception;
1710
        }
1711
        return false;
1712
    }
1713
1714
    /**
1715
     * Close the active SMTP session if one exists.
1716
     * @return void
1717
     */
1718
    public function smtpClose()
1719
    {
1720
        if (is_a($this->smtp, 'SMTP')) {
1721
            if ($this->smtp->connected()) {
1722
                $this->smtp->quit();
1723
                $this->smtp->close();
1724
            }
1725
        }
1726
    }
1727
1728
    /**
1729
     * Set the language for error messages.
1730
     * Returns false if it cannot load the language file.
1731
     * The default language is English.
1732
     * @param string $langcode ISO 639-1 2-character language code (e.g. French is "fr")
1733
     * @param string $lang_path Path to the language file directory, with trailing separator (slash)
1734
     * @return boolean
1735
     * @access public
1736
     */
1737
    public function setLanguage($langcode = 'en', $lang_path = '')
1738
    {
1739
        // Backwards compatibility for renamed language codes
1740
        $renamed_langcodes = array(
1741
            'br' => 'pt_br',
1742
            'cz' => 'cs',
1743
            'dk' => 'da',
1744
            'no' => 'nb',
1745
            'se' => 'sv',
1746
        );
1747
1748
        if (isset($renamed_langcodes[$langcode])) {
1749
            $langcode = $renamed_langcodes[$langcode];
1750
        }
1751
1752
        // Define full set of translatable strings in English
1753
        $PHPMAILER_LANG = array(
1754
            'authenticate' => 'SMTP Error: Could not authenticate.',
1755
            'connect_host' => 'SMTP Error: Could not connect to SMTP host.',
1756
            'data_not_accepted' => 'SMTP Error: data not accepted.',
1757
            'empty_message' => 'Message body empty',
1758
            'encoding' => 'Unknown encoding: ',
1759
            'execute' => 'Could not execute: ',
1760
            'file_access' => 'Could not access file: ',
1761
            'file_open' => 'File Error: Could not open file: ',
1762
            'from_failed' => 'The following From address failed: ',
1763
            'instantiate' => 'Could not instantiate mail function.',
1764
            'invalid_address' => 'Invalid address: ',
1765
            'mailer_not_supported' => ' mailer is not supported.',
1766
            'provide_address' => 'You must provide at least one recipient email address.',
1767
            'recipients_failed' => 'SMTP Error: The following recipients failed: ',
1768
            'signing' => 'Signing Error: ',
1769
            'smtp_connect_failed' => 'SMTP connect() failed.',
1770
            'smtp_error' => 'SMTP server error: ',
1771
            'variable_set' => 'Cannot set or reset variable: ',
1772
            'extension_missing' => 'Extension missing: '
1773
        );
1774
        if (empty($lang_path)) {
1775
            // Calculate an absolute path so it can work if CWD is not here
1776
            $lang_path = dirname(__FILE__). DIRECTORY_SEPARATOR . 'language'. DIRECTORY_SEPARATOR;
1777
        }
1778
        //Validate $langcode
1779
        if (!preg_match('/^[a-z]{2}(?:_[a-zA-Z]{2})?$/', $langcode)) {
1780
            $langcode = 'en';
1781
        }
1782
        $foundlang = true;
1783
        $lang_file = $lang_path . 'phpmailer.lang-' . $langcode . '.php';
1784
        // There is no English translation file
1785
        if ($langcode != 'en') {
1786
            // Make sure language file path is readable
1787
            if (!is_readable($lang_file)) {
1788
                $foundlang = false;
1789
            } else {
1790
                // Overwrite language-specific strings.
1791
                // This way we'll never have missing translation keys.
1792
                $foundlang = include $lang_file;
1793
            }
1794
        }
1795
        $this->language = $PHPMAILER_LANG;
1796
        return (boolean)$foundlang; // Returns false if language not found
1797
    }
1798
1799
    /**
1800
     * Get the array of strings for the current language.
1801
     * @return array
1802
     */
1803
    public function getTranslations()
1804
    {
1805
        return $this->language;
1806
    }
1807
1808
    /**
1809
     * Create recipient headers.
1810
     * @access public
1811
     * @param string $type
1812
     * @param array $addr An array of recipient,
1813
     * where each recipient is a 2-element indexed array with element 0 containing an address
1814
     * and element 1 containing a name, like:
1815
     * array(array('[email protected]', 'Joe User'), array('[email protected]', 'Zoe User'))
1816
     * @return string
1817
     */
1818
    public function addrAppend($type, $addr)
1819
    {
1820
        $addresses = array();
1821
        foreach ($addr as $address) {
1822
            $addresses[] = $this->addrFormat($address);
1823
        }
1824
        return $type . ': ' . implode(', ', $addresses) . $this->LE;
1825
    }
1826
1827
    /**
1828
     * Format an address for use in a message header.
1829
     * @access public
1830
     * @param array $addr A 2-element indexed array, element 0 containing an address, element 1 containing a name
1831
     *      like array('[email protected]', 'Joe User')
1832
     * @return string
1833
     */
1834
    public function addrFormat($addr)
1835
    {
1836
        if (empty($addr[1])) { // No name provided
1837
            return $this->secureHeader($addr[0]);
1838
        } else {
1839
            return $this->encodeHeader($this->secureHeader($addr[1]), 'phrase') . ' <' . $this->secureHeader(
1840
                $addr[0]
1841
            ) . '>';
1842
        }
1843
    }
1844
1845
    /**
1846
     * Word-wrap message.
1847
     * For use with mailers that do not automatically perform wrapping
1848
     * and for quoted-printable encoded messages.
1849
     * Original written by philippe.
1850
     * @param string $message The message to wrap
1851
     * @param integer $length The line length to wrap to
1852
     * @param boolean $qp_mode Whether to run in Quoted-Printable mode
1853
     * @access public
1854
     * @return string
1855
     */
1856
    public function wrapText($message, $length, $qp_mode = false)
1857
    {
1858
        if ($qp_mode) {
1859
            $soft_break = sprintf(' =%s', $this->LE);
1860
        } else {
1861
            $soft_break = $this->LE;
1862
        }
1863
        // If utf-8 encoding is used, we will need to make sure we don't
1864
        // split multibyte characters when we wrap
1865
        $is_utf8 = (strtolower($this->CharSet) == 'utf-8');
1866
        $lelen = strlen($this->LE);
1867
        $crlflen = strlen(self::CRLF);
1868
1869
        $message = $this->fixEOL($message);
1870
        //Remove a trailing line break
1871
        if (substr($message, -$lelen) == $this->LE) {
1872
            $message = substr($message, 0, -$lelen);
1873
        }
1874
1875
        //Split message into lines
1876
        $lines = explode($this->LE, $message);
1877
        //Message will be rebuilt in here
1878
        $message = '';
1879
        foreach ($lines as $line) {
1880
            $words = explode(' ', $line);
1881
            $buf = '';
1882
            $firstword = true;
1883
            foreach ($words as $word) {
1884
                if ($qp_mode and (strlen($word) > $length)) {
1885
                    $space_left = $length - strlen($buf) - $crlflen;
1886
                    if (!$firstword) {
1887
                        if ($space_left > 20) {
1888
                            $len = $space_left;
1889 View Code Duplication
                            if ($is_utf8) {
1890
                                $len = $this->utf8CharBoundary($word, $len);
1891
                            } elseif (substr($word, $len - 1, 1) == '=') {
1892
                                $len--;
1893
                            } elseif (substr($word, $len - 2, 1) == '=') {
1894
                                $len -= 2;
1895
                            }
1896
                            $part = substr($word, 0, $len);
1897
                            $word = substr($word, $len);
1898
                            $buf .= ' ' . $part;
1899
                            $message .= $buf . sprintf('=%s', self::CRLF);
1900
                        } else {
1901
                            $message .= $buf . $soft_break;
1902
                        }
1903
                        $buf = '';
1904
                    }
1905
                    while (strlen($word) > 0) {
1906
                        if ($length <= 0) {
1907
                            break;
1908
                        }
1909
                        $len = $length;
1910 View Code Duplication
                        if ($is_utf8) {
1911
                            $len = $this->utf8CharBoundary($word, $len);
1912
                        } elseif (substr($word, $len - 1, 1) == '=') {
1913
                            $len--;
1914
                        } elseif (substr($word, $len - 2, 1) == '=') {
1915
                            $len -= 2;
1916
                        }
1917
                        $part = substr($word, 0, $len);
1918
                        $word = substr($word, $len);
1919
1920
                        if (strlen($word) > 0) {
1921
                            $message .= $part . sprintf('=%s', self::CRLF);
1922
                        } else {
1923
                            $buf = $part;
1924
                        }
1925
                    }
1926
                } else {
1927
                    $buf_o = $buf;
1928
                    if (!$firstword) {
1929
                        $buf .= ' ';
1930
                    }
1931
                    $buf .= $word;
1932
1933
                    if (strlen($buf) > $length and $buf_o != '') {
1934
                        $message .= $buf_o . $soft_break;
1935
                        $buf = $word;
1936
                    }
1937
                }
1938
                $firstword = false;
1939
            }
1940
            $message .= $buf . self::CRLF;
1941
        }
1942
1943
        return $message;
1944
    }
1945
1946
    /**
1947
     * Find the last character boundary prior to $maxLength in a utf-8
1948
     * quoted-printable encoded string.
1949
     * Original written by Colin Brown.
1950
     * @access public
1951
     * @param string $encodedText utf-8 QP text
1952
     * @param integer $maxLength Find the last character boundary prior to this length
1953
     * @return integer
1954
     */
1955
    public function utf8CharBoundary($encodedText, $maxLength)
1956
    {
1957
        $foundSplitPos = false;
1958
        $lookBack = 3;
1959
        while (!$foundSplitPos) {
1960
            $lastChunk = substr($encodedText, $maxLength - $lookBack, $lookBack);
1961
            $encodedCharPos = strpos($lastChunk, '=');
1962
            if (false !== $encodedCharPos) {
1963
                // Found start of encoded character byte within $lookBack block.
1964
                // Check the encoded byte value (the 2 chars after the '=')
1965
                $hex = substr($encodedText, $maxLength - $lookBack + $encodedCharPos + 1, 2);
1966
                $dec = hexdec($hex);
1967
                if ($dec < 128) {
1968
                    // Single byte character.
1969
                    // If the encoded char was found at pos 0, it will fit
1970
                    // otherwise reduce maxLength to start of the encoded char
1971
                    if ($encodedCharPos > 0) {
1972
                        $maxLength = $maxLength - ($lookBack - $encodedCharPos);
1973
                    }
1974
                    $foundSplitPos = true;
1975
                } elseif ($dec >= 192) {
1976
                    // First byte of a multi byte character
1977
                    // Reduce maxLength to split at start of character
1978
                    $maxLength = $maxLength - ($lookBack - $encodedCharPos);
1979
                    $foundSplitPos = true;
1980
                } elseif ($dec < 192) {
1981
                    // Middle byte of a multi byte character, look further back
1982
                    $lookBack += 3;
1983
                }
1984
            } else {
1985
                // No encoded character found
1986
                $foundSplitPos = true;
1987
            }
1988
        }
1989
        return $maxLength;
1990
    }
1991
1992
    /**
1993
     * Apply word wrapping to the message body.
1994
     * Wraps the message body to the number of chars set in the WordWrap property.
1995
     * You should only do this to plain-text bodies as wrapping HTML tags may break them.
1996
     * This is called automatically by createBody(), so you don't need to call it yourself.
1997
     * @access public
1998
     * @return void
1999
     */
2000
    public function setWordWrap()
2001
    {
2002
        if ($this->WordWrap < 1) {
2003
            return;
2004
        }
2005
2006
        switch ($this->message_type) {
2007
            case 'alt':
2008
            case 'alt_inline':
2009
            case 'alt_attach':
2010
            case 'alt_inline_attach':
2011
                $this->AltBody = $this->wrapText($this->AltBody, $this->WordWrap);
2012
                break;
2013
            default:
2014
                $this->Body = $this->wrapText($this->Body, $this->WordWrap);
2015
                break;
2016
        }
2017
    }
2018
2019
    /**
2020
     * Assemble message headers.
2021
     * @access public
2022
     * @return string The assembled headers
2023
     */
2024
    public function createHeader()
2025
    {
2026
        $result = '';
2027
2028
        if ($this->MessageDate == '') {
2029
            $this->MessageDate = self::rfcDate();
2030
        }
2031
        $result .= $this->headerLine('Date', $this->MessageDate);
2032
2033
        // To be created automatically by mail()
2034
        if ($this->SingleTo) {
2035
            if ($this->Mailer != 'mail') {
2036
                foreach ($this->to as $toaddr) {
2037
                    $this->SingleToArray[] = $this->addrFormat($toaddr);
2038
                }
2039
            }
2040
        } else {
2041
            if (count($this->to) > 0) {
2042
                if ($this->Mailer != 'mail') {
2043
                    $result .= $this->addrAppend('To', $this->to);
2044
                }
2045
            } elseif (count($this->cc) == 0) {
2046
                $result .= $this->headerLine('To', 'undisclosed-recipients:;');
2047
            }
2048
        }
2049
2050
        $result .= $this->addrAppend('From', array(array(trim($this->From), $this->FromName)));
2051
2052
        // sendmail and mail() extract Cc from the header before sending
2053
        if (count($this->cc) > 0) {
2054
            $result .= $this->addrAppend('Cc', $this->cc);
2055
        }
2056
2057
        // sendmail and mail() extract Bcc from the header before sending
2058
        if ((
2059
                $this->Mailer == 'sendmail' or $this->Mailer == 'qmail' or $this->Mailer == 'mail'
2060
            )
2061
            and count($this->bcc) > 0
2062
        ) {
2063
            $result .= $this->addrAppend('Bcc', $this->bcc);
2064
        }
2065
2066
        if (count($this->ReplyTo) > 0) {
2067
            $result .= $this->addrAppend('Reply-To', $this->ReplyTo);
2068
        }
2069
2070
        // mail() sets the subject itself
2071
        if ($this->Mailer != 'mail') {
2072
            $result .= $this->headerLine('Subject', $this->encodeHeader($this->secureHeader($this->Subject)));
2073
        }
2074
2075
        // Only allow a custom message ID if it conforms to RFC 5322 section 3.6.4
2076
        // https://tools.ietf.org/html/rfc5322#section-3.6.4
2077
        if ('' != $this->MessageID and preg_match('/^<.*@.*>$/', $this->MessageID)) {
2078
            $this->lastMessageID = $this->MessageID;
2079
        } else {
2080
            $this->lastMessageID = sprintf('<%s@%s>', $this->uniqueid, $this->serverHostname());
2081
        }
2082
        $result .= $this->headerLine('Message-ID', $this->lastMessageID);
2083
        if (!is_null($this->Priority)) {
2084
            $result .= $this->headerLine('X-Priority', $this->Priority);
2085
        }
2086
        if ($this->XMailer == '') {
2087
            $result .= $this->headerLine(
2088
                'X-Mailer',
2089
                'PHPMailer ' . $this->Version . ' (https://github.com/PHPMailer/PHPMailer)'
2090
            );
2091
        } else {
2092
            $myXmailer = trim($this->XMailer);
2093
            if ($myXmailer) {
2094
                $result .= $this->headerLine('X-Mailer', $myXmailer);
2095
            }
2096
        }
2097
2098
        if ($this->ConfirmReadingTo != '') {
2099
            $result .= $this->headerLine('Disposition-Notification-To', '<' . $this->ConfirmReadingTo . '>');
2100
        }
2101
2102
        // Add custom headers
2103
        foreach ($this->CustomHeader as $header) {
2104
            $result .= $this->headerLine(
2105
                trim($header[0]),
2106
                $this->encodeHeader(trim($header[1]))
2107
            );
2108
        }
2109
        if (!$this->sign_key_file) {
2110
            $result .= $this->headerLine('MIME-Version', '1.0');
2111
            $result .= $this->getMailMIME();
2112
        }
2113
2114
        return $result;
2115
    }
2116
2117
    /**
2118
     * Get the message MIME type headers.
2119
     * @access public
2120
     * @return string
2121
     */
2122
    public function getMailMIME()
2123
    {
2124
        $result = '';
2125
        $ismultipart = true;
2126
        switch ($this->message_type) {
2127 View Code Duplication
            case 'inline':
2128
                $result .= $this->headerLine('Content-Type', 'multipart/related;');
2129
                $result .= $this->textLine("\tboundary=\"" . $this->boundary[1] . '"');
2130
                break;
2131
            case 'attach':
2132
            case 'inline_attach':
2133
            case 'alt_attach':
2134 View Code Duplication
            case 'alt_inline_attach':
2135
                $result .= $this->headerLine('Content-Type', 'multipart/mixed;');
2136
                $result .= $this->textLine("\tboundary=\"" . $this->boundary[1] . '"');
2137
                break;
2138
            case 'alt':
2139 View Code Duplication
            case 'alt_inline':
2140
                $result .= $this->headerLine('Content-Type', 'multipart/alternative;');
2141
                $result .= $this->textLine("\tboundary=\"" . $this->boundary[1] . '"');
2142
                break;
2143
            default:
2144
                // Catches case 'plain': and case '':
2145
                $result .= $this->textLine('Content-Type: ' . $this->ContentType . '; charset=' . $this->CharSet);
2146
                $ismultipart = false;
2147
                break;
2148
        }
2149
        // RFC1341 part 5 says 7bit is assumed if not specified
2150
        if ($this->Encoding != '7bit') {
2151
            // RFC 2045 section 6.4 says multipart MIME parts may only use 7bit, 8bit or binary CTE
2152
            if ($ismultipart) {
2153
                if ($this->Encoding == '8bit') {
2154
                    $result .= $this->headerLine('Content-Transfer-Encoding', '8bit');
2155
                }
2156
                // The only remaining alternatives are quoted-printable and base64, which are both 7bit compatible
2157
            } else {
2158
                $result .= $this->headerLine('Content-Transfer-Encoding', $this->Encoding);
2159
            }
2160
        }
2161
2162
        if ($this->Mailer != 'mail') {
2163
            $result .= $this->LE;
2164
        }
2165
2166
        return $result;
2167
    }
2168
2169
    /**
2170
     * Returns the whole MIME message.
2171
     * Includes complete headers and body.
2172
     * Only valid post preSend().
2173
     * @see PHPMailer::preSend()
2174
     * @access public
2175
     * @return string
2176
     */
2177
    public function getSentMIMEMessage()
2178
    {
2179
        return rtrim($this->MIMEHeader . $this->mailHeader, "\n\r") . self::CRLF . self::CRLF . $this->MIMEBody;
2180
    }
2181
2182
    /**
2183
     * Create unique ID
2184
     * @return string
2185
     */
2186
    protected function generateId() {
2187
        return md5(uniqid(time()));
2188
    }
2189
2190
    /**
2191
     * Assemble the message body.
2192
     * Returns an empty string on failure.
2193
     * @access public
2194
     * @throws phpmailerException
2195
     * @return string The assembled message body
2196
     */
2197
    public function createBody()
2198
    {
2199
        $body = '';
2200
        //Create unique IDs and preset boundaries
2201
        $this->uniqueid = $this->generateId();
2202
        $this->boundary[1] = 'b1_' . $this->uniqueid;
2203
        $this->boundary[2] = 'b2_' . $this->uniqueid;
2204
        $this->boundary[3] = 'b3_' . $this->uniqueid;
2205
2206
        if ($this->sign_key_file) {
2207
            $body .= $this->getMailMIME() . $this->LE;
2208
        }
2209
2210
        $this->setWordWrap();
2211
2212
        $bodyEncoding = $this->Encoding;
2213
        $bodyCharSet = $this->CharSet;
2214
        //Can we do a 7-bit downgrade?
2215
        if ($bodyEncoding == '8bit' and !$this->has8bitChars($this->Body)) {
2216
            $bodyEncoding = '7bit';
2217
            //All ISO 8859, Windows codepage and UTF-8 charsets are ascii compatible up to 7-bit
2218
            $bodyCharSet = 'us-ascii';
2219
        }
2220
        //If lines are too long, and we're not already using an encoding that will shorten them,
2221
        //change to quoted-printable transfer encoding for the body part only
2222
        if ('base64' != $this->Encoding and self::hasLineLongerThanMax($this->Body)) {
2223
            $bodyEncoding = 'quoted-printable';
2224
        }
2225
2226
        $altBodyEncoding = $this->Encoding;
2227
        $altBodyCharSet = $this->CharSet;
2228
        //Can we do a 7-bit downgrade?
2229
        if ($altBodyEncoding == '8bit' and !$this->has8bitChars($this->AltBody)) {
2230
            $altBodyEncoding = '7bit';
2231
            //All ISO 8859, Windows codepage and UTF-8 charsets are ascii compatible up to 7-bit
2232
            $altBodyCharSet = 'us-ascii';
2233
        }
2234
        //If lines are too long, and we're not already using an encoding that will shorten them,
2235
        //change to quoted-printable transfer encoding for the alt body part only
2236
        if ('base64' != $altBodyEncoding and self::hasLineLongerThanMax($this->AltBody)) {
2237
            $altBodyEncoding = 'quoted-printable';
2238
        }
2239
        //Use this as a preamble in all multipart message types
2240
        $mimepre = "This is a multi-part message in MIME format." . $this->LE . $this->LE;
2241
        switch ($this->message_type) {
2242 View Code Duplication
            case 'inline':
2243
                $body .= $mimepre;
2244
                $body .= $this->getBoundary($this->boundary[1], $bodyCharSet, '', $bodyEncoding);
2245
                $body .= $this->encodeString($this->Body, $bodyEncoding);
2246
                $body .= $this->LE . $this->LE;
2247
                $body .= $this->attachAll('inline', $this->boundary[1]);
2248
                break;
2249 View Code Duplication
            case 'attach':
2250
                $body .= $mimepre;
2251
                $body .= $this->getBoundary($this->boundary[1], $bodyCharSet, '', $bodyEncoding);
2252
                $body .= $this->encodeString($this->Body, $bodyEncoding);
2253
                $body .= $this->LE . $this->LE;
2254
                $body .= $this->attachAll('attachment', $this->boundary[1]);
2255
                break;
2256
            case 'inline_attach':
2257
                $body .= $mimepre;
2258
                $body .= $this->textLine('--' . $this->boundary[1]);
2259
                $body .= $this->headerLine('Content-Type', 'multipart/related;');
2260
                $body .= $this->textLine("\tboundary=\"" . $this->boundary[2] . '"');
2261
                $body .= $this->LE;
2262
                $body .= $this->getBoundary($this->boundary[2], $bodyCharSet, '', $bodyEncoding);
2263
                $body .= $this->encodeString($this->Body, $bodyEncoding);
2264
                $body .= $this->LE . $this->LE;
2265
                $body .= $this->attachAll('inline', $this->boundary[2]);
2266
                $body .= $this->LE;
2267
                $body .= $this->attachAll('attachment', $this->boundary[1]);
2268
                break;
2269
            case 'alt':
2270
                $body .= $mimepre;
2271
                $body .= $this->getBoundary($this->boundary[1], $altBodyCharSet, 'text/plain', $altBodyEncoding);
2272
                $body .= $this->encodeString($this->AltBody, $altBodyEncoding);
2273
                $body .= $this->LE . $this->LE;
2274
                $body .= $this->getBoundary($this->boundary[1], $bodyCharSet, 'text/html', $bodyEncoding);
2275
                $body .= $this->encodeString($this->Body, $bodyEncoding);
2276
                $body .= $this->LE . $this->LE;
2277
                if (!empty($this->Ical)) {
2278
                    $body .= $this->getBoundary($this->boundary[1], '', 'text/calendar; method=REQUEST', '');
2279
                    $body .= $this->encodeString($this->Ical, $this->Encoding);
2280
                    $body .= $this->LE . $this->LE;
2281
                }
2282
                $body .= $this->endBoundary($this->boundary[1]);
2283
                break;
2284 View Code Duplication
            case 'alt_inline':
2285
                $body .= $mimepre;
2286
                $body .= $this->getBoundary($this->boundary[1], $altBodyCharSet, 'text/plain', $altBodyEncoding);
2287
                $body .= $this->encodeString($this->AltBody, $altBodyEncoding);
2288
                $body .= $this->LE . $this->LE;
2289
                $body .= $this->textLine('--' . $this->boundary[1]);
2290
                $body .= $this->headerLine('Content-Type', 'multipart/related;');
2291
                $body .= $this->textLine("\tboundary=\"" . $this->boundary[2] . '"');
2292
                $body .= $this->LE;
2293
                $body .= $this->getBoundary($this->boundary[2], $bodyCharSet, 'text/html', $bodyEncoding);
2294
                $body .= $this->encodeString($this->Body, $bodyEncoding);
2295
                $body .= $this->LE . $this->LE;
2296
                $body .= $this->attachAll('inline', $this->boundary[2]);
2297
                $body .= $this->LE;
2298
                $body .= $this->endBoundary($this->boundary[1]);
2299
                break;
2300 View Code Duplication
            case 'alt_attach':
2301
                $body .= $mimepre;
2302
                $body .= $this->textLine('--' . $this->boundary[1]);
2303
                $body .= $this->headerLine('Content-Type', 'multipart/alternative;');
2304
                $body .= $this->textLine("\tboundary=\"" . $this->boundary[2] . '"');
2305
                $body .= $this->LE;
2306
                $body .= $this->getBoundary($this->boundary[2], $altBodyCharSet, 'text/plain', $altBodyEncoding);
2307
                $body .= $this->encodeString($this->AltBody, $altBodyEncoding);
2308
                $body .= $this->LE . $this->LE;
2309
                $body .= $this->getBoundary($this->boundary[2], $bodyCharSet, 'text/html', $bodyEncoding);
2310
                $body .= $this->encodeString($this->Body, $bodyEncoding);
2311
                $body .= $this->LE . $this->LE;
2312
                $body .= $this->endBoundary($this->boundary[2]);
2313
                $body .= $this->LE;
2314
                $body .= $this->attachAll('attachment', $this->boundary[1]);
2315
                break;
2316
            case 'alt_inline_attach':
2317
                $body .= $mimepre;
2318
                $body .= $this->textLine('--' . $this->boundary[1]);
2319
                $body .= $this->headerLine('Content-Type', 'multipart/alternative;');
2320
                $body .= $this->textLine("\tboundary=\"" . $this->boundary[2] . '"');
2321
                $body .= $this->LE;
2322
                $body .= $this->getBoundary($this->boundary[2], $altBodyCharSet, 'text/plain', $altBodyEncoding);
2323
                $body .= $this->encodeString($this->AltBody, $altBodyEncoding);
2324
                $body .= $this->LE . $this->LE;
2325
                $body .= $this->textLine('--' . $this->boundary[2]);
2326
                $body .= $this->headerLine('Content-Type', 'multipart/related;');
2327
                $body .= $this->textLine("\tboundary=\"" . $this->boundary[3] . '"');
2328
                $body .= $this->LE;
2329
                $body .= $this->getBoundary($this->boundary[3], $bodyCharSet, 'text/html', $bodyEncoding);
2330
                $body .= $this->encodeString($this->Body, $bodyEncoding);
2331
                $body .= $this->LE . $this->LE;
2332
                $body .= $this->attachAll('inline', $this->boundary[3]);
2333
                $body .= $this->LE;
2334
                $body .= $this->endBoundary($this->boundary[2]);
2335
                $body .= $this->LE;
2336
                $body .= $this->attachAll('attachment', $this->boundary[1]);
2337
                break;
2338
            default:
2339
                // Catch case 'plain' and case '', applies to simple `text/plain` and `text/html` body content types
2340
                //Reset the `Encoding` property in case we changed it for line length reasons
2341
                $this->Encoding = $bodyEncoding;
2342
                $body .= $this->encodeString($this->Body, $this->Encoding);
2343
                break;
2344
        }
2345
2346
        if ($this->isError()) {
2347
            $body = '';
2348
        } elseif ($this->sign_key_file) {
2349
            try {
2350
                if (!defined('PKCS7_TEXT')) {
2351
                    throw new phpmailerException($this->lang('extension_missing') . 'openssl');
2352
                }
2353
                // @TODO would be nice to use php://temp streams here, but need to wrap for PHP < 5.1
2354
                $file = tempnam(sys_get_temp_dir(), 'mail');
2355
                if (false === file_put_contents($file, $body)) {
2356
                    throw new phpmailerException($this->lang('signing') . ' Could not write temp file');
2357
                }
2358
                $signed = tempnam(sys_get_temp_dir(), 'signed');
2359
                //Workaround for PHP bug https://bugs.php.net/bug.php?id=69197
2360
                if (empty($this->sign_extracerts_file)) {
2361
                    $sign = @openssl_pkcs7_sign(
2362
                        $file,
2363
                        $signed,
2364
                        'file://' . realpath($this->sign_cert_file),
2365
                        array('file://' . realpath($this->sign_key_file), $this->sign_key_pass),
2366
                        null
2367
                    );
2368
                } else {
2369
                    $sign = @openssl_pkcs7_sign(
2370
                        $file,
2371
                        $signed,
2372
                        'file://' . realpath($this->sign_cert_file),
2373
                        array('file://' . realpath($this->sign_key_file), $this->sign_key_pass),
2374
                        null,
2375
                        PKCS7_DETACHED,
2376
                        $this->sign_extracerts_file
2377
                    );
2378
                }
2379
                if ($sign) {
2380
                    @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...
2381
                    $body = file_get_contents($signed);
2382
                    @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...
2383
                    //The message returned by openssl contains both headers and body, so need to split them up
2384
                    $parts = explode("\n\n", $body, 2);
2385
                    $this->MIMEHeader .= $parts[0] . $this->LE . $this->LE;
2386
                    $body = $parts[1];
2387
                } else {
2388
                    @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...
2389
                    @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...
2390
                    throw new phpmailerException($this->lang('signing') . openssl_error_string());
2391
                }
2392
            } catch (phpmailerException $exc) {
2393
                $body = '';
2394
                if ($this->exceptions) {
2395
                    throw $exc;
2396
                }
2397
            }
2398
        }
2399
        return $body;
2400
    }
2401
2402
    /**
2403
     * Return the start of a message boundary.
2404
     * @access protected
2405
     * @param string $boundary
2406
     * @param string $charSet
2407
     * @param string $contentType
2408
     * @param string $encoding
2409
     * @return string
2410
     */
2411
    protected function getBoundary($boundary, $charSet, $contentType, $encoding)
2412
    {
2413
        $result = '';
2414
        if ($charSet == '') {
2415
            $charSet = $this->CharSet;
2416
        }
2417
        if ($contentType == '') {
2418
            $contentType = $this->ContentType;
2419
        }
2420
        if ($encoding == '') {
2421
            $encoding = $this->Encoding;
2422
        }
2423
        $result .= $this->textLine('--' . $boundary);
2424
        $result .= sprintf('Content-Type: %s; charset=%s', $contentType, $charSet);
2425
        $result .= $this->LE;
2426
        // RFC1341 part 5 says 7bit is assumed if not specified
2427
        if ($encoding != '7bit') {
2428
            $result .= $this->headerLine('Content-Transfer-Encoding', $encoding);
2429
        }
2430
        $result .= $this->LE;
2431
2432
        return $result;
2433
    }
2434
2435
    /**
2436
     * Return the end of a message boundary.
2437
     * @access protected
2438
     * @param string $boundary
2439
     * @return string
2440
     */
2441
    protected function endBoundary($boundary)
2442
    {
2443
        return $this->LE . '--' . $boundary . '--' . $this->LE;
2444
    }
2445
2446
    /**
2447
     * Set the message type.
2448
     * PHPMailer only supports some preset message types, not arbitrary MIME structures.
2449
     * @access protected
2450
     * @return void
2451
     */
2452
    protected function setMessageType()
2453
    {
2454
        $type = array();
2455
        if ($this->alternativeExists()) {
2456
            $type[] = 'alt';
2457
        }
2458
        if ($this->inlineImageExists()) {
2459
            $type[] = 'inline';
2460
        }
2461
        if ($this->attachmentExists()) {
2462
            $type[] = 'attach';
2463
        }
2464
        $this->message_type = implode('_', $type);
2465
        if ($this->message_type == '') {
2466
            //The 'plain' message_type refers to the message having a single body element, not that it is plain-text
2467
            $this->message_type = 'plain';
2468
        }
2469
    }
2470
2471
    /**
2472
     * Format a header line.
2473
     * @access public
2474
     * @param string $name
2475
     * @param string $value
2476
     * @return string
2477
     */
2478
    public function headerLine($name, $value)
2479
    {
2480
        return $name . ': ' . $value . $this->LE;
2481
    }
2482
2483
    /**
2484
     * Return a formatted mail line.
2485
     * @access public
2486
     * @param string $value
2487
     * @return string
2488
     */
2489
    public function textLine($value)
2490
    {
2491
        return $value . $this->LE;
2492
    }
2493
2494
    /**
2495
     * Add an attachment from a path on the filesystem.
2496
     * Never use a user-supplied path to a file!
2497
     * Returns false if the file could not be found or read.
2498
     * @param string $path Path to the attachment.
2499
     * @param string $name Overrides the attachment name.
2500
     * @param string $encoding File encoding (see $Encoding).
2501
     * @param string $type File extension (MIME) type.
2502
     * @param string $disposition Disposition to use
2503
     * @throws phpmailerException
2504
     * @return boolean
2505
     */
2506
    public function addAttachment($path, $name = '', $encoding = 'base64', $type = '', $disposition = 'attachment')
2507
    {
2508
        try {
2509
            if (!@is_file($path)) {
2510
                throw new phpmailerException($this->lang('file_access') . $path, self::STOP_CONTINUE);
2511
            }
2512
2513
            // If a MIME type is not specified, try to work it out from the file name
2514
            if ($type == '') {
2515
                $type = self::filenameToType($path);
2516
            }
2517
2518
            $filename = basename($path);
2519
            if ($name == '') {
2520
                $name = $filename;
2521
            }
2522
2523
            $this->attachment[] = array(
2524
                0 => $path,
2525
                1 => $filename,
2526
                2 => $name,
2527
                3 => $encoding,
2528
                4 => $type,
2529
                5 => false, // isStringAttachment
2530
                6 => $disposition,
2531
                7 => 0
2532
            );
2533
2534
        } catch (phpmailerException $exc) {
2535
            $this->setError($exc->getMessage());
2536
            $this->edebug($exc->getMessage());
2537
            if ($this->exceptions) {
2538
                throw $exc;
2539
            }
2540
            return false;
2541
        }
2542
        return true;
2543
    }
2544
2545
    /**
2546
     * Return the array of attachments.
2547
     * @return array
2548
     */
2549
    public function getAttachments()
2550
    {
2551
        return $this->attachment;
2552
    }
2553
2554
    /**
2555
     * Attach all file, string, and binary attachments to the message.
2556
     * Returns an empty string on failure.
2557
     * @access protected
2558
     * @param string $disposition_type
2559
     * @param string $boundary
2560
     * @return string
2561
     */
2562
    protected function attachAll($disposition_type, $boundary)
2563
    {
2564
        // Return text of body
2565
        $mime = array();
2566
        $cidUniq = array();
2567
        $incl = array();
2568
2569
        // Add all attachments
2570
        foreach ($this->attachment as $attachment) {
2571
            // Check if it is a valid disposition_filter
2572
            if ($attachment[6] == $disposition_type) {
2573
                // Check for string attachment
2574
                $string = '';
2575
                $path = '';
2576
                $bString = $attachment[5];
2577
                if ($bString) {
2578
                    $string = $attachment[0];
2579
                } else {
2580
                    $path = $attachment[0];
2581
                }
2582
2583
                $inclhash = md5(serialize($attachment));
2584
                if (in_array($inclhash, $incl)) {
2585
                    continue;
2586
                }
2587
                $incl[] = $inclhash;
2588
                $name = $attachment[2];
2589
                $encoding = $attachment[3];
2590
                $type = $attachment[4];
2591
                $disposition = $attachment[6];
2592
                $cid = $attachment[7];
2593
                if ($disposition == 'inline' && array_key_exists($cid, $cidUniq)) {
2594
                    continue;
2595
                }
2596
                $cidUniq[$cid] = true;
2597
2598
                $mime[] = sprintf('--%s%s', $boundary, $this->LE);
2599
                //Only include a filename property if we have one
2600
                if (!empty($name)) {
2601
                    $mime[] = sprintf(
2602
                        'Content-Type: %s; name="%s"%s',
2603
                        $type,
2604
                        $this->encodeHeader($this->secureHeader($name)),
2605
                        $this->LE
2606
                    );
2607
                } else {
2608
                    $mime[] = sprintf(
2609
                        'Content-Type: %s%s',
2610
                        $type,
2611
                        $this->LE
2612
                    );
2613
                }
2614
                // RFC1341 part 5 says 7bit is assumed if not specified
2615
                if ($encoding != '7bit') {
2616
                    $mime[] = sprintf('Content-Transfer-Encoding: %s%s', $encoding, $this->LE);
2617
                }
2618
2619
                if ($disposition == 'inline') {
2620
                    $mime[] = sprintf('Content-ID: <%s>%s', $cid, $this->LE);
2621
                }
2622
2623
                // If a filename contains any of these chars, it should be quoted,
2624
                // but not otherwise: RFC2183 & RFC2045 5.1
2625
                // Fixes a warning in IETF's msglint MIME checker
2626
                // Allow for bypassing the Content-Disposition header totally
2627
                if (!(empty($disposition))) {
2628
                    $encoded_name = $this->encodeHeader($this->secureHeader($name));
2629
                    if (preg_match('/[ \(\)<>@,;:\\"\/\[\]\?=]/', $encoded_name)) {
2630
                        $mime[] = sprintf(
2631
                            'Content-Disposition: %s; filename="%s"%s',
2632
                            $disposition,
2633
                            $encoded_name,
2634
                            $this->LE . $this->LE
2635
                        );
2636
                    } else {
2637
                        if (!empty($encoded_name)) {
2638
                            $mime[] = sprintf(
2639
                                'Content-Disposition: %s; filename=%s%s',
2640
                                $disposition,
2641
                                $encoded_name,
2642
                                $this->LE . $this->LE
2643
                            );
2644
                        } else {
2645
                            $mime[] = sprintf(
2646
                                'Content-Disposition: %s%s',
2647
                                $disposition,
2648
                                $this->LE . $this->LE
2649
                            );
2650
                        }
2651
                    }
2652
                } else {
2653
                    $mime[] = $this->LE;
2654
                }
2655
2656
                // Encode as string attachment
2657
                if ($bString) {
2658
                    $mime[] = $this->encodeString($string, $encoding);
2659
                    if ($this->isError()) {
2660
                        return '';
2661
                    }
2662
                    $mime[] = $this->LE . $this->LE;
2663
                } else {
2664
                    $mime[] = $this->encodeFile($path, $encoding);
2665
                    if ($this->isError()) {
2666
                        return '';
2667
                    }
2668
                    $mime[] = $this->LE . $this->LE;
2669
                }
2670
            }
2671
        }
2672
2673
        $mime[] = sprintf('--%s--%s', $boundary, $this->LE);
2674
2675
        return implode('', $mime);
2676
    }
2677
2678
    /**
2679
     * Encode a file attachment in requested format.
2680
     * Returns an empty string on failure.
2681
     * @param string $path The full path to the file
2682
     * @param string $encoding The encoding to use; one of 'base64', '7bit', '8bit', 'binary', 'quoted-printable'
2683
     * @throws phpmailerException
2684
     * @access protected
2685
     * @return string
2686
     */
2687
    protected function encodeFile($path, $encoding = 'base64')
2688
    {
2689
        try {
2690
            if (!is_readable($path)) {
2691
                throw new phpmailerException($this->lang('file_open') . $path, self::STOP_CONTINUE);
2692
            }
2693
            $magic_quotes = get_magic_quotes_runtime();
2694
            if ($magic_quotes) {
2695
                if (version_compare(PHP_VERSION, '5.3.0', '<')) {
2696
                    set_magic_quotes_runtime(false);
2697
                } else {
2698
                    //Doesn't exist in PHP 5.4, but we don't need to check because
2699
                    //get_magic_quotes_runtime always returns false in 5.4+
2700
                    //so it will never get here
2701
                    ini_set('magic_quotes_runtime', false);
2702
                }
2703
            }
2704
            $file_buffer = file_get_contents($path);
2705
            $file_buffer = $this->encodeString($file_buffer, $encoding);
2706
            if ($magic_quotes) {
2707
                if (version_compare(PHP_VERSION, '5.3.0', '<')) {
2708
                    set_magic_quotes_runtime($magic_quotes);
2709
                } else {
2710
                    ini_set('magic_quotes_runtime', $magic_quotes);
2711
                }
2712
            }
2713
            return $file_buffer;
2714
        } catch (Exception $exc) {
2715
            $this->setError($exc->getMessage());
2716
            return '';
2717
        }
2718
    }
2719
2720
    /**
2721
     * Encode a string in requested format.
2722
     * Returns an empty string on failure.
2723
     * @param string $str The text to encode
2724
     * @param string $encoding The encoding to use; one of 'base64', '7bit', '8bit', 'binary', 'quoted-printable'
2725
     * @access public
2726
     * @return string
2727
     */
2728
    public function encodeString($str, $encoding = 'base64')
2729
    {
2730
        $encoded = '';
2731
        switch (strtolower($encoding)) {
2732
            case 'base64':
2733
                $encoded = chunk_split(base64_encode($str), 76, $this->LE);
2734
                break;
2735
            case '7bit':
2736
            case '8bit':
2737
                $encoded = $this->fixEOL($str);
2738
                // Make sure it ends with a line break
2739
                if (substr($encoded, -(strlen($this->LE))) != $this->LE) {
2740
                    $encoded .= $this->LE;
2741
                }
2742
                break;
2743
            case 'binary':
2744
                $encoded = $str;
2745
                break;
2746
            case 'quoted-printable':
2747
                $encoded = $this->encodeQP($str);
2748
                break;
2749
            default:
2750
                $this->setError($this->lang('encoding') . $encoding);
2751
                break;
2752
        }
2753
        return $encoded;
2754
    }
2755
2756
    /**
2757
     * Encode a header string optimally.
2758
     * Picks shortest of Q, B, quoted-printable or none.
2759
     * @access public
2760
     * @param string $str
2761
     * @param string $position
2762
     * @return string
2763
     */
2764
    public function encodeHeader($str, $position = 'text')
2765
    {
2766
        $matchcount = 0;
2767
        switch (strtolower($position)) {
2768
            case 'phrase':
2769
                if (!preg_match('/[\200-\377]/', $str)) {
2770
                    // Can't use addslashes as we don't know the value of magic_quotes_sybase
2771
                    $encoded = addcslashes($str, "\0..\37\177\\\"");
2772
                    if (($str == $encoded) && !preg_match('/[^A-Za-z0-9!#$%&\'*+\/=?^_`{|}~ -]/', $str)) {
2773
                        return ($encoded);
2774
                    } else {
2775
                        return ("\"$encoded\"");
2776
                    }
2777
                }
2778
                $matchcount = preg_match_all('/[^\040\041\043-\133\135-\176]/', $str, $matches);
2779
                break;
2780
            /** @noinspection PhpMissingBreakStatementInspection */
2781
            case 'comment':
2782
                $matchcount = preg_match_all('/[()"]/', $str, $matches);
2783
                // Intentional fall-through
2784
            case 'text':
2785
            default:
2786
                $matchcount += preg_match_all('/[\000-\010\013\014\016-\037\177-\377]/', $str, $matches);
2787
                break;
2788
        }
2789
2790
        //There are no chars that need encoding
2791
        if ($matchcount == 0) {
2792
            return ($str);
2793
        }
2794
2795
        $maxlen = 75 - 7 - strlen($this->CharSet);
2796
        // Try to select the encoding which should produce the shortest output
2797
        if ($matchcount > strlen($str) / 3) {
2798
            // More than a third of the content will need encoding, so B encoding will be most efficient
2799
            $encoding = 'B';
2800
            if (function_exists('mb_strlen') && $this->hasMultiBytes($str)) {
2801
                // Use a custom function which correctly encodes and wraps long
2802
                // multibyte strings without breaking lines within a character
2803
                $encoded = $this->base64EncodeWrapMB($str, "\n");
2804
            } else {
2805
                $encoded = base64_encode($str);
2806
                $maxlen -= $maxlen % 4;
2807
                $encoded = trim(chunk_split($encoded, $maxlen, "\n"));
2808
            }
2809
        } else {
2810
            $encoding = 'Q';
2811
            $encoded = $this->encodeQ($str, $position);
2812
            $encoded = $this->wrapText($encoded, $maxlen, true);
2813
            $encoded = str_replace('=' . self::CRLF, "\n", trim($encoded));
2814
        }
2815
2816
        $encoded = preg_replace('/^(.*)$/m', ' =?' . $this->CharSet . "?$encoding?\\1?=", $encoded);
2817
        $encoded = trim(str_replace("\n", $this->LE, $encoded));
2818
2819
        return $encoded;
2820
    }
2821
2822
    /**
2823
     * Check if a string contains multi-byte characters.
2824
     * @access public
2825
     * @param string $str multi-byte text to wrap encode
2826
     * @return boolean
2827
     */
2828
    public function hasMultiBytes($str)
2829
    {
2830
        if (function_exists('mb_strlen')) {
2831
            return (strlen($str) > mb_strlen($str, $this->CharSet));
2832
        } else { // Assume no multibytes (we can't handle without mbstring functions anyway)
2833
            return false;
2834
        }
2835
    }
2836
2837
    /**
2838
     * Does a string contain any 8-bit chars (in any charset)?
2839
     * @param string $text
2840
     * @return boolean
2841
     */
2842
    public function has8bitChars($text)
2843
    {
2844
        return (boolean)preg_match('/[\x80-\xFF]/', $text);
2845
    }
2846
2847
    /**
2848
     * Encode and wrap long multibyte strings for mail headers
2849
     * without breaking lines within a character.
2850
     * Adapted from a function by paravoid
2851
     * @link http://www.php.net/manual/en/function.mb-encode-mimeheader.php#60283
2852
     * @access public
2853
     * @param string $str multi-byte text to wrap encode
2854
     * @param string $linebreak string to use as linefeed/end-of-line
2855
     * @return string
2856
     */
2857
    public function base64EncodeWrapMB($str, $linebreak = null)
2858
    {
2859
        $start = '=?' . $this->CharSet . '?B?';
2860
        $end = '?=';
2861
        $encoded = '';
2862
        if ($linebreak === null) {
2863
            $linebreak = $this->LE;
2864
        }
2865
2866
        $mb_length = mb_strlen($str, $this->CharSet);
2867
        // Each line must have length <= 75, including $start and $end
2868
        $length = 75 - strlen($start) - strlen($end);
2869
        // Average multi-byte ratio
2870
        $ratio = $mb_length / strlen($str);
2871
        // Base64 has a 4:3 ratio
2872
        $avgLength = floor($length * $ratio * .75);
2873
2874
        for ($i = 0; $i < $mb_length; $i += $offset) {
2875
            $lookBack = 0;
2876
            do {
2877
                $offset = $avgLength - $lookBack;
2878
                $chunk = mb_substr($str, $i, $offset, $this->CharSet);
2879
                $chunk = base64_encode($chunk);
2880
                $lookBack++;
2881
            } while (strlen($chunk) > $length);
2882
            $encoded .= $chunk . $linebreak;
2883
        }
2884
2885
        // Chomp the last linefeed
2886
        $encoded = substr($encoded, 0, -strlen($linebreak));
2887
        return $encoded;
2888
    }
2889
2890
    /**
2891
     * Encode a string in quoted-printable format.
2892
     * According to RFC2045 section 6.7.
2893
     * @access public
2894
     * @param string $string The text to encode
2895
     * @param integer $line_max Number of chars allowed on a line before wrapping
2896
     * @return string
2897
     * @link http://www.php.net/manual/en/function.quoted-printable-decode.php#89417 Adapted from this comment
2898
     */
2899
    public function encodeQP($string, $line_max = 76)
2900
    {
2901
        // Use native function if it's available (>= PHP5.3)
2902
        if (function_exists('quoted_printable_encode')) {
2903
            return quoted_printable_encode($string);
2904
        }
2905
        // Fall back to a pure PHP implementation
2906
        $string = str_replace(
2907
            array('%20', '%0D%0A.', '%0D%0A', '%'),
2908
            array(' ', "\r\n=2E", "\r\n", '='),
2909
            rawurlencode($string)
2910
        );
2911
        return preg_replace('/[^\r\n]{' . ($line_max - 3) . '}[^=\r\n]{2}/', "$0=\r\n", $string);
2912
    }
2913
2914
    /**
2915
     * Backward compatibility wrapper for an old QP encoding function that was removed.
2916
     * @see PHPMailer::encodeQP()
2917
     * @access public
2918
     * @param string $string
2919
     * @param integer $line_max
2920
     * @param boolean $space_conv
2921
     * @return string
2922
     * @deprecated Use encodeQP instead.
2923
     */
2924
    public function encodeQPphp(
2925
        $string,
2926
        $line_max = 76,
2927
        /** @noinspection PhpUnusedParameterInspection */ $space_conv = false
0 ignored issues
show
The parameter $space_conv is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
2928
    ) {
2929
        return $this->encodeQP($string, $line_max);
2930
    }
2931
2932
    /**
2933
     * Encode a string using Q encoding.
2934
     * @link http://tools.ietf.org/html/rfc2047
2935
     * @param string $str the text to encode
2936
     * @param string $position Where the text is going to be used, see the RFC for what that means
2937
     * @access public
2938
     * @return string
2939
     */
2940
    public function encodeQ($str, $position = 'text')
2941
    {
2942
        // There should not be any EOL in the string
2943
        $pattern = '';
2944
        $encoded = str_replace(array("\r", "\n"), '', $str);
2945
        switch (strtolower($position)) {
2946
            case 'phrase':
2947
                // RFC 2047 section 5.3
2948
                $pattern = '^A-Za-z0-9!*+\/ -';
2949
                break;
2950
            /** @noinspection PhpMissingBreakStatementInspection */
2951
            case 'comment':
2952
                // RFC 2047 section 5.2
2953
                $pattern = '\(\)"';
2954
                // intentional fall-through
2955
                // for this reason we build the $pattern without including delimiters and []
2956
            case 'text':
2957
            default:
2958
                // RFC 2047 section 5.1
2959
                // Replace every high ascii, control, =, ? and _ characters
2960
                $pattern = '\000-\011\013\014\016-\037\075\077\137\177-\377' . $pattern;
2961
                break;
2962
        }
2963
        $matches = array();
2964
        if (preg_match_all("/[{$pattern}]/", $encoded, $matches)) {
2965
            // If the string contains an '=', make sure it's the first thing we replace
2966
            // so as to avoid double-encoding
2967
            $eqkey = array_search('=', $matches[0]);
2968
            if (false !== $eqkey) {
2969
                unset($matches[0][$eqkey]);
2970
                array_unshift($matches[0], '=');
2971
            }
2972
            foreach (array_unique($matches[0]) as $char) {
2973
                $encoded = str_replace($char, '=' . sprintf('%02X', ord($char)), $encoded);
2974
            }
2975
        }
2976
        // Replace every spaces to _ (more readable than =20)
2977
        return str_replace(' ', '_', $encoded);
2978
    }
2979
2980
    /**
2981
     * Add a string or binary attachment (non-filesystem).
2982
     * This method can be used to attach ascii or binary data,
2983
     * such as a BLOB record from a database.
2984
     * @param string $string String attachment data.
2985
     * @param string $filename Name of the attachment.
2986
     * @param string $encoding File encoding (see $Encoding).
2987
     * @param string $type File extension (MIME) type.
2988
     * @param string $disposition Disposition to use
2989
     * @return void
2990
     */
2991
    public function addStringAttachment(
2992
        $string,
2993
        $filename,
2994
        $encoding = 'base64',
2995
        $type = '',
2996
        $disposition = 'attachment'
2997
    ) {
2998
        // If a MIME type is not specified, try to work it out from the file name
2999
        if ($type == '') {
3000
            $type = self::filenameToType($filename);
3001
        }
3002
        // Append to $attachment array
3003
        $this->attachment[] = array(
3004
            0 => $string,
3005
            1 => $filename,
3006
            2 => basename($filename),
3007
            3 => $encoding,
3008
            4 => $type,
3009
            5 => true, // isStringAttachment
3010
            6 => $disposition,
3011
            7 => 0
3012
        );
3013
    }
3014
3015
    /**
3016
     * Add an embedded (inline) attachment from a file.
3017
     * This can include images, sounds, and just about any other document type.
3018
     * These differ from 'regular' attachments in that they are intended to be
3019
     * displayed inline with the message, not just attached for download.
3020
     * This is used in HTML messages that embed the images
3021
     * the HTML refers to using the $cid value.
3022
     * Never use a user-supplied path to a file!
3023
     * @param string $path Path to the attachment.
3024
     * @param string $cid Content ID of the attachment; Use this to reference
3025
     *        the content when using an embedded image in HTML.
3026
     * @param string $name Overrides the attachment name.
3027
     * @param string $encoding File encoding (see $Encoding).
3028
     * @param string $type File MIME type.
3029
     * @param string $disposition Disposition to use
3030
     * @return boolean True on successfully adding an attachment
3031
     */
3032
    public function addEmbeddedImage($path, $cid, $name = '', $encoding = 'base64', $type = '', $disposition = 'inline')
3033
    {
3034
        if (!@is_file($path)) {
3035
            $this->setError($this->lang('file_access') . $path);
3036
            return false;
3037
        }
3038
3039
        // If a MIME type is not specified, try to work it out from the file name
3040
        if ($type == '') {
3041
            $type = self::filenameToType($path);
3042
        }
3043
3044
        $filename = basename($path);
3045
        if ($name == '') {
3046
            $name = $filename;
3047
        }
3048
3049
        // Append to $attachment array
3050
        $this->attachment[] = array(
3051
            0 => $path,
3052
            1 => $filename,
3053
            2 => $name,
3054
            3 => $encoding,
3055
            4 => $type,
3056
            5 => false, // isStringAttachment
3057
            6 => $disposition,
3058
            7 => $cid
3059
        );
3060
        return true;
3061
    }
3062
3063
    /**
3064
     * Add an embedded stringified attachment.
3065
     * This can include images, sounds, and just about any other document type.
3066
     * Be sure to set the $type to an image type for images:
3067
     * JPEG images use 'image/jpeg', GIF uses 'image/gif', PNG uses 'image/png'.
3068
     * @param string $string The attachment binary data.
3069
     * @param string $cid Content ID of the attachment; Use this to reference
3070
     *        the content when using an embedded image in HTML.
3071
     * @param string $name
3072
     * @param string $encoding File encoding (see $Encoding).
3073
     * @param string $type MIME type.
3074
     * @param string $disposition Disposition to use
3075
     * @return boolean True on successfully adding an attachment
3076
     */
3077
    public function addStringEmbeddedImage(
3078
        $string,
3079
        $cid,
3080
        $name = '',
3081
        $encoding = 'base64',
3082
        $type = '',
3083
        $disposition = 'inline'
3084
    ) {
3085
        // If a MIME type is not specified, try to work it out from the name
3086
        if ($type == '' and !empty($name)) {
3087
            $type = self::filenameToType($name);
3088
        }
3089
3090
        // Append to $attachment array
3091
        $this->attachment[] = array(
3092
            0 => $string,
3093
            1 => $name,
3094
            2 => $name,
3095
            3 => $encoding,
3096
            4 => $type,
3097
            5 => true, // isStringAttachment
3098
            6 => $disposition,
3099
            7 => $cid
3100
        );
3101
        return true;
3102
    }
3103
3104
    /**
3105
     * Check if an inline attachment is present.
3106
     * @access public
3107
     * @return boolean
3108
     */
3109
    public function inlineImageExists()
3110
    {
3111
        foreach ($this->attachment as $attachment) {
3112
            if ($attachment[6] == 'inline') {
3113
                return true;
3114
            }
3115
        }
3116
        return false;
3117
    }
3118
3119
    /**
3120
     * Check if an attachment (non-inline) is present.
3121
     * @return boolean
3122
     */
3123
    public function attachmentExists()
3124
    {
3125
        foreach ($this->attachment as $attachment) {
3126
            if ($attachment[6] == 'attachment') {
3127
                return true;
3128
            }
3129
        }
3130
        return false;
3131
    }
3132
3133
    /**
3134
     * Check if this message has an alternative body set.
3135
     * @return boolean
3136
     */
3137
    public function alternativeExists()
3138
    {
3139
        return !empty($this->AltBody);
3140
    }
3141
3142
    /**
3143
     * Clear queued addresses of given kind.
3144
     * @access protected
3145
     * @param string $kind 'to', 'cc', or 'bcc'
3146
     * @return void
3147
     */
3148
    public function clearQueuedAddresses($kind)
3149
    {
3150
        $RecipientsQueue = $this->RecipientsQueue;
3151
        foreach ($RecipientsQueue as $address => $params) {
3152
            if ($params[0] == $kind) {
3153
                unset($this->RecipientsQueue[$address]);
3154
            }
3155
        }
3156
    }
3157
3158
    /**
3159
     * Clear all To recipients.
3160
     * @return void
3161
     */
3162
    public function clearAddresses()
3163
    {
3164
        foreach ($this->to as $to) {
3165
            unset($this->all_recipients[strtolower($to[0])]);
3166
        }
3167
        $this->to = array();
3168
        $this->clearQueuedAddresses('to');
3169
    }
3170
3171
    /**
3172
     * Clear all CC recipients.
3173
     * @return void
3174
     */
3175 View Code Duplication
    public function clearCCs()
3176
    {
3177
        foreach ($this->cc as $cc) {
3178
            unset($this->all_recipients[strtolower($cc[0])]);
3179
        }
3180
        $this->cc = array();
3181
        $this->clearQueuedAddresses('cc');
3182
    }
3183
3184
    /**
3185
     * Clear all BCC recipients.
3186
     * @return void
3187
     */
3188 View Code Duplication
    public function clearBCCs()
3189
    {
3190
        foreach ($this->bcc as $bcc) {
3191
            unset($this->all_recipients[strtolower($bcc[0])]);
3192
        }
3193
        $this->bcc = array();
3194
        $this->clearQueuedAddresses('bcc');
3195
    }
3196
3197
    /**
3198
     * Clear all ReplyTo recipients.
3199
     * @return void
3200
     */
3201
    public function clearReplyTos()
3202
    {
3203
        $this->ReplyTo = array();
3204
        $this->ReplyToQueue = array();
3205
    }
3206
3207
    /**
3208
     * Clear all recipient types.
3209
     * @return void
3210
     */
3211
    public function clearAllRecipients()
3212
    {
3213
        $this->to = array();
3214
        $this->cc = array();
3215
        $this->bcc = array();
3216
        $this->all_recipients = array();
3217
        $this->RecipientsQueue = array();
3218
    }
3219
3220
    /**
3221
     * Clear all filesystem, string, and binary attachments.
3222
     * @return void
3223
     */
3224
    public function clearAttachments()
3225
    {
3226
        $this->attachment = array();
3227
    }
3228
3229
    /**
3230
     * Clear all custom headers.
3231
     * @return void
3232
     */
3233
    public function clearCustomHeaders()
3234
    {
3235
        $this->CustomHeader = array();
3236
    }
3237
3238
    /**
3239
     * Add an error message to the error container.
3240
     * @access protected
3241
     * @param string $msg
3242
     * @return void
3243
     */
3244
    protected function setError($msg)
3245
    {
3246
        $this->error_count++;
3247
        if ($this->Mailer == 'smtp' and !is_null($this->smtp)) {
3248
            $lasterror = $this->smtp->getError();
3249
            if (!empty($lasterror['error'])) {
3250
                $msg .= $this->lang('smtp_error') . $lasterror['error'];
3251
                if (!empty($lasterror['detail'])) {
3252
                    $msg .= ' Detail: '. $lasterror['detail'];
3253
                }
3254
                if (!empty($lasterror['smtp_code'])) {
3255
                    $msg .= ' SMTP code: ' . $lasterror['smtp_code'];
3256
                }
3257
                if (!empty($lasterror['smtp_code_ex'])) {
3258
                    $msg .= ' Additional SMTP info: ' . $lasterror['smtp_code_ex'];
3259
                }
3260
            }
3261
        }
3262
        $this->ErrorInfo = $msg;
3263
    }
3264
3265
    /**
3266
     * Return an RFC 822 formatted date.
3267
     * @access public
3268
     * @return string
3269
     * @static
3270
     */
3271
    public static function rfcDate()
3272
    {
3273
        // Set the time zone to whatever the default is to avoid 500 errors
3274
        // Will default to UTC if it's not set properly in php.ini
3275
        date_default_timezone_set(@date_default_timezone_get());
3276
        return date('D, j M Y H:i:s O');
3277
    }
3278
3279
    /**
3280
     * Get the server hostname.
3281
     * Returns 'localhost.localdomain' if unknown.
3282
     * @access protected
3283
     * @return string
3284
     */
3285
    protected function serverHostname()
3286
    {
3287
        $result = 'localhost.localdomain';
3288
        if (!empty($this->Hostname)) {
3289
            $result = $this->Hostname;
3290
        } elseif (isset($_SERVER) and array_key_exists('SERVER_NAME', $_SERVER) and !empty($_SERVER['SERVER_NAME'])) {
3291
            $result = $_SERVER['SERVER_NAME'];
3292
        } elseif (function_exists('gethostname') && gethostname() !== false) {
3293
            $result = gethostname();
3294
        } elseif (php_uname('n') !== false) {
3295
            $result = php_uname('n');
3296
        }
3297
        return $result;
3298
    }
3299
3300
    /**
3301
     * Get an error message in the current language.
3302
     * @access protected
3303
     * @param string $key
3304
     * @return string
3305
     */
3306
    protected function lang($key)
3307
    {
3308
        if (count($this->language) < 1) {
3309
            $this->setLanguage('en'); // set the default language
3310
        }
3311
3312
        if (array_key_exists($key, $this->language)) {
3313
            if ($key == 'smtp_connect_failed') {
3314
                //Include a link to troubleshooting docs on SMTP connection failure
3315
                //this is by far the biggest cause of support questions
3316
                //but it's usually not PHPMailer's fault.
3317
                return $this->language[$key] . ' https://github.com/PHPMailer/PHPMailer/wiki/Troubleshooting';
3318
            }
3319
            return $this->language[$key];
3320
        } else {
3321
            //Return the key as a fallback
3322
            return $key;
3323
        }
3324
    }
3325
3326
    /**
3327
     * Check if an error occurred.
3328
     * @access public
3329
     * @return boolean True if an error did occur.
3330
     */
3331
    public function isError()
3332
    {
3333
        return ($this->error_count > 0);
3334
    }
3335
3336
    /**
3337
     * Ensure consistent line endings in a string.
3338
     * Changes every end of line from CRLF, CR or LF to $this->LE.
3339
     * @access public
3340
     * @param string $str String to fixEOL
3341
     * @return string
3342
     */
3343
    public function fixEOL($str)
3344
    {
3345
        // Normalise to \n
3346
        $nstr = str_replace(array("\r\n", "\r"), "\n", $str);
3347
        // Now convert LE as needed
3348
        if ($this->LE !== "\n") {
3349
            $nstr = str_replace("\n", $this->LE, $nstr);
3350
        }
3351
        return $nstr;
3352
    }
3353
3354
    /**
3355
     * Add a custom header.
3356
     * $name value can be overloaded to contain
3357
     * both header name and value (name:value)
3358
     * @access public
3359
     * @param string $name Custom header name
3360
     * @param string $value Header value
3361
     * @return void
3362
     */
3363
    public function addCustomHeader($name, $value = null)
3364
    {
3365
        if ($value === null) {
3366
            // Value passed in as name:value
3367
            $this->CustomHeader[] = explode(':', $name, 2);
3368
        } else {
3369
            $this->CustomHeader[] = array($name, $value);
3370
        }
3371
    }
3372
3373
    /**
3374
     * Returns all custom headers.
3375
     * @return array
3376
     */
3377
    public function getCustomHeaders()
3378
    {
3379
        return $this->CustomHeader;
3380
    }
3381
3382
    /**
3383
     * Create a message body from an HTML string.
3384
     * Automatically inlines images and creates a plain-text version by converting the HTML,
3385
     * overwriting any existing values in Body and AltBody.
3386
     * Do not source $message content from user input!
3387
     * $basedir is prepended when handling relative URLs, e.g. <img src="/images/a.png"> and must not be empty
3388
     * will look for an image file in $basedir/images/a.png and convert it to inline.
3389
     * If you don't provide a $basedir, relative paths will be left untouched (and thus probably break in email)
3390
     * If you don't want to apply these transformations to your HTML, just set Body and AltBody directly.
3391
     * @access public
3392
     * @param string $message HTML message string
3393
     * @param string $basedir Absolute path to a base directory to prepend to relative paths to images
3394
     * @param boolean|callable $advanced Whether to use the internal HTML to text converter
3395
     *    or your own custom converter @see PHPMailer::html2text()
3396
     * @return string $message The transformed message Body
3397
     */
3398
    public function msgHTML($message, $basedir = '', $advanced = false)
3399
    {
3400
        preg_match_all('/(src|background)=["\'](.*)["\']/Ui', $message, $images);
3401
        if (array_key_exists(2, $images)) {
3402
            if (strlen($basedir) > 1 && substr($basedir, -1) != '/') {
3403
                // Ensure $basedir has a trailing /
3404
                $basedir .= '/';
3405
            }
3406
            foreach ($images[2] as $imgindex => $url) {
3407
                // Convert data URIs into embedded images
3408
                if (preg_match('#^data:(image[^;,]*)(;base64)?,#', $url, $match)) {
3409
                    $data = substr($url, strpos($url, ','));
3410
                    if ($match[2]) {
3411
                        $data = base64_decode($data);
3412
                    } else {
3413
                        $data = rawurldecode($data);
3414
                    }
3415
                    $cid = md5($url) . '@phpmailer.0'; // RFC2392 S 2
3416
                    if ($this->addStringEmbeddedImage($data, $cid, 'embed' . $imgindex, 'base64', $match[1])) {
3417
                        $message = str_replace(
3418
                            $images[0][$imgindex],
3419
                            $images[1][$imgindex] . '="cid:' . $cid . '"',
3420
                            $message
3421
                        );
3422
                    }
3423
                    continue;
3424
                }
3425
                if (
3426
                    // Only process relative URLs if a basedir is provided (i.e. no absolute local paths)
3427
                    !empty($basedir)
3428
                    // Ignore URLs containing parent dir traversal (..)
3429
                    && (strpos($url, '..') === false)
3430
                    // Do not change urls that are already inline images
3431
                    && substr($url, 0, 4) !== 'cid:'
3432
                    // Do not change absolute URLs, including anonymous protocol
3433
                    && !preg_match('#^[a-z][a-z0-9+.-]*:?//#i', $url)
3434
                ) {
3435
                    $filename = basename($url);
3436
                    $directory = dirname($url);
3437
                    if ($directory == '.') {
3438
                        $directory = '';
3439
                    }
3440
                    $cid = md5($url) . '@phpmailer.0'; // RFC2392 S 2
3441
                    if (strlen($directory) > 1 && substr($directory, -1) != '/') {
3442
                        $directory .= '/';
3443
                    }
3444
                    if ($this->addEmbeddedImage(
3445
                        $basedir . $directory . $filename,
3446
                        $cid,
3447
                        $filename,
3448
                        'base64',
3449
                        self::_mime_types((string)self::mb_pathinfo($filename, PATHINFO_EXTENSION))
3450
                    )
3451
                    ) {
3452
                        $message = preg_replace(
3453
                            '/' . $images[1][$imgindex] . '=["\']' . preg_quote($url, '/') . '["\']/Ui',
3454
                            $images[1][$imgindex] . '="cid:' . $cid . '"',
3455
                            $message
3456
                        );
3457
                    }
3458
                }
3459
            }
3460
        }
3461
        $this->isHTML(true);
3462
        // Convert all message body line breaks to CRLF, makes quoted-printable encoding work much better
3463
        $this->Body = $this->normalizeBreaks($message);
3464
        $this->AltBody = $this->normalizeBreaks($this->html2text($message, $advanced));
3465
        if (!$this->alternativeExists()) {
3466
            $this->AltBody = 'To view this email message, open it in a program that understands HTML!' .
3467
                self::CRLF . self::CRLF;
3468
        }
3469
        return $this->Body;
3470
    }
3471
3472
    /**
3473
     * Convert an HTML string into plain text.
3474
     * This is used by msgHTML().
3475
     * Note - older versions of this function used a bundled advanced converter
3476
     * which was been removed for license reasons in #232.
3477
     * Example usage:
3478
     * <code>
3479
     * // Use default conversion
3480
     * $plain = $mail->html2text($html);
3481
     * // Use your own custom converter
3482
     * $plain = $mail->html2text($html, function($html) {
3483
     *     $converter = new MyHtml2text($html);
3484
     *     return $converter->get_text();
3485
     * });
3486
     * </code>
3487
     * @param string $html The HTML text to convert
3488
     * @param boolean|callable $advanced Any boolean value to use the internal converter,
3489
     *   or provide your own callable for custom conversion.
3490
     * @return string
3491
     */
3492
    public function html2text($html, $advanced = false)
3493
    {
3494
        if (is_callable($advanced)) {
3495
            return call_user_func($advanced, $html);
3496
        }
3497
        return html_entity_decode(
3498
            trim(strip_tags(preg_replace('/<(head|title|style|script)[^>]*>.*?<\/\\1>/si', '', $html))),
3499
            ENT_QUOTES,
3500
            $this->CharSet
3501
        );
3502
    }
3503
3504
    /**
3505
     * Get the MIME type for a file extension.
3506
     * @param string $ext File extension
3507
     * @access public
3508
     * @return string MIME type of file.
3509
     * @static
3510
     */
3511
    public static function _mime_types($ext = '')
3512
    {
3513
        $mimes = array(
3514
            'xl'    => 'application/excel',
3515
            'js'    => 'application/javascript',
3516
            'hqx'   => 'application/mac-binhex40',
3517
            'cpt'   => 'application/mac-compactpro',
3518
            'bin'   => 'application/macbinary',
3519
            'doc'   => 'application/msword',
3520
            'word'  => 'application/msword',
3521
            'xlsx'  => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
3522
            'xltx'  => 'application/vnd.openxmlformats-officedocument.spreadsheetml.template',
3523
            'potx'  => 'application/vnd.openxmlformats-officedocument.presentationml.template',
3524
            'ppsx'  => 'application/vnd.openxmlformats-officedocument.presentationml.slideshow',
3525
            'pptx'  => 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
3526
            'sldx'  => 'application/vnd.openxmlformats-officedocument.presentationml.slide',
3527
            'docx'  => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
3528
            'dotx'  => 'application/vnd.openxmlformats-officedocument.wordprocessingml.template',
3529
            'xlam'  => 'application/vnd.ms-excel.addin.macroEnabled.12',
3530
            'xlsb'  => 'application/vnd.ms-excel.sheet.binary.macroEnabled.12',
3531
            'class' => 'application/octet-stream',
3532
            'dll'   => 'application/octet-stream',
3533
            'dms'   => 'application/octet-stream',
3534
            'exe'   => 'application/octet-stream',
3535
            'lha'   => 'application/octet-stream',
3536
            'lzh'   => 'application/octet-stream',
3537
            'psd'   => 'application/octet-stream',
3538
            'sea'   => 'application/octet-stream',
3539
            'so'    => 'application/octet-stream',
3540
            'oda'   => 'application/oda',
3541
            'pdf'   => 'application/pdf',
3542
            'ai'    => 'application/postscript',
3543
            'eps'   => 'application/postscript',
3544
            'ps'    => 'application/postscript',
3545
            'smi'   => 'application/smil',
3546
            'smil'  => 'application/smil',
3547
            'mif'   => 'application/vnd.mif',
3548
            'xls'   => 'application/vnd.ms-excel',
3549
            'ppt'   => 'application/vnd.ms-powerpoint',
3550
            'wbxml' => 'application/vnd.wap.wbxml',
3551
            'wmlc'  => 'application/vnd.wap.wmlc',
3552
            'dcr'   => 'application/x-director',
3553
            'dir'   => 'application/x-director',
3554
            'dxr'   => 'application/x-director',
3555
            'dvi'   => 'application/x-dvi',
3556
            'gtar'  => 'application/x-gtar',
3557
            'php3'  => 'application/x-httpd-php',
3558
            'php4'  => 'application/x-httpd-php',
3559
            'php'   => 'application/x-httpd-php',
3560
            'phtml' => 'application/x-httpd-php',
3561
            'phps'  => 'application/x-httpd-php-source',
3562
            'swf'   => 'application/x-shockwave-flash',
3563
            'sit'   => 'application/x-stuffit',
3564
            'tar'   => 'application/x-tar',
3565
            'tgz'   => 'application/x-tar',
3566
            'xht'   => 'application/xhtml+xml',
3567
            'xhtml' => 'application/xhtml+xml',
3568
            'zip'   => 'application/zip',
3569
            'mid'   => 'audio/midi',
3570
            'midi'  => 'audio/midi',
3571
            'mp2'   => 'audio/mpeg',
3572
            'mp3'   => 'audio/mpeg',
3573
            'mpga'  => 'audio/mpeg',
3574
            'aif'   => 'audio/x-aiff',
3575
            'aifc'  => 'audio/x-aiff',
3576
            'aiff'  => 'audio/x-aiff',
3577
            'ram'   => 'audio/x-pn-realaudio',
3578
            'rm'    => 'audio/x-pn-realaudio',
3579
            'rpm'   => 'audio/x-pn-realaudio-plugin',
3580
            'ra'    => 'audio/x-realaudio',
3581
            'wav'   => 'audio/x-wav',
3582
            'bmp'   => 'image/bmp',
3583
            'gif'   => 'image/gif',
3584
            'jpeg'  => 'image/jpeg',
3585
            'jpe'   => 'image/jpeg',
3586
            'jpg'   => 'image/jpeg',
3587
            'png'   => 'image/png',
3588
            'tiff'  => 'image/tiff',
3589
            'tif'   => 'image/tiff',
3590
            'eml'   => 'message/rfc822',
3591
            'css'   => 'text/css',
3592
            'html'  => 'text/html',
3593
            'htm'   => 'text/html',
3594
            'shtml' => 'text/html',
3595
            'log'   => 'text/plain',
3596
            'text'  => 'text/plain',
3597
            'txt'   => 'text/plain',
3598
            'rtx'   => 'text/richtext',
3599
            'rtf'   => 'text/rtf',
3600
            'vcf'   => 'text/vcard',
3601
            'vcard' => 'text/vcard',
3602
            'xml'   => 'text/xml',
3603
            'xsl'   => 'text/xml',
3604
            'mpeg'  => 'video/mpeg',
3605
            'mpe'   => 'video/mpeg',
3606
            'mpg'   => 'video/mpeg',
3607
            'mov'   => 'video/quicktime',
3608
            'qt'    => 'video/quicktime',
3609
            'rv'    => 'video/vnd.rn-realvideo',
3610
            'avi'   => 'video/x-msvideo',
3611
            'movie' => 'video/x-sgi-movie'
3612
        );
3613
        if (array_key_exists(strtolower($ext), $mimes)) {
3614
            return $mimes[strtolower($ext)];
3615
        }
3616
        return 'application/octet-stream';
3617
    }
3618
3619
    /**
3620
     * Map a file name to a MIME type.
3621
     * Defaults to 'application/octet-stream', i.e.. arbitrary binary data.
3622
     * @param string $filename A file name or full path, does not need to exist as a file
3623
     * @return string
3624
     * @static
3625
     */
3626
    public static function filenameToType($filename)
3627
    {
3628
        // In case the path is a URL, strip any query string before getting extension
3629
        $qpos = strpos($filename, '?');
3630
        if (false !== $qpos) {
3631
            $filename = substr($filename, 0, $qpos);
3632
        }
3633
        $pathinfo = self::mb_pathinfo($filename);
3634
        return self::_mime_types($pathinfo['extension']);
3635
    }
3636
3637
    /**
3638
     * Multi-byte-safe pathinfo replacement.
3639
     * Drop-in replacement for pathinfo(), but multibyte-safe, cross-platform-safe, old-version-safe.
3640
     * Works similarly to the one in PHP >= 5.2.0
3641
     * @link http://www.php.net/manual/en/function.pathinfo.php#107461
3642
     * @param string $path A filename or path, does not need to exist as a file
3643
     * @param integer|string $options Either a PATHINFO_* constant,
3644
     *      or a string name to return only the specified piece, allows 'filename' to work on PHP < 5.2
3645
     * @return string|array
0 ignored issues
show
Consider making the return type a bit more specific; maybe use string|array<string,string>.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
3646
     * @static
3647
     */
3648
    public static function mb_pathinfo($path, $options = null)
3649
    {
3650
        $ret = array('dirname' => '', 'basename' => '', 'extension' => '', 'filename' => '');
3651
        $pathinfo = array();
3652
        if (preg_match('%^(.*?)[\\\\/]*(([^/\\\\]*?)(\.([^\.\\\\/]+?)|))[\\\\/\.]*$%im', $path, $pathinfo)) {
3653
            if (array_key_exists(1, $pathinfo)) {
3654
                $ret['dirname'] = $pathinfo[1];
3655
            }
3656
            if (array_key_exists(2, $pathinfo)) {
3657
                $ret['basename'] = $pathinfo[2];
3658
            }
3659
            if (array_key_exists(5, $pathinfo)) {
3660
                $ret['extension'] = $pathinfo[5];
3661
            }
3662
            if (array_key_exists(3, $pathinfo)) {
3663
                $ret['filename'] = $pathinfo[3];
3664
            }
3665
        }
3666
        switch ($options) {
3667
            case PATHINFO_DIRNAME:
3668
            case 'dirname':
3669
                return $ret['dirname'];
3670
            case PATHINFO_BASENAME:
3671
            case 'basename':
3672
                return $ret['basename'];
3673
            case PATHINFO_EXTENSION:
3674
            case 'extension':
3675
                return $ret['extension'];
3676
            case PATHINFO_FILENAME:
3677
            case 'filename':
3678
                return $ret['filename'];
3679
            default:
3680
                return $ret;
3681
        }
3682
    }
3683
3684
    /**
3685
     * Set or reset instance properties.
3686
     * You should avoid this function - it's more verbose, less efficient, more error-prone and
3687
     * harder to debug than setting properties directly.
3688
     * Usage Example:
3689
     * `$mail->set('SMTPSecure', 'tls');`
3690
     *   is the same as:
3691
     * `$mail->SMTPSecure = 'tls';`
3692
     * @access public
3693
     * @param string $name The property name to set
3694
     * @param mixed $value The value to set the property to
3695
     * @return boolean
3696
     * @TODO Should this not be using the __set() magic function?
3697
     */
3698
    public function set($name, $value = '')
3699
    {
3700
        if (property_exists($this, $name)) {
3701
            $this->$name = $value;
3702
            return true;
3703
        } else {
3704
            $this->setError($this->lang('variable_set') . $name);
3705
            return false;
3706
        }
3707
    }
3708
3709
    /**
3710
     * Strip newlines to prevent header injection.
3711
     * @access public
3712
     * @param string $str
3713
     * @return string
3714
     */
3715
    public function secureHeader($str)
3716
    {
3717
        return trim(str_replace(array("\r", "\n"), '', $str));
3718
    }
3719
3720
    /**
3721
     * Normalize line breaks in a string.
3722
     * Converts UNIX LF, Mac CR and Windows CRLF line breaks into a single line break format.
3723
     * Defaults to CRLF (for message bodies) and preserves consecutive breaks.
3724
     * @param string $text
3725
     * @param string $breaktype What kind of line break to use, defaults to CRLF
3726
     * @return string
3727
     * @access public
3728
     * @static
3729
     */
3730
    public static function normalizeBreaks($text, $breaktype = "\r\n")
3731
    {
3732
        return preg_replace('/(\r\n|\r|\n)/ms', $breaktype, $text);
3733
    }
3734
3735
    /**
3736
     * Set the public and private key files and password for S/MIME signing.
3737
     * @access public
3738
     * @param string $cert_filename
3739
     * @param string $key_filename
3740
     * @param string $key_pass Password for private key
3741
     * @param string $extracerts_filename Optional path to chain certificate
3742
     */
3743
    public function sign($cert_filename, $key_filename, $key_pass, $extracerts_filename = '')
3744
    {
3745
        $this->sign_cert_file = $cert_filename;
3746
        $this->sign_key_file = $key_filename;
3747
        $this->sign_key_pass = $key_pass;
3748
        $this->sign_extracerts_file = $extracerts_filename;
3749
    }
3750
3751
    /**
3752
     * Quoted-Printable-encode a DKIM header.
3753
     * @access public
3754
     * @param string $txt
3755
     * @return string
3756
     */
3757
    public function DKIM_QP($txt)
3758
    {
3759
        $line = '';
3760
        for ($i = 0; $i < strlen($txt); $i++) {
3761
            $ord = ord($txt[$i]);
3762
            if (((0x21 <= $ord) && ($ord <= 0x3A)) || $ord == 0x3C || ((0x3E <= $ord) && ($ord <= 0x7E))) {
3763
                $line .= $txt[$i];
3764
            } else {
3765
                $line .= '=' . sprintf('%02X', $ord);
3766
            }
3767
        }
3768
        return $line;
3769
    }
3770
3771
    /**
3772
     * Generate a DKIM signature.
3773
     * @access public
3774
     * @param string $signHeader
3775
     * @throws phpmailerException
3776
     * @return string The DKIM signature value
3777
     */
3778
    public function DKIM_Sign($signHeader)
3779
    {
3780
        if (!defined('PKCS7_TEXT')) {
3781
            if ($this->exceptions) {
3782
                throw new phpmailerException($this->lang('extension_missing') . 'openssl');
3783
            }
3784
            return '';
3785
        }
3786
        $privKeyStr = !empty($this->DKIM_private_string) ? $this->DKIM_private_string : file_get_contents($this->DKIM_private);
3787
        if ('' != $this->DKIM_passphrase) {
3788
            $privKey = openssl_pkey_get_private($privKeyStr, $this->DKIM_passphrase);
3789
        } else {
3790
            $privKey = openssl_pkey_get_private($privKeyStr);
3791
        }
3792
        //Workaround for missing digest algorithms in old PHP & OpenSSL versions
3793
        //@link http://stackoverflow.com/a/11117338/333340
3794
        if (version_compare(PHP_VERSION, '5.3.0') >= 0 and
3795
            in_array('sha256WithRSAEncryption', openssl_get_md_methods(true))) {
3796
            if (openssl_sign($signHeader, $signature, $privKey, 'sha256WithRSAEncryption')) {
3797
                openssl_pkey_free($privKey);
3798
                return base64_encode($signature);
3799
            }
3800
        } else {
3801
            $pinfo = openssl_pkey_get_details($privKey);
3802
            $hash = hash('sha256', $signHeader);
3803
            //'Magic' constant for SHA256 from RFC3447
3804
            //@link https://tools.ietf.org/html/rfc3447#page-43
3805
            $t = '3031300d060960864801650304020105000420' . $hash;
3806
            $pslen = $pinfo['bits'] / 8 - (strlen($t) / 2 + 3);
3807
            $eb = pack('H*', '0001' . str_repeat('FF', $pslen) . '00' . $t);
3808
3809
            if (openssl_private_encrypt($eb, $signature, $privKey, OPENSSL_NO_PADDING)) {
3810
                openssl_pkey_free($privKey);
3811
                return base64_encode($signature);
3812
            }
3813
        }
3814
        openssl_pkey_free($privKey);
3815
        return '';
3816
    }
3817
3818
    /**
3819
     * Generate a DKIM canonicalization header.
3820
     * @access public
3821
     * @param string $signHeader Header
3822
     * @return string
3823
     */
3824
    public function DKIM_HeaderC($signHeader)
3825
    {
3826
        $signHeader = preg_replace('/\r\n\s+/', ' ', $signHeader);
3827
        $lines = explode("\r\n", $signHeader);
3828
        foreach ($lines as $key => $line) {
3829
            list($heading, $value) = explode(':', $line, 2);
3830
            $heading = strtolower($heading);
3831
            $value = preg_replace('/\s{2,}/', ' ', $value); // Compress useless spaces
3832
            $lines[$key] = $heading . ':' . trim($value); // Don't forget to remove WSP around the value
3833
        }
3834
        $signHeader = implode("\r\n", $lines);
3835
        return $signHeader;
3836
    }
3837
3838
    /**
3839
     * Generate a DKIM canonicalization body.
3840
     * @access public
3841
     * @param string $body Message Body
3842
     * @return string
3843
     */
3844
    public function DKIM_BodyC($body)
3845
    {
3846
        if ($body == '') {
3847
            return "\r\n";
3848
        }
3849
        // stabilize line endings
3850
        $body = str_replace("\r\n", "\n", $body);
3851
        $body = str_replace("\n", "\r\n", $body);
3852
        // END stabilize line endings
3853 View Code Duplication
        while (substr($body, strlen($body) - 4, 4) == "\r\n\r\n") {
3854
            $body = substr($body, 0, strlen($body) - 2);
3855
        }
3856
        return $body;
3857
    }
3858
3859
    /**
3860
     * Create the DKIM header and body in a new message header.
3861
     * @access public
3862
     * @param string $headers_line Header lines
3863
     * @param string $subject Subject
3864
     * @param string $body Body
3865
     * @return string
3866
     */
3867
    public function DKIM_Add($headers_line, $subject, $body)
3868
    {
3869
        $DKIMsignatureType = 'rsa-sha256'; // Signature & hash algorithms
3870
        $DKIMcanonicalization = 'relaxed/simple'; // Canonicalization of header/body
3871
        $DKIMquery = 'dns/txt'; // Query method
3872
        $DKIMtime = time(); // Signature Timestamp = seconds since 00:00:00 - Jan 1, 1970 (UTC time zone)
3873
        $subject_header = "Subject: $subject";
3874
        $headers = explode($this->LE, $headers_line);
3875
        $from_header = '';
3876
        $to_header = '';
3877
        $date_header = '';
3878
        $current = '';
3879
        foreach ($headers as $header) {
3880
            if (strpos($header, 'From:') === 0) {
3881
                $from_header = $header;
3882
                $current = 'from_header';
3883
            } elseif (strpos($header, 'To:') === 0) {
3884
                $to_header = $header;
3885
                $current = 'to_header';
3886
            } elseif (strpos($header, 'Date:') === 0) {
3887
                $date_header = $header;
3888
                $current = 'date_header';
3889
            } else {
3890
                if (!empty($$current) && strpos($header, ' =?') === 0) {
3891
                    $$current .= $header;
3892
                } else {
3893
                    $current = '';
3894
                }
3895
            }
3896
        }
3897
        $from = str_replace('|', '=7C', $this->DKIM_QP($from_header));
3898
        $to = str_replace('|', '=7C', $this->DKIM_QP($to_header));
3899
        $date = str_replace('|', '=7C', $this->DKIM_QP($date_header));
3900
        $subject = str_replace(
3901
            '|',
3902
            '=7C',
3903
            $this->DKIM_QP($subject_header)
3904
        ); // Copied header fields (dkim-quoted-printable)
3905
        $body = $this->DKIM_BodyC($body);
3906
        $DKIMlen = strlen($body); // Length of body
3907
        $DKIMb64 = base64_encode(pack('H*', hash('sha256', $body))); // Base64 of packed binary SHA-256 hash of body
3908
        if ('' == $this->DKIM_identity) {
3909
            $ident = '';
3910
        } else {
3911
            $ident = ' i=' . $this->DKIM_identity . ';';
3912
        }
3913
        $dkimhdrs = 'DKIM-Signature: v=1; a=' .
3914
            $DKIMsignatureType . '; q=' .
3915
            $DKIMquery . '; l=' .
3916
            $DKIMlen . '; s=' .
3917
            $this->DKIM_selector .
3918
            ";\r\n" .
3919
            "\tt=" . $DKIMtime . '; c=' . $DKIMcanonicalization . ";\r\n" .
3920
            "\th=From:To:Date:Subject;\r\n" .
3921
            "\td=" . $this->DKIM_domain . ';' . $ident . "\r\n" .
3922
            "\tz=$from\r\n" .
3923
            "\t|$to\r\n" .
3924
            "\t|$date\r\n" .
3925
            "\t|$subject;\r\n" .
3926
            "\tbh=" . $DKIMb64 . ";\r\n" .
3927
            "\tb=";
3928
        $toSign = $this->DKIM_HeaderC(
3929
            $from_header . "\r\n" .
3930
            $to_header . "\r\n" .
3931
            $date_header . "\r\n" .
3932
            $subject_header . "\r\n" .
3933
            $dkimhdrs
3934
        );
3935
        $signed = $this->DKIM_Sign($toSign);
3936
        return $dkimhdrs . $signed . "\r\n";
3937
    }
3938
3939
    /**
3940
     * Detect if a string contains a line longer than the maximum line length allowed.
3941
     * @param string $str
3942
     * @return boolean
3943
     * @static
3944
     */
3945
    public static function hasLineLongerThanMax($str)
3946
    {
3947
        //+2 to include CRLF line break for a 1000 total
3948
        return (boolean)preg_match('/^(.{'.(self::MAX_LINE_LENGTH + 2).',})/m', $str);
3949
    }
3950
3951
    /**
3952
     * Allows for public read access to 'to' property.
3953
     * @note: Before the send() call, queued addresses (i.e. with IDN) are not yet included.
3954
     * @access public
3955
     * @return array
3956
     */
3957
    public function getToAddresses()
3958
    {
3959
        return $this->to;
3960
    }
3961
3962
    /**
3963
     * Allows for public read access to 'cc' property.
3964
     * @note: Before the send() call, queued addresses (i.e. with IDN) are not yet included.
3965
     * @access public
3966
     * @return array
3967
     */
3968
    public function getCcAddresses()
3969
    {
3970
        return $this->cc;
3971
    }
3972
3973
    /**
3974
     * Allows for public read access to 'bcc' property.
3975
     * @note: Before the send() call, queued addresses (i.e. with IDN) are not yet included.
3976
     * @access public
3977
     * @return array
3978
     */
3979
    public function getBccAddresses()
3980
    {
3981
        return $this->bcc;
3982
    }
3983
3984
    /**
3985
     * Allows for public read access to 'ReplyTo' property.
3986
     * @note: Before the send() call, queued addresses (i.e. with IDN) are not yet included.
3987
     * @access public
3988
     * @return array
3989
     */
3990
    public function getReplyToAddresses()
3991
    {
3992
        return $this->ReplyTo;
3993
    }
3994
3995
    /**
3996
     * Allows for public read access to 'all_recipients' property.
3997
     * @note: Before the send() call, queued addresses (i.e. with IDN) are not yet included.
3998
     * @access public
3999
     * @return array
4000
     */
4001
    public function getAllRecipientAddresses()
4002
    {
4003
        return $this->all_recipients;
4004
    }
4005
4006
    /**
4007
     * Perform a callback.
4008
     * @param boolean $isSent
4009
     * @param array $to
4010
     * @param array $cc
4011
     * @param array $bcc
4012
     * @param string $subject
4013
     * @param string $body
4014
     * @param string $from
4015
     */
4016
    protected function doCallback($isSent, $to, $cc, $bcc, $subject, $body, $from)
4017
    {
4018
        if (!empty($this->action_function) && is_callable($this->action_function)) {
4019
            $params = array($isSent, $to, $cc, $bcc, $subject, $body, $from);
4020
            call_user_func_array($this->action_function, $params);
4021
        }
4022
    }
4023
}
4024
4025
/**
4026
 * PHPMailer exception handler
4027
 * @package PHPMailer
4028
 */
4029
class phpmailerException extends Exception
4030
{
4031
    /**
4032
     * Prettify error message output
4033
     * @return string
4034
     */
4035
    public function errorMessage()
4036
    {
4037
        $errorMsg = '<strong>' . $this->getMessage() . "</strong><br />\n";
4038
        return $errorMsg;
4039
    }
4040
}
4041