PHPMailer::createBody()   F
last analyzed

Complexity

Conditions 26
Paths 16128

Size

Total Lines 192

Duplication

Lines 46
Ratio 23.96 %

Importance

Changes 0
Metric Value
cc 26
nc 16128
nop 0
dl 46
loc 192
rs 0
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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.26';
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 int
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 int
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 bool
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 int
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 bool
259
     */
260
    public $SMTPAutoTLS = true;
261
262
    /**
263
     * Whether to use SMTP authentication.
264
     * Uses the Username and Password properties.
265
     * @var bool
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 = [];
276
277
    /**
278
     * SMTP username.
279
     * @var string
280
     */
281
    public $Username = '';
282
283
    /**
284
     * SMTP password.
285
     * @var string
286
     */
287
    public $Password = '';
288
289
    /**
290
     * SMTP auth type.
291
     * Options are CRAM-MD5, LOGIN, PLAIN, NTLM, XOAUTH2, attempted in that order if not specified
292
     * @var string
293
     */
294
    public $AuthType = '';
295
296
    /**
297
     * SMTP realm.
298
     * Used for NTLM auth
299
     * @var string
300
     */
301
    public $Realm = '';
302
303
    /**
304
     * SMTP workstation.
305
     * Used for NTLM auth
306
     * @var string
307
     */
308
    public $Workstation = '';
309
310
    /**
311
     * The SMTP server timeout in seconds.
312
     * Default of 5 minutes (300sec) is from RFC2821 section 4.5.3.2
313
     * @var int
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 int
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 bool
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 bool
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 = [];
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 bool
376
     */
377
    public $do_verp = false;
378
379
    /**
380
     * Whether to allow sending messages with an empty body.
381
     * @var bool
382
     */
383
    public $AllowEmpty = false;
384
385
    /**
386
     * The default line ending.
387
     * @note The default remains "\n". We force CRLF where we know
388
     *        it must be used via self::CRLF.
389
     * @var string
390
     */
391
    public $LE = "\n";
392
393
    /**
394
     * DKIM selector.
395
     * @var string
396
     */
397
    public $DKIM_selector = '';
398
399
    /**
400
     * DKIM Identity.
401
     * Usually the email address used as the source of the email.
402
     * @var string
403
     */
404
    public $DKIM_identity = '';
405
406
    /**
407
     * DKIM passphrase.
408
     * Used if your key is encrypted.
409
     * @var string
410
     */
411
    public $DKIM_passphrase = '';
412
413
    /**
414
     * DKIM signing domain name.
415
     * @example 'example.com'
416
     * @var string
417
     */
418
    public $DKIM_domain = '';
419
420
    /**
421
     * DKIM private key file path.
422
     * @var string
423
     */
424
    public $DKIM_private = '';
425
426
    /**
427
     * DKIM private key string.
428
     * If set, takes precedence over `$DKIM_private`.
429
     * @var string
430
     */
431
    public $DKIM_private_string = '';
432
433
    /**
434
     * Callback Action function name.
435
     *
436
     * The function that handles the result of the send email action.
437
     * It is called out by send() for each email sent.
438
     *
439
     * Value can be any php callable: http://www.php.net/is_callable
440
     *
441
     * Parameters:
442
     *   boolean $result        result of the send action
443
     *   array   $to            email addresses of the recipients
444
     *   array   $cc            cc email addresses
445
     *   array   $bcc           bcc email addresses
446
     *   string  $subject       the subject
447
     *   string  $body          the email body
448
     *   string  $from          email address of sender
449
     * @var string
450
     */
451
    public $action_function = '';
452
453
    /**
454
     * What to put in the X-Mailer header.
455
     * Options: An empty string for PHPMailer default, whitespace for none, or a string to use
456
     * @var string
457
     */
458
    public $XMailer = '';
459
460
    /**
461
     * Which validator to use by default when validating email addresses.
462
     * May be a callable to inject your own validator, but there are several built-in validators.
463
     * @see PHPMailer::validateAddress()
464
     * @var string|callable
465
     * @static
466
     */
467
    public static $validator = 'auto';
468
469
    /**
470
     * An instance of the SMTP sender class.
471
     * @var SMTP
472
     * @access protected
473
     */
474
    protected $smtp = null;
475
476
    /**
477
     * The array of 'to' names and addresses.
478
     * @var array
479
     * @access protected
480
     */
481
    protected $to = [];
482
483
    /**
484
     * The array of 'cc' names and addresses.
485
     * @var array
486
     * @access protected
487
     */
488
    protected $cc = [];
489
490
    /**
491
     * The array of 'bcc' names and addresses.
492
     * @var array
493
     * @access protected
494
     */
495
    protected $bcc = [];
496
497
    /**
498
     * The array of reply-to names and addresses.
499
     * @var array
500
     * @access protected
501
     */
502
    protected $ReplyTo = [];
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 = [];
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 = [];
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 = [];
534
535
    /**
536
     * The array of attachments.
537
     * @var array
538
     * @access protected
539
     */
540
    protected $attachment = [];
541
542
    /**
543
     * The array of custom headers.
544
     * @var array
545
     * @access protected
546
     */
547
    protected $CustomHeader = [];
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 = [];
569
570
    /**
571
     * The array of available languages.
572
     * @var array
573
     * @access protected
574
     */
575
    protected $language = [];
576
577
    /**
578
     * The number of errors encountered.
579
     * @var int
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 bool
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 int
650
     */
651
    const MAX_LINE_LENGTH = 998;
652
653
    /**
654
     * Constructor.
655
     * @param bool $exceptions Should we throw external exceptions?
0 ignored issues
show
Documentation introduced by
Should the type for parameter $exceptions not be boolean|null?

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.

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

Loading history...
656
     */
657
    public function __construct($exceptions = null)
658
    {
659
        if (null !== $exceptions) {
660
            $this->exceptions = (bool)$exceptions;
661
        }
662
        //Pick an appropriate debug output format automatically
663
        $this->Debugoutput = (false !== mb_strpos(PHP_SAPI, 'cli') ? 'echo' : 'html');
664
    }
665
666
    /**
667
     * Destructor.
668
     */
669
    public function __destruct()
670
    {
671
        //Close any open SMTP connection nicely
672
        $this->smtpClose();
673
    }
674
675
    /**
676
     * Call mail() in a safe_mode-aware fashion.
677
     * Also, unless sendmail_path points to sendmail (or something that
678
     * claims to be sendmail), don't pass params (not a perfect fix,
679
     * but it will do)
680
     * @param string $to      To
681
     * @param string $subject Subject
682
     * @param string $body    Message Body
683
     * @param string $header  Additional Header(s)
684
     * @param string $params  Params
685
     * @access private
686
     * @return bool
687
     */
688
    private function mailPassthru($to, $subject, $body, $header, $params)
689
    {
690
        //Check overloading of mail function to avoid double-encoding
691
        if (ini_get('mbstring.func_overload') & 1) {
692
            $subject = $this->secureHeader($subject);
693
        } else {
694
            $subject = $this->encodeHeader($this->secureHeader($subject));
695
        }
696
697
        //Can't use additional_parameters in safe_mode, calling mail() with null params breaks
698
        //@link http://php.net/manual/en/function.mail.php
699
        if (ini_get('safe_mode') or !$this->UseSendmailOptions or null === $params) {
700
            $result = @mail($to, $subject, $body, $header);
701
        } else {
702
            $result = @mail($to, $subject, $body, $header, $params);
703
        }
704
705
        return $result;
706
    }
707
708
    /**
709
     * Output debugging info via user-defined method.
710
     * Only generates output if SMTP debug output is enabled (@see SMTP::$do_debug).
711
     * @see PHPMailer::$Debugoutput
712
     * @see PHPMailer::$SMTPDebug
713
     * @param string $str
714
     */
715
    protected function edebug($str)
716
    {
717
        if ($this->SMTPDebug <= 0) {
718
            return;
719
        }
720
        //Avoid clash with built-in function names
721 View Code Duplication
        if (!in_array($this->Debugoutput, ['error_log', 'html', 'echo'], true) and is_callable($this->Debugoutput)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
722
            call_user_func($this->Debugoutput, $str, $this->SMTPDebug);
723
724
            return;
725
        }
726
        switch ($this->Debugoutput) {
727
            case 'error_log':
728
                //Don't output, just log
729
                error_log($str);
730
                break;
731
            case 'html':
732
                //Cleans up output a bit for a better looking, HTML-safe output
733
                echo htmlentities(preg_replace('/[\r\n]+/', '', $str), ENT_QUOTES, 'UTF-8') . "<br>\n";
734
                break;
735
            case 'echo':
736 View Code Duplication
            default:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
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("\n", "\n                   \t                  ", trim($str)) . "\n";
740
        }
741
    }
742
743
    /**
744
     * Sets message type to HTML or plain.
745
     * @param bool $isHtml True for HTML mode.
746
     */
747
    public function isHTML($isHtml = true)
748
    {
749
        if ($isHtml) {
750
            $this->ContentType = 'text/html';
751
        } else {
752
            $this->ContentType = 'text/plain';
753
        }
754
    }
755
756
    /**
757
     * Send messages using SMTP.
758
     */
759
    public function isSMTP()
760
    {
761
        $this->Mailer = 'smtp';
762
    }
763
764
    /**
765
     * Send messages using PHP's mail() function.
766
     */
767
    public function isMail()
768
    {
769
        $this->Mailer = 'mail';
770
    }
771
772
    /**
773
     * Send messages using $Sendmail.
774
     */
775 View Code Duplication
    public function isSendmail()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
776
    {
777
        $ini_sendmail_path = ini_get('sendmail_path');
778
779
        if (!mb_stristr($ini_sendmail_path, 'sendmail')) {
780
            $this->Sendmail = '/usr/sbin/sendmail';
781
        } else {
782
            $this->Sendmail = $ini_sendmail_path;
783
        }
784
        $this->Mailer = 'sendmail';
785
    }
786
787
    /**
788
     * Send messages using qmail.
789
     */
790 View Code Duplication
    public function isQmail()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
791
    {
792
        $ini_sendmail_path = ini_get('sendmail_path');
793
794
        if (!mb_stristr($ini_sendmail_path, 'qmail')) {
795
            $this->Sendmail = '/var/qmail/bin/qmail-inject';
796
        } else {
797
            $this->Sendmail = $ini_sendmail_path;
798
        }
799
        $this->Mailer = 'qmail';
800
    }
801
802
    /**
803
     * Add a "To" address.
804
     * @param string $address The email address to send to
805
     * @param string $name
806
     * @throws \phpmailerException
807
     * @return bool true on success, false if address already used or invalid in some way
808
     */
809
    public function addAddress($address, $name = '')
810
    {
811
        return $this->addOrEnqueueAnAddress('to', $address, $name);
812
    }
813
814
    /**
815
     * Add a "CC" address.
816
     * @note: This function works with the SMTP mailer on win32, not with the "mail" mailer.
817
     * @param string $address The email address to send to
818
     * @param string $name
819
     * @throws \phpmailerException
820
     * @return bool true on success, false if address already used or invalid in some way
821
     */
822
    public function addCC($address, $name = '')
823
    {
824
        return $this->addOrEnqueueAnAddress('cc', $address, $name);
825
    }
826
827
    /**
828
     * Add a "BCC" address.
829
     * @note: This function works with the SMTP mailer on win32, not with the "mail" mailer.
830
     * @param string $address The email address to send to
831
     * @param string $name
832
     * @throws \phpmailerException
833
     * @return bool true on success, false if address already used or invalid in some way
834
     */
835
    public function addBCC($address, $name = '')
836
    {
837
        return $this->addOrEnqueueAnAddress('bcc', $address, $name);
838
    }
839
840
    /**
841
     * Add a "Reply-To" address.
842
     * @param string $address The email address to reply to
843
     * @param string $name
844
     * @throws \phpmailerException
845
     * @return bool true on success, false if address already used or invalid in some way
846
     */
847
    public function addReplyTo($address, $name = '')
848
    {
849
        return $this->addOrEnqueueAnAddress('Reply-To', $address, $name);
850
    }
851
852
    /**
853
     * Add an address to one of the recipient arrays or to the ReplyTo array. Because PHPMailer
854
     * can't validate addresses with an IDN without knowing the PHPMailer::$CharSet (that can still
855
     * be modified after calling this function), addition of such addresses is delayed until send().
856
     * Addresses that have been added already return false, but do not throw exceptions.
857
     * @param string $kind    One of 'to', 'cc', 'bcc', or 'ReplyTo'
858
     * @param string $address The email address to send, resp. to reply to
859
     * @param string $name
860
     * @throws phpmailerException
861
     * @return bool true on success, false if address already used or invalid in some way
862
     * @access protected
863
     */
864
    protected function addOrEnqueueAnAddress($kind, $address, $name)
865
    {
866
        $address = trim($address);
867
        $name    = trim(preg_replace('/[\r\n]+/', '', $name)); //Strip breaks and trim
868
        if (false === ($pos = mb_strrpos($address, '@'))) {
869
            // At-sign is misssing.
870
            $error_message = $this->lang('invalid_address') . " (addAnAddress $kind): $address";
871
            $this->setError($error_message);
872
            $this->edebug($error_message);
873
            if ($this->exceptions) {
874
                throw new phpmailerException($error_message);
875
            }
876
877
            return false;
878
        }
879
        $params = [$kind, $address, $name];
880
        // Enqueue addresses with IDN until we know the PHPMailer::$CharSet.
881
        if ($this->has8bitChars(mb_substr($address, ++$pos)) and $this->idnSupported()) {
882
            if ('Reply-To' != $kind) {
883
                if (!array_key_exists($address, $this->RecipientsQueue)) {
884
                    $this->RecipientsQueue[$address] = $params;
885
886
                    return true;
887
                }
888
            } else {
889
                if (!array_key_exists($address, $this->ReplyToQueue)) {
890
                    $this->ReplyToQueue[$address] = $params;
891
892
                    return true;
893
                }
894
            }
895
896
            return false;
897
        }
898
        // Immediately add standard addresses without IDN.
899
        return call_user_func_array([$this, 'addAnAddress'], $params);
900
    }
901
902
    /**
903
     * Add an address to one of the recipient arrays or to the ReplyTo array.
904
     * Addresses that have been added already return false, but do not throw exceptions.
905
     * @param string $kind    One of 'to', 'cc', 'bcc', or 'ReplyTo'
906
     * @param string $address The email address to send, resp. to reply to
907
     * @param string $name
908
     * @throws phpmailerException
909
     * @return bool true on success, false if address already used or invalid in some way
910
     * @access protected
911
     */
912
    protected function addAnAddress($kind, $address, $name = '')
913
    {
914
        if (!in_array($kind, ['to', 'cc', 'bcc', 'Reply-To'], true)) {
915
            $error_message = $this->lang('Invalid recipient kind: ') . $kind;
916
            $this->setError($error_message);
917
            $this->edebug($error_message);
918
            if ($this->exceptions) {
919
                throw new phpmailerException($error_message);
920
            }
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
932
            return false;
933
        }
934
        if ('Reply-To' != $kind) {
935 View Code Duplication
            if (!array_key_exists(mb_strtolower($address), $this->all_recipients)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
936
                array_push($this->$kind, [$address, $name]);
937
                $this->all_recipients[mb_strtolower($address)] = true;
938
939
                return true;
940
            }
941
        } else {
942 View Code Duplication
            if (!array_key_exists(mb_strtolower($address), $this->ReplyTo)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
943
                $this->ReplyTo[mb_strtolower($address)] = [$address, $name];
944
945
                return true;
946
            }
947
        }
948
949
        return false;
950
    }
951
952
    /**
953
     * Parse and validate a string containing one or more RFC822-style comma-separated email addresses
954
     * of the form "display name <address>" into an array of name/address pairs.
955
     * Uses the imap_rfc822_parse_adrlist function if the IMAP extension is available.
956
     * Note that quotes in the name part are removed.
957
     * @param string $addrstr The address list string
958
     * @param bool   $useimap Whether to use the IMAP extension to parse the list
959
     * @return array
960
     * @link http://www.andrew.cmu.edu/user/agreen1/testing/mrbs/web/Mail/RFC822.php A more careful implementation
961
     */
962
    public function parseAddresses($addrstr, $useimap = true)
963
    {
964
        $addresses = [];
965
        if ($useimap and function_exists('imap_rfc822_parse_adrlist')) {
966
            //Use this built-in parser if it's available
967
            $list = imap_rfc822_parse_adrlist($addrstr, '');
968
            foreach ($list as $address) {
969
                if ('.SYNTAX-ERROR.' != $address->host) {
970
                    if ($this->validateAddress($address->mailbox . '@' . $address->host)) {
971
                        $addresses[] = [
972
                            'name'    => (property_exists($address, 'personal') ? $address->personal : ''),
973
                            'address' => $address->mailbox . '@' . $address->host,
974
                        ];
975
                    }
976
                }
977
            }
978
        } else {
979
            //Use this simpler parser
980
            $list = explode(',', $addrstr);
981
            foreach ($list as $address) {
982
                $address = trim($address);
983
                //Is there a separate name part?
984
                if (false === mb_strpos($address, '<')) {
985
                    //No separate name, just use the whole thing
986
                    if ($this->validateAddress($address)) {
987
                        $addresses[] = [
988
                            'name'    => '',
989
                            'address' => $address,
990
                        ];
991
                    }
992
                } else {
993
                    list($name, $email) = explode('<', $address);
994
                    $email = trim(str_replace('>', '', $email));
995
                    if ($this->validateAddress($email)) {
996
                        $addresses[] = [
997
                            'name'    => trim(str_replace(['"', "'"], '', $name)),
998
                            'address' => $email,
999
                        ];
1000
                    }
1001
                }
1002
            }
1003
        }
1004
1005
        return $addresses;
1006
    }
1007
1008
    /**
1009
     * Set the From and FromName properties.
1010
     * @param string $address
1011
     * @param string $name
1012
     * @param bool   $auto Whether to also set the Sender address, defaults to true
1013
     * @throws phpmailerException
1014
     * @return bool
1015
     */
