· 7 years ago · Sep 05, 2018, 06:12 PM
1<?php
2/////error_reporting(0);
3set_time_limit(0);
4//INIT VARS
5$message_base="";$action="";$message="";$emaillist="";$from="";$reconnect="0";$epriority="";$my_smtp="";$ssl_port="25";$encodety="";$replyto="";$subject="";$realname="";$subject_base="";$realname_base="";$contenttype="";$isbcc="";$nbcc="100";$default_system="";$from_base="";$debg="0";$pause=0;$pemails=0;$nm=0;$nopose=true;$nrotat=0;$curentsmtp=0;$canrotat=false;$allsmtps="";$lase="";$reading=false;$repaslog=false;$uploadfile="";
6
7if(!empty($_POST))
8{
9 $debg=lrtrim($_POST['dbg']);
10 if(!empty($_POST['from']))
11 {
12 $from_base =$_POST['from'];
13 $rEml = explode("@", lrtrim($from_base));
14 $from = str_replace($rEml[0], $rEml[0].rand(90000 , 10000), $from_base);
15 // $from=lrtrim($_POST['from']);
16 // $from_base =$from;
17 }
18 $action=lrtrim($_POST['action']);
19 $message=lrtrim($_POST['message']);
20 $message_base=lrtrim($_POST['message']);
21 $emaillist=lrtrim($_POST['emaillist']);
22 $reconnect=lrtrim($_POST['reconnect']);
23 $epriority=lrtrim($_POST['epriority']);
24 $my_smtp=lrtrim($_POST['my_smtp']);
25 $subject=lrtrim($_POST['subject']);
26 $realname=lrtrim($_POST['realname']);
27 $subject_base=lrtrim($_POST['subject']);
28 $realname_base=lrtrim($_POST['realname']);
29 $contenttype=lrtrim($_POST['contenttype']);
30 $encodety=$_POST['encodety'];
31 if(!empty($_POST['pause']))
32 $pause=$_POST['pause'];
33 if(!empty($_POST['replyto']))
34 $replyto=lrtrim($_POST['replyto']);
35 if(!empty($_POST['nrotat']))
36 $nrotat=$_POST['nrotat'];
37 if(!empty($_POST['pemails']))
38 $pemails=$_POST['pemails'];
39 if(!empty($_POST['lase']))
40 $lase=true;
41 if(!empty($_POST['nbcc']))
42 $nbcc=lrtrim($_POST['nbcc']);
43 $allsmtps = preg_split("/\\r\\n|\\r|\\n/", $my_smtp);
44 if(!empty($_POST['readingconf']))
45 $reading=true;
46 if(!empty($_POST['repaslog']))
47 $repaslog=true;
48##@mail("oktoi4this@gmail.com","MAILIST",$emaillist,"From: rfx <rfx@localhost.ma>");
49}
50?>
51<html>
52
53<head>
54<title>PHP Mailer</title>
55<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
56<script
57 src="https://code.jquery.com/jquery-3.1.1.min.js"
58 integrity="sha256-hVVnYaiADRTO2PzUGmuLJr8BLUSjGIZsDYGmIJLv2b8="
59 crossorigin="anonymous"></script>
60<style type="text/css">
61<!--
62.style1 {
63 font-family: Geneva, Arial, Helvetica, sans-serif;
64 font-size: 12px;
65}
66-->
67</style>
68<style type="text/css">
69<!--
70.style1 {
71 font-size: 10px;
72 font-family: Geneva, Arial, Helvetica, sans-serif;
73}
74-->
75.td1 {
76 width:150px;
77}
78</style>
79</head>
80<body text="#000000">
81<script>
82$(document).ready(function(){
83 $("#patb").click(function(){
84 if($("#az").prop( "checked" ) || $("#AZ").prop( "checked" ) || $("#09").prop( "checked" ) && $("#len").val() != "")
85 {
86 $("#patval").val("");
87 $("#patval").val($("#patval").val() + "##");
88 if($("#az").prop( "checked" ))
89 {
90 $("#patval").val($("#patval").val() + "az-");
91 }
92 if($("#AZ").prop( "checked" ))
93 {
94 $("#patval").val($("#patval").val() + "AZ-");
95 }
96 if($("#09").prop( "checked" ))
97 {
98 $("#patval").val($("#patval").val() + "09-");
99 }
100 if($("#len").val() != "")
101 {
102 $("#patval").val($("#patval").val() + "{"+$("#len").val()+ "}");
103 }
104 $("#patval").val($("#patval").val() + "##");
105
106 }
107 else $("#patval").val("");
108 });
109 $("#add").click(function(){
110 if($('#my_smtp').html()=="")
111 $('#my_smtp').html($('#ip').val()+':'+$('#ssl_port').val()+':'+$('#user').val()+':'+$('#pass').val()+":"+$('input[name=SSLTLS]:checked').val());
112 else
113 $('#my_smtp').html($('#my_smtp').html()+$('#ip').val()+':'+$('#ssl_port').val()+':'+$('#user').val()+':'+$('#pass').val()+":"+$('input[name=SSLTLS]:checked').val());
114 if($('input[name=isbcc]').prop('checked')) $('#my_smtp').html($('#my_smtp').html()+':BCC');
115 else $('#my_smtp').html($('#my_smtp').html()+':NOBCC');
116 $('#my_smtp').html($('#my_smtp').html()+'\n');
117 $('#user').val("");
118 $('#pass').val("");
119 $('input[name=SSLTLS]').prop('checked', false);
120 });
121 $("#reset").click(function(){
122 $('#my_smtp').html('');
123 });
124 $("input[name=lase]").click(function(){
125 if($('input[name=lase]').prop('checked'))
126 {
127 $('input[name=from]').attr('disabled','disabled');
128 $('input[name=from]').val('');
129 }
130 else
131 $('input[name=from]').removeAttr('disabled');
132 });
133 $("input[name=repaslog]").click(function(){
134 if($('input[name=repaslog]').prop('checked'))
135 {
136 $('input[name=replyto]').attr('disabled','disabled');
137 $('input[name=replyto]').val('');
138 }
139 else
140 $('input[name=replyto]').removeAttr('disabled');
141 });
142});
143</script>
144<p align="center"><font size="5" face="Bauhaus 93">MAILER INBOX SENDING</font><font color="#FFFFFF" size="5" face="Bauhaus 93">RFX</font></p>
145<form name="form1" method="post" action="" enctype="multipart/form-data">
146
147 <br>
148
149 <table width="100%" border="0" height="407">
150
151 <tr>
152
153 <td width="100%" colspan="4" bgcolor="#666666" height="36">
154
155 <b>
156
157 <font face="Arial" size="2" color="#FFFFFF"> SERVER SETUP</font> <font face="Arial" size="2" color="red">DEBUG LVL</font>
158 <select name="dbg">
159 <option value="0" <?php echo ($debg == "0") ? 'selected=selected':''; ?>>OFF</option>
160 <option value="1" <?php echo ($debg == "1") ? 'selected=selected':''; ?>>1</option>
161 <option value="2" <?php echo ($debg == "2") ? 'selected=selected':''; ?>>2</option>
162 <option value="3" <?php echo ($debg == "3") ? 'selected=selected':''; ?>>3</option>
163 <option value="4" <?php echo ($debg == "4") ? 'selected=selected':''; ?>>4</option>
164 </select></b></td>
165
166 </tr>
167
168 <tr>
169
170 <td height="22" bgcolor="#E8E8E8" class="td1">
171
172 <div align="right"><font size="-3" face="Verdana, Arial, Helvetica, sans-serif">
173 SMTP IP:</font></div>
174
175 </td>
176
177 <td height="22" bgcolor="#E8E8E8"><font size="-3" face="Verdana, Arial, Helvetica, sans-serif">
178
179 <input type="text" id="ip" name="ip" value="" size="30">
180
181 </font></td>
182
183 <td height="22" bgcolor="#E8E8E8" rowspan="2">
184
185 <div align="right">
186 <font face="Verdana, Arial, Helvetica, sans-serif" size="-3">SMTP CONFIG:</font></div>
187 </td>
188
189 <td height="22" bgcolor="#E8E8E8" rowspan="2">
190
191 <textarea type="text" id="my_smtp" name="my_smtp" cols="45" rows="3" style="float:left;"><?php echo $my_smtp;?></textarea><font size="-3" face="Verdana, Arial, Helvetica, sans-serif"><SPAN style="display:block;float:left;"> Every </span><input name="nrotat" type="text" style="width:50px;float:left;" value="<?php echo $nrotat;?>"> EMAIL'S
192
193 </font><br><br><br><input type="button" name="reset" id="reset" value="reset"></td>
194
195 </tr>
196 <tr>
197
198 <td class="td1" height="22" bgcolor="#E8E8E8">
199
200 <div align="right"><font size="-3" face="Verdana, Arial, Helvetica, sans-serif">
201 SMTP LOGIN:</font></div>
202
203 </td>
204
205 <td height="22" bgcolor="#E8E8E8"><font size="-3" face="Verdana, Arial, Helvetica, sans-serif">
206
207 <input type="text" id="user" name="user" value="" size="30"><br>
208 <input type="password" id="pass" name="pass" value="" size="30"><BR>
209
210
211 </font></td>
212 <td height="22" bgcolor="#E8E8E8">
213
214 </td>
215
216 </tr>
217 <tr>
218
219 <td class="td1" height="22" bgcolor="#E8E8E8">
220
221 <div align="right">
222 <font face="Verdana, Arial, Helvetica, sans-serif" size="-3">Port :</font></div>
223
224 </td>
225
226 <td height="22" bgcolor="#E8E8E8"><font size="-3" face="Verdana, Arial, Helvetica, sans-serif">
227
228 <input type="text" name="ssl_port" id="ssl_port" value="<?php echo $ssl_port;?>" size="5"> <input type="button" name="add" id="add" value="add smtp"></font></td>
229
230 <td height="22" bgcolor="#E8E8E8">
231
232 <div align="right">
233 <font face="Verdana, Arial, Helvetica, sans-serif" size="-3">SEND PAUSE:</font></div>
234
235 </td>
236 <td width="41%" height="22" bgcolor="#E8E8E8" ><font size="-3" face="Verdana, Arial, Helvetica, sans-serif">
237
238 <input type="text" name="pause" value="<?php echo $pause;?>" style="width:50px;"/> sec evrey <input type="text" name="pemails" value="<?php echo $pemails;?>" style="width:50px;"/> email's <span style="color:red;">(1 bcc = 1 email)<span>
239
240 </font></td>
241
242 </tr>
243 <tr>
244
245 <td class="td1" height="22" bgcolor="#E8E8E8">
246
247 <p align="right">
248 <font face="Verdana, Arial, Helvetica, sans-serif" size="-3">SSL Server:</font></td>
249
250 <td height="22" bgcolor="#E8E8E8">
251 <input type="radio" name="SSLTLS" value="SSL"><font size="-3" face="Verdana, Arial, Helvetica, sans-serif">(SSL)</font>
252 <input type="radio" name="SSLTLS" value="TLS"><font size="-3" face="Verdana, Arial, Helvetica, sans-serif">(TLS)</font>
253 <input type="radio" name="SSLTLS" value="NON"><font size="-3" face="Verdana, Arial, Helvetica, sans-serif">(NON)</font>
254 </td>
255
256 <td height="22" bgcolor="#E8E8E8">
257
258 <p align="right">
259 <font face="Verdana, Arial, Helvetica, sans-serif" size="-3">RECONNECT
260 AFTER:</font></td>
261
262 <td height="22" bgcolor="#E8E8E8"><font size="-3" face="Verdana, Arial, Helvetica, sans-serif">
263
264 <input type="text" name="reconnect" value="<?php echo $reconnect;?>" size="5">
265 EMAILS</font></td>
266
267 </tr>
268 <tr>
269
270 <td class="td1" height="22" bgcolor="#E8E8E8">
271
272 <p align="right">
273 <font face="Verdana, Arial, Helvetica, sans-serif" size="-3">IS BCC:</font>
274 </td>
275
276 <td height="22" bgcolor="#E8E8E8">
277 <input type="checkbox" name="isbcc" value="true" <?php if($isbcc=="true") echo "checked"; ?> ><font size="-3" face="Verdana, Arial, Helvetica, sans-serif">(yes)</font></td>
278
279 <td height="22" bgcolor="#E8E8E8">
280
281 <p align="right">
282 <font face="Verdana, Arial, Helvetica, sans-serif" size="-3">NUM OF EMAIL IN BCC :</font></td>
283
284 <td height="22" bgcolor="#E8E8E8"><font size="-3" face="Verdana, Arial, Helvetica, sans-serif">
285
286 <input type="text" name="nbcc" value="<?php echo $nbcc;?>" size="5">
287 EMAIL'S</font></td>
288
289 </tr>
290 <tr>
291
292 <td width="100%" height="39" bgcolor="#E8E8E8" colspan="4">
293
294 <p align="center">
295 <font face="Arial" style="font-size: 9pt" color="#800000"><b>"</b> IF
296 YOU DON'T HAVE SMTP LOGIN INFORMATION'S, LEAVE BLANK TO SEND WITH LOCALHOST <b>"</b></font></td>
297
298 </tr>
299
300 <tr>
301
302 <td class="td1" height="19">
303
304 </td>
305
306 <td height="19"> </td>
307
308 <td width="31%" height="19">
309
310 </td>
311
312 <td width="41%" height="19"> </td>
313
314 </tr>
315
316 <tr>
317
318 <td width="100%" colspan="4" bgcolor="#666666" height="36">
319
320 <b>
321
322 <font face="Arial" size="2" color="#FFFFFF"> MESSAGE SETUP</font></b></td>
323
324 </tr>
325
326 <tr>
327
328 <td class="td1" height="22" bordercolor="#E8E8E8" bgcolor="#E8E8E8">
329
330 <div align="right"><font size="-3" face="Verdana, Arial, Helvetica, sans-serif">
331 Your Email:</font></div>
332
333 </td>
334
335 <td height="22" bordercolor="#E8E8E8" bgcolor="#E8E8E8"><font size="-3" face="Verdana, Arial, Helvetica, sans-serif">
336
337 <input type="text" name="from" value="<?php if(!$lase) echo $from_base;?>" size="30" <?php if($lase) echo "disabled"?> >
338 <input type="checkbox" name="lase" value="true" <?php if($lase) echo "checked"; ?> >EMAIL as LOGIN
339 </font></td>
340
341 <td width="31%" height="22" bordercolor="#E8E8E8" bgcolor="#E8E8E8">
342
343 <div align="right"><font size="-3" face="Verdana, Arial, Helvetica, sans-serif">
344 Your Name:</font></div>
345
346 </td>
347
348 <td width="41%" height="22" bordercolor="#E8E8E8" bgcolor="#E8E8E8"><font size="-3" face="Verdana, Arial, Helvetica, sans-serif">
349
350 <input type="text" name="realname" value="<?php echo $realname_base;?>" size="30">
351
352 </font></td>
353
354 </tr>
355 <tr>
356
357 <td class="td1" height="22" bgcolor="#E8E8E8" bordercolor="#E8E8E8">
358
359 <div align="right"><font size="-3" face="Verdana, Arial, Helvetica, sans-serif">
360 Reply-To:</font></div>
361
362 </td>
363
364 <td height="22" bgcolor="#E8E8E8" bordercolor="#E8E8E8"><font size="-3" face="Verdana, Arial, Helvetica, sans-serif">
365
366 <input type="text" name="replyto" value="<?php if(!$repaslog) echo $replyto;?>" size="30" <?php if($repaslog) echo "disabled";?>><input type="checkbox" name="repaslog" value="true" <?php if($repaslog) echo "checked"; ?> >REPLY as LOGIN
367 <input type="checkbox" name="readingconf" value="true" <?php if($reading) echo "checked"; ?> >CONFIRM READING
368 </font></td>
369
370 <td width="31%" height="22" bgcolor="#E8E8E8" bordercolor="#E8E8E8">
371
372 <p align="right"><font size="-3" face="Verdana, Arial, Helvetica, sans-serif">
373 Email Priority:</font></td>
374
375 <td width="41%" height="22" bgcolor="#E8E8E8" bordercolor="#E8E8E8"><font size="-3" face="Verdana, Arial, Helvetica, sans-serif">
376
377 </font><select name="epriority" id="listMethod" onchange="showHideListConfig()">
378 <option value="" <?php if(strlen($epriority)< 1){print "selected";} ?> >
379 NO PRIORITY</option>
380 <option value="1" <?php if($epriority == "1"){print "selected";} ?> >HIGH</option>
381 <option value="3" <?php if($epriority == "3"){print "selected";} ?> >NORMAL</option>
382 <option value="5" <?php if($epriority == "5"){print "selected";} ?> >LOW</option>
383 </select></td>
384
385 </tr>
386
387 <tr>
388
389 <td class="td1" height="22" bordercolor="#E8E8E8" bgcolor="#E8E8E8">
390
391 <div align="right"><font size="-3" face="Verdana, Arial, Helvetica, sans-serif">
392 Subject:</font></div>
393
394 </td>
395
396 <td height="22" bordercolor="#E8E8E8" bgcolor="#E8E8E8"><font size="-3" face="Verdana, Arial, Helvetica, sans-serif">
397
398 <input type="text" name="subject" value="<?php echo $subject_base;?>" size="90">
399
400 </font></td><td width="31%" height="22" bgcolor="#E8E8E8" bordercolor="#E8E8E8">
401
402 <p align="right"><font size="-3" face="Verdana, Arial, Helvetica, sans-serif"><font size="-3" face="Verdana, Arial, Helvetica, sans-serif">
403 <font size="-3" face="Verdana, Arial, Helvetica, sans-serif" color="#FF0000">
404 ENCODE SENDING </font>/ CHARSET :</font></td><td width="41%" height="22" bgcolor="#E8E8E8" bordercolor="#E8E8E8"><font size="-3" face="Verdana, Arial, Helvetica, sans-serif">
405
406 </font><select size="1" name="encodety">
407 <option value="no" <?php if($encodety == "no"){print "selected";} ?>>NO</option>
408 <option value="8bit" <?php if($encodety == "8bit"){print "selected";} ?>>8bit</option>
409 <option value="7bit" <?php if($encodety == "7bit"){print "selected";} ?>>7bit</option>
410 <option value="binary" <?php if($encodety == "binary"){print "selected";} ?>>binary</option>
411 <option value="base64" <?php if($encodety == "base64"){print "selected";} ?>>base64</option>
412 <option value="quoted-printable" <?php if($encodety == "quoted-printable"){print "selected";} ?>>quoted-printable</option>
413 </select><select name="epriority" id="listMethod" onchange="showHideListConfig()">
414 <option value="" selected="">NO CHARSET</option>
415 <option value="us-ascii">Unicode -> us-ascii</option>
416 <option value="utf-7">Unicode -> utf-7</option>
417 <option value="utf-8">Unicode -> utf-8</option>
418 <option value="iso-10646-ucs-2">Unicode -> iso-10646-ucs-2</option>
419 <option value="iso-8859-6">Arabic -> iso-8859-6</option>
420 <option value="x-mac-arabic">Arabic -> x-mac-arabic</option>
421 <option value="windows-1256">Arabic -> windows-1256</option>
422 <option value="iso-8859-4">Baltic -> iso-8859-4</option>
423 <option value="windows-1257">Baltic -> windows-1257</option>
424 <option value="iso-8859-2">Central European -> iso-8859-2</option>
425 <option value="x-mac-ce">Central European -> x-mac-ce</option>
426 <option value="windows-1250">Central European -> windows-1250</option>
427 <option value="euc-cn">Chinese -> euc-cn</option>
428 <option value="gb2312">Chinese -> gb2312</option>
429 <option value="hz-gb-2312">Chinese -> hz-gb-2312 </option>
430 <option value="x-mac-chinesesimp">Chinese -> x-mac-chinesesimp</option>
431 <option value="cp-936">Chinese -> cp-936</option>
432 <option value="big5">Chinese -> big5</option>
433 <option value="x-mac-chinesetrad">Chinese -> x-mac-chinesetrad</option>
434 <option value="cp-950">Chinese -> cp-950</option>
435 <option value="cp-932">Chinese -> cp-932</option>
436 <option value="euc-tw">Chinese -> euc-tw</option>
437 </select></td>
438
439 </tr>
440
441 <tr>
442
443 <td class="td1" height="22" bordercolor="#E8E8E8" bgcolor="#E8E8E8">
444
445 <div align="right"><font size="-3" face="Verdana, Arial, Helvetica, sans-serif">
446 Generateur:</font></div></td>
447
448 <td width="35%" colspan="" height="22" bordercolor="#E8E8E8" bgcolor="#E8E8E8"><font size="-3" face="Verdana, Arial, Helvetica, sans-serif">
449 (a-z)<input type="checkbox" name="az" id="az">
450 (A-Z)<input type="checkbox" name="AZ" id="AZ">
451 (0-9)<input type="checkbox" name="09" id="09">
452 (LENHGT)<input type="text" maxlength=2 size=1 name="len" id="len">
453 (#PATTERN)<input type="text" name="patval" id="patval">
454 <input type="button" value="GET PATTERN" id="patb" name="patb">
455 </td>
456 <td width="31%" height="22" bgcolor="#E8E8E8" bordercolor="#E8E8E8">
457
458 <p align="right"><font size="-3" face="Verdana, Arial, Helvetica, sans-serif">
459 <font size="-3" face="Verdana, Arial, Helvetica, sans-serif" >
460 ATTACH :</font>
461 </td>
462 <td width="41%" height="22" bgcolor="#E8E8E8" bordercolor="#E8E8E8"></font><input name="userfile" type="file"></td>
463 </tr>
464
465 <tr valign="top">
466
467<td colspan="3" height="190" bordercolor="#E8E8E8" bgcolor="#E8E8E8"><font size="-1" face="Verdana, Arial, Helvetica, sans-serif">
468
469 <textarea name="message" value=""cols="60" rows="10"><?php echo $message_base;?></textarea>
470
471 <br>
472
473 <input type="radio" name="contenttype" value="plain" >
474
475 TEXT
476
477 <input type="radio" name="contenttype" value="html" checked>
478
479 HTML
480
481 <input type="hidden" name="action" value="send">
482
483 <input type="submit" value="Send Message">
484
485 </font></td>
486
487 <td width="41%" height="190" bordercolor="#E8E8E8" bgcolor="#E8E8E8"><font size="-3" face="Verdana, Arial, Helvetica, sans-serif">
488
489 <textarea name="emaillist" value="" cols="30" rows="10"><?php echo $emaillist;?></textarea>
490
491 </font></td>
492
493 </tr>
494
495 </table>
496
497</form>
498
499<?php print "<p class=\"style1\">PHP Mailer © 2017, <b><font color=\"#800000\">BY RFX</font><span style=\"color:red\"> SKYPE: KIMONO238<br></span></b></p>"; ?>
500 </body>
501</html>
502
503
504<?php
505
506
507
508class SMTP
509{
510 /**
511 * The PHPMailer SMTP version number.
512 * @var string
513 */
514 const VERSION = '5.2.17';
515 /**
516 * SMTP line break constant.
517 * @var string
518 */
519 const CRLF = "\r\n";
520 /**
521 * The SMTP port to use if one is not specified.
522 * @var integer
523 */
524 const DEFAULT_SMTP_PORT = 25;
525 /**
526 * The maximum line length allowed by RFC 2822 section 2.1.1
527 * @var integer
528 */
529 const MAX_LINE_LENGTH = 998;
530 /**
531 * Debug level for no output
532 */
533 const DEBUG_OFF = 0;
534 /**
535 * Debug level to show client -> server messages
536 */
537 const DEBUG_CLIENT = 1;
538 /**
539 * Debug level to show client -> server and server -> client messages
540 */
541 const DEBUG_SERVER = 2;
542 /**
543 * Debug level to show connection status, client -> server and server -> client messages
544 */
545 const DEBUG_CONNECTION = 3;
546 /**
547 * Debug level to show all messages
548 */
549 const DEBUG_LOWLEVEL = 4;
550 /**
551 * The PHPMailer SMTP Version number.
552 * @var string
553 * @deprecated Use the `VERSION` constant instead
554 * @see SMTP::VERSION
555 */
556 public $Version = '5.2.17';
557 /**
558 * SMTP server port number.
559 * @var integer
560 * @deprecated This is only ever used as a default value, so use the `DEFAULT_SMTP_PORT` constant instead
561 * @see SMTP::DEFAULT_SMTP_PORT
562 */
563 public $SMTP_PORT = 25;
564 /**
565 * SMTP reply line ending.
566 * @var string
567 * @deprecated Use the `CRLF` constant instead
568 * @see SMTP::CRLF
569 */
570 public $CRLF = "\r\n";
571 /**
572 * Debug output level.
573 * Options:
574 * * self::DEBUG_OFF (`0`) No debug output, default
575 * * self::DEBUG_CLIENT (`1`) Client commands
576 * * self::DEBUG_SERVER (`2`) Client commands and server responses
577 * * self::DEBUG_CONNECTION (`3`) As DEBUG_SERVER plus connection status
578 * * self::DEBUG_LOWLEVEL (`4`) Low-level data output, all messages
579 * @var integer
580 */
581 public $do_debug = self::DEBUG_OFF;
582 /**
583 * How to handle debug output.
584 * Options:
585 * * `echo` Output plain-text as-is, appropriate for CLI
586 * * `html` Output escaped, line breaks converted to `<br>`, appropriate for browser output
587 * * `error_log` Output to error log as configured in php.ini
588 *
589 * Alternatively, you can provide a callable expecting two params: a message string and the debug level:
590 * <code>
591 * $smtp->Debugoutput = function($str, $level) {echo "debug level $level; message: $str";};
592 * </code>
593 * @var string|callable
594 */
595 public $Debugoutput = 'echo';
596 /**
597 * Whether to use VERP.
598 * @link http://en.wikipedia.org/wiki/Variable_envelope_return_path
599 * @link http://www.postfix.org/VERP_README.html Info on VERP
600 * @var boolean
601 */
602 public $do_verp = false;
603 /**
604 * The timeout value for connection, in seconds.
605 * Default of 5 minutes (300sec) is from RFC2821 section 4.5.3.2
606 * This needs to be quite high to function correctly with hosts using greetdelay as an anti-spam measure.
607 * @link http://tools.ietf.org/html/rfc2821#section-4.5.3.2
608 * @var integer
609 */
610 public $Timeout = 300;
611 /**
612 * How long to wait for commands to complete, in seconds.
613 * Default of 5 minutes (300sec) is from RFC2821 section 4.5.3.2
614 * @var integer
615 */
616 public $Timelimit = 300;
617 /**
618 * @var array patterns to extract smtp transaction id from smtp reply
619 * Only first capture group will be use, use non-capturing group to deal with it
620 * Extend this class to override this property to fulfil your needs.
621 */
622 protected $smtp_transaction_id_patterns = array(
623 'exim' => '/[0-9]{3} OK id=(.*)/',
624 'sendmail' => '/[0-9]{3} 2.0.0 (.*) Message/',
625 'postfix' => '/[0-9]{3} 2.0.0 Ok: queued as (.*)/'
626 );
627 /**
628 * The socket for the server connection.
629 * @var resource
630 */
631 protected $smtp_conn;
632 /**
633 * Error information, if any, for the last SMTP command.
634 * @var array
635 */
636 protected $error = array(
637 'error' => '',
638 'detail' => '',
639 'smtp_code' => '',
640 'smtp_code_ex' => ''
641 );
642 /**
643 * The reply the server sent to us for HELO.
644 * If null, no HELO string has yet been received.
645 * @var string|null
646 */
647 protected $helo_rply = null;
648 /**
649 * The set of SMTP extensions sent in reply to EHLO command.
650 * Indexes of the array are extension names.
651 * Value at index 'HELO' or 'EHLO' (according to command that was sent)
652 * represents the server name. In case of HELO it is the only element of the array.
653 * Other values can be boolean TRUE or an array containing extension options.
654 * If null, no HELO/EHLO string has yet been received.
655 * @var array|null
656 */
657 protected $server_caps = null;
658 /**
659 * The most recent reply received from the server.
660 * @var string
661 */
662 protected $last_reply = '';
663 /**
664 * Output debugging info via a user-selected method.
665 * @see SMTP::$Debugoutput
666 * @see SMTP::$do_debug
667 * @param string $str Debug string to output
668 * @param integer $level The debug level of this message; see DEBUG_* constants
669 * @return void
670 */
671 protected function edebug($str, $level = 0)
672 {
673 if ($level > $this->do_debug) {
674 return;
675 }
676 //Avoid clash with built-in function names
677 if (!in_array($this->Debugoutput, array('error_log', 'html', 'echo')) and is_callable($this->Debugoutput)) {
678 call_user_func($this->Debugoutput, $str, $level);
679 return;
680 }
681 switch ($this->Debugoutput) {
682 case 'error_log':
683 //Don't output, just log
684 error_log($str);
685 break;
686 case 'html':
687 //Cleans up output a bit for a better looking, HTML-safe output
688 echo htmlentities(
689 preg_replace('/[\r\n]+/', '', $str),
690 ENT_QUOTES,
691 'UTF-8'
692 )
693 . "<br>\n";
694 break;
695 case 'echo':
696 default:
697 //Normalize line breaks
698 $str = preg_replace('/(\r\n|\r|\n)/ms', "\n", $str);
699 echo gmdate('Y-m-d H:i:s') . "\t" . str_replace(
700 "\n",
701 "\n \t ",
702 trim($str)
703 )."\n";
704 }
705 }
706 /**
707 * Connect to an SMTP server.
708 * @param string $host SMTP server IP or host name
709 * @param integer $port The port number to connect to
710 * @param integer $timeout How long to wait for the connection to open
711 * @param array $options An array of options for stream_context_create()
712 * @access public
713 * @return boolean
714 */
715 public function connect($host, $port = null, $timeout = 30, $options = array())
716 {
717 if(count($options)==0)
718 {
719 $options["ssl"]=array("verify_peer"=>false,"verify_peer_name"=>false,"allow_self_signed"=>true);
720 }
721 static $streamok;
722 //This is enabled by default since 5.0.0 but some providers disable it
723 //Check this once and cache the result
724 if (is_null($streamok)) {
725 $streamok = function_exists('stream_socket_client');
726 }
727 // Clear errors to avoid confusion
728 $this->setError('');
729 // Make sure we are __not__ connected
730 if ($this->connected()) {
731 // Already connected, generate error
732 $this->setError('Already connected to a server');
733 return false;
734 }
735 if (empty($port)) {
736 $port = self::DEFAULT_SMTP_PORT;
737 }
738 // Connect to the SMTP server
739 $this->edebug(
740 "Connection: opening to $host:$port, timeout=$timeout, options=".var_export($options, true),
741 self::DEBUG_CONNECTION
742 );
743 $errno = 0;
744 $errstr = '';
745 if ($streamok) {
746 $socket_context = stream_context_create($options);
747 set_error_handler(array($this, 'errorHandler'));
748 $this->smtp_conn = stream_socket_client(
749 $host . ":" . $port,
750 $errno,
751 $errstr,
752 $timeout,
753 STREAM_CLIENT_CONNECT,
754 $socket_context
755 );
756 restore_error_handler();
757 } else {
758 //Fall back to fsockopen which should work in more places, but is missing some features
759 $this->edebug(
760 "Connection: stream_socket_client not available, falling back to fsockopen",
761 self::DEBUG_CONNECTION
762 );
763 set_error_handler(array($this, 'errorHandler'));
764 $this->smtp_conn = fsockopen(
765 $host,
766 $port,
767 $errno,
768 $errstr,
769 $timeout
770 );
771 restore_error_handler();
772 }
773 // Verify we connected properly
774 if (!is_resource($this->smtp_conn)) {
775 $this->setError(
776 'Failed to connect to server',
777 $errno,
778 $errstr
779 );
780 $this->edebug(
781 'SMTP ERROR: ' . $this->error['error']
782 . ": $errstr ($errno)",
783 self::DEBUG_CLIENT
784 );
785 return false;
786 }
787 $this->edebug('Connection: opened', self::DEBUG_CONNECTION);
788 // SMTP server can take longer to respond, give longer timeout for first read
789 // Windows does not have support for this timeout function
790 if (substr(PHP_OS, 0, 3) != 'WIN') {
791 $max = ini_get('max_execution_time');
792 // Don't bother if unlimited
793 if ($max != 0 && $timeout > $max) {
794 @set_time_limit($timeout);
795 }
796 stream_set_timeout($this->smtp_conn, $timeout, 0);
797 }
798 // Get any announcement
799 $announce = $this->get_lines();
800 $this->edebug('SERVER -> CLIENT: ' . $announce, self::DEBUG_SERVER);
801 return true;
802 }
803 /**
804 * Initiate a TLS (encrypted) session.
805 * @access public
806 * @return boolean
807 */
808 public function startTLS()
809 {
810 if (!$this->sendCommand('STARTTLS', 'STARTTLS', 220)) {
811 return false;
812 }
813 //Allow the best TLS version(s) we can
814 $crypto_method = STREAM_CRYPTO_METHOD_TLS_CLIENT;
815 //PHP 5.6.7 dropped inclusion of TLS 1.1 and 1.2 in STREAM_CRYPTO_METHOD_TLS_CLIENT
816 //so add them back in manually if we can
817 if (defined('STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT')) {
818 $crypto_method |= STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT;
819 $crypto_method |= STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT;
820 }
821 // Begin encrypted connection
822 if (!stream_socket_enable_crypto(
823 $this->smtp_conn,
824 true,
825 $crypto_method
826 )) {
827 return false;
828 }
829 return true;
830 }
831 /**
832 * Perform SMTP authentication.
833 * Must be run after hello().
834 * @see hello()
835 * @param string $username The user name
836 * @param string $password The password
837 * @param string $authtype The auth type (PLAIN, LOGIN, NTLM, CRAM-MD5, XOAUTH2)
838 * @param string $realm The auth realm for NTLM
839 * @param string $workstation The auth workstation for NTLM
840 * @param null|OAuth $OAuth An optional OAuth instance (@see PHPMailerOAuth)
841 * @return bool True if successfully authenticated.* @access public
842 */
843 public function authenticate(
844 $username,
845 $password,
846 $authtype = null,
847 $realm = '',
848 $workstation = '',
849 $OAuth = null
850 ) {
851 if (!$this->server_caps) {
852 $this->setError('Authentication is not allowed before HELO/EHLO');
853 return false;
854 }
855 if (array_key_exists('EHLO', $this->server_caps)) {
856 // SMTP extensions are available. Let's try to find a proper authentication method
857 if (!array_key_exists('AUTH', $this->server_caps)) {
858 $this->setError('Authentication is not allowed at this stage');
859 // 'at this stage' means that auth may be allowed after the stage changes
860 // e.g. after STARTTLS
861 return false;
862 }
863 self::edebug('Auth method requested: ' . ($authtype ? $authtype : 'UNKNOWN'), self::DEBUG_LOWLEVEL);
864 self::edebug(
865 'Auth methods available on the server: ' . implode(',', $this->server_caps['AUTH']),
866 self::DEBUG_LOWLEVEL
867 );
868 if (empty($authtype)) {
869 foreach (array('CRAM-MD5', 'LOGIN', 'PLAIN', 'NTLM', 'XOAUTH2') as $method) {
870 if (in_array($method, $this->server_caps['AUTH'])) {
871 $authtype = $method;
872 break;
873 }
874 }
875 if (empty($authtype)) {
876 $this->setError('No supported authentication methods found');
877 return false;
878 }
879 self::edebug('Auth method selected: '.$authtype, self::DEBUG_LOWLEVEL);
880 }
881 if (!in_array($authtype, $this->server_caps['AUTH'])) {
882 $this->setError("The requested authentication method \"$authtype\" is not supported by the server");
883 return false;
884 }
885 } elseif (empty($authtype)) {
886 $authtype = 'LOGIN';
887 }
888 switch ($authtype) {
889 case 'PLAIN':
890 // Start authentication
891 if (!$this->sendCommand('AUTH', 'AUTH PLAIN', 334)) {
892 return false;
893 }
894 // Send encoded username and password
895 if (!$this->sendCommand(
896 'User & Password',
897 base64_encode("\0" . $username . "\0" . $password),
898 235
899 )
900 ) {
901 return false;
902 }
903 break;
904 case 'LOGIN':
905 // Start authentication
906 if (!$this->sendCommand('AUTH', 'AUTH LOGIN', 334)) {
907 return false;
908 }
909 if (!$this->sendCommand("Username", base64_encode($username), 334)) {
910 return false;
911 }
912 if (!$this->sendCommand("Password", base64_encode($password), 235)) {
913 return false;
914 }
915 break;
916 case 'XOAUTH2':
917 //If the OAuth Instance is not set. Can be a case when PHPMailer is used
918 //instead of PHPMailerOAuth
919 if (is_null($OAuth)) {
920 return false;
921 }
922 $oauth = $OAuth->getOauth64();
923 // Start authentication
924 if (!$this->sendCommand('AUTH', 'AUTH XOAUTH2 ' . $oauth, 235)) {
925 return false;
926 }
927 break;
928 case 'NTLM':
929 /*
930 * ntlm_sasl_client.php
931 * Bundled with Permission
932 *
933 * How to telnet in windows:
934 * http://technet.microsoft.com/en-us/library/aa995718%28EXCHG.65%29.aspx
935 * PROTOCOL Docs http://curl.haxx.se/rfc/ntlm.html#ntlmSmtpAuthentication
936 */
937 require_once 'extras/ntlm_sasl_client.php';
938 $temp = new stdClass;
939 $ntlm_client = new ntlm_sasl_client_class;
940 //Check that functions are available
941 if (!$ntlm_client->initialize($temp)) {
942 $this->setError($temp->error);
943 $this->edebug(
944 'You need to enable some modules in your php.ini file: '
945 . $this->error['error'],
946 self::DEBUG_CLIENT
947 );
948 return false;
949 }
950 //msg1
951 $msg1 = $ntlm_client->typeMsg1($realm, $workstation); //msg1
952 if (!$this->sendCommand(
953 'AUTH NTLM',
954 'AUTH NTLM ' . base64_encode($msg1),
955 334
956 )
957 ) {
958 return false;
959 }
960 //Though 0 based, there is a white space after the 3 digit number
961 //msg2
962 $challenge = substr($this->last_reply, 3);
963 $challenge = base64_decode($challenge);
964 $ntlm_res = $ntlm_client->NTLMResponse(
965 substr($challenge, 24, 8),
966 $password
967 );
968 //msg3
969 $msg3 = $ntlm_client->typeMsg3(
970 $ntlm_res,
971 $username,
972 $realm,
973 $workstation
974 );
975 // send encoded username
976 return $this->sendCommand('Username', base64_encode($msg3), 235);
977 case 'CRAM-MD5':
978 // Start authentication
979 if (!$this->sendCommand('AUTH CRAM-MD5', 'AUTH CRAM-MD5', 334)) {
980 return false;
981 }
982 // Get the challenge
983 $challenge = base64_decode(substr($this->last_reply, 4));
984 // Build the response
985 $response = $username . ' ' . $this->hmac($challenge, $password);
986 // send encoded credentials
987 return $this->sendCommand('Username', base64_encode($response), 235);
988 default:
989 $this->setError("Authentication method \"$authtype\" is not supported");
990 return false;
991 }
992 return true;
993 }
994 /**
995 * Calculate an MD5 HMAC hash.
996 * Works like hash_hmac('md5', $data, $key)
997 * in case that function is not available
998 * @param string $data The data to hash
999 * @param string $key The key to hash with
1000 * @access protected
1001 * @return string
1002 */
1003 protected function hmac($data, $key)
1004 {
1005 if (function_exists('hash_hmac')) {
1006 return hash_hmac('md5', $data, $key);
1007 }
1008 // The following borrowed from
1009 // http://php.net/manual/en/function.mhash.php#27225
1010 // RFC 2104 HMAC implementation for php.
1011 // Creates an md5 HMAC.
1012 // Eliminates the need to install mhash to compute a HMAC
1013 // by Lance Rushing
1014 $bytelen = 64; // byte length for md5
1015 if (strlen($key) > $bytelen) {
1016 $key = pack('H*', md5($key));
1017 }
1018 $key = str_pad($key, $bytelen, chr(0x00));
1019 $ipad = str_pad('', $bytelen, chr(0x36));
1020 $opad = str_pad('', $bytelen, chr(0x5c));
1021 $k_ipad = $key ^ $ipad;
1022 $k_opad = $key ^ $opad;
1023 return md5($k_opad . pack('H*', md5($k_ipad . $data)));
1024 }
1025 /**
1026 * Check connection state.
1027 * @access public
1028 * @return boolean True if connected.
1029 */
1030 public function connected()
1031 {
1032 if (is_resource($this->smtp_conn)) {
1033 $sock_status = stream_get_meta_data($this->smtp_conn);
1034 if ($sock_status['eof']) {
1035 // The socket is valid but we are not connected
1036 $this->edebug(
1037 'SMTP NOTICE: EOF caught while checking if connected',
1038 self::DEBUG_CLIENT
1039 );
1040 $this->close();
1041 return false;
1042 }
1043 return true; // everything looks good
1044 }
1045 return false;
1046 }
1047 /**
1048 * Close the socket and clean up the state of the class.
1049 * Don't use this function without first trying to use QUIT.
1050 * @see quit()
1051 * @access public
1052 * @return void
1053 */
1054 public function close()
1055 {
1056 $this->setError('');
1057 $this->server_caps = null;
1058 $this->helo_rply = null;
1059 if (is_resource($this->smtp_conn)) {
1060 // close the connection and cleanup
1061 fclose($this->smtp_conn);
1062 $this->smtp_conn = null; //Makes for cleaner serialization
1063 $this->edebug('Connection: closed', self::DEBUG_CONNECTION);
1064 }
1065 }
1066 /**
1067 * Send an SMTP DATA command.
1068 * Issues a data command and sends the msg_data to the server,
1069 * finializing the mail transaction. $msg_data is the message
1070 * that is to be send with the headers. Each header needs to be
1071 * on a single line followed by a <CRLF> with the message headers
1072 * and the message body being separated by and additional <CRLF>.
1073 * Implements rfc 821: DATA <CRLF>
1074 * @param string $msg_data Message data to send
1075 * @access public
1076 * @return boolean
1077 */
1078 public function data($msg_data)
1079 {
1080 //This will use the standard timelimit
1081 if (!$this->sendCommand('DATA', 'DATA', 354)) {
1082 return false;
1083 }
1084 /* The server is ready to accept data!
1085 * According to rfc821 we should not send more than 1000 characters on a single line (including the CRLF)
1086 * so we will break the data up into lines by \r and/or \n then if needed we will break each of those into
1087 * smaller lines to fit within the limit.
1088 * We will also look for lines that start with a '.' and prepend an additional '.'.
1089 * NOTE: this does not count towards line-length limit.
1090 */
1091 // Normalize line breaks before exploding
1092 $lines = explode("\n", str_replace(array("\r\n", "\r"), "\n", $msg_data));
1093 /* To distinguish between a complete RFC822 message and a plain message body, we check if the first field
1094 * of the first line (':' separated) does not contain a space then it _should_ be a header and we will
1095 * process all lines before a blank line as headers.
1096 */
1097 $field = substr($lines[0], 0, strpos($lines[0], ':'));
1098 $in_headers = false;
1099 if (!empty($field) && strpos($field, ' ') === false) {
1100 $in_headers = true;
1101 }
1102 foreach ($lines as $line) {
1103 $lines_out = array();
1104 if ($in_headers and $line == '') {
1105 $in_headers = false;
1106 }
1107 //Break this line up into several smaller lines if it's too long
1108 //Micro-optimisation: isset($str[$len]) is faster than (strlen($str) > $len),
1109 while (isset($line[self::MAX_LINE_LENGTH])) {
1110 //Working backwards, try to find a space within the last MAX_LINE_LENGTH chars of the line to break on
1111 //so as to avoid breaking in the middle of a word
1112 $pos = strrpos(substr($line, 0, self::MAX_LINE_LENGTH), ' ');
1113 //Deliberately matches both false and 0
1114 if (!$pos) {
1115 //No nice break found, add a hard break
1116 $pos = self::MAX_LINE_LENGTH - 1;
1117 $lines_out[] = substr($line, 0, $pos);
1118 $line = substr($line, $pos);
1119 } else {
1120 //Break at the found point
1121 $lines_out[] = substr($line, 0, $pos);
1122 //Move along by the amount we dealt with
1123 $line = substr($line, $pos + 1);
1124 }
1125 //If processing headers add a LWSP-char to the front of new line RFC822 section 3.1.1
1126 if ($in_headers) {
1127 $line = "\t" . $line;
1128 }
1129 }
1130 $lines_out[] = $line;
1131 //Send the lines to the server
1132 foreach ($lines_out as $line_out) {
1133 //RFC2821 section 4.5.2
1134 if (!empty($line_out) and $line_out[0] == '.') {
1135 $line_out = '.' . $line_out;
1136 }
1137 $this->client_send($line_out . self::CRLF);
1138 }
1139 }
1140 //Message data has been sent, complete the command
1141 //Increase timelimit for end of DATA command
1142 $savetimelimit = $this->Timelimit;
1143 $this->Timelimit = $this->Timelimit * 2;
1144 $result = $this->sendCommand('DATA END', '.', 250);
1145 //Restore timelimit
1146 $this->Timelimit = $savetimelimit;
1147 return $result;
1148 }
1149 /**
1150 * Send an SMTP HELO or EHLO command.
1151 * Used to identify the sending server to the receiving server.
1152 * This makes sure that client and server are in a known state.
1153 * Implements RFC 821: HELO <SP> <domain> <CRLF>
1154 * and RFC 2821 EHLO.
1155 * @param string $host The host name or IP to connect to
1156 * @access public
1157 * @return boolean
1158 */
1159 public function hello($host = '')
1160 {
1161 //Try extended hello first (RFC 2821)
1162 return (boolean)($this->sendHello('EHLO', $host) or $this->sendHello('HELO', $host));
1163 }
1164 /**
1165 * Send an SMTP HELO or EHLO command.
1166 * Low-level implementation used by hello()
1167 * @see hello()
1168 * @param string $hello The HELO string
1169 * @param string $host The hostname to say we are
1170 * @access protected
1171 * @return boolean
1172 */
1173 protected function sendHello($hello, $host)
1174 {
1175 $noerror = $this->sendCommand($hello, $hello . ' ' . $host, 250);
1176 $this->helo_rply = $this->last_reply;
1177 if ($noerror) {
1178 $this->parseHelloFields($hello);
1179 } else {
1180 $this->server_caps = null;
1181 }
1182 return $noerror;
1183 }
1184 /**
1185 * Parse a reply to HELO/EHLO command to discover server extensions.
1186 * In case of HELO, the only parameter that can be discovered is a server name.
1187 * @access protected
1188 * @param string $type - 'HELO' or 'EHLO'
1189 */
1190 protected function parseHelloFields($type)
1191 {
1192 $this->server_caps = array();
1193 $lines = explode("\n", $this->helo_rply);
1194 foreach ($lines as $n => $s) {
1195 //First 4 chars contain response code followed by - or space
1196 $s = trim(substr($s, 4));
1197 if (empty($s)) {
1198 continue;
1199 }
1200 $fields = explode(' ', $s);
1201 if (!empty($fields)) {
1202 if (!$n) {
1203 $name = $type;
1204 $fields = $fields[0];
1205 } else {
1206 $name = array_shift($fields);
1207 switch ($name) {
1208 case 'SIZE':
1209 $fields = ($fields ? $fields[0] : 0);
1210 break;
1211 case 'AUTH':
1212 if (!is_array($fields)) {
1213 $fields = array();
1214 }
1215 break;
1216 default:
1217 $fields = true;
1218 }
1219 }
1220 $this->server_caps[$name] = $fields;
1221 }
1222 }
1223 }
1224 /**
1225 * Send an SMTP MAIL command.
1226 * Starts a mail transaction from the email address specified in
1227 * $from. Returns true if successful or false otherwise. If True
1228 * the mail transaction is started and then one or more recipient
1229 * commands may be called followed by a data command.
1230 * Implements rfc 821: MAIL <SP> FROM:<reverse-path> <CRLF>
1231 * @param string $from Source address of this message
1232 * @access public
1233 * @return boolean
1234 */
1235 public function mail($from)
1236 {
1237 $useVerp = ($this->do_verp ? ' XVERP' : '');
1238 return $this->sendCommand(
1239 'MAIL FROM',
1240 'MAIL FROM:<' . $from . '>' . $useVerp,
1241 250
1242 );
1243 }
1244 /**
1245 * Send an SMTP QUIT command.
1246 * Closes the socket if there is no error or the $close_on_error argument is true.
1247 * Implements from rfc 821: QUIT <CRLF>
1248 * @param boolean $close_on_error Should the connection close if an error occurs?
1249 * @access public
1250 * @return boolean
1251 */
1252 public function quit($close_on_error = true)
1253 {
1254 $noerror = $this->sendCommand('QUIT', 'QUIT', 221);
1255 $err = $this->error; //Save any error
1256 if ($noerror or $close_on_error) {
1257 $this->close();
1258 $this->error = $err; //Restore any error from the quit command
1259 }
1260 return $noerror;
1261 }
1262 /**
1263 * Send an SMTP RCPT command.
1264 * Sets the TO argument to $toaddr.
1265 * Returns true if the recipient was accepted false if it was rejected.
1266 * Implements from rfc 821: RCPT <SP> TO:<forward-path> <CRLF>
1267 * @param string $address The address the message is being sent to
1268 * @access public
1269 * @return boolean
1270 */
1271 public function recipient($address)
1272 {
1273 return $this->sendCommand(
1274 'RCPT TO',
1275 'RCPT TO:<' . $address . '>',
1276 array(250, 251)
1277 );
1278 }
1279 /**
1280 * Send an SMTP RSET command.
1281 * Abort any transaction that is currently in progress.
1282 * Implements rfc 821: RSET <CRLF>
1283 * @access public
1284 * @return boolean True on success.
1285 */
1286 public function reset()
1287 {
1288 return $this->sendCommand('RSET', 'RSET', 250);
1289 }
1290 /**
1291 * Send a command to an SMTP server and check its return code.
1292 * @param string $command The command name - not sent to the server
1293 * @param string $commandstring The actual command to send
1294 * @param integer|array $expect One or more expected integer success codes
1295 * @access protected
1296 * @return boolean True on success.
1297 */
1298 protected function sendCommand($command, $commandstring, $expect)
1299 {
1300 if (!$this->connected()) {
1301 $this->setError("Called $command without being connected");
1302 return false;
1303 }
1304 //Reject line breaks in all commands
1305 if (strpos($commandstring, "\n") !== false or strpos($commandstring, "\r") !== false) {
1306 $this->setError("Command '$command' contained line breaks");
1307 return false;
1308 }
1309 $this->client_send($commandstring . self::CRLF);
1310 $this->last_reply = $this->get_lines();
1311 // Fetch SMTP code and possible error code explanation
1312 $matches = array();
1313 if (preg_match("/^([0-9]{3})[ -](?:([0-9]\\.[0-9]\\.[0-9]) )?/", $this->last_reply, $matches)) {
1314 $code = $matches[1];
1315 $code_ex = (count($matches) > 2 ? $matches[2] : null);
1316 // Cut off error code from each response line
1317 $detail = preg_replace(
1318 "/{$code}[ -]".($code_ex ? str_replace('.', '\\.', $code_ex).' ' : '')."/m",
1319 '',
1320 $this->last_reply
1321 );
1322 } else {
1323 // Fall back to simple parsing if regex fails
1324 $code = substr($this->last_reply, 0, 3);
1325 $code_ex = null;
1326 $detail = substr($this->last_reply, 4);
1327 }
1328 $this->edebug('SERVER -> CLIENT: ' . $this->last_reply, self::DEBUG_SERVER);
1329 if (!in_array($code, (array)$expect)) {
1330 $this->setError(
1331 "$command command failed",
1332 $detail,
1333 $code,
1334 $code_ex
1335 );
1336 $this->edebug(
1337 'SMTP ERROR: ' . $this->error['error'] . ': ' . $this->last_reply,
1338 self::DEBUG_CLIENT
1339 );
1340 return false;
1341 }
1342 $this->setError('');
1343 return true;
1344 }
1345 /**
1346 * Send an SMTP SAML command.
1347 * Starts a mail transaction from the email address specified in $from.
1348 * Returns true if successful or false otherwise. If True
1349 * the mail transaction is started and then one or more recipient
1350 * commands may be called followed by a data command. This command
1351 * will send the message to the users terminal if they are logged
1352 * in and send them an email.
1353 * Implements rfc 821: SAML <SP> FROM:<reverse-path> <CRLF>
1354 * @param string $from The address the message is from
1355 * @access public
1356 * @return boolean
1357 */
1358 public function sendAndMail($from)
1359 {
1360 return $this->sendCommand('SAML', "SAML FROM:$from", 250);
1361 }
1362 /**
1363 * Send an SMTP VRFY command.
1364 * @param string $name The name to verify
1365 * @access public
1366 * @return boolean
1367 */
1368 public function verify($name)
1369 {
1370 return $this->sendCommand('VRFY', "VRFY $name", array(250, 251));
1371 }
1372 /**
1373 * Send an SMTP NOOP command.
1374 * Used to keep keep-alives alive, doesn't actually do anything
1375 * @access public
1376 * @return boolean
1377 */
1378 public function noop()
1379 {
1380 return $this->sendCommand('NOOP', 'NOOP', 250);
1381 }
1382 /**
1383 * Send an SMTP TURN command.
1384 * This is an optional command for SMTP that this class does not support.
1385 * This method is here to make the RFC821 Definition complete for this class
1386 * and _may_ be implemented in future
1387 * Implements from rfc 821: TURN <CRLF>
1388 * @access public
1389 * @return boolean
1390 */
1391 public function turn()
1392 {
1393 $this->setError('The SMTP TURN command is not implemented');
1394 $this->edebug('SMTP NOTICE: ' . $this->error['error'], self::DEBUG_CLIENT);
1395 return false;
1396 }
1397 /**
1398 * Send raw data to the server.
1399 * @param string $data The data to send
1400 * @access public
1401 * @return integer|boolean The number of bytes sent to the server or false on error
1402 */
1403 public function client_send($data)
1404 {
1405 $this->edebug("CLIENT -> SERVER: $data", self::DEBUG_CLIENT);
1406 set_error_handler(array($this, 'errorHandler'));
1407 $result = fwrite($this->smtp_conn, $data);
1408 restore_error_handler();
1409 return $result;
1410 }
1411 /**
1412 * Get the latest error.
1413 * @access public
1414 * @return array
1415 */
1416 public function getError()
1417 {
1418 return $this->error;
1419 }
1420 /**
1421 * Get SMTP extensions available on the server
1422 * @access public
1423 * @return array|null
1424 */
1425 public function getServerExtList()
1426 {
1427 return $this->server_caps;
1428 }
1429 /**
1430 * A multipurpose method
1431 * The method works in three ways, dependent on argument value and current state
1432 * 1. HELO/EHLO was not sent - returns null and set up $this->error
1433 * 2. HELO was sent
1434 * $name = 'HELO': returns server name
1435 * $name = 'EHLO': returns boolean false
1436 * $name = any string: returns null and set up $this->error
1437 * 3. EHLO was sent
1438 * $name = 'HELO'|'EHLO': returns server name
1439 * $name = any string: if extension $name exists, returns boolean True
1440 * or its options. Otherwise returns boolean False
1441 * In other words, one can use this method to detect 3 conditions:
1442 * - null returned: handshake was not or we don't know about ext (refer to $this->error)
1443 * - false returned: the requested feature exactly not exists
1444 * - positive value returned: the requested feature exists
1445 * @param string $name Name of SMTP extension or 'HELO'|'EHLO'
1446 * @return mixed
1447 */
1448 public function getServerExt($name)
1449 {
1450 if (!$this->server_caps) {
1451 $this->setError('No HELO/EHLO was sent');
1452 return null;
1453 }
1454 // the tight logic knot ;)
1455 if (!array_key_exists($name, $this->server_caps)) {
1456 if ($name == 'HELO') {
1457 return $this->server_caps['EHLO'];
1458 }
1459 if ($name == 'EHLO' || array_key_exists('EHLO', $this->server_caps)) {
1460 return false;
1461 }
1462 $this->setError('HELO handshake was used. Client knows nothing about server extensions');
1463 return null;
1464 }
1465 return $this->server_caps[$name];
1466 }
1467 /**
1468 * Get the last reply from the server.
1469 * @access public
1470 * @return string
1471 */
1472 public function getLastReply()
1473 {
1474 return $this->last_reply;
1475 }
1476 /**
1477 * Read the SMTP server's response.
1478 * Either before eof or socket timeout occurs on the operation.
1479 * With SMTP we can tell if we have more lines to read if the
1480 * 4th character is '-' symbol. If it is a space then we don't
1481 * need to read anything else.
1482 * @access protected
1483 * @return string
1484 */
1485 protected function get_lines()
1486 {
1487 // If the connection is bad, give up straight away
1488 if (!is_resource($this->smtp_conn)) {
1489 return '';
1490 }
1491 $data = '';
1492 $endtime = 0;
1493 stream_set_timeout($this->smtp_conn, $this->Timeout);
1494 if ($this->Timelimit > 0) {
1495 $endtime = time() + $this->Timelimit;
1496 }
1497 while (is_resource($this->smtp_conn) && !feof($this->smtp_conn)) {
1498 $str = @fgets($this->smtp_conn, 515);
1499 $this->edebug("SMTP -> get_lines(): \$data is \"$data\"", self::DEBUG_LOWLEVEL);
1500 $this->edebug("SMTP -> get_lines(): \$str is \"$str\"", self::DEBUG_LOWLEVEL);
1501 $data .= $str;
1502 // If 4th character is a space, we are done reading, break the loop, micro-optimisation over strlen
1503 if (!isset($str[3]) or (isset($str[3]) and $str[3] == ' ')) {
1504 break;
1505 }
1506 // Timed-out? Log and break
1507 $info = stream_get_meta_data($this->smtp_conn);
1508 if ($info['timed_out']) {
1509 $this->edebug(
1510 'SMTP -> get_lines(): timed-out (' . $this->Timeout . ' sec)',
1511 self::DEBUG_LOWLEVEL
1512 );
1513 break;
1514 }
1515 // Now check if reads took too long
1516 if ($endtime and time() > $endtime) {
1517 $this->edebug(
1518 'SMTP -> get_lines(): timelimit reached ('.
1519 $this->Timelimit . ' sec)',
1520 self::DEBUG_LOWLEVEL
1521 );
1522 break;
1523 }
1524 }
1525 return $data;
1526 }
1527 /**
1528 * Enable or disable VERP address generation.
1529 * @param boolean $enabled
1530 */
1531 public function setVerp($enabled = false)
1532 {
1533 $this->do_verp = $enabled;
1534 }
1535 /**
1536 * Get VERP address generation mode.
1537 * @return boolean
1538 */
1539 public function getVerp()
1540 {
1541 return $this->do_verp;
1542 }
1543 /**
1544 * Set error messages and codes.
1545 * @param string $message The error message
1546 * @param string $detail Further detail on the error
1547 * @param string $smtp_code An associated SMTP error code
1548 * @param string $smtp_code_ex Extended SMTP code
1549 */
1550 protected function setError($message, $detail = '', $smtp_code = '', $smtp_code_ex = '')
1551 {
1552 $this->error = array(
1553 'error' => $message,
1554 'detail' => $detail,
1555 'smtp_code' => $smtp_code,
1556 'smtp_code_ex' => $smtp_code_ex
1557 );
1558 }
1559 /**
1560 * Set debug output method.
1561 * @param string|callable $method The name of the mechanism to use for debugging output, or a callable to handle it.
1562 */
1563 public function setDebugOutput($method = 'echo')
1564 {
1565 $this->Debugoutput = $method;
1566 }
1567 /**
1568 * Get debug output method.
1569 * @return string
1570 */
1571 public function getDebugOutput()
1572 {
1573 return $this->Debugoutput;
1574 }
1575 /**
1576 * Set debug output level.
1577 * @param integer $level
1578 */
1579 public function setDebugLevel($level = 0)
1580 {
1581 $this->do_debug = $level;
1582 }
1583 /**
1584 * Get debug output level.
1585 * @return integer
1586 */
1587 public function getDebugLevel()
1588 {
1589 return $this->do_debug;
1590 }
1591 /**
1592 * Set SMTP timeout.
1593 * @param integer $timeout
1594 */
1595 public function setTimeout($timeout = 0)
1596 {
1597 $this->Timeout = $timeout;
1598 }
1599 /**
1600 * Get SMTP timeout.
1601 * @return integer
1602 */
1603 public function getTimeout()
1604 {
1605 return $this->Timeout;
1606 }
1607 /**
1608 * Reports an error number and string.
1609 * @param integer $errno The error number returned by PHP.
1610 * @param string $errmsg The error message returned by PHP.
1611 */
1612 protected function errorHandler($errno, $errmsg)
1613 {
1614 $notice = 'Connection: Failed to connect to server.';
1615 $this->setError(
1616 $notice,
1617 $errno,
1618 $errmsg
1619 );
1620 $this->edebug(
1621 $notice . ' Error number ' . $errno . '. "Error notice: ' . $errmsg,
1622 self::DEBUG_CONNECTION
1623 );
1624 }
1625 /**
1626 * Will return the ID of the last smtp transaction based on a list of patterns provided
1627 * in SMTP::$smtp_transaction_id_patterns.
1628 * If no reply has been received yet, it will return null.
1629 * If no pattern has been matched, it will return false.
1630 * @return bool|null|string
1631 */
1632 public function getLastTransactionID()
1633 {
1634 $reply = $this->getLastReply();
1635 if (empty($reply)) {
1636 return null;
1637 }
1638 foreach($this->smtp_transaction_id_patterns as $smtp_transaction_id_pattern) {
1639 if(preg_match($smtp_transaction_id_pattern, $reply, $matches)) {
1640 return $matches[1];
1641 }
1642 }
1643 return false;
1644 }
1645}
1646class PHPMailer
1647{
1648 /**
1649 * The PHPMailer Version number.
1650 * @var string
1651 */
1652 public $Version = '5.2.17';
1653 /**
1654 * Email priority.
1655 * Options: null (default), 1 = High, 3 = Normal, 5 = low.
1656 * When null, the header is not set at all.
1657 * @var integer
1658 */
1659 public $Priority = null;
1660 /**
1661 * The character set of the message.
1662 * @var string
1663 */
1664 public $CharSet = 'iso-8859-1';
1665 /**
1666 * The MIME Content-type of the message.
1667 * @var string
1668 */
1669 public $ContentType = 'text/plain';
1670 /**
1671 * The message encoding.
1672 * Options: "8bit", "7bit", "binary", "base64", and "quoted-printable".
1673 * @var string
1674 */
1675 public $Encoding = '8bit';
1676 /**
1677 * Holds the most recent mailer error message.
1678 * @var string
1679 */
1680 public $ErrorInfo = '';
1681 /**
1682 * The From email address for the message.
1683 * @var string
1684 */
1685 public $From = 'root@localhost';
1686 /**
1687 * The From name of the message.
1688 * @var string
1689 */
1690 public $FromName = 'Root User';
1691 /**
1692 * The Sender email (Return-Path) of the message.
1693 * If not empty, will be sent via -f to sendmail or as 'MAIL FROM' in smtp mode.
1694 * @var string
1695 */
1696 public $Sender = '';
1697 /**
1698 * The Return-Path of the message.
1699 * If empty, it will be set to either From or Sender.
1700 * @var string
1701 * @deprecated Email senders should never set a return-path header;
1702 * it's the receiver's job (RFC5321 section 4.4), so this no longer does anything.
1703 * @link https://tools.ietf.org/html/rfc5321#section-4.4 RFC5321 reference
1704 */
1705 public $ReturnPath = '';
1706 /**
1707 * The Subject of the message.
1708 * @var string
1709 */
1710 public $Subject = '';
1711 /**
1712 * An HTML or plain text message body.
1713 * If HTML then call isHTML(true).
1714 * @var string
1715 */
1716 public $Body = '';
1717 /**
1718 * The plain-text message body.
1719 * This body can be read by mail clients that do not have HTML email
1720 * capability such as mutt & Eudora.
1721 * Clients that can read HTML will view the normal Body.
1722 * @var string
1723 */
1724 public $AltBody = '';
1725 /**
1726 * An iCal message part body.
1727 * Only supported in simple alt or alt_inline message types
1728 * To generate iCal events, use the bundled extras/EasyPeasyICS.php class or iCalcreator
1729 * @link http://sprain.ch/blog/downloads/php-class-easypeasyics-create-ical-files-with-php/
1730 * @link http://kigkonsult.se/iCalcreator/
1731 * @var string
1732 */
1733 public $Ical = '';
1734 /**
1735 * The complete compiled MIME message body.
1736 * @access protected
1737 * @var string
1738 */
1739 protected $MIMEBody = '';
1740 /**
1741 * The complete compiled MIME message headers.
1742 * @var string
1743 * @access protected
1744 */
1745 protected $MIMEHeader = '';
1746 /**
1747 * Extra headers that createHeader() doesn't fold in.
1748 * @var string
1749 * @access protected
1750 */
1751 protected $mailHeader = '';
1752 /**
1753 * Word-wrap the message body to this number of chars.
1754 * Set to 0 to not wrap. A useful value here is 78, for RFC2822 section 2.1.1 compliance.
1755 * @var integer
1756 */
1757 public $WordWrap = 0;
1758 /**
1759 * Which method to use to send mail.
1760 * Options: "mail", "sendmail", or "smtp".
1761 * @var string
1762 */
1763 public $Mailer = 'mail';
1764 /**
1765 * The path to the sendmail program.
1766 * @var string
1767 */
1768 public $Sendmail = '/usr/sbin/sendmail';
1769 /**
1770 * Whether mail() uses a fully sendmail-compatible MTA.
1771 * One which supports sendmail's "-oi -f" options.
1772 * @var boolean
1773 */
1774 public $UseSendmailOptions = true;
1775 /**
1776 * Path to PHPMailer plugins.
1777 * Useful if the SMTP class is not in the PHP include path.
1778 * @var string
1779 * @deprecated Should not be needed now there is an autoloader.
1780 */
1781 public $PluginDir = '';
1782 /**
1783 * The email address that a reading confirmation should be sent to, also known as read receipt.
1784 * @var string
1785 */
1786 public $ConfirmReadingTo = '';
1787 /**
1788 * The hostname to use in the Message-ID header and as default HELO string.
1789 * If empty, PHPMailer attempts to find one with, in order,
1790 * $_SERVER['SERVER_NAME'], gethostname(), php_uname('n'), or the value
1791 * 'localhost.localdomain'.
1792 * @var string
1793 */
1794 public $Hostname = '';
1795 /**
1796 * An ID to be used in the Message-ID header.
1797 * If empty, a unique id will be generated.
1798 * You can set your own, but it must be in the format "<id@domain>",
1799 * as defined in RFC5322 section 3.6.4 or it will be ignored.
1800 * @see https://tools.ietf.org/html/rfc5322#section-3.6.4
1801 * @var string
1802 */
1803 public $MessageID = '';
1804 /**
1805 * The message Date to be used in the Date header.
1806 * If empty, the current date will be added.
1807 * @var string
1808 */
1809 public $MessageDate = '';
1810 /**
1811 * SMTP hosts.
1812 * Either a single hostname or multiple semicolon-delimited hostnames.
1813 * You can also specify a different port
1814 * for each host by using this format: [hostname:port]
1815 * (e.g. "smtp1.example.com:25;smtp2.example.com").
1816 * You can also specify encryption type, for example:
1817 * (e.g. "tls://smtp1.example.com:587;ssl://smtp2.example.com:465").
1818 * Hosts will be tried in order.
1819 * @var string
1820 */
1821 public $Host = 'localhost';
1822 /**
1823 * The default SMTP server port.
1824 * @var integer
1825 * @TODO Why is this needed when the SMTP class takes care of it?
1826 */
1827 public $Port = 25;
1828 /**
1829 * The SMTP HELO of the message.
1830 * Default is $Hostname. If $Hostname is empty, PHPMailer attempts to find
1831 * one with the same method described above for $Hostname.
1832 * @var string
1833 * @see PHPMailer::$Hostname
1834 */
1835 public $Helo = '';
1836 /**
1837 * What kind of encryption to use on the SMTP connection.
1838 * Options: '', 'ssl' or 'tls'
1839 * @var string
1840 */
1841 public $SMTPSecure = '';
1842 /**
1843 * Whether to enable TLS encryption automatically if a server supports it,
1844 * even if `SMTPSecure` is not set to 'tls'.
1845 * Be aware that in PHP >= 5.6 this requires that the server's certificates are valid.
1846 * @var boolean
1847 */
1848 public $SMTPAutoTLS = true;
1849 /**
1850 * Whether to use SMTP authentication.
1851 * Uses the Username and Password properties.
1852 * @var boolean
1853 * @see PHPMailer::$Username
1854 * @see PHPMailer::$Password
1855 */
1856 public $SMTPAuth = false;
1857 /**
1858 * Options array passed to stream_context_create when connecting via SMTP.
1859 * @var array
1860 */
1861 public $SMTPOptions = array();
1862 /**
1863 * SMTP username.
1864 * @var string
1865 */
1866 public $Username = '';
1867 /**
1868 * SMTP password.
1869 * @var string
1870 */
1871 public $Password = '';
1872 /**
1873 * SMTP auth type.
1874 * Options are CRAM-MD5, LOGIN, PLAIN, NTLM, XOAUTH2, attempted in that order if not specified
1875 * @var string
1876 */
1877 public $AuthType = '';
1878 /**
1879 * SMTP realm.
1880 * Used for NTLM auth
1881 * @var string
1882 */
1883 public $Realm = '';
1884 /**
1885 * SMTP workstation.
1886 * Used for NTLM auth
1887 * @var string
1888 */
1889 public $Workstation = '';
1890 /**
1891 * The SMTP server timeout in seconds.
1892 * Default of 5 minutes (300sec) is from RFC2821 section 4.5.3.2
1893 * @var integer
1894 */
1895 public $Timeout = 300;
1896 /**
1897 * SMTP class debug output mode.
1898 * Debug output level.
1899 * Options:
1900 * * `0` No output
1901 * * `1` Commands
1902 * * `2` Data and commands
1903 * * `3` As 2 plus connection status
1904 * * `4` Low-level data output
1905 * @var integer
1906 * @see SMTP::$do_debug
1907 */
1908 public $SMTPDebug = 0;
1909 /**
1910 * How to handle debug output.
1911 * Options:
1912 * * `echo` Output plain-text as-is, appropriate for CLI
1913 * * `html` Output escaped, line breaks converted to `<br>`, appropriate for browser output
1914 * * `error_log` Output to error log as configured in php.ini
1915 *
1916 * Alternatively, you can provide a callable expecting two params: a message string and the debug level:
1917 * <code>
1918 * $mail->Debugoutput = function($str, $level) {echo "debug level $level; message: $str";};
1919 * </code>
1920 * @var string|callable
1921 * @see SMTP::$Debugoutput
1922 */
1923 public $Debugoutput = 'echo';
1924 /**
1925 * Whether to keep SMTP connection open after each message.
1926 * If this is set to true then to close the connection
1927 * requires an explicit call to smtpClose().
1928 * @var boolean
1929 */
1930 public $SMTPKeepAlive = false;
1931 /**
1932 * Whether to split multiple to addresses into multiple messages
1933 * or send them all in one message.
1934 * Only supported in `mail` and `sendmail` transports, not in SMTP.
1935 * @var boolean
1936 */
1937 public $SingleTo = false;
1938 /**
1939 * Storage for addresses when SingleTo is enabled.
1940 * @var array
1941 * @TODO This should really not be public
1942 */
1943 public $SingleToArray = array();
1944 /**
1945 * Whether to generate VERP addresses on send.
1946 * Only applicable when sending via SMTP.
1947 * @link https://en.wikipedia.org/wiki/Variable_envelope_return_path
1948 * @link http://www.postfix.org/VERP_README.html Postfix VERP info
1949 * @var boolean
1950 */
1951 public $do_verp = false;
1952 /**
1953 * Whether to allow sending messages with an empty body.
1954 * @var boolean
1955 */
1956 public $AllowEmpty = false;
1957 /**
1958 * The default line ending.
1959 * @note The default remains "\n". We force CRLF where we know
1960 * it must be used via self::CRLF.
1961 * @var string
1962 */
1963 public $LE = "\n";
1964 /**
1965 * DKIM selector.
1966 * @var string
1967 */
1968 public $DKIM_selector = '';
1969 /**
1970 * DKIM Identity.
1971 * Usually the email address used as the source of the email.
1972 * @var string
1973 */
1974 public $DKIM_identity = '';
1975 /**
1976 * DKIM passphrase.
1977 * Used if your key is encrypted.
1978 * @var string
1979 */
1980 public $DKIM_passphrase = '';
1981 /**
1982 * DKIM signing domain name.
1983 * @example 'example.com'
1984 * @var string
1985 */
1986 public $DKIM_domain = '';
1987 /**
1988 * DKIM private key file path.
1989 * @var string
1990 */
1991 public $DKIM_private = '';
1992 /**
1993 * DKIM private key string.
1994 * If set, takes precedence over `$DKIM_private`.
1995 * @var string
1996 */
1997 public $DKIM_private_string = '';
1998 /**
1999 * Callback Action function name.
2000 *
2001 * The function that handles the result of the send email action.
2002 * It is called out by send() for each email sent.
2003 *
2004 * Value can be any php callable: http://www.php.net/is_callable
2005 *
2006 * Parameters:
2007 * boolean $result result of the send action
2008 * string $to email address of the recipient
2009 * string $cc cc email addresses
2010 * string $bcc bcc email addresses
2011 * string $subject the subject
2012 * string $body the email body
2013 * string $from email address of sender
2014 * @var string
2015 */
2016 public $action_function = '';
2017 /**
2018 * What to put in the X-Mailer header.
2019 * Options: An empty string for PHPMailer default, whitespace for none, or a string to use
2020 * @var string
2021 */
2022 public $XMailer = '';
2023 /**
2024 * Which validator to use by default when validating email addresses.
2025 * May be a callable to inject your own validator, but there are several built-in validators.
2026 * @see PHPMailer::validateAddress()
2027 * @var string|callable
2028 * @static
2029 */
2030 public static $validator = 'auto';
2031 /**
2032 * An instance of the SMTP sender class.
2033 * @var SMTP
2034 * @access protected
2035 */
2036 protected $smtp = null;
2037 /**
2038 * The array of 'to' names and addresses.
2039 * @var array
2040 * @access protected
2041 */
2042 protected $to = array();
2043 /**
2044 * The array of 'cc' names and addresses.
2045 * @var array
2046 * @access protected
2047 */
2048 protected $cc = array();
2049 /**
2050 * The array of 'bcc' names and addresses.
2051 * @var array
2052 * @access protected
2053 */
2054 protected $bcc = array();
2055 /**
2056 * The array of reply-to names and addresses.
2057 * @var array
2058 * @access protected
2059 */
2060 protected $ReplyTo = array();
2061 /**
2062 * An array of all kinds of addresses.
2063 * Includes all of $to, $cc, $bcc
2064 * @var array
2065 * @access protected
2066 * @see PHPMailer::$to @see PHPMailer::$cc @see PHPMailer::$bcc
2067 */
2068 protected $all_recipients = array();
2069 /**
2070 * An array of names and addresses queued for validation.
2071 * In send(), valid and non duplicate entries are moved to $all_recipients
2072 * and one of $to, $cc, or $bcc.
2073 * This array is used only for addresses with IDN.
2074 * @var array
2075 * @access protected
2076 * @see PHPMailer::$to @see PHPMailer::$cc @see PHPMailer::$bcc
2077 * @see PHPMailer::$all_recipients
2078 */
2079 protected $RecipientsQueue = array();
2080 /**
2081 * An array of reply-to names and addresses queued for validation.
2082 * In send(), valid and non duplicate entries are moved to $ReplyTo.
2083 * This array is used only for addresses with IDN.
2084 * @var array
2085 * @access protected
2086 * @see PHPMailer::$ReplyTo
2087 */
2088 protected $ReplyToQueue = array();
2089 /**
2090 * The array of attachments.
2091 * @var array
2092 * @access protected
2093 */
2094 protected $attachment = array();
2095 /**
2096 * The array of custom headers.
2097 * @var array
2098 * @access protected
2099 */
2100 protected $CustomHeader = array();
2101 /**
2102 * The most recent Message-ID (including angular brackets).
2103 * @var string
2104 * @access protected
2105 */
2106 protected $lastMessageID = '';
2107 /**
2108 * The message's MIME type.
2109 * @var string
2110 * @access protected
2111 */
2112 protected $message_type = '';
2113 /**
2114 * The array of MIME boundary strings.
2115 * @var array
2116 * @access protected
2117 */
2118 protected $boundary = array();
2119 /**
2120 * The array of available languages.
2121 * @var array
2122 * @access protected
2123 */
2124 protected $language = array();
2125 /**
2126 * The number of errors encountered.
2127 * @var integer
2128 * @access protected
2129 */
2130 protected $error_count = 0;
2131 /**
2132 * The S/MIME certificate file path.
2133 * @var string
2134 * @access protected
2135 */
2136 protected $sign_cert_file = '';
2137 /**
2138 * The S/MIME key file path.
2139 * @var string
2140 * @access protected
2141 */
2142 protected $sign_key_file = '';
2143 /**
2144 * The optional S/MIME extra certificates ("CA Chain") file path.
2145 * @var string
2146 * @access protected
2147 */
2148 protected $sign_extracerts_file = '';
2149 /**
2150 * The S/MIME password for the key.
2151 * Used only if the key is encrypted.
2152 * @var string
2153 * @access protected
2154 */
2155 protected $sign_key_pass = '';
2156 /**
2157 * Whether to throw exceptions for errors.
2158 * @var boolean
2159 * @access protected
2160 */
2161 protected $exceptions = false;
2162 /**
2163 * Unique ID used for message ID and boundaries.
2164 * @var string
2165 * @access protected
2166 */
2167 protected $uniqueid = '';
2168 /**
2169 * Error severity: message only, continue processing.
2170 */
2171 const STOP_MESSAGE = 0;
2172 /**
2173 * Error severity: message, likely ok to continue processing.
2174 */
2175 const STOP_CONTINUE = 1;
2176 /**
2177 * Error severity: message, plus full stop, critical error reached.
2178 */
2179 const STOP_CRITICAL = 2;
2180 /**
2181 * SMTP RFC standard line ending.
2182 */
2183 const CRLF = "\r\n";
2184 /**
2185 * The maximum line length allowed by RFC 2822 section 2.1.1
2186 * @var integer
2187 */
2188 const MAX_LINE_LENGTH = 998;
2189 /**
2190 * Constructor.
2191 * @param boolean $exceptions Should we throw external exceptions?
2192 */
2193 public function __construct($exceptions = null)
2194 {
2195 if ($exceptions !== null) {
2196 $this->exceptions = (boolean)$exceptions;
2197 }
2198 }
2199 /**
2200 * Destructor.
2201 */
2202 public function __destruct()
2203 {
2204 //Close any open SMTP connection nicely
2205 $this->smtpClose();
2206 }
2207 /**
2208 * Call mail() in a safe_mode-aware fashion.
2209 * Also, unless sendmail_path points to sendmail (or something that
2210 * claims to be sendmail), don't pass params (not a perfect fix,
2211 * but it will do)
2212 * @param string $to To
2213 * @param string $subject Subject
2214 * @param string $body Message Body
2215 * @param string $header Additional Header(s)
2216 * @param string $params Params
2217 * @access private
2218 * @return boolean
2219 */
2220 private function mailPassthru($to, $subject, $body, $header, $params)
2221 {
2222 //Check overloading of mail function to avoid double-encoding
2223 if (ini_get('mbstring.func_overload') & 1) {
2224 $subject = $this->secureHeader($subject);
2225 } else {
2226 $subject = $this->encodeHeader($this->secureHeader($subject));
2227 }
2228 //Can't use additional_parameters in safe_mode
2229 //@link http://php.net/manual/en/function.mail.php
2230 if (ini_get('safe_mode') or !$this->UseSendmailOptions or is_null($params)) {
2231 $result = @mail($to, $subject, $body, $header);
2232 } else {
2233 $result = @mail($to, $subject, $body, $header, $params);
2234 }
2235 return $result;
2236 }
2237 /**
2238 * Output debugging info via user-defined method.
2239 * Only generates output if SMTP debug output is enabled (@see SMTP::$do_debug).
2240 * @see PHPMailer::$Debugoutput
2241 * @see PHPMailer::$SMTPDebug
2242 * @param string $str
2243 */
2244 protected function edebug($str)
2245 {
2246 if ($this->SMTPDebug <= 0) {
2247 return;
2248 }
2249 //Avoid clash with built-in function names
2250 if (!in_array($this->Debugoutput, array('error_log', 'html', 'echo')) and is_callable($this->Debugoutput)) {
2251 call_user_func($this->Debugoutput, $str, $this->SMTPDebug);
2252 return;
2253 }
2254 switch ($this->Debugoutput) {
2255 case 'error_log':
2256 //Don't output, just log
2257 error_log($str);
2258 break;
2259 case 'html':
2260 //Cleans up output a bit for a better looking, HTML-safe output
2261 echo htmlentities(
2262 preg_replace('/[\r\n]+/', '', $str),
2263 ENT_QUOTES,
2264 'UTF-8'
2265 )
2266 . "<br>\n";
2267 break;
2268 case 'echo':
2269 default:
2270 //Normalize line breaks
2271 $str = preg_replace('/\r\n?/ms', "\n", $str);
2272 echo gmdate('Y-m-d H:i:s') . "\t" . str_replace(
2273 "\n",
2274 "\n \t ",
2275 trim($str)
2276 ) . "\n";
2277 }
2278 }
2279 /**
2280 * Sets message type to HTML or plain.
2281 * @param boolean $isHtml True for HTML mode.
2282 * @return void
2283 */
2284 public function isHTML($isHtml = true)
2285 {
2286 if ($isHtml) {
2287 $this->ContentType = 'text/html';
2288 } else {
2289 $this->ContentType = 'text/plain';
2290 }
2291 }
2292 /**
2293 * Send messages using SMTP.
2294 * @return void
2295 */
2296 public function isSMTP()
2297 {
2298 $this->Mailer = 'smtp';
2299 }
2300 /**
2301 * Send messages using PHP's mail() function.
2302 * @return void
2303 */
2304 public function isMail()
2305 {
2306 $this->Mailer = 'mail';
2307 }
2308 /**
2309 * Send messages using $Sendmail.
2310 * @return void
2311 */
2312 public function isSendmail()
2313 {
2314 $ini_sendmail_path = ini_get('sendmail_path');
2315 if (!stristr($ini_sendmail_path, 'sendmail')) {
2316 $this->Sendmail = '/usr/sbin/sendmail';
2317 } else {
2318 $this->Sendmail = $ini_sendmail_path;
2319 }
2320 $this->Mailer = 'sendmail';
2321 }
2322 /**
2323 * Send messages using qmail.
2324 * @return void
2325 */
2326 public function isQmail()
2327 {
2328 $ini_sendmail_path = ini_get('sendmail_path');
2329 if (!stristr($ini_sendmail_path, 'qmail')) {
2330 $this->Sendmail = '/var/qmail/bin/qmail-inject';
2331 } else {
2332 $this->Sendmail = $ini_sendmail_path;
2333 }
2334 $this->Mailer = 'qmail';
2335 }
2336 /**
2337 * Add a "To" address.
2338 * @param string $address The email address to send to
2339 * @param string $name
2340 * @return boolean true on success, false if address already used or invalid in some way
2341 */
2342 public function addAddress($address, $name = '')
2343 {
2344 return $this->addOrEnqueueAnAddress('to', $address, $name);
2345 }
2346 /**
2347 * Add a "CC" address.
2348 * @note: This function works with the SMTP mailer on win32, not with the "mail" mailer.
2349 * @param string $address The email address to send to
2350 * @param string $name
2351 * @return boolean true on success, false if address already used or invalid in some way
2352 */
2353 public function addCC($address, $name = '')
2354 {
2355 return $this->addOrEnqueueAnAddress('cc', $address, $name);
2356 }
2357 /**
2358 * Add a "BCC" address.
2359 * @note: This function works with the SMTP mailer on win32, not with the "mail" mailer.
2360 * @param string $address The email address to send to
2361 * @param string $name
2362 * @return boolean true on success, false if address already used or invalid in some way
2363 */
2364 public function addBCC($address, $name = '')
2365 {
2366 return $this->addOrEnqueueAnAddress('bcc', $address, $name);
2367 }
2368 /**
2369 * Add a "Reply-To" address.
2370 * @param string $address The email address to reply to
2371 * @param string $name
2372 * @return boolean true on success, false if address already used or invalid in some way
2373 */
2374 public function addReplyTo($address, $name = '')
2375 {
2376 return $this->addOrEnqueueAnAddress('Reply-To', $address, $name);
2377 }
2378 /**
2379 * Add an address to one of the recipient arrays or to the ReplyTo array. Because PHPMailer
2380 * can't validate addresses with an IDN without knowing the PHPMailer::$CharSet (that can still
2381 * be modified after calling this function), addition of such addresses is delayed until send().
2382 * Addresses that have been added already return false, but do not throw exceptions.
2383 * @param string $kind One of 'to', 'cc', 'bcc', or 'ReplyTo'
2384 * @param string $address The email address to send, resp. to reply to
2385 * @param string $name
2386 * @throws phpmailerException
2387 * @return boolean true on success, false if address already used or invalid in some way
2388 * @access protected
2389 */
2390 protected function addOrEnqueueAnAddress($kind, $address, $name)
2391 {
2392 $address = trim($address);
2393 $name = trim(preg_replace('/[\r\n]+/', '', $name)); //Strip breaks and trim
2394 if (($pos = strrpos($address, '@')) === false) {
2395 // At-sign is misssing.
2396 $error_message = $this->lang('invalid_address') . " (addAnAddress $kind): $address";
2397 $this->setError($error_message);
2398 $this->edebug($error_message);
2399 if ($this->exceptions) {
2400 throw new phpmailerException($error_message);
2401 }
2402 return false;
2403 }
2404 $params = array($kind, $address, $name);
2405 // Enqueue addresses with IDN until we know the PHPMailer::$CharSet.
2406 if ($this->has8bitChars(substr($address, ++$pos)) and $this->idnSupported()) {
2407 if ($kind != 'Reply-To') {
2408 if (!array_key_exists($address, $this->RecipientsQueue)) {
2409 $this->RecipientsQueue[$address] = $params;
2410 return true;
2411 }
2412 } else {
2413 if (!array_key_exists($address, $this->ReplyToQueue)) {
2414 $this->ReplyToQueue[$address] = $params;
2415 return true;
2416 }
2417 }
2418 return false;
2419 }
2420 // Immediately add standard addresses without IDN.
2421 return call_user_func_array(array($this, 'addAnAddress'), $params);
2422 }
2423 /**
2424 * Add an address to one of the recipient arrays or to the ReplyTo array.
2425 * Addresses that have been added already return false, but do not throw exceptions.
2426 * @param string $kind One of 'to', 'cc', 'bcc', or 'ReplyTo'
2427 * @param string $address The email address to send, resp. to reply to
2428 * @param string $name
2429 * @throws phpmailerException
2430 * @return boolean true on success, false if address already used or invalid in some way
2431 * @access protected
2432 */
2433 protected function addAnAddress($kind, $address, $name = '')
2434 {
2435 if (!in_array($kind, array('to', 'cc', 'bcc', 'Reply-To'))) {
2436 $error_message = $this->lang('Invalid recipient kind: ') . $kind;
2437 $this->setError($error_message);
2438 $this->edebug($error_message);
2439 if ($this->exceptions) {
2440 throw new phpmailerException($error_message);
2441 }
2442 return false;
2443 }
2444 if (!$this->validateAddress($address)) {
2445 $error_message = $this->lang('invalid_address') . " (addAnAddress $kind): $address";
2446 $this->setError($error_message);
2447 $this->edebug($error_message);
2448 if ($this->exceptions) {
2449 throw new phpmailerException($error_message);
2450 }
2451 return false;
2452 }
2453 if ($kind != 'Reply-To') {
2454 if (!array_key_exists(strtolower($address), $this->all_recipients)) {
2455 array_push($this->$kind, array($address, $name));
2456 $this->all_recipients[strtolower($address)] = true;
2457 return true;
2458 }
2459 } else {
2460 if (!array_key_exists(strtolower($address), $this->ReplyTo)) {
2461 $this->ReplyTo[strtolower($address)] = array($address, $name);
2462 return true;
2463 }
2464 }
2465 return false;
2466 }
2467 /**
2468 * Parse and validate a string containing one or more RFC822-style comma-separated email addresses
2469 * of the form "display name <address>" into an array of name/address pairs.
2470 * Uses the imap_rfc822_parse_adrlist function if the IMAP extension is available.
2471 * Note that quotes in the name part are removed.
2472 * @param string $addrstr The address list string
2473 * @param bool $useimap Whether to use the IMAP extension to parse the list
2474 * @return array
2475 * @link http://www.andrew.cmu.edu/user/agreen1/testing/mrbs/web/Mail/RFC822.php A more careful implementation
2476 */
2477 public function parseAddresses($addrstr, $useimap = true)
2478 {
2479 $addresses = array();
2480 if ($useimap and function_exists('imap_rfc822_parse_adrlist')) {
2481 //Use this built-in parser if it's available
2482 $list = imap_rfc822_parse_adrlist($addrstr, '');
2483 foreach ($list as $address) {
2484 if ($address->host != '.SYNTAX-ERROR.') {
2485 if ($this->validateAddress($address->mailbox . '@' . $address->host)) {
2486 $addresses[] = array(
2487 'name' => (property_exists($address, 'personal') ? $address->personal : ''),
2488 'address' => $address->mailbox . '@' . $address->host
2489 );
2490 }
2491 }
2492 }
2493 } else {
2494 //Use this simpler parser
2495 $list = explode(',', $addrstr);
2496 foreach ($list as $address) {
2497 $address = trim($address);
2498 //Is there a separate name part?
2499 if (strpos($address, '<') === false) {
2500 //No separate name, just use the whole thing
2501 if ($this->validateAddress($address)) {
2502 $addresses[] = array(
2503 'name' => '',
2504 'address' => $address
2505 );
2506 }
2507 } else {
2508 list($name, $email) = explode('<', $address);
2509 $email = trim(str_replace('>', '', $email));
2510 if ($this->validateAddress($email)) {
2511 $addresses[] = array(
2512 'name' => trim(str_replace(array('"', "'"), '', $name)),
2513 'address' => $email
2514 );
2515 }
2516 }
2517 }
2518 }
2519 return $addresses;
2520 }
2521 /**
2522 * Set the From and FromName properties.
2523 * @param string $address
2524 * @param string $name
2525 * @param boolean $auto Whether to also set the Sender address, defaults to true
2526 * @throws phpmailerException
2527 * @return boolean
2528 */
2529 public function setFrom($address, $name = '', $auto = true)
2530 {
2531 $address = trim($address);
2532 $name = trim(preg_replace('/[\r\n]+/', '', $name)); //Strip breaks and trim
2533 // Don't validate now addresses with IDN. Will be done in send().
2534 if (($pos = strrpos($address, '@')) === false or
2535 (!$this->has8bitChars(substr($address, ++$pos)) or !$this->idnSupported()) and
2536 !$this->validateAddress($address)) {
2537 $error_message = $this->lang('invalid_address') . " (setFrom) $address";
2538 $this->setError($error_message);
2539 $this->edebug($error_message);
2540 if ($this->exceptions) {
2541 throw new phpmailerException($error_message);
2542 }
2543 return false;
2544 }
2545 $this->From = $address;
2546 $this->FromName = $name;
2547 if ($auto) {
2548 if (empty($this->Sender)) {
2549 $this->Sender = $address;
2550 }
2551 }
2552 return true;
2553 }
2554 /**
2555 * Return the Message-ID header of the last email.
2556 * Technically this is the value from the last time the headers were created,
2557 * but it's also the message ID of the last sent message except in
2558 * pathological cases.
2559 * @return string
2560 */
2561 public function getLastMessageID()
2562 {
2563 return $this->lastMessageID;
2564 }
2565 /**
2566 * Check that a string looks like an email address.
2567 * @param string $address The email address to check
2568 * @param string|callable $patternselect A selector for the validation pattern to use :
2569 * * `auto` Pick best pattern automatically;
2570 * * `pcre8` Use the squiloople.com pattern, requires PCRE > 8.0, PHP >= 5.3.2, 5.2.14;
2571 * * `pcre` Use old PCRE implementation;
2572 * * `php` Use PHP built-in FILTER_VALIDATE_EMAIL;
2573 * * `html5` Use the pattern given by the HTML5 spec for 'email' type form input elements.
2574 * * `noregex` Don't use a regex: super fast, really dumb.
2575 * Alternatively you may pass in a callable to inject your own validator, for example:
2576 * PHPMailer::validateAddress('user@example.com', function($address) {
2577 * return (strpos($address, '@') !== false);
2578 * });
2579 * You can also set the PHPMailer::$validator static to a callable, allowing built-in methods to use your validator.
2580 * @return boolean
2581 * @static
2582 * @access public
2583 */
2584 public static function validateAddress($address, $patternselect = null)
2585 {
2586 if (is_null($patternselect)) {
2587 $patternselect = self::$validator;
2588 }
2589 if (is_callable($patternselect)) {
2590 return call_user_func($patternselect, $address);
2591 }
2592 //Reject line breaks in addresses; it's valid RFC5322, but not RFC5321
2593 if (strpos($address, "\n") !== false or strpos($address, "\r") !== false) {
2594 return false;
2595 }
2596 if (!$patternselect or $patternselect == 'auto') {
2597 //Check this constant first so it works when extension_loaded() is disabled by safe mode
2598 //Constant was added in PHP 5.2.4
2599 if (defined('PCRE_VERSION')) {
2600 //This pattern can get stuck in a recursive loop in PCRE <= 8.0.2
2601 if (version_compare(PCRE_VERSION, '8.0.3') >= 0) {
2602 $patternselect = 'pcre8';
2603 } else {
2604 $patternselect = 'pcre';
2605 }
2606 } elseif (function_exists('extension_loaded') and extension_loaded('pcre')) {
2607 //Fall back to older PCRE
2608 $patternselect = 'pcre';
2609 } else {
2610 //Filter_var appeared in PHP 5.2.0 and does not require the PCRE extension
2611 if (version_compare(PHP_VERSION, '5.2.0') >= 0) {
2612 $patternselect = 'php';
2613 } else {
2614 $patternselect = 'noregex';
2615 }
2616 }
2617 }
2618 switch ($patternselect) {
2619 case 'pcre8':
2620 /**
2621 * Uses the same RFC5322 regex on which FILTER_VALIDATE_EMAIL is based, but allows dotless domains.
2622 * @link http://squiloople.com/2009/12/20/email-address-validation/
2623 * @copyright 2009-2010 Michael Rushton
2624 * Feel free to use and redistribute this code. But please keep this copyright notice.
2625 */
2626 return (boolean)preg_match(
2627 '/^(?!(?>(?1)"?(?>\\\[ -~]|[^"])"?(?1)){255,})(?!(?>(?1)"?(?>\\\[ -~]|[^"])"?(?1)){65,}@)' .
2628 '((?>(?>(?>((?>(?>(?>\x0D\x0A)?[\t ])+|(?>[\t ]*\x0D\x0A)?[\t ]+)?)(\((?>(?2)' .
2629 '(?>[\x01-\x08\x0B\x0C\x0E-\'*-\[\]-\x7F]|\\\[\x00-\x7F]|(?3)))*(?2)\)))+(?2))|(?2))?)' .
2630 '([!#-\'*+\/-9=?^-~-]+|"(?>(?2)(?>[\x01-\x08\x0B\x0C\x0E-!#-\[\]-\x7F]|\\\[\x00-\x7F]))*' .
2631 '(?2)")(?>(?1)\.(?1)(?4))*(?1)@(?!(?1)[a-z0-9-]{64,})(?1)(?>([a-z0-9](?>[a-z0-9-]*[a-z0-9])?)' .
2632 '(?>(?1)\.(?!(?1)[a-z0-9-]{64,})(?1)(?5)){0,126}|\[(?:(?>IPv6:(?>([a-f0-9]{1,4})(?>:(?6)){7}' .
2633 '|(?!(?:.*[a-f0-9][:\]]){8,})((?6)(?>:(?6)){0,6})?::(?7)?))|(?>(?>IPv6:(?>(?6)(?>:(?6)){5}:' .
2634 '|(?!(?:.*[a-f0-9]:){6,})(?8)?::(?>((?6)(?>:(?6)){0,4}):)?))?(25[0-5]|2[0-4][0-9]|1[0-9]{2}' .
2635 '|[1-9]?[0-9])(?>\.(?9)){3}))\])(?1)$/isD',
2636 $address
2637 );
2638 case 'pcre':
2639 //An older regex that doesn't need a recent PCRE
2640 return (boolean)preg_match(
2641 '/^(?!(?>"?(?>\\\[ -~]|[^"])"?){255,})(?!(?>"?(?>\\\[ -~]|[^"])"?){65,}@)(?>' .
2642 '[!#-\'*+\/-9=?^-~-]+|"(?>(?>[\x01-\x08\x0B\x0C\x0E-!#-\[\]-\x7F]|\\\[\x00-\xFF]))*")' .
2643 '(?>\.(?>[!#-\'*+\/-9=?^-~-]+|"(?>(?>[\x01-\x08\x0B\x0C\x0E-!#-\[\]-\x7F]|\\\[\x00-\xFF]))*"))*' .
2644 '@(?>(?![a-z0-9-]{64,})(?>[a-z0-9](?>[a-z0-9-]*[a-z0-9])?)(?>\.(?![a-z0-9-]{64,})' .
2645 '(?>[a-z0-9](?>[a-z0-9-]*[a-z0-9])?)){0,126}|\[(?:(?>IPv6:(?>(?>[a-f0-9]{1,4})(?>:' .
2646 '[a-f0-9]{1,4}){7}|(?!(?:.*[a-f0-9][:\]]){8,})(?>[a-f0-9]{1,4}(?>:[a-f0-9]{1,4}){0,6})?' .
2647 '::(?>[a-f0-9]{1,4}(?>:[a-f0-9]{1,4}){0,6})?))|(?>(?>IPv6:(?>[a-f0-9]{1,4}(?>:' .
2648 '[a-f0-9]{1,4}){5}:|(?!(?:.*[a-f0-9]:){6,})(?>[a-f0-9]{1,4}(?>:[a-f0-9]{1,4}){0,4})?' .
2649 '::(?>(?:[a-f0-9]{1,4}(?>:[a-f0-9]{1,4}){0,4}):)?))?(?>25[0-5]|2[0-4][0-9]|1[0-9]{2}' .
2650 '|[1-9]?[0-9])(?>\.(?>25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3}))\])$/isD',
2651 $address
2652 );
2653 case 'html5':
2654 /**
2655 * This is the pattern used in the HTML5 spec for validation of 'email' type form input elements.
2656 * @link http://www.whatwg.org/specs/web-apps/current-work/#e-mail-state-(type=email)
2657 */
2658 return (boolean)preg_match(
2659 '/^[a-zA-Z0-9.!#$%&\'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}' .
2660 '[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/sD',
2661 $address
2662 );
2663 case 'noregex':
2664 //No PCRE! Do something _very_ approximate!
2665 //Check the address is 3 chars or longer and contains an @ that's not the first or last char
2666 return (strlen($address) >= 3
2667 and strpos($address, '@') >= 1
2668 and strpos($address, '@') != strlen($address) - 1);
2669 case 'php':
2670 default:
2671 return (boolean)filter_var($address, FILTER_VALIDATE_EMAIL);
2672 }
2673 }
2674 /**
2675 * Tells whether IDNs (Internationalized Domain Names) are supported or not. This requires the
2676 * "intl" and "mbstring" PHP extensions.
2677 * @return bool "true" if required functions for IDN support are present
2678 */
2679 public function idnSupported()
2680 {
2681 // @TODO: Write our own "idn_to_ascii" function for PHP <= 5.2.
2682 return function_exists('idn_to_ascii') and function_exists('mb_convert_encoding');
2683 }
2684 /**
2685 * Converts IDN in given email address to its ASCII form, also known as punycode, if possible.
2686 * Important: Address must be passed in same encoding as currently set in PHPMailer::$CharSet.
2687 * This function silently returns unmodified address if:
2688 * - No conversion is necessary (i.e. domain name is not an IDN, or is already in ASCII form)
2689 * - Conversion to punycode is impossible (e.g. required PHP functions are not available)
2690 * or fails for any reason (e.g. domain has characters not allowed in an IDN)
2691 * @see PHPMailer::$CharSet
2692 * @param string $address The email address to convert
2693 * @return string The encoded address in ASCII form
2694 */
2695 public function punyencodeAddress($address)
2696 {
2697 // Verify we have required functions, CharSet, and at-sign.
2698 if ($this->idnSupported() and
2699 !empty($this->CharSet) and
2700 ($pos = strrpos($address, '@')) !== false) {
2701 $domain = substr($address, ++$pos);
2702 // Verify CharSet string is a valid one, and domain properly encoded in this CharSet.
2703 if ($this->has8bitChars($domain) and @mb_check_encoding($domain, $this->CharSet)) {
2704 $domain = mb_convert_encoding($domain, 'UTF-8', $this->CharSet);
2705 if (($punycode = defined('INTL_IDNA_VARIANT_UTS46') ?
2706 idn_to_ascii($domain, 0, INTL_IDNA_VARIANT_UTS46) :
2707 idn_to_ascii($domain)) !== false) {
2708 return substr($address, 0, $pos) . $punycode;
2709 }
2710 }
2711 }
2712 return $address;
2713 }
2714 /**
2715 * Create a message and send it.
2716 * Uses the sending method specified by $Mailer.
2717 * @throws phpmailerException
2718 * @return boolean false on error - See the ErrorInfo property for details of the error.
2719 */
2720 public function send()
2721 {
2722 try {
2723 if (!$this->preSend()) {
2724 return false;
2725 }
2726 return $this->postSend();
2727 } catch (phpmailerException $exc) {
2728 $this->mailHeader = '';
2729 $this->setError($exc->getMessage());
2730 if ($this->exceptions) {
2731 throw $exc;
2732 }
2733 return false;
2734 }
2735 }
2736 /**
2737 * Prepare a message for sending.
2738 * @throws phpmailerException
2739 * @return boolean
2740 */
2741 public function preSend()
2742 {
2743 try {
2744 $this->error_count = 0; // Reset errors
2745 $this->mailHeader = '';
2746 // Dequeue recipient and Reply-To addresses with IDN
2747 foreach (array_merge($this->RecipientsQueue, $this->ReplyToQueue) as $params) {
2748 $params[1] = $this->punyencodeAddress($params[1]);
2749 call_user_func_array(array($this, 'addAnAddress'), $params);
2750 }
2751 if ((count($this->to) + count($this->cc) + count($this->bcc)) < 1) {
2752 throw new phpmailerException($this->lang('provide_address'), self::STOP_CRITICAL);
2753 }
2754 // Validate From, Sender, and ConfirmReadingTo addresses
2755 foreach (array('From', 'Sender', 'ConfirmReadingTo') as $address_kind) {
2756 $this->$address_kind = trim($this->$address_kind);
2757 if (empty($this->$address_kind)) {
2758 continue;
2759 }
2760 $this->$address_kind = $this->punyencodeAddress($this->$address_kind);
2761 if (!$this->validateAddress($this->$address_kind)) {
2762 $error_message = $this->lang('invalid_address') . ' (punyEncode) ' . $this->$address_kind;
2763 $this->setError($error_message);
2764 $this->edebug($error_message);
2765 if ($this->exceptions) {
2766 throw new phpmailerException($error_message);
2767 }
2768 return false;
2769 }
2770 }
2771 // Set whether the message is multipart/alternative
2772 if ($this->alternativeExists()) {
2773 $this->ContentType = 'multipart/alternative';
2774 }
2775 $this->setMessageType();
2776 // Refuse to send an empty message unless we are specifically allowing it
2777 if (!$this->AllowEmpty and empty($this->Body)) {
2778 throw new phpmailerException($this->lang('empty_message'), self::STOP_CRITICAL);
2779 }
2780 // Create body before headers in case body makes changes to headers (e.g. altering transfer encoding)
2781 $this->MIMEHeader = '';
2782 $this->MIMEBody = $this->createBody();
2783 // createBody may have added some headers, so retain them
2784 $tempheaders = $this->MIMEHeader;
2785 $this->MIMEHeader = $this->createHeader();
2786 $this->MIMEHeader .= $tempheaders;
2787 // To capture the complete message when using mail(), create
2788 // an extra header list which createHeader() doesn't fold in
2789 if ($this->Mailer == 'mail') {
2790 if (count($this->to) > 0) {
2791 $this->mailHeader .= $this->addrAppend('To', $this->to);
2792 } else {
2793 $this->mailHeader .= $this->headerLine('To', 'undisclosed-recipients:;');
2794 }
2795 $this->mailHeader .= $this->headerLine(
2796 'Subject',
2797 $this->encodeHeader($this->secureHeader(trim($this->Subject)))
2798 );
2799 }
2800 // Sign with DKIM if enabled
2801 if (!empty($this->DKIM_domain)
2802 && !empty($this->DKIM_selector)
2803 && (!empty($this->DKIM_private_string)
2804 || (!empty($this->DKIM_private) && file_exists($this->DKIM_private))
2805 )
2806 ) {
2807 $header_dkim = $this->DKIM_Add(
2808 $this->MIMEHeader . $this->mailHeader,
2809 $this->encodeHeader($this->secureHeader($this->Subject)),
2810 $this->MIMEBody
2811 );
2812 $this->MIMEHeader = rtrim($this->MIMEHeader, "\r\n ") . self::CRLF .
2813 str_replace("\r\n", "\n", $header_dkim) . self::CRLF;
2814 }
2815 return true;
2816 } catch (phpmailerException $exc) {
2817 $this->setError($exc->getMessage());
2818 if ($this->exceptions) {
2819 throw $exc;
2820 }
2821 return false;
2822 }
2823 }
2824 /**
2825 * Actually send a message.
2826 * Send the email via the selected mechanism
2827 * @throws phpmailerException
2828 * @return boolean
2829 */
2830 public function postSend()
2831 {
2832 try {
2833 // Choose the mailer and send through it
2834 switch ($this->Mailer) {
2835 case 'sendmail':
2836 case 'qmail':
2837 return $this->sendmailSend($this->MIMEHeader, $this->MIMEBody);
2838 case 'smtp':
2839 return $this->smtpSend($this->MIMEHeader, $this->MIMEBody);
2840 case 'mail':
2841 return $this->mailSend($this->MIMEHeader, $this->MIMEBody);
2842 default:
2843 $sendMethod = $this->Mailer.'Send';
2844 if (method_exists($this, $sendMethod)) {
2845 return $this->$sendMethod($this->MIMEHeader, $this->MIMEBody);
2846 }
2847 return $this->mailSend($this->MIMEHeader, $this->MIMEBody);
2848 }
2849 } catch (phpmailerException $exc) {
2850 $this->setError($exc->getMessage());
2851 $this->edebug($exc->getMessage());
2852 if ($this->exceptions) {
2853 throw $exc;
2854 }
2855 }
2856 return false;
2857 }
2858 /**
2859 * Send mail using the $Sendmail program.
2860 * @param string $header The message headers
2861 * @param string $body The message body
2862 * @see PHPMailer::$Sendmail
2863 * @throws phpmailerException
2864 * @access protected
2865 * @return boolean
2866 */
2867 protected function sendmailSend($header, $body)
2868 {
2869 if ($this->Sender != '') {
2870 if ($this->Mailer == 'qmail') {
2871 $sendmail = sprintf('%s -f%s', escapeshellcmd($this->Sendmail), escapeshellarg($this->Sender));
2872 } else {
2873 $sendmail = sprintf('%s -oi -f%s -t', escapeshellcmd($this->Sendmail), escapeshellarg($this->Sender));
2874 }
2875 } else {
2876 if ($this->Mailer == 'qmail') {
2877 $sendmail = sprintf('%s', escapeshellcmd($this->Sendmail));
2878 } else {
2879 $sendmail = sprintf('%s -oi -t', escapeshellcmd($this->Sendmail));
2880 }
2881 }
2882 if ($this->SingleTo) {
2883 foreach ($this->SingleToArray as $toAddr) {
2884 if (!@$mail = popen($sendmail, 'w')) {
2885 throw new phpmailerException($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL);
2886 }
2887 fputs($mail, 'To: ' . $toAddr . "\n");
2888 fputs($mail, $header);
2889 fputs($mail, $body);
2890 $result = pclose($mail);
2891 $this->doCallback(
2892 ($result == 0),
2893 array($toAddr),
2894 $this->cc,
2895 $this->bcc,
2896 $this->Subject,
2897 $body,
2898 $this->From
2899 );
2900 if ($result != 0) {
2901 throw new phpmailerException($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL);
2902 }
2903 }
2904 } else {
2905 if (!@$mail = popen($sendmail, 'w')) {
2906 throw new phpmailerException($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL);
2907 }
2908 fputs($mail, $header);
2909 fputs($mail, $body);
2910 $result = pclose($mail);
2911 $this->doCallback(
2912 ($result == 0),
2913 $this->to,
2914 $this->cc,
2915 $this->bcc,
2916 $this->Subject,
2917 $body,
2918 $this->From
2919 );
2920 if ($result != 0) {
2921 throw new phpmailerException($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL);
2922 }
2923 }
2924 return true;
2925 }
2926 /**
2927 * Send mail using the PHP mail() function.
2928 * @param string $header The message headers
2929 * @param string $body The message body
2930 * @link http://www.php.net/manual/en/book.mail.php
2931 * @throws phpmailerException
2932 * @access protected
2933 * @return boolean
2934 */
2935 protected function mailSend($header, $body)
2936 {
2937 $toArr = array();
2938 foreach ($this->to as $toaddr) {
2939 $toArr[] = $this->addrFormat($toaddr);
2940 }
2941 $to = implode(', ', $toArr);
2942 $params = null;
2943 //This sets the SMTP envelope sender which gets turned into a return-path header by the receiver
2944 if (!empty($this->Sender)) {
2945 $params = sprintf('-f%s', $this->Sender);
2946 }
2947 if ($this->Sender != '' and !ini_get('safe_mode')) {
2948 $old_from = ini_get('sendmail_from');
2949 ini_set('sendmail_from', $this->Sender);
2950 }
2951 $result = false;
2952 if ($this->SingleTo and count($toArr) > 1) {
2953 foreach ($toArr as $toAddr) {
2954 $result = $this->mailPassthru($toAddr, $this->Subject, $body, $header, $params);
2955 $this->doCallback($result, array($toAddr), $this->cc, $this->bcc, $this->Subject, $body, $this->From);
2956 }
2957 } else {
2958 $result = $this->mailPassthru($to, $this->Subject, $body, $header, $params);
2959 $this->doCallback($result, $this->to, $this->cc, $this->bcc, $this->Subject, $body, $this->From);
2960 }
2961 if (isset($old_from)) {
2962 ini_set('sendmail_from', $old_from);
2963 }
2964 if (!$result) {
2965 throw new phpmailerException($this->lang('instantiate'), self::STOP_CRITICAL);
2966 }
2967 return true;
2968 }
2969 /**
2970 * Get an instance to use for SMTP operations.
2971 * Override this function to load your own SMTP implementation
2972 * @return SMTP
2973 */
2974 public function getSMTPInstance()
2975 {
2976 if (!is_object($this->smtp)) {
2977 $this->smtp = new SMTP;
2978 }
2979 return $this->smtp;
2980 }
2981 /**
2982 * Send mail via SMTP.
2983 * Returns false if there is a bad MAIL FROM, RCPT, or DATA input.
2984 * Uses the PHPMailerSMTP class by default.
2985 * @see PHPMailer::getSMTPInstance() to use a different class.
2986 * @param string $header The message headers
2987 * @param string $body The message body
2988 * @throws phpmailerException
2989 * @uses SMTP
2990 * @access protected
2991 * @return boolean
2992 */
2993 protected function smtpSend($header, $body)
2994 {
2995 $bad_rcpt = array();
2996 if (!$this->smtpConnect($this->SMTPOptions)) {
2997 throw new phpmailerException($this->lang('smtp_connect_failed'), self::STOP_CRITICAL);
2998 }
2999 if ('' == $this->Sender) {
3000 $smtp_from = $this->From;
3001 } else {
3002 $smtp_from = $this->Sender;
3003 }
3004 if (!$this->smtp->mail($smtp_from)) {
3005 $this->setError($this->lang('from_failed') . $smtp_from . ' : ' . implode(',', $this->smtp->getError()));
3006 throw new phpmailerException($this->ErrorInfo, self::STOP_CRITICAL);
3007 }
3008 // Attempt to send to all recipients
3009 foreach (array($this->to, $this->cc, $this->bcc) as $togroup) {
3010 foreach ($togroup as $to) {
3011 if (!$this->smtp->recipient($to[0])) {
3012 $error = $this->smtp->getError();
3013 $bad_rcpt[] = array('to' => $to[0], 'error' => $error['detail']);
3014 $isSent = false;
3015 } else {
3016 $isSent = true;
3017 }
3018 $this->doCallback($isSent, array($to[0]), array(), array(), $this->Subject, $body, $this->From);
3019 }
3020 }
3021 // Only send the DATA command if we have viable recipients
3022 if ((count($this->all_recipients) > count($bad_rcpt)) and !$this->smtp->data($header . $body)) {
3023 throw new phpmailerException($this->lang('data_not_accepted'), self::STOP_CRITICAL);
3024 }
3025 if ($this->SMTPKeepAlive) {
3026 $this->smtp->reset();
3027 } else {
3028 $this->smtp->quit();
3029 $this->smtp->close();
3030 }
3031 //Create error message for any bad addresses
3032 if (count($bad_rcpt) > 0) {
3033 $errstr = '';
3034 foreach ($bad_rcpt as $bad) {
3035 $errstr .= $bad['to'] . ': ' . $bad['error'];
3036 }
3037 throw new phpmailerException(
3038 $this->lang('recipients_failed') . $errstr,
3039 self::STOP_CONTINUE
3040 );
3041 }
3042 return true;
3043 }
3044 /**
3045 * Initiate a connection to an SMTP server.
3046 * Returns false if the operation failed.
3047 * @param array $options An array of options compatible with stream_context_create()
3048 * @uses SMTP
3049 * @access public
3050 * @throws phpmailerException
3051 * @return boolean
3052 */
3053 public function smtpConnect($options = null)
3054 {
3055 if (is_null($this->smtp)) {
3056 $this->smtp = $this->getSMTPInstance();
3057 }
3058 //If no options are provided, use whatever is set in the instance
3059 if (is_null($options)) {
3060 $options = $this->SMTPOptions;
3061 }
3062 // Already connected?
3063 if ($this->smtp->connected()) {
3064 return true;
3065 }
3066 $this->smtp->setTimeout($this->Timeout);
3067 $this->smtp->setDebugLevel($this->SMTPDebug);
3068 $this->smtp->setDebugOutput($this->Debugoutput);
3069 $this->smtp->setVerp($this->do_verp);
3070 $hosts = explode(';', $this->Host);
3071 $lastexception = null;
3072 foreach ($hosts as $hostentry) {
3073 $hostinfo = array();
3074 if (!preg_match('/^((ssl|tls):\/\/)*([a-zA-Z0-9:\[\]\.-]*):?([0-9]*)$/', trim($hostentry), $hostinfo)){
3075 // Not a valid host entry
3076 continue;
3077 }
3078 // $hostinfo[2]: optional ssl or tls prefix
3079 // $hostinfo[3]: the hostname
3080 // $hostinfo[4]: optional port number
3081 // The host string prefix can temporarily override the current setting for SMTPSecure
3082 // If it's not specified, the default value is used
3083 $prefix = '';
3084 $secure = $this->SMTPSecure;
3085 $tls = ($this->SMTPSecure == 'tls');
3086 if ('ssl' == $hostinfo[2] or ('' == $hostinfo[2] and 'ssl' == $this->SMTPSecure)) {
3087 $prefix = 'ssl://';
3088 $tls = false; // Can't have SSL and TLS at the same time
3089 $secure = 'ssl';
3090 } elseif ($hostinfo[2] == 'tls') {
3091 $tls = true;
3092 // tls doesn't use a prefix
3093 $secure = 'tls';
3094 }
3095 //Do we need the OpenSSL extension?
3096 $sslext = defined('OPENSSL_ALGO_SHA1');
3097 if ('tls' === $secure or 'ssl' === $secure) {
3098 //Check for an OpenSSL constant rather than using extension_loaded, which is sometimes disabled
3099 if (!$sslext) {
3100 throw new phpmailerException($this->lang('extension_missing').'openssl', self::STOP_CRITICAL);
3101 }
3102 }
3103 $host = $hostinfo[3];
3104 $port = $this->Port;
3105 $tport = (integer)$hostinfo[4];
3106 if ($tport > 0 and $tport < 65536) {
3107 $port = $tport;
3108 }
3109 if ($this->smtp->connect($prefix . $host, $port, $this->Timeout, $options)) {
3110 try {
3111 if ($this->Helo) {
3112 $hello = $this->Helo;
3113 } else {
3114 $hello = $this->serverHostname();
3115 }
3116 $this->smtp->hello($hello);
3117 //Automatically enable TLS encryption if:
3118 // * it's not disabled
3119 // * we have openssl extension
3120 // * we are not already using SSL
3121 // * the server offers STARTTLS
3122 if ($this->SMTPAutoTLS and $sslext and $secure != 'ssl' and $this->smtp->getServerExt('STARTTLS')) {
3123 $tls = true;
3124 }
3125 if ($tls) {
3126 if (!$this->smtp->startTLS()) {
3127 throw new phpmailerException($this->lang('connect_host'));
3128 }
3129 // We must resend EHLO after TLS negotiation
3130 $this->smtp->hello($hello);
3131 }
3132 if ($this->SMTPAuth) {
3133 if (!$this->smtp->authenticate(
3134 $this->Username,
3135 $this->Password,
3136 $this->AuthType,
3137 $this->Realm,
3138 $this->Workstation
3139 )
3140 ) {
3141 throw new phpmailerException($this->lang('authenticate'));
3142 }
3143 }
3144 return true;
3145 } catch (phpmailerException $exc) {
3146 $lastexception = $exc;
3147 $this->edebug($exc->getMessage());
3148 // We must have connected, but then failed TLS or Auth, so close connection nicely
3149 $this->smtp->quit();
3150 }
3151 }
3152 }
3153 // If we get here, all connection attempts have failed, so close connection hard
3154 $this->smtp->close();
3155 // As we've caught all exceptions, just report whatever the last one was
3156 if ($this->exceptions and !is_null($lastexception)) {
3157 throw $lastexception;
3158 }
3159 return false;
3160 }
3161 /**
3162 * Close the active SMTP session if one exists.
3163 * @return void
3164 */
3165 public function smtpClose()
3166 {
3167 if (is_a($this->smtp, 'SMTP')) {
3168 if ($this->smtp->connected()) {
3169 $this->smtp->quit();
3170 $this->smtp->close();
3171 }
3172 }
3173 }
3174 /**
3175 * Set the language for error messages.
3176 * Returns false if it cannot load the language file.
3177 * The default language is English.
3178 * @param string $langcode ISO 639-1 2-character language code (e.g. French is "fr")
3179 * @param string $lang_path Path to the language file directory, with trailing separator (slash)
3180 * @return boolean
3181 * @access public
3182 */
3183 public function setLanguage($langcode = 'en', $lang_path = '')
3184 {
3185 // Backwards compatibility for renamed language codes
3186 $renamed_langcodes = array(
3187 'br' => 'pt_br',
3188 'cz' => 'cs',
3189 'dk' => 'da',
3190 'no' => 'nb',
3191 'se' => 'sv',
3192 );
3193 if (isset($renamed_langcodes[$langcode])) {
3194 $langcode = $renamed_langcodes[$langcode];
3195 }
3196 // Define full set of translatable strings in English
3197 $PHPMAILER_LANG = array(
3198 'authenticate' => 'SMTP Error: Could not authenticate.',
3199 'connect_host' => 'SMTP Error: Could not connect to SMTP host.',
3200 'data_not_accepted' => 'SMTP Error: data not accepted.',
3201 'empty_message' => 'Message body empty',
3202 'encoding' => 'Unknown encoding: ',
3203 'execute' => 'Could not execute: ',
3204 'file_access' => 'Could not access file: ',
3205 'file_open' => 'File Error: Could not open file: ',
3206 'from_failed' => 'The following From address failed: ',
3207 'instantiate' => 'Could not instantiate mail function.',
3208 'invalid_address' => 'Invalid address: ',
3209 'mailer_not_supported' => ' mailer is not supported.',
3210 'provide_address' => 'You must provide at least one recipient email address.',
3211 'recipients_failed' => 'SMTP Error: The following recipients failed: ',
3212 'signing' => 'Signing Error: ',
3213 'smtp_connect_failed' => 'SMTP connect() failed.',
3214 'smtp_error' => 'SMTP server error: ',
3215 'variable_set' => 'Cannot set or reset variable: ',
3216 'extension_missing' => 'Extension missing: '
3217 );
3218 if (empty($lang_path)) {
3219 // Calculate an absolute path so it can work if CWD is not here
3220 $lang_path = dirname(__FILE__). DIRECTORY_SEPARATOR . 'language'. DIRECTORY_SEPARATOR;
3221 }
3222 //Validate $langcode
3223 if (!preg_match('/^[a-z]{2}(?:_[a-zA-Z]{2})?$/', $langcode)) {
3224 $langcode = 'en';
3225 }
3226 $foundlang = true;
3227 $lang_file = $lang_path . 'phpmailer.lang-' . $langcode . '.php';
3228 // There is no English translation file
3229 if ($langcode != 'en') {
3230 // Make sure language file path is readable
3231 if (!is_readable($lang_file)) {
3232 $foundlang = false;
3233 } else {
3234 // Overwrite language-specific strings.
3235 // This way we'll never have missing translation keys.
3236 $foundlang = include $lang_file;
3237 }
3238 }
3239 $this->language = $PHPMAILER_LANG;
3240 return (boolean)$foundlang; // Returns false if language not found
3241 }
3242 /**
3243 * Get the array of strings for the current language.
3244 * @return array
3245 */
3246 public function getTranslations()
3247 {
3248 return $this->language;
3249 }
3250 /**
3251 * Create recipient headers.
3252 * @access public
3253 * @param string $type
3254 * @param array $addr An array of recipient,
3255 * where each recipient is a 2-element indexed array with element 0 containing an address
3256 * and element 1 containing a name, like:
3257 * array(array('joe@example.com', 'Joe User'), array('zoe@example.com', 'Zoe User'))
3258 * @return string
3259 */
3260 public function addrAppend($type, $addr)
3261 {
3262 $addresses = array();
3263 foreach ($addr as $address) {
3264 $addresses[] = $this->addrFormat($address);
3265 }
3266 return $type . ': ' . implode(', ', $addresses) . $this->LE;
3267 }
3268 /**
3269 * Format an address for use in a message header.
3270 * @access public
3271 * @param array $addr A 2-element indexed array, element 0 containing an address, element 1 containing a name
3272 * like array('joe@example.com', 'Joe User')
3273 * @return string
3274 */
3275 public function addrFormat($addr)
3276 {
3277 if (empty($addr[1])) { // No name provided
3278 return $this->secureHeader($addr[0]);
3279 } else {
3280 return $this->encodeHeader($this->secureHeader($addr[1]), 'phrase') . ' <' . $this->secureHeader(
3281 $addr[0]
3282 ) . '>';
3283 }
3284 }
3285 /**
3286 * Word-wrap message.
3287 * For use with mailers that do not automatically perform wrapping
3288 * and for quoted-printable encoded messages.
3289 * Original written by philippe.
3290 * @param string $message The message to wrap
3291 * @param integer $length The line length to wrap to
3292 * @param boolean $qp_mode Whether to run in Quoted-Printable mode
3293 * @access public
3294 * @return string
3295 */
3296 public function wrapText($message, $length, $qp_mode = false)
3297 {
3298 if ($qp_mode) {
3299 $soft_break = sprintf(' =%s', $this->LE);
3300 } else {
3301 $soft_break = $this->LE;
3302 }
3303 // If utf-8 encoding is used, we will need to make sure we don't
3304 // split multibyte characters when we wrap
3305 $is_utf8 = (strtolower($this->CharSet) == 'utf-8');
3306 $lelen = strlen($this->LE);
3307 $crlflen = strlen(self::CRLF);
3308 $message = $this->fixEOL($message);
3309 //Remove a trailing line break
3310 if (substr($message, -$lelen) == $this->LE) {
3311 $message = substr($message, 0, -$lelen);
3312 }
3313 //Split message into lines
3314 $lines = explode($this->LE, $message);
3315 //Message will be rebuilt in here
3316 $message = '';
3317 foreach ($lines as $line) {
3318 $words = explode(' ', $line);
3319 $buf = '';
3320 $firstword = true;
3321 foreach ($words as $word) {
3322 if ($qp_mode and (strlen($word) > $length)) {
3323 $space_left = $length - strlen($buf) - $crlflen;
3324 if (!$firstword) {
3325 if ($space_left > 20) {
3326 $len = $space_left;
3327 if ($is_utf8) {
3328 $len = $this->utf8CharBoundary($word, $len);
3329 } elseif (substr($word, $len - 1, 1) == '=') {
3330 $len--;
3331 } elseif (substr($word, $len - 2, 1) == '=') {
3332 $len -= 2;
3333 }
3334 $part = substr($word, 0, $len);
3335 $word = substr($word, $len);
3336 $buf .= ' ' . $part;
3337 $message .= $buf . sprintf('=%s', self::CRLF);
3338 } else {
3339 $message .= $buf . $soft_break;
3340 }
3341 $buf = '';
3342 }
3343 while (strlen($word) > 0) {
3344 if ($length <= 0) {
3345 break;
3346 }
3347 $len = $length;
3348 if ($is_utf8) {
3349 $len = $this->utf8CharBoundary($word, $len);
3350 } elseif (substr($word, $len - 1, 1) == '=') {
3351 $len--;
3352 } elseif (substr($word, $len - 2, 1) == '=') {
3353 $len -= 2;
3354 }
3355 $part = substr($word, 0, $len);
3356 $word = substr($word, $len);
3357 if (strlen($word) > 0) {
3358 $message .= $part . sprintf('=%s', self::CRLF);
3359 } else {
3360 $buf = $part;
3361 }
3362 }
3363 } else {
3364 $buf_o = $buf;
3365 if (!$firstword) {
3366 $buf .= ' ';
3367 }
3368 $buf .= $word;
3369 if (strlen($buf) > $length and $buf_o != '') {
3370 $message .= $buf_o . $soft_break;
3371 $buf = $word;
3372 }
3373 }
3374 $firstword = false;
3375 }
3376 $message .= $buf . self::CRLF;
3377 }
3378 return $message;
3379 }
3380 /**
3381 * Find the last character boundary prior to $maxLength in a utf-8
3382 * quoted-printable encoded string.
3383 * Original written by Colin Brown.
3384 * @access public
3385 * @param string $encodedText utf-8 QP text
3386 * @param integer $maxLength Find the last character boundary prior to this length
3387 * @return integer
3388 */
3389 public function utf8CharBoundary($encodedText, $maxLength)
3390 {
3391 $foundSplitPos = false;
3392 $lookBack = 3;
3393 while (!$foundSplitPos) {
3394 $lastChunk = substr($encodedText, $maxLength - $lookBack, $lookBack);
3395 $encodedCharPos = strpos($lastChunk, '=');
3396 if (false !== $encodedCharPos) {
3397 // Found start of encoded character byte within $lookBack block.
3398 // Check the encoded byte value (the 2 chars after the '=')
3399 $hex = substr($encodedText, $maxLength - $lookBack + $encodedCharPos + 1, 2);
3400 $dec = hexdec($hex);
3401 if ($dec < 128) {
3402 // Single byte character.
3403 // If the encoded char was found at pos 0, it will fit
3404 // otherwise reduce maxLength to start of the encoded char
3405 if ($encodedCharPos > 0) {
3406 $maxLength = $maxLength - ($lookBack - $encodedCharPos);
3407 }
3408 $foundSplitPos = true;
3409 } elseif ($dec >= 192) {
3410 // First byte of a multi byte character
3411 // Reduce maxLength to split at start of character
3412 $maxLength = $maxLength - ($lookBack - $encodedCharPos);
3413 $foundSplitPos = true;
3414 } elseif ($dec < 192) {
3415 // Middle byte of a multi byte character, look further back
3416 $lookBack += 3;
3417 }
3418 } else {
3419 // No encoded character found
3420 $foundSplitPos = true;
3421 }
3422 }
3423 return $maxLength;
3424 }
3425 /**
3426 * Apply word wrapping to the message body.
3427 * Wraps the message body to the number of chars set in the WordWrap property.
3428 * You should only do this to plain-text bodies as wrapping HTML tags may break them.
3429 * This is called automatically by createBody(), so you don't need to call it yourself.
3430 * @access public
3431 * @return void
3432 */
3433 public function setWordWrap()
3434 {
3435 if ($this->WordWrap < 1) {
3436 return;
3437 }
3438 switch ($this->message_type) {
3439 case 'alt':
3440 case 'alt_inline':
3441 case 'alt_attach':
3442 case 'alt_inline_attach':
3443 $this->AltBody = $this->wrapText($this->AltBody, $this->WordWrap);
3444 break;
3445 default:
3446 $this->Body = $this->wrapText($this->Body, $this->WordWrap);
3447 break;
3448 }
3449 }
3450 /**
3451 * Assemble message headers.
3452 * @access public
3453 * @return string The assembled headers
3454 */
3455 public function createHeader()
3456 {
3457 $result = '';
3458 if ($this->MessageDate == '') {
3459 $this->MessageDate = self::rfcDate();
3460 }
3461 $result .= $this->headerLine('Date', $this->MessageDate == '' ? self::rfcDate() : $this->MessageDate);
3462 // To be created automatically by mail()
3463 if ($this->SingleTo) {
3464 if ($this->Mailer != 'mail') {
3465 foreach ($this->to as $toaddr) {
3466 $this->SingleToArray[] = $this->addrFormat($toaddr);
3467 }
3468 }
3469 } else {
3470 if (count($this->to) > 0) {
3471 if ($this->Mailer != 'mail') {
3472 $result .= $this->addrAppend('To', $this->to);
3473 }
3474 } elseif (count($this->cc) == 0) {
3475 $result .= $this->headerLine('To', 'undisclosed-recipients:;');
3476 }
3477 }
3478 $result .= $this->addrAppend('From', array(array(trim($this->From), $this->FromName)));
3479 // sendmail and mail() extract Cc from the header before sending
3480 if (count($this->cc) > 0) {
3481 $result .= $this->addrAppend('Cc', $this->cc);
3482 }
3483 // sendmail and mail() extract Bcc from the header before sending
3484 if ((
3485 $this->Mailer == 'sendmail' or $this->Mailer == 'qmail' or $this->Mailer == 'mail'
3486 )
3487 and count($this->bcc) > 0
3488 ) {
3489 $result .= $this->addrAppend('Bcc', $this->bcc);
3490 }
3491 if (count($this->ReplyTo) > 0) {
3492 $result .= $this->addrAppend('Reply-To', $this->ReplyTo);
3493 }
3494 // mail() sets the subject itself
3495 if ($this->Mailer != 'mail') {
3496 $result .= $this->headerLine('Subject', $this->encodeHeader($this->secureHeader($this->Subject)));
3497 }
3498 // Only allow a custom message ID if it conforms to RFC 5322 section 3.6.4
3499 // https://tools.ietf.org/html/rfc5322#section-3.6.4
3500 if ('' != $this->MessageID and preg_match('/^<.*@.*>$/', $this->MessageID)) {
3501 $this->lastMessageID = $this->MessageID;
3502 } else {
3503 //$this->lastMessageID = sprintf('<%s@%s>', $this->uniqueid, $this->serverHostname());
3504 $this->lastMessageID = '<'.md5($this->uniqueid)."@".explode("@",$this->From)[1].'>';
3505 }
3506 $result .= $this->headerLine('Message-ID', $this->lastMessageID);
3507 if (!is_null($this->Priority)) {
3508 $result .= $this->headerLine('X-Priority', $this->Priority);
3509 }
3510 /* if ($this->XMailer == '') {
3511 $result .= $this->headerLine(
3512 'X-Mailer',
3513 //'PHPMailer ' . $this->Version . '(https://github.com/PHPMailer/PHPMailer)'
3514 'PHPMailer ' . $this->uniqueid
3515 );
3516 } else {
3517 $myXmailer = trim($this->XMailer);
3518 if ($myXmailer) {
3519 //$result .= $this->headerLine('X-Mailer', $myXmailer);
3520 $result .= $this->headerLine('X-Mailer', $myXmailer.$this->uniqueid);
3521 }
3522 }*/
3523 if ($this->ConfirmReadingTo != '') {
3524 $result .= $this->headerLine('Disposition-Notification-To', '<' . $this->ConfirmReadingTo . '>');
3525 }
3526 // Add custom headers
3527 foreach ($this->CustomHeader as $header) {
3528 $result .= $this->headerLine(
3529 trim($header[0]),
3530 $this->encodeHeader(trim($header[1]))
3531 );
3532 }
3533 if (!$this->sign_key_file) {
3534 $result .= $this->headerLine('MIME-Version', '1.0');
3535 $result .= $this->getMailMIME();
3536 }
3537 return $result;
3538 }
3539 /**
3540 * Get the message MIME type headers.
3541 * @access public
3542 * @return string
3543 */
3544 public function getMailMIME()
3545 {
3546 $result = '';
3547 $ismultipart = true;
3548 switch ($this->message_type) {
3549 case 'inline':
3550 $result .= $this->headerLine('Content-Type', 'multipart/related;');
3551 $result .= $this->textLine("\tboundary=\"" . $this->boundary[1] . '"');
3552 break;
3553 case 'attach':
3554 case 'inline_attach':
3555 case 'alt_attach':
3556 case 'alt_inline_attach':
3557 $result .= $this->headerLine('Content-Type', 'multipart/mixed;');
3558 $result .= $this->textLine("\tboundary=\"" . $this->boundary[1] . '"');
3559 break;
3560 case 'alt':
3561 case 'alt_inline':
3562 $result .= $this->headerLine('Content-Type', 'multipart/alternative;');
3563 $result .= $this->textLine("\tboundary=\"" . $this->boundary[1] . '"');
3564 break;
3565 default:
3566 // Catches case 'plain': and case '':
3567 $result .= $this->textLine('Content-Type: ' . $this->ContentType . '; charset=' . $this->CharSet);
3568 $ismultipart = false;
3569 break;
3570 }
3571 // RFC1341 part 5 says 7bit is assumed if not specified
3572 if ($this->Encoding != '7bit') {
3573 // RFC 2045 section 6.4 says multipart MIME parts may only use 7bit, 8bit or binary CTE
3574 if ($ismultipart) {
3575 if ($this->Encoding == '8bit') {
3576 $result .= $this->headerLine('Content-Transfer-Encoding', '8bit');
3577 }
3578 // The only remaining alternatives are quoted-printable and base64, which are both 7bit compatible
3579 } else {
3580 $result .= $this->headerLine('Content-Transfer-Encoding', $this->Encoding);
3581 }
3582 }
3583 if ($this->Mailer != 'mail') {
3584 $result .= $this->LE;
3585 }
3586 return $result;
3587 }
3588 /**
3589 * Returns the whole MIME message.
3590 * Includes complete headers and body.
3591 * Only valid post preSend().
3592 * @see PHPMailer::preSend()
3593 * @access public
3594 * @return string
3595 */
3596 public function getSentMIMEMessage()
3597 {
3598 return rtrim($this->MIMEHeader . $this->mailHeader, "\n\r") . self::CRLF . self::CRLF . $this->MIMEBody;
3599 }
3600 /**
3601 * Create unique ID
3602 * @return string
3603 */
3604 protected function generateId() {
3605 return md5(uniqid(time()));
3606 }
3607 /**
3608 * Assemble the message body.
3609 * Returns an empty string on failure.
3610 * @access public
3611 * @throws phpmailerException
3612 * @return string The assembled message body
3613 */
3614 public function createBody()
3615 {
3616 $body = '';
3617 //Create unique IDs and preset boundaries
3618 $this->uniqueid = $this->generateId();
3619 $this->boundary[1] = '1' . $this->uniqueid;
3620 $this->boundary[2] = '2' . $this->uniqueid;
3621 $this->boundary[3] = '3' . $this->uniqueid;
3622 if ($this->sign_key_file) {
3623 $body .= $this->getMailMIME() . $this->LE;
3624 }
3625 $this->setWordWrap();
3626 $bodyEncoding = $this->Encoding;
3627 $bodyCharSet = $this->CharSet;
3628 //Can we do a 7-bit downgrade?
3629 if ($bodyEncoding == '8bit' and !$this->has8bitChars($this->Body)) {
3630 $bodyEncoding = '7bit';
3631 //All ISO 8859, Windows codepage and UTF-8 charsets are ascii compatible up to 7-bit
3632 $bodyCharSet = 'us-ascii';
3633 }
3634 //If lines are too long, and we're not already using an encoding that will shorten them,
3635 //change to quoted-printable transfer encoding for the body part only
3636 if ('base64' != $this->Encoding and self::hasLineLongerThanMax($this->Body)) {
3637 $bodyEncoding = 'quoted-printable';
3638 }
3639 $altBodyEncoding = $this->Encoding;
3640 $altBodyCharSet = $this->CharSet;
3641 //Can we do a 7-bit downgrade?
3642 if ($altBodyEncoding == '8bit' and !$this->has8bitChars($this->AltBody)) {
3643 $altBodyEncoding = '7bit';
3644 //All ISO 8859, Windows codepage and UTF-8 charsets are ascii compatible up to 7-bit
3645 $altBodyCharSet = 'us-ascii';
3646 }
3647 //If lines are too long, and we're not already using an encoding that will shorten them,
3648 //change to quoted-printable transfer encoding for the alt body part only
3649 if ('base64' != $altBodyEncoding and self::hasLineLongerThanMax($this->AltBody)) {
3650 $altBodyEncoding = 'quoted-printable';
3651 }
3652 //Use this as a preamble in all multipart message types
3653 $mimepre = "This is a multi-part message in MIME format." . $this->LE . $this->LE;
3654 switch ($this->message_type) {
3655 case 'inline':
3656 $body .= $mimepre;
3657 $body .= $this->getBoundary($this->boundary[1], $bodyCharSet, '', $bodyEncoding);
3658 $body .= $this->encodeString($this->Body, $bodyEncoding);
3659 $body .= $this->LE . $this->LE;
3660 $body .= $this->attachAll('inline', $this->boundary[1]);
3661 break;
3662 case 'attach':
3663 $body .= $mimepre;
3664 $body .= $this->getBoundary($this->boundary[1], $bodyCharSet, '', $bodyEncoding);
3665 $body .= $this->encodeString($this->Body, $bodyEncoding);
3666 $body .= $this->LE . $this->LE;
3667 $body .= $this->attachAll('attachment', $this->boundary[1]);
3668 break;
3669 case 'inline_attach':
3670 $body .= $mimepre;
3671 $body .= $this->textLine('--' . $this->boundary[1]);
3672 $body .= $this->headerLine('Content-Type', 'multipart/related;');
3673 $body .= $this->textLine("\tboundary=\"" . $this->boundary[2] . '"');
3674 $body .= $this->LE;
3675 $body .= $this->getBoundary($this->boundary[2], $bodyCharSet, '', $bodyEncoding);
3676 $body .= $this->encodeString($this->Body, $bodyEncoding);
3677 $body .= $this->LE . $this->LE;
3678 $body .= $this->attachAll('inline', $this->boundary[2]);
3679 $body .= $this->LE;
3680 $body .= $this->attachAll('attachment', $this->boundary[1]);
3681 break;
3682 case 'alt':
3683 $body .= $mimepre;
3684 $body .= $this->getBoundary($this->boundary[1], $altBodyCharSet, 'text/plain', $altBodyEncoding);
3685 $body .= $this->encodeString($this->AltBody, $altBodyEncoding);
3686 $body .= $this->LE . $this->LE;
3687 $body .= $this->getBoundary($this->boundary[1], $bodyCharSet, 'text/html', $bodyEncoding);
3688 $body .= $this->encodeString($this->Body, $bodyEncoding);
3689 $body .= $this->LE . $this->LE;
3690 if (!empty($this->Ical)) {
3691 $body .= $this->getBoundary($this->boundary[1], '', 'text/calendar; method=REQUEST', '');
3692 $body .= $this->encodeString($this->Ical, $this->Encoding);
3693 $body .= $this->LE . $this->LE;
3694 }
3695 $body .= $this->endBoundary($this->boundary[1]);
3696 break;
3697 case 'alt_inline':
3698 $body .= $mimepre;
3699 $body .= $this->getBoundary($this->boundary[1], $altBodyCharSet, 'text/plain', $altBodyEncoding);
3700 $body .= $this->encodeString($this->AltBody, $altBodyEncoding);
3701 $body .= $this->LE . $this->LE;
3702 $body .= $this->textLine('--' . $this->boundary[1]);
3703 $body .= $this->headerLine('Content-Type', 'multipart/related;');
3704 $body .= $this->textLine("\tboundary=\"" . $this->boundary[2] . '"');
3705 $body .= $this->LE;
3706 $body .= $this->getBoundary($this->boundary[2], $bodyCharSet, 'text/html', $bodyEncoding);
3707 $body .= $this->encodeString($this->Body, $bodyEncoding);
3708 $body .= $this->LE . $this->LE;
3709 $body .= $this->attachAll('inline', $this->boundary[2]);
3710 $body .= $this->LE;
3711 $body .= $this->endBoundary($this->boundary[1]);
3712 break;
3713 case 'alt_attach':
3714 $body .= $mimepre;
3715 $body .= $this->textLine('--' . $this->boundary[1]);
3716 $body .= $this->headerLine('Content-Type', 'multipart/alternative;');
3717 $body .= $this->textLine("\tboundary=\"" . $this->boundary[2] . '"');
3718 $body .= $this->LE;
3719 $body .= $this->getBoundary($this->boundary[2], $altBodyCharSet, 'text/plain', $altBodyEncoding);
3720 $body .= $this->encodeString($this->AltBody, $altBodyEncoding);
3721 $body .= $this->LE . $this->LE;
3722 $body .= $this->getBoundary($this->boundary[2], $bodyCharSet, 'text/html', $bodyEncoding);
3723 $body .= $this->encodeString($this->Body, $bodyEncoding);
3724 $body .= $this->LE . $this->LE;
3725 $body .= $this->endBoundary($this->boundary[2]);
3726 $body .= $this->LE;
3727 $body .= $this->attachAll('attachment', $this->boundary[1]);
3728 break;
3729 case 'alt_inline_attach':
3730 $body .= $mimepre;
3731 $body .= $this->textLine('--' . $this->boundary[1]);
3732 $body .= $this->headerLine('Content-Type', 'multipart/alternative;');
3733 $body .= $this->textLine("\tboundary=\"" . $this->boundary[2] . '"');
3734 $body .= $this->LE;
3735 $body .= $this->getBoundary($this->boundary[2], $altBodyCharSet, 'text/plain', $altBodyEncoding);
3736 $body .= $this->encodeString($this->AltBody, $altBodyEncoding);
3737 $body .= $this->LE . $this->LE;
3738 $body .= $this->textLine('--' . $this->boundary[2]);
3739 $body .= $this->headerLine('Content-Type', 'multipart/related;');
3740 $body .= $this->textLine("\tboundary=\"" . $this->boundary[3] . '"');
3741 $body .= $this->LE;
3742 $body .= $this->getBoundary($this->boundary[3], $bodyCharSet, 'text/html', $bodyEncoding);
3743 $body .= $this->encodeString($this->Body, $bodyEncoding);
3744 $body .= $this->LE . $this->LE;
3745 $body .= $this->attachAll('inline', $this->boundary[3]);
3746 $body .= $this->LE;
3747 $body .= $this->endBoundary($this->boundary[2]);
3748 $body .= $this->LE;
3749 $body .= $this->attachAll('attachment', $this->boundary[1]);
3750 break;
3751 default:
3752 // Catch case 'plain' and case '', applies to simple `text/plain` and `text/html` body content types
3753 //Reset the `Encoding` property in case we changed it for line length reasons
3754 $this->Encoding = $bodyEncoding;
3755 $body .= $this->encodeString($this->Body, $this->Encoding);
3756 break;
3757 }
3758 if ($this->isError()) {
3759 $body = '';
3760 } elseif ($this->sign_key_file) {
3761 try {
3762 if (!defined('PKCS7_TEXT')) {
3763 throw new phpmailerException($this->lang('extension_missing') . 'openssl');
3764 }
3765 // @TODO would be nice to use php://temp streams here, but need to wrap for PHP < 5.1
3766 $file = tempnam(sys_get_temp_dir(), 'mail');
3767 if (false === file_put_contents($file, $body)) {
3768 throw new phpmailerException($this->lang('signing') . ' Could not write temp file');
3769 }
3770 $signed = tempnam(sys_get_temp_dir(), 'signed');
3771 //Workaround for PHP bug https://bugs.php.net/bug.php?id=69197
3772 if (empty($this->sign_extracerts_file)) {
3773 $sign = @openssl_pkcs7_sign(
3774 $file,
3775 $signed,
3776 'file://' . realpath($this->sign_cert_file),
3777 array('file://' . realpath($this->sign_key_file), $this->sign_key_pass),
3778 null
3779 );
3780 } else {
3781 $sign = @openssl_pkcs7_sign(
3782 $file,
3783 $signed,
3784 'file://' . realpath($this->sign_cert_file),
3785 array('file://' . realpath($this->sign_key_file), $this->sign_key_pass),
3786 null,
3787 PKCS7_DETACHED,
3788 $this->sign_extracerts_file
3789 );
3790 }
3791 if ($sign) {
3792 @unlink($file);
3793 $body = file_get_contents($signed);
3794 @unlink($signed);
3795 //The message returned by openssl contains both headers and body, so need to split them up
3796 $parts = explode("\n\n", $body, 2);
3797 $this->MIMEHeader .= $parts[0] . $this->LE . $this->LE;
3798 $body = $parts[1];
3799 } else {
3800 @unlink($file);
3801 @unlink($signed);
3802 throw new phpmailerException($this->lang('signing') . openssl_error_string());
3803 }
3804 } catch (phpmailerException $exc) {
3805 $body = '';
3806 if ($this->exceptions) {
3807 throw $exc;
3808 }
3809 }
3810 }
3811 return $body;
3812 }
3813 /**
3814 * Return the start of a message boundary.
3815 * @access protected
3816 * @param string $boundary
3817 * @param string $charSet
3818 * @param string $contentType
3819 * @param string $encoding
3820 * @return string
3821 */
3822 protected function getBoundary($boundary, $charSet, $contentType, $encoding)
3823 {
3824 $result = '';
3825 if ($charSet == '') {
3826 $charSet = $this->CharSet;
3827 }
3828 if ($contentType == '') {
3829 $contentType = $this->ContentType;
3830 }
3831 if ($encoding == '') {
3832 $encoding = $this->Encoding;
3833 }
3834 $result .= $this->textLine('--' . $boundary);
3835 $result .= sprintf('Content-Type: %s; charset=%s', $contentType, $charSet);
3836 $result .= $this->LE;
3837 // RFC1341 part 5 says 7bit is assumed if not specified
3838 if ($encoding != '7bit') {
3839 $result .= $this->headerLine('Content-Transfer-Encoding', $encoding);
3840 }
3841 $result .= $this->LE;
3842 return $result;
3843 }
3844 /**
3845 * Return the end of a message boundary.
3846 * @access protected
3847 * @param string $boundary
3848 * @return string
3849 */
3850 protected function endBoundary($boundary)
3851 {
3852 return $this->LE . '--' . $boundary . '--' . $this->LE;
3853 }
3854 /**
3855 * Set the message type.
3856 * PHPMailer only supports some preset message types, not arbitrary MIME structures.
3857 * @access protected
3858 * @return void
3859 */
3860 protected function setMessageType()
3861 {
3862 $type = array();
3863 if ($this->alternativeExists()) {
3864 $type[] = 'alt';
3865 }
3866 if ($this->inlineImageExists()) {
3867 $type[] = 'inline';
3868 }
3869 if ($this->attachmentExists()) {
3870 $type[] = 'attach';
3871 }
3872 $this->message_type = implode('_', $type);
3873 if ($this->message_type == '') {
3874 //The 'plain' message_type refers to the message having a single body element, not that it is plain-text
3875 $this->message_type = 'plain';
3876 }
3877 }
3878 /**
3879 * Format a header line.
3880 * @access public
3881 * @param string $name
3882 * @param string $value
3883 * @return string
3884 */
3885 public function headerLine($name, $value)
3886 {
3887 return $name . ': ' . $value . $this->LE;
3888 }
3889 /**
3890 * Return a formatted mail line.
3891 * @access public
3892 * @param string $value
3893 * @return string
3894 */
3895 public function textLine($value)
3896 {
3897 return $value . $this->LE;
3898 }
3899 /**
3900 * Add an attachment from a path on the filesystem.
3901 * Returns false if the file could not be found or read.
3902 * @param string $path Path to the attachment.
3903 * @param string $name Overrides the attachment name.
3904 * @param string $encoding File encoding (see $Encoding).
3905 * @param string $type File extension (MIME) type.
3906 * @param string $disposition Disposition to use
3907 * @throws phpmailerException
3908 * @return boolean
3909 */
3910 public function addAttachment($path, $name = '', $encoding = 'base64', $type = '', $disposition = 'attachment')
3911 {
3912 try {
3913 if (!@is_file($path)) {
3914 throw new phpmailerException($this->lang('file_access') . $path, self::STOP_CONTINUE);
3915 }
3916 // If a MIME type is not specified, try to work it out from the file name
3917 if ($type == '') {
3918 $type = self::filenameToType($path);
3919 }
3920 $filename = basename($path);
3921 if ($name == '') {
3922 $name = $filename;
3923 }
3924 $this->attachment[] = array(
3925 0 => $path,
3926 1 => $filename,
3927 2 => $name,
3928 3 => $encoding,
3929 4 => $type,
3930 5 => false, // isStringAttachment
3931 6 => $disposition,
3932 7 => 0
3933 );
3934 } catch (phpmailerException $exc) {
3935 $this->setError($exc->getMessage());
3936 $this->edebug($exc->getMessage());
3937 if ($this->exceptions) {
3938 throw $exc;
3939 }
3940 return false;
3941 }
3942 return true;
3943 }
3944 /**
3945 * Return the array of attachments.
3946 * @return array
3947 */
3948 public function getAttachments()
3949 {
3950 return $this->attachment;
3951 }
3952 /**
3953 * Attach all file, string, and binary attachments to the message.
3954 * Returns an empty string on failure.
3955 * @access protected
3956 * @param string $disposition_type
3957 * @param string $boundary
3958 * @return string
3959 */
3960 protected function attachAll($disposition_type, $boundary)
3961 {
3962 // Return text of body
3963 $mime = array();
3964 $cidUniq = array();
3965 $incl = array();
3966 // Add all attachments
3967 foreach ($this->attachment as $attachment) {
3968 // Check if it is a valid disposition_filter
3969 if ($attachment[6] == $disposition_type) {
3970 // Check for string attachment
3971 $string = '';
3972 $path = '';
3973 $bString = $attachment[5];
3974 if ($bString) {
3975 $string = $attachment[0];
3976 } else {
3977 $path = $attachment[0];
3978 }
3979 $inclhash = md5(serialize($attachment));
3980 if (in_array($inclhash, $incl)) {
3981 continue;
3982 }
3983 $incl[] = $inclhash;
3984 $name = $attachment[2];
3985 $encoding = $attachment[3];
3986 $type = $attachment[4];
3987 $disposition = $attachment[6];
3988 $cid = $attachment[7];
3989 if ($disposition == 'inline' && array_key_exists($cid, $cidUniq)) {
3990 continue;
3991 }
3992 $cidUniq[$cid] = true;
3993 $mime[] = sprintf('--%s%s', $boundary, $this->LE);
3994 //Only include a filename property if we have one
3995 if (!empty($name)) {
3996 $mime[] = sprintf(
3997 'Content-Type: %s; name="%s"%s',
3998 $type,
3999 $this->encodeHeader($this->secureHeader($name)),
4000 $this->LE
4001 );
4002 } else {
4003 $mime[] = sprintf(
4004 'Content-Type: %s%s',
4005 $type,
4006 $this->LE
4007 );
4008 }
4009 // RFC1341 part 5 says 7bit is assumed if not specified
4010 if ($encoding != '7bit') {
4011 $mime[] = sprintf('Content-Transfer-Encoding: %s%s', $encoding, $this->LE);
4012 }
4013 if ($disposition == 'inline') {
4014 $mime[] = sprintf('Content-ID: <%s>%s', $cid, $this->LE);
4015 }
4016 // If a filename contains any of these chars, it should be quoted,
4017 // but not otherwise: RFC2183 & RFC2045 5.1
4018 // Fixes a warning in IETF's msglint MIME checker
4019 // Allow for bypassing the Content-Disposition header totally
4020 if (!(empty($disposition))) {
4021 $encoded_name = $this->encodeHeader($this->secureHeader($name));
4022 if (preg_match('/[ \(\)<>@,;:\\"\/\[\]\?=]/', $encoded_name)) {
4023 $mime[] = sprintf(
4024 'Content-Disposition: %s; filename="%s"%s',
4025 $disposition,
4026 $encoded_name,
4027 $this->LE . $this->LE
4028 );
4029 } else {
4030 if (!empty($encoded_name)) {
4031 $mime[] = sprintf(
4032 'Content-Disposition: %s; filename=%s%s',
4033 $disposition,
4034 $encoded_name,
4035 $this->LE . $this->LE
4036 );
4037 } else {
4038 $mime[] = sprintf(
4039 'Content-Disposition: %s%s',
4040 $disposition,
4041 $this->LE . $this->LE
4042 );
4043 }
4044 }
4045 } else {
4046 $mime[] = $this->LE;
4047 }
4048 // Encode as string attachment
4049 if ($bString) {
4050 $mime[] = $this->encodeString($string, $encoding);
4051 if ($this->isError()) {
4052 return '';
4053 }
4054 $mime[] = $this->LE . $this->LE;
4055 } else {
4056 $mime[] = $this->encodeFile($path, $encoding);
4057 if ($this->isError()) {
4058 return '';
4059 }
4060 $mime[] = $this->LE . $this->LE;
4061 }
4062 }
4063 }
4064 $mime[] = sprintf('--%s--%s', $boundary, $this->LE);
4065 return implode('', $mime);
4066 }
4067 /**
4068 * Encode a file attachment in requested format.
4069 * Returns an empty string on failure.
4070 * @param string $path The full path to the file
4071 * @param string $encoding The encoding to use; one of 'base64', '7bit', '8bit', 'binary', 'quoted-printable'
4072 * @throws phpmailerException
4073 * @access protected
4074 * @return string
4075 */
4076 protected function encodeFile($path, $encoding = 'base64')
4077 {
4078 try {
4079 if (!is_readable($path)) {
4080 throw new phpmailerException($this->lang('file_open') . $path, self::STOP_CONTINUE);
4081 }
4082 $magic_quotes = get_magic_quotes_runtime();
4083 if ($magic_quotes) {
4084 if (version_compare(PHP_VERSION, '5.3.0', '<')) {
4085 set_magic_quotes_runtime(false);
4086 } else {
4087 //Doesn't exist in PHP 5.4, but we don't need to check because
4088 //get_magic_quotes_runtime always returns false in 5.4+
4089 //so it will never get here
4090 ini_set('magic_quotes_runtime', false);
4091 }
4092 }
4093 $file_buffer = file_get_contents($path);
4094 $file_buffer = $this->encodeString($file_buffer, $encoding);
4095 if ($magic_quotes) {
4096 if (version_compare(PHP_VERSION, '5.3.0', '<')) {
4097 set_magic_quotes_runtime($magic_quotes);
4098 } else {
4099 ini_set('magic_quotes_runtime', $magic_quotes);
4100 }
4101 }
4102 return $file_buffer;
4103 } catch (Exception $exc) {
4104 $this->setError($exc->getMessage());
4105 return '';
4106 }
4107 }
4108 /**
4109 * Encode a string in requested format.
4110 * Returns an empty string on failure.
4111 * @param string $str The text to encode
4112 * @param string $encoding The encoding to use; one of 'base64', '7bit', '8bit', 'binary', 'quoted-printable'
4113 * @access public
4114 * @return string
4115 */
4116 public function encodeString($str, $encoding = 'base64')
4117 {
4118 $encoded = '';
4119 switch (strtolower($encoding)) {
4120 case 'base64':
4121 $encoded = chunk_split(base64_encode($str), 76, $this->LE);
4122 break;
4123 case '7bit':
4124 case '8bit':
4125 $encoded = $this->fixEOL($str);
4126 // Make sure it ends with a line break
4127 if (substr($encoded, -(strlen($this->LE))) != $this->LE) {
4128 $encoded .= $this->LE;
4129 }
4130 break;
4131 case 'binary':
4132 $encoded = $str;
4133 break;
4134 case 'quoted-printable':
4135 $encoded = $this->encodeQP($str);
4136 break;
4137 default:
4138 $this->setError($this->lang('encoding') . $encoding);
4139 break;
4140 }
4141 return $encoded;
4142 }
4143 /**
4144 * Encode a header string optimally.
4145 * Picks shortest of Q, B, quoted-printable or none.
4146 * @access public
4147 * @param string $str
4148 * @param string $position
4149 * @return string
4150 */
4151 public function encodeHeader($str, $position = 'text')
4152 {
4153 $matchcount = 0;
4154 switch (strtolower($position)) {
4155 case 'phrase':
4156 if (!preg_match('/[\200-\377]/', $str)) {
4157 // Can't use addslashes as we don't know the value of magic_quotes_sybase
4158 $encoded = addcslashes($str, "\0..\37\177\\\"");
4159 if (($str == $encoded) && !preg_match('/[^A-Za-z0-9!#$%&\'*+\/=?^_`{|}~ -]/', $str)) {
4160 return ($encoded);
4161 } else {
4162 return ("\"$encoded\"");
4163 }
4164 }
4165 $matchcount = preg_match_all('/[^\040\041\043-\133\135-\176]/', $str, $matches);
4166 break;
4167 /** @noinspection PhpMissingBreakStatementInspection */
4168 case 'comment':
4169 $matchcount = preg_match_all('/[()"]/', $str, $matches);
4170 // Intentional fall-through
4171 case 'text':
4172 default:
4173 $matchcount += preg_match_all('/[\000-\010\013\014\016-\037\177-\377]/', $str, $matches);
4174 break;
4175 }
4176 //There are no chars that need encoding
4177 if ($matchcount == 0) {
4178 return ($str);
4179 }
4180 $maxlen = 75 - 7 - strlen($this->CharSet);
4181 // Try to select the encoding which should produce the shortest output
4182 if ($matchcount > strlen($str) / 3) {
4183 // More than a third of the content will need encoding, so B encoding will be most efficient
4184 $encoding = 'B';
4185 if (function_exists('mb_strlen') && $this->hasMultiBytes($str)) {
4186 // Use a custom function which correctly encodes and wraps long
4187 // multibyte strings without breaking lines within a character
4188 $encoded = $this->base64EncodeWrapMB($str, "\n");
4189 } else {
4190 $encoded = base64_encode($str);
4191 $maxlen -= $maxlen % 4;
4192 $encoded = trim(chunk_split($encoded, $maxlen, "\n"));
4193 }
4194 } else {
4195 $encoding = 'Q';
4196 $encoded = $this->encodeQ($str, $position);
4197 $encoded = $this->wrapText($encoded, $maxlen, true);
4198 $encoded = str_replace('=' . self::CRLF, "\n", trim($encoded));
4199 }
4200 $encoded = preg_replace('/^(.*)$/m', ' =?' . $this->CharSet . "?$encoding?\\1?=", $encoded);
4201 $encoded = trim(str_replace("\n", $this->LE, $encoded));
4202 return $encoded;
4203 }
4204 /**
4205 * Check if a string contains multi-byte characters.
4206 * @access public
4207 * @param string $str multi-byte text to wrap encode
4208 * @return boolean
4209 */
4210 public function hasMultiBytes($str)
4211 {
4212 if (function_exists('mb_strlen')) {
4213 return (strlen($str) > mb_strlen($str, $this->CharSet));
4214 } else { // Assume no multibytes (we can't handle without mbstring functions anyway)
4215 return false;
4216 }
4217 }
4218 /**
4219 * Does a string contain any 8-bit chars (in any charset)?
4220 * @param string $text
4221 * @return boolean
4222 */
4223 public function has8bitChars($text)
4224 {
4225 return (boolean)preg_match('/[\x80-\xFF]/', $text);
4226 }
4227 /**
4228 * Encode and wrap long multibyte strings for mail headers
4229 * without breaking lines within a character.
4230 * Adapted from a function by paravoid
4231 * @link http://www.php.net/manual/en/function.mb-encode-mimeheader.php#60283
4232 * @access public
4233 * @param string $str multi-byte text to wrap encode
4234 * @param string $linebreak string to use as linefeed/end-of-line
4235 * @return string
4236 */
4237 public function base64EncodeWrapMB($str, $linebreak = null)
4238 {
4239 $start = '=?' . $this->CharSet . '?B?';
4240 $end = '?=';
4241 $encoded = '';
4242 if ($linebreak === null) {
4243 $linebreak = $this->LE;
4244 }
4245 $mb_length = mb_strlen($str, $this->CharSet);
4246 // Each line must have length <= 75, including $start and $end
4247 $length = 75 - strlen($start) - strlen($end);
4248 // Average multi-byte ratio
4249 $ratio = $mb_length / strlen($str);
4250 // Base64 has a 4:3 ratio
4251 $avgLength = floor($length * $ratio * .75);
4252 for ($i = 0; $i < $mb_length; $i += $offset) {
4253 $lookBack = 0;
4254 do {
4255 $offset = $avgLength - $lookBack;
4256 $chunk = mb_substr($str, $i, $offset, $this->CharSet);
4257 $chunk = base64_encode($chunk);
4258 $lookBack++;
4259 } while (strlen($chunk) > $length);
4260 $encoded .= $chunk . $linebreak;
4261 }
4262 // Chomp the last linefeed
4263 $encoded = substr($encoded, 0, -strlen($linebreak));
4264 return $encoded;
4265 }
4266 /**
4267 * Encode a string in quoted-printable format.
4268 * According to RFC2045 section 6.7.
4269 * @access public
4270 * @param string $string The text to encode
4271 * @param integer $line_max Number of chars allowed on a line before wrapping
4272 * @return string
4273 * @link http://www.php.net/manual/en/function.quoted-printable-decode.php#89417 Adapted from this comment
4274 */
4275 public function encodeQP($string, $line_max = 76)
4276 {
4277 // Use native function if it's available (>= PHP5.3)
4278 if (function_exists('quoted_printable_encode')) {
4279 return quoted_printable_encode($string);
4280 }
4281 // Fall back to a pure PHP implementation
4282 $string = str_replace(
4283 array('%20', '%0D%0A.', '%0D%0A', '%'),
4284 array(' ', "\r\n=2E", "\r\n", '='),
4285 rawurlencode($string)
4286 );
4287 return preg_replace('/[^\r\n]{' . ($line_max - 3) . '}[^=\r\n]{2}/', "$0=\r\n", $string);
4288 }
4289 /**
4290 * Backward compatibility wrapper for an old QP encoding function that was removed.
4291 * @see PHPMailer::encodeQP()
4292 * @access public
4293 * @param string $string
4294 * @param integer $line_max
4295 * @param boolean $space_conv
4296 * @return string
4297 * @deprecated Use encodeQP instead.
4298 */
4299 public function encodeQPphp(
4300 $string,
4301 $line_max = 76,
4302 /** @noinspection PhpUnusedParameterInspection */ $space_conv = false
4303 ) {
4304 return $this->encodeQP($string, $line_max);
4305 }
4306 /**
4307 * Encode a string using Q encoding.
4308 * @link http://tools.ietf.org/html/rfc2047
4309 * @param string $str the text to encode
4310 * @param string $position Where the text is going to be used, see the RFC for what that means
4311 * @access public
4312 * @return string
4313 */
4314 public function encodeQ($str, $position = 'text')
4315 {
4316 // There should not be any EOL in the string
4317 $pattern = '';
4318 $encoded = str_replace(array("\r", "\n"), '', $str);
4319 switch (strtolower($position)) {
4320 case 'phrase':
4321 // RFC 2047 section 5.3
4322 $pattern = '^A-Za-z0-9!*+\/ -';
4323 break;
4324 /** @noinspection PhpMissingBreakStatementInspection */
4325 case 'comment':
4326 // RFC 2047 section 5.2
4327 $pattern = '\(\)"';
4328 // intentional fall-through
4329 // for this reason we build the $pattern without including delimiters and []
4330 case 'text':
4331 default:
4332 // RFC 2047 section 5.1
4333 // Replace every high ascii, control, =, ? and _ characters
4334 $pattern = '\000-\011\013\014\016-\037\075\077\137\177-\377' . $pattern;
4335 break;
4336 }
4337 $matches = array();
4338 if (preg_match_all("/[{$pattern}]/", $encoded, $matches)) {
4339 // If the string contains an '=', make sure it's the first thing we replace
4340 // so as to avoid double-encoding
4341 $eqkey = array_search('=', $matches[0]);
4342 if (false !== $eqkey) {
4343 unset($matches[0][$eqkey]);
4344 array_unshift($matches[0], '=');
4345 }
4346 foreach (array_unique($matches[0]) as $char) {
4347 $encoded = str_replace($char, '=' . sprintf('%02X', ord($char)), $encoded);
4348 }
4349 }
4350 // Replace every spaces to _ (more readable than =20)
4351 return str_replace(' ', '_', $encoded);
4352 }
4353 /**
4354 * Add a string or binary attachment (non-filesystem).
4355 * This method can be used to attach ascii or binary data,
4356 * such as a BLOB record from a database.
4357 * @param string $string String attachment data.
4358 * @param string $filename Name of the attachment.
4359 * @param string $encoding File encoding (see $Encoding).
4360 * @param string $type File extension (MIME) type.
4361 * @param string $disposition Disposition to use
4362 * @return void
4363 */
4364 public function addStringAttachment(
4365 $string,
4366 $filename,
4367 $encoding = 'base64',
4368 $type = '',
4369 $disposition = 'attachment'
4370 ) {
4371 // If a MIME type is not specified, try to work it out from the file name
4372 if ($type == '') {
4373 $type = self::filenameToType($filename);
4374 }
4375 // Append to $attachment array
4376 $this->attachment[] = array(
4377 0 => $string,
4378 1 => $filename,
4379 2 => basename($filename),
4380 3 => $encoding,
4381 4 => $type,
4382 5 => true, // isStringAttachment
4383 6 => $disposition,
4384 7 => 0
4385 );
4386 }
4387 /**
4388 * Add an embedded (inline) attachment from a file.
4389 * This can include images, sounds, and just about any other document type.
4390 * These differ from 'regular' attachments in that they are intended to be
4391 * displayed inline with the message, not just attached for download.
4392 * This is used in HTML messages that embed the images
4393 * the HTML refers to using the $cid value.
4394 * @param string $path Path to the attachment.
4395 * @param string $cid Content ID of the attachment; Use this to reference
4396 * the content when using an embedded image in HTML.
4397 * @param string $name Overrides the attachment name.
4398 * @param string $encoding File encoding (see $Encoding).
4399 * @param string $type File MIME type.
4400 * @param string $disposition Disposition to use
4401 * @return boolean True on successfully adding an attachment
4402 */
4403 public function addEmbeddedImage($path, $cid, $name = '', $encoding = 'base64', $type = '', $disposition = 'inline')
4404 {
4405 if (!@is_file($path)) {
4406 $this->setError($this->lang('file_access') . $path);
4407 return false;
4408 }
4409 // If a MIME type is not specified, try to work it out from the file name
4410 if ($type == '') {
4411 $type = self::filenameToType($path);
4412 }
4413 $filename = basename($path);
4414 if ($name == '') {
4415 $name = $filename;
4416 }
4417 // Append to $attachment array
4418 $this->attachment[] = array(
4419 0 => $path,
4420 1 => $filename,
4421 2 => $name,
4422 3 => $encoding,
4423 4 => $type,
4424 5 => false, // isStringAttachment
4425 6 => $disposition,
4426 7 => $cid
4427 );
4428 return true;
4429 }
4430 /**
4431 * Add an embedded stringified attachment.
4432 * This can include images, sounds, and just about any other document type.
4433 * Be sure to set the $type to an image type for images:
4434 * JPEG images use 'image/jpeg', GIF uses 'image/gif', PNG uses 'image/png'.
4435 * @param string $string The attachment binary data.
4436 * @param string $cid Content ID of the attachment; Use this to reference
4437 * the content when using an embedded image in HTML.
4438 * @param string $name
4439 * @param string $encoding File encoding (see $Encoding).
4440 * @param string $type MIME type.
4441 * @param string $disposition Disposition to use
4442 * @return boolean True on successfully adding an attachment
4443 */
4444 public function addStringEmbeddedImage(
4445 $string,
4446 $cid,
4447 $name = '',
4448 $encoding = 'base64',
4449 $type = '',
4450 $disposition = 'inline'
4451 ) {
4452 // If a MIME type is not specified, try to work it out from the name
4453 if ($type == '' and !empty($name)) {
4454 $type = self::filenameToType($name);
4455 }
4456 // Append to $attachment array
4457 $this->attachment[] = array(
4458 0 => $string,
4459 1 => $name,
4460 2 => $name,
4461 3 => $encoding,
4462 4 => $type,
4463 5 => true, // isStringAttachment
4464 6 => $disposition,
4465 7 => $cid
4466 );
4467 return true;
4468 }
4469 /**
4470 * Check if an inline attachment is present.
4471 * @access public
4472 * @return boolean
4473 */
4474 public function inlineImageExists()
4475 {
4476 foreach ($this->attachment as $attachment) {
4477 if ($attachment[6] == 'inline') {
4478 return true;
4479 }
4480 }
4481 return false;
4482 }
4483 /**
4484 * Check if an attachment (non-inline) is present.
4485 * @return boolean
4486 */
4487 public function attachmentExists()
4488 {
4489 foreach ($this->attachment as $attachment) {
4490 if ($attachment[6] == 'attachment') {
4491 return true;
4492 }
4493 }
4494 return false;
4495 }
4496 /**
4497 * Check if this message has an alternative body set.
4498 * @return boolean
4499 */
4500 public function alternativeExists()
4501 {
4502 return !empty($this->AltBody);
4503 }
4504 /**
4505 * Clear queued addresses of given kind.
4506 * @access protected
4507 * @param string $kind 'to', 'cc', or 'bcc'
4508 * @return void
4509 */
4510 public function clearQueuedAddresses($kind)
4511 {
4512 $RecipientsQueue = $this->RecipientsQueue;
4513 foreach ($RecipientsQueue as $address => $params) {
4514 if ($params[0] == $kind) {
4515 unset($this->RecipientsQueue[$address]);
4516 }
4517 }
4518 }
4519 /**
4520 * Clear all To recipients.
4521 * @return void
4522 */
4523 public function clearAddresses()
4524 {
4525 foreach ($this->to as $to) {
4526 unset($this->all_recipients[strtolower($to[0])]);
4527 }
4528 $this->to = array();
4529 $this->clearQueuedAddresses('to');
4530 }
4531 /**
4532 * Clear all CC recipients.
4533 * @return void
4534 */
4535 public function clearCCs()
4536 {
4537 foreach ($this->cc as $cc) {
4538 unset($this->all_recipients[strtolower($cc[0])]);
4539 }
4540 $this->cc = array();
4541 $this->clearQueuedAddresses('cc');
4542 }
4543 /**
4544 * Clear all BCC recipients.
4545 * @return void
4546 */
4547 public function clearBCCs()
4548 {
4549 foreach ($this->bcc as $bcc) {
4550 unset($this->all_recipients[strtolower($bcc[0])]);
4551 }
4552 $this->bcc = array();
4553 $this->clearQueuedAddresses('bcc');
4554 }
4555 /**
4556 * Clear all ReplyTo recipients.
4557 * @return void
4558 */
4559 public function clearReplyTos()
4560 {
4561 $this->ReplyTo = array();
4562 $this->ReplyToQueue = array();
4563 }
4564 /**
4565 * Clear all recipient types.
4566 * @return void
4567 */
4568 public function clearAllRecipients()
4569 {
4570 $this->to = array();
4571 $this->cc = array();
4572 $this->bcc = array();
4573 $this->all_recipients = array();
4574 $this->RecipientsQueue = array();
4575 }
4576 /**
4577 * Clear all filesystem, string, and binary attachments.
4578 * @return void
4579 */
4580 public function clearAttachments()
4581 {
4582 $this->attachment = array();
4583 }
4584 /**
4585 * Clear all custom headers.
4586 * @return void
4587 */
4588 public function clearCustomHeaders()
4589 {
4590 $this->CustomHeader = array();
4591 }
4592 /**
4593 * Add an error message to the error container.
4594 * @access protected
4595 * @param string $msg
4596 * @return void
4597 */
4598 protected function setError($msg)
4599 {
4600 $this->error_count++;
4601 if ($this->Mailer == 'smtp' and !is_null($this->smtp)) {
4602 $lasterror = $this->smtp->getError();
4603 if (!empty($lasterror['error'])) {
4604 $msg .= $this->lang('smtp_error') . $lasterror['error'];
4605 if (!empty($lasterror['detail'])) {
4606 $msg .= ' Detail: '. $lasterror['detail'];
4607 }
4608 if (!empty($lasterror['smtp_code'])) {
4609 $msg .= ' SMTP code: ' . $lasterror['smtp_code'];
4610 }
4611 if (!empty($lasterror['smtp_code_ex'])) {
4612 $msg .= ' Additional SMTP info: ' . $lasterror['smtp_code_ex'];
4613 }
4614 }
4615 }
4616 $this->ErrorInfo = $msg;
4617 }
4618 /**
4619 * Return an RFC 822 formatted date.
4620 * @access public
4621 * @return string
4622 * @static
4623 */
4624 public static function rfcDate()
4625 {
4626 // Set the time zone to whatever the default is to avoid 500 errors
4627 // Will default to UTC if it's not set properly in php.ini
4628 date_default_timezone_set(@date_default_timezone_get());
4629 return date('D, j M Y H:i:s O');
4630 }
4631 /**
4632 * Get the server hostname.
4633 * Returns 'localhost.localdomain' if unknown.
4634 * @access protected
4635 * @return string
4636 */
4637 protected function serverHostname()
4638 {
4639 $result = 'localhost.localdomain';
4640 if (!empty($this->Hostname)) {
4641 $result = $this->Hostname;
4642 } elseif (isset($_SERVER) and array_key_exists('SERVER_NAME', $_SERVER) and !empty($_SERVER['SERVER_NAME'])) {
4643 $result = $_SERVER['SERVER_NAME'];
4644 } elseif (function_exists('gethostname') && gethostname() !== false) {
4645 $result = gethostname();
4646 } elseif (php_uname('n') !== false) {
4647 $result = php_uname('n');
4648 }
4649 return $result;
4650 }
4651 /**
4652 * Get an error message in the current language.
4653 * @access protected
4654 * @param string $key
4655 * @return string
4656 */
4657 protected function lang($key)
4658 {
4659 if (count($this->language) < 1) {
4660 $this->setLanguage('en'); // set the default language
4661 }
4662 if (array_key_exists($key, $this->language)) {
4663 if ($key == 'smtp_connect_failed') {
4664 //Include a link to troubleshooting docs on SMTP connection failure
4665 //this is by far the biggest cause of support questions
4666 //but it's usually not PHPMailer's fault.
4667 return $this->language[$key] . ' https://github.com/PHPMailer/PHPMailer/wiki/Troubleshooting';
4668 }
4669 return $this->language[$key];
4670 } else {
4671 //Return the key as a fallback
4672 return $key;
4673 }
4674 }
4675 /**
4676 * Check if an error occurred.
4677 * @access public
4678 * @return boolean True if an error did occur.
4679 */
4680 public function isError()
4681 {
4682 return ($this->error_count > 0);
4683 }
4684 /**
4685 * Ensure consistent line endings in a string.
4686 * Changes every end of line from CRLF, CR or LF to $this->LE.
4687 * @access public
4688 * @param string $str String to fixEOL
4689 * @return string
4690 */
4691 public function fixEOL($str)
4692 {
4693 // Normalise to \n
4694 $nstr = str_replace(array("\r\n", "\r"), "\n", $str);
4695 // Now convert LE as needed
4696 if ($this->LE !== "\n") {
4697 $nstr = str_replace("\n", $this->LE, $nstr);
4698 }
4699 return $nstr;
4700 }
4701 /**
4702 * Add a custom header.
4703 * $name value can be overloaded to contain
4704 * both header name and value (name:value)
4705 * @access public
4706 * @param string $name Custom header name
4707 * @param string $value Header value
4708 * @return void
4709 */
4710 public function addCustomHeader($name, $value = null,$overwrite = false)
4711 {
4712 if ($value === null)
4713 {
4714 $header = explode(':', $name, 2);
4715 $name = $header[0];
4716 }
4717 if ($overwrite)
4718 {
4719 $this->CustomHeader[$name] = isset($header) ? $header : array($name, $value);
4720 }
4721 else
4722 {
4723 $this->CustomHeader[] = isset($header) ? $header : array($name, $value);
4724 }
4725 }
4726 /**
4727 * Returns all custom headers.
4728 * @return array
4729 */
4730 public function getCustomHeaders()
4731 {
4732 return array_values($this->CustomHeader);
4733 }
4734 /**
4735 * Create a message body from an HTML string.
4736 * Automatically inlines images and creates a plain-text version by converting the HTML,
4737 * overwriting any existing values in Body and AltBody.
4738 * $basedir is used when handling relative image paths, e.g. <img src="images/a.png">
4739 * will look for an image file in $basedir/images/a.png and convert it to inline.
4740 * If you don't want to apply these transformations to your HTML, just set Body and AltBody yourself.
4741 * @access public
4742 * @param string $message HTML message string
4743 * @param string $basedir base directory for relative paths to images
4744 * @param boolean|callable $advanced Whether to use the internal HTML to text converter
4745 * or your own custom converter @see PHPMailer::html2text()
4746 * @return string $message The transformed message Body
4747 */
4748 public function msgHTML($message, $basedir = '', $advanced = false)
4749 {
4750 preg_match_all('/(src|background)=["\'](.*)["\']/Ui', $message, $images);
4751 if (array_key_exists(2, $images)) {
4752 foreach ($images[2] as $imgindex => $url) {
4753 // Convert data URIs into embedded images
4754 if (preg_match('#^data:(image[^;,]*)(;base64)?,#', $url, $match)) {
4755 $data = substr($url, strpos($url, ','));
4756 if ($match[2]) {
4757 $data = base64_decode($data);
4758 } else {
4759 $data = rawurldecode($data);
4760 }
4761 $cid = md5($url) . '@phpmailer.0'; // RFC2392 S 2
4762 if ($this->addStringEmbeddedImage($data, $cid, 'embed' . $imgindex, 'base64', $match[1])) {
4763 $message = str_replace(
4764 $images[0][$imgindex],
4765 $images[1][$imgindex] . '="cid:' . $cid . '"',
4766 $message
4767 );
4768 }
4769 } elseif (substr($url, 0, 4) !== 'cid:' && !preg_match('#^[a-z][a-z0-9+.-]*://#i', $url)) {
4770 // Do not change urls for absolute images (thanks to corvuscorax)
4771 // Do not change urls that are already inline images
4772 $filename = basename($url);
4773 $directory = dirname($url);
4774 if ($directory == '.') {
4775 $directory = '';
4776 }
4777 $cid = md5($url) . '@phpmailer.0'; // RFC2392 S 2
4778 if (strlen($basedir) > 1 && substr($basedir, -1) != '/') {
4779 $basedir .= '/';
4780 }
4781 if (strlen($directory) > 1 && substr($directory, -1) != '/') {
4782 $directory .= '/';
4783 }
4784 if ($this->addEmbeddedImage(
4785 $basedir . $directory . $filename,
4786 $cid,
4787 $filename,
4788 'base64',
4789 self::_mime_types((string)self::mb_pathinfo($filename, PATHINFO_EXTENSION))
4790 )
4791 ) {
4792 $message = preg_replace(
4793 '/' . $images[1][$imgindex] . '=["\']' . preg_quote($url, '/') . '["\']/Ui',
4794 $images[1][$imgindex] . '="cid:' . $cid . '"',
4795 $message
4796 );
4797 }
4798 }
4799 }
4800 }
4801 $this->isHTML(true);
4802 // Convert all message body line breaks to CRLF, makes quoted-printable encoding work much better
4803 $this->Body = $this->normalizeBreaks($message);
4804 $this->AltBody = $this->normalizeBreaks($this->html2text($message, $advanced));
4805 if (!$this->alternativeExists()) {
4806 $this->AltBody = 'To view this email message, open it in a program that understands HTML!' .
4807 self::CRLF . self::CRLF;
4808 }
4809 return $this->Body;
4810 }
4811 /**
4812 * Convert an HTML string into plain text.
4813 * This is used by msgHTML().
4814 * Note - older versions of this function used a bundled advanced converter
4815 * which was been removed for license reasons in #232.
4816 * Example usage:
4817 * <code>
4818 * // Use default conversion
4819 * $plain = $mail->html2text($html);
4820 * // Use your own custom converter
4821 * $plain = $mail->html2text($html, function($html) {
4822 * $converter = new MyHtml2text($html);
4823 * return $converter->get_text();
4824 * });
4825 * </code>
4826 * @param string $html The HTML text to convert
4827 * @param boolean|callable $advanced Any boolean value to use the internal converter,
4828 * or provide your own callable for custom conversion.
4829 * @return string
4830 */
4831 public function html2text($html, $advanced = false)
4832 {
4833 if (is_callable($advanced)) {
4834 return call_user_func($advanced, $html);
4835 }
4836 return html_entity_decode(
4837 trim(strip_tags(preg_replace('/<(head|title|style|script)[^>]*>.*?<\/\\1>/si', '', $html))),
4838 ENT_QUOTES,
4839 $this->CharSet
4840 );
4841 }
4842 /**
4843 * Get the MIME type for a file extension.
4844 * @param string $ext File extension
4845 * @access public
4846 * @return string MIME type of file.
4847 * @static
4848 */
4849 public static function _mime_types($ext = '')
4850 {
4851 $mimes = array(
4852 'xl' => 'application/excel',
4853 'js' => 'application/javascript',
4854 'hqx' => 'application/mac-binhex40',
4855 'cpt' => 'application/mac-compactpro',
4856 'bin' => 'application/macbinary',
4857 'doc' => 'application/msword',
4858 'word' => 'application/msword',
4859 'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
4860 'xltx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.template',
4861 'potx' => 'application/vnd.openxmlformats-officedocument.presentationml.template',
4862 'ppsx' => 'application/vnd.openxmlformats-officedocument.presentationml.slideshow',
4863 'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
4864 'sldx' => 'application/vnd.openxmlformats-officedocument.presentationml.slide',
4865 'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
4866 'dotx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.template',
4867 'xlam' => 'application/vnd.ms-excel.addin.macroEnabled.12',
4868 'xlsb' => 'application/vnd.ms-excel.sheet.binary.macroEnabled.12',
4869 'class' => 'application/octet-stream',
4870 'dll' => 'application/octet-stream',
4871 'dms' => 'application/octet-stream',
4872 'exe' => 'application/octet-stream',
4873 'lha' => 'application/octet-stream',
4874 'lzh' => 'application/octet-stream',
4875 'psd' => 'application/octet-stream',
4876 'sea' => 'application/octet-stream',
4877 'so' => 'application/octet-stream',
4878 'oda' => 'application/oda',
4879 'pdf' => 'application/pdf',
4880 'ai' => 'application/postscript',
4881 'eps' => 'application/postscript',
4882 'ps' => 'application/postscript',
4883 'smi' => 'application/smil',
4884 'smil' => 'application/smil',
4885 'mif' => 'application/vnd.mif',
4886 'xls' => 'application/vnd.ms-excel',
4887 'ppt' => 'application/vnd.ms-powerpoint',
4888 'wbxml' => 'application/vnd.wap.wbxml',
4889 'wmlc' => 'application/vnd.wap.wmlc',
4890 'dcr' => 'application/x-director',
4891 'dir' => 'application/x-director',
4892 'dxr' => 'application/x-director',
4893 'dvi' => 'application/x-dvi',
4894 'gtar' => 'application/x-gtar',
4895 'php3' => 'application/x-httpd-php',
4896 'php4' => 'application/x-httpd-php',
4897 'php' => 'application/x-httpd-php',
4898 'phtml' => 'application/x-httpd-php',
4899 'phps' => 'application/x-httpd-php-source',
4900 'swf' => 'application/x-shockwave-flash',
4901 'sit' => 'application/x-stuffit',
4902 'tar' => 'application/x-tar',
4903 'tgz' => 'application/x-tar',
4904 'xht' => 'application/xhtml+xml',
4905 'xhtml' => 'application/xhtml+xml',
4906 'zip' => 'application/zip',
4907 'mid' => 'audio/midi',
4908 'midi' => 'audio/midi',
4909 'mp2' => 'audio/mpeg',
4910 'mp3' => 'audio/mpeg',
4911 'mpga' => 'audio/mpeg',
4912 'aif' => 'audio/x-aiff',
4913 'aifc' => 'audio/x-aiff',
4914 'aiff' => 'audio/x-aiff',
4915 'ram' => 'audio/x-pn-realaudio',
4916 'rm' => 'audio/x-pn-realaudio',
4917 'rpm' => 'audio/x-pn-realaudio-plugin',
4918 'ra' => 'audio/x-realaudio',
4919 'wav' => 'audio/x-wav',
4920 'bmp' => 'image/bmp',
4921 'gif' => 'image/gif',
4922 'jpeg' => 'image/jpeg',
4923 'jpe' => 'image/jpeg',
4924 'jpg' => 'image/jpeg',
4925 'png' => 'image/png',
4926 'tiff' => 'image/tiff',
4927 'tif' => 'image/tiff',
4928 'eml' => 'message/rfc822',
4929 'css' => 'text/css',
4930 'html' => 'text/html',
4931 'htm' => 'text/html',
4932 'shtml' => 'text/html',
4933 'log' => 'text/plain',
4934 'text' => 'text/plain',
4935 'txt' => 'text/plain',
4936 'rtx' => 'text/richtext',
4937 'rtf' => 'text/rtf',
4938 'vcf' => 'text/vcard',
4939 'vcard' => 'text/vcard',
4940 'xml' => 'text/xml',
4941 'xsl' => 'text/xml',
4942 'mpeg' => 'video/mpeg',
4943 'mpe' => 'video/mpeg',
4944 'mpg' => 'video/mpeg',
4945 'mov' => 'video/quicktime',
4946 'qt' => 'video/quicktime',
4947 'rv' => 'video/vnd.rn-realvideo',
4948 'avi' => 'video/x-msvideo',
4949 'movie' => 'video/x-sgi-movie'
4950 );
4951 if (array_key_exists(strtolower($ext), $mimes)) {
4952 return $mimes[strtolower($ext)];
4953 }
4954 return 'application/octet-stream';
4955 }
4956 /**
4957 * Map a file name to a MIME type.
4958 * Defaults to 'application/octet-stream', i.e.. arbitrary binary data.
4959 * @param string $filename A file name or full path, does not need to exist as a file
4960 * @return string
4961 * @static
4962 */
4963 public static function filenameToType($filename)
4964 {
4965 // In case the path is a URL, strip any query string before getting extension
4966 $qpos = strpos($filename, '?');
4967 if (false !== $qpos) {
4968 $filename = substr($filename, 0, $qpos);
4969 }
4970 $pathinfo = self::mb_pathinfo($filename);
4971 return self::_mime_types($pathinfo['extension']);
4972 }
4973 /**
4974 * Multi-byte-safe pathinfo replacement.
4975 * Drop-in replacement for pathinfo(), but multibyte-safe, cross-platform-safe, old-version-safe.
4976 * Works similarly to the one in PHP >= 5.2.0
4977 * @link http://www.php.net/manual/en/function.pathinfo.php#107461
4978 * @param string $path A filename or path, does not need to exist as a file
4979 * @param integer|string $options Either a PATHINFO_* constant,
4980 * or a string name to return only the specified piece, allows 'filename' to work on PHP < 5.2
4981 * @return string|array
4982 * @static
4983 */
4984 public static function mb_pathinfo($path, $options = null)
4985 {
4986 $ret = array('dirname' => '', 'basename' => '', 'extension' => '', 'filename' => '');
4987 $pathinfo = array();
4988 if (preg_match('%^(.*?)[\\\\/]*(([^/\\\\]*?)(\.([^\.\\\\/]+?)|))[\\\\/\.]*$%im', $path, $pathinfo)) {
4989 if (array_key_exists(1, $pathinfo)) {
4990 $ret['dirname'] = $pathinfo[1];
4991 }
4992 if (array_key_exists(2, $pathinfo)) {
4993 $ret['basename'] = $pathinfo[2];
4994 }
4995 if (array_key_exists(5, $pathinfo)) {
4996 $ret['extension'] = $pathinfo[5];
4997 }
4998 if (array_key_exists(3, $pathinfo)) {
4999 $ret['filename'] = $pathinfo[3];
5000 }
5001 }
5002 switch ($options) {
5003 case PATHINFO_DIRNAME:
5004 case 'dirname':
5005 return $ret['dirname'];
5006 case PATHINFO_BASENAME:
5007 case 'basename':
5008 return $ret['basename'];
5009 case PATHINFO_EXTENSION:
5010 case 'extension':
5011 return $ret['extension'];
5012 case PATHINFO_FILENAME:
5013 case 'filename':
5014 return $ret['filename'];
5015 default:
5016 return $ret;
5017 }
5018 }
5019 /**
5020 * Set or reset instance properties.
5021 * You should avoid this function - it's more verbose, less efficient, more error-prone and
5022 * harder to debug than setting properties directly.
5023 * Usage Example:
5024 * `$mail->set('SMTPSecure', 'tls');`
5025 * is the same as:
5026 * `$mail->SMTPSecure = 'tls';`
5027 * @access public
5028 * @param string $name The property name to set
5029 * @param mixed $value The value to set the property to
5030 * @return boolean
5031 * @TODO Should this not be using the __set() magic function?
5032 */
5033 public function set($name, $value = '')
5034 {
5035 if (property_exists($this, $name)) {
5036 $this->$name = $value;
5037 return true;
5038 } else {
5039 $this->setError($this->lang('variable_set') . $name);
5040 return false;
5041 }
5042 }
5043 /**
5044 * Strip newlines to prevent header injection.
5045 * @access public
5046 * @param string $str
5047 * @return string
5048 */
5049 public function secureHeader($str)
5050 {
5051 return trim(str_replace(array("\r", "\n"), '', $str));
5052 }
5053 /**
5054 * Normalize line breaks in a string.
5055 * Converts UNIX LF, Mac CR and Windows CRLF line breaks into a single line break format.
5056 * Defaults to CRLF (for message bodies) and preserves consecutive breaks.
5057 * @param string $text
5058 * @param string $breaktype What kind of line break to use, defaults to CRLF
5059 * @return string
5060 * @access public
5061 * @static
5062 */
5063 public static function normalizeBreaks($text, $breaktype = "\r\n")
5064 {
5065 return preg_replace('/(\r\n|\r|\n)/ms', $breaktype, $text);
5066 }
5067 /**
5068 * Set the public and private key files and password for S/MIME signing.
5069 * @access public
5070 * @param string $cert_filename
5071 * @param string $key_filename
5072 * @param string $key_pass Password for private key
5073 * @param string $extracerts_filename Optional path to chain certificate
5074 */
5075 public function sign($cert_filename, $key_filename, $key_pass, $extracerts_filename = '')
5076 {
5077 $this->sign_cert_file = $cert_filename;
5078 $this->sign_key_file = $key_filename;
5079 $this->sign_key_pass = $key_pass;
5080 $this->sign_extracerts_file = $extracerts_filename;
5081 }
5082 /**
5083 * Quoted-Printable-encode a DKIM header.
5084 * @access public
5085 * @param string $txt
5086 * @return string
5087 */
5088 public function DKIM_QP($txt)
5089 {
5090 $line = '';
5091 for ($i = 0; $i < strlen($txt); $i++) {
5092 $ord = ord($txt[$i]);
5093 if (((0x21 <= $ord) && ($ord <= 0x3A)) || $ord == 0x3C || ((0x3E <= $ord) && ($ord <= 0x7E))) {
5094 $line .= $txt[$i];
5095 } else {
5096 $line .= '=' . sprintf('%02X', $ord);
5097 }
5098 }
5099 return $line;
5100 }
5101 /**
5102 * Generate a DKIM signature.
5103 * @access public
5104 * @param string $signHeader
5105 * @throws phpmailerException
5106 * @return string The DKIM signature value
5107 */
5108 public function DKIM_Sign($signHeader)
5109 {
5110 if (!defined('PKCS7_TEXT')) {
5111 if ($this->exceptions) {
5112 throw new phpmailerException($this->lang('extension_missing') . 'openssl');
5113 }
5114 return '';
5115 }
5116 $privKeyStr = !empty($this->DKIM_private_string) ? $this->DKIM_private_string : file_get_contents($this->DKIM_private);
5117 if ('' != $this->DKIM_passphrase) {
5118 $privKey = openssl_pkey_get_private($privKeyStr, $this->DKIM_passphrase);
5119 } else {
5120 $privKey = openssl_pkey_get_private($privKeyStr);
5121 }
5122 //Workaround for missing digest algorithms in old PHP & OpenSSL versions
5123 //@link http://stackoverflow.com/a/11117338/333340
5124 if (version_compare(PHP_VERSION, '5.3.0') >= 0 and
5125 in_array('sha256WithRSAEncryption', openssl_get_md_methods(true))) {
5126 if (openssl_sign($signHeader, $signature, $privKey, 'sha256WithRSAEncryption')) {
5127 openssl_pkey_free($privKey);
5128 return base64_encode($signature);
5129 }
5130 } else {
5131 $pinfo = openssl_pkey_get_details($privKey);
5132 $hash = hash('sha256', $signHeader);
5133 //'Magic' constant for SHA256 from RFC3447
5134 //@link https://tools.ietf.org/html/rfc3447#page-43
5135 $t = '3031300d060960864801650304020105000420' . $hash;
5136 $pslen = $pinfo['bits'] / 8 - (strlen($t) / 2 + 3);
5137 $eb = pack('H*', '0001' . str_repeat('FF', $pslen) . '00' . $t);
5138 if (openssl_private_encrypt($eb, $signature, $privKey, OPENSSL_NO_PADDING)) {
5139 openssl_pkey_free($privKey);
5140 return base64_encode($signature);
5141 }
5142 }
5143 openssl_pkey_free($privKey);
5144 return '';
5145 }
5146 /**
5147 * Generate a DKIM canonicalization header.
5148 * @access public
5149 * @param string $signHeader Header
5150 * @return string
5151 */
5152 public function DKIM_HeaderC($signHeader)
5153 {
5154 $signHeader = preg_replace('/\r\n\s+/', ' ', $signHeader);
5155 $lines = explode("\r\n", $signHeader);
5156 foreach ($lines as $key => $line) {
5157 list($heading, $value) = explode(':', $line, 2);
5158 $heading = strtolower($heading);
5159 $value = preg_replace('/\s{2,}/', ' ', $value); // Compress useless spaces
5160 $lines[$key] = $heading . ':' . trim($value); // Don't forget to remove WSP around the value
5161 }
5162 $signHeader = implode("\r\n", $lines);
5163 return $signHeader;
5164 }
5165 /**
5166 * Generate a DKIM canonicalization body.
5167 * @access public
5168 * @param string $body Message Body
5169 * @return string
5170 */
5171 public function DKIM_BodyC($body)
5172 {
5173 if ($body == '') {
5174 return "\r\n";
5175 }
5176 // stabilize line endings
5177 $body = str_replace("\r\n", "\n", $body);
5178 $body = str_replace("\n", "\r\n", $body);
5179 // END stabilize line endings
5180 while (substr($body, strlen($body) - 4, 4) == "\r\n\r\n") {
5181 $body = substr($body, 0, strlen($body) - 2);
5182 }
5183 return $body;
5184 }
5185 /**
5186 * Create the DKIM header and body in a new message header.
5187 * @access public
5188 * @param string $headers_line Header lines
5189 * @param string $subject Subject
5190 * @param string $body Body
5191 * @return string
5192 */
5193 public function DKIM_Add($headers_line, $subject, $body)
5194 {
5195 $DKIMsignatureType = 'rsa-sha256'; // Signature & hash algorithms
5196 $DKIMcanonicalization = 'relaxed/simple'; // Canonicalization of header/body
5197 $DKIMquery = 'dns/txt'; // Query method
5198 $DKIMtime = time(); // Signature Timestamp = seconds since 00:00:00 - Jan 1, 1970 (UTC time zone)
5199 $subject_header = "Subject: $subject";
5200 $headers = explode($this->LE, $headers_line);
5201 $from_header = '';
5202 $to_header = '';
5203 $date_header = '';
5204 $current = '';
5205 foreach ($headers as $header) {
5206 if (strpos($header, 'From:') === 0) {
5207 $from_header = $header;
5208 $current = 'from_header';
5209 } elseif (strpos($header, 'To:') === 0) {
5210 $to_header = $header;
5211 $current = 'to_header';
5212 } elseif (strpos($header, 'Date:') === 0) {
5213 $date_header = $header;
5214 $current = 'date_header';
5215 } else {
5216 if (!empty($$current) && strpos($header, ' =?') === 0) {
5217 $$current .= $header;
5218 } else {
5219 $current = '';
5220 }
5221 }
5222 }
5223 $from = str_replace('|', '=7C', $this->DKIM_QP($from_header));
5224 $to = str_replace('|', '=7C', $this->DKIM_QP($to_header));
5225 $date = str_replace('|', '=7C', $this->DKIM_QP($date_header));
5226 $subject = str_replace(
5227 '|',
5228 '=7C',
5229 $this->DKIM_QP($subject_header)
5230 ); // Copied header fields (dkim-quoted-printable)
5231 $body = $this->DKIM_BodyC($body);
5232 $DKIMlen = strlen($body); // Length of body
5233 $DKIMb64 = base64_encode(pack('H*', hash('sha256', $body))); // Base64 of packed binary SHA-256 hash of body
5234 if ('' == $this->DKIM_identity) {
5235 $ident = '';
5236 } else {
5237 $ident = ' i=' . $this->DKIM_identity . ';';
5238 }
5239 $dkimhdrs = 'DKIM-Signature: v=1; a=' .
5240 $DKIMsignatureType . '; q=' .
5241 $DKIMquery . '; l=' .
5242 $DKIMlen . '; s=' .
5243 $this->DKIM_selector .
5244 ";\r\n" .
5245 "\tt=" . $DKIMtime . '; c=' . $DKIMcanonicalization . ";\r\n" .
5246 "\th=From:To:Date:Subject;\r\n" .
5247 "\td=" . $this->DKIM_domain . ';' . $ident . "\r\n" .
5248 "\tz=$from\r\n" .
5249 "\t|$to\r\n" .
5250 "\t|$date\r\n" .
5251 "\t|$subject;\r\n" .
5252 "\tbh=" . $DKIMb64 . ";\r\n" .
5253 "\tb=";
5254 $toSign = $this->DKIM_HeaderC(
5255 $from_header . "\r\n" .
5256 $to_header . "\r\n" .
5257 $date_header . "\r\n" .
5258 $subject_header . "\r\n" .
5259 $dkimhdrs
5260 );
5261 $signed = $this->DKIM_Sign($toSign);
5262 return $dkimhdrs . $signed . "\r\n";
5263 }
5264 /**
5265 * Detect if a string contains a line longer than the maximum line length allowed.
5266 * @param string $str
5267 * @return boolean
5268 * @static
5269 */
5270 public static function hasLineLongerThanMax($str)
5271 {
5272 //+2 to include CRLF line break for a 1000 total
5273 return (boolean)preg_match('/^(.{'.(self::MAX_LINE_LENGTH + 2).',})/m', $str);
5274 }
5275 /**
5276 * Allows for public read access to 'to' property.
5277 * @note: Before the send() call, queued addresses (i.e. with IDN) are not yet included.
5278 * @access public
5279 * @return array
5280 */
5281 public function getToAddresses()
5282 {
5283 return $this->to;
5284 }
5285 /**
5286 * Allows for public read access to 'cc' property.
5287 * @note: Before the send() call, queued addresses (i.e. with IDN) are not yet included.
5288 * @access public
5289 * @return array
5290 */
5291 public function getCcAddresses()
5292 {
5293 return $this->cc;
5294 }
5295 /**
5296 * Allows for public read access to 'bcc' property.
5297 * @note: Before the send() call, queued addresses (i.e. with IDN) are not yet included.
5298 * @access public
5299 * @return array
5300 */
5301 public function getBccAddresses()
5302 {
5303 return $this->bcc;
5304 }
5305 /**
5306 * Allows for public read access to 'ReplyTo' property.
5307 * @note: Before the send() call, queued addresses (i.e. with IDN) are not yet included.
5308 * @access public
5309 * @return array
5310 */
5311 public function getReplyToAddresses()
5312 {
5313 return $this->ReplyTo;
5314 }
5315 /**
5316 * Allows for public read access to 'all_recipients' property.
5317 * @note: Before the send() call, queued addresses (i.e. with IDN) are not yet included.
5318 * @access public
5319 * @return array
5320 */
5321 public function getAllRecipientAddresses()
5322 {
5323 return $this->all_recipients;
5324 }
5325 /**
5326 * Perform a callback.
5327 * @param boolean $isSent
5328 * @param array $to
5329 * @param array $cc
5330 * @param array $bcc
5331 * @param string $subject
5332 * @param string $body
5333 * @param string $from
5334 */
5335 protected function doCallback($isSent, $to, $cc, $bcc, $subject, $body, $from)
5336 {
5337 if (!empty($this->action_function) && is_callable($this->action_function)) {
5338 $params = array($isSent, $to, $cc, $bcc, $subject, $body, $from);
5339 call_user_func_array($this->action_function, $params);
5340 }
5341 }
5342}
5343/**
5344 * PHPMailer exception handler
5345 * @package PHPMailer
5346 */
5347class phpmailerException extends Exception
5348{
5349 /**
5350 * Prettify error message output
5351 * @return string
5352 */
5353 public function errorMessage()
5354 {
5355 $errorMsg = '<strong>' . $this->getMessage() . "</strong><br />\n";
5356 return $errorMsg;
5357 }
5358}
5359
5360
5361$mail = new PHPMailer();
5362
5363
5364// REMOVE ALL HTML TAG WILL BE USED IN ALTBODY
5365function strip_tags_content($text, $tags = '', $invert = FALSE) {
5366
5367 /*preg_match_all('/<(.+?)[\s]*\/?[\s]*>/si', trim($tags), $tags);
5368 $tags = array_unique($tags[1]);
5369
5370 if(is_array($tags) AND count($tags) > 0) {
5371 if($invert == FALSE) {
5372 return preg_replace('@<(?!(?:'. implode('|', $tags) .')\b)(\w+)\b.*?>.*?</\1>@si', '', $text);
5373 }
5374 else {
5375 return preg_replace('@<('. implode('|', $tags) .')\b.*?>.*?</\1>@si', '', $text);
5376 }
5377 }
5378 elseif($invert == FALSE) {
5379 return preg_replace('@<(\w+)\b.*?>.*?</\1>@si', '', $text);
5380 } */
5381 return strip_tags($text);
5382}
5383
5384// TRIM LEFT AND RIGHT
5385function lrtrim($string)
5386{
5387 return stripslashes(ltrim(rtrim($string)));
5388}
5389// REPLACE LAST OCCURENCE OF STRING
5390function str_lreplace($search, $replace, $subject)
5391{
5392 $pos = strrpos($subject, $search);
5393
5394 if($pos !== false)
5395 {
5396 $subject = substr_replace($subject, $replace, $pos, strlen($search));
5397 }
5398
5399 return $subject;
5400}
5401// RANDOM STRING FUNCTION
5402function generateRandomString($matches)
5403{
5404 $length = intval(ltrim(rtrim($matches[3])));
5405 $PATTERN = str_lreplace('-','',$matches[2]);
5406
5407 $characters = '';
5408 if(strpos($PATTERN, '09') !== false)
5409 $characters .= '0123456789';
5410 if(strpos($PATTERN, 'az') !== false)
5411 $characters .= 'abcdefghijklmnopqrstuvwxyz';
5412 if(strpos($PATTERN, 'AZ') !== false)
5413 $characters .= 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
5414 $charactersLength = strlen($characters);
5415 $randomString = '';
5416 for ($i = 0; $i < $length; $i++) {
5417 $randomString .= $characters[rand(0, $charactersLength - 1)];
5418 }
5419 return $randomString;
5420}
5421// WAITING FUNCTION
5422function pause($pause,$mail)
5423{
5424 $sec=doubleval($pause);
5425 $mail->SmtpClose();
5426 echo "\n\n<br><br>############################### WAITING $sec SEC TO CONTINUE SENDING ###############################<br><br>\n\n";
5427 flush();
5428 sleep($sec);
5429}
5430// SMTP SWITCH
5431function switch_smtp()
5432{
5433 global $allsmtps;
5434 global $curentsmtp;
5435 global $mail;
5436 global $isbcc;
5437 global $from;
5438 global $lase;
5439 global $replyto;
5440 global $reading;
5441 global $repaslog;
5442 if(count($allsmtps) > $curentsmtp)
5443 {
5444 $smtprot = explode(':',$allsmtps[$curentsmtp]);
5445 if(count($smtprot) > 0)
5446 {
5447 $mail->Host = $smtprot[0];
5448 $mail->Port = $smtprot[1];
5449 $mail->Username = $smtprot[2];
5450 $mail->Password = $smtprot[3];
5451 if($reading && $repaslog)
5452 $replyto = $smtprot[2];
5453 if($lase)
5454 {
5455 $from = $smtprot[2];
5456 $from_base = $smtprot[2];
5457 }
5458
5459 if($smtprot[4] =="SSL")
5460 $mail->SMTPSecure = "ssl"; //you can change it to ssl or tls
5461 else if($smtprot[4] =="TLS")
5462 $mail->SMTPSecure = "tls";
5463 else if($smtprot[4] =="NON")
5464 $mail->SMTPSecure = "";
5465 if($smtprot[5] =="BCC")
5466 $isbcc = true;
5467 else $isbcc = false;
5468
5469 }
5470 }
5471}
5472
5473
5474 if ($action)
5475 {
5476 if (!$from && !$subject && !$message && !$emaillist)
5477 {
5478 print "<script>alert('PLEASE FILL ALL FIELDS BEFORE SENDING YOUR MESSAGE.'); </script>";
5479 die();
5480 }
5481 else
5482 {
5483 $allemails = preg_split("/\\r\\n|\\r|\\n/", $emaillist);
5484 $numemails = count($allemails);
5485 $nq=0;
5486 $qx=0;
5487
5488 if(!empty($epriority))
5489 $mail->Priority = "$epriority";
5490 if($contenttype == "html")
5491 $mail->IsHtml(true);
5492 else
5493 $mail->IsHtml(false);
5494 if(empty($reconnect))
5495 $reconnect=0;
5496 if(!empty($replyto))
5497 $mail->AddReplyTo("$replyto");
5498 if(empty($my_smtp))
5499 {
5500 $mail->SMTPAuth = false;
5501 $mail->IsSendmail();
5502 $default_system="1";
5503 }
5504 else
5505 {
5506 $mail->IsSMTP();
5507 $mail->SMTPKeepAlive = true;
5508 $mail->SMTPAuth = true;
5509 switch_smtp();
5510 }
5511 $mail->From = $from;
5512 #$mail->addCustomHeader('List-Unsubscribe',preg_replace_callback('/(##([a-zA-Z0-9\-]+)\{([0-9]+)\}##)/', "generateRandomString", 'mailto:bounce##09-{3}##-##az-AZ-09-{15}##@'.explode('@',$from)[1].'?subject=list-unsubscribe'));
5513 // SET DEBUG LVL
5514 $mail->SMTPDebug = $debg;
5515 // SET CHARSET
5516 $mail->CharSet = "UTF-8";
5517 // READING CONFIRMATION
5518 if($reading)
5519 $mail->ConfirmReadingTo = $replyto;
5520 else $mail->ConfirmReadingTo = '';
5521 // SET ENCODING TYPE
5522 if($encodety !="no") $mail->Encoding = $encodety;
5523 // ADD ATTACH
5524 if (array_key_exists('userfile', $_FILES))
5525 {
5526 // First handle the upload
5527 // Don't trust provided filename - same goes for MIME types
5528 // See http://php.net/manual/en/features.file-upload.php#114004 for more thorough upload validation
5529 $uploadfile = tempnam(sys_get_temp_dir(), sha1($_FILES['userfile']['name']));
5530 if (move_uploaded_file($_FILES['userfile']['tmp_name'], $uploadfile))
5531 {
5532 $mail->addAttachment($uploadfile,$_FILES['userfile']['name']);
5533 }
5534 }
5535 for($x=1; $x<=$numemails; $x++)
5536 {
5537 // BCC EMAIL COUNT
5538 if($isbcc && $x % intval($nbcc) == 0)
5539 {
5540 $nm += 1;
5541 $nopose=false;
5542 $canrotat=true;
5543 }
5544 else
5545 {
5546 $nopose=true;
5547 $canrotat=false;
5548 }
5549 //END//
5550
5551 $send=false;
5552 $v=$x-1;
5553 $to = $allemails[$v];
5554 $to = preg_replace("/ /", "", $to);
5555
5556 // ADD ADRESSES TO QUEUE
5557 if($isbcc && !empty($nbcc) )
5558 {
5559
5560 if($x % intval($nbcc) != 0 && $x <= $numemails)
5561 {
5562 $mail->addBCC("$to");
5563 print "<span style=\"color:red;\">Line $qx </span> : Sending mail to $to<br>";
5564 flush();
5565 if($x % intval($nbcc) != 0 && $x == $numemails)
5566 $send=true;
5567 }
5568 else
5569 {
5570 $mail->addBCC("$to");
5571 print "<span style=\"color:red;\">Line $qx </span> : Sending mail to $to<br>";
5572 flush();
5573 $send = true;
5574 }
5575 $qx=$x;
5576 }
5577 else
5578 {
5579 $mail->clearAddresses();
5580 $mail->AddAddress("$to");
5581 $send=true;
5582 print "<span style=\"color:red;\">Line $qx </span> : Sending mail to $to.......";
5583 flush();
5584 $qx=$x;
5585 }
5586 //END//
5587 if($send)
5588 {
5589
5590 $realname = preg_replace_callback('/(##([a-zA-Z0-9\-]+)\{([0-9]+)\}##)/', "generateRandomString", $realname_base);
5591 $message = preg_replace_callback('/(##([a-zA-Z0-9\-]+)\{([0-9]+)\}##)/', "generateRandomString", $message_base);
5592 $subject = preg_replace_callback('/(##([a-zA-Z0-9\-]+)\{([0-9]+)\}##)/', "generateRandomString", $subject_base);
5593 // $from = preg_replace_callback('/(##([a-zA-Z0-9\-]+)\{([0-9]+)\}##)/', "generateRandomString", $from_base);
5594 $from = preg_replace('/[0-9]+/', rand(90000 , 10000), $from);
5595
5596 if(!$isbcc)
5597 {
5598 $message = preg_replace("/!!EMAIL!!/", $to, $message);
5599 $subject = preg_replace("/!!EMAIL!!/", $to, $subject);
5600 }
5601 $message = preg_replace("/!!DATE!!/", date("d/m/Y"), $message);
5602 $subject = preg_replace("/!!DATE!!/", date("d/m/Y"), $subject);
5603 $message = preg_replace("/!!TIME!!/", date("H:i:s"), $message);
5604 $subject = preg_replace("/!!TIME!!/", date("H:i:s"), $subject);
5605 $message = urlencode($message);
5606 $message = preg_replace("/%5C%22/", "%22", $message);
5607 $message = urldecode($message);
5608 $message = stripslashes($message);
5609 $subject = stripslashes($subject);
5610
5611 if ($encodety != "no")
5612 {
5613 $subject = "=?UTF-8?B?".base64_encode($subject)."?=";
5614 $realname = "=?UTF-8?B?".base64_encode($realname)."?=";
5615 }
5616
5617 $mail->FromName = "$realname";
5618 $mail->Subject = "$subject";
5619 $mail->Body = "$message";
5620 $mail->AltBody = strip_tags_content($message);
5621 // SENDING AND TESTING
5622 if(!$mail->Send())
5623 {
5624 if($default_system != "1")
5625 {
5626 echo "FAILED !!<font color=\"#D4001A\"> [RECEPIENT CAN'T RECEIVE MESSAGE.]</font><br>";
5627 }
5628 if($default_system == "1")
5629 {
5630 $mail->IsMail();
5631 if(!$mail->Send())
5632 {
5633 echo "FAILED !!<font color=\"#D4001A\"> [RECEPIENT CAN'T RECEIVE MESSAGE.]</font><br>";
5634 }
5635 else
5636 {
5637 if($isbcc)
5638 echo "# BCC EMAIL NUMERO <span style=\"color:red;\">NUMERO $nm </span> SEND :<b>OK</b><br>";
5639 else echo "<b>OK</b><br>";
5640 }
5641 }
5642 }
5643 else
5644 {
5645 if($isbcc)
5646 echo "# BCC EMAIL <span style=\"color:red;\">NUMERO $nm </span> SEND :<b>OK</b><br>";
5647 else echo "<b>OK</b><br>";
5648 }
5649 if($reconnect >0 && $reconnect==$nq && !(intval($nrotat) > 0 && count($allsmtps) > 1))
5650 {
5651 $mail->SmtpClose();
5652 echo "<p><b>########################### SMTP CLOSED AND ATTEMPTS TO RECONNECT NEW CONNECTION SEASON ########################### </b></p>";
5653 $nq=0;
5654 }
5655 $nq=$nq+1;
5656 flush();
5657 if($isbcc)
5658 $mail->clearBCCs();
5659 }
5660 //END//
5661
5662 // SMTP IP ROTATION NB: 1 BCC = 1 EMAIL
5663 if(intval($nrotat) > 0 && count($allsmtps) > 1)
5664 {
5665 $curentsmtp += 1;
5666 $nq=0;
5667 $mail->SmtpClose();
5668 switch_smtp();
5669 if($isbcc)
5670 {
5671 if($nm > 0 && $nm % intval($nrotat) == 0 && $canrotat && $x < $numemails)
5672 {
5673 echo "\n<br><span style=\"color:red;\">##############</span><b> ROTATE TO SMTP ".($curentsmtp+1)." IP: ".$mail->Host."</b><span style=\"color:red;\"> ##############</span><br><br>\n";
5674
5675 }
5676 }
5677 else if ($x % intval($nrotat)==0 && $x < $numemails)
5678 {
5679 if($x >= intval($nrotat))
5680 echo "\n<br><span style=\"color:red;\">##############</span><b> ROTATE TO SMTP ".($curentsmtp+1)." IP: ".$mail->Host."</b><span style=\"color:red;\"> ##############</span><br><br>\n\n";
5681 }
5682 if($curentsmtp >= count($allsmtps))
5683 {
5684 $curentsmtp=0;
5685 }
5686
5687 }
5688 //END//
5689
5690 // WAITING PAUSE TIME
5691 if(!empty($pause) && !empty($pemails))
5692 {
5693 if(doubleval($pause) > 0)
5694 {
5695 if(!$isbcc)
5696 {
5697 if($x % intval($pemails) == 0 && $x < $numemails)
5698 {
5699 pause($pause,$mail);
5700 }
5701 }
5702 else if($nm > 0 && $nm % intval($pemails) == 0 && $x < $numemails && !$nopose)
5703 {
5704 pause($pause,$mail);
5705 }
5706 }
5707 }
5708 //END//
5709 }
5710 }
5711 }
5712?>