= 37 && $currchr <= 45) || ($currchr >= 47 && $currchr <= 60) || $currchr == 62 || $currchr == 63 || ($currchr >= 65 && $currchr <= 90) || $currchr == 95 || ($currchr >= 97 && $currchr <= 122)) { if (!$restrictmore) $data2 .= $data[$x]; else if (($currchr >= 48 && $currchr <= 57) || ($currchr >= 65 && $currchr <= 90) || ($currchr >= 97 && $currchr <= 122)) $data2 .= sprintf("=%02X", $currchr); else $data2 .= $data[$x]; } else if ($currchr == 13 && $x + 1 < $y && ord($data[$x + 1]) == 10) { $data2 .= "\r\n"; $x++; } else { $data2 .= sprintf("=%02X", $currchr); } } // Break the string on 75 character boundaries and add '=' character. $data2 = explode("\r\n", $data2); $result = ""; foreach ($data2 as $currline) { $x2 = 0; $y2 = strlen($currline); while ($x2 + 75 < $y2) { if ($currline[$x2 + 74] == '=') { $result .= substr($currline, $x2, 74); $x2 += 74; } else if ($currline[$x2 + 73] == '=') { $result .= substr($currline, $x2, 73); $x2 += 73; } else { $result .= substr($currline, $x2, 75); $x2 += 75; } $result .= "=\r\n"; } if ($x2 < $y2) $result .= substr($currline, $x2, $y2 - $x2); $result .= "\r\n"; } return $result; } public static function ConvertEmailMessageToRFC1341($data, $restrictmore = false) { $data = self::ReplaceNewlines("\r\n", $data); return self::ConvertToRFC1341($data, $restrictmore); } // RFC1342 is a hacky workaround to encode headers in e-mails. public static function ConvertToRFC1342($data, $lang = "UTF-8", $encodeb64 = true) { $result = ""; // An individual RFC1342-compliant string can only be 75 characters long, 6 must be markers, // one must be the encoding method, and at least one must be data (adjusted to 4 required // spaces to simplify processing). if (strlen($lang) > 75 - 6 - 1 - 4) return $result; $lang = strtoupper($lang); if ($lang != "ISO-8859-1" && $lang != "US-ASCII") $encodeb64 = true; $maxdatalength = 75 - 6 - strlen($lang) - 1; if ($encodeb64) { $maxdatalength = $maxdatalength * 3 / 4; $y = strlen($data); if ($lang == "UTF-8") { $x = 0; $pos = 0; $size = 0; while (UTF8::NextChrPos($data, $y, $pos, $size)) { if ($pos + $size - $x > $maxdatalength) { if ($x) $result .= " "; $result .= "=?" . $lang . "?B?" . base64_encode(substr($data, $x, $pos - $x)) . "?="; $x = $pos; } } } else { for ($x = 0; $x + $maxdatalength < $y; $x += $maxdatalength) { if ($x) $result .= " "; $result .= "=?" . $lang . "?B?" . base64_encode(substr($data, $x, $maxdatalength)) . "?="; } } if ($x < $y) { if ($x) $result .= " "; $result .= "=?" . $lang . "?B?" . base64_encode(substr($data, $x, $y - $x)) . "?="; } } else { // Quoted printable. $maxdatalength = $maxdatalength / 3; $y = strlen($data); for ($x = 0; $x + $maxdatalength < $y; $x += $maxdatalength) { if ($x) $result .= " "; $result .= "=?" . $lang . "?Q?" . str_replace(" ", "_", self::ConvertToRFC1341(substr($data, $x, $maxdatalength), true)) . "?="; } if ($x < $y) { if ($x) $result .= " "; $result .= "=?" . $lang . "?Q?" . str_replace(" ", "_", self::ConvertToRFC1341(substr($data, $x, $y - $x), true)) . "?="; } } return $result; } private static function SMTP_Translate() { $args = func_get_args(); if (!count($args)) return ""; return call_user_func_array((defined("CS_TRANSLATE_FUNC") && function_exists(CS_TRANSLATE_FUNC) ? CS_TRANSLATE_FUNC : "sprintf"), $args); } // Takes a potentially invalid e-mail address and attempts to make it valid. public static function MakeValidEmailAddress($email, $options = array()) { $email = str_replace("\t", " ", $email); $email = str_replace("\r", " ", $email); $email = str_replace("\n", " ", $email); $email = trim($email); // Reverse parse out the initial domain/IP address part of the e-mail address. $domain = ""; $state = "domend"; $cfwsdepth = 0; while ($email != "" && $state != "") { $prevchr = substr($email, -2, 1); $lastchr = substr($email, -1); switch ($state) { case "domend": { if ($lastchr == ")") { $laststate = "domain"; $state = "cfws"; } else if ($lastchr == "]" || $lastchr == "}") { $domain .= "]"; $email = trim(substr($email, 0, -1)); $state = "ipaddr"; } else { $state = "domain"; } break; } case "cfws": { if ($prevchr == "\\") $email = trim(substr($email, 0, -2)); else if ($lastchr == ")") { $email = trim(substr($email, 0, -1)); $depth++; } else if ($lastchr == "(") { $email = trim(substr($email, 0, -1)); $depth--; if (!$depth && substr($email, -1) != ")") $state = $laststate; } else $email = trim(substr($email, 0, -1)); break; } case "ipaddr": { if ($lastchr == "[" || $lastchr == "{" || $lastchr == "@") { $domain .= "["; $state = "@"; if ($lastchr == "@") break; } else if ($lastchr == "," || $lastchr == ".") $domain .= "."; else if ($lastchr == ";" || $lastchr == ":") $domain .= ":"; else if (preg_match('/[A-Za-z0-9]/', $lastchr)) $domain .= $lastchr; $email = trim(substr($email, 0, -1)); break; } case "domain": { if ($lastchr == "@") { $state = "@"; break; } else if ($lastchr == ")") { $state = "cfws"; $laststate = "@"; break; } else if ($lastchr == "," || $lastchr == ".") $domain .= "."; else if (preg_match('/[A-Za-z0-9-]/', $lastchr)) $domain .= $lastchr; $email = trim(substr($email, 0, -1)); break; } case "@": { if ($lastchr == "@") $state = ""; $email = trim(substr($email, 0, -1)); break; } } } $domain = strrev($domain); $parts = explode(".", $domain); foreach ($parts as $num => $part) $parts[$num] = str_replace(" ", "-", trim(str_replace("-", " ", $part))); $domain = implode(".", $parts); // Forward parse out the local part of the e-mail address. // Remove CFWS (comments, folding whitespace). while (substr($email, 0, 1) == "(") { while ($email != "") { $currchr = substr($email, 0, 1); if ($currchr == "\\") $email = trim(substr($email, 2)); else if ($currchr == "(") { $depth++; $email = trim(substr($email, 1)); } else if ($currchr == ")") { $email = trim(substr($email, 1)); $depth--; if (!$depth && substr($email, 0, 1) != "(") break; } } } // Process quoted/unquoted string. $local = ""; if (substr($email, 0, 1) == "\"") { $email = substr($email, 1); while ($email != "") { $currchr = substr($email, 0, 1); $nextchr = substr($email, 1, 1); if ($currchr == "\\") { if ($nextchr == "\\" || $nextchr == "\"") { $local .= substr($email, 0, 2); $email = substr($email, 2); } else if (ord($nextchr) >= 33 && ord($nextchr) <= 126) { $local .= substr($email, 1, 1); $email = substr($email, 2); } } else if ($currchr == "\"") break; else if (ord($currchr) >= 33 && ord($nextchr) <= 126) { $local .= substr($email, 0, 1); $email = substr($email, 1); } else $email = substr($email, 1); } if (substr($local, -1) != "\"") $local .= "\""; } else { while ($email != "") { $currchr = substr($email, 0, 1); if (preg_match("/[A-Za-z0-9]/", $currchr) || $currchr == "!" || $currchr == "#" || $currchr == "\$" || $currchr == "%" || $currchr == "&" || $currchr == "'" || $currchr == "*" || $currchr == "+" || $currchr == "-" || $currchr == "/" || $currchr == "=" || $currchr == "?" || $currchr == "^" || $currchr == "_" || $currchr == "`" || $currchr == "{" || $currchr == "|" || $currchr == "}" || $currchr == "~" || $currchr == ".") { $local .= $currchr; $email = substr($email, 1); } else break; } $local = preg_replace('/[.]+/', ".", $local); if (substr($local, 0, 1) == ".") $local = substr($local, 1); if (substr($local, -1) == ".") $local = substr($local, 0, -1); } while (substr($local, -2) == "\\\"") $local = substr($local, 0, -2) . "\""; if ($local == "\"" || $local == "\"\"") $local = ""; // Analyze the domain/IP part and fix any issues. $domain = preg_replace('/[.]+/', ".", $domain); if (substr($domain, -1) == "]") { if (substr($domain, 0, 1) != "[") $domain = "[" . $domain; // Process the IP address. if (strtolower(substr($domain, 0, 6)) == "[ipv6:") $ipaddr = IPAddr::NormalizeIP(substr($domain, 6, -1)); else $ipaddr = IPAddr::NormalizeIP(substr($domain, 1, -1)); if ($ipaddr["ipv4"] != "") $domain = "[" . $ipaddr["ipv4"] . "]"; else $domain = "[IPv6:" . $ipaddr["ipv6"] . "]"; } else { // Process the domain. if (substr($domain, 0, 1) == ".") $domain = substr($domain, 1); if (substr($domain, -1) == ".") $domain = substr($domain, 0, -1); $domain = explode(".", $domain); foreach ($domain as $num => $part) { if (substr($part, 0, 1) == "-") $part = substr($part, 1); if (substr($part, -1) == "-") $part = substr($part, 0, -1); if (strlen($part) > 63) $part = substr($part, 0, 63); $domain[$num] = $part; } $domain = implode(".", $domain); } // Validate the final lengths. $y = strlen($local); $y2 = strlen($domain); $email = $local . "@" . $domain; if (!$y) return array("success" => false, "error" => self::SMTP_Translate("Missing local part of e-mail address."), "errorcode" => "missing_local_part", "info" => $email); if (!$y2) return array("success" => false, "error" => self::SMTP_Translate("Missing domain part of e-mail address."), "errorcode" => "missing_domain_part", "info" => $email); if ($y > 64 || $y2 > 253 || $y + $y2 + 1 > 253) return array("success" => false, "error" => self::SMTP_Translate("E-mail address is too long."), "errorcode" => "email_too_long", "info" => $email); // Process results. if (substr($domain, 0, 1) == "[" && substr($domain, -1) == "]") $result = array("success" => true, "email" => $email, "lookup" => false, "type" => "IP"); else if (isset($options["usedns"]) && $options["usedns"] === false) $result = array("success" => true, "email" => $email, "lookup" => false, "type" => "Domain"); else if ((!isset($options["usednsttlcache"]) || $options["usednsttlcache"] === true) && isset(self::$dnsttlcache[$domain]) && self::$dnsttlcache[$domain] >= time()) $result = array("success" => true, "email" => $email, "lookup" => false, "type" => "CachedDNS"); else { // Check for a mail server based on a DNS lookup. $result = self::GetDNSRecord($domain, array("MX", "A"), (isset($options["nameservers"]) ? $options["nameservers"] : array("8.8.8.8", "8.8.4.4")), (!isset($options["usednsttlcache"]) || $options["usednsttlcache"] === true)); if ($result["success"]) $result = array("success" => true, "email" => $email, "lookup" => true, "type" => $result["type"], "records" => $result["records"]); } return $result; } public static function UpdateDNSTTLCache() { $ts = time(); foreach (self::$dnsttlcache as $domain => $ts2) { if ($ts2 > $ts) unset(self::$dnsttlcache[$domain]); } } public static function GetDNSRecord($domain, $types = array("MX", "A"), $nameservers = array("8.8.8.8", "8.8.4.4"), $cache = true) { // Check for a mail server based on a DNS lookup. if (!class_exists("Net_DNS2_Resolver")) require_once str_replace("\\", "/", dirname(__FILE__)) . "/Net/DNS2.php"; $resolver = new Net_DNS2_Resolver(array("nameservers" => $nameservers)); try { foreach ($types as $type) { $response = $resolver->query($domain, $type); if ($response && count($response->answer)) { if ($cache) { $minttl = -1; foreach ($response->answer as $answer) { if ($minttl < 0 || ($answer->ttl > 0 && $answer->ttl < $minttl)) $minttl = $answer->ttl; } self::$dnsttlcache[$domain] = time() + $minttl; } return array("success" => true, "type" => $type, "records" => $response); } } return array("success" => false, "error" => self::SMTP_Translate("Invalid domain name or missing DNS record."), "errorcode" => "invalid_domain_or_missing_record", "info" => $domain); } catch (Exception $e) { return array("success" => false, "error" => self::SMTP_Translate("Invalid domain name. Internal exception occurred."), "errorcode" => "dns_library_exception", "info" => self::SMTP_Translate("%s (%s).", $e->getMessage(), $domain)); } } public static function EmailAddressesToNamesAndEmail(&$destnames, &$destaddrs, $emailaddrs, $removenames = false, $options = array()) { $destnames = array(); $destaddrs = array(); $data = str_replace("\t", " ", $emailaddrs); $data = str_replace("\r", " ", $data); $data = str_replace("\n", " ", $data); $data = trim($data); // Parse e-mail addresses out of the string with a state engine. // Parsed in reverse because that is easier than trying to figure out if each address // starts with a name OR a quoted string for the local part of the e-mail address. // The e-mail address parsing in this state engine is intentionally incomplete. // The goal is to identify '"name" , name , emailaddr' variations. $found = false; while ($data != "") { $name = ""; $email = ""; $state = "addrend"; $cfwsdepth = 0; $inbracket = false; while ($data != "" && $state != "") { $prevchr = substr($data, -2, 1); $lastchr = substr($data, -1); switch ($state) { case "addrend": { if ($lastchr == ">") { $data = trim(substr($data, 0, -1)); $inbracket = true; $state = "domend"; } else if ($lastchr == "," || $lastchr == ";") { $data = trim(substr($data, 0, -1)); } else $state = "domend"; break; } case "domend": { if ($lastchr == ")") { $laststate = "domain"; $state = "cfws"; } else if ($lastchr == "]" || $lastchr == "}") { $email .= "]"; $data = trim(substr($data, 0, -1)); $state = "ipaddr"; } else { $state = "domain"; } break; } case "cfws": { if ($prevchr == "\\") $data = trim(substr($data, 0, -2)); else if ($lastchr == ")") { $data = trim(substr($data, 0, -1)); $depth++; } else if ($lastchr == "(") { $data = trim(substr($data, 0, -1)); $depth--; if (!$depth && substr($data, -1) != ")") $state = $laststate; } else $data = trim(substr($data, 0, -1)); break; } case "ipaddr": { if ($lastchr == "[" || $lastchr == "{" || $lastchr == "@") { $email .= "["; $state = "@"; if ($lastchr == "@") break; } else if ($lastchr == "," || $lastchr == ".") $email .= "."; else if ($lastchr == ";" || $lastchr == ":") $email .= ":"; else if (preg_match('/[A-Za-z0-9]/', $lastchr)) $email .= $lastchr; $data = trim(substr($data, 0, -1)); break; } case "domain": { if ($lastchr == "@") { $state = "@"; break; } else if ($lastchr == ")") { $state = "cfws"; $laststate = "@"; break; } else if ($lastchr == "," || $lastchr == ".") $email .= "."; else if (preg_match('/[A-Za-z0-9-]/', $lastchr)) $email .= $lastchr; $data = trim(substr($data, 0, -1)); break; } case "@": { if ($lastchr == "@") { $email .= "@"; $state = "localend"; } $data = trim(substr($data, 0, -1)); break; } case "localend": { if ($lastchr == ")") { $state = "cfws"; $laststate = "localend"; } else if ($lastchr == "\"") { $email .= "\""; $data = substr($data, 0, -1); $state = "quotedlocal"; } else $state = "local"; break; } case "quotedlocal": { if ($prevchr == "\\") { $email .= $lastchar . $prevchr; $data = substr($data, 0, -2); } else if ($lastchr == "\"") { $email .= $lastchar; $data = trim(substr($data, 0, -1)); $state = "localstart"; } else { $email .= $lastchar; $data = substr($data, 0, -1); } break; } case "local": { if (preg_match("/[A-Za-z0-9]/", $lastchr) || $lastchr == "!" || $lastchr == "#" || $lastchr == "\$" || $lastchr == "%" || $lastchr == "&" || $lastchr == "'" || $lastchr == "*" || $lastchr == "+" || $lastchr == "-" || $lastchr == "/" || $lastchr == "=" || $lastchr == "?" || $lastchr == "^" || $lastchr == "_" || $lastchr == "`" || $lastchr == "{" || $lastchr == "|" || $lastchr == "}" || $lastchr == "~" || $lastchr == ".") { $email .= $lastchr; $data = substr($data, 0, -1); } else if ($lastchr == ")") { $state = "cfws"; $laststate = "localstart"; } else if ($inbracket) { if ($lastchr == "<") $state = "localstart"; else $data = substr($data, 0, -1); } else if ($lastchr == " " || $lastchr == "," || $lastchr == ";") $state = "localstart"; else $data = substr($data, 0, -1); break; } case "localstart": { if ($inbracket) { if ($lastchr == "<") $state = "nameend"; $data = trim(substr($data, 0, -1)); } else if ($lastchr == "," || $lastchr == ";") $state = ""; else $data = trim(substr($data, 0, -1)); break; } case "nameend": { if ($lastchr == "\"") { $data = substr($data, 0, -1); $state = "quotedname"; } else $state = "name"; break; } case "quotedname": { if ($prevchr == "\\") { $name .= $lastchar . $prevchr; $data = substr($data, 0, -2); } else if ($lastchr == "\"") { $data = trim(substr($data, 0, -1)); $state = ""; } else { $name .= $lastchr; $data = substr($data, 0, -1); } break; } case "name": { if ($lastchr == "," || $lastchr == ";") $state = ""; else { $name .= $lastchr; $data = substr($data, 0, -1); } break; } } } $email = self::MakeValidEmailAddress(strrev($email), $options); if ($email["success"]) { if ($removenames) $name = ""; $name = trim(strrev($name)); if (substr($name, 0, 1) == "\"") $name = trim(substr($name, 1)); $name = str_replace("\\\\", "\\", $name); $name = str_replace("\\\"", "\"", $name); $destnames[] = $name; $destaddrs[] = $email["email"]; $found = true; } $data = trim($data); } $destnames = array_reverse($destnames); $destaddrs = array_reverse($destaddrs); return $found; } // Takes in a comma-separated list of e-mail addresses and returns appropriate e-mail headers. public static function EmailAddressesToEmailHeaders($emailaddrs, $headername, $multiple = true, $removenames = false, $options = array()) { $result = ""; $tempnames = array(); $tempaddrs = array(); self::EmailAddressesToNamesAndEmail($tempnames, $tempaddrs, $emailaddrs, $removenames, $options); $y = count($tempnames); for ($x = 0; $x < $y && ($multiple || $result == ""); $x++) { $name = $tempnames[$x]; $emailaddr = $tempaddrs[$x]; if ($name != "" && !UTF8::IsASCII($name)) $name = self::ConvertToRFC1342($name) . " "; else if ($name != "") $name = '"' . $name . '" '; if ($result != "") $result .= ",\r\n "; if ($name != "") $result .= $name . '<' . $emailaddr . '>'; else $result .= $emailaddr; } if ($result != "" && $headername != "") $result = $headername . ": " . $result . "\r\n"; return $result; } public static function GetUserAgent($type) { if ($type == "Thunderbird") return "User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:24.0) Gecko/20100101 Thunderbird/24.0\r\n"; else if ($type == "Thunderbird2") return "X-Mailer: Thunderbird 2.0.0.16 (Windows/20080708)\r\n"; else if ($type == "OutlookExpress") return "X-Mailer: Microsoft Outlook Express 6.00.2900.3198\r\nX-MimeOLE: Produced By Microsoft MimeOLE V6.00.2900.3198\r\n"; else if ($type == "Exchange") return "X-Mailer: Produced By Microsoft Exchange V6.0.6619.12\r\n"; else if ($type == "OfficeOutlook") return "X-Mailer: Microsoft Office Outlook 12.0\r\n"; return ""; } public static function GetTimeLeft($start, $limit) { if ($limit === false) return false; $difftime = microtime(true) - $start; if ($difftime >= $limit) return 0; return $limit - $difftime; } private static function ProcessRateLimit($size, $start, $limit, $async) { $difftime = microtime(true) - $start; if ($difftime > 0.0) { if ($size / $difftime > $limit) { // Sleeping for some amount of time will equalize the rate. // So, solve this for $x: $size / ($x + $difftime) = $limit $amount = ($size - ($limit * $difftime)) / $limit; if ($async) return microtime(true) + $amount; else usleep($amount); } } return -1.0; } private static function StreamTimedOut($fp) { if (!function_exists("stream_get_meta_data")) return false; $info = stream_get_meta_data($fp); return $info["timed_out"]; } // Reads one or more lines in. private static function ProcessState__ReadLine(&$state) { while (strpos($state["data"], "\n") === false) { $data2 = @fgets($state["fp"], 116000); if ($data2 === false) return array("success" => false, "error" => self::SMTP_Translate("Underlying stream encountered a read error."), "errorcode" => "stream_read_error"); if (strpos($data2, "\n") === false) { if (feof($state["fp"])) return array("success" => false, "error" => self::SMTP_Translate("Remote peer disconnected."), "errorcode" => "peer_disconnected"); if (self::StreamTimedOut($state["fp"])) return array("success" => false, "error" => self::SMTP_Translate("Underlying stream timed out."), "errorcode" => "stream_timeout_exceeded"); if ($state["async"] && $data2 === "") return array("success" => false, "error" => self::SMTP_Translate("Non-blocking read returned no data."), "errorcode" => "no_data"); } if ($state["timeout"] !== false && self::GetTimeLeft($state["startts"], $state["timeout"]) == 0) return array("success" => false, "error" => self::SMTP_Translate("HTTP timeout exceeded."), "errorcode" => "timeout_exceeded"); $state["result"]["rawrecvsize"] += strlen($data2); $state["data"] .= $data2; if (isset($state["options"]["recvratelimit"])) $state["waituntil"] = self::ProcessRateLimit($state["rawsize"], $state["recvstart"], $state["options"]["recvratelimit"], $state["async"]); if (isset($state["options"]["debug_callback"]) && is_callable($state["options"]["debug_callback"])) call_user_func_array($state["options"]["debug_callback"], array("rawrecv", $data2, &$state["options"]["debug_callback_opts"])); else if ($state["debug"]) $state["result"]["rawrecv"] .= $data2; } return array("success" => true); } // Writes data out. private static function ProcessState__WriteData(&$state) { if ($state["data"] !== "") { $result = @fwrite($state["fp"], $state["data"]); if ($result === false || feof($state["fp"])) return array("success" => false, "error" => self::SMTP_Translate("A fwrite() failure occurred. Most likely cause: Connection failure."), "errorcode" => "fwrite_failed"); if ($state["timeout"] !== false && self::GetTimeLeft($state["startts"], $state["timeout"]) == 0) return array("success" => false, "error" => self::SMTP_Translate("HTTP timeout exceeded."), "errorcode" => "timeout_exceeded"); $data2 = substr($state["data"], 0, $result); $state["data"] = (string)substr($state["data"], $result); $state["result"]["rawsendsize"] += $result; if (isset($state["options"]["sendratelimit"])) { $state["waituntil"] = self::ProcessRateLimit($state["result"]["rawsendsize"], $state["result"]["connected"], $state["options"]["sendratelimit"], $state["async"]); if (microtime(true) < $state["waituntil"]) return array("success" => false, "error" => self::SMTP_Translate("Rate limit for non-blocking connection has not been reached."), "errorcode" => "no_data"); } if (isset($state["options"]["debug_callback"]) && is_callable($state["options"]["debug_callback"])) call_user_func_array($state["options"]["debug_callback"], array("rawsend", $data2, &$state["options"]["debug_callback_opts"])); else if ($state["debug"]) $state["result"]["rawsend"] .= $data2; } return array("success" => true); } public static function ForceClose(&$state) { if ($state["fp"] !== false) { @fclose($state["fp"]); $state["fp"] = false; } if (isset($state["currentfile"]) && $state["currentfile"] !== false) { if ($state["currentfile"]["fp"] !== false) @fclose($state["currentfile"]["fp"]); $state["currentfile"] = false; } } private static function CleanupErrorState(&$state, $result) { if (!$result["success"] && $result["errorcode"] !== "no_data") { self::ForceClose($state); $state["error"] = $result; } return $result; } private static function InitSMTPRequest(&$state, $command, $expectedcode, $nextstate, $expectederror) { $state["data"] = $command . "\r\n"; $state["state"] = "send_request"; $state["expectedcode"] = $expectedcode; $state["nextstate"] = $nextstate; $state["expectederror"] = $expectederror; } public static function ProcessState(&$state) { if (isset($state["error"])) return $state["error"]; if ($state["timeout"] !== false && self::GetTimeLeft($state["startts"], $state["timeout"]) == 0) return self::CleanupErrorState($state, array("success" => false, "error" => self::SMTP_Translate("HTTP timeout exceeded."), "errorcode" => "timeout_exceeded")); if (microtime(true) < $state["waituntil"]) return array("success" => false, "error" => self::SMTP_Translate("Rate limit for non-blocking connection has not been reached."), "errorcode" => "no_data"); while ($state["state"] !== "done") { switch ($state["state"]) { case "connecting": { if (function_exists("stream_select") && $state["async"]) { $readfp = NULL; $writefp = array($state["fp"]); $exceptfp = NULL; $result = @stream_select($readfp, $writefp, $exceptfp, 0); if ($result === false) return self::CleanupErrorState($state, array("success" => false, "error" => self::SMTP_Translate("A stream_select() failure occurred. Most likely cause: Connection failure."), "errorcode" => "stream_select_failed")); if (!count($writefp)) return array("success" => false, "error" => self::SMTP_Translate("Connection not established yet."), "errorcode" => "no_data"); } // Handle peer certificate retrieval. if (function_exists("stream_context_get_options")) { $contextopts = stream_context_get_options($state["fp"]); if ($state["secure"] && isset($state["options"]["sslopts"]) && is_array($state["options"]["sslopts"]) && isset($contextopts["ssl"]["peer_certificate"])) { if (isset($state["options"]["debug_callback"]) && is_callable($state["options"]["debug_callback"])) call_user_func_array($state["options"]["debug_callback"], array("peercert", @openssl_x509_parse($contextopts["ssl"]["peer_certificate"]), &$state["options"]["debug_callback_opts"])); } } // Deal with failed connections that hang applications. if (isset($state["options"]["streamtimeout"]) && $state["options"]["streamtimeout"] !== false && function_exists("stream_set_timeout")) @stream_set_timeout($state["fp"], $state["options"]["streamtimeout"]); $state["result"]["connected"] = microtime(true); $state["data"] = ""; $state["code"] = 0; $state["expectedcode"] = 220; $state["expectederror"] = self::SMTP_Translate("Expected a 220 response from the SMTP server upon connecting."); $state["response"] = ""; $state["state"] = "get_response"; $state["nextstate"] = "helo_ehlo"; break; } case "send_request": { // Send the request to the server. $result = self::ProcessState__WriteData($state); if (!$result["success"]) return self::CleanupErrorState($state, $result); $state["code"] = 0; $state["response"] = ""; // Handle QUIT differently. $state["state"] = ($state["nextstate"] === "done" ? "done" : "get_response"); break; } case "get_response": { $result = self::ProcessState__ReadLine($state); if (!$result["success"]) return self::CleanupErrorState($state, $result); $currline = $state["data"]; $state["data"] = ""; if (strlen($currline) >= 4) { $state["response"] .= substr($currline, 4); $state["code"] = (int)substr($currline, 0, 3); if (substr($currline, 3, 1) == " ") { if ($state["expectedcode"] > 0 && $state["code"] !== $state["expectedcode"]) return self::CleanupErrorState($state, array("success" => false, "error" => $state["expectederror"], "errorcode" => "invalid_response", "info" => $state["code"] . " " . $state["response"])); $state["response"] = self::ReplaceNewlines("\r\n", $state["response"]); $state["state"] = $state["nextstate"]; } } break; } case "helo_ehlo": { // Send EHLO or HELO depending on server support. $hostname = (isset($state["options"]["hostname"]) ? $state["options"]["hostname"] : "[" . trim(isset($_SERVER["SERVER_ADDR"]) && $_SERVER["SERVER_ADDR"] != "127.0.0.1" ? $_SERVER["SERVER_ADDR"] : "192.168.0.101") . "]"); $state["size_supported"] = 0; if (strpos($state["response"], " ESMTP") !== false) { self::InitSMTPRequest($state, "EHLO " . $hostname, 250, "esmtp_extensions", self::SMTP_Translate("Expected a 250 response from the SMTP server upon EHLO.")); } else { self::InitSMTPRequest($state, "HELO " . $hostname, 250, "mail_from", self::SMTP_Translate("Expected a 250 response from the SMTP server upon HELO.")); } break; } case "esmtp_extensions": { // Process supported ESMTP extensions. $auth = ""; $smtpdata = explode("\r\n", $state["response"]); $y = count($smtpdata); for ($x = 1; $x < $y; $x++) { if (strtoupper(substr($smtpdata[$x], 0, 4)) == "AUTH" && ($smtpdata[$x][4] == ' ' || $smtpdata[$x][4] == '=')) $auth = strtoupper(substr($smtpdata[$x], 5)); if (strtoupper(substr($smtpdata[$x], 0, 4)) == "SIZE" && ($smtpdata[$x][4] == ' ' || $smtpdata[$x][4] == '=')) $state["size_supported"] = (int)substr($smtpdata[$x], 5); } $state["state"] = "mail_from"; // Process login (if any and supported). if (strpos($auth, "LOGIN") !== false) { $state["username"] = (isset($state["options"]["username"]) ? (string)$state["options"]["username"] : ""); $state["password"] = (isset($state["options"]["password"]) ? (string)$state["options"]["password"] : ""); if ($state["username"] !== "" || $state["password"] !== "") { self::InitSMTPRequest($state, "AUTH LOGIN", 334, "auth_login_username", self::SMTP_Translate("Expected a 334 response from the SMTP server upon AUTH LOGIN.")); } } break; } case "auth_login_username": { self::InitSMTPRequest($state, base64_encode($state["username"]), 334, "auth_login_password", self::SMTP_Translate("Expected a 334 response from the SMTP server upon AUTH LOGIN username.")); break; } case "auth_login_password": { self::InitSMTPRequest($state, base64_encode($state["password"]), 235, "mail_from", self::SMTP_Translate("Expected a 235 response from the SMTP server upon AUTH LOGIN password.")); break; } case "mail_from": { self::InitSMTPRequest($state, "MAIL FROM:<" . $state["fromaddrs"][0] . ">" . ($state["size_supported"] ? " SIZE=" . strlen($state["message"]) : ""), 250, "rcpt_to", self::SMTP_Translate("Expected a 250 response from the SMTP server upon MAIL FROM.")); break; } case "rcpt_to": { $addr = array_shift($state["toaddrs"]); self::InitSMTPRequest($state, "RCPT TO:<" . $addr . ">", 250, (count($state["toaddrs"]) ? "rcpt_to" : "data"), self::SMTP_Translate("Expected a 250 response from the SMTP server upon RCPT TO.")); break; } case "data": { self::InitSMTPRequest($state, "DATA", 354, "send_message", self::SMTP_Translate("Expected a 354 response from the SMTP server upon DATA.")); break; } case "send_message": { self::InitSMTPRequest($state, $state["message"] . "\r\n.", 250, "quit", self::SMTP_Translate("Expected a 250 response from the SMTP server upon sending the e-mail.")); break; } case "quit": { self::InitSMTPRequest($state, "QUIT", 0, "done", ""); break; } } } $state["result"]["endts"] = microtime(true); fclose($state["fp"]); return $state["result"]; } private static function SMTP_RandomHexString($length) { $lookup = "0123456789ABCDEF"; $result = ""; while ($length) { $result .= $lookup[mt_rand(0, 15)]; $length--; } return $result; } private static function ProcessSSLOptions(&$options, $key, $host) { if (isset($options[$key]["auto_cainfo"])) { unset($options[$key]["auto_cainfo"]); $cainfo = ini_get("curl.cainfo"); if ($cainfo !== false && strlen($cainfo) > 0) $options[$key]["cafile"] = $cainfo; else if (file_exists(str_replace("\\", "/", dirname(__FILE__)) . "/cacert.pem")) $options[$key]["cafile"] = str_replace("\\", "/", dirname(__FILE__)) . "/cacert.pem"; } if (isset($options[$key]["auto_cn_match"])) { unset($options[$key]["auto_cn_match"]); $options[$key]["CN_match"] = $host; } if (isset($options[$key]["auto_sni"])) { unset($options[$key]["auto_sni"]); $options[$key]["SNI_enabled"] = true; $options[$key]["SNI_server_name"] = $host; } } // Sends an e-mail by directly connecting to a SMTP server using PHP sockets. Much more powerful than calling mail(). public static function SendSMTPEmail($toaddr, $fromaddr, $message, $options = array()) { $startts = microtime(true); $timeout = (isset($options["timeout"]) ? $options["timeout"] : false); if (!function_exists("stream_socket_client") && !function_exists("fsockopen")) return array("success" => false, "error" => self::SMTP_Translate("The functions 'stream_socket_client' and 'fsockopen' do not exist."), "errorcode" => "function_check"); $temptonames = array(); $temptoaddrs = array(); $tempfromnames = array(); $tempfromaddrs = array(); if (!self::EmailAddressesToNamesAndEmail($temptonames, $temptoaddrs, $toaddr, true, $options)) return array("success" => false, "error" => self::SMTP_Translate("Invalid 'To' e-mail address(es)."), "errorcode" => "invalid_to_address", "info" => $toaddr); if (!self::EmailAddressesToNamesAndEmail($tempfromnames, $tempfromaddrs, $fromaddr, true, $options)) return array("success" => false, "error" => self::SMTP_Translate("Invalid 'From' e-mail address."), "errorcode" => "invalid_from_address", "info" => $fromaddr); $server = (isset($options["server"]) ? $options["server"] : "localhost"); $secure = (isset($options["secure"]) ? $options["secure"] : false); $port = (isset($options["port"]) ? (int)$options["port"] : -1); if ($port < 0 || $port > 65535) $port = ($secure ? 465 : 25); $debug = (isset($options["debug"]) ? $options["debug"] : false); $headers = "Message-ID: <" . self::SMTP_RandomHexString(8) . "." . self::SMTP_RandomHexString(7) . "@" . substr($tempfromaddrs[0], strrpos($tempfromaddrs[0], "@") + 1) . ">\r\n"; $headers .= "Date: " . date("D, d M Y H:i:s O") . "\r\n"; $message = $headers . $message; $message = self::ReplaceNewlines("\r\n", $message); $message = str_replace("\r\n.\r\n", "\r\n..\r\n", $message); // Set up the final output array. $result = array("success" => true, "rawsendsize" => 0, "rawrecvsize" => 0, "startts" => $startts); $debug = (isset($options["debug"]) && $options["debug"]); if ($debug) { $result["rawsend"] = ""; $result["rawrecv"] = ""; } if ($timeout !== false && self::GetTimeLeft($startts, $timeout) == 0) return array("success" => false, "error" => self::SMTP_Translate("HTTP timeout exceeded."), "errorcode" => "timeout_exceeded"); // Connect to the target server. $hostname = (isset($options["hostname"]) ? $options["hostname"] : "[" . trim(isset($_SERVER["SERVER_ADDR"]) && $_SERVER["SERVER_ADDR"] != "127.0.0.1" ? $_SERVER["SERVER_ADDR"] : "192.168.0.101") . "]"); $errornum = 0; $errorstr = ""; if (isset($options["fp"]) && is_resource($options["fp"])) { $fp = $options["fp"]; unset($options["fp"]); } else { if (!isset($options["connecttimeout"])) $options["connecttimeout"] = 10; $timeleft = self::GetTimeLeft($startts, $timeout); if ($timeleft !== false) $options["connecttimeout"] = min($options["connecttimeout"], $timeleft); if (!function_exists("stream_socket_client")) $fp = @fsockopen(($secure ? "tls://" : "") . $server, $port, $errornum, $errorstr, $options["connecttimeout"]); else { $context = @stream_context_create(); if (isset($options["source_ip"])) $context["socket"] = array("bindto" => $options["source_ip"] . ":0"); if ($secure && isset($options["sslopts"]) && is_array($options["sslopts"])) { self::ProcessSSLOptions($options, "sslopts", $server); foreach ($options["sslopts"] as $key => $val) @stream_context_set_option($context, "ssl", $key, $val); } $fp = @stream_socket_client(($secure ? "tls://" : "") . $server . ":" . $port, $errornum, $errorstr, $options["connecttimeout"], STREAM_CLIENT_CONNECT | (isset($options["async"]) && $options["async"] ? STREAM_CLIENT_ASYNC_CONNECT : 0), $context); } if ($fp === false) return array("success" => false, "error" => self::SMTP_Translate("Unable to establish a SMTP connection to '%s'.", ($secure ? "tls://" : "") . $server . ":" . $port), "errorcode" => "connection_failure", "info" => $errorstr . " (" . $errornum . ")"); } if (function_exists("stream_set_blocking")) @stream_set_blocking($fp, (isset($options["async"]) && $options["async"] ? 0 : 1)); // Initialize the connection request state array. $state = array( "fp" => $fp, "async" => (isset($options["async"]) ? $options["async"] : false), "debug" => $debug, "startts" => $startts, "timeout" => $timeout, "waituntil" => -1.0, "data" => "", "code" => 0, "expectedcode" => 0, "expectederror" => "", "response" => "", "fromaddrs" => $tempfromaddrs, "toaddrs" => $temptoaddrs, "message" => $message, "secure" => $secure, "state" => "connecting", "options" => $options, "result" => $result ); // Return the state for async calls. Caller must call ProcessState(). if ($state["async"]) return array("success" => true, "state" => $state); // Run through all of the valid states and return the result. return self::ProcessState($state); } // Has to be public so that TagFilter can successfully call. public static function SMTP_HTMLTagFilter($stack, &$content, $open, $tagname, &$attrs, $options) { $content = str_replace(array(" ", " ", "\xC2\xA0"), array(" ", " ", " "), $content); $content = str_replace("&", "&", $content); $content = str_replace(""", "\"", $content); if ($tagname === "head") return array("keep_tag" => false, "keep_interior" => false); if ($tagname === "style") return array("keep_tag" => false, "keep_interior" => false); if ($tagname === "script") return array("keep_tag" => false, "keep_interior" => false); if ($tagname === "a" && (!isset($attrs["href"]) || trim($attrs["href"]) === "")) return array("keep_tag" => false, "keep_interior" => false); if ($tagname === "/a" && $stack[0]["keep_interior"]) { if ($stack[0]["attrs"]["href"] === trim($content)) $content = " [ " . trim($content) . " ] "; else if (trim($content) !== "") $content = " " . trim($content) . " (" . trim($stack[0]["attrs"]["href"]) . ") "; } if ($tagname === "img") { if (!isset($attrs["src"])) $attrs["src"] = ""; if (isset($attrs["alt"]) && trim($attrs["alt"]) !== "" && trim($attrs["alt"]) !== $attrs["src"]) $content .= trim($attrs["alt"]) . "\n\n"; } if ($tagname === "table" || $tagname === "blockquote" || $tagname === "ul") self::$depths[] = $tagname; if ($tagname === "ol") self::$depths[] = 1; if (trim($content) !== "") { if ($tagname === "/tr") $content = ltrim($content) . "\n\n"; if ($tagname === "/th") $content = "*" . ltrim($content) . "*\n"; if ($tagname === "/td") $content = ltrim($content) . "\n"; if ($tagname === "/div") $content = ltrim($content) . "\n"; if ($tagname === "/li") $content = "\n" . (count(self::$depths) && is_int(self::$depths[count(self::$depths) - 1]) ? sprintf("%d. ", self::$depths[count(self::$depths) - 1]++) : "- ") . ltrim($content) . "\n"; if ($tagname === "br") $content .= "\n"; if ($tagname === "/h1" || $tagname === "/h2" || $tagname === "/h3") $content = "*" . trim($content) . "*\n\n"; if ($tagname === "/h4" || $tagname === "/h5" || $tagname === "/h6") $content = "*" . trim($content) . "*\n"; if ($tagname === "/i" || $tagname === "/em") $content = " _" . trim($content) . "_ "; if ($tagname === "/b" || $tagname === "/strong") $content = " *" . trim($content) . "* "; if ($tagname === "/p") $content = "\n\n" . trim($content) . "\n\n"; if ($tagname === "/blockquote") $content = "------------------------\n" . trim($content) . "\n------------------------\n"; if ($tagname === "/ul" || $tagname === "/ol" || $tagname === "/table" || $tagname === "/blockquote") { // Indent the lines of content varying amounts depending on final depth. $prefix = ""; if ($tagname === "/table") $prefix .= "\xFF\xFF"; if ($tagname === "/ul" || $tagname === "/ol") $prefix .= "\xFF\xFF" . (count(self::$depths) > 1 ? "\xFF\xFF" : ""); if ($tagname === "/blockquote") $prefix .= "\xFF\xFF\xFF\xFF"; $lines = explode("\n", $content); foreach ($lines as $num => $line) { if (trim($line) !== "") { if ($line{0} !== "\xFF" && (($tagname === "/ul" && $line{0} !== "-") || ($tagname === "/ol" && !(int)$line{0}))) $prefix2 = "\xFF\xFF"; else $prefix2 = ""; $lines[$num] = $prefix . $prefix2 . trim($line); } } $content = "\n\n" . implode("\n", $lines) . "\n\n"; } if ($tagname === "/pre") $content = "\n\n" . $content . "\n\n"; } if ($tagname === "/table" || $tagname === "/blockquote" || $tagname === "/ul" || $tagname === "/ol") array_pop(self::$depths); return array("keep_tag" => false); } // Has to be public so that TagFilter can successfully call. public static function SMTP_HTMLContentFilter($stack, $result, &$content, $options) { if (TagFilter::GetParentPos($stack, "pre") === false) { $content = preg_replace('/\s{2,}/', " ", str_replace(array("\r\n", "\n", "\r", "\t"), " ", $content)); if ($result !== "" && substr($result, -1) === "\n") $content = trim($content); } } public static function ConvertHTMLToText($data) { self::$depths = array(); // Load TagFilter. if (!class_exists("TagFilter")) require_once str_replace("\\", "/", dirname(__FILE__)) . "/tag_filter.php"; $data = UTF8::MakeValid($data); $options = TagFilter::GetHTMLOptions(); $options["tag_callback"] = "SMTP::SMTP_HTMLTagFilter"; $options["content_callback"] = "SMTP::SMTP_HTMLContentFilter"; $data = TagFilter::Run($data, $options); $data = str_replace("\xFF", " ", $data); $data = UTF8::MakeValid($data); return $data; } private static function MIME_RandomString($length) { $lookup = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; $result = ""; while ($length) { $result .= $lookup[mt_rand(0, 61)]; $length--; } return $result; } public static function SendEmail($fromaddr, $toaddr, $subject, $options = array()) { $subject = str_replace("\r", " ", $subject); $subject = str_replace("\n", " ", $subject); if (!UTF8::IsASCII($subject)) $subject = self::ConvertToRFC1342($subject); $replytoaddr = (isset($options["replytoaddr"]) ? $options["replytoaddr"] : ""); $ccaddr = (isset($options["ccaddr"]) ? $options["ccaddr"] : ""); $bccaddr = (isset($options["bccaddr"]) ? $options["bccaddr"] : ""); $headers = (isset($options["headers"]) ? $options["headers"] : ""); $textmessage = (isset($options["textmessage"]) ? $options["textmessage"] : ""); $htmlmessage = (isset($options["htmlmessage"]) ? $options["htmlmessage"] : ""); $attachments = (isset($options["attachments"]) ? $options["attachments"] : array()); $messagetoaddr = self::EmailAddressesToEmailHeaders($toaddr, "To", true, false, $options); $replytoaddr = self::EmailAddressesToEmailHeaders($replytoaddr, "Reply-To", false, false, $options); if ($replytoaddr == "") $replytoaddr = self::EmailAddressesToEmailHeaders($fromaddr, "Reply-To", false, false, $options); $messagefromaddr = self::EmailAddressesToEmailHeaders($fromaddr, "From", false, false, $options); if ($messagefromaddr == "" && $replytoaddr == "") return array("success" => false, "error" => self::SMTP_Translate("From address is invalid."), "errorcode" => "invalid_from_address", "info" => $fromaddr); if ($ccaddr != "") $toaddr .= ", " . $ccaddr; $ccaddr = self::EmailAddressesToEmailHeaders($ccaddr, "Cc", true, false, $options); if ($bccaddr != "") $toaddr .= ", " . $bccaddr; $bccaddr = self::EmailAddressesToEmailHeaders($bccaddr, "Bcc", true, false, $options); if ($htmlmessage == "" && !count($attachments)) { // Plain-text e-mail. $destheaders = ""; $destheaders .= $messagefromaddr; if ($headers != "") $destheaders .= $headers; $destheaders .= "MIME-Version: 1.0\r\n"; if (!isset($options["usemail"]) || !$options["usemail"]) $destheaders .= $messagetoaddr; if ($replytoaddr != "") $destheaders .= $replytoaddr; if ($ccaddr != "") $destheaders .= $ccaddr; if ($bccaddr != "") $destheaders .= $bccaddr; if (!isset($options["usemail"]) || !$options["usemail"]) $destheaders .= "Subject: " . $subject . "\r\n"; $destheaders .= "Content-Type: text/plain; charset=UTF-8\r\n"; $destheaders .= "Content-Transfer-Encoding: quoted-printable\r\n"; $message = self::ConvertEmailMessageToRFC1341($textmessage); } else { // MIME e-mail (HTML, text, attachments). $mimeboundary = "--------" . self::MIME_RandomString(25); $destheaders = ""; $destheaders .= $messagefromaddr; if ($headers != "") $destheaders .= $headers; $destheaders .= "MIME-Version: 1.0\r\n"; if (!isset($options["usemail"]) || !$options["usemail"]) $destheaders .= $messagetoaddr; if ($replytoaddr != "") $destheaders .= $replytoaddr; if ($ccaddr != "") $destheaders .= $ccaddr; if ($bccaddr != "") $destheaders .= $bccaddr; if (!isset($options["usemail"]) || !$options["usemail"]) $destheaders .= "Subject: " . $subject . "\r\n"; if (count($attachments) && isset($options["inlineattachments"]) && $options["inlineattachments"]) $destheaders .= "Content-Type: multipart/related; boundary=\"" . $mimeboundary . "\"; type=\"multipart/alternative\"\r\n"; else if (count($attachments)) $destheaders .= "Content-Type: multipart/mixed; boundary=\"" . $mimeboundary . "\"\r\n"; else if ($textmessage != "" && $htmlmessage != "") $destheaders .= "Content-Type: multipart/alternative; boundary=\"" . $mimeboundary . "\"\r\n"; else $mimeboundary = ""; if ($mimeboundary != "") $mimecontent = "This is a multi-part message in MIME format.\r\n"; else $mimecontent = ""; if ($textmessage == "" || $htmlmessage == "" || !count($attachments)) $mimeboundary2 = $mimeboundary; else { $mimeboundary2 = "--------" . self::MIME_RandomString(25); $mimecontent .= "--" . $mimeboundary . "\r\n"; $mimecontent .= "Content-Type: multipart/alternative; boundary=\"" . $mimeboundary2 . "\"\r\n"; $mimecontent .= "\r\n"; } if ($textmessage != "") { if ($mimeboundary2 != "") { $mimecontent .= "--" . $mimeboundary2 . "\r\n"; $mimecontent .= "Content-Type: text/plain; charset=UTF-8\r\n"; $mimecontent .= "Content-Transfer-Encoding: quoted-printable\r\n"; $mimecontent .= "\r\n"; } else { $destheaders .= "Content-Type: text/plain; charset=UTF-8\r\n"; $destheaders .= "Content-Transfer-Encoding: quoted-printable\r\n"; } $message = self::ConvertEmailMessageToRFC1341($textmessage); $mimecontent .= $message; $mimecontent .= "\r\n"; } if ($htmlmessage != "") { if ($mimeboundary2 != "") { $mimecontent .= "--" . $mimeboundary2 . "\r\n"; $mimecontent .= "Content-Type: text/html; charset=UTF-8\r\n"; $mimecontent .= "Content-Transfer-Encoding: quoted-printable\r\n"; $mimecontent .= "\r\n"; } else { $destheaders .= "Content-Type: text/html; charset=UTF-8\r\n"; $destheaders .= "Content-Transfer-Encoding: quoted-printable\r\n"; } $message = self::ConvertEmailMessageToRFC1341($htmlmessage); $mimecontent .= $message; $mimecontent .= "\r\n"; } if ($mimeboundary2 != "" && $mimeboundary != $mimeboundary2) $mimecontent .= "--" . $mimeboundary2 . "--\r\n"; // Process the attachments. $y = count($attachments); for ($x = 0; $x < $y; $x++) { $mimecontent .= "--" . $mimeboundary . "\r\n"; $type = str_replace("\r", "", $attachments[$x]["type"]); $type = str_replace("\n", "", $type); $type = UTF8::ConvertToASCII($type); if (!isset($attachments[$x]["name"])) $name = ""; else { $name = str_replace("\r", "", $attachments[$x]["name"]); $name = str_replace("\n", "", $name); $name = self::FilenameSafe($name); } if (!isset($attachments[$x]["location"])) $location = ""; else { $location = str_replace("\r", "", $attachments[$x]["location"]); $location = str_replace("\n", "", $location); $location = UTF8::ConvertToASCII($location); } if (!isset($attachments[$x]["cid"])) $cid = ""; else { $cid = str_replace("\r", "", $attachments[$x]["cid"]); $cid = str_replace("\n", "", $cid); $cid = UTF8::ConvertToASCII($cid); } $mimecontent .= "Content-Type: " . $type . ($name != "" ? "; name=\"" . $name . "\"" : "") . "\r\n"; if ($cid != "") $mimecontent .= "Content-ID: <" . $cid . ">\r\n"; if ($location != "") $mimecontent .= "Content-Location: " . $location . "\r\n"; $mimecontent .= "Content-Transfer-Encoding: base64\r\n"; if ($name != "") $mimecontent .= "Content-Disposition: inline; filename=\"" . $name . "\"\r\n"; $mimecontent .= "\r\n"; $mimecontent .= chunk_split(base64_encode($attachments[$x]["data"])); $mimecontent .= "\r\n"; } if ($mimeboundary != "") $mimecontent .= "--" . $mimeboundary . "--\r\n"; $message = $mimecontent; } if (isset($options["returnresults"]) && $options["returnresults"]) return array("success" => true, "toaddr" => $toaddr, "fromaddr" => $fromaddr, "headers" => $destheaders, "subject" => $subject, "message" => $message); else if (isset($options["usemail"]) && $options["usemail"]) { $result = mail($toaddr, $subject, self::ReplaceNewlines("\n", $message), $destheaders); if (!$result) return array("success" => false, "error" => self::SMTP_Translate("PHP mail() call failed."), "errorcode" => "mail_call_failed"); return array("success" => true); } else { return self::SendSMTPEmail($toaddr, $fromaddr, $destheaders . "\r\n" . $message, $options); } } } ?>