1016
    public function setFrom($address, $name = '', $auto = true)
1017
    {
1018
        $address = trim($address);
1019
        $name    = trim(preg_replace('/[\r\n]+/', '', $name)); //Strip breaks and trim
1020
        // Don't validate now addresses with IDN. Will be done in send().
1021
        if (false === ($pos = mb_strrpos($address, '@')) or (!$this->has8bitChars(mb_substr($address, ++$pos)) or !$this->idnSupported()) and !$this->validateAddress($address)) {
1022
            $error_message = $this->lang('invalid_address') . " (setFrom) $address";
1023
            $this->setError($error_message);
1024
            $this->edebug($error_message);
1025
            if ($this->exceptions) {
1026
                throw new phpmailerException($error_message);
1027
            }
1028
1029
            return false;
1030
        }
1031
        $this->From     = $address;
1032
        $this->FromName = $name;
1033
        if ($auto) {
1034
            if (empty($this->Sender)) {
1035
                $this->Sender = $address;
1036
            }
1037
        }
1038
1039
        return true;
1040
    }
1041
1042
    /**
1043
     * Return the Message-ID header of the last email.
1044
     * Technically this is the value from the last time the headers were created,
1045
     * but it's also the message ID of the last sent message except in
1046
     * pathological cases.
1047
     * @return string
1048
     */
1049
    public function getLastMessageID()
1050
    {
1051
        return $this->lastMessageID;
1052
    }
1053
1054
    /**
1055
     * Check that a string looks like an email address.
1056
     * @param string          $address       The email address to check
1057
     * @param string|callable $patternselect A selector for the validation pattern to use :
0 ignored issues
show
Documentation introduced by
Should the type for parameter $patternselect not be callable|null?

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.

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

Loading history...
1058
     *                                       * `auto` Pick best pattern automatically;
1059
     *                                       * `pcre8` Use the squiloople.com pattern, requires PCRE > 8.0, PHP >= 5.3.2, 5.2.14;
1060
     *                                       * `pcre` Use old PCRE implementation;
1061
     *                                       * `php` Use PHP built-in FILTER_VALIDATE_EMAIL;
1062
     *                                       * `html5` Use the pattern given by the HTML5 spec for 'email' type form input elements.
1063
     *                                       * `noregex` Don't use a regex: super fast, really dumb.
1064
     *                                       Alternatively you may pass in a callable to inject your own validator, for example:
1065
     *                                       PHPMailer::validateAddress('[email protected]', function($address) {
1066
     *                                       return (strpos($address, '@') !== false);
1067
     *                                       });
1068
     *                                       You can also set the PHPMailer::$validator static to a callable, allowing built-in methods to use your validator.
1069
     * @return bool
1070
     * @static
1071
     * @access public
1072
     */
1073
    public static function validateAddress($address, $patternselect = null)
1074
    {
1075
        if (null === $patternselect) {
1076
            $patternselect = self::$validator;
1077
        }
1078
        if (is_callable($patternselect)) {
1079
            return call_user_func($patternselect, $address);
1080
        }
1081
        //Reject line breaks in addresses; it's valid RFC5322, but not RFC5321
1082
        if (false !== mb_strpos($address, "\n") or false !== mb_strpos($address, "\r")) {
1083
            return false;
1084
        }
1085
        if (!$patternselect or 'auto' == $patternselect) {
1086
            //Check this constant first so it works when extension_loaded() is disabled by safe mode
1087
            //Constant was added in PHP 5.2.4
1088
            if (defined('PCRE_VERSION')) {
1089
                //This pattern can get stuck in a recursive loop in PCRE <= 8.0.2
1090
                if (version_compare(PCRE_VERSION, '8.0.3') >= 0) {
1091
                    $patternselect = 'pcre8';
1092
                } else {
1093
                    $patternselect = 'pcre';
1094
                }
1095
            } elseif (function_exists('extension_loaded') and extension_loaded('pcre')) {
1096
                //Fall back to older PCRE
1097
                $patternselect = 'pcre';
1098
            } else {
1099
                //Filter_var appeared in PHP 5.2.0 and does not require the PCRE extension
1100
                if (version_compare(PHP_VERSION, '5.2.0') >= 0) {
1101
                    $patternselect = 'php';
1102
                } else {
1103
                    $patternselect = 'noregex';
1104
                }
1105
            }
1106
        }
1107
        switch ($patternselect) {
1108 View Code Duplication
            case 'pcre8':
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1109
                /**
1110
                 * Uses the same RFC5322 regex on which FILTER_VALIDATE_EMAIL is based, but allows dotless domains.
1111
                 * @link      http://squiloople.com/2009/12/20/email-address-validation/
1112
                 * @copyright 2009-2010 Michael Rushton
1113
                 * Feel free to use and redistribute this code. But please keep this copyright notice.
1114
                 */
1115
                return (bool)preg_match('/^(?!(?>(?1)"?(?>\\\[ -~]|[^"])"?(?1)){255,})(?!(?>(?1)"?(?>\\\[ -~]|[^"])"?(?1)){65,}@)'
1116
                                        . '((?>(?>(?>((?>(?>(?>\x0D\x0A)?[\t ])+|(?>[\t ]*\x0D\x0A)?[\t ]+)?)(\((?>(?2)'
1117
                                        . '(?>[\x01-\x08\x0B\x0C\x0E-\'*-\[\]-\x7F]|\\\[\x00-\x7F]|(?3)))*(?2)\)))+(?2))|(?2))?)'
1118
                                        . '([!#-\'*+\/-9=?^-~-]+|"(?>(?2)(?>[\x01-\x08\x0B\x0C\x0E-!#-\[\]-\x7F]|\\\[\x00-\x7F]))*'
1119
                                        . '(?2)")(?>(?1)\.(?1)(?4))*(?1)@(?!(?1)[a-z0-9-]{64,})(?1)(?>([a-z0-9](?>[a-z0-9-]*[a-z0-9])?)'
1120
                                        . '(?>(?1)\.(?!(?1)[a-z0-9-]{64,})(?1)(?5)){0,126}|\[(?:(?>IPv6:(?>([a-f0-9]{1,4})(?>:(?6)){7}'
1121
                                        . '|(?!(?:.*[a-f0-9][:\]]){8,})((?6)(?>:(?6)){0,6})?::(?7)?))|(?>(?>IPv6:(?>(?6)(?>:(?6)){5}:'
1122
                                        . '|(?!(?:.*[a-f0-9]:){6,})(?8)?::(?>((?6)(?>:(?6)){0,4}):)?))?(25[0-5]|2[0-4][0-9]|1[0-9]{2}'
1123
                                        . '|[1-9]?[0-9])(?>\.(?9)){3}))\])(?1)$/isD', $address);
1124 View Code Duplication
            case 'pcre':
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1125
                //An older regex that doesn't need a recent PCRE
1126
                return (bool)preg_match('/^(?!(?>"?(?>\\\[ -~]|[^"])"?){255,})(?!(?>"?(?>\\\[ -~]|[^"])"?){65,}@)(?>'
1127
                                        . '[!#-\'*+\/-9=?^-~-]+|"(?>(?>[\x01-\x08\x0B\x0C\x0E-!#-\[\]-\x7F]|\\\[\x00-\xFF]))*")'
1128
                                        . '(?>\.(?>[!#-\'*+\/-9=?^-~-]+|"(?>(?>[\x01-\x08\x0B\x0C\x0E-!#-\[\]-\x7F]|\\\[\x00-\xFF]))*"))*'
1129
                                        . '@(?>(?![a-z0-9-]{64,})(?>[a-z0-9](?>[a-z0-9-]*[a-z0-9])?)(?>\.(?![a-z0-9-]{64,})'
1130
                                        . '(?>[a-z0-9](?>[a-z0-9-]*[a-z0-9])?)){0,126}|\[(?:(?>IPv6:(?>(?>[a-f0-9]{1,4})(?>:'
1131
                                        . '[a-f0-9]{1,4}){7}|(?!(?:.*[a-f0-9][:\]]){8,})(?>[a-f0-9]{1,4}(?>:[a-f0-9]{1,4}){0,6})?'
1132
                                        . '::(?>[a-f0-9]{1,4}(?>:[a-f0-9]{1,4}){0,6})?))|(?>(?>IPv6:(?>[a-f0-9]{1,4}(?>:'
1133
                                        . '[a-f0-9]{1,4}){5}:|(?!(?:.*[a-f0-9]:){6,})(?>[a-f0-9]{1,4}(?>:[a-f0-9]{1,4}){0,4})?'
1134
                                        . '::(?>(?:[a-f0-9]{1,4}(?>:[a-f0-9]{1,4}){0,4}):)?))?(?>25[0-5]|2[0-4][0-9]|1[0-9]{2}'
1135
                                        . '|[1-9]?[0-9])(?>\.(?>25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3}))\])$/isD', $address);
1136
            case 'html5':
1137
                /**
1138
                 * This is the pattern used in the HTML5 spec for validation of 'email' type form input elements.
1139
                 * @link http://www.whatwg.org/specs/web-apps/current-work/#e-mail-state-(type=email)
1140
                 */
1141
                return (bool)preg_match('/^[a-zA-Z0-9.!#$%&\'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}' . '[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/sD', $address);
1142
            case 'noregex':
1143
                //No PCRE! Do something _very_ approximate!
1144
                //Check the address is 3 chars or longer and contains an @ that's not the first or last char
1145
                return (mb_strlen($address) >= 3 and mb_strpos($address, '@') >= 1 and mb_strpos($address, '@') != mb_strlen($address) - 1);
1146
            case 'php':
1147
            default:
1148
                return (bool)filter_var($address, FILTER_VALIDATE_EMAIL);
1149
        }
1150
    }
1151
1152
    /**
1153
     * Tells whether IDNs (Internationalized Domain Names) are supported or not. This requires the
1154
     * "intl" and "mbstring" PHP extensions.
1155
     * @return bool "true" if required functions for IDN support are present
1156
     */
1157
    public function idnSupported()
1158
    {
1159
        // @TODO: Write our own "idn_to_ascii" function for PHP <= 5.2.
1160
        return function_exists('idn_to_ascii') and function_exists('mb_convert_encoding');
1161
    }
1162
1163
    /**
1164
     * Converts IDN in given email address to its ASCII form, also known as punycode, if possible.
1165
     * Important: Address must be passed in same encoding as currently set in PHPMailer::$CharSet.
1166
     * This function silently returns unmodified address if:
1167
     * - No conversion is necessary (i.e. domain name is not an IDN, or is already in ASCII form)
1168
     * - Conversion to punycode is impossible (e.g. required PHP functions are not available)
1169
     *   or fails for any reason (e.g. domain has characters not allowed in an IDN)
1170
     * @see PHPMailer::$CharSet
1171
     * @param string $address The email address to convert
1172
     * @return string The encoded address in ASCII form
1173
     */
1174
    public function punyencodeAddress($address)
1175
    {
1176
        // Verify we have required functions, CharSet, and at-sign.
1177
        if ($this->idnSupported() and !empty($this->CharSet) and false !== ($pos = mb_strrpos($address, '@'))) {
1178
            $domain = mb_substr($address, ++$pos);
1179
            // Verify CharSet string is a valid one, and domain properly encoded in this CharSet.
1180
            if ($this->has8bitChars($domain) and @mb_check_encoding($domain, $this->CharSet)) {
1181
                $domain = mb_convert_encoding($domain, 'UTF-8', $this->CharSet);
1182
                if (false !== ($punycode = defined('INTL_IDNA_VARIANT_UTS46') ? idn_to_ascii($domain, 0, INTL_IDNA_VARIANT_UTS46) : idn_to_ascii($domain))) {
1183
                    return mb_substr($address, 0, $pos) . $punycode;
1184
                }
1185
            }
1186
        }
1187
1188
        return $address;
1189
    }
1190
1191
    /**
1192
     * Create a message and send it.
1193
     * Uses the sending method specified by $Mailer.
1194
     * @throws phpmailerException
1195
     * @return bool false on error - See the ErrorInfo property for details of the error.
1196
     */
1197
    public function send()
1198
    {
1199
        try {
1200
            if (!$this->preSend()) {
1201
                return false;
1202
            }
1203
1204
            return $this->postSend();
1205
        }
1206
        catch (phpmailerException $exc) {
1207
            $this->mailHeader = '';
1208
            $this->setError($exc->getMessage());
1209
            if ($this->exceptions) {
1210
                throw $exc;
1211
            }
1212
1213
            return false;
1214
        }
1215
    }
1216
1217
    /**
1218
     * Prepare a message for sending.
1219
     * @throws phpmailerException
1220
     * @return bool
1221
     */
1222
    public function preSend()
1223
    {
1224
        try {
1225
            $this->error_count = 0; // Reset errors
1226
            $this->mailHeader  = '';
1227
1228
            // Dequeue recipient and Reply-To addresses with IDN
1229
            foreach (array_merge($this->RecipientsQueue, $this->ReplyToQueue) as $params) {
1230
                $params[1] = $this->punyencodeAddress($params[1]);
1231
                call_user_func_array([$this, 'addAnAddress'], $params);
1232
            }
1233
            if ((count($this->to) + count($this->cc) + count($this->bcc)) < 1) {
1234
                throw new phpmailerException($this->lang('provide_address'), self::STOP_CRITICAL);
1235
            }
1236
1237
            // Validate From, Sender, and ConfirmReadingTo addresses
1238
            foreach (['From', 'Sender', 'ConfirmReadingTo'] as $address_kind) {
1239
                $this->$address_kind = trim($this->$address_kind);
1240
                if (empty($this->$address_kind)) {
1241
                    continue;
1242
                }
1243
                $this->$address_kind = $this->punyencodeAddress($this->$address_kind);
1244
                if (!$this->validateAddress($this->$address_kind)) {
1245
                    $error_message = $this->lang('invalid_address') . ' (punyEncode) ' . $this->$address_kind;
1246
                    $this->setError($error_message);
1247
                    $this->edebug($error_message);
1248
                    if ($this->exceptions) {
1249
                        throw new phpmailerException($error_message);
1250
                    }
1251
1252
                    return false;
1253
                }
1254
            }
1255
1256
            // Set whether the message is multipart/alternative
1257
            if ($this->alternativeExists()) {
1258
                $this->ContentType = 'multipart/alternative';
1259
            }
1260
1261
            $this->setMessageType();
1262
            // Refuse to send an empty message unless we are specifically allowing it
1263
            if (!$this->AllowEmpty and empty($this->Body)) {
1264
                throw new phpmailerException($this->lang('empty_message'), self::STOP_CRITICAL);
1265
            }
1266
1267
            // Create body before headers in case body makes changes to headers (e.g. altering transfer encoding)
1268
            $this->MIMEHeader = '';
1269
            $this->MIMEBody   = $this->createBody();
1270
            // createBody may have added some headers, so retain them
1271
            $tempheaders      = $this->MIMEHeader;
1272
            $this->MIMEHeader = $this->createHeader();
1273
            $this->MIMEHeader .= $tempheaders;
1274
1275
            // To capture the complete message when using mail(), create
1276
            // an extra header list which createHeader() doesn't fold in
1277
            if ('mail' == $this->Mailer) {
1278
                if (count($this->to) > 0) {
1279
                    $this->mailHeader .= $this->addrAppend('To', $this->to);
1280
                } else {
1281
                    $this->mailHeader .= $this->headerLine('To', 'undisclosed-recipients:;');
1282
                }
1283
                $this->mailHeader .= $this->headerLine('Subject', $this->encodeHeader($this->secureHeader(trim($this->Subject))));
1284
            }
1285
1286
            // Sign with DKIM if enabled
1287
            if (!empty($this->DKIM_domain)
1288
                && !empty($this->DKIM_selector)
1289
                && (!empty($this->DKIM_private_string)
1290
                    || (!empty($this->DKIM_private) && file_exists($this->DKIM_private)))) {
1291
                $header_dkim      = $this->DKIM_Add($this->MIMEHeader . $this->mailHeader, $this->encodeHeader($this->secureHeader($this->Subject)), $this->MIMEBody);
1292
                $this->MIMEHeader = rtrim($this->MIMEHeader, "\r\n ") . self::CRLF . str_replace("\r\n", "\n", $header_dkim) . self::CRLF;
1293
            }
1294
1295
            return true;
1296
        }
1297
        catch (phpmailerException $exc) {
1298
            $this->setError($exc->getMessage());
1299
            if ($this->exceptions) {
1300
                throw $exc;
1301
            }
1302
1303
            return false;
1304
        }
1305
    }
1306
1307
    /**
1308
     * Actually send a message.
1309
     * Send the email via the selected mechanism
1310
     * @throws phpmailerException
1311
     * @return bool
1312
     */
1313
    public function postSend()
1314
    {
1315
        try {
1316
            // Choose the mailer and send through it
1317
            switch ($this->Mailer) {
1318
                case 'sendmail':
1319
                case 'qmail':
1320
                    return $this->sendmailSend($this->MIMEHeader, $this->MIMEBody);
1321
                case 'smtp':
1322
                    return $this->smtpSend($this->MIMEHeader, $this->MIMEBody);
1323
                case 'mail':
1324
                    return $this->mailSend($this->MIMEHeader, $this->MIMEBody);
1325
                default:
1326
                    $sendMethod = $this->Mailer . 'Send';
1327
                    if (method_exists($this, $sendMethod)) {
1328
                        return $this->$sendMethod($this->MIMEHeader, $this->MIMEBody);
1329
                    }
1330
1331
                    return $this->mailSend($this->MIMEHeader, $this->MIMEBody);
1332
            }
1333
        }
1334
        catch (phpmailerException $exc) {
1335
            $this->setError($exc->getMessage());
1336
            $this->edebug($exc->getMessage());
1337
            if ($this->exceptions) {
1338
                throw $exc;
1339
            }
1340
        }
1341
1342
        return false;
1343
    }
1344
1345
    /**
1346
     * Send mail using the $Sendmail program.
1347
     * @param string $header The message headers
1348
     * @param string $body   The message body
1349
     * @see    PHPMailer::$Sendmail
1350
     * @throws phpmailerException
1351
     * @access protected
1352
     * @return bool
1353
     */
1354
    protected function sendmailSend($header, $body)
1355
    {
1356
        // CVE-2016-10033, CVE-2016-10045: Don't pass -f if characters will be escaped.
1357
        if (!empty($this->Sender) and self::isShellSafe($this->Sender)) {
1358
            if ('qmail' == $this->Mailer) {
1359
                $sendmailFmt = '%s -f%s';
1360
            } else {
1361
                $sendmailFmt = '%s -oi -f%s -t';
1362
            }
1363
        } else {
1364
            if ('qmail' == $this->Mailer) {
1365
                $sendmailFmt = '%s';
1366
            } else {
1367
                $sendmailFmt = '%s -oi -t';
1368
            }
1369
        }
1370
1371
        // TODO: If possible, this should be changed to escapeshellarg.  Needs thorough testing.
1372
        $sendmail = sprintf($sendmailFmt, escapeshellcmd($this->Sendmail), $this->Sender);
1373
1374
        if ($this->SingleTo) {
1375
            foreach ($this->SingleToArray as $toAddr) {
1376 View Code Duplication
                if (!@$mail = popen($sendmail, 'w')) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1377
                    throw new phpmailerException($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL);
1378
                }
1379
                fwrite($mail, 'To: ' . $toAddr . "\n");
1380
                fwrite($mail, $header);
1381
                fwrite($mail, $body);
1382
                $result = pclose($mail);
1383
                $this->doCallback((0 == $result), [$toAddr], $this->cc, $this->bcc, $this->Subject, $body, $this->From);
1384
                if (0 != $result) {
1385
                    throw new phpmailerException($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL);
1386
                }
1387
            }
1388
        } else {
1389 View Code Duplication
            if (!@$mail = popen($sendmail, 'w')) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1390
                throw new phpmailerException($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL);
1391
            }
1392
            fwrite($mail, $header);
1393
            fwrite($mail, $body);
1394
            $result = pclose($mail);
1395
            $this->doCallback((0 == $result), $this->to, $this->cc, $this->bcc, $this->Subject, $body, $this->From);
1396
            if (0 != $result) {
1397
                throw new phpmailerException($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL);
1398
            }
1399
        }
1400
1401
        return true;
1402
    }
1403
1404
    /**
1405
     * Fix CVE-2016-10033 and CVE-2016-10045 by disallowing potentially unsafe shell characters.
1406
     *
1407
     * Note that escapeshellarg and escapeshellcmd are inadequate for our purposes, especially on Windows.
1408
     * @param string $string The string to be validated
1409
     * @see    https://github.com/PHPMailer/PHPMailer/issues/924 CVE-2016-10045 bug report
1410
     * @access protected
1411
     * @return bool
1412
     */
1413
    protected static function isShellSafe($string)
1414
    {
1415
        // Future-proof
1416
        if (escapeshellcmd($string) !== $string or !in_array(escapeshellarg($string), ["'$string'", "\"$string\""], true)) {
1417
            return false;
1418
        }
1419
1420
        $length = mb_strlen($string);
1421
1422
        for ($i = 0; $i < $length; $i++) {
1423
            $c = $string[$i];
1424
1425
            // All other characters have a special meaning in at least one common shell, including = and +.
1426
            // Full stop (.) has a special meaning in cmd.exe, but its impact should be negligible here.
1427
            // Note that this does permit non-Latin alphanumeric characters based on the current locale.
1428
            if (!ctype_alnum($c) && false === mb_strpos('@_-.', $c)) {
1429
                return false;
1430
            }
1431
        }
1432
1433
        return true;
1434
    }
1435
1436
    /**
1437
     * Send mail using the PHP mail() function.
1438
     * @param string $header The message headers
1439
     * @param string $body   The message body
1440
     * @link   http://www.php.net/manual/en/book.mail.php
1441
     * @throws phpmailerException
1442
     * @access protected
1443
     * @return bool
1444
     */
1445
    protected function mailSend($header, $body)
1446
    {
1447
        $toArr = [];
1448
        foreach ($this->to as $toaddr) {
1449
            $toArr[] = $this->addrFormat($toaddr);
1450
        }
1451
        $to = implode(', ', $toArr);
1452
1453
        $params = null;
1454
        //This sets the SMTP envelope sender which gets turned into a return-path header by the receiver
1455
        if (!empty($this->Sender) and $this->validateAddress($this->Sender)) {
1456
            // CVE-2016-10033, CVE-2016-10045: Don't pass -f if characters will be escaped.
1457
            if (self::isShellSafe($this->Sender)) {
1458
                $params = sprintf('-f%s', $this->Sender);
1459
            }
1460
        }
1461
        if (!empty($this->Sender) and !ini_get('safe_mode') and $this->validateAddress($this->Sender)) {
1462
            $old_from = ini_get('sendmail_from');
1463
            ini_set('sendmail_from', $this->Sender);
1464
        }
1465
        $result = false;
1466
        if ($this->SingleTo and count($toArr) > 1) {
1467
            foreach ($toArr as $toAddr) {
1468
                $result = $this->mailPassthru($toAddr, $this->Subject, $body, $header, $params);
1469
                $this->doCallback($result, [$toAddr], $this->cc, $this->bcc, $this->Subject, $body, $this->From);
1470
            }
1471
        } else {
1472
            $result = $this->mailPassthru($to, $this->Subject, $body, $header, $params);
1473
            $this->doCallback($result, $this->to, $this->cc, $this->bcc, $this->Subject, $body, $this->From);
1474
        }
1475
        if (isset($old_from)) {
1476
            ini_set('sendmail_from', $old_from);
1477
        }
1478
        if (!$result) {
1479
            throw new phpmailerException($this->lang('instantiate'), self::STOP_CRITICAL);
1480
        }
1481
1482
        return true;
1483
    }
1484
1485
    /**
1486
     * Get an instance to use for SMTP operations.
1487
     * Override this function to load your own SMTP implementation
1488
     * @return SMTP
1489
     */
1490
    public function getSMTPInstance()
1491
    {
1492
        if (!is_object($this->smtp)) {
1493
            $this->smtp = new SMTP();
1494
        }
1495
1496
        return $this->smtp;
1497
    }
1498
1499
    /**
1500
     * Send mail via SMTP.
1501
     * Returns false if there is a bad MAIL FROM, RCPT, or DATA input.
1502
     * Uses the PHPMailerSMTP class by default.
1503
     * @see    PHPMailer::getSMTPInstance() to use a different class.
1504
     * @param string $header The message headers
1505
     * @param string $body   The message body
1506
     * @throws phpmailerException
1507
     * @uses   SMTP
1508
     * @access protected
1509
     * @return bool
1510
     */
1511
    protected function smtpSend($header, $body)
1512
    {
1513
        $bad_rcpt = [];
1514
        if (!$this->smtpConnect($this->SMTPOptions)) {
1515
            throw new phpmailerException($this->lang('smtp_connect_failed'), self::STOP_CRITICAL);
1516
        }
1517
        if (!empty($this->Sender) and $this->validateAddress($this->Sender)) {
1518
            $smtp_from = $this->Sender;
1519
        } else {
1520
            $smtp_from = $this->From;
1521
        }
1522
        if (!$this->smtp->mail($smtp_from)) {
1523
            $this->setError($this->lang('from_failed') . $smtp_from . ' : ' . implode(',', $this->smtp->getError()));
1524
            throw new phpmailerException($this->ErrorInfo, self::STOP_CRITICAL);
1525
        }
1526
1527
        // Attempt to send to all recipients
1528
        foreach ([$this->to, $this->cc, $this->bcc] as $togroup) {
1529
            foreach ($togroup as $to) {
1530
                if (!$this->smtp->recipient($to[0])) {
1531
                    $error      = $this->smtp->getError();
1532
                    $bad_rcpt[] = ['to' => $to[0], 'error' => $error['detail']];
1533
                    $isSent     = false;
1534
                } else {
1535
                    $isSent = true;
1536
                }
1537
                $this->doCallback($isSent, [$to[0]], [], [], $this->Subject, $body, $this->From);
1538
            }
1539
        }
1540
1541
        // Only send the DATA command if we have viable recipients
1542
        if ((count($this->all_recipients) > count($bad_rcpt)) and !$this->smtp->data($header . $body)) {
1543
            throw new phpmailerException($this->lang('data_not_accepted'), self::STOP_CRITICAL);
1544
        }
1545
        if ($this->SMTPKeepAlive) {
1546
            $this->smtp->reset();
1547
        } else {
1548
            $this->smtp->quit();
1549
            $this->smtp->close();
1550
        }
1551
        //Create error message for any bad addresses
1552
        if (count($bad_rcpt) > 0) {
1553
            $errstr = '';
1554
            foreach ($bad_rcpt as $bad) {
1555
                $errstr .= $bad['to'] . ': ' . $bad['error'];
1556
            }
1557
            throw new phpmailerException($this->lang('recipients_failed') . $errstr, self::STOP_CONTINUE);
1558
        }
1559
1560
        return true;
1561
    }
1562
1563
    /**
1564
     * Initiate a connection to an SMTP server.
1565
     * Returns false if the operation failed.
1566
     * @param array $options An array of options compatible with stream_context_create()
0 ignored issues
show
Documentation introduced by
Should the type for parameter $options not be array|null?

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.

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

Loading history...
1567
     * @uses   SMTP
1568
     * @access public
1569
     * @throws phpmailerException
1570
     * @return bool
1571
     */
1572
    public function smtpConnect($options = null)
1573
    {
1574
        if (null === $this->smtp) {
1575
            $this->smtp = $this->getSMTPInstance();
1576
        }
1577
1578
        //If no options are provided, use whatever is set in the instance
1579
        if (null === $options) {
1580
            $options = $this->SMTPOptions;
1581
        }
1582
1583
        // Already connected?
1584
        if ($this->smtp->connected()) {
1585
            return true;
1586
        }
1587
1588
        $this->smtp->setTimeout($this->Timeout);
1589
        $this->smtp->setDebugLevel($this->SMTPDebug);
1590
        $this->smtp->setDebugOutput($this->Debugoutput);
1591
        $this->smtp->setVerp($this->do_verp);
1592
        $hosts         = explode(';', $this->Host);
1593
        $lastexception = null;
1594
1595
        foreach ($hosts as $hostentry) {
1596
            $hostinfo = [];
1597
            if (!preg_match('/^((ssl|tls):\/\/)*([a-zA-Z0-9\.-]*|\[[a-fA-F0-9:]+\]):?([0-9]*)$/', trim($hostentry), $hostinfo)) {
1598
                // Not a valid host entry
1599
                $this->edebug('Ignoring invalid host: ' . $hostentry);
1600
                continue;
1601
            }
1602
            // $hostinfo[2]: optional ssl or tls prefix
1603
            // $hostinfo[3]: the hostname
1604
            // $hostinfo[4]: optional port number
1605
            // The host string prefix can temporarily override the current setting for SMTPSecure
1606
            // If it's not specified, the default value is used
1607
            $prefix = '';
1608
            $secure = $this->SMTPSecure;
1609
            $tls    = ('tls' == $this->SMTPSecure);
1610
            if ('ssl' == $hostinfo[2] or ('' == $hostinfo[2] and 'ssl' == $this->SMTPSecure)) {
1611
                $prefix = 'ssl://';
1612
                $tls    = false; // Can't have SSL and TLS at the same time
1613
                $secure = 'ssl';
1614
            } elseif ('tls' == $hostinfo[2]) {
1615
                $tls = true;
1616
                // tls doesn't use a prefix
1617
                $secure = 'tls';
1618
            }
1619
            //Do we need the OpenSSL extension?
1620
            $sslext = defined('OPENSSL_ALGO_SHA1');
1621
            if ('tls' === $secure or 'ssl' === $secure) {
1622
                //Check for an OpenSSL constant rather than using extension_loaded, which is sometimes disabled
1623
                if (!$sslext) {
1624
                    throw new phpmailerException($this->lang('extension_missing') . 'openssl', self::STOP_CRITICAL);
1625
                }
1626
            }
1627
            $host  = $hostinfo[3];
1628
            $port  = $this->Port;
1629
            $tport = (int)$hostinfo[4];
1630
            if ($tport > 0 and $tport < 65536) {
1631
                $port = $tport;
1632
            }
1633
            if ($this->smtp->connect($prefix . $host, $port, $this->Timeout, $options)) {
1634
                try {
1635
                    if ($this->Helo) {
1636
                        $hello = $this->Helo;
1637
                    } else {
1638
                        $hello = $this->serverHostname();
1639
                    }
1640
                    $this->smtp->hello($hello);
1641
                    //Automatically enable TLS encryption if:
1642
                    // * it's not disabled
1643
                    // * we have openssl extension
1644
                    // * we are not already using SSL
1645
                    // * the server offers STARTTLS
1646
                    if ($this->SMTPAutoTLS and $sslext and 'ssl' != $secure and $this->smtp->getServerExt('STARTTLS')) {
1647
                        $tls = true;
1648
                    }
1649
                    if ($tls) {
1650
                        if (!$this->smtp->startTLS()) {
1651
                            throw new phpmailerException($this->lang('connect_host'));
1652
                        }
1653
                        // We must resend EHLO after TLS negotiation
1654
                        $this->smtp->hello($hello);
1655
                    }
1656
                    if ($this->SMTPAuth) {
1657
                        if (!$this->smtp->authenticate($this->Username, $this->Password, $this->AuthType, $this->Realm, $this->Workstation)) {
1658
                            throw new phpmailerException($this->lang('authenticate'));
1659
                        }
1660
                    }
1661
1662
                    return true;
1663
                }
1664
                catch (phpmailerException $exc) {
1665
                    $lastexception = $exc;
1666
                    $this->edebug($exc->getMessage());
1667
                    // We must have connected, but then failed TLS or Auth, so close connection nicely
1668
                    $this->smtp->quit();
1669
                }
1670
            }
1671
        }
1672
        // If we get here, all connection attempts have failed, so close connection hard
1673
        $this->smtp->close();
1674
        // As we've caught all exceptions, just report whatever the last one was
1675
        if ($this->exceptions and null !== $lastexception) {
1676
            throw $lastexception;
1677
        }
1678
1679
        return false;
1680
    }
1681
1682
    /**
1683
     * Close the active SMTP session if one exists.
1684
     */
1685
    public function smtpClose()
1686
    {
1687
        if (is_a($this->smtp, 'SMTP')) {
1688
            if ($this->smtp->connected()) {
1689
                $this->smtp->quit();
1690
                $this->smtp->close();
1691
            }
1692
        }
1693
    }
1694
1695
    /**
1696
     * Set the language for error messages.
1697
     * Returns false if it cannot load the language file.
1698
     * The default language is English.
1699
     * @param string $langcode  ISO 639-1 2-character language code (e.g. French is "fr")
1700
     * @param string $lang_path Path to the language file directory, with trailing separator (slash)
1701
     * @return bool
1702
     * @access public
1703
     */
1704
    public function setLanguage($langcode = 'en', $lang_path = '')
1705
    {
1706
        // Backwards compatibility for renamed language codes
1707
        $renamed_langcodes = [
1708
            'br' => 'pt_br',
1709
            'cz' => 'cs',
1710
            'dk' => 'da',
1711
            'no' => 'nb',
1712
            'se' => 'sv',
1713
            'sr' => 'rs',
1714
        ];
1715
1716
        if (isset($renamed_langcodes[$langcode])) {
1717
            $langcode = $renamed_langcodes[$langcode];
1718
        }
1719
1720
        // Define full set of translatable strings in English
1721
        $PHPMAILER_LANG = [
1722
            'authenticate'         => 'SMTP Error: Could not authenticate.',
1723
            'connect_host'         => 'SMTP Error: Could not connect to SMTP host.',
1724
            'data_not_accepted'    => 'SMTP Error: data not accepted.',
1725
            'empty_message'        => 'Message body empty',
1726
            'encoding'             => 'Unknown encoding: ',
1727
            'execute'              => 'Could not execute: ',
1728
            'file_access'          => 'Could not access file: ',
1729
            'file_open'            => 'File Error: Could not open file: ',
1730
            'from_failed'          => 'The following From address failed: ',
1731
            'instantiate'          => 'Could not instantiate mail function.',
1732
            'invalid_address'      => 'Invalid address: ',
1733
            'mailer_not_supported' => ' mailer is not supported.',
1734
            'provide_address'      => 'You must provide at least one recipient email address.',
1735
            'recipients_failed'    => 'SMTP Error: The following recipients failed: ',
1736
            'signing'              => 'Signing Error: ',
1737
            'smtp_connect_failed'  => 'SMTP connect() failed.',
1738
            'smtp_error'           => 'SMTP server error: ',
1739
            'variable_set'         => 'Cannot set or reset variable: ',
1740
            'extension_missing'    => 'Extension missing: ',
1741
        ];
1742
        if (empty($lang_path)) {
1743
            // Calculate an absolute path so it can work if CWD is not here
1744
            $lang_path = dirname(__FILE__) . DIRECTORY_SEPARATOR . 'language' . DIRECTORY_SEPARATOR;
1745
        }
1746
        //Validate $langcode
1747
        if (!preg_match('/^[a-z]{2}(?:_[a-zA-Z]{2})?$/', $langcode)) {
1748
            $langcode = 'en';
1749
        }
1750
        $foundlang = true;
1751
        $lang_file = $lang_path . 'phpmailer.lang-' . $langcode . '.php';
1752
        // There is no English translation file
1753
        if ('en' != $langcode) {
1754
            // Make sure language file path is readable
1755
            if (!is_readable($lang_file)) {
1756
                $foundlang = false;
1757
            } else {
1758
                // Overwrite language-specific strings.
1759
                // This way we'll never have missing translation keys.
1760
                $foundlang = include $lang_file;
1761
            }
1762
        }
1763
        $this->language = $PHPMAILER_LANG;
1764
1765
        return (bool)$foundlang; // Returns false if language not found
1766
    }
