ILIAS  release_5-4 Revision v5.4.26-12-gabc799a52e6
PHPMailer.php
Go to the documentation of this file.
1<?php
21namespace PHPMailer\PHPMailer;
22
32{
33 const CHARSET_ASCII = 'us-ascii';
34 const CHARSET_ISO88591 = 'iso-8859-1';
35 const CHARSET_UTF8 = 'utf-8';
36
37 const CONTENT_TYPE_PLAINTEXT = 'text/plain';
38 const CONTENT_TYPE_TEXT_CALENDAR = 'text/calendar';
39 const CONTENT_TYPE_TEXT_HTML = 'text/html';
40 const CONTENT_TYPE_MULTIPART_ALTERNATIVE = 'multipart/alternative';
41 const CONTENT_TYPE_MULTIPART_MIXED = 'multipart/mixed';
42 const CONTENT_TYPE_MULTIPART_RELATED = 'multipart/related';
43
44 const ENCODING_7BIT = '7bit';
45 const ENCODING_8BIT = '8bit';
46 const ENCODING_BASE64 = 'base64';
47 const ENCODING_BINARY = 'binary';
48 const ENCODING_QUOTED_PRINTABLE = 'quoted-printable';
49
50 const ENCRYPTION_STARTTLS = 'tls';
51 const ENCRYPTION_SMTPS = 'ssl';
52
53 const ICAL_METHOD_REQUEST = 'REQUEST';
54 const ICAL_METHOD_PUBLISH = 'PUBLISH';
55 const ICAL_METHOD_REPLY = 'REPLY';
56 const ICAL_METHOD_ADD = 'ADD';
57 const ICAL_METHOD_CANCEL = 'CANCEL';
58 const ICAL_METHOD_REFRESH = 'REFRESH';
59 const ICAL_METHOD_COUNTER = 'COUNTER';
60 const ICAL_METHOD_DECLINECOUNTER = 'DECLINECOUNTER';
61
69 public $Priority;
70
77
84
92
98 public $ErrorInfo = '';
99
105 public $From = 'root@localhost';
106
112 public $FromName = 'Root User';
113
122 public $Sender = '';
123
129 public $Subject = '';
130
137 public $Body = '';
138
147 public $AltBody = '';
148
159 public $Ical = '';
160
166 protected static $IcalMethods = [
175 ];
176
182 protected $MIMEBody = '';
183
189 protected $MIMEHeader = '';
190
196 protected $mailHeader = '';
197
206 public $WordWrap = 0;
207
214 public $Mailer = 'mail';
215
221 public $Sendmail = '/usr/sbin/sendmail';
222
229 public $UseSendmailOptions = true;
230
236 public $ConfirmReadingTo = '';
237
248 public $Hostname = '';
249
260 public $MessageID = '';
261
268 public $MessageDate = '';
269
282 public $Host = 'localhost';
283
289 public $Port = 25;
290
300 public $Helo = '';
301
308 public $SMTPSecure = '';
309
317 public $SMTPAutoTLS = true;
318
328 public $SMTPAuth = false;
329
335 public $SMTPOptions = [];
336
342 public $Username = '';
343
349 public $Password = '';
350
357 public $AuthType = '';
358
364 protected $oauth;
365
372 public $Timeout = 300;
373
385 public $dsn = '';
386
401 public $SMTPDebug = 0;
402
427 public $Debugoutput = 'echo';
428
436 public $SMTPKeepAlive = false;
437
445 public $SingleTo = false;
446
452 protected $SingleToArray = [];
453
463 public $do_verp = false;
464
470 public $AllowEmpty = false;
471
477 public $DKIM_selector = '';
478
485 public $DKIM_identity = '';
486
493 public $DKIM_passphrase = '';
494
502 public $DKIM_domain = '';
503
510
519
525 public $DKIM_private = '';
526
535
557 public $action_function = '';
558
565 public $XMailer = '';
566
576 public static $validator = 'php';
577
583 protected $smtp;
584
590 protected $to = [];
591
597 protected $cc = [];
598
604 protected $bcc = [];
605
611 protected $ReplyTo = [];
612
623 protected $all_recipients = [];
624
638 protected $RecipientsQueue = [];
639
649 protected $ReplyToQueue = [];
650
656 protected $attachment = [];
657
663 protected $CustomHeader = [];
664
670 protected $lastMessageID = '';
671
677 protected $message_type = '';
678
684 protected $boundary = [];
685
691 protected $language = [];
692
698 protected $error_count = 0;
699
705 protected $sign_cert_file = '';
706
712 protected $sign_key_file = '';
713
719 protected $sign_extracerts_file = '';
720
727 protected $sign_key_pass = '';
728
734 protected $exceptions = false;
735
741 protected $uniqueid = '';
742
748 const VERSION = '6.1.6';
749
755 const STOP_MESSAGE = 0;
756
762 const STOP_CONTINUE = 1;
763
769 const STOP_CRITICAL = 2;
770
775 const CRLF = "\r\n";
776
780 const FWS = ' ';
781
787 protected static $LE = self::CRLF;
788
798
804 const MAX_LINE_LENGTH = 998;
805
814 const STD_LINE_LENGTH = 76;
815
821 public function __construct($exceptions = null)
822 {
823 if (null !== $exceptions) {
824 $this->exceptions = (bool) $exceptions;
825 }
826 //Pick an appropriate debug output format automatically
827 $this->Debugoutput = (strpos(PHP_SAPI, 'cli') !== false ? 'echo' : 'html');
828 }
829
833 public function __destruct()
834 {
835 //Close any open SMTP connection nicely
836 $this->smtpClose();
837 }
838
853 private function mailPassthru($to, $subject, $body, $header, $params)
854 {
855 //Check overloading of mail function to avoid double-encoding
856 if (ini_get('mbstring.func_overload') & 1) {
857 $subject = $this->secureHeader($subject);
858 } else {
859 $subject = $this->encodeHeader($this->secureHeader($subject));
860 }
861 // patch-mjansen: begin #20376
862 if (0 == strlen($to) && strpos($header, 'To: undisclosed-recipients:;') !== false) {
863 $to = 'undisclosed-recipients:;';
864 $header = preg_replace('/To: undisclosed-recipients:;(\s*)/', '', $header);
865 }
866 // patch-mjansen: end
867 //Calling mail() with null params breaks
868 if (!$this->UseSendmailOptions || null === $params) {
869 $result = @mail($to, $subject, $body, $header);
870 } else {
871 $result = @mail($to, $subject, $body, $header, $params);
872 }
873
874 return $result;
875 }
876
886 protected function edebug($str)
887 {
888 if ($this->SMTPDebug <= 0) {
889 return;
890 }
891 //Is this a PSR-3 logger?
892 if ($this->Debugoutput instanceof \Psr\Log\LoggerInterface) {
893 $this->Debugoutput->debug($str);
894
895 return;
896 }
897 //Avoid clash with built-in function names
898 if (is_callable($this->Debugoutput) && !in_array($this->Debugoutput, ['error_log', 'html', 'echo'])) {
899 call_user_func($this->Debugoutput, $str, $this->SMTPDebug);
900
901 return;
902 }
903 switch ($this->Debugoutput) {
904 case 'error_log':
905 //Don't output, just log
906 error_log($str);
907 break;
908 case 'html':
909 //Cleans up output a bit for a better looking, HTML-safe output
910 echo htmlentities(
911 preg_replace('/[\r\n]+/', '', $str),
912 ENT_QUOTES,
913 'UTF-8'
914 ), "<br>\n";
915 break;
916 case 'echo':
917 default:
918 //Normalize line breaks
919 $str = preg_replace('/\r\n|\r/m', "\n", $str);
920 echo gmdate('Y-m-d H:i:s'),
921 "\t",
922 //Trim trailing space
923 trim(
924 //Indent for readability, except for trailing break
925 str_replace(
926 "\n",
927 "\n \t ",
928 trim($str)
929 )
930 ),
931 "\n";
932 }
933 }
934
940 public function isHTML($isHtml = true)
941 {
942 if ($isHtml) {
943 $this->ContentType = static::CONTENT_TYPE_TEXT_HTML;
944 } else {
945 $this->ContentType = static::CONTENT_TYPE_PLAINTEXT;
946 }
947 }
948
952 public function isSMTP()
953 {
954 $this->Mailer = 'smtp';
955 }
956
960 public function isMail()
961 {
962 $this->Mailer = 'mail';
963 }
964
968 public function isSendmail()
969 {
970 $ini_sendmail_path = ini_get('sendmail_path');
971
972 if (false === stripos($ini_sendmail_path, 'sendmail')) {
973 $this->Sendmail = '/usr/sbin/sendmail';
974 } else {
975 $this->Sendmail = $ini_sendmail_path;
976 }
977 $this->Mailer = 'sendmail';
978 }
979
983 public function isQmail()
984 {
985 $ini_sendmail_path = ini_get('sendmail_path');
986
987 if (false === stripos($ini_sendmail_path, 'qmail')) {
988 $this->Sendmail = '/var/qmail/bin/qmail-inject';
989 } else {
990 $this->Sendmail = $ini_sendmail_path;
991 }
992 $this->Mailer = 'qmail';
993 }
994
1005 public function addAddress($address, $name = '')
1006 {
1007 return $this->addOrEnqueueAnAddress('to', $address, $name);
1008 }
1009
1020 public function addCC($address, $name = '')
1021 {
1022 return $this->addOrEnqueueAnAddress('cc', $address, $name);
1023 }
1024
1035 public function addBCC($address, $name = '')
1036 {
1037 return $this->addOrEnqueueAnAddress('bcc', $address, $name);
1038 }
1039
1050 public function addReplyTo($address, $name = '')
1051 {
1052 return $this->addOrEnqueueAnAddress('Reply-To', $address, $name);
1053 }
1054
1069 protected function addOrEnqueueAnAddress($kind, $address, $name)
1070 {
1071 $address = trim($address);
1072 $name = trim(preg_replace('/[\r\n]+/', '', $name)); //Strip breaks and trim
1073 $pos = strrpos($address, '@');
1074 if (false === $pos) {
1075 // At-sign is missing.
1076 $error_message = sprintf(
1077 '%s (%s): %s',
1078 $this->lang('invalid_address'),
1079 $kind,
1080 $address
1081 );
1082 $this->setError($error_message);
1083 $this->edebug($error_message);
1084 if ($this->exceptions) {
1085 throw new Exception($error_message);
1086 }
1087
1088 return false;
1089 }
1090 $params = [$kind, $address, $name];
1091 // Enqueue addresses with IDN until we know the PHPMailer::$CharSet.
1092 if (static::idnSupported() && $this->has8bitChars(substr($address, ++$pos))) {
1093 if ('Reply-To' !== $kind) {
1094 if (!array_key_exists($address, $this->RecipientsQueue)) {
1095 $this->RecipientsQueue[$address] = $params;
1096
1097 return true;
1098 }
1099 } elseif (!array_key_exists($address, $this->ReplyToQueue)) {
1100 $this->ReplyToQueue[$address] = $params;
1101
1102 return true;
1103 }
1104
1105 return false;
1106 }
1107
1108 // Immediately add standard addresses without IDN.
1109 return call_user_func_array([$this, 'addAnAddress'], $params);
1110 }
1111
1124 protected function addAnAddress($kind, $address, $name = '')
1125 {
1126 if (!in_array($kind, ['to', 'cc', 'bcc', 'Reply-To'])) {
1127 $error_message = sprintf(
1128 '%s: %s',
1129 $this->lang('Invalid recipient kind'),
1130 $kind
1131 );
1132 $this->setError($error_message);
1133 $this->edebug($error_message);
1134 if ($this->exceptions) {
1135 throw new Exception($error_message);
1136 }
1137
1138 return false;
1139 }
1140 if (!static::validateAddress($address)) {
1141 $error_message = sprintf(
1142 '%s (%s): %s',
1143 $this->lang('invalid_address'),
1144 $kind,
1145 $address
1146 );
1147 $this->setError($error_message);
1148 $this->edebug($error_message);
1149 if ($this->exceptions) {
1150 throw new Exception($error_message);
1151 }
1152
1153 return false;
1154 }
1155 if ('Reply-To' !== $kind) {
1156 if (!array_key_exists(strtolower($address), $this->all_recipients)) {
1157 $this->{$kind}[] = [$address, $name];
1158 $this->all_recipients[strtolower($address)] = true;
1159
1160 return true;
1161 }
1162 } elseif (!array_key_exists(strtolower($address), $this->ReplyTo)) {
1163 $this->ReplyTo[strtolower($address)] = [$address, $name];
1164
1165 return true;
1166 }
1167
1168 return false;
1169 }
1170
1184 public static function parseAddresses($addrstr, $useimap = true)
1185 {
1186 $addresses = [];
1187 if ($useimap && function_exists('imap_rfc822_parse_adrlist')) {
1188 //Use this built-in parser if it's available
1189 $list = imap_rfc822_parse_adrlist($addrstr, '');
1190 foreach ($list as $address) {
1191 if (('.SYNTAX-ERROR.' !== $address->host) && static::validateAddress(
1192 $address->mailbox . '@' . $address->host
1193 )) {
1194 $addresses[] = [
1195 'name' => (property_exists($address, 'personal') ? $address->personal : ''),
1196 'address' => $address->mailbox . '@' . $address->host,
1197 ];
1198 }
1199 }
1200 } else {
1201 //Use this simpler parser
1202 $list = explode(',', $addrstr);
1203 foreach ($list as $address) {
1204 $address = trim($address);
1205 //Is there a separate name part?
1206 if (strpos($address, '<') === false) {
1207 //No separate name, just use the whole thing
1208 if (static::validateAddress($address)) {
1209 $addresses[] = [
1210 'name' => '',
1211 'address' => $address,
1212 ];
1213 }
1214 } else {
1215 list($name, $email) = explode('<', $address);
1216 $email = trim(str_replace('>', '', $email));
1217 if (static::validateAddress($email)) {
1218 $addresses[] = [
1219 'name' => trim(str_replace(['"', "'"], '', $name)),
1220 'address' => $email,
1221 ];
1222 }
1223 }
1224 }
1225 }
1226
1227 return $addresses;
1228 }
1229
1241 public function setFrom($address, $name = '', $auto = true)
1242 {
1243 $address = trim($address);
1244 $name = trim(preg_replace('/[\r\n]+/', '', $name)); //Strip breaks and trim
1245 // Don't validate now addresses with IDN. Will be done in send().
1246 $pos = strrpos($address, '@');
1247 if ((false === $pos)
1248 || ((!$this->has8bitChars(substr($address, ++$pos)) || !static::idnSupported())
1249 && !static::validateAddress($address))
1250 ) {
1251 $error_message = sprintf(
1252 '%s (From): %s',
1253 $this->lang('invalid_address'),
1254 $address
1255 );
1256 $this->setError($error_message);
1257 $this->edebug($error_message);
1258 if ($this->exceptions) {
1259 throw new Exception($error_message);
1260 }
1261
1262 return false;
1263 }
1264 $this->From = $address;
1265 $this->FromName = $name;
1266 if ($auto && empty($this->Sender)) {
1267 $this->Sender = $address;
1268 }
1269
1270 return true;
1271 }
1272
1281 public function getLastMessageID()
1282 {
1283 return $this->lastMessageID;
1284 }
1285
1310 public static function validateAddress($address, $patternselect = null)
1311 {
1312 if (null === $patternselect) {
1313 $patternselect = static::$validator;
1314 }
1315 if (is_callable($patternselect)) {
1316 return $patternselect($address);
1317 }
1318 //Reject line breaks in addresses; it's valid RFC5322, but not RFC5321
1319 if (strpos($address, "\n") !== false || strpos($address, "\r") !== false) {
1320 return false;
1321 }
1322 switch ($patternselect) {
1323 case 'pcre': //Kept for BC
1324 case 'pcre8':
1325 /*
1326 * A more complex and more permissive version of the RFC5322 regex on which FILTER_VALIDATE_EMAIL
1327 * is based.
1328 * In addition to the addresses allowed by filter_var, also permits:
1329 * * dotless domains: `a@b`
1330 * * comments: `1234 @ local(blah) .machine .example`
1331 * * quoted elements: `'"test blah"@example.org'`
1332 * * numeric TLDs: `a@b.123`
1333 * * unbracketed IPv4 literals: `a@192.168.0.1`
1334 * * IPv6 literals: 'first.last@[IPv6:a1::]'
1335 * Not all of these will necessarily work for sending!
1336 *
1337 * @see http://squiloople.com/2009/12/20/email-address-validation/
1338 * @copyright 2009-2010 Michael Rushton
1339 * Feel free to use and redistribute this code. But please keep this copyright notice.
1340 */
1341 return (bool) preg_match(
1342 '/^(?!(?>(?1)"?(?>\\\[ -~]|[^"])"?(?1)){255,})(?!(?>(?1)"?(?>\\\[ -~]|[^"])"?(?1)){65,}@)' .
1343 '((?>(?>(?>((?>(?>(?>\x0D\x0A)?[\t ])+|(?>[\t ]*\x0D\x0A)?[\t ]+)?)(\‍((?>(?2)' .
1344 '(?>[\x01-\x08\x0B\x0C\x0E-\'*-\[\]-\x7F]|\\\[\x00-\x7F]|(?3)))*(?2)\‍)))+(?2))|(?2))?)' .
1345 '([!#-\'*+\/-9=?^-~-]+|"(?>(?2)(?>[\x01-\x08\x0B\x0C\x0E-!#-\[\]-\x7F]|\\\[\x00-\x7F]))*' .
1346 '(?2)")(?>(?1)\.(?1)(?4))*(?1)@(?!(?1)[a-z0-9-]{64,})(?1)(?>([a-z0-9](?>[a-z0-9-]*[a-z0-9])?)' .
1347 '(?>(?1)\.(?!(?1)[a-z0-9-]{64,})(?1)(?5)){0,126}|\[(?:(?>IPv6:(?>([a-f0-9]{1,4})(?>:(?6)){7}' .
1348 '|(?!(?:.*[a-f0-9][:\]]){8,})((?6)(?>:(?6)){0,6})?::(?7)?))|(?>(?>IPv6:(?>(?6)(?>:(?6)){5}:' .
1349 '|(?!(?:.*[a-f0-9]:){6,})(?8)?::(?>((?6)(?>:(?6)){0,4}):)?))?(25[0-5]|2[0-4][0-9]|1[0-9]{2}' .
1350 '|[1-9]?[0-9])(?>\.(?9)){3}))\])(?1)$/isD',
1351 $address
1352 );
1353 case 'html5':
1354 /*
1355 * This is the pattern used in the HTML5 spec for validation of 'email' type form input elements.
1356 *
1357 * @see http://www.whatwg.org/specs/web-apps/current-work/#e-mail-state-(type=email)
1358 */
1359 return (bool) preg_match(
1360 '/^[a-zA-Z0-9.!#$%&\'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}' .
1361 '[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/sD',
1362 $address
1363 );
1364 case 'php':
1365 default:
1366 return filter_var($address, FILTER_VALIDATE_EMAIL) !== false;
1367 }
1368 }
1369
1376 public static function idnSupported()
1377 {
1378 return function_exists('idn_to_ascii') && function_exists('mb_convert_encoding');
1379 }
1380
1395 public function punyencodeAddress($address)
1396 {
1397 // Verify we have required functions, CharSet, and at-sign.
1398 $pos = strrpos($address, '@');
1399 if (!empty($this->CharSet) &&
1400 false !== $pos &&
1401 static::idnSupported()
1402 ) {
1403 $domain = substr($address, ++$pos);
1404 // Verify CharSet string is a valid one, and domain properly encoded in this CharSet.
1405 if ($this->has8bitChars($domain) && @mb_check_encoding($domain, $this->CharSet)) {
1406 $domain = mb_convert_encoding($domain, 'UTF-8', $this->CharSet);
1407 //Ignore IDE complaints about this line - method signature changed in PHP 5.4
1408 $errorcode = 0;
1409 if (defined('INTL_IDNA_VARIANT_UTS46')) {
1410 $punycode = idn_to_ascii($domain, $errorcode, INTL_IDNA_VARIANT_UTS46);
1411 } elseif (defined('INTL_IDNA_VARIANT_2003')) {
1412 $punycode = idn_to_ascii($domain, $errorcode, INTL_IDNA_VARIANT_2003);
1413 } else {
1414 $punycode = idn_to_ascii($domain, $errorcode);
1415 }
1416 if (false !== $punycode) {
1417 return substr($address, 0, $pos) . $punycode;
1418 }
1419 }
1420 }
1421
1422 return $address;
1423 }
1424
1433 public function send()
1434 {
1435 try {
1436 if (!$this->preSend()) {
1437 return false;
1438 }
1439
1440 return $this->postSend();
1441 } catch (Exception $exc) {
1442 $this->mailHeader = '';
1443 $this->setError($exc->getMessage());
1444 if ($this->exceptions) {
1445 throw $exc;
1446 }
1447
1448 return false;
1449 }
1450 }
1451
1459 public function preSend()
1460 {
1461 if ('smtp' === $this->Mailer
1462 || ('mail' === $this->Mailer && stripos(PHP_OS, 'WIN') === 0)
1463 ) {
1464 //SMTP mandates RFC-compliant line endings
1465 //and it's also used with mail() on Windows
1466 static::setLE(self::CRLF);
1467 } else {
1468 //Maintain backward compatibility with legacy Linux command line mailers
1469 static::setLE(PHP_EOL);
1470 }
1471 //Check for buggy PHP versions that add a header with an incorrect line break
1472 if ('mail' === $this->Mailer
1473 && ((PHP_VERSION_ID >= 70000 && PHP_VERSION_ID < 70017)
1474 || (PHP_VERSION_ID >= 70100 && PHP_VERSION_ID < 70103))
1475 && ini_get('mail.add_x_header') === '1'
1476 && stripos(PHP_OS, 'WIN') === 0
1477 ) {
1478 trigger_error(
1479 'Your version of PHP is affected by a bug that may result in corrupted messages.' .
1480 ' To fix it, switch to sending using SMTP, disable the mail.add_x_header option in' .
1481 ' your php.ini, switch to MacOS or Linux, or upgrade your PHP to version 7.0.17+ or 7.1.3+.',
1482 E_USER_WARNING
1483 );
1484 }
1485
1486 try {
1487 $this->error_count = 0; // Reset errors
1488 $this->mailHeader = '';
1489
1490 // Dequeue recipient and Reply-To addresses with IDN
1491 foreach (array_merge($this->RecipientsQueue, $this->ReplyToQueue) as $params) {
1492 $params[1] = $this->punyencodeAddress($params[1]);
1493 call_user_func_array([$this, 'addAnAddress'], $params);
1494 }
1495 if (count($this->to) + count($this->cc) + count($this->bcc) < 1) {
1496 throw new Exception($this->lang('provide_address'), self::STOP_CRITICAL);
1497 }
1498
1499 // Validate From, Sender, and ConfirmReadingTo addresses
1500 foreach (['From', 'Sender', 'ConfirmReadingTo'] as $address_kind) {
1501 $this->$address_kind = trim($this->$address_kind);
1502 if (empty($this->$address_kind)) {
1503 continue;
1504 }
1505 $this->$address_kind = $this->punyencodeAddress($this->$address_kind);
1506 if (!static::validateAddress($this->$address_kind)) {
1507 $error_message = sprintf(
1508 '%s (%s): %s',
1509 $this->lang('invalid_address'),
1510 $address_kind,
1511 $this->$address_kind
1512 );
1513 $this->setError($error_message);
1514 $this->edebug($error_message);
1515 if ($this->exceptions) {
1516 throw new Exception($error_message);
1517 }
1518
1519 return false;
1520 }
1521 }
1522
1523 // Set whether the message is multipart/alternative
1524 if ($this->alternativeExists()) {
1525 $this->ContentType = static::CONTENT_TYPE_MULTIPART_ALTERNATIVE;
1526 }
1527
1528 $this->setMessageType();
1529 // Refuse to send an empty message unless we are specifically allowing it
1530 if (!$this->AllowEmpty && empty($this->Body)) {
1531 throw new Exception($this->lang('empty_message'), self::STOP_CRITICAL);
1532 }
1533
1534 //Trim subject consistently
1535 $this->Subject = trim($this->Subject);
1536 // Create body before headers in case body makes changes to headers (e.g. altering transfer encoding)
1537 $this->MIMEHeader = '';
1538 $this->MIMEBody = $this->createBody();
1539 // createBody may have added some headers, so retain them
1540 $tempheaders = $this->MIMEHeader;
1541 $this->MIMEHeader = $this->createHeader();
1542 $this->MIMEHeader .= $tempheaders;
1543
1544 // To capture the complete message when using mail(), create
1545 // an extra header list which createHeader() doesn't fold in
1546 if ('mail' === $this->Mailer) {
1547 if (count($this->to) > 0) {
1548 $this->mailHeader .= $this->addrAppend('To', $this->to);
1549 } else {
1550 $this->mailHeader .= $this->headerLine('To', 'undisclosed-recipients:;');
1551 }
1552 $this->mailHeader .= $this->headerLine(
1553 'Subject',
1554 $this->encodeHeader($this->secureHeader($this->Subject))
1555 );
1556 }
1557
1558 // Sign with DKIM if enabled
1559 if (!empty($this->DKIM_domain)
1560 && !empty($this->DKIM_selector)
1561 && (!empty($this->DKIM_private_string)
1562 || (!empty($this->DKIM_private)
1563 && static::isPermittedPath($this->DKIM_private)
1564 && file_exists($this->DKIM_private)
1565 )
1566 )
1567 ) {
1568 $header_dkim = $this->DKIM_Add(
1569 $this->MIMEHeader . $this->mailHeader,
1570 $this->encodeHeader($this->secureHeader($this->Subject)),
1571 $this->MIMEBody
1572 );
1573 $this->MIMEHeader = static::stripTrailingWSP($this->MIMEHeader) . static::$LE .
1574 static::normalizeBreaks($header_dkim) . static::$LE;
1575 }
1576
1577 return true;
1578 } catch (Exception $exc) {
1579 $this->setError($exc->getMessage());
1580 if ($this->exceptions) {
1581 throw $exc;
1582 }
1583
1584 return false;
1585 }
1586 }
1587
1595 public function postSend()
1596 {
1597 try {
1598 // Choose the mailer and send through it
1599 switch ($this->Mailer) {
1600 case 'sendmail':
1601 case 'qmail':
1602 return $this->sendmailSend($this->MIMEHeader, $this->MIMEBody);
1603 case 'smtp':
1604 return $this->smtpSend($this->MIMEHeader, $this->MIMEBody);
1605 case 'mail':
1606 return $this->mailSend($this->MIMEHeader, $this->MIMEBody);
1607 default:
1608 $sendMethod = $this->Mailer . 'Send';
1609 if (method_exists($this, $sendMethod)) {
1610 return $this->$sendMethod($this->MIMEHeader, $this->MIMEBody);
1611 }
1612
1613 return $this->mailSend($this->MIMEHeader, $this->MIMEBody);
1614 }
1615 } catch (Exception $exc) {
1616 $this->setError($exc->getMessage());
1617 $this->edebug($exc->getMessage());
1618 if ($this->exceptions) {
1619 throw $exc;
1620 }
1621 }
1622
1623 return false;
1624 }
1625
1638 protected function sendmailSend($header, $body)
1639 {
1640 $header = static::stripTrailingWSP($header) . static::$LE . static::$LE;
1641
1642 // CVE-2016-10033, CVE-2016-10045: Don't pass -f if characters will be escaped.
1643 if (!empty($this->Sender) && self::isShellSafe($this->Sender)) {
1644 if ('qmail' === $this->Mailer) {
1645 $sendmailFmt = '%s -f%s';
1646 } else {
1647 $sendmailFmt = '%s -oi -f%s -t';
1648 }
1649 } elseif ('qmail' === $this->Mailer) {
1650 $sendmailFmt = '%s';
1651 } else {
1652 $sendmailFmt = '%s -oi -t';
1653 }
1654
1655 $sendmail = sprintf($sendmailFmt, escapeshellcmd($this->Sendmail), $this->Sender);
1656
1657 if ($this->SingleTo) {
1658 foreach ($this->SingleToArray as $toAddr) {
1659 $mail = @popen($sendmail, 'w');
1660 if (!$mail) {
1661 throw new Exception($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL);
1662 }
1663 fwrite($mail, 'To: ' . $toAddr . "\n");
1664 fwrite($mail, $header);
1665 fwrite($mail, $body);
1666 $result = pclose($mail);
1667 $this->doCallback(
1668 ($result === 0),
1669 [$toAddr],
1670 $this->cc,
1671 $this->bcc,
1672 $this->Subject,
1673 $body,
1674 $this->From,
1675 []
1676 );
1677 if (0 !== $result) {
1678 throw new Exception($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL);
1679 }
1680 }
1681 } else {
1682 $mail = @popen($sendmail, 'w');
1683 if (!$mail) {
1684 throw new Exception($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL);
1685 }
1686 fwrite($mail, $header);
1687 fwrite($mail, $body);
1688 $result = pclose($mail);
1689 $this->doCallback(
1690 ($result === 0),
1691 $this->to,
1692 $this->cc,
1693 $this->bcc,
1694 $this->Subject,
1695 $body,
1696 $this->From,
1697 []
1698 );
1699 if (0 !== $result) {
1700 throw new Exception($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL);
1701 }
1702 }
1703
1704 return true;
1705 }
1706
1717 protected static function isShellSafe($string)
1718 {
1719 // Future-proof
1720 if (escapeshellcmd($string) !== $string
1721 || !in_array(escapeshellarg($string), ["'$string'", "\"$string\""])
1722 ) {
1723 return false;
1724 }
1725
1726 $length = strlen($string);
1727
1728 for ($i = 0; $i < $length; ++$i) {
1729 $c = $string[$i];
1730
1731 // All other characters have a special meaning in at least one common shell, including = and +.
1732 // Full stop (.) has a special meaning in cmd.exe, but its impact should be negligible here.
1733 // Note that this does permit non-Latin alphanumeric characters based on the current locale.
1734 if (!ctype_alnum($c) && strpos('@_-.', $c) === false) {
1735 return false;
1736 }
1737 }
1738
1739 return true;
1740 }
1741
1751 protected static function isPermittedPath($path)
1752 {
1753 return !preg_match('#^[a-z]+://#i', $path);
1754 }
1755
1768 protected function mailSend($header, $body)
1769 {
1770 $header = static::stripTrailingWSP($header) . static::$LE . static::$LE;
1771
1772 $toArr = [];
1773 foreach ($this->to as $toaddr) {
1774 $toArr[] = $this->addrFormat($toaddr);
1775 }
1776 $to = implode(', ', $toArr);
1777
1778 $params = null;
1779 //This sets the SMTP envelope sender which gets turned into a return-path header by the receiver
1780 //A space after `-f` is optional, but there is a long history of its presence
1781 //causing problems, so we don't use one
1782 //Exim docs: http://www.exim.org/exim-html-current/doc/html/spec_html/ch-the_exim_command_line.html
1783 //Sendmail docs: http://www.sendmail.org/~ca/email/man/sendmail.html
1784 //Qmail docs: http://www.qmail.org/man/man8/qmail-inject.html
1785 //Example problem: https://www.drupal.org/node/1057954
1786 // CVE-2016-10033, CVE-2016-10045: Don't pass -f if characters will be escaped.
1787 if (!empty($this->Sender) && static::validateAddress($this->Sender) && self::isShellSafe($this->Sender)) {
1788 $params = sprintf('-f%s', $this->Sender);
1789 }
1790 if (!empty($this->Sender) && static::validateAddress($this->Sender)) {
1791 $old_from = ini_get('sendmail_from');
1792 ini_set('sendmail_from', $this->Sender);
1793 }
1794 $result = false;
1795 if ($this->SingleTo && count($toArr) > 1) {
1796 foreach ($toArr as $toAddr) {
1797 $result = $this->mailPassthru($toAddr, $this->Subject, $body, $header, $params);
1798 $this->doCallback($result, [$toAddr], $this->cc, $this->bcc, $this->Subject, $body, $this->From, []);
1799 }
1800 } else {
1801 $result = $this->mailPassthru($to, $this->Subject, $body, $header, $params);
1802 $this->doCallback($result, $this->to, $this->cc, $this->bcc, $this->Subject, $body, $this->From, []);
1803 }
1804 if (isset($old_from)) {
1805 ini_set('sendmail_from', $old_from);
1806 }
1807 if (!$result) {
1808 throw new Exception($this->lang('instantiate'), self::STOP_CRITICAL);
1809 }
1810
1811 return true;
1812 }
1813
1821 public function getSMTPInstance()
1822 {
1823 if (!is_object($this->smtp)) {
1824 $this->smtp = new SMTP();
1825 }
1826
1827 return $this->smtp;
1828 }
1829
1835 public function setSMTPInstance(SMTP $smtp)
1836 {
1837 $this->smtp = $smtp;
1838
1839 return $this->smtp;
1840 }
1841
1857 protected function smtpSend($header, $body)
1858 {
1859 $header = static::stripTrailingWSP($header) . static::$LE . static::$LE;
1860 $bad_rcpt = [];
1861 if (!$this->smtpConnect($this->SMTPOptions)) {
1862 throw new Exception($this->lang('smtp_connect_failed'), self::STOP_CRITICAL);
1863 }
1864 //Sender already validated in preSend()
1865 if ('' === $this->Sender) {
1866 $smtp_from = $this->From;
1867 } else {
1868 $smtp_from = $this->Sender;
1869 }
1870 if (!$this->smtp->mail($smtp_from)) {
1871 $this->setError($this->lang('from_failed') . $smtp_from . ' : ' . implode(',', $this->smtp->getError()));
1872 throw new Exception($this->ErrorInfo, self::STOP_CRITICAL);
1873 }
1874
1875 $callbacks = [];
1876 // Attempt to send to all recipients
1877 foreach ([$this->to, $this->cc, $this->bcc] as $togroup) {
1878 foreach ($togroup as $to) {
1879 if (!$this->smtp->recipient($to[0], $this->dsn)) {
1880 $error = $this->smtp->getError();
1881 $bad_rcpt[] = ['to' => $to[0], 'error' => $error['detail']];
1882 $isSent = false;
1883 } else {
1884 $isSent = true;
1885 }
1886
1887 $callbacks[] = ['issent'=>$isSent, 'to'=>$to[0]];
1888 }
1889 }
1890
1891 // Only send the DATA command if we have viable recipients
1892 if ((count($this->all_recipients) > count($bad_rcpt)) && !$this->smtp->data($header . $body)) {
1893 throw new Exception($this->lang('data_not_accepted'), self::STOP_CRITICAL);
1894 }
1895
1896 $smtp_transaction_id = $this->smtp->getLastTransactionID();
1897
1898 if ($this->SMTPKeepAlive) {
1899 $this->smtp->reset();
1900 } else {
1901 $this->smtp->quit();
1902 $this->smtp->close();
1903 }
1904
1905 foreach ($callbacks as $cb) {
1906 $this->doCallback(
1907 $cb['issent'],
1908 [$cb['to']],
1909 [],
1910 [],
1911 $this->Subject,
1912 $body,
1913 $this->From,
1914 ['smtp_transaction_id' => $smtp_transaction_id]
1915 );
1916 }
1917
1918 //Create error message for any bad addresses
1919 if (count($bad_rcpt) > 0) {
1920 $errstr = '';
1921 foreach ($bad_rcpt as $bad) {
1922 $errstr .= $bad['to'] . ': ' . $bad['error'];
1923 }
1924 throw new Exception($this->lang('recipients_failed') . $errstr, self::STOP_CONTINUE);
1925 }
1926
1927 return true;
1928 }
1929
1942 public function smtpConnect($options = null)
1943 {
1944 if (null === $this->smtp) {
1945 $this->smtp = $this->getSMTPInstance();
1946 }
1947
1948 //If no options are provided, use whatever is set in the instance
1949 if (null === $options) {
1951 }
1952
1953 // Already connected?
1954 if ($this->smtp->connected()) {
1955 return true;
1956 }
1957
1958 $this->smtp->setTimeout($this->Timeout);
1959 $this->smtp->setDebugLevel($this->SMTPDebug);
1960 $this->smtp->setDebugOutput($this->Debugoutput);
1961 $this->smtp->setVerp($this->do_verp);
1962 $hosts = explode(';', $this->Host);
1963 $lastexception = null;
1964
1965 foreach ($hosts as $hostentry) {
1966 $hostinfo = [];
1967 if (!preg_match(
1968 '/^(?:(ssl|tls):\/\/)?(.+?)(?::(\d+))?$/',
1969 trim($hostentry),
1970 $hostinfo
1971 )) {
1972 $this->edebug($this->lang('invalid_hostentry') . ' ' . trim($hostentry));
1973 // Not a valid host entry
1974 continue;
1975 }
1976 // $hostinfo[1]: optional ssl or tls prefix
1977 // $hostinfo[2]: the hostname
1978 // $hostinfo[3]: optional port number
1979 // The host string prefix can temporarily override the current setting for SMTPSecure
1980 // If it's not specified, the default value is used
1981
1982 //Check the host name is a valid name or IP address before trying to use it
1983 if (!static::isValidHost($hostinfo[2])) {
1984 $this->edebug($this->lang('invalid_host') . ' ' . $hostinfo[2]);
1985 continue;
1986 }
1987 $prefix = '';
1988 $secure = $this->SMTPSecure;
1989 $tls = (static::ENCRYPTION_STARTTLS === $this->SMTPSecure);
1990 if ('ssl' === $hostinfo[1] || ('' === $hostinfo[1] && static::ENCRYPTION_SMTPS === $this->SMTPSecure)) {
1991 $prefix = 'ssl://';
1992 $tls = false; // Can't have SSL and TLS at the same time
1993 $secure = static::ENCRYPTION_SMTPS;
1994 } elseif ('tls' === $hostinfo[1]) {
1995 $tls = true;
1996 // tls doesn't use a prefix
1997 $secure = static::ENCRYPTION_STARTTLS;
1998 }
1999 //Do we need the OpenSSL extension?
2000 $sslext = defined('OPENSSL_ALGO_SHA256');
2001 if (static::ENCRYPTION_STARTTLS === $secure || static::ENCRYPTION_SMTPS === $secure) {
2002 //Check for an OpenSSL constant rather than using extension_loaded, which is sometimes disabled
2003 if (!$sslext) {
2004 throw new Exception($this->lang('extension_missing') . 'openssl', self::STOP_CRITICAL);
2005 }
2006 }
2007 $host = $hostinfo[2];
2008 $port = $this->Port;
2009 if (array_key_exists(3, $hostinfo) && is_numeric($hostinfo[3]) && $hostinfo[3] > 0 && $hostinfo[3] < 65536) {
2010 $port = (int) $hostinfo[3];
2011 }
2012 if ($this->smtp->connect($prefix . $host, $port, $this->Timeout, $options)) {
2013 try {
2014 if ($this->Helo) {
2015 $hello = $this->Helo;
2016 } else {
2017 $hello = $this->serverHostname();
2018 }
2019 $this->smtp->hello($hello);
2020 //Automatically enable TLS encryption if:
2021 // * it's not disabled
2022 // * we have openssl extension
2023 // * we are not already using SSL
2024 // * the server offers STARTTLS
2025 if ($this->SMTPAutoTLS && $sslext && 'ssl' !== $secure && $this->smtp->getServerExt('STARTTLS')) {
2026 $tls = true;
2027 }
2028 if ($tls) {
2029 if (!$this->smtp->startTLS()) {
2030 throw new Exception($this->lang('connect_host'));
2031 }
2032 // We must resend EHLO after TLS negotiation
2033 $this->smtp->hello($hello);
2034 }
2035 if ($this->SMTPAuth && !$this->smtp->authenticate(
2036 $this->Username,
2037 $this->Password,
2038 $this->AuthType,
2039 $this->oauth
2040 )) {
2041 throw new Exception($this->lang('authenticate'));
2042 }
2043
2044 return true;
2045 } catch (Exception $exc) {
2046 $lastexception = $exc;
2047 $this->edebug($exc->getMessage());
2048 // We must have connected, but then failed TLS or Auth, so close connection nicely
2049 $this->smtp->quit();
2050 }
2051 }
2052 }
2053 // If we get here, all connection attempts have failed, so close connection hard
2054 $this->smtp->close();
2055 // As we've caught all exceptions, just report whatever the last one was
2056 if ($this->exceptions && null !== $lastexception) {
2057 throw $lastexception;
2058 }
2059
2060 return false;
2061 }
2062
2066 public function smtpClose()
2067 {
2068 if ((null !== $this->smtp) && $this->smtp->connected()) {
2069 $this->smtp->quit();
2070 $this->smtp->close();
2071 }
2072 }
2073
2084 public function setLanguage($langcode = 'en', $lang_path = '')
2085 {
2086 // Backwards compatibility for renamed language codes
2087 $renamed_langcodes = [
2088 'br' => 'pt_br',
2089 'cz' => 'cs',
2090 'dk' => 'da',
2091 'no' => 'nb',
2092 'se' => 'sv',
2093 'rs' => 'sr',
2094 'tg' => 'tl',
2095 'am' => 'hy',
2096 ];
2097
2098 if (isset($renamed_langcodes[$langcode])) {
2099 $langcode = $renamed_langcodes[$langcode];
2100 }
2101
2102 // Define full set of translatable strings in English
2103 $PHPMAILER_LANG = [
2104 'authenticate' => 'SMTP Error: Could not authenticate.',
2105 'connect_host' => 'SMTP Error: Could not connect to SMTP host.',
2106 'data_not_accepted' => 'SMTP Error: data not accepted.',
2107 'empty_message' => 'Message body empty',
2108 'encoding' => 'Unknown encoding: ',
2109 'execute' => 'Could not execute: ',
2110 'file_access' => 'Could not access file: ',
2111 'file_open' => 'File Error: Could not open file: ',
2112 'from_failed' => 'The following From address failed: ',
2113 'instantiate' => 'Could not instantiate mail function.',
2114 'invalid_address' => 'Invalid address: ',
2115 'invalid_hostentry' => 'Invalid hostentry: ',
2116 'invalid_host' => 'Invalid host: ',
2117 'mailer_not_supported' => ' mailer is not supported.',
2118 'provide_address' => 'You must provide at least one recipient email address.',
2119 'recipients_failed' => 'SMTP Error: The following recipients failed: ',
2120 'signing' => 'Signing Error: ',
2121 'smtp_connect_failed' => 'SMTP connect() failed.',
2122 'smtp_error' => 'SMTP server error: ',
2123 'variable_set' => 'Cannot set or reset variable: ',
2124 'extension_missing' => 'Extension missing: ',
2125 ];
2126 if (empty($lang_path)) {
2127 // Calculate an absolute path so it can work if CWD is not here
2128 $lang_path = dirname(__DIR__) . DIRECTORY_SEPARATOR . 'language' . DIRECTORY_SEPARATOR;
2129 }
2130 //Validate $langcode
2131 if (!preg_match('/^[a-z]{2}(?:_[a-zA-Z]{2})?$/', $langcode)) {
2132 $langcode = 'en';
2133 }
2134 $foundlang = true;
2135 $lang_file = $lang_path . 'phpmailer.lang-' . $langcode . '.php';
2136 // There is no English translation file
2137 if ('en' !== $langcode) {
2138 // Make sure language file path is readable
2139 if (!static::isPermittedPath($lang_file) || !file_exists($lang_file)) {
2140 $foundlang = false;
2141 } else {
2142 // Overwrite language-specific strings.
2143 // This way we'll never have missing translation keys.
2144 $foundlang = include $lang_file;
2145 }
2146 }
2147 $this->language = $PHPMAILER_LANG;
2148
2149 return (bool) $foundlang; // Returns false if language not found
2150 }
2151
2157 public function getTranslations()
2158 {
2159 return $this->language;
2160 }
2161
2173 public function addrAppend($type, $addr)
2174 {
2175 $addresses = [];
2176 foreach ($addr as $address) {
2177 $addresses[] = $this->addrFormat($address);
2178 }
2179
2180 return $type . ': ' . implode(', ', $addresses) . static::$LE;
2181 }
2182
2191 public function addrFormat($addr)
2192 {
2193 if (empty($addr[1])) { // No name provided
2194 return $this->secureHeader($addr[0]);
2195 }
2196
2197 return $this->encodeHeader($this->secureHeader($addr[1]), 'phrase') .
2198 ' <' . $this->secureHeader($addr[0]) . '>';
2199 }
2200
2213 public function wrapText($message, $length, $qp_mode = false)
2214 {
2215 if ($qp_mode) {
2216 $soft_break = sprintf(' =%s', static::$LE);
2217 } else {
2218 $soft_break = static::$LE;
2219 }
2220 // If utf-8 encoding is used, we will need to make sure we don't
2221 // split multibyte characters when we wrap
2222 $is_utf8 = static::CHARSET_UTF8 === strtolower($this->CharSet);
2223 $lelen = strlen(static::$LE);
2224 $crlflen = strlen(static::$LE);
2225
2226 $message = static::normalizeBreaks($message);
2227 //Remove a trailing line break
2228 if (substr($message, -$lelen) === static::$LE) {
2229 $message = substr($message, 0, -$lelen);
2230 }
2231
2232 //Split message into lines
2233 $lines = explode(static::$LE, $message);
2234 //Message will be rebuilt in here
2235 $message = '';
2236 foreach ($lines as $line) {
2237 $words = explode(' ', $line);
2238 $buf = '';
2239 $firstword = true;
2240 foreach ($words as $word) {
2241 if ($qp_mode && (strlen($word) > $length)) {
2242 $space_left = $length - strlen($buf) - $crlflen;
2243 if (!$firstword) {
2244 if ($space_left > 20) {
2245 $len = $space_left;
2246 if ($is_utf8) {
2247 $len = $this->utf8CharBoundary($word, $len);
2248 } elseif ('=' === substr($word, $len - 1, 1)) {
2249 --$len;
2250 } elseif ('=' === substr($word, $len - 2, 1)) {
2251 $len -= 2;
2252 }
2253 $part = substr($word, 0, $len);
2254 $word = substr($word, $len);
2255 $buf .= ' ' . $part;
2256 $message .= $buf . sprintf('=%s', static::$LE);
2257 } else {
2258 $message .= $buf . $soft_break;
2259 }
2260 $buf = '';
2261 }
2262 while ($word !== '') {
2263 if ($length <= 0) {
2264 break;
2265 }
2266 $len = $length;
2267 if ($is_utf8) {
2268 $len = $this->utf8CharBoundary($word, $len);
2269 } elseif ('=' === substr($word, $len - 1, 1)) {
2270 --$len;
2271 } elseif ('=' === substr($word, $len - 2, 1)) {
2272 $len -= 2;
2273 }
2274 $part = substr($word, 0, $len);
2275 $word = (string) substr($word, $len);
2276
2277 if ($word !== '') {
2278 $message .= $part . sprintf('=%s', static::$LE);
2279 } else {
2280 $buf = $part;
2281 }
2282 }
2283 } else {
2284 $buf_o = $buf;
2285 if (!$firstword) {
2286 $buf .= ' ';
2287 }
2288 $buf .= $word;
2289
2290 if ('' !== $buf_o && strlen($buf) > $length) {
2291 $message .= $buf_o . $soft_break;
2292 $buf = $word;
2293 }
2294 }
2295 $firstword = false;
2296 }
2297 $message .= $buf . static::$LE;
2298 }
2299
2300 return $message;
2301 }
2302
2313 public function utf8CharBoundary($encodedText, $maxLength)
2314 {
2315 $foundSplitPos = false;
2316 $lookBack = 3;
2317 while (!$foundSplitPos) {
2318 $lastChunk = substr($encodedText, $maxLength - $lookBack, $lookBack);
2319 $encodedCharPos = strpos($lastChunk, '=');
2320 if (false !== $encodedCharPos) {
2321 // Found start of encoded character byte within $lookBack block.
2322 // Check the encoded byte value (the 2 chars after the '=')
2323 $hex = substr($encodedText, $maxLength - $lookBack + $encodedCharPos + 1, 2);
2324 $dec = hexdec($hex);
2325 if ($dec < 128) {
2326 // Single byte character.
2327 // If the encoded char was found at pos 0, it will fit
2328 // otherwise reduce maxLength to start of the encoded char
2329 if ($encodedCharPos > 0) {
2330 $maxLength -= $lookBack - $encodedCharPos;
2331 }
2332 $foundSplitPos = true;
2333 } elseif ($dec >= 192) {
2334 // First byte of a multi byte character
2335 // Reduce maxLength to split at start of character
2336 $maxLength -= $lookBack - $encodedCharPos;
2337 $foundSplitPos = true;
2338 } elseif ($dec < 192) {
2339 // Middle byte of a multi byte character, look further back
2340 $lookBack += 3;
2341 }
2342 } else {
2343 // No encoded character found
2344 $foundSplitPos = true;
2345 }
2346 }
2347
2348 return $maxLength;
2349 }
2350
2357 public function setWordWrap()
2358 {
2359 if ($this->WordWrap < 1) {
2360 return;
2361 }
2362
2363 switch ($this->message_type) {
2364 case 'alt':
2365 case 'alt_inline':
2366 case 'alt_attach':
2367 case 'alt_inline_attach':
2368 $this->AltBody = $this->wrapText($this->AltBody, $this->WordWrap);
2369 break;
2370 default:
2371 $this->Body = $this->wrapText($this->Body, $this->WordWrap);
2372 break;
2373 }
2374 }
2375
2381 public function createHeader()
2382 {
2383 $result = '';
2384
2385 $result .= $this->headerLine('Date', '' === $this->MessageDate ? self::rfcDate() : $this->MessageDate);
2386
2387 // To be created automatically by mail()
2388 if ($this->SingleTo) {
2389 if ('mail' !== $this->Mailer) {
2390 foreach ($this->to as $toaddr) {
2391 $this->SingleToArray[] = $this->addrFormat($toaddr);
2392 }
2393 }
2394 } elseif (count($this->to) > 0) {
2395 if ('mail' !== $this->Mailer) {
2396 $result .= $this->addrAppend('To', $this->to);
2397 }
2398 } elseif (count($this->cc) === 0) {
2399 $result .= $this->headerLine('To', 'undisclosed-recipients:;');
2400 }
2401
2402 $result .= $this->addrAppend('From', [[trim($this->From), $this->FromName]]);
2403
2404 // sendmail and mail() extract Cc from the header before sending
2405 if (count($this->cc) > 0) {
2406 $result .= $this->addrAppend('Cc', $this->cc);
2407 }
2408
2409 // sendmail and mail() extract Bcc from the header before sending
2410 if ((
2411 'sendmail' === $this->Mailer || 'qmail' === $this->Mailer || 'mail' === $this->Mailer
2412 )
2413 && count($this->bcc) > 0
2414 ) {
2415 $result .= $this->addrAppend('Bcc', $this->bcc);
2416 }
2417
2418 if (count($this->ReplyTo) > 0) {
2419 $result .= $this->addrAppend('Reply-To', $this->ReplyTo);
2420 }
2421
2422 // mail() sets the subject itself
2423 if ('mail' !== $this->Mailer) {
2424 $result .= $this->headerLine('Subject', $this->encodeHeader($this->secureHeader($this->Subject)));
2425 }
2426
2427 // Only allow a custom message ID if it conforms to RFC 5322 section 3.6.4
2428 // https://tools.ietf.org/html/rfc5322#section-3.6.4
2429 if ('' !== $this->MessageID && preg_match('/^<.*@.*>$/', $this->MessageID)) {
2430 $this->lastMessageID = $this->MessageID;
2431 } else {
2432 $this->lastMessageID = sprintf('<%s@%s>', $this->uniqueid, $this->serverHostname());
2433 }
2434 $result .= $this->headerLine('Message-ID', $this->lastMessageID);
2435 if (null !== $this->Priority) {
2436 $result .= $this->headerLine('X-Priority', $this->Priority);
2437 }
2438 if ('' === $this->XMailer) {
2439 $result .= $this->headerLine(
2440 'X-Mailer',
2441 'PHPMailer ' . self::VERSION . ' (https://github.com/PHPMailer/PHPMailer)'
2442 );
2443 } else {
2444 $myXmailer = trim($this->XMailer);
2445 if ($myXmailer) {
2446 $result .= $this->headerLine('X-Mailer', $myXmailer);
2447 }
2448 }
2449
2450 if ('' !== $this->ConfirmReadingTo) {
2451 $result .= $this->headerLine('Disposition-Notification-To', '<' . $this->ConfirmReadingTo . '>');
2452 }
2453
2454 // Add custom headers
2455 foreach ($this->CustomHeader as $header) {
2456 $result .= $this->headerLine(
2457 trim($header[0]),
2458 $this->encodeHeader(trim($header[1]))
2459 );
2460 }
2461 if (!$this->sign_key_file) {
2462 $result .= $this->headerLine('MIME-Version', '1.0');
2463 $result .= $this->getMailMIME();
2464 }
2465
2466 return $result;
2467 }
2468
2474 public function getMailMIME()
2475 {
2476 $result = '';
2477 $ismultipart = true;
2478 switch ($this->message_type) {
2479 case 'inline':
2480 $result .= $this->headerLine('Content-Type', static::CONTENT_TYPE_MULTIPART_RELATED . ';');
2481 $result .= $this->textLine(' boundary="' . $this->boundary[1] . '"');
2482 break;
2483 case 'attach':
2484 case 'inline_attach':
2485 case 'alt_attach':
2486 case 'alt_inline_attach':
2487 $result .= $this->headerLine('Content-Type', static::CONTENT_TYPE_MULTIPART_MIXED . ';');
2488 $result .= $this->textLine(' boundary="' . $this->boundary[1] . '"');
2489 break;
2490 case 'alt':
2491 case 'alt_inline':
2492 $result .= $this->headerLine('Content-Type', static::CONTENT_TYPE_MULTIPART_ALTERNATIVE . ';');
2493 $result .= $this->textLine(' boundary="' . $this->boundary[1] . '"');
2494 break;
2495 default:
2496 // Catches case 'plain': and case '':
2497 $result .= $this->textLine('Content-Type: ' . $this->ContentType . '; charset=' . $this->CharSet);
2498 $ismultipart = false;
2499 break;
2500 }
2501 // RFC1341 part 5 says 7bit is assumed if not specified
2502 if (static::ENCODING_7BIT !== $this->Encoding) {
2503 // RFC 2045 section 6.4 says multipart MIME parts may only use 7bit, 8bit or binary CTE
2504 if ($ismultipart) {
2505 if (static::ENCODING_8BIT === $this->Encoding) {
2506 $result .= $this->headerLine('Content-Transfer-Encoding', static::ENCODING_8BIT);
2507 }
2508 // The only remaining alternatives are quoted-printable and base64, which are both 7bit compatible
2509 } else {
2510 $result .= $this->headerLine('Content-Transfer-Encoding', $this->Encoding);
2511 }
2512 }
2513
2514 if ('mail' !== $this->Mailer) {
2515// $result .= static::$LE;
2516 }
2517
2518 return $result;
2519 }
2520
2530 public function getSentMIMEMessage()
2531 {
2532 return static::stripTrailingWSP($this->MIMEHeader . $this->mailHeader) .
2533 static::$LE . static::$LE . $this->MIMEBody;
2534 }
2535
2541 protected function generateId()
2542 {
2543 $len = 32; //32 bytes = 256 bits
2544 $bytes = '';
2545 if (function_exists('random_bytes')) {
2546 try {
2547 $bytes = random_bytes($len);
2548 } catch (\Exception $e) {
2549 //Do nothing
2550 }
2551 } elseif (function_exists('openssl_random_pseudo_bytes')) {
2553 $bytes = openssl_random_pseudo_bytes($len);
2554 }
2555 if ($bytes === '') {
2556 //We failed to produce a proper random string, so make do.
2557 //Use a hash to force the length to the same as the other methods
2558 $bytes = hash('sha256', uniqid((string) mt_rand(), true), true);
2559 }
2560
2561 //We don't care about messing up base64 format here, just want a random string
2562 return str_replace(['=', '+', '/'], '', base64_encode(hash('sha256', $bytes, true)));
2563 }
2564
2573 public function createBody()
2574 {
2575 $body = '';
2576 //Create unique IDs and preset boundaries
2577 $this->uniqueid = $this->generateId();
2578 $this->boundary[1] = 'b1_' . $this->uniqueid;
2579 $this->boundary[2] = 'b2_' . $this->uniqueid;
2580 $this->boundary[3] = 'b3_' . $this->uniqueid;
2581
2582 if ($this->sign_key_file) {
2583 $body .= $this->getMailMIME() . static::$LE;
2584 }
2585
2586 $this->setWordWrap();
2587
2588 $bodyEncoding = $this->Encoding;
2589 $bodyCharSet = $this->CharSet;
2590 //Can we do a 7-bit downgrade?
2591 if (static::ENCODING_8BIT === $bodyEncoding && !$this->has8bitChars($this->Body)) {
2592 $bodyEncoding = static::ENCODING_7BIT;
2593 //All ISO 8859, Windows codepage and UTF-8 charsets are ascii compatible up to 7-bit
2594 $bodyCharSet = static::CHARSET_ASCII;
2595 }
2596 //If lines are too long, and we're not already using an encoding that will shorten them,
2597 //change to quoted-printable transfer encoding for the body part only
2598 if (static::ENCODING_BASE64 !== $this->Encoding && static::hasLineLongerThanMax($this->Body)) {
2599 $bodyEncoding = static::ENCODING_QUOTED_PRINTABLE;
2600 }
2601
2602 $altBodyEncoding = $this->Encoding;
2603 $altBodyCharSet = $this->CharSet;
2604 //Can we do a 7-bit downgrade?
2605 if (static::ENCODING_8BIT === $altBodyEncoding && !$this->has8bitChars($this->AltBody)) {
2606 $altBodyEncoding = static::ENCODING_7BIT;
2607 //All ISO 8859, Windows codepage and UTF-8 charsets are ascii compatible up to 7-bit
2608 $altBodyCharSet = static::CHARSET_ASCII;
2609 }
2610 //If lines are too long, and we're not already using an encoding that will shorten them,
2611 //change to quoted-printable transfer encoding for the alt body part only
2612 if (static::ENCODING_BASE64 !== $altBodyEncoding && static::hasLineLongerThanMax($this->AltBody)) {
2613 $altBodyEncoding = static::ENCODING_QUOTED_PRINTABLE;
2614 }
2615 //Use this as a preamble in all multipart message types
2616 $mimepre = 'This is a multi-part message in MIME format.' . static::$LE . static::$LE;
2617 switch ($this->message_type) {
2618 case 'inline':
2619 $body .= $mimepre;
2620 $body .= $this->getBoundary($this->boundary[1], $bodyCharSet, '', $bodyEncoding);
2621 $body .= $this->encodeString($this->Body, $bodyEncoding);
2622 $body .= static::$LE;
2623 $body .= $this->attachAll('inline', $this->boundary[1]);
2624 break;
2625 case 'attach':
2626 $body .= $mimepre;
2627 $body .= $this->getBoundary($this->boundary[1], $bodyCharSet, '', $bodyEncoding);
2628 $body .= $this->encodeString($this->Body, $bodyEncoding);
2629 $body .= static::$LE;
2630 $body .= $this->attachAll('attachment', $this->boundary[1]);
2631 break;
2632 case 'inline_attach':
2633 $body .= $mimepre;
2634 $body .= $this->textLine('--' . $this->boundary[1]);
2635 $body .= $this->headerLine('Content-Type', static::CONTENT_TYPE_MULTIPART_RELATED . ';');
2636 $body .= $this->textLine(' boundary="' . $this->boundary[2] . '";');
2637 $body .= $this->textLine(' type="' . static::CONTENT_TYPE_TEXT_HTML . '"');
2638 $body .= static::$LE;
2639 $body .= $this->getBoundary($this->boundary[2], $bodyCharSet, '', $bodyEncoding);
2640 $body .= $this->encodeString($this->Body, $bodyEncoding);
2641 $body .= static::$LE;
2642 $body .= $this->attachAll('inline', $this->boundary[2]);
2643 $body .= static::$LE;
2644 $body .= $this->attachAll('attachment', $this->boundary[1]);
2645 break;
2646 case 'alt':
2647 $body .= $mimepre;
2648 $body .= $this->getBoundary(
2649 $this->boundary[1],
2650 $altBodyCharSet,
2651 static::CONTENT_TYPE_PLAINTEXT,
2652 $altBodyEncoding
2653 );
2654 $body .= $this->encodeString($this->AltBody, $altBodyEncoding);
2655 $body .= static::$LE;
2656 $body .= $this->getBoundary(
2657 $this->boundary[1],
2658 $bodyCharSet,
2659 static::CONTENT_TYPE_TEXT_HTML,
2660 $bodyEncoding
2661 );
2662 $body .= $this->encodeString($this->Body, $bodyEncoding);
2663 $body .= static::$LE;
2664 if (!empty($this->Ical)) {
2665 $method = static::ICAL_METHOD_REQUEST;
2666 foreach (static::$IcalMethods as $imethod) {
2667 if (stripos($this->Ical, 'METHOD:' . $imethod) !== false) {
2668 $method = $imethod;
2669 break;
2670 }
2671 }
2672 $body .= $this->getBoundary(
2673 $this->boundary[1],
2674 '',
2675 static::CONTENT_TYPE_TEXT_CALENDAR . '; method=' . $method,
2676 ''
2677 );
2678 $body .= $this->encodeString($this->Ical, $this->Encoding);
2679 $body .= static::$LE;
2680 }
2681 $body .= $this->endBoundary($this->boundary[1]);
2682 break;
2683 case 'alt_inline':
2684 $body .= $mimepre;
2685 $body .= $this->getBoundary(
2686 $this->boundary[1],
2687 $altBodyCharSet,
2688 static::CONTENT_TYPE_PLAINTEXT,
2689 $altBodyEncoding
2690 );
2691 $body .= $this->encodeString($this->AltBody, $altBodyEncoding);
2692 $body .= static::$LE;
2693 $body .= $this->textLine('--' . $this->boundary[1]);
2694 $body .= $this->headerLine('Content-Type', static::CONTENT_TYPE_MULTIPART_RELATED . ';');
2695 $body .= $this->textLine(' boundary="' . $this->boundary[2] . '";');
2696 $body .= $this->textLine(' type="' . static::CONTENT_TYPE_TEXT_HTML . '"');
2697 $body .= static::$LE;
2698 $body .= $this->getBoundary(
2699 $this->boundary[2],
2700 $bodyCharSet,
2701 static::CONTENT_TYPE_TEXT_HTML,
2702 $bodyEncoding
2703 );
2704 $body .= $this->encodeString($this->Body, $bodyEncoding);
2705 $body .= static::$LE;
2706 $body .= $this->attachAll('inline', $this->boundary[2]);
2707 $body .= static::$LE;
2708 $body .= $this->endBoundary($this->boundary[1]);
2709 break;
2710 case 'alt_attach':
2711 $body .= $mimepre;
2712 $body .= $this->textLine('--' . $this->boundary[1]);
2713 $body .= $this->headerLine('Content-Type', static::CONTENT_TYPE_MULTIPART_ALTERNATIVE . ';');
2714 $body .= $this->textLine(' boundary="' . $this->boundary[2] . '"');
2715 $body .= static::$LE;
2716 $body .= $this->getBoundary(
2717 $this->boundary[2],
2718 $altBodyCharSet,
2719 static::CONTENT_TYPE_PLAINTEXT,
2720 $altBodyEncoding
2721 );
2722 $body .= $this->encodeString($this->AltBody, $altBodyEncoding);
2723 $body .= static::$LE;
2724 $body .= $this->getBoundary(
2725 $this->boundary[2],
2726 $bodyCharSet,
2727 static::CONTENT_TYPE_TEXT_HTML,
2728 $bodyEncoding
2729 );
2730 $body .= $this->encodeString($this->Body, $bodyEncoding);
2731 $body .= static::$LE;
2732 if (!empty($this->Ical)) {
2733 $method = static::ICAL_METHOD_REQUEST;
2734 foreach (static::$IcalMethods as $imethod) {
2735 if (stripos($this->Ical, 'METHOD:' . $imethod) !== false) {
2736 $method = $imethod;
2737 break;
2738 }
2739 }
2740 $body .= $this->getBoundary(
2741 $this->boundary[2],
2742 '',
2743 static::CONTENT_TYPE_TEXT_CALENDAR . '; method=' . $method,
2744 ''
2745 );
2746 $body .= $this->encodeString($this->Ical, $this->Encoding);
2747 }
2748 $body .= $this->endBoundary($this->boundary[2]);
2749 $body .= static::$LE;
2750 $body .= $this->attachAll('attachment', $this->boundary[1]);
2751 break;
2752 case 'alt_inline_attach':
2753 $body .= $mimepre;
2754 $body .= $this->textLine('--' . $this->boundary[1]);
2755 $body .= $this->headerLine('Content-Type', static::CONTENT_TYPE_MULTIPART_ALTERNATIVE . ';');
2756 $body .= $this->textLine(' boundary="' . $this->boundary[2] . '"');
2757 $body .= static::$LE;
2758 $body .= $this->getBoundary(
2759 $this->boundary[2],
2760 $altBodyCharSet,
2761 static::CONTENT_TYPE_PLAINTEXT,
2762 $altBodyEncoding
2763 );
2764 $body .= $this->encodeString($this->AltBody, $altBodyEncoding);
2765 $body .= static::$LE;
2766 $body .= $this->textLine('--' . $this->boundary[2]);
2767 $body .= $this->headerLine('Content-Type', static::CONTENT_TYPE_MULTIPART_RELATED . ';');
2768 $body .= $this->textLine(' boundary="' . $this->boundary[3] . '";');
2769 $body .= $this->textLine(' type="' . static::CONTENT_TYPE_TEXT_HTML . '"');
2770 $body .= static::$LE;
2771 $body .= $this->getBoundary(
2772 $this->boundary[3],
2773 $bodyCharSet,
2774 static::CONTENT_TYPE_TEXT_HTML,
2775 $bodyEncoding
2776 );
2777 $body .= $this->encodeString($this->Body, $bodyEncoding);
2778 $body .= static::$LE;
2779 $body .= $this->attachAll('inline', $this->boundary[3]);
2780 $body .= static::$LE;
2781 $body .= $this->endBoundary($this->boundary[2]);
2782 $body .= static::$LE;
2783 $body .= $this->attachAll('attachment', $this->boundary[1]);
2784 break;
2785 default:
2786 // Catch case 'plain' and case '', applies to simple `text/plain` and `text/html` body content types
2787 //Reset the `Encoding` property in case we changed it for line length reasons
2788 $this->Encoding = $bodyEncoding;
2789 $body .= $this->encodeString($this->Body, $this->Encoding);
2790 break;
2791 }
2792
2793 if ($this->isError()) {
2794 $body = '';
2795 if ($this->exceptions) {
2796 throw new Exception($this->lang('empty_message'), self::STOP_CRITICAL);
2797 }
2798 } elseif ($this->sign_key_file) {
2799 try {
2800 if (!defined('PKCS7_TEXT')) {
2801 throw new Exception($this->lang('extension_missing') . 'openssl');
2802 }
2803
2804 $file = tempnam(sys_get_temp_dir(), 'srcsign');
2805 $signed = tempnam(sys_get_temp_dir(), 'mailsign');
2806 file_put_contents($file, $body);
2807
2808 //Workaround for PHP bug https://bugs.php.net/bug.php?id=69197
2809 if (empty($this->sign_extracerts_file)) {
2810 $sign = @openssl_pkcs7_sign(
2811 $file,
2812 $signed,
2813 'file://' . realpath($this->sign_cert_file),
2814 ['file://' . realpath($this->sign_key_file), $this->sign_key_pass],
2815 []
2816 );
2817 } else {
2818 $sign = @openssl_pkcs7_sign(
2819 $file,
2820 $signed,
2821 'file://' . realpath($this->sign_cert_file),
2822 ['file://' . realpath($this->sign_key_file), $this->sign_key_pass],
2823 [],
2824 PKCS7_DETACHED,
2825 $this->sign_extracerts_file
2826 );
2827 }
2828
2829 @unlink($file);
2830 if ($sign) {
2831 $body = file_get_contents($signed);
2832 @unlink($signed);
2833 //The message returned by openssl contains both headers and body, so need to split them up
2834 $parts = explode("\n\n", $body, 2);
2835 $this->MIMEHeader .= $parts[0] . static::$LE . static::$LE;
2836 $body = $parts[1];
2837 } else {
2838 @unlink($signed);
2839 throw new Exception($this->lang('signing') . openssl_error_string());
2840 }
2841 } catch (Exception $exc) {
2842 $body = '';
2843 if ($this->exceptions) {
2844 throw $exc;
2845 }
2846 }
2847 }
2848
2849 return $body;
2850 }
2851
2862 protected function getBoundary($boundary, $charSet, $contentType, $encoding)
2863 {
2864 $result = '';
2865 if ('' === $charSet) {
2866 $charSet = $this->CharSet;
2867 }
2868 if ('' === $contentType) {
2870 }
2871 if ('' === $encoding) {
2872 $encoding = $this->Encoding;
2873 }
2874 $result .= $this->textLine('--' . $boundary);
2875 $result .= sprintf('Content-Type: %s; charset=%s', $contentType, $charSet);
2876 $result .= static::$LE;
2877 // RFC1341 part 5 says 7bit is assumed if not specified
2878 if (static::ENCODING_7BIT !== $encoding) {
2879 $result .= $this->headerLine('Content-Transfer-Encoding', $encoding);
2880 }
2881 $result .= static::$LE;
2882
2883 return $result;
2884 }
2885
2893 protected function endBoundary($boundary)
2894 {
2895 return static::$LE . '--' . $boundary . '--' . static::$LE;
2896 }
2897
2902 protected function setMessageType()
2903 {
2904 $type = [];
2905 if ($this->alternativeExists()) {
2906 $type[] = 'alt';
2907 }
2908 if ($this->inlineImageExists()) {
2909 $type[] = 'inline';
2910 }
2911 if ($this->attachmentExists()) {
2912 $type[] = 'attach';
2913 }
2914 $this->message_type = implode('_', $type);
2915 if ('' === $this->message_type) {
2916 //The 'plain' message_type refers to the message having a single body element, not that it is plain-text
2917 $this->message_type = 'plain';
2918 }
2919 }
2920
2929 public function headerLine($name, $value)
2930 {
2931 return $name . ': ' . $value . static::$LE;
2932 }
2933
2941 public function textLine($value)
2942 {
2943 return $value . static::$LE;
2944 }
2945
2963 public function addAttachment(
2964 $path,
2965 $name = '',
2966 $encoding = self::ENCODING_BASE64,
2967 $type = '',
2968 $disposition = 'attachment'
2969 ) {
2970 try {
2971 if (!static::isPermittedPath($path) || !@is_file($path) || !is_readable($path)) {
2972 throw new Exception($this->lang('file_access') . $path, self::STOP_CONTINUE);
2973 }
2974
2975 // If a MIME type is not specified, try to work it out from the file name
2976 if ('' === $type) {
2977 $type = static::filenameToType($path);
2978 }
2979
2980 $filename = (string) static::mb_pathinfo($path, PATHINFO_BASENAME);
2981 if ('' === $name) {
2982 $name = $filename;
2983 }
2984
2985 if (!$this->validateEncoding($encoding)) {
2986 throw new Exception($this->lang('encoding') . $encoding);
2987 }
2988
2989 $this->attachment[] = [
2990 0 => $path,
2991 1 => $filename,
2992 2 => $name,
2993 3 => $encoding,
2994 4 => $type,
2995 5 => false, // isStringAttachment
2996 6 => $disposition,
2997 7 => $name,
2998 ];
2999 } catch (Exception $exc) {
3000 $this->setError($exc->getMessage());
3001 $this->edebug($exc->getMessage());
3002 if ($this->exceptions) {
3003 throw $exc;
3004 }
3005
3006 return false;
3007 }
3008
3009 return true;
3010 }
3011
3017 public function getAttachments()
3018 {
3019 return $this->attachment;
3020 }
3021
3033 protected function attachAll($disposition_type, $boundary)
3034 {
3035 // Return text of body
3036 $mime = [];
3037 $cidUniq = [];
3038 $incl = [];
3039
3040 // Add all attachments
3041 foreach ($this->attachment as $attachment) {
3042 // Check if it is a valid disposition_filter
3043 if ($attachment[6] === $disposition_type) {
3044 // Check for string attachment
3045 $string = '';
3046 $path = '';
3047 $bString = $attachment[5];
3048 if ($bString) {
3049 $string = $attachment[0];
3050 } else {
3051 $path = $attachment[0];
3052 }
3053
3054 $inclhash = hash('sha256', serialize($attachment));
3055 if (in_array($inclhash, $incl, true)) {
3056 continue;
3057 }
3058 $incl[] = $inclhash;
3059 $name = $attachment[2];
3060 $encoding = $attachment[3];
3061 $type = $attachment[4];
3062 $disposition = $attachment[6];
3063 $cid = $attachment[7];
3064 if ('inline' === $disposition && array_key_exists($cid, $cidUniq)) {
3065 continue;
3066 }
3067 $cidUniq[$cid] = true;
3068
3069 $mime[] = sprintf('--%s%s', $boundary, static::$LE);
3070 //Only include a filename property if we have one
3071 if (!empty($name)) {
3072 $mime[] = sprintf(
3073 'Content-Type: %s; name=%s%s',
3074 $type,
3075 static::quotedString($this->encodeHeader($this->secureHeader($name))),
3076 static::$LE
3077 );
3078 } else {
3079 $mime[] = sprintf(
3080 'Content-Type: %s%s',
3081 $type,
3082 static::$LE
3083 );
3084 }
3085 // RFC1341 part 5 says 7bit is assumed if not specified
3086 if (static::ENCODING_7BIT !== $encoding) {
3087 $mime[] = sprintf('Content-Transfer-Encoding: %s%s', $encoding, static::$LE);
3088 }
3089
3090 //Only set Content-IDs on inline attachments
3091 if ((string) $cid !== '' && $disposition === 'inline') {
3092 $mime[] = 'Content-ID: <' . $this->encodeHeader($this->secureHeader($cid)) . '>' . static::$LE;
3093 }
3094
3095 // Allow for bypassing the Content-Disposition header
3096 if (!empty($disposition)) {
3097 $encoded_name = $this->encodeHeader($this->secureHeader($name));
3098 if (!empty($encoded_name)) {
3099 $mime[] = sprintf(
3100 'Content-Disposition: %s; filename=%s%s',
3101 $disposition,
3102 static::quotedString($encoded_name),
3103 static::$LE . static::$LE
3104 );
3105 } else {
3106 $mime[] = sprintf(
3107 'Content-Disposition: %s%s',
3108 $disposition,
3109 static::$LE . static::$LE
3110 );
3111 }
3112 } else {
3113 $mime[] = static::$LE;
3114 }
3115
3116 // Encode as string attachment
3117 if ($bString) {
3118 $mime[] = $this->encodeString($string, $encoding);
3119 } else {
3120 $mime[] = $this->encodeFile($path, $encoding);
3121 }
3122 if ($this->isError()) {
3123 return '';
3124 }
3125 $mime[] = static::$LE;
3126 }
3127 }
3128
3129 $mime[] = sprintf('--%s--%s', $boundary, static::$LE);
3130
3131 return implode('', $mime);
3132 }
3133
3143 protected function encodeFile($path, $encoding = self::ENCODING_BASE64)
3144 {
3145 try {
3146 if (!static::isPermittedPath($path) || !file_exists($path) || !is_readable($path)) {
3147 throw new Exception($this->lang('file_open') . $path, self::STOP_CONTINUE);
3148 }
3149 $file_buffer = file_get_contents($path);
3150 if (false === $file_buffer) {
3151 throw new Exception($this->lang('file_open') . $path, self::STOP_CONTINUE);
3152 }
3153 $file_buffer = $this->encodeString($file_buffer, $encoding);
3154
3155 return $file_buffer;
3156 } catch (Exception $exc) {
3157 $this->setError($exc->getMessage());
3158 $this->edebug($exc->getMessage());
3159 if ($this->exceptions) {
3160 throw $exc;
3161 }
3162
3163 return '';
3164 }
3165 }
3166
3178 public function encodeString($str, $encoding = self::ENCODING_BASE64)
3179 {
3180 $encoded = '';
3181 switch (strtolower($encoding)) {
3182 case static::ENCODING_BASE64:
3183 $encoded = chunk_split(
3184 base64_encode($str),
3185 static::STD_LINE_LENGTH,
3186 static::$LE
3187 );
3188 break;
3189 case static::ENCODING_7BIT:
3190 case static::ENCODING_8BIT:
3191 $encoded = static::normalizeBreaks($str);
3192 // Make sure it ends with a line break
3193 if (substr($encoded, -(strlen(static::$LE))) !== static::$LE) {
3194 $encoded .= static::$LE;
3195 }
3196 break;
3197 case static::ENCODING_BINARY:
3198 $encoded = $str;
3199 break;
3200 case static::ENCODING_QUOTED_PRINTABLE:
3201 $encoded = $this->encodeQP($str);
3202 break;
3203 default:
3204 $this->setError($this->lang('encoding') . $encoding);
3205 if ($this->exceptions) {
3206 throw new Exception($this->lang('encoding') . $encoding);
3207 }
3208 break;
3209 }
3210
3211 return $encoded;
3212 }
3213
3224 public function encodeHeader($str, $position = 'text')
3225 {
3226 $matchcount = 0;
3227 switch (strtolower($position)) {
3228 case 'phrase':
3229 if (!preg_match('/[\200-\377]/', $str)) {
3230 // Can't use addslashes as we don't know the value of magic_quotes_sybase
3231 $encoded = addcslashes($str, "\0..\37\177\\\"");
3232 if (($str === $encoded) && !preg_match('/[^A-Za-z0-9!#$%&\'*+\/=?^_`{|}~ -]/', $str)) {
3233 return $encoded;
3234 }
3235
3236 return "\"$encoded\"";
3237 }
3238 $matchcount = preg_match_all('/[^\040\041\043-\133\135-\176]/', $str, $matches);
3239 break;
3240 /* @noinspection PhpMissingBreakStatementInspection */
3241 case 'comment':
3242 $matchcount = preg_match_all('/[()"]/', $str, $matches);
3243 //fallthrough
3244 case 'text':
3245 default:
3246 $matchcount += preg_match_all('/[\000-\010\013\014\016-\037\177-\377]/', $str, $matches);
3247 break;
3248 }
3249
3250 if ($this->has8bitChars($str)) {
3251 $charset = $this->CharSet;
3252 } else {
3253 $charset = static::CHARSET_ASCII;
3254 }
3255
3256 // Q/B encoding adds 8 chars and the charset ("` =?<charset>?[QB]?<content>?=`").
3257 $overhead = 8 + strlen($charset);
3258
3259 if ('mail' === $this->Mailer) {
3260 $maxlen = static::MAIL_MAX_LINE_LENGTH - $overhead;
3261 } else {
3262 $maxlen = static::MAX_LINE_LENGTH - $overhead;
3263 }
3264
3265 // Select the encoding that produces the shortest output and/or prevents corruption.
3266 if ($matchcount > strlen($str) / 3) {
3267 // More than 1/3 of the content needs encoding, use B-encode.
3268 $encoding = 'B';
3269 } elseif ($matchcount > 0) {
3270 // Less than 1/3 of the content needs encoding, use Q-encode.
3271 $encoding = 'Q';
3272 } elseif (strlen($str) > $maxlen) {
3273 // No encoding needed, but value exceeds max line length, use Q-encode to prevent corruption.
3274 $encoding = 'Q';
3275 } else {
3276 // No reformatting needed
3277 $encoding = false;
3278 }
3279
3280 switch ($encoding) {
3281 case 'B':
3282 if ($this->hasMultiBytes($str)) {
3283 // Use a custom function which correctly encodes and wraps long
3284 // multibyte strings without breaking lines within a character
3285 $encoded = $this->base64EncodeWrapMB($str, "\n");
3286 } else {
3287 $encoded = base64_encode($str);
3288 $maxlen -= $maxlen % 4;
3289 $encoded = trim(chunk_split($encoded, $maxlen, "\n"));
3290 }
3291 $encoded = preg_replace('/^(.*)$/m', ' =?' . $charset . "?$encoding?\\1?=", $encoded);
3292 break;
3293 case 'Q':
3294 $encoded = $this->encodeQ($str, $position);
3295 $encoded = $this->wrapText($encoded, $maxlen, true);
3296 $encoded = str_replace('=' . static::$LE, "\n", trim($encoded));
3297 $encoded = preg_replace('/^(.*)$/m', ' =?' . $charset . "?$encoding?\\1?=", $encoded);
3298 break;
3299 default:
3300 return $str;
3301 }
3302
3303 return trim(static::normalizeBreaks($encoded));
3304 }
3305
3313 public function hasMultiBytes($str)
3314 {
3315 if (function_exists('mb_strlen')) {
3316 return strlen($str) > mb_strlen($str, $this->CharSet);
3317 }
3318
3319 // Assume no multibytes (we can't handle without mbstring functions anyway)
3320 return false;
3321 }
3322
3330 public function has8bitChars($text)
3331 {
3332 return (bool) preg_match('/[\x80-\xFF]/', $text);
3333 }
3334
3347 public function base64EncodeWrapMB($str, $linebreak = null)
3348 {
3349 $start = '=?' . $this->CharSet . '?B?';
3350 $end = '?=';
3351 $encoded = '';
3352 if (null === $linebreak) {
3353 $linebreak = static::$LE;
3354 }
3355
3356 $mb_length = mb_strlen($str, $this->CharSet);
3357 // Each line must have length <= 75, including $start and $end
3358 $length = 75 - strlen($start) - strlen($end);
3359 // Average multi-byte ratio
3360 $ratio = $mb_length / strlen($str);
3361 // Base64 has a 4:3 ratio
3362 $avgLength = floor($length * $ratio * .75);
3363
3364 $offset = 0;
3365 for ($i = 0; $i < $mb_length; $i += $offset) {
3366 $lookBack = 0;
3367 do {
3368 $offset = $avgLength - $lookBack;
3369 $chunk = mb_substr($str, $i, $offset, $this->CharSet);
3370 $chunk = base64_encode($chunk);
3371 ++$lookBack;
3372 } while (strlen($chunk) > $length);
3373 $encoded .= $chunk . $linebreak;
3374 }
3375
3376 // Chomp the last linefeed
3377 return substr($encoded, 0, -strlen($linebreak));
3378 }
3379
3388 public function encodeQP($string)
3389 {
3390 return static::normalizeBreaks(quoted_printable_encode($string));
3391 }
3392
3403 public function encodeQ($str, $position = 'text')
3404 {
3405 // There should not be any EOL in the string
3406 $pattern = '';
3407 $encoded = str_replace(["\r", "\n"], '', $str);
3408 switch (strtolower($position)) {
3409 case 'phrase':
3410 // RFC 2047 section 5.3
3411 $pattern = '^A-Za-z0-9!*+\/ -';
3412 break;
3413 /*
3414 * RFC 2047 section 5.2.
3415 * Build $pattern without including delimiters and []
3416 */
3417 /* @noinspection PhpMissingBreakStatementInspection */
3418 case 'comment':
3419 $pattern = '\‍(\‍)"';
3420 /* Intentional fall through */
3421 case 'text':
3422 default:
3423 // RFC 2047 section 5.1
3424 // Replace every high ascii, control, =, ? and _ characters
3425 $pattern = '\000-\011\013\014\016-\037\075\077\137\177-\377' . $pattern;
3426 break;
3427 }
3428 $matches = [];
3429 if (preg_match_all("/[{$pattern}]/", $encoded, $matches)) {
3430 // If the string contains an '=', make sure it's the first thing we replace
3431 // so as to avoid double-encoding
3432 $eqkey = array_search('=', $matches[0], true);
3433 if (false !== $eqkey) {
3434 unset($matches[0][$eqkey]);
3435 array_unshift($matches[0], '=');
3436 }
3437 foreach (array_unique($matches[0]) as $char) {
3438 $encoded = str_replace($char, '=' . sprintf('%02X', ord($char)), $encoded);
3439 }
3440 }
3441 // Replace spaces with _ (more readable than =20)
3442 // RFC 2047 section 4.2(2)
3443 return str_replace(' ', '_', $encoded);
3444 }
3445
3461 public function addStringAttachment(
3462 $string,
3463 $filename,
3464 $encoding = self::ENCODING_BASE64,
3465 $type = '',
3466 $disposition = 'attachment'
3467 ) {
3468 try {
3469 // If a MIME type is not specified, try to work it out from the file name
3470 if ('' === $type) {
3471 $type = static::filenameToType($filename);
3472 }
3473
3474 if (!$this->validateEncoding($encoding)) {
3475 throw new Exception($this->lang('encoding') . $encoding);
3476 }
3477
3478 // Append to $attachment array
3479 $this->attachment[] = [
3480 0 => $string,
3481 1 => $filename,
3482 2 => static::mb_pathinfo($filename, PATHINFO_BASENAME),
3483 3 => $encoding,
3484 4 => $type,
3485 5 => true, // isStringAttachment
3486 6 => $disposition,
3487 7 => 0,
3488 ];
3489 } catch (Exception $exc) {
3490 $this->setError($exc->getMessage());
3491 $this->edebug($exc->getMessage());
3492 if ($this->exceptions) {
3493 throw $exc;
3494 }
3495
3496 return false;
3497 }
3498
3499 return true;
3500 }
3501
3523 public function addEmbeddedImage(
3524 $path,
3525 $cid,
3526 $name = '',
3527 $encoding = self::ENCODING_BASE64,
3528 $type = '',
3529 $disposition = 'inline'
3530 ) {
3531 try {
3532 if (!static::isPermittedPath($path) || !@is_file($path) || !is_readable($path)) {
3533 throw new Exception($this->lang('file_access') . $path, self::STOP_CONTINUE);
3534 }
3535
3536 // If a MIME type is not specified, try to work it out from the file name
3537 if ('' === $type) {
3538 $type = static::filenameToType($path);
3539 }
3540
3541 if (!$this->validateEncoding($encoding)) {
3542 throw new Exception($this->lang('encoding') . $encoding);
3543 }
3544
3545 $filename = (string) static::mb_pathinfo($path, PATHINFO_BASENAME);
3546 if ('' === $name) {
3547 $name = $filename;
3548 }
3549
3550 // Append to $attachment array
3551 $this->attachment[] = [
3552 0 => $path,
3553 1 => $filename,
3554 2 => $name,
3555 3 => $encoding,
3556 4 => $type,
3557 5 => false, // isStringAttachment
3558 6 => $disposition,
3559 7 => $cid,
3560 ];
3561 } catch (Exception $exc) {
3562 $this->setError($exc->getMessage());
3563 $this->edebug($exc->getMessage());
3564 if ($this->exceptions) {
3565 throw $exc;
3566 }
3567
3568 return false;
3569 }
3570
3571 return true;
3572 }
3573
3593 public function addStringEmbeddedImage(
3594 $string,
3595 $cid,
3596 $name = '',
3597 $encoding = self::ENCODING_BASE64,
3598 $type = '',
3599 $disposition = 'inline'
3600 ) {
3601 try {
3602 // If a MIME type is not specified, try to work it out from the name
3603 if ('' === $type && !empty($name)) {
3604 $type = static::filenameToType($name);
3605 }
3606
3607 if (!$this->validateEncoding($encoding)) {
3608 throw new Exception($this->lang('encoding') . $encoding);
3609 }
3610
3611 // Append to $attachment array
3612 $this->attachment[] = [
3613 0 => $string,
3614 1 => $name,
3615 2 => $name,
3616 3 => $encoding,
3617 4 => $type,
3618 5 => true, // isStringAttachment
3619 6 => $disposition,
3620 7 => $cid,
3621 ];
3622 } catch (Exception $exc) {
3623 $this->setError($exc->getMessage());
3624 $this->edebug($exc->getMessage());
3625 if ($this->exceptions) {
3626 throw $exc;
3627 }
3628
3629 return false;
3630 }
3631
3632 return true;
3633 }
3634
3642 protected function validateEncoding($encoding)
3643 {
3644 return in_array(
3645 $encoding,
3646 [
3647 self::ENCODING_7BIT,
3648 self::ENCODING_QUOTED_PRINTABLE,
3649 self::ENCODING_BASE64,
3650 self::ENCODING_8BIT,
3651 self::ENCODING_BINARY,
3652 ],
3653 true
3654 );
3655 }
3656
3664 protected function cidExists($cid)
3665 {
3666 foreach ($this->attachment as $attachment) {
3667 if ('inline' === $attachment[6] && $cid === $attachment[7]) {
3668 return true;
3669 }
3670 }
3671
3672 return false;
3673 }
3674
3680 public function inlineImageExists()
3681 {
3682 foreach ($this->attachment as $attachment) {
3683 if ('inline' === $attachment[6]) {
3684 return true;
3685 }
3686 }
3687
3688 return false;
3689 }
3690
3696 public function attachmentExists()
3697 {
3698 foreach ($this->attachment as $attachment) {
3699 if ('attachment' === $attachment[6]) {
3700 return true;
3701 }
3702 }
3703
3704 return false;
3705 }
3706
3712 public function alternativeExists()
3713 {
3714 return !empty($this->AltBody);
3715 }
3716
3722 public function clearQueuedAddresses($kind)
3723 {
3724 $this->RecipientsQueue = array_filter(
3725 $this->RecipientsQueue,
3726 static function ($params) use ($kind) {
3727 return $params[0] !== $kind;
3728 }
3729 );
3730 }
3731
3735 public function clearAddresses()
3736 {
3737 foreach ($this->to as $to) {
3738 unset($this->all_recipients[strtolower($to[0])]);
3739 }
3740 $this->to = [];
3741 $this->clearQueuedAddresses('to');
3742 }
3743
3747 public function clearCCs()
3748 {
3749 foreach ($this->cc as $cc) {
3750 unset($this->all_recipients[strtolower($cc[0])]);
3751 }
3752 $this->cc = [];
3753 $this->clearQueuedAddresses('cc');
3754 }
3755
3759 public function clearBCCs()
3760 {
3761 foreach ($this->bcc as $bcc) {
3762 unset($this->all_recipients[strtolower($bcc[0])]);
3763 }
3764 $this->bcc = [];
3765 $this->clearQueuedAddresses('bcc');
3766 }
3767
3771 public function clearReplyTos()
3772 {
3773 $this->ReplyTo = [];
3774 $this->ReplyToQueue = [];
3775 }
3776
3780 public function clearAllRecipients()
3781 {
3782 $this->to = [];
3783 $this->cc = [];
3784 $this->bcc = [];
3785 $this->all_recipients = [];
3786 $this->RecipientsQueue = [];
3787 }
3788
3792 public function clearAttachments()
3793 {
3794 $this->attachment = [];
3795 }
3796
3800 public function clearCustomHeaders()
3801 {
3802 $this->CustomHeader = [];
3803 }
3804
3810 protected function setError($msg)
3811 {
3813 if ('smtp' === $this->Mailer && null !== $this->smtp) {
3814 $lasterror = $this->smtp->getError();
3815 if (!empty($lasterror['error'])) {
3816 $msg .= $this->lang('smtp_error') . $lasterror['error'];
3817 if (!empty($lasterror['detail'])) {
3818 $msg .= ' Detail: ' . $lasterror['detail'];
3819 }
3820 if (!empty($lasterror['smtp_code'])) {
3821 $msg .= ' SMTP code: ' . $lasterror['smtp_code'];
3822 }
3823 if (!empty($lasterror['smtp_code_ex'])) {
3824 $msg .= ' Additional SMTP info: ' . $lasterror['smtp_code_ex'];
3825 }
3826 }
3827 }
3828 $this->ErrorInfo = $msg;
3829 }
3830
3836 public static function rfcDate()
3837 {
3838 // Set the time zone to whatever the default is to avoid 500 errors
3839 // Will default to UTC if it's not set properly in php.ini
3840 date_default_timezone_set(@date_default_timezone_get());
3841
3842 return date('D, j M Y H:i:s O');
3843 }
3844
3851 protected function serverHostname()
3852 {
3853 $result = '';
3854 if (!empty($this->Hostname)) {
3856 } elseif (isset($_SERVER) && array_key_exists('SERVER_NAME', $_SERVER)) {
3857 $result = $_SERVER['SERVER_NAME'];
3858 } elseif (function_exists('gethostname') && gethostname() !== false) {
3859 $result = gethostname();
3860 } elseif (php_uname('n') !== false) {
3861 $result = php_uname('n');
3862 }
3863 if (!static::isValidHost($result)) {
3864 return 'localhost.localdomain';
3865 }
3866
3867 return $result;
3868 }
3869
3878 public static function isValidHost($host)
3879 {
3880 //Simple syntax limits
3881 if (empty($host)
3882 || !is_string($host)
3883 || strlen($host) > 256
3884 || !preg_match('/^([a-zA-Z\d.-]*|\[[a-fA-F\d:]+])$/', $host)
3885 ) {
3886 return false;
3887 }
3888 //Looks like a bracketed IPv6 address
3889 if (strlen($host) > 2 && substr($host, 0, 1) === '[' && substr($host, -1, 1) === ']') {
3890 return filter_var(substr($host, 1, -1), FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) !== false;
3891 }
3892 //If removing all the dots results in a numeric string, it must be an IPv4 address.
3893 //Need to check this first because otherwise things like `999.0.0.0` are considered valid host names
3894 if (is_numeric(str_replace('.', '', $host))) {
3895 //Is it a valid IPv4 address?
3896 return filter_var($host, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) !== false;
3897 }
3898 if (filter_var('http://' . $host, FILTER_VALIDATE_URL) !== false) {
3899 //Is it a syntactically valid hostname?
3900 return true;
3901 }
3902
3903 return false;
3904 }
3905
3913 protected function lang($key)
3914 {
3915 if (count($this->language) < 1) {
3916 $this->setLanguage(); // set the default language
3917 }
3918
3919 if (array_key_exists($key, $this->language)) {
3920 if ('smtp_connect_failed' === $key) {
3921 //Include a link to troubleshooting docs on SMTP connection failure
3922 //this is by far the biggest cause of support questions
3923 //but it's usually not PHPMailer's fault.
3924 return $this->language[$key] . ' https://github.com/PHPMailer/PHPMailer/wiki/Troubleshooting';
3925 }
3926
3927 return $this->language[$key];
3928 }
3929
3930 //Return the key as a fallback
3931 return $key;
3932 }
3933
3939 public function isError()
3940 {
3941 return $this->error_count > 0;
3942 }
3943
3954 public function addCustomHeader($name, $value = null)
3955 {
3956 if (null === $value && strpos($name, ':') !== false) {
3957 // Value passed in as name:value
3958 list($name, $value) = explode(':', $name, 2);
3959 }
3960 $name = trim($name);
3961 $value = trim($value);
3962 //Ensure name is not empty, and that neither name nor value contain line breaks
3963 if (empty($name) || strpbrk($name . $value, "\r\n") !== false) {
3964 if ($this->exceptions) {
3965 throw new Exception('Invalid header name or value');
3966 }
3967
3968 return false;
3969 }
3970 $this->CustomHeader[] = [$name, $value];
3971
3972 return true;
3973 }
3974
3980 public function getCustomHeaders()
3981 {
3982 return $this->CustomHeader;
3983 }
3984
4005 public function msgHTML($message, $basedir = '', $advanced = false)
4006 {
4007 preg_match_all('/(?<!-)(src|background)=["\'](.*)["\']/Ui', $message, $images);
4008 if (array_key_exists(2, $images)) {
4009 if (strlen($basedir) > 1 && '/' !== substr($basedir, -1)) {
4010 // Ensure $basedir has a trailing /
4011 $basedir .= '/';
4012 }
4013 foreach ($images[2] as $imgindex => $url) {
4014 // Convert data URIs into embedded images
4015 //e.g. "data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="
4016 $match = [];
4017 if (preg_match('#^data:(image/(?:jpe?g|gif|png));?(base64)?,(.+)#', $url, $match)) {
4018 if (count($match) === 4 && static::ENCODING_BASE64 === $match[2]) {
4019 $data = base64_decode($match[3]);
4020 } elseif ('' === $match[2]) {
4021 $data = rawurldecode($match[3]);
4022 } else {
4023 //Not recognised so leave it alone
4024 continue;
4025 }
4026 //Hash the decoded data, not the URL, so that the same data-URI image used in multiple places
4027 //will only be embedded once, even if it used a different encoding
4028 $cid = substr(hash('sha256', $data), 0, 32) . '@phpmailer.0'; // RFC2392 S 2
4029
4030 if (!$this->cidExists($cid)) {
4032 $data,
4033 $cid,
4034 'embed' . $imgindex,
4035 static::ENCODING_BASE64,
4036 $match[1]
4037 );
4038 }
4039 $message = str_replace(
4040 $images[0][$imgindex],
4041 $images[1][$imgindex] . '="cid:' . $cid . '"',
4042 $message
4043 );
4044 continue;
4045 }
4046 if (// Only process relative URLs if a basedir is provided (i.e. no absolute local paths)
4047 !empty($basedir)
4048 // Ignore URLs containing parent dir traversal (..)
4049 && (strpos($url, '..') === false)
4050 // Do not change urls that are already inline images
4051 && 0 !== strpos($url, 'cid:')
4052 // Do not change absolute URLs, including anonymous protocol
4053 && !preg_match('#^[a-z][a-z0-9+.-]*:?//#i', $url)
4054 ) {
4055 $filename = static::mb_pathinfo($url, PATHINFO_BASENAME);
4056 $directory = dirname($url);
4057 if ('.' === $directory) {
4058 $directory = '';
4059 }
4060 // RFC2392 S 2
4061 $cid = substr(hash('sha256', $url), 0, 32) . '@phpmailer.0';
4062 if (strlen($basedir) > 1 && '/' !== substr($basedir, -1)) {
4063 $basedir .= '/';
4064 }
4065 if (strlen($directory) > 1 && '/' !== substr($directory, -1)) {
4066 $directory .= '/';
4067 }
4068 if ($this->addEmbeddedImage(
4069 $basedir . $directory . $filename,
4070 $cid,
4071 $filename,
4072 static::ENCODING_BASE64,
4073 static::_mime_types((string) static::mb_pathinfo($filename, PATHINFO_EXTENSION))
4074 )
4075 ) {
4076 $message = preg_replace(
4077 '/' . $images[1][$imgindex] . '=["\']' . preg_quote($url, '/') . '["\']/Ui',
4078 $images[1][$imgindex] . '="cid:' . $cid . '"',
4079 $message
4080 );
4081 }
4082 }
4083 }
4084 }
4085 $this->isHTML();
4086 // Convert all message body line breaks to LE, makes quoted-printable encoding work much better
4087 $this->Body = static::normalizeBreaks($message);
4088 $this->AltBody = static::normalizeBreaks($this->html2text($message, $advanced));
4089 if (!$this->alternativeExists()) {
4090 $this->AltBody = 'This is an HTML-only message. To view it, activate HTML in your email application.'
4091 . static::$LE;
4092 }
4093
4094 return $this->Body;
4095 }
4096
4120 public function html2text($html, $advanced = false)
4121 {
4122 if (is_callable($advanced)) {
4123 return $advanced($html);
4124 }
4125
4126 return html_entity_decode(
4127 trim(strip_tags(preg_replace('/<(head|title|style|script)[^>]*>.*?<\/\\1>/si', '', $html))),
4128 ENT_QUOTES,
4129 $this->CharSet
4130 );
4131 }
4132
4140 public static function _mime_types($ext = '')
4141 {
4142 $mimes = [
4143 'xl' => 'application/excel',
4144 'js' => 'application/javascript',
4145 'hqx' => 'application/mac-binhex40',
4146 'cpt' => 'application/mac-compactpro',
4147 'bin' => 'application/macbinary',
4148 'doc' => 'application/msword',
4149 'word' => 'application/msword',
4150 'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
4151 'xltx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.template',
4152 'potx' => 'application/vnd.openxmlformats-officedocument.presentationml.template',
4153 'ppsx' => 'application/vnd.openxmlformats-officedocument.presentationml.slideshow',
4154 'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
4155 'sldx' => 'application/vnd.openxmlformats-officedocument.presentationml.slide',
4156 'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
4157 'dotx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.template',
4158 'xlam' => 'application/vnd.ms-excel.addin.macroEnabled.12',
4159 'xlsb' => 'application/vnd.ms-excel.sheet.binary.macroEnabled.12',
4160 'class' => 'application/octet-stream',
4161 'dll' => 'application/octet-stream',
4162 'dms' => 'application/octet-stream',
4163 'exe' => 'application/octet-stream',
4164 'lha' => 'application/octet-stream',
4165 'lzh' => 'application/octet-stream',
4166 'psd' => 'application/octet-stream',
4167 'sea' => 'application/octet-stream',
4168 'so' => 'application/octet-stream',
4169 'oda' => 'application/oda',
4170 'pdf' => 'application/pdf',
4171 'ai' => 'application/postscript',
4172 'eps' => 'application/postscript',
4173 'ps' => 'application/postscript',
4174 'smi' => 'application/smil',
4175 'smil' => 'application/smil',
4176 'mif' => 'application/vnd.mif',
4177 'xls' => 'application/vnd.ms-excel',
4178 'ppt' => 'application/vnd.ms-powerpoint',
4179 'wbxml' => 'application/vnd.wap.wbxml',
4180 'wmlc' => 'application/vnd.wap.wmlc',
4181 'dcr' => 'application/x-director',
4182 'dir' => 'application/x-director',
4183 'dxr' => 'application/x-director',
4184 'dvi' => 'application/x-dvi',
4185 'gtar' => 'application/x-gtar',
4186 'php3' => 'application/x-httpd-php',
4187 'php4' => 'application/x-httpd-php',
4188 'php' => 'application/x-httpd-php',
4189 'phtml' => 'application/x-httpd-php',
4190 'phps' => 'application/x-httpd-php-source',
4191 'swf' => 'application/x-shockwave-flash',
4192 'sit' => 'application/x-stuffit',
4193 'tar' => 'application/x-tar',
4194 'tgz' => 'application/x-tar',
4195 'xht' => 'application/xhtml+xml',
4196 'xhtml' => 'application/xhtml+xml',
4197 'zip' => 'application/zip',
4198 'mid' => 'audio/midi',
4199 'midi' => 'audio/midi',
4200 'mp2' => 'audio/mpeg',
4201 'mp3' => 'audio/mpeg',
4202 'm4a' => 'audio/mp4',
4203 'mpga' => 'audio/mpeg',
4204 'aif' => 'audio/x-aiff',
4205 'aifc' => 'audio/x-aiff',
4206 'aiff' => 'audio/x-aiff',
4207 'ram' => 'audio/x-pn-realaudio',
4208 'rm' => 'audio/x-pn-realaudio',
4209 'rpm' => 'audio/x-pn-realaudio-plugin',
4210 'ra' => 'audio/x-realaudio',
4211 'wav' => 'audio/x-wav',
4212 'mka' => 'audio/x-matroska',
4213 'bmp' => 'image/bmp',
4214 'gif' => 'image/gif',
4215 'jpeg' => 'image/jpeg',
4216 'jpe' => 'image/jpeg',
4217 'jpg' => 'image/jpeg',
4218 'png' => 'image/png',
4219 'tiff' => 'image/tiff',
4220 'tif' => 'image/tiff',
4221 'webp' => 'image/webp',
4222 'heif' => 'image/heif',
4223 'heifs' => 'image/heif-sequence',
4224 'heic' => 'image/heic',
4225 'heics' => 'image/heic-sequence',
4226 'eml' => 'message/rfc822',
4227 'css' => 'text/css',
4228 'html' => 'text/html',
4229 'htm' => 'text/html',
4230 'shtml' => 'text/html',
4231 'log' => 'text/plain',
4232 'text' => 'text/plain',
4233 'txt' => 'text/plain',
4234 'rtx' => 'text/richtext',
4235 'rtf' => 'text/rtf',
4236 'vcf' => 'text/vcard',
4237 'vcard' => 'text/vcard',
4238 'ics' => 'text/calendar',
4239 'xml' => 'text/xml',
4240 'xsl' => 'text/xml',
4241 'wmv' => 'video/x-ms-wmv',
4242 'mpeg' => 'video/mpeg',
4243 'mpe' => 'video/mpeg',
4244 'mpg' => 'video/mpeg',
4245 'mp4' => 'video/mp4',
4246 'm4v' => 'video/mp4',
4247 'mov' => 'video/quicktime',
4248 'qt' => 'video/quicktime',
4249 'rv' => 'video/vnd.rn-realvideo',
4250 'avi' => 'video/x-msvideo',
4251 'movie' => 'video/x-sgi-movie',
4252 'webm' => 'video/webm',
4253 'mkv' => 'video/x-matroska',
4254 ];
4255 $ext = strtolower($ext);
4256 if (array_key_exists($ext, $mimes)) {
4257 return $mimes[$ext];
4258 }
4259
4260 return 'application/octet-stream';
4261 }
4262
4271 public static function filenameToType($filename)
4272 {
4273 // In case the path is a URL, strip any query string before getting extension
4274 $qpos = strpos($filename, '?');
4275 if (false !== $qpos) {
4276 $filename = substr($filename, 0, $qpos);
4277 }
4278 $ext = static::mb_pathinfo($filename, PATHINFO_EXTENSION);
4279
4280 return static::_mime_types($ext);
4281 }
4282
4295 public static function mb_pathinfo($path, $options = null)
4296 {
4297 $ret = ['dirname' => '', 'basename' => '', 'extension' => '', 'filename' => ''];
4298 $pathinfo = [];
4299 if (preg_match('#^(.*?)[\\\\/]*(([^/\\\\]*?)(\.([^.\\\\/]+?)|))[\\\\/.]*$#m', $path, $pathinfo)) {
4300 if (array_key_exists(1, $pathinfo)) {
4301 $ret['dirname'] = $pathinfo[1];
4302 }
4303 if (array_key_exists(2, $pathinfo)) {
4304 $ret['basename'] = $pathinfo[2];
4305 }
4306 if (array_key_exists(5, $pathinfo)) {
4307 $ret['extension'] = $pathinfo[5];
4308 }
4309 if (array_key_exists(3, $pathinfo)) {
4310 $ret['filename'] = $pathinfo[3];
4311 }
4312 }
4313 switch ($options) {
4314 case PATHINFO_DIRNAME:
4315 case 'dirname':
4316 return $ret['dirname'];
4317 case PATHINFO_BASENAME:
4318 case 'basename':
4319 return $ret['basename'];
4320 case PATHINFO_EXTENSION:
4321 case 'extension':
4322 return $ret['extension'];
4323 case PATHINFO_FILENAME:
4324 case 'filename':
4325 return $ret['filename'];
4326 default:
4327 return $ret;
4328 }
4329 }
4330
4345 public function set($name, $value = '')
4346 {
4347 if (property_exists($this, $name)) {
4348 $this->$name = $value;
4349
4350 return true;
4351 }
4352 $this->setError($this->lang('variable_set') . $name);
4353
4354 return false;
4355 }
4356
4364 public function secureHeader($str)
4365 {
4366 return trim(str_replace(["\r", "\n"], '', $str));
4367 }
4368
4379 public static function normalizeBreaks($text, $breaktype = null)
4380 {
4381 if (null === $breaktype) {
4382 $breaktype = static::$LE;
4383 }
4384 // Normalise to \n
4385 $text = str_replace([self::CRLF, "\r"], "\n", $text);
4386 // Now convert LE as needed
4387 if ("\n" !== $breaktype) {
4388 $text = str_replace("\n", $breaktype, $text);
4389 }
4390
4391 return $text;
4392 }
4393
4401 public static function stripTrailingWSP($text)
4402 {
4403 return rtrim($text, " \r\n\t");
4404 }
4405
4411 public static function getLE()
4412 {
4413 return static::$LE;
4414 }
4415
4421 protected static function setLE($le)
4422 {
4423 static::$LE = $le;
4424 }
4425
4434 public function sign($cert_filename, $key_filename, $key_pass, $extracerts_filename = '')
4435 {
4436 $this->sign_cert_file = $cert_filename;
4437 $this->sign_key_file = $key_filename;
4438 $this->sign_key_pass = $key_pass;
4439 $this->sign_extracerts_file = $extracerts_filename;
4440 }
4441
4449 public function DKIM_QP($txt)
4450 {
4451 $line = '';
4452 $len = strlen($txt);
4453 for ($i = 0; $i < $len; ++$i) {
4454 $ord = ord($txt[$i]);
4455 if (((0x21 <= $ord) && ($ord <= 0x3A)) || $ord === 0x3C || ((0x3E <= $ord) && ($ord <= 0x7E))) {
4456 $line .= $txt[$i];
4457 } else {
4458 $line .= '=' . sprintf('%02X', $ord);
4459 }
4460 }
4461
4462 return $line;
4463 }
4464
4474 public function DKIM_Sign($signHeader)
4475 {
4476 if (!defined('PKCS7_TEXT')) {
4477 if ($this->exceptions) {
4478 throw new Exception($this->lang('extension_missing') . 'openssl');
4479 }
4480
4481 return '';
4482 }
4483 $privKeyStr = !empty($this->DKIM_private_string) ?
4484 $this->DKIM_private_string :
4485 file_get_contents($this->DKIM_private);
4486 if ('' !== $this->DKIM_passphrase) {
4487 $privKey = openssl_pkey_get_private($privKeyStr, $this->DKIM_passphrase);
4488 } else {
4489 $privKey = openssl_pkey_get_private($privKeyStr);
4490 }
4491 if (openssl_sign($signHeader, $signature, $privKey, 'sha256WithRSAEncryption')) {
4492 openssl_pkey_free($privKey);
4493
4494 return base64_encode($signature);
4495 }
4496 openssl_pkey_free($privKey);
4497
4498 return '';
4499 }
4500
4512 public function DKIM_HeaderC($signHeader)
4513 {
4514 //Normalize breaks to CRLF (regardless of the mailer)
4515 $signHeader = static::normalizeBreaks($signHeader, self::CRLF);
4516 //Unfold header lines
4517 //Note PCRE \s is too broad a definition of whitespace; RFC5322 defines it as `[ \t]`
4518 //@see https://tools.ietf.org/html/rfc5322#section-2.2
4519 //That means this may break if you do something daft like put vertical tabs in your headers.
4520 $signHeader = preg_replace('/\r\n[ \t]+/', ' ', $signHeader);
4521 //Break headers out into an array
4522 $lines = explode(self::CRLF, $signHeader);
4523 foreach ($lines as $key => $line) {
4524 //If the header is missing a :, skip it as it's invalid
4525 //This is likely to happen because the explode() above will also split
4526 //on the trailing LE, leaving an empty line
4527 if (strpos($line, ':') === false) {
4528 continue;
4529 }
4530 list($heading, $value) = explode(':', $line, 2);
4531 //Lower-case header name
4532 $heading = strtolower($heading);
4533 //Collapse white space within the value, also convert WSP to space
4534 $value = preg_replace('/[ \t]+/', ' ', $value);
4535 //RFC6376 is slightly unclear here - it says to delete space at the *end* of each value
4536 //But then says to delete space before and after the colon.
4537 //Net result is the same as trimming both ends of the value.
4538 //By elimination, the same applies to the field name
4539 $lines[$key] = trim($heading, " \t") . ':' . trim($value, " \t");
4540 }
4541
4542 return implode(self::CRLF, $lines);
4543 }
4544
4556 public function DKIM_BodyC($body)
4557 {
4558 if (empty($body)) {
4559 return self::CRLF;
4560 }
4561 // Normalize line endings to CRLF
4562 $body = static::normalizeBreaks($body, self::CRLF);
4563
4564 //Reduce multiple trailing line breaks to a single one
4565 return static::stripTrailingWSP($body) . self::CRLF;
4566 }
4567
4579 public function DKIM_Add($headers_line, $subject, $body)
4580 {
4581 $DKIMsignatureType = 'rsa-sha256'; // Signature & hash algorithms
4582 $DKIMcanonicalization = 'relaxed/simple'; // Canonicalization methods of header & body
4583 $DKIMquery = 'dns/txt'; // Query method
4584 $DKIMtime = time();
4585 //Always sign these headers without being asked
4586 //Recommended list from https://tools.ietf.org/html/rfc6376#section-5.4.1
4587 $autoSignHeaders = [
4588 'from',
4589 'to',
4590 'cc',
4591 'date',
4592 'subject',
4593 'reply-to',
4594 'message-id',
4595 'content-type',
4596 'mime-version',
4597 'x-mailer',
4598 ];
4599 if (stripos($headers_line, 'Subject') === false) {
4600 $headers_line .= 'Subject: ' . $subject . static::$LE;
4601 }
4602 $headerLines = explode(static::$LE, $headers_line);
4603 $currentHeaderLabel = '';
4604 $currentHeaderValue = '';
4605 $parsedHeaders = [];
4606 $headerLineIndex = 0;
4607 $headerLineCount = count($headerLines);
4608 foreach ($headerLines as $headerLine) {
4609 $matches = [];
4610 if (preg_match('/^([^ \t]*?)(?::[ \t]*)(.*)$/', $headerLine, $matches)) {
4611 if ($currentHeaderLabel !== '') {
4612 //We were previously in another header; This is the start of a new header, so save the previous one
4613 $parsedHeaders[] = ['label' => $currentHeaderLabel, 'value' => $currentHeaderValue];
4614 }
4615 $currentHeaderLabel = $matches[1];
4616 $currentHeaderValue = $matches[2];
4617 } elseif (preg_match('/^[ \t]+(.*)$/', $headerLine, $matches)) {
4618 //This is a folded continuation of the current header, so unfold it
4619 $currentHeaderValue .= ' ' . $matches[1];
4620 }
4621 ++$headerLineIndex;
4622 if ($headerLineIndex >= $headerLineCount) {
4623 //This was the last line, so finish off this header
4624 $parsedHeaders[] = ['label' => $currentHeaderLabel, 'value' => $currentHeaderValue];
4625 }
4626 }
4627 $copiedHeaders = [];
4628 $headersToSignKeys = [];
4629 $headersToSign = [];
4630 foreach ($parsedHeaders as $header) {
4631 //Is this header one that must be included in the DKIM signature?
4632 if (in_array(strtolower($header['label']), $autoSignHeaders, true)) {
4633 $headersToSignKeys[] = $header['label'];
4634 $headersToSign[] = $header['label'] . ': ' . $header['value'];
4635 if ($this->DKIM_copyHeaderFields) {
4636 $copiedHeaders[] = $header['label'] . ':' . //Note no space after this, as per RFC
4637 str_replace('|', '=7C', $this->DKIM_QP($header['value']));
4638 }
4639 continue;
4640 }
4641 //Is this an extra custom header we've been asked to sign?
4642 if (in_array($header['label'], $this->DKIM_extraHeaders, true)) {
4643 //Find its value in custom headers
4644 foreach ($this->CustomHeader as $customHeader) {
4645 if ($customHeader[0] === $header['label']) {
4646 $headersToSignKeys[] = $header['label'];
4647 $headersToSign[] = $header['label'] . ': ' . $header['value'];
4648 if ($this->DKIM_copyHeaderFields) {
4649 $copiedHeaders[] = $header['label'] . ':' . //Note no space after this, as per RFC
4650 str_replace('|', '=7C', $this->DKIM_QP($header['value']));
4651 }
4652 //Skip straight to the next header
4653 continue 2;
4654 }
4655 }
4656 }
4657 }
4658 $copiedHeaderFields = '';
4659 if ($this->DKIM_copyHeaderFields && count($copiedHeaders) > 0) {
4660 //Assemble a DKIM 'z' tag
4661 $copiedHeaderFields = ' z=';
4662 $first = true;
4663 foreach ($copiedHeaders as $copiedHeader) {
4664 if (!$first) {
4665 $copiedHeaderFields .= static::$LE . ' |';
4666 }
4667 //Fold long values
4668 if (strlen($copiedHeader) > self::STD_LINE_LENGTH - 3) {
4669 $copiedHeaderFields .= substr(
4670 chunk_split($copiedHeader, self::STD_LINE_LENGTH - 3, static::$LE . self::FWS),
4671 0,
4672 -strlen(static::$LE . self::FWS)
4673 );
4674 } else {
4675 $copiedHeaderFields .= $copiedHeader;
4676 }
4677 $first = false;
4678 }
4679 $copiedHeaderFields .= ';' . static::$LE;
4680 }
4681 $headerKeys = ' h=' . implode(':', $headersToSignKeys) . ';' . static::$LE;
4682 $headerValues = implode(static::$LE, $headersToSign);
4683 $body = $this->DKIM_BodyC($body);
4684 $DKIMb64 = base64_encode(pack('H*', hash('sha256', $body))); // Base64 of packed binary SHA-256 hash of body
4685 $ident = '';
4686 if ('' !== $this->DKIM_identity) {
4687 $ident = ' i=' . $this->DKIM_identity . ';' . static::$LE;
4688 }
4689 //The DKIM-Signature header is included in the signature *except for* the value of the `b` tag
4690 //which is appended after calculating the signature
4691 //https://tools.ietf.org/html/rfc6376#section-3.5
4692 $dkimSignatureHeader = 'DKIM-Signature: v=1;' .
4693 ' d=' . $this->DKIM_domain . ';' .
4694 ' s=' . $this->DKIM_selector . ';' . static::$LE .
4695 ' a=' . $DKIMsignatureType . ';' .
4696 ' q=' . $DKIMquery . ';' .
4697 ' t=' . $DKIMtime . ';' .
4698 ' c=' . $DKIMcanonicalization . ';' . static::$LE .
4699 $headerKeys .
4700 $ident .
4701 $copiedHeaderFields .
4702 ' bh=' . $DKIMb64 . ';' . static::$LE .
4703 ' b=';
4704 //Canonicalize the set of headers
4705 $canonicalizedHeaders = $this->DKIM_HeaderC(
4706 $headerValues . static::$LE . $dkimSignatureHeader
4707 );
4708 $signature = $this->DKIM_Sign($canonicalizedHeaders);
4709 $signature = trim(chunk_split($signature, self::STD_LINE_LENGTH - 3, static::$LE . self::FWS));
4710
4711 return static::normalizeBreaks($dkimSignatureHeader . $signature);
4712 }
4713
4722 public static function hasLineLongerThanMax($str)
4723 {
4724 return (bool) preg_match('/^(.{' . (self::MAX_LINE_LENGTH + strlen(static::$LE)) . ',})/m', $str);
4725 }
4726
4737 public static function quotedString($str)
4738 {
4739 if (preg_match('/[ ()<>@,;:"\/\[\]?=]/', $str)) {
4740 //If the string contains any of these chars, it must be double-quoted
4741 //and any double quotes must be escaped with a backslash
4742 return '"' . str_replace('"', '\\"', $str) . '"';
4743 }
4744
4745 //Return the string untouched, it doesn't need quoting
4746 return $str;
4747 }
4748
4755 public function getToAddresses()
4756 {
4757 return $this->to;
4758 }
4759
4766 public function getCcAddresses()
4767 {
4768 return $this->cc;
4769 }
4770
4777 public function getBccAddresses()
4778 {
4779 return $this->bcc;
4780 }
4781
4788 public function getReplyToAddresses()
4789 {
4790 return $this->ReplyTo;
4791 }
4792
4800 {
4801 return $this->all_recipients;
4802 }
4803
4816 protected function doCallback($isSent, $to, $cc, $bcc, $subject, $body, $from, $extra)
4817 {
4818 if (!empty($this->action_function) && is_callable($this->action_function)) {
4819 call_user_func($this->action_function, $isSent, $to, $cc, $bcc, $subject, $body, $from, $extra);
4820 }
4821 }
4822
4828 public function getOAuth()
4829 {
4830 return $this->oauth;
4831 }
4832
4836 public function setOAuth(OAuth $oauth)
4837 {
4838 $this->oauth = $oauth;
4839 }
4840}
$result
$path
Definition: aliased.php:25
attachment()
Definition: attachment.php:2
$filename
Definition: buildRTE.php:89
if(!array_key_exists('domain', $_REQUEST)) $domain
Definition: resume.php:8
An exception for terminatinating execution or to throw for unit testing.
PHPMailer exception handler.
Definition: Exception.php:29
OAuth - OAuth2 authentication wrapper class.
Definition: OAuth.php:36
$dsn
Comma separated list of DSN notifications 'NEVER' under no circumstances a DSN must be returned to th...
Definition: PHPMailer.php:385
addReplyTo($address, $name='')
Add a "Reply-To" address.
Definition: PHPMailer.php:1050
getCcAddresses()
Allows for public read access to 'cc' property.
Definition: PHPMailer.php:4766
static mb_pathinfo($path, $options=null)
Multi-byte-safe pathinfo replacement.
Definition: PHPMailer.php:4295
attachAll($disposition_type, $boundary)
Attach all file, string, and binary attachments to the message.
Definition: PHPMailer.php:3033
encodeHeader($str, $position='text')
Encode a header value (not including its label) optimally.
Definition: PHPMailer.php:3224
doCallback($isSent, $to, $cc, $bcc, $subject, $body, $from, $extra)
Perform a callback.
Definition: PHPMailer.php:4816
static getLE()
Return the current line break format string.
Definition: PHPMailer.php:4411
setSMTPInstance(SMTP $smtp)
Provide an instance to use for SMTP operations.
Definition: PHPMailer.php:1835
smtpSend($header, $body)
Send mail via SMTP.
Definition: PHPMailer.php:1857
setWordWrap()
Apply word wrapping to the message body.
Definition: PHPMailer.php:2357
DKIM_QP($txt)
Quoted-Printable-encode a DKIM header.
Definition: PHPMailer.php:4449
alternativeExists()
Check if this message has an alternative body set.
Definition: PHPMailer.php:3712
inlineImageExists()
Check if an inline attachment is present.
Definition: PHPMailer.php:3680
static rfcDate()
Return an RFC 822 formatted date.
Definition: PHPMailer.php:3836
addAttachment( $path, $name='', $encoding=self::ENCODING_BASE64, $type='', $disposition='attachment')
Add an attachment from a path on the filesystem.
Definition: PHPMailer.php:2963
serverHostname()
Get the server hostname.
Definition: PHPMailer.php:3851
isSendmail()
Send messages using $Sendmail.
Definition: PHPMailer.php:968
addCC($address, $name='')
Add a "CC" address.
Definition: PHPMailer.php:1020
smtpConnect($options=null)
Initiate a connection to an SMTP server.
Definition: PHPMailer.php:1942
__construct($exceptions=null)
Constructor.
Definition: PHPMailer.php:821
DKIM_Sign($signHeader)
Generate a DKIM signature.
Definition: PHPMailer.php:4474
hasMultiBytes($str)
Check if a string contains multi-byte characters.
Definition: PHPMailer.php:3313
static isShellSafe($string)
Fix CVE-2016-10033 and CVE-2016-10045 by disallowing potentially unsafe shell characters.
Definition: PHPMailer.php:1717
const CRLF
The SMTP standard CRLF line break.
Definition: PHPMailer.php:775
static parseAddresses($addrstr, $useimap=true)
Parse and validate a string containing one or more RFC822-style comma-separated email addresses of th...
Definition: PHPMailer.php:1184
edebug($str)
Output debugging info via user-defined method.
Definition: PHPMailer.php:886
lang($key)
Get an error message in the current language.
Definition: PHPMailer.php:3913
setFrom($address, $name='', $auto=true)
Set the From and FromName properties.
Definition: PHPMailer.php:1241
clearBCCs()
Clear all BCC recipients.
Definition: PHPMailer.php:3759
punyencodeAddress($address)
Converts IDN in given email address to its ASCII form, also known as punycode, if possible.
Definition: PHPMailer.php:1395
textLine($value)
Return a formatted mail line.
Definition: PHPMailer.php:2941
getTranslations()
Get the array of strings for the current language.
Definition: PHPMailer.php:2157
base64EncodeWrapMB($str, $linebreak=null)
Encode and wrap long multibyte strings for mail headers without breaking lines within a character.
Definition: PHPMailer.php:3347
getOAuth()
Get the OAuth instance.
Definition: PHPMailer.php:4828
isError()
Check if an error occurred.
Definition: PHPMailer.php:3939
addAddress($address, $name='')
Add a "To" address.
Definition: PHPMailer.php:1005
mailSend($header, $body)
Send mail using the PHP mail() function.
Definition: PHPMailer.php:1768
static _mime_types($ext='')
Get the MIME type for a file extension.
Definition: PHPMailer.php:4140
getAllRecipientAddresses()
Allows for public read access to 'all_recipients' property.
Definition: PHPMailer.php:4799
clearReplyTos()
Clear all ReplyTo recipients.
Definition: PHPMailer.php:3771
msgHTML($message, $basedir='', $advanced=false)
Create a message body from an HTML string.
Definition: PHPMailer.php:4005
sendmailSend($header, $body)
Send mail using the $Sendmail program.
Definition: PHPMailer.php:1638
addEmbeddedImage( $path, $cid, $name='', $encoding=self::ENCODING_BASE64, $type='', $disposition='inline')
Add an embedded (inline) attachment from a file.
Definition: PHPMailer.php:3523
getCustomHeaders()
Returns all custom headers.
Definition: PHPMailer.php:3980
generateId()
Create a unique ID to use for boundaries.
Definition: PHPMailer.php:2541
mailPassthru($to, $subject, $body, $header, $params)
Call mail() in a safe_mode-aware fashion.
Definition: PHPMailer.php:853
addBCC($address, $name='')
Add a "BCC" address.
Definition: PHPMailer.php:1035
static idnSupported()
Tells whether IDNs (Internationalized Domain Names) are supported or not.
Definition: PHPMailer.php:1376
static isPermittedPath($path)
Check whether a file path is of a permitted type.
Definition: PHPMailer.php:1751
encodeQ($str, $position='text')
Encode a string using Q encoding.
Definition: PHPMailer.php:3403
static setLE($le)
Set the line break format string, e.g.
Definition: PHPMailer.php:4421
setLanguage($langcode='en', $lang_path='')
Set the language for error messages.
Definition: PHPMailer.php:2084
createBody()
Assemble the message body.
Definition: PHPMailer.php:2573
preSend()
Prepare a message for sending.
Definition: PHPMailer.php:1459
send()
Create a message and send it.
Definition: PHPMailer.php:1433
addCustomHeader($name, $value=null)
Add a custom header.
Definition: PHPMailer.php:3954
getBoundary($boundary, $charSet, $contentType, $encoding)
Return the start of a message boundary.
Definition: PHPMailer.php:2862
static stripTrailingWSP($text)
Remove trailing breaks from a string.
Definition: PHPMailer.php:4401
clearCustomHeaders()
Clear all custom headers.
Definition: PHPMailer.php:3800
sign($cert_filename, $key_filename, $key_pass, $extracerts_filename='')
Set the public and private key files and password for S/MIME signing.
Definition: PHPMailer.php:4434
smtpClose()
Close the active SMTP session if one exists.
Definition: PHPMailer.php:2066
getSentMIMEMessage()
Returns the whole MIME message.
Definition: PHPMailer.php:2530
static normalizeBreaks($text, $breaktype=null)
Normalize line breaks in a string.
Definition: PHPMailer.php:4379
setOAuth(OAuth $oauth)
Set an OAuth instance.
Definition: PHPMailer.php:4836
setMessageType()
Set the message type.
Definition: PHPMailer.php:2902
static quotedString($str)
If a string contains any "special" characters, double-quote the name, and escape any double quotes wi...
Definition: PHPMailer.php:4737
clearQueuedAddresses($kind)
Clear queued addresses of given kind.
Definition: PHPMailer.php:3722
addAnAddress($kind, $address, $name='')
Add an address to one of the recipient arrays or to the ReplyTo array.
Definition: PHPMailer.php:1124
static isValidHost($host)
Validate whether a string contains a valid value to use as a hostname or IP address.
Definition: PHPMailer.php:3878
wrapText($message, $length, $qp_mode=false)
Word-wrap message.
Definition: PHPMailer.php:2213
isQmail()
Send messages using qmail.
Definition: PHPMailer.php:983
secureHeader($str)
Strip newlines to prevent header injection.
Definition: PHPMailer.php:4364
clearCCs()
Clear all CC recipients.
Definition: PHPMailer.php:3747
clearAllRecipients()
Clear all recipient types.
Definition: PHPMailer.php:3780
getMailMIME()
Get the message MIME type headers.
Definition: PHPMailer.php:2474
const FWS
"Folding White Space" a white space string used for line folding.
Definition: PHPMailer.php:780
addStringEmbeddedImage( $string, $cid, $name='', $encoding=self::ENCODING_BASE64, $type='', $disposition='inline')
Add an embedded stringified attachment.
Definition: PHPMailer.php:3593
addOrEnqueueAnAddress($kind, $address, $name)
Add an address to one of the recipient arrays or to the ReplyTo array.
Definition: PHPMailer.php:1069
createHeader()
Assemble message headers.
Definition: PHPMailer.php:2381
setError($msg)
Add an error message to the error container.
Definition: PHPMailer.php:3810
static validateAddress($address, $patternselect=null)
Check that a string looks like an email address.
Definition: PHPMailer.php:1310
utf8CharBoundary($encodedText, $maxLength)
Find the last character boundary prior to $maxLength in a utf-8 quoted-printable encoded string.
Definition: PHPMailer.php:2313
isMail()
Send messages using PHP's mail() function.
Definition: PHPMailer.php:960
DKIM_Add($headers_line, $subject, $body)
Create the DKIM header and body in a new message header.
Definition: PHPMailer.php:4579
encodeQP($string)
Encode a string in quoted-printable format.
Definition: PHPMailer.php:3388
getToAddresses()
Allows for public read access to 'to' property.
Definition: PHPMailer.php:4755
clearAddresses()
Clear all To recipients.
Definition: PHPMailer.php:3735
DKIM_BodyC($body)
Generate a DKIM canonicalization body.
Definition: PHPMailer.php:4556
attachmentExists()
Check if an attachment (non-inline) is present.
Definition: PHPMailer.php:3696
validateEncoding($encoding)
Validate encodings.
Definition: PHPMailer.php:3642
getSMTPInstance()
Get an instance to use for SMTP operations.
Definition: PHPMailer.php:1821
encodeFile($path, $encoding=self::ENCODING_BASE64)
Encode a file attachment in requested format.
Definition: PHPMailer.php:3143
getLastMessageID()
Return the Message-ID header of the last email.
Definition: PHPMailer.php:1281
DKIM_HeaderC($signHeader)
Generate a DKIM canonicalization header.
Definition: PHPMailer.php:4512
static filenameToType($filename)
Map a file name to a MIME type.
Definition: PHPMailer.php:4271
html2text($html, $advanced=false)
Convert an HTML string into plain text.
Definition: PHPMailer.php:4120
endBoundary($boundary)
Return the end of a message boundary.
Definition: PHPMailer.php:2893
getAttachments()
Return the array of attachments.
Definition: PHPMailer.php:3017
isSMTP()
Send messages using SMTP.
Definition: PHPMailer.php:952
encodeString($str, $encoding=self::ENCODING_BASE64)
Encode a string in requested format.
Definition: PHPMailer.php:3178
postSend()
Actually send a message via the selected mechanism.
Definition: PHPMailer.php:1595
addrFormat($addr)
Format an address for use in a message header.
Definition: PHPMailer.php:2191
isHTML($isHtml=true)
Sets message type to HTML or plain.
Definition: PHPMailer.php:940
headerLine($name, $value)
Format a header line.
Definition: PHPMailer.php:2929
addStringAttachment( $string, $filename, $encoding=self::ENCODING_BASE64, $type='', $disposition='attachment')
Add a string or binary attachment (non-filesystem).
Definition: PHPMailer.php:3461
clearAttachments()
Clear all filesystem, string, and binary attachments.
Definition: PHPMailer.php:3792
cidExists($cid)
Check if an embedded attachment is present with this cid.
Definition: PHPMailer.php:3664
getBccAddresses()
Allows for public read access to 'bcc' property.
Definition: PHPMailer.php:4777
static hasLineLongerThanMax($str)
Detect if a string contains a line longer than the maximum line length allowed by RFC 2822 section 2....
Definition: PHPMailer.php:4722
addrAppend($type, $addr)
Create recipient headers.
Definition: PHPMailer.php:2173
has8bitChars($text)
Does a string contain any 8-bit chars (in any charset)?
Definition: PHPMailer.php:3330
getReplyToAddresses()
Allows for public read access to 'ReplyTo' property.
Definition: PHPMailer.php:4788
PHPMailer RFC821 SMTP email transport class.
Definition: SMTP.php:31
PHP_EOL
Definition: complexTest.php:7
$key
Definition: croninfo.php:18
$i
Definition: disco.tpl.php:19
$txt
Definition: error.php:11
$html
Definition: example_001.php:87
if( $orgName !==null) if($spconfig->hasValue('contacts')) $email
Definition: metadata.php:201
catch(Exception $e) $message
if( $path[strlen( $path) - 1]==='/') if(is_dir($path)) if(!file_exists( $path)) if(preg_match('#\.php$#D', mb_strtolower($path, 'UTF-8'))) $contentType
Definition: module.php:144
hash(StreamInterface $stream, $algo, $rawOutput=false)
Calculate a hash of a Stream.
Definition: functions.php:406
mail($to, $subject, $message, $additional_headers=null, $additional_parameters=null)
Get an OAuth2 token from an OAuth2 provider.
PHPMailer - PHP email creation and transport class.
$ret
Definition: parser.php:6
$PHPMAILER_LANG['authenticate']
$type
$url
if(isset($_REQUEST['delete'])) $list
Definition: registry.php:41
if((!isset($_SERVER['DOCUMENT_ROOT'])) OR(empty($_SERVER['DOCUMENT_ROOT']))) $_SERVER['DOCUMENT_ROOT']
$from
$start
Definition: bench.php:8
$data
Definition: bench.php:6
$text
Definition: errorreport.php:18