diff --git a/custom/vendor/PHPMailer.Lite.php b/custom/vendor/PHPMailer.Lite.php new file mode 100644 index 0000000..c951fb5 --- /dev/null +++ b/custom/vendor/PHPMailer.Lite.php @@ -0,0 +1,1676 @@ + + * @copyright 2004-2023 (C) Andy Prevost - All Rights Reserved + * @version 2024.1.3.0 + * @requires PHP version 8.0.0 (and up) + * @license MIT - Distributed under the MIT License + * shown here: + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the 'Software'), to deal in the Software without + * restriction, including without limitation the rights to use, copy, + * modify, merge, publish, distribute, sublicense, and/or sell copies + * of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + **/ +/* Last updated: 21 Mar 2024 @ 16:34 (EST) */ + +namespace codeworxtech\PHPMailerLite; + +if (version_compare(PHP_VERSION, "8.0.0", "<=")) { + exit("Sorry, this version of PHPMailer Lite will only run on PHP version 8.0.0 or greater!\n"); +} + +class PHPMailerLite +{ + + /* CONSTANTS */ + const VERSION = "2024.1.3.0"; + const CRLF = "\r\n"; + const MAILSEP = ", "; + + /* SMTP CONSTANTS */ + const TIMEVAL = 30; // seconds + + /* PROPERTIES, PRIVATE & PROTECTED */ + private array $attachments = []; + private string $bcc = ""; + private array $boundary = []; + private string $charSet = "utf-8"; + private string $cc = ""; + private string $confirm_read = ""; + private array $customHeader = []; + private string $encode_hdr = "base64"; + private string $msgType = ""; + private string $recipients = ""; + private string $recipients_rt = ""; + private string $replyTo = ""; + private string $sender = ""; + private string $senderEmail = "root@localhost"; + private string $senderName = "No Reply"; + private string $sendmailPath = ""; + private string $transport = ""; + private array $transports = ["smtp", "sendmail", "imap"]; + private int $wordWrapLen = 70; + private string $timeStart = ""; + + /* PROPERTIES, PUBLIC */ + /** + * debug has a cap + */ + public string $debug = ""; + public string $hostname = ""; + public bool $imapAddToSent = false; + public string $imapHost = ""; + public string $imapPort = "143/imap/notls"; + public string $imapUsername = ""; + public string $imapPassword = ""; + public string $messageICal = ""; + public string $messageHTML = ""; + public string $messageText = ""; + public string $mimeMail = ""; + public int $priority = 0; + public string $returnPath = ""; + public string $subject = ""; + public bool $useIMAP = false; + + /* SMTP PROPERTIES, PUBLIC PRIVATE & PROTECTED */ + public array $smtpAccount = []; + public bool $smtpDebug = false; + public string $smtpDomain = ""; + private $smtperr = ""; + private $smtperrstr = ""; + public string $smtpFrom = ""; + public string $smtpHost = ""; + public bool $smtpKeepAlive = false; + public array $smtpOptions = []; //["ssl"=>["verify_peer"=>false,"verify_peer_name"=>false,"allow_self_signed"=>true]]; + public string $smtpPassword = ""; + public string $smtpPort = ""; + private $smtpStream = 0; + public string $smtpUsername = ""; + public bool $smtpUseVerp = false; + + /* METHODS ************/ + + function __construct() + { + $this->timeStart = microtime(true); + // Set boundaries + $uniqId = md5(uniqid(time())); + $this->boundary[0] = "P0_" . $uniqId; + $this->boundary[1] = "P1_" . $uniqId; + $this->boundary[2] = "P2_" . $uniqId; + } + + function __destruct() + { + unset($this->cc); + unset($this->bcc); + unset($this->recipients); + unset($this->attachments); + unset($this->messageICal); + unset($this->messageHTML); + unset($this->messageText); + } + + /** + * Adds an attachment from a path on the filesystem. + * Returns false if the file could not be found + * or accessed. + * @param string $path Path to the attachment. + * @param string $name Overrides the attachment name. + * @param string $encoding File encoding (see $Encoding). + * @param string $type File extension (MIME) type. + * @return bool + */ + public function AddAttachment($path, $name = "", $encoding = "base64", $type = "") + { + self::IsExploitPath($path, true); + if ($type == "") { + $type = self::GetMimeType($path); + } + $filename = basename($path); + if ($name == "") { + $name = $filename; + } + $this->attachments[] = [0 => $path, 1 => $filename, 2 => $name, 3 => $encoding, 4 => $type, 5 => false, 6 => "attachment", 7 => 0]; + return true; + } + + /** + * Add a BCC + * @param string $bcc + */ + public function AddBCC($param) + { + $sep = (trim($this->bcc) != "") ? self::MAILSEP : ""; + $this->bcc .= $sep . self::EmailFormatRFC($param); + $sep = (trim($this->recipients_rt) != "") ? self::MAILSEP : ""; + $this->recipients_rt .= $sep . self::EmailExtractEmail($param); + } + + /** + * Add a CC + * @param string $cc + */ + public function AddCC($param) + { + $sep = (trim($this->cc) != "") ? self::MAILSEP : ""; + $this->cc .= $sep . self::EmailFormatRFC($param); + $sep = (trim($this->recipients_rt) != "") ? self::MAILSEP : ""; + $this->recipients_rt .= $sep . self::EmailExtractEmail($param); + } + + /** + * Adds a custom header + */ + public function AddCustomHeader($custom_header) + { + $this->customHeader = explode(":", $custom_header, 2); + } + + /** + * Adds an embedded attachment. This can include images (backgrounds,etc). + * @param string $path Path (location) of attachment. + * @param string $cid Content ID of the attachment. Use to identify + * the Id for accessing the image in an HTML doc. + * @param string $name Overrides the attachment name. + * @param string $encoding Mime encoding. + * @param string $type File extension. + * @return bool + */ + public function AddEmbeddedImage($path, $cid, $name = "", $encoding = "", $type = "", $attach = "") + { + if ($encoding == "") { + $encoding = "base64"; + } + self::IsExploitPath($path, true); + if (!@is_file($path)) { + return false; + } + if ($type == "") { + $type = mime_content_type($path); + } + $filename = basename($path); + if ($name == "") { + $name = $filename; + } + if (!self::IsImageDuplicate(0, $path)) { + // Append to $attachments array + $this->attachments[] = [0 => $path, 1 => $filename, 2 => $name, 3 => $encoding, 4 => $type, 5 => false, 6 => "inline", 7 => $cid]; + // Add second copy as downloadable attachment + if ($attach != "") { + $mimeType = mime_content_type($path); + $this->attachments[] = [0 => $path, 1 => $attach, 2 => $attach, 3 => $encoding, 4 => $mimeType, 5 => false, 6 => "attachment", 7 => 0]; + } + return true; + } + return false; + } + + /** + * Adds email message to IMAP INBOX.Sent + * @param string $message (optional) + * @param string $folder (optional) + * @param string $options (optional) + */ + public function AddMessageToSent($folder = "INBOX.Sent", $options = null) + { + if (!$this->imapAddToSent) { + return; + } + $mailbox = "{" . $this->imapHost . ":" . $this->imapPort . "}" . $folder; + if (!empty($this->imapHost) && !empty($this->imapUsername) && !empty($this->imapPassword)) { + $cnx = @imap_open($mailbox, $this->imapUsername, $this->imapPassword); + $res = imap_append($cnx, $mailbox, $this->mimeMail, null, date("r")); + @imap_errors(); + @imap_alerts(); + @imap_close($cnx); + } + } + + /** + * Add a recipient + * @param string $email + */ + public function AddRecipient($param) + { + $sep = (trim($this->recipients) != "") ? self::MAILSEP : ""; + $this->recipients .= $sep . self::EmailFormatRFC($param); + $sep = (trim($this->recipients_rt) != "") ? self::MAILSEP : ""; + $this->recipients_rt .= $sep . self::EmailExtractEmail(self::EmailFormatRFC($param)); + } + + /** + * Adds a string or binary attachment (non-filesystem) to the list. + * This method can be used to attach ascii or binary data, + * such as a BLOB record from a database. + * @param string $string String attachment data. + * @param string $filename Name of the attachment. + * @param string $encoding File encoding (see $Encoding). + * @param string $type File extension (MIME) type. + * @return void + */ + public function AddStringAttachment($string, $filename, $encoding = "base64", $type = "") + { + if ($type == "") { + self::GetMimeType($string, "string"); + } + $this->attachments[] = [0 => $string, 1 => $filename, 2 => basename($filename), 3 => $encoding, 4 => $type, 5 => true, 6 => "attachment", 7 => 0]; + } + + /** + * Build attachment + * @param string $attachment + * @return string + */ + private function BuildAttachment($attachment = "", $bkey = 0) + { + $mime = $cidUniq = $incl = []; + // add parameter passed in function + if (!empty($attachment) && is_string($attachment)) { + if (self::IsPathSafe($attachment) !== true) { + return false; + } + $mimeType = mime_content_type($attachment); + $fileContent = file_get_contents($attachment); + $fileContent = (!empty($this->encode_hdr)) ? chunk_split(base64_encode($fileContent)) : $fileContent; + $data = "Content-Type: " . $mimeType . "; name=" . basename($attachment) . self::CRLF; + $data .= (!empty($this->encode_hdr)) ? "Content-Transfer-Encoding: " . $this->encode_hdr . self::CRLF : ""; + $data .= "Content-ID: <" . basename($attachment) . ">" . self::CRLF; + $data .= self::CRLF . $fileContent . self::CRLF; + $data = self::GetBoundary($bkey) . $data; + return $data; + } + // Add all other attachments and check for string attachment + $bString = $attachment[5]; + if ($bString) { + $string = $attachment[0]; + } else { + $path = $attachment[0]; + if (self::IsPathSafe($path) !== true) { + return false; + } + } + if (in_array($attachment[0], $incl)) { + return; + } + if (@in_array($path, $incl)) { + return; + } + + $filename = $attachment[1]; + $name = $attachment[2]; + $encoding = $attachment[3]; + $type = $attachment[4]; + $disposition = $attachment[6]; + $cid = $attachment[7]; + $incl[] = $attachment[0]; + + if ($disposition == "inline" && isset($cidUniq[$cid])) { + return; + } + $cidUniq[$cid] = true; + + $mime[] = "Content-Type: " . $type . "; name=\"" . $name . "\"" . self::CRLF; + if (!empty($encoding)) { + $mime[] = "Content-Transfer-Encoding: " . $encoding . self::CRLF; + } + + if ($disposition == "inline") { + $mime[] = "Content-ID: <" . $cid . ">" . self::CRLF; + } + $mime[] = "Content-Disposition: " . $disposition . "; filename=\"" . $name . "\"" . self::CRLF . self::CRLF; + + // Encode as string attachment + if ($bString) { + $str = (!empty($encoding)) ? chunk_split(base64_encode($string), $this->wordWrapLen, self::CRLF) : chunk_split($string, $this->wordWrapLen, self::CRLF); + $mime[] = $str; + $mime[] = self::CRLF . self::CRLF; + } else { + $str = (!empty($encoding)) ? chunk_split(base64_encode(file_get_contents($path)), $this->wordWrapLen, self::CRLF) : chunk_split(file_get_contents($path), $this->wordWrapLen, self::CRLF); + $mime[] = chunk_split(base64_encode(file_get_contents($path)), $this->wordWrapLen, self::CRLF); + $mime[] = self::CRLF . self::CRLF; + } + $data = implode("", $mime); + $data = self::GetBoundary($bkey) . $data; + return $data; + } + + /** + * Build message body + * @return string + */ + private function BuildBody() + { + self::GetMsgType(); + $body = self::GetMsgPart(); + // $body .= self::CRLF; + + if ($this->msgType == "message") { + return $body; + } + // attachment only + elseif ($this->msgType == "attachment") { + foreach ($this->attachments as $attachment) { + if ($attachment[6] === "attachment") { + $body .= self::BuildAttachment($attachment, 0); + } + } + return $body; + } + // message with attachment + elseif ($this->msgType == "attachment|message") { + $body .= self::CRLF; + foreach ($this->attachments as $attachment) { + if ($attachment[6] === "attachment") { + $body .= self::BuildAttachment($attachment, 0); + } + } + $body .= self::GetBoundary(0, "--"); + } + // message with attachment + elseif ($this->msgType == "attachment|inline|message") { + foreach ($this->attachments as $attachment) { + if ($attachment[6] === "inline") { + $body .= self::BuildAttachment($attachment, 1); + } + } + $body .= self::GetBoundary(1, "--"); + $body .= self::CRLF; + foreach ($this->attachments as $attachment) { + if ($attachment[6] === "attachment") { + $body .= self::BuildAttachment($attachment, 0); + } + } + $body .= self::GetBoundary(0, "--"); + $body .= self::CRLF; + } + // message with inline (iCalendar option) + elseif ($this->msgType == "inline|message") { + $body .= self::CRLF; + // inline + foreach ($this->attachments as $attachment) { + if ($attachment[6] === "inline") { + $body .= self::BuildAttachment($attachment, 0); + } + } + $body .= self::GetBoundary(0, "--"); + $body .= self::CRLF; + } + $body = str_replace(self::CRLF . self::CRLF . self::CRLF, self::CRLF . self::CRLF, $body); + return $body; + } + + /** + * Builds message header + * @return string + */ + public function BuildHeader($type = "smtp") + { + $mimeTxt = "This is a multipart message in MIME format." . self::CRLF; + $messageID = md5((idate("U") - 1000000000) . uniqid()) . "@" . self::ServerHostname(); + if (empty($this->sender)) { + if ($this->senderEmail == "root@localhost") { + $this->senderEmail = "noreply@" . self::GetMailServer(); + } + self::SetSender([$this->senderEmail => $this->senderName]); + } + $hdr = "Return-Path: " . ((!empty($this->returnPath)) ? $this->returnPath : $this->sender) . self::CRLF; + $hdr .= "Date: " . date("r") . self::CRLF; + $hdr .= "From: " . $this->sender . self::CRLF; + $hdr .= "Reply-To: " . ((!empty($this->replyTo)) ? $this->replyTo : $this->sender) . self::CRLF; + $hdr .= (!empty($this->cc)) ? "Cc: " . $this->cc . self::CRLF : ""; + $hdr .= (!empty($this->bcc)) ? "Bcc: " . $this->bcc . self::CRLF : ""; + $hdr .= "Message-Id: <" . $messageID . ">" . self::CRLF; + $hdr .= "X-Originating-IP: " . $_SERVER['SERVER_ADDR'] . self::CRLF; + $hdr .= "X-Mailer: PHPMailer Lite v" . PHPMailerLite::VERSION . " " . $this->transport . " (https://phpmailer.pro/)" . self::CRLF; + $hdr .= ($this->priority !== 0) ? "X-Priority: " . $this->priority . self::CRLF : ""; + for ($index = 0; $index < count($this->customHeader); $index++) { + $hdr .= trim($this->customHeader[$index][0]) . ": " . self::MbEncode(trim($this->customHeader[$index][1])) . self::CRLF; + } + if ($this->transport == "smtp" || $this->transport == "sendmail") { + $hdr .= "To: " . $this->recipients . self::CRLF; + $hdr .= "Subject: " . self::MbEncode($this->subject) . self::CRLF; + } + $hdr .= "MIME-Version: 1.0" . self::CRLF; + if (stripos($this->msgType, "attachment") !== false) { + $hdr .= self::GetContentTypeHdr("multipart/mixed", 0) . self::CRLF; + $hdr .= $mimeTxt; + if (stripos($this->msgType, "inline") !== false) { + // sub header for inline + $hdr .= self::GetBoundary(0); + $hdr .= self::GetContentTypeHdr("multipart/related", 1) . self::CRLF; + $hdr .= self::CRLF; + $hdr .= self::GetBoundary(1); + $hdr .= self::GetContentTypeHdr("multipart/alternative", 2); + $hdr .= self::CRLF; + } else { + // sub header for message + $hdr .= self::GetBoundary(0); + $hdr .= self::GetContentTypeHdr("multipart/alternative", 1); + $hdr .= self::CRLF; + } + } elseif (stripos($this->msgType, "inline") !== false) { + $hdr .= self::GetContentTypeHdr("multipart/related", 0) . self::CRLF; + $hdr .= $mimeTxt; + // sub header for message + $hdr .= self::GetBoundary(0); + $hdr .= self::GetContentTypeHdr("multipart/alternative", 1) . self::CRLF; + $hdr .= self::CRLF; + } elseif ($this->msgType == "message") { + $hdr .= self::GetContentTypeHdr("multipart/alternative", 0) . self::CRLF; + $hdr .= $mimeTxt; + } + // message only + return $hdr; + } + + /** + * Sets the HTML message and returns modifications for inline images and backgrounds + * will also set text message if it does not exist (can over ride) + * @param string $content content of the HTML message + * @param string $basedir directory to the location of the images (relative to file) + */ + private function DataToHTML($content, $basedir = "") + { + if (is_file($content)) { + self::IsExploitPath($content, true); + $thisdir = (dirname($content) != "") ? rtrim(dirname($content), '/') . '/' : ""; + $basedir = ($basedir == "") ? $thisdir : ""; + $content = file_get_contents($content); + } + preg_match_all("/(src|background)=\"(.*)\"/Ui", $content, $images); + if (!empty($images[2])) { + foreach ($images[2] as $i => $url) { + if (!preg_match('#^[A-z]+://#', $url)) { + if ($basedir != "") { + $url = rtrim($basedir, '/') . '/' . $url; + } + $filename = basename($url); + $directory = dirname($url); + $cid = "cid:" . md5($filename); + if ($directory == ".") { + $directory = ""; + } + $mimeType = mime_content_type($url); + if (strlen($directory) > 1 && substr($directory, -1) != '/') { + $directory .= '/'; + } + self::IsExploitPath($directory . $filename, true); + if (self::AddEmbeddedImage($directory . $filename, md5($filename), $filename, "base64", $mimeType)) { + $content = preg_replace("/" . $images[1][$i] . "=\"" . preg_quote($images[2][$i], '/') . "\"/Ui", $images[1][$i] . "=\"" . $cid . "\"", $content); + } + } + } + } + $this->messageHTML = $content; + } + + /** + * input string or array containing email addresses (separated by comma) + * in almost any format - can be single address or multiple + * with or without correct spacing, quote marks + * removes items without emails + * returns RFC 5322 formatted string + * @var string or array + * @return string + */ + private function EmailFormatRFC($data) + { + $rz = ""; + if (is_string($data)) { + foreach ((explode(",", $data)) as $key => $val) { + $val = trim($val); + preg_match('/[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,9}/', $val, $bk); + if (filter_var($bk[0], FILTER_VALIDATE_EMAIL)) { + $email = trim($bk[0]); + $name = $val; + if (!empty($email)) { + if (!empty($name)) { + $rz .= self::EmailExtractName($name) . " "; + } + $rz .= "<" . self::EmailExtractEmail($email) . ">, "; + } + } + } + return rtrim($rz, ", "); + } else { + foreach ($data as $key => $val) { + $name = $email = ""; + if (is_array($val)) { + $kkey = trim(str_replace(["<", ">"], "", key($val))); + $vval = trim(str_replace(["<", ">"], "", $val[$kkey])); + if (filter_var($kkey, FILTER_VALIDATE_EMAIL)) { + $email = "<" . $kkey . ">"; + $name = ($vval != "") ? $vval : ""; + } elseif (filter_var($vval, FILTER_VALIDATE_EMAIL)) { + $email = "<" . $vval . ">"; + $name = ($kkey != "") ? $kkey : ""; + } + if (!empty($email)) { + if (!empty($name)) { + $rz .= self::EmailExtractName($name) . " "; + } + $rz .= "<" . self::EmailExtractEmail($email) . ">, "; + } + } else { + if (is_numeric($key) && str_contains($val, "<")) { + $t = explode("<", $val); + $key = trim($t[0]); + $val = trim(str_replace(["<", ">"], "", $t[1])); + } + $key = trim($key); + $val = trim($val); + if (filter_var($key, FILTER_VALIDATE_EMAIL)) { + $email = $key; + $name = ($val != "") ? $val : ""; + } elseif (filter_var($val, FILTER_VALIDATE_EMAIL)) { + $email = $val; + $name = (!is_numeric($key) && $key != "") ? $key : ""; + } + if (!empty($email)) { + if (!empty($name)) { + $rz .= self::EmailExtractName($name) . " "; + } + $rz .= "<" . self::EmailExtractEmail($email) . ">, "; + } + } + } + return rtrim($rz, ", "); + } + } + + /** + * extracts email address from a string + * returns clean (shell safe) email address (WITHOUT TOKENS) + * @var string + * @return string + */ + private function EmailExtractEmail($str) + { + if (!empty($str) && is_string($str)) { + preg_match('/[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,9}/', $str, $bk); + if (!empty($bk[0])) { + $email = $bk[0]; + } + $email = str_ireplace(["\r", "\n", "\t", '"', ",", "<", ">"], "", $email); + if (filter_var($email, FILTER_VALIDATE_EMAIL) && self::IsValidEmail($email)) { + return $email; + } + } + return false; + } + + /** + * extracts name portion from string email address + * returns clean (shell safe) name + * @var string + * @return string + */ + private function EmailExtractName($str) + { + if (trim($str) == "") { + return; + } + $pattern = '/[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,9}/'; + preg_match($pattern, $str, $match); + $addy = (!empty($match[0])) ? $match[0] : ""; + return trim(str_ireplace([$addy, "<", ">", "[", "]", "\r", "\n", "\t"], "", $str)); + } + + /** + * Changes all CRLF or CR to LF + * @return string + */ + private function FixCRLF($str) + { + return str_ireplace(["\r\n", "\r", "\n"], self::CRLF, $str); + } + + /** + * Creates the boundary line / end boundary line + * @param string $type = wrap, body, spec, none + * @param string $end (optional, triggers adding two dashes at end) + * @return string (boundary line) + */ + private function GetBoundary($bkey, $end = "") + { + return "--" . $this->boundary[$bkey] . $end . self::CRLF; + } + + /** + * Creates the Content-Type directive for the body + * @param string $type = multipart/mixed / multipart/related / multipart/alternative + * @param string $charset + * @param string $encoding + * @param string $cid (optional) + * @return string (content type line) + */ + private function GetContentTypeBody($type, $charset, $encoding, $cid = "") + { + $data = "Content-Type: " . $type . ";" . self::CRLF; + $data .= "\t" . $charset . self::CRLF; + $data .= "Content-Transfer-Encoding: " . $encoding . self::CRLF; + if ($cid != "") { + $data .= "Content-ID: <" . $cid . ">" . self::CRLF; + } + return $data; + } + + /** + * Creates the Content-Type directive for the header + * type = multipart/mixed / multipart/related / multipart/alternative + * bkey = boundary (wrap / body / spec) + * @return string (content type line) + */ + private function GetContentTypeHdr($type, $bkey) + { + $data = "Content-Type: " . $type . ";" . self::CRLF; + return $data . "\t" . "boundary=\"" . $this->boundary[$bkey] . "\""; + } + + /** + * dual use method + * 1- with only $url passed, either a host or path (string) and returns the MX record domain name + * 2- with a fully qualified mail server passed, returns true/false if an MX record matches + * @param string $url + * @param string $validate + * @return string (mail server) (if $validate is 'no_test' and mail server found) + * @return bool (if no mail server found) + */ + private function GetMailServer($url = "", $validate = "no_test") + { + if (empty($url)) { + $url = $_SERVER['SERVER_NAME']; + } + $tld = substr($url, strpos($url, ".") + 1); + if ($validate === "is_valid") { + $url = $tld; + } + if (preg_match('/(?P[a-z0-9][a-z0-9\-]{1,63}\.[a-z\.]{2,6})$/i', $tld, $match)) { + getmxrr($match['domain'], $mx_details); + if (is_array($mx_details) && count($mx_details) > 0) { + if ($validate === "is_valid") { + if ($url == reset($mx_details)) { + return true; + } + } else { + return reset($mx_details); + } + } + } + return false; + } + + /** + * Gets MIME type of file or string + * if file: USE ONLY AFTER VERIFYING FILE EXISTS + * if string: designed for file data read in as string, will not + * properly detect html vs text + * returns 'application/octet-stream' if not found (or file encoded) + * @param string $resource (filename or string) + * @param string $type ('string' or 'file', defaults to 'file') + * @return string + */ + private function GetMimeType($resource, $type = "file") + { + if ($type == "string") { + if (function_exists('finfo_buffer') && function_exists('finfo_open') && defined('FILEINFO_MIME_TYPE')) { + return finfo_buffer(finfo_open(FILEINFO_MIME_TYPE), $resource); + } + } else { + if (function_exists('finfo_file') && function_exists('finfo_open') && defined('FILEINFO_MIME_TYPE')) { + return finfo_file(finfo_open(FILEINFO_MIME_TYPE), $resource); + } + return mime_content_type($resource); + } + return "application/octet-stream"; + } + + /** + * Builds plain text and HTML portion of message + * @return string + */ + protected function GetMsgPart($bkey = "") + { + $data = $html = ""; + $bkey = 0; + if (!empty($this->messageICal)) { + if (@is_file($this->messageICal)) { + self::IsExploitPath($this->messageICal, true); + $thisdir = (dirname($this->messageICal) != "") ? rtrim(dirname($this->messageICal), '/') . '/' : ""; + $string = file_get_contents($this->messageICal); + $filename = basename($this->messageICal); + } else { + $string = $this->messageICal; + $filename = basename("calendar.ics"); + } + self::AddStringAttachment($string, $filename, "base64", "text/calendar"); + } + if (trim($this->messageHTML) != "") { + if (is_file($this->messageHTML)) { + self::IsExploitPath($this->messageHTML, true); + $thisdir = (dirname($this->messageHTML) != "") ? rtrim(dirname($this->messageHTML), '/') . '/' : ""; + self::DataToHTML(file_get_contents($this->messageHTML), $thisdir); + } + self::GetMsgType(); + if ( + stripos($this->msgType, "attachment") !== false && + stripos($this->msgType, "inline") !== false + ) { + $bkey = 2; + } elseif ( + stripos($this->msgType, "attachment") !== false || + stripos($this->msgType, "inline") !== false + ) { + $bkey = 1; + } + $html .= self::GetBoundary($bkey); + $html .= self::GetContentTypeBody("text/html", "charset=\"" . $this->charSet . "\"", "base64") . self::CRLF; + $html .= base64_encode($this->messageHTML) . self::CRLF . self::CRLF; + } + $data .= self::CRLF; + $data .= self::GetBoundary($bkey); + $data .= self::GetContentTypeBody("text/plain", "charset=\"" . $this->charSet . "\"", "7bit"); + $data .= self::CRLF; + $data .= ((trim($this->messageText) != "") ? self::WrapText($this->messageText, $this->wordWrapLen) : "") . self::CRLF; + $data .= self::CRLF; + $data .= self::CRLF; + $data .= $html; + return $data . self::GetBoundary($bkey, "--"); + } + + /** + * Gets email message type + * @return string + */ + protected function GetMsgType($type = "") + { + if (is_string($type) && !empty($type)) { + $type = explode("|", rtrim($type, "|") . "|"); + } else { + $type = []; + } + if (!in_array("message", $type) && ($this->messageHTML != "" || $this->messageText != "")) { + $type[] = "message"; + } + foreach ($this->attachments as $attachment) { + if ($attachment[6] === "inline" && !in_array("inline", $type)) { + $type[] = "inline"; + } elseif ($attachment[6] === "attachment" && !in_array("attachment", $type)) { + $type[] = "attachment"; + } + } + if (count($type) == 0) { + $type[] = "inline"; + } + sort($type); + $this->msgType = implode("|", $type); + } + + /** + * Check the attachments array for a duplicate image + * will not add if duplicate exists + * @param string $id (attachments[0]) + * @param string $param value + * @return bool + */ + private function IsImageDuplicate($id, $param) + { + if (isset($this->attachments) && ($this->attachments) > 0) { + foreach ($this->attachments as $key => $val) { + if ($val[$id] === $param) { + return $key; + } + } + return false; + } + } + + /** + * Checks string for multibyte characters + * @param $str string + * @return bool (true if multibyte) + */ + private function IsMultibyte($str) + { + return(mb_strlen($str) != strlen($str)) ? true : false; + } + + /** + * Check if file path is safe (real, accessible). + * @param string $path Relative or absolute path to a file + * @return bool + */ + protected function IsPathSafe($path, $opt_exit = false) + { + // path decode (note %00 - null - removed in decode) + for ($i = 0; $i <= substr_count($path, "%"); $i++) { + $path = rawurldecode($path); + } + // convert all slashes to system default + $path = str_replace(["\\", "/"], DIRECTORY_SEPARATOR, $path); + if (!is_dir($path) && file_exists($path)) { + return true; + } + // check for other exploits + if (self::IsExploitPath($path)) { + return false; + } + // check for path traversal + if (strpos(realpath($path), $_SERVER['DOCUMENT_ROOT']) === false) { + if ($opt_exit) { + exit('Cannot execute
'); + } + return false; + } + // check for valid path, path/file + if (is_file($path)) { + $path = str_replace(basename($path), "", $path); + } + $realPath = str_replace(rtrim($_SERVER['DOCUMENT_ROOT'] . dirname($_SERVER['PHP_SELF']), '/') . '/', "", realpath($path)); + if (strpos($path, "/")) { + $realPath = rtrim($realPath, '/') . '/'; + } + if (($path === false) || (strcmp(rtrim($path, '/'), rtrim($realPath, '/')) !== 0)) { + return false; + } + return(file_exists($path) && is_readable($path) && is_dir($path)) ? true : false; + } + + /** + * Prevent attacks by disallowing unsafe shell characters. + * Modified version (Thanks to Paul Buonopane ) + * Modification: CRITICAL STOP on NOT safe + * @param string $email (the string to be tested for shell safety) + * @return bool + */ + protected function IsShellSafe($email) + { + $safe = true; + if (empty(trim($email))) { + $safe = false; + } + if ($safe && function_exists('ctype_alnum')) { + for ($i = 0; $i < strlen($email); $i++) { + $chr = $email[$i]; + if (!ctype_alnum($chr) && strpos('@_-.', $chr) === false) { + $safe = false; + } + } + } + if ($safe) { + $safe = (bool) preg_match('/\A[\pL\pN._@-]*\z/ui', $email); + } + if ($safe === false) { + exit("Cannot Process: Email Address failed shell safe validation
"); + } + return true; + } + + /** + * Check if path (file) exists and is readable + * @var string $path + * @return bool + */ + private function IsExploitPath($path, $optExit = false) + { + $isExploit = (is_file($path) && is_readable($path)) ? false : true; + if ($isExploit && $optExit !== false) { + echo 'path: ' . $path . '
'; + exit("Unable to send, access not allowed"); + } + return $isExploit; + } + + /** + * Validate an email address, probably the most robust validator available + * @param string $email The email address to check + * @return boolean + */ + private function IsValidEmail($email) + { + $temp = explode("@", $email); + $domn = array_pop($temp); + $rz = true; + if (!filter_var($email, FILTER_VALIDATE_EMAIL)) { + $rz = false; + } + if ($rz) { + $rz = false; + $check = @fsockopen($domn, 80, $errno, $errstr, 1); + if ($check) { + $rz = true; + @fclose($check); + } + } + if ($rz) { + $rz = (bool) preg_match('/^(?!(?:(?:\x22?\x5C[\x00-\x7E]\x22?)|(?:\x22?[^\x5C\x22]\x22?)){255,})(?!(?:(?:\x22?\x5C[\x00-\x7E]\x22?)|(?:\x22?[^\x5C\x22]\x22?)){65,}@)(?:(?:[\x21\x23-\x27\x2A\x2B\x2D\x2F-\x39\x3D\x3F\x5E-\x7E]+)|(?:\x22(?:[\x01-\x08\x0B\x0C\x0E-\x1F\x21\x23-\x5B\x5D-\x7F]|(?:\x5C[\x00-\x7F]))*\x22))(?:\.(?:(?:[\x21\x23-\x27\x2A\x2B\x2D\x2F-\x39\x3D\x3F\x5E-\x7E]+)|(?:\x22(?:[\x01-\x08\x0B\x0C\x0E-\x1F\x21\x23-\x5B\x5D-\x7F]|(?:\x5C[\x00-\x7F]))*\x22)))*@(?:(?:(?!.*[^.]{64,})(?:(?:(?:xn--)?[a-z0-9]+(?:-[a-z0-9]+)*\.){1,126}){1,}(?:(?:[a-z][a-z0-9]*)|(?:(?:xn--)[a-z0-9]+))(?:-[a-z0-9]+)*)|(?:\[(?:(?:IPv6:(?:(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){7})|(?:(?!(?:.*[a-f0-9][:\]]){7,})(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){0,5})?::(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){0,5})?)))|(?:(?:IPv6:(?:(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){5}:)|(?:(?!(?:.*[a-f0-9]:){5,})(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){0,3})?::(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){0,3}:)?)))?(?:(?:25[0-5])|(?:2[0-4][0-9])|(?:1[0-9]{2})|(?:[1-9]?[0-9]))(?:\.(?:(?:25[0-5])|(?:2[0-4][0-9])|(?:1[0-9]{2})|(?:[1-9]?[0-9]))){3}))\]))$/iD', $email); + } + return $rz; + } + + /** + * Encodes and wraps long multibyte strings for mail headers + * without breaking lines within a character + * validates $str as multibyte + * @param string $str multi-byte string to encode + * @return string + */ + private function MbEncode($str, $len = 76) + { + $str = self::SafeStr($str); + if (mb_strlen($str) != strlen($str)) { + return $str; + } + if (function_exists('mb_internal_encoding') && function_exists('mb_encode_mimeheader')) { + mb_internal_encoding("UTF-8"); + return mb_encode_mimeheader($str, "UTF-8"); + } else { + $prefs = ["scheme" => "Q", "input-charset" => "utf-8", "output-charset" => "utf-8", "line-length" => $len]; + return iconv_mime_encode($str, $prefs); + } + } + + /** + * Filter data (ascii and url-encoded) to prevent header injection + * @param string $str String + * @return string (trimmed) + */ + private function SafeStr($str) + { + return trim(str_ireplace(["\r", "\n", "%0d", "%0a", "Content-Type:", "bcc:", "to:", "cc:"], "", $str)); + } + + /** + * Send the email + * @return bool + */ + public function Send($via = "smtp") + { + if ($via != "") { + $via = strtolower($via); + } + if (!in_array($via, $this->transports)) { + exit($via . " Transport Not Available"); + } + $this->transport = $via; + if ($this->recipients == "" && $this->bcc != "") { + $this->recipients = "undisclosed-recipients:"; + } + $ret = false; + if (empty($this->sender)) { + if ($this->senderEmail == "root@localhost") { + $this->senderEmail = "noreply@" . self::GetMailServer(); + } + self::SetSender([$this->senderEmail => $this->senderName]); + } + if (trim($this->returnPath) == "") { + $this->returnPath = self::EmailExtractEmail($this->sender); + } + if (empty(trim($this->sender)) || empty(trim($this->recipients))) { + exit("✗ Critical error: missing Sender and/or recipients.
" . self::CRLF); + } + $body = self::BuildBody(); + // order of Sending preference: SMTP, Sendmail, IMAP, PHP Mail() + if ($this->transport == "smtp") { + $hdr = self::BuildHeader("smtp"); + $ret = self::TransportSMTP($hdr, $body); + $this->mimeMail = $hdr . self::CRLF . $body; + } + if ( + $this->transport == "sendmail" || + ($ret === false && $this->transport == "smtp") + ) { + $ret = self::TransportSendmail($body); + } + if ( + $this->transport == "imap" || + ($ret === false && ($this->transport == "sendmail" || $this->transport == "smtp")) + ) { + $hdr = self::BuildHeader("imap"); + $ret = self::TransportIMAP($hdr, $body); + $this->mimeMail = $hdr . self::CRLF . $body; + } + if ($ret === true) { + self::AddMessageToSent("INBOX.Sent"); + } + return $ret; + } + + /** + * IMAP transport ONLY + * Security to ALL the data and email addresses + * must occur BEFORE calling this function + * @return bool + */ + protected function TransportIMAP($hdr, $body) + { + if (empty(trim($this->sender)) || empty(trim($this->recipients))) { + return false; + } + self::GetMsgType($this->msgType); + if ($this->mimeMail == "") { + $this->mimeMail = $hdr . self::CRLF . $body; + } + $to = $this->recipients; + $subject = self::MbEncode($this->subject); + $cc = (!empty($this->cc)) ? $this->cc : null; + $bcc = (!empty($this->bcc)) ? $this->bcc : null; + $rpath = $this->sender; + $ret = imap_mail($to, $subject, $body, $hdr, $cc, $bcc, $rpath); + return $ret; + } + + /* Sendmail transport ONLY + * Security to ALL the data and email addresses + * must occur BEFORE calling this function + * @return bool + */ + protected function TransportSendmail($body) + { + $ret = false; + $this->sendmailPath = ini_get("sendmail_path"); + if (!empty($this->sendmailPath)) { + $opt = [0 => ["pipe", "r"], 1 => ["pipe", "w"], 2 => ["pipe", "w"]]; + $this->sendmailPath = str_replace([" -o "," -i "]," -oi ", $this->sendmailPath); + $command = $this->sendmailPath . " -f" . self::EmailExtractEmail($this->sender); + if (function_exists('proc_open')) { + $hdr = self::BuildHeader('proc'); + $hndl = proc_open($command, $opt, $bk); + if (is_resource($hndl)) { + fwrite($bk[0], $hdr); + fwrite($bk[0], $body . self::CRLF); + fclose($bk[0]); + if (proc_close($hndl) != -1) { + $ret = true; + } + } + } + } + $this->mimeMail = $hdr . self::CRLF . $body; + return $ret; + } + + /* SMTP transport ONLY + * Security to ALL the data and email addresses + * must occur BEFORE calling this function + * @return bool + */ + protected function TransportSMTP($hdr, $body) + { + if (!is_null($this->debug)) { + $this->smtpDebug = $this->debug; + } + if ($this->smtpDebug == 1) { + $this->smtpDebug = true; + } + if (empty($this->smtpDomain)) { + $this->smtpDomain = self::GetMailServer(); + } + if (empty(trim($this->sender)) || empty(trim($this->recipients))) { + return false; + } + if (self::SMTPconnect() === false) { + return false; + } + if (self::SMTPenvelope() === false) { + return false; + } + if (self::SMTPdata($hdr, $body) === false) { + return false; + } + if (is_resource($this->smtpStream)) { + if ($this->smtpKeepAlive == true) { + self::SMTPreset(); + } + } else { + return false; + } + return true; + } + /* */ + + /** + * Returns server hostname or 'localhost.localdomain' if unknown + * @return string + */ + private function ServerHostname() + { + if (!empty($this->hostname)) { + return $this->hostname; + } elseif (isset($_SERVER['SERVER_NAME'])) { + return $_SERVER['SERVER_NAME']; + } + return "localhost.localdomain"; + } + + /** + * Set plain text + * @param string $content + */ + public function SetBodyText($content) + { + $this->messageText = $content; + } + + /** + * Set email address for confirm email is read + * @param string $email The email address + */ + public function SetConfirmRead($param) + { + $this->confirm_read = self::EmailFormatRFC($param); + } + + /** + * Set email address for confirm email is received + * @param string $email The email address + */ + public function SetConfirmReceipt($param) + { + $this->confirm_rcpt = self::EmailFormatRFC($param); + } + + /* + * Set priority + * @param integer $param - from 1 (highest) to 5 (lowest) + * @return bool + */ + public function SetPriority($param) + { + return(!intval($param)) ? false : $this->priority = intval($param); + } + + /** + * Set replyTo + * @param string $email + * @return bool + */ + public function AddReplyTo($param) + { + $this->replyTo = self::EmailFormatRFC($param); + return true; + } + + /** + * Set sender + * @param mixed (string or array) $email/$name + */ + public function SetSender($param) + { + $tmpAddy = self::EmailFormatRFC($param); + $senderName = self::EmailExtractName($tmpAddy); + $senderEmail = self::IsShellSafe(self::EmailExtractEmail($tmpAddy)); + $tmpAddy = self::EmailFormatRFC([$senderName => $senderEmail]); + $this->sender = $this->replyTo = $this->returnPath = self::EmailFormatRFC($param); + unset($tmpAddy); + + + + $this->sender = self::EmailFormatRFC($param); + $this->smtpFrom = self::EmailExtractEmail($this->sender); + } + + /** + * Set subject + * @param string $param The subject of the email + */ + public function SetSubject($param) + { + $this->subject = $param; + } + + /** + * Uses SMTP transport by default, set to false to use Sendmail as default + * @param bool + */ + public function useIMAP($param = true) + { + $this->useIMAP = ($param === true) ? true : false; + } + + /** + * Wraps message for use with mailers that do not automatically + * perform wrapping + * @param string $message The message to wrap + * @param integer $length The line length to wrap to + * @return string + */ + public function WrapText($message, $length) + { + $message = self::FixCRLF($message); + if (substr($message, -1) == self::CRLF) { + $message = substr($message, 0, -1); + } + $line = explode(self::CRLF, $message); + $message = ""; + for ($i = 0; $i < count($line); $i++) { + $line_part = explode(" ", $line[$i]); + $buf = ""; + for ($e = 0; $e < count($line_part); $e++) { + $word = $line_part[$e]; + $buf_o = $buf; + $buf .= ($e == 0) ? $word : (" " . $word); + + if (strlen($buf) > $length and $buf_o != "") { + $message .= $buf_o . "\n"; + $buf = $word; + } + } + $message .= $buf . self::CRLF; + } + return $message; + } + + /* END - METHODS ************/ + + /* SMTP METHODS ************/ + + /** + * Sets SMTP Account (Username and password) + * @return mixed + */ + public function SetSMTPaccount($array) + { + $pwd = trim(reset($array)); + $uname = (is_numeric(key($array))) ? $pwd : trim(key($array)); + $this->smtpUsername = $uname; + $this->smtpPassword = $pwd; + } + + /** + * Set SMTP host + * @param string $param + */ + public function SetSMTPhost($param) + { + $this->smtpHost = self::SafeStr($param); + } + + /** + * Set SMTP port + * @param integer $param + */ + public function SetSMTPport($param) + { + $this->smtpPort = self::SafeStr($param); + } + + /** + * Set SMTP password + * @param string $param + */ + public function SetSMTPpass($param) + { + $this->smtpPassword = $param; + } + + /** + * Set SMTP username + * @param string $param + */ + public function SetSMTPuser($param) + { + $this->smtpUsername = self::SafeStr($param); + } + + /** + * Connect to the server + * return code: 220 success + * @return bool + */ + private function SMTPconnect() + { + // check if already connected + if ($this->smtpStream) { + return false; + } + // check for host + if (!empty($this->smtpHost)) { + if (self::GetMailServer($this->smtpHost, "is_valid") === false) { + exit(__LINE__ . " " . "✗ Critical error: invalid SMTP server.
" . self::CRLF); + } + $host_name = $this->smtpHost; + $server_arr = [$this->smtpHost]; + } else { + $host_name = $this->smtpDomain; + $server_arr = [$this->smtpDomain]; + } + // check for port + if (!empty($this->smtpPort)) { + $srv_ports = [$this->smtpPort]; + } else { + $srv_ports = [587, 2525, 25]; + } + // connect to the smtp server + $connect_options = $this->smtpOptions; + $create_options = (!empty($connect_options)) ? stream_context_create($connect_options) : null; + foreach ($server_arr as $host) { + if (empty($code) || $code != "220") { + foreach ($srv_ports as $port) { + if (function_exists('stream_socket_client')) { + $this->smtpStream = @stream_socket_client($host . ":" . $port, $errno, $errstr, self::TIMEVAL, STREAM_CLIENT_CONNECT, $create_options); + } else { + $this->smtpStream = @fsockopen($host, $port, $errno, $errstr, self::TIMEVAL); + } + if ($errno != "") { + $this->smtperr = $errno; + } + if ($errstr != "") { + $this->smtperrstr = $errstr; + } + if (!$this->smtpStream) { + return false; + } + $this->smtpHost = $host; + $this->smtpPort = $port; + $code = self::SMTPgetResponse(['220'], 'SMTP CONNECT'); + if ($code == "220") { + break; + } + } + } else { + break; + } + } + if ($code == "220") { + stream_set_timeout($this->smtpStream, self::TIMEVAL); + return $this->smtpStream; + } + return false; + } + + /** + * Sends header and message to SMTP Server + * return code: 250 success (possible 251, have to allow for this) + * @return bool + */ + private function SMTPdata($hdr, $body) + { + if (is_resource($this->smtpStream)) { + self::SMTPisStreamConnected(); + // initiate DATA stream + fwrite($this->smtpStream, "DATA" . self::CRLF); + $code = self::SMTPgetResponse(['354'], 'DATA HEADER'); + // send the header + $hdr_arr = self::StrSplitUnicode($hdr); + foreach ($hdr_arr as $line) { + fwrite($this->smtpStream, $line . self::CRLF); + } + // send the body + $body_arr = self::StrSplitUnicode($body); + foreach ($body_arr as $line) { + fwrite($this->smtpStream, $line . self::CRLF); + } + // end DATA stream + fwrite($this->smtpStream, "." . self::CRLF); + $code = self::SMTPgetResponse(['250'], 'DATA'); + return true; + } + return false; + } + + /** + * Send envelope to SMTP Server + * @return bool + */ + private function SMTPenvelope() + { + if (is_resource($this->smtpStream)) { + // send EHLO command + fwrite($this->smtpStream, "EHLO " . $this->smtpHost . self::CRLF); + $code = self::SMTPgetResponse(['250'], 'EHLO'); + self::SMTPisStreamConnected(); + // send STARTTLS command + fwrite($this->smtpStream, "STARTTLS" . self::CRLF); + $code = self::SMTPgetResponse(['220'], 'STARTTLS'); + // initiate secure tls encryption + $crypto_method = STREAM_CRYPTO_METHOD_TLS_CLIENT; + if (defined('STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT')) { + $crypto_method = STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT; + } elseif (defined('STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT')) { + $crypto_method = STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT; + } elseif (defined('STREAM_CRYPTO_METHOD_TLSv1_0_CLIENT')) { + $crypto_method = STREAM_CRYPTO_METHOD_TLSv1_0_CLIENT; + } + stream_socket_enable_crypto($this->smtpStream, true, $crypto_method); + // resend EHLO after tls negotiation + fwrite($this->smtpStream, "EHLO " . $this->smtpHost . self::CRLF); + $code = self::SMTPgetResponse(['250'], 'EHLO'); + self::SMTPisStreamConnected(); + if (!empty($this->smtpUsername) && !empty($this->smtpUsername)) { + // Authenticate + fwrite($this->smtpStream, "AUTH LOGIN" . self::CRLF); + $code = self::SMTPgetResponse(['334'], 'AUTH LOGIN'); + // Send encoded username + fwrite($this->smtpStream, base64_encode($this->smtpUsername) . self::CRLF); + $code = self::SMTPgetResponse(['334'], 'USER'); + // Send encoded password + fputs($this->smtpStream, base64_encode($this->smtpPassword) . self::CRLF); + $code = self::SMTPgetResponse(['235'], 'PASSWORD'); + } + self::SMTPisStreamConnected(); + fwrite($this->smtpStream, "MAIL FROM: <" . $this->smtpFrom . ">" . (($this->smtpUseVerp) ? "XVERP" : "") . self::CRLF); + $code = self::SMTPgetResponse(['250'], 'MAIL FROM'); + self::SMTPisStreamConnected(); + if (strpos($this->recipients_rt, ",") !== false) { + $emails = explode(",", $this->recipients_rt); + foreach ($emails as $email) { + fwrite($this->smtpStream, "RCPT TO: <" . trim($email) . '>' . self::CRLF); + $code = self::SMTPgetResponse(['250', '251'], 'RCPT TO'); + } + } else { + fwrite($this->smtpStream, "RCPT TO: <" . trim($this->recipients_rt) . '>' . self::CRLF); + $code = self::SMTPgetResponse(['250', '251'], 'RCPT TO'); + } + return true; + } + return false; + } + + /** + * Get response code returned by SMTP server + * @return string $code + */ + private function SMTPgetResponse($expected, $desc = "") + { + $total = ""; + if ($desc == "SMTP CONNECT" && @$errstr != "") { + $data = $this->smtperrstr; + $code = $this->smtperr; + } elseif ($desc == "CLOSE CONNECTION") { + $data = "Connection closed (true)"; + $code = $this->smtpStream; + } else { + $data = fread($this->smtpStream, 8192); + $code = substr($data, 0, 3); + } + if ($this->smtpDebug === true) { + $data = str_ireplace(["\r\n", "\r", "\n"], "\n", $data); + $data = str_replace([$code . "-", $code . " "], "", $data); + $data = trim(str_replace(" ", " ", $data)); + $data = str_ireplace("\n", "\n" . str_repeat(" ", 4), $data); + $data = trim(str_ireplace("\n", "
", $data)); + } + + if ($desc == "SMTP CONNECT" && $code == "220") { + if ($this->smtpDebug === true) { + echo self::ThrowResponse("TRUE", "", "Server: " . $this->smtpHost . ", Port: " . $this->smtpPort . "
", true); + } + } + + if (in_array($code, $expected) && $this->smtpDebug === true) { + echo self::ThrowResponse($code, $desc, $data, true); + } elseif (!in_array($code, $expected)) { + echo self::ThrowResponse($code, $desc, $data, false); + } + if ($this->smtpDebug === true) { + if (trim($this->timeStart) != "") { + if ($desc == "CLOSE CONNECTION") { + $total = "Total "; + } + echo "
" . str_repeat(" ", 4) . $total . "Elapsed time: " . number_format(microtime(true) - $this->timeStart, 3) . " sec.
"; + } + } + return $code; + } + + /** + * Returns true if connected to a server otherwise false + * @access public + * @return bool + */ + private function SMTPisStreamConnected($error_msg = "Not connected to SMTP server, aborting.") + { + if (!empty($this->smtpStream)) { + $status = socket_get_status($this->smtpStream); + if ($status["eof"]) { + fclose($this->smtpStream); + $this->smtpStream = 0; + } else { + return true; + } + } + self::ThrowResponse("0", "CRITICAL ERROR", $error_msg, false); + return false; + } + + /** + * Sends QUIT to SMTP Server then closes the stream + * return code: 221 success + * @return bool + */ + private function SMTPquit() + { + self::SMTPisStreamConnected(); + fwrite($this->smtpStream, "QUIT" . self::CRLF); + $code = self::SMTPgetResponse(['221'], 'QUIT'); + // close the connection and reset the stream value + if (!empty($this->smtpStream)) { + fclose($this->smtpStream); + $code = self::SMTPgetResponse(['1'], 'CLOSE CONNECTION'); + $this->smtpStream = 0; + } + return true; + } + + /** + * Sends smtp command RCPT TO + * Returns true if recipient (email) accepted (false if not) + * return code: 250 success (possible 251, have to allow for this) + * @return bool + */ + public function SMTPrecipient($param) + { + $addresses = []; + if (is_string($param) && strpos($param, ',') !== false) { + $addresses = explode(',', $param); + } elseif (is_string($param)) { + $addresses[] = $param; + } + $emails = []; + foreach ($addresses as $key => $val) { + $emails[] = self::EmailExtractEmail($val); + } + foreach ($emails as $email) { + fwrite($this->smtpStream, "RCPT TO: <" . $email . ">" . self::CRLF); + $code = self::SMTPgetResponse(['250', '251'], 'RCPT TO'); + } + } + + /* Send RSET (aborts any transport in progress and keeps connection alive) + * Implements RFC 821: RSET + * return code 250 success + * @return bool + */ + private function SMTPreset() + { + self::SMTPisStreamConnected("Called SMTP_KeepAlive without connection."); + fwrite($this->smtpStream, "RSET" . self::CRLF); + $code = self::SMTPgetResponse(['250'], 'RSET'); + return true; + } + + /** + * splits a string into an array of max length $l + * @return array + */ + private function StrSplitUnicode($str, $length = 998) + { + if ($length > 0) { + $ret = []; + $len = mb_strlen($str, "UTF-8"); + for ($i = 0; $i < $len; $i += $length) { + $ret[] = mb_substr($str, $i, $length, "UTF-8"); + } + return $ret; + } + return preg_split("//u", $str, -1, PREG_SPLIT_NO_EMPTY); + } + + public function ThrowResponse($code, $desc, $data, $success = true) + { + $msg_lang = "%s %s (%s)
    " . trim("%s"); + $icn = ($success === true) ? "✓" : "✗"; + $clr = ($success === true) ? "" : "color:red;"; + $typ = ($success === true) ? "Success" : "Error"; + if (trim($desc) != "") { + $desc = " " . $desc; + } + $msg = sprintf($msg_lang, $clr, $icn, $typ, $code . $desc, $data); + if ($success !== true && $this->smtpDebug === true) { + throw new Exception($msg . $data . "
"); + return; + } + return $msg . self::CRLF; + } + /* END - SMTP METHODS ************/ +} +/* PHPMailer Lite part of PHP Exception error handling + * (note, namespace makes Exception unique) + */ +class Exception extends \Exception +{ + public function errorMessage() + { + $msg = str_ireplace(["
", "
", "
"], "\n", $this->getMessage()); + $errorMsg = ""; + $errorMsg .= "
"; + $errorMsg .= htmlentities($msg); + $errorMsg .= "
\n"; + return $errorMsg; + } + public function errorMessageRaw() + { + $msg = $this->getMessage(); + if ($this->getCode() == PHPMailerLite::ERR_CRITICAL) { + return $msg; + exit(); + } + return $msg; + } +}