1767
1768
    /**
1769
     * Get the array of strings for the current language.
1770
     * @return array
1771
     */
1772
    public function getTranslations()
1773
    {
1774
        return $this->language;
1775
    }
1776
1777
    /**
1778
     * Create recipient headers.
1779
     * @access public
1780
     * @param string $type
1781
     * @param array  $addr An array of recipient,
1782
     *                     where each recipient is a 2-element indexed array with element 0 containing an address
1783
     *                     and element 1 containing a name, like:
1784
     *                     array(array('[email protected]', 'Joe User'), array('[email protected]', 'Zoe User'))
1785
     * @return string
1786
     */
1787
    public function addrAppend($type, $addr)
1788
    {
1789
        $addresses = [];
1790
        foreach ($addr as $address) {
1791
            $addresses[] = $this->addrFormat($address);
1792
        }
1793
1794
        return $type . ': ' . implode(', ', $addresses) . $this->LE;
1795
    }
1796
1797
    /**
1798
     * Format an address for use in a message header.
1799
     * @access public
1800
     * @param array $addr A 2-element indexed array, element 0 containing an address, element 1 containing a name
1801
     *                    like array('[email protected]', 'Joe User')
1802
     * @return string
1803
     */
1804
    public function addrFormat($addr)
1805
    {
1806
        if (empty($addr[1])) { // No name provided
1807
            return $this->secureHeader($addr[0]);
1808
        }
1809
1810
        return $this->encodeHeader($this->secureHeader($addr[1]), 'phrase') . ' <' . $this->secureHeader($addr[0]) . '>';
1811
    }
1812
1813
    /**
1814
     * Word-wrap message.
1815
     * For use with mailers that do not automatically perform wrapping
1816
     * and for quoted-printable encoded messages.
1817
     * Original written by philippe.
1818
     * @param string $message The message to wrap
1819
     * @param int    $length  The line length to wrap to
1820
     * @param bool   $qp_mode Whether to run in Quoted-Printable mode
1821
     * @access public
1822
     * @return string
1823
     */
1824
    public function wrapText($message, $length, $qp_mode = false)
1825
    {
1826
        if ($qp_mode) {
1827
            $soft_break = sprintf(' =%s', $this->LE);
1828
        } else {
1829
            $soft_break = $this->LE;
1830
        }
1831
        // If utf-8 encoding is used, we will need to make sure we don't
1832
        // split multibyte characters when we wrap
1833
        $is_utf8 = ('utf-8' == mb_strtolower($this->CharSet));
1834
        $lelen   = mb_strlen($this->LE);
1835
        $crlflen = mb_strlen(self::CRLF);
1836
1837
        $message = $this->fixEOL($message);
1838
        //Remove a trailing line break
1839
        if (mb_substr($message, -$lelen) == $this->LE) {
1840
            $message = mb_substr($message, 0, -$lelen);
1841
        }
1842
1843
        //Split message into lines
1844
        $lines = explode($this->LE, $message);
1845
        //Message will be rebuilt in here
1846
        $message = '';
1847
        foreach ($lines as $line) {
1848
            $words     = explode(' ', $line);
1849
            $buf       = '';
1850
            $firstword = true;
1851
            foreach ($words as $word) {
1852
                if ($qp_mode and (mb_strlen($word) > $length)) {
1853
                    $space_left = $length - mb_strlen($buf) - $crlflen;
1854
                    if (!$firstword) {
1855
                        if ($space_left > 20) {
1856
                            $len = $space_left;
1857 View Code Duplication
                            if ($is_utf8) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1858
                                $len = $this->utf8CharBoundary($word, $len);
1859
                            } elseif ('=' == mb_substr($word, $len - 1, 1)) {
1860
                                $len--;
1861
                            } elseif ('=' == mb_substr($word, $len - 2, 1)) {
1862
                                $len -= 2;
1863
                            }
1864
                            $part    = mb_substr($word, 0, $len);
1865
                            $word    = mb_substr($word, $len);
1866
                            $buf     .= ' ' . $part;
1867
                            $message .= $buf . sprintf('=%s', self::CRLF);
1868
                        } else {
1869
                            $message .= $buf . $soft_break;
1870
                        }
1871
                        $buf = '';
1872
                    }
1873
                    while (mb_strlen($word) > 0) {
1874
                        if ($length <= 0) {
1875
                            break;
1876
                        }
1877
                        $len = $length;
1878 View Code Duplication
                        if ($is_utf8) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1879
                            $len = $this->utf8CharBoundary($word, $len);
1880
                        } elseif ('=' == mb_substr($word, $len - 1, 1)) {
1881
                            $len--;
1882
                        } elseif ('=' == mb_substr($word, $len - 2, 1)) {
1883
                            $len -= 2;
1884
                        }
1885
                        $part = mb_substr($word, 0, $len);
1886
                        $word = mb_substr($word, $len);
1887
1888
                        if (mb_strlen($word) > 0) {
1889
                            $message .= $part . sprintf('=%s', self::CRLF);
1890
                        } else {
1891
                            $buf = $part;
1892
                        }
1893
                    }
1894
                } else {
1895
                    $buf_o = $buf;
1896
                    if (!$firstword) {
1897
                        $buf .= ' ';
1898
                    }
1899
                    $buf .= $word;
1900
1901
                    if (mb_strlen($buf) > $length and '' != $buf_o) {
1902
                        $message .= $buf_o . $soft_break;
1903
                        $buf     = $word;
1904
                    }
1905
                }
1906
                $firstword = false;
1907
            }
1908
            $message .= $buf . self::CRLF;
1909
        }
1910
1911
        return $message;
1912
    }
1913
1914
    /**
1915
     * Find the last character boundary prior to $maxLength in a utf-8
1916
     * quoted-printable encoded string.
1917
     * Original written by Colin Brown.
1918
     * @access public
1919
     * @param string $encodedText utf-8 QP text
1920
     * @param int    $maxLength   Find the last character boundary prior to this length
1921
     * @return int
1922
     */
1923
    public function utf8CharBoundary($encodedText, $maxLength)
1924
    {
1925
        $foundSplitPos = false;
1926
        $lookBack      = 3;
1927
        while (!$foundSplitPos) {
1928
            $lastChunk      = mb_substr($encodedText, $maxLength - $lookBack, $lookBack);
1929
            $encodedCharPos = mb_strpos($lastChunk, '=');
1930
            if (false !== $encodedCharPos) {
1931
                // Found start of encoded character byte within $lookBack block.
1932
                // Check the encoded byte value (the 2 chars after the '=')
1933
                $hex = mb_substr($encodedText, $maxLength - $lookBack + $encodedCharPos + 1, 2);
1934
                $dec = hexdec($hex);
1935
                if ($dec < 128) {
1936
                    // Single byte character.
1937
                    // If the encoded char was found at pos 0, it will fit
1938
                    // otherwise reduce maxLength to start of the encoded char
1939
                    if ($encodedCharPos > 0) {
1940
                        $maxLength = $maxLength - ($lookBack - $encodedCharPos);
1941
                    }
1942
                    $foundSplitPos = true;
1943
                } elseif ($dec >= 192) {
1944
                    // First byte of a multi byte character
1945
                    // Reduce maxLength to split at start of character
1946
                    $maxLength     = $maxLength - ($lookBack - $encodedCharPos);
1947
                    $foundSplitPos = true;
1948
                } elseif ($dec < 192) {
1949
                    // Middle byte of a multi byte character, look further back
1950
                    $lookBack += 3;
1951
                }
1952
            } else {
1953
                // No encoded character found
1954
                $foundSplitPos = true;
1955
            }
1956
        }
1957
1958
        return $maxLength;
1959
    }
1960
1961
    /**
1962
     * Apply word wrapping to the message body.
1963
     * Wraps the message body to the number of chars set in the WordWrap property.
1964
     * You should only do this to plain-text bodies as wrapping HTML tags may break them.
1965
     * This is called automatically by createBody(), so you don't need to call it yourself.
1966
     * @access public
1967
     */
1968
    public function setWordWrap()
1969
    {
1970
        if ($this->WordWrap < 1) {
1971
            return;
1972
        }
1973
1974
        switch ($this->message_type) {
1975
            case 'alt':
1976
            case 'alt_inline':
1977
            case 'alt_attach':
1978
            case 'alt_inline_attach':
1979
                $this->AltBody = $this->wrapText($this->AltBody, $this->WordWrap);
1980
                break;
1981
            default:
1982
                $this->Body = $this->wrapText($this->Body, $this->WordWrap);
1983
                break;
1984
        }
1985
    }
1986
1987
    /**
1988
     * Assemble message headers.
1989
     * @access public
1990
     * @return string The assembled headers
1991
     */
1992
    public function createHeader()
1993
    {
1994
        $result = '';
1995
1996
        $result .= $this->headerLine('Date', '' == $this->MessageDate ? self::rfcDate() : $this->MessageDate);
1997
1998
        // To be created automatically by mail()
1999
        if ($this->SingleTo) {
2000
            if ('mail' != $this->Mailer) {
2001
                foreach ($this->to as $toaddr) {
2002
                    $this->SingleToArray[] = $this->addrFormat($toaddr);
2003
                }
2004
            }
2005
        } else {
2006
            if (count($this->to) > 0) {
2007
                if ('mail' != $this->Mailer) {
2008
                    $result .= $this->addrAppend('To', $this->to);
2009
                }
2010
            } elseif (0 == count($this->cc)) {
2011
                $result .= $this->headerLine('To', 'undisclosed-recipients:;');
2012
            }
2013
        }
2014
2015
        $result .= $this->addrAppend('From', [[trim($this->From), $this->FromName]]);
2016
2017
        // sendmail and mail() extract Cc from the header before sending
2018
        if (count($this->cc) > 0) {
2019
            $result .= $this->addrAppend('Cc', $this->cc);
2020
        }
2021
2022
        // sendmail and mail() extract Bcc from the header before sending
2023
        if (('sendmail' == $this->Mailer or 'qmail' == $this->Mailer or 'mail' == $this->Mailer) and count($this->bcc) > 0) {
2024
            $result .= $this->addrAppend('Bcc', $this->bcc);
2025
        }
2026
2027
        if (count($this->ReplyTo) > 0) {
2028
            $result .= $this->addrAppend('Reply-To', $this->ReplyTo);
2029
        }
2030
2031
        // mail() sets the subject itself
2032
        if ('mail' != $this->Mailer) {
2033
            $result .= $this->headerLine('Subject', $this->encodeHeader($this->secureHeader($this->Subject)));
2034
        }
2035
2036
        // Only allow a custom message ID if it conforms to RFC 5322 section 3.6.4
2037
        // https://tools.ietf.org/html/rfc5322#section-3.6.4
2038
        if ('' != $this->MessageID and preg_match('/^<.*@.*>$/', $this->MessageID)) {
2039
            $this->lastMessageID = $this->MessageID;
2040
        } else {
2041
            $this->lastMessageID = sprintf('<%s@%s>', $this->uniqueid, $this->serverHostname());
2042
        }
2043
        $result .= $this->headerLine('Message-ID', $this->lastMessageID);
2044
        if (null !== $this->Priority) {
2045
            $result .= $this->headerLine('X-Priority', $this->Priority);
2046
        }
2047
        if ('' == $this->XMailer) {
2048
            $result .= $this->headerLine('X-Mailer', 'PHPMailer ' . $this->Version . ' (https://github.com/PHPMailer/PHPMailer)');
2049
        } else {
2050
            $myXmailer = trim($this->XMailer);
2051
            if ($myXmailer) {
2052
                $result .= $this->headerLine('X-Mailer', $myXmailer);
2053
            }
2054
        }
2055
2056
        if ('' != $this->ConfirmReadingTo) {
2057
            $result .= $this->headerLine('Disposition-Notification-To', '<' . $this->ConfirmReadingTo . '>');
2058
        }
2059
2060
        // Add custom headers
2061
        foreach ($this->CustomHeader as $header) {
2062
            $result .= $this->headerLine(trim($header[0]), $this->encodeHeader(trim($header[1])));
2063
        }
2064
        if (!$this->sign_key_file) {
2065
            $result .= $this->headerLine('MIME-Version', '1.0');
2066
            $result .= $this->getMailMIME();
2067
        }
2068
2069
        return $result;
2070
    }
2071
2072
    /**
2073
     * Get the message MIME type headers.
2074
     * @access public
2075
     * @return string
2076
     */
2077
    public function getMailMIME()
2078
    {
2079
        $result      = '';
2080
        $ismultipart = true;
2081
        switch ($this->message_type) {
2082 View Code Duplication
            case 'inline':
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2083
                $result .= $this->headerLine('Content-Type', 'multipart/related;');
2084
                $result .= $this->textLine("\tboundary=\"" . $this->boundary[1] . '"');
2085
                break;
2086
            case 'attach':
2087
            case 'inline_attach':
2088
            case 'alt_attach':
2089 View Code Duplication
            case 'alt_inline_attach':
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2090
                $result .= $this->headerLine('Content-Type', 'multipart/mixed;');
2091
                $result .= $this->textLine("\tboundary=\"" . $this->boundary[1] . '"');
2092
                break;
2093
            case 'alt':
2094 View Code Duplication
            case 'alt_inline':
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2095
                $result .= $this->headerLine('Content-Type', 'multipart/alternative;');
2096
                $result .= $this->textLine("\tboundary=\"" . $this->boundary[1] . '"');
2097
                break;
2098
            default:
2099
                // Catches case 'plain': and case '':
2100
                $result      .= $this->textLine('Content-Type: ' . $this->ContentType . '; charset=' . $this->CharSet);
2101
                $ismultipart = false;
2102
                break;
2103
        }
2104
        // RFC1341 part 5 says 7bit is assumed if not specified
2105
        if ('7bit' != $this->Encoding) {
2106
            // RFC 2045 section 6.4 says multipart MIME parts may only use 7bit, 8bit or binary CTE
2107
            if ($ismultipart) {
2108
                if ('8bit' == $this->Encoding) {
2109
                    $result .= $this->headerLine('Content-Transfer-Encoding', '8bit');
2110
                }
2111
                // The only remaining alternatives are quoted-printable and base64, which are both 7bit compatible
2112
            } else {
2113
                $result .= $this->headerLine('Content-Transfer-Encoding', $this->Encoding);
2114
            }
2115
        }
2116
2117
        if ('mail' != $this->Mailer) {
2118
            $result .= $this->LE;
2119
        }
2120
2121
        return $result;
2122
    }
2123
2124
    /**
2125
     * Returns the whole MIME message.
2126
     * Includes complete headers and body.
2127
     * Only valid post preSend().
2128
     * @see    PHPMailer::preSend()
2129
     * @access public
2130
     * @return string
2131
     */
2132
    public function getSentMIMEMessage()
2133
    {
2134
        return rtrim($this->MIMEHeader . $this->mailHeader, "\n\r") . self::CRLF . self::CRLF . $this->MIMEBody;
2135
    }
2136
2137
    /**
2138
     * Create unique ID
2139
     * @return string
2140
     */
2141
    protected function generateId()
2142
    {
2143
        return md5(uniqid(time()));
2144
    }
2145
2146
    /**
2147
     * Assemble the message body.
2148
     * Returns an empty string on failure.
2149
     * @access public
2150
     * @throws phpmailerException
2151
     * @return string The assembled message body
2152
     */
2153
    public function createBody()
