PluginProbe
AcyMailing – An Ultimate Newsletter Plugin and Marketing Automation Solution for WordPress / 11.1.1
AcyMailing – An Ultimate Newsletter Plugin and Marketing Automation Solution for WordPress v11.1.1
11.1.1 11.1.0 11.0.5 11.0.4 11.0.3 11.0.2 11.0.1 11.0.0 10.11.1 10.11.0 10.10.2 10.10.1 10.10.0 10.9.1 trunk 10.0.0 10.0.1 10.1.0 10.1.1 10.1.2 10.1.3 10.1.4 10.2.0 10.2.1 10.2.2 All 62 releases
← All changes | back/Libraries/Mailer/Mailer.php +0 -5270 11.0.1 → 11.1.1 View file →
@@ -1,5270 +1,0 @@
1 -<?php
2 -
3 -/**
4 - * Customized version of PHPMailer by Acyba
5 - * PHPMailer - PHP email creation and transport class.
6 - * PHP Version 5.5.
7 - *
8 - * @see https://github.com/PHPMailer/PHPMailer/ The PHPMailer GitHub project
9 - *
10 - * @author Marcus Bointon (Synchro/coolbru) <[email protected]>
11 - * @author Jim Jagielski (jimjag) <[email protected]>
12 - * @author Andy Prevost (codeworxtech) <[email protected]>
13 - * @author Brent R. Matzelle (original founder)
14 - * @copyright 2012 - 2020 Marcus Bointon
15 - * @copyright 2010 - 2012 Jim Jagielski
16 - * @copyright 2004 - 2009 Andy Prevost
17 - * @license https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html GNU Lesser General Public License
18 - * @note This program is distributed in the hope that it will be useful - WITHOUT
19 - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
20 - * FITNESS FOR A PARTICULAR PURPOSE.
21 - */
22 -
23 -namespace AcyMailerPhp;
24 -
25 -acym_cmsLoaded();
26 -
27 -/**
28 - * PHPMailer - PHP email creation and transport class.
29 - *
30 - * @author Marcus Bointon (Synchro/coolbru) <[email protected]>
31 - * @author Jim Jagielski (jimjag) <[email protected]>
32 - * @author Andy Prevost (codeworxtech) <[email protected]>
33 - * @author Brent R. Matzelle (original founder)
34 - */
35 -class Mailer
36 -{
37 - const CHARSET_ASCII = 'us-ascii';
38 - const CHARSET_ISO88591 = 'iso-8859-1';
39 - const CHARSET_UTF8 = 'utf-8';
40 -
41 - const CONTENT_TYPE_PLAINTEXT = 'text/plain';
42 - const CONTENT_TYPE_TEXT_CALENDAR = 'text/calendar';
43 - const CONTENT_TYPE_TEXT_HTML = 'text/html';
44 - const CONTENT_TYPE_MULTIPART_ALTERNATIVE = 'multipart/alternative';
45 - const CONTENT_TYPE_MULTIPART_MIXED = 'multipart/mixed';
46 - const CONTENT_TYPE_MULTIPART_RELATED = 'multipart/related';
47 -
48 - const ENCODING_7BIT = '7bit';
49 - const ENCODING_8BIT = '8bit';
50 - const ENCODING_BASE64 = 'base64';
51 - const ENCODING_BINARY = 'binary';
52 - const ENCODING_QUOTED_PRINTABLE = 'quoted-printable';
53 -
54 - const ENCRYPTION_STARTTLS = 'tls';
55 - const ENCRYPTION_SMTPS = 'ssl';
56 -
57 - const ICAL_METHOD_REQUEST = 'REQUEST';
58 - const ICAL_METHOD_PUBLISH = 'PUBLISH';
59 - const ICAL_METHOD_REPLY = 'REPLY';
60 - const ICAL_METHOD_ADD = 'ADD';
61 - const ICAL_METHOD_CANCEL = 'CANCEL';
62 - const ICAL_METHOD_REFRESH = 'REFRESH';
63 - const ICAL_METHOD_COUNTER = 'COUNTER';
64 - const ICAL_METHOD_DECLINECOUNTER = 'DECLINECOUNTER';
65 -
66 - /**
67 - * Email priority.
68 - * Options: null (default), 1 = High, 3 = Normal, 5 = low.
69 - * When null, the header is not set at all.
70 - *
71 - * @var int|null
72 - */
73 - public $Priority;
74 -
75 - /**
76 - * The character set of the message.
77 - *
78 - * @var string
79 - */
80 - public $CharSet = self::CHARSET_ISO88591;
81 -
82 - /**
83 - * The MIME Content-type of the message.
84 - *
85 - * @var string
86 - */
87 - public $ContentType = self::CONTENT_TYPE_PLAINTEXT;
88 -
89 - /**
90 - * The message encoding.
91 - * Options: "8bit", "7bit", "binary", "base64", and "quoted-printable".
92 - *
93 - * @var string
94 - */
95 - public $Encoding = self::ENCODING_8BIT;
96 -
97 - /**
98 - * Holds the most recent mailer error message.
99 - *
100 - * @var string
101 - */
102 - public $ErrorInfo = '';
103 -
104 - /**
105 - * The From email address for the message.
106 - *
107 - * @var string
108 - */
109 - public $From = '';
110 -
111 - /**
112 - * The From name of the message.
113 - *
114 - * @var string
115 - */
116 - public $FromName = '';
117 -
118 - /**
119 - * The envelope sender of the message.
120 - * This will usually be turned into a Return-Path header by the receiver,
121 - * and is the address that bounces will be sent to.
122 - * If not empty, will be passed via `-f` to sendmail or as the 'MAIL FROM' value over SMTP.
123 - *
124 - * @var string
125 - */
126 - public $Sender = '';
127 -
128 - /**
129 - * The Subject of the message.
130 - *
131 - * @var string
132 - */
133 - public $Subject = '';
134 -
135 - /**
136 - * An HTML or plain text message body.
137 - * If HTML then call isHTML(true).
138 - *
139 - * @var string
140 - */
141 - public $Body = '';
142 -
143 - /**
144 - * The plain-text message body.
145 - * This body can be read by mail clients that do not have HTML email
146 - * capability such as mutt & Eudora.
147 - * Clients that can read HTML will view the normal Body.
148 - *
149 - * @var string
150 - */
151 - public $AltBody = '';
152 -
153 - /**
154 - * An iCal message part body.
155 - * Only supported in simple alt or alt_inline message types
156 - * To generate iCal event structures, use classes like EasyPeasyICS or iCalcreator.
157 - *
158 - * @see https://kigkonsult.se/iCalcreator/
159 - *
160 - * @var string
161 - */
162 - public $Ical = '';
163 -
164 - /**
165 - * Value-array of "method" in Contenttype header "text/calendar"
166 - *
167 - * @var string[]
168 - */
169 - protected static $IcalMethods = [
170 - self::ICAL_METHOD_REQUEST,
171 - self::ICAL_METHOD_PUBLISH,
172 - self::ICAL_METHOD_REPLY,
173 - self::ICAL_METHOD_ADD,
174 - self::ICAL_METHOD_CANCEL,
175 - self::ICAL_METHOD_REFRESH,
176 - self::ICAL_METHOD_COUNTER,
177 - self::ICAL_METHOD_DECLINECOUNTER,
178 - ];
179 -
180 - /**
181 - * The complete compiled MIME message body.
182 - *
183 - * @var string
184 - */
185 - protected $MIMEBody = '';
186 -
187 - /**
188 - * The complete compiled MIME message headers.
189 - *
190 - * @var string
191 - */
192 - protected $MIMEHeader = '';
193 -
194 - /**
195 - * Extra headers that createHeader() doesn't fold in.
196 - *
197 - * @var string
198 - */
199 - protected $mailHeader = '';
200 -
201 - /**
202 - * Word-wrap the message body to this number of chars.
203 - * Set to 0 to not wrap. A useful value here is 78, for RFC2822 section 2.1.1 compliance.
204 - *
205 - * @see static::STD_LINE_LENGTH
206 - *
207 - * @var int
208 - */
209 - public $WordWrap = 0;
210 -
211 - /**
212 - * Which method to use to send mail.
213 - * Options: "mail", "sendmail", or "smtp".
214 - *
215 - * @var string
216 - */
217 - public $Mailer = 'mail';
218 -
219 - /**
220 - * The path to the sendmail program.
221 - *
222 - * @var string
223 - */
224 - public $Sendmail = '/usr/sbin/sendmail';
225 -
226 - /**
227 - * Whether mail() uses a fully sendmail-compatible MTA.
228 - * One which supports sendmail's "-oi -f" options.
229 - *
230 - * @var bool
231 - */
232 - public $UseSendmailOptions = true;
233 -
234 - /**
235 - * The email address that a reading confirmation should be sent to, also known as read receipt.
236 - *
237 - * @var string
238 - */
239 - public $ConfirmReadingTo = '';
240 -
241 - /**
242 - * The hostname to use in the Message-ID header and as default HELO string.
243 - * If empty, PHPMailer attempts to find one with, in order,
244 - * $_SERVER['SERVER_NAME'], gethostname(), php_uname('n'), or the value
245 - * 'localhost.localdomain'.
246 - *
247 - * @see PHPMailer::$Helo
248 - *
249 - * @var string
250 - */
251 - public $Hostname = '';
252 -
253 - /**
254 - * An ID to be used in the Message-ID header.
255 - * If empty, a unique id will be generated.
256 - * You can set your own, but it must be in the format "<id@domain>",
257 - * as defined in RFC5322 section 3.6.4 or it will be ignored.
258 - *
259 - * @see https://tools.ietf.org/html/rfc5322#section-3.6.4
260 - *
261 - * @var string
262 - */
263 - public $MessageID = '';
264 -
265 - /**
266 - * The message Date to be used in the Date header.
267 - * If empty, the current date will be added.
268 - *
269 - * @var string
270 - */
271 - public $MessageDate = '';
272 -
273 - /**
274 - * SMTP hosts.
275 - * Either a single hostname or multiple semicolon-delimited hostnames.
276 - * You can also specify a different port
277 - * for each host by using this format: [hostname:port]
278 - * (e.g. "smtp1.example.com:25;smtp2.example.com").
279 - * You can also specify encryption type, for example:
280 - * (e.g. "tls://smtp1.example.com:587;ssl://smtp2.example.com:465").
281 - * Hosts will be tried in order.
282 - *
283 - * @var string
284 - */
285 - public $Host = 'localhost';
286 -
287 - /**
288 - * The default SMTP server port.
289 - *
290 - * @var int
291 - */
292 - public $Port = 25;
293 -
294 - /**
295 - * The SMTP HELO/EHLO name used for the SMTP connection.
296 - * Default is $Hostname. If $Hostname is empty, PHPMailer attempts to find
297 - * one with the same method described above for $Hostname.
298 - *
299 - * @see PHPMailer::$Hostname
300 - *
301 - * @var string
302 - */
303 - public $Helo = '';
304 -
305 - /**
306 - * What kind of encryption to use on the SMTP connection.
307 - * Options: '', static::ENCRYPTION_STARTTLS, or static::ENCRYPTION_SMTPS.
308 - *
309 - * @var string
310 - */
311 - public $SMTPSecure = '';
312 -
313 - /**
314 - * Whether to enable TLS encryption automatically if a server supports it,
315 - * even if `SMTPSecure` is not set to 'tls'.
316 - * Be aware that in PHP >= 5.6 this requires that the server's certificates are valid.
317 - *
318 - * @var bool
319 - */
320 - public $SMTPAutoTLS = true;
321 -
322 - /**
323 - * Whether to use SMTP authentication.
324 - * Uses the Username and Password properties.
325 - *
326 - * @see PHPMailer::$Username
327 - * @see PHPMailer::$Password
328 - *
329 - * @var bool
330 - */
331 - public $SMTPAuth = false;
332 -
333 - /**
334 - * Options array passed to stream_context_create when connecting via SMTP.
335 - *
336 - * @var array
337 - */
338 - public $SMTPOptions = [];
339 -
340 - /**
341 - * SMTP username.
342 - *
343 - * @var string
344 - */
345 - public $Username = '';
346 -
347 - /**
348 - * SMTP password.
349 - *
350 - * @var string
351 - */
352 - public $Password = '';
353 -
354 - /**
355 - * SMTP authentication type. Options are CRAM-MD5, LOGIN, PLAIN, XOAUTH2.
356 - * If not specified, the first one from that list that the server supports will be selected.
357 - *
358 - * @var string
359 - */
360 - public $AuthType = '';
361 -
362 - /**
363 - * SMTP SMTPXClient command attibutes
364 - *
365 - * @var array
366 - */
367 - protected $SMTPXClient = [];
368 -
369 - /**
370 - * An implementation of the PHPMailer OAuthTokenProvider interface.
371 - *
372 - * @var OAuthTokenProvider
373 - */
374 - protected $oauth;
375 -
376 - /**
377 - * The SMTP server timeout in seconds.
378 - * Default of 5 minutes (300sec) is from RFC2821 section 4.5.3.2.
379 - *
380 - * @var int
381 - */
382 - public $Timeout = 300;
383 -
384 - /**
385 - * Comma separated list of DSN notifications
386 - * 'NEVER' under no circumstances a DSN must be returned to the sender.
387 - * If you use NEVER all other notifications will be ignored.
388 - * 'SUCCESS' will notify you when your mail has arrived at its destination.
389 - * 'FAILURE' will arrive if an error occurred during delivery.
390 - * 'DELAY' will notify you if there is an unusual delay in delivery, but the actual
391 - * delivery's outcome (success or failure) is not yet decided.
392 - *
393 - * @see https://tools.ietf.org/html/rfc3461 See section 4.1 for more information about NOTIFY
394 - */
395 - public $dsn = '';
396 -
397 - /**
398 - * SMTP class debug output mode.
399 - * Debug output level.
400 - * Options:
401 - * @see SMTP::DEBUG_OFF: No output
402 - * @see SMTP::DEBUG_CLIENT: Client messages
403 - * @see SMTP::DEBUG_SERVER: Client and server messages
404 - * @see SMTP::DEBUG_CONNECTION: As SERVER plus connection status
405 - * @see SMTP::DEBUG_LOWLEVEL: Noisy, low-level data output, rarely needed
406 - *
407 - * @see SMTP::$do_debug
408 - *
409 - * @var int
410 - */
411 - public $SMTPDebug = 0;
412 -
413 - /**
414 - * How to handle debug output.
415 - * Options:
416 - * * `echo` Output plain-text as-is, appropriate for CLI
417 - * * `html` Output escaped, line breaks converted to `<br>`, appropriate for browser output
418 - * * `error_log` Output to error log as configured in php.ini
419 - * By default PHPMailer will use `echo` if run from a `cli` or `cli-server` SAPI, `html` otherwise.
420 - * Alternatively, you can provide a callable expecting two params: a message string and the debug level:
421 - *
422 - * ```php
423 - * $mail->Debugoutput = function($str, $level) {echo "debug level $level; message: $str";};
424 - * ```
425 - *
426 - * Alternatively, you can pass in an instance of a PSR-3 compatible logger, though only `debug`
427 - * level output is used:
428 - *
429 - * ```php
430 - * $mail->Debugoutput = new myPsr3Logger;
431 - * ```
432 - *
433 - * @see SMTP::$Debugoutput
434 - *
435 - * @var string|callable|\Psr\Log\LoggerInterface
436 - */
437 - public $Debugoutput = 'echo';
438 -
439 - /**
440 - * Whether to keep the SMTP connection open after each message.
441 - * If this is set to true then the connection will remain open after a send,
442 - * and closing the connection will require an explicit call to smtpClose().
443 - * It's a good idea to use this if you are sending multiple messages as it reduces overhead.
444 - * See the mailing list example for how to use it.
445 - *
446 - * @var bool
447 - */
448 - public $SMTPKeepAlive = false;
449 -
450 - /**
451 - * Whether to split multiple to addresses into multiple messages
452 - * or send them all in one message.
453 - * Only supported in `mail` and `sendmail` transports, not in SMTP.
454 - *
455 - * @var bool
456 - *
457 - * @deprecated 6.0.0 PHPMailer isn't a mailing list manager!
458 - */
459 - public $SingleTo = false;
460 -
461 - /**
462 - * Storage for addresses when SingleTo is enabled.
463 - *
464 - * @var array
465 - */
466 - protected $SingleToArray = [];
467 -
468 - /**
469 - * Whether to generate VERP addresses on send.
470 - * Only applicable when sending via SMTP.
471 - *
472 - * @see https://en.wikipedia.org/wiki/Variable_envelope_return_path
473 - * @see https://www.postfix.org/VERP_README.html Postfix VERP info
474 - *
475 - * @var bool
476 - */
477 - public $do_verp = false;
478 -
479 - /**
480 - * Whether to allow sending messages with an empty body.
481 - *
482 - * @var bool
483 - */
484 - public $AllowEmpty = false;
485 -
486 - /**
487 - * DKIM selector.
488 - *
489 - * @var string
490 - */
491 - public $DKIM_selector = '';
492 -
493 - /**
494 - * DKIM Identity.
495 - * Usually the email address used as the source of the email.
496 - *
497 - * @var string
498 - */
499 - public $DKIM_identity = '';
500 -
501 - /**
502 - * DKIM passphrase.
503 - * Used if your key is encrypted.
504 - *
505 - * @var string
506 - */
507 - public $DKIM_passphrase = '';
508 -
509 - /**
510 - * DKIM signing domain name.
511 - *
512 - * @example 'example.com'
513 - *
514 - * @var string
515 - */
516 - public $DKIM_domain = '';
517 -
518 - /**
519 - * DKIM Copy header field values for diagnostic use.
520 - *
521 - * @var bool
522 - */
523 - public $DKIM_copyHeaderFields = true;
524 -
525 - /**
526 - * DKIM Extra signing headers.
527 - *
528 - * @example ['List-Unsubscribe', 'List-Help']
529 - *
530 - * @var array
531 - */
532 - public $DKIM_extraHeaders = [];
533 -
534 - /**
535 - * DKIM private key file path.
536 - *
537 - * @var string
538 - */
539 - public $DKIM_private = '';
540 -
541 - /**
542 - * DKIM private key string.
543 - *
544 - * If set, takes precedence over `$DKIM_private`.
545 - *
546 - * @var string
547 - */
548 - public $DKIM_private_string = '';
549 -
550 - /**
551 - * Callback Action function name.
552 - *
553 - * The function that handles the result of the send email action.
554 - * It is called out by send() for each email sent.
555 - *
556 - * Value can be any php callable: https://www.php.net/is_callable
557 - *
558 - * Parameters:
559 - * bool $result result of the send action
560 - * array $to email addresses of the recipients
561 - * array $cc cc email addresses
562 - * array $bcc bcc email addresses
563 - * string $subject the subject
564 - * string $body the email body
565 - * string $from email address of sender
566 - * string $extra extra information of possible use
567 - * "smtp_transaction_id' => last smtp transaction id
568 - *
569 - * @var string
570 - */
571 - public $action_function = '';
572 -
573 - /**
574 - * What to put in the X-Mailer header.
575 - * Options: An empty string for PHPMailer default, whitespace/null for none, or a string to use.
576 - *
577 - * @var string|null
578 - */
579 - public $XMailer = '';
580 -
581 - /**
582 - * Which validator to use by default when validating email addresses.
583 - * May be a callable to inject your own validator, but there are several built-in validators.
584 - * The default validator uses PHP's FILTER_VALIDATE_EMAIL filter_var option.
585 - *
586 - * @see PHPMailer::validateAddress()
587 - *
588 - * @var string|callable
589 - */
590 - public static $validator = 'php';
591 -
592 - /**
593 - * An instance of the SMTP sender class.
594 - *
595 - * @var SMTP
596 - */
597 - protected $smtp;
598 -
599 - /**
600 - * The array of 'to' names and addresses.
601 - *
602 - * @var array
603 - */
604 - protected $to = [];
605 -
606 - /**
607 - * The array of 'cc' names and addresses.
608 - *
609 - * @var array
610 - */
611 - protected $cc = [];
612 -
613 - /**
614 - * The array of 'bcc' names and addresses.
615 - *
616 - * @var array
617 - */
618 - protected $bcc = [];
619 -
620 - /**
621 - * The array of reply-to names and addresses.
622 - *
623 - * @var array
624 - */
625 - protected $ReplyTo = [];
626 -
627 - /**
628 - * An array of all kinds of addresses.
629 - * Includes all of $to, $cc, $bcc.
630 - *
631 - * @see PHPMailer::$to
632 - * @see PHPMailer::$cc
633 - * @see PHPMailer::$bcc
634 - *
635 - * @var array
636 - */
637 - protected $all_recipients = [];
638 -
639 - /**
640 - * An array of names and addresses queued for validation.
641 - * In send(), valid and non duplicate entries are moved to $all_recipients
642 - * and one of $to, $cc, or $bcc.
643 - * This array is used only for addresses with IDN.
644 - *
645 - * @see PHPMailer::$to
646 - * @see PHPMailer::$cc
647 - * @see PHPMailer::$bcc
648 - * @see PHPMailer::$all_recipients
649 - *
650 - * @var array
651 - */
652 - protected $RecipientsQueue = [];
653 -
654 - /**
655 - * An array of reply-to names and addresses queued for validation.
656 - * In send(), valid and non duplicate entries are moved to $ReplyTo.
657 - * This array is used only for addresses with IDN.
658 - *
659 - * @see PHPMailer::$ReplyTo
660 - *
661 - * @var array
662 - */
663 - protected $ReplyToQueue = [];
664 -
665 - /**
666 - * The array of attachments.
667 - *
668 - * @var array
669 - */
670 - protected $attachment = [];
671 -
672 - /**
673 - * The array of custom headers.
674 - *
675 - * @var array
676 - */
677 - protected $CustomHeader = [];
678 -
679 - /**
680 - * The most recent Message-ID (including angular brackets).
681 - *
682 - * @var string
683 - */
684 - protected $lastMessageID = '';
685 -
686 - /**
687 - * The message's MIME type.
688 - *
689 - * @var string
690 - */
691 - protected $message_type = '';
692 -
693 - /**
694 - * The array of MIME boundary strings.
695 - *
696 - * @var array
697 - */
698 - protected $boundary = [];
699 -
700 - /**
701 - * The array of available text strings for the current language.
702 - *
703 - * @var array
704 - */
705 - protected $language = [];
706 -
707 - /**
708 - * The number of errors encountered.
709 - *
710 - * @var int
711 - */
712 - protected $error_count = 0;
713 -
714 - /**
715 - * The S/MIME certificate file path.
716 - *
717 - * @var string
718 - */
719 - protected $sign_cert_file = '';
720 -
721 - /**
722 - * The S/MIME key file path.
723 - *
724 - * @var string
725 - */
726 - protected $sign_key_file = '';
727 -
728 - /**
729 - * The optional S/MIME extra certificates ("CA Chain") file path.
730 - *
731 - * @var string
732 - */
733 - protected $sign_extracerts_file = '';
734 -
735 - /**
736 - * The S/MIME password for the key.
737 - * Used only if the key is encrypted.
738 - *
739 - * @var string
740 - */
741 - protected $sign_key_pass = '';
742 -
743 - /**
744 - * Whether to throw exceptions for errors.
745 - *
746 - * @var bool
747 - */
748 - protected $exceptions = false;
749 -
750 - /**
751 - * Unique ID used for message ID and boundaries.
752 - *
753 - * @var string
754 - */
755 - protected $uniqueid = '';
756 -
757 - /**
758 - * The PHPMailer Version number.
759 - *
760 - * @var string
761 - */
762 - const VERSION = '6.9.1';
763 -
764 - /**
765 - * Error severity: message only, continue processing.
766 - *
767 - * @var int
768 - */
769 - const STOP_MESSAGE = 0;
770 -
771 - /**
772 - * Error severity: message, likely ok to continue processing.
773 - *
774 - * @var int
775 - */
776 - const STOP_CONTINUE = 1;
777 -
778 - /**
779 - * Error severity: message, plus full stop, critical error reached.
780 - *
781 - * @var int
782 - */
783 - const STOP_CRITICAL = 2;
784 -
785 - /**
786 - * The SMTP standard CRLF line break.
787 - * If you want to change line break format, change static::$LE, not this.
788 - */
789 - const CRLF = "\r\n";
790 -
791 - /**
792 - * "Folding White Space" a white space string used for line folding.
793 - */
794 - const FWS = ' ';
795 -
796 - /**
797 - * SMTP RFC standard line ending; Carriage Return, Line Feed.
798 - *
799 - * @var string
800 - */
801 - protected static $LE = self::CRLF;
802 -
803 - /**
804 - * The maximum line length supported by mail().
805 - *
806 - * Background: mail() will sometimes corrupt messages
807 - * with headers longer than 65 chars, see #818.
808 - *
809 - * @var int
810 - */
811 - const MAIL_MAX_LINE_LENGTH = 63;
812 -
813 - /**
814 - * The maximum line length allowed by RFC 2822 section 2.1.1.
815 - *
816 - * @var int
817 - */
818 - const MAX_LINE_LENGTH = 998;
819 -
820 - /**
821 - * The lower maximum line length allowed by RFC 2822 section 2.1.1.
822 - * This length does NOT include the line break
823 - * 76 means that lines will be 77 or 78 chars depending on whether
824 - * the line break format is LF or CRLF; both are valid.
825 - *
826 - * @var int
827 - */
828 - const STD_LINE_LENGTH = 76;
829 -
830 - /**
831 - * Constructor.
832 - *
833 - * @param bool $exceptions Should we throw external exceptions?
834 - */
835 - public function __construct($exceptions = null)
836 - {
837 - if (null !== $exceptions) {
838 - $this->exceptions = (bool)$exceptions;
839 - }
840 - //Pick an appropriate debug output format automatically
841 - $this->Debugoutput = (strpos(PHP_SAPI, 'cli') !== false ? 'echo' : 'html');
842 - }
843 -
844 - /**
845 - * Destructor.
846 - */
847 - public function __destruct()
848 - {
849 - //Close any open SMTP connection nicely
850 - $this->smtpClose();
851 - }
852 -
853 - /**
854 - * Call mail() in a safe_mode-aware fashion.
855 - * Also, unless sendmail_path points to sendmail (or something that
856 - * claims to be sendmail), don't pass params (not a perfect fix,
857 - * but it will do).
858 - *
859 - * @param string $to To
860 - * @param string $subject Subject
861 - * @param string $body Message Body
862 - * @param string $header Additional Header(s)
863 - * @param string|null $params Params
864 - *
865 - * @return bool
866 - */
867 - private function mailPassthru($to, $subject, $body, $header, $params)
868 - {
869 - //Check overloading of mail function to avoid double-encoding
870 - if ((int)ini_get('mbstring.func_overload') & 1) {
871 - $subject = $this->secureHeader($subject);
872 - } else {
873 - $subject = $this->encodeHeader($this->secureHeader($subject));
874 - }
875 - //Calling mail() with null params breaks
876 - $this->edebug('Sending with mail()');
877 - $this->edebug('Sendmail path: '.ini_get('sendmail_path'));
878 - $this->edebug("Envelope sender: {$this->Sender}");
879 - $this->edebug("To: {$to}");
880 - $this->edebug("Subject: {$subject}");
881 - $this->edebug("Headers: {$header}");
882 - if (!$this->UseSendmailOptions || null === $params) {
883 - $result = @mail($to, $subject, $body, $header);
884 - } else {
885 - $this->edebug("Additional params: {$params}");
886 - $result = @mail($to, $subject, $body, $header, $params);
887 - }
888 - $this->edebug('Result: '.($result ? 'true' : 'false'));
889 -
890 - return $result;
891 - }
892 -
893 - /**
894 - * Output debugging info via a user-defined method.
895 - * Only generates output if debug output is enabled.
896 - *
897 - * @param string $str
898 - *
899 - * @see PHPMailer::$SMTPDebug
900 - *
901 - * @see PHPMailer::$Debugoutput
902 - */
903 - protected function edebug($str)
904 - {
905 - if ($this->SMTPDebug <= 0) {
906 - return;
907 - }
908 - //Is this a PSR-3 logger?
909 - if ($this->Debugoutput instanceof \Psr\Log\LoggerInterface) {
910 - $this->Debugoutput->debug(rtrim($str, "\r\n"));
911 -
912 - return;
913 - }
914 - //Avoid clash with built-in function names
915 - if (is_callable($this->Debugoutput) && !in_array($this->Debugoutput, ['error_log', 'html', 'echo'])) {
916 - call_user_func($this->Debugoutput, $str, $this->SMTPDebug);
917 -
918 - return;
919 - }
920 - switch ($this->Debugoutput) {
921 - case 'error_log':
922 - //Don't output, just log
923 - /** @noinspection ForgottenDebugOutputInspection */
924 - error_log($str);
925 - break;
926 - case 'html':
927 - //Cleans up output a bit for a better looking, HTML-safe output
928 - echo htmlentities(
929 - preg_replace('/[\r\n]+/', '', $str),
930 - ENT_QUOTES,
931 - 'UTF-8'
932 - ), "<br>\n";
933 - break;
934 - case 'echo':
935 - default:
936 - //Normalize line breaks
937 - $str = preg_replace('/\r\n|\r/m', "\n", $str);
938 - echo gmdate('Y-m-d H:i:s'),
939 - "\t",
940 - //Trim trailing space
941 - trim(
942 - //Indent for readability, except for trailing break
943 - str_replace(
944 - "\n",
945 - "\n \t ",
946 - trim($str)
947 - )
948 - ),
949 - "\n";
950 - }
951 - }
952 -
953 - /**
954 - * Sets message type to HTML or plain.
955 - *
956 - * @param bool $isHtml True for HTML mode
957 - */
958 - public function isHTML($isHtml = true)
959 - {
960 - if ($isHtml) {
961 - $this->ContentType = static::CONTENT_TYPE_TEXT_HTML;
962 - } else {
963 - $this->ContentType = static::CONTENT_TYPE_PLAINTEXT;
964 - }
965 - }
966 -
967 - /**
968 - * Send messages using SMTP.
969 - */
970 - public function isSMTP()
971 - {
972 - $this->Mailer = 'smtp';
973 - }
974 -
975 - /**
976 - * Send messages using PHP's mail() function.
977 - */
978 - public function isMail()
979 - {
980 - $this->Mailer = 'mail';
981 - }
982 -
983 - /**
984 - * Send messages using $Sendmail.
985 - */
986 - public function isSendmail()
987 - {
988 - $ini_sendmail_path = ini_get('sendmail_path');
989 -
990 - if (false === stripos($ini_sendmail_path, 'sendmail')) {
991 - $this->Sendmail = '/usr/sbin/sendmail';
992 - } else {
993 - $this->Sendmail = $ini_sendmail_path;
994 - }
995 - $this->Mailer = 'sendmail';
996 - }
997 -
998 - /**
999 - * Send messages using qmail.
1000 - */
1001 - public function isQmail()
1002 - {
1003 - $ini_sendmail_path = ini_get('sendmail_path');
1004 -
1005 - if (false === stripos($ini_sendmail_path, 'qmail')) {
1006 - $this->Sendmail = '/var/qmail/bin/qmail-inject';
1007 - } else {
1008 - $this->Sendmail = $ini_sendmail_path;
1009 - }
1010 - $this->Mailer = 'qmail';
1011 - }
1012 -
1013 - /**
1014 - * Add a "To" address.
1015 - *
1016 - * @param string $address The email address to send to
1017 - * @param string $name
1018 - *
1019 - * @return bool true on success, false if address already used or invalid in some way
1020 - * @throws Exception
1021 - *
1022 - */
1023 - public function addAddress($address, $name = '')
1024 - {
1025 - return $this->addOrEnqueueAnAddress('to', $address, $name);
1026 - }
1027 -
1028 - /**
1029 - * Add a "CC" address.
1030 - *
1031 - * @param string $address The email address to send to
1032 - * @param string $name
1033 - *
1034 - * @return bool true on success, false if address already used or invalid in some way
1035 - * @throws Exception
1036 - *
1037 - */
1038 - public function addCC($address, $name = '')
1039 - {
1040 - return $this->addOrEnqueueAnAddress('cc', $address, $name);
1041 - }
1042 -
1043 - /**
1044 - * Add a "BCC" address.
1045 - *
1046 - * @param string $address The email address to send to
1047 - * @param string $name
1048 - *
1049 - * @return bool true on success, false if address already used or invalid in some way
1050 - * @throws Exception
1051 - *
1052 - */
1053 - public function addBCC($address, $name = '')
1054 - {
1055 - return $this->addOrEnqueueAnAddress('bcc', $address, $name);
1056 - }
1057 -
1058 - /**
1059 - * Add a "Reply-To" address.
1060 - *
1061 - * @param string $address The email address to reply to
1062 - * @param string $name
1063 - *
1064 - * @return bool true on success, false if address already used or invalid in some way
1065 - * @throws Exception
1066 - *
1067 - */
1068 - public function addReplyTo($address, $name = '')
1069 - {
1070 - return $this->addOrEnqueueAnAddress('Reply-To', $address, $name);
1071 - }
1072 -
1073 - /**
1074 - * Add an address to one of the recipient arrays or to the ReplyTo array. Because PHPMailer
1075 - * can't validate addresses with an IDN without knowing the PHPMailer::$CharSet (that can still
1076 - * be modified after calling this function), addition of such addresses is delayed until send().
1077 - * Addresses that have been added already return false, but do not throw exceptions.
1078 - *
1079 - * @param string $kind One of 'to', 'cc', 'bcc', or 'ReplyTo'
1080 - * @param string $address The email address
1081 - * @param string $name An optional username associated with the address
1082 - *
1083 - * @return bool true on success, false if address already used or invalid in some way
1084 - * @throws Exception
1085 - *
1086 - */
1087 - protected function addOrEnqueueAnAddress($kind, $address, $name)
1088 - {
1089 - $pos = false;
1090 - if ($address !== null) {
1091 - $address = trim($address);
1092 - $pos = strrpos($address, '@');
1093 - }
1094 - if (false === $pos) {
1095 - //At-sign is missing.
1096 - $error_message = sprintf(
1097 - '%s (%s): %s',
1098 - $this->lang('invalid_address'),
1099 - $kind,
1100 - $address
1101 - );
1102 - $this->setError($error_message);
1103 - $this->edebug($error_message);
1104 - if ($this->exceptions) {
1105 - throw new Exception($error_message);
1106 - }
1107 -
1108 - return false;
1109 - }
1110 - if ($name !== null && is_string($name)) {
1111 - $name = trim(preg_replace('/[\r\n]+/', '', $name)); //Strip breaks and trim
1112 - } else {
1113 - $name = '';
1114 - }
1115 - $params = [$kind, $address, $name];
1116 - //Enqueue addresses with IDN until we know the PHPMailer::$CharSet.
1117 - //Domain is assumed to be whatever is after the last @ symbol in the address
1118 - if (static::idnSupported() && $this->has8bitChars(substr($address, ++$pos))) {
1119 - if ('Reply-To' !== $kind) {
1120 - if (!array_key_exists($address, $this->RecipientsQueue)) {
1121 - $this->RecipientsQueue[$address] = $params;
1122 -
1123 - return true;
1124 - }
1125 - } elseif (!array_key_exists($address, $this->ReplyToQueue)) {
1126 - $this->ReplyToQueue[$address] = $params;
1127 -
1128 - return true;
1129 - }
1130 -
1131 - return false;
1132 - }
1133 -
1134 - //Immediately add standard addresses without IDN.
1135 - return call_user_func_array([$this, 'addAnAddress'], $params);
1136 - }
1137 -
1138 - /**
1139 - * Set the boundaries to use for delimiting MIME parts.
1140 - * If you override this, ensure you set all 3 boundaries to unique values.
1141 - * The default boundaries include a "=_" sequence which cannot occur in quoted-printable bodies,
1142 - * as suggested by https://www.rfc-editor.org/rfc/rfc2045#section-6.7
1143 - *
1144 - * @return void
1145 - */
1146 - public function setBoundaries()
1147 - {
1148 - $this->uniqueid = $this->generateId();
1149 - $this->boundary[1] = 'b1=_'.$this->uniqueid;
1150 - $this->boundary[2] = 'b2=_'.$this->uniqueid;
1151 - $this->boundary[3] = 'b3=_'.$this->uniqueid;
1152 - }
1153 -
1154 - /**
1155 - * Add an address to one of the recipient arrays or to the ReplyTo array.
1156 - * Addresses that have been added already return false, but do not throw exceptions.
1157 - *
1158 - * @param string $kind One of 'to', 'cc', 'bcc', or 'ReplyTo'
1159 - * @param string $address The email address to send, resp. to reply to
1160 - * @param string $name
1161 - *
1162 - * @return bool true on success, false if address already used or invalid in some way
1163 - * @throws Exception
1164 - *
1165 - */
1166 - protected function addAnAddress($kind, $address, $name = '')
1167 - {
1168 - if (!in_array($kind, ['to', 'cc', 'bcc', 'Reply-To'])) {
1169 - $error_message = sprintf(
1170 - '%s: %s',
1171 - $this->lang('Invalid recipient kind'),
1172 - $kind
1173 - );
1174 - $this->setError($error_message);
1175 - $this->edebug($error_message);
1176 - if ($this->exceptions) {
1177 - throw new Exception($error_message);
1178 - }
1179 -
1180 - return false;
1181 - }
1182 - if (!static::validateAddress($address)) {
1183 - $error_message = sprintf(
1184 - '%s (%s): %s',
1185 - $this->lang('invalid_address'),
1186 - $kind,
1187 - $address
1188 - );
1189 - $this->setError($error_message);
1190 - $this->edebug($error_message);
1191 - if ($this->exceptions) {
1192 - throw new Exception($error_message);
1193 - }
1194 -
1195 - return false;
1196 - }
1197 - if ('Reply-To' !== $kind) {
1198 - if (!array_key_exists(strtolower($address), $this->all_recipients)) {
1199 - $this->{$kind}[] = [$address, $name];
1200 - $this->all_recipients[strtolower($address)] = true;
1201 -
1202 - return true;
1203 - }
1204 - } elseif (!array_key_exists(strtolower($address), $this->ReplyTo)) {
1205 - $this->ReplyTo[strtolower($address)] = [$address, $name];
1206 -
1207 - return true;
1208 - }
1209 -
1210 - return false;
1211 - }
1212 -
1213 - /**
1214 - * Parse and validate a string containing one or more RFC822-style comma-separated email addresses
1215 - * of the form "display name <address>" into an array of name/address pairs.
1216 - * Uses the imap_rfc822_parse_adrlist function if the IMAP extension is available.
1217 - * Note that quotes in the name part are removed.
1218 - *
1219 - * @see https://www.andrew.cmu.edu/user/agreen1/testing/mrbs/web/Mail/RFC822.php A more careful implementation
1220 - *
1221 - * @param string $addrstr The address list string
1222 - * @param bool $useimap Whether to use the IMAP extension to parse the list
1223 - * @param string $charset The charset to use when decoding the address list string.
1224 - *
1225 - * @return array
1226 - */
1227 - public static function parseAddresses($addrstr, $useimap = true, $charset = self::CHARSET_ISO88591)
1228 - {
1229 - $addresses = [];
1230 - if ($useimap && function_exists('imap_rfc822_parse_adrlist')) {
1231 - //Use this built-in parser if it's available
1232 - $list = imap_rfc822_parse_adrlist($addrstr, '');
1233 - // Clear any potential IMAP errors to get rid of notices being thrown at end of script.
1234 - imap_errors();
1235 - foreach ($list as $address) {
1236 - if (
1237 - '.SYNTAX-ERROR.' !== $address->host &&
1238 - static::validateAddress($address->mailbox.'@'.$address->host)
1239 - ) {
1240 - //Decode the name part if it's present and encoded
1241 - if (
1242 - property_exists($address, 'personal') &&
1243 - //Check for a Mbstring constant rather than using extension_loaded, which is sometimes disabled
1244 - defined('MB_CASE_UPPER') &&
1245 - preg_match('/^=\?.*\?=$/s', $address->personal)
1246 - ) {
1247 - $origCharset = mb_internal_encoding();
1248 - mb_internal_encoding($charset);
1249 - //Undo any RFC2047-encoded spaces-as-underscores
1250 - $address->personal = str_replace('_', '=20', $address->personal);
1251 - //Decode the name
1252 - $address->personal = mb_decode_mimeheader($address->personal);
1253 - mb_internal_encoding($origCharset);
1254 - }
1255 -
1256 - $addresses[] = [
1257 - 'name' => (property_exists($address, 'personal') ? $address->personal : ''),
1258 - 'address' => $address->mailbox.'@'.$address->host,
1259 - ];
1260 - }
1261 - }
1262 - } else {
1263 - //Use this simpler parser
1264 - $list = explode(',', $addrstr);
1265 - foreach ($list as $address) {
1266 - $address = trim($address);
1267 - //Is there a separate name part?
1268 - if (strpos($address, '<') === false) {
1269 - //No separate name, just use the whole thing
1270 - if (static::validateAddress($address)) {
1271 - $addresses[] = [
1272 - 'name' => '',
1273 - 'address' => $address,
1274 - ];
1275 - }
1276 - } else {
1277 - [$name, $email] = explode('<', $address);
1278 - $email = trim(str_replace('>', '', $email));
1279 - $name = trim($name);
1280 - if (static::validateAddress($email)) {
1281 - //Check for a Mbstring constant rather than using extension_loaded, which is sometimes disabled
1282 - //If this name is encoded, decode it
1283 - if (defined('MB_CASE_UPPER') && preg_match('/^=\?.*\?=$/s', $name)) {
1284 - $origCharset = mb_internal_encoding();
1285 - mb_internal_encoding($charset);
1286 - //Undo any RFC2047-encoded spaces-as-underscores
1287 - $name = str_replace('_', '=20', $name);
1288 - //Decode the name
1289 - $name = mb_decode_mimeheader($name);
1290 - mb_internal_encoding($origCharset);
1291 - }
1292 - $addresses[] = [
1293 - //Remove any surrounding quotes and spaces from the name
1294 - 'name' => trim($name, '\'" '),
1295 - 'address' => $email,
1296 - ];
1297 - }
1298 - }
1299 - }
1300 - }
1301 -
1302 - return $addresses;
1303 - }
1304 -
1305 - /**
1306 - * Set the From and FromName properties.
1307 - *
1308 - * @param string $address
1309 - * @param string $name
1310 - * @param bool $auto Whether to also set the Sender address, defaults to true
1311 - *
1312 - * @return bool
1313 - * @throws Exception
1314 - *
1315 - */
1316 - public function setFrom($address, $name = '', $auto = true)
1317 - {
1318 - $address = trim((string)$address);
1319 - $name = trim(preg_replace('/[\r\n]+/', '', $name)); //Strip breaks and trim
1320 - //Don't validate now addresses with IDN. Will be done in send().
1321 - $pos = strrpos($address, '@');
1322 - if (
1323 - (false === $pos)
1324 - || ((!$this->has8bitChars(substr($address, ++$pos)) || !static::idnSupported())
1325 - && !static::validateAddress($address))
1326 - ) {
1327 - $error_message = sprintf(
1328 - '%s (From): %s',
1329 - $this->lang('invalid_address'),
1330 - $address
1331 - );
1332 - $this->setError($error_message);
1333 - $this->edebug($error_message);
1334 - if ($this->exceptions) {
1335 - throw new Exception($error_message);
1336 - }
1337 -
1338 - return false;
1339 - }
1340 - $this->From = $address;
1341 - $this->FromName = $name;
1342 - if ($auto && empty($this->Sender)) {
1343 - $this->Sender = $address;
1344 - }
1345 -
1346 - return true;
1347 - }
1348 -
1349 - /**
1350 - * Return the Message-ID header of the last email.
1351 - * Technically this is the value from the last time the headers were created,
1352 - * but it's also the message ID of the last sent message except in
1353 - * pathological cases.
1354 - *
1355 - * @return string
1356 - */
1357 - public function getLastMessageID()
1358 - {
1359 - return $this->lastMessageID;
1360 - }
1361 -
1362 - /**
1363 - * Check that a string looks like an email address.
1364 - * Validation patterns supported:
1365 - * * `auto` Pick best pattern automatically;
1366 - * * `pcre8` Use the squiloople.com pattern, requires PCRE > 8.0;
1367 - * * `pcre` Use old PCRE implementation;
1368 - * * `php` Use PHP built-in FILTER_VALIDATE_EMAIL;
1369 - * * `html5` Use the pattern given by the HTML5 spec for 'email' type form input elements.
1370 - * * `noregex` Don't use a regex: super fast, really dumb.
1371 - * Alternatively you may pass in a callable to inject your own validator, for example:
1372 - *
1373 - * ```php
1374 - * PHPMailer::validateAddress('[email protected]', function($address) {
1375 - * return (strpos($address, '@') !== false);
1376 - * });
1377 - * ```
1378 - *
1379 - * You can also set the PHPMailer::$validator static to a callable, allowing built-in methods to use your validator.
1380 - *
1381 - * @param string $address The email address to check
1382 - * @param string|callable $patternselect Which pattern to use
1383 - *
1384 - * @return bool
1385 - */
1386 - public static function validateAddress($address, $patternselect = null)
1387 - {
1388 - if (null === $patternselect) {
1389 - $patternselect = static::$validator;
1390 - }
1391 - //Don't allow strings as callables, see SECURITY.md and CVE-2021-3603
1392 - if (is_callable($patternselect) && !is_string($patternselect)) {
1393 - return call_user_func($patternselect, $address);
1394 - }
1395 - //Reject line breaks in addresses; it's valid RFC5322, but not RFC5321
1396 - if (strpos($address, "\n") !== false || strpos($address, "\r") !== false) {
1397 - return false;
1398 - }
1399 - switch ($patternselect) {
1400 - case 'pcre': //Kept for BC
1401 - case 'pcre8':
1402 - /*
1403 - * A more complex and more permissive version of the RFC5322 regex on which FILTER_VALIDATE_EMAIL
1404 - * is based.
1405 - * In addition to the addresses allowed by filter_var, also permits:
1406 - * * dotless domains: `a@b`
1407 - * * comments: `1234 @ local(blah) .machine .example`
1408 - * * quoted elements: `'"test blah"@example.org'`
1409 - * * numeric TLDs: `[email protected]`
1410 - * * unbracketed IPv4 literals: `[email protected]`
1411 - * * IPv6 literals: 'first.last@[IPv6:a1::]'
1412 - * Not all of these will necessarily work for sending!
1413 - *
1414 - * @copyright 2009-2010 Michael Rushton
1415 - * Feel free to use and redistribute this code. But please keep this copyright notice.
1416 - */
1417 - return (bool)preg_match(
1418 - '/^(?!(?>(?1)"?(?>\\\[ -~]|[^"])"?(?1)){255,})(?!(?>(?1)"?(?>\\\[ -~]|[^"])"?(?1)){65,}@)'.
1419 - '((?>(?>(?>((?>(?>(?>\x0D\x0A)?[\t ])+|(?>[\t ]*\x0D\x0A)?[\t ]+)?)(\((?>(?2)'.
1420 - '(?>[\x01-\x08\x0B\x0C\x0E-\'*-\[\]-\x7F]|\\\[\x00-\x7F]|(?3)))*(?2)\)))+(?2))|(?2))?)'.
1421 - '([!#-\'*+\/-9=?^-~-]+|"(?>(?2)(?>[\x01-\x08\x0B\x0C\x0E-!#-\[\]-\x7F]|\\\[\x00-\x7F]))*'.
1422 - '(?2)")(?>(?1)\.(?1)(?4))*(?1)@(?!(?1)[a-z0-9-]{64,})(?1)(?>([a-z0-9](?>[a-z0-9-]*[a-z0-9])?)'.
1423 - '(?>(?1)\.(?!(?1)[a-z0-9-]{64,})(?1)(?5)){0,126}|\[(?:(?>IPv6:(?>([a-f0-9]{1,4})(?>:(?6)){7}'.
1424 - '|(?!(?:.*[a-f0-9][:\]]){8,})((?6)(?>:(?6)){0,6})?::(?7)?))|(?>(?>IPv6:(?>(?6)(?>:(?6)){5}:'.
1425 - '|(?!(?:.*[a-f0-9]:){6,})(?8)?::(?>((?6)(?>:(?6)){0,4}):)?))?(25[0-5]|2[0-4][0-9]|1[0-9]{2}'.
1426 - '|[1-9]?[0-9])(?>\.(?9)){3}))\])(?1)$/isD',
1427 - $address
1428 - );
1429 - case 'html5':
1430 - /*
1431 - * This is the pattern used in the HTML5 spec for validation of 'email' type form input elements.
1432 - *
1433 - * @see https://html.spec.whatwg.org/#e-mail-state-(type=email)
1434 - */
1435 - return (bool)preg_match(
1436 - '/^[a-zA-Z0-9.!#$%&\'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}'.
1437 - '[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/sD',
1438 - $address
1439 - );
1440 - case 'php':
1441 - default:
1442 - return filter_var($address, FILTER_VALIDATE_EMAIL) !== false;
1443 - }
1444 - }
1445 -
1446 - /**
1447 - * Tells whether IDNs (Internationalized Domain Names) are supported or not. This requires the
1448 - * `intl` and `mbstring` PHP extensions.
1449 - *
1450 - * @return bool `true` if required functions for IDN support are present
1451 - */
1452 - public static function idnSupported()
1453 - {
1454 - return function_exists('idn_to_ascii') && function_exists('mb_convert_encoding');
1455 - }
1456 -
1457 - /**
1458 - * Converts IDN in given email address to its ASCII form, also known as punycode, if possible.
1459 - * Important: Address must be passed in same encoding as currently set in PHPMailer::$CharSet.
1460 - * This function silently returns unmodified address if:
1461 - * - No conversion is necessary (i.e. domain name is not an IDN, or is already in ASCII form)
1462 - * - Conversion to punycode is impossible (e.g. required PHP functions are not available)
1463 - * or fails for any reason (e.g. domain contains characters not allowed in an IDN).
1464 - *
1465 - * @param string $address The email address to convert
1466 - *
1467 - * @return string The encoded address in ASCII form
1468 - * @see PHPMailer::$CharSet
1469 - *
1470 - */
1471 - public function punyencodeAddress($address)
1472 - {
1473 - //Verify we have required functions, CharSet, and at-sign.
1474 - $pos = strrpos($address, '@');
1475 - if (
1476 - !empty($this->CharSet) &&
1477 - false !== $pos &&
1478 - static::idnSupported()
1479 - ) {
1480 - $domain = substr($address, ++$pos);
1481 - //Verify CharSet string is a valid one, and domain properly encoded in this CharSet.
1482 - if ($this->has8bitChars($domain) && @mb_check_encoding($domain, $this->CharSet)) {
1483 - //Convert the domain from whatever charset it's in to UTF-8
1484 - $domain = mb_convert_encoding($domain, self::CHARSET_UTF8, $this->CharSet);
1485 - //Ignore IDE complaints about this line - method signature changed in PHP 5.4
1486 - $errorcode = 0;
1487 - if (defined('INTL_IDNA_VARIANT_UTS46')) {
1488 - //Use the current punycode standard (appeared in PHP 7.2)
1489 - $punycode = idn_to_ascii(
1490 - $domain,
1491 - \IDNA_DEFAULT | \IDNA_USE_STD3_RULES | \IDNA_CHECK_BIDI |
1492 - \IDNA_CHECK_CONTEXTJ | \IDNA_NONTRANSITIONAL_TO_ASCII,
1493 - \INTL_IDNA_VARIANT_UTS46
1494 - );
1495 - } elseif (defined('INTL_IDNA_VARIANT_2003')) {
1496 - //Fall back to this old, deprecated/removed encoding
1497 - $punycode = idn_to_ascii($domain, $errorcode, \INTL_IDNA_VARIANT_2003);
1498 - } else {
1499 - //Fall back to a default we don't know about
1500 - $punycode = idn_to_ascii($domain, $errorcode);
1501 - }
1502 - if (false !== $punycode) {
1503 - return substr($address, 0, $pos).$punycode;
1504 - }
1505 - }
1506 - }
1507 -
1508 - return $address;
1509 - }
1510 -
1511 - /**
1512 - * Create a message and send it.
1513 - * Uses the sending method specified by $Mailer.
1514 - *
1515 - * @return bool false on error - See the ErrorInfo property for details of the error
1516 - * @throws Exception
1517 - *
1518 - */
1519 - public function send()
1520 - {
1521 - try {
1522 - if (!$this->preSend()) {
1523 - return false;
1524 - }
1525 -
1526 - return $this->postSend();
1527 - } catch (Exception $exc) {
1528 - $this->mailHeader = '';
1529 - $this->setError($exc->getMessage());
1530 - if ($this->exceptions) {
1531 - throw $exc;
1532 - }
1533 -
1534 - return false;
1535 - }
1536 - }
1537 -
1538 - /**
1539 - * Prepare a message for sending.
1540 - *
1541 - * @return bool
1542 - * @throws Exception
1543 - *
1544 - */
1545 - public function preSend()
1546 - {
1547 - if (
1548 - 'smtp' === $this->Mailer
1549 - || ('mail' === $this->Mailer && (\PHP_VERSION_ID >= 80000 || stripos(PHP_OS, 'WIN') === 0))
1550 - ) {
1551 - //SMTP mandates RFC-compliant line endings
1552 - //and it's also used with mail() on Windows
1553 - static::setLE(self::CRLF);
1554 - } else {
1555 - //Maintain backward compatibility with legacy Linux command line mailers
1556 - static::setLE(PHP_EOL);
1557 - }
1558 - //Check for buggy PHP versions that add a header with an incorrect line break
1559 - if (
1560 - 'mail' === $this->Mailer
1561 - && ((\PHP_VERSION_ID >= 70000 && \PHP_VERSION_ID < 70017)
1562 - || (\PHP_VERSION_ID >= 70100 && \PHP_VERSION_ID < 70103))
1563 - && ini_get('mail.add_x_header') === '1'
1564 - && stripos(PHP_OS, 'WIN') === 0
1565 - ) {
1566 - trigger_error($this->lang('buggy_php'), E_USER_WARNING);
1567 - }
1568 -
1569 - try {
1570 - $this->error_count = 0; //Reset errors
1571 - $this->mailHeader = '';
1572 -
1573 - //Dequeue recipient and Reply-To addresses with IDN
1574 - foreach (array_merge($this->RecipientsQueue, $this->ReplyToQueue) as $params) {
1575 - $params[1] = $this->punyencodeAddress($params[1]);
1576 - call_user_func_array([$this, 'addAnAddress'], $params);
1577 - }
1578 - if (count($this->to) + count($this->cc) + count($this->bcc) < 1) {
1579 - throw new Exception($this->lang('provide_address'), self::STOP_CRITICAL);
1580 - }
1581 -
1582 - //Validate From, Sender, and ConfirmReadingTo addresses
1583 - foreach (['From', 'Sender', 'ConfirmReadingTo'] as $address_kind) {
1584 - if ($this->{$address_kind} === null) {
1585 - $this->{$address_kind} = '';
1586 - continue;
1587 - }
1588 - $this->{$address_kind} = trim($this->{$address_kind});
1589 - if (empty($this->{$address_kind})) {
1590 - continue;
1591 - }
1592 - $this->{$address_kind} = $this->punyencodeAddress($this->{$address_kind});
1593 - if (!static::validateAddress($this->{$address_kind})) {
1594 - $error_message = sprintf(
1595 - '%s (%s): %s',
1596 - $this->lang('invalid_address'),
1597 - $address_kind,
1598 - $this->{$address_kind}
1599 - );
1600 - $this->setError($error_message);
1601 - $this->edebug($error_message);
1602 - if ($this->exceptions) {
1603 - throw new Exception($error_message);
1604 - }
1605 -
1606 - return false;
1607 - }
1608 - }
1609 -
1610 - //Set whether the message is multipart/alternative
1611 - if ($this->alternativeExists()) {
1612 - $this->ContentType = static::CONTENT_TYPE_MULTIPART_ALTERNATIVE;
1613 - }
1614 -
1615 - $this->setMessageType();
1616 - //Refuse to send an empty message unless we are specifically allowing it
1617 - if (!$this->AllowEmpty && empty($this->Body)) {
1618 - throw new Exception($this->lang('empty_message'), self::STOP_CRITICAL);
1619 - }
1620 -
1621 - //Trim subject consistently
1622 - $this->Subject = trim($this->Subject);
1623 - //Create body before headers in case body makes changes to headers (e.g. altering transfer encoding)
1624 - $this->MIMEHeader = '';
1625 - $this->MIMEBody = $this->createBody();
1626 - //createBody may have added some headers, so retain them
1627 - $tempheaders = $this->MIMEHeader;
1628 - $this->MIMEHeader = $this->createHeader();
1629 - $this->MIMEHeader .= $tempheaders;
1630 -
1631 - //To capture the complete message when using mail(), create
1632 - //an extra header list which createHeader() doesn't fold in
1633 - if ('mail' === $this->Mailer) {
1634 - if (count($this->to) > 0) {
1635 - $this->mailHeader .= $this->addrAppend('To', $this->to);
1636 - } else {
1637 - $this->mailHeader .= $this->headerLine('To', 'undisclosed-recipients:;');
1638 - }
1639 - $this->mailHeader .= $this->headerLine(
1640 - 'Subject',
1641 - $this->encodeHeader($this->secureHeader($this->Subject))
1642 - );
1643 - }
1644 -
1645 - //Sign with DKIM if enabled
1646 - if (
1647 - !empty($this->DKIM_domain)
1648 - && !empty($this->DKIM_selector)
1649 - && (!empty($this->DKIM_private_string)
1650 - || (!empty($this->DKIM_private)
1651 - && static::isPermittedPath($this->DKIM_private)
1652 - && file_exists($this->DKIM_private)
1653 - )
1654 - )
1655 - ) {
1656 - $header_dkim = $this->DKIM_Add(
1657 - $this->MIMEHeader.$this->mailHeader,
1658 - $this->encodeHeader($this->secureHeader($this->Subject)),
1659 - $this->MIMEBody
1660 - );
1661 - $this->MIMEHeader = static::stripTrailingWSP($this->MIMEHeader).static::$LE.
1662 - static::normalizeBreaks($header_dkim).static::$LE;
1663 - }
1664 -
1665 - return true;
1666 - } catch (Exception $exc) {
1667 - $this->setError($exc->getMessage());
1668 - if ($this->exceptions) {
1669 - throw $exc;
1670 - }
1671 -
1672 - return false;
1673 - }
1674 - }
1675 -
1676 - /**
1677 - * Actually send a message via the selected mechanism.
1678 - *
1679 - * @return bool
1680 - * @throws Exception
1681 - *
1682 - */
1683 - public function postSend()
1684 - {
1685 - try {
1686 - //Choose the mailer and send through it
1687 - switch ($this->Mailer) {
1688 - case 'sendmail':
1689 - case 'qmail':
1690 - return $this->sendmailSend($this->MIMEHeader, $this->MIMEBody);
1691 - case 'smtp':
1692 - return $this->smtpSend($this->MIMEHeader, $this->MIMEBody);
1693 - case 'mail':
1694 - return $this->mailSend($this->MIMEHeader, $this->MIMEBody);
1695 - default:
1696 - $sendMethod = $this->Mailer.'Send';
1697 - if (method_exists($this, $sendMethod)) {
1698 - return $this->{$sendMethod}($this->MIMEHeader, $this->MIMEBody);
1699 - }
1700 -
1701 - return $this->mailSend($this->MIMEHeader, $this->MIMEBody);
1702 - }
1703 - } catch (Exception $exc) {
1704 - $this->setError($exc->getMessage());
1705 - $this->edebug($exc->getMessage());
1706 - if ($this->Mailer === 'smtp' && $this->SMTPKeepAlive == true && $this->smtp->connected()) {
1707 - $this->smtp->reset();
1708 - }
1709 - if ($this->exceptions) {
1710 - throw $exc;
1711 - }
1712 - }
1713 -
1714 - return false;
1715 - }
1716 -
1717 - /**
1718 - * Send mail using the $Sendmail program.
1719 - *
1720 - * @param string $header The message headers
1721 - * @param string $body The message body
1722 - *
1723 - * @return bool
1724 - * @throws Exception
1725 - *
1726 - * @see PHPMailer::$Sendmail
1727 - *
1728 - */
1729 - protected function sendmailSend($header, $body)
1730 - {
1731 - if ($this->Mailer === 'qmail') {
1732 - $this->edebug('Sending with qmail');
1733 - } else {
1734 - $this->edebug('Sending with sendmail');
1735 - }
1736 - $header = static::stripTrailingWSP($header).static::$LE.static::$LE;
1737 - //This sets the SMTP envelope sender which gets turned into a return-path header by the receiver
1738 - //A space after `-f` is optional, but there is a long history of its presence
1739 - //causing problems, so we don't use one
1740 - //Exim docs: https://www.exim.org/exim-html-current/doc/html/spec_html/ch-the_exim_command_line.html
1741 - //Sendmail docs: https://www.sendmail.org/~ca/email/man/sendmail.html
1742 - //Example problem: https://www.drupal.org/node/1057954
1743 -
1744 - //PHP 5.6 workaround
1745 - $sendmail_from_value = ini_get('sendmail_from');
1746 - if (empty($this->Sender) && !empty($sendmail_from_value)) {
1747 - //PHP config has a sender address we can use
1748 - $this->Sender = ini_get('sendmail_from');
1749 - }
1750 - //CVE-2016-10033, CVE-2016-10045: Don't pass -f if characters will be escaped.
1751 - if (!empty($this->Sender) && static::validateAddress($this->Sender) && self::isShellSafe($this->Sender)) {
1752 - if ($this->Mailer === 'qmail') {
1753 - $sendmailFmt = '%s -f%s';
1754 - } else {
1755 - $sendmailFmt = '%s -oi -f%s -t';
1756 - }
1757 - } else {
1758 - //allow sendmail to choose a default envelope sender. It may
1759 - //seem preferable to force it to use the From header as with
1760 - //SMTP, but that introduces new problems (see
1761 - //<https://github.com/PHPMailer/PHPMailer/issues/2298>), and
1762 - //it has historically worked this way.
1763 - $sendmailFmt = '%s -oi -t';
1764 - }
1765 -
1766 - $sendmail = sprintf($sendmailFmt, escapeshellcmd($this->Sendmail), $this->Sender);
1767 - $this->edebug('Sendmail path: '.$this->Sendmail);
1768 - $this->edebug('Sendmail command: '.$sendmail);
1769 - $this->edebug('Envelope sender: '.$this->Sender);
1770 - $this->edebug("Headers: {$header}");
1771 -
1772 - if ($this->SingleTo) {
1773 - foreach ($this->SingleToArray as $toAddr) {
1774 - $mail = @popen($sendmail, 'w');
1775 - if (!$mail) {
1776 - throw new Exception($this->lang('execute').$this->Sendmail, self::STOP_CRITICAL);
1777 - }
1778 - $this->edebug("To: {$toAddr}");
1779 - fwrite($mail, 'To: '.$toAddr."\n");
1780 - fwrite($mail, $header);
1781 - fwrite($mail, $body);
1782 - $result = pclose($mail);
1783 - $addrinfo = static::parseAddresses($toAddr, true, $this->CharSet);
1784 - $this->doCallback(
1785 - ($result === 0),
1786 - [[$addrinfo['address'], $addrinfo['name']]],
1787 - $this->cc,
1788 - $this->bcc,
1789 - $this->Subject,
1790 - $body,
1791 - $this->From,
1792 - []
1793 - );
1794 - $this->edebug("Result: ".($result === 0 ? 'true' : 'false'));
1795 - if (0 !== $result) {
1796 - throw new Exception($this->lang('execute').$this->Sendmail, self::STOP_CRITICAL);
1797 - }
1798 - }
1799 - } else {
1800 - $mail = @popen($sendmail, 'w');
1801 - if (!$mail) {
1802 - throw new Exception($this->lang('execute').$this->Sendmail, self::STOP_CRITICAL);
1803 - }
1804 - fwrite($mail, $header);
1805 - fwrite($mail, $body);
1806 - $result = pclose($mail);
1807 - $this->doCallback(
1808 - ($result === 0),
1809 - $this->to,
1810 - $this->cc,
1811 - $this->bcc,
1812 - $this->Subject,
1813 - $body,
1814 - $this->From,
1815 - []
1816 - );
1817 - $this->edebug("Result: ".($result === 0 ? 'true' : 'false'));
1818 - if (0 !== $result) {
1819 - throw new Exception($this->lang('execute').$this->Sendmail, self::STOP_CRITICAL);
1820 - }
1821 - }
1822 -
1823 - return true;
1824 - }
1825 -
1826 - /**
1827 - * Fix CVE-2016-10033 and CVE-2016-10045 by disallowing potentially unsafe shell characters.
1828 - * Note that escapeshellarg and escapeshellcmd are inadequate for our purposes, especially on Windows.
1829 - *
1830 - * @see https://github.com/PHPMailer/PHPMailer/issues/924 CVE-2016-10045 bug report
1831 - *
1832 - * @param string $string The string to be validated
1833 - *
1834 - * @return bool
1835 - */
1836 - protected static function isShellSafe($string)
1837 - {
1838 - //It's not possible to use shell commands safely (which includes the mail() function) without escapeshellarg,
1839 - //but some hosting providers disable it, creating a security problem that we don't want to have to deal with,
1840 - //so we don't.
1841 - if (!function_exists('escapeshellarg') || !function_exists('escapeshellcmd')) {
1842 - return false;
1843 - }
1844 -
1845 - if (
1846 - escapeshellcmd($string) !== $string
1847 - || !in_array(escapeshellarg($string), ["'$string'", "\"$string\""])
1848 - ) {
1849 - return false;
1850 - }
1851 -
1852 - $length = strlen($string);
1853 -
1854 - for ($i = 0; $i < $length; ++$i) {
1855 - $c = $string[$i];
1856 -
1857 - //All other characters have a special meaning in at least one common shell, including = and +.
1858 - //Full stop (.) has a special meaning in cmd.exe, but its impact should be negligible here.
1859 - //Note that this does permit non-Latin alphanumeric characters based on the current locale.
1860 - if (!ctype_alnum($c) && strpos('@_-.', $c) === false) {
1861 - return false;
1862 - }
1863 - }
1864 -
1865 - return true;
1866 - }
1867 -
1868 - /**
1869 - * Check whether a file path is of a permitted type.
1870 - * Used to reject URLs and phar files from functions that access local file paths,
1871 - * such as addAttachment.
1872 - *
1873 - * @param string $path A relative or absolute path to a file
1874 - *
1875 - * @return bool
1876 - */
1877 - protected static function isPermittedPath($path)
1878 - {
1879 - //Matches scheme definition from https://tools.ietf.org/html/rfc3986#section-3.1
1880 - return !preg_match('#^[a-z][a-z\d+.-]*://#i', $path);
1881 - }
1882 -
1883 - /**
1884 - * Check whether a file path is safe, accessible, and readable.
1885 - *
1886 - * @param string $path A relative or absolute path to a file
1887 - *
1888 - * @return bool
1889 - */
1890 - protected static function fileIsAccessible($path)
1891 - {
1892 - if (!static::isPermittedPath($path)) {
1893 - return false;
1894 - }
1895 - $readable = is_file($path);
1896 - //If not a UNC path (expected to start with \\), check read permission, see #2069
1897 - if (strpos($path, '\\\\') !== 0) {
1898 - $readable = $readable && is_readable($path);
1899 - }
1900 -
1901 - return $readable;
1902 - }
1903 -
1904 - /**
1905 - * Send mail using the PHP mail() function.
1906 - *
1907 - * @see https://www.php.net/manual/en/book.mail.php
1908 - *
1909 - * @param string $header The message headers
1910 - * @param string $body The message body
1911 - *
1912 - * @throws Exception
1913 - *
1914 - * @return bool
1915 - */
1916 - protected function mailSend($header, $body)
1917 - {
1918 - $header = static::stripTrailingWSP($header).static::$LE.static::$LE;
1919 -
1920 - $toArr = [];
1921 - foreach ($this->to as $toaddr) {
1922 - $toArr[] = $this->addrFormat($toaddr);
1923 - }
1924 - $to = trim(implode(', ', $toArr));
1925 -
1926 - //If there are no To-addresses (e.g. when sending only to BCC-addresses)
1927 - //the following should be added to get a correct DKIM-signature.
1928 - //Compare with $this->preSend()
1929 - if ($to === '') {
1930 - $to = 'undisclosed-recipients:;';
1931 - }
1932 -
1933 - $params = null;
1934 - //This sets the SMTP envelope sender which gets turned into a return-path header by the receiver
1935 - //A space after `-f` is optional, but there is a long history of its presence
1936 - //causing problems, so we don't use one
1937 - //Exim docs: https://www.exim.org/exim-html-current/doc/html/spec_html/ch-the_exim_command_line.html
1938 - //Sendmail docs: https://www.sendmail.org/~ca/email/man/sendmail.html
1939 - //Example problem: https://www.drupal.org/node/1057954
1940 - //CVE-2016-10033, CVE-2016-10045: Don't pass -f if characters will be escaped.
1941 -
1942 - //PHP 5.6 workaround
1943 - $sendmail_from_value = ini_get('sendmail_from');
1944 - if (empty($this->Sender) && !empty($sendmail_from_value)) {
1945 - //PHP config has a sender address we can use
1946 - $this->Sender = ini_get('sendmail_from');
1947 - }
1948 - if (!empty($this->Sender) && static::validateAddress($this->Sender)) {
1949 - if (self::isShellSafe($this->Sender)) {
1950 - $params = sprintf('-f%s', $this->Sender);
1951 - }
1952 - $old_from = ini_get('sendmail_from');
1953 - ini_set('sendmail_from', $this->Sender);
1954 - }
1955 - $result = false;
1956 - if ($this->SingleTo && count($toArr) > 1) {
1957 - foreach ($toArr as $toAddr) {
1958 - $result = $this->mailPassthru($toAddr, $this->Subject, $body, $header, $params);
1959 - $addrinfo = static::parseAddresses($toAddr, true, $this->CharSet);
1960 - $this->doCallback(
1961 - $result,
1962 - [[$addrinfo['address'], $addrinfo['name']]],
1963 - $this->cc,
1964 - $this->bcc,
1965 - $this->Subject,
1966 - $body,
1967 - $this->From,
1968 - []
1969 - );
1970 - }
1971 - } else {
1972 - $result = $this->mailPassthru($to, $this->Subject, $body, $header, $params);
1973 - $this->doCallback($result, $this->to, $this->cc, $this->bcc, $this->Subject, $body, $this->From, []);
1974 - }
1975 - if (isset($old_from)) {
1976 - ini_set('sendmail_from', $old_from);
1977 - }
1978 - if (!$result) {
1979 - throw new Exception($this->lang('instantiate'), self::STOP_CRITICAL);
1980 - }
1981 -
1982 - return true;
1983 - }
1984 -
1985 - /**
1986 - * Get an instance to use for SMTP operations.
1987 - * Override this function to load your own SMTP implementation,
1988 - * or set one with setSMTPInstance.
1989 - *
1990 - * @return SMTP
1991 - */
1992 - public function getSMTPInstance()
1993 - {
1994 - if (!is_object($this->smtp)) {
1995 - $this->smtp = new Smtp();
1996 - }
1997 -
1998 - return $this->smtp;
1999 - }
2000 -
2001 - /**
2002 - * Provide an instance to use for SMTP operations.
2003 - *
2004 - * @return SMTP
2005 - */
2006 - public function setSMTPInstance(SMTP $smtp)
2007 - {
2008 - $this->smtp = $smtp;
2009 -
2010 - return $this->smtp;
2011 - }
2012 -
2013 - /**
2014 - * Provide SMTP XCLIENT attributes
2015 - *
2016 - * @param string $name Attribute name
2017 - * @param ?string $value Attribute value
2018 - *
2019 - * @return bool
2020 - */
2021 - public function setSMTPXclientAttribute($name, $value)
2022 - {
2023 - if (!in_array($name, SMTP::$xclient_allowed_attributes)) {
2024 - return false;
2025 - }
2026 - if (isset($this->SMTPXClient[$name]) && $value === null) {
2027 - unset($this->SMTPXClient[$name]);
2028 - } elseif ($value !== null) {
2029 - $this->SMTPXClient[$name] = $value;
2030 - }
2031 -
2032 - return true;
2033 - }
2034 -
2035 - /**
2036 - * Get SMTP XCLIENT attributes
2037 - *
2038 - * @return array
2039 - */
2040 - public function getSMTPXclientAttributes()
2041 - {
2042 - return $this->SMTPXClient;
2043 - }
2044 -
2045 - /**
2046 - * Send mail via SMTP.
2047 - * Returns false if there is a bad MAIL FROM, RCPT, or DATA input.
2048 - *
2049 - * @param string $header The message headers
2050 - * @param string $body The message body
2051 - *
2052 - * @return bool
2053 - * @throws Exception
2054 - *
2055 - * @see PHPMailer::setSMTPInstance() to use a different class.
2056 - *
2057 - * @uses \PHPMailer\PHPMailer\SMTP
2058 - *
2059 - */
2060 - protected function smtpSend($header, $body)
2061 - {
2062 - $header = static::stripTrailingWSP($header).static::$LE.static::$LE;
2063 - $bad_rcpt = [];
2064 - if (!$this->smtpConnect($this->SMTPOptions)) {
2065 - throw new Exception($this->lang('smtp_connect_failed'), self::STOP_CRITICAL);
2066 - }
2067 - //Sender already validated in preSend()
2068 - if ('' === $this->Sender) {
2069 - $smtp_from = $this->From;
2070 - } else {
2071 - $smtp_from = $this->Sender;
2072 - }
2073 - if (count($this->SMTPXClient)) {
2074 - $this->smtp->xclient($this->SMTPXClient);
2075 - }
2076 - if (!$this->smtp->mail($smtp_from)) {
2077 - $this->setError($this->lang('from_failed').$smtp_from.' : '.implode(',', $this->smtp->getError()));
2078 - throw new Exception($this->ErrorInfo, self::STOP_CRITICAL);
2079 - }
2080 -
2081 - $callbacks = [];
2082 - //Attempt to send to all recipients
2083 - foreach ([$this->to, $this->cc, $this->bcc] as $togroup) {
2084 - foreach ($togroup as $to) {
2085 - if (!$this->smtp->recipient($to[0], $this->dsn)) {
2086 - $error = $this->smtp->getError();
2087 - $bad_rcpt[] = ['to' => $to[0], 'error' => $error['detail']];
2088 - $isSent = false;
2089 - } else {
2090 - $isSent = true;
2091 - }
2092 -
2093 - $callbacks[] = ['issent' => $isSent, 'to' => $to[0], 'name' => $to[1]];
2094 - }
2095 - }
2096 -
2097 - //Only send the DATA command if we have viable recipients
2098 - if ((count($this->all_recipients) > count($bad_rcpt)) && !$this->smtp->data($header.$body)) {
2099 - throw new Exception($this->lang('data_not_accepted'), self::STOP_CRITICAL);
2100 - }
2101 -
2102 - $smtp_transaction_id = $this->smtp->getLastTransactionID();
2103 -
2104 - if ($this->SMTPKeepAlive) {
2105 - $this->smtp->reset();
2106 - } else {
2107 - $this->smtp->quit();
2108 - $this->smtp->close();
2109 - }
2110 -
2111 - foreach ($callbacks as $cb) {
2112 - $this->doCallback(
2113 - $cb['issent'],
2114 - [[$cb['to'], $cb['name']]],
2115 - [],
2116 - [],
2117 - $this->Subject,
2118 - $body,
2119 - $this->From,
2120 - ['smtp_transaction_id' => $smtp_transaction_id]
2121 - );
2122 - }
2123 -
2124 - //Create error message for any bad addresses
2125 - if (count($bad_rcpt) > 0) {
2126 - $errstr = '';
2127 - foreach ($bad_rcpt as $bad) {
2128 - $errstr .= $bad['to'].': '.$bad['error'].', ';
2129 - }
2130 - $errstr = rtrim($errstr, ', ');
2131 -
2132 - $errorTmp = $this->smtp->getError();
2133 - $errorLbl = empty($errorTmp) ? $this->Lang('recipients_failed') : implode(', ', $errorTmp);
2134 - $this->setError($errorLbl.' ('.$errstr.') ');
2135 -
2136 - // Avoid the nested MAIL command error
2137 - $this->smtp->Reset();
2138 - throw new Exception($this->ErrorInfo);
2139 - }
2140 -
2141 - return true;
2142 - }
2143 -
2144 - /**
2145 - * Initiate a connection to an SMTP server.
2146 - * Returns false if the operation failed.
2147 - *
2148 - * @param array $options An array of options compatible with stream_context_create()
2149 - *
2150 - * @return bool
2151 - * @throws Exception
2152 - *
2153 - * @uses \PHPMailer\PHPMailer\SMTP
2154 - *
2155 - */
2156 - public function smtpConnect($options = null)
2157 - {
2158 - if (null === $this->smtp) {
2159 - $this->smtp = $this->getSMTPInstance();
2160 - }
2161 -
2162 - //If no options are provided, use whatever is set in the instance
2163 - if (null === $options) {
2164 - $options = $this->SMTPOptions;
2165 - }
2166 -
2167 - //Already connected?
2168 - if ($this->smtp->connected()) {
2169 - return true;
2170 - }
2171 -
2172 - $this->smtp->setTimeout($this->Timeout);
2173 - $this->smtp->setDebugLevel($this->SMTPDebug);
2174 - $this->smtp->setDebugOutput($this->Debugoutput);
2175 - $this->smtp->setVerp($this->do_verp);
2176 - if ($this->Host === null) {
2177 - $this->Host = 'localhost';
2178 - }
2179 - $hosts = explode(';', $this->Host);
2180 - $lastexception = null;
2181 -
2182 - foreach ($hosts as $hostentry) {
2183 - $hostinfo = [];
2184 - if (
2185 - !preg_match(
2186 - '/^(?:(ssl|tls):\/\/)?(.+?)(?::(\d+))?$/',
2187 - trim($hostentry),
2188 - $hostinfo
2189 - )
2190 - ) {
2191 - $this->edebug($this->lang('invalid_hostentry').' '.trim($hostentry));
2192 - //Not a valid host entry
2193 - continue;
2194 - }
2195 - //$hostinfo[1]: optional ssl or tls prefix
2196 - //$hostinfo[2]: the hostname
2197 - //$hostinfo[3]: optional port number
2198 - //The host string prefix can temporarily override the current setting for SMTPSecure
2199 - //If it's not specified, the default value is used
2200 -
2201 - //Check the host name is a valid name or IP address before trying to use it
2202 - if (!static::isValidHost($hostinfo[2])) {
2203 - $this->edebug($this->lang('invalid_host').' '.$hostinfo[2]);
2204 - continue;
2205 - }
2206 - $prefix = '';
2207 - $secure = $this->SMTPSecure;
2208 - $tls = (static::ENCRYPTION_STARTTLS === $this->SMTPSecure);
2209 - if ('ssl' === $hostinfo[1] || ('' === $hostinfo[1] && static::ENCRYPTION_SMTPS === $this->SMTPSecure)) {
2210 - $prefix = 'ssl://';
2211 - $tls = false; //Can't have SSL and TLS at the same time
2212 - $secure = static::ENCRYPTION_SMTPS;
2213 - } elseif ('tls' === $hostinfo[1]) {
2214 - $tls = true;
2215 - //TLS doesn't use a prefix
2216 - $secure = static::ENCRYPTION_STARTTLS;
2217 - }
2218 - //Do we need the OpenSSL extension?
2219 - $sslext = defined('OPENSSL_ALGO_SHA256');
2220 - if (static::ENCRYPTION_STARTTLS === $secure || static::ENCRYPTION_SMTPS === $secure) {
2221 - //Check for an OpenSSL constant rather than using extension_loaded, which is sometimes disabled
2222 - if (!$sslext) {
2223 - throw new Exception($this->lang('extension_missing').'openssl', self::STOP_CRITICAL);
2224 - }
2225 - }
2226 - $host = $hostinfo[2];
2227 - $port = $this->Port;
2228 - if (
2229 - array_key_exists(3, $hostinfo) &&
2230 - is_numeric($hostinfo[3]) &&
2231 - $hostinfo[3] > 0 &&
2232 - $hostinfo[3] < 65536
2233 - ) {
2234 - $port = (int)$hostinfo[3];
2235 - }
2236 - if ($this->smtp->connect($prefix.$host, $port, $this->Timeout, $options)) {
2237 - try {
2238 - if ($this->Helo) {
2239 - $hello = $this->Helo;
2240 - } else {
2241 - $hello = $this->serverHostname();
2242 - }
2243 - $this->smtp->hello($hello);
2244 - //Automatically enable TLS encryption if:
2245 - //* it's not disabled
2246 - //* we are not connecting to localhost
2247 - //* we have openssl extension
2248 - //* we are not already using SSL
2249 - //* the server offers STARTTLS
2250 - if (
2251 - $this->SMTPAutoTLS &&
2252 - $this->Host !== 'localhost' &&
2253 - $sslext &&
2254 - $secure !== 'ssl' &&
2255 - $this->smtp->getServerExt('STARTTLS')
2256 - ) {
2257 - $tls = true;
2258 - }
2259 - if ($tls) {
2260 - if (!$this->smtp->startTLS()) {
2261 - $message = $this->getSmtpErrorMessage('connect_host');
2262 - throw new Exception($message);
2263 - }
2264 - //We must resend EHLO after TLS negotiation
2265 - $this->smtp->hello($hello);
2266 - }
2267 - if (
2268 - $this->SMTPAuth && !$this->smtp->authenticate(
2269 - $this->Username,
2270 - $this->Password,
2271 - $this->AuthType,
2272 - $this->oauth
2273 - )
2274 - ) {
2275 - $errorTmp = $this->smtp->getError();
2276 - $errorLbl = empty($errorTmp) ? $this->lang('authenticate') : implode(', ', $errorTmp);
2277 - throw new Exception($errorLbl);
2278 - }
2279 -
2280 - return true;
2281 - } catch (Exception $exc) {
2282 - $lastexception = $exc;
2283 - $this->edebug($exc->getMessage());
2284 - //We must have connected, but then failed TLS or Auth, so close connection nicely
2285 - $this->smtp->quit();
2286 - }
2287 - }
2288 - }
2289 - //If we get here, all connection attempts have failed, so close connection hard
2290 - $this->smtp->close();
2291 - //As we've caught all exceptions, just report whatever the last one was
2292 - if ($this->exceptions && null !== $lastexception) {
2293 - throw $lastexception;
2294 - }
2295 - if ($this->exceptions) {
2296 - // no exception was thrown, likely $this->smtp->connect() failed
2297 - $message = $this->getSmtpErrorMessage('connect_host');
2298 - throw new Exception($message);
2299 - }
2300 -
2301 - return false;
2302 - }
2303 -
2304 - /**
2305 - * Close the active SMTP session if one exists.
2306 - */
2307 - public function smtpClose()
2308 - {
2309 - if ((null !== $this->smtp) && $this->smtp->connected()) {
2310 - $this->smtp->quit();
2311 - $this->smtp->close();
2312 - }
2313 - }
2314 -
2315 - /**
2316 - * Set the language for error messages.
2317 - * The default language is English.
2318 - *
2319 - * @param string $langcode ISO 639-1 2-character language code (e.g. French is "fr")
2320 - * Optionally, the language code can be enhanced with a 4-character
2321 - * script annotation and/or a 2-character country annotation.
2322 - * @param string $lang_path Path to the language file directory, with trailing separator (slash)
2323 - * Do not set this from user input!
2324 - *
2325 - * @return bool Returns true if the requested language was loaded, false otherwise.
2326 - */
2327 - public function setLanguage($langcode = 'en', $lang_path = '')
2328 - {
2329 - //Backwards compatibility for renamed language codes
2330 - $renamed_langcodes = [
2331 - 'br' => 'pt_br',
2332 - 'cz' => 'cs',
2333 - 'dk' => 'da',
2334 - 'no' => 'nb',
2335 - 'se' => 'sv',
2336 - 'rs' => 'sr',
2337 - 'tg' => 'tl',
2338 - 'am' => 'hy',
2339 - ];
2340 -
2341 - if (array_key_exists($langcode, $renamed_langcodes)) {
2342 - $langcode = $renamed_langcodes[$langcode];
2343 - }
2344 -
2345 - //Define full set of translatable strings in English
2346 - $PHPMAILER_LANG = [
2347 - 'authenticate' => 'SMTP Error: Could not authenticate.',
2348 - 'buggy_php' => 'Your version of PHP is affected by a bug that may result in corrupted messages.'.
2349 - ' To fix it, switch to sending using SMTP, disable the mail.add_x_header option in'.
2350 - ' your php.ini, switch to MacOS or Linux, or upgrade your PHP to version 7.0.17+ or 7.1.3+.',
2351 - 'connect_host' => 'SMTP Error: Could not connect to SMTP host.',
2352 - 'data_not_accepted' => 'SMTP Error: data not accepted.',
2353 - 'empty_message' => 'Message body empty',
2354 - 'encoding' => 'Unknown encoding: ',
2355 - 'execute' => 'Could not execute: ',
2356 - 'extension_missing' => 'Extension missing: ',
2357 - 'file_access' => 'Could not access file: ',
2358 - 'file_open' => 'File Error: Could not open file: ',
2359 - 'from_failed' => 'The following From address failed: ',
2360 - 'instantiate' => 'Could not instantiate mail function.',
2361 - 'invalid_address' => 'Invalid address: ',
2362 - 'invalid_header' => 'Invalid header name or value',
2363 - 'invalid_hostentry' => 'Invalid hostentry: ',
2364 - 'invalid_host' => 'Invalid host: ',
2365 - 'mailer_not_supported' => ' mailer is not supported.',
2366 - 'provide_address' => 'You must provide at least one recipient email address.',
2367 - 'recipients_failed' => 'SMTP Error: The following recipients failed: ',
2368 - 'signing' => 'Signing Error: ',
2369 - 'smtp_code' => 'SMTP code: ',
2370 - 'smtp_code_ex' => 'Additional SMTP info: ',
2371 - 'smtp_connect_failed' => 'SMTP connect() failed.',
2372 - 'smtp_detail' => 'Detail: ',
2373 - 'smtp_error' => 'SMTP server error: ',
2374 - 'variable_set' => 'Cannot set or reset variable: ',
2375 - ];
2376 - if (empty($lang_path)) {
2377 - //Calculate an absolute path so it can work if CWD is not here
2378 - $lang_path = dirname(__DIR__).DIRECTORY_SEPARATOR.'language'.DIRECTORY_SEPARATOR;
2379 - }
2380 -
2381 - //Validate $langcode
2382 - $foundlang = true;
2383 - $langcode = strtolower($langcode);
2384 - if (
2385 - !preg_match('/^(?P<lang>[a-z]{2})(?P<script>_[a-z]{4})?(?P<country>_[a-z]{2})?$/', $langcode, $matches)
2386 - && $langcode !== 'en'
2387 - ) {
2388 - $foundlang = false;
2389 - $langcode = 'en';
2390 - }
2391 -
2392 - //There is no English translation file
2393 - if ('en' !== $langcode) {
2394 - $langcodes = [];
2395 - if (!empty($matches['script']) && !empty($matches['country'])) {
2396 - $langcodes[] = $matches['lang'].$matches['script'].$matches['country'];
2397 - }
2398 - if (!empty($matches['country'])) {
2399 - $langcodes[] = $matches['lang'].$matches['country'];
2400 - }
2401 - if (!empty($matches['script'])) {
2402 - $langcodes[] = $matches['lang'].$matches['script'];
2403 - }
2404 - $langcodes[] = $matches['lang'];
2405 -
2406 - //Try and find a readable language file for the requested language.
2407 - $foundFile = false;
2408 - foreach ($langcodes as $code) {
2409 - $lang_file = $lang_path.'phpmailer.lang-'.$code.'.php';
2410 - if (static::fileIsAccessible($lang_file)) {
2411 - $foundFile = true;
2412 - break;
2413 - }
2414 - }
2415 -
2416 - if ($foundFile === false) {
2417 - $foundlang = false;
2418 - } else {
2419 - $lines = file($lang_file);
2420 - foreach ($lines as $line) {
2421 - //Translation file lines look like this:
2422 - //$PHPMAILER_LANG['authenticate'] = 'SMTP-Fehler: Authentifizierung fehlgeschlagen.';
2423 - //These files are parsed as text and not PHP so as to avoid the possibility of code injection
2424 - //See https://blog.stevenlevithan.com/archives/match-quoted-string
2425 - $matches = [];
2426 - if (
2427 - preg_match(
2428 - '/^\$PHPMAILER_LANG\[\'([a-z\d_]+)\'\]\s*=\s*(["\'])(.+)*?\2;/',
2429 - $line,
2430 - $matches
2431 - ) &&
2432 - //Ignore unknown translation keys
2433 - array_key_exists($matches[1], $PHPMAILER_LANG)
2434 - ) {
2435 - //Overwrite language-specific strings so we'll never have missing translation keys.
2436 - $PHPMAILER_LANG[$matches[1]] = (string)$matches[3];
2437 - }
2438 - }
2439 - }
2440 - }
2441 - $this->language = $PHPMAILER_LANG;
2442 -
2443 - return $foundlang; //Returns false if language not found
2444 - }
2445 -
2446 - /**
2447 - * Get the array of strings for the current language.
2448 - *
2449 - * @return array
2450 - */
2451 - public function getTranslations()
2452 - {
2453 - if (empty($this->language)) {
2454 - $this->setLanguage(); // Set the default language.
2455 - }
2456 -
2457 - return $this->language;
2458 - }
2459 -
2460 - /**
2461 - * Create recipient headers.
2462 - *
2463 - * @param string $type
2464 - * @param array $addr An array of recipients,
2465 - * where each recipient is a 2-element indexed array with element 0 containing an address
2466 - * and element 1 containing a name, like:
2467 - * [['[email protected]', 'Joe User'], ['[email protected]', 'Zoe User']]
2468 - *
2469 - * @return string
2470 - */
2471 - public function addrAppend($type, $addr)
2472 - {
2473 - $addresses = [];
2474 - foreach ($addr as $address) {
2475 - $addresses[] = $this->addrFormat($address);
2476 - }
2477 -
2478 - return $type.': '.implode(', ', $addresses).static::$LE;
2479 - }
2480 -
2481 - /**
2482 - * Format an address for use in a message header.
2483 - *
2484 - * @param array $addr A 2-element indexed array, element 0 containing an address, element 1 containing a name like
2485 - * ['[email protected]', 'Joe User']
2486 - *
2487 - * @return string
2488 - */
2489 - public function addrFormat($addr)
2490 - {
2491 - if (!isset($addr[1]) || ($addr[1] === '')) { //No name provided
2492 - return $this->secureHeader($addr[0]);
2493 - }
2494 -
2495 - return $this->encodeHeader($this->secureHeader($addr[1]), 'phrase').
2496 - ' <'.$this->secureHeader($addr[0]).'>';
2497 - }
2498 -
2499 - /**
2500 - * Word-wrap message.
2501 - * For use with mailers that do not automatically perform wrapping
2502 - * and for quoted-printable encoded messages.
2503 - * Original written by philippe.
2504 - *
2505 - * @param string $message The message to wrap
2506 - * @param int $length The line length to wrap to
2507 - * @param bool $qp_mode Whether to run in Quoted-Printable mode
2508 - *
2509 - * @return string
2510 - */
2511 - public function wrapText($message, $length, $qp_mode = false)
2512 - {
2513 - if ($qp_mode) {
2514 - $soft_break = sprintf(' =%s', static::$LE);
2515 - } else {
2516 - $soft_break = static::$LE;
2517 - }
2518 - //If utf-8 encoding is used, we will need to make sure we don't
2519 - //split multibyte characters when we wrap
2520 - $is_utf8 = static::CHARSET_UTF8 === strtolower($this->CharSet);
2521 - $lelen = strlen(static::$LE);
2522 - $crlflen = strlen(static::$LE);
2523 -
2524 - $message = static::normalizeBreaks($message);
2525 - //Remove a trailing line break
2526 - if (substr($message, -$lelen) === static::$LE) {
2527 - $message = substr($message, 0, -$lelen);
2528 - }
2529 -
2530 - //Split message into lines
2531 - $lines = explode(static::$LE, $message);
2532 - //Message will be rebuilt in here
2533 - $message = '';
2534 - foreach ($lines as $line) {
2535 - $words = explode(' ', $line);
2536 - $buf = '';
2537 - $firstword = true;
2538 - foreach ($words as $word) {
2539 - if ($qp_mode && (strlen($word) > $length)) {
2540 - $space_left = $length - strlen($buf) - $crlflen;
2541 - if (!$firstword) {
2542 - if ($space_left > 20) {
2543 - $len = $space_left;
2544 - if ($is_utf8) {
2545 - $len = $this->utf8CharBoundary($word, $len);
2546 - } elseif ('=' === substr($word, $len - 1, 1)) {
2547 - --$len;
2548 - } elseif ('=' === substr($word, $len - 2, 1)) {
2549 - $len -= 2;
2550 - }
2551 - $part = substr($word, 0, $len);
2552 - $word = substr($word, $len);
2553 - $buf .= ' '.$part;
2554 - $message .= $buf.sprintf('=%s', static::$LE);
2555 - } else {
2556 - $message .= $buf.$soft_break;
2557 - }
2558 - $buf = '';
2559 - }
2560 - while ($word !== '') {
2561 - if ($length <= 0) {
2562 - break;
2563 - }
2564 - $len = $length;
2565 - if ($is_utf8) {
2566 - $len = $this->utf8CharBoundary($word, $len);
2567 - } elseif ('=' === substr($word, $len - 1, 1)) {
2568 - --$len;
2569 - } elseif ('=' === substr($word, $len - 2, 1)) {
2570 - $len -= 2;
2571 - }
2572 - $part = substr($word, 0, $len);
2573 - $word = (string)substr($word, $len);
2574 -
2575 - if ($word !== '') {
2576 - $message .= $part.sprintf('=%s', static::$LE);
2577 - } else {
2578 - $buf = $part;
2579 - }
2580 - }
2581 - } else {
2582 - $buf_o = $buf;
2583 - if (!$firstword) {
2584 - $buf .= ' ';
2585 - }
2586 - $buf .= $word;
2587 -
2588 - if ('' !== $buf_o && strlen($buf) > $length) {
2589 - $message .= $buf_o.$soft_break;
2590 - $buf = $word;
2591 - }
2592 - }
2593 - $firstword = false;
2594 - }
2595 - $message .= $buf.static::$LE;
2596 - }
2597 -
2598 - return $message;
2599 - }
2600 -
2601 - /**
2602 - * Find the last character boundary prior to $maxLength in a utf-8
2603 - * quoted-printable encoded string.
2604 - * Original written by Colin Brown.
2605 - *
2606 - * @param string $encodedText utf-8 QP text
2607 - * @param int $maxLength Find the last character boundary prior to this length
2608 - *
2609 - * @return int
2610 - */
2611 - public function utf8CharBoundary($encodedText, $maxLength)
2612 - {
2613 - $foundSplitPos = false;
2614 - $lookBack = 3;
2615 - while (!$foundSplitPos) {
2616 - $lastChunk = substr($encodedText, $maxLength - $lookBack, $lookBack);
2617 - $encodedCharPos = strpos($lastChunk, '=');
2618 - if (false !== $encodedCharPos) {
2619 - //Found start of encoded character byte within $lookBack block.
2620 - //Check the encoded byte value (the 2 chars after the '=')
2621 - $hex = substr($encodedText, $maxLength - $lookBack + $encodedCharPos + 1, 2);
2622 - $dec = hexdec($hex);
2623 - if ($dec < 128) {
2624 - //Single byte character.
2625 - //If the encoded char was found at pos 0, it will fit
2626 - //otherwise reduce maxLength to start of the encoded char
2627 - if ($encodedCharPos > 0) {
2628 - $maxLength -= $lookBack - $encodedCharPos;
2629 - }
2630 - $foundSplitPos = true;
2631 - } elseif ($dec >= 192) {
2632 - //First byte of a multi byte character
2633 - //Reduce maxLength to split at start of character
2634 - $maxLength -= $lookBack - $encodedCharPos;
2635 - $foundSplitPos = true;
2636 - } elseif ($dec < 192) {
2637 - //Middle byte of a multi byte character, look further back
2638 - $lookBack += 3;
2639 - }
2640 - } else {
2641 - //No encoded character found
2642 - $foundSplitPos = true;
2643 - }
2644 - }
2645 -
2646 - return $maxLength;
2647 - }
2648 -
2649 - /**
2650 - * Apply word wrapping to the message body.
2651 - * Wraps the message body to the number of chars set in the WordWrap property.
2652 - * You should only do this to plain-text bodies as wrapping HTML tags may break them.
2653 - * This is called automatically by createBody(), so you don't need to call it yourself.
2654 - */
2655 - public function setWordWrap()
2656 - {
2657 - if ($this->WordWrap < 1) {
2658 - return;
2659 - }
2660 -
2661 - switch ($this->message_type) {
2662 - case 'alt':
2663 - case 'alt_inline':
2664 - case 'alt_attach':
2665 - case 'alt_inline_attach':
2666 - $this->AltBody = $this->wrapText($this->AltBody, $this->WordWrap);
2667 - break;
2668 - default:
2669 - $this->Body = $this->wrapText($this->Body, $this->WordWrap);
2670 - break;
2671 - }
2672 - }
2673 -
2674 - /**
2675 - * Assemble message headers.
2676 - *
2677 - * @return string The assembled headers
2678 - */
2679 - public function createHeader()
2680 - {
2681 - $result = '';
2682 -
2683 - $result .= $this->headerLine('Date', '' === $this->MessageDate ? self::rfcDate() : $this->MessageDate);
2684 -
2685 - //The To header is created automatically by mail(), so needs to be omitted here
2686 - if ('mail' !== $this->Mailer) {
2687 - if ($this->SingleTo) {
2688 - foreach ($this->to as $toaddr) {
2689 - $this->SingleToArray[] = $this->addrFormat($toaddr);
2690 - }
2691 - } elseif (count($this->to) > 0) {
2692 - $result .= $this->addrAppend('To', $this->to);
2693 - } elseif (count($this->cc) === 0) {
2694 - $result .= $this->headerLine('To', 'undisclosed-recipients:;');
2695 - }
2696 - }
2697 - $result .= $this->addrAppend('From', [[trim($this->From), $this->FromName]]);
2698 -
2699 - //sendmail and mail() extract Cc from the header before sending
2700 - if (count($this->cc) > 0) {
2701 - $result .= $this->addrAppend('Cc', $this->cc);
2702 - }
2703 -
2704 - //sendmail and mail() extract Bcc from the header before sending
2705 - if (
2706 - (
2707 - 'sendmail' === $this->Mailer || 'qmail' === $this->Mailer || 'mail' === $this->Mailer
2708 - )
2709 - && count($this->bcc) > 0
2710 - ) {
2711 - $result .= $this->addrAppend('Bcc', $this->bcc);
2712 - }
2713 -
2714 - if (count($this->ReplyTo) > 0) {
2715 - $result .= $this->addrAppend('Reply-To', $this->ReplyTo);
2716 - }
2717 -
2718 - //mail() sets the subject itself
2719 - if ('mail' !== $this->Mailer) {
2720 - $result .= $this->headerLine('Subject', $this->encodeHeader($this->secureHeader($this->Subject)));
2721 - }
2722 -
2723 - //Only allow a custom message ID if it conforms to RFC 5322 section 3.6.4
2724 - //https://tools.ietf.org/html/rfc5322#section-3.6.4
2725 - if (
2726 - '' !== $this->MessageID &&
2727 - preg_match(
2728 - '/^<((([a-z\d!#$%&\'*+\/=?^_`{|}~-]+(\.[a-z\d!#$%&\'*+\/=?^_`{|}~-]+)*)'.
2729 - '|("(([\x01-\x08\x0B\x0C\x0E-\x1F\x7F]|[\x21\x23-\x5B\x5D-\x7E])'.
2730 - '|(\\[\x01-\x09\x0B\x0C\x0E-\x7F]))*"))@(([a-z\d!#$%&\'*+\/=?^_`{|}~-]+'.
2731 - '(\.[a-z\d!#$%&\'*+\/=?^_`{|}~-]+)*)|(\[(([\x01-\x08\x0B\x0C\x0E-\x1F\x7F]'.
2732 - '|[\x21-\x5A\x5E-\x7E])|(\\[\x01-\x09\x0B\x0C\x0E-\x7F]))*\])))>$/Di',
2733 - $this->MessageID
2734 - )
2735 - ) {
2736 - $this->lastMessageID = $this->MessageID;
2737 - } else {
2738 - $this->lastMessageID = sprintf('<%s@%s>', $this->uniqueid, $this->serverHostname());
2739 - }
2740 - $result .= $this->headerLine('Message-ID', $this->lastMessageID);
2741 - if (null !== $this->Priority) {
2742 - $result .= $this->headerLine('X-Priority', $this->Priority);
2743 - }
2744 - if ('' === $this->XMailer) {
2745 - //Empty string for default X-Mailer header
2746 - $result .= $this->headerLine(
2747 - 'X-Mailer',
2748 - 'PHPMailer '.self::VERSION.' (https://github.com/PHPMailer/PHPMailer)'
2749 - );
2750 - } elseif (is_string($this->XMailer) && trim($this->XMailer) !== '') {
2751 - //Some string
2752 - $result .= $this->headerLine('X-Mailer', trim($this->XMailer));
2753 - } //Other values result in no X-Mailer header
2754 -
2755 - if ('' !== $this->ConfirmReadingTo) {
2756 - $result .= $this->headerLine('Disposition-Notification-To', '<'.$this->ConfirmReadingTo.'>');
2757 - }
2758 -
2759 - //Add custom headers
2760 - foreach ($this->CustomHeader as $header) {
2761 - $result .= $this->headerLine(
2762 - trim($header[0]),
2763 - $this->encodeHeader(trim($header[1]))
2764 - );
2765 - }
2766 - if (!$this->sign_key_file) {
2767 - $result .= $this->headerLine('MIME-Version', '1.0');
2768 - $result .= $this->getMailMIME();
2769 - }
2770 -
2771 - return $result;
2772 - }
2773 -
2774 - /**
2775 - * Get the message MIME type headers.
2776 - *
2777 - * @return string
2778 - */
2779 - public function getMailMIME()
2780 - {
2781 - $result = '';
2782 - $ismultipart = true;
2783 - switch ($this->message_type) {
2784 - case 'inline':
2785 - $result .= $this->headerLine('Content-Type', static::CONTENT_TYPE_MULTIPART_RELATED.';');
2786 - $result .= $this->textLine(' boundary="'.$this->boundary[1].'"');
2787 - break;
2788 - case 'attach':
2789 - case 'inline_attach':
2790 - case 'alt_attach':
2791 - case 'alt_inline_attach':
2792 - $result .= $this->headerLine('Content-Type', static::CONTENT_TYPE_MULTIPART_MIXED.';');
2793 - $result .= $this->textLine(' boundary="'.$this->boundary[1].'"');
2794 - break;
2795 - case 'alt':
2796 - case 'alt_inline':
2797 - $result .= $this->headerLine('Content-Type', static::CONTENT_TYPE_MULTIPART_ALTERNATIVE.';');
2798 - $result .= $this->textLine(' boundary="'.$this->boundary[1].'"');
2799 - break;
2800 - default:
2801 - //Catches case 'plain': and case '':
2802 - $result .= $this->textLine('Content-Type: '.$this->ContentType.'; charset='.$this->CharSet);
2803 - $ismultipart = false;
2804 - break;
2805 - }
2806 - //RFC1341 part 5 says 7bit is assumed if not specified
2807 - if (static::ENCODING_7BIT !== $this->Encoding) {
2808 - //RFC 2045 section 6.4 says multipart MIME parts may only use 7bit, 8bit or binary CTE
2809 - if ($ismultipart) {
2810 - if (static::ENCODING_8BIT === $this->Encoding) {
2811 - $result .= $this->headerLine('Content-Transfer-Encoding', static::ENCODING_8BIT);
2812 - }
2813 - //The only remaining alternatives are quoted-printable and base64, which are both 7bit compatible
2814 - } else {
2815 - $result .= $this->headerLine('Content-Transfer-Encoding', $this->Encoding);
2816 - }
2817 - }
2818 -
2819 - return $result;
2820 - }
2821 -
2822 - /**
2823 - * Returns the whole MIME message.
2824 - * Includes complete headers and body.
2825 - * Only valid post preSend().
2826 - *
2827 - * @return string
2828 - * @see PHPMailer::preSend()
2829 - *
2830 - */
2831 - public function getSentMIMEMessage()
2832 - {
2833 - return static::stripTrailingWSP($this->MIMEHeader.$this->mailHeader).
2834 - static::$LE.static::$LE.$this->MIMEBody;
2835 - }
2836 -
2837 - /**
2838 - * Create a unique ID to use for boundaries.
2839 - *
2840 - * @return string
2841 - */
2842 - protected function generateId()
2843 - {
2844 - $len = 32; //32 bytes = 256 bits
2845 - $bytes = '';
2846 - if (function_exists('random_bytes')) {
2847 - try {
2848 - $bytes = random_bytes($len);
2849 - } catch (\Exception $e) {
2850 - //Do nothing
2851 - }
2852 - } elseif (function_exists('openssl_random_pseudo_bytes')) {
2853 - /** @noinspection CryptographicallySecureRandomnessInspection */
2854 - $bytes = openssl_random_pseudo_bytes($len);
2855 - }
2856 - if ($bytes === '') {
2857 - //We failed to produce a proper random string, so make do.
2858 - //Use a hash to force the length to the same as the other methods
2859 - $bytes = hash('sha256', uniqid((string)mt_rand(), true), true);
2860 - }
2861 -
2862 - //We don't care about messing up base64 format here, just want a random string
2863 - return str_replace(['=', '+', '/'], '', base64_encode(hash('sha256', $bytes, true)));
2864 - }
2865 -
2866 - /**
2867 - * Assemble the message body.
2868 - * Returns an empty string on failure.
2869 - *
2870 - * @return string The assembled message body
2871 - * @throws Exception
2872 - *
2873 - */
2874 - public function createBody()
2875 - {
2876 - $body = '';
2877 - //Create unique IDs and preset boundaries
2878 - $this->setBoundaries();
2879 -
2880 - if ($this->sign_key_file) {
2881 - $body .= $this->getMailMIME().static::$LE;
2882 - }
2883 -
2884 - $this->setWordWrap();
2885 -
2886 - $bodyEncoding = $this->Encoding;
2887 - $bodyCharSet = $this->CharSet;
2888 - //Can we do a 7-bit downgrade?
2889 - if (static::ENCODING_8BIT === $bodyEncoding && !$this->has8bitChars($this->Body)) {
2890 - $bodyEncoding = static::ENCODING_7BIT;
2891 - //All ISO 8859, Windows codepage and UTF-8 charsets are ascii compatible up to 7-bit
2892 - $bodyCharSet = static::CHARSET_ASCII;
2893 - }
2894 - //If lines are too long, and we're not already using an encoding that will shorten them,
2895 - //change to quoted-printable transfer encoding for the body part only
2896 - if (static::ENCODING_BASE64 !== $this->Encoding && static::hasLineLongerThanMax($this->Body)) {
2897 - $bodyEncoding = static::ENCODING_QUOTED_PRINTABLE;
2898 - }
2899 -
2900 - $altBodyEncoding = $this->Encoding;
2901 - $altBodyCharSet = $this->CharSet;
2902 - //Can we do a 7-bit downgrade?
2903 - if (static::ENCODING_8BIT === $altBodyEncoding && !$this->has8bitChars($this->AltBody)) {
2904 - $altBodyEncoding = static::ENCODING_7BIT;
2905 - //All ISO 8859, Windows codepage and UTF-8 charsets are ascii compatible up to 7-bit
2906 - $altBodyCharSet = static::CHARSET_ASCII;
2907 - }
2908 - //If lines are too long, and we're not already using an encoding that will shorten them,
2909 - //change to quoted-printable transfer encoding for the alt body part only
2910 - if (static::ENCODING_BASE64 !== $altBodyEncoding && static::hasLineLongerThanMax($this->AltBody)) {
2911 - $altBodyEncoding = static::ENCODING_QUOTED_PRINTABLE;
2912 - }
2913 - //Use this as a preamble in all multipart message types
2914 - $mimepre = '';
2915 - switch ($this->message_type) {
2916 - case 'inline':
2917 - $body .= $mimepre;
2918 - $body .= $this->getBoundary($this->boundary[1], $bodyCharSet, '', $bodyEncoding);
2919 - $body .= $this->encodeString($this->Body, $bodyEncoding);
2920 - $body .= static::$LE;
2921 - $body .= $this->attachAll('inline', $this->boundary[1]);
2922 - break;
2923 - case 'attach':
2924 - $body .= $mimepre;
2925 - $body .= $this->getBoundary($this->boundary[1], $bodyCharSet, '', $bodyEncoding);
2926 - $body .= $this->encodeString($this->Body, $bodyEncoding);
2927 - $body .= static::$LE;
2928 - $body .= $this->attachAll('attachment', $this->boundary[1]);
2929 - break;
2930 - case 'inline_attach':
2931 - $body .= $mimepre;
2932 - $body .= $this->textLine('--'.$this->boundary[1]);
2933 - $body .= $this->headerLine('Content-Type', static::CONTENT_TYPE_MULTIPART_RELATED.';');
2934 - $body .= $this->textLine(' boundary="'.$this->boundary[2].'";');
2935 - $body .= $this->textLine(' type="'.static::CONTENT_TYPE_TEXT_HTML.'"');
2936 - $body .= static::$LE;
2937 - $body .= $this->getBoundary($this->boundary[2], $bodyCharSet, '', $bodyEncoding);
2938 - $body .= $this->encodeString($this->Body, $bodyEncoding);
2939 - $body .= static::$LE;
2940 - $body .= $this->attachAll('inline', $this->boundary[2]);
2941 - $body .= static::$LE;
2942 - $body .= $this->attachAll('attachment', $this->boundary[1]);
2943 - break;
2944 - case 'alt':
2945 - $body .= $mimepre;
2946 - $body .= $this->getBoundary(
2947 - $this->boundary[1],
2948 - $altBodyCharSet,
2949 - static::CONTENT_TYPE_PLAINTEXT,
2950 - $altBodyEncoding
2951 - );
2952 - $body .= $this->encodeString($this->AltBody, $altBodyEncoding);
2953 - $body .= static::$LE;
2954 - $body .= $this->getBoundary(
2955 - $this->boundary[1],
2956 - $bodyCharSet,
2957 - static::CONTENT_TYPE_TEXT_HTML,
2958 - $bodyEncoding
2959 - );
2960 - $body .= $this->encodeString($this->Body, $bodyEncoding);
2961 - $body .= static::$LE;
2962 - if (!empty($this->Ical)) {
2963 - $method = static::ICAL_METHOD_REQUEST;
2964 - foreach (static::$IcalMethods as $imethod) {
2965 - if (stripos($this->Ical, 'METHOD:'.$imethod) !== false) {
2966 - $method = $imethod;
2967 - break;
2968 - }
2969 - }
2970 - $body .= $this->getBoundary(
2971 - $this->boundary[1],
2972 - '',
2973 - static::CONTENT_TYPE_TEXT_CALENDAR.'; method='.$method,
2974 - ''
2975 - );
2976 - $body .= $this->encodeString($this->Ical, $this->Encoding);
2977 - $body .= static::$LE;
2978 - }
2979 - $body .= $this->endBoundary($this->boundary[1]);
2980 - break;
2981 - case 'alt_inline':
2982 - $body .= $mimepre;
2983 - $body .= $this->getBoundary(
2984 - $this->boundary[1],
2985 - $altBodyCharSet,
2986 - static::CONTENT_TYPE_PLAINTEXT,
2987 - $altBodyEncoding
2988 - );
2989 - $body .= $this->encodeString($this->AltBody, $altBodyEncoding);
2990 - $body .= static::$LE;
2991 - $body .= $this->textLine('--'.$this->boundary[1]);
2992 - $body .= $this->headerLine('Content-Type', static::CONTENT_TYPE_MULTIPART_RELATED.';');
2993 - $body .= $this->textLine(' boundary="'.$this->boundary[2].'";');
2994 - $body .= $this->textLine(' type="'.static::CONTENT_TYPE_TEXT_HTML.'"');
2995 - $body .= static::$LE;
2996 - $body .= $this->getBoundary(
2997 - $this->boundary[2],
2998 - $bodyCharSet,
2999 - static::CONTENT_TYPE_TEXT_HTML,
3000 - $bodyEncoding
3001 - );
3002 - $body .= $this->encodeString($this->Body, $bodyEncoding);
3003 - $body .= static::$LE;
3004 - $body .= $this->attachAll('inline', $this->boundary[2]);
3005 - $body .= static::$LE;
3006 - $body .= $this->endBoundary($this->boundary[1]);
3007 - break;
3008 - case 'alt_attach':
3009 - $body .= $mimepre;
3010 - $body .= $this->textLine('--'.$this->boundary[1]);
3011 - $body .= $this->headerLine('Content-Type', static::CONTENT_TYPE_MULTIPART_ALTERNATIVE.';');
3012 - $body .= $this->textLine(' boundary="'.$this->boundary[2].'"');
3013 - $body .= static::$LE;
3014 - $body .= $this->getBoundary(
3015 - $this->boundary[2],
3016 - $altBodyCharSet,
3017 - static::CONTENT_TYPE_PLAINTEXT,
3018 - $altBodyEncoding
3019 - );
3020 - $body .= $this->encodeString($this->AltBody, $altBodyEncoding);
3021 - $body .= static::$LE;
3022 - $body .= $this->getBoundary(
3023 - $this->boundary[2],
3024 - $bodyCharSet,
3025 - static::CONTENT_TYPE_TEXT_HTML,
3026 - $bodyEncoding
3027 - );
3028 - $body .= $this->encodeString($this->Body, $bodyEncoding);
3029 - $body .= static::$LE;
3030 - if (!empty($this->Ical)) {
3031 - $method = static::ICAL_METHOD_REQUEST;
3032 - foreach (static::$IcalMethods as $imethod) {
3033 - if (stripos($this->Ical, 'METHOD:'.$imethod) !== false) {
3034 - $method = $imethod;
3035 - break;
3036 - }
3037 - }
3038 - $body .= $this->getBoundary(
3039 - $this->boundary[2],
3040 - '',
3041 - static::CONTENT_TYPE_TEXT_CALENDAR.'; method='.$method,
3042 - ''
3043 - );
3044 - $body .= $this->encodeString($this->Ical, $this->Encoding);
3045 - }
3046 - $body .= $this->endBoundary($this->boundary[2]);
3047 - $body .= static::$LE;
3048 - $body .= $this->attachAll('attachment', $this->boundary[1]);
3049 - break;
3050 - case 'alt_inline_attach':
3051 - $body .= $mimepre;
3052 - $body .= $this->textLine('--'.$this->boundary[1]);
3053 - $body .= $this->headerLine('Content-Type', static::CONTENT_TYPE_MULTIPART_ALTERNATIVE.';');
3054 - $body .= $this->textLine(' boundary="'.$this->boundary[2].'"');
3055 - $body .= static::$LE;
3056 - $body .= $this->getBoundary(
3057 - $this->boundary[2],
3058 - $altBodyCharSet,
3059 - static::CONTENT_TYPE_PLAINTEXT,
3060 - $altBodyEncoding
3061 - );
3062 - $body .= $this->encodeString($this->AltBody, $altBodyEncoding);
3063 - $body .= static::$LE;
3064 - $body .= $this->textLine('--'.$this->boundary[2]);
3065 - $body .= $this->headerLine('Content-Type', static::CONTENT_TYPE_MULTIPART_RELATED.';');
3066 - $body .= $this->textLine(' boundary="'.$this->boundary[3].'";');
3067 - $body .= $this->textLine(' type="'.static::CONTENT_TYPE_TEXT_HTML.'"');
3068 - $body .= static::$LE;
3069 - $body .= $this->getBoundary(
3070 - $this->boundary[3],
3071 - $bodyCharSet,
3072 - static::CONTENT_TYPE_TEXT_HTML,
3073 - $bodyEncoding
3074 - );
3075 - $body .= $this->encodeString($this->Body, $bodyEncoding);
3076 - $body .= static::$LE;
3077 - $body .= $this->attachAll('inline', $this->boundary[3]);
3078 - $body .= static::$LE;
3079 - $body .= $this->endBoundary($this->boundary[2]);
3080 - $body .= static::$LE;
3081 - $body .= $this->attachAll('attachment', $this->boundary[1]);
3082 - break;
3083 - default:
3084 - //Catch case 'plain' and case '', applies to simple `text/plain` and `text/html` body content types
3085 - //Reset the `Encoding` property in case we changed it for line length reasons
3086 - $this->Encoding = $bodyEncoding;
3087 - $body .= $this->encodeString($this->Body, $this->Encoding);
3088 - break;
3089 - }
3090 -
3091 - if ($this->isError()) {
3092 - $body = '';
3093 - if ($this->exceptions) {
3094 - throw new Exception($this->lang('empty_message'), self::STOP_CRITICAL);
3095 - }
3096 - } elseif ($this->sign_key_file) {
3097 - try {
3098 - if (!defined('PKCS7_TEXT')) {
3099 - throw new Exception($this->lang('extension_missing').'openssl');
3100 - }
3101 -
3102 - $file = tempnam(sys_get_temp_dir(), 'srcsign');
3103 - $signed = tempnam(sys_get_temp_dir(), 'mailsign');
3104 - file_put_contents($file, $body);
3105 -
3106 - //Workaround for PHP bug https://bugs.php.net/bug.php?id=69197
3107 - if (empty($this->sign_extracerts_file)) {
3108 - $sign = @openssl_pkcs7_sign(
3109 - $file,
3110 - $signed,
3111 - 'file://'.realpath($this->sign_cert_file),
3112 - ['file://'.realpath($this->sign_key_file), $this->sign_key_pass],
3113 - []
3114 - );
3115 - } else {
3116 - $sign = @openssl_pkcs7_sign(
3117 - $file,
3118 - $signed,
3119 - 'file://'.realpath($this->sign_cert_file),
3120 - ['file://'.realpath($this->sign_key_file), $this->sign_key_pass],
3121 - [],
3122 - PKCS7_DETACHED,
3123 - $this->sign_extracerts_file
3124 - );
3125 - }
3126 -
3127 - @unlink($file);
3128 - if ($sign) {
3129 - $body = file_get_contents($signed);
3130 - @unlink($signed);
3131 - //The message returned by openssl contains both headers and body, so need to split them up
3132 - $parts = explode("\n\n", $body, 2);
3133 - $this->MIMEHeader .= $parts[0].static::$LE.static::$LE;
3134 - $body = $parts[1];
3135 - } else {
3136 - @unlink($signed);
3137 - throw new Exception($this->lang('signing').openssl_error_string());
3138 - }
3139 - } catch (Exception $exc) {
3140 - $body = '';
3141 - if ($this->exceptions) {
3142 - throw $exc;
3143 - }
3144 - }
3145 - }
3146 -
3147 - return $body;
3148 - }
3149 -
3150 - /**
3151 - * Get the boundaries that this message will use
3152 - * @return array
3153 - */
3154 - public function getBoundaries()
3155 - {
3156 - if (empty($this->boundary)) {
3157 - $this->setBoundaries();
3158 - }
3159 -
3160 - return $this->boundary;
3161 - }
3162 -
3163 - /**
3164 - * Return the start of a message boundary.
3165 - *
3166 - * @param string $boundary
3167 - * @param string $charSet
3168 - * @param string $contentType
3169 - * @param string $encoding
3170 - *
3171 - * @return string
3172 - */
3173 - protected function getBoundary($boundary, $charSet, $contentType, $encoding)
3174 - {
3175 - $result = '';
3176 - if ('' === $charSet) {
3177 - $charSet = $this->CharSet;
3178 - }
3179 - if ('' === $contentType) {
3180 - $contentType = $this->ContentType;
3181 - }
3182 - if ('' === $encoding) {
3183 - $encoding = $this->Encoding;
3184 - }
3185 - $result .= $this->textLine('--'.$boundary);
3186 - $result .= sprintf('Content-Type: %s; charset=%s', $contentType, $charSet);
3187 - $result .= static::$LE;
3188 - //RFC1341 part 5 says 7bit is assumed if not specified
3189 - if (static::ENCODING_7BIT !== $encoding) {
3190 - $result .= $this->headerLine('Content-Transfer-Encoding', $encoding);
3191 - }
3192 - $result .= static::$LE;
3193 -
3194 - return $result;
3195 - }
3196 -
3197 - /**
3198 - * Return the end of a message boundary.
3199 - *
3200 - * @param string $boundary
3201 - *
3202 - * @return string
3203 - */
3204 - protected function endBoundary($boundary)
3205 - {
3206 - return static::$LE.'--'.$boundary.'--'.static::$LE;
3207 - }
3208 -
3209 - /**
3210 - * Set the message type.
3211 - * PHPMailer only supports some preset message types, not arbitrary MIME structures.
3212 - */
3213 - protected function setMessageType()
3214 - {
3215 - $type = [];
3216 - if ($this->alternativeExists()) {
3217 - $type[] = 'alt';
3218 - }
3219 - if ($this->inlineImageExists()) {
3220 - $type[] = 'inline';
3221 - }
3222 - if ($this->attachmentExists()) {
3223 - $type[] = 'attach';
3224 - }
3225 - $this->message_type = implode('_', $type);
3226 - if ('' === $this->message_type) {
3227 - //The 'plain' message_type refers to the message having a single body element, not that it is plain-text
3228 - $this->message_type = 'plain';
3229 - }
3230 - }
3231 -
3232 - /**
3233 - * Format a header line.
3234 - *
3235 - * @param string $name
3236 - * @param string|int $value
3237 - *
3238 - * @return string
3239 - */
3240 - public function headerLine($name, $value)
3241 - {
3242 - return $name.': '.$value.static::$LE;
3243 - }
3244 -
3245 - /**
3246 - * Return a formatted mail line.
3247 - *
3248 - * @param string $value
3249 - *
3250 - * @return string
3251 - */
3252 - public function textLine($value)
3253 - {
3254 - return $value.static::$LE;
3255 - }
3256 -
3257 - /**
3258 - * Add an attachment from a path on the filesystem.
3259 - * Never use a user-supplied path to a file!
3260 - * Returns false if the file could not be found or read.
3261 - * Explicitly *does not* support passing URLs; PHPMailer is not an HTTP client.
3262 - * If you need to do that, fetch the resource yourself and pass it in via a local file or string.
3263 - *
3264 - * @param string $path Path to the attachment
3265 - * @param string $name Overrides the attachment name
3266 - * @param string $encoding File encoding (see $Encoding)
3267 - * @param string $type MIME type, e.g. `image/jpeg`; determined automatically from $path if not specified
3268 - * @param string $disposition Disposition to use
3269 - *
3270 - * @return bool
3271 - * @throws Exception
3272 - *
3273 - */
3274 - public function addAttachment(
3275 - $path,
3276 - $name = '',
3277 - $encoding = self::ENCODING_BASE64,
3278 - $type = '',
3279 - $disposition = 'attachment'
3280 - ) {
3281 - try {
3282 - if (!static::fileIsAccessible($path)) {
3283 - throw new Exception($this->lang('file_access').$path, self::STOP_CONTINUE);
3284 - }
3285 -
3286 - //If a MIME type is not specified, try to work it out from the file name
3287 - if ('' === $type) {
3288 - $type = static::filenameToType($path);
3289 - }
3290 -
3291 - $filename = (string)static::mb_pathinfo($path, PATHINFO_BASENAME);
3292 - if ('' === $name) {
3293 - $name = $filename;
3294 - }
3295 - if (!$this->validateEncoding($encoding)) {
3296 - throw new Exception($this->lang('encoding').$encoding);
3297 - }
3298 -
3299 - $this->attachment[] = [
3300 - 0 => $path,
3301 - 1 => $filename,
3302 - 2 => $name,
3303 - 3 => $encoding,
3304 - 4 => $type,
3305 - 5 => false, //isStringAttachment
3306 - 6 => $disposition,
3307 - 7 => $name,
3308 - ];
3309 - } catch (Exception $exc) {
3310 - $this->setError($exc->getMessage());
3311 - $this->edebug($exc->getMessage());
3312 - if ($this->exceptions) {
3313 - throw $exc;
3314 - }
3315 -
3316 - return false;
3317 - }
3318 -
3319 - return true;
3320 - }
3321 -
3322 - /**
3323 - * Return the array of attachments.
3324 - *
3325 - * @return array
3326 - */
3327 - public function getAttachments()
3328 - {
3329 - return $this->attachment;
3330 - }
3331 -
3332 - /**
3333 - * Attach all file, string, and binary attachments to the message.
3334 - * Returns an empty string on failure.
3335 - *
3336 - * @param string $disposition_type
3337 - * @param string $boundary
3338 - *
3339 - * @return string
3340 - * @throws Exception
3341 - *
3342 - */
3343 - protected function attachAll($disposition_type, $boundary)
3344 - {
3345 - //Return text of body
3346 - $mime = [];
3347 - $cidUniq = [];
3348 - $incl = [];
3349 -
3350 - //Add all attachments
3351 - foreach ($this->attachment as $attachment) {
3352 - //Check if it is a valid disposition_filter
3353 - if ($attachment[6] === $disposition_type) {
3354 - //Check for string attachment
3355 - $string = '';
3356 - $path = '';
3357 - $bString = $attachment[5];
3358 - if ($bString) {
3359 - $string = $attachment[0];
3360 - } else {
3361 - $path = $attachment[0];
3362 - }
3363 -
3364 - $inclhash = hash('sha256', serialize($attachment));
3365 - if (in_array($inclhash, $incl, true)) {
3366 - continue;
3367 - }
3368 - $incl[] = $inclhash;
3369 - $name = $attachment[2];
3370 - $encoding = $attachment[3];
3371 - $type = $attachment[4];
3372 - $disposition = $attachment[6];
3373 - $cid = $attachment[7];
3374 - if ('inline' === $disposition && array_key_exists($cid, $cidUniq)) {
3375 - continue;
3376 - }
3377 - $cidUniq[$cid] = true;
3378 -
3379 - $mime[] = sprintf('--%s%s', $boundary, static::$LE);
3380 - //Only include a filename property if we have one
3381 - if (!empty($name)) {
3382 - $mime[] = sprintf(
3383 - 'Content-Type: %s; name=%s%s',
3384 - $type,
3385 - static::quotedString($this->encodeHeader($this->secureHeader($name))),
3386 - static::$LE
3387 - );
3388 - } else {
3389 - $mime[] = sprintf(
3390 - 'Content-Type: %s%s',
3391 - $type,
3392 - static::$LE
3393 - );
3394 - }
3395 - //RFC1341 part 5 says 7bit is assumed if not specified
3396 - if (static::ENCODING_7BIT !== $encoding) {
3397 - $mime[] = sprintf('Content-Transfer-Encoding: %s%s', $encoding, static::$LE);
3398 - }
3399 -
3400 - //Only set Content-IDs on inline attachments
3401 - if ((string)$cid !== '' && $disposition === 'inline') {
3402 - $mime[] = 'Content-ID: <'.$this->encodeHeader($this->secureHeader($cid)).'>'.static::$LE;
3403 - }
3404 -
3405 - //Allow for bypassing the Content-Disposition header
3406 - if (!empty($disposition)) {
3407 - $encoded_name = $this->encodeHeader($this->secureHeader($name));
3408 - if (!empty($encoded_name)) {
3409 - $mime[] = sprintf(
3410 - 'Content-Disposition: %s; filename=%s%s',
3411 - $disposition,
3412 - static::quotedString($encoded_name),
3413 - static::$LE.static::$LE
3414 - );
3415 - } else {
3416 - $mime[] = sprintf(
3417 - 'Content-Disposition: %s%s',
3418 - $disposition,
3419 - static::$LE.static::$LE
3420 - );
3421 - }
3422 - } else {
3423 - $mime[] = static::$LE;
3424 - }
3425 -
3426 - //Encode as string attachment
3427 - if ($bString) {
3428 - $mime[] = $this->encodeString($string, $encoding);
3429 - } else {
3430 - $mime[] = $this->encodeFile($path, $encoding);
3431 - }
3432 - if ($this->isError()) {
3433 - return '';
3434 - }
3435 - $mime[] = static::$LE;
3436 - }
3437 - }
3438 -
3439 - $mime[] = sprintf('--%s--%s', $boundary, static::$LE);
3440 -
3441 - return implode('', $mime);
3442 - }
3443 -
3444 - /**
3445 - * Encode a file attachment in requested format.
3446 - * Returns an empty string on failure.
3447 - *
3448 - * @param string $path The full path to the file
3449 - * @param string $encoding The encoding to use; one of 'base64', '7bit', '8bit', 'binary', 'quoted-printable'
3450 - *
3451 - * @return string
3452 - */
3453 - protected function encodeFile($path, $encoding = self::ENCODING_BASE64)
3454 - {
3455 - try {
3456 - if (!static::fileIsAccessible($path)) {
3457 - throw new Exception($this->lang('file_open').$path, self::STOP_CONTINUE);
3458 - }
3459 - $file_buffer = file_get_contents($path);
3460 - if (false === $file_buffer) {
3461 - throw new Exception($this->lang('file_open').$path, self::STOP_CONTINUE);
3462 - }
3463 - $file_buffer = $this->encodeString($file_buffer, $encoding);
3464 -
3465 - return $file_buffer;
3466 - } catch (Exception $exc) {
3467 - $this->setError($exc->getMessage());
3468 - $this->edebug($exc->getMessage());
3469 - if ($this->exceptions) {
3470 - throw $exc;
3471 - }
3472 -
3473 - return '';
3474 - }
3475 - }
3476 -
3477 - /**
3478 - * Encode a string in requested format.
3479 - * Returns an empty string on failure.
3480 - *
3481 - * @param string $str The text to encode
3482 - * @param string $encoding The encoding to use; one of 'base64', '7bit', '8bit', 'binary', 'quoted-printable'
3483 - *
3484 - * @return string
3485 - * @throws Exception
3486 - *
3487 - */
3488 - public function encodeString($str, $encoding = self::ENCODING_BASE64)
3489 - {
3490 - $encoded = '';
3491 - switch (strtolower($encoding)) {
3492 - case static::ENCODING_BASE64:
3493 - $encoded = chunk_split(
3494 - base64_encode($str),
3495 - static::STD_LINE_LENGTH,
3496 - static::$LE
3497 - );
3498 - break;
3499 - case static::ENCODING_7BIT:
3500 - case static::ENCODING_8BIT:
3501 - $encoded = static::normalizeBreaks($str);
3502 - //Make sure it ends with a line break
3503 - if (substr($encoded, -(strlen(static::$LE))) !== static::$LE) {
3504 - $encoded .= static::$LE;
3505 - }
3506 - break;
3507 - case static::ENCODING_BINARY:
3508 - $encoded = $str;
3509 - break;
3510 - case static::ENCODING_QUOTED_PRINTABLE:
3511 - $encoded = $this->encodeQP($str);
3512 - break;
3513 - default:
3514 - $this->setError($this->lang('encoding').$encoding);
3515 - if ($this->exceptions) {
3516 - throw new Exception($this->lang('encoding').$encoding);
3517 - }
3518 - break;
3519 - }
3520 -
3521 - return $encoded;
3522 - }
3523 -
3524 - /**
3525 - * Encode a header value (not including its label) optimally.
3526 - * Picks shortest of Q, B, or none. Result includes folding if needed.
3527 - * See RFC822 definitions for phrase, comment and text positions.
3528 - *
3529 - * @param string $str The header value to encode
3530 - * @param string $position What context the string will be used in
3531 - *
3532 - * @return string
3533 - */
3534 - public function encodeHeader($str, $position = 'text')
3535 - {
3536 - $matchcount = 0;
3537 - switch (strtolower($position)) {
3538 - case 'phrase':
3539 - if (!preg_match('/[\200-\377]/', $str)) {
3540 - //Can't use addslashes as we don't know the value of magic_quotes_sybase
3541 - $encoded = addcslashes($str, "\0..\37\177\\\"");
3542 - if (($str === $encoded) && !preg_match('/[^A-Za-z0-9!#$%&\'*+\/=?^_`{|}~ -]/', $str)) {
3543 - return $encoded;
3544 - }
3545 -
3546 - return "\"$encoded\"";
3547 - }
3548 - $matchcount = preg_match_all('/[^\040\041\043-\133\135-\176]/', $str, $matches);
3549 - break;
3550 - /* @noinspection PhpMissingBreakStatementInspection */
3551 - case 'comment':
3552 - $matchcount = preg_match_all('/[()"]/', $str, $matches);
3553 - //fallthrough
3554 - case 'text':
3555 - default:
3556 - $matchcount += preg_match_all('/[\000-\010\013\014\016-\037\177-\377]/', $str, $matches);
3557 - break;
3558 - }
3559 -
3560 - if ($this->has8bitChars($str)) {
3561 - $charset = $this->CharSet;
3562 - } else {
3563 - $charset = static::CHARSET_ASCII;
3564 - }
3565 -
3566 - //Q/B encoding adds 8 chars and the charset ("` =?<charset>?[QB]?<content>?=`").
3567 - $overhead = 8 + strlen($charset);
3568 -
3569 - if ('mail' === $this->Mailer) {
3570 - $maxlen = static::MAIL_MAX_LINE_LENGTH - $overhead;
3571 - } else {
3572 - $maxlen = static::MAX_LINE_LENGTH - $overhead;
3573 - }
3574 -
3575 - //Select the encoding that produces the shortest output and/or prevents corruption.
3576 - if ($matchcount > strlen($str) / 3) {
3577 - //More than 1/3 of the content needs encoding, use B-encode.
3578 - $encoding = 'B';
3579 - } elseif ($matchcount > 0) {
3580 - //Less than 1/3 of the content needs encoding, use Q-encode.
3581 - $encoding = 'Q';
3582 - } elseif (strlen($str) > $maxlen) {
3583 - //No encoding needed, but value exceeds max line length, use Q-encode to prevent corruption.
3584 - $encoding = 'Q';
3585 - } else {
3586 - //No reformatting needed
3587 - $encoding = false;
3588 - }
3589 -
3590 - switch ($encoding) {
3591 - case 'B':
3592 - if ($this->hasMultiBytes($str)) {
3593 - //Use a custom function which correctly encodes and wraps long
3594 - //multibyte strings without breaking lines within a character
3595 - $encoded = $this->base64EncodeWrapMB($str, "\n");
3596 - } else {
3597 - $encoded = base64_encode($str);
3598 - $maxlen -= $maxlen % 4;
3599 - $encoded = trim(chunk_split($encoded, $maxlen, "\n"));
3600 - }
3601 - $encoded = preg_replace('/^(.*)$/m', ' =?'.$charset."?$encoding?\\1?=", $encoded);
3602 - break;
3603 - case 'Q':
3604 - $encoded = $this->encodeQ($str, $position);
3605 - $encoded = $this->wrapText($encoded, $maxlen, true);
3606 - $encoded = str_replace('='.static::$LE, "\n", trim($encoded));
3607 - $encoded = preg_replace('/^(.*)$/m', ' =?'.$charset."?$encoding?\\1?=", $encoded);
3608 - break;
3609 - default:
3610 - return $str;
3611 - }
3612 -
3613 - return trim(static::normalizeBreaks($encoded));
3614 - }
3615 -
3616 - /**
3617 - * Check if a string contains multi-byte characters.
3618 - *
3619 - * @param string $str multi-byte text to wrap encode
3620 - *
3621 - * @return bool
3622 - */
3623 - public function hasMultiBytes($str)
3624 - {
3625 - if (function_exists('mb_strlen')) {
3626 - return strlen($str) > mb_strlen($str, $this->CharSet);
3627 - }
3628 -
3629 - //Assume no multibytes (we can't handle without mbstring functions anyway)
3630 - return false;
3631 - }
3632 -
3633 - /**
3634 - * Does a string contain any 8-bit chars (in any charset)?
3635 - *
3636 - * @param string $text
3637 - *
3638 - * @return bool
3639 - */
3640 - public function has8bitChars($text)
3641 - {
3642 - return (bool)preg_match('/[\x80-\xFF]/', $text);
3643 - }
3644 -
3645 - /**
3646 - * Encode and wrap long multibyte strings for mail headers
3647 - * without breaking lines within a character.
3648 - * Adapted from a function by paravoid.
3649 - *
3650 - * @see https://www.php.net/manual/en/function.mb-encode-mimeheader.php#60283
3651 - *
3652 - * @param string $str multi-byte text to wrap encode
3653 - * @param string $linebreak string to use as linefeed/end-of-line
3654 - *
3655 - * @return string
3656 - */
3657 - public function base64EncodeWrapMB($str, $linebreak = null)
3658 - {
3659 - $start = '=?'.$this->CharSet.'?B?';
3660 - $end = '?=';
3661 - $encoded = '';
3662 - if (null === $linebreak) {
3663 - $linebreak = static::$LE;
3664 - }
3665 -
3666 - $mb_length = mb_strlen($str, $this->CharSet);
3667 - //Each line must have length <= 75, including $start and $end
3668 - $length = 75 - strlen($start) - strlen($end);
3669 - //Average multi-byte ratio
3670 - $ratio = $mb_length / strlen($str);
3671 - //Base64 has a 4:3 ratio
3672 - $avgLength = floor($length * $ratio * .75);
3673 -
3674 - $offset = 0;
3675 - for ($i = 0; $i < $mb_length; $i += $offset) {
3676 - $lookBack = 0;
3677 - do {
3678 - $offset = $avgLength - $lookBack;
3679 - $chunk = mb_substr($str, $i, $offset, $this->CharSet);
3680 - $chunk = base64_encode($chunk);
3681 - ++$lookBack;
3682 - } while (strlen($chunk) > $length);
3683 - $encoded .= $chunk.$linebreak;
3684 - }
3685 -
3686 - //Chomp the last linefeed
3687 - return substr($encoded, 0, -strlen($linebreak));
3688 - }
3689 -
3690 - /**
3691 - * Encode a string in quoted-printable format.
3692 - * According to RFC2045 section 6.7.
3693 - *
3694 - * @param string $string The text to encode
3695 - *
3696 - * @return string
3697 - */
3698 - public function encodeQP($string)
3699 - {
3700 - return static::normalizeBreaks(quoted_printable_encode($string));
3701 - }
3702 -
3703 - /**
3704 - * Encode a string using Q encoding.
3705 - *
3706 - * @see https://www.rfc-editor.org/rfc/rfc2047#section-4.2
3707 - *
3708 - * @param string $str the text to encode
3709 - * @param string $position Where the text is going to be used, see the RFC for what that means
3710 - *
3711 - * @return string
3712 - */
3713 - public function encodeQ($str, $position = 'text')
3714 - {
3715 - //There should not be any EOL in the string
3716 - $pattern = '';
3717 - $encoded = str_replace(["\r", "\n"], '', $str);
3718 - switch (strtolower($position)) {
3719 - case 'phrase':
3720 - //RFC 2047 section 5.3
3721 - $pattern = '^A-Za-z0-9!*+\/ -';
3722 - break;
3723 - /*
3724 - * RFC 2047 section 5.2.
3725 - * Build $pattern without including delimiters and []
3726 - */
3727 - /* @noinspection PhpMissingBreakStatementInspection */
3728 - case 'comment':
3729 - $pattern = '\(\)"';
3730 - /* Intentional fall through */
3731 - case 'text':
3732 - default:
3733 - //RFC 2047 section 5.1
3734 - //Replace every high ascii, control, =, ? and _ characters
3735 - $pattern = '\000-\011\013\014\016-\037\075\077\137\177-\377'.$pattern;
3736 - break;
3737 - }
3738 - $matches = [];
3739 - if (preg_match_all("/[{$pattern}]/", $encoded, $matches)) {
3740 - //If the string contains an '=', make sure it's the first thing we replace
3741 - //so as to avoid double-encoding
3742 - $eqkey = array_search('=', $matches[0], true);
3743 - if (false !== $eqkey) {
3744 - unset($matches[0][$eqkey]);
3745 - array_unshift($matches[0], '=');
3746 - }
3747 - foreach (array_unique($matches[0]) as $char) {
3748 - $encoded = str_replace($char, '='.sprintf('%02X', ord($char)), $encoded);
3749 - }
3750 - }
3751 - //Replace spaces with _ (more readable than =20)
3752 - //RFC 2047 section 4.2(2)
3753 - return str_replace(' ', '_', $encoded);
3754 - }
3755 -
3756 - /**
3757 - * Add a string or binary attachment (non-filesystem).
3758 - * This method can be used to attach ascii or binary data,
3759 - * such as a BLOB record from a database.
3760 - *
3761 - * @param string $string String attachment data
3762 - * @param string $filename Name of the attachment
3763 - * @param string $encoding File encoding (see $Encoding)
3764 - * @param string $type File extension (MIME) type
3765 - * @param string $disposition Disposition to use
3766 - *
3767 - * @return bool True on successfully adding an attachment
3768 - * @throws Exception
3769 - *
3770 - */
3771 - public function addStringAttachment(
3772 - $string,
3773 - $filename,
3774 - $encoding = self::ENCODING_BASE64,
3775 - $type = '',
3776 - $disposition = 'attachment'
3777 - ) {
3778 - try {
3779 - //If a MIME type is not specified, try to work it out from the file name
3780 - if ('' === $type) {
3781 - $type = static::filenameToType($filename);
3782 - }
3783 -
3784 - if (!$this->validateEncoding($encoding)) {
3785 - throw new Exception($this->lang('encoding').$encoding);
3786 - }
3787 -
3788 - //Append to $attachment array
3789 - $this->attachment[] = [
3790 - 0 => $string,
3791 - 1 => $filename,
3792 - 2 => static::mb_pathinfo($filename, PATHINFO_BASENAME),
3793 - 3 => $encoding,
3794 - 4 => $type,
3795 - 5 => true, //isStringAttachment
3796 - 6 => $disposition,
3797 - 7 => 0,
3798 - ];
3799 - } catch (Exception $exc) {
3800 - $this->setError($exc->getMessage());
3801 - $this->edebug($exc->getMessage());
3802 - if ($this->exceptions) {
3803 - throw $exc;
3804 - }
3805 -
3806 - return false;
3807 - }
3808 -
3809 - return true;
3810 - }
3811 -
3812 - /**
3813 - * Add an embedded (inline) attachment from a file.
3814 - * This can include images, sounds, and just about any other document type.
3815 - * These differ from 'regular' attachments in that they are intended to be
3816 - * displayed inline with the message, not just attached for download.
3817 - * This is used in HTML messages that embed the images
3818 - * the HTML refers to using the `$cid` value in `img` tags, for example `<img src="cid:mylogo">`.
3819 - * Never use a user-supplied path to a file!
3820 - *
3821 - * @param string $path Path to the attachment
3822 - * @param string $cid Content ID of the attachment; Use this to reference
3823 - * the content when using an embedded image in HTML
3824 - * @param string $name Overrides the attachment filename
3825 - * @param string $encoding File encoding (see $Encoding) defaults to `base64`
3826 - * @param string $type File MIME type (by default mapped from the `$path` filename's extension)
3827 - * @param string $disposition Disposition to use: `inline` (default) or `attachment`
3828 - * (unlikely you want this – {@see `addAttachment()`} instead)
3829 - *
3830 - * @return bool True on successfully adding an attachment
3831 - * @throws Exception
3832 - *
3833 - */
3834 - public function addEmbeddedImage(
3835 - $path,
3836 - $cid,
3837 - $name = '',
3838 - $encoding = self::ENCODING_BASE64,
3839 - $type = '',
3840 - $disposition = 'inline'
3841 - ) {
3842 - try {
3843 - if (!static::fileIsAccessible($path)) {
3844 - throw new Exception($this->lang('file_access').$path, self::STOP_CONTINUE);
3845 - }
3846 -
3847 - //If a MIME type is not specified, try to work it out from the file name
3848 - if ('' === $type) {
3849 - $type = static::filenameToType($path);
3850 - }
3851 -
3852 - if (!$this->validateEncoding($encoding)) {
3853 - throw new Exception($this->lang('encoding').$encoding);
3854 - }
3855 -
3856 - $filename = (string)static::mb_pathinfo($path, PATHINFO_BASENAME);
3857 - if ('' === $name) {
3858 - $name = $filename;
3859 - }
3860 -
3861 - //Append to $attachment array
3862 - $this->attachment[] = [
3863 - 0 => $path,
3864 - 1 => $filename,
3865 - 2 => $name,
3866 - 3 => $encoding,
3867 - 4 => $type,
3868 - 5 => false, //isStringAttachment
3869 - 6 => $disposition,
3870 - 7 => $cid,
3871 - ];
3872 - } catch (Exception $exc) {
3873 - $this->setError($exc->getMessage());
3874 - $this->edebug($exc->getMessage());
3875 - if ($this->exceptions) {
3876 - throw $exc;
3877 - }
3878 -
3879 - return false;
3880 - }
3881 -
3882 - return true;
3883 - }
3884 -
3885 - /**
3886 - * Add an embedded stringified attachment.
3887 - * This can include images, sounds, and just about any other document type.
3888 - * If your filename doesn't contain an extension, be sure to set the $type to an appropriate MIME type.
3889 - *
3890 - * @param string $string The attachment binary data
3891 - * @param string $cid Content ID of the attachment; Use this to reference
3892 - * the content when using an embedded image in HTML
3893 - * @param string $name A filename for the attachment. If this contains an extension,
3894 - * PHPMailer will attempt to set a MIME type for the attachment.
3895 - * For example 'file.jpg' would get an 'image/jpeg' MIME type.
3896 - * @param string $encoding File encoding (see $Encoding), defaults to 'base64'
3897 - * @param string $type MIME type - will be used in preference to any automatically derived type
3898 - * @param string $disposition Disposition to use
3899 - *
3900 - * @return bool True on successfully adding an attachment
3901 - * @throws Exception
3902 - *
3903 - */
3904 - public function addStringEmbeddedImage(
3905 - $string,
3906 - $cid,
3907 - $name = '',
3908 - $encoding = self::ENCODING_BASE64,
3909 - $type = '',
3910 - $disposition = 'inline'
3911 - ) {
3912 - try {
3913 - //If a MIME type is not specified, try to work it out from the name
3914 - if ('' === $type && !empty($name)) {
3915 - $type = static::filenameToType($name);
3916 - }
3917 -
3918 - if (!$this->validateEncoding($encoding)) {
3919 - throw new Exception($this->lang('encoding').$encoding);
3920 - }
3921 -
3922 - //Append to $attachment array
3923 - $this->attachment[] = [
3924 - 0 => $string,
3925 - 1 => $name,
3926 - 2 => $name,
3927 - 3 => $encoding,
3928 - 4 => $type,
3929 - 5 => true, //isStringAttachment
3930 - 6 => $disposition,
3931 - 7 => $cid,
3932 - ];
3933 - } catch (Exception $exc) {
3934 - $this->setError($exc->getMessage());
3935 - $this->edebug($exc->getMessage());
3936 - if ($this->exceptions) {
3937 - throw $exc;
3938 - }
3939 -
3940 - return false;
3941 - }
3942 -
3943 - return true;
3944 - }
3945 -
3946 - /**
3947 - * Validate encodings.
3948 - *
3949 - * @param string $encoding
3950 - *
3951 - * @return bool
3952 - */
3953 - protected function validateEncoding($encoding)
3954 - {
3955 - return in_array(
3956 - $encoding,
3957 - [
3958 - self::ENCODING_7BIT,
3959 - self::ENCODING_QUOTED_PRINTABLE,
3960 - self::ENCODING_BASE64,
3961 - self::ENCODING_8BIT,
3962 - self::ENCODING_BINARY,
3963 - ],
3964 - true
3965 - );
3966 - }
3967 -
3968 - /**
3969 - * Check if an embedded attachment is present with this cid.
3970 - *
3971 - * @param string $cid
3972 - *
3973 - * @return bool
3974 - */
3975 - protected function cidExists($cid)
3976 - {
3977 - foreach ($this->attachment as $attachment) {
3978 - if ('inline' === $attachment[6] && $cid === $attachment[7]) {
3979 - return true;
3980 - }
3981 - }
3982 -
3983 - return false;
3984 - }
3985 -
3986 - /**
3987 - * Check if an inline attachment is present.
3988 - *
3989 - * @return bool
3990 - */
3991 - public function inlineImageExists()
3992 - {
3993 - foreach ($this->attachment as $attachment) {
3994 - if ('inline' === $attachment[6]) {
3995 - return true;
3996 - }
3997 - }
3998 -
3999 - return false;
4000 - }
4001 -
4002 - /**
4003 - * Check if an attachment (non-inline) is present.
4004 - *
4005 - * @return bool
4006 - */
4007 - public function attachmentExists()
4008 - {
4009 - foreach ($this->attachment as $attachment) {
4010 - if ('attachment' === $attachment[6]) {
4011 - return true;
4012 - }
4013 - }
4014 -
4015 - return false;
4016 - }
4017 -
4018 - /**
4019 - * Check if this message has an alternative body set.
4020 - *
4021 - * @return bool
4022 - */
4023 - public function alternativeExists()
4024 - {
4025 - return !empty($this->AltBody);
4026 - }
4027 -
4028 - /**
4029 - * Clear queued addresses of given kind.
4030 - *
4031 - * @param string $kind 'to', 'cc', or 'bcc'
4032 - */
4033 - public function clearQueuedAddresses($kind)
4034 - {
4035 - $this->RecipientsQueue = array_filter(
4036 - $this->RecipientsQueue,
4037 - static function ($params) use ($kind) {
4038 - return $params[0] !== $kind;
4039 - }
4040 - );
4041 - }
4042 -
4043 - /**
4044 - * Clear all To recipients.
4045 - */
4046 - public function clearAddresses()
4047 - {
4048 - foreach ($this->to as $to) {
4049 - unset($this->all_recipients[strtolower($to[0])]);
4050 - }
4051 - $this->to = [];
4052 - $this->clearQueuedAddresses('to');
4053 - }
4054 -
4055 - /**
4056 - * Clear all CC recipients.
4057 - */
4058 - public function clearCCs()
4059 - {
4060 - foreach ($this->cc as $cc) {
4061 - unset($this->all_recipients[strtolower($cc[0])]);
4062 - }
4063 - $this->cc = [];
4064 - $this->clearQueuedAddresses('cc');
4065 - }
4066 -
4067 - /**
4068 - * Clear all BCC recipients.
4069 - */
4070 - public function clearBCCs()
4071 - {
4072 - foreach ($this->bcc as $bcc) {
4073 - unset($this->all_recipients[strtolower($bcc[0])]);
4074 - }
4075 - $this->bcc = [];
4076 - $this->clearQueuedAddresses('bcc');
4077 - }
4078 -
4079 - /**
4080 - * Clear all ReplyTo recipients.
4081 - */
4082 - public function clearReplyTos()
4083 - {
4084 - $this->ReplyTo = [];
4085 - $this->ReplyToQueue = [];
4086 - }
4087 -
4088 - /**
4089 - * Clear all recipient types.
4090 - */
4091 - public function clearAllRecipients()
4092 - {
4093 - $this->to = [];
4094 - $this->cc = [];
4095 - $this->bcc = [];
4096 - $this->all_recipients = [];
4097 - $this->RecipientsQueue = [];
4098 - }
4099 -
4100 - /**
4101 - * Clear all filesystem, string, and binary attachments.
4102 - */
4103 - public function clearAttachments()
4104 - {
4105 - $this->attachment = [];
4106 - }
4107 -
4108 - /**
4109 - * Clear all custom headers.
4110 - */
4111 - public function clearCustomHeaders()
4112 - {
4113 - $this->CustomHeader = [];
4114 - }
4115 -
4116 - /**
4117 - * Clear a specific custom header by name or name and value.
4118 - * $name value can be overloaded to contain
4119 - * both header name and value (name:value).
4120 - *
4121 - * @param string $name Custom header name
4122 - * @param string|null $value Header value
4123 - *
4124 - * @return bool True if a header was replaced successfully
4125 - */
4126 - public function clearCustomHeader($name, $value = null)
4127 - {
4128 - if (null === $value && strpos($name, ':') !== false) {
4129 - //Value passed in as name:value
4130 - [$name, $value] = explode(':', $name, 2);
4131 - }
4132 - $name = trim($name);
4133 - $value = (null === $value) ? null : trim($value);
4134 -
4135 - foreach ($this->CustomHeader as $k => $pair) {
4136 - if ($pair[0] == $name) {
4137 - // We remove the header if the value is not provided or it matches.
4138 - if (null === $value || $pair[1] == $value) {
4139 - unset($this->CustomHeader[$k]);
4140 - }
4141 - }
4142 - }
4143 -
4144 - return true;
4145 - }
4146 -
4147 - /**
4148 - * Replace a custom header.
4149 - * $name value can be overloaded to contain
4150 - * both header name and value (name:value).
4151 - *
4152 - * @param string $name Custom header name
4153 - * @param string|null $value Header value
4154 - *
4155 - * @return bool True if a header was replaced successfully
4156 - * @throws Exception
4157 - */
4158 - public function replaceCustomHeader($name, $value = null)
4159 - {
4160 - if (null === $value && strpos($name, ':') !== false) {
4161 - //Value passed in as name:value
4162 - [$name, $value] = explode(':', $name, 2);
4163 - }
4164 - $name = trim($name);
4165 - $value = (null === $value) ? '' : trim($value);
4166 -
4167 - $replaced = false;
4168 - foreach ($this->CustomHeader as $k => $pair) {
4169 - if ($pair[0] == $name) {
4170 - if ($replaced) {
4171 - unset($this->CustomHeader[$k]);
4172 - continue;
4173 - }
4174 - if (strpbrk($name.$value, "\r\n") !== false) {
4175 - if ($this->exceptions) {
4176 - throw new Exception($this->lang('invalid_header'));
4177 - }
4178 -
4179 - return false;
4180 - }
4181 - $this->CustomHeader[$k] = [$name, $value];
4182 - $replaced = true;
4183 - }
4184 - }
4185 -
4186 - return true;
4187 - }
4188 -
4189 - /**
4190 - * Add an error message to the error container.
4191 - *
4192 - * @param string $msg
4193 - */
4194 - protected function setError($msg)
4195 - {
4196 - ++$this->error_count;
4197 - if ('smtp' === $this->Mailer && null !== $this->smtp) {
4198 - $lasterror = $this->smtp->getError();
4199 - if (!empty($lasterror['error'])) {
4200 - $msg .= $this->lang('smtp_error').$lasterror['error'];
4201 - if (!empty($lasterror['detail'])) {
4202 - $msg .= ' '.$this->lang('smtp_detail').$lasterror['detail'];
4203 - }
4204 - if (!empty($lasterror['smtp_code'])) {
4205 - $msg .= ' '.$this->lang('smtp_code').$lasterror['smtp_code'];
4206 - }
4207 - if (!empty($lasterror['smtp_code_ex'])) {
4208 - $msg .= ' '.$this->lang('smtp_code_ex').$lasterror['smtp_code_ex'];
4209 - }
4210 - }
4211 - }
4212 - $this->ErrorInfo = $msg;
4213 - }
4214 -
4215 - /**
4216 - * Return an RFC 822 formatted date.
4217 - *
4218 - * @return string
4219 - */
4220 - public static function rfcDate()
4221 - {
4222 - //Set the time zone to whatever the default is to avoid 500 errors
4223 - //Will default to UTC if it's not set properly in php.ini
4224 - date_default_timezone_set(@date_default_timezone_get());
4225 -
4226 - return date('D, j M Y H:i:s O');
4227 - }
4228 -
4229 - /**
4230 - * Get the server hostname.
4231 - * Returns 'localhost.localdomain' if unknown.
4232 - *
4233 - * @return string
4234 - */
4235 - protected function serverHostname()
4236 - {
4237 - $result = '';
4238 - if (!empty($this->Hostname)) {
4239 - $result = $this->Hostname;
4240 - } elseif (isset($_SERVER) && array_key_exists('SERVER_NAME', $_SERVER)) {
4241 - $result = $_SERVER['SERVER_NAME'];
4242 - } elseif (function_exists('gethostname') && gethostname() !== false) {
4243 - $result = gethostname();
4244 - } elseif (php_uname('n') !== false) {
4245 - $result = php_uname('n');
4246 - }
4247 - if (!static::isValidHost($result)) {
4248 - return 'localhost.localdomain';
4249 - }
4250 -
4251 - return $result;
4252 - }
4253 -
4254 - /**
4255 - * Validate whether a string contains a valid value to use as a hostname or IP address.
4256 - * IPv6 addresses must include [], e.g. `[::1]`, not just `::1`.
4257 - *
4258 - * @param string $host The host name or IP address to check
4259 - *
4260 - * @return bool
4261 - */
4262 - public static function isValidHost($host)
4263 - {
4264 - //Simple syntax limits
4265 - if (
4266 - empty($host)
4267 - || !is_string($host)
4268 - || strlen($host) > 256
4269 - || !preg_match('/^([a-zA-Z\d.-]*|\[[a-fA-F\d:]+\])$/', $host)
4270 - ) {
4271 - return false;
4272 - }
4273 - //Looks like a bracketed IPv6 address
4274 - if (strlen($host) > 2 && substr($host, 0, 1) === '[' && substr($host, -1, 1) === ']') {
4275 - return filter_var(substr($host, 1, -1), FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) !== false;
4276 - }
4277 - //If removing all the dots results in a numeric string, it must be an IPv4 address.
4278 - //Need to check this first because otherwise things like `999.0.0.0` are considered valid host names
4279 - if (is_numeric(str_replace('.', '', $host))) {
4280 - //Is it a valid IPv4 address?
4281 - return filter_var($host, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) !== false;
4282 - }
4283 -
4284 - //Is it a syntactically valid hostname (when embedded in a URL)?
4285 - return filter_var('https://'.$host, FILTER_VALIDATE_URL) !== false;
4286 - }
4287 -
4288 - /**
4289 - * Get an error message in the current language.
4290 - *
4291 - * @param string $key
4292 - *
4293 - * @return string
4294 - */
4295 - protected function lang($key)
4296 - {
4297 - if (count($this->language) < 1) {
4298 - $this->setLanguage(); //Set the default language
4299 - }
4300 -
4301 - if (array_key_exists($key, $this->language)) {
4302 - if ('smtp_connect_failed' === $key) {
4303 - //Include a link to troubleshooting docs on SMTP connection failure.
4304 - //This is by far the biggest cause of support questions
4305 - //but it's usually not PHPMailer's fault.
4306 - return $this->language[$key].' https://github.com/PHPMailer/PHPMailer/wiki/Troubleshooting';
4307 - }
4308 -
4309 - return $this->language[$key];
4310 - }
4311 -
4312 - //Return the key as a fallback
4313 - return $key;
4314 - }
4315 -
4316 - /**
4317 - * Build an error message starting with a generic one and adding details if possible.
4318 - *
4319 - * @param string $base_key
4320 - *
4321 - * @return string
4322 - */
4323 - private function getSmtpErrorMessage($base_key)
4324 - {
4325 - $message = $this->lang($base_key);
4326 - $error = $this->smtp->getError();
4327 - if (!empty($error['error'])) {
4328 - $message .= ' '.$error['error'];
4329 - if (!empty($error['detail'])) {
4330 - $message .= ' '.$error['detail'];
4331 - }
4332 - }
4333 -
4334 - return $message;
4335 - }
4336 -
4337 - /**
4338 - * Check if an error occurred.
4339 - *
4340 - * @return bool True if an error did occur
4341 - */
4342 - public function isError()
4343 - {
4344 - return $this->error_count > 0;
4345 - }
4346 -
4347 - /**
4348 - * Add a custom header.
4349 - * $name value can be overloaded to contain
4350 - * both header name and value (name:value).
4351 - *
4352 - * @param string $name Custom header name
4353 - * @param string|null $value Header value
4354 - *
4355 - * @return bool True if a header was set successfully
4356 - * @throws Exception
4357 - */
4358 - public function addCustomHeader($name, $value = null)
4359 - {
4360 - if (null === $value && strpos($name, ':') !== false) {
4361 - //Value passed in as name:value
4362 - [$name, $value] = explode(':', $name, 2);
4363 - }
4364 - $name = trim($name);
4365 - $value = (null === $value) ? '' : trim($value);
4366 - //Ensure name is not empty, and that neither name nor value contain line breaks
4367 - if (empty($name) || strpbrk($name.$value, "\r\n") !== false) {
4368 - if ($this->exceptions) {
4369 - throw new Exception($this->lang('invalid_header'));
4370 - }
4371 -
4372 - return false;
4373 - }
4374 - $this->CustomHeader[] = [$name, $value];
4375 -
4376 - return true;
4377 - }
4378 -
4379 - /**
4380 - * Returns all custom headers.
4381 - *
4382 - * @return array
4383 - */
4384 - public function getCustomHeaders()
4385 - {
4386 - return $this->CustomHeader;
4387 - }
4388 -
4389 - /**
4390 - * Create a message body from an HTML string.
4391 - * Automatically inlines images and creates a plain-text version by converting the HTML,
4392 - * overwriting any existing values in Body and AltBody.
4393 - * Do not source $message content from user input!
4394 - * $basedir is prepended when handling relative URLs, e.g. <img src="/images/a.png"> and must not be empty
4395 - * will look for an image file in $basedir/images/a.png and convert it to inline.
4396 - * If you don't provide a $basedir, relative paths will be left untouched (and thus probably break in email)
4397 - * Converts data-uri images into embedded attachments.
4398 - * If you don't want to apply these transformations to your HTML, just set Body and AltBody directly.
4399 - *
4400 - * @param string $message HTML message string
4401 - * @param string $basedir Absolute path to a base directory to prepend to relative paths to images
4402 - * @param bool|callable $advanced Whether to use the internal HTML to text converter
4403 - * or your own custom converter
4404 - *
4405 - * @return string The transformed message body
4406 - *
4407 - * @throws Exception
4408 - *
4409 - * @see PHPMailer::html2text()
4410 - */
4411 - public function msgHTML($message, $basedir = '', $advanced = false)
4412 - {
4413 - preg_match_all('/(?<!-)(src|background)=["\'](.*)["\']/Ui', $message, $images);
4414 - if (array_key_exists(2, $images)) {
4415 - if (strlen($basedir) > 1 && '/' !== substr($basedir, -1)) {
4416 - //Ensure $basedir has a trailing /
4417 - $basedir .= '/';
4418 - }
4419 - foreach ($images[2] as $imgindex => $url) {
4420 - //Convert data URIs into embedded images
4421 - //e.g. "data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="
4422 - $match = [];
4423 - if (preg_match('#^data:(image/(?:jpe?g|gif|png));?(base64)?,(.+)#', $url, $match)) {
4424 - if (count($match) === 4 && static::ENCODING_BASE64 === $match[2]) {
4425 - $data = base64_decode($match[3]);
4426 - } elseif ('' === $match[2]) {
4427 - $data = rawurldecode($match[3]);
4428 - } else {
4429 - //Not recognised so leave it alone
4430 - continue;
4431 - }
4432 - //Hash the decoded data, not the URL, so that the same data-URI image used in multiple places
4433 - //will only be embedded once, even if it used a different encoding
4434 - $cid = substr(hash('sha256', $data), 0, 32).'@phpmailer.0'; //RFC2392 S 2
4435 -
4436 - if (!$this->cidExists($cid)) {
4437 - $this->addStringEmbeddedImage(
4438 - $data,
4439 - $cid,
4440 - 'embed'.$imgindex,
4441 - static::ENCODING_BASE64,
4442 - $match[1]
4443 - );
4444 - }
4445 - $message = str_replace(
4446 - $images[0][$imgindex],
4447 - $images[1][$imgindex].'="cid:'.$cid.'"',
4448 - $message
4449 - );
4450 - continue;
4451 - }
4452 - if (
4453 - //Only process relative URLs if a basedir is provided (i.e. no absolute local paths)
4454 - !empty($basedir)
4455 - //Ignore URLs containing parent dir traversal (..)
4456 - && (strpos($url, '..') === false)
4457 - //Do not change urls that are already inline images
4458 - && 0 !== strpos($url, 'cid:')
4459 - //Do not change absolute URLs, including anonymous protocol
4460 - && !preg_match('#^[a-z][a-z0-9+.-]*:?//#i', $url)
4461 - ) {
4462 - $filename = static::mb_pathinfo($url, PATHINFO_BASENAME);
4463 - $directory = dirname($url);
4464 - if ('.' === $directory) {
4465 - $directory = '';
4466 - }
4467 - //RFC2392 S 2
4468 - $cid = substr(hash('sha256', $url), 0, 32).'@phpmailer.0';
4469 - if (strlen($basedir) > 1 && '/' !== substr($basedir, -1)) {
4470 - $basedir .= '/';
4471 - }
4472 - if (strlen($directory) > 1 && '/' !== substr($directory, -1)) {
4473 - $directory .= '/';
4474 - }
4475 - if (
4476 - $this->addEmbeddedImage(
4477 - $basedir.$directory.$filename,
4478 - $cid,
4479 - $filename,
4480 - static::ENCODING_BASE64,
4481 - static::_mime_types((string)static::mb_pathinfo($filename, PATHINFO_EXTENSION))
4482 - )
4483 - ) {
4484 - $message = preg_replace(
4485 - '/'.$images[1][$imgindex].'=["\']'.preg_quote($url, '/').'["\']/Ui',
4486 - $images[1][$imgindex].'="cid:'.$cid.'"',
4487 - $message
4488 - );
4489 - }
4490 - }
4491 - }
4492 - }
4493 - $this->isHTML();
4494 - //Convert all message body line breaks to LE, makes quoted-printable encoding work much better
4495 - $this->Body = static::normalizeBreaks($message);
4496 - $this->AltBody = static::normalizeBreaks($this->html2text($message, $advanced));
4497 - if (!$this->alternativeExists()) {
4498 - $this->AltBody = 'This is an HTML-only message. To view it, activate HTML in your email application.'
4499 - .static::$LE;
4500 - }
4501 -
4502 - return $this->Body;
4503 - }
4504 -
4505 - /**
4506 - * Convert an HTML string into plain text.
4507 - * This is used by msgHTML().
4508 - * Note - older versions of this function used a bundled advanced converter
4509 - * which was removed for license reasons in #232.
4510 - * Example usage:
4511 - *
4512 - * ```php
4513 - * //Use default conversion
4514 - * $plain = $mail->html2text($html);
4515 - * //Use your own custom converter
4516 - * $plain = $mail->html2text($html, function($html) {
4517 - * $converter = new MyHtml2text($html);
4518 - * return $converter->get_text();
4519 - * });
4520 - * ```
4521 - *
4522 - * @param string $html The HTML text to convert
4523 - * @param bool|callable $advanced Any boolean value to use the internal converter,
4524 - * or provide your own callable for custom conversion.
4525 - * *Never* pass user-supplied data into this parameter
4526 - *
4527 - * @return string
4528 - */
4529 - public function html2text($html, $advanced = false)
4530 - {
4531 - if (is_callable($advanced)) {
4532 - return call_user_func($advanced, $html);
4533 - }
4534 -
4535 - return html_entity_decode(
4536 - trim(strip_tags(preg_replace('/<(head|title|style|script)[^>]*>.*?<\/\\1>/si', '', $html))),
4537 - ENT_QUOTES,
4538 - $this->CharSet
4539 - );
4540 - }
4541 -
4542 - /**
4543 - * Get the MIME type for a file extension.
4544 - *
4545 - * @param string $ext File extension
4546 - *
4547 - * @return string MIME type of file
4548 - */
4549 - public static function _mime_types($ext = '')
4550 - {
4551 - $mimes = [
4552 - 'xl' => 'application/excel',
4553 - 'js' => 'application/javascript',
4554 - 'hqx' => 'application/mac-binhex40',
4555 - 'cpt' => 'application/mac-compactpro',
4556 - 'bin' => 'application/macbinary',
4557 - 'doc' => 'application/msword',
4558 - 'word' => 'application/msword',
4559 - 'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
4560 - 'xltx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.template',
4561 - 'potx' => 'application/vnd.openxmlformats-officedocument.presentationml.template',
4562 - 'ppsx' => 'application/vnd.openxmlformats-officedocument.presentationml.slideshow',
4563 - 'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
4564 - 'sldx' => 'application/vnd.openxmlformats-officedocument.presentationml.slide',
4565 - 'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
4566 - 'dotx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.template',
4567 - 'xlam' => 'application/vnd.ms-excel.addin.macroEnabled.12',
4568 - 'xlsb' => 'application/vnd.ms-excel.sheet.binary.macroEnabled.12',
4569 - 'class' => 'application/octet-stream',
4570 - 'dll' => 'application/octet-stream',
4571 - 'dms' => 'application/octet-stream',
4572 - 'exe' => 'application/octet-stream',
4573 - 'lha' => 'application/octet-stream',
4574 - 'lzh' => 'application/octet-stream',
4575 - 'psd' => 'application/octet-stream',
4576 - 'sea' => 'application/octet-stream',
4577 - 'so' => 'application/octet-stream',
4578 - 'oda' => 'application/oda',
4579 - 'pdf' => 'application/pdf',
4580 - 'ai' => 'application/postscript',
4581 - 'eps' => 'application/postscript',
4582 - 'ps' => 'application/postscript',
4583 - 'smi' => 'application/smil',
4584 - 'smil' => 'application/smil',
4585 - 'mif' => 'application/vnd.mif',
4586 - 'xls' => 'application/vnd.ms-excel',
4587 - 'ppt' => 'application/vnd.ms-powerpoint',
4588 - 'wbxml' => 'application/vnd.wap.wbxml',
4589 - 'wmlc' => 'application/vnd.wap.wmlc',
4590 - 'dcr' => 'application/x-director',
4591 - 'dir' => 'application/x-director',
4592 - 'dxr' => 'application/x-director',
4593 - 'dvi' => 'application/x-dvi',
4594 - 'gtar' => 'application/x-gtar',
4595 - 'php3' => 'application/x-httpd-php',
4596 - 'php4' => 'application/x-httpd-php',
4597 - 'php' => 'application/x-httpd-php',
4598 - 'phtml' => 'application/x-httpd-php',
4599 - 'phps' => 'application/x-httpd-php-source',
4600 - 'swf' => 'application/x-shockwave-flash',
4601 - 'sit' => 'application/x-stuffit',
4602 - 'tar' => 'application/x-tar',
4603 - 'tgz' => 'application/x-tar',
4604 - 'xht' => 'application/xhtml+xml',
4605 - 'xhtml' => 'application/xhtml+xml',
4606 - 'zip' => 'application/zip',
4607 - 'mid' => 'audio/midi',
4608 - 'midi' => 'audio/midi',
4609 - 'mp2' => 'audio/mpeg',
4610 - 'mp3' => 'audio/mpeg',
4611 - 'm4a' => 'audio/mp4',
4612 - 'mpga' => 'audio/mpeg',
4613 - 'aif' => 'audio/x-aiff',
4614 - 'aifc' => 'audio/x-aiff',
4615 - 'aiff' => 'audio/x-aiff',
4616 - 'ram' => 'audio/x-pn-realaudio',
4617 - 'rm' => 'audio/x-pn-realaudio',
4618 - 'rpm' => 'audio/x-pn-realaudio-plugin',
4619 - 'ra' => 'audio/x-realaudio',
4620 - 'wav' => 'audio/x-wav',
4621 - 'mka' => 'audio/x-matroska',
4622 - 'bmp' => 'image/bmp',
4623 - 'gif' => 'image/gif',
4624 - 'jpeg' => 'image/jpeg',
4625 - 'jpe' => 'image/jpeg',
4626 - 'jpg' => 'image/jpeg',
4627 - 'png' => 'image/png',
4628 - 'tiff' => 'image/tiff',
4629 - 'tif' => 'image/tiff',
4630 - 'webp' => 'image/webp',
4631 - 'avif' => 'image/avif',
4632 - 'heif' => 'image/heif',
4633 - 'heifs' => 'image/heif-sequence',
4634 - 'heic' => 'image/heic',
4635 - 'heics' => 'image/heic-sequence',
4636 - 'eml' => 'message/rfc822',
4637 - 'css' => 'text/css',
4638 - 'html' => 'text/html',
4639 - 'htm' => 'text/html',
4640 - 'shtml' => 'text/html',
4641 - 'log' => 'text/plain',
4642 - 'text' => 'text/plain',
4643 - 'txt' => 'text/plain',
4644 - 'rtx' => 'text/richtext',
4645 - 'rtf' => 'text/rtf',
4646 - 'vcf' => 'text/vcard',
4647 - 'vcard' => 'text/vcard',
4648 - 'ics' => 'text/calendar',
4649 - 'xml' => 'text/xml',
4650 - 'xsl' => 'text/xml',
4651 - 'csv' => 'text/csv',
4652 - 'wmv' => 'video/x-ms-wmv',
4653 - 'mpeg' => 'video/mpeg',
4654 - 'mpe' => 'video/mpeg',
4655 - 'mpg' => 'video/mpeg',
4656 - 'mp4' => 'video/mp4',
4657 - 'm4v' => 'video/mp4',
4658 - 'mov' => 'video/quicktime',
4659 - 'qt' => 'video/quicktime',
4660 - 'rv' => 'video/vnd.rn-realvideo',
4661 - 'avi' => 'video/x-msvideo',
4662 - 'movie' => 'video/x-sgi-movie',
4663 - 'webm' => 'video/webm',
4664 - 'mkv' => 'video/x-matroska',
4665 - ];
4666 - $ext = strtolower($ext);
4667 - if (array_key_exists($ext, $mimes)) {
4668 - return $mimes[$ext];
4669 - }
4670 -
4671 - return 'application/octet-stream';
4672 - }
4673 -
4674 - /**
4675 - * Map a file name to a MIME type.
4676 - * Defaults to 'application/octet-stream', i.e.. arbitrary binary data.
4677 - *
4678 - * @param string $filename A file name or full path, does not need to exist as a file
4679 - *
4680 - * @return string
4681 - */
4682 - public static function filenameToType($filename)
4683 - {
4684 - //In case the path is a URL, strip any query string before getting extension
4685 - $qpos = strpos($filename, '?');
4686 - if (false !== $qpos) {
4687 - $filename = substr($filename, 0, $qpos);
4688 - }
4689 - $ext = static::mb_pathinfo($filename, PATHINFO_EXTENSION);
4690 -
4691 - return static::_mime_types($ext);
4692 - }
4693 -
4694 - /**
4695 - * Multi-byte-safe pathinfo replacement.
4696 - * Drop-in replacement for pathinfo(), but multibyte- and cross-platform-safe.
4697 - *
4698 - * @see https://www.php.net/manual/en/function.pathinfo.php#107461
4699 - *
4700 - * @param string $path A filename or path, does not need to exist as a file
4701 - * @param int|string $options Either a PATHINFO_* constant,
4702 - * or a string name to return only the specified piece
4703 - *
4704 - * @return string|array
4705 - */
4706 - public static function mb_pathinfo($path, $options = null)
4707 - {
4708 - $ret = ['dirname' => '', 'basename' => '', 'extension' => '', 'filename' => ''];
4709 - $pathinfo = [];
4710 - if (preg_match('#^(.*?)[\\\\/]*(([^/\\\\]*?)(\.([^.\\\\/]+?)|))[\\\\/.]*$#m', $path, $pathinfo)) {
4711 - if (array_key_exists(1, $pathinfo)) {
4712 - $ret['dirname'] = $pathinfo[1];
4713 - }
4714 - if (array_key_exists(2, $pathinfo)) {
4715 - $ret['basename'] = $pathinfo[2];
4716 - }
4717 - if (array_key_exists(5, $pathinfo)) {
4718 - $ret['extension'] = $pathinfo[5];
4719 - }
4720 - if (array_key_exists(3, $pathinfo)) {
4721 - $ret['filename'] = $pathinfo[3];
4722 - }
4723 - }
4724 - switch ($options) {
4725 - case PATHINFO_DIRNAME:
4726 - case 'dirname':
4727 - return $ret['dirname'];
4728 - case PATHINFO_BASENAME:
4729 - case 'basename':
4730 - return $ret['basename'];
4731 - case PATHINFO_EXTENSION:
4732 - case 'extension':
4733 - return $ret['extension'];
4734 - case PATHINFO_FILENAME:
4735 - case 'filename':
4736 - return $ret['filename'];
4737 - default:
4738 - return $ret;
4739 - }
4740 - }
4741 -
4742 - /**
4743 - * Set or reset instance properties.
4744 - * You should avoid this function - it's more verbose, less efficient, more error-prone and
4745 - * harder to debug than setting properties directly.
4746 - * Usage Example:
4747 - * `$mail->set('SMTPSecure', static::ENCRYPTION_STARTTLS);`
4748 - * is the same as:
4749 - * `$mail->SMTPSecure = static::ENCRYPTION_STARTTLS;`.
4750 - *
4751 - * @param string $name The property name to set
4752 - * @param mixed $value The value to set the property to
4753 - *
4754 - * @return bool
4755 - */
4756 - public function set($name, $value = '')
4757 - {
4758 - if (property_exists($this, $name)) {
4759 - $this->{$name} = $value;
4760 -
4761 - return true;
4762 - }
4763 - $this->setError($this->lang('variable_set').$name);
4764 -
4765 - return false;
4766 - }
4767 -
4768 - /**
4769 - * Strip newlines to prevent header injection.
4770 - *
4771 - * @param string $str
4772 - *
4773 - * @return string
4774 - */
4775 - public function secureHeader($str)
4776 - {
4777 - return trim(str_replace(["\r", "\n"], '', $str));
4778 - }
4779 -
4780 - /**
4781 - * Normalize line breaks in a string.
4782 - * Converts UNIX LF, Mac CR and Windows CRLF line breaks into a single line break format.
4783 - * Defaults to CRLF (for message bodies) and preserves consecutive breaks.
4784 - *
4785 - * @param string $text
4786 - * @param string $breaktype What kind of line break to use; defaults to static::$LE
4787 - *
4788 - * @return string
4789 - */
4790 - public static function normalizeBreaks($text, $breaktype = null)
4791 - {
4792 - if (null === $breaktype) {
4793 - $breaktype = static::$LE;
4794 - }
4795 - //Normalise to \n
4796 - $text = str_replace([self::CRLF, "\r"], "\n", $text);
4797 - //Now convert LE as needed
4798 - if ("\n" !== $breaktype) {
4799 - $text = str_replace("\n", $breaktype, $text);
4800 - }
4801 -
4802 - return $text;
4803 - }
4804 -
4805 - /**
4806 - * Remove trailing whitespace from a string.
4807 - *
4808 - * @param string $text
4809 - *
4810 - * @return string The text to remove whitespace from
4811 - */
4812 - public static function stripTrailingWSP($text)
4813 - {
4814 - return rtrim($text, " \r\n\t");
4815 - }
4816 -
4817 - /**
4818 - * Strip trailing line breaks from a string.
4819 - *
4820 - * @param string $text
4821 - *
4822 - * @return string The text to remove breaks from
4823 - */
4824 - public static function stripTrailingBreaks($text)
4825 - {
4826 - return rtrim($text, "\r\n");
4827 - }
4828 -
4829 - /**
4830 - * Return the current line break format string.
4831 - *
4832 - * @return string
4833 - */
4834 - public static function getLE()
4835 - {
4836 - return static::$LE;
4837 - }
4838 -
4839 - /**
4840 - * Set the line break format string, e.g. "\r\n".
4841 - *
4842 - * @param string $le
4843 - */
4844 - protected static function setLE($le)
4845 - {
4846 - static::$LE = $le;
4847 - }
4848 -
4849 - /**
4850 - * Set the public and private key files and password for S/MIME signing.
4851 - *
4852 - * @param string $cert_filename
4853 - * @param string $key_filename
4854 - * @param string $key_pass Password for private key
4855 - * @param string $extracerts_filename Optional path to chain certificate
4856 - */
4857 - public function sign($cert_filename, $key_filename, $key_pass, $extracerts_filename = '')
4858 - {
4859 - $this->sign_cert_file = $cert_filename;
4860 - $this->sign_key_file = $key_filename;
4861 - $this->sign_key_pass = $key_pass;
4862 - $this->sign_extracerts_file = $extracerts_filename;
4863 - }
4864 -
4865 - /**
4866 - * Quoted-Printable-encode a DKIM header.
4867 - *
4868 - * @param string $txt
4869 - *
4870 - * @return string
4871 - */
4872 - public function DKIM_QP($txt)
4873 - {
4874 - $line = '';
4875 - $len = strlen($txt);
4876 - for ($i = 0; $i < $len; ++$i) {
4877 - $ord = ord($txt[$i]);
4878 - if (((0x21 <= $ord) && ($ord <= 0x3A)) || $ord === 0x3C || ((0x3E <= $ord) && ($ord <= 0x7E))) {
4879 - $line .= $txt[$i];
4880 - } else {
4881 - $line .= '='.sprintf('%02X', $ord);
4882 - }
4883 - }
4884 -
4885 - return $line;
4886 - }
4887 -
4888 - /**
4889 - * Generate a DKIM signature.
4890 - *
4891 - * @param string $signHeader
4892 - *
4893 - * @return string The DKIM signature value
4894 - * @throws Exception
4895 - *
4896 - */
4897 - public function DKIM_Sign($signHeader)
4898 - {
4899 - if (!defined('PKCS7_TEXT')) {
4900 - if ($this->exceptions) {
4901 - throw new Exception($this->lang('extension_missing').'openssl');
4902 - }
4903 -
4904 - return '';
4905 - }
4906 - $privKeyStr = !empty($this->DKIM_private_string)
4907 - ?
4908 - $this->DKIM_private_string
4909 - :
4910 - file_get_contents($this->DKIM_private);
4911 - if ('' !== $this->DKIM_passphrase) {
4912 - $privKey = openssl_pkey_get_private($privKeyStr, $this->DKIM_passphrase);
4913 - } else {
4914 - $privKey = openssl_pkey_get_private($privKeyStr);
4915 - }
4916 - if (openssl_sign($signHeader, $signature, $privKey, 'sha256WithRSAEncryption')) {
4917 - if (\PHP_MAJOR_VERSION < 8) {
4918 - openssl_pkey_free($privKey);
4919 - }
4920 -
4921 - return base64_encode($signature);
4922 - }
4923 - if (\PHP_MAJOR_VERSION < 8) {
4924 - openssl_pkey_free($privKey);
4925 - }
4926 -
4927 - return '';
4928 - }
4929 -
4930 - /**
4931 - * Generate a DKIM canonicalization header.
4932 - * Uses the 'relaxed' algorithm from RFC6376 section 3.4.2.
4933 - * Canonicalized headers should *always* use CRLF, regardless of mailer setting.
4934 - *
4935 - * @see https://tools.ietf.org/html/rfc6376#section-3.4.2
4936 - *
4937 - * @param string $signHeader Header
4938 - *
4939 - * @return string
4940 - */
4941 - public function DKIM_HeaderC($signHeader)
4942 - {
4943 - //Normalize breaks to CRLF (regardless of the mailer)
4944 - $signHeader = static::normalizeBreaks($signHeader, self::CRLF);
4945 - //Unfold header lines
4946 - //Note PCRE \s is too broad a definition of whitespace; RFC5322 defines it as `[ \t]`
4947 - //@see https://tools.ietf.org/html/rfc5322#section-2.2
4948 - //That means this may break if you do something daft like put vertical tabs in your headers.
4949 - $signHeader = preg_replace('/\r\n[ \t]+/', ' ', $signHeader);
4950 - //Break headers out into an array
4951 - $lines = explode(self::CRLF, $signHeader);
4952 - foreach ($lines as $key => $line) {
4953 - //If the header is missing a :, skip it as it's invalid
4954 - //This is likely to happen because the explode() above will also split
4955 - //on the trailing LE, leaving an empty line
4956 - if (strpos($line, ':') === false) {
4957 - continue;
4958 - }
4959 - [$heading, $value] = explode(':', $line, 2);
4960 - //Lower-case header name
4961 - $heading = strtolower($heading);
4962 - //Collapse white space within the value, also convert WSP to space
4963 - $value = preg_replace('/[ \t]+/', ' ', $value);
4964 - //RFC6376 is slightly unclear here - it says to delete space at the *end* of each value
4965 - //But then says to delete space before and after the colon.
4966 - //Net result is the same as trimming both ends of the value.
4967 - //By elimination, the same applies to the field name
4968 - $lines[$key] = trim($heading, " \t").':'.trim($value, " \t");
4969 - }
4970 -
4971 - return implode(self::CRLF, $lines);
4972 - }
4973 -
4974 - /**
4975 - * Generate a DKIM canonicalization body.
4976 - * Uses the 'simple' algorithm from RFC6376 section 3.4.3.
4977 - * Canonicalized bodies should *always* use CRLF, regardless of mailer setting.
4978 - *
4979 - * @see https://tools.ietf.org/html/rfc6376#section-3.4.3
4980 - *
4981 - * @param string $body Message Body
4982 - *
4983 - * @return string
4984 - */
4985 - public function DKIM_BodyC($body)
4986 - {
4987 - if (empty($body)) {
4988 - return self::CRLF;
4989 - }
4990 - //Normalize line endings to CRLF
4991 - $body = static::normalizeBreaks($body, self::CRLF);
4992 -
4993 - //Reduce multiple trailing line breaks to a single one
4994 - return static::stripTrailingBreaks($body).self::CRLF;
4995 - }
4996 -
4997 - /**
4998 - * Create the DKIM header and body in a new message header.
4999 - *
5000 - * @param string $headers_line Header lines
5001 - * @param string $subject Subject
5002 - * @param string $body Body
5003 - *
5004 - * @return string
5005 - * @throws Exception
5006 - *
5007 - */
5008 - public function DKIM_Add($headers_line, $subject, $body)
5009 - {
5010 - $DKIMsignatureType = 'rsa-sha256'; //Signature & hash algorithms
5011 - $DKIMcanonicalization = 'relaxed/simple'; //Canonicalization methods of header & body
5012 - $DKIMquery = 'dns/txt'; //Query method
5013 - $DKIMtime = time();
5014 - //Always sign these headers without being asked
5015 - //Recommended list from https://tools.ietf.org/html/rfc6376#section-5.4.1
5016 - $autoSignHeaders = [
5017 - 'from',
5018 - 'to',
5019 - 'cc',
5020 - 'date',
5021 - 'subject',
5022 - 'reply-to',
5023 - 'message-id',
5024 - 'content-type',
5025 - 'mime-version',
5026 - 'x-mailer',
5027 - ];
5028 - if (stripos($headers_line, 'Subject') === false) {
5029 - $headers_line .= 'Subject: '.$subject.static::$LE;
5030 - }
5031 - $headerLines = explode(static::$LE, $headers_line);
5032 - $currentHeaderLabel = '';
5033 - $currentHeaderValue = '';
5034 - $parsedHeaders = [];
5035 - $headerLineIndex = 0;
5036 - $headerLineCount = count($headerLines);
5037 - foreach ($headerLines as $headerLine) {
5038 - $matches = [];
5039 - if (preg_match('/^([^ \t]*?)(?::[ \t]*)(.*)$/', $headerLine, $matches)) {
5040 - if ($currentHeaderLabel !== '') {
5041 - //We were previously in another header; This is the start of a new header, so save the previous one
5042 - $parsedHeaders[] = ['label' => $currentHeaderLabel, 'value' => $currentHeaderValue];
5043 - }
5044 - $currentHeaderLabel = $matches[1];
5045 - $currentHeaderValue = $matches[2];
5046 - } elseif (preg_match('/^[ \t]+(.*)$/', $headerLine, $matches)) {
5047 - //This is a folded continuation of the current header, so unfold it
5048 - $currentHeaderValue .= ' '.$matches[1];
5049 - }
5050 - ++$headerLineIndex;
5051 - if ($headerLineIndex >= $headerLineCount) {
5052 - //This was the last line, so finish off this header
5053 - $parsedHeaders[] = ['label' => $currentHeaderLabel, 'value' => $currentHeaderValue];
5054 - }
5055 - }
5056 - $copiedHeaders = [];
5057 - $headersToSignKeys = [];
5058 - $headersToSign = [];
5059 - foreach ($parsedHeaders as $header) {
5060 - //Is this header one that must be included in the DKIM signature?
5061 - if (in_array(strtolower($header['label']), $autoSignHeaders, true)) {
5062 - $headersToSignKeys[] = $header['label'];
5063 - $headersToSign[] = $header['label'].': '.$header['value'];
5064 - if ($this->DKIM_copyHeaderFields) {
5065 - $copiedHeaders[] = $header['label'].':'. //Note no space after this, as per RFC
5066 - str_replace('|', '=7C', $this->DKIM_QP($header['value']));
5067 - }
5068 - continue;
5069 - }
5070 - //Is this an extra custom header we've been asked to sign?
5071 - if (in_array($header['label'], $this->DKIM_extraHeaders, true)) {
5072 - //Find its value in custom headers
5073 - foreach ($this->CustomHeader as $customHeader) {
5074 - if ($customHeader[0] === $header['label']) {
5075 - $headersToSignKeys[] = $header['label'];
5076 - $headersToSign[] = $header['label'].': '.$header['value'];
5077 - if ($this->DKIM_copyHeaderFields) {
5078 - $copiedHeaders[] = $header['label'].':'. //Note no space after this, as per RFC
5079 - str_replace('|', '=7C', $this->DKIM_QP($header['value']));
5080 - }
5081 - //Skip straight to the next header
5082 - continue 2;
5083 - }
5084 - }
5085 - }
5086 - }
5087 - $copiedHeaderFields = '';
5088 - if ($this->DKIM_copyHeaderFields && count($copiedHeaders) > 0) {
5089 - //Assemble a DKIM 'z' tag
5090 - $copiedHeaderFields = ' z=';
5091 - $first = true;
5092 - foreach ($copiedHeaders as $copiedHeader) {
5093 - if (!$first) {
5094 - $copiedHeaderFields .= static::$LE.' |';
5095 - }
5096 - //Fold long values
5097 - if (strlen($copiedHeader) > self::STD_LINE_LENGTH - 3) {
5098 - $copiedHeaderFields .= substr(
5099 - chunk_split($copiedHeader, self::STD_LINE_LENGTH - 3, static::$LE.self::FWS),
5100 - 0,
5101 - -strlen(static::$LE.self::FWS)
5102 - );
5103 - } else {
5104 - $copiedHeaderFields .= $copiedHeader;
5105 - }
5106 - $first = false;
5107 - }
5108 - $copiedHeaderFields .= ';'.static::$LE;
5109 - }
5110 - $headerKeys = ' h='.implode(':', $headersToSignKeys).';'.static::$LE;
5111 - $headerValues = implode(static::$LE, $headersToSign);
5112 - $body = $this->DKIM_BodyC($body);
5113 - //Base64 of packed binary SHA-256 hash of body
5114 - $DKIMb64 = base64_encode(pack('H*', hash('sha256', $body)));
5115 - $ident = '';
5116 - if ('' !== $this->DKIM_identity) {
5117 - $ident = ' i='.$this->DKIM_identity.';'.static::$LE;
5118 - }
5119 - //The DKIM-Signature header is included in the signature *except for* the value of the `b` tag
5120 - //which is appended after calculating the signature
5121 - //https://tools.ietf.org/html/rfc6376#section-3.5
5122 - $dkimSignatureHeader = 'DKIM-Signature: v=1;'.
5123 - ' d='.$this->DKIM_domain.';'.
5124 - ' s='.$this->DKIM_selector.';'.static::$LE.
5125 - ' a='.$DKIMsignatureType.';'.
5126 - ' q='.$DKIMquery.';'.
5127 - ' t='.$DKIMtime.';'.
5128 - ' c='.$DKIMcanonicalization.';'.static::$LE.
5129 - $headerKeys.
5130 - $ident.
5131 - $copiedHeaderFields.
5132 - ' bh='.$DKIMb64.';'.static::$LE.
5133 - ' b=';
5134 - //Canonicalize the set of headers
5135 - $canonicalizedHeaders = $this->DKIM_HeaderC(
5136 - $headerValues.static::$LE.$dkimSignatureHeader
5137 - );
5138 - $signature = $this->DKIM_Sign($canonicalizedHeaders);
5139 - $signature = trim(chunk_split($signature, self::STD_LINE_LENGTH - 3, static::$LE.self::FWS));
5140 -
5141 - return static::normalizeBreaks($dkimSignatureHeader.$signature);
5142 - }
5143 -
5144 - /**
5145 - * Detect if a string contains a line longer than the maximum line length
5146 - * allowed by RFC 2822 section 2.1.1.
5147 - *
5148 - * @param string $str
5149 - *
5150 - * @return bool
5151 - */
5152 - public static function hasLineLongerThanMax($str)
5153 - {
5154 - return (bool)preg_match('/^(.{'.(self::MAX_LINE_LENGTH + strlen(static::$LE)).',})/m', $str);
5155 - }
5156 -
5157 - /**
5158 - * If a string contains any "special" characters, double-quote the name,
5159 - * and escape any double quotes with a backslash.
5160 - *
5161 - * @param string $str
5162 - *
5163 - * @return string
5164 - *
5165 - * @see RFC822 3.4.1
5166 - */
5167 - public static function quotedString($str)
5168 - {
5169 - if (preg_match('/[ ()<>@,;:"\/\[\]?=]/', $str)) {
5170 - //If the string contains any of these chars, it must be double-quoted
5171 - //and any double quotes must be escaped with a backslash
5172 - return '"'.str_replace('"', '\\"', $str).'"';
5173 - }
5174 -
5175 - //Return the string untouched, it doesn't need quoting
5176 - return $str;
5177 - }
5178 -
5179 - /**
5180 - * Allows for public read access to 'to' property.
5181 - * Before the send() call, queued addresses (i.e. with IDN) are not yet included.
5182 - *
5183 - * @return array
5184 - */
5185 - public function getToAddresses()
5186 - {
5187 - return $this->to;
5188 - }
5189 -
5190 - /**
5191 - * Allows for public read access to 'cc' property.
5192 - * Before the send() call, queued addresses (i.e. with IDN) are not yet included.
5193 - *
5194 - * @return array
5195 - */
5196 - public function getCcAddresses()
5197 - {
5198 - return $this->cc;
5199 - }
5200 -
5201 - /**
5202 - * Allows for public read access to 'bcc' property.
5203 - * Before the send() call, queued addresses (i.e. with IDN) are not yet included.
5204 - *
5205 - * @return array
5206 - */
5207 - public function getBccAddresses()
5208 - {
5209 - return $this->bcc;
5210 - }
5211 -
5212 - /**
5213 - * Allows for public read access to 'ReplyTo' property.
5214 - * Before the send() call, queued addresses (i.e. with IDN) are not yet included.
5215 - *
5216 - * @return array
5217 - */
5218 - public function getReplyToAddresses()
5219 - {
5220 - return $this->ReplyTo;
5221 - }
5222 -
5223 - /**
5224 - * Allows for public read access to 'all_recipients' property.
5225 - * Before the send() call, queued addresses (i.e. with IDN) are not yet included.
5226 - *
5227 - * @return array
5228 - */
5229 - public function getAllRecipientAddresses()
5230 - {
5231 - return $this->all_recipients;
5232 - }
5233 -
5234 - /**
5235 - * Perform a callback.
5236 - *
5237 - * @param bool $isSent
5238 - * @param array $to
5239 - * @param array $cc
5240 - * @param array $bcc
5241 - * @param string $subject
5242 - * @param string $body
5243 - * @param string $from
5244 - * @param array $extra
5245 - */
5246 - protected function doCallback($isSent, $to, $cc, $bcc, $subject, $body, $from, $extra)
5247 - {
5248 - if (!empty($this->action_function) && is_callable($this->action_function)) {
5249 - call_user_func($this->action_function, $isSent, $to, $cc, $bcc, $subject, $body, $from, $extra);
5250 - }
5251 - }
5252 -
5253 - /**
5254 - * Get the OAuthTokenProvider instance.
5255 - *
5256 - * @return OAuthTokenProvider
5257 - */
5258 - public function getOAuth()
5259 - {
5260 - return $this->oauth;
5261 - }
5262 -
5263 - /**
5264 - * Set an OAuthTokenProvider instance.
5265 - */
5266 - public function setOAuth(OAuthTokenProvider $oauth)
5267 - {
5268 - $this->oauth = $oauth;
5269 - }
5270 -}