ILIAS  release_5-4 Revision v5.4.26-12-gabc799a52e6
PHPMailer.php
Go to the documentation of this file.
1 <?php
21 namespace PHPMailer\PHPMailer;
22 
31 class PHPMailer
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 
76  public $CharSet = self::CHARSET_ISO88591;
77 
83  public $ContentType = self::CONTENT_TYPE_PLAINTEXT;
84 
91  public $Encoding = self::ENCODING_8BIT;
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 = [
167  self::ICAL_METHOD_REQUEST,
168  self::ICAL_METHOD_PUBLISH,
169  self::ICAL_METHOD_REPLY,
170  self::ICAL_METHOD_ADD,
171  self::ICAL_METHOD_CANCEL,
172  self::ICAL_METHOD_REFRESH,
173  self::ICAL_METHOD_COUNTER,
174  self::ICAL_METHOD_DECLINECOUNTER,
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 
509  public $DKIM_copyHeaderFields = true;
510 
518  public $DKIM_extraHeaders = [];
519 
525  public $DKIM_private = '';
526 
534  public $DKIM_private_string = '';
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)) {
4031  $this->addStringEmbeddedImage(
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 
4799  public function getAllRecipientAddresses()
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 }
addAttachment( $path, $name='', $encoding=self::ENCODING_BASE64, $type='', $disposition='attachment')
Add an attachment from a path on the filesystem.
Definition: PHPMailer.php:2963
setMessageType()
Set the message type.
Definition: PHPMailer.php:2902
if($orgName !==null) if($spconfig->hasValue('contacts')) $email
Definition: metadata.php:201
addBCC($address, $name='')
Add a "BCC" address.
Definition: PHPMailer.php:1035
const FWS
"Folding White Space" a white space string used for line folding.
Definition: PHPMailer.php:780
$path
Definition: aliased.php:25
attachmentExists()
Check if an attachment (non-inline) is present.
Definition: PHPMailer.php:3696
generateId()
Create a unique ID to use for boundaries.
Definition: PHPMailer.php:2541
if(isset($_REQUEST['delete'])) $list
Definition: registry.php:41
getAttachments()
Return the array of attachments.
Definition: PHPMailer.php:3017
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
static isValidHost($host)
Validate whether a string contains a valid value to use as a hostname or IP address.
Definition: PHPMailer.php:3878
isQmail()
Send messages using qmail.
Definition: PHPMailer.php:983
getTranslations()
Get the array of strings for the current language.
Definition: PHPMailer.php:2157
smtpSend($header, $body)
Send mail via SMTP.
Definition: PHPMailer.php:1857
if((!isset($_SERVER['DOCUMENT_ROOT'])) OR(empty($_SERVER['DOCUMENT_ROOT']))) $_SERVER['DOCUMENT_ROOT']
encodeFile($path, $encoding=self::ENCODING_BASE64)
Encode a file attachment in requested format.
Definition: PHPMailer.php:3143
encodeString($str, $encoding=self::ENCODING_BASE64)
Encode a string in requested format.
Definition: PHPMailer.php:3178
base64EncodeWrapMB($str, $linebreak=null)
Encode and wrap long multibyte strings for mail headers without breaking lines within a character...
Definition: PHPMailer.php:3347
clearAllRecipients()
Clear all recipient types.
Definition: PHPMailer.php:3780
clearCCs()
Clear all CC recipients.
Definition: PHPMailer.php:3747
$result
$type
endBoundary($boundary)
Return the end of a message boundary.
Definition: PHPMailer.php:2893
sendmailSend($header, $body)
Send mail using the $Sendmail program.
Definition: PHPMailer.php:1638
alternativeExists()
Check if this message has an alternative body set.
Definition: PHPMailer.php:3712
getLastMessageID()
Return the Message-ID header of the last email.
Definition: PHPMailer.php:1281
createBody()
Assemble the message body.
Definition: PHPMailer.php:2573
secureHeader($str)
Strip newlines to prevent header injection.
Definition: PHPMailer.php:4364
postSend()
Actually send a message via the selected mechanism.
Definition: PHPMailer.php:1595
getOAuth()
Get the OAuth instance.
Definition: PHPMailer.php:4828
isSMTP()
Send messages using SMTP.
Definition: PHPMailer.php:952
serverHostname()
Get the server hostname.
Definition: PHPMailer.php:3851
isError()
Check if an error occurred.
Definition: PHPMailer.php:3939
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
getCustomHeaders()
Returns all custom headers.
Definition: PHPMailer.php:3980
getBoundary($boundary, $charSet, $contentType, $encoding)
Return the start of a message boundary.
Definition: PHPMailer.php:2862
smtpClose()
Close the active SMTP session if one exists.
Definition: PHPMailer.php:2066
static getLE()
Return the current line break format string.
Definition: PHPMailer.php:4411
clearAttachments()
Clear all filesystem, string, and binary attachments.
Definition: PHPMailer.php:3792
encodeHeader($str, $position='text')
Encode a header value (not including its label) optimally.
Definition: PHPMailer.php:3224
$from
validateEncoding($encoding)
Validate encodings.
Definition: PHPMailer.php:3642
PHP_EOL
Definition: complexTest.php:5
encodeQP($string)
Encode a string in quoted-printable format.
Definition: PHPMailer.php:3388
static isPermittedPath($path)
Check whether a file path is of a permitted type.
Definition: PHPMailer.php:1751
edebug($str)
Output debugging info via user-defined method.
Definition: PHPMailer.php:886
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
addAnAddress($kind, $address, $name='')
Add an address to one of the recipient arrays or to the ReplyTo array.
Definition: PHPMailer.php:1124
cidExists($cid)
Check if an embedded attachment is present with this cid.
Definition: PHPMailer.php:3664
DKIM_Add($headers_line, $subject, $body)
Create the DKIM header and body in a new message header.
Definition: PHPMailer.php:4579
mail($to, $subject, $message, $additional_headers=null, $additional_parameters=null)
isHTML($isHtml=true)
Sets message type to HTML or plain.
Definition: PHPMailer.php:940
setLanguage($langcode='en', $lang_path='')
Set the language for error messages.
Definition: PHPMailer.php:2084
static filenameToType($filename)
Map a file name to a MIME type.
Definition: PHPMailer.php:4271
utf8CharBoundary($encodedText, $maxLength)
Find the last character boundary prior to $maxLength in a utf-8 quoted-printable encoded string...
Definition: PHPMailer.php:2313
getToAddresses()
Allows for public read access to &#39;to&#39; property.
Definition: PHPMailer.php:4755
getAllRecipientAddresses()
Allows for public read access to &#39;all_recipients&#39; property.
Definition: PHPMailer.php:4799
DKIM_BodyC($body)
Generate a DKIM canonicalization body.
Definition: PHPMailer.php:4556
smtpConnect($options=null)
Initiate a connection to an SMTP server.
Definition: PHPMailer.php:1942
setWordWrap()
Apply word wrapping to the message body.
Definition: PHPMailer.php:2357
PHPMailer - PHP email creation and transport class.
$start
Definition: bench.php:8
setError($msg)
Add an error message to the error container.
Definition: PHPMailer.php:3810
html2text($html, $advanced=false)
Convert an HTML string into plain text.
Definition: PHPMailer.php:4120
getBccAddresses()
Allows for public read access to &#39;bcc&#39; property.
Definition: PHPMailer.php:4777
textLine($value)
Return a formatted mail line.
Definition: PHPMailer.php:2941
getSentMIMEMessage()
Returns the whole MIME message.
Definition: PHPMailer.php:2530
$PHPMAILER_LANG['authenticate']
getReplyToAddresses()
Allows for public read access to &#39;ReplyTo&#39; property.
Definition: PHPMailer.php:4788
preSend()
Prepare a message for sending.
Definition: PHPMailer.php:1459
addStringEmbeddedImage( $string, $cid, $name='', $encoding=self::ENCODING_BASE64, $type='', $disposition='inline')
Add an embedded stringified attachment.
Definition: PHPMailer.php:3593
setOAuth(OAuth $oauth)
Set an OAuth instance.
Definition: PHPMailer.php:4836
DKIM_QP($txt)
Quoted-Printable-encode a DKIM header.
Definition: PHPMailer.php:4449
inlineImageExists()
Check if an inline attachment is present.
Definition: PHPMailer.php:3680
getMailMIME()
Get the message MIME type headers.
Definition: PHPMailer.php:2474
punyencodeAddress($address)
Converts IDN in given email address to its ASCII form, also known as punycode, if possible...
Definition: PHPMailer.php:1395
clearQueuedAddresses($kind)
Clear queued addresses of given kind.
Definition: PHPMailer.php:3722
catch(Exception $e) $message
static mb_pathinfo($path, $options=null)
Multi-byte-safe pathinfo replacement.
Definition: PHPMailer.php:4295
addEmbeddedImage( $path, $cid, $name='', $encoding=self::ENCODING_BASE64, $type='', $disposition='inline')
Add an embedded (inline) attachment from a file.
Definition: PHPMailer.php:3523
DKIM_HeaderC($signHeader)
Generate a DKIM canonicalization header.
Definition: PHPMailer.php:4512
isMail()
Send messages using PHP&#39;s mail() function.
Definition: PHPMailer.php:960
clearCustomHeaders()
Clear all custom headers.
Definition: PHPMailer.php:3800
addAddress($address, $name='')
Add a "To" address.
Definition: PHPMailer.php:1005
clearAddresses()
Clear all To recipients.
Definition: PHPMailer.php:3735
getCcAddresses()
Allows for public read access to &#39;cc&#39; property.
Definition: PHPMailer.php:4766
doCallback($isSent, $to, $cc, $bcc, $subject, $body, $from, $extra)
Perform a callback.
Definition: PHPMailer.php:4816
$dsn
Comma separated list of DSN notifications &#39;NEVER&#39; under no circumstances a DSN must be returned to th...
Definition: PHPMailer.php:385
getSMTPInstance()
Get an instance to use for SMTP operations.
Definition: PHPMailer.php:1821
$text
Definition: errorreport.php:18
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
headerLine($name, $value)
Format a header line.
Definition: PHPMailer.php:2929
setFrom($address, $name='', $auto=true)
Set the From and FromName properties.
Definition: PHPMailer.php:1241
static validateAddress($address, $patternselect=null)
Check that a string looks like an email address.
Definition: PHPMailer.php:1310
static _mime_types($ext='')
Get the MIME type for a file extension.
Definition: PHPMailer.php:4140
msgHTML($message, $basedir='', $advanced=false)
Create a message body from an HTML string.
Definition: PHPMailer.php:4005
static rfcDate()
Return an RFC 822 formatted date.
Definition: PHPMailer.php:3836
addReplyTo($address, $name='')
Add a "Reply-To" address.
Definition: PHPMailer.php:1050
isSendmail()
Send messages using $Sendmail.
Definition: PHPMailer.php:968
has8bitChars($text)
Does a string contain any 8-bit chars (in any charset)?
Definition: PHPMailer.php:3330
$txt
Definition: error.php:11
addCustomHeader($name, $value=null)
Add a custom header.
Definition: PHPMailer.php:3954
$filename
Definition: buildRTE.php:89
static idnSupported()
Tells whether IDNs (Internationalized Domain Names) are supported or not.
Definition: PHPMailer.php:1376
DKIM_Sign($signHeader)
Generate a DKIM signature.
Definition: PHPMailer.php:4474
send()
Create a message and send it.
Definition: PHPMailer.php:1433
OAuth - OAuth2 authentication wrapper class.
Definition: OAuth.php:35
mailPassthru($to, $subject, $body, $header, $params)
Call mail() in a safe_mode-aware fashion.
Definition: PHPMailer.php:853
static normalizeBreaks($text, $breaktype=null)
Normalize line breaks in a string.
Definition: PHPMailer.php:4379
addStringAttachment( $string, $filename, $encoding=self::ENCODING_BASE64, $type='', $disposition='attachment')
Add a string or binary attachment (non-filesystem).
Definition: PHPMailer.php:3461
setSMTPInstance(SMTP $smtp)
Provide an instance to use for SMTP operations.
Definition: PHPMailer.php:1835
clearBCCs()
Clear all BCC recipients.
Definition: PHPMailer.php:3759
$ret
Definition: parser.php:6
attachment()
Definition: attachment.php:2
$i
Definition: disco.tpl.php:19
clearReplyTos()
Clear all ReplyTo recipients.
Definition: PHPMailer.php:3771
$url
lang($key)
Get an error message in the current language.
Definition: PHPMailer.php:3913
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
hasMultiBytes($str)
Check if a string contains multi-byte characters.
Definition: PHPMailer.php:3313
static setLE($le)
Set the line break format string, e.g.
Definition: PHPMailer.php:4421
__construct($exceptions=null)
Constructor.
Definition: PHPMailer.php:821
addCC($address, $name='')
Add a "CC" address.
Definition: PHPMailer.php:1020
static quotedString($str)
If a string contains any "special" characters, double-quote the name, and escape any double quotes wi...
Definition: PHPMailer.php:4737
if(!array_key_exists('domain', $_REQUEST)) $domain
Definition: resume.php:8
static stripTrailingWSP($text)
Remove trailing breaks from a string.
Definition: PHPMailer.php:4401
PHPMailer RFC821 SMTP email transport class.
Definition: SMTP.php:30
hash(StreamInterface $stream, $algo, $rawOutput=false)
Calculate a hash of a Stream.
Definition: functions.php:406
attachAll($disposition_type, $boundary)
Attach all file, string, and binary attachments to the message.
Definition: PHPMailer.php:3033
$key
Definition: croninfo.php:18
wrapText($message, $length, $qp_mode=false)
Word-wrap message.
Definition: PHPMailer.php:2213
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
mailSend($header, $body)
Send mail using the PHP mail() function.
Definition: PHPMailer.php:1768
$html
Definition: example_001.php:87
encodeQ($str, $position='text')
Encode a string using Q encoding.
Definition: PHPMailer.php:3403
$data
Definition: bench.php:6
addrFormat($addr)
Format an address for use in a message header.
Definition: PHPMailer.php:2191
Get an OAuth2 token from an OAuth2 provider.