2154
    {
2155
        $body = '';
2156
        //Create unique IDs and preset boundaries
2157
        $this->uniqueid    = $this->generateId();
2158
        $this->boundary[1] = 'b1_' . $this->uniqueid;
2159
        $this->boundary[2] = 'b2_' . $this->uniqueid;
2160
        $this->boundary[3] = 'b3_' . $this->uniqueid;
2161
2162
        if ($this->sign_key_file) {
2163
            $body .= $this->getMailMIME() . $this->LE;
2164
        }
2165
2166
        $this->setWordWrap();
2167
2168
        $bodyEncoding = $this->Encoding;
2169
        $bodyCharSet  = $this->CharSet;
2170
        //Can we do a 7-bit downgrade?
2171
        if ('8bit' == $bodyEncoding and !$this->has8bitChars($this->Body)) {
2172
            $bodyEncoding = '7bit';
2173
            //All ISO 8859, Windows codepage and UTF-8 charsets are ascii compatible up to 7-bit
2174
            $bodyCharSet = 'us-ascii';
2175
        }
2176
        //If lines are too long, and we're not already using an encoding that will shorten them,
2177
        //change to quoted-printable transfer encoding for the body part only
2178
        if ('base64' != $this->Encoding and self::hasLineLongerThanMax($this->Body)) {
2179
            $bodyEncoding = 'quoted-printable';
2180
        }
2181
2182
        $altBodyEncoding = $this->Encoding;
2183
        $altBodyCharSet  = $this->CharSet;
2184
        //Can we do a 7-bit downgrade?
2185
        if ('8bit' == $altBodyEncoding and !$this->has8bitChars($this->AltBody)) {
2186
            $altBodyEncoding = '7bit';
2187
            //All ISO 8859, Windows codepage and UTF-8 charsets are ascii compatible up to 7-bit
2188
            $altBodyCharSet = 'us-ascii';
2189
        }
2190
        //If lines are too long, and we're not already using an encoding that will shorten them,
2191
        //change to quoted-printable transfer encoding for the alt body part only
2192
        if ('base64' != $altBodyEncoding and self::hasLineLongerThanMax($this->AltBody)) {
2193
            $altBodyEncoding = 'quoted-printable';
2194
        }
2195
        //Use this as a preamble in all multipart message types
2196
        $mimepre = 'This is a multi-part message in MIME format.' . $this->LE . $this->LE;
2197
        switch ($this->message_type) {
2198 View Code Duplication
            case 'inline':
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2199
                $body .= $mimepre;
2200
                $body .= $this->getBoundary($this->boundary[1], $bodyCharSet, '', $bodyEncoding);
2201
                $body .= $this->encodeString($this->Body, $bodyEncoding);
2202
                $body .= $this->LE . $this->LE;
2203
                $body .= $this->attachAll('inline', $this->boundary[1]);
2204
                break;
2205 View Code Duplication
            case 'attach':
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2206
                $body .= $mimepre;
2207
                $body .= $this->getBoundary($this->boundary[1], $bodyCharSet, '', $bodyEncoding);
2208
                $body .= $this->encodeString($this->Body, $bodyEncoding);
2209
                $body .= $this->LE . $this->LE;
2210
                $body .= $this->attachAll('attachment', $this->boundary[1]);
2211
                break;
2212
            case 'inline_attach':
2213
                $body .= $mimepre;
2214
                $body .= $this->textLine('--' . $this->boundary[1]);
2215
                $body .= $this->headerLine('Content-Type', 'multipart/related;');
2216
                $body .= $this->textLine("\tboundary=\"" . $this->boundary[2] . '"');
2217
                $body .= $this->LE;
2218
                $body .= $this->getBoundary($this->boundary[2], $bodyCharSet, '', $bodyEncoding);
2219
                $body .= $this->encodeString($this->Body, $bodyEncoding);
2220
                $body .= $this->LE . $this->LE;
2221
                $body .= $this->attachAll('inline', $this->boundary[2]);
2222
                $body .= $this->LE;
2223
                $body .= $this->attachAll('attachment', $this->boundary[1]);
2224
                break;
2225
            case 'alt':
2226
                $body .= $mimepre;
2227
                $body .= $this->getBoundary($this->boundary[1], $altBodyCharSet, 'text/plain', $altBodyEncoding);
2228
                $body .= $this->encodeString($this->AltBody, $altBodyEncoding);
2229
                $body .= $this->LE . $this->LE;
2230
                $body .= $this->getBoundary($this->boundary[1], $bodyCharSet, 'text/html', $bodyEncoding);
2231
                $body .= $this->encodeString($this->Body, $bodyEncoding);
2232
                $body .= $this->LE . $this->LE;
2233
                if (!empty($this->Ical)) {
2234
                    $body .= $this->getBoundary($this->boundary[1], '', 'text/calendar; method=REQUEST', '');
2235
                    $body .= $this->encodeString($this->Ical, $this->Encoding);
2236
                    $body .= $this->LE . $this->LE;
2237
                }
2238
                $body .= $this->endBoundary($this->boundary[1]);
2239
                break;
2240 View Code Duplication
            case 'alt_inline':
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2241
                $body .= $mimepre;
2242
                $body .= $this->getBoundary($this->boundary[1], $altBodyCharSet, 'text/plain', $altBodyEncoding);
2243
                $body .= $this->encodeString($this->AltBody, $altBodyEncoding);
2244
                $body .= $this->LE . $this->LE;
2245
                $body .= $this->textLine('--' . $this->boundary[1]);
2246
                $body .= $this->headerLine('Content-Type', 'multipart/related;');
2247
                $body .= $this->textLine("\tboundary=\"" . $this->boundary[2] . '"');
2248
                $body .= $this->LE;
2249
                $body .= $this->getBoundary($this->boundary[2], $bodyCharSet, 'text/html', $bodyEncoding);
2250
                $body .= $this->encodeString($this->Body, $bodyEncoding);
2251
                $body .= $this->LE . $this->LE;
2252
                $body .= $this->attachAll('inline', $this->boundary[2]);
2253
                $body .= $this->LE;
2254
                $body .= $this->endBoundary($this->boundary[1]);
2255
                break;
2256 View Code Duplication
            case 'alt_attach':
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2257
                $body .= $mimepre;
2258
                $body .= $this->textLine('--' . $this->boundary[1]);
2259
                $body .= $this->headerLine('Content-Type', 'multipart/alternative;');
2260
                $body .= $this->textLine("\tboundary=\"" . $this->boundary[2] . '"');
2261
                $body .= $this->LE;
2262
                $body .= $this->getBoundary($this->boundary[2], $altBodyCharSet, 'text/plain', $altBodyEncoding);
2263
                $body .= $this->encodeString($this->AltBody, $altBodyEncoding);
2264
                $body .= $this->LE . $this->LE;
2265
                $body .= $this->getBoundary($this->boundary[2], $bodyCharSet, 'text/html', $bodyEncoding);
2266
                $body .= $this->encodeString($this->Body, $bodyEncoding);
2267
                $body .= $this->LE . $this->LE;
2268
                $body .= $this->endBoundary($this->boundary[2]);
2269
                $body .= $this->LE;
2270
                $body .= $this->attachAll('attachment', $this->boundary[1]);
2271
                break;
2272
            case 'alt_inline_attach':
2273
                $body .= $mimepre;
2274
                $body .= $this->textLine('--' . $this->boundary[1]);
2275
                $body .= $this->headerLine('Content-Type', 'multipart/alternative;');
2276
                $body .= $this->textLine("\tboundary=\"" . $this->boundary[2] . '"');
2277
                $body .= $this->LE;
2278
                $body .= $this->getBoundary($this->boundary[2], $altBodyCharSet, 'text/plain', $altBodyEncoding);
2279
                $body .= $this->encodeString($this->AltBody, $altBodyEncoding);
2280
                $body .= $this->LE . $this->LE;
2281
                $body .= $this->textLine('--' . $this->boundary[2]);
2282
                $body .= $this->headerLine('Content-Type', 'multipart/related;');
2283
                $body .= $this->textLine("\tboundary=\"" . $this->boundary[3] . '"');
2284
                $body .= $this->LE;
2285
                $body .= $this->getBoundary($this->boundary[3], $bodyCharSet, 'text/html', $bodyEncoding);
2286
                $body .= $this->encodeString($this->Body, $bodyEncoding);
2287
                $body .= $this->LE . $this->LE;
2288
                $body .= $this->attachAll('inline', $this->boundary[3]);
2289
                $body .= $this->LE;
2290
                $body .= $this->endBoundary($this->boundary[2]);
2291
                $body .= $this->LE;
2292
                $body .= $this->attachAll('attachment', $this->boundary[1]);
2293
                break;
2294
            default:
2295
                // Catch case 'plain' and case '', applies to simple `text/plain` and `text/html` body content types
2296
                //Reset the `Encoding` property in case we changed it for line length reasons
2297
                $this->Encoding = $bodyEncoding;
2298
                $body           .= $this->encodeString($this->Body, $this->Encoding);
2299
                break;
2300
        }
2301
2302
        if ($this->isError()) {
2303
            $body = '';
2304
        } elseif ($this->sign_key_file) {
2305
            try {
2306
                if (!defined('PKCS7_TEXT')) {
2307
                    throw new phpmailerException($this->lang('extension_missing') . 'openssl');
2308
                }
2309
                // @TODO would be nice to use php://temp streams here, but need to wrap for PHP < 5.1
2310
                $file = tempnam(sys_get_temp_dir(), 'mail');
2311
                if (false === file_put_contents($file, $body)) {
2312
                    throw new phpmailerException($this->lang('signing') . ' Could not write temp file');
2313
                }
2314
                $signed = tempnam(sys_get_temp_dir(), 'signed');
2315
                //Workaround for PHP bug https://bugs.php.net/bug.php?id=69197
2316
                if (empty($this->sign_extracerts_file)) {
2317
                    $sign = @openssl_pkcs7_sign($file, $signed, 'file://' . realpath($this->sign_cert_file), ['file://' . realpath($this->sign_key_file), $this->sign_key_pass], null);
2318
                } else {
2319
                    $sign = @openssl_pkcs7_sign($file, $signed, 'file://' . realpath($this->sign_cert_file), ['file://' . realpath($this->sign_key_file), $this->sign_key_pass], null, PKCS7_DETACHED, $this->sign_extracerts_file);
2320
                }
2321
                if ($sign) {
2322
                    @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...
2323
                    $body = file_get_contents($signed);
2324
                    @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...
2325
                    //The message returned by openssl contains both headers and body, so need to split them up
2326
                    $parts            = explode("\n\n", $body, 2);
2327
                    $this->MIMEHeader .= $parts[0] . $this->LE . $this->LE;
2328
                    $body             = $parts[1];
2329
                } else {
2330
                    @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...
2331
                    @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...
2332
                    throw new phpmailerException($this->lang('signing') . openssl_error_string());
2333
                }
2334
            }
2335
            catch (phpmailerException $exc) {
2336
                $body = '';
2337
                if ($this->exceptions) {
2338
                    throw $exc;
2339
                }
2340
            }
2341
        }
2342
2343
        return $body;
2344
    }
2345
2346
    /**
2347
     * Return the start of a message boundary.
2348
     * @access protected
2349
     * @param string $boundary
2350
     * @param string $charSet
2351
     * @param string $contentType
2352
     * @param string $encoding
2353
     * @return string
2354
     */
2355
    protected function getBoundary($boundary, $charSet, $contentType, $encoding)
2356
    {
2357
        $result = '';
2358
        if ('' == $charSet) {
2359
            $charSet = $this->CharSet;
2360
        }
2361
        if ('' == $contentType) {
2362
            $contentType = $this->ContentType;
2363
        }
2364
        if ('' == $encoding) {
2365
            $encoding = $this->Encoding;
2366
        }
2367
        $result .= $this->textLine('--' . $boundary);
2368
        $result .= sprintf('Content-Type: %s; charset=%s', $contentType, $charSet);
2369
        $result .= $this->LE;
2370
        // RFC1341 part 5 says 7bit is assumed if not specified
2371
        if ('7bit' != $encoding) {
2372
            $result .= $this->headerLine('Content-Transfer-Encoding', $encoding);
2373
        }
2374
        $result .= $this->LE;
2375
2376
        return $result;
2377
    }
2378
2379
    /**
2380
     * Return the end of a message boundary.
2381
     * @access protected
2382
     * @param string $boundary
2383
     * @return string
2384
     */
2385
    protected function endBoundary($boundary)
2386
    {
2387
        return $this->LE . '--' . $boundary . '--' . $this->LE;
2388
    }
2389
2390
    /**
2391
     * Set the message type.
2392
     * PHPMailer only supports some preset message types, not arbitrary MIME structures.
2393
     * @access protected
2394
     */
2395
    protected function setMessageType()
2396
    {
2397
        $type = [];
2398
        if ($this->alternativeExists()) {
2399
            $type[] = 'alt';
2400
        }
2401
        if ($this->inlineImageExists()) {
2402
            $type[] = 'inline';
2403
        }
2404
        if ($this->attachmentExists()) {
2405
            $type[] = 'attach';
2406
        }
2407
        $this->message_type = implode('_', $type);
2408
        if ('' == $this->message_type) {
2409
            //The 'plain' message_type refers to the message having a single body element, not that it is plain-text
2410
            $this->message_type = 'plain';
2411
        }
2412
    }
2413
2414
    /**
2415
     * Format a header line.
2416
     * @access public
2417
     * @param string $name
2418
     * @param string $value
2419
     * @return string
2420
     */
2421
    public function headerLine($name, $value)
2422
    {
2423
        return $name . ': ' . $value . $this->LE;
2424
    }
2425
2426
    /**
2427
     * Return a formatted mail line.
2428
     * @access public
2429
     * @param string $value
2430
     * @return string
2431
     */
2432
    public function textLine($value)
2433
    {
2434
        return $value . $this->LE;
2435
    }
2436
2437
    /**
2438
     * Add an attachment from a path on the filesystem.
2439
     * Never use a user-supplied path to a file!
2440
     * Returns false if the file could not be found or read.
2441
     * @param string $path        Path to the attachment.
2442
     * @param string $name        Overrides the attachment name.
2443
     * @param string $encoding    File encoding (see $Encoding).
2444
     * @param string $type        File extension (MIME) type.
2445
     * @param string $disposition Disposition to use
2446
     * @throws phpmailerException
2447
     * @return bool
2448
     */
2449
    public function addAttachment($path, $name = '', $encoding = 'base64', $type = '', $disposition = 'attachment')
2450
    {
2451
        try {
2452
            if (!@is_file($path)) {
2453
                throw new phpmailerException($this->lang('file_access') . $path, self::STOP_CONTINUE);
2454
            }
2455
2456
            // If a MIME type is not specified, try to work it out from the file name
2457
            if ('' == $type) {
2458
                $type = self::filenameToType($path);
2459
            }
2460
2461
            $filename = basename($path);
2462
            if ('' == $name) {
2463
                $name = $filename;
2464
            }
2465
2466
            $this->attachment[] = [
2467
                0 => $path,
2468
                1 => $filename,
2469
                2 => $name,
2470
                3 => $encoding,
2471
                4 => $type,
2472
                5 => false, // isStringAttachment
2473
                6 => $disposition,
2474
                7 => 0,
2475
            ];
2476
        }
2477
        catch (phpmailerException $exc) {
2478
            $this->setError($exc->getMessage());
2479
            $this->edebug($exc->getMessage());
2480
            if ($this->exceptions) {
2481
                throw $exc;
2482
            }
2483
2484
            return false;
2485
        }
2486
2487
        return true;
2488
    }
2489
2490
    /**
2491
     * Return the array of attachments.
2492
     * @return array
2493
     */
2494
    public function getAttachments()
2495
    {
2496
        return $this->attachment;
2497
    }
2498
2499
    /**
2500
     * Attach all file, string, and binary attachments to the message.
2501
     * Returns an empty string on failure.
2502
     * @access protected
2503
     * @param string $disposition_type
2504
     * @param string $boundary
2505
     * @throws \phpmailerException
2506
     * @return string
2507
     */
2508
    protected function attachAll($disposition_type, $boundary)
2509
    {
2510
        // Return text of body
2511
        $mime    = [];
2512
        $cidUniq = [];
2513
        $incl    = [];
2514
2515
        // Add all attachments
2516
        foreach ($this->attachment as $attachment) {
2517
            // Check if it is a valid disposition_filter
2518
            if ($attachment[6] == $disposition_type) {
2519
                // Check for string attachment
2520
                $string  = '';
2521
                $path    = '';
2522
                $bString = $attachment[5];
2523
                if ($bString) {
2524
                    $string = $attachment[0];
2525
                } else {
2526
                    $path = $attachment[0];
2527
                }
2528
2529
                $inclhash = md5(serialize($attachment));
2530
                if (in_array($inclhash, $incl, true)) {
2531
                    continue;
2532
                }
2533
                $incl[]      = $inclhash;
2534
                $name        = $attachment[2];
2535
                $encoding    = $attachment[3];
2536
                $type        = $attachment[4];
2537
                $disposition = $attachment[6];
2538
                $cid         = $attachment[7];
2539
                if ('inline' == $disposition && array_key_exists($cid, $cidUniq)) {
2540
                    continue;
2541
                }
2542
                $cidUniq[$cid] = true;
2543
2544
                $mime[] = sprintf('--%s%s', $boundary, $this->LE);
2545
                //Only include a filename property if we have one
2546
                if (!empty($name)) {
2547
                    $mime[] = sprintf('Content-Type: %s; name="%s"%s', $type, $this->encodeHeader($this->secureHeader($name)), $this->LE);
2548
                } else {
2549
                    $mime[] = sprintf('Content-Type: %s%s', $type, $this->LE);
2550
                }
2551
                // RFC1341 part 5 says 7bit is assumed if not specified
2552
                if ('7bit' != $encoding) {
2553
                    $mime[] = sprintf('Content-Transfer-Encoding: %s%s', $encoding, $this->LE);
2554
                }
2555
2556
                if ('inline' == $disposition) {
2557
                    $mime[] = sprintf('Content-ID: <%s>%s', $cid, $this->LE);
2558
                }
2559
2560
                // If a filename contains any of these chars, it should be quoted,
2561
                // but not otherwise: RFC2183 & RFC2045 5.1
2562
                // Fixes a warning in IETF's msglint MIME checker
2563
                // Allow for bypassing the Content-Disposition header totally
2564
                if (!(empty($disposition))) {
2565
                    $encoded_name = $this->encodeHeader($this->secureHeader($name));
2566
                    if (preg_match('/[ \(\)<>@,;:\\"\/\[\]\?=]/', $encoded_name)) {
2567
                        $mime[] = sprintf('Content-Disposition: %s; filename="%s"%s', $disposition, $encoded_name, $this->LE . $this->LE);
2568
                    } else {
2569
                        if (!empty($encoded_name)) {
2570
                            $mime[] = sprintf('Content-Disposition: %s; filename=%s%s', $disposition, $encoded_name, $this->LE . $this->LE);
2571
                        } else {
2572
                            $mime[] = sprintf('Content-Disposition: %s%s', $disposition, $this->LE . $this->LE);
2573
                        }
2574
                    }
2575
                } else {
2576
                    $mime[] = $this->LE;
2577
                }
2578
2579
                // Encode as string attachment
2580
                if ($bString) {
2581
                    $mime[] = $this->encodeString($string, $encoding);
2582
                    if ($this->isError()) {
2583
                        return '';
2584
                    }
2585
                    $mime[] = $this->LE . $this->LE;
2586
                } else {
2587
                    $mime[] = $this->encodeFile($path, $encoding);
2588
                    if ($this->isError()) {
2589
                        return '';
2590
                    }
2591
                    $mime[] = $this->LE . $this->LE;
2592
                }
2593
            }
2594
        }
2595
2596
        $mime[] = sprintf('--%s--%s', $boundary, $this->LE);
2597
2598
        return implode('', $mime);
2599
    }
2600
2601
    /**
2602
     * Encode a file attachment in requested format.
2603
     * Returns an empty string on failure.
2604
     * @param string $path     The full path to the file
2605
     * @param string $encoding The encoding to use; one of 'base64', '7bit', '8bit', 'binary', 'quoted-printable'
2606
     * @access protected
2607
     * @return string
2608
     */
2609
    protected function encodeFile($path, $encoding = 'base64')
2610
    {
2611
        try {
2612
            if (!is_readable($path)) {
2613
                throw new phpmailerException($this->lang('file_open') . $path, self::STOP_CONTINUE);
2614
            }
2615
            $magic_quotes = get_magic_quotes_runtime();
2616
            if ($magic_quotes) {
2617
                if (version_compare(PHP_VERSION, '5.3.0', '<')) {
2618
                    set_magic_quotes_runtime(false);
2619
                } else {
2620
                    //Doesn't exist in PHP 5.4, but we don't need to check because
2621
                    //get_magic_quotes_runtime always returns false in 5.4+
2622
                    //so it will never get here
2623
                    ini_set('magic_quotes_runtime', false);
2624
                }
2625
            }
2626
            $file_buffer = file_get_contents($path);
2627
            $file_buffer = $this->encodeString($file_buffer, $encoding);
2628
            if ($magic_quotes) {
2629
                if (version_compare(PHP_VERSION, '5.3.0', '<')) {
2630
                    set_magic_quotes_runtime($magic_quotes);
2631
                } else {
2632
                    ini_set('magic_quotes_runtime', $magic_quotes);
2633
                }
2634
            }
2635
2636
            return $file_buffer;
2637
        }
2638
        catch (Exception $exc) {
2639
            $this->setError($exc->getMessage());
2640
2641
            return '';
2642
        }
2643
    }
2644
2645
    /**
2646
     * Encode a string in requested format.
2647
     * Returns an empty string on failure.
2648
     * @param string $str      The text to encode
2649
     * @param string $encoding The encoding to use; one of 'base64', '7bit', '8bit', 'binary', 'quoted-printable'
2650
     * @access public
2651
     * @return string
2652
     */
2653
    public function encodeString($str, $encoding = 'base64')
2654
    {
2655
        $encoded = '';
2656
        switch (mb_strtolower($encoding)) {
2657
            case 'base64':
2658
                $encoded = chunk_split(base64_encode($str), 76, $this->LE);
2659
                break;
2660
            case '7bit':
2661
            case '8bit':
2662
                $encoded = $this->fixEOL($str);
2663
                // Make sure it ends with a line break
2664
                if (mb_substr($encoded, -(mb_strlen($this->LE))) != $this->LE) {
2665
                    $encoded .= $this->LE;
2666
                }
2667
                break;
2668
            case 'binary':
2669
                $encoded = $str;
2670
                break;
2671
            case 'quoted-printable':
2672
                $encoded = $this->encodeQP($str);
2673
                break;
2674
            default:
2675
                $this->setError($this->lang('encoding') . $encoding);
2676
                break;
2677
        }
2678
2679
        return $encoded;
2680
    }
2681
2682
    /**
2683
     * Encode a header string optimally.
2684
     * Picks shortest of Q, B, quoted-printable or none.
2685
     * @access public
2686
     * @param string $str
2687
     * @param string $position
2688
     * @return string
2689
     */
2690
    public function encodeHeader($str, $position = 'text')
2691
    {
2692
        $matchcount = 0;
2693
        switch (mb_strtolower($position)) {
2694
            case 'phrase':
2695
                if (!preg_match('/[\200-\377]/', $str)) {
2696
                    // Can't use addslashes as we don't know the value of magic_quotes_sybase
2697
                    $encoded = addcslashes($str, "\0..\37\177\\\"");
2698
                    if (($str == $encoded) && !preg_match('/[^A-Za-z0-9!#$%&\'*+\/=?^_`{|}~ -]/', $str)) {
2699
                        return ($encoded);
2700
                    }
2701
2702
                    return ("\"$encoded\"");
2703
                }
2704
                $matchcount = preg_match_all('/[^\040\041\043-\133\135-\176]/', $str, $matches);
2705
                break;
2706
            /** @noinspection PhpMissingBreakStatementInspection */
2707
            case 'comment':
2708
                $matchcount = preg_match_all('/[()"]/', $str, $matches);
2709
            // Intentional fall-through
2710
            // no break
2711
            case 'text':
2712
            default:
2713
                $matchcount += preg_match_all('/[\000-\010\013\014\016-\037\177-\377]/', $str, $matches);
2714
                break;
2715
        }
2716
2717
        //There are no chars that need encoding
2718
        if (0 == $matchcount) {
2719
            return ($str);
2720
        }
2721
2722
        $maxlen = 75 - 7 - mb_strlen($this->CharSet);
2723
        // Try to select the encoding which should produce the shortest output
2724
        if ($matchcount > mb_strlen($str) / 3) {
2725
            // More than a third of the content will need encoding, so B encoding will be most efficient
2726
            $encoding = 'B';
2727
            if (function_exists('mb_strlen') && $this->hasMultiBytes($str)) {
2728
                // Use a custom function which correctly encodes and wraps long
2729
                // multibyte strings without breaking lines within a character
2730
                $encoded = $this->base64EncodeWrapMB($str, "\n");
2731
            } else {
2732
                $encoded = base64_encode($str);
2733
                $maxlen  -= $maxlen % 4;
2734
                $encoded = trim(chunk_split($encoded, $maxlen, "\n"));
2735
            }
2736
        } else {
2737
            $encoding = 'Q';
2738
            $encoded  = $this->encodeQ($str, $position);
2739
            $encoded  = $this->wrapText($encoded, $maxlen, true);
2740
            $encoded  = str_replace('=' . self::CRLF, "\n", trim($encoded));
2741
        }
2742
2743
        $encoded = preg_replace('/^(.*)$/m', ' =?' . $this->CharSet . "?$encoding?\\1?=", $encoded);
2744
        $encoded = trim(str_replace("\n", $this->LE, $encoded));
2745
2746
        return $encoded;
2747
    }
2748
2749
    /**
2750
     * Check if a string contains multi-byte characters.
2751
     * @access public
2752
     * @param string $str multi-byte text to wrap encode
2753
     * @return bool
2754
     */
2755
    public function hasMultiBytes($str)
2756
    {
2757
        if (function_exists('mb_strlen')) {
2758
            return (mb_strlen($str) > mb_strlen($str, $this->CharSet));
2759
        }   // Assume no multibytes (we can't handle without mbstring functions anyway)
2760
        return false;
2761
    }
2762
2763
    /**
2764
     * Does a string contain any 8-bit chars (in any charset)?
2765
     * @param string $text
2766
     * @return bool
2767
     */
2768
    public function has8bitChars($text)
2769
    {
2770
        return (bool)preg_match('/[\x80-\xFF]/', $text);
2771
    }
2772
2773
    /**
2774
     * Encode and wrap long multibyte strings for mail headers
2775
     * without breaking lines within a character.
2776
     * Adapted from a function by paravoid
2777
     * @link   http://www.php.net/manual/en/function.mb-encode-mimeheader.php#60283
2778
     * @access public
2779
     * @param string $str       multi-byte text to wrap encode
2780
     * @param string $linebreak string to use as linefeed/end-of-line
0 ignored issues
show
Documentation introduced by
Should the type for parameter $linebreak not be string|null?

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.

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

Loading history...
2781
     * @return string
2782
     */
2783
    public function base64EncodeWrapMB($str, $linebreak = null)
2784
    {
2785
        $start   = '=?' . $this->CharSet . '?B?';
2786
        $end     = '?=';
2787
        $encoded = '';
2788
        if (null === $linebreak) {
2789
            $linebreak = $this->LE;
2790
        }
2791
2792
        $mb_length = mb_strlen($str, $this->CharSet);
2793
        // Each line must have length <= 75, including $start and $end
2794
        $length = 75 - mb_strlen($start) - mb_strlen($end);
2795
        // Average multi-byte ratio
2796
        $ratio = $mb_length / mb_strlen($str);
2797
        // Base64 has a 4:3 ratio
2798
        $avgLength = floor($length * $ratio * .75);
2799
2800
        for ($i = 0; $i < $mb_length; $i += $offset) {
2801
            $lookBack = 0;
2802
            do {
2803
                $offset = $avgLength - $lookBack;
2804
                $chunk  = mb_substr($str, $i, $offset, $this->CharSet);
2805
                $chunk  = base64_encode($chunk);
2806
                $lookBack++;
2807
            } while (mb_strlen($chunk) > $length);
2808
            $encoded .= $chunk . $linebreak;
2809
        }
2810
2811
        // Chomp the last linefeed
2812
        $encoded = mb_substr($encoded, 0, -mb_strlen($linebreak));
2813
2814
        return $encoded;
2815
    }
2816
2817
    /**
2818
     * Encode a string in quoted-printable format.
2819
     * According to RFC2045 section 6.7.
2820
     * @access public
2821
     * @param string $string   The text to encode
2822
     * @param int    $line_max Number of chars allowed on a line before wrapping
2823
     * @return string
2824
     * @link   http://www.php.net/manual/en/function.quoted-printable-decode.php#89417 Adapted from this comment
2825
     */
2826
    public function encodeQP($string, $line_max = 76)
2827
    {
2828
        // Use native function if it's available (>= PHP5.3)
2829
        if (function_exists('quoted_printable_encode')) {
2830
            return quoted_printable_encode($string);
2831
        }
2832
        // Fall back to a pure PHP implementation
2833
        $string = str_replace(['%20', '%0D%0A.', '%0D%0A', '%'], [' ', "\r\n=2E", "\r\n", '='], rawurlencode($string));
2834
2835
        return preg_replace('/[^\r\n]{' . ($line_max - 3) . '}[^=\r\n]{2}/', "$0=\r\n", $string);
2836
    }
2837
2838
    /**
2839
     * Backward compatibility wrapper for an old QP encoding function that was removed.
2840
     * @see        PHPMailer::encodeQP()
2841
     * @access     public
2842
     * @param string $string
2843
     * @param int    $line_max
2844
     * @param bool   $space_conv
2845
     * @return string
2846
     * @deprecated Use encodeQP instead.
2847
     */
2848
    public function encodeQPphp(
2849
        $string,
2850
        $line_max = 76,
2851
        /** @noinspection PhpUnusedParameterInspection */
2852
        $space_conv = false)
0 ignored issues
show
Unused Code introduced by
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...
2853
    {
2854
        return $this->encodeQP($string, $line_max);
2855
    }
2856
2857
    /**
2858
     * Encode a string using Q encoding.
2859
     * @link   http://tools.ietf.org/html/rfc2047
2860
     * @param string $str      the text to encode
2861
     * @param string $position Where the text is going to be used, see the RFC for what that means
2862
     * @access public
2863
     * @return string
2864
     */
2865
    public function encodeQ($str, $position = 'text')
2866
    {
2867
        // There should not be any EOL in the string
2868
        $pattern = '';
2869
        $encoded = str_replace(["\r", "\n"], '', $str);
2870
        switch (mb_strtolower($position)) {
2871
            case 'phrase':
2872
                // RFC 2047 section 5.3
2873
                $pattern = '^A-Za-z0-9!*+\/ -';
2874
                break;
2875
            /** @noinspection PhpMissingBreakStatementInspection */
2876
            case 'comment':
2877
                // RFC 2047 section 5.2
2878
                $pattern = '\(\)"';
2879
            // intentional fall-through
2880
            // for this reason we build the $pattern without including delimiters and []
2881
            // no break
2882
            case 'text':
2883
            default:
2884
                // RFC 2047 section 5.1
2885
                // Replace every high ascii, control, =, ? and _ characters
2886
                $pattern = '\000-\011\013\014\016-\037\075\077\137\177-\377' . $pattern;
2887
                break;
2888
        }
2889
        $matches = [];
2890
        if (preg_match_all("/[{$pattern}]/", $encoded, $matches)) {
2891
            // If the string contains an '=', make sure it's the first thing we replace
2892
            // so as to avoid double-encoding
2893
            $eqkey = array_search('=', $matches[0], true);
2894
            if (false !== $eqkey) {
2895
                unset($matches[0][$eqkey]);
2896
                array_unshift($matches[0], '=');
2897
            }
2898
            foreach (array_unique($matches[0]) as $char) {
2899
                $encoded = str_replace($char, '=' . sprintf('%02X', ord($char)), $encoded);
2900
            }
2901
        }
2902
        // Replace every spaces to _ (more readable than =20)
2903
        return str_replace(' ', '_', $encoded);
2904
    }
2905
2906
    /**
2907
     * Add a string or binary attachment (non-filesystem).
2908
     * This method can be used to attach ascii or binary data,
2909
     * such as a BLOB record from a database.
2910
     * @param string $string      String attachment data.
2911
     * @param string $filename    Name of the attachment.
2912
     * @param string $encoding    File encoding (see $Encoding).
2913
     * @param string $type        File extension (MIME) type.
2914
     * @param string $disposition Disposition to use
2915
     */
2916
    public function addStringAttachment(
2917
        $string,
2918
        $filename,
2919
        $encoding = 'base64',
2920
        $type = '',
2921
        $disposition = 'attachment')
2922
    {
2923
        // If a MIME type is not specified, try to work it out from the file name
2924
        if ('' == $type) {
2925
            $type = self::filenameToType($filename);
2926
        }
2927
        // Append to $attachment array
2928
        $this->attachment[] = [
2929
            0 => $string,
2930
            1 => $filename,
2931
            2 => basename($filename),
2932
            3 => $encoding,
2933
            4 => $type,
2934
            5 => true, // isStringAttachment
2935
            6 => $disposition,
2936
            7 => 0,
2937
        ];
2938
    }
2939
2940
    /**
2941
     * Add an embedded (inline) attachment from a file.
2942
     * This can include images, sounds, and just about any other document type.
2943
     * These differ from 'regular' attachments in that they are intended to be
2944
     * displayed inline with the message, not just attached for download.
2945
     * This is used in HTML messages that embed the images
2946
     * the HTML refers to using the $cid value.
2947
     * Never use a user-supplied path to a file!
2948
     * @param string $path        Path to the attachment.
2949
     * @param string $cid         Content ID of the attachment; Use this to reference
2950
     *                            the content when using an embedded image in HTML.
2951
     * @param string $name        Overrides the attachment name.
2952
     * @param string $encoding    File encoding (see $Encoding).
2953
     * @param string $type        File MIME type.
2954
     * @param string $disposition Disposition to use
2955
     * @return bool True on successfully adding an attachment
2956
     */
2957
    public function addEmbeddedImage($path, $cid, $name = '', $encoding = 'base64', $type = '', $disposition = 'inline')
2958
    {
2959
        if (!@is_file($path)) {
2960
            $this->setError($this->lang('file_access') . $path);
2961
2962
            return false;
2963
        }
2964
2965
        // If a MIME type is not specified, try to work it out from the file name
2966
        if ('' == $type) {
2967
            $type = self::filenameToType($path);
2968
        }
2969
2970
        $filename = basename($path);
2971
        if ('' == $name) {
2972
            $name = $filename;
2973
        }
2974
2975
        // Append to $attachment array
2976
        $this->attachment[] = [
2977
            0 => $path,
2978
            1 => $filename,
2979
            2 => $name,
2980
            3 => $encoding,
2981
            4 => $type,
2982
            5 => false, // isStringAttachment
2983
            6 => $disposition,
2984
            7 => $cid,
2985
        ];
2986
2987
        return true;
2988
    }
2989
2990
    /**
2991
     * Add an embedded stringified attachment.
2992
     * This can include images, sounds, and just about any other document type.
2993
     * Be sure to set the $type to an image type for images:
2994
     * JPEG images use 'image/jpeg', GIF uses 'image/gif', PNG uses 'image/png'.
2995
     * @param string $string      The attachment binary data.
2996
     * @param string $cid         Content ID of the attachment; Use this to reference
2997
     *                            the content when using an embedded image in HTML.
2998
     * @param string $name
2999
     * @param string $encoding    File encoding (see $Encoding).
3000
     * @param string $type        MIME type.
3001
     * @param string $disposition Disposition to use
3002
     * @return bool True on successfully adding an attachment
3003
     */
3004
    public function addStringEmbeddedImage(
3005
        $string,
3006
        $cid,
3007
        $name = '',
3008
        $encoding = 'base64',
3009
        $type = '',
3010
        $disposition = 'inline')
3011
    {
3012
        // If a MIME type is not specified, try to work it out from the name
3013
        if ('' == $type and !empty($name)) {
3014
            $type = self::filenameToType($name);
3015
        }
3016
3017
        // Append to $attachment array
3018
        $this->attachment[] = [
3019
            0 => $string,
3020
            1 => $name,
3021
            2 => $name,
3022
            3 => $encoding,
3023
            4 => $type,
3024
            5 => true, // isStringAttachment
3025
            6 => $disposition,
3026
            7 => $cid,
3027
        ];
3028
3029
        return true;
3030
    }
3031
3032
    /**
3033
     * Check if an inline attachment is present.
3034
     * @access public
3035
     * @return bool
3036
     */
3037
    public function inlineImageExists()
3038
    {
3039
        foreach ($this->attachment as $attachment) {
3040
            if ('inline' == $attachment[6]) {
3041
                return true;
3042
            }
3043
        }
3044
3045
        return false;
3046
    }
3047
3048
    /**
3049
     * Check if an attachment (non-inline) is present.
3050
     * @return bool
3051
     */
3052
    public function attachmentExists()
3053
    {
3054
        foreach ($this->attachment as $attachment) {
3055
            if ('attachment' == $attachment[6]) {
3056
                return true;
3057
            }
3058
        }
3059
3060
        return false;
3061
    }
3062
3063
    /**
3064
     * Check if this message has an alternative body set.
3065
     * @return bool
3066
     */
3067
    public function alternativeExists()
3068
    {
3069
        return !empty($this->AltBody);
3070
    }
3071
3072
    /**
3073
     * Clear queued addresses of given kind.
3074
     * @access protected
3075
     * @param string $kind 'to', 'cc', or 'bcc'
3076
     */
3077
    public function clearQueuedAddresses($kind)
