ILIAS  release_5-3 Revision v5.3.23-19-g915713cf615
class.phpmailer.php
Go to the documentation of this file.
1 <?php
28 class PHPMailer
29 {
34  public $Version = '5.2.24';
35 
42  public $Priority = null;
43 
48  public $CharSet = 'iso-8859-1';
49 
54  public $ContentType = 'text/plain';
55 
61  public $Encoding = '8bit';
62 
67  public $ErrorInfo = '';
68 
73  public $From = 'root@localhost';
74 
79  public $FromName = 'Root User';
80 
86  public $Sender = '';
87 
96  public $ReturnPath = '';
97 
102  public $Subject = '';
103 
109  public $Body = '';
110 
118  public $AltBody = '';
119 
128  public $Ical = '';
129 
135  protected $MIMEBody = '';
136 
142  protected $MIMEHeader = '';
143 
149  protected $mailHeader = '';
150 
156  public $WordWrap = 0;
157 
163  public $Mailer = 'mail';
164 
169  public $Sendmail = '/usr/sbin/sendmail';
170 
176  public $UseSendmailOptions = true;
177 
184  public $PluginDir = '';
185 
190  public $ConfirmReadingTo = '';
191 
199  public $Hostname = '';
200 
209  public $MessageID = '';
210 
216  public $MessageDate = '';
217 
229  public $Host = 'localhost';
230 
236  public $Port = 25;
237 
245  public $Helo = '';
246 
252  public $SMTPSecure = '';
253 
260  public $SMTPAutoTLS = true;
261 
269  public $SMTPAuth = false;
270 
275  public $SMTPOptions = array();
276 
281  public $Username = '';
282 
287  public $Password = '';
288 
294  public $AuthType = '';
295 
301  public $Realm = '';
302 
308  public $Workstation = '';
309 
315  public $Timeout = 300;
316 
329  public $SMTPDebug = 0;
330 
345  public $Debugoutput = 'echo';
346 
353  public $SMTPKeepAlive = false;
354 
361  public $SingleTo = false;
362 
368  public $SingleToArray = array();
369 
377  public $do_verp = false;
378 
383  public $AllowEmpty = false;
384 
391  public $LE = "\n";
392 
397  public $DKIM_selector = '';
398 
404  public $DKIM_identity = '';
405 
411  public $DKIM_passphrase = '';
412 
418  public $DKIM_domain = '';
419 
424  public $DKIM_private = '';
425 
431  public $DKIM_private_string = '';
432 
451  public $action_function = '';
452 
458  public $XMailer = '';
459 
467  public static $validator = 'auto';
468 
474  protected $smtp = null;
475 
481  protected $to = array();
482 
488  protected $cc = array();
489 
495  protected $bcc = array();
496 
502  protected $ReplyTo = array();
503 
511  protected $all_recipients = array();
512 
523  protected $RecipientsQueue = array();
524 
533  protected $ReplyToQueue = array();
534 
540  protected $attachment = array();
541 
547  protected $CustomHeader = array();
548 
554  protected $lastMessageID = '';
555 
561  protected $message_type = '';
562 
568  protected $boundary = array();
569 
575  protected $language = array();
576 
582  protected $error_count = 0;
583 
589  protected $sign_cert_file = '';
590 
596  protected $sign_key_file = '';
597 
603  protected $sign_extracerts_file = '';
604 
611  protected $sign_key_pass = '';
612 
618  protected $exceptions = false;
619 
625  protected $uniqueid = '';
626 
630  const STOP_MESSAGE = 0;
631 
635  const STOP_CONTINUE = 1;
636 
640  const STOP_CRITICAL = 2;
641 
645  const CRLF = "\r\n";
646 
651  const MAX_LINE_LENGTH = 998;
652 
657  public function __construct($exceptions = null)
658  {
659  if ($exceptions !== null) {
660  $this->exceptions = (boolean)$exceptions;
661  }
662  }
663 
667  public function __destruct()
668  {
669  //Close any open SMTP connection nicely
670  $this->smtpClose();
671  }
672 
686  private function mailPassthru($to, $subject, $body, $header, $params)
687  {
688  //Check overloading of mail function to avoid double-encoding
689  if (ini_get('mbstring.func_overload') & 1) {
690  $subject = $this->secureHeader($subject);
691  } else {
692  $subject = $this->encodeHeader($this->secureHeader($subject));
693  }
694  // patch-mjansen: begin #20376
695  if(0 == strlen($to) && strpos($header, 'To: undisclosed-recipients:;') !== false)
696  {
697  $to = 'undisclosed-recipients:;';
698  $header = preg_replace('/To: undisclosed-recipients:;(\s*)/', '', $header);
699  }
700  // patch-mjansen: end
701  //Can't use additional_parameters in safe_mode, calling mail() with null params breaks
702  //@link http://php.net/manual/en/function.mail.php
703  if (ini_get('safe_mode') or !$this->UseSendmailOptions or is_null($params)) {
704  $result = @mail($to, $subject, $body, $header);
705  } else {
706  $result = @mail($to, $subject, $body, $header, $params);
707  }
708  return $result;
709  }
717  protected function edebug($str)
718  {
719  if ($this->SMTPDebug <= 0) {
720  return;
721  }
722  //Avoid clash with built-in function names
723  if (!in_array($this->Debugoutput, array('error_log', 'html', 'echo')) and is_callable($this->Debugoutput)) {
724  call_user_func($this->Debugoutput, $str, $this->SMTPDebug);
725  return;
726  }
727  switch ($this->Debugoutput) {
728  case 'error_log':
729  //Don't output, just log
730  error_log($str);
731  break;
732  case 'html':
733  //Cleans up output a bit for a better looking, HTML-safe output
734  echo htmlentities(
735  preg_replace('/[\r\n]+/', '', $str),
736  ENT_QUOTES,
737  'UTF-8'
738  )
739  . "<br>\n";
740  break;
741  case 'echo':
742  default:
743  //Normalize line breaks
744  $str = preg_replace('/\r\n?/ms', "\n", $str);
745  echo gmdate('Y-m-d H:i:s') . "\t" . str_replace(
746  "\n",
747  "\n \t ",
748  trim($str)
749  ) . "\n";
750  }
751  }
752 
758  public function isHTML($isHtml = true)
759  {
760  if ($isHtml) {
761  $this->ContentType = 'text/html';
762  } else {
763  $this->ContentType = 'text/plain';
764  }
765  }
766 
771  public function isSMTP()
772  {
773  $this->Mailer = 'smtp';
774  }
775 
780  public function isMail()
781  {
782  $this->Mailer = 'mail';
783  }
784 
789  public function isSendmail()
790  {
791  $ini_sendmail_path = ini_get('sendmail_path');
792 
793  if (!stristr($ini_sendmail_path, 'sendmail')) {
794  $this->Sendmail = '/usr/sbin/sendmail';
795  } else {
796  $this->Sendmail = $ini_sendmail_path;
797  }
798  $this->Mailer = 'sendmail';
799  }
800 
805  public function isQmail()
806  {
807  $ini_sendmail_path = ini_get('sendmail_path');
808 
809  if (!stristr($ini_sendmail_path, 'qmail')) {
810  $this->Sendmail = '/var/qmail/bin/qmail-inject';
811  } else {
812  $this->Sendmail = $ini_sendmail_path;
813  }
814  $this->Mailer = 'qmail';
815  }
816 
823  public function addAddress($address, $name = '')
824  {
825  return $this->addOrEnqueueAnAddress('to', $address, $name);
826  }
827 
835  public function addCC($address, $name = '')
836  {
837  return $this->addOrEnqueueAnAddress('cc', $address, $name);
838  }
839 
847  public function addBCC($address, $name = '')
848  {
849  return $this->addOrEnqueueAnAddress('bcc', $address, $name);
850  }
851 
858  public function addReplyTo($address, $name = '')
859  {
860  return $this->addOrEnqueueAnAddress('Reply-To', $address, $name);
861  }
862 
875  protected function addOrEnqueueAnAddress($kind, $address, $name)
876  {
877  $address = trim($address);
878  $name = trim(preg_replace('/[\r\n]+/', '', $name)); //Strip breaks and trim
879  if (($pos = strrpos($address, '@')) === false) {
880  // At-sign is misssing.
881  $error_message = $this->lang('invalid_address') . " (addAnAddress $kind): $address";
882  $this->setError($error_message);
883  $this->edebug($error_message);
884  if ($this->exceptions) {
885  throw new phpmailerException($error_message);
886  }
887  return false;
888  }
889  $params = array($kind, $address, $name);
890  // Enqueue addresses with IDN until we know the PHPMailer::$CharSet.
891  if ($this->has8bitChars(substr($address, ++$pos)) and $this->idnSupported()) {
892  if ($kind != 'Reply-To') {
893  if (!array_key_exists($address, $this->RecipientsQueue)) {
894  $this->RecipientsQueue[$address] = $params;
895  return true;
896  }
897  } else {
898  if (!array_key_exists($address, $this->ReplyToQueue)) {
899  $this->ReplyToQueue[$address] = $params;
900  return true;
901  }
902  }
903  return false;
904  }
905  // Immediately add standard addresses without IDN.
906  return call_user_func_array(array($this, 'addAnAddress'), $params);
907  }
908 
919  protected function addAnAddress($kind, $address, $name = '')
920  {
921  if (!in_array($kind, array('to', 'cc', 'bcc', 'Reply-To'))) {
922  $error_message = $this->lang('Invalid recipient kind: ') . $kind;
923  $this->setError($error_message);
924  $this->edebug($error_message);
925  if ($this->exceptions) {
926  throw new phpmailerException($error_message);
927  }
928  return false;
929  }
930  if (!$this->validateAddress($address)) {
931  $error_message = $this->lang('invalid_address') . " (addAnAddress $kind): $address";
932  $this->setError($error_message);
933  $this->edebug($error_message);
934  if ($this->exceptions) {
935  throw new phpmailerException($error_message);
936  }
937  return false;
938  }
939  if ($kind != 'Reply-To') {
940  if (!array_key_exists(strtolower($address), $this->all_recipients)) {
941  array_push($this->$kind, array($address, $name));
942  $this->all_recipients[strtolower($address)] = true;
943  return true;
944  }
945  } else {
946  if (!array_key_exists(strtolower($address), $this->ReplyTo)) {
947  $this->ReplyTo[strtolower($address)] = array($address, $name);
948  return true;
949  }
950  }
951  return false;
952  }
953 
964  public function parseAddresses($addrstr, $useimap = true)
965  {
966  $addresses = array();
967  if ($useimap and function_exists('imap_rfc822_parse_adrlist')) {
968  //Use this built-in parser if it's available
969  $list = imap_rfc822_parse_adrlist($addrstr, '');
970  foreach ($list as $address) {
971  if ($address->host != '.SYNTAX-ERROR.') {
972  if ($this->validateAddress($address->mailbox . '@' . $address->host)) {
973  $addresses[] = array(
974  'name' => (property_exists($address, 'personal') ? $address->personal : ''),
975  'address' => $address->mailbox . '@' . $address->host
976  );
977  }
978  }
979  }
980  } else {
981  //Use this simpler parser
982  $list = explode(',', $addrstr);
983  foreach ($list as $address) {
984  $address = trim($address);
985  //Is there a separate name part?
986  if (strpos($address, '<') === false) {
987  //No separate name, just use the whole thing
988  if ($this->validateAddress($address)) {
989  $addresses[] = array(
990  'name' => '',
991  'address' => $address
992  );
993  }
994  } else {
995  list($name, $email) = explode('<', $address);
996  $email = trim(str_replace('>', '', $email));
997  if ($this->validateAddress($email)) {
998  $addresses[] = array(
999  'name' => trim(str_replace(array('"', "'"), '', $name)),
1000  'address' => $email
1001  );
1002  }
1003  }
1004  }
1005  }
1006  return $addresses;
1007  }
1008 
1017  public function setFrom($address, $name = '', $auto = true)
1018  {
1019  $address = trim($address);
1020  $name = trim(preg_replace('/[\r\n]+/', '', $name)); //Strip breaks and trim
1021  // Don't validate now addresses with IDN. Will be done in send().
1022  if (($pos = strrpos($address, '@')) === false or
1023  (!$this->has8bitChars(substr($address, ++$pos)) or !$this->idnSupported()) and
1024  !$this->validateAddress($address)) {
1025  $error_message = $this->lang('invalid_address') . " (setFrom) $address";
1026  $this->setError($error_message);
1027  $this->edebug($error_message);
1028  if ($this->exceptions) {
1029  throw new phpmailerException($error_message);
1030  }
1031  return false;
1032  }
1033  $this->From = $address;
1034  $this->FromName = $name;
1035  if ($auto) {
1036  if (empty($this->Sender)) {
1037  $this->Sender = $address;
1038  }
1039  }
1040  return true;
1041  }
1042 
1050  public function getLastMessageID()
1051  {
1052  return $this->lastMessageID;
1053  }
1054 
1074  public static function validateAddress($address, $patternselect = null)
1075  {
1076  if (is_null($patternselect)) {
1077  $patternselect = self::$validator;
1078  }
1079  if (is_callable($patternselect)) {
1080  return call_user_func($patternselect, $address);
1081  }
1082  //Reject line breaks in addresses; it's valid RFC5322, but not RFC5321
1083  if (strpos($address, "\n") !== false or strpos($address, "\r") !== false) {
1084  return false;
1085  }
1086  if (!$patternselect or $patternselect == 'auto') {
1087  //Check this constant first so it works when extension_loaded() is disabled by safe mode
1088  //Constant was added in PHP 5.2.4
1089  if (defined('PCRE_VERSION')) {
1090  //This pattern can get stuck in a recursive loop in PCRE <= 8.0.2
1091  if (version_compare(PCRE_VERSION, '8.0.3') >= 0) {
1092  $patternselect = 'pcre8';
1093  } else {
1094  $patternselect = 'pcre';
1095  }
1096  } elseif (function_exists('extension_loaded') and extension_loaded('pcre')) {
1097  //Fall back to older PCRE
1098  $patternselect = 'pcre';
1099  } else {
1100  //Filter_var appeared in PHP 5.2.0 and does not require the PCRE extension
1101  if (version_compare(PHP_VERSION, '5.2.0') >= 0) {
1102  $patternselect = 'php';
1103  } else {
1104  $patternselect = 'noregex';
1105  }
1106  }
1107  }
1108  switch ($patternselect) {
1109  case 'pcre8':
1116  return (boolean)preg_match(
1117  '/^(?!(?>(?1)"?(?>\\\[ -~]|[^"])"?(?1)){255,})(?!(?>(?1)"?(?>\\\[ -~]|[^"])"?(?1)){65,}@)' .
1118  '((?>(?>(?>((?>(?>(?>\x0D\x0A)?[\t ])+|(?>[\t ]*\x0D\x0A)?[\t ]+)?)(\((?>(?2)' .
1119  '(?>[\x01-\x08\x0B\x0C\x0E-\'*-\[\]-\x7F]|\\\[\x00-\x7F]|(?3)))*(?2)\)))+(?2))|(?2))?)' .
1120  '([!#-\'*+\/-9=?^-~-]+|"(?>(?2)(?>[\x01-\x08\x0B\x0C\x0E-!#-\[\]-\x7F]|\\\[\x00-\x7F]))*' .
1121  '(?2)")(?>(?1)\.(?1)(?4))*(?1)@(?!(?1)[a-z0-9-]{64,})(?1)(?>([a-z0-9](?>[a-z0-9-]*[a-z0-9])?)' .
1122  '(?>(?1)\.(?!(?1)[a-z0-9-]{64,})(?1)(?5)){0,126}|\[(?:(?>IPv6:(?>([a-f0-9]{1,4})(?>:(?6)){7}' .
1123  '|(?!(?:.*[a-f0-9][:\]]){8,})((?6)(?>:(?6)){0,6})?::(?7)?))|(?>(?>IPv6:(?>(?6)(?>:(?6)){5}:' .
1124  '|(?!(?:.*[a-f0-9]:){6,})(?8)?::(?>((?6)(?>:(?6)){0,4}):)?))?(25[0-5]|2[0-4][0-9]|1[0-9]{2}' .
1125  '|[1-9]?[0-9])(?>\.(?9)){3}))\])(?1)$/isD',
1126  $address
1127  );
1128  case 'pcre':
1129  //An older regex that doesn't need a recent PCRE
1130  return (boolean)preg_match(
1131  '/^(?!(?>"?(?>\\\[ -~]|[^"])"?){255,})(?!(?>"?(?>\\\[ -~]|[^"])"?){65,}@)(?>' .
1132  '[!#-\'*+\/-9=?^-~-]+|"(?>(?>[\x01-\x08\x0B\x0C\x0E-!#-\[\]-\x7F]|\\\[\x00-\xFF]))*")' .
1133  '(?>\.(?>[!#-\'*+\/-9=?^-~-]+|"(?>(?>[\x01-\x08\x0B\x0C\x0E-!#-\[\]-\x7F]|\\\[\x00-\xFF]))*"))*' .
1134  '@(?>(?![a-z0-9-]{64,})(?>[a-z0-9](?>[a-z0-9-]*[a-z0-9])?)(?>\.(?![a-z0-9-]{64,})' .
1135  '(?>[a-z0-9](?>[a-z0-9-]*[a-z0-9])?)){0,126}|\[(?:(?>IPv6:(?>(?>[a-f0-9]{1,4})(?>:' .
1136  '[a-f0-9]{1,4}){7}|(?!(?:.*[a-f0-9][:\]]){8,})(?>[a-f0-9]{1,4}(?>:[a-f0-9]{1,4}){0,6})?' .
1137  '::(?>[a-f0-9]{1,4}(?>:[a-f0-9]{1,4}){0,6})?))|(?>(?>IPv6:(?>[a-f0-9]{1,4}(?>:' .
1138  '[a-f0-9]{1,4}){5}:|(?!(?:.*[a-f0-9]:){6,})(?>[a-f0-9]{1,4}(?>:[a-f0-9]{1,4}){0,4})?' .
1139  '::(?>(?:[a-f0-9]{1,4}(?>:[a-f0-9]{1,4}){0,4}):)?))?(?>25[0-5]|2[0-4][0-9]|1[0-9]{2}' .
1140  '|[1-9]?[0-9])(?>\.(?>25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3}))\])$/isD',
1141  $address
1142  );
1143  case 'html5':
1148  return (boolean)preg_match(
1149  '/^[a-zA-Z0-9.!#$%&\'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}' .
1150  '[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/sD',
1151  $address
1152  );
1153  case 'noregex':
1154  //No PCRE! Do something _very_ approximate!
1155  //Check the address is 3 chars or longer and contains an @ that's not the first or last char
1156  return (strlen($address) >= 3
1157  and strpos($address, '@') >= 1
1158  and strpos($address, '@') != strlen($address) - 1);
1159  case 'php':
1160  default:
1161  return (boolean)filter_var($address, FILTER_VALIDATE_EMAIL);
1162  }
1163  }
1164 
1170  public function idnSupported()
1171  {
1172  // @TODO: Write our own "idn_to_ascii" function for PHP <= 5.2.
1173  return function_exists('idn_to_ascii') and function_exists('mb_convert_encoding');
1174  }
1175 
1187  public function punyencodeAddress($address)
1188  {
1189  // Verify we have required functions, CharSet, and at-sign.
1190  if ($this->idnSupported() and
1191  !empty($this->CharSet) and
1192  ($pos = strrpos($address, '@')) !== false) {
1193  $domain = substr($address, ++$pos);
1194  // Verify CharSet string is a valid one, and domain properly encoded in this CharSet.
1195  if ($this->has8bitChars($domain) and @mb_check_encoding($domain, $this->CharSet)) {
1196  $domain = mb_convert_encoding($domain, 'UTF-8', $this->CharSet);
1197  if (($punycode = defined('INTL_IDNA_VARIANT_UTS46') ?
1198  idn_to_ascii($domain, 0, INTL_IDNA_VARIANT_UTS46) :
1199  idn_to_ascii($domain)) !== false) {
1200  return substr($address, 0, $pos) . $punycode;
1201  }
1202  }
1203  }
1204  return $address;
1205  }
1206 
1213  public function send()
1214  {
1215  try {
1216  if (!$this->preSend()) {
1217  return false;
1218  }
1219  return $this->postSend();
1220  } catch (phpmailerException $exc) {
1221  $this->mailHeader = '';
1222  $this->setError($exc->getMessage());
1223  if ($this->exceptions) {
1224  throw $exc;
1225  }
1226  return false;
1227  }
1228  }
1229 
1235  public function preSend()
1236  {
1237  try {
1238  $this->error_count = 0; // Reset errors
1239  $this->mailHeader = '';
1240 
1241  // Dequeue recipient and Reply-To addresses with IDN
1242  foreach (array_merge($this->RecipientsQueue, $this->ReplyToQueue) as $params) {
1243  $params[1] = $this->punyencodeAddress($params[1]);
1244  call_user_func_array(array($this, 'addAnAddress'), $params);
1245  }
1246  if ((count($this->to) + count($this->cc) + count($this->bcc)) < 1) {
1247  throw new phpmailerException($this->lang('provide_address'), self::STOP_CRITICAL);
1248  }
1249 
1250  // Validate From, Sender, and ConfirmReadingTo addresses
1251  foreach (array('From', 'Sender', 'ConfirmReadingTo') as $address_kind) {
1252  $this->$address_kind = trim($this->$address_kind);
1253  if (empty($this->$address_kind)) {
1254  continue;
1255  }
1256  $this->$address_kind = $this->punyencodeAddress($this->$address_kind);
1257  if (!$this->validateAddress($this->$address_kind)) {
1258  $error_message = $this->lang('invalid_address') . ' (punyEncode) ' . $this->$address_kind;
1259  $this->setError($error_message);
1260  $this->edebug($error_message);
1261  if ($this->exceptions) {
1262  throw new phpmailerException($error_message);
1263  }
1264  return false;
1265  }
1266  }
1267 
1268  // Set whether the message is multipart/alternative
1269  if ($this->alternativeExists()) {
1270  $this->ContentType = 'multipart/alternative';
1271  }
1272 
1273  $this->setMessageType();
1274  // Refuse to send an empty message unless we are specifically allowing it
1275  if (!$this->AllowEmpty and empty($this->Body)) {
1276  throw new phpmailerException($this->lang('empty_message'), self::STOP_CRITICAL);
1277  }
1278 
1279  // Create body before headers in case body makes changes to headers (e.g. altering transfer encoding)
1280  $this->MIMEHeader = '';
1281  $this->MIMEBody = $this->createBody();
1282  // createBody may have added some headers, so retain them
1283  $tempheaders = $this->MIMEHeader;
1284  $this->MIMEHeader = $this->createHeader();
1285  $this->MIMEHeader .= $tempheaders;
1286 
1287  // To capture the complete message when using mail(), create
1288  // an extra header list which createHeader() doesn't fold in
1289  if ($this->Mailer == 'mail') {
1290  if (count($this->to) > 0) {
1291  $this->mailHeader .= $this->addrAppend('To', $this->to);
1292  } else {
1293  $this->mailHeader .= $this->headerLine('To', 'undisclosed-recipients:;');
1294  }
1295  $this->mailHeader .= $this->headerLine(
1296  'Subject',
1297  $this->encodeHeader($this->secureHeader(trim($this->Subject)))
1298  );
1299  }
1300 
1301  // Sign with DKIM if enabled
1302  if (!empty($this->DKIM_domain)
1303  && !empty($this->DKIM_selector)
1304  && (!empty($this->DKIM_private_string)
1305  || (!empty($this->DKIM_private) && file_exists($this->DKIM_private))
1306  )
1307  ) {
1308  $header_dkim = $this->DKIM_Add(
1309  $this->MIMEHeader . $this->mailHeader,
1310  $this->encodeHeader($this->secureHeader($this->Subject)),
1311  $this->MIMEBody
1312  );
1313  $this->MIMEHeader = rtrim($this->MIMEHeader, "\r\n ") . self::CRLF .
1314  str_replace("\r\n", "\n", $header_dkim) . self::CRLF;
1315  }
1316  return true;
1317  } catch (phpmailerException $exc) {
1318  $this->setError($exc->getMessage());
1319  if ($this->exceptions) {
1320  throw $exc;
1321  }
1322  return false;
1323  }
1324  }
1325 
1332  public function postSend()
1333  {
1334  try {
1335  // Choose the mailer and send through it
1336  switch ($this->Mailer) {
1337  case 'sendmail':
1338  case 'qmail':
1339  return $this->sendmailSend($this->MIMEHeader, $this->MIMEBody);
1340  case 'smtp':
1341  return $this->smtpSend($this->MIMEHeader, $this->MIMEBody);
1342  case 'mail':
1343  return $this->mailSend($this->MIMEHeader, $this->MIMEBody);
1344  default:
1345  $sendMethod = $this->Mailer.'Send';
1346  if (method_exists($this, $sendMethod)) {
1347  return $this->$sendMethod($this->MIMEHeader, $this->MIMEBody);
1348  }
1349 
1350  return $this->mailSend($this->MIMEHeader, $this->MIMEBody);
1351  }
1352  } catch (phpmailerException $exc) {
1353  $this->setError($exc->getMessage());
1354  $this->edebug($exc->getMessage());
1355  if ($this->exceptions) {
1356  throw $exc;
1357  }
1358  }
1359  return false;
1360  }
1361 
1371  protected function sendmailSend($header, $body)
1372  {
1373  // CVE-2016-10033, CVE-2016-10045: Don't pass -f if characters will be escaped.
1374  if (!empty($this->Sender) and self::isShellSafe($this->Sender)) {
1375  if ($this->Mailer == 'qmail') {
1376  $sendmailFmt = '%s -f%s';
1377  } else {
1378  $sendmailFmt = '%s -oi -f%s -t';
1379  }
1380  } else {
1381  if ($this->Mailer == 'qmail') {
1382  $sendmailFmt = '%s';
1383  } else {
1384  $sendmailFmt = '%s -oi -t';
1385  }
1386  }
1387 
1388  // TODO: If possible, this should be changed to escapeshellarg. Needs thorough testing.
1389  $sendmail = sprintf($sendmailFmt, escapeshellcmd($this->Sendmail), $this->Sender);
1390 
1391  if ($this->SingleTo) {
1392  foreach ($this->SingleToArray as $toAddr) {
1393  if (!@$mail = popen($sendmail, 'w')) {
1394  throw new phpmailerException($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL);
1395  }
1396  fputs($mail, 'To: ' . $toAddr . "\n");
1397  fputs($mail, $header);
1398  fputs($mail, $body);
1399  $result = pclose($mail);
1400  $this->doCallback(
1401  ($result == 0),
1402  array($toAddr),
1403  $this->cc,
1404  $this->bcc,
1405  $this->Subject,
1406  $body,
1407  $this->From
1408  );
1409  if ($result != 0) {
1410  throw new phpmailerException($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL);
1411  }
1412  }
1413  } else {
1414  if (!@$mail = popen($sendmail, 'w')) {
1415  throw new phpmailerException($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL);
1416  }
1417  fputs($mail, $header);
1418  fputs($mail, $body);
1419  $result = pclose($mail);
1420  $this->doCallback(
1421  ($result == 0),
1422  $this->to,
1423  $this->cc,
1424  $this->bcc,
1425  $this->Subject,
1426  $body,
1427  $this->From
1428  );
1429  if ($result != 0) {
1430  throw new phpmailerException($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL);
1431  }
1432  }
1433  return true;
1434  }
1435 
1445  protected static function isShellSafe($string)
1446  {
1447  // Future-proof
1448  if (escapeshellcmd($string) !== $string
1449  or !in_array(escapeshellarg($string), array("'$string'", "\"$string\""))
1450  ) {
1451  return false;
1452  }
1453 
1454  $length = strlen($string);
1455 
1456  for ($i = 0; $i < $length; $i++) {
1457  $c = $string[$i];
1458 
1459  // All other characters have a special meaning in at least one common shell, including = and +.
1460  // Full stop (.) has a special meaning in cmd.exe, but its impact should be negligible here.
1461  // Note that this does permit non-Latin alphanumeric characters based on the current locale.
1462  if (!ctype_alnum($c) && strpos('@_-.', $c) === false) {
1463  return false;
1464  }
1465  }
1466 
1467  return true;
1468  }
1469 
1479  protected function mailSend($header, $body)
1480  {
1481  $toArr = array();
1482  foreach ($this->to as $toaddr) {
1483  $toArr[] = $this->addrFormat($toaddr);
1484  }
1485  $to = implode(', ', $toArr);
1486 
1487  $params = null;
1488  //This sets the SMTP envelope sender which gets turned into a return-path header by the receiver
1489  if (!empty($this->Sender) and $this->validateAddress($this->Sender)) {
1490  // CVE-2016-10033, CVE-2016-10045: Don't pass -f if characters will be escaped.
1491  if (self::isShellSafe($this->Sender)) {
1492  $params = sprintf('-f%s', $this->Sender);
1493  }
1494  }
1495  if (!empty($this->Sender) and !ini_get('safe_mode') and $this->validateAddress($this->Sender)) {
1496  $old_from = ini_get('sendmail_from');
1497  ini_set('sendmail_from', $this->Sender);
1498  }
1499  $result = false;
1500  if ($this->SingleTo and count($toArr) > 1) {
1501  foreach ($toArr as $toAddr) {
1502  $result = $this->mailPassthru($toAddr, $this->Subject, $body, $header, $params);
1503  $this->doCallback($result, array($toAddr), $this->cc, $this->bcc, $this->Subject, $body, $this->From);
1504  }
1505  } else {
1506  $result = $this->mailPassthru($to, $this->Subject, $body, $header, $params);
1507  $this->doCallback($result, $this->to, $this->cc, $this->bcc, $this->Subject, $body, $this->From);
1508  }
1509  if (isset($old_from)) {
1510  ini_set('sendmail_from', $old_from);
1511  }
1512  if (!$result) {
1513  throw new phpmailerException($this->lang('instantiate'), self::STOP_CRITICAL);
1514  }
1515  return true;
1516  }
1517 
1523  public function getSMTPInstance()
1524  {
1525  if (!is_object($this->smtp)) {
1526  $this->smtp = new SMTP;
1527  }
1528  return $this->smtp;
1529  }
1530 
1543  protected function smtpSend($header, $body)
1544  {
1545  $bad_rcpt = array();
1546  if (!$this->smtpConnect($this->SMTPOptions)) {
1547  throw new phpmailerException($this->lang('smtp_connect_failed'), self::STOP_CRITICAL);
1548  }
1549  if (!empty($this->Sender) and $this->validateAddress($this->Sender)) {
1550  $smtp_from = $this->Sender;
1551  } else {
1552  $smtp_from = $this->From;
1553  }
1554  if (!$this->smtp->mail($smtp_from)) {
1555  $this->setError($this->lang('from_failed') . $smtp_from . ' : ' . implode(',', $this->smtp->getError()));
1556  throw new phpmailerException($this->ErrorInfo, self::STOP_CRITICAL);
1557  }
1558 
1559  // Attempt to send to all recipients
1560  foreach (array($this->to, $this->cc, $this->bcc) as $togroup) {
1561  foreach ($togroup as $to) {
1562  if (!$this->smtp->recipient($to[0])) {
1563  $error = $this->smtp->getError();
1564  $bad_rcpt[] = array('to' => $to[0], 'error' => $error['detail']);
1565  $isSent = false;
1566  } else {
1567  $isSent = true;
1568  }
1569  $this->doCallback($isSent, array($to[0]), array(), array(), $this->Subject, $body, $this->From);
1570  }
1571  }
1572 
1573  // Only send the DATA command if we have viable recipients
1574  if ((count($this->all_recipients) > count($bad_rcpt)) and !$this->smtp->data($header . $body)) {
1575  throw new phpmailerException($this->lang('data_not_accepted'), self::STOP_CRITICAL);
1576  }
1577  if ($this->SMTPKeepAlive) {
1578  $this->smtp->reset();
1579  } else {
1580  $this->smtp->quit();
1581  $this->smtp->close();
1582  }
1583  //Create error message for any bad addresses
1584  if (count($bad_rcpt) > 0) {
1585  $errstr = '';
1586  foreach ($bad_rcpt as $bad) {
1587  $errstr .= $bad['to'] . ': ' . $bad['error'];
1588  }
1589  throw new phpmailerException(
1590  $this->lang('recipients_failed') . $errstr,
1591  self::STOP_CONTINUE
1592  );
1593  }
1594  return true;
1595  }
1596 
1606  public function smtpConnect($options = null)
1607  {
1608  if (is_null($this->smtp)) {
1609  $this->smtp = $this->getSMTPInstance();
1610  }
1611 
1612  //If no options are provided, use whatever is set in the instance
1613  if (is_null($options)) {
1615  }
1616 
1617  // Already connected?
1618  if ($this->smtp->connected()) {
1619  return true;
1620  }
1621 
1622  $this->smtp->setTimeout($this->Timeout);
1623  $this->smtp->setDebugLevel($this->SMTPDebug);
1624  $this->smtp->setDebugOutput($this->Debugoutput);
1625  $this->smtp->setVerp($this->do_verp);
1626  $hosts = explode(';', $this->Host);
1627  $lastexception = null;
1628 
1629  foreach ($hosts as $hostentry) {
1630  $hostinfo = array();
1631  if (!preg_match(
1632  '/^((ssl|tls):\/\/)*([a-zA-Z0-9\.-]*|\[[a-fA-F0-9:]+\]):?([0-9]*)$/',
1633  trim($hostentry),
1634  $hostinfo
1635  )) {
1636  // Not a valid host entry
1637  $this->edebug('Ignoring invalid host: ' . $hostentry);
1638  continue;
1639  }
1640  // $hostinfo[2]: optional ssl or tls prefix
1641  // $hostinfo[3]: the hostname
1642  // $hostinfo[4]: optional port number
1643  // The host string prefix can temporarily override the current setting for SMTPSecure
1644  // If it's not specified, the default value is used
1645  $prefix = '';
1646  $secure = $this->SMTPSecure;
1647  $tls = ($this->SMTPSecure == 'tls');
1648  if ('ssl' == $hostinfo[2] or ('' == $hostinfo[2] and 'ssl' == $this->SMTPSecure)) {
1649  $prefix = 'ssl://';
1650  $tls = false; // Can't have SSL and TLS at the same time
1651  $secure = 'ssl';
1652  } elseif ($hostinfo[2] == 'tls') {
1653  $tls = true;
1654  // tls doesn't use a prefix
1655  $secure = 'tls';
1656  }
1657  //Do we need the OpenSSL extension?
1658  $sslext = defined('OPENSSL_ALGO_SHA1');
1659  if ('tls' === $secure or 'ssl' === $secure) {
1660  //Check for an OpenSSL constant rather than using extension_loaded, which is sometimes disabled
1661  if (!$sslext) {
1662  throw new phpmailerException($this->lang('extension_missing').'openssl', self::STOP_CRITICAL);
1663  }
1664  }
1665  $host = $hostinfo[3];
1666  $port = $this->Port;
1667  $tport = (integer)$hostinfo[4];
1668  if ($tport > 0 and $tport < 65536) {
1669  $port = $tport;
1670  }
1671  if ($this->smtp->connect($prefix . $host, $port, $this->Timeout, $options)) {
1672  try {
1673  if ($this->Helo) {
1674  $hello = $this->Helo;
1675  } else {
1676  $hello = $this->serverHostname();
1677  }
1678  $this->smtp->hello($hello);
1679  //Automatically enable TLS encryption if:
1680  // * it's not disabled
1681  // * we have openssl extension
1682  // * we are not already using SSL
1683  // * the server offers STARTTLS
1684  if ($this->SMTPAutoTLS and $sslext and $secure != 'ssl' and $this->smtp->getServerExt('STARTTLS')) {
1685  $tls = true;
1686  }
1687  if ($tls) {
1688  if (!$this->smtp->startTLS()) {
1689  throw new phpmailerException($this->lang('connect_host'));
1690  }
1691  // We must resend EHLO after TLS negotiation
1692  $this->smtp->hello($hello);
1693  }
1694  if ($this->SMTPAuth) {
1695  if (!$this->smtp->authenticate(
1696  $this->Username,
1697  $this->Password,
1698  $this->AuthType,
1699  $this->Realm,
1700  $this->Workstation
1701  )
1702  ) {
1703  throw new phpmailerException($this->lang('authenticate'));
1704  }
1705  }
1706  return true;
1707  } catch (phpmailerException $exc) {
1708  $lastexception = $exc;
1709  $this->edebug($exc->getMessage());
1710  // We must have connected, but then failed TLS or Auth, so close connection nicely
1711  $this->smtp->quit();
1712  }
1713  }
1714  }
1715  // If we get here, all connection attempts have failed, so close connection hard
1716  $this->smtp->close();
1717  // As we've caught all exceptions, just report whatever the last one was
1718  if ($this->exceptions and !is_null($lastexception)) {
1719  throw $lastexception;
1720  }
1721  return false;
1722  }
1723 
1728  public function smtpClose()
1729  {
1730  if (is_a($this->smtp, 'SMTP')) {
1731  if ($this->smtp->connected()) {
1732  $this->smtp->quit();
1733  $this->smtp->close();
1734  }
1735  }
1736  }
1737 
1747  public function setLanguage($langcode = 'en', $lang_path = '')
1748  {
1749  // Backwards compatibility for renamed language codes
1750  $renamed_langcodes = array(
1751  'br' => 'pt_br',
1752  'cz' => 'cs',
1753  'dk' => 'da',
1754  'no' => 'nb',
1755  'se' => 'sv',
1756  'sr' => 'rs'
1757  );
1758 
1759  if (isset($renamed_langcodes[$langcode])) {
1760  $langcode = $renamed_langcodes[$langcode];
1761  }
1762 
1763  // Define full set of translatable strings in English
1765  'authenticate' => 'SMTP Error: Could not authenticate.',
1766  'connect_host' => 'SMTP Error: Could not connect to SMTP host.',
1767  'data_not_accepted' => 'SMTP Error: data not accepted.',
1768  'empty_message' => 'Message body empty',
1769  'encoding' => 'Unknown encoding: ',
1770  'execute' => 'Could not execute: ',
1771  'file_access' => 'Could not access file: ',
1772  'file_open' => 'File Error: Could not open file: ',
1773  'from_failed' => 'The following From address failed: ',
1774  'instantiate' => 'Could not instantiate mail function.',
1775  'invalid_address' => 'Invalid address: ',
1776  'mailer_not_supported' => ' mailer is not supported.',
1777  'provide_address' => 'You must provide at least one recipient email address.',
1778  'recipients_failed' => 'SMTP Error: The following recipients failed: ',
1779  'signing' => 'Signing Error: ',
1780  'smtp_connect_failed' => 'SMTP connect() failed.',
1781  'smtp_error' => 'SMTP server error: ',
1782  'variable_set' => 'Cannot set or reset variable: ',
1783  'extension_missing' => 'Extension missing: '
1784  );
1785  if (empty($lang_path)) {
1786  // Calculate an absolute path so it can work if CWD is not here
1787  $lang_path = dirname(__FILE__). DIRECTORY_SEPARATOR . 'language'. DIRECTORY_SEPARATOR;
1788  }
1789  //Validate $langcode
1790  if (!preg_match('/^[a-z]{2}(?:_[a-zA-Z]{2})?$/', $langcode)) {
1791  $langcode = 'en';
1792  }
1793  $foundlang = true;
1794  $lang_file = $lang_path . 'phpmailer.lang-' . $langcode . '.php';
1795  // There is no English translation file
1796  if ($langcode != 'en') {
1797  // Make sure language file path is readable
1798  if (!is_readable($lang_file)) {
1799  $foundlang = false;
1800  } else {
1801  // Overwrite language-specific strings.
1802  // This way we'll never have missing translation keys.
1803  $foundlang = include $lang_file;
1804  }
1805  }
1806  $this->language = $PHPMAILER_LANG;
1807  return (boolean)$foundlang; // Returns false if language not found
1808  }
1809 
1814  public function getTranslations()
1815  {
1816  return $this->language;
1817  }
1818 
1829  public function addrAppend($type, $addr)
1830  {
1831  $addresses = array();
1832  foreach ($addr as $address) {
1833  $addresses[] = $this->addrFormat($address);
1834  }
1835  return $type . ': ' . implode(', ', $addresses) . $this->LE;
1836  }
1837 
1845  public function addrFormat($addr)
1846  {
1847  if (empty($addr[1])) { // No name provided
1848  return $this->secureHeader($addr[0]);
1849  } else {
1850  return $this->encodeHeader($this->secureHeader($addr[1]), 'phrase') . ' <' . $this->secureHeader(
1851  $addr[0]
1852  ) . '>';
1853  }
1854  }
1855 
1867  public function wrapText($message, $length, $qp_mode = false)
1868  {
1869  if ($qp_mode) {
1870  $soft_break = sprintf(' =%s', $this->LE);
1871  } else {
1872  $soft_break = $this->LE;
1873  }
1874  // If utf-8 encoding is used, we will need to make sure we don't
1875  // split multibyte characters when we wrap
1876  $is_utf8 = (strtolower($this->CharSet) == 'utf-8');
1877  $lelen = strlen($this->LE);
1878  $crlflen = strlen(self::CRLF);
1879 
1880  $message = $this->fixEOL($message);
1881  //Remove a trailing line break
1882  if (substr($message, -$lelen) == $this->LE) {
1883  $message = substr($message, 0, -$lelen);
1884  }
1885 
1886  //Split message into lines
1887  $lines = explode($this->LE, $message);
1888  //Message will be rebuilt in here
1889  $message = '';
1890  foreach ($lines as $line) {
1891  $words = explode(' ', $line);
1892  $buf = '';
1893  $firstword = true;
1894  foreach ($words as $word) {
1895  if ($qp_mode and (strlen($word) > $length)) {
1896  $space_left = $length - strlen($buf) - $crlflen;
1897  if (!$firstword) {
1898  if ($space_left > 20) {
1899  $len = $space_left;
1900  if ($is_utf8) {
1901  $len = $this->utf8CharBoundary($word, $len);
1902  } elseif (substr($word, $len - 1, 1) == '=') {
1903  $len--;
1904  } elseif (substr($word, $len - 2, 1) == '=') {
1905  $len -= 2;
1906  }
1907  $part = substr($word, 0, $len);
1908  $word = substr($word, $len);
1909  $buf .= ' ' . $part;
1910  $message .= $buf . sprintf('=%s', self::CRLF);
1911  } else {
1912  $message .= $buf . $soft_break;
1913  }
1914  $buf = '';
1915  }
1916  while (strlen($word) > 0) {
1917  if ($length <= 0) {
1918  break;
1919  }
1920  $len = $length;
1921  if ($is_utf8) {
1922  $len = $this->utf8CharBoundary($word, $len);
1923  } elseif (substr($word, $len - 1, 1) == '=') {
1924  $len--;
1925  } elseif (substr($word, $len - 2, 1) == '=') {
1926  $len -= 2;
1927  }
1928  $part = substr($word, 0, $len);
1929  $word = substr($word, $len);
1930 
1931  if (strlen($word) > 0) {
1932  $message .= $part . sprintf('=%s', self::CRLF);
1933  } else {
1934  $buf = $part;
1935  }
1936  }
1937  } else {
1938  $buf_o = $buf;
1939  if (!$firstword) {
1940  $buf .= ' ';
1941  }
1942  $buf .= $word;
1943 
1944  if (strlen($buf) > $length and $buf_o != '') {
1945  $message .= $buf_o . $soft_break;
1946  $buf = $word;
1947  }
1948  }
1949  $firstword = false;
1950  }
1951  $message .= $buf . self::CRLF;
1952  }
1953 
1954  return $message;
1955  }
1956 
1966  public function utf8CharBoundary($encodedText, $maxLength)
1967  {
1968  $foundSplitPos = false;
1969  $lookBack = 3;
1970  while (!$foundSplitPos) {
1971  $lastChunk = substr($encodedText, $maxLength - $lookBack, $lookBack);
1972  $encodedCharPos = strpos($lastChunk, '=');
1973  if (false !== $encodedCharPos) {
1974  // Found start of encoded character byte within $lookBack block.
1975  // Check the encoded byte value (the 2 chars after the '=')
1976  $hex = substr($encodedText, $maxLength - $lookBack + $encodedCharPos + 1, 2);
1977  $dec = hexdec($hex);
1978  if ($dec < 128) {
1979  // Single byte character.
1980  // If the encoded char was found at pos 0, it will fit
1981  // otherwise reduce maxLength to start of the encoded char
1982  if ($encodedCharPos > 0) {
1983  $maxLength = $maxLength - ($lookBack - $encodedCharPos);
1984  }
1985  $foundSplitPos = true;
1986  } elseif ($dec >= 192) {
1987  // First byte of a multi byte character
1988  // Reduce maxLength to split at start of character
1989  $maxLength = $maxLength - ($lookBack - $encodedCharPos);
1990  $foundSplitPos = true;
1991  } elseif ($dec < 192) {
1992  // Middle byte of a multi byte character, look further back
1993  $lookBack += 3;
1994  }
1995  } else {
1996  // No encoded character found
1997  $foundSplitPos = true;
1998  }
1999  }
2000  return $maxLength;
2001  }
2002 
2011  public function setWordWrap()
2012  {
2013  if ($this->WordWrap < 1) {
2014  return;
2015  }
2016 
2017  switch ($this->message_type) {
2018  case 'alt':
2019  case 'alt_inline':
2020  case 'alt_attach':
2021  case 'alt_inline_attach':
2022  $this->AltBody = $this->wrapText($this->AltBody, $this->WordWrap);
2023  break;
2024  default:
2025  $this->Body = $this->wrapText($this->Body, $this->WordWrap);
2026  break;
2027  }
2028  }
2029 
2035  public function createHeader()
2036  {
2037  $result = '';
2038 
2039  $result .= $this->headerLine('Date', $this->MessageDate == '' ? self::rfcDate() : $this->MessageDate);
2040 
2041  // To be created automatically by mail()
2042  if ($this->SingleTo) {
2043  if ($this->Mailer != 'mail') {
2044  foreach ($this->to as $toaddr) {
2045  $this->SingleToArray[] = $this->addrFormat($toaddr);
2046  }
2047  }
2048  } else {
2049  if (count($this->to) > 0) {
2050  if ($this->Mailer != 'mail') {
2051  $result .= $this->addrAppend('To', $this->to);
2052  }
2053  } elseif (count($this->cc) == 0) {
2054  $result .= $this->headerLine('To', 'undisclosed-recipients:;');
2055  }
2056  }
2057 
2058  $result .= $this->addrAppend('From', array(array(trim($this->From), $this->FromName)));
2059 
2060  // sendmail and mail() extract Cc from the header before sending
2061  if (count($this->cc) > 0) {
2062  $result .= $this->addrAppend('Cc', $this->cc);
2063  }
2064 
2065  // sendmail and mail() extract Bcc from the header before sending
2066  if ((
2067  $this->Mailer == 'sendmail' or $this->Mailer == 'qmail' or $this->Mailer == 'mail'
2068  )
2069  and count($this->bcc) > 0
2070  ) {
2071  $result .= $this->addrAppend('Bcc', $this->bcc);
2072  }
2073 
2074  if (count($this->ReplyTo) > 0) {
2075  $result .= $this->addrAppend('Reply-To', $this->ReplyTo);
2076  }
2077 
2078  // mail() sets the subject itself
2079  if ($this->Mailer != 'mail') {
2080  $result .= $this->headerLine('Subject', $this->encodeHeader($this->secureHeader($this->Subject)));
2081  }
2082 
2083  // Only allow a custom message ID if it conforms to RFC 5322 section 3.6.4
2084  // https://tools.ietf.org/html/rfc5322#section-3.6.4
2085  if ('' != $this->MessageID and preg_match('/^<.*@.*>$/', $this->MessageID)) {
2086  $this->lastMessageID = $this->MessageID;
2087  } else {
2088  $this->lastMessageID = sprintf('<%s@%s>', $this->uniqueid, $this->serverHostname());
2089  }
2090  $result .= $this->headerLine('Message-ID', $this->lastMessageID);
2091  if (!is_null($this->Priority)) {
2092  $result .= $this->headerLine('X-Priority', $this->Priority);
2093  }
2094  if ($this->XMailer == '') {
2095  $result .= $this->headerLine(
2096  'X-Mailer',
2097  'PHPMailer ' . $this->Version . ' (https://github.com/PHPMailer/PHPMailer)'
2098  );
2099  } else {
2100  $myXmailer = trim($this->XMailer);
2101  if ($myXmailer) {
2102  $result .= $this->headerLine('X-Mailer', $myXmailer);
2103  }
2104  }
2105 
2106  if ($this->ConfirmReadingTo != '') {
2107  $result .= $this->headerLine('Disposition-Notification-To', '<' . $this->ConfirmReadingTo . '>');
2108  }
2109 
2110  // Add custom headers
2111  foreach ($this->CustomHeader as $header) {
2112  $result .= $this->headerLine(
2113  trim($header[0]),
2114  $this->encodeHeader(trim($header[1]))
2115  );
2116  }
2117  if (!$this->sign_key_file) {
2118  $result .= $this->headerLine('MIME-Version', '1.0');
2119  $result .= $this->getMailMIME();
2120  }
2121 
2122  return $result;
2123  }
2124 
2130  public function getMailMIME()
2131  {
2132  $result = '';
2133  $ismultipart = true;
2134  switch ($this->message_type) {
2135  case 'inline':
2136  $result .= $this->headerLine('Content-Type', 'multipart/related;');
2137  $result .= $this->textLine("\tboundary=\"" . $this->boundary[1] . '"');
2138  break;
2139  case 'attach':
2140  case 'inline_attach':
2141  case 'alt_attach':
2142  case 'alt_inline_attach':
2143  $result .= $this->headerLine('Content-Type', 'multipart/mixed;');
2144  $result .= $this->textLine("\tboundary=\"" . $this->boundary[1] . '"');
2145  break;
2146  case 'alt':
2147  case 'alt_inline':
2148  $result .= $this->headerLine('Content-Type', 'multipart/alternative;');
2149  $result .= $this->textLine("\tboundary=\"" . $this->boundary[1] . '"');
2150  break;
2151  default:
2152  // Catches case 'plain': and case '':
2153  $result .= $this->textLine('Content-Type: ' . $this->ContentType . '; charset=' . $this->CharSet);
2154  $ismultipart = false;
2155  break;
2156  }
2157  // RFC1341 part 5 says 7bit is assumed if not specified
2158  if ($this->Encoding != '7bit') {
2159  // RFC 2045 section 6.4 says multipart MIME parts may only use 7bit, 8bit or binary CTE
2160  if ($ismultipart) {
2161  if ($this->Encoding == '8bit') {
2162  $result .= $this->headerLine('Content-Transfer-Encoding', '8bit');
2163  }
2164  // The only remaining alternatives are quoted-printable and base64, which are both 7bit compatible
2165  } else {
2166  $result .= $this->headerLine('Content-Transfer-Encoding', $this->Encoding);
2167  }
2168  }
2169 
2170  if ($this->Mailer != 'mail') {
2171  $result .= $this->LE;
2172  }
2173 
2174  return $result;
2175  }
2176 
2185  public function getSentMIMEMessage()
2186  {
2187  return rtrim($this->MIMEHeader . $this->mailHeader, "\n\r") . self::CRLF . self::CRLF . $this->MIMEBody;
2188  }
2189 
2194  protected function generateId() {
2195  return md5(uniqid(time()));
2196  }
2197 
2205  public function createBody()
2206  {
2207  $body = '';
2208  //Create unique IDs and preset boundaries
2209  $this->uniqueid = $this->generateId();
2210  $this->boundary[1] = 'b1_' . $this->uniqueid;
2211  $this->boundary[2] = 'b2_' . $this->uniqueid;
2212  $this->boundary[3] = 'b3_' . $this->uniqueid;
2213 
2214  if ($this->sign_key_file) {
2215  $body .= $this->getMailMIME() . $this->LE;
2216  }
2217 
2218  $this->setWordWrap();
2219 
2220  $bodyEncoding = $this->Encoding;
2221  $bodyCharSet = $this->CharSet;
2222  //Can we do a 7-bit downgrade?
2223  if ($bodyEncoding == '8bit' and !$this->has8bitChars($this->Body)) {
2224  $bodyEncoding = '7bit';
2225  //All ISO 8859, Windows codepage and UTF-8 charsets are ascii compatible up to 7-bit
2226  $bodyCharSet = 'us-ascii';
2227  }
2228  //If lines are too long, and we're not already using an encoding that will shorten them,
2229  //change to quoted-printable transfer encoding for the body part only
2230  if ('base64' != $this->Encoding and self::hasLineLongerThanMax($this->Body)) {
2231  $bodyEncoding = 'quoted-printable';
2232  }
2233 
2234  $altBodyEncoding = $this->Encoding;
2235  $altBodyCharSet = $this->CharSet;
2236  //Can we do a 7-bit downgrade?
2237  if ($altBodyEncoding == '8bit' and !$this->has8bitChars($this->AltBody)) {
2238  $altBodyEncoding = '7bit';
2239  //All ISO 8859, Windows codepage and UTF-8 charsets are ascii compatible up to 7-bit
2240  $altBodyCharSet = 'us-ascii';
2241  }
2242  //If lines are too long, and we're not already using an encoding that will shorten them,
2243  //change to quoted-printable transfer encoding for the alt body part only
2244  if ('base64' != $altBodyEncoding and self::hasLineLongerThanMax($this->AltBody)) {
2245  $altBodyEncoding = 'quoted-printable';
2246  }
2247  //Use this as a preamble in all multipart message types
2248  $mimepre = "This is a multi-part message in MIME format." . $this->LE . $this->LE;
2249  switch ($this->message_type) {
2250  case 'inline':
2251  $body .= $mimepre;
2252  $body .= $this->getBoundary($this->boundary[1], $bodyCharSet, '', $bodyEncoding);
2253  $body .= $this->encodeString($this->Body, $bodyEncoding);
2254  $body .= $this->LE . $this->LE;
2255  $body .= $this->attachAll('inline', $this->boundary[1]);
2256  break;
2257  case 'attach':
2258  $body .= $mimepre;
2259  $body .= $this->getBoundary($this->boundary[1], $bodyCharSet, '', $bodyEncoding);
2260  $body .= $this->encodeString($this->Body, $bodyEncoding);
2261  $body .= $this->LE . $this->LE;
2262  $body .= $this->attachAll('attachment', $this->boundary[1]);
2263  break;
2264  case 'inline_attach':
2265  $body .= $mimepre;
2266  $body .= $this->textLine('--' . $this->boundary[1]);
2267  $body .= $this->headerLine('Content-Type', 'multipart/related;');
2268  $body .= $this->textLine("\tboundary=\"" . $this->boundary[2] . '"');
2269  $body .= $this->LE;
2270  $body .= $this->getBoundary($this->boundary[2], $bodyCharSet, '', $bodyEncoding);
2271  $body .= $this->encodeString($this->Body, $bodyEncoding);
2272  $body .= $this->LE . $this->LE;
2273  $body .= $this->attachAll('inline', $this->boundary[2]);
2274  $body .= $this->LE;
2275  $body .= $this->attachAll('attachment', $this->boundary[1]);
2276  break;
2277  case 'alt':
2278  $body .= $mimepre;
2279  $body .= $this->getBoundary($this->boundary[1], $altBodyCharSet, 'text/plain', $altBodyEncoding);
2280  $body .= $this->encodeString($this->AltBody, $altBodyEncoding);
2281  $body .= $this->LE . $this->LE;
2282  $body .= $this->getBoundary($this->boundary[1], $bodyCharSet, 'text/html', $bodyEncoding);
2283  $body .= $this->encodeString($this->Body, $bodyEncoding);
2284  $body .= $this->LE . $this->LE;
2285  if (!empty($this->Ical)) {
2286  $body .= $this->getBoundary($this->boundary[1], '', 'text/calendar; method=REQUEST', '');
2287  $body .= $this->encodeString($this->Ical, $this->Encoding);
2288  $body .= $this->LE . $this->LE;
2289  }
2290  $body .= $this->endBoundary($this->boundary[1]);
2291  break;
2292  case 'alt_inline':
2293  $body .= $mimepre;
2294  $body .= $this->getBoundary($this->boundary[1], $altBodyCharSet, 'text/plain', $altBodyEncoding);
2295  $body .= $this->encodeString($this->AltBody, $altBodyEncoding);
2296  $body .= $this->LE . $this->LE;
2297  $body .= $this->textLine('--' . $this->boundary[1]);
2298  $body .= $this->headerLine('Content-Type', 'multipart/related;');
2299  $body .= $this->textLine("\tboundary=\"" . $this->boundary[2] . '"');
2300  $body .= $this->LE;
2301  $body .= $this->getBoundary($this->boundary[2], $bodyCharSet, 'text/html', $bodyEncoding);
2302  $body .= $this->encodeString($this->Body, $bodyEncoding);
2303  $body .= $this->LE . $this->LE;
2304  $body .= $this->attachAll('inline', $this->boundary[2]);
2305  $body .= $this->LE;
2306  $body .= $this->endBoundary($this->boundary[1]);
2307  break;
2308  case 'alt_attach':
2309  $body .= $mimepre;
2310  $body .= $this->textLine('--' . $this->boundary[1]);
2311  $body .= $this->headerLine('Content-Type', 'multipart/alternative;');
2312  $body .= $this->textLine("\tboundary=\"" . $this->boundary[2] . '"');
2313  $body .= $this->LE;
2314  $body .= $this->getBoundary($this->boundary[2], $altBodyCharSet, 'text/plain', $altBodyEncoding);
2315  $body .= $this->encodeString($this->AltBody, $altBodyEncoding);
2316  $body .= $this->LE . $this->LE;
2317  $body .= $this->getBoundary($this->boundary[2], $bodyCharSet, 'text/html', $bodyEncoding);
2318  $body .= $this->encodeString($this->Body, $bodyEncoding);
2319  $body .= $this->LE . $this->LE;
2320  $body .= $this->endBoundary($this->boundary[2]);
2321  $body .= $this->LE;
2322  $body .= $this->attachAll('attachment', $this->boundary[1]);
2323  break;
2324  case 'alt_inline_attach':
2325  $body .= $mimepre;
2326  $body .= $this->textLine('--' . $this->boundary[1]);
2327  $body .= $this->headerLine('Content-Type', 'multipart/alternative;');
2328  $body .= $this->textLine("\tboundary=\"" . $this->boundary[2] . '"');
2329  $body .= $this->LE;
2330  $body .= $this->getBoundary($this->boundary[2], $altBodyCharSet, 'text/plain', $altBodyEncoding);
2331  $body .= $this->encodeString($this->AltBody, $altBodyEncoding);
2332  $body .= $this->LE . $this->LE;
2333  $body .= $this->textLine('--' . $this->boundary[2]);
2334  $body .= $this->headerLine('Content-Type', 'multipart/related;');
2335  $body .= $this->textLine("\tboundary=\"" . $this->boundary[3] . '"');
2336  $body .= $this->LE;
2337  $body .= $this->getBoundary($this->boundary[3], $bodyCharSet, 'text/html', $bodyEncoding);
2338  $body .= $this->encodeString($this->Body, $bodyEncoding);
2339  $body .= $this->LE . $this->LE;
2340  $body .= $this->attachAll('inline', $this->boundary[3]);
2341  $body .= $this->LE;
2342  $body .= $this->endBoundary($this->boundary[2]);
2343  $body .= $this->LE;
2344  $body .= $this->attachAll('attachment', $this->boundary[1]);
2345  break;
2346  default:
2347  // Catch case 'plain' and case '', applies to simple `text/plain` and `text/html` body content types
2348  //Reset the `Encoding` property in case we changed it for line length reasons
2349  $this->Encoding = $bodyEncoding;
2350  $body .= $this->encodeString($this->Body, $this->Encoding);
2351  break;
2352  }
2353 
2354  if ($this->isError()) {
2355  $body = '';
2356  } elseif ($this->sign_key_file) {
2357  try {
2358  if (!defined('PKCS7_TEXT')) {
2359  throw new phpmailerException($this->lang('extension_missing') . 'openssl');
2360  }
2361  // @TODO would be nice to use php://temp streams here, but need to wrap for PHP < 5.1
2362  $file = tempnam(sys_get_temp_dir(), 'mail');
2363  if (false === file_put_contents($file, $body)) {
2364  throw new phpmailerException($this->lang('signing') . ' Could not write temp file');
2365  }
2366  $signed = tempnam(sys_get_temp_dir(), 'signed');
2367  //Workaround for PHP bug https://bugs.php.net/bug.php?id=69197
2368  if (empty($this->sign_extracerts_file)) {
2369  $sign = @openssl_pkcs7_sign(
2370  $file,
2371  $signed,
2372  'file://' . realpath($this->sign_cert_file),
2373  array('file://' . realpath($this->sign_key_file), $this->sign_key_pass),
2374  null
2375  );
2376  } else {
2377  $sign = @openssl_pkcs7_sign(
2378  $file,
2379  $signed,
2380  'file://' . realpath($this->sign_cert_file),
2381  array('file://' . realpath($this->sign_key_file), $this->sign_key_pass),
2382  null,
2383  PKCS7_DETACHED,
2384  $this->sign_extracerts_file
2385  );
2386  }
2387  if ($sign) {
2388  @unlink($file);
2389  $body = file_get_contents($signed);
2390  @unlink($signed);
2391  //The message returned by openssl contains both headers and body, so need to split them up
2392  $parts = explode("\n\n", $body, 2);
2393  $this->MIMEHeader .= $parts[0] . $this->LE . $this->LE;
2394  $body = $parts[1];
2395  } else {
2396  @unlink($file);
2397  @unlink($signed);
2398  throw new phpmailerException($this->lang('signing') . openssl_error_string());
2399  }
2400  } catch (phpmailerException $exc) {
2401  $body = '';
2402  if ($this->exceptions) {
2403  throw $exc;
2404  }
2405  }
2406  }
2407  return $body;
2408  }
2409 
2419  protected function getBoundary($boundary, $charSet, $contentType, $encoding)
2420  {
2421  $result = '';
2422  if ($charSet == '') {
2423  $charSet = $this->CharSet;
2424  }
2425  if ($contentType == '') {
2427  }
2428  if ($encoding == '') {
2429  $encoding = $this->Encoding;
2430  }
2431  $result .= $this->textLine('--' . $boundary);
2432  $result .= sprintf('Content-Type: %s; charset=%s', $contentType, $charSet);
2433  $result .= $this->LE;
2434  // RFC1341 part 5 says 7bit is assumed if not specified
2435  if ($encoding != '7bit') {
2436  $result .= $this->headerLine('Content-Transfer-Encoding', $encoding);
2437  }
2438  $result .= $this->LE;
2439 
2440  return $result;
2441  }
2442 
2449  protected function endBoundary($boundary)
2450  {
2451  return $this->LE . '--' . $boundary . '--' . $this->LE;
2452  }
2453 
2460  protected function setMessageType()
2461  {
2462  $type = array();
2463  if ($this->alternativeExists()) {
2464  $type[] = 'alt';
2465  }
2466  if ($this->inlineImageExists()) {
2467  $type[] = 'inline';
2468  }
2469  if ($this->attachmentExists()) {
2470  $type[] = 'attach';
2471  }
2472  $this->message_type = implode('_', $type);
2473  if ($this->message_type == '') {
2474  //The 'plain' message_type refers to the message having a single body element, not that it is plain-text
2475  $this->message_type = 'plain';
2476  }
2477  }
2478 
2486  public function headerLine($name, $value)
2487  {
2488  return $name . ': ' . $value . $this->LE;
2489  }
2490 
2497  public function textLine($value)
2498  {
2499  return $value . $this->LE;
2500  }
2501 
2514  public function addAttachment($path, $name = '', $encoding = 'base64', $type = '', $disposition = 'attachment')
2515  {
2516  try {
2517  if (!@is_file($path)) {
2518  throw new phpmailerException($this->lang('file_access') . $path, self::STOP_CONTINUE);
2519  }
2520 
2521  // If a MIME type is not specified, try to work it out from the file name
2522  if ($type == '') {
2523  $type = self::filenameToType($path);
2524  }
2525 
2526  $filename = basename($path);
2527  if ($name == '') {
2528  $name = $filename;
2529  }
2530 
2531  $this->attachment[] = array(
2532  0 => $path,
2533  1 => $filename,
2534  2 => $name,
2535  3 => $encoding,
2536  4 => $type,
2537  5 => false, // isStringAttachment
2538  6 => $disposition,
2539  7 => 0
2540  );
2541 
2542  } catch (phpmailerException $exc) {
2543  $this->setError($exc->getMessage());
2544  $this->edebug($exc->getMessage());
2545  if ($this->exceptions) {
2546  throw $exc;
2547  }
2548  return false;
2549  }
2550  return true;
2551  }
2552 
2557  public function getAttachments()
2558  {
2559  return $this->attachment;
2560  }
2561 
2570  protected function attachAll($disposition_type, $boundary)
2571  {
2572  // Return text of body
2573  $mime = array();
2574  $cidUniq = array();
2575  $incl = array();
2576 
2577  // Add all attachments
2578  foreach ($this->attachment as $attachment) {
2579  // Check if it is a valid disposition_filter
2580  if ($attachment[6] == $disposition_type) {
2581  // Check for string attachment
2582  $string = '';
2583  $path = '';
2584  $bString = $attachment[5];
2585  if ($bString) {
2586  $string = $attachment[0];
2587  } else {
2588  $path = $attachment[0];
2589  }
2590 
2591  $inclhash = md5(serialize($attachment));
2592  if (in_array($inclhash, $incl)) {
2593  continue;
2594  }
2595  $incl[] = $inclhash;
2596  $name = $attachment[2];
2597  $encoding = $attachment[3];
2598  $type = $attachment[4];
2599  $disposition = $attachment[6];
2600  $cid = $attachment[7];
2601  if ($disposition == 'inline' && array_key_exists($cid, $cidUniq)) {
2602  continue;
2603  }
2604  $cidUniq[$cid] = true;
2605 
2606  $mime[] = sprintf('--%s%s', $boundary, $this->LE);
2607  //Only include a filename property if we have one
2608  if (!empty($name)) {
2609  $mime[] = sprintf(
2610  'Content-Type: %s; name="%s"%s',
2611  $type,
2612  $this->encodeHeader($this->secureHeader($name)),
2613  $this->LE
2614  );
2615  } else {
2616  $mime[] = sprintf(
2617  'Content-Type: %s%s',
2618  $type,
2619  $this->LE
2620  );
2621  }
2622  // RFC1341 part 5 says 7bit is assumed if not specified
2623  if ($encoding != '7bit') {
2624  $mime[] = sprintf('Content-Transfer-Encoding: %s%s', $encoding, $this->LE);
2625  }
2626 
2627  if ($disposition == 'inline') {
2628  $mime[] = sprintf('Content-ID: <%s>%s', $cid, $this->LE);
2629  }
2630 
2631  // If a filename contains any of these chars, it should be quoted,
2632  // but not otherwise: RFC2183 & RFC2045 5.1
2633  // Fixes a warning in IETF's msglint MIME checker
2634  // Allow for bypassing the Content-Disposition header totally
2635  if (!(empty($disposition))) {
2636  $encoded_name = $this->encodeHeader($this->secureHeader($name));
2637  if (preg_match('/[ \(\)<>@,;:\\"\/\[\]\?=]/', $encoded_name)) {
2638  $mime[] = sprintf(
2639  'Content-Disposition: %s; filename="%s"%s',
2640  $disposition,
2641  $encoded_name,
2642  $this->LE . $this->LE
2643  );
2644  } else {
2645  if (!empty($encoded_name)) {
2646  $mime[] = sprintf(
2647  'Content-Disposition: %s; filename=%s%s',
2648  $disposition,
2649  $encoded_name,
2650  $this->LE . $this->LE
2651  );
2652  } else {
2653  $mime[] = sprintf(
2654  'Content-Disposition: %s%s',
2655  $disposition,
2656  $this->LE . $this->LE
2657  );
2658  }
2659  }
2660  } else {
2661  $mime[] = $this->LE;
2662  }
2663 
2664  // Encode as string attachment
2665  if ($bString) {
2666  $mime[] = $this->encodeString($string, $encoding);
2667  if ($this->isError()) {
2668  return '';
2669  }
2670  $mime[] = $this->LE . $this->LE;
2671  } else {
2672  $mime[] = $this->encodeFile($path, $encoding);
2673  if ($this->isError()) {
2674  return '';
2675  }
2676  $mime[] = $this->LE . $this->LE;
2677  }
2678  }
2679  }
2680 
2681  $mime[] = sprintf('--%s--%s', $boundary, $this->LE);
2682 
2683  return implode('', $mime);
2684  }
2685 
2695  protected function encodeFile($path, $encoding = 'base64')
2696  {
2697  try {
2698  if (!is_readable($path)) {
2699  throw new phpmailerException($this->lang('file_open') . $path, self::STOP_CONTINUE);
2700  }
2701  $magic_quotes = get_magic_quotes_runtime();
2702  if ($magic_quotes) {
2703  if (version_compare(PHP_VERSION, '5.3.0', '<')) {
2704  set_magic_quotes_runtime(false);
2705  } else {
2706  //Doesn't exist in PHP 5.4, but we don't need to check because
2707  //get_magic_quotes_runtime always returns false in 5.4+
2708  //so it will never get here
2709  ini_set('magic_quotes_runtime', false);
2710  }
2711  }
2712  $file_buffer = file_get_contents($path);
2713  $file_buffer = $this->encodeString($file_buffer, $encoding);
2714  if ($magic_quotes) {
2715  if (version_compare(PHP_VERSION, '5.3.0', '<')) {
2716  set_magic_quotes_runtime($magic_quotes);
2717  } else {
2718  ini_set('magic_quotes_runtime', $magic_quotes);
2719  }
2720  }
2721  return $file_buffer;
2722  } catch (Exception $exc) {
2723  $this->setError($exc->getMessage());
2724  return '';
2725  }
2726  }
2727 
2736  public function encodeString($str, $encoding = 'base64')
2737  {
2738  $encoded = '';
2739  switch (strtolower($encoding)) {
2740  case 'base64':
2741  $encoded = chunk_split(base64_encode($str), 76, $this->LE);
2742  break;
2743  case '7bit':
2744  case '8bit':
2745  $encoded = $this->fixEOL($str);
2746  // Make sure it ends with a line break
2747  if (substr($encoded, -(strlen($this->LE))) != $this->LE) {
2748  $encoded .= $this->LE;
2749  }
2750  break;
2751  case 'binary':
2752  $encoded = $str;
2753  break;
2754  case 'quoted-printable':
2755  $encoded = $this->encodeQP($str);
2756  break;
2757  default:
2758  $this->setError($this->lang('encoding') . $encoding);
2759  break;
2760  }
2761  return $encoded;
2762  }
2763 
2772  public function encodeHeader($str, $position = 'text')
2773  {
2774  $matchcount = 0;
2775  switch (strtolower($position)) {
2776  case 'phrase':
2777  if (!preg_match('/[\200-\377]/', $str)) {
2778  // Can't use addslashes as we don't know the value of magic_quotes_sybase
2779  $encoded = addcslashes($str, "\0..\37\177\\\"");
2780  if (($str == $encoded) && !preg_match('/[^A-Za-z0-9!#$%&\'*+\/=?^_`{|}~ -]/', $str)) {
2781  return ($encoded);
2782  } else {
2783  return ("\"$encoded\"");
2784  }
2785  }
2786  $matchcount = preg_match_all('/[^\040\041\043-\133\135-\176]/', $str, $matches);
2787  break;
2789  case 'comment':
2790  $matchcount = preg_match_all('/[()"]/', $str, $matches);
2791  // Intentional fall-through
2792  case 'text':
2793  default:
2794  $matchcount += preg_match_all('/[\000-\010\013\014\016-\037\177-\377]/', $str, $matches);
2795  break;
2796  }
2797 
2798  //There are no chars that need encoding
2799  if ($matchcount == 0) {
2800  return ($str);
2801  }
2802 
2803  $maxlen = 75 - 7 - strlen($this->CharSet);
2804  // Try to select the encoding which should produce the shortest output
2805  if ($matchcount > strlen($str) / 3) {
2806  // More than a third of the content will need encoding, so B encoding will be most efficient
2807  $encoding = 'B';
2808  if (function_exists('mb_strlen') && $this->hasMultiBytes($str)) {
2809  // Use a custom function which correctly encodes and wraps long
2810  // multibyte strings without breaking lines within a character
2811  $encoded = $this->base64EncodeWrapMB($str, "\n");
2812  } else {
2813  $encoded = base64_encode($str);
2814  $maxlen -= $maxlen % 4;
2815  $encoded = trim(chunk_split($encoded, $maxlen, "\n"));
2816  }
2817  } else {
2818  $encoding = 'Q';
2819  $encoded = $this->encodeQ($str, $position);
2820  $encoded = $this->wrapText($encoded, $maxlen, true);
2821  $encoded = str_replace('=' . self::CRLF, "\n", trim($encoded));
2822  }
2823 
2824  $encoded = preg_replace('/^(.*)$/m', ' =?' . $this->CharSet . "?$encoding?\\1?=", $encoded);
2825  $encoded = trim(str_replace("\n", $this->LE, $encoded));
2826 
2827  return $encoded;
2828  }
2829 
2836  public function hasMultiBytes($str)
2837  {
2838  if (function_exists('mb_strlen')) {
2839  return (strlen($str) > mb_strlen($str, $this->CharSet));
2840  } else { // Assume no multibytes (we can't handle without mbstring functions anyway)
2841  return false;
2842  }
2843  }
2844 
2850  public function has8bitChars($text)
2851  {
2852  return (boolean)preg_match('/[\x80-\xFF]/', $text);
2853  }
2854 
2865  public function base64EncodeWrapMB($str, $linebreak = null)
2866  {
2867  $start = '=?' . $this->CharSet . '?B?';
2868  $end = '?=';
2869  $encoded = '';
2870  if ($linebreak === null) {
2871  $linebreak = $this->LE;
2872  }
2873 
2874  $mb_length = mb_strlen($str, $this->CharSet);
2875  // Each line must have length <= 75, including $start and $end
2876  $length = 75 - strlen($start) - strlen($end);
2877  // Average multi-byte ratio
2878  $ratio = $mb_length / strlen($str);
2879  // Base64 has a 4:3 ratio
2880  $avgLength = floor($length * $ratio * .75);
2881 
2882  for ($i = 0; $i < $mb_length; $i += $offset) {
2883  $lookBack = 0;
2884  do {
2885  $offset = $avgLength - $lookBack;
2886  $chunk = mb_substr($str, $i, $offset, $this->CharSet);
2887  $chunk = base64_encode($chunk);
2888  $lookBack++;
2889  } while (strlen($chunk) > $length);
2890  $encoded .= $chunk . $linebreak;
2891  }
2892 
2893  // Chomp the last linefeed
2894  $encoded = substr($encoded, 0, -strlen($linebreak));
2895  return $encoded;
2896  }
2897 
2907  public function encodeQP($string, $line_max = 76)
2908  {
2909  // Use native function if it's available (>= PHP5.3)
2910  if (function_exists('quoted_printable_encode')) {
2911  return quoted_printable_encode($string);
2912  }
2913  // Fall back to a pure PHP implementation
2914  $string = str_replace(
2915  array('%20', '%0D%0A.', '%0D%0A', '%'),
2916  array(' ', "\r\n=2E", "\r\n", '='),
2917  rawurlencode($string)
2918  );
2919  return preg_replace('/[^\r\n]{' . ($line_max - 3) . '}[^=\r\n]{2}/', "$0=\r\n", $string);
2920  }
2921 
2932  public function encodeQPphp(
2933  $string,
2934  $line_max = 76, $space_conv = false
2936  ) {
2937  return $this->encodeQP($string, $line_max);
2938  }
2939 
2948  public function encodeQ($str, $position = 'text')
2949  {
2950  // There should not be any EOL in the string
2951  $pattern = '';
2952  $encoded = str_replace(array("\r", "\n"), '', $str);
2953  switch (strtolower($position)) {
2954  case 'phrase':
2955  // RFC 2047 section 5.3
2956  $pattern = '^A-Za-z0-9!*+\/ -';
2957  break;
2959  case 'comment':
2960  // RFC 2047 section 5.2
2961  $pattern = '\(\)"';
2962  // intentional fall-through
2963  // for this reason we build the $pattern without including delimiters and []
2964  case 'text':
2965  default:
2966  // RFC 2047 section 5.1
2967  // Replace every high ascii, control, =, ? and _ characters
2968  $pattern = '\000-\011\013\014\016-\037\075\077\137\177-\377' . $pattern;
2969  break;
2970  }
2971  $matches = array();
2972  if (preg_match_all("/[{$pattern}]/", $encoded, $matches)) {
2973  // If the string contains an '=', make sure it's the first thing we replace
2974  // so as to avoid double-encoding
2975  $eqkey = array_search('=', $matches[0]);
2976  if (false !== $eqkey) {
2977  unset($matches[0][$eqkey]);
2978  array_unshift($matches[0], '=');
2979  }
2980  foreach (array_unique($matches[0]) as $char) {
2981  $encoded = str_replace($char, '=' . sprintf('%02X', ord($char)), $encoded);
2982  }
2983  }
2984  // Replace every spaces to _ (more readable than =20)
2985  return str_replace(' ', '_', $encoded);
2986  }
2987 
2999  public function addStringAttachment(
3000  $string,
3001  $filename,
3002  $encoding = 'base64',
3003  $type = '',
3004  $disposition = 'attachment'
3005  ) {
3006  // If a MIME type is not specified, try to work it out from the file name
3007  if ($type == '') {
3008  $type = self::filenameToType($filename);
3009  }
3010  // Append to $attachment array
3011  $this->attachment[] = array(
3012  0 => $string,
3013  1 => $filename,
3014  2 => basename($filename),
3015  3 => $encoding,
3016  4 => $type,
3017  5 => true, // isStringAttachment
3018  6 => $disposition,
3019  7 => 0
3020  );
3021  }
3022 
3040  public function addEmbeddedImage($path, $cid, $name = '', $encoding = 'base64', $type = '', $disposition = 'inline')
3041  {
3042  if (!@is_file($path)) {
3043  $this->setError($this->lang('file_access') . $path);
3044  return false;
3045  }
3046 
3047  // If a MIME type is not specified, try to work it out from the file name
3048  if ($type == '') {
3049  $type = self::filenameToType($path);
3050  }
3051 
3052  $filename = basename($path);
3053  if ($name == '') {
3054  $name = $filename;
3055  }
3056 
3057  // Append to $attachment array
3058  $this->attachment[] = array(
3059  0 => $path,
3060  1 => $filename,
3061  2 => $name,
3062  3 => $encoding,
3063  4 => $type,
3064  5 => false, // isStringAttachment
3065  6 => $disposition,
3066  7 => $cid
3067  );
3068  return true;
3069  }
3070 
3085  public function addStringEmbeddedImage(
3086  $string,
3087  $cid,
3088  $name = '',
3089  $encoding = 'base64',
3090  $type = '',
3091  $disposition = 'inline'
3092  ) {
3093  // If a MIME type is not specified, try to work it out from the name
3094  if ($type == '' and !empty($name)) {
3095  $type = self::filenameToType($name);
3096  }
3097 
3098  // Append to $attachment array
3099  $this->attachment[] = array(
3100  0 => $string,
3101  1 => $name,
3102  2 => $name,
3103  3 => $encoding,
3104  4 => $type,
3105  5 => true, // isStringAttachment
3106  6 => $disposition,
3107  7 => $cid
3108  );
3109  return true;
3110  }
3111 
3117  public function inlineImageExists()
3118  {
3119  foreach ($this->attachment as $attachment) {
3120  if ($attachment[6] == 'inline') {
3121  return true;
3122  }
3123  }
3124  return false;
3125  }
3126 
3131  public function attachmentExists()
3132  {
3133  foreach ($this->attachment as $attachment) {
3134  if ($attachment[6] == 'attachment') {
3135  return true;
3136  }
3137  }
3138  return false;
3139  }
3140 
3145  public function alternativeExists()
3146  {
3147  return !empty($this->AltBody);
3148  }
3149 
3156  public function clearQueuedAddresses($kind)
3157  {
3159  foreach ($RecipientsQueue as $address => $params) {
3160  if ($params[0] == $kind) {
3161  unset($this->RecipientsQueue[$address]);
3162  }
3163  }
3164  }
3165 
3170  public function clearAddresses()
3171  {
3172  foreach ($this->to as $to) {
3173  unset($this->all_recipients[strtolower($to[0])]);
3174  }
3175  $this->to = array();
3176  $this->clearQueuedAddresses('to');
3177  }
3178 
3183  public function clearCCs()
3184  {
3185  foreach ($this->cc as $cc) {
3186  unset($this->all_recipients[strtolower($cc[0])]);
3187  }
3188  $this->cc = array();
3189  $this->clearQueuedAddresses('cc');
3190  }
3191 
3196  public function clearBCCs()
3197  {
3198  foreach ($this->bcc as $bcc) {
3199  unset($this->all_recipients[strtolower($bcc[0])]);
3200  }
3201  $this->bcc = array();
3202  $this->clearQueuedAddresses('bcc');
3203  }
3204 
3209  public function clearReplyTos()
3210  {
3211  $this->ReplyTo = array();
3212  $this->ReplyToQueue = array();
3213  }
3214 
3219  public function clearAllRecipients()
3220  {
3221  $this->to = array();
3222  $this->cc = array();
3223  $this->bcc = array();
3224  $this->all_recipients = array();
3225  $this->RecipientsQueue = array();
3226  }
3227 
3232  public function clearAttachments()
3233  {
3234  $this->attachment = array();
3235  }
3236 
3241  public function clearCustomHeaders()
3242  {
3243  $this->CustomHeader = array();
3244  }
3245 
3252  protected function setError($msg)
3253  {
3254  $this->error_count++;
3255  if ($this->Mailer == 'smtp' and !is_null($this->smtp)) {
3256  $lasterror = $this->smtp->getError();
3257  if (!empty($lasterror['error'])) {
3258  $msg .= $this->lang('smtp_error') . $lasterror['error'];
3259  if (!empty($lasterror['detail'])) {
3260  $msg .= ' Detail: '. $lasterror['detail'];
3261  }
3262  if (!empty($lasterror['smtp_code'])) {
3263  $msg .= ' SMTP code: ' . $lasterror['smtp_code'];
3264  }
3265  if (!empty($lasterror['smtp_code_ex'])) {
3266  $msg .= ' Additional SMTP info: ' . $lasterror['smtp_code_ex'];
3267  }
3268  }
3269  }
3270  $this->ErrorInfo = $msg;
3271  }
3272 
3279  public static function rfcDate()
3280  {
3281  // Set the time zone to whatever the default is to avoid 500 errors
3282  // Will default to UTC if it's not set properly in php.ini
3283  date_default_timezone_set(@date_default_timezone_get());
3284  return date('D, j M Y H:i:s O');
3285  }
3286 
3293  protected function serverHostname()
3294  {
3295  $result = 'localhost.localdomain';
3296  if (!empty($this->Hostname)) {
3298  } elseif (isset($_SERVER) and array_key_exists('SERVER_NAME', $_SERVER) and !empty($_SERVER['SERVER_NAME'])) {
3299  $result = $_SERVER['SERVER_NAME'];
3300  } elseif (function_exists('gethostname') && gethostname() !== false) {
3301  $result = gethostname();
3302  } elseif (php_uname('n') !== false) {
3303  $result = php_uname('n');
3304  }
3305  return $result;
3306  }
3307 
3314  protected function lang($key)
3315  {
3316  if (count($this->language) < 1) {
3317  $this->setLanguage('en'); // set the default language
3318  }
3319 
3320  if (array_key_exists($key, $this->language)) {
3321  if ($key == 'smtp_connect_failed') {
3322  //Include a link to troubleshooting docs on SMTP connection failure
3323  //this is by far the biggest cause of support questions
3324  //but it's usually not PHPMailer's fault.
3325  return $this->language[$key] . ' https://github.com/PHPMailer/PHPMailer/wiki/Troubleshooting';
3326  }
3327  return $this->language[$key];
3328  } else {
3329  //Return the key as a fallback
3330  return $key;
3331  }
3332  }
3333 
3339  public function isError()
3340  {
3341  return ($this->error_count > 0);
3342  }
3343 
3351  public function fixEOL($str)
3352  {
3353  // Normalise to \n
3354  $nstr = str_replace(array("\r\n", "\r"), "\n", $str);
3355  // Now convert LE as needed
3356  if ($this->LE !== "\n") {
3357  $nstr = str_replace("\n", $this->LE, $nstr);
3358  }
3359  return $nstr;
3360  }
3361 
3371  public function addCustomHeader($name, $value = null)
3372  {
3373  if ($value === null) {
3374  // Value passed in as name:value
3375  $this->CustomHeader[] = explode(':', $name, 2);
3376  } else {
3377  $this->CustomHeader[] = array($name, $value);
3378  }
3379  }
3380 
3385  public function getCustomHeaders()
3386  {
3387  return $this->CustomHeader;
3388  }
3389 
3406  public function msgHTML($message, $basedir = '', $advanced = false)
3407  {
3408  preg_match_all('/(src|background)=["\'](.*)["\']/Ui', $message, $images);
3409  if (array_key_exists(2, $images)) {
3410  if (strlen($basedir) > 1 && substr($basedir, -1) != '/') {
3411  // Ensure $basedir has a trailing /
3412  $basedir .= '/';
3413  }
3414  foreach ($images[2] as $imgindex => $url) {
3415  // Convert data URIs into embedded images
3416  if (preg_match('#^data:(image[^;,]*)(;base64)?,#', $url, $match)) {
3417  $data = substr($url, strpos($url, ','));
3418  if ($match[2]) {
3419  $data = base64_decode($data);
3420  } else {
3421  $data = rawurldecode($data);
3422  }
3423  $cid = md5($url) . '@phpmailer.0'; // RFC2392 S 2
3424  if ($this->addStringEmbeddedImage($data, $cid, 'embed' . $imgindex, 'base64', $match[1])) {
3425  $message = str_replace(
3426  $images[0][$imgindex],
3427  $images[1][$imgindex] . '="cid:' . $cid . '"',
3428  $message
3429  );
3430  }
3431  continue;
3432  }
3433  if (
3434  // Only process relative URLs if a basedir is provided (i.e. no absolute local paths)
3435  !empty($basedir)
3436  // Ignore URLs containing parent dir traversal (..)
3437  && (strpos($url, '..') === false)
3438  // Do not change urls that are already inline images
3439  && substr($url, 0, 4) !== 'cid:'
3440  // Do not change absolute URLs, including anonymous protocol
3441  && !preg_match('#^[a-z][a-z0-9+.-]*:?//#i', $url)
3442  ) {
3443  $filename = basename($url);
3444  $directory = dirname($url);
3445  if ($directory == '.') {
3446  $directory = '';
3447  }
3448  $cid = md5($url) . '@phpmailer.0'; // RFC2392 S 2
3449  if (strlen($directory) > 1 && substr($directory, -1) != '/') {
3450  $directory .= '/';
3451  }
3452  if ($this->addEmbeddedImage(
3453  $basedir . $directory . $filename,
3454  $cid,
3455  $filename,
3456  'base64',
3457  self::_mime_types((string)self::mb_pathinfo($filename, PATHINFO_EXTENSION))
3458  )
3459  ) {
3460  $message = preg_replace(
3461  '/' . $images[1][$imgindex] . '=["\']' . preg_quote($url, '/') . '["\']/Ui',
3462  $images[1][$imgindex] . '="cid:' . $cid . '"',
3463  $message
3464  );
3465  }
3466  }
3467  }
3468  }
3469  $this->isHTML(true);
3470  // Convert all message body line breaks to CRLF, makes quoted-printable encoding work much better
3471  $this->Body = $this->normalizeBreaks($message);
3472  $this->AltBody = $this->normalizeBreaks($this->html2text($message, $advanced));
3473  if (!$this->alternativeExists()) {
3474  $this->AltBody = 'To view this email message, open it in a program that understands HTML!' .
3475  self::CRLF . self::CRLF;
3476  }
3477  return $this->Body;
3478  }
3479 
3500  public function html2text($html, $advanced = false)
3501  {
3502  if (is_callable($advanced)) {
3503  return call_user_func($advanced, $html);
3504  }
3505  return html_entity_decode(
3506  trim(strip_tags(preg_replace('/<(head|title|style|script)[^>]*>.*?<\/\\1>/si', '', $html))),
3507  ENT_QUOTES,
3508  $this->CharSet
3509  );
3510  }
3511 
3519  public static function _mime_types($ext = '')
3520  {
3521  $mimes = array(
3522  'xl' => 'application/excel',
3523  'js' => 'application/javascript',
3524  'hqx' => 'application/mac-binhex40',
3525  'cpt' => 'application/mac-compactpro',
3526  'bin' => 'application/macbinary',
3527  'doc' => 'application/msword',
3528  'word' => 'application/msword',
3529  'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
3530  'xltx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.template',
3531  'potx' => 'application/vnd.openxmlformats-officedocument.presentationml.template',
3532  'ppsx' => 'application/vnd.openxmlformats-officedocument.presentationml.slideshow',
3533  'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
3534  'sldx' => 'application/vnd.openxmlformats-officedocument.presentationml.slide',
3535  'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
3536  'dotx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.template',
3537  'xlam' => 'application/vnd.ms-excel.addin.macroEnabled.12',
3538  'xlsb' => 'application/vnd.ms-excel.sheet.binary.macroEnabled.12',
3539  'class' => 'application/octet-stream',
3540  'dll' => 'application/octet-stream',
3541  'dms' => 'application/octet-stream',
3542  'exe' => 'application/octet-stream',
3543  'lha' => 'application/octet-stream',
3544  'lzh' => 'application/octet-stream',
3545  'psd' => 'application/octet-stream',
3546  'sea' => 'application/octet-stream',
3547  'so' => 'application/octet-stream',
3548  'oda' => 'application/oda',
3549  'pdf' => 'application/pdf',
3550  'ai' => 'application/postscript',
3551  'eps' => 'application/postscript',
3552  'ps' => 'application/postscript',
3553  'smi' => 'application/smil',
3554  'smil' => 'application/smil',
3555  'mif' => 'application/vnd.mif',
3556  'xls' => 'application/vnd.ms-excel',
3557  'ppt' => 'application/vnd.ms-powerpoint',
3558  'wbxml' => 'application/vnd.wap.wbxml',
3559  'wmlc' => 'application/vnd.wap.wmlc',
3560  'dcr' => 'application/x-director',
3561  'dir' => 'application/x-director',
3562  'dxr' => 'application/x-director',
3563  'dvi' => 'application/x-dvi',
3564  'gtar' => 'application/x-gtar',
3565  'php3' => 'application/x-httpd-php',
3566  'php4' => 'application/x-httpd-php',
3567  'php' => 'application/x-httpd-php',
3568  'phtml' => 'application/x-httpd-php',
3569  'phps' => 'application/x-httpd-php-source',
3570  'swf' => 'application/x-shockwave-flash',
3571  'sit' => 'application/x-stuffit',
3572  'tar' => 'application/x-tar',
3573  'tgz' => 'application/x-tar',
3574  'xht' => 'application/xhtml+xml',
3575  'xhtml' => 'application/xhtml+xml',
3576  'zip' => 'application/zip',
3577  'mid' => 'audio/midi',
3578  'midi' => 'audio/midi',
3579  'mp2' => 'audio/mpeg',
3580  'mp3' => 'audio/mpeg',
3581  'mpga' => 'audio/mpeg',
3582  'aif' => 'audio/x-aiff',
3583  'aifc' => 'audio/x-aiff',
3584  'aiff' => 'audio/x-aiff',
3585  'ram' => 'audio/x-pn-realaudio',
3586  'rm' => 'audio/x-pn-realaudio',
3587  'rpm' => 'audio/x-pn-realaudio-plugin',
3588  'ra' => 'audio/x-realaudio',
3589  'wav' => 'audio/x-wav',
3590  'bmp' => 'image/bmp',
3591  'gif' => 'image/gif',
3592  'jpeg' => 'image/jpeg',
3593  'jpe' => 'image/jpeg',
3594  'jpg' => 'image/jpeg',
3595  'png' => 'image/png',
3596  'tiff' => 'image/tiff',
3597  'tif' => 'image/tiff',
3598  'eml' => 'message/rfc822',
3599  'css' => 'text/css',
3600  'html' => 'text/html',
3601  'htm' => 'text/html',
3602  'shtml' => 'text/html',
3603  'log' => 'text/plain',
3604  'text' => 'text/plain',
3605  'txt' => 'text/plain',
3606  'rtx' => 'text/richtext',
3607  'rtf' => 'text/rtf',
3608  'vcf' => 'text/vcard',
3609  'vcard' => 'text/vcard',
3610  'xml' => 'text/xml',
3611  'xsl' => 'text/xml',
3612  'mpeg' => 'video/mpeg',
3613  'mpe' => 'video/mpeg',
3614  'mpg' => 'video/mpeg',
3615  'mov' => 'video/quicktime',
3616  'qt' => 'video/quicktime',
3617  'rv' => 'video/vnd.rn-realvideo',
3618  'avi' => 'video/x-msvideo',
3619  'movie' => 'video/x-sgi-movie'
3620  );
3621  if (array_key_exists(strtolower($ext), $mimes)) {
3622  return $mimes[strtolower($ext)];
3623  }
3624  return 'application/octet-stream';
3625  }
3626 
3634  public static function filenameToType($filename)
3635  {
3636  // In case the path is a URL, strip any query string before getting extension
3637  $qpos = strpos($filename, '?');
3638  if (false !== $qpos) {
3639  $filename = substr($filename, 0, $qpos);
3640  }
3641  $pathinfo = self::mb_pathinfo($filename);
3642  return self::_mime_types($pathinfo['extension']);
3643  }
3644 
3656  public static function mb_pathinfo($path, $options = null)
3657  {
3658  $ret = array('dirname' => '', 'basename' => '', 'extension' => '', 'filename' => '');
3659  $pathinfo = array();
3660  if (preg_match('%^(.*?)[\\\\/]*(([^/\\\\]*?)(\.([^\.\\\\/]+?)|))[\\\\/\.]*$%im', $path, $pathinfo)) {
3661  if (array_key_exists(1, $pathinfo)) {
3662  $ret['dirname'] = $pathinfo[1];
3663  }
3664  if (array_key_exists(2, $pathinfo)) {
3665  $ret['basename'] = $pathinfo[2];
3666  }
3667  if (array_key_exists(5, $pathinfo)) {
3668  $ret['extension'] = $pathinfo[5];
3669  }
3670  if (array_key_exists(3, $pathinfo)) {
3671  $ret['filename'] = $pathinfo[3];
3672  }
3673  }
3674  switch ($options) {
3675  case PATHINFO_DIRNAME:
3676  case 'dirname':
3677  return $ret['dirname'];
3678  case PATHINFO_BASENAME:
3679  case 'basename':
3680  return $ret['basename'];
3681  case PATHINFO_EXTENSION:
3682  case 'extension':
3683  return $ret['extension'];
3684  case PATHINFO_FILENAME:
3685  case 'filename':
3686  return $ret['filename'];
3687  default:
3688  return $ret;
3689  }
3690  }
3691 
3706  public function set($name, $value = '')
3707  {
3708  if (property_exists($this, $name)) {
3709  $this->$name = $value;
3710  return true;
3711  } else {
3712  $this->setError($this->lang('variable_set') . $name);
3713  return false;
3714  }
3715  }
3716 
3723  public function secureHeader($str)
3724  {
3725  return trim(str_replace(array("\r", "\n"), '', $str));
3726  }
3727 
3738  public static function normalizeBreaks($text, $breaktype = "\r\n")
3739  {
3740  return preg_replace('/(\r\n|\r|\n)/ms', $breaktype, $text);
3741  }
3742 
3751  public function sign($cert_filename, $key_filename, $key_pass, $extracerts_filename = '')
3752  {
3753  $this->sign_cert_file = $cert_filename;
3754  $this->sign_key_file = $key_filename;
3755  $this->sign_key_pass = $key_pass;
3756  $this->sign_extracerts_file = $extracerts_filename;
3757  }
3758 
3765  public function DKIM_QP($txt)
3766  {
3767  $line = '';
3768  for ($i = 0; $i < strlen($txt); $i++) {
3769  $ord = ord($txt[$i]);
3770  if (((0x21 <= $ord) && ($ord <= 0x3A)) || $ord == 0x3C || ((0x3E <= $ord) && ($ord <= 0x7E))) {
3771  $line .= $txt[$i];
3772  } else {
3773  $line .= '=' . sprintf('%02X', $ord);
3774  }
3775  }
3776  return $line;
3777  }
3778 
3786  public function DKIM_Sign($signHeader)
3787  {
3788  if (!defined('PKCS7_TEXT')) {
3789  if ($this->exceptions) {
3790  throw new phpmailerException($this->lang('extension_missing') . 'openssl');
3791  }
3792  return '';
3793  }
3794  $privKeyStr = !empty($this->DKIM_private_string) ? $this->DKIM_private_string : file_get_contents($this->DKIM_private);
3795  if ('' != $this->DKIM_passphrase) {
3796  $privKey = openssl_pkey_get_private($privKeyStr, $this->DKIM_passphrase);
3797  } else {
3798  $privKey = openssl_pkey_get_private($privKeyStr);
3799  }
3800  //Workaround for missing digest algorithms in old PHP & OpenSSL versions
3801  //@link http://stackoverflow.com/a/11117338/333340
3802  if (version_compare(PHP_VERSION, '5.3.0') >= 0 and
3803  in_array('sha256WithRSAEncryption', openssl_get_md_methods(true))) {
3804  if (openssl_sign($signHeader, $signature, $privKey, 'sha256WithRSAEncryption')) {
3805  openssl_pkey_free($privKey);
3806  return base64_encode($signature);
3807  }
3808  } else {
3809  $pinfo = openssl_pkey_get_details($privKey);
3810  $hash = hash('sha256', $signHeader);
3811  //'Magic' constant for SHA256 from RFC3447
3812  //@link https://tools.ietf.org/html/rfc3447#page-43
3813  $t = '3031300d060960864801650304020105000420' . $hash;
3814  $pslen = $pinfo['bits'] / 8 - (strlen($t) / 2 + 3);
3815  $eb = pack('H*', '0001' . str_repeat('FF', $pslen) . '00' . $t);
3816 
3817  if (openssl_private_encrypt($eb, $signature, $privKey, OPENSSL_NO_PADDING)) {
3818  openssl_pkey_free($privKey);
3819  return base64_encode($signature);
3820  }
3821  }
3822  openssl_pkey_free($privKey);
3823  return '';
3824  }
3825 
3832  public function DKIM_HeaderC($signHeader)
3833  {
3834  $signHeader = preg_replace('/\r\n\s+/', ' ', $signHeader);
3835  $lines = explode("\r\n", $signHeader);
3836  foreach ($lines as $key => $line) {
3837  list($heading, $value) = explode(':', $line, 2);
3838  $heading = strtolower($heading);
3839  $value = preg_replace('/\s{2,}/', ' ', $value); // Compress useless spaces
3840  $lines[$key] = $heading . ':' . trim($value); // Don't forget to remove WSP around the value
3841  }
3842  $signHeader = implode("\r\n", $lines);
3843  return $signHeader;
3844  }
3845 
3852  public function DKIM_BodyC($body)
3853  {
3854  if ($body == '') {
3855  return "\r\n";
3856  }
3857  // stabilize line endings
3858  $body = str_replace("\r\n", "\n", $body);
3859  $body = str_replace("\n", "\r\n", $body);
3860  // END stabilize line endings
3861  while (substr($body, strlen($body) - 4, 4) == "\r\n\r\n") {
3862  $body = substr($body, 0, strlen($body) - 2);
3863  }
3864  return $body;
3865  }
3866 
3875  public function DKIM_Add($headers_line, $subject, $body)
3876  {
3877  $DKIMsignatureType = 'rsa-sha256'; // Signature & hash algorithms
3878  $DKIMcanonicalization = 'relaxed/simple'; // Canonicalization of header/body
3879  $DKIMquery = 'dns/txt'; // Query method
3880  $DKIMtime = time(); // Signature Timestamp = seconds since 00:00:00 - Jan 1, 1970 (UTC time zone)
3881  $subject_header = "Subject: $subject";
3882  $headers = explode($this->LE, $headers_line);
3883  $from_header = '';
3884  $to_header = '';
3885  $date_header = '';
3886  $current = '';
3887  foreach ($headers as $header) {
3888  if (strpos($header, 'From:') === 0) {
3889  $from_header = $header;
3890  $current = 'from_header';
3891  } elseif (strpos($header, 'To:') === 0) {
3892  $to_header = $header;
3893  $current = 'to_header';
3894  } elseif (strpos($header, 'Date:') === 0) {
3895  $date_header = $header;
3896  $current = 'date_header';
3897  } else {
3898  if (!empty($$current) && strpos($header, ' =?') === 0) {
3899  $$current .= $header;
3900  } else {
3901  $current = '';
3902  }
3903  }
3904  }
3905  $from = str_replace('|', '=7C', $this->DKIM_QP($from_header));
3906  $to = str_replace('|', '=7C', $this->DKIM_QP($to_header));
3907  $date = str_replace('|', '=7C', $this->DKIM_QP($date_header));
3908  $subject = str_replace(
3909  '|',
3910  '=7C',
3911  $this->DKIM_QP($subject_header)
3912  ); // Copied header fields (dkim-quoted-printable)
3913  $body = $this->DKIM_BodyC($body);
3914  $DKIMlen = strlen($body); // Length of body
3915  $DKIMb64 = base64_encode(pack('H*', hash('sha256', $body))); // Base64 of packed binary SHA-256 hash of body
3916  if ('' == $this->DKIM_identity) {
3917  $ident = '';
3918  } else {
3919  $ident = ' i=' . $this->DKIM_identity . ';';
3920  }
3921  $dkimhdrs = 'DKIM-Signature: v=1; a=' .
3922  $DKIMsignatureType . '; q=' .
3923  $DKIMquery . '; l=' .
3924  $DKIMlen . '; s=' .
3925  $this->DKIM_selector .
3926  ";\r\n" .
3927  "\tt=" . $DKIMtime . '; c=' . $DKIMcanonicalization . ";\r\n" .
3928  "\th=From:To:Date:Subject;\r\n" .
3929  "\td=" . $this->DKIM_domain . ';' . $ident . "\r\n" .
3930  "\tz=$from\r\n" .
3931  "\t|$to\r\n" .
3932  "\t|$date\r\n" .
3933  "\t|$subject;\r\n" .
3934  "\tbh=" . $DKIMb64 . ";\r\n" .
3935  "\tb=";
3936  $toSign = $this->DKIM_HeaderC(
3937  $from_header . "\r\n" .
3938  $to_header . "\r\n" .
3939  $date_header . "\r\n" .
3940  $subject_header . "\r\n" .
3941  $dkimhdrs
3942  );
3943  $signed = $this->DKIM_Sign($toSign);
3944  return $dkimhdrs . $signed . "\r\n";
3945  }
3946 
3953  public static function hasLineLongerThanMax($str)
3954  {
3955  //+2 to include CRLF line break for a 1000 total
3956  return (boolean)preg_match('/^(.{'.(self::MAX_LINE_LENGTH + 2).',})/m', $str);
3957  }
3958 
3965  public function getToAddresses()
3966  {
3967  return $this->to;
3968  }
3969 
3976  public function getCcAddresses()
3977  {
3978  return $this->cc;
3979  }
3980 
3987  public function getBccAddresses()
3988  {
3989  return $this->bcc;
3990  }
3991 
3998  public function getReplyToAddresses()
3999  {
4000  return $this->ReplyTo;
4001  }
4002 
4009  public function getAllRecipientAddresses()
4010  {
4011  return $this->all_recipients;
4012  }
4013 
4024  protected function doCallback($isSent, $to, $cc, $bcc, $subject, $body, $from)
4025  {
4026  if (!empty($this->action_function) && is_callable($this->action_function)) {
4027  $params = array($isSent, $to, $cc, $bcc, $subject, $body, $from);
4028  call_user_func_array($this->action_function, $params);
4029  }
4030  }
4031 }
4032 
4038 {
4043  public function errorMessage()
4044  {
4045  $errorMsg = '<strong>' . htmlspecialchars($this->getMessage()) . "</strong><br />\n";
4046  return $errorMsg;
4047  }
4048 }
File written to
addAnAddress($kind, $address, $name='')
Add an address to one of the recipient arrays or to the ReplyTo array.
$params
Definition: disable.php:11
static $validator
static validateAddress($address, $patternselect=null)
Check that a string looks like an email address.
generateId()
Create unique ID.
setError($msg)
Add an error message to the error container.
if($orgName !==null) if($spconfig->hasValue('contacts')) $email
Definition: metadata.php:193
getSentMIMEMessage()
Returns the whole MIME message.
attachmentExists()
Check if an attachment (non-inline) is present.
addCustomHeader($name, $value=null)
Add a custom header.
addOrEnqueueAnAddress($kind, $address, $name)
Add an address to one of the recipient arrays or to the ReplyTo array.
headerLine($name, $value)
Format a header line.
if(isset($_REQUEST['delete'])) $list
Definition: registry.php:41
clearQueuedAddresses($kind)
Clear queued addresses of given kind.
if((!isset($_SERVER['DOCUMENT_ROOT'])) OR(empty($_SERVER['DOCUMENT_ROOT']))) $_SERVER['DOCUMENT_ROOT']
addEmbeddedImage($path, $cid, $name='', $encoding='base64', $type='', $disposition='inline')
Add an embedded (inline) attachment from a file.
getAllRecipientAddresses()
Allows for public read access to &#39;all_recipients&#39; property.
idnSupported()
Tells whether IDNs (Internationalized Domain Names) are supported or not.
postSend()
Actually send a message.
$result
__construct($exceptions=null)
Constructor.
base64EncodeWrapMB($str, $linebreak=null)
Encode and wrap long multibyte strings for mail headers without breaking lines within a character...
$type
createBody()
Assemble the message body.
setMessageType()
Set the message type.
errorMessage()
Prettify error message output.
clearAddresses()
Clear all To recipients.
isSMTP()
Send messages using SMTP.
static rfcDate()
Return an RFC 822 formatted date.
addAddress($address, $name='')
Add a "To" address.
static mb_pathinfo($path, $options=null)
Multi-byte-safe pathinfo replacement.
const STOP_CRITICAL
Error severity: message, plus full stop, critical error reached.
setLanguage($langcode='en', $lang_path='')
Set the language for error messages.
encodeQ($str, $position='text')
Encode a string using Q encoding.
static filenameToType($filename)
Map a file name to a MIME type.
$end
Definition: saml1-acs.php:18
addStringAttachment( $string, $filename, $encoding='base64', $type='', $disposition='attachment')
Add a string or binary attachment (non-filesystem).
getTranslations()
Get the array of strings for the current language.
has8bitChars($text)
Does a string contain any 8-bit chars (in any charset)?
smtpClose()
Close the active SMTP session if one exists.
wrapText($message, $length, $qp_mode=false)
Word-wrap message.
msgHTML($message, $basedir='', $advanced=false)
Create a message body from an HTML string.
$from
getCustomHeaders()
Returns all custom headers.
DKIM_Sign($signHeader)
Generate a DKIM signature.
const MAX_LINE_LENGTH
addBCC($address, $name='')
Add a "BCC" address.
isHTML($isHtml=true)
Sets message type to HTML or plain.
getReplyToAddresses()
Allows for public read access to &#39;ReplyTo&#39; property.
mail($to, $subject, $message, $additional_headers=null, $additional_parameters=null)
setFrom($address, $name='', $auto=true)
Set the From and FromName properties.
clearCustomHeaders()
Clear all custom headers.
static hasLineLongerThanMax($str)
Detect if a string contains a line longer than the maximum line length allowed.
sign($cert_filename, $key_filename, $key_pass, $extracerts_filename='')
Set the public and private key files and password for S/MIME signing.
addAttachment($path, $name='', $encoding='base64', $type='', $disposition='attachment')
Add an attachment from a path on the filesystem.
doCallback($isSent, $to, $cc, $bcc, $subject, $body, $from)
Perform a callback.
clearBCCs()
Clear all BCC recipients.
PHPMailer - PHP email creation and transport class.
edebug($str)
Output debugging info via user-defined method.
createHeader()
Assemble message headers.
DKIM_BodyC($body)
Generate a DKIM canonicalization body.
$error
Definition: Error.php:17
if($format !==null) $name
Definition: metadata.php:146
parseAddresses($addrstr, $useimap=true)
Parse and validate a string containing one or more RFC822-style comma-separated email addresses of th...
getCcAddresses()
Allows for public read access to &#39;cc&#39; property.
catch(Exception $e) $message
isError()
Check if an error occurred.
attachAll($disposition_type, $boundary)
Attach all file, string, and binary attachments to the message.
__destruct()
Destructor.
utf8CharBoundary($encodedText, $maxLength)
Find the last character boundary prior to $maxLength in a utf-8 quoted-printable encoded string...
smtpSend($header, $body)
Send mail via SMTP.
clearAllRecipients()
Clear all recipient types.
serverHostname()
Get the server hostname.
date( 'd-M-Y', $objPHPExcel->getProperties() ->getCreated())
isMail()
Send messages using PHP&#39;s mail() function.
$text
Definition: errorreport.php:18
const CRLF
SMTP RFC standard line ending.
DKIM_QP($txt)
Quoted-Printable-encode a DKIM header.
send()
Create a message and send it.
preSend()
Prepare a message for sending.
inlineImageExists()
Check if an inline attachment is present.
secureHeader($str)
Strip newlines to prevent header injection.
isQmail()
Send messages using qmail.
endBoundary($boundary)
Return the end of a message boundary.
const STOP_MESSAGE
Error severity: message only, continue processing.
addReplyTo($address, $name='')
Add a "Reply-To" address.
$txt
Definition: error.php:11
Create styles array
The data for the language used.
addrFormat($addr)
Format an address for use in a message header.
encodeFile($path, $encoding='base64')
Encode a file attachment in requested format.
addCC($address, $name='')
Add a "CC" address.
addrAppend($type, $addr)
Create recipient headers.
$PHPMAILER_LANG['authenticate']
getBccAddresses()
Allows for public read access to &#39;bcc&#39; property.
encodeQP($string, $line_max=76)
Encode a string in quoted-printable format.
textLine($value)
Return a formatted mail line.
static normalizeBreaks($text, $breaktype="\")
Normalize line breaks in a string.
mailPassthru($to, $subject, $body, $header, $params)
Call mail() in a safe_mode-aware fashion.
isSendmail()
Send messages using $Sendmail.
hasMultiBytes($str)
Check if a string contains multi-byte characters.
fixEOL($str)
Ensure consistent line endings in a string.
clearCCs()
Clear all CC recipients.
static _mime_types($ext='')
Get the MIME type for a file extension.
encodeString($str, $encoding='base64')
Encode a string in requested format.
getBoundary($boundary, $charSet, $contentType, $encoding)
Return the start of a message boundary.
encodeQPphp( $string, $line_max=76, $space_conv=false)
Backward compatibility wrapper for an old QP encoding function that was removed.
$ret
Definition: parser.php:6
clearReplyTos()
Clear all ReplyTo recipients.
getLastMessageID()
Return the Message-ID header of the last email.
$i
Definition: disco.tpl.php:19
if($path[strlen($path) - 1]==='/') if(is_dir($path)) if(!file_exists($path)) if(preg_match('#\.php$#D', $path)) $contentType
Definition: module.php:142
getSMTPInstance()
Get an instance to use for SMTP operations.
$url
smtpConnect($options=null)
Initiate a connection to an SMTP server.
Add data(end) time
Method that wraps PHPs time in order to allow simulations with the workflow.
if(!file_exists("$old.txt")) if($old===$new) if(file_exists("$new.txt")) $file
clearAttachments()
Clear all filesystem, string, and binary attachments.
html2text($html, $advanced=false)
Convert an HTML string into plain text.
encodeHeader($str, $position='text')
Encode a header string optimally.
const STOP_CONTINUE
Error severity: message, likely ok to continue processing.
if(!array_key_exists('domain', $_REQUEST)) $domain
Definition: resume.php:8
sendmailSend($header, $body)
Send mail using the $Sendmail program.
addStringEmbeddedImage( $string, $cid, $name='', $encoding='base64', $type='', $disposition='inline')
Add an embedded stringified attachment.
defined( 'APPLICATION_ENV')||define( 'APPLICATION_ENV'
Definition: bootstrap.php:27
mailSend($header, $body)
Send mail using the PHP mail() function.
setWordWrap()
Apply word wrapping to the message body.
getToAddresses()
Allows for public read access to &#39;to&#39; property.
DKIM_HeaderC($signHeader)
Generate a DKIM canonicalization header.
DKIM_Add($headers_line, $subject, $body)
Create the DKIM header and body in a new message header.
hash(StreamInterface $stream, $algo, $rawOutput=false)
Calculate a hash of a Stream.
Definition: functions.php:406
$key
Definition: croninfo.php:18
$html
Definition: example_001.php:87
punyencodeAddress($address)
Converts IDN in given email address to its ASCII form, also known as punycode, if possible...
alternativeExists()
Check if this message has an alternative body set.
lang($key)
Get an error message in the current language.
getAttachments()
Return the array of attachments.
if(!isset($_REQUEST['ReturnTo'])) if(!isset($_REQUEST['AuthId'])) $options
Definition: as_login.php:20
static isShellSafe($string)
Fix CVE-2016-10033 and CVE-2016-10045 by disallowing potentially unsafe shell characters.
getMailMIME()
Get the message MIME type headers.