ILIAS  release_5-2 Revision v5.2.25-18-g3f80b828510
class.phpmailer.php
Go to the documentation of this file.
1 <?php
28 class PHPMailer
29 {
34  public $Version = '5.2.22';
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('/^((ssl|tls):\/\/)*([a-zA-Z0-9\.-]*):?([0-9]*)$/', trim($hostentry), $hostinfo)) {
1632  // Not a valid host entry
1633  continue;
1634  }
1635  // $hostinfo[2]: optional ssl or tls prefix
1636  // $hostinfo[3]: the hostname
1637  // $hostinfo[4]: optional port number
1638  // The host string prefix can temporarily override the current setting for SMTPSecure
1639  // If it's not specified, the default value is used
1640  $prefix = '';
1641  $secure = $this->SMTPSecure;
1642  $tls = ($this->SMTPSecure == 'tls');
1643  if ('ssl' == $hostinfo[2] or ('' == $hostinfo[2] and 'ssl' == $this->SMTPSecure)) {
1644  $prefix = 'ssl://';
1645  $tls = false; // Can't have SSL and TLS at the same time
1646  $secure = 'ssl';
1647  } elseif ($hostinfo[2] == 'tls') {
1648  $tls = true;
1649  // tls doesn't use a prefix
1650  $secure = 'tls';
1651  }
1652  //Do we need the OpenSSL extension?
1653  $sslext = defined('OPENSSL_ALGO_SHA1');
1654  if ('tls' === $secure or 'ssl' === $secure) {
1655  //Check for an OpenSSL constant rather than using extension_loaded, which is sometimes disabled
1656  if (!$sslext) {
1657  throw new phpmailerException($this->lang('extension_missing').'openssl', self::STOP_CRITICAL);
1658  }
1659  }
1660  $host = $hostinfo[3];
1661  $port = $this->Port;
1662  $tport = (integer)$hostinfo[4];
1663  if ($tport > 0 and $tport < 65536) {
1664  $port = $tport;
1665  }
1666  if ($this->smtp->connect($prefix . $host, $port, $this->Timeout, $options)) {
1667  try {
1668  if ($this->Helo) {
1669  $hello = $this->Helo;
1670  } else {
1671  $hello = $this->serverHostname();
1672  }
1673  $this->smtp->hello($hello);
1674  //Automatically enable TLS encryption if:
1675  // * it's not disabled
1676  // * we have openssl extension
1677  // * we are not already using SSL
1678  // * the server offers STARTTLS
1679  if ($this->SMTPAutoTLS and $sslext and $secure != 'ssl' and $this->smtp->getServerExt('STARTTLS')) {
1680  $tls = true;
1681  }
1682  if ($tls) {
1683  if (!$this->smtp->startTLS()) {
1684  throw new phpmailerException($this->lang('connect_host'));
1685  }
1686  // We must resend EHLO after TLS negotiation
1687  $this->smtp->hello($hello);
1688  }
1689  if ($this->SMTPAuth) {
1690  if (!$this->smtp->authenticate(
1691  $this->Username,
1692  $this->Password,
1693  $this->AuthType,
1694  $this->Realm,
1695  $this->Workstation
1696  )
1697  ) {
1698  throw new phpmailerException($this->lang('authenticate'));
1699  }
1700  }
1701  return true;
1702  } catch (phpmailerException $exc) {
1703  $lastexception = $exc;
1704  $this->edebug($exc->getMessage());
1705  // We must have connected, but then failed TLS or Auth, so close connection nicely
1706  $this->smtp->quit();
1707  }
1708  }
1709  }
1710  // If we get here, all connection attempts have failed, so close connection hard
1711  $this->smtp->close();
1712  // As we've caught all exceptions, just report whatever the last one was
1713  if ($this->exceptions and !is_null($lastexception)) {
1714  throw $lastexception;
1715  }
1716  return false;
1717  }
1718 
1723  public function smtpClose()
1724  {
1725  if (is_a($this->smtp, 'SMTP')) {
1726  if ($this->smtp->connected()) {
1727  $this->smtp->quit();
1728  $this->smtp->close();
1729  }
1730  }
1731  }
1732 
1742  public function setLanguage($langcode = 'en', $lang_path = '')
1743  {
1744  // Backwards compatibility for renamed language codes
1745  $renamed_langcodes = array(
1746  'br' => 'pt_br',
1747  'cz' => 'cs',
1748  'dk' => 'da',
1749  'no' => 'nb',
1750  'se' => 'sv',
1751  );
1752 
1753  if (isset($renamed_langcodes[$langcode])) {
1754  $langcode = $renamed_langcodes[$langcode];
1755  }
1756 
1757  // Define full set of translatable strings in English
1759  'authenticate' => 'SMTP Error: Could not authenticate.',
1760  'connect_host' => 'SMTP Error: Could not connect to SMTP host.',
1761  'data_not_accepted' => 'SMTP Error: data not accepted.',
1762  'empty_message' => 'Message body empty',
1763  'encoding' => 'Unknown encoding: ',
1764  'execute' => 'Could not execute: ',
1765  'file_access' => 'Could not access file: ',
1766  'file_open' => 'File Error: Could not open file: ',
1767  'from_failed' => 'The following From address failed: ',
1768  'instantiate' => 'Could not instantiate mail function.',
1769  'invalid_address' => 'Invalid address: ',
1770  'mailer_not_supported' => ' mailer is not supported.',
1771  'provide_address' => 'You must provide at least one recipient email address.',
1772  'recipients_failed' => 'SMTP Error: The following recipients failed: ',
1773  'signing' => 'Signing Error: ',
1774  'smtp_connect_failed' => 'SMTP connect() failed.',
1775  'smtp_error' => 'SMTP server error: ',
1776  'variable_set' => 'Cannot set or reset variable: ',
1777  'extension_missing' => 'Extension missing: '
1778  );
1779  if (empty($lang_path)) {
1780  // Calculate an absolute path so it can work if CWD is not here
1781  $lang_path = dirname(__FILE__). DIRECTORY_SEPARATOR . 'language'. DIRECTORY_SEPARATOR;
1782  }
1783  //Validate $langcode
1784  if (!preg_match('/^[a-z]{2}(?:_[a-zA-Z]{2})?$/', $langcode)) {
1785  $langcode = 'en';
1786  }
1787  $foundlang = true;
1788  $lang_file = $lang_path . 'phpmailer.lang-' . $langcode . '.php';
1789  // There is no English translation file
1790  if ($langcode != 'en') {
1791  // Make sure language file path is readable
1792  if (!is_readable($lang_file)) {
1793  $foundlang = false;
1794  } else {
1795  // Overwrite language-specific strings.
1796  // This way we'll never have missing translation keys.
1797  $foundlang = include $lang_file;
1798  }
1799  }
1800  $this->language = $PHPMAILER_LANG;
1801  return (boolean)$foundlang; // Returns false if language not found
1802  }
1803 
1808  public function getTranslations()
1809  {
1810  return $this->language;
1811  }
1812 
1823  public function addrAppend($type, $addr)
1824  {
1825  $addresses = array();
1826  foreach ($addr as $address) {
1827  $addresses[] = $this->addrFormat($address);
1828  }
1829  return $type . ': ' . implode(', ', $addresses) . $this->LE;
1830  }
1831 
1839  public function addrFormat($addr)
1840  {
1841  if (empty($addr[1])) { // No name provided
1842  return $this->secureHeader($addr[0]);
1843  } else {
1844  return $this->encodeHeader($this->secureHeader($addr[1]), 'phrase') . ' <' . $this->secureHeader(
1845  $addr[0]
1846  ) . '>';
1847  }
1848  }
1849 
1861  public function wrapText($message, $length, $qp_mode = false)
1862  {
1863  if ($qp_mode) {
1864  $soft_break = sprintf(' =%s', $this->LE);
1865  } else {
1866  $soft_break = $this->LE;
1867  }
1868  // If utf-8 encoding is used, we will need to make sure we don't
1869  // split multibyte characters when we wrap
1870  $is_utf8 = (strtolower($this->CharSet) == 'utf-8');
1871  $lelen = strlen($this->LE);
1872  $crlflen = strlen(self::CRLF);
1873 
1874  $message = $this->fixEOL($message);
1875  //Remove a trailing line break
1876  if (substr($message, -$lelen) == $this->LE) {
1877  $message = substr($message, 0, -$lelen);
1878  }
1879 
1880  //Split message into lines
1881  $lines = explode($this->LE, $message);
1882  //Message will be rebuilt in here
1883  $message = '';
1884  foreach ($lines as $line) {
1885  $words = explode(' ', $line);
1886  $buf = '';
1887  $firstword = true;
1888  foreach ($words as $word) {
1889  if ($qp_mode and (strlen($word) > $length)) {
1890  $space_left = $length - strlen($buf) - $crlflen;
1891  if (!$firstword) {
1892  if ($space_left > 20) {
1893  $len = $space_left;
1894  if ($is_utf8) {
1895  $len = $this->utf8CharBoundary($word, $len);
1896  } elseif (substr($word, $len - 1, 1) == '=') {
1897  $len--;
1898  } elseif (substr($word, $len - 2, 1) == '=') {
1899  $len -= 2;
1900  }
1901  $part = substr($word, 0, $len);
1902  $word = substr($word, $len);
1903  $buf .= ' ' . $part;
1904  $message .= $buf . sprintf('=%s', self::CRLF);
1905  } else {
1906  $message .= $buf . $soft_break;
1907  }
1908  $buf = '';
1909  }
1910  while (strlen($word) > 0) {
1911  if ($length <= 0) {
1912  break;
1913  }
1914  $len = $length;
1915  if ($is_utf8) {
1916  $len = $this->utf8CharBoundary($word, $len);
1917  } elseif (substr($word, $len - 1, 1) == '=') {
1918  $len--;
1919  } elseif (substr($word, $len - 2, 1) == '=') {
1920  $len -= 2;
1921  }
1922  $part = substr($word, 0, $len);
1923  $word = substr($word, $len);
1924 
1925  if (strlen($word) > 0) {
1926  $message .= $part . sprintf('=%s', self::CRLF);
1927  } else {
1928  $buf = $part;
1929  }
1930  }
1931  } else {
1932  $buf_o = $buf;
1933  if (!$firstword) {
1934  $buf .= ' ';
1935  }
1936  $buf .= $word;
1937 
1938  if (strlen($buf) > $length and $buf_o != '') {
1939  $message .= $buf_o . $soft_break;
1940  $buf = $word;
1941  }
1942  }
1943  $firstword = false;
1944  }
1945  $message .= $buf . self::CRLF;
1946  }
1947 
1948  return $message;
1949  }
1950 
1960  public function utf8CharBoundary($encodedText, $maxLength)
1961  {
1962  $foundSplitPos = false;
1963  $lookBack = 3;
1964  while (!$foundSplitPos) {
1965  $lastChunk = substr($encodedText, $maxLength - $lookBack, $lookBack);
1966  $encodedCharPos = strpos($lastChunk, '=');
1967  if (false !== $encodedCharPos) {
1968  // Found start of encoded character byte within $lookBack block.
1969  // Check the encoded byte value (the 2 chars after the '=')
1970  $hex = substr($encodedText, $maxLength - $lookBack + $encodedCharPos + 1, 2);
1971  $dec = hexdec($hex);
1972  if ($dec < 128) {
1973  // Single byte character.
1974  // If the encoded char was found at pos 0, it will fit
1975  // otherwise reduce maxLength to start of the encoded char
1976  if ($encodedCharPos > 0) {
1977  $maxLength = $maxLength - ($lookBack - $encodedCharPos);
1978  }
1979  $foundSplitPos = true;
1980  } elseif ($dec >= 192) {
1981  // First byte of a multi byte character
1982  // Reduce maxLength to split at start of character
1983  $maxLength = $maxLength - ($lookBack - $encodedCharPos);
1984  $foundSplitPos = true;
1985  } elseif ($dec < 192) {
1986  // Middle byte of a multi byte character, look further back
1987  $lookBack += 3;
1988  }
1989  } else {
1990  // No encoded character found
1991  $foundSplitPos = true;
1992  }
1993  }
1994  return $maxLength;
1995  }
1996 
2005  public function setWordWrap()
2006  {
2007  if ($this->WordWrap < 1) {
2008  return;
2009  }
2010 
2011  switch ($this->message_type) {
2012  case 'alt':
2013  case 'alt_inline':
2014  case 'alt_attach':
2015  case 'alt_inline_attach':
2016  $this->AltBody = $this->wrapText($this->AltBody, $this->WordWrap);
2017  break;
2018  default:
2019  $this->Body = $this->wrapText($this->Body, $this->WordWrap);
2020  break;
2021  }
2022  }
2023 
2029  public function createHeader()
2030  {
2031  $result = '';
2032 
2033  if ($this->MessageDate == '') {
2034  $this->MessageDate = self::rfcDate();
2035  }
2036  $result .= $this->headerLine('Date', $this->MessageDate);
2037 
2038  // To be created automatically by mail()
2039  if ($this->SingleTo) {
2040  if ($this->Mailer != 'mail') {
2041  foreach ($this->to as $toaddr) {
2042  $this->SingleToArray[] = $this->addrFormat($toaddr);
2043  }
2044  }
2045  } else {
2046  if (count($this->to) > 0) {
2047  if ($this->Mailer != 'mail') {
2048  $result .= $this->addrAppend('To', $this->to);
2049  }
2050  } elseif (count($this->cc) == 0) {
2051  $result .= $this->headerLine('To', 'undisclosed-recipients:;');
2052  }
2053  }
2054 
2055  $result .= $this->addrAppend('From', array(array(trim($this->From), $this->FromName)));
2056 
2057  // sendmail and mail() extract Cc from the header before sending
2058  if (count($this->cc) > 0) {
2059  $result .= $this->addrAppend('Cc', $this->cc);
2060  }
2061 
2062  // sendmail and mail() extract Bcc from the header before sending
2063  if ((
2064  $this->Mailer == 'sendmail' or $this->Mailer == 'qmail' or $this->Mailer == 'mail'
2065  )
2066  and count($this->bcc) > 0
2067  ) {
2068  $result .= $this->addrAppend('Bcc', $this->bcc);
2069  }
2070 
2071  if (count($this->ReplyTo) > 0) {
2072  $result .= $this->addrAppend('Reply-To', $this->ReplyTo);
2073  }
2074 
2075  // mail() sets the subject itself
2076  if ($this->Mailer != 'mail') {
2077  $result .= $this->headerLine('Subject', $this->encodeHeader($this->secureHeader($this->Subject)));
2078  }
2079 
2080  // Only allow a custom message ID if it conforms to RFC 5322 section 3.6.4
2081  // https://tools.ietf.org/html/rfc5322#section-3.6.4
2082  if ('' != $this->MessageID and preg_match('/^<.*@.*>$/', $this->MessageID)) {
2083  $this->lastMessageID = $this->MessageID;
2084  } else {
2085  $this->lastMessageID = sprintf('<%s@%s>', $this->uniqueid, $this->serverHostname());
2086  }
2087  $result .= $this->headerLine('Message-ID', $this->lastMessageID);
2088  if (!is_null($this->Priority)) {
2089  $result .= $this->headerLine('X-Priority', $this->Priority);
2090  }
2091  if ($this->XMailer == '') {
2092  $result .= $this->headerLine(
2093  'X-Mailer',
2094  'PHPMailer ' . $this->Version . ' (https://github.com/PHPMailer/PHPMailer)'
2095  );
2096  } else {
2097  $myXmailer = trim($this->XMailer);
2098  if ($myXmailer) {
2099  $result .= $this->headerLine('X-Mailer', $myXmailer);
2100  }
2101  }
2102 
2103  if ($this->ConfirmReadingTo != '') {
2104  $result .= $this->headerLine('Disposition-Notification-To', '<' . $this->ConfirmReadingTo . '>');
2105  }
2106 
2107  // Add custom headers
2108  foreach ($this->CustomHeader as $header) {
2109  $result .= $this->headerLine(
2110  trim($header[0]),
2111  $this->encodeHeader(trim($header[1]))
2112  );
2113  }
2114  if (!$this->sign_key_file) {
2115  $result .= $this->headerLine('MIME-Version', '1.0');
2116  $result .= $this->getMailMIME();
2117  }
2118 
2119  return $result;
2120  }
2121 
2127  public function getMailMIME()
2128  {
2129  $result = '';
2130  $ismultipart = true;
2131  switch ($this->message_type) {
2132  case 'inline':
2133  $result .= $this->headerLine('Content-Type', 'multipart/related;');
2134  $result .= $this->textLine("\tboundary=\"" . $this->boundary[1] . '"');
2135  break;
2136  case 'attach':
2137  case 'inline_attach':
2138  case 'alt_attach':
2139  case 'alt_inline_attach':
2140  $result .= $this->headerLine('Content-Type', 'multipart/mixed;');
2141  $result .= $this->textLine("\tboundary=\"" . $this->boundary[1] . '"');
2142  break;
2143  case 'alt':
2144  case 'alt_inline':
2145  $result .= $this->headerLine('Content-Type', 'multipart/alternative;');
2146  $result .= $this->textLine("\tboundary=\"" . $this->boundary[1] . '"');
2147  break;
2148  default:
2149  // Catches case 'plain': and case '':
2150  $result .= $this->textLine('Content-Type: ' . $this->ContentType . '; charset=' . $this->CharSet);
2151  $ismultipart = false;
2152  break;
2153  }
2154  // RFC1341 part 5 says 7bit is assumed if not specified
2155  if ($this->Encoding != '7bit') {
2156  // RFC 2045 section 6.4 says multipart MIME parts may only use 7bit, 8bit or binary CTE
2157  if ($ismultipart) {
2158  if ($this->Encoding == '8bit') {
2159  $result .= $this->headerLine('Content-Transfer-Encoding', '8bit');
2160  }
2161  // The only remaining alternatives are quoted-printable and base64, which are both 7bit compatible
2162  } else {
2163  $result .= $this->headerLine('Content-Transfer-Encoding', $this->Encoding);
2164  }
2165  }
2166 
2167  if ($this->Mailer != 'mail') {
2168  $result .= $this->LE;
2169  }
2170 
2171  return $result;
2172  }
2173 
2182  public function getSentMIMEMessage()
2183  {
2184  return rtrim($this->MIMEHeader . $this->mailHeader, "\n\r") . self::CRLF . self::CRLF . $this->MIMEBody;
2185  }
2186 
2191  protected function generateId() {
2192  return md5(uniqid(time()));
2193  }
2194 
2202  public function createBody()
2203  {
2204  $body = '';
2205  //Create unique IDs and preset boundaries
2206  $this->uniqueid = $this->generateId();
2207  $this->boundary[1] = 'b1_' . $this->uniqueid;
2208  $this->boundary[2] = 'b2_' . $this->uniqueid;
2209  $this->boundary[3] = 'b3_' . $this->uniqueid;
2210 
2211  if ($this->sign_key_file) {
2212  $body .= $this->getMailMIME() . $this->LE;
2213  }
2214 
2215  $this->setWordWrap();
2216 
2217  $bodyEncoding = $this->Encoding;
2218  $bodyCharSet = $this->CharSet;
2219  //Can we do a 7-bit downgrade?
2220  if ($bodyEncoding == '8bit' and !$this->has8bitChars($this->Body)) {
2221  $bodyEncoding = '7bit';
2222  //All ISO 8859, Windows codepage and UTF-8 charsets are ascii compatible up to 7-bit
2223  $bodyCharSet = 'us-ascii';
2224  }
2225  //If lines are too long, and we're not already using an encoding that will shorten them,
2226  //change to quoted-printable transfer encoding for the body part only
2227  if ('base64' != $this->Encoding and self::hasLineLongerThanMax($this->Body)) {
2228  $bodyEncoding = 'quoted-printable';
2229  }
2230 
2231  $altBodyEncoding = $this->Encoding;
2232  $altBodyCharSet = $this->CharSet;
2233  //Can we do a 7-bit downgrade?
2234  if ($altBodyEncoding == '8bit' and !$this->has8bitChars($this->AltBody)) {
2235  $altBodyEncoding = '7bit';
2236  //All ISO 8859, Windows codepage and UTF-8 charsets are ascii compatible up to 7-bit
2237  $altBodyCharSet = 'us-ascii';
2238  }
2239  //If lines are too long, and we're not already using an encoding that will shorten them,
2240  //change to quoted-printable transfer encoding for the alt body part only
2241  if ('base64' != $altBodyEncoding and self::hasLineLongerThanMax($this->AltBody)) {
2242  $altBodyEncoding = 'quoted-printable';
2243  }
2244  //Use this as a preamble in all multipart message types
2245  $mimepre = "This is a multi-part message in MIME format." . $this->LE . $this->LE;
2246  switch ($this->message_type) {
2247  case 'inline':
2248  $body .= $mimepre;
2249  $body .= $this->getBoundary($this->boundary[1], $bodyCharSet, '', $bodyEncoding);
2250  $body .= $this->encodeString($this->Body, $bodyEncoding);
2251  $body .= $this->LE . $this->LE;
2252  $body .= $this->attachAll('inline', $this->boundary[1]);
2253  break;
2254  case 'attach':
2255  $body .= $mimepre;
2256  $body .= $this->getBoundary($this->boundary[1], $bodyCharSet, '', $bodyEncoding);
2257  $body .= $this->encodeString($this->Body, $bodyEncoding);
2258  $body .= $this->LE . $this->LE;
2259  $body .= $this->attachAll('attachment', $this->boundary[1]);
2260  break;
2261  case 'inline_attach':
2262  $body .= $mimepre;
2263  $body .= $this->textLine('--' . $this->boundary[1]);
2264  $body .= $this->headerLine('Content-Type', 'multipart/related;');
2265  $body .= $this->textLine("\tboundary=\"" . $this->boundary[2] . '"');
2266  $body .= $this->LE;
2267  $body .= $this->getBoundary($this->boundary[2], $bodyCharSet, '', $bodyEncoding);
2268  $body .= $this->encodeString($this->Body, $bodyEncoding);
2269  $body .= $this->LE . $this->LE;
2270  $body .= $this->attachAll('inline', $this->boundary[2]);
2271  $body .= $this->LE;
2272  $body .= $this->attachAll('attachment', $this->boundary[1]);
2273  break;
2274  case 'alt':
2275  $body .= $mimepre;
2276  $body .= $this->getBoundary($this->boundary[1], $altBodyCharSet, 'text/plain', $altBodyEncoding);
2277  $body .= $this->encodeString($this->AltBody, $altBodyEncoding);
2278  $body .= $this->LE . $this->LE;
2279  $body .= $this->getBoundary($this->boundary[1], $bodyCharSet, 'text/html', $bodyEncoding);
2280  $body .= $this->encodeString($this->Body, $bodyEncoding);
2281  $body .= $this->LE . $this->LE;
2282  if (!empty($this->Ical)) {
2283  $body .= $this->getBoundary($this->boundary[1], '', 'text/calendar; method=REQUEST', '');
2284  $body .= $this->encodeString($this->Ical, $this->Encoding);
2285  $body .= $this->LE . $this->LE;
2286  }
2287  $body .= $this->endBoundary($this->boundary[1]);
2288  break;
2289  case 'alt_inline':
2290  $body .= $mimepre;
2291  $body .= $this->getBoundary($this->boundary[1], $altBodyCharSet, 'text/plain', $altBodyEncoding);
2292  $body .= $this->encodeString($this->AltBody, $altBodyEncoding);
2293  $body .= $this->LE . $this->LE;
2294  $body .= $this->textLine('--' . $this->boundary[1]);
2295  $body .= $this->headerLine('Content-Type', 'multipart/related;');
2296  $body .= $this->textLine("\tboundary=\"" . $this->boundary[2] . '"');
2297  $body .= $this->LE;
2298  $body .= $this->getBoundary($this->boundary[2], $bodyCharSet, 'text/html', $bodyEncoding);
2299  $body .= $this->encodeString($this->Body, $bodyEncoding);
2300  $body .= $this->LE . $this->LE;
2301  $body .= $this->attachAll('inline', $this->boundary[2]);
2302  $body .= $this->LE;
2303  $body .= $this->endBoundary($this->boundary[1]);
2304  break;
2305  case 'alt_attach':
2306  $body .= $mimepre;
2307  $body .= $this->textLine('--' . $this->boundary[1]);
2308  $body .= $this->headerLine('Content-Type', 'multipart/alternative;');
2309  $body .= $this->textLine("\tboundary=\"" . $this->boundary[2] . '"');
2310  $body .= $this->LE;
2311  $body .= $this->getBoundary($this->boundary[2], $altBodyCharSet, 'text/plain', $altBodyEncoding);
2312  $body .= $this->encodeString($this->AltBody, $altBodyEncoding);
2313  $body .= $this->LE . $this->LE;
2314  $body .= $this->getBoundary($this->boundary[2], $bodyCharSet, 'text/html', $bodyEncoding);
2315  $body .= $this->encodeString($this->Body, $bodyEncoding);
2316  $body .= $this->LE . $this->LE;
2317  $body .= $this->endBoundary($this->boundary[2]);
2318  $body .= $this->LE;
2319  $body .= $this->attachAll('attachment', $this->boundary[1]);
2320  break;
2321  case 'alt_inline_attach':
2322  $body .= $mimepre;
2323  $body .= $this->textLine('--' . $this->boundary[1]);
2324  $body .= $this->headerLine('Content-Type', 'multipart/alternative;');
2325  $body .= $this->textLine("\tboundary=\"" . $this->boundary[2] . '"');
2326  $body .= $this->LE;
2327  $body .= $this->getBoundary($this->boundary[2], $altBodyCharSet, 'text/plain', $altBodyEncoding);
2328  $body .= $this->encodeString($this->AltBody, $altBodyEncoding);
2329  $body .= $this->LE . $this->LE;
2330  $body .= $this->textLine('--' . $this->boundary[2]);
2331  $body .= $this->headerLine('Content-Type', 'multipart/related;');
2332  $body .= $this->textLine("\tboundary=\"" . $this->boundary[3] . '"');
2333  $body .= $this->LE;
2334  $body .= $this->getBoundary($this->boundary[3], $bodyCharSet, 'text/html', $bodyEncoding);
2335  $body .= $this->encodeString($this->Body, $bodyEncoding);
2336  $body .= $this->LE . $this->LE;
2337  $body .= $this->attachAll('inline', $this->boundary[3]);
2338  $body .= $this->LE;
2339  $body .= $this->endBoundary($this->boundary[2]);
2340  $body .= $this->LE;
2341  $body .= $this->attachAll('attachment', $this->boundary[1]);
2342  break;
2343  default:
2344  // Catch case 'plain' and case '', applies to simple `text/plain` and `text/html` body content types
2345  //Reset the `Encoding` property in case we changed it for line length reasons
2346  $this->Encoding = $bodyEncoding;
2347  $body .= $this->encodeString($this->Body, $this->Encoding);
2348  break;
2349  }
2350 
2351  if ($this->isError()) {
2352  $body = '';
2353  } elseif ($this->sign_key_file) {
2354  try {
2355  if (!defined('PKCS7_TEXT')) {
2356  throw new phpmailerException($this->lang('extension_missing') . 'openssl');
2357  }
2358  // @TODO would be nice to use php://temp streams here, but need to wrap for PHP < 5.1
2359  $file = tempnam(sys_get_temp_dir(), 'mail');
2360  if (false === file_put_contents($file, $body)) {
2361  throw new phpmailerException($this->lang('signing') . ' Could not write temp file');
2362  }
2363  $signed = tempnam(sys_get_temp_dir(), 'signed');
2364  //Workaround for PHP bug https://bugs.php.net/bug.php?id=69197
2365  if (empty($this->sign_extracerts_file)) {
2366  $sign = @openssl_pkcs7_sign(
2367  $file,
2368  $signed,
2369  'file://' . realpath($this->sign_cert_file),
2370  array('file://' . realpath($this->sign_key_file), $this->sign_key_pass),
2371  null
2372  );
2373  } else {
2374  $sign = @openssl_pkcs7_sign(
2375  $file,
2376  $signed,
2377  'file://' . realpath($this->sign_cert_file),
2378  array('file://' . realpath($this->sign_key_file), $this->sign_key_pass),
2379  null,
2380  PKCS7_DETACHED,
2381  $this->sign_extracerts_file
2382  );
2383  }
2384  if ($sign) {
2385  @unlink($file);
2386  $body = file_get_contents($signed);
2387  @unlink($signed);
2388  //The message returned by openssl contains both headers and body, so need to split them up
2389  $parts = explode("\n\n", $body, 2);
2390  $this->MIMEHeader .= $parts[0] . $this->LE . $this->LE;
2391  $body = $parts[1];
2392  } else {
2393  @unlink($file);
2394  @unlink($signed);
2395  throw new phpmailerException($this->lang('signing') . openssl_error_string());
2396  }
2397  } catch (phpmailerException $exc) {
2398  $body = '';
2399  if ($this->exceptions) {
2400  throw $exc;
2401  }
2402  }
2403  }
2404  return $body;
2405  }
2406 
2416  protected function getBoundary($boundary, $charSet, $contentType, $encoding)
2417  {
2418  $result = '';
2419  if ($charSet == '') {
2420  $charSet = $this->CharSet;
2421  }
2422  if ($contentType == '') {
2423  $contentType = $this->ContentType;
2424  }
2425  if ($encoding == '') {
2426  $encoding = $this->Encoding;
2427  }
2428  $result .= $this->textLine('--' . $boundary);
2429  $result .= sprintf('Content-Type: %s; charset=%s', $contentType, $charSet);
2430  $result .= $this->LE;
2431  // RFC1341 part 5 says 7bit is assumed if not specified
2432  if ($encoding != '7bit') {
2433  $result .= $this->headerLine('Content-Transfer-Encoding', $encoding);
2434  }
2435  $result .= $this->LE;
2436 
2437  return $result;
2438  }
2439 
2446  protected function endBoundary($boundary)
2447  {
2448  return $this->LE . '--' . $boundary . '--' . $this->LE;
2449  }
2450 
2457  protected function setMessageType()
2458  {
2459  $type = array();
2460  if ($this->alternativeExists()) {
2461  $type[] = 'alt';
2462  }
2463  if ($this->inlineImageExists()) {
2464  $type[] = 'inline';
2465  }
2466  if ($this->attachmentExists()) {
2467  $type[] = 'attach';
2468  }
2469  $this->message_type = implode('_', $type);
2470  if ($this->message_type == '') {
2471  //The 'plain' message_type refers to the message having a single body element, not that it is plain-text
2472  $this->message_type = 'plain';
2473  }
2474  }
2475 
2483  public function headerLine($name, $value)
2484  {
2485  return $name . ': ' . $value . $this->LE;
2486  }
2487 
2494  public function textLine($value)
2495  {
2496  return $value . $this->LE;
2497  }
2498 
2511  public function addAttachment($path, $name = '', $encoding = 'base64', $type = '', $disposition = 'attachment')
2512  {
2513  try {
2514  if (!@is_file($path)) {
2515  throw new phpmailerException($this->lang('file_access') . $path, self::STOP_CONTINUE);
2516  }
2517 
2518  // If a MIME type is not specified, try to work it out from the file name
2519  if ($type == '') {
2520  $type = self::filenameToType($path);
2521  }
2522 
2523  $filename = basename($path);
2524  if ($name == '') {
2525  $name = $filename;
2526  }
2527 
2528  $this->attachment[] = array(
2529  0 => $path,
2530  1 => $filename,
2531  2 => $name,
2532  3 => $encoding,
2533  4 => $type,
2534  5 => false, // isStringAttachment
2535  6 => $disposition,
2536  7 => 0
2537  );
2538 
2539  } catch (phpmailerException $exc) {
2540  $this->setError($exc->getMessage());
2541  $this->edebug($exc->getMessage());
2542  if ($this->exceptions) {
2543  throw $exc;
2544  }
2545  return false;
2546  }
2547  return true;
2548  }
2549 
2554  public function getAttachments()
2555  {
2556  return $this->attachment;
2557  }
2558 
2567  protected function attachAll($disposition_type, $boundary)
2568  {
2569  // Return text of body
2570  $mime = array();
2571  $cidUniq = array();
2572  $incl = array();
2573 
2574  // Add all attachments
2575  foreach ($this->attachment as $attachment) {
2576  // Check if it is a valid disposition_filter
2577  if ($attachment[6] == $disposition_type) {
2578  // Check for string attachment
2579  $string = '';
2580  $path = '';
2581  $bString = $attachment[5];
2582  if ($bString) {
2583  $string = $attachment[0];
2584  } else {
2585  $path = $attachment[0];
2586  }
2587 
2588  $inclhash = md5(serialize($attachment));
2589  if (in_array($inclhash, $incl)) {
2590  continue;
2591  }
2592  $incl[] = $inclhash;
2593  $name = $attachment[2];
2594  $encoding = $attachment[3];
2595  $type = $attachment[4];
2596  $disposition = $attachment[6];
2597  $cid = $attachment[7];
2598  if ($disposition == 'inline' && array_key_exists($cid, $cidUniq)) {
2599  continue;
2600  }
2601  $cidUniq[$cid] = true;
2602 
2603  $mime[] = sprintf('--%s%s', $boundary, $this->LE);
2604  //Only include a filename property if we have one
2605  if (!empty($name)) {
2606  $mime[] = sprintf(
2607  'Content-Type: %s; name="%s"%s',
2608  $type,
2609  $this->encodeHeader($this->secureHeader($name)),
2610  $this->LE
2611  );
2612  } else {
2613  $mime[] = sprintf(
2614  'Content-Type: %s%s',
2615  $type,
2616  $this->LE
2617  );
2618  }
2619  // RFC1341 part 5 says 7bit is assumed if not specified
2620  if ($encoding != '7bit') {
2621  $mime[] = sprintf('Content-Transfer-Encoding: %s%s', $encoding, $this->LE);
2622  }
2623 
2624  if ($disposition == 'inline') {
2625  $mime[] = sprintf('Content-ID: <%s>%s', $cid, $this->LE);
2626  }
2627 
2628  // If a filename contains any of these chars, it should be quoted,
2629  // but not otherwise: RFC2183 & RFC2045 5.1
2630  // Fixes a warning in IETF's msglint MIME checker
2631  // Allow for bypassing the Content-Disposition header totally
2632  if (!(empty($disposition))) {
2633  $encoded_name = $this->encodeHeader($this->secureHeader($name));
2634  if (preg_match('/[ \(\)<>@,;:\\"\/\[\]\?=]/', $encoded_name)) {
2635  $mime[] = sprintf(
2636  'Content-Disposition: %s; filename="%s"%s',
2637  $disposition,
2638  $encoded_name,
2639  $this->LE . $this->LE
2640  );
2641  } else {
2642  if (!empty($encoded_name)) {
2643  $mime[] = sprintf(
2644  'Content-Disposition: %s; filename=%s%s',
2645  $disposition,
2646  $encoded_name,
2647  $this->LE . $this->LE
2648  );
2649  } else {
2650  $mime[] = sprintf(
2651  'Content-Disposition: %s%s',
2652  $disposition,
2653  $this->LE . $this->LE
2654  );
2655  }
2656  }
2657  } else {
2658  $mime[] = $this->LE;
2659  }
2660 
2661  // Encode as string attachment
2662  if ($bString) {
2663  $mime[] = $this->encodeString($string, $encoding);
2664  if ($this->isError()) {
2665  return '';
2666  }
2667  $mime[] = $this->LE . $this->LE;
2668  } else {
2669  $mime[] = $this->encodeFile($path, $encoding);
2670  if ($this->isError()) {
2671  return '';
2672  }
2673  $mime[] = $this->LE . $this->LE;
2674  }
2675  }
2676  }
2677 
2678  $mime[] = sprintf('--%s--%s', $boundary, $this->LE);
2679 
2680  return implode('', $mime);
2681  }
2682 
2692  protected function encodeFile($path, $encoding = 'base64')
2693  {
2694  try {
2695  if (!is_readable($path)) {
2696  throw new phpmailerException($this->lang('file_open') . $path, self::STOP_CONTINUE);
2697  }
2698  $magic_quotes = get_magic_quotes_runtime();
2699  if ($magic_quotes) {
2700  if (version_compare(PHP_VERSION, '5.3.0', '<')) {
2701  set_magic_quotes_runtime(false);
2702  } else {
2703  //Doesn't exist in PHP 5.4, but we don't need to check because
2704  //get_magic_quotes_runtime always returns false in 5.4+
2705  //so it will never get here
2706  ini_set('magic_quotes_runtime', false);
2707  }
2708  }
2709  $file_buffer = file_get_contents($path);
2710  $file_buffer = $this->encodeString($file_buffer, $encoding);
2711  if ($magic_quotes) {
2712  if (version_compare(PHP_VERSION, '5.3.0', '<')) {
2713  set_magic_quotes_runtime($magic_quotes);
2714  } else {
2715  ini_set('magic_quotes_runtime', $magic_quotes);
2716  }
2717  }
2718  return $file_buffer;
2719  } catch (Exception $exc) {
2720  $this->setError($exc->getMessage());
2721  return '';
2722  }
2723  }
2724 
2733  public function encodeString($str, $encoding = 'base64')
2734  {
2735  $encoded = '';
2736  switch (strtolower($encoding)) {
2737  case 'base64':
2738  $encoded = chunk_split(base64_encode($str), 76, $this->LE);
2739  break;
2740  case '7bit':
2741  case '8bit':
2742  $encoded = $this->fixEOL($str);
2743  // Make sure it ends with a line break
2744  if (substr($encoded, -(strlen($this->LE))) != $this->LE) {
2745  $encoded .= $this->LE;
2746  }
2747  break;
2748  case 'binary':
2749  $encoded = $str;
2750  break;
2751  case 'quoted-printable':
2752  $encoded = $this->encodeQP($str);
2753  break;
2754  default:
2755  $this->setError($this->lang('encoding') . $encoding);
2756  break;
2757  }
2758  return $encoded;
2759  }
2760 
2769  public function encodeHeader($str, $position = 'text')
2770  {
2771  $matchcount = 0;
2772  switch (strtolower($position)) {
2773  case 'phrase':
2774  if (!preg_match('/[\200-\377]/', $str)) {
2775  // Can't use addslashes as we don't know the value of magic_quotes_sybase
2776  $encoded = addcslashes($str, "\0..\37\177\\\"");
2777  if (($str == $encoded) && !preg_match('/[^A-Za-z0-9!#$%&\'*+\/=?^_`{|}~ -]/', $str)) {
2778  return ($encoded);
2779  } else {
2780  return ("\"$encoded\"");
2781  }
2782  }
2783  $matchcount = preg_match_all('/[^\040\041\043-\133\135-\176]/', $str, $matches);
2784  break;
2786  case 'comment':
2787  $matchcount = preg_match_all('/[()"]/', $str, $matches);
2788  // Intentional fall-through
2789  case 'text':
2790  default:
2791  $matchcount += preg_match_all('/[\000-\010\013\014\016-\037\177-\377]/', $str, $matches);
2792  break;
2793  }
2794 
2795  //There are no chars that need encoding
2796  if ($matchcount == 0) {
2797  return ($str);
2798  }
2799 
2800  $maxlen = 75 - 7 - strlen($this->CharSet);
2801  // Try to select the encoding which should produce the shortest output
2802  if ($matchcount > strlen($str) / 3) {
2803  // More than a third of the content will need encoding, so B encoding will be most efficient
2804  $encoding = 'B';
2805  if (function_exists('mb_strlen') && $this->hasMultiBytes($str)) {
2806  // Use a custom function which correctly encodes and wraps long
2807  // multibyte strings without breaking lines within a character
2808  $encoded = $this->base64EncodeWrapMB($str, "\n");
2809  } else {
2810  $encoded = base64_encode($str);
2811  $maxlen -= $maxlen % 4;
2812  $encoded = trim(chunk_split($encoded, $maxlen, "\n"));
2813  }
2814  } else {
2815  $encoding = 'Q';
2816  $encoded = $this->encodeQ($str, $position);
2817  $encoded = $this->wrapText($encoded, $maxlen, true);
2818  $encoded = str_replace('=' . self::CRLF, "\n", trim($encoded));
2819  }
2820 
2821  $encoded = preg_replace('/^(.*)$/m', ' =?' . $this->CharSet . "?$encoding?\\1?=", $encoded);
2822  $encoded = trim(str_replace("\n", $this->LE, $encoded));
2823 
2824  return $encoded;
2825  }
2826 
2833  public function hasMultiBytes($str)
2834  {
2835  if (function_exists('mb_strlen')) {
2836  return (strlen($str) > mb_strlen($str, $this->CharSet));
2837  } else { // Assume no multibytes (we can't handle without mbstring functions anyway)
2838  return false;
2839  }
2840  }
2841 
2847  public function has8bitChars($text)
2848  {
2849  return (boolean)preg_match('/[\x80-\xFF]/', $text);
2850  }
2851 
2862  public function base64EncodeWrapMB($str, $linebreak = null)
2863  {
2864  $start = '=?' . $this->CharSet . '?B?';
2865  $end = '?=';
2866  $encoded = '';
2867  if ($linebreak === null) {
2868  $linebreak = $this->LE;
2869  }
2870 
2871  $mb_length = mb_strlen($str, $this->CharSet);
2872  // Each line must have length <= 75, including $start and $end
2873  $length = 75 - strlen($start) - strlen($end);
2874  // Average multi-byte ratio
2875  $ratio = $mb_length / strlen($str);
2876  // Base64 has a 4:3 ratio
2877  $avgLength = floor($length * $ratio * .75);
2878 
2879  for ($i = 0; $i < $mb_length; $i += $offset) {
2880  $lookBack = 0;
2881  do {
2882  $offset = $avgLength - $lookBack;
2883  $chunk = mb_substr($str, $i, $offset, $this->CharSet);
2884  $chunk = base64_encode($chunk);
2885  $lookBack++;
2886  } while (strlen($chunk) > $length);
2887  $encoded .= $chunk . $linebreak;
2888  }
2889 
2890  // Chomp the last linefeed
2891  $encoded = substr($encoded, 0, -strlen($linebreak));
2892  return $encoded;
2893  }
2894 
2904  public function encodeQP($string, $line_max = 76)
2905  {
2906  // Use native function if it's available (>= PHP5.3)
2907  if (function_exists('quoted_printable_encode')) {
2908  return quoted_printable_encode($string);
2909  }
2910  // Fall back to a pure PHP implementation
2911  $string = str_replace(
2912  array('%20', '%0D%0A.', '%0D%0A', '%'),
2913  array(' ', "\r\n=2E", "\r\n", '='),
2914  rawurlencode($string)
2915  );
2916  return preg_replace('/[^\r\n]{' . ($line_max - 3) . '}[^=\r\n]{2}/', "$0=\r\n", $string);
2917  }
2918 
2929  public function encodeQPphp(
2930  $string,
2931  $line_max = 76, $space_conv = false
2933  ) {
2934  return $this->encodeQP($string, $line_max);
2935  }
2936 
2945  public function encodeQ($str, $position = 'text')
2946  {
2947  // There should not be any EOL in the string
2948  $pattern = '';
2949  $encoded = str_replace(array("\r", "\n"), '', $str);
2950  switch (strtolower($position)) {
2951  case 'phrase':
2952  // RFC 2047 section 5.3
2953  $pattern = '^A-Za-z0-9!*+\/ -';
2954  break;
2956  case 'comment':
2957  // RFC 2047 section 5.2
2958  $pattern = '\(\)"';
2959  // intentional fall-through
2960  // for this reason we build the $pattern without including delimiters and []
2961  case 'text':
2962  default:
2963  // RFC 2047 section 5.1
2964  // Replace every high ascii, control, =, ? and _ characters
2965  $pattern = '\000-\011\013\014\016-\037\075\077\137\177-\377' . $pattern;
2966  break;
2967  }
2968  $matches = array();
2969  if (preg_match_all("/[{$pattern}]/", $encoded, $matches)) {
2970  // If the string contains an '=', make sure it's the first thing we replace
2971  // so as to avoid double-encoding
2972  $eqkey = array_search('=', $matches[0]);
2973  if (false !== $eqkey) {
2974  unset($matches[0][$eqkey]);
2975  array_unshift($matches[0], '=');
2976  }
2977  foreach (array_unique($matches[0]) as $char) {
2978  $encoded = str_replace($char, '=' . sprintf('%02X', ord($char)), $encoded);
2979  }
2980  }
2981  // Replace every spaces to _ (more readable than =20)
2982  return str_replace(' ', '_', $encoded);
2983  }
2984 
2996  public function addStringAttachment(
2997  $string,
2998  $filename,
2999  $encoding = 'base64',
3000  $type = '',
3001  $disposition = 'attachment'
3002  ) {
3003  // If a MIME type is not specified, try to work it out from the file name
3004  if ($type == '') {
3005  $type = self::filenameToType($filename);
3006  }
3007  // Append to $attachment array
3008  $this->attachment[] = array(
3009  0 => $string,
3010  1 => $filename,
3011  2 => basename($filename),
3012  3 => $encoding,
3013  4 => $type,
3014  5 => true, // isStringAttachment
3015  6 => $disposition,
3016  7 => 0
3017  );
3018  }
3019 
3037  public function addEmbeddedImage($path, $cid, $name = '', $encoding = 'base64', $type = '', $disposition = 'inline')
3038  {
3039  if (!@is_file($path)) {
3040  $this->setError($this->lang('file_access') . $path);
3041  return false;
3042  }
3043 
3044  // If a MIME type is not specified, try to work it out from the file name
3045  if ($type == '') {
3046  $type = self::filenameToType($path);
3047  }
3048 
3049  $filename = basename($path);
3050  if ($name == '') {
3051  $name = $filename;
3052  }
3053 
3054  // Append to $attachment array
3055  $this->attachment[] = array(
3056  0 => $path,
3057  1 => $filename,
3058  2 => $name,
3059  3 => $encoding,
3060  4 => $type,
3061  5 => false, // isStringAttachment
3062  6 => $disposition,
3063  7 => $cid
3064  );
3065  return true;
3066  }
3067 
3082  public function addStringEmbeddedImage(
3083  $string,
3084  $cid,
3085  $name = '',
3086  $encoding = 'base64',
3087  $type = '',
3088  $disposition = 'inline'
3089  ) {
3090  // If a MIME type is not specified, try to work it out from the name
3091  if ($type == '' and !empty($name)) {
3092  $type = self::filenameToType($name);
3093  }
3094 
3095  // Append to $attachment array
3096  $this->attachment[] = array(
3097  0 => $string,
3098  1 => $name,
3099  2 => $name,
3100  3 => $encoding,
3101  4 => $type,
3102  5 => true, // isStringAttachment
3103  6 => $disposition,
3104  7 => $cid
3105  );
3106  return true;
3107  }
3108 
3114  public function inlineImageExists()
3115  {
3116  foreach ($this->attachment as $attachment) {
3117  if ($attachment[6] == 'inline') {
3118  return true;
3119  }
3120  }
3121  return false;
3122  }
3123 
3128  public function attachmentExists()
3129  {
3130  foreach ($this->attachment as $attachment) {
3131  if ($attachment[6] == 'attachment') {
3132  return true;
3133  }
3134  }
3135  return false;
3136  }
3137 
3142  public function alternativeExists()
3143  {
3144  return !empty($this->AltBody);
3145  }
3146 
3153  public function clearQueuedAddresses($kind)
3154  {
3156  foreach ($RecipientsQueue as $address => $params) {
3157  if ($params[0] == $kind) {
3158  unset($this->RecipientsQueue[$address]);
3159  }
3160  }
3161  }
3162 
3167  public function clearAddresses()
3168  {
3169  foreach ($this->to as $to) {
3170  unset($this->all_recipients[strtolower($to[0])]);
3171  }
3172  $this->to = array();
3173  $this->clearQueuedAddresses('to');
3174  }
3175 
3180  public function clearCCs()
3181  {
3182  foreach ($this->cc as $cc) {
3183  unset($this->all_recipients[strtolower($cc[0])]);
3184  }
3185  $this->cc = array();
3186  $this->clearQueuedAddresses('cc');
3187  }
3188 
3193  public function clearBCCs()
3194  {
3195  foreach ($this->bcc as $bcc) {
3196  unset($this->all_recipients[strtolower($bcc[0])]);
3197  }
3198  $this->bcc = array();
3199  $this->clearQueuedAddresses('bcc');
3200  }
3201 
3206  public function clearReplyTos()
3207  {
3208  $this->ReplyTo = array();
3209  $this->ReplyToQueue = array();
3210  }
3211 
3216  public function clearAllRecipients()
3217  {
3218  $this->to = array();
3219  $this->cc = array();
3220  $this->bcc = array();
3221  $this->all_recipients = array();
3222  $this->RecipientsQueue = array();
3223  }
3224 
3229  public function clearAttachments()
3230  {
3231  $this->attachment = array();
3232  }
3233 
3238  public function clearCustomHeaders()
3239  {
3240  $this->CustomHeader = array();
3241  }
3242 
3249  protected function setError($msg)
3250  {
3251  $this->error_count++;
3252  if ($this->Mailer == 'smtp' and !is_null($this->smtp)) {
3253  $lasterror = $this->smtp->getError();
3254  if (!empty($lasterror['error'])) {
3255  $msg .= $this->lang('smtp_error') . $lasterror['error'];
3256  if (!empty($lasterror['detail'])) {
3257  $msg .= ' Detail: '. $lasterror['detail'];
3258  }
3259  if (!empty($lasterror['smtp_code'])) {
3260  $msg .= ' SMTP code: ' . $lasterror['smtp_code'];
3261  }
3262  if (!empty($lasterror['smtp_code_ex'])) {
3263  $msg .= ' Additional SMTP info: ' . $lasterror['smtp_code_ex'];
3264  }
3265  }
3266  }
3267  $this->ErrorInfo = $msg;
3268  }
3269 
3276  public static function rfcDate()
3277  {
3278  // Set the time zone to whatever the default is to avoid 500 errors
3279  // Will default to UTC if it's not set properly in php.ini
3280  date_default_timezone_set(@date_default_timezone_get());
3281  return date('D, j M Y H:i:s O');
3282  }
3283 
3290  protected function serverHostname()
3291  {
3292  $result = 'localhost.localdomain';
3293  if (!empty($this->Hostname)) {
3295  } elseif (isset($_SERVER) and array_key_exists('SERVER_NAME', $_SERVER) and !empty($_SERVER['SERVER_NAME'])) {
3296  $result = $_SERVER['SERVER_NAME'];
3297  } elseif (function_exists('gethostname') && gethostname() !== false) {
3298  $result = gethostname();
3299  } elseif (php_uname('n') !== false) {
3300  $result = php_uname('n');
3301  }
3302  return $result;
3303  }
3304 
3311  protected function lang($key)
3312  {
3313  if (count($this->language) < 1) {
3314  $this->setLanguage('en'); // set the default language
3315  }
3316 
3317  if (array_key_exists($key, $this->language)) {
3318  if ($key == 'smtp_connect_failed') {
3319  //Include a link to troubleshooting docs on SMTP connection failure
3320  //this is by far the biggest cause of support questions
3321  //but it's usually not PHPMailer's fault.
3322  return $this->language[$key] . ' https://github.com/PHPMailer/PHPMailer/wiki/Troubleshooting';
3323  }
3324  return $this->language[$key];
3325  } else {
3326  //Return the key as a fallback
3327  return $key;
3328  }
3329  }
3330 
3336  public function isError()
3337  {
3338  return ($this->error_count > 0);
3339  }
3340 
3348  public function fixEOL($str)
3349  {
3350  // Normalise to \n
3351  $nstr = str_replace(array("\r\n", "\r"), "\n", $str);
3352  // Now convert LE as needed
3353  if ($this->LE !== "\n") {
3354  $nstr = str_replace("\n", $this->LE, $nstr);
3355  }
3356  return $nstr;
3357  }
3358 
3368  public function addCustomHeader($name, $value = null)
3369  {
3370  if ($value === null) {
3371  // Value passed in as name:value
3372  $this->CustomHeader[] = explode(':', $name, 2);
3373  } else {
3374  $this->CustomHeader[] = array($name, $value);
3375  }
3376  }
3377 
3382  public function getCustomHeaders()
3383  {
3384  return $this->CustomHeader;
3385  }
3386 
3403  public function msgHTML($message, $basedir = '', $advanced = false)
3404  {
3405  preg_match_all('/(src|background)=["\'](.*)["\']/Ui', $message, $images);
3406  if (array_key_exists(2, $images)) {
3407  if (strlen($basedir) > 1 && substr($basedir, -1) != '/') {
3408  // Ensure $basedir has a trailing /
3409  $basedir .= '/';
3410  }
3411  foreach ($images[2] as $imgindex => $url) {
3412  // Convert data URIs into embedded images
3413  if (preg_match('#^data:(image[^;,]*)(;base64)?,#', $url, $match)) {
3414  $data = substr($url, strpos($url, ','));
3415  if ($match[2]) {
3416  $data = base64_decode($data);
3417  } else {
3418  $data = rawurldecode($data);
3419  }
3420  $cid = md5($url) . '@phpmailer.0'; // RFC2392 S 2
3421  if ($this->addStringEmbeddedImage($data, $cid, 'embed' . $imgindex, 'base64', $match[1])) {
3422  $message = str_replace(
3423  $images[0][$imgindex],
3424  $images[1][$imgindex] . '="cid:' . $cid . '"',
3425  $message
3426  );
3427  }
3428  continue;
3429  }
3430  if (
3431  // Only process relative URLs if a basedir is provided (i.e. no absolute local paths)
3432  !empty($basedir)
3433  // Ignore URLs containing parent dir traversal (..)
3434  && (strpos($url, '..') === false)
3435  // Do not change urls that are already inline images
3436  && substr($url, 0, 4) !== 'cid:'
3437  // Do not change absolute URLs, including anonymous protocol
3438  && !preg_match('#^[a-z][a-z0-9+.-]*:?//#i', $url)
3439  ) {
3440  $filename = basename($url);
3441  $directory = dirname($url);
3442  if ($directory == '.') {
3443  $directory = '';
3444  }
3445  $cid = md5($url) . '@phpmailer.0'; // RFC2392 S 2
3446  if (strlen($directory) > 1 && substr($directory, -1) != '/') {
3447  $directory .= '/';
3448  }
3449  if ($this->addEmbeddedImage(
3450  $basedir . $directory . $filename,
3451  $cid,
3452  $filename,
3453  'base64',
3454  self::_mime_types((string)self::mb_pathinfo($filename, PATHINFO_EXTENSION))
3455  )
3456  ) {
3457  $message = preg_replace(
3458  '/' . $images[1][$imgindex] . '=["\']' . preg_quote($url, '/') . '["\']/Ui',
3459  $images[1][$imgindex] . '="cid:' . $cid . '"',
3460  $message
3461  );
3462  }
3463  }
3464  }
3465  }
3466  $this->isHTML(true);
3467  // Convert all message body line breaks to CRLF, makes quoted-printable encoding work much better
3468  $this->Body = $this->normalizeBreaks($message);
3469  $this->AltBody = $this->normalizeBreaks($this->html2text($message, $advanced));
3470  if (!$this->alternativeExists()) {
3471  $this->AltBody = 'To view this email message, open it in a program that understands HTML!' .
3472  self::CRLF . self::CRLF;
3473  }
3474  return $this->Body;
3475  }
3476 
3497  public function html2text($html, $advanced = false)
3498  {
3499  if (is_callable($advanced)) {
3500  return call_user_func($advanced, $html);
3501  }
3502  return html_entity_decode(
3503  trim(strip_tags(preg_replace('/<(head|title|style|script)[^>]*>.*?<\/\\1>/si', '', $html))),
3504  ENT_QUOTES,
3505  $this->CharSet
3506  );
3507  }
3508 
3516  public static function _mime_types($ext = '')
3517  {
3518  $mimes = array(
3519  'xl' => 'application/excel',
3520  'js' => 'application/javascript',
3521  'hqx' => 'application/mac-binhex40',
3522  'cpt' => 'application/mac-compactpro',
3523  'bin' => 'application/macbinary',
3524  'doc' => 'application/msword',
3525  'word' => 'application/msword',
3526  'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
3527  'xltx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.template',
3528  'potx' => 'application/vnd.openxmlformats-officedocument.presentationml.template',
3529  'ppsx' => 'application/vnd.openxmlformats-officedocument.presentationml.slideshow',
3530  'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
3531  'sldx' => 'application/vnd.openxmlformats-officedocument.presentationml.slide',
3532  'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
3533  'dotx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.template',
3534  'xlam' => 'application/vnd.ms-excel.addin.macroEnabled.12',
3535  'xlsb' => 'application/vnd.ms-excel.sheet.binary.macroEnabled.12',
3536  'class' => 'application/octet-stream',
3537  'dll' => 'application/octet-stream',
3538  'dms' => 'application/octet-stream',
3539  'exe' => 'application/octet-stream',
3540  'lha' => 'application/octet-stream',
3541  'lzh' => 'application/octet-stream',
3542  'psd' => 'application/octet-stream',
3543  'sea' => 'application/octet-stream',
3544  'so' => 'application/octet-stream',
3545  'oda' => 'application/oda',
3546  'pdf' => 'application/pdf',
3547  'ai' => 'application/postscript',
3548  'eps' => 'application/postscript',
3549  'ps' => 'application/postscript',
3550  'smi' => 'application/smil',
3551  'smil' => 'application/smil',
3552  'mif' => 'application/vnd.mif',
3553  'xls' => 'application/vnd.ms-excel',
3554  'ppt' => 'application/vnd.ms-powerpoint',
3555  'wbxml' => 'application/vnd.wap.wbxml',
3556  'wmlc' => 'application/vnd.wap.wmlc',
3557  'dcr' => 'application/x-director',
3558  'dir' => 'application/x-director',
3559  'dxr' => 'application/x-director',
3560  'dvi' => 'application/x-dvi',
3561  'gtar' => 'application/x-gtar',
3562  'php3' => 'application/x-httpd-php',
3563  'php4' => 'application/x-httpd-php',
3564  'php' => 'application/x-httpd-php',
3565  'phtml' => 'application/x-httpd-php',
3566  'phps' => 'application/x-httpd-php-source',
3567  'swf' => 'application/x-shockwave-flash',
3568  'sit' => 'application/x-stuffit',
3569  'tar' => 'application/x-tar',
3570  'tgz' => 'application/x-tar',
3571  'xht' => 'application/xhtml+xml',
3572  'xhtml' => 'application/xhtml+xml',
3573  'zip' => 'application/zip',
3574  'mid' => 'audio/midi',
3575  'midi' => 'audio/midi',
3576  'mp2' => 'audio/mpeg',
3577  'mp3' => 'audio/mpeg',
3578  'mpga' => 'audio/mpeg',
3579  'aif' => 'audio/x-aiff',
3580  'aifc' => 'audio/x-aiff',
3581  'aiff' => 'audio/x-aiff',
3582  'ram' => 'audio/x-pn-realaudio',
3583  'rm' => 'audio/x-pn-realaudio',
3584  'rpm' => 'audio/x-pn-realaudio-plugin',
3585  'ra' => 'audio/x-realaudio',
3586  'wav' => 'audio/x-wav',
3587  'bmp' => 'image/bmp',
3588  'gif' => 'image/gif',
3589  'jpeg' => 'image/jpeg',
3590  'jpe' => 'image/jpeg',
3591  'jpg' => 'image/jpeg',
3592  'png' => 'image/png',
3593  'tiff' => 'image/tiff',
3594  'tif' => 'image/tiff',
3595  'eml' => 'message/rfc822',
3596  'css' => 'text/css',
3597  'html' => 'text/html',
3598  'htm' => 'text/html',
3599  'shtml' => 'text/html',
3600  'log' => 'text/plain',
3601  'text' => 'text/plain',
3602  'txt' => 'text/plain',
3603  'rtx' => 'text/richtext',
3604  'rtf' => 'text/rtf',
3605  'vcf' => 'text/vcard',
3606  'vcard' => 'text/vcard',
3607  'xml' => 'text/xml',
3608  'xsl' => 'text/xml',
3609  'mpeg' => 'video/mpeg',
3610  'mpe' => 'video/mpeg',
3611  'mpg' => 'video/mpeg',
3612  'mov' => 'video/quicktime',
3613  'qt' => 'video/quicktime',
3614  'rv' => 'video/vnd.rn-realvideo',
3615  'avi' => 'video/x-msvideo',
3616  'movie' => 'video/x-sgi-movie'
3617  );
3618  if (array_key_exists(strtolower($ext), $mimes)) {
3619  return $mimes[strtolower($ext)];
3620  }
3621  return 'application/octet-stream';
3622  }
3623 
3631  public static function filenameToType($filename)
3632  {
3633  // In case the path is a URL, strip any query string before getting extension
3634  $qpos = strpos($filename, '?');
3635  if (false !== $qpos) {
3636  $filename = substr($filename, 0, $qpos);
3637  }
3638  $pathinfo = self::mb_pathinfo($filename);
3639  return self::_mime_types($pathinfo['extension']);
3640  }
3641 
3653  public static function mb_pathinfo($path, $options = null)
3654  {
3655  $ret = array('dirname' => '', 'basename' => '', 'extension' => '', 'filename' => '');
3656  $pathinfo = array();
3657  if (preg_match('%^(.*?)[\\\\/]*(([^/\\\\]*?)(\.([^\.\\\\/]+?)|))[\\\\/\.]*$%im', $path, $pathinfo)) {
3658  if (array_key_exists(1, $pathinfo)) {
3659  $ret['dirname'] = $pathinfo[1];
3660  }
3661  if (array_key_exists(2, $pathinfo)) {
3662  $ret['basename'] = $pathinfo[2];
3663  }
3664  if (array_key_exists(5, $pathinfo)) {
3665  $ret['extension'] = $pathinfo[5];
3666  }
3667  if (array_key_exists(3, $pathinfo)) {
3668  $ret['filename'] = $pathinfo[3];
3669  }
3670  }
3671  switch ($options) {
3672  case PATHINFO_DIRNAME:
3673  case 'dirname':
3674  return $ret['dirname'];
3675  case PATHINFO_BASENAME:
3676  case 'basename':
3677  return $ret['basename'];
3678  case PATHINFO_EXTENSION:
3679  case 'extension':
3680  return $ret['extension'];
3681  case PATHINFO_FILENAME:
3682  case 'filename':
3683  return $ret['filename'];
3684  default:
3685  return $ret;
3686  }
3687  }
3688 
3703  public function set($name, $value = '')
3704  {
3705  if (property_exists($this, $name)) {
3706  $this->$name = $value;
3707  return true;
3708  } else {
3709  $this->setError($this->lang('variable_set') . $name);
3710  return false;
3711  }
3712  }
3713 
3720  public function secureHeader($str)
3721  {
3722  return trim(str_replace(array("\r", "\n"), '', $str));
3723  }
3724 
3735  public static function normalizeBreaks($text, $breaktype = "\r\n")
3736  {
3737  return preg_replace('/(\r\n|\r|\n)/ms', $breaktype, $text);
3738  }
3739 
3748  public function sign($cert_filename, $key_filename, $key_pass, $extracerts_filename = '')
3749  {
3750  $this->sign_cert_file = $cert_filename;
3751  $this->sign_key_file = $key_filename;
3752  $this->sign_key_pass = $key_pass;
3753  $this->sign_extracerts_file = $extracerts_filename;
3754  }
3755 
3762  public function DKIM_QP($txt)
3763  {
3764  $line = '';
3765  for ($i = 0; $i < strlen($txt); $i++) {
3766  $ord = ord($txt[$i]);
3767  if (((0x21 <= $ord) && ($ord <= 0x3A)) || $ord == 0x3C || ((0x3E <= $ord) && ($ord <= 0x7E))) {
3768  $line .= $txt[$i];
3769  } else {
3770  $line .= '=' . sprintf('%02X', $ord);
3771  }
3772  }
3773  return $line;
3774  }
3775 
3783  public function DKIM_Sign($signHeader)
3784  {
3785  if (!defined('PKCS7_TEXT')) {
3786  if ($this->exceptions) {
3787  throw new phpmailerException($this->lang('extension_missing') . 'openssl');
3788  }
3789  return '';
3790  }
3791  $privKeyStr = !empty($this->DKIM_private_string) ? $this->DKIM_private_string : file_get_contents($this->DKIM_private);
3792  if ('' != $this->DKIM_passphrase) {
3793  $privKey = openssl_pkey_get_private($privKeyStr, $this->DKIM_passphrase);
3794  } else {
3795  $privKey = openssl_pkey_get_private($privKeyStr);
3796  }
3797  //Workaround for missing digest algorithms in old PHP & OpenSSL versions
3798  //@link http://stackoverflow.com/a/11117338/333340
3799  if (version_compare(PHP_VERSION, '5.3.0') >= 0 and
3800  in_array('sha256WithRSAEncryption', openssl_get_md_methods(true))) {
3801  if (openssl_sign($signHeader, $signature, $privKey, 'sha256WithRSAEncryption')) {
3802  openssl_pkey_free($privKey);
3803  return base64_encode($signature);
3804  }
3805  } else {
3806  $pinfo = openssl_pkey_get_details($privKey);
3807  $hash = hash('sha256', $signHeader);
3808  //'Magic' constant for SHA256 from RFC3447
3809  //@link https://tools.ietf.org/html/rfc3447#page-43
3810  $t = '3031300d060960864801650304020105000420' . $hash;
3811  $pslen = $pinfo['bits'] / 8 - (strlen($t) / 2 + 3);
3812  $eb = pack('H*', '0001' . str_repeat('FF', $pslen) . '00' . $t);
3813 
3814  if (openssl_private_encrypt($eb, $signature, $privKey, OPENSSL_NO_PADDING)) {
3815  openssl_pkey_free($privKey);
3816  return base64_encode($signature);
3817  }
3818  }
3819  openssl_pkey_free($privKey);
3820  return '';
3821  }
3822 
3829  public function DKIM_HeaderC($signHeader)
3830  {
3831  $signHeader = preg_replace('/\r\n\s+/', ' ', $signHeader);
3832  $lines = explode("\r\n", $signHeader);
3833  foreach ($lines as $key => $line) {
3834  list($heading, $value) = explode(':', $line, 2);
3835  $heading = strtolower($heading);
3836  $value = preg_replace('/\s{2,}/', ' ', $value); // Compress useless spaces
3837  $lines[$key] = $heading . ':' . trim($value); // Don't forget to remove WSP around the value
3838  }
3839  $signHeader = implode("\r\n", $lines);
3840  return $signHeader;
3841  }
3842 
3849  public function DKIM_BodyC($body)
3850  {
3851  if ($body == '') {
3852  return "\r\n";
3853  }
3854  // stabilize line endings
3855  $body = str_replace("\r\n", "\n", $body);
3856  $body = str_replace("\n", "\r\n", $body);
3857  // END stabilize line endings
3858  while (substr($body, strlen($body) - 4, 4) == "\r\n\r\n") {
3859  $body = substr($body, 0, strlen($body) - 2);
3860  }
3861  return $body;
3862  }
3863 
3872  public function DKIM_Add($headers_line, $subject, $body)
3873  {
3874  $DKIMsignatureType = 'rsa-sha256'; // Signature & hash algorithms
3875  $DKIMcanonicalization = 'relaxed/simple'; // Canonicalization of header/body
3876  $DKIMquery = 'dns/txt'; // Query method
3877  $DKIMtime = time(); // Signature Timestamp = seconds since 00:00:00 - Jan 1, 1970 (UTC time zone)
3878  $subject_header = "Subject: $subject";
3879  $headers = explode($this->LE, $headers_line);
3880  $from_header = '';
3881  $to_header = '';
3882  $date_header = '';
3883  $current = '';
3884  foreach ($headers as $header) {
3885  if (strpos($header, 'From:') === 0) {
3886  $from_header = $header;
3887  $current = 'from_header';
3888  } elseif (strpos($header, 'To:') === 0) {
3889  $to_header = $header;
3890  $current = 'to_header';
3891  } elseif (strpos($header, 'Date:') === 0) {
3892  $date_header = $header;
3893  $current = 'date_header';
3894  } else {
3895  if (!empty($$current) && strpos($header, ' =?') === 0) {
3896  $$current .= $header;
3897  } else {
3898  $current = '';
3899  }
3900  }
3901  }
3902  $from = str_replace('|', '=7C', $this->DKIM_QP($from_header));
3903  $to = str_replace('|', '=7C', $this->DKIM_QP($to_header));
3904  $date = str_replace('|', '=7C', $this->DKIM_QP($date_header));
3905  $subject = str_replace(
3906  '|',
3907  '=7C',
3908  $this->DKIM_QP($subject_header)
3909  ); // Copied header fields (dkim-quoted-printable)
3910  $body = $this->DKIM_BodyC($body);
3911  $DKIMlen = strlen($body); // Length of body
3912  $DKIMb64 = base64_encode(pack('H*', hash('sha256', $body))); // Base64 of packed binary SHA-256 hash of body
3913  if ('' == $this->DKIM_identity) {
3914  $ident = '';
3915  } else {
3916  $ident = ' i=' . $this->DKIM_identity . ';';
3917  }
3918  $dkimhdrs = 'DKIM-Signature: v=1; a=' .
3919  $DKIMsignatureType . '; q=' .
3920  $DKIMquery . '; l=' .
3921  $DKIMlen . '; s=' .
3922  $this->DKIM_selector .
3923  ";\r\n" .
3924  "\tt=" . $DKIMtime . '; c=' . $DKIMcanonicalization . ";\r\n" .
3925  "\th=From:To:Date:Subject;\r\n" .
3926  "\td=" . $this->DKIM_domain . ';' . $ident . "\r\n" .
3927  "\tz=$from\r\n" .
3928  "\t|$to\r\n" .
3929  "\t|$date\r\n" .
3930  "\t|$subject;\r\n" .
3931  "\tbh=" . $DKIMb64 . ";\r\n" .
3932  "\tb=";
3933  $toSign = $this->DKIM_HeaderC(
3934  $from_header . "\r\n" .
3935  $to_header . "\r\n" .
3936  $date_header . "\r\n" .
3937  $subject_header . "\r\n" .
3938  $dkimhdrs
3939  );
3940  $signed = $this->DKIM_Sign($toSign);
3941  return $dkimhdrs . $signed . "\r\n";
3942  }
3943 
3950  public static function hasLineLongerThanMax($str)
3951  {
3952  //+2 to include CRLF line break for a 1000 total
3953  return (boolean)preg_match('/^(.{'.(self::MAX_LINE_LENGTH + 2).',})/m', $str);
3954  }
3955 
3962  public function getToAddresses()
3963  {
3964  return $this->to;
3965  }
3966 
3973  public function getCcAddresses()
3974  {
3975  return $this->cc;
3976  }
3977 
3984  public function getBccAddresses()
3985  {
3986  return $this->bcc;
3987  }
3988 
3995  public function getReplyToAddresses()
3996  {
3997  return $this->ReplyTo;
3998  }
3999 
4006  public function getAllRecipientAddresses()
4007  {
4008  return $this->all_recipients;
4009  }
4010 
4021  protected function doCallback($isSent, $to, $cc, $bcc, $subject, $body, $from)
4022  {
4023  if (!empty($this->action_function) && is_callable($this->action_function)) {
4024  $params = array($isSent, $to, $cc, $bcc, $subject, $body, $from);
4025  call_user_func_array($this->action_function, $params);
4026  }
4027  }
4028 }
4029 
4035 {
4040  public function errorMessage()
4041  {
4042  $errorMsg = '<strong>' . $this->getMessage() . "</strong><br />\n";
4043  return $errorMsg;
4044  }
4045 }
File written to
addAnAddress($kind, $address, $name='')
Add an address to one of the recipient arrays or to the ReplyTo array.
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.
$error
Definition: Error.php:17
getSentMIMEMessage()
Returns the whole MIME message.
attachmentExists()
Check if an attachment (non-inline) is present.
addCustomHeader($name, $value=null)
Add a custom header.
$path
Definition: aliased.php:25
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.
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...
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.
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.
exceptions()
Get all exception thrown sofar and reset the logger.
$url
Definition: shib_logout.php:72
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.
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.
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...
if(!is_array($argv)) $options
$header
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.
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:12
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.
$text
getSMTPInstance()
Get an instance to use for SMTP operations.
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.
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.
$html
Definition: example_001.php:87
$params
Definition: example_049.php:96
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.
static isShellSafe($string)
Fix CVE-2016-10033 and CVE-2016-10045 by disallowing potentially unsafe shell characters.
getMailMIME()
Get the message MIME type headers.