3078
    {
3079
        $RecipientsQueue = $this->RecipientsQueue;
3080
        foreach ($RecipientsQueue as $address => $params) {
3081
            if ($params[0] == $kind) {
3082
                unset($this->RecipientsQueue[$address]);
3083
            }
3084
        }
3085
    }
3086
3087
    /**
3088
     * Clear all To recipients.
3089
     */
3090
    public function clearAddresses()
3091
    {
3092
        foreach ($this->to as $to) {
3093
            unset($this->all_recipients[mb_strtolower($to[0])]);
3094
        }
3095
        $this->to = [];
3096
        $this->clearQueuedAddresses('to');
3097
    }
3098
3099
    /**
3100
     * Clear all CC recipients.
3101
     */
3102 View Code Duplication
    public function clearCCs()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
3103
    {
3104
        foreach ($this->cc as $cc) {
3105
            unset($this->all_recipients[mb_strtolower($cc[0])]);
3106
        }
3107
        $this->cc = [];
3108
        $this->clearQueuedAddresses('cc');
3109
    }
3110
3111
    /**
3112
     * Clear all BCC recipients.
3113
     */
3114 View Code Duplication
    public function clearBCCs()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
3115
    {
3116
        foreach ($this->bcc as $bcc) {
3117
            unset($this->all_recipients[mb_strtolower($bcc[0])]);
3118
        }
3119
        $this->bcc = [];
3120
        $this->clearQueuedAddresses('bcc');
3121
    }
3122
3123
    /**
3124
     * Clear all ReplyTo recipients.
3125
     */
3126
    public function clearReplyTos()
3127
    {
3128
        $this->ReplyTo      = [];
3129
        $this->ReplyToQueue = [];
3130
    }
3131
3132
    /**
3133
     * Clear all recipient types.
3134
     */
3135
    public function clearAllRecipients()
3136
    {
3137
        $this->to              = [];
3138
        $this->cc              = [];
3139
        $this->bcc             = [];
3140
        $this->all_recipients  = [];
3141
        $this->RecipientsQueue = [];
3142
    }
3143
3144
    /**
3145
     * Clear all filesystem, string, and binary attachments.
3146
     */
3147
    public function clearAttachments()
3148
    {
3149
        $this->attachment = [];
3150
    }
3151
3152
    /**
3153
     * Clear all custom headers.
3154
     */
3155
    public function clearCustomHeaders()
3156
    {
3157
        $this->CustomHeader = [];
3158
    }
3159
3160
    /**
3161
     * Add an error message to the error container.
3162
     * @access protected
3163
     * @param string $msg
3164
     */
3165
    protected function setError($msg)
3166
    {
3167
        $this->error_count++;
3168
        if ('smtp' == $this->Mailer and null !== $this->smtp) {
3169
            $lasterror = $this->smtp->getError();
3170
            if (!empty($lasterror['error'])) {
3171
                $msg .= $this->lang('smtp_error') . $lasterror['error'];
3172
                if (!empty($lasterror['detail'])) {
3173
                    $msg .= ' Detail: ' . $lasterror['detail'];
3174
                }
3175
                if (!empty($lasterror['smtp_code'])) {
3176
                    $msg .= ' SMTP code: ' . $lasterror['smtp_code'];
3177
                }
3178
                if (!empty($lasterror['smtp_code_ex'])) {
3179
                    $msg .= ' Additional SMTP info: ' . $lasterror['smtp_code_ex'];
3180
                }
3181
            }
3182
        }
3183
        $this->ErrorInfo = $msg;
3184
    }
3185
3186
    /**
3187
     * Return an RFC 822 formatted date.
3188
     * @access public
3189
     * @return string
3190
     * @static
3191
     */
3192
    public static function rfcDate()
3193
    {
3194
        // Set the time zone to whatever the default is to avoid 500 errors
3195
        // Will default to UTC if it's not set properly in php.ini
3196
        date_default_timezone_set(@date_default_timezone_get());
3197
3198
        return date('D, j M Y H:i:s O');
3199
    }
3200
3201
    /**
3202
     * Get the server hostname.
3203
     * Returns 'localhost.localdomain' if unknown.
3204
     * @access protected
3205
     * @return string
3206
     */
3207
    protected function serverHostname()
3208
    {
3209
        $result = 'localhost.localdomain';
3210
        if (!empty($this->Hostname)) {
3211
            $result = $this->Hostname;
3212
        } elseif (isset($_SERVER) and array_key_exists('SERVER_NAME', $_SERVER) and !empty($_SERVER['SERVER_NAME'])) {
3213
            $result = $_SERVER['SERVER_NAME'];
3214
        } elseif (function_exists('gethostname') && false !== gethostname()) {
3215
            $result = gethostname();
3216
        } elseif (false !== php_uname('n')) {
3217
            $result = php_uname('n');
3218
        }
3219
3220
        return $result;
3221
    }
3222
3223
    /**
3224
     * Get an error message in the current language.
3225
     * @access protected
3226
     * @param string $key
3227
     * @return string
3228
     */
3229
    protected function lang($key)
3230
    {
3231
        if (count($this->language) < 1) {
3232
            $this->setLanguage('en'); // set the default language
3233
        }
3234
3235
        if (array_key_exists($key, $this->language)) {
3236
            if ('smtp_connect_failed' == $key) {
3237
                //Include a link to troubleshooting docs on SMTP connection failure
3238
                //this is by far the biggest cause of support questions
3239
                //but it's usually not PHPMailer's fault.
3240
                return $this->language[$key] . ' https://github.com/PHPMailer/PHPMailer/wiki/Troubleshooting';
3241
            }
3242
3243
            return $this->language[$key];
3244
        }
3245
        //Return the key as a fallback
3246
        return $key;
3247
    }
3248
3249
    /**
3250
     * Check if an error occurred.
3251
     * @access public
3252
     * @return bool True if an error did occur.
3253
     */
3254
    public function isError()
3255
    {
3256
        return ($this->error_count > 0);
3257
    }
3258
3259
    /**
3260
     * Ensure consistent line endings in a string.
3261
     * Changes every end of line from CRLF, CR or LF to $this->LE.
3262
     * @access public
3263
     * @param string $str String to fixEOL
3264
     * @return string
3265
     */
3266
    public function fixEOL($str)
3267
    {
3268
        // Normalise to \n
3269
        $nstr = str_replace(["\r\n", "\r"], "\n", $str);
3270
        // Now convert LE as needed
3271
        if ("\n" !== $this->LE) {
3272
            $nstr = str_replace("\n", $this->LE, $nstr);
3273
        }
3274
3275
        return $nstr;
3276
    }
3277
3278
    /**
3279
     * Add a custom header.
3280
     * $name value can be overloaded to contain
3281
     * both header name and value (name:value)
3282
     * @access public
3283
     * @param string $name  Custom header name
3284
     * @param string $value Header value
0 ignored issues
show
Documentation introduced by
Should the type for parameter $value not be string|null?

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.

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

Loading history...
3285
     */
3286
    public function addCustomHeader($name, $value = null)
3287
    {
3288
        if (null === $value) {
3289
            // Value passed in as name:value
3290
            $this->CustomHeader[] = explode(':', $name, 2);
3291
        } else {
3292
            $this->CustomHeader[] = [$name, $value];
3293
        }
3294
    }
3295
3296
    /**
3297
     * Returns all custom headers.
3298
     * @return array
3299
     */
3300
    public function getCustomHeaders()
3301
    {
3302
        return $this->CustomHeader;
3303
    }
3304
3305
    /**
3306
     * Create a message body from an HTML string.
3307
     * Automatically inlines images and creates a plain-text version by converting the HTML,
3308
     * overwriting any existing values in Body and AltBody.
3309
     * Do not source $message content from user input!
3310
     * $basedir is prepended when handling relative URLs, e.g. <img src="/images/a.png"> and must not be empty
3311
     * will look for an image file in $basedir/images/a.png and convert it to inline.
3312
     * If you don't provide a $basedir, relative paths will be left untouched (and thus probably break in email)
3313
     * If you don't want to apply these transformations to your HTML, just set Body and AltBody directly.
3314
     * @access public
3315
     * @param string        $message  HTML message string
3316
     * @param string        $basedir  Absolute path to a base directory to prepend to relative paths to images
3317
     * @param bool|callable $advanced Whether to use the internal HTML to text converter
3318
     *                                or your own custom converter @see PHPMailer::html2text()
3319
     * @return string $message The transformed message Body
3320
     */
3321
    public function msgHTML($message, $basedir = '', $advanced = false)
3322
    {
3323
        preg_match_all('/(src|background)=["\'](.*)["\']/Ui', $message, $images);
3324
        if (array_key_exists(2, $images)) {
3325
            if (mb_strlen($basedir) > 1 && '/' != mb_substr($basedir, -1)) {
3326
                // Ensure $basedir has a trailing /
3327
                $basedir .= '/';
3328
            }
3329
            foreach ($images[2] as $imgindex => $url) {
3330
                // Convert data URIs into embedded images
3331
                if (preg_match('#^data:(image[^;,]*)(;base64)?,#', $url, $match)) {
3332
                    $data = mb_substr($url, mb_strpos($url, ','));
3333
                    if ($match[2]) {
3334
                        $data = base64_decode($data, true);
3335
                    } else {
3336
                        $data = rawurldecode($data);
3337
                    }
3338
                    $cid = md5($url) . '@phpmailer.0'; // RFC2392 S 2
3339
                    if ($this->addStringEmbeddedImage($data, $cid, 'embed' . $imgindex, 'base64', $match[1])) {
3340
                        $message = str_replace($images[0][$imgindex], $images[1][$imgindex] . '="cid:' . $cid . '"', $message);
3341
                    }
3342
                    continue;
3343
                }
3344
                if (// Only process relative URLs if a basedir is provided (i.e. no absolute local paths)
3345
                    !empty($basedir)
3346
                    // Ignore URLs containing parent dir traversal (..)
3347
                    && (false === mb_strpos($url, '..'))
3348
                    // Do not change urls that are already inline images
3349
                    && 'cid:' !== mb_substr($url, 0, 4)
3350
                    // Do not change absolute URLs, including anonymous protocol
3351
                    && !preg_match('#^[a-z][a-z0-9+.-]*:?//#i', $url)) {
3352
                    $filename  = basename($url);
3353
                    $directory = dirname($url);
3354
                    if ('.' == $directory) {
3355
                        $directory = '';
3356
                    }
3357
                    $cid = md5($url) . '@phpmailer.0'; // RFC2392 S 2
3358
                    if (mb_strlen($directory) > 1 && '/' != mb_substr($directory, -1)) {
3359
                        $directory .= '/';
3360
                    }
3361
                    if ($this->addEmbeddedImage($basedir . $directory . $filename, $cid, $filename, 'base64', self::_mime_types((string)self::mb_pathinfo($filename, PATHINFO_EXTENSION)))) {
3362
                        $message = preg_replace('/' . $images[1][$imgindex] . '=["\']' . preg_quote($url, '/') . '["\']/Ui', $images[1][$imgindex] . '="cid:' . $cid . '"', $message);
3363
                    }
3364
                }
3365
            }
3366
        }
3367
        $this->isHTML(true);
3368
        // Convert all message body line breaks to CRLF, makes quoted-printable encoding work much better
3369
        $this->Body    = $this->normalizeBreaks($message);
3370
        $this->AltBody = $this->normalizeBreaks($this->html2text($message, $advanced));
3371
        if (!$this->alternativeExists()) {
3372
            $this->AltBody = 'To view this email message, open it in a program that understands HTML!' . self::CRLF . self::CRLF;
3373
        }
3374
3375
        return $this->Body;
3376
    }
3377
3378
    /**
3379
     * Convert an HTML string into plain text.
3380
     * This is used by msgHTML().
3381
     * Note - older versions of this function used a bundled advanced converter
3382
     * which was been removed for license reasons in #232.
3383
     * Example usage:
3384
     * <code>
3385
     * // Use default conversion
3386
     * $plain = $mail->html2text($html);
3387
     * // Use your own custom converter
3388
     * $plain = $mail->html2text($html, function($html) {
3389
     *     $converter = new MyHtml2text($html);
3390
     *     return $converter->get_text();
3391
     * });
3392
     * </code>
3393
     * @param string        $html     The HTML text to convert
3394
     * @param bool|callable $advanced Any boolean value to use the internal converter,
3395
     *                                or provide your own callable for custom conversion.
3396
     * @return string
3397
     */
3398
    public function html2text($html, $advanced = false)
3399
    {
3400
        if (is_callable($advanced)) {
3401
            return call_user_func($advanced, $html);
3402
        }
3403
3404
        return html_entity_decode(trim(strip_tags(preg_replace('/<(head|title|style|script)[^>]*>.*?<\/\\1>/si', '', $html))), ENT_QUOTES, $this->CharSet);
3405
    }
3406
3407
    /**
3408
     * Get the MIME type for a file extension.
3409
     * @param string $ext File extension
3410
     * @access public
3411
     * @return string MIME type of file.
3412
     * @static
3413
     */
3414
    public static function _mime_types($ext = '')
3415
    {
3416
        $mimes = [
3417
            'xl'    => 'application/excel',
3418
            'js'    => 'application/javascript',
3419
            'hqx'   => 'application/mac-binhex40',
3420
            'cpt'   => 'application/mac-compactpro',
3421
            'bin'   => 'application/macbinary',
3422
            'doc'   => 'application/msword',
3423
            'word'  => 'application/msword',
3424
            'xlsx'  => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
3425
            'xltx'  => 'application/vnd.openxmlformats-officedocument.spreadsheetml.template',
3426
            'potx'  => 'application/vnd.openxmlformats-officedocument.presentationml.template',
3427
            'ppsx'  => 'application/vnd.openxmlformats-officedocument.presentationml.slideshow',
3428
            'pptx'  => 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
3429
            'sldx'  => 'application/vnd.openxmlformats-officedocument.presentationml.slide',
3430
            'docx'  => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
3431
            'dotx'  => 'application/vnd.openxmlformats-officedocument.wordprocessingml.template',
3432
            'xlam'  => 'application/vnd.ms-excel.addin.macroEnabled.12',
3433
            'xlsb'  => 'application/vnd.ms-excel.sheet.binary.macroEnabled.12',
3434
            'class' => 'application/octet-stream',
3435
            'dll'   => 'application/octet-stream',
3436
            'dms'   => 'application/octet-stream',
3437
            'exe'   => 'application/octet-stream',
3438
            'lha'   => 'application/octet-stream',
3439
            'lzh'   => 'application/octet-stream',
3440
            'psd'   => 'application/octet-stream',
3441
            'sea'   => 'application/octet-stream',
3442
            'so'    => 'application/octet-stream',
3443
            'oda'   => 'application/oda',
3444
            'pdf'   => 'application/pdf',
3445
            'ai'    => 'application/postscript',
3446
            'eps'   => 'application/postscript',
3447
            'ps'    => 'application/postscript',
3448
            'smi'   => 'application/smil',
3449
            'smil'  => 'application/smil',
3450
            'mif'   => 'application/vnd.mif',
3451
            'xls'   => 'application/vnd.ms-excel',
3452
            'ppt'   => 'application/vnd.ms-powerpoint',
3453
            'wbxml' => 'application/vnd.wap.wbxml',
3454
            'wmlc'  => 'application/vnd.wap.wmlc',
3455
            'dcr'   => 'application/x-director',
3456
            'dir'   => 'application/x-director',
3457
            'dxr'   => 'application/x-director',
3458
            'dvi'   => 'application/x-dvi',
3459
            'gtar'  => 'application/x-gtar',
3460
            'php3'  => 'application/x-httpd-php',
3461
            'php4'  => 'application/x-httpd-php',
3462
            'php'   => 'application/x-httpd-php',
3463
            'phtml' => 'application/x-httpd-php',
3464
            'phps'  => 'application/x-httpd-php-source',
3465
            'swf'   => 'application/x-shockwave-flash',
3466
            'sit'   => 'application/x-stuffit',
3467
            'tar'   => 'application/x-tar',
3468
            'tgz'   => 'application/x-tar',
3469
            'xht'   => 'application/xhtml+xml',
3470
            'xhtml' => 'application/xhtml+xml',
3471
            'zip'   => 'application/zip',
3472
            'mid'   => 'audio/midi',
3473
            'midi'  => 'audio/midi',
3474
            'mp2'   => 'audio/mpeg',
3475
            'mp3'   => 'audio/mpeg',
3476
            'mpga'  => 'audio/mpeg',
3477
            'aif'   => 'audio/x-aiff',
3478
            'aifc'  => 'audio/x-aiff',
3479
            'aiff'  => 'audio/x-aiff',
3480
            'ram'   => 'audio/x-pn-realaudio',
3481
            'rm'    => 'audio/x-pn-realaudio',
3482
            'rpm'   => 'audio/x-pn-realaudio-plugin',
3483
            'ra'    => 'audio/x-realaudio',
3484
            'wav'   => 'audio/x-wav',
3485
            'bmp'   => 'image/bmp',
3486
            'gif'   => 'image/gif',
3487
            'jpeg'  => 'image/jpeg',
3488
            'jpe'   => 'image/jpeg',
3489
            'jpg'   => 'image/jpeg',
3490
            'png'   => 'image/png',
3491
            'tiff'  => 'image/tiff',
3492
            'tif'   => 'image/tiff',
3493
            'eml'   => 'message/rfc822',
3494
            'css'   => 'text/css',
3495
            'html'  => 'text/html',
3496
            'htm'   => 'text/html',
3497
            'shtml' => 'text/html',
3498
            'log'   => 'text/plain',
3499
            'text'  => 'text/plain',
3500
            'txt'   => 'text/plain',
3501
            'rtx'   => 'text/richtext',
3502
            'rtf'   => 'text/rtf',
3503
            'vcf'   => 'text/vcard',
3504
            'vcard' => 'text/vcard',
3505
            'xml'   => 'text/xml',
3506
            'xsl'   => 'text/xml',
3507
            'mpeg'  => 'video/mpeg',
3508
            'mpe'   => 'video/mpeg',
3509
            'mpg'   => 'video/mpeg',
3510
            'mov'   => 'video/quicktime',
3511
            'qt'    => 'video/quicktime',
3512
            'rv'    => 'video/vnd.rn-realvideo',
3513
            'avi'   => 'video/x-msvideo',
3514
            'movie' => 'video/x-sgi-movie',
3515
        ];
3516
        if (array_key_exists(mb_strtolower($ext), $mimes)) {
3517
            return $mimes[mb_strtolower($ext)];
3518
        }
3519
3520
        return 'application/octet-stream';
3521
    }
