1: <?php
2: /**
3: * PHPMailer - PHP email creation and transport class.
4: * PHP Version 5
5: * @package PHPMailer
6: * @link https://github.com/PHPMailer/PHPMailer/ The PHPMailer GitHub project
7: * @author Marcus Bointon (Synchro/coolbru) <phpmailer@synchromedia.co.uk>
8: * @author Jim Jagielski (jimjag) <jimjag@gmail.com>
9: * @author Andy Prevost (codeworxtech) <codeworxtech@users.sourceforge.net>
10: * @author Brent R. Matzelle (original founder)
11: * @copyright 2012 - 2014 Marcus Bointon
12: * @copyright 2010 - 2012 Jim Jagielski
13: * @copyright 2004 - 2009 Andy Prevost
14: * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
15: * @note This program is distributed in the hope that it will be useful - WITHOUT
16: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
17: * FITNESS FOR A PARTICULAR PURPOSE.
18: */
19:
20: /**
21: * PHPMailer - PHP email creation and transport class.
22: * @package PHPMailer
23: * @author Marcus Bointon (Synchro/coolbru) <phpmailer@synchromedia.co.uk>
24: * @author Jim Jagielski (jimjag) <jimjag@gmail.com>
25: * @author Andy Prevost (codeworxtech) <codeworxtech@users.sourceforge.net>
26: * @author Brent R. Matzelle (original founder)
27: */
28: class PHPMailer
29: {
30: /**
31: * The PHPMailer Version number.
32: * @var string
33: */
34: public $Version = '5.2.28';
35:
36: /**
37: * Email priority.
38: * Options: null (default), 1 = High, 3 = Normal, 5 = low.
39: * When null, the header is not set at all.
40: * @var integer
41: */
42: public $Priority = null;
43:
44: /**
45: * The character set of the message.
46: * @var string
47: */
48: public $CharSet = 'iso-8859-1';
49:
50: /**
51: * The MIME Content-type of the message.
52: * @var string
53: */
54: public $ContentType = 'text/plain';
55:
56: /**
57: * The message encoding.
58: * Options: "8bit", "7bit", "binary", "base64", and "quoted-printable".
59: * @var string
60: */
61: public $Encoding = '8bit';
62:
63: /**
64: * Holds the most recent mailer error message.
65: * @var string
66: */
67: public $ErrorInfo = '';
68:
69: /**
70: * The From email address for the message.
71: * @var string
72: */
73: public $From = 'root@localhost';
74:
75: /**
76: * The From name of the message.
77: * @var string
78: */
79: public $FromName = 'Root User';
80:
81: /**
82: * The Sender email (Return-Path) of the message.
83: * If not empty, will be sent via -f to sendmail or as 'MAIL FROM' in smtp mode.
84: * @var string
85: */
86: public $Sender = '';
87:
88: /**
89: * The Return-Path of the message.
90: * If empty, it will be set to either From or Sender.
91: * @var string
92: * @deprecated Email senders should never set a return-path header;
93: * it's the receiver's job (RFC5321 section 4.4), so this no longer does anything.
94: * @link https://tools.ietf.org/html/rfc5321#section-4.4 RFC5321 reference
95: */
96: public $ReturnPath = '';
97:
98: /**
99: * The Subject of the message.
100: * @var string
101: */
102: public $Subject = '';
103:
104: /**
105: * An HTML or plain text message body.
106: * If HTML then call isHTML(true).
107: * @var string
108: */
109: public $Body = '';
110:
111: /**
112: * The plain-text message body.
113: * This body can be read by mail clients that do not have HTML email
114: * capability such as mutt & Eudora.
115: * Clients that can read HTML will view the normal Body.
116: * @var string
117: */
118: public $AltBody = '';
119:
120: /**
121: * An iCal message part body.
122: * Only supported in simple alt or alt_inline message types
123: * To generate iCal events, use the bundled extras/EasyPeasyICS.php class or iCalcreator
124: * @link http://sprain.ch/blog/downloads/php-class-easypeasyics-create-ical-files-with-php/
125: * @link http://kigkonsult.se/iCalcreator/
126: * @var string
127: */
128: public $Ical = '';
129:
130: /**
131: * The complete compiled MIME message body.
132: * @access protected
133: * @var string
134: */
135: protected $MIMEBody = '';
136:
137: /**
138: * The complete compiled MIME message headers.
139: * @var string
140: * @access protected
141: */
142: protected $MIMEHeader = '';
143:
144: /**
145: * Extra headers that createHeader() doesn't fold in.
146: * @var string
147: * @access protected
148: */
149: protected $mailHeader = '';
150:
151: /**
152: * Word-wrap the message body to this number of chars.
153: * Set to 0 to not wrap. A useful value here is 78, for RFC2822 section 2.1.1 compliance.
154: * @var integer
155: */
156: public $WordWrap = 0;
157:
158: /**
159: * Which method to use to send mail.
160: * Options: "mail", "sendmail", or "smtp".
161: * @var string
162: */
163: public $Mailer = 'mail';
164:
165: /**
166: * The path to the sendmail program.
167: * @var string
168: */
169: public $Sendmail = '/usr/sbin/sendmail';
170:
171: /**
172: * Whether mail() uses a fully sendmail-compatible MTA.
173: * One which supports sendmail's "-oi -f" options.
174: * @var boolean
175: */
176: public $UseSendmailOptions = true;
177:
178: /**
179: * Path to PHPMailer plugins.
180: * Useful if the SMTP class is not in the PHP include path.
181: * @var string
182: * @deprecated Should not be needed now there is an autoloader.
183: */
184: public $PluginDir = '';
185:
186: /**
187: * The email address that a reading confirmation should be sent to, also known as read receipt.
188: * @var string
189: */
190: public $ConfirmReadingTo = '';
191:
192: /**
193: * The hostname to use in the Message-ID header and as default HELO string.
194: * If empty, PHPMailer attempts to find one with, in order,
195: * $_SERVER['SERVER_NAME'], gethostname(), php_uname('n'), or the value
196: * 'localhost.localdomain'.
197: * @var string
198: */
199: public $Hostname = '';
200:
201: /**
202: * An ID to be used in the Message-ID header.
203: * If empty, a unique id will be generated.
204: * You can set your own, but it must be in the format "<id@domain>",
205: * as defined in RFC5322 section 3.6.4 or it will be ignored.
206: * @see https://tools.ietf.org/html/rfc5322#section-3.6.4
207: * @var string
208: */
209: public $MessageID = '';
210:
211: /**
212: * The message Date to be used in the Date header.
213: * If empty, the current date will be added.
214: * @var string
215: */
216: public $MessageDate = '';
217:
218: /**
219: * SMTP hosts.
220: * Either a single hostname or multiple semicolon-delimited hostnames.
221: * You can also specify a different port
222: * for each host by using this format: [hostname:port]
223: * (e.g. "smtp1.example.com:25;smtp2.example.com").
224: * You can also specify encryption type, for example:
225: * (e.g. "tls://smtp1.example.com:587;ssl://smtp2.example.com:465").
226: * Hosts will be tried in order.
227: * @var string
228: */
229: public $Host = 'localhost';
230:
231: /**
232: * The default SMTP server port.
233: * @var integer
234: * @TODO Why is this needed when the SMTP class takes care of it?
235: */
236: public $Port = 25;
237:
238: /**
239: * The SMTP HELO of the message.
240: * Default is $Hostname. If $Hostname is empty, PHPMailer attempts to find
241: * one with the same method described above for $Hostname.
242: * @var string
243: * @see PHPMailer::$Hostname
244: */
245: public $Helo = '';
246:
247: /**
248: * What kind of encryption to use on the SMTP connection.
249: * Options: '', 'ssl' or 'tls'
250: * @var string
251: */
252: public $SMTPSecure = '';
253:
254: /**
255: * Whether to enable TLS encryption automatically if a server supports it,
256: * even if `SMTPSecure` is not set to 'tls'.
257: * Be aware that in PHP >= 5.6 this requires that the server's certificates are valid.
258: * @var boolean
259: */
260: public $SMTPAutoTLS = true;
261:
262: /**
263: * Whether to use SMTP authentication.
264: * Uses the Username and Password properties.
265: * @var boolean
266: * @see PHPMailer::$Username
267: * @see PHPMailer::$Password
268: */
269: public $SMTPAuth = false;
270:
271: /**
272: * Options array passed to stream_context_create when connecting via SMTP.
273: * @var array
274: */
275: public $SMTPOptions = array();
276:
277: /**
278: * SMTP username.
279: * @var string
280: */
281: public $Username = '';
282:
283: /**
284: * SMTP password.
285: * @var string
286: */
287: public $Password = '';
288:
289: /**
290: * SMTP auth type.
291: * Options are CRAM-MD5, LOGIN, PLAIN, NTLM, XOAUTH2, attempted in that order if not specified
292: * @var string
293: */
294: public $AuthType = '';
295:
296: /**
297: * SMTP realm.
298: * Used for NTLM auth
299: * @var string
300: */
301: public $Realm = '';
302:
303: /**
304: * SMTP workstation.
305: * Used for NTLM auth
306: * @var string
307: */
308: public $Workstation = '';
309:
310: /**
311: * The SMTP server timeout in seconds.
312: * Default of 5 minutes (300sec) is from RFC2821 section 4.5.3.2
313: * @var integer
314: */
315: public $Timeout = 300;
316:
317: /**
318: * SMTP class debug output mode.
319: * Debug output level.
320: * Options:
321: * * `0` No output
322: * * `1` Commands
323: * * `2` Data and commands
324: * * `3` As 2 plus connection status
325: * * `4` Low-level data output
326: * @var integer
327: * @see SMTP::$do_debug
328: */
329: public $SMTPDebug = 0;
330:
331: /**
332: * How to handle debug output.
333: * Options:
334: * * `echo` Output plain-text as-is, appropriate for CLI
335: * * `html` Output escaped, line breaks converted to `<br>`, appropriate for browser output
336: * * `error_log` Output to error log as configured in php.ini
337: *
338: * Alternatively, you can provide a callable expecting two params: a message string and the debug level:
339: * <code>
340: * $mail->Debugoutput = function($str, $level) {echo "debug level $level; message: $str";};
341: * </code>
342: * @var string|callable
343: * @see SMTP::$Debugoutput
344: */
345: public $Debugoutput = 'echo';
346:
347: /**
348: * Whether to keep SMTP connection open after each message.
349: * If this is set to true then to close the connection
350: * requires an explicit call to smtpClose().
351: * @var boolean
352: */
353: public $SMTPKeepAlive = false;
354:
355: /**
356: * Whether to split multiple to addresses into multiple messages
357: * or send them all in one message.
358: * Only supported in `mail` and `sendmail` transports, not in SMTP.
359: * @var boolean
360: */
361: public $SingleTo = false;
362:
363: /**
364: * Storage for addresses when SingleTo is enabled.
365: * @var array
366: * @TODO This should really not be public
367: */
368: public $SingleToArray = array();
369:
370: /**
371: * Whether to generate VERP addresses on send.
372: * Only applicable when sending via SMTP.
373: * @link https://en.wikipedia.org/wiki/Variable_envelope_return_path
374: * @link http://www.postfix.org/VERP_README.html Postfix VERP info
375: * @var boolean
376: */
377: public $do_verp = false;
378:
379: /**
380: * Whether to allow sending messages with an empty body.
381: * @var boolean
382: */
383: public $AllowEmpty = false;
384:
385: /**
386: * The default line ending.
387: * @note The default remains "\n". We force CRLF where we know
388: * it must be used via self::CRLF.
389: * @var string
390: */
391: public $LE = "\n";
392:
393: /**
394: * DKIM selector.
395: * @var string
396: */
397: public $DKIM_selector = '';
398:
399: /**
400: * DKIM Identity.
401: * Usually the email address used as the source of the email.
402: * @var string
403: */
404: public $DKIM_identity = '';
405:
406: /**
407: * DKIM passphrase.
408: * Used if your key is encrypted.
409: * @var string
410: */
411: public $DKIM_passphrase = '';
412:
413: /**
414: * DKIM signing domain name.
415: * @example 'example.com'
416: * @var string
417: */
418: public $DKIM_domain = '';
419:
420: /**
421: * DKIM private key file path.
422: * @var string
423: */
424: public $DKIM_private = '';
425:
426: /**
427: * DKIM private key string.
428: * If set, takes precedence over `$DKIM_private`.
429: * @var string
430: */
431: public $DKIM_private_string = '';
432:
433: /**
434: * Callback Action function name.
435: *
436: * The function that handles the result of the send email action.
437: * It is called out by send() for each email sent.
438: *
439: * Value can be any php callable: http://www.php.net/is_callable
440: *
441: * Parameters:
442: * boolean $result result of the send action
443: * array $to email addresses of the recipients
444: * array $cc cc email addresses
445: * array $bcc bcc email addresses
446: * string $subject the subject
447: * string $body the email body
448: * string $from email address of sender
449: * @var string
450: */
451: public $action_function = '';
452:
453: /**
454: * What to put in the X-Mailer header.
455: * Options: An empty string for PHPMailer default, whitespace for none, or a string to use
456: * @var string
457: */
458: public $XMailer = '';
459:
460: /**
461: * Which validator to use by default when validating email addresses.
462: * May be a callable to inject your own validator, but there are several built-in validators.
463: * @see PHPMailer::validateAddress()
464: * @var string|callable
465: * @static
466: */
467: public static $validator = 'auto';
468:
469: /**
470: * An instance of the SMTP sender class.
471: * @var SMTP
472: * @access protected
473: */
474: protected $smtp = null;
475:
476: /**
477: * The array of 'to' names and addresses.
478: * @var array
479: * @access protected
480: */
481: protected $to = array();
482:
483: /**
484: * The array of 'cc' names and addresses.
485: * @var array
486: * @access protected
487: */
488: protected $cc = array();
489:
490: /**
491: * The array of 'bcc' names and addresses.
492: * @var array
493: * @access protected
494: */
495: protected $bcc = array();
496:
497: /**
498: * The array of reply-to names and addresses.
499: * @var array
500: * @access protected
501: */
502: protected $ReplyTo = array();
503:
504: /**
505: * An array of all kinds of addresses.
506: * Includes all of $to, $cc, $bcc
507: * @var array
508: * @access protected
509: * @see PHPMailer::$to @see PHPMailer::$cc @see PHPMailer::$bcc
510: */
511: protected $all_recipients = array();
512:
513: /**
514: * An array of names and addresses queued for validation.
515: * In send(), valid and non duplicate entries are moved to $all_recipients
516: * and one of $to, $cc, or $bcc.
517: * This array is used only for addresses with IDN.
518: * @var array
519: * @access protected
520: * @see PHPMailer::$to @see PHPMailer::$cc @see PHPMailer::$bcc
521: * @see PHPMailer::$all_recipients
522: */
523: protected $RecipientsQueue = array();
524:
525: /**
526: * An array of reply-to names and addresses queued for validation.
527: * In send(), valid and non duplicate entries are moved to $ReplyTo.
528: * This array is used only for addresses with IDN.
529: * @var array
530: * @access protected
531: * @see PHPMailer::$ReplyTo
532: */
533: protected $ReplyToQueue = array();
534:
535: /**
536: * The array of attachments.
537: * @var array
538: * @access protected
539: */
540: protected $attachment = array();
541:
542: /**
543: * The array of custom headers.
544: * @var array
545: * @access protected
546: */
547: protected $CustomHeader = array();
548:
549: /**
550: * The most recent Message-ID (including angular brackets).
551: * @var string
552: * @access protected
553: */
554: protected $lastMessageID = '';
555:
556: /**
557: * The message's MIME type.
558: * @var string
559: * @access protected
560: */
561: protected $message_type = '';
562:
563: /**
564: * The array of MIME boundary strings.
565: * @var array
566: * @access protected
567: */
568: protected $boundary = array();
569:
570: /**
571: * The array of available languages.
572: * @var array
573: * @access protected
574: */
575: protected $language = array();
576:
577: /**
578: * The number of errors encountered.
579: * @var integer
580: * @access protected
581: */
582: protected $error_count = 0;
583:
584: /**
585: * The S/MIME certificate file path.
586: * @var string
587: * @access protected
588: */
589: protected $sign_cert_file = '';
590:
591: /**
592: * The S/MIME key file path.
593: * @var string
594: * @access protected
595: */
596: protected $sign_key_file = '';
597:
598: /**
599: * The optional S/MIME extra certificates ("CA Chain") file path.
600: * @var string
601: * @access protected
602: */
603: protected $sign_extracerts_file = '';
604:
605: /**
606: * The S/MIME password for the key.
607: * Used only if the key is encrypted.
608: * @var string
609: * @access protected
610: */
611: protected $sign_key_pass = '';
612:
613: /**
614: * Whether to throw exceptions for errors.
615: * @var boolean
616: * @access protected
617: */
618: protected $exceptions = false;
619:
620: /**
621: * Unique ID used for message ID and boundaries.
622: * @var string
623: * @access protected
624: */
625: protected $uniqueid = '';
626:
627: /**
628: * Error severity: message only, continue processing.
629: */
630: const STOP_MESSAGE = 0;
631:
632: /**
633: * Error severity: message, likely ok to continue processing.
634: */
635: const STOP_CONTINUE = 1;
636:
637: /**
638: * Error severity: message, plus full stop, critical error reached.
639: */
640: const STOP_CRITICAL = 2;
641:
642: /**
643: * SMTP RFC standard line ending.
644: */
645: const CRLF = "\r\n";
646:
647: /**
648: * The maximum line length allowed by RFC 2822 section 2.1.1
649: * @var integer
650: */
651: const MAX_LINE_LENGTH = 998;
652:
653: /**
654: * Constructor.
655: * @param boolean $exceptions Should we throw external exceptions?
656: */
657: public function __construct($exceptions = null)
658: {
659: if ($exceptions !== null) {
660: $this->exceptions = (boolean)$exceptions;
661: }
662: //Pick an appropriate debug output format automatically
663: $this->Debugoutput = (strpos(PHP_SAPI, 'cli') !== false ? 'echo' : 'html');
664: }
665:
666: /**
667: * Destructor.
668: */
669: public function __destruct()
670: {
671: //Close any open SMTP connection nicely
672: $this->smtpClose();
673: }
674:
675: /**
676: * Call mail() in a safe_mode-aware fashion.
677: * Also, unless sendmail_path points to sendmail (or something that
678: * claims to be sendmail), don't pass params (not a perfect fix,
679: * but it will do)
680: * @param string $to To
681: * @param string $subject Subject
682: * @param string $body Message Body
683: * @param string $header Additional Header(s)
684: * @param string $params Params
685: * @access private
686: * @return boolean
687: */
688: private function mailPassthru($to, $subject, $body, $header, $params)
689: {
690: //Check overloading of mail function to avoid double-encoding
691: if (ini_get('mbstring.func_overload') & 1) {
692: $subject = $this->secureHeader($subject);
693: } else {
694: $subject = $this->encodeHeader($this->secureHeader($subject));
695: }
696:
697: //Can't use additional_parameters in safe_mode, calling mail() with null params breaks
698: //@link http://php.net/manual/en/function.mail.php
699: if (ini_get('safe_mode') or !$this->UseSendmailOptions or is_null($params)) {
700: $result = @mail($to, $subject, $body, $header);
701: } else {
702: $result = @mail($to, $subject, $body, $header, $params);
703: }
704: return $result;
705: }
706: /**
707: * Output debugging info via user-defined method.
708: * Only generates output if SMTP debug output is enabled (@see SMTP::$do_debug).
709: * @see PHPMailer::$Debugoutput
710: * @see PHPMailer::$SMTPDebug
711: * @param string $str
712: */
713: protected function edebug($str)
714: {
715: if ($this->SMTPDebug <= 0) {
716: return;
717: }
718: //Avoid clash with built-in function names
719: if (!in_array($this->Debugoutput, array('error_log', 'html', 'echo')) and is_callable($this->Debugoutput)) {
720: call_user_func($this->Debugoutput, $str, $this->SMTPDebug);
721: return;
722: }
723: switch ($this->Debugoutput) {
724: case 'error_log':
725: //Don't output, just log
726: error_log($str);
727: break;
728: case 'html':
729: //Cleans up output a bit for a better looking, HTML-safe output
730: echo htmlentities(
731: preg_replace('/[\r\n]+/', '', $str),
732: ENT_QUOTES,
733: 'UTF-8'
734: )
735: . "<br>\n";
736: break;
737: case 'echo':
738: default:
739: //Normalize line breaks
740: $str = preg_replace('/\r\n?/ms', "\n", $str);
741: echo gmdate('Y-m-d H:i:s') . "\t" . str_replace(
742: "\n",
743: "\n \t ",
744: trim($str)
745: ) . "\n";
746: }
747: }
748:
749: /**
750: * Sets message type to HTML or plain.
751: * @param boolean $isHtml True for HTML mode.
752: * @return void
753: */
754: public function isHTML($isHtml = true)
755: {
756: if ($isHtml) {
757: $this->ContentType = 'text/html';
758: } else {
759: $this->ContentType = 'text/plain';
760: }
761: }
762:
763: /**
764: * Send messages using SMTP.
765: * @return void
766: */
767: public function isSMTP()
768: {
769: $this->Mailer = 'smtp';
770: }
771:
772: /**
773: * Send messages using PHP's mail() function.
774: * @return void
775: */
776: public function isMail()
777: {
778: $this->Mailer = 'mail';
779: }
780:
781: /**
782: * Send messages using $Sendmail.
783: * @return void
784: */
785: public function isSendmail()
786: {
787: $ini_sendmail_path = ini_get('sendmail_path');
788:
789: if (!stristr($ini_sendmail_path, 'sendmail')) {
790: $this->Sendmail = '/usr/sbin/sendmail';
791: } else {
792: $this->Sendmail = $ini_sendmail_path;
793: }
794: $this->Mailer = 'sendmail';
795: }
796:
797: /**
798: * Send messages using qmail.
799: * @return void
800: */
801: public function isQmail()
802: {
803: $ini_sendmail_path = ini_get('sendmail_path');
804:
805: if (!stristr($ini_sendmail_path, 'qmail')) {
806: $this->Sendmail = '/var/qmail/bin/qmail-inject';
807: } else {
808: $this->Sendmail = $ini_sendmail_path;
809: }
810: $this->Mailer = 'qmail';
811: }
812:
813: /**
814: * Add a "To" address.
815: * @param string $address The email address to send to
816: * @param string $name
817: * @return boolean true on success, false if address already used or invalid in some way
818: */
819: public function addAddress($address, $name = '')
820: {
821: return $this->addOrEnqueueAnAddress('to', $address, $name);
822: }
823:
824: /**
825: * Add a "CC" address.
826: * @note: This function works with the SMTP mailer on win32, not with the "mail" mailer.
827: * @param string $address The email address to send to
828: * @param string $name
829: * @return boolean true on success, false if address already used or invalid in some way
830: */
831: public function addCC($address, $name = '')
832: {
833: return $this->addOrEnqueueAnAddress('cc', $address, $name);
834: }
835:
836: /**
837: * Add a "BCC" address.
838: * @note: This function works with the SMTP mailer on win32, not with the "mail" mailer.
839: * @param string $address The email address to send to
840: * @param string $name
841: * @return boolean true on success, false if address already used or invalid in some way
842: */
843: public function addBCC($address, $name = '')
844: {
845: return $this->addOrEnqueueAnAddress('bcc', $address, $name);
846: }
847:
848: /**
849: * Add a "Reply-To" address.
850: * @param string $address The email address to reply to
851: * @param string $name
852: * @return boolean true on success, false if address already used or invalid in some way
853: */
854: public function addReplyTo($address, $name = '')
855: {
856: return $this->addOrEnqueueAnAddress('Reply-To', $address, $name);
857: }
858:
859: /**
860: * Add an address to one of the recipient arrays or to the ReplyTo array. Because PHPMailer
861: * can't validate addresses with an IDN without knowing the PHPMailer::$CharSet (that can still
862: * be modified after calling this function), addition of such addresses is delayed until send().
863: * Addresses that have been added already return false, but do not throw exceptions.
864: * @param string $kind One of 'to', 'cc', 'bcc', or 'ReplyTo'
865: * @param string $address The email address to send, resp. to reply to
866: * @param string $name
867: * @throws phpmailerException
868: * @return boolean true on success, false if address already used or invalid in some way
869: * @access protected
870: */
871: protected function addOrEnqueueAnAddress($kind, $address, $name)
872: {
873: $address = trim($address);
874: $name = trim(preg_replace('/[\r\n]+/', '', $name)); //Strip breaks and trim
875: if (($pos = strrpos($address, '@')) === false) {
876: // At-sign is misssing.
877: $error_message = $this->lang('invalid_address') . " (addAnAddress $kind): $address";
878: $this->setError($error_message);
879: $this->edebug($error_message);
880: if ($this->exceptions) {
881: throw new phpmailerException($error_message);
882: }
883: return false;
884: }
885: $params = array($kind, $address, $name);
886: // Enqueue addresses with IDN until we know the PHPMailer::$CharSet.
887: if ($this->has8bitChars(substr($address, ++$pos)) and $this->idnSupported()) {
888: if ($kind != 'Reply-To') {
889: if (!array_key_exists($address, $this->RecipientsQueue)) {
890: $this->RecipientsQueue[$address] = $params;
891: return true;
892: }
893: } else {
894: if (!array_key_exists($address, $this->ReplyToQueue)) {
895: $this->ReplyToQueue[$address] = $params;
896: return true;
897: }
898: }
899: return false;
900: }
901: // Immediately add standard addresses without IDN.
902: return call_user_func_array(array($this, 'addAnAddress'), $params);
903: }
904:
905: /**
906: * Add an address to one of the recipient arrays or to the ReplyTo array.
907: * Addresses that have been added already return false, but do not throw exceptions.
908: * @param string $kind One of 'to', 'cc', 'bcc', or 'ReplyTo'
909: * @param string $address The email address to send, resp. to reply to
910: * @param string $name
911: * @throws phpmailerException
912: * @return boolean true on success, false if address already used or invalid in some way
913: * @access protected
914: */
915: protected function addAnAddress($kind, $address, $name = '')
916: {
917: if (!in_array($kind, array('to', 'cc', 'bcc', 'Reply-To'))) {
918: $error_message = $this->lang('Invalid recipient kind: ') . $kind;
919: $this->setError($error_message);
920: $this->edebug($error_message);
921: if ($this->exceptions) {
922: throw new phpmailerException($error_message);
923: }
924: return false;
925: }
926: if (!$this->validateAddress($address)) {
927: $error_message = $this->lang('invalid_address') . " (addAnAddress $kind): $address";
928: $this->setError($error_message);
929: $this->edebug($error_message);
930: if ($this->exceptions) {
931: throw new phpmailerException($error_message);
932: }
933: return false;
934: }
935: if ($kind != 'Reply-To') {
936: if (!array_key_exists(strtolower($address), $this->all_recipients)) {
937: array_push($this->$kind, array($address, $name));
938: $this->all_recipients[strtolower($address)] = true;
939: return true;
940: }
941: } else {
942: if (!array_key_exists(strtolower($address), $this->ReplyTo)) {
943: $this->ReplyTo[strtolower($address)] = array($address, $name);
944: return true;
945: }
946: }
947: return false;
948: }
949:
950: /**
951: * Parse and validate a string containing one or more RFC822-style comma-separated email addresses
952: * of the form "display name <address>" into an array of name/address pairs.
953: * Uses the imap_rfc822_parse_adrlist function if the IMAP extension is available.
954: * Note that quotes in the name part are removed.
955: * @param string $addrstr The address list string
956: * @param bool $useimap Whether to use the IMAP extension to parse the list
957: * @return array
958: * @link http://www.andrew.cmu.edu/user/agreen1/testing/mrbs/web/Mail/RFC822.php A more careful implementation
959: */
960: public function parseAddresses($addrstr, $useimap = true)
961: {
962: $addresses = array();
963: if ($useimap and function_exists('imap_rfc822_parse_adrlist')) {
964: //Use this built-in parser if it's available
965: $list = imap_rfc822_parse_adrlist($addrstr, '');
966: foreach ($list as $address) {
967: if ($address->host != '.SYNTAX-ERROR.') {
968: if ($this->validateAddress($address->mailbox . '@' . $address->host)) {
969: $addresses[] = array(
970: 'name' => (property_exists($address, 'personal') ? $address->personal : ''),
971: 'address' => $address->mailbox . '@' . $address->host
972: );
973: }
974: }
975: }
976: } else {
977: //Use this simpler parser
978: $list = explode(',', $addrstr);
979: foreach ($list as $address) {
980: $address = trim($address);
981: //Is there a separate name part?
982: if (strpos($address, '<') === false) {
983: //No separate name, just use the whole thing
984: if ($this->validateAddress($address)) {
985: $addresses[] = array(
986: 'name' => '',
987: 'address' => $address
988: );
989: }
990: } else {
991: list($name, $email) = explode('<', $address);
992: $email = trim(str_replace('>', '', $email));
993: if ($this->validateAddress($email)) {
994: $addresses[] = array(
995: 'name' => trim(str_replace(array('"', "'"), '', $name)),
996: 'address' => $email
997: );
998: }
999: }
1000: }
1001: }
1002: return $addresses;
1003: }
1004:
1005: /**
1006: * Set the From and FromName properties.
1007: * @param string $address
1008: * @param string $name
1009: * @param boolean $auto Whether to also set the Sender address, defaults to true
1010: * @throws phpmailerException
1011: * @return boolean
1012: */
1013: public function setFrom($address, $name = '', $auto = true)
1014: {
1015: $address = trim($address);
1016: $name = trim(preg_replace('/[\r\n]+/', '', $name)); //Strip breaks and trim
1017: // Don't validate now addresses with IDN. Will be done in send().
1018: if (($pos = strrpos($address, '@')) === false or
1019: (!$this->has8bitChars(substr($address, ++$pos)) or !$this->idnSupported()) and
1020: !$this->validateAddress($address)) {
1021: $error_message = $this->lang('invalid_address') . " (setFrom) $address";
1022: $this->setError($error_message);
1023: $this->edebug($error_message);
1024: if ($this->exceptions) {
1025: throw new phpmailerException($error_message);
1026: }
1027: return false;
1028: }
1029: $this->From = $address;
1030: $this->FromName = $name;
1031: if ($auto) {
1032: if (empty($this->Sender)) {
1033: $this->Sender = $address;
1034: }
1035: }
1036: return true;
1037: }
1038:
1039: /**
1040: * Return the Message-ID header of the last email.
1041: * Technically this is the value from the last time the headers were created,
1042: * but it's also the message ID of the last sent message except in
1043: * pathological cases.
1044: * @return string
1045: */
1046: public function getLastMessageID()
1047: {
1048: return $this->lastMessageID;
1049: }
1050:
1051: /**
1052: * Check that a string looks like an email address.
1053: * @param string $address The email address to check
1054: * @param string|callable $patternselect A selector for the validation pattern to use :
1055: * * `auto` Pick best pattern automatically;
1056: * * `pcre8` Use the squiloople.com pattern, requires PCRE > 8.0, PHP >= 5.3.2, 5.2.14;
1057: * * `pcre` Use old PCRE implementation;
1058: * * `php` Use PHP built-in FILTER_VALIDATE_EMAIL;
1059: * * `html5` Use the pattern given by the HTML5 spec for 'email' type form input elements.
1060: * * `noregex` Don't use a regex: super fast, really dumb.
1061: * Alternatively you may pass in a callable to inject your own validator, for example:
1062: * PHPMailer::validateAddress('user@example.com', function($address) {
1063: * return (strpos($address, '@') !== false);
1064: * });
1065: * You can also set the PHPMailer::$validator static to a callable, allowing built-in methods to use your validator.
1066: * @return boolean
1067: * @static
1068: * @access public
1069: */
1070: public static function validateAddress($address, $patternselect = null)
1071: {
1072: if (is_null($patternselect)) {
1073: $patternselect = self::$validator;
1074: }
1075: if (is_callable($patternselect)) {
1076: return call_user_func($patternselect, $address);
1077: }
1078: //Reject line breaks in addresses; it's valid RFC5322, but not RFC5321
1079: if (strpos($address, "\n") !== false or strpos($address, "\r") !== false) {
1080: return false;
1081: }
1082: if (!$patternselect or $patternselect == 'auto') {
1083: //Check this constant first so it works when extension_loaded() is disabled by safe mode
1084: //Constant was added in PHP 5.2.4
1085: if (defined('PCRE_VERSION')) {
1086: //This pattern can get stuck in a recursive loop in PCRE <= 8.0.2
1087: if (version_compare(PCRE_VERSION, '8.0.3') >= 0) {
1088: $patternselect = 'pcre8';
1089: } else {
1090: $patternselect = 'pcre';
1091: }
1092: } elseif (function_exists('extension_loaded') and extension_loaded('pcre')) {
1093: //Fall back to older PCRE
1094: $patternselect = 'pcre';
1095: } else {
1096: //Filter_var appeared in PHP 5.2.0 and does not require the PCRE extension
1097: if (version_compare(PHP_VERSION, '5.2.0') >= 0) {
1098: $patternselect = 'php';
1099: } else {
1100: $patternselect = 'noregex';
1101: }
1102: }
1103: }
1104: switch ($patternselect) {
1105: case 'pcre8':
1106: /**
1107: * Uses the same RFC5322 regex on which FILTER_VALIDATE_EMAIL is based, but allows dotless domains.
1108: * @link http://squiloople.com/2009/12/20/email-address-validation/
1109: * @copyright 2009-2010 Michael Rushton
1110: * Feel free to use and redistribute this code. But please keep this copyright notice.
1111: */
1112: return (boolean)preg_match(
1113: '/^(?!(?>(?1)"?(?>\\\[ -~]|[^"])"?(?1)){255,})(?!(?>(?1)"?(?>\\\[ -~]|[^"])"?(?1)){65,}@)' .
1114: '((?>(?>(?>((?>(?>(?>\x0D\x0A)?[\t ])+|(?>[\t ]*\x0D\x0A)?[\t ]+)?)(\((?>(?2)' .
1115: '(?>[\x01-\x08\x0B\x0C\x0E-\'*-\[\]-\x7F]|\\\[\x00-\x7F]|(?3)))*(?2)\)))+(?2))|(?2))?)' .
1116: '([!#-\'*+\/-9=?^-~-]+|"(?>(?2)(?>[\x01-\x08\x0B\x0C\x0E-!#-\[\]-\x7F]|\\\[\x00-\x7F]))*' .
1117: '(?2)")(?>(?1)\.(?1)(?4))*(?1)@(?!(?1)[a-z0-9-]{64,})(?1)(?>([a-z0-9](?>[a-z0-9-]*[a-z0-9])?)' .
1118: '(?>(?1)\.(?!(?1)[a-z0-9-]{64,})(?1)(?5)){0,126}|\[(?:(?>IPv6:(?>([a-f0-9]{1,4})(?>:(?6)){7}' .
1119: '|(?!(?:.*[a-f0-9][:\]]){8,})((?6)(?>:(?6)){0,6})?::(?7)?))|(?>(?>IPv6:(?>(?6)(?>:(?6)){5}:' .
1120: '|(?!(?:.*[a-f0-9]:){6,})(?8)?::(?>((?6)(?>:(?6)){0,4}):)?))?(25[0-5]|2[0-4][0-9]|1[0-9]{2}' .
1121: '|[1-9]?[0-9])(?>\.(?9)){3}))\])(?1)$/isD',
1122: $address
1123: );
1124: case 'pcre':
1125: //An older regex that doesn't need a recent PCRE
1126: return (boolean)preg_match(
1127: '/^(?!(?>"?(?>\\\[ -~]|[^"])"?){255,})(?!(?>"?(?>\\\[ -~]|[^"])"?){65,}@)(?>' .
1128: '[!#-\'*+\/-9=?^-~-]+|"(?>(?>[\x01-\x08\x0B\x0C\x0E-!#-\[\]-\x7F]|\\\[\x00-\xFF]))*")' .
1129: '(?>\.(?>[!#-\'*+\/-9=?^-~-]+|"(?>(?>[\x01-\x08\x0B\x0C\x0E-!#-\[\]-\x7F]|\\\[\x00-\xFF]))*"))*' .
1130: '@(?>(?![a-z0-9-]{64,})(?>[a-z0-9](?>[a-z0-9-]*[a-z0-9])?)(?>\.(?![a-z0-9-]{64,})' .
1131: '(?>[a-z0-9](?>[a-z0-9-]*[a-z0-9])?)){0,126}|\[(?:(?>IPv6:(?>(?>[a-f0-9]{1,4})(?>:' .
1132: '[a-f0-9]{1,4}){7}|(?!(?:.*[a-f0-9][:\]]){8,})(?>[a-f0-9]{1,4}(?>:[a-f0-9]{1,4}){0,6})?' .
1133: '::(?>[a-f0-9]{1,4}(?>:[a-f0-9]{1,4}){0,6})?))|(?>(?>IPv6:(?>[a-f0-9]{1,4}(?>:' .
1134: '[a-f0-9]{1,4}){5}:|(?!(?:.*[a-f0-9]:){6,})(?>[a-f0-9]{1,4}(?>:[a-f0-9]{1,4}){0,4})?' .
1135: '::(?>(?:[a-f0-9]{1,4}(?>:[a-f0-9]{1,4}){0,4}):)?))?(?>25[0-5]|2[0-4][0-9]|1[0-9]{2}' .
1136: '|[1-9]?[0-9])(?>\.(?>25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3}))\])$/isD',
1137: $address
1138: );
1139: case 'html5':
1140: /**
1141: * This is the pattern used in the HTML5 spec for validation of 'email' type form input elements.
1142: * @link http://www.whatwg.org/specs/web-apps/current-work/#e-mail-state-(type=email)
1143: */
1144: return (boolean)preg_match(
1145: '/^[a-zA-Z0-9.!#$%&\'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}' .
1146: '[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/sD',
1147: $address
1148: );
1149: case 'noregex':
1150: //No PCRE! Do something _very_ approximate!
1151: //Check the address is 3 chars or longer and contains an @ that's not the first or last char
1152: return (strlen($address) >= 3
1153: and strpos($address, '@') >= 1
1154: and strpos($address, '@') != strlen($address) - 1);
1155: case 'php':
1156: default:
1157: return (boolean)filter_var($address, FILTER_VALIDATE_EMAIL);
1158: }
1159: }
1160:
1161: /**
1162: * Tells whether IDNs (Internationalized Domain Names) are supported or not. This requires the
1163: * "intl" and "mbstring" PHP extensions.
1164: * @return bool "true" if required functions for IDN support are present
1165: */
1166: public function idnSupported()
1167: {
1168: // @TODO: Write our own "idn_to_ascii" function for PHP <= 5.2.
1169: return function_exists('idn_to_ascii') and function_exists('mb_convert_encoding');
1170: }
1171:
1172: /**
1173: * Converts IDN in given email address to its ASCII form, also known as punycode, if possible.
1174: * Important: Address must be passed in same encoding as currently set in PHPMailer::$CharSet.
1175: * This function silently returns unmodified address if:
1176: * - No conversion is necessary (i.e. domain name is not an IDN, or is already in ASCII form)
1177: * - Conversion to punycode is impossible (e.g. required PHP functions are not available)
1178: * or fails for any reason (e.g. domain has characters not allowed in an IDN)
1179: * @see PHPMailer::$CharSet
1180: * @param string $address The email address to convert
1181: * @return string The encoded address in ASCII form
1182: */
1183: public function punyencodeAddress($address)
1184: {
1185: // Verify we have required functions, CharSet, and at-sign.
1186: if ($this->idnSupported() and
1187: !empty($this->CharSet) and
1188: ($pos = strrpos($address, '@')) !== false) {
1189: $domain = substr($address, ++$pos);
1190: // Verify CharSet string is a valid one, and domain properly encoded in this CharSet.
1191: if ($this->has8bitChars($domain) and @mb_check_encoding($domain, $this->CharSet)) {
1192: $domain = mb_convert_encoding($domain, 'UTF-8', $this->CharSet);
1193: if (($punycode = defined('INTL_IDNA_VARIANT_UTS46') ?
1194: idn_to_ascii($domain, 0, INTL_IDNA_VARIANT_UTS46) :
1195: idn_to_ascii($domain)) !== false) {
1196: return substr($address, 0, $pos) . $punycode;
1197: }
1198: }
1199: }
1200: return $address;
1201: }
1202:
1203: /**
1204: * Create a message and send it.
1205: * Uses the sending method specified by $Mailer.
1206: * @throws phpmailerException
1207: * @return boolean false on error - See the ErrorInfo property for details of the error.
1208: */
1209: public function send()
1210: {
1211: try {
1212: if (!$this->preSend()) {
1213: return false;
1214: }
1215: return $this->postSend();
1216: } catch (phpmailerException $exc) {
1217: $this->mailHeader = '';
1218: $this->setError($exc->getMessage());
1219: if ($this->exceptions) {
1220: throw $exc;
1221: }
1222: return false;
1223: }
1224: }
1225:
1226: /**
1227: * Prepare a message for sending.
1228: * @throws phpmailerException
1229: * @return boolean
1230: */
1231: public function preSend()
1232: {
1233: try {
1234: $this->error_count = 0; // Reset errors
1235: $this->mailHeader = '';
1236:
1237: // Dequeue recipient and Reply-To addresses with IDN
1238: foreach (array_merge($this->RecipientsQueue, $this->ReplyToQueue) as $params) {
1239: $params[1] = $this->punyencodeAddress($params[1]);
1240: call_user_func_array(array($this, 'addAnAddress'), $params);
1241: }
1242: if ((count($this->to) + count($this->cc) + count($this->bcc)) < 1) {
1243: throw new phpmailerException($this->lang('provide_address'), self::STOP_CRITICAL);
1244: }
1245:
1246: // Validate From, Sender, and ConfirmReadingTo addresses
1247: foreach (array('From', 'Sender', 'ConfirmReadingTo') as $address_kind) {
1248: $this->$address_kind = trim($this->$address_kind);
1249: if (empty($this->$address_kind)) {
1250: continue;
1251: }
1252: $this->$address_kind = $this->punyencodeAddress($this->$address_kind);
1253: if (!$this->validateAddress($this->$address_kind)) {
1254: $error_message = $this->lang('invalid_address') . ' (punyEncode) ' . $this->$address_kind;
1255: $this->setError($error_message);
1256: $this->edebug($error_message);
1257: if ($this->exceptions) {
1258: throw new phpmailerException($error_message);
1259: }
1260: return false;
1261: }
1262: }
1263:
1264: // Set whether the message is multipart/alternative
1265: if ($this->alternativeExists()) {
1266: $this->ContentType = 'multipart/alternative';
1267: }
1268:
1269: $this->setMessageType();
1270: // Refuse to send an empty message unless we are specifically allowing it
1271: if (!$this->AllowEmpty and empty($this->Body)) {
1272: throw new phpmailerException($this->lang('empty_message'), self::STOP_CRITICAL);
1273: }
1274:
1275: // Create body before headers in case body makes changes to headers (e.g. altering transfer encoding)
1276: $this->MIMEHeader = '';
1277: $this->MIMEBody = $this->createBody();
1278: // createBody may have added some headers, so retain them
1279: $tempheaders = $this->MIMEHeader;
1280: $this->MIMEHeader = $this->createHeader();
1281: $this->MIMEHeader .= $tempheaders;
1282:
1283: // To capture the complete message when using mail(), create
1284: // an extra header list which createHeader() doesn't fold in
1285: if ($this->Mailer == 'mail') {
1286: if (count($this->to) > 0) {
1287: $this->mailHeader .= $this->addrAppend('To', $this->to);
1288: } else {
1289: $this->mailHeader .= $this->headerLine('To', 'undisclosed-recipients:;');
1290: }
1291: $this->mailHeader .= $this->headerLine(
1292: 'Subject',
1293: $this->encodeHeader($this->secureHeader(trim($this->Subject)))
1294: );
1295: }
1296:
1297: // Sign with DKIM if enabled
1298: if (!empty($this->DKIM_domain)
1299: and !empty($this->DKIM_selector)
1300: and (!empty($this->DKIM_private_string)
1301: or (!empty($this->DKIM_private)
1302: and self::isPermittedPath($this->DKIM_private)
1303: and file_exists($this->DKIM_private)
1304: )
1305: )
1306: ) {
1307: $header_dkim = $this->DKIM_Add(
1308: $this->MIMEHeader . $this->mailHeader,
1309: $this->encodeHeader($this->secureHeader($this->Subject)),
1310: $this->MIMEBody
1311: );
1312: $this->MIMEHeader = rtrim($this->MIMEHeader, "\r\n ") . self::CRLF .
1313: str_replace("\r\n", "\n", $header_dkim) . self::CRLF;
1314: }
1315: return true;
1316: } catch (phpmailerException $exc) {
1317: $this->setError($exc->getMessage());
1318: if ($this->exceptions) {
1319: throw $exc;
1320: }
1321: return false;
1322: }
1323: }
1324:
1325: /**
1326: * Actually send a message.
1327: * Send the email via the selected mechanism
1328: * @throws phpmailerException
1329: * @return boolean
1330: */
1331: public function postSend()
1332: {
1333: try {
1334: // Choose the mailer and send through it
1335: switch ($this->Mailer) {
1336: case 'sendmail':
1337: case 'qmail':
1338: return $this->sendmailSend($this->MIMEHeader, $this->MIMEBody);
1339: case 'smtp':
1340: return $this->smtpSend($this->MIMEHeader, $this->MIMEBody);
1341: case 'mail':
1342: return $this->mailSend($this->MIMEHeader, $this->MIMEBody);
1343: default:
1344: $sendMethod = $this->Mailer.'Send';
1345: if (method_exists($this, $sendMethod)) {
1346: return $this->$sendMethod($this->MIMEHeader, $this->MIMEBody);
1347: }
1348:
1349: return $this->mailSend($this->MIMEHeader, $this->MIMEBody);
1350: }
1351: } catch (phpmailerException $exc) {
1352: $this->setError($exc->getMessage());
1353: $this->edebug($exc->getMessage());
1354: if ($this->exceptions) {
1355: throw $exc;
1356: }
1357: }
1358: return false;
1359: }
1360:
1361: /**
1362: * Send mail using the $Sendmail program.
1363: * @param string $header The message headers
1364: * @param string $body The message body
1365: * @see PHPMailer::$Sendmail
1366: * @throws phpmailerException
1367: * @access protected
1368: * @return boolean
1369: */
1370: protected function sendmailSend($header, $body)
1371: {
1372: // CVE-2016-10033, CVE-2016-10045: Don't pass -f if characters will be escaped.
1373: if (!empty($this->Sender) and self::isShellSafe($this->Sender)) {
1374: if ($this->Mailer == 'qmail') {
1375: $sendmailFmt = '%s -f%s';
1376: } else {
1377: $sendmailFmt = '%s -oi -f%s -t';
1378: }
1379: } else {
1380: if ($this->Mailer == 'qmail') {
1381: $sendmailFmt = '%s';
1382: } else {
1383: $sendmailFmt = '%s -oi -t';
1384: }
1385: }
1386:
1387: // TODO: If possible, this should be changed to escapeshellarg. Needs thorough testing.
1388: $sendmail = sprintf($sendmailFmt, escapeshellcmd($this->Sendmail), $this->Sender);
1389:
1390: if ($this->SingleTo) {
1391: foreach ($this->SingleToArray as $toAddr) {
1392: if (!@$mail = popen($sendmail, 'w')) {
1393: throw new phpmailerException($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL);
1394: }
1395: fputs($mail, 'To: ' . $toAddr . "\n");
1396: fputs($mail, $header);
1397: fputs($mail, $body);
1398: $result = pclose($mail);
1399: $this->doCallback(
1400: ($result == 0),
1401: array($toAddr),
1402: $this->cc,
1403: $this->bcc,
1404: $this->Subject,
1405: $body,
1406: $this->From
1407: );
1408: if ($result != 0) {
1409: throw new phpmailerException($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL);
1410: }
1411: }
1412: } else {
1413: if (!@$mail = popen($sendmail, 'w')) {
1414: throw new phpmailerException($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL);
1415: }
1416: fputs($mail, $header);
1417: fputs($mail, $body);
1418: $result = pclose($mail);
1419: $this->doCallback(
1420: ($result == 0),
1421: $this->to,
1422: $this->cc,
1423: $this->bcc,
1424: $this->Subject,
1425: $body,
1426: $this->From
1427: );
1428: if ($result != 0) {
1429: throw new phpmailerException($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL);
1430: }
1431: }
1432: return true;
1433: }
1434:
1435: /**
1436: * Fix CVE-2016-10033 and CVE-2016-10045 by disallowing potentially unsafe shell characters.
1437: *
1438: * Note that escapeshellarg and escapeshellcmd are inadequate for our purposes, especially on Windows.
1439: * @param string $string The string to be validated
1440: * @see https://github.com/PHPMailer/PHPMailer/issues/924 CVE-2016-10045 bug report
1441: * @access protected
1442: * @return boolean
1443: */
1444: protected static function isShellSafe($string)
1445: {
1446: // Future-proof
1447: if (escapeshellcmd($string) !== $string
1448: or !in_array(escapeshellarg($string), array("'$string'", "\"$string\""))
1449: ) {
1450: return false;
1451: }
1452:
1453: $length = strlen($string);
1454:
1455: for ($i = 0; $i < $length; $i++) {
1456: $c = $string[$i];
1457:
1458: // All other characters have a special meaning in at least one common shell, including = and +.
1459: // Full stop (.) has a special meaning in cmd.exe, but its impact should be negligible here.
1460: // Note that this does permit non-Latin alphanumeric characters based on the current locale.
1461: if (!ctype_alnum($c) && strpos('@_-.', $c) === false) {
1462: return false;
1463: }
1464: }
1465:
1466: return true;
1467: }
1468:
1469: /**
1470: * Check whether a file path is of a permitted type.
1471: * Used to reject URLs and phar files from functions that access local file paths,
1472: * such as addAttachment.
1473: * @param string $path A relative or absolute path to a file.
1474: * @return bool
1475: */
1476: protected static function isPermittedPath($path)
1477: {
1478: return !preg_match('#^[a-z]+://#i', $path);
1479: }
1480:
1481: /**
1482: * Send mail using the PHP mail() function.
1483: * @param string $header The message headers
1484: * @param string $body The message body
1485: * @link http://www.php.net/manual/en/book.mail.php
1486: * @throws phpmailerException
1487: * @access protected
1488: * @return boolean
1489: */
1490: protected function mailSend($header, $body)
1491: {
1492: $toArr = array();
1493: foreach ($this->to as $toaddr) {
1494: $toArr[] = $this->addrFormat($toaddr);
1495: }
1496: $to = implode(', ', $toArr);
1497:
1498: $params = null;
1499: //This sets the SMTP envelope sender which gets turned into a return-path header by the receiver
1500: if (!empty($this->Sender) and $this->validateAddress($this->Sender)) {
1501: // CVE-2016-10033, CVE-2016-10045: Don't pass -f if characters will be escaped.
1502: if (self::isShellSafe($this->Sender)) {
1503: $params = sprintf('-f%s', $this->Sender);
1504: }
1505: }
1506: if (!empty($this->Sender) and !ini_get('safe_mode') and $this->validateAddress($this->Sender)) {
1507: $old_from = ini_get('sendmail_from');
1508: ini_set('sendmail_from', $this->Sender);
1509: }
1510: $result = false;
1511: if ($this->SingleTo and count($toArr) > 1) {
1512: foreach ($toArr as $toAddr) {
1513: $result = $this->mailPassthru($toAddr, $this->Subject, $body, $header, $params);
1514: $this->doCallback($result, array($toAddr), $this->cc, $this->bcc, $this->Subject, $body, $this->From);
1515: }
1516: } else {
1517: $result = $this->mailPassthru($to, $this->Subject, $body, $header, $params);
1518: $this->doCallback($result, $this->to, $this->cc, $this->bcc, $this->Subject, $body, $this->From);
1519: }
1520: if (isset($old_from)) {
1521: ini_set('sendmail_from', $old_from);
1522: }
1523: if (!$result) {
1524: throw new phpmailerException($this->lang('instantiate'), self::STOP_CRITICAL);
1525: }
1526: return true;
1527: }
1528:
1529: /**
1530: * Get an instance to use for SMTP operations.
1531: * Override this function to load your own SMTP implementation
1532: * @return SMTP
1533: */
1534: public function getSMTPInstance()
1535: {
1536: if (!is_object($this->smtp)) {
1537: $this->smtp = new SMTP;
1538: }
1539: return $this->smtp;
1540: }
1541:
1542: /**
1543: * Send mail via SMTP.
1544: * Returns false if there is a bad MAIL FROM, RCPT, or DATA input.
1545: * Uses the PHPMailerSMTP class by default.
1546: * @see PHPMailer::getSMTPInstance() to use a different class.
1547: * @param string $header The message headers
1548: * @param string $body The message body
1549: * @throws phpmailerException
1550: * @uses SMTP
1551: * @access protected
1552: * @return boolean
1553: */
1554: protected function smtpSend($header, $body)
1555: {
1556: $bad_rcpt = array();
1557: if (!$this->smtpConnect($this->SMTPOptions)) {
1558: throw new phpmailerException($this->lang('smtp_connect_failed'), self::STOP_CRITICAL);
1559: }
1560: if (!empty($this->Sender) and $this->validateAddress($this->Sender)) {
1561: $smtp_from = $this->Sender;
1562: } else {
1563: $smtp_from = $this->From;
1564: }
1565: if (!$this->smtp->mail($smtp_from)) {
1566: $this->setError($this->lang('from_failed') . $smtp_from . ' : ' . implode(',', $this->smtp->getError()));
1567: throw new phpmailerException($this->ErrorInfo, self::STOP_CRITICAL);
1568: }
1569:
1570: // Attempt to send to all recipients
1571: foreach (array($this->to, $this->cc, $this->bcc) as $togroup) {
1572: foreach ($togroup as $to) {
1573: if (!$this->smtp->recipient($to[0])) {
1574: $error = $this->smtp->getError();
1575: $bad_rcpt[] = array('to' => $to[0], 'error' => $error['detail']);
1576: $isSent = false;
1577: } else {
1578: $isSent = true;
1579: }
1580: $this->doCallback($isSent, array($to[0]), array(), array(), $this->Subject, $body, $this->From);
1581: }
1582: }
1583:
1584: // Only send the DATA command if we have viable recipients
1585: if ((count($this->all_recipients) > count($bad_rcpt)) and !$this->smtp->data($header . $body)) {
1586: throw new phpmailerException($this->lang('data_not_accepted'), self::STOP_CRITICAL);
1587: }
1588: if ($this->SMTPKeepAlive) {
1589: $this->smtp->reset();
1590: } else {
1591: $this->smtp->quit();
1592: $this->smtp->close();
1593: }
1594: //Create error message for any bad addresses
1595: if (count($bad_rcpt) > 0) {
1596: $errstr = '';
1597: foreach ($bad_rcpt as $bad) {
1598: $errstr .= $bad['to'] . ': ' . $bad['error'];
1599: }
1600: throw new phpmailerException(
1601: $this->lang('recipients_failed') . $errstr,
1602: self::STOP_CONTINUE
1603: );
1604: }
1605: return true;
1606: }
1607:
1608: /**
1609: * Initiate a connection to an SMTP server.
1610: * Returns false if the operation failed.
1611: * @param array $options An array of options compatible with stream_context_create()
1612: * @uses SMTP
1613: * @access public
1614: * @throws phpmailerException
1615: * @return boolean
1616: */
1617: public function smtpConnect($options = null)
1618: {
1619: if (is_null($this->smtp)) {
1620: $this->smtp = $this->getSMTPInstance();
1621: }
1622:
1623: //If no options are provided, use whatever is set in the instance
1624: if (is_null($options)) {
1625: $options = $this->SMTPOptions;
1626: }
1627:
1628: // Already connected?
1629: if ($this->smtp->connected()) {
1630: return true;
1631: }
1632:
1633: $this->smtp->setTimeout($this->Timeout);
1634: $this->smtp->setDebugLevel($this->SMTPDebug);
1635: $this->smtp->setDebugOutput($this->Debugoutput);
1636: $this->smtp->setVerp($this->do_verp);
1637: $hosts = explode(';', $this->Host);
1638: $lastexception = null;
1639:
1640: foreach ($hosts as $hostentry) {
1641: $hostinfo = array();
1642: if (!preg_match(
1643: '/^((ssl|tls):\/\/)*([a-zA-Z0-9\.-]*|\[[a-fA-F0-9:]+\]):?([0-9]*)$/',
1644: trim($hostentry),
1645: $hostinfo
1646: )) {
1647: // Not a valid host entry
1648: $this->edebug('Ignoring invalid host: ' . $hostentry);
1649: continue;
1650: }
1651: // $hostinfo[2]: optional ssl or tls prefix
1652: // $hostinfo[3]: the hostname
1653: // $hostinfo[4]: optional port number
1654: // The host string prefix can temporarily override the current setting for SMTPSecure
1655: // If it's not specified, the default value is used
1656: $prefix = '';
1657: $secure = $this->SMTPSecure;
1658: $tls = ($this->SMTPSecure == 'tls');
1659: if ('ssl' == $hostinfo[2] or ('' == $hostinfo[2] and 'ssl' == $this->SMTPSecure)) {
1660: $prefix = 'ssl://';
1661: $tls = false; // Can't have SSL and TLS at the same time
1662: $secure = 'ssl';
1663: } elseif ($hostinfo[2] == 'tls') {
1664: $tls = true;
1665: // tls doesn't use a prefix
1666: $secure = 'tls';
1667: }
1668: //Do we need the OpenSSL extension?
1669: $sslext = defined('OPENSSL_ALGO_SHA1');
1670: if ('tls' === $secure or 'ssl' === $secure) {
1671: //Check for an OpenSSL constant rather than using extension_loaded, which is sometimes disabled
1672: if (!$sslext) {
1673: throw new phpmailerException($this->lang('extension_missing').'openssl', self::STOP_CRITICAL);
1674: }
1675: }
1676: $host = $hostinfo[3];
1677: $port = $this->Port;
1678: $tport = (integer)$hostinfo[4];
1679: if ($tport > 0 and $tport < 65536) {
1680: $port = $tport;
1681: }
1682: if ($this->smtp->connect($prefix . $host, $port, $this->Timeout, $options)) {
1683: try {
1684: if ($this->Helo) {
1685: $hello = $this->Helo;
1686: } else {
1687: $hello = $this->serverHostname();
1688: }
1689: $this->smtp->hello($hello);
1690: //Automatically enable TLS encryption if:
1691: // * it's not disabled
1692: // * we have openssl extension
1693: // * we are not already using SSL
1694: // * the server offers STARTTLS
1695: if ($this->SMTPAutoTLS and $sslext and $secure != 'ssl' and $this->smtp->getServerExt('STARTTLS')) {
1696: $tls = true;
1697: }
1698: if ($tls) {
1699: if (!$this->smtp->startTLS()) {
1700: throw new phpmailerException($this->lang('connect_host'));
1701: }
1702: // We must resend EHLO after TLS negotiation
1703: $this->smtp->hello($hello);
1704: }
1705: if ($this->SMTPAuth) {
1706: if (!$this->smtp->authenticate(
1707: $this->Username,
1708: $this->Password,
1709: $this->AuthType,
1710: $this->Realm,
1711: $this->Workstation
1712: )
1713: ) {
1714: throw new phpmailerException($this->lang('authenticate'));
1715: }
1716: }
1717: return true;
1718: } catch (phpmailerException $exc) {
1719: $lastexception = $exc;
1720: $this->edebug($exc->getMessage());
1721: // We must have connected, but then failed TLS or Auth, so close connection nicely
1722: $this->smtp->quit();
1723: }
1724: }
1725: }
1726: // If we get here, all connection attempts have failed, so close connection hard
1727: $this->smtp->close();
1728: // As we've caught all exceptions, just report whatever the last one was
1729: if ($this->exceptions and !is_null($lastexception)) {
1730: throw $lastexception;
1731: }
1732: return false;
1733: }
1734:
1735: /**
1736: * Close the active SMTP session if one exists.
1737: * @return void
1738: */
1739: public function smtpClose()
1740: {
1741: if (is_a($this->smtp, 'SMTP')) {
1742: if ($this->smtp->connected()) {
1743: $this->smtp->quit();
1744: $this->smtp->close();
1745: }
1746: }
1747: }
1748:
1749: /**
1750: * Set the language for error messages.
1751: * Returns false if it cannot load the language file.
1752: * The default language is English.
1753: * @param string $langcode ISO 639-1 2-character language code (e.g. French is "fr")
1754: * @param string $lang_path Path to the language file directory, with trailing separator (slash)
1755: * @return boolean
1756: * @access public
1757: */
1758: public function setLanguage($langcode = 'en', $lang_path = '')
1759: {
1760: // Backwards compatibility for renamed language codes
1761: $renamed_langcodes = array(
1762: 'br' => 'pt_br',
1763: 'cz' => 'cs',
1764: 'dk' => 'da',
1765: 'no' => 'nb',
1766: 'se' => 'sv',
1767: 'sr' => 'rs'
1768: );
1769:
1770: if (isset($renamed_langcodes[$langcode])) {
1771: $langcode = $renamed_langcodes[$langcode];
1772: }
1773:
1774: // Define full set of translatable strings in English
1775: $PHPMAILER_LANG = array(
1776: 'authenticate' => 'SMTP Error: Could not authenticate.',
1777: 'connect_host' => 'SMTP Error: Could not connect to SMTP host.',
1778: 'data_not_accepted' => 'SMTP Error: data not accepted.',
1779: 'empty_message' => 'Message body empty',
1780: 'encoding' => 'Unknown encoding: ',
1781: 'execute' => 'Could not execute: ',
1782: 'file_access' => 'Could not access file: ',
1783: 'file_open' => 'File Error: Could not open file: ',
1784: 'from_failed' => 'The following From address failed: ',
1785: 'instantiate' => 'Could not instantiate mail function.',
1786: 'invalid_address' => 'Invalid address: ',
1787: 'mailer_not_supported' => ' mailer is not supported.',
1788: 'provide_address' => 'You must provide at least one recipient email address.',
1789: 'recipients_failed' => 'SMTP Error: The following recipients failed: ',
1790: 'signing' => 'Signing Error: ',
1791: 'smtp_connect_failed' => 'SMTP connect() failed.',
1792: 'smtp_error' => 'SMTP server error: ',
1793: 'variable_set' => 'Cannot set or reset variable: ',
1794: 'extension_missing' => 'Extension missing: '
1795: );
1796: if (empty($lang_path)) {
1797: // Calculate an absolute path so it can work if CWD is not here
1798: $lang_path = dirname(__FILE__). DIRECTORY_SEPARATOR . 'language'. DIRECTORY_SEPARATOR;
1799: }
1800: //Validate $langcode
1801: if (!preg_match('/^[a-z]{2}(?:_[a-zA-Z]{2})?$/', $langcode)) {
1802: $langcode = 'en';
1803: }
1804: $foundlang = true;
1805: $lang_file = $lang_path . 'phpmailer.lang-' . $langcode . '.php';
1806: // There is no English translation file
1807: if ($langcode != 'en') {
1808: // Make sure language file path is readable
1809: if (!self::isPermittedPath($lang_file) or !is_readable($lang_file)) {
1810: $foundlang = false;
1811: } else {
1812: // Overwrite language-specific strings.
1813: // This way we'll never have missing translation keys.
1814: $foundlang = include $lang_file;
1815: }
1816: }
1817: $this->language = $PHPMAILER_LANG;
1818: return (boolean)$foundlang; // Returns false if language not found
1819: }
1820:
1821: /**
1822: * Get the array of strings for the current language.
1823: * @return array
1824: */
1825: public function getTranslations()
1826: {
1827: return $this->language;
1828: }
1829:
1830: /**
1831: * Create recipient headers.
1832: * @access public
1833: * @param string $type
1834: * @param array $addr An array of recipient,
1835: * where each recipient is a 2-element indexed array with element 0 containing an address
1836: * and element 1 containing a name, like:
1837: * array(array('joe@example.com', 'Joe User'), array('zoe@example.com', 'Zoe User'))
1838: * @return string
1839: */
1840: public function addrAppend($type, $addr)
1841: {
1842: $addresses = array();
1843: foreach ($addr as $address) {
1844: $addresses[] = $this->addrFormat($address);
1845: }
1846: return $type . ': ' . implode(', ', $addresses) . $this->LE;
1847: }
1848:
1849: /**
1850: * Format an address for use in a message header.
1851: * @access public
1852: * @param array $addr A 2-element indexed array, element 0 containing an address, element 1 containing a name
1853: * like array('joe@example.com', 'Joe User')
1854: * @return string
1855: */
1856: public function addrFormat($addr)
1857: {
1858: if (empty($addr[1])) { // No name provided
1859: return $this->secureHeader($addr[0]);
1860: } else {
1861: return $this->encodeHeader($this->secureHeader($addr[1]), 'phrase') . ' <' . $this->secureHeader(
1862: $addr[0]
1863: ) . '>';
1864: }
1865: }
1866:
1867: /**
1868: * Word-wrap message.
1869: * For use with mailers that do not automatically perform wrapping
1870: * and for quoted-printable encoded messages.
1871: * Original written by philippe.
1872: * @param string $message The message to wrap
1873: * @param integer $length The line length to wrap to
1874: * @param boolean $qp_mode Whether to run in Quoted-Printable mode
1875: * @access public
1876: * @return string
1877: */
1878: public function wrapText($message, $length, $qp_mode = false)
1879: {
1880: if ($qp_mode) {
1881: $soft_break = sprintf(' =%s', $this->LE);
1882: } else {
1883: $soft_break = $this->LE;
1884: }
1885: // If utf-8 encoding is used, we will need to make sure we don't
1886: // split multibyte characters when we wrap
1887: $is_utf8 = (strtolower($this->CharSet) == 'utf-8');
1888: $lelen = strlen($this->LE);
1889: $crlflen = strlen(self::CRLF);
1890:
1891: $message = $this->fixEOL($message);
1892: //Remove a trailing line break
1893: if (substr($message, -$lelen) == $this->LE) {
1894: $message = substr($message, 0, -$lelen);
1895: }
1896:
1897: //Split message into lines
1898: $lines = explode($this->LE, $message);
1899: //Message will be rebuilt in here
1900: $message = '';
1901: foreach ($lines as $line) {
1902: $words = explode(' ', $line);
1903: $buf = '';
1904: $firstword = true;
1905: foreach ($words as $word) {
1906: if ($qp_mode and (strlen($word) > $length)) {
1907: $space_left = $length - strlen($buf) - $crlflen;
1908: if (!$firstword) {
1909: if ($space_left > 20) {
1910: $len = $space_left;
1911: if ($is_utf8) {
1912: $len = $this->utf8CharBoundary($word, $len);
1913: } elseif (substr($word, $len - 1, 1) == '=') {
1914: $len--;
1915: } elseif (substr($word, $len - 2, 1) == '=') {
1916: $len -= 2;
1917: }
1918: $part = substr($word, 0, $len);
1919: $word = substr($word, $len);
1920: $buf .= ' ' . $part;
1921: $message .= $buf . sprintf('=%s', self::CRLF);
1922: } else {
1923: $message .= $buf . $soft_break;
1924: }
1925: $buf = '';
1926: }
1927: while (strlen($word) > 0) {
1928: if ($length <= 0) {
1929: break;
1930: }
1931: $len = $length;
1932: if ($is_utf8) {
1933: $len = $this->utf8CharBoundary($word, $len);
1934: } elseif (substr($word, $len - 1, 1) == '=') {
1935: $len--;
1936: } elseif (substr($word, $len - 2, 1) == '=') {
1937: $len -= 2;
1938: }
1939: $part = substr($word, 0, $len);
1940: $word = substr($word, $len);
1941:
1942: if (strlen($word) > 0) {
1943: $message .= $part . sprintf('=%s', self::CRLF);
1944: } else {
1945: $buf = $part;
1946: }
1947: }
1948: } else {
1949: $buf_o = $buf;
1950: if (!$firstword) {
1951: $buf .= ' ';
1952: }
1953: $buf .= $word;
1954:
1955: if (strlen($buf) > $length and $buf_o != '') {
1956: $message .= $buf_o . $soft_break;
1957: $buf = $word;
1958: }
1959: }
1960: $firstword = false;
1961: }
1962: $message .= $buf . self::CRLF;
1963: }
1964:
1965: return $message;
1966: }
1967:
1968: /**
1969: * Find the last character boundary prior to $maxLength in a utf-8
1970: * quoted-printable encoded string.
1971: * Original written by Colin Brown.
1972: * @access public
1973: * @param string $encodedText utf-8 QP text
1974: * @param integer $maxLength Find the last character boundary prior to this length
1975: * @return integer
1976: */
1977: public function utf8CharBoundary($encodedText, $maxLength)
1978: {
1979: $foundSplitPos = false;
1980: $lookBack = 3;
1981: while (!$foundSplitPos) {
1982: $lastChunk = substr($encodedText, $maxLength - $lookBack, $lookBack);
1983: $encodedCharPos = strpos($lastChunk, '=');
1984: if (false !== $encodedCharPos) {
1985: // Found start of encoded character byte within $lookBack block.
1986: // Check the encoded byte value (the 2 chars after the '=')
1987: $hex = substr($encodedText, $maxLength - $lookBack + $encodedCharPos + 1, 2);
1988: $dec = hexdec($hex);
1989: if ($dec < 128) {
1990: // Single byte character.
1991: // If the encoded char was found at pos 0, it will fit
1992: // otherwise reduce maxLength to start of the encoded char
1993: if ($encodedCharPos > 0) {
1994: $maxLength = $maxLength - ($lookBack - $encodedCharPos);
1995: }
1996: $foundSplitPos = true;
1997: } elseif ($dec >= 192) {
1998: // First byte of a multi byte character
1999: // Reduce maxLength to split at start of character
2000: $maxLength = $maxLength - ($lookBack - $encodedCharPos);
2001: $foundSplitPos = true;
2002: } elseif ($dec < 192) {
2003: // Middle byte of a multi byte character, look further back
2004: $lookBack += 3;
2005: }
2006: } else {
2007: // No encoded character found
2008: $foundSplitPos = true;
2009: }
2010: }
2011: return $maxLength;
2012: }
2013:
2014: /**
2015: * Apply word wrapping to the message body.
2016: * Wraps the message body to the number of chars set in the WordWrap property.
2017: * You should only do this to plain-text bodies as wrapping HTML tags may break them.
2018: * This is called automatically by createBody(), so you don't need to call it yourself.
2019: * @access public
2020: * @return void
2021: */
2022: public function setWordWrap()
2023: {
2024: if ($this->WordWrap < 1) {
2025: return;
2026: }
2027:
2028: switch ($this->message_type) {
2029: case 'alt':
2030: case 'alt_inline':
2031: case 'alt_attach':
2032: case 'alt_inline_attach':
2033: $this->AltBody = $this->wrapText($this->AltBody, $this->WordWrap);
2034: break;
2035: default:
2036: $this->Body = $this->wrapText($this->Body, $this->WordWrap);
2037: break;
2038: }
2039: }
2040:
2041: /**
2042: * Assemble message headers.
2043: * @access public
2044: * @return string The assembled headers
2045: */
2046: public function createHeader()
2047: {
2048: $result = '';
2049:
2050: $result .= $this->headerLine('Date', $this->MessageDate == '' ? self::rfcDate() : $this->MessageDate);
2051:
2052: // To be created automatically by mail()
2053: if ($this->SingleTo) {
2054: if ($this->Mailer != 'mail') {
2055: foreach ($this->to as $toaddr) {
2056: $this->SingleToArray[] = $this->addrFormat($toaddr);
2057: }
2058: }
2059: } else {
2060: if (count($this->to) > 0) {
2061: if ($this->Mailer != 'mail') {
2062: $result .= $this->addrAppend('To', $this->to);
2063: }
2064: } elseif (count($this->cc) == 0) {
2065: $result .= $this->headerLine('To', 'undisclosed-recipients:;');
2066: }
2067: }
2068:
2069: $result .= $this->addrAppend('From', array(array(trim($this->From), $this->FromName)));
2070:
2071: // sendmail and mail() extract Cc from the header before sending
2072: if (count($this->cc) > 0) {
2073: $result .= $this->addrAppend('Cc', $this->cc);
2074: }
2075:
2076: // sendmail and mail() extract Bcc from the header before sending
2077: if ((
2078: $this->Mailer == 'sendmail' or $this->Mailer == 'qmail' or $this->Mailer == 'mail'
2079: )
2080: and count($this->bcc) > 0
2081: ) {
2082: $result .= $this->addrAppend('Bcc', $this->bcc);
2083: }
2084:
2085: if (count($this->ReplyTo) > 0) {
2086: $result .= $this->addrAppend('Reply-To', $this->ReplyTo);
2087: }
2088:
2089: // mail() sets the subject itself
2090: if ($this->Mailer != 'mail') {
2091: $result .= $this->headerLine('Subject', $this->encodeHeader($this->secureHeader($this->Subject)));
2092: }
2093:
2094: // Only allow a custom message ID if it conforms to RFC 5322 section 3.6.4
2095: // https://tools.ietf.org/html/rfc5322#section-3.6.4
2096: if ('' != $this->MessageID and preg_match('/^<.*@.*>$/', $this->MessageID)) {
2097: $this->lastMessageID = $this->MessageID;
2098: } else {
2099: $this->lastMessageID = sprintf('<%s@%s>', $this->uniqueid, $this->serverHostname());
2100: }
2101: $result .= $this->headerLine('Message-ID', $this->lastMessageID);
2102: if (!is_null($this->Priority)) {
2103: $result .= $this->headerLine('X-Priority', $this->Priority);
2104: }
2105: if ($this->XMailer == '') {
2106: $result .= $this->headerLine(
2107: 'X-Mailer',
2108: 'PHPMailer ' . $this->Version . ' (https://github.com/PHPMailer/PHPMailer)'
2109: );
2110: } else {
2111: $myXmailer = trim($this->XMailer);
2112: if ($myXmailer) {
2113: $result .= $this->headerLine('X-Mailer', $myXmailer);
2114: }
2115: }
2116:
2117: if ($this->ConfirmReadingTo != '') {
2118: $result .= $this->headerLine('Disposition-Notification-To', '<' . $this->ConfirmReadingTo . '>');
2119: }
2120:
2121: // Add custom headers
2122: foreach ($this->CustomHeader as $header) {
2123: $result .= $this->headerLine(
2124: trim($header[0]),
2125: $this->encodeHeader(trim($header[1]))
2126: );
2127: }
2128: if (!$this->sign_key_file) {
2129: $result .= $this->headerLine('MIME-Version', '1.0');
2130: $result .= $this->getMailMIME();
2131: }
2132:
2133: return $result;
2134: }
2135:
2136: /**
2137: * Get the message MIME type headers.
2138: * @access public
2139: * @return string
2140: */
2141: public function getMailMIME()
2142: {
2143: $result = '';
2144: $ismultipart = true;
2145: switch ($this->message_type) {
2146: case 'inline':
2147: $result .= $this->headerLine('Content-Type', 'multipart/related;');
2148: $result .= $this->textLine("\tboundary=\"" . $this->boundary[1] . '"');
2149: break;
2150: case 'attach':
2151: case 'inline_attach':
2152: case 'alt_attach':
2153: case 'alt_inline_attach':
2154: $result .= $this->headerLine('Content-Type', 'multipart/mixed;');
2155: $result .= $this->textLine("\tboundary=\"" . $this->boundary[1] . '"');
2156: break;
2157: case 'alt':
2158: case 'alt_inline':
2159: $result .= $this->headerLine('Content-Type', 'multipart/alternative;');
2160: $result .= $this->textLine("\tboundary=\"" . $this->boundary[1] . '"');
2161: break;
2162: default:
2163: // Catches case 'plain': and case '':
2164: $result .= $this->textLine('Content-Type: ' . $this->ContentType . '; charset=' . $this->CharSet);
2165: $ismultipart = false;
2166: break;
2167: }
2168: // RFC1341 part 5 says 7bit is assumed if not specified
2169: if ($this->Encoding != '7bit') {
2170: // RFC 2045 section 6.4 says multipart MIME parts may only use 7bit, 8bit or binary CTE
2171: if ($ismultipart) {
2172: if ($this->Encoding == '8bit') {
2173: $result .= $this->headerLine('Content-Transfer-Encoding', '8bit');
2174: }
2175: // The only remaining alternatives are quoted-printable and base64, which are both 7bit compatible
2176: } else {
2177: $result .= $this->headerLine('Content-Transfer-Encoding', $this->Encoding);
2178: }
2179: }
2180:
2181: if ($this->Mailer != 'mail') {
2182: $result .= $this->LE;
2183: }
2184:
2185: return $result;
2186: }
2187:
2188: /**
2189: * Returns the whole MIME message.
2190: * Includes complete headers and body.
2191: * Only valid post preSend().
2192: * @see PHPMailer::preSend()
2193: * @access public
2194: * @return string
2195: */
2196: public function getSentMIMEMessage()
2197: {
2198: return rtrim($this->MIMEHeader . $this->mailHeader, "\n\r") . self::CRLF . self::CRLF . $this->MIMEBody;
2199: }
2200:
2201: /**
2202: * Create unique ID
2203: * @return string
2204: */
2205: protected function generateId() {
2206: return md5(uniqid(time()));
2207: }
2208:
2209: /**
2210: * Assemble the message body.
2211: * Returns an empty string on failure.
2212: * @access public
2213: * @throws phpmailerException
2214: * @return string The assembled message body
2215: */
2216: public function createBody()
2217: {
2218: $body = '';
2219: //Create unique IDs and preset boundaries
2220: $this->uniqueid = $this->generateId();
2221: $this->boundary[1] = 'b1_' . $this->uniqueid;
2222: $this->boundary[2] = 'b2_' . $this->uniqueid;
2223: $this->boundary[3] = 'b3_' . $this->uniqueid;
2224:
2225: if ($this->sign_key_file) {
2226: $body .= $this->getMailMIME() . $this->LE;
2227: }
2228:
2229: $this->setWordWrap();
2230:
2231: $bodyEncoding = $this->Encoding;
2232: $bodyCharSet = $this->CharSet;
2233: //Can we do a 7-bit downgrade?
2234: if ($bodyEncoding == '8bit' and !$this->has8bitChars($this->Body)) {
2235: $bodyEncoding = '7bit';
2236: //All ISO 8859, Windows codepage and UTF-8 charsets are ascii compatible up to 7-bit
2237: $bodyCharSet = 'us-ascii';
2238: }
2239: //If lines are too long, and we're not already using an encoding that will shorten them,
2240: //change to quoted-printable transfer encoding for the body part only
2241: if ('base64' != $this->Encoding and self::hasLineLongerThanMax($this->Body)) {
2242: $bodyEncoding = 'quoted-printable';
2243: }
2244:
2245: $altBodyEncoding = $this->Encoding;
2246: $altBodyCharSet = $this->CharSet;
2247: //Can we do a 7-bit downgrade?
2248: if ($altBodyEncoding == '8bit' and !$this->has8bitChars($this->AltBody)) {
2249: $altBodyEncoding = '7bit';
2250: //All ISO 8859, Windows codepage and UTF-8 charsets are ascii compatible up to 7-bit
2251: $altBodyCharSet = 'us-ascii';
2252: }
2253: //If lines are too long, and we're not already using an encoding that will shorten them,
2254: //change to quoted-printable transfer encoding for the alt body part only
2255: if ('base64' != $altBodyEncoding and self::hasLineLongerThanMax($this->AltBody)) {
2256: $altBodyEncoding = 'quoted-printable';
2257: }
2258: //Use this as a preamble in all multipart message types
2259: $mimepre = "This is a multi-part message in MIME format." . $this->LE . $this->LE;
2260: switch ($this->message_type) {
2261: case 'inline':
2262: $body .= $mimepre;
2263: $body .= $this->getBoundary($this->boundary[1], $bodyCharSet, '', $bodyEncoding);
2264: $body .= $this->encodeString($this->Body, $bodyEncoding);
2265: $body .= $this->LE . $this->LE;
2266: $body .= $this->attachAll('inline', $this->boundary[1]);
2267: break;
2268: case 'attach':
2269: $body .= $mimepre;
2270: $body .= $this->getBoundary($this->boundary[1], $bodyCharSet, '', $bodyEncoding);
2271: $body .= $this->encodeString($this->Body, $bodyEncoding);
2272: $body .= $this->LE . $this->LE;
2273: $body .= $this->attachAll('attachment', $this->boundary[1]);
2274: break;
2275: case 'inline_attach':
2276: $body .= $mimepre;
2277: $body .= $this->textLine('--' . $this->boundary[1]);
2278: $body .= $this->headerLine('Content-Type', 'multipart/related;');
2279: $body .= $this->textLine("\tboundary=\"" . $this->boundary[2] . '"');
2280: $body .= $this->LE;
2281: $body .= $this->getBoundary($this->boundary[2], $bodyCharSet, '', $bodyEncoding);
2282: $body .= $this->encodeString($this->Body, $bodyEncoding);
2283: $body .= $this->LE . $this->LE;
2284: $body .= $this->attachAll('inline', $this->boundary[2]);
2285: $body .= $this->LE;
2286: $body .= $this->attachAll('attachment', $this->boundary[1]);
2287: break;
2288: case 'alt':
2289: $body .= $mimepre;
2290: $body .= $this->getBoundary($this->boundary[1], $altBodyCharSet, 'text/plain', $altBodyEncoding);
2291: $body .= $this->encodeString($this->AltBody, $altBodyEncoding);
2292: $body .= $this->LE . $this->LE;
2293: $body .= $this->getBoundary($this->boundary[1], $bodyCharSet, 'text/html', $bodyEncoding);
2294: $body .= $this->encodeString($this->Body, $bodyEncoding);
2295: $body .= $this->LE . $this->LE;
2296: if (!empty($this->Ical)) {
2297: $body .= $this->getBoundary($this->boundary[1], '', 'text/calendar; method=REQUEST', '');
2298: $body .= $this->encodeString($this->Ical, $this->Encoding);
2299: $body .= $this->LE . $this->LE;
2300: }
2301: $body .= $this->endBoundary($this->boundary[1]);
2302: break;
2303: case 'alt_inline':
2304: $body .= $mimepre;
2305: $body .= $this->getBoundary($this->boundary[1], $altBodyCharSet, 'text/plain', $altBodyEncoding);
2306: $body .= $this->encodeString($this->AltBody, $altBodyEncoding);
2307: $body .= $this->LE . $this->LE;
2308: $body .= $this->textLine('--' . $this->boundary[1]);
2309: $body .= $this->headerLine('Content-Type', 'multipart/related;');
2310: $body .= $this->textLine("\tboundary=\"" . $this->boundary[2] . '"');
2311: $body .= $this->LE;
2312: $body .= $this->getBoundary($this->boundary[2], $bodyCharSet, 'text/html', $bodyEncoding);
2313: $body .= $this->encodeString($this->Body, $bodyEncoding);
2314: $body .= $this->LE . $this->LE;
2315: $body .= $this->attachAll('inline', $this->boundary[2]);
2316: $body .= $this->LE;
2317: $body .= $this->endBoundary($this->boundary[1]);
2318: break;
2319: case 'alt_attach':
2320: $body .= $mimepre;
2321: $body .= $this->textLine('--' . $this->boundary[1]);
2322: $body .= $this->headerLine('Content-Type', 'multipart/alternative;');
2323: $body .= $this->textLine("\tboundary=\"" . $this->boundary[2] . '"');
2324: $body .= $this->LE;
2325: $body .= $this->getBoundary($this->boundary[2], $altBodyCharSet, 'text/plain', $altBodyEncoding);
2326: $body .= $this->encodeString($this->AltBody, $altBodyEncoding);
2327: $body .= $this->LE . $this->LE;
2328: $body .= $this->getBoundary($this->boundary[2], $bodyCharSet, 'text/html', $bodyEncoding);
2329: $body .= $this->encodeString($this->Body, $bodyEncoding);
2330: $body .= $this->LE . $this->LE;
2331: $body .= $this->endBoundary($this->boundary[2]);
2332: $body .= $this->LE;
2333: $body .= $this->attachAll('attachment', $this->boundary[1]);
2334: break;
2335: case 'alt_inline_attach':
2336: $body .= $mimepre;
2337: $body .= $this->textLine('--' . $this->boundary[1]);
2338: $body .= $this->headerLine('Content-Type', 'multipart/alternative;');
2339: $body .= $this->textLine("\tboundary=\"" . $this->boundary[2] . '"');
2340: $body .= $this->LE;
2341: $body .= $this->getBoundary($this->boundary[2], $altBodyCharSet, 'text/plain', $altBodyEncoding);
2342: $body .= $this->encodeString($this->AltBody, $altBodyEncoding);
2343: $body .= $this->LE . $this->LE;
2344: $body .= $this->textLine('--' . $this->boundary[2]);
2345: $body .= $this->headerLine('Content-Type', 'multipart/related;');
2346: $body .= $this->textLine("\tboundary=\"" . $this->boundary[3] . '"');
2347: $body .= $this->LE;
2348: $body .= $this->getBoundary($this->boundary[3], $bodyCharSet, 'text/html', $bodyEncoding);
2349: $body .= $this->encodeString($this->Body, $bodyEncoding);
2350: $body .= $this->LE . $this->LE;
2351: $body .= $this->attachAll('inline', $this->boundary[3]);
2352: $body .= $this->LE;
2353: $body .= $this->endBoundary($this->boundary[2]);
2354: $body .= $this->LE;
2355: $body .= $this->attachAll('attachment', $this->boundary[1]);
2356: break;
2357: default:
2358: // Catch case 'plain' and case '', applies to simple `text/plain` and `text/html` body content types
2359: //Reset the `Encoding` property in case we changed it for line length reasons
2360: $this->Encoding = $bodyEncoding;
2361: $body .= $this->encodeString($this->Body, $this->Encoding);
2362: break;
2363: }
2364:
2365: if ($this->isError()) {
2366: $body = '';
2367: } elseif ($this->sign_key_file) {
2368: try {
2369: if (!defined('PKCS7_TEXT')) {
2370: throw new phpmailerException($this->lang('extension_missing') . 'openssl');
2371: }
2372: // @TODO would be nice to use php://temp streams here, but need to wrap for PHP < 5.1
2373: $file = tempnam(sys_get_temp_dir(), 'mail');
2374: if (false === file_put_contents($file, $body)) {
2375: throw new phpmailerException($this->lang('signing') . ' Could not write temp file');
2376: }
2377: $signed = tempnam(sys_get_temp_dir(), 'signed');
2378: //Workaround for PHP bug https://bugs.php.net/bug.php?id=69197
2379: if (empty($this->sign_extracerts_file)) {
2380: $sign = @openssl_pkcs7_sign(
2381: $file,
2382: $signed,
2383: 'file://' . realpath($this->sign_cert_file),
2384: array('file://' . realpath($this->sign_key_file), $this->sign_key_pass),
2385: null
2386: );
2387: } else {
2388: $sign = @openssl_pkcs7_sign(
2389: $file,
2390: $signed,
2391: 'file://' . realpath($this->sign_cert_file),
2392: array('file://' . realpath($this->sign_key_file), $this->sign_key_pass),
2393: null,
2394: PKCS7_DETACHED,
2395: $this->sign_extracerts_file
2396: );
2397: }
2398: if ($sign) {
2399: @unlink($file);
2400: $body = file_get_contents($signed);
2401: @unlink($signed);
2402: //The message returned by openssl contains both headers and body, so need to split them up
2403: $parts = explode("\n\n", $body, 2);
2404: $this->MIMEHeader .= $parts[0] . $this->LE . $this->LE;
2405: $body = $parts[1];
2406: } else {
2407: @unlink($file);
2408: @unlink($signed);
2409: throw new phpmailerException($this->lang('signing') . openssl_error_string());
2410: }
2411: } catch (phpmailerException $exc) {
2412: $body = '';
2413: if ($this->exceptions) {
2414: throw $exc;
2415: }
2416: }
2417: }
2418: return $body;
2419: }
2420:
2421: /**
2422: * Return the start of a message boundary.
2423: * @access protected
2424: * @param string $boundary
2425: * @param string $charSet
2426: * @param string $contentType
2427: * @param string $encoding
2428: * @return string
2429: */
2430: protected function getBoundary($boundary, $charSet, $contentType, $encoding)
2431: {
2432: $result = '';
2433: if ($charSet == '') {
2434: $charSet = $this->CharSet;
2435: }
2436: if ($contentType == '') {
2437: $contentType = $this->ContentType;
2438: }
2439: if ($encoding == '') {
2440: $encoding = $this->Encoding;
2441: }
2442: $result .= $this->textLine('--' . $boundary);
2443: $result .= sprintf('Content-Type: %s; charset=%s', $contentType, $charSet);
2444: $result .= $this->LE;
2445: // RFC1341 part 5 says 7bit is assumed if not specified
2446: if ($encoding != '7bit') {
2447: $result .= $this->headerLine('Content-Transfer-Encoding', $encoding);
2448: }
2449: $result .= $this->LE;
2450:
2451: return $result;
2452: }
2453:
2454: /**
2455: * Return the end of a message boundary.
2456: * @access protected
2457: * @param string $boundary
2458: * @return string
2459: */
2460: protected function endBoundary($boundary)
2461: {
2462: return $this->LE . '--' . $boundary . '--' . $this->LE;
2463: }
2464:
2465: /**
2466: * Set the message type.
2467: * PHPMailer only supports some preset message types, not arbitrary MIME structures.
2468: * @access protected
2469: * @return void
2470: */
2471: protected function setMessageType()
2472: {
2473: $type = array();
2474: if ($this->alternativeExists()) {
2475: $type[] = 'alt';
2476: }
2477: if ($this->inlineImageExists()) {
2478: $type[] = 'inline';
2479: }
2480: if ($this->attachmentExists()) {
2481: $type[] = 'attach';
2482: }
2483: $this->message_type = implode('_', $type);
2484: if ($this->message_type == '') {
2485: //The 'plain' message_type refers to the message having a single body element, not that it is plain-text
2486: $this->message_type = 'plain';
2487: }
2488: }
2489:
2490: /**
2491: * Format a header line.
2492: * @access public
2493: * @param string $name
2494: * @param string $value
2495: * @return string
2496: */
2497: public function headerLine($name, $value)
2498: {
2499: return $name . ': ' . $value . $this->LE;
2500: }
2501:
2502: /**
2503: * Return a formatted mail line.
2504: * @access public
2505: * @param string $value
2506: * @return string
2507: */
2508: public function textLine($value)
2509: {
2510: return $value . $this->LE;
2511: }
2512:
2513: /**
2514: * Add an attachment from a path on the filesystem.
2515: * Never use a user-supplied path to a file!
2516: * Returns false if the file could not be found or read.
2517: * Explicitly *does not* support passing URLs; PHPMailer is not an HTTP client.
2518: * If you need to do that, fetch the resource yourself and pass it in via a local file or string.
2519: * @param string $path Path to the attachment.
2520: * @param string $name Overrides the attachment name.
2521: * @param string $encoding File encoding (see $Encoding).
2522: * @param string $type File extension (MIME) type.
2523: * @param string $disposition Disposition to use
2524: * @throws phpmailerException
2525: * @return boolean
2526: */
2527: public function addAttachment($path, $name = '', $encoding = 'base64', $type = '', $disposition = 'attachment')
2528: {
2529: try {
2530: if (!self::isPermittedPath($path) or !@is_file($path)) {
2531: throw new phpmailerException($this->lang('file_access') . $path, self::STOP_CONTINUE);
2532: }
2533:
2534: // If a MIME type is not specified, try to work it out from the file name
2535: if ($type == '') {
2536: $type = self::filenameToType($path);
2537: }
2538:
2539: $filename = basename($path);
2540: if ($name == '') {
2541: $name = $filename;
2542: }
2543:
2544: $this->attachment[] = array(
2545: 0 => $path,
2546: 1 => $filename,
2547: 2 => $name,
2548: 3 => $encoding,
2549: 4 => $type,
2550: 5 => false, // isStringAttachment
2551: 6 => $disposition,
2552: 7 => 0
2553: );
2554:
2555: } catch (phpmailerException $exc) {
2556: $this->setError($exc->getMessage());
2557: $this->edebug($exc->getMessage());
2558: if ($this->exceptions) {
2559: throw $exc;
2560: }
2561: return false;
2562: }
2563: return true;
2564: }
2565:
2566: /**
2567: * Return the array of attachments.
2568: * @return array
2569: */
2570: public function getAttachments()
2571: {
2572: return $this->attachment;
2573: }
2574:
2575: /**
2576: * Attach all file, string, and binary attachments to the message.
2577: * Returns an empty string on failure.
2578: * @access protected
2579: * @param string $disposition_type
2580: * @param string $boundary
2581: * @return string
2582: */
2583: protected function attachAll($disposition_type, $boundary)
2584: {
2585: // Return text of body
2586: $mime = array();
2587: $cidUniq = array();
2588: $incl = array();
2589:
2590: // Add all attachments
2591: foreach ($this->attachment as $attachment) {
2592: // Check if it is a valid disposition_filter
2593: if ($attachment[6] == $disposition_type) {
2594: // Check for string attachment
2595: $string = '';
2596: $path = '';
2597: $bString = $attachment[5];
2598: if ($bString) {
2599: $string = $attachment[0];
2600: } else {
2601: $path = $attachment[0];
2602: }
2603:
2604: $inclhash = md5(serialize($attachment));
2605: if (in_array($inclhash, $incl)) {
2606: continue;
2607: }
2608: $incl[] = $inclhash;
2609: $name = $attachment[2];
2610: $encoding = $attachment[3];
2611: $type = $attachment[4];
2612: $disposition = $attachment[6];
2613: $cid = $attachment[7];
2614: if ($disposition == 'inline' && array_key_exists($cid, $cidUniq)) {
2615: continue;
2616: }
2617: $cidUniq[$cid] = true;
2618:
2619: $mime[] = sprintf('--%s%s', $boundary, $this->LE);
2620: //Only include a filename property if we have one
2621: if (!empty($name)) {
2622: $mime[] = sprintf(
2623: 'Content-Type: %s; name="%s"%s',
2624: $type,
2625: $this->encodeHeader($this->secureHeader($name)),
2626: $this->LE
2627: );
2628: } else {
2629: $mime[] = sprintf(
2630: 'Content-Type: %s%s',
2631: $type,
2632: $this->LE
2633: );
2634: }
2635: // RFC1341 part 5 says 7bit is assumed if not specified
2636: if ($encoding != '7bit') {
2637: $mime[] = sprintf('Content-Transfer-Encoding: %s%s', $encoding, $this->LE);
2638: }
2639:
2640: if ($disposition == 'inline') {
2641: $mime[] = sprintf('Content-ID: <%s>%s', $cid, $this->LE);
2642: }
2643:
2644: // If a filename contains any of these chars, it should be quoted,
2645: // but not otherwise: RFC2183 & RFC2045 5.1
2646: // Fixes a warning in IETF's msglint MIME checker
2647: // Allow for bypassing the Content-Disposition header totally
2648: if (!(empty($disposition))) {
2649: $encoded_name = $this->encodeHeader($this->secureHeader($name));
2650: if (preg_match('/[ \(\)<>@,;:\\"\/\[\]\?=]/', $encoded_name)) {
2651: $mime[] = sprintf(
2652: 'Content-Disposition: %s; filename="%s"%s',
2653: $disposition,
2654: $encoded_name,
2655: $this->LE . $this->LE
2656: );
2657: } else {
2658: if (!empty($encoded_name)) {
2659: $mime[] = sprintf(
2660: 'Content-Disposition: %s; filename=%s%s',
2661: $disposition,
2662: $encoded_name,
2663: $this->LE . $this->LE
2664: );
2665: } else {
2666: $mime[] = sprintf(
2667: 'Content-Disposition: %s%s',
2668: $disposition,
2669: $this->LE . $this->LE
2670: );
2671: }
2672: }
2673: } else {
2674: $mime[] = $this->LE;
2675: }
2676:
2677: // Encode as string attachment
2678: if ($bString) {
2679: $mime[] = $this->encodeString($string, $encoding);
2680: if ($this->isError()) {
2681: return '';
2682: }
2683: $mime[] = $this->LE . $this->LE;
2684: } else {
2685: $mime[] = $this->encodeFile($path, $encoding);
2686: if ($this->isError()) {
2687: return '';
2688: }
2689: $mime[] = $this->LE . $this->LE;
2690: }
2691: }
2692: }
2693:
2694: $mime[] = sprintf('--%s--%s', $boundary, $this->LE);
2695:
2696: return implode('', $mime);
2697: }
2698:
2699: /**
2700: * Encode a file attachment in requested format.
2701: * Returns an empty string on failure.
2702: * @param string $path The full path to the file
2703: * @param string $encoding The encoding to use; one of 'base64', '7bit', '8bit', 'binary', 'quoted-printable'
2704: * @throws phpmailerException
2705: * @access protected
2706: * @return string
2707: */
2708: protected function encodeFile($path, $encoding = 'base64')
2709: {
2710: try {
2711: if (!self::isPermittedPath($path) or !file_exists($path)) {
2712: throw new phpmailerException($this->lang('file_open') . $path, self::STOP_CONTINUE);
2713: }
2714: $magic_quotes = false;
2715: if( version_compare(PHP_VERSION, '7.4.0', '<') ) {
2716: $magic_quotes = get_magic_quotes_runtime();
2717: }
2718: if ($magic_quotes) {
2719: if (version_compare(PHP_VERSION, '5.3.0', '<')) {
2720: set_magic_quotes_runtime(false);
2721: } else {
2722: //Doesn't exist in PHP 5.4, but we don't need to check because
2723: //get_magic_quotes_runtime always returns false in 5.4+
2724: //so it will never get here
2725: ini_set('magic_quotes_runtime', false);
2726: }
2727: }
2728: $file_buffer = file_get_contents($path);
2729: $file_buffer = $this->encodeString($file_buffer, $encoding);
2730: if ($magic_quotes) {
2731: if (version_compare(PHP_VERSION, '5.3.0', '<')) {
2732: set_magic_quotes_runtime($magic_quotes);
2733: } else {
2734: ini_set('magic_quotes_runtime', $magic_quotes);
2735: }
2736: }
2737: return $file_buffer;
2738: } catch (Exception $exc) {
2739: $this->setError($exc->getMessage());
2740: return '';
2741: }
2742: }
2743:
2744: /**
2745: * Encode a string in requested format.
2746: * Returns an empty string on failure.
2747: * @param string $str The text to encode
2748: * @param string $encoding The encoding to use; one of 'base64', '7bit', '8bit', 'binary', 'quoted-printable'
2749: * @access public
2750: * @return string
2751: */
2752: public function encodeString($str, $encoding = 'base64')
2753: {
2754: $encoded = '';
2755: switch (strtolower($encoding)) {
2756: case 'base64':
2757: $encoded = chunk_split(base64_encode($str), 76, $this->LE);
2758: break;
2759: case '7bit':
2760: case '8bit':
2761: $encoded = $this->fixEOL($str);
2762: // Make sure it ends with a line break
2763: if (substr($encoded, -(strlen($this->LE))) != $this->LE) {
2764: $encoded .= $this->LE;
2765: }
2766: break;
2767: case 'binary':
2768: $encoded = $str;
2769: break;
2770: case 'quoted-printable':
2771: $encoded = $this->encodeQP($str);
2772: break;
2773: default:
2774: $this->setError($this->lang('encoding') . $encoding);
2775: break;
2776: }
2777: return $encoded;
2778: }
2779:
2780: /**
2781: * Encode a header string optimally.
2782: * Picks shortest of Q, B, quoted-printable or none.
2783: * @access public
2784: * @param string $str
2785: * @param string $position
2786: * @return string
2787: */
2788: public function encodeHeader($str, $position = 'text')
2789: {
2790: $matchcount = 0;
2791: switch (strtolower($position)) {
2792: case 'phrase':
2793: if (!preg_match('/[\200-\377]/', $str)) {
2794: // Can't use addslashes as we don't know the value of magic_quotes_sybase
2795: $encoded = addcslashes($str, "\0..\37\177\\\"");
2796: if (($str == $encoded) && !preg_match('/[^A-Za-z0-9!#$%&\'*+\/=?^_`{|}~ -]/', $str)) {
2797: return ($encoded);
2798: } else {
2799: return ("\"$encoded\"");
2800: }
2801: }
2802: $matchcount = preg_match_all('/[^\040\041\043-\133\135-\176]/', $str, $matches);
2803: break;
2804: /** @noinspection PhpMissingBreakStatementInspection */
2805: case 'comment':
2806: $matchcount = preg_match_all('/[()"]/', $str, $matches);
2807: // Intentional fall-through
2808: case 'text':
2809: default:
2810: $matchcount += preg_match_all('/[\000-\010\013\014\016-\037\177-\377]/', $str, $matches);
2811: break;
2812: }
2813:
2814: //There are no chars that need encoding
2815: if ($matchcount == 0) {
2816: return ($str);
2817: }
2818:
2819: $maxlen = 75 - 7 - strlen($this->CharSet);
2820: // Try to select the encoding which should produce the shortest output
2821: if ($matchcount > strlen($str) / 3) {
2822: // More than a third of the content will need encoding, so B encoding will be most efficient
2823: $encoding = 'B';
2824: if (function_exists('mb_strlen') && $this->hasMultiBytes($str)) {
2825: // Use a custom function which correctly encodes and wraps long
2826: // multibyte strings without breaking lines within a character
2827: $encoded = $this->base64EncodeWrapMB($str, "\n");
2828: } else {
2829: $encoded = base64_encode($str);
2830: $maxlen -= $maxlen % 4;
2831: $encoded = trim(chunk_split($encoded, $maxlen, "\n"));
2832: }
2833: } else {
2834: $encoding = 'Q';
2835: $encoded = $this->encodeQ($str, $position);
2836: $encoded = $this->wrapText($encoded, $maxlen, true);
2837: $encoded = str_replace('=' . self::CRLF, "\n", trim($encoded));
2838: }
2839:
2840: $encoded = preg_replace('/^(.*)$/m', ' =?' . $this->CharSet . "?$encoding?\\1?=", $encoded);
2841: $encoded = trim(str_replace("\n", $this->LE, $encoded));
2842:
2843: return $encoded;
2844: }
2845:
2846: /**
2847: * Check if a string contains multi-byte characters.
2848: * @access public
2849: * @param string $str multi-byte text to wrap encode
2850: * @return boolean
2851: */
2852: public function hasMultiBytes($str)
2853: {
2854: if (function_exists('mb_strlen')) {
2855: return (strlen($str) > mb_strlen($str, $this->CharSet));
2856: } else { // Assume no multibytes (we can't handle without mbstring functions anyway)
2857: return false;
2858: }
2859: }
2860:
2861: /**
2862: * Does a string contain any 8-bit chars (in any charset)?
2863: * @param string $text
2864: * @return boolean
2865: */
2866: public function has8bitChars($text)
2867: {
2868: return (boolean)preg_match('/[\x80-\xFF]/', $text);
2869: }
2870:
2871: /**
2872: * Encode and wrap long multibyte strings for mail headers
2873: * without breaking lines within a character.
2874: * Adapted from a function by paravoid
2875: * @link http://www.php.net/manual/en/function.mb-encode-mimeheader.php#60283
2876: * @access public
2877: * @param string $str multi-byte text to wrap encode
2878: * @param string $linebreak string to use as linefeed/end-of-line
2879: * @return string
2880: */
2881: public function base64EncodeWrapMB($str, $linebreak = null)
2882: {
2883: $start = '=?' . $this->CharSet . '?B?';
2884: $end = '?=';
2885: $encoded = '';
2886: if ($linebreak === null) {
2887: $linebreak = $this->LE;
2888: }
2889:
2890: $mb_length = mb_strlen($str, $this->CharSet);
2891: // Each line must have length <= 75, including $start and $end
2892: $length = 75 - strlen($start) - strlen($end);
2893: // Average multi-byte ratio
2894: $ratio = $mb_length / strlen($str);
2895: // Base64 has a 4:3 ratio
2896: $avgLength = floor($length * $ratio * .75);
2897:
2898: for ($i = 0; $i < $mb_length; $i += $offset) {
2899: $lookBack = 0;
2900: do {
2901: $offset = $avgLength - $lookBack;
2902: $chunk = mb_substr($str, $i, $offset, $this->CharSet);
2903: $chunk = base64_encode($chunk);
2904: $lookBack++;
2905: } while (strlen($chunk) > $length);
2906: $encoded .= $chunk . $linebreak;
2907: }
2908:
2909: // Chomp the last linefeed
2910: $encoded = substr($encoded, 0, -strlen($linebreak));
2911: return $encoded;
2912: }
2913:
2914: /**
2915: * Encode a string in quoted-printable format.
2916: * According to RFC2045 section 6.7.
2917: * @access public
2918: * @param string $string The text to encode
2919: * @param integer $line_max Number of chars allowed on a line before wrapping
2920: * @return string
2921: * @link http://www.php.net/manual/en/function.quoted-printable-decode.php#89417 Adapted from this comment
2922: */
2923: public function encodeQP($string, $line_max = 76)
2924: {
2925: // Use native function if it's available (>= PHP5.3)
2926: if (function_exists('quoted_printable_encode')) {
2927: return quoted_printable_encode($string);
2928: }
2929: // Fall back to a pure PHP implementation
2930: $string = str_replace(
2931: array('%20', '%0D%0A.', '%0D%0A', '%'),
2932: array(' ', "\r\n=2E", "\r\n", '='),
2933: rawurlencode($string)
2934: );
2935: return preg_replace('/[^\r\n]{' . ($line_max - 3) . '}[^=\r\n]{2}/', "$0=\r\n", $string);
2936: }
2937:
2938: /**
2939: * Backward compatibility wrapper for an old QP encoding function that was removed.
2940: * @see PHPMailer::encodeQP()
2941: * @access public
2942: * @param string $string
2943: * @param integer $line_max
2944: * @param boolean $space_conv
2945: * @return string
2946: * @deprecated Use encodeQP instead.
2947: */
2948: public function encodeQPphp(
2949: $string,
2950: $line_max = 76,
2951: /** @noinspection PhpUnusedParameterInspection */ $space_conv = false
2952: ) {
2953: return $this->encodeQP($string, $line_max);
2954: }
2955:
2956: /**
2957: * Encode a string using Q encoding.
2958: * @link http://tools.ietf.org/html/rfc2047
2959: * @param string $str the text to encode
2960: * @param string $position Where the text is going to be used, see the RFC for what that means
2961: * @access public
2962: * @return string
2963: */
2964: public function encodeQ($str, $position = 'text')
2965: {
2966: // There should not be any EOL in the string
2967: $pattern = '';
2968: $encoded = str_replace(array("\r", "\n"), '', $str);
2969: switch (strtolower($position)) {
2970: case 'phrase':
2971: // RFC 2047 section 5.3
2972: $pattern = '^A-Za-z0-9!*+\/ -';
2973: break;
2974: /** @noinspection PhpMissingBreakStatementInspection */
2975: case 'comment':
2976: // RFC 2047 section 5.2
2977: $pattern = '\(\)"';
2978: // intentional fall-through
2979: // for this reason we build the $pattern without including delimiters and []
2980: case 'text':
2981: default:
2982: // RFC 2047 section 5.1
2983: // Replace every high ascii, control, =, ? and _ characters
2984: $pattern = '\000-\011\013\014\016-\037\075\077\137\177-\377' . $pattern;
2985: break;
2986: }
2987: $matches = array();
2988: if (preg_match_all("/[{$pattern}]/", $encoded, $matches)) {
2989: // If the string contains an '=', make sure it's the first thing we replace
2990: // so as to avoid double-encoding
2991: $eqkey = array_search('=', $matches[0]);
2992: if (false !== $eqkey) {
2993: unset($matches[0][$eqkey]);
2994: array_unshift($matches[0], '=');
2995: }
2996: foreach (array_unique($matches[0]) as $char) {
2997: $encoded = str_replace($char, '=' . sprintf('%02X', ord($char)), $encoded);
2998: }
2999: }
3000: // Replace every spaces to _ (more readable than =20)
3001: return str_replace(' ', '_', $encoded);
3002: }
3003:
3004: /**
3005: * Add a string or binary attachment (non-filesystem).
3006: * This method can be used to attach ascii or binary data,
3007: * such as a BLOB record from a database.
3008: * @param string $string String attachment data.
3009: * @param string $filename Name of the attachment.
3010: * @param string $encoding File encoding (see $Encoding).
3011: * @param string $type File extension (MIME) type.
3012: * @param string $disposition Disposition to use
3013: * @return void
3014: */
3015: public function addStringAttachment(
3016: $string,
3017: $filename,
3018: $encoding = 'base64',
3019: $type = '',
3020: $disposition = 'attachment'
3021: ) {
3022: // If a MIME type is not specified, try to work it out from the file name
3023: if ($type == '') {
3024: $type = self::filenameToType($filename);
3025: }
3026: // Append to $attachment array
3027: $this->attachment[] = array(
3028: 0 => $string,
3029: 1 => $filename,
3030: 2 => basename($filename),
3031: 3 => $encoding,
3032: 4 => $type,
3033: 5 => true, // isStringAttachment
3034: 6 => $disposition,
3035: 7 => 0
3036: );
3037: }
3038:
3039: /**
3040: * Add an embedded (inline) attachment from a file.
3041: * This can include images, sounds, and just about any other document type.
3042: * These differ from 'regular' attachments in that they are intended to be
3043: * displayed inline with the message, not just attached for download.
3044: * This is used in HTML messages that embed the images
3045: * the HTML refers to using the $cid value.
3046: * Never use a user-supplied path to a file!
3047: * @param string $path Path to the attachment.
3048: * @param string $cid Content ID of the attachment; Use this to reference
3049: * the content when using an embedded image in HTML.
3050: * @param string $name Overrides the attachment name.
3051: * @param string $encoding File encoding (see $Encoding).
3052: * @param string $type File MIME type.
3053: * @param string $disposition Disposition to use
3054: * @return boolean True on successfully adding an attachment
3055: */
3056: public function addEmbeddedImage($path, $cid, $name = '', $encoding = 'base64', $type = '', $disposition = 'inline')
3057: {
3058: if (!self::isPermittedPath($path) or !@is_file($path)) {
3059: $this->setError($this->lang('file_access') . $path);
3060: return false;
3061: }
3062:
3063: // If a MIME type is not specified, try to work it out from the file name
3064: if ($type == '') {
3065: $type = self::filenameToType($path);
3066: }
3067:
3068: $filename = basename($path);
3069: if ($name == '') {
3070: $name = $filename;
3071: }
3072:
3073: // Append to $attachment array
3074: $this->attachment[] = array(
3075: 0 => $path,
3076: 1 => $filename,
3077: 2 => $name,
3078: 3 => $encoding,
3079: 4 => $type,
3080: 5 => false, // isStringAttachment
3081: 6 => $disposition,
3082: 7 => $cid
3083: );
3084: return true;
3085: }
3086:
3087: /**
3088: * Add an embedded stringified attachment.
3089: * This can include images, sounds, and just about any other document type.
3090: * Be sure to set the $type to an image type for images:
3091: * JPEG images use 'image/jpeg', GIF uses 'image/gif', PNG uses 'image/png'.
3092: * @param string $string The attachment binary data.
3093: * @param string $cid Content ID of the attachment; Use this to reference
3094: * the content when using an embedded image in HTML.
3095: * @param string $name
3096: * @param string $encoding File encoding (see $Encoding).
3097: * @param string $type MIME type.
3098: * @param string $disposition Disposition to use
3099: * @return boolean True on successfully adding an attachment
3100: */
3101: public function addStringEmbeddedImage(
3102: $string,
3103: $cid,
3104: $name = '',
3105: $encoding = 'base64',
3106: $type = '',
3107: $disposition = 'inline'
3108: ) {
3109: // If a MIME type is not specified, try to work it out from the name
3110: if ($type == '' and !empty($name)) {
3111: $type = self::filenameToType($name);
3112: }
3113:
3114: // Append to $attachment array
3115: $this->attachment[] = array(
3116: 0 => $string,
3117: 1 => $name,
3118: 2 => $name,
3119: 3 => $encoding,
3120: 4 => $type,
3121: 5 => true, // isStringAttachment
3122: 6 => $disposition,
3123: 7 => $cid
3124: );
3125: return true;
3126: }
3127:
3128: /**
3129: * Check if an inline attachment is present.
3130: * @access public
3131: * @return boolean
3132: */
3133: public function inlineImageExists()
3134: {
3135: foreach ($this->attachment as $attachment) {
3136: if ($attachment[6] == 'inline') {
3137: return true;
3138: }
3139: }
3140: return false;
3141: }
3142:
3143: /**
3144: * Check if an attachment (non-inline) is present.
3145: * @return boolean
3146: */
3147: public function attachmentExists()
3148: {
3149: foreach ($this->attachment as $attachment) {
3150: if ($attachment[6] == 'attachment') {
3151: return true;
3152: }
3153: }
3154: return false;
3155: }
3156:
3157: /**
3158: * Check if this message has an alternative body set.
3159: * @return boolean
3160: */
3161: public function alternativeExists()
3162: {
3163: return !empty($this->AltBody);
3164: }
3165:
3166: /**
3167: * Clear queued addresses of given kind.
3168: * @access protected
3169: * @param string $kind 'to', 'cc', or 'bcc'
3170: * @return void
3171: */
3172: public function clearQueuedAddresses($kind)
3173: {
3174: $RecipientsQueue = $this->RecipientsQueue;
3175: foreach ($RecipientsQueue as $address => $params) {
3176: if ($params[0] == $kind) {
3177: unset($this->RecipientsQueue[$address]);
3178: }
3179: }
3180: }
3181:
3182: /**
3183: * Clear all To recipients.
3184: * @return void
3185: */
3186: public function clearAddresses()
3187: {
3188: foreach ($this->to as $to) {
3189: unset($this->all_recipients[strtolower($to[0])]);
3190: }
3191: $this->to = array();
3192: $this->clearQueuedAddresses('to');
3193: }
3194:
3195: /**
3196: * Clear all CC recipients.
3197: * @return void
3198: */
3199: public function clearCCs()
3200: {
3201: foreach ($this->cc as $cc) {
3202: unset($this->all_recipients[strtolower($cc[0])]);
3203: }
3204: $this->cc = array();
3205: $this->clearQueuedAddresses('cc');
3206: }
3207:
3208: /**
3209: * Clear all BCC recipients.
3210: * @return void
3211: */
3212: public function clearBCCs()
3213: {
3214: foreach ($this->bcc as $bcc) {
3215: unset($this->all_recipients[strtolower($bcc[0])]);
3216: }
3217: $this->bcc = array();
3218: $this->clearQueuedAddresses('bcc');
3219: }
3220:
3221: /**
3222: * Clear all ReplyTo recipients.
3223: * @return void
3224: */
3225: public function clearReplyTos()
3226: {
3227: $this->ReplyTo = array();
3228: $this->ReplyToQueue = array();
3229: }
3230:
3231: /**
3232: * Clear all recipient types.
3233: * @return void
3234: */
3235: public function clearAllRecipients()
3236: {
3237: $this->to = array();
3238: $this->cc = array();
3239: $this->bcc = array();
3240: $this->all_recipients = array();
3241: $this->RecipientsQueue = array();
3242: }
3243:
3244: /**
3245: * Clear all filesystem, string, and binary attachments.
3246: * @return void
3247: */
3248: public function clearAttachments()
3249: {
3250: $this->attachment = array();
3251: }
3252:
3253: /**
3254: * Clear all custom headers.
3255: * @return void
3256: */
3257: public function clearCustomHeaders()
3258: {
3259: $this->CustomHeader = array();
3260: }
3261:
3262: /**
3263: * Add an error message to the error container.
3264: * @access protected
3265: * @param string $msg
3266: * @return void
3267: */
3268: protected function setError($msg)
3269: {
3270: $this->error_count++;
3271: if ($this->Mailer == 'smtp' and !is_null($this->smtp)) {
3272: $lasterror = $this->smtp->getError();
3273: if (!empty($lasterror['error'])) {
3274: $msg .= $this->lang('smtp_error') . $lasterror['error'];
3275: if (!empty($lasterror['detail'])) {
3276: $msg .= ' Detail: '. $lasterror['detail'];
3277: }
3278: if (!empty($lasterror['smtp_code'])) {
3279: $msg .= ' SMTP code: ' . $lasterror['smtp_code'];
3280: }
3281: if (!empty($lasterror['smtp_code_ex'])) {
3282: $msg .= ' Additional SMTP info: ' . $lasterror['smtp_code_ex'];
3283: }
3284: }
3285: }
3286: $this->ErrorInfo = $msg;
3287: }
3288:
3289: /**
3290: * Return an RFC 822 formatted date.
3291: * @access public
3292: * @return string
3293: * @static
3294: */
3295: public static function rfcDate()
3296: {
3297: // Set the time zone to whatever the default is to avoid 500 errors
3298: // Will default to UTC if it's not set properly in php.ini
3299: date_default_timezone_set(@date_default_timezone_get());
3300: return date('D, j M Y H:i:s O');
3301: }
3302:
3303: /**
3304: * Get the server hostname.
3305: * Returns 'localhost.localdomain' if unknown.
3306: * @access protected
3307: * @return string
3308: */
3309: protected function serverHostname()
3310: {
3311: $result = 'localhost.localdomain';
3312: if (!empty($this->Hostname)) {
3313: $result = $this->Hostname;
3314: } elseif (isset($_SERVER) and array_key_exists('SERVER_NAME', $_SERVER) and !empty($_SERVER['SERVER_NAME'])) {
3315: $result = $_SERVER['SERVER_NAME'];
3316: } elseif (function_exists('gethostname') && gethostname() !== false) {
3317: $result = gethostname();
3318: } elseif (php_uname('n') !== false) {
3319: $result = php_uname('n');
3320: }
3321: return $result;
3322: }
3323:
3324: /**
3325: * Get an error message in the current language.
3326: * @access protected
3327: * @param string $key
3328: * @return string
3329: */
3330: protected function lang($key)
3331: {
3332: if (count($this->language) < 1) {
3333: $this->setLanguage('en'); // set the default language
3334: }
3335:
3336: if (array_key_exists($key, $this->language)) {
3337: if ($key == 'smtp_connect_failed') {
3338: //Include a link to troubleshooting docs on SMTP connection failure
3339: //this is by far the biggest cause of support questions
3340: //but it's usually not PHPMailer's fault.
3341: return $this->language[$key] . ' https://github.com/PHPMailer/PHPMailer/wiki/Troubleshooting';
3342: }
3343: return $this->language[$key];
3344: } else {
3345: //Return the key as a fallback
3346: return $key;
3347: }
3348: }
3349:
3350: /**
3351: * Check if an error occurred.
3352: * @access public
3353: * @return boolean True if an error did occur.
3354: */
3355: public function isError()
3356: {
3357: return ($this->error_count > 0);
3358: }
3359:
3360: /**
3361: * Ensure consistent line endings in a string.
3362: * Changes every end of line from CRLF, CR or LF to $this->LE.
3363: * @access public
3364: * @param string $str String to fixEOL
3365: * @return string
3366: */
3367: public function fixEOL($str)
3368: {
3369: // Normalise to \n
3370: $nstr = str_replace(array("\r\n", "\r"), "\n", $str);
3371: // Now convert LE as needed
3372: if ($this->LE !== "\n") {
3373: $nstr = str_replace("\n", $this->LE, $nstr);
3374: }
3375: return $nstr;
3376: }
3377:
3378: /**
3379: * Add a custom header.
3380: * $name value can be overloaded to contain
3381: * both header name and value (name:value)
3382: * @access public
3383: * @param string $name Custom header name
3384: * @param string $value Header value
3385: * @return void
3386: */
3387: public function addCustomHeader($name, $value = null)
3388: {
3389: if ($value === null) {
3390: // Value passed in as name:value
3391: $this->CustomHeader[] = explode(':', $name, 2);
3392: } else {
3393: $this->CustomHeader[] = array($name, $value);
3394: }
3395: }
3396:
3397: /**
3398: * Returns all custom headers.
3399: * @return array
3400: */
3401: public function getCustomHeaders()
3402: {
3403: return $this->CustomHeader;
3404: }
3405:
3406: /**
3407: * Create a message body from an HTML string.
3408: * Automatically inlines images and creates a plain-text version by converting the HTML,
3409: * overwriting any existing values in Body and AltBody.
3410: * Do not source $message content from user input!
3411: * $basedir is prepended when handling relative URLs, e.g. <img src="/images/a.png"> and must not be empty
3412: * will look for an image file in $basedir/images/a.png and convert it to inline.
3413: * If you don't provide a $basedir, relative paths will be left untouched (and thus probably break in email)
3414: * If you don't want to apply these transformations to your HTML, just set Body and AltBody directly.
3415: * @access public
3416: * @param string $message HTML message string
3417: * @param string $basedir Absolute path to a base directory to prepend to relative paths to images
3418: * @param boolean|callable $advanced Whether to use the internal HTML to text converter
3419: * or your own custom converter @see PHPMailer::html2text()
3420: * @return string $message The transformed message Body
3421: */
3422: public function msgHTML($message, $basedir = '', $advanced = false)
3423: {
3424: preg_match_all('/(src|background)=["\'](.*)["\']/Ui', $message, $images);
3425: if (array_key_exists(2, $images)) {
3426: if (strlen($basedir) > 1 && substr($basedir, -1) != '/') {
3427: // Ensure $basedir has a trailing /
3428: $basedir .= '/';
3429: }
3430: foreach ($images[2] as $imgindex => $url) {
3431: // Convert data URIs into embedded images
3432: if (preg_match('#^data:(image[^;,]*)(;base64)?,#', $url, $match)) {
3433: $data = substr($url, strpos($url, ','));
3434: if ($match[2]) {
3435: $data = base64_decode($data);
3436: } else {
3437: $data = rawurldecode($data);
3438: }
3439: $cid = md5($url) . '@phpmailer.0'; // RFC2392 S 2
3440: if ($this->addStringEmbeddedImage($data, $cid, 'embed' . $imgindex, 'base64', $match[1])) {
3441: $message = str_replace(
3442: $images[0][$imgindex],
3443: $images[1][$imgindex] . '="cid:' . $cid . '"',
3444: $message
3445: );
3446: }
3447: continue;
3448: }
3449: if (
3450: // Only process relative URLs if a basedir is provided (i.e. no absolute local paths)
3451: !empty($basedir)
3452: // Ignore URLs containing parent dir traversal (..)
3453: && (strpos($url, '..') === false)
3454: // Do not change urls that are already inline images
3455: && substr($url, 0, 4) !== 'cid:'
3456: // Do not change absolute URLs, including anonymous protocol
3457: && !preg_match('#^[a-z][a-z0-9+.-]*:?//#i', $url)
3458: ) {
3459: $filename = basename($url);
3460: $directory = dirname($url);
3461: if ($directory == '.') {
3462: $directory = '';
3463: }
3464: $cid = md5($url) . '@phpmailer.0'; // RFC2392 S 2
3465: if (strlen($directory) > 1 && substr($directory, -1) != '/') {
3466: $directory .= '/';
3467: }
3468: if ($this->addEmbeddedImage(
3469: $basedir . $directory . $filename,
3470: $cid,
3471: $filename,
3472: 'base64',
3473: self::_mime_types((string)self::mb_pathinfo($filename, PATHINFO_EXTENSION))
3474: )
3475: ) {
3476: $message = preg_replace(
3477: '/' . $images[1][$imgindex] . '=["\']' . preg_quote($url, '/') . '["\']/Ui',
3478: $images[1][$imgindex] . '="cid:' . $cid . '"',
3479: $message
3480: );
3481: }
3482: }
3483: }
3484: }
3485: $this->isHTML(true);
3486: // Convert all message body line breaks to CRLF, makes quoted-printable encoding work much better
3487: $this->Body = $this->normalizeBreaks($message);
3488: $this->AltBody = $this->normalizeBreaks($this->html2text($message, $advanced));
3489: if (!$this->alternativeExists()) {
3490: $this->AltBody = 'To view this email message, open it in a program that understands HTML!' .
3491: self::CRLF . self::CRLF;
3492: }
3493: return $this->Body;
3494: }
3495:
3496: /**
3497: * Convert an HTML string into plain text.
3498: * This is used by msgHTML().
3499: * Note - older versions of this function used a bundled advanced converter
3500: * which was been removed for license reasons in #232.
3501: * Example usage:
3502: * <code>
3503: * // Use default conversion
3504: * $plain = $mail->html2text($html);
3505: * // Use your own custom converter
3506: * $plain = $mail->html2text($html, function($html) {
3507: * $converter = new MyHtml2text($html);
3508: * return $converter->get_text();
3509: * });
3510: * </code>
3511: * @param string $html The HTML text to convert
3512: * @param boolean|callable $advanced Any boolean value to use the internal converter,
3513: * or provide your own callable for custom conversion.
3514: * @return string
3515: */
3516: public function html2text($html, $advanced = false)
3517: {
3518: if (is_callable($advanced)) {
3519: return call_user_func($advanced, $html);
3520: }
3521: return html_entity_decode(
3522: trim(strip_tags(preg_replace('/<(head|title|style|script)[^>]*>.*?<\/\\1>/si', '', $html))),
3523: ENT_QUOTES,
3524: $this->CharSet
3525: );
3526: }
3527:
3528: /**
3529: * Get the MIME type for a file extension.
3530: * @param string $ext File extension
3531: * @access public
3532: * @return string MIME type of file.
3533: * @static
3534: */
3535: public static function _mime_types($ext = '')
3536: {
3537: $mimes = array(
3538: 'xl' => 'application/excel',
3539: 'js' => 'application/javascript',
3540: 'hqx' => 'application/mac-binhex40',
3541: 'cpt' => 'application/mac-compactpro',
3542: 'bin' => 'application/macbinary',
3543: 'doc' => 'application/msword',
3544: 'word' => 'application/msword',
3545: 'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
3546: 'xltx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.template',
3547: 'potx' => 'application/vnd.openxmlformats-officedocument.presentationml.template',
3548: 'ppsx' => 'application/vnd.openxmlformats-officedocument.presentationml.slideshow',
3549: 'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
3550: 'sldx' => 'application/vnd.openxmlformats-officedocument.presentationml.slide',
3551: 'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
3552: 'dotx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.template',
3553: 'xlam' => 'application/vnd.ms-excel.addin.macroEnabled.12',
3554: 'xlsb' => 'application/vnd.ms-excel.sheet.binary.macroEnabled.12',
3555: 'class' => 'application/octet-stream',
3556: 'dll' => 'application/octet-stream',
3557: 'dms' => 'application/octet-stream',
3558: 'exe' => 'application/octet-stream',
3559: 'lha' => 'application/octet-stream',
3560: 'lzh' => 'application/octet-stream',
3561: 'psd' => 'application/octet-stream',
3562: 'sea' => 'application/octet-stream',
3563: 'so' => 'application/octet-stream',
3564: 'oda' => 'application/oda',
3565: 'pdf' => 'application/pdf',
3566: 'ai' => 'application/postscript',
3567: 'eps' => 'application/postscript',
3568: 'ps' => 'application/postscript',
3569: 'smi' => 'application/smil',
3570: 'smil' => 'application/smil',
3571: 'mif' => 'application/vnd.mif',
3572: 'xls' => 'application/vnd.ms-excel',
3573: 'ppt' => 'application/vnd.ms-powerpoint',
3574: 'wbxml' => 'application/vnd.wap.wbxml',
3575: 'wmlc' => 'application/vnd.wap.wmlc',
3576: 'dcr' => 'application/x-director',
3577: 'dir' => 'application/x-director',
3578: 'dxr' => 'application/x-director',
3579: 'dvi' => 'application/x-dvi',
3580: 'gtar' => 'application/x-gtar',
3581: 'php3' => 'application/x-httpd-php',
3582: 'php4' => 'application/x-httpd-php',
3583: 'php' => 'application/x-httpd-php',
3584: 'phtml' => 'application/x-httpd-php',
3585: 'phps' => 'application/x-httpd-php-source',
3586: 'swf' => 'application/x-shockwave-flash',
3587: 'sit' => 'application/x-stuffit',
3588: 'tar' => 'application/x-tar',
3589: 'tgz' => 'application/x-tar',
3590: 'xht' => 'application/xhtml+xml',
3591: 'xhtml' => 'application/xhtml+xml',
3592: 'zip' => 'application/zip',
3593: 'mid' => 'audio/midi',
3594: 'midi' => 'audio/midi',
3595: 'mp2' => 'audio/mpeg',
3596: 'mp3' => 'audio/mpeg',
3597: 'mpga' => 'audio/mpeg',
3598: 'aif' => 'audio/x-aiff',
3599: 'aifc' => 'audio/x-aiff',
3600: 'aiff' => 'audio/x-aiff',
3601: 'ram' => 'audio/x-pn-realaudio',
3602: 'rm' => 'audio/x-pn-realaudio',
3603: 'rpm' => 'audio/x-pn-realaudio-plugin',
3604: 'ra' => 'audio/x-realaudio',
3605: 'wav' => 'audio/x-wav',
3606: 'bmp' => 'image/bmp',
3607: 'gif' => 'image/gif',
3608: 'jpeg' => 'image/jpeg',
3609: 'jpe' => 'image/jpeg',
3610: 'jpg' => 'image/jpeg',
3611: 'png' => 'image/png',
3612: 'tiff' => 'image/tiff',
3613: 'tif' => 'image/tiff',
3614: 'eml' => 'message/rfc822',
3615: 'css' => 'text/css',
3616: 'html' => 'text/html',
3617: 'htm' => 'text/html',
3618: 'shtml' => 'text/html',
3619: 'log' => 'text/plain',
3620: 'text' => 'text/plain',
3621: 'txt' => 'text/plain',
3622: 'rtx' => 'text/richtext',
3623: 'rtf' => 'text/rtf',
3624: 'vcf' => 'text/vcard',
3625: 'vcard' => 'text/vcard',
3626: 'xml' => 'text/xml',
3627: 'xsl' => 'text/xml',
3628: 'mpeg' => 'video/mpeg',
3629: 'mpe' => 'video/mpeg',
3630: 'mpg' => 'video/mpeg',
3631: 'mov' => 'video/quicktime',
3632: 'qt' => 'video/quicktime',
3633: 'rv' => 'video/vnd.rn-realvideo',
3634: 'avi' => 'video/x-msvideo',
3635: 'movie' => 'video/x-sgi-movie'
3636: );
3637: if (array_key_exists(strtolower($ext), $mimes)) {
3638: return $mimes[strtolower($ext)];
3639: }
3640: return 'application/octet-stream';
3641: }
3642:
3643: /**
3644: * Map a file name to a MIME type.
3645: * Defaults to 'application/octet-stream', i.e.. arbitrary binary data.
3646: * @param string $filename A file name or full path, does not need to exist as a file
3647: * @return string
3648: * @static
3649: */
3650: public static function filenameToType($filename)
3651: {
3652: // In case the path is a URL, strip any query string before getting extension
3653: $qpos = strpos($filename, '?');
3654: if (false !== $qpos) {
3655: $filename = substr($filename, 0, $qpos);
3656: }
3657: $pathinfo = self::mb_pathinfo($filename);
3658: return self::_mime_types($pathinfo['extension']);
3659: }
3660:
3661: /**
3662: * Multi-byte-safe pathinfo replacement.
3663: * Drop-in replacement for pathinfo(), but multibyte-safe, cross-platform-safe, old-version-safe.
3664: * Works similarly to the one in PHP >= 5.2.0
3665: * @link http://www.php.net/manual/en/function.pathinfo.php#107461
3666: * @param string $path A filename or path, does not need to exist as a file
3667: * @param integer|string $options Either a PATHINFO_* constant,
3668: * or a string name to return only the specified piece, allows 'filename' to work on PHP < 5.2
3669: * @return string|array
3670: * @static
3671: */
3672: public static function mb_pathinfo($path, $options = null)
3673: {
3674: $ret = array('dirname' => '', 'basename' => '', 'extension' => '', 'filename' => '');
3675: $pathinfo = array();
3676: if (preg_match('%^(.*?)[\\\\/]*(([^/\\\\]*?)(\.([^\.\\\\/]+?)|))[\\\\/\.]*$%im', $path, $pathinfo)) {
3677: if (array_key_exists(1, $pathinfo)) {
3678: $ret['dirname'] = $pathinfo[1];
3679: }
3680: if (array_key_exists(2, $pathinfo)) {
3681: $ret['basename'] = $pathinfo[2];
3682: }
3683: if (array_key_exists(5, $pathinfo)) {
3684: $ret['extension'] = $pathinfo[5];
3685: }
3686: if (array_key_exists(3, $pathinfo)) {
3687: $ret['filename'] = $pathinfo[3];
3688: }
3689: }
3690: switch ($options) {
3691: case PATHINFO_DIRNAME:
3692: case 'dirname':
3693: return $ret['dirname'];
3694: case PATHINFO_BASENAME:
3695: case 'basename':
3696: return $ret['basename'];
3697: case PATHINFO_EXTENSION:
3698: case 'extension':
3699: return $ret['extension'];
3700: case PATHINFO_FILENAME:
3701: case 'filename':
3702: return $ret['filename'];
3703: default:
3704: return $ret;
3705: }
3706: }
3707:
3708: /**
3709: * Set or reset instance properties.
3710: * You should avoid this function - it's more verbose, less efficient, more error-prone and
3711: * harder to debug than setting properties directly.
3712: * Usage Example:
3713: * `$mail->set('SMTPSecure', 'tls');`
3714: * is the same as:
3715: * `$mail->SMTPSecure = 'tls';`
3716: * @access public
3717: * @param string $name The property name to set
3718: * @param mixed $value The value to set the property to
3719: * @return boolean
3720: * @TODO Should this not be using the __set() magic function?
3721: */
3722: public function set($name, $value = '')
3723: {
3724: if (property_exists($this, $name)) {
3725: $this->$name = $value;
3726: return true;
3727: } else {
3728: $this->setError($this->lang('variable_set') . $name);
3729: return false;
3730: }
3731: }
3732:
3733: /**
3734: * Strip newlines to prevent header injection.
3735: * @access public
3736: * @param string $str
3737: * @return string
3738: */
3739: public function secureHeader($str)
3740: {
3741: return trim(str_replace(array("\r", "\n"), '', $str));
3742: }
3743:
3744: /**
3745: * Normalize line breaks in a string.
3746: * Converts UNIX LF, Mac CR and Windows CRLF line breaks into a single line break format.
3747: * Defaults to CRLF (for message bodies) and preserves consecutive breaks.
3748: * @param string $text
3749: * @param string $breaktype What kind of line break to use, defaults to CRLF
3750: * @return string
3751: * @access public
3752: * @static
3753: */
3754: public static function normalizeBreaks($text, $breaktype = "\r\n")
3755: {
3756: return preg_replace('/(\r\n|\r|\n)/ms', $breaktype, $text);
3757: }
3758:
3759: /**
3760: * Set the public and private key files and password for S/MIME signing.
3761: * @access public
3762: * @param string $cert_filename
3763: * @param string $key_filename
3764: * @param string $key_pass Password for private key
3765: * @param string $extracerts_filename Optional path to chain certificate
3766: */
3767: public function sign($cert_filename, $key_filename, $key_pass, $extracerts_filename = '')
3768: {
3769: $this->sign_cert_file = $cert_filename;
3770: $this->sign_key_file = $key_filename;
3771: $this->sign_key_pass = $key_pass;
3772: $this->sign_extracerts_file = $extracerts_filename;
3773: }
3774:
3775: /**
3776: * Quoted-Printable-encode a DKIM header.
3777: * @access public
3778: * @param string $txt
3779: * @return string
3780: */
3781: public function DKIM_QP($txt)
3782: {
3783: $line = '';
3784: for ($i = 0; $i < strlen($txt); $i++) {
3785: $ord = ord($txt[$i]);
3786: if (((0x21 <= $ord) && ($ord <= 0x3A)) || $ord == 0x3C || ((0x3E <= $ord) && ($ord <= 0x7E))) {
3787: $line .= $txt[$i];
3788: } else {
3789: $line .= '=' . sprintf('%02X', $ord);
3790: }
3791: }
3792: return $line;
3793: }
3794:
3795: /**
3796: * Generate a DKIM signature.
3797: * @access public
3798: * @param string $signHeader
3799: * @throws phpmailerException
3800: * @return string The DKIM signature value
3801: */
3802: public function DKIM_Sign($signHeader)
3803: {
3804: if (!defined('PKCS7_TEXT')) {
3805: if ($this->exceptions) {
3806: throw new phpmailerException($this->lang('extension_missing') . 'openssl');
3807: }
3808: return '';
3809: }
3810: $privKeyStr = !empty($this->DKIM_private_string) ? $this->DKIM_private_string : file_get_contents($this->DKIM_private);
3811: if ('' != $this->DKIM_passphrase) {
3812: $privKey = openssl_pkey_get_private($privKeyStr, $this->DKIM_passphrase);
3813: } else {
3814: $privKey = openssl_pkey_get_private($privKeyStr);
3815: }
3816: //Workaround for missing digest algorithms in old PHP & OpenSSL versions
3817: //@link http://stackoverflow.com/a/11117338/333340
3818: if (version_compare(PHP_VERSION, '5.3.0') >= 0 and
3819: in_array('sha256WithRSAEncryption', openssl_get_md_methods(true))) {
3820: if (openssl_sign($signHeader, $signature, $privKey, 'sha256WithRSAEncryption')) {
3821: openssl_pkey_free($privKey);
3822: return base64_encode($signature);
3823: }
3824: } else {
3825: $pinfo = openssl_pkey_get_details($privKey);
3826: $hash = hash('sha256', $signHeader);
3827: //'Magic' constant for SHA256 from RFC3447
3828: //@link https://tools.ietf.org/html/rfc3447#page-43
3829: $t = '3031300d060960864801650304020105000420' . $hash;
3830: $pslen = $pinfo['bits'] / 8 - (strlen($t) / 2 + 3);
3831: $eb = pack('H*', '0001' . str_repeat('FF', $pslen) . '00' . $t);
3832:
3833: if (openssl_private_encrypt($eb, $signature, $privKey, OPENSSL_NO_PADDING)) {
3834: openssl_pkey_free($privKey);
3835: return base64_encode($signature);
3836: }
3837: }
3838: openssl_pkey_free($privKey);
3839: return '';
3840: }
3841:
3842: /**
3843: * Generate a DKIM canonicalization header.
3844: * @access public
3845: * @param string $signHeader Header
3846: * @return string
3847: */
3848: public function DKIM_HeaderC($signHeader)
3849: {
3850: $signHeader = preg_replace('/\r\n\s+/', ' ', $signHeader);
3851: $lines = explode("\r\n", $signHeader);
3852: foreach ($lines as $key => $line) {
3853: list($heading, $value) = explode(':', $line, 2);
3854: $heading = strtolower($heading);
3855: $value = preg_replace('/\s{2,}/', ' ', $value); // Compress useless spaces
3856: $lines[$key] = $heading . ':' . trim($value); // Don't forget to remove WSP around the value
3857: }
3858: $signHeader = implode("\r\n", $lines);
3859: return $signHeader;
3860: }
3861:
3862: /**
3863: * Generate a DKIM canonicalization body.
3864: * @access public
3865: * @param string $body Message Body
3866: * @return string
3867: */
3868: public function DKIM_BodyC($body)
3869: {
3870: if ($body == '') {
3871: return "\r\n";
3872: }
3873: // stabilize line endings
3874: $body = str_replace("\r\n", "\n", $body);
3875: $body = str_replace("\n", "\r\n", $body);
3876: // END stabilize line endings
3877: while (substr($body, strlen($body) - 4, 4) == "\r\n\r\n") {
3878: $body = substr($body, 0, strlen($body) - 2);
3879: }
3880: return $body;
3881: }
3882:
3883: /**
3884: * Create the DKIM header and body in a new message header.
3885: * @access public
3886: * @param string $headers_line Header lines
3887: * @param string $subject Subject
3888: * @param string $body Body
3889: * @return string
3890: */
3891: public function DKIM_Add($headers_line, $subject, $body)
3892: {
3893: $DKIMsignatureType = 'rsa-sha256'; // Signature & hash algorithms
3894: $DKIMcanonicalization = 'relaxed/simple'; // Canonicalization of header/body
3895: $DKIMquery = 'dns/txt'; // Query method
3896: $DKIMtime = time(); // Signature Timestamp = seconds since 00:00:00 - Jan 1, 1970 (UTC time zone)
3897: $subject_header = "Subject: $subject";
3898: $headers = explode($this->LE, $headers_line);
3899: $from_header = '';
3900: $to_header = '';
3901: $date_header = '';
3902: $current = '';
3903: foreach ($headers as $header) {
3904: if (strpos($header, 'From:') === 0) {
3905: $from_header = $header;
3906: $current = 'from_header';
3907: } elseif (strpos($header, 'To:') === 0) {
3908: $to_header = $header;
3909: $current = 'to_header';
3910: } elseif (strpos($header, 'Date:') === 0) {
3911: $date_header = $header;
3912: $current = 'date_header';
3913: } else {
3914: if (!empty($$current) && strpos($header, ' =?') === 0) {
3915: $$current .= $header;
3916: } else {
3917: $current = '';
3918: }
3919: }
3920: }
3921: $from = str_replace('|', '=7C', $this->DKIM_QP($from_header));
3922: $to = str_replace('|', '=7C', $this->DKIM_QP($to_header));
3923: $date = str_replace('|', '=7C', $this->DKIM_QP($date_header));
3924: $subject = str_replace(
3925: '|',
3926: '=7C',
3927: $this->DKIM_QP($subject_header)
3928: ); // Copied header fields (dkim-quoted-printable)
3929: $body = $this->DKIM_BodyC($body);
3930: $DKIMlen = strlen($body); // Length of body
3931: $DKIMb64 = base64_encode(pack('H*', hash('sha256', $body))); // Base64 of packed binary SHA-256 hash of body
3932: if ('' == $this->DKIM_identity) {
3933: $ident = '';
3934: } else {
3935: $ident = ' i=' . $this->DKIM_identity . ';';
3936: }
3937: $dkimhdrs = 'DKIM-Signature: v=1; a=' .
3938: $DKIMsignatureType . '; q=' .
3939: $DKIMquery . '; l=' .
3940: $DKIMlen . '; s=' .
3941: $this->DKIM_selector .
3942: ";\r\n" .
3943: "\tt=" . $DKIMtime . '; c=' . $DKIMcanonicalization . ";\r\n" .
3944: "\th=From:To:Date:Subject;\r\n" .
3945: "\td=" . $this->DKIM_domain . ';' . $ident . "\r\n" .
3946: "\tz=$from\r\n" .
3947: "\t|$to\r\n" .
3948: "\t|$date\r\n" .
3949: "\t|$subject;\r\n" .
3950: "\tbh=" . $DKIMb64 . ";\r\n" .
3951: "\tb=";
3952: $toSign = $this->DKIM_HeaderC(
3953: $from_header . "\r\n" .
3954: $to_header . "\r\n" .
3955: $date_header . "\r\n" .
3956: $subject_header . "\r\n" .
3957: $dkimhdrs
3958: );
3959: $signed = $this->DKIM_Sign($toSign);
3960: return $dkimhdrs . $signed . "\r\n";
3961: }
3962:
3963: /**
3964: * Detect if a string contains a line longer than the maximum line length allowed.
3965: * @param string $str
3966: * @return boolean
3967: * @static
3968: */
3969: public static function hasLineLongerThanMax($str)
3970: {
3971: //+2 to include CRLF line break for a 1000 total
3972: return (boolean)preg_match('/^(.{'.(self::MAX_LINE_LENGTH + 2).',})/m', $str);
3973: }
3974:
3975: /**
3976: * Allows for public read access to 'to' property.
3977: * @note: Before the send() call, queued addresses (i.e. with IDN) are not yet included.
3978: * @access public
3979: * @return array
3980: */
3981: public function getToAddresses()
3982: {
3983: return $this->to;
3984: }
3985:
3986: /**
3987: * Allows for public read access to 'cc' property.
3988: * @note: Before the send() call, queued addresses (i.e. with IDN) are not yet included.
3989: * @access public
3990: * @return array
3991: */
3992: public function getCcAddresses()
3993: {
3994: return $this->cc;
3995: }
3996:
3997: /**
3998: * Allows for public read access to 'bcc' property.
3999: * @note: Before the send() call, queued addresses (i.e. with IDN) are not yet included.
4000: * @access public
4001: * @return array
4002: */
4003: public function getBccAddresses()
4004: {
4005: return $this->bcc;
4006: }
4007:
4008: /**
4009: * Allows for public read access to 'ReplyTo' property.
4010: * @note: Before the send() call, queued addresses (i.e. with IDN) are not yet included.
4011: * @access public
4012: * @return array
4013: */
4014: public function getReplyToAddresses()
4015: {
4016: return $this->ReplyTo;
4017: }
4018:
4019: /**
4020: * Allows for public read access to 'all_recipients' property.
4021: * @note: Before the send() call, queued addresses (i.e. with IDN) are not yet included.
4022: * @access public
4023: * @return array
4024: */
4025: public function getAllRecipientAddresses()
4026: {
4027: return $this->all_recipients;
4028: }
4029:
4030: /**
4031: * Perform a callback.
4032: * @param boolean $isSent
4033: * @param array $to
4034: * @param array $cc
4035: * @param array $bcc
4036: * @param string $subject
4037: * @param string $body
4038: * @param string $from
4039: */
4040: protected function doCallback($isSent, $to, $cc, $bcc, $subject, $body, $from)
4041: {
4042: if (!empty($this->action_function) && is_callable($this->action_function)) {
4043: $params = array($isSent, $to, $cc, $bcc, $subject, $body, $from);
4044: call_user_func_array($this->action_function, $params);
4045: }
4046: }
4047: }
4048:
4049: /**
4050: * PHPMailer exception handler
4051: * @package PHPMailer
4052: */
4053: class phpmailerException extends Exception
4054: {
4055: /**
4056: * Prettify error message output
4057: * @return string
4058: */
4059: public function errorMessage()
4060: {
4061: $errorMsg = '<strong>' . htmlspecialchars($this->getMessage()) . "</strong><br />\n";
4062: return $errorMsg;
4063: }
4064: }
4065: