2019-09-14 18:29:05 +00:00
< ? 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 \n X-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 ) !== " " )
{
2021-11-08 07:08:56 +00:00
if ( $line [ 0 ] !== " \xFF " && (( $tagname === " /ul " && $line [ 0 ] !== " - " ) || ( $tagname === " /ol " && ! ( int ) $line [ 0 ]))) $prefix2 = " \xFF \xFF " ;
2019-09-14 18:29:05 +00:00
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 );
}
}
}
2021-11-08 07:08:56 +00:00
?>