mirror of https://github.com/tildeclub/site.git
				
				
				
			
		
			
	
	
		
			1519 lines
		
	
	
		
			54 KiB
		
	
	
	
		
			PHP
		
	
	
	
		
		
			
		
	
	
			1519 lines
		
	
	
		
			54 KiB
		
	
	
	
		
			PHP
		
	
	
	
|  | <?php | ||
|  | 	// CubicleSoft PHP SMTP e-mail functions.
 | ||
|  | 	// (C) 2014 CubicleSoft.  All Rights Reserved.
 | ||
|  | 
 | ||
|  | 	// Load dependencies.
 | ||
|  | 	if (!class_exists("UTF8"))  require_once str_replace("\\", "/", dirname(__FILE__)) . "/utf8.php"; | ||
|  | 	if (!class_exists("IPAddr"))  require_once str_replace("\\", "/", dirname(__FILE__)) . "/ipaddr.php"; | ||
|  | 
 | ||
|  | 	class SMTP | ||
|  | 	{ | ||
|  | 		public static $dnsttlcache = array(); | ||
|  | 		private static $depths = array(), $purifier = false, $html = false; | ||
|  | 
 | ||
|  | 		// Reduce dependencies.  Duplicates code though.
 | ||
|  | 		private static function FilenameSafe($filename) | ||
|  | 		{ | ||
|  | 			return preg_replace('/[_]+/', "_", preg_replace('/[^A-Za-z0-9_.\-]/', "_", $filename)); | ||
|  | 		} | ||
|  | 
 | ||
|  | 		private static function ReplaceNewlines($replacewith, $data) | ||
|  | 		{ | ||
|  | 			$data = str_replace("\r\n", "\n", $data); | ||
|  | 			$data = str_replace("\r", "\n", $data); | ||
|  | 			$data = str_replace("\n", $replacewith, $data); | ||
|  | 
 | ||
|  | 			return $data; | ||
|  | 		} | ||
|  | 
 | ||
|  | 		// RFC1341 is a hacky workaround to allow 8-bit over 7-bit transport.
 | ||
|  | 		// Also known as "Quoted Printable".
 | ||
|  | 		public static function ConvertToRFC1341($data, $restrictmore = false) | ||
|  | 		{ | ||
|  | 			$data2 = ""; | ||
|  | 
 | ||
|  | 			// Ranges are limited so that EBCDIC transport works.
 | ||
|  | 			// Also, PHP's mail() function doesn't deal well with lines that start with '.'.
 | ||
|  | 			// http://us2.php.net/manual/en/function.mail.php
 | ||
|  | 			$y = strlen($data); | ||
|  | 			for ($x = 0; $x < $y; $x++) | ||
|  | 			{ | ||
|  | 				$currchr = ord($data[$x]); | ||
|  | 				if ($currchr == 9 || $currchr == 32 || ($currchr >= 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" <emailaddr>, name <emailaddr>, 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); | ||
|  | 			} | ||
|  | 		} | ||
|  | 	} | ||
|  | ?>
 |