3522
3523
    /**
3524
     * Map a file name to a MIME type.
3525
     * Defaults to 'application/octet-stream', i.e.. arbitrary binary data.
3526
     * @param string $filename A file name or full path, does not need to exist as a file
3527
     * @return string
3528
     * @static
3529
     */
3530
    public static function filenameToType($filename)
3531
    {
3532
        // In case the path is a URL, strip any query string before getting extension
3533
        $qpos = mb_strpos($filename, '?');
3534
        if (false !== $qpos) {
3535
            $filename = mb_substr($filename, 0, $qpos);
3536
        }
3537
        $pathinfo = self::mb_pathinfo($filename);
3538
3539
        return self::_mime_types($pathinfo['extension']);
3540
    }
3541
3542
    /**
3543
     * Multi-byte-safe pathinfo replacement.
3544
     * Drop-in replacement for pathinfo(), but multibyte-safe, cross-platform-safe, old-version-safe.
3545
     * Works similarly to the one in PHP >= 5.2.0
3546
     * @link http://www.php.net/manual/en/function.pathinfo.php#107461
3547
     * @param string     $path    A filename or path, does not need to exist as a file
3548
     * @param int|string $options Either a PATHINFO_* constant,
0 ignored issues
show
Documentation introduced by
Should the type for parameter $options not be integer|string|null?

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.

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

Loading history...
3549
     *                            or a string name to return only the specified piece, allows 'filename' to work on PHP < 5.2
3550
     * @return string|array
3551
     * @static
3552
     */
3553
    public static function mb_pathinfo($path, $options = null)
3554
    {
3555
        $ret      = ['dirname' => '', 'basename' => '', 'extension' => '', 'filename' => ''];
3556
        $pathinfo = [];
3557
        if (preg_match('%^(.*?)[\\\\/]*(([^/\\\\]*?)(\.([^\.\\\\/]+?)|))[\\\\/\.]*$%im', $path, $pathinfo)) {
3558
            if (array_key_exists(1, $pathinfo)) {
3559
                $ret['dirname'] = $pathinfo[1];
3560
            }
3561
            if (array_key_exists(2, $pathinfo)) {
3562
                $ret['basename'] = $pathinfo[2];
3563
            }
3564
            if (array_key_exists(5, $pathinfo)) {
3565
                $ret['extension'] = $pathinfo[5];
3566
            }
3567
            if (array_key_exists(3, $pathinfo)) {
3568
                $ret['filename'] = $pathinfo[3];
3569
            }
3570
        }
3571
        switch ($options) {
3572
            case PATHINFO_DIRNAME:
3573
            case 'dirname':
3574
                return $ret['dirname'];
3575
            case PATHINFO_BASENAME:
3576
            case 'basename':
3577
                return $ret['basename'];
3578
            case PATHINFO_EXTENSION:
3579
            case 'extension':
3580
                return $ret['extension'];
3581
            case PATHINFO_FILENAME:
3582
            case 'filename':
3583
                return $ret['filename'];
3584
            default:
3585
                return $ret;
3586
        }
3587
    }
3588
3589
    /**
3590
     * Set or reset instance properties.
3591
     * You should avoid this function - it's more verbose, less efficient, more error-prone and
3592
     * harder to debug than setting properties directly.
3593
     * Usage Example:
3594
     * `$mail->set('SMTPSecure', 'tls');`
3595
     *   is the same as:
3596
     * `$mail->SMTPSecure = 'tls';`
3597
     * @access public
3598
     * @param string $name  The property name to set
3599
     * @param mixed  $value The value to set the property to
3600
     * @return bool
3601
     * @TODO   Should this not be using the __set() magic function?
3602
     */
3603
    public function set($name, $value = '')
3604
    {
3605
        if (property_exists($this, $name)) {
3606
            $this->$name = $value;
3607
3608
            return true;
3609
        }
3610
        $this->setError($this->lang('variable_set') . $name);
3611
3612
        return false;
3613
    }
3614
3615
    /**
3616
     * Strip newlines to prevent header injection.
3617
     * @access public
3618
     * @param string $str
3619
     * @return string
3620
     */
3621
    public function secureHeader($str)
3622
    {
3623
        return trim(str_replace(["\r", "\n"], '', $str));
3624
    }
3625
3626
    /**
3627
     * Normalize line breaks in a string.
3628
     * Converts UNIX LF, Mac CR and Windows CRLF line breaks into a single line break format.
3629
     * Defaults to CRLF (for message bodies) and preserves consecutive breaks.
3630
     * @param string $text
3631
     * @param string $breaktype What kind of line break to use, defaults to CRLF
3632
     * @return string
3633
     * @access public
3634
     * @static
3635
     */
3636
    public static function normalizeBreaks($text, $breaktype = "\r\n")
3637
    {
3638
        return preg_replace('/(\r\n|\r|\n)/ms', $breaktype, $text);
3639
    }
3640
3641
    /**
3642
     * Set the public and private key files and password for S/MIME signing.
3643
     * @access public
3644
     * @param string $cert_filename
3645
     * @param string $key_filename
3646
     * @param string $key_pass            Password for private key
3647
     * @param string $extracerts_filename Optional path to chain certificate
3648
     */
3649
    public function sign($cert_filename, $key_filename, $key_pass, $extracerts_filename = '')
3650
    {
3651
        $this->sign_cert_file       = $cert_filename;
3652
        $this->sign_key_file        = $key_filename;
3653
        $this->sign_key_pass        = $key_pass;
3654
        $this->sign_extracerts_file = $extracerts_filename;
3655
    }
3656
3657
    /**
3658
     * Quoted-Printable-encode a DKIM header.
3659
     * @access public
3660
     * @param string $txt
3661
     * @return string
3662
     */
3663
    public function DKIM_QP($txt)
3664
    {
3665
        $line = '';
3666
        for ($i = 0; $i < mb_strlen($txt); $i++) {
3667
            $ord = ord($txt[$i]);
3668
            if ((($ord >= 0x21) && ($ord <= 0x3A)) || 0x3C == $ord || (($ord >= 0x3E) && ($ord <= 0x7E))) {
3669
                $line .= $txt[$i];
3670
            } else {
3671
                $line .= '=' . sprintf('%02X', $ord);
3672
            }
3673
        }
3674
3675
        return $line;
3676
    }
3677
3678
    /**
3679
     * Generate a DKIM signature.
3680
     * @access public
3681
     * @param string $signHeader
3682
     * @throws phpmailerException
3683
     * @return string The DKIM signature value
3684
     */
3685
    public function DKIM_Sign($signHeader)
3686
    {
3687
        if (!defined('PKCS7_TEXT')) {
3688
            if ($this->exceptions) {
3689
                throw new phpmailerException($this->lang('extension_missing') . 'openssl');
3690
            }
3691
3692
            return '';
3693
        }
3694
        $privKeyStr = !empty($this->DKIM_private_string) ? $this->DKIM_private_string : file_get_contents($this->DKIM_private);
3695
        if ('' != $this->DKIM_passphrase) {
3696
            $privKey = openssl_pkey_get_private($privKeyStr, $this->DKIM_passphrase);
3697
        } else {
3698
            $privKey = openssl_pkey_get_private($privKeyStr);
3699
        }
3700
        //Workaround for missing digest algorithms in old PHP & OpenSSL versions
3701
        //@link http://stackoverflow.com/a/11117338/333340
3702
        if (version_compare(PHP_VERSION, '5.3.0') >= 0 and in_array('sha256WithRSAEncryption', openssl_get_md_methods(true), true)) {
3703
            if (openssl_sign($signHeader, $signature, $privKey, 'sha256WithRSAEncryption')) {
3704
                openssl_pkey_free($privKey);
3705
3706
                return base64_encode($signature);
3707
            }
3708
        } else {
3709
            $pinfo = openssl_pkey_get_details($privKey);
3710
            $hash  = hash('sha256', $signHeader);
3711
            //'Magic' constant for SHA256 from RFC3447
3712
            //@link https://tools.ietf.org/html/rfc3447#page-43
3713
            $t     = '3031300d060960864801650304020105000420' . $hash;
3714
            $pslen = $pinfo['bits'] / 8 - (mb_strlen($t) / 2 + 3);
3715
            $eb    = pack('H*', '0001' . str_repeat('FF', $pslen) . '00' . $t);
3716
3717
            if (openssl_private_encrypt($eb, $signature, $privKey, OPENSSL_NO_PADDING)) {
3718
                openssl_pkey_free($privKey);
3719
3720
                return base64_encode($signature);
3721
            }
3722
        }
3723
        openssl_pkey_free($privKey);
3724
3725
        return '';
3726
    }
3727
3728
    /**
3729
     * Generate a DKIM canonicalization header.
3730
     * @access public
3731
     * @param string $signHeader Header
3732
     * @return string
3733
     */
3734
    public function DKIM_HeaderC($signHeader)
3735
    {
3736
        $signHeader = preg_replace('/\r\n\s+/', ' ', $signHeader);
3737
        $lines      = explode("\r\n", $signHeader);
3738
        foreach ($lines as $key => $line) {
3739
            list($heading, $value) = explode(':', $line, 2);
3740
            $heading     = mb_strtolower($heading);
3741
            $value       = preg_replace('/\s{2,}/', ' ', $value); // Compress useless spaces
3742
            $lines[$key] = $heading . ':' . trim($value); // Don't forget to remove WSP around the value
3743
        }
3744
        $signHeader = implode("\r\n", $lines);
3745
3746
        return $signHeader;
3747
    }
3748
3749
    /**
3750
     * Generate a DKIM canonicalization body.
3751
     * @access public
3752
     * @param string $body Message Body
3753
     * @return string
3754
     */
3755
    public function DKIM_BodyC($body)
3756
    {
3757
        if ('' == $body) {
3758
            return "\r\n";
3759
        }
3760
        // stabilize line endings
3761
        $body = str_replace("\r\n", "\n", $body);
3762
        $body = str_replace("\n", "\r\n", $body);
3763
        // END stabilize line endings
3764
        while ("\r\n\r\n" == mb_substr($body, mb_strlen($body) - 4, 4)) {
3765
            $body = mb_substr($body, 0, mb_strlen($body) - 2);
3766
        }
3767
3768
        return $body;
3769
    }
3770
3771
    /**
3772
     * Create the DKIM header and body in a new message header.
3773
     * @access public
3774
     * @param string $headers_line Header lines
3775
     * @param string $subject      Subject
3776
     * @param string $body         Body
3777
     * @throws \phpmailerException
3778
     * @return string
3779
     */
3780
    public function DKIM_Add($headers_line, $subject, $body)
3781
    {
3782
        $DKIMsignatureType    = 'rsa-sha256'; // Signature & hash algorithms
3783
        $DKIMcanonicalization = 'relaxed/simple'; // Canonicalization of header/body
3784
        $DKIMquery            = 'dns/txt'; // Query method
3785
        $DKIMtime             = time(); // Signature Timestamp = seconds since 00:00:00 - Jan 1, 1970 (UTC time zone)
3786
        $subject_header       = "Subject: $subject";
3787
        $headers              = explode($this->LE, $headers_line);
3788
        $from_header          = '';
3789
        $to_header            = '';
3790
        $date_header          = '';
3791
        $current              = '';
3792
        foreach ($headers as $header) {
3793
            if (0 === mb_strpos($header, 'From:')) {
3794
                $from_header = $header;
3795
                $current     = 'from_header';
3796
            } elseif (0 === mb_strpos($header, 'To:')) {
3797
                $to_header = $header;
3798
                $current   = 'to_header';
3799
            } elseif (0 === mb_strpos($header, 'Date:')) {
3800
                $date_header = $header;
3801
                $current     = 'date_header';
3802
            } else {
3803
                if (!empty($$current) && 0 === mb_strpos($header, ' =?')) {
3804
                    $$current .= $header;
3805
                } else {
3806
                    $current = '';
3807
                }
3808
            }
3809
        }
3810
        $from    = str_replace('|', '=7C', $this->DKIM_QP($from_header));
3811
        $to      = str_replace('|', '=7C', $this->DKIM_QP($to_header));
3812
        $date    = str_replace('|', '=7C', $this->DKIM_QP($date_header));
3813
        $subject = str_replace('|', '=7C', $this->DKIM_QP($subject_header)); // Copied header fields (dkim-quoted-printable)
3814
        $body    = $this->DKIM_BodyC($body);
3815
        $DKIMlen = mb_strlen($body); // Length of body
3816
        $DKIMb64 = base64_encode(pack('H*', hash('sha256', $body))); // Base64 of packed binary SHA-256 hash of body
3817
        if ('' == $this->DKIM_identity) {
3818
            $ident = '';
3819
        } else {
3820
            $ident = ' i=' . $this->DKIM_identity . ';';
3821
        }
3822
        $dkimhdrs = 'DKIM-Signature: v=1; a='
3823
                    . $DKIMsignatureType
3824
                    . '; q='
3825
                    . $DKIMquery
3826
                    . '; l='
3827
                    . $DKIMlen
3828
                    . '; s='
3829
                    . $this->DKIM_selector
3830
                    . ";\r\n"
3831
                    . "\tt="
3832
                    . $DKIMtime
3833
                    . '; c='
3834
                    . $DKIMcanonicalization
3835
                    . ";\r\n"
3836
                    . "\th=From:To:Date:Subject;\r\n"
3837
                    . "\td="
3838
                    . $this->DKIM_domain
3839
                    . ';'
3840
                    . $ident
3841
                    . "\r\n"
3842
                    . "\tz=$from\r\n"
3843
                    . "\t|$to\r\n"
3844
                    . "\t|$date\r\n"
3845
                    . "\t|$subject;\r\n"
3846
                    . "\tbh="
3847
                    . $DKIMb64
3848
                    . ";\r\n"
3849
                    . "\tb=";
3850
        $toSign   = $this->DKIM_HeaderC($from_header . "\r\n" . $to_header . "\r\n" . $date_header . "\r\n" . $subject_header . "\r\n" . $dkimhdrs);
3851
        $signed   = $this->DKIM_Sign($toSign);
3852
3853
        return $dkimhdrs . $signed . "\r\n";
3854
    }
3855
3856
    /**
3857
     * Detect if a string contains a line longer than the maximum line length allowed.
3858
     * @param string $str
3859
     * @return bool
3860
     * @static
3861
     */
3862
    public static function hasLineLongerThanMax($str)
3863
    {
3864
        //+2 to include CRLF line break for a 1000 total
3865
        return (bool)preg_match('/^(.{' . (self::MAX_LINE_LENGTH + 2) . ',})/m', $str);
3866
    }
3867
3868
    /**
3869
     * Allows for public read access to 'to' property.
3870
     * @note   : Before the send() call, queued addresses (i.e. with IDN) are not yet included.
3871
     * @access public
3872
     * @return array
3873
     */
3874
    public function getToAddresses()
3875
    {
3876
        return $this->to;
3877
    }
3878
3879
    /**
3880
     * Allows for public read access to 'cc' property.
3881
     * @note   : Before the send() call, queued addresses (i.e. with IDN) are not yet included.
3882
     * @access public
3883
     * @return array
3884
     */
3885
    public function getCcAddresses()
3886
    {
3887
        return $this->cc;
3888
    }
3889
3890
    /**
3891
     * Allows for public read access to 'bcc' property.
3892
     * @note   : Before the send() call, queued addresses (i.e. with IDN) are not yet included.
3893
     * @access public
3894
     * @return array
3895
     */
3896
    public function getBccAddresses()
3897
    {
3898
        return $this->bcc;
3899
    }
3900
3901
    /**
3902
     * Allows for public read access to 'ReplyTo' property.
3903
     * @note   : Before the send() call, queued addresses (i.e. with IDN) are not yet included.
3904
     * @access public
3905
     * @return array
3906
     */
3907
    public function getReplyToAddresses()
3908
    {
3909
        return $this->ReplyTo;
3910
    }
3911
3912
    /**
3913
     * Allows for public read access to 'all_recipients' property.
3914
     * @note   : Before the send() call, queued addresses (i.e. with IDN) are not yet included.
3915
     * @access public
3916
     * @return array
3917
     */
3918
    public function getAllRecipientAddresses()
3919
    {
3920
        return $this->all_recipients;
3921
    }
3922
3923
    /**
3924
     * Perform a callback.
3925
     * @param bool   $isSent
3926
     * @param array  $to
3927
     * @param array  $cc
3928
     * @param array  $bcc
3929
     * @param string $subject
3930
     * @param string $body
3931
     * @param string $from
3932
     */
3933
    protected function doCallback($isSent, $to, $cc, $bcc, $subject, $body, $from)
3934
    {
3935
        if (!empty($this->action_function) && is_callable($this->action_function)) {
3936
            $params = [$isSent, $to, $cc, $bcc, $subject, $body, $from];
3937
            call_user_func_array($this->action_function, $params);
3938
        }
3939
    }
3940
}
3941
3942
/**
3943
 * PHPMailer exception handler
3944
 * @package PHPMailer
3945
 */
3946
class phpmailerException extends Exception
3947
{
3948
    /**
3949
     * Prettify error message output
3950
     * @return string
3951
     */
3952
    public function errorMessage()
3953
    {
3954
        $errorMsg = '<strong>' . htmlspecialchars($this->getMessage()) . "</strong><br />\n";
3955
3956
        return $errorMsg;
3957
    }
3958
}
3959