'INVALID', // POP3 server host name 'user'=>'INVALID', // Authentication user name 'passwd'=>'INVALID', // Authentication password 'port'=>110, // POP 3 server host port, usually 110 // but some servers use other ports. // Gmail uses 995. 'tls'=>false, // Establish secure connections using TLS 'realm'=>'', // Authentication realm or domain 'workstation'=>'', // Workstation for NTLM authentication 'apop'=>false, // Use APOP authentication 'authmech'=>'USER',// SASL authentication mechanism 'debug'=>false, // Output debug information 'htmldebug'=>true, // Debug information is in HTML 'joinheader'=>true // Concatenate headers split in multiple lines )); SDV($wmLegalFrom, array());; SDV($wmThrottlePage, "Site.WikiMailThrottle");; SDV($wmThrottleConfig, array( array(600, 20, 'wikimail: too many messages in the last 10 minutes'), array(7200, 40, 'wikimail: too many messages in the last 2 hours'), array(86400, 100, 'wikimail: too many messages in the last 24 hours') )); SDV($wmFakeReceiveDir, false); // for testing purposes SDV($wmFakeSendDir, false); // for testing purposes # wmMkProfilePOP3() # Parameters - see comments in the first few initialization lines - only the # host, user, and passwd are required (if 'default' then $WikiMail... values # will be used) function wmMkProfilePOP3($profile_name, $host, $user, $passwd, $port='default', $TLS='default', $Realm='default', $Workstation='default', $APOP='default', $AuthMech='default', $Debug='default', $HTMLDebug='default', $JoinHeader='default') { global $WikiMailErr, $wmProfilePOP3; $updating = ($wmProfilePOP3[$profile_name] ? true : false); $tmp = array( 'host'=>$host, 'user'=>$user, 'passwd'=>$passwd, 'port'=>$port, 'tls'=>$TLS, 'realm'=>$Realm, 'workstation'=>$Workstation, 'apop'=>$APOP, 'authmech'=>$AuthMech, 'debug'=>$Debug, 'htmldebug'=>$HTMLDebug, 'joinheader'=>$JoinHeader ); foreach ($tmp as $k => $v) { #echo "profilename=$profile_name, k=$k, v=$v
\n"; if ($v == 'default') { if (!$updating) $wmProfilePOP3[$profile_name][$k] = $wmProfilePOP3['default'][$k]; } else $wmProfilePOP3[$profile_name][$k] = $v; #echo "AFTER: " . $wmProfilePOP3[$profile_name][$k] . "
\n"; } foreach (array('host', 'user', 'passwd') as $v) if ($wmProfilePOP3[$profile_name][$v] == 'INVALID') { $WikiMailErr = "ERROR: Invalid $v"; return(false); } return(true); } # wmInitPOP3 # Initialize pop3_class, open the mailbox, login - ready for wmRetrievePOP3() # Returns true on success, false on error function wmInitPOP3($profile_name) { global $WikiMailErr, $wmProfilePOP3; if (!isset($wmProfilePOP3[$profile_name]['host'])) { $WikiMailErr = "ERROR: Profile \"$profile_name\" incorrectly set up."; return(false); } $pop3=new wm_pop3_class; # POP 3 server host name $pop3->hostname = $wmProfilePOP3[$profile_name]['host']; #echo "wmInitPOP3: profilename=$profile_name, wmhost=" . $wmProfilePOP3[$profile_name]['host']. ", host=$pop3->hostname
\n"; # Authentication user name $pop3->user=$wmProfilePOP3[$profile_name]['user']; # Authentication password $pop3->passwd=$wmProfilePOP3[$profile_name]['passwd']; # POP 3 server host port $pop3->port=$wmProfilePOP3[$profile_name]['port']; # Establish secure connections using TLS $pop3->tls=$wmProfilePOP3[$profile_name]['tls']; # Authentication realm or domain $pop3->realm=$wmProfilePOP3[$profile_name]['realm']; # Workstation for NTLM authentication $pop3->workstation=$wmProfilePOP3[$profile_name]['workstation']; # Use APOP authentication $pop3->APOP=$wmProfilePOP3[$profile_name]['apop']; # SASL authentication mechanism $pop3->authentication_mechanism= $wmProfilePOP3[$profile_name]['authmech']; # Output debug information $pop3->debug=$wmProfilePOP3[$profile_name]['debug']; # Debug information is in HTML $pop3->html_debug= $wmProfilePOP3[$profile_name]['htmldebug']; # Concatenate headers split in multiple lines $pop3->join_continuation_header_lines= $wmProfilePOP3[$profile_name]['joinheader']; $wmProfilePOP3[$profile_name]['pop3'] = $pop3; return(wmOpenPOP3($profile_name)); } # wmOpenPOP3() # Return value is true if success on opening, false otherwise function wmOpenPOP3($profile_name) { global $WikiMailErr, $wmProfilePOP3; #echo "wmOpenPOP3: profile=$profile_name
\n"; if (!($pop3 = &$wmProfilePOP3[$profile_name]['pop3'])) { $WikiMailErr = "ERROR: Profile \"$profile_name\" has not been properly initialized."; return(false); } if(($WikiMailErr=$pop3->Open())!="") { echo "ERROR: WikiMail: Cannot open the mailbox (host=$pop3->host)
\n"; if($WikiMailErr!="") echo "

Error: ",HtmlSpecialChars($WikiMailErr),"

"; return(false); } if(($WikiMailErr=$pop3->Login($pop3->user, $pop3->passwd, $pop3->APOP))!="") { echo "ERROR: WikiMail: Cannot login to the mailbox
\n"; if($WikiMailErr!="") echo "

Error: ",HtmlSpecialChars($WikiMailErr),"

"; return(false); } return(true); } # wmRetrievePOP3() # Returns array(headers, body) of next message, false on failure or no message function wmRetrievePOP3($profile_name, $DeleteAfterRetrieve = true) { global $WikiMailErr, $wmFakeReceiveDir, $wmProfilePOP3; static $messages = 0, $i = 0; if (!($pop3 = &$wmProfilePOP3[$profile_name]['pop3'])) { $WikiMailErr = "ERROR: Profile \"$profile_name\" has not been properly initialized."; return(false); } if ($i == 0) { if(($WikiMailErr=$pop3->Statistics($messages,$size))!="") { echo "ERROR: WikiMail: Cannot get statistics from the mailbox
\n"; if($WikiMailErr!="") echo "

Error: ",HtmlSpecialChars($WikiMailErr),"

"; return(false); } if ($messages == 0) return(false); $i = 1; } else { $i++; if ($i > $messages) return(false); } if (($WikiMailErr=$pop3->RetrieveMessage($i,$headers,$body,-1))!="") { if($WikiMailErr!="") echo "

Error: ",HtmlSpecialChars($WikiMailErr),"

"; return(false); } else $pop3->currmsg = $i; if($DeleteAfterRetrieve && ($WikiMailErr=$pop3->DeleteMessage($i))!="") { echo "ERROR: WikiMail: Unable to delete message #${i}
\n"; if($WikiMailErr!="") echo "

Error: ",HtmlSpecialChars($WikiMailErr),"

"; return(false); } #$body = implode("\n", $body); foreach ($headers as $h) { list($k, $v) = explode(":", $h, 2); $header[trim(strtolower($k))] = trim($v); } return(array($header, $headers, $body)); } # wmDeletePOP3 function wmDeletePOP3($profile_name) { global $WikiMailErr, $wmProfilePOP3; if (!($pop3 = &$wmProfilePOP3[$profile_name]['pop3'])) { $WikiMailErr = "ERROR: Profile \"$profile_name\" has not been properly initialized."; return(false); } if ($pop3->currmsg < 0) { $WikiMailErr = "ERROR: No current message to delete."; return(false); } if(($WikiMailErr=$pop3->DeleteMessage($pop3->currmsg))!="") { echo "ERROR: WikiMail: Unable to delete message #${i}
\n"; if($WikiMailErr!="") echo "

Error: ",HtmlSpecialChars($WikiMailErr),"

"; return(false); } } function wmClosePOP3($profile_name) { global $WikiMailErr, $wmProfilePOP3; if (!($pop3 = &$wmProfilePOP3[$profile_name]['pop3'])) { $WikiMailErr = "ERROR: Profile \"$profile_name\" has not been properly initialized."; return(false); } if ($WikiMailErr=$pop3->Close()) { if($WikiMailErr!="") echo "

Error: ",HtmlSpecialChars($WikiMailErr),"

"; } return($WikiMailErr == ''); // if no error then we return success } # This function fakes retrieving email from a directory $wmFakeReceiveDir where # the files are named 001.txt, 002.txt, etc. and have a *very* basic format # *** ONLY FOR TESTING *** # ===(snip)=== #From: from@email.address #To: to@email.address #Date: June 1, 2008 #Subject: what the subject is # #This is the body # ===(snip)=== function wmFakeRetrieve() { global $wmFakeReceiveDir; static $messages = 0, $i = 0; $i++; $fn = sprintf("%03d.txt", $i); if (!file_exists("$wmFakeReceiveDir/$fn")) return(false); $fp = fopen("$wmFakeReceiveDir/$fn", "r"); $text = fread($fp, filesize("$wmFakeReceiveDir/$fn")); fclose($fp); list($headers, $body) = explode("\n\n", $text); if (!$headers || !$body) list($headers, $body) = explode(PHP_EOL.PHP_EOL, $text); dbg(1, "Fake: headers=$headers, body=$body"); $headers = explode("\n", $headers); $body = explode("\n", $body); #$body = implode("\n", $body); foreach ($headers as $h) { list($k, $v) = explode(":", $h, 2); $header[trim(strtolower($k))] = trim($v); } return(array($header, $headers, $body)); } # wmSendMail() # $pagename # $to - email address (or array of email addresses) # $subject - a string with the subject of the message. # $message - a string with the content of the message. # -the markup rules listed in $MarkupRules will always be run over # the text. This normally includes processing of (:if ...:) and # (:include ...:) but can also include other rules by an administrator. # -FmtPageName() will also be run over $message, giving variable substitution # capability # $more_headers - passed directly to mail() as $additional_headers # $more_params - passed directly to mail() as $additional_parameters # $from - optional valid email address - if blank it will default to # sendmail.from in php.ini # $format - NULL - use "html" if ($MarkupRules === true), "text" otherwise # - "html" - always use HTML format in sending the email # - "text" - always use text format in sending the email # $MarkupRules - overloaded as follows # - if (is_array($MarkupRules)) then it is a list of markup rules (just the # ID of the rule) and these rules will be run over $message. # Useful rules (these are the default if the param is not specified): # "include" - allow pages to be included # "comment" - allow comments to be stripped out # "if" - allow conditional processing # "{$var}" - expand variables # - if ($MarkupRules === true) then ALL rules will be run over $message # and format=HTML will be the default # - if ($MarkupRules === false) then NO rules will be run over $message #RETURN VALUE # non-empty string = error message, unsuccessful # empty string = no error message, successful # **NOTE that this is reverse from a typical true/false return value function wmSendMail($pagename, $to, $subject, $message, $more_headers = '', $more_params = '', $from=NULL, $format = NULL, $MarkupRules = array('include', 'comment', 'if')) { global $WikiMailSMTP, $wmThrottlePage, $wmLegalFrom; $func = 'wmSendMail()'; dbg(4,"$func: Entering"); dbg(3,"$func: to=" . od($to) . ", subject=$subject"); dbg(2,"$func: message=$message"); $errs = false; if ($msg = wmThrottled($pagename, time())) { Err($pagename, array(), "ERROR: $func: $msg"); return($msg); } if ($from && (!$wmLegalFrom || in_array($from, $wmLegalFrom))) $more_headers .= "From: $from\r\n"; #ini_set("sendmail_from",$from); elseif ($WikiMailSMTP['From']) $more_headers .= "From: $WikiMailSMTP[From]\r\n"; #ini_set("sendmail_from", $WikiMailSMTP['From']); if ($WikiMailSMTP['Host']) ini_set('SMTP', $WikiMailSMTP['Host']); if ($WikiMailSMTP['Port']) ini_set('smtp_port', $WikiMailSMTP['Port']); if ($WikiMailSMTP['SendmailPath']) ini_set('sendmail_path', $WikiMailSMTP['SendmailPath']); dbg(1,"$func: smtp host=" . ini_get('SMTP') . ", port=" . ini_get('smtp_port')); $subject = FmtPagename($subject, $pagename); $message = FmtPagename($message, $pagename); if (!$format) $format = (($MarkupRules === true) ? 'html' : 'text'); if (is_array($MarkupRules)) { dbg($d*1, "$func: Running a set of markup rules: " . implode(", ", $MarkupRules)); $message = RunMarkupRules($pagename, $MarkupRules, $message); } elseif ($MarkupRules === true) { dbg($d*1, "$func: Running full MarkupToHTML()"); $message = MarkupToHTML($pagename, $message); } $savemessage = $message; $nl = "\r\n"; $separator = 'PHP' . md5(uniqid(time())); $more_headers .= "MIME-Version: 1.0" . $nl; if ($format == 'html') { $more_headers .= "Content-type: text/html; charset=iso-8859-1".$nl; $more_headers .= 'Content-Transfer-Encoding: 7bit' . $nl; $message = "\n$message\n"; } elseif ($format == 'text' || $format == 'both') { $message = ''; if ($format == 'both') { $more_headers .= "Content-Type: multipart/alternative;\n\tboundary=\"$separator\"" . $nl; $message .= "This is a multi-part message in MIME format.\nIf you are reading this, consider upgrading your e-mail client to a MIME-compatible client.\n"; $message .= "${nl}--{$separator}${nl}"; $message .= "Content-Type: text/plain; charset=ISO-8859-15${nl}"; $message .= "Content-Transfer-Encoding: 7bit${nl}${nl}"; } $message .= strip_tags($savemessage); if ($format == 'both') { $message .= "${nl}--{$separator}${nl}";; $message .= "Content-Type: text/html; charset=ISO-8859-15${nl}"; $message .= "Content-Transfer-Encoding: 7bit${nl}"; $message .= "\n{$savemessage}"; $message .= "\n--{$separator}\n"; } } #echo "
${more_headers}xxxxxxxxx===xxxxxxx${message}
\n"; foreach ((array)$to as $to_addr) { dbg(4,"$func: Sending"); dbg(3,"$func: to=" . od($to_addr) . ", subject=$subject, format=$format"); dbg(2,"$func: message=$message"); wmLogThrottle($pagename, "to: $to_addr, subject: $subject"); if (!mail($to_addr, $subject, $message, $more_headers, $more_params)) { Err($pagename, array(), "ERROR: $func: mail() failed"); $errs = true; } } return ($errs); } # WikiMailBoiledEmail() # This function takes a "fancy" email and boils it down to its essence # i.e., "John Jacobs " becomes "john@jacobs.com" function wmBoiledEmail($email) { # This needs to get a lot more robust, but this probably handles 95% of # cases in this simple form. if (preg_match('/<([^>]*)>/', $email, $m)) $email = $m[1]; return($email); } # wmLogThrottle() function wmLogThrottle($pagename, $text) { global $wmThrottlePage; if (!defined('toolbox')) return; // silently ignore it $text = time() . ':' . $text; logit($pagename, $wmThrottlePage, $text, array('pre'=>true)); } # wmThrottled() # Return message if we've done too many too quickly. Otherwise return false. # $wmThrottleConfig is an array # [n][0] in the last this number of seconds # [n][1] maximum number of events # [n][2] string to return if exceeded # It should be sorted where [n][0] is always less than [n+1][0] function wmThrottled($pagename, $needle) { global $wmThrottleConfig, $wmThrottlePage; $wmThrottlePN = MakePageName($pagename, FmtPageName($wmThrottlePage, $pagename)); $page = ReadPage($wmThrottlePN, READPAGE_CURRENT); $rows = explode("\n", $page['text']); reset($wmThrottleConfig); $throttle = each($wmThrottleConfig); if ($throttle) { foreach ($rows as $i => $row) { if ($pos = strpos($row, ':')) { $rowtime = substr($row, 0, $pos)+0; if ($rowtime <= $needle-$throttle[1][0]) { if (!($throttle = each($wmThrottleConfig))) return(false); } if ($i >= $throttle[1][1]) return($throttle[1][2]); } } } return(false); } class wm_pop3_class extends pop3_class { var $user=""; var $passwd=""; var $currmsg=-1; } ##### This section (below) is taken from Manuel Lemos' script pop3.php ##### /* * pop3.php * * @(#) $Header: /home/mlemos/cvsroot/pop3/pop3.php,v 1.22 2008/01/25 02:10:47 mlemos Exp $ * */ class pop3_class { var $hostname=""; var $port=110; var $tls=0; var $quit_handshake=1; var $error=""; var $authentication_mechanism="USER"; var $realm=""; var $workstation=""; var $join_continuation_header_lines=1; /* Private variables - DO NOT ACCESS */ var $connection=0; var $state="DISCONNECTED"; var $greeting=""; var $must_update=0; var $debug=0; var $html_debug=0; var $next_token=""; var $message_buffer=""; var $connection_name = ''; /* Private methods - DO NOT CALL */ Function Tokenize($string,$separator="") { if(!strcmp($separator,"")) { $separator=$string; $string=$this->next_token; } for($character=0;$characternext_token=substr($string,$found+1); return(substr($string,0,$found)); } else { $this->next_token=""; return($string); } } Function SetError($error) { return($this->error=$error); } Function OutputDebug($message) { $message.="\n"; if($this->html_debug) $message=str_replace("\n","
\n",HtmlSpecialChars($message)); echo $message; flush(); } Function GetLine() { for($line="";;) { if(feof($this->connection)) return(0); $line.=fgets($this->connection,100); $length=strlen($line); if($length>=2 && substr($line,$length-2,2)=="\r\n") { $line=substr($line,0,$length-2); if($this->debug) $this->OutputDebug("S $line"); return($line); } } } Function PutLine($line) { if($this->debug) $this->OutputDebug("C $line"); return(fputs($this->connection,"$line\r\n")); } Function OpenConnection() { if($this->tls) { $version=explode(".",function_exists("phpversion") ? phpversion() : "3.0.7"); $php_version=intval($version[0])*1000000+intval($version[1])*1000+intval($version[2]); if($php_version<4003000) return("establishing TLS connections requires at least PHP version 4.3.0"); if(!function_exists("extension_loaded") || !extension_loaded("openssl")) return("establishing TLS connections requires the OpenSSL extension enabled"); } if($this->hostname=="") return($this->SetError("2 it was not specified a valid hostname")); if($this->debug) $this->OutputDebug("Connecting to ".$this->hostname." ..."); if(($this->connection=@fsockopen(($this->tls ? "tls://" : "").$this->hostname, $this->port, $error, $error_message))==0) { switch($error) { case -3: return($this->SetError("-3 socket could not be created")); case -4: return($this->SetError("-4 dns lookup on hostname \"$hostname\" failed")); case -5: return($this->SetError("-5 connection refused or timed out")); case -6: return($this->SetError("-6 fdopen() call failed")); case -7: return($this->SetError("-7 setvbuf() call failed")); default: return($this->SetError($error." could not connect to the host \"".$this->hostname."\": ".$error_message)); } } return(""); } Function CloseConnection() { if($this->debug) $this->OutputDebug("Closing connection."); if($this->connection!=0) { fclose($this->connection); $this->connection=0; } } /* Public methods */ /* Open method - set the object variable $hostname to the POP3 server address. */ Function Open() { if($this->state!="DISCONNECTED") return($this->SetError("1 a connection is already opened")); if(($error=$this->OpenConnection())!="") return($error); $this->greeting=$this->GetLine(); if(GetType($this->greeting)!="string" || $this->Tokenize($this->greeting," ")!="+OK") { $this->CloseConnection(); return($this->SetError("3 POP3 server greeting was not found")); } $this->Tokenize("<"); $this->must_update=0; $this->state="AUTHORIZATION"; return(""); } /* Close method - this method must be called at least if there are any messages to be deleted */ Function Close() { if($this->state=="DISCONNECTED") return($this->SetError("no connection was opened")); while($this->state=='GETMESSAGE') { if(strlen($error=$this->GetMessage(8000, $message, $end_of_message))) return($error); } if($this->must_update || $this->quit_handshake) { if($this->PutLine("QUIT")==0) return($this->SetError("Could not send the QUIT command")); $response=$this->GetLine(); if(GetType($response)!="string") return($this->SetError("Could not get quit command response")); if($this->Tokenize($response," ")!="+OK") return($this->SetError("Could not quit the connection: ".$this->Tokenize("\r\n"))); } $this->CloseConnection(); $this->state="DISCONNECTED"; pop3_class::SetConnection(-1, $this->connection_name, $this); return(""); } /* Login method - pass the user name and password of POP account. Set $apop to 1 or 0 wether you want to login using APOP method or not. */ Function Login($user,$password,$apop=0) { if($this->state!="AUTHORIZATION") return($this->SetError("connection is not in AUTHORIZATION state")); if($apop) { if(!strcmp($this->greeting,"")) return($this->SetError("Server does not seem to support APOP authentication")); if($this->PutLine("APOP $user ".md5("<".$this->greeting.">".$password))==0) return($this->SetError("Could not send the APOP command")); $response=$this->GetLine(); if(GetType($response)!="string") return($this->SetError("Could not get APOP login command response")); if($this->Tokenize($response," ")!="+OK") return($this->SetError("APOP login failed: ".$this->Tokenize("\r\n"))); } else { $authenticated=0; if(strcmp($this->authentication_mechanism,"USER") && function_exists("class_exists") && class_exists("sasl_client_class")) { if(strlen($this->authentication_mechanism)) $mechanisms=array($this->authentication_mechanism); else { $mechanisms=array(); if($this->PutLine("CAPA")==0) return($this->SetError("Could not send the CAPA command")); $response=$this->GetLine(); if(GetType($response)!="string") return($this->SetError("Could not get CAPA command response")); if(!strcmp($this->Tokenize($response," "),"+OK")) { for(;;) { $response=$this->GetLine(); if(GetType($response)!="string") return($this->SetError("Could not retrieve the supported authentication methods")); switch($this->Tokenize($response," ")) { case ".": break 2; case "SASL": for($method=1;strlen($mechanism=$this->Tokenize(" "));$method++) $mechanisms[]=$mechanism; break; } } } } $sasl=new sasl_client_class; $sasl->SetCredential("user",$user); $sasl->SetCredential("password",$password); if(strlen($this->realm)) $sasl->SetCredential("realm",$this->realm); if(strlen($this->workstation)) $sasl->SetCredential("workstation",$this->workstation); do { $status=$sasl->Start($mechanisms,$message,$interactions); } while($status==SASL_INTERACT); switch($status) { case SASL_CONTINUE: break; case SASL_NOMECH: if(strlen($this->authentication_mechanism)) return($this->SetError("authenticated mechanism ".$this->authentication_mechanism." may not be used: ".$sasl->error)); break; default: return($this->SetError("Could not start the SASL authentication client: ".$sasl->error)); } if(strlen($sasl->mechanism)) { if($this->PutLine("AUTH ".$sasl->mechanism.(IsSet($message) ? " ".base64_encode($message) : ""))==0) return("Could not send the AUTH command"); $response=$this->GetLine(); if(GetType($response)!="string") return("Could not get AUTH command response"); switch($this->Tokenize($response," ")) { case "+OK": $response=""; break; case "+": $response=base64_decode($this->Tokenize("\r\n")); break; default: return($this->SetError("Authentication error: ".$this->Tokenize("\r\n"))); } for(;!$authenticated;) { do { $status=$sasl->Step($response,$message,$interactions); } while($status==SASL_INTERACT); switch($status) { case SASL_CONTINUE: if($this->PutLine(base64_encode($message))==0) return("Could not send message authentication step message"); $response=$this->GetLine(); if(GetType($response)!="string") return("Could not get authentication step message response"); switch($this->Tokenize($response," ")) { case "+OK": $authenticated=1; break; case "+": $response=base64_decode($this->Tokenize("\r\n")); break; default: return($this->SetError("Authentication error: ".$this->Tokenize("\r\n"))); } break; default: return($this->SetError("Could not process the SASL authentication step: ".$sasl->error)); } } } } if(!$authenticated) { if($this->PutLine("USER $user")==0) return($this->SetError("Could not send the USER command")); $response=$this->GetLine(); if(GetType($response)!="string") return($this->SetError("Could not get user login entry response")); if($this->Tokenize($response," ")!="+OK") return($this->SetError("User error: ".$this->Tokenize("\r\n"))); if($this->PutLine("PASS $password")==0) return($this->SetError("Could not send the PASS command")); $response=$this->GetLine(); if(GetType($response)!="string") return($this->SetError("Could not get login password entry response")); if($this->Tokenize($response," ")!="+OK") return($this->SetError("Password error: ".$this->Tokenize("\r\n"))); } } $this->state="TRANSACTION"; return(""); } /* Statistics method - pass references to variables to hold the number of messages in the mail box and the size that they take in bytes. */ Function Statistics(&$messages,&$size) { if($this->state!="TRANSACTION") #plb return($this->SetError("connection is not in TRANSACTION state (".$this->state.")")); #return($this->SetError("connection is not in TRANSACTION state")); if($this->PutLine("STAT")==0) return($this->SetError("Could not send the STAT command")); $response=$this->GetLine(); if(GetType($response)!="string") return($this->SetError("Could not get the statistics command response")); if($this->Tokenize($response," ")!="+OK") return($this->SetError("Could not get the statistics: ".$this->Tokenize("\r\n"))); $messages=$this->Tokenize(" "); $size=$this->Tokenize(" "); return(""); } /* ListMessages method - the $message argument indicates the number of a message to be listed. If you specify an empty string it will list all messages in the mail box. The $unique_id flag indicates if you want to list the each message unique identifier, otherwise it will return the size of each message listed. If you list all messages the result will be returned in an array. */ Function ListMessages($message,$unique_id) { if($this->state!="TRANSACTION") return($this->SetError("connection is not in TRANSACTION state")); if($unique_id) $list_command="UIDL"; else $list_command="LIST"; if($this->PutLine("$list_command".($message ? " ".$message : ""))==0) return($this->SetError("Could not send the $list_command command")); $response=$this->GetLine(); if(GetType($response)!="string") return($this->SetError("Could not get message list command response")); if($this->Tokenize($response," ")!="+OK") return($this->SetError("Could not get the message listing: ".$this->Tokenize("\r\n"))); if($message=="") { for($messages=array();;) { $response=$this->GetLine(); if(GetType($response)!="string") return($this->SetError("Could not get message list response")); if($response==".") break; $message=intval($this->Tokenize($response," ")); if($unique_id) $messages[$message]=$this->Tokenize(" "); else $messages[$message]=intval($this->Tokenize(" ")); } return($messages); } else { $message=intval($this->Tokenize(" ")); $value=$this->Tokenize(" "); return($unique_id ? $value : intval($value)); } } /* RetrieveMessage method - the $message argument indicates the number of a message to be listed. Pass a reference variables that will hold the arrays of the $header and $body lines. The $lines argument tells how many lines of the message are to be retrieved. Pass a negative number if you want to retrieve the whole message. */ Function RetrieveMessage($message,&$headers,&$body,$lines) { if($this->state!="TRANSACTION") return($this->SetError("connection is not in TRANSACTION state")); if($lines<0) { $command="RETR"; $arguments="$message"; } else { $command="TOP"; $arguments="$message $lines"; } if($this->PutLine("$command $arguments")==0) return($this->SetError("Could not send the $command command")); $response=$this->GetLine(); if(GetType($response)!="string") return($this->SetError("Could not get message retrieval command response")); if($this->Tokenize($response," ")!="+OK") return($this->SetError("Could not retrieve the message: ".$this->Tokenize("\r\n"))); for($headers=$body=array(),$line=0;;) { $response=$this->GetLine(); if(GetType($response)!="string") return($this->SetError("Could not retrieve the message")); switch($response) { case ".": return(""); case "": break 2; default: if(substr($response,0,1)==".") $response=substr($response,1,strlen($response)-1); break; } if($this->join_continuation_header_lines && $line>0 && ($response[0]=="\t" || $response[0]==" ")) $headers[$line-1].=$response; else { $headers[$line]=$response; $line++; } } for($line=0;;$line++) { $response=$this->GetLine(); if(GetType($response)!="string") return($this->SetError("Could not retrieve the message")); switch($response) { case ".": return(""); default: if(substr($response,0,1)==".") $response=substr($response,1,strlen($response)-1); break; } $body[$line]=$response; } return(""); } /* OpenMessage method - the $message argument indicates the number of a message to be opened. The $lines argument tells how many lines of the message are to be retrieved. Pass a negative number if you want to retrieve the whole message. */ Function OpenMessage($message, $lines=-1) { if($this->state!="TRANSACTION") return($this->SetError("connection is not in TRANSACTION state")); if($lines<0) { $command="RETR"; $arguments="$message"; } else { $command="TOP"; $arguments="$message $lines"; } if($this->PutLine("$command $arguments")==0) return($this->SetError("Could not send the $command command")); $response=$this->GetLine(); if(GetType($response)!="string") return($this->SetError("Could not get message retrieval command response")); if($this->Tokenize($response," ")!="+OK") return($this->SetError("Could not retrieve the message: ".$this->Tokenize("\r\n"))); $this->state="GETMESSAGE"; $this->message_buffer=""; return(""); } /* GetMessage method - the $count argument indicates the number of bytes to be read from an opened message. The $message returns by reference the data read from the message. The $end_of_message argument returns by reference a boolean value indicated whether it was reached the end of the message. */ Function GetMessage($count, &$message, &$end_of_message) { if($this->state!="GETMESSAGE") return($this->SetError("connection is not in GETMESSAGE state")); $message=""; $end_of_message=0; while($count>strlen($this->message_buffer) && !$end_of_message) { $response=$this->GetLine(); if(GetType($response)!="string") return($this->SetError("Could not retrieve the message headers")); if(!strcmp($response,".")) { $end_of_message=1; $this->state="TRANSACTION"; break; } else { if(substr($response,0,1)==".") $response=substr($response,1,strlen($response)-1); $this->message_buffer.=$response."\r\n"; } } if($end_of_message || $count>=strlen($this->message_buffer)) { $message=$this->message_buffer; $this->message_buffer=""; } else { $message=substr($this->message_buffer, 0, $count); $this->message_buffer=substr($this->message_buffer, $count); } return(""); } /* DeleteMessage method - the $message argument indicates the number of a message to be marked as deleted. Messages will only be effectively deleted upon a successful call to the Close method. */ Function DeleteMessage($message) { if($this->state!="TRANSACTION") return($this->SetError("connection is not in TRANSACTION state")); if($this->PutLine("DELE $message")==0) return($this->SetError("Could not send the DELE command")); $response=$this->GetLine(); if(GetType($response)!="string") return($this->SetError("Could not get message delete command response")); if($this->Tokenize($response," ")!="+OK") return($this->SetError("Could not delete the message: ".$this->Tokenize("\r\n"))); $this->must_update=1; return(""); } /* ResetDeletedMessages method - Reset the list of marked to be deleted messages. No messages will be marked to be deleted upon a successful call to this method. */ Function ResetDeletedMessages() { if($this->state!="TRANSACTION") return($this->SetError("connection is not in TRANSACTION state")); if($this->PutLine("RSET")==0) return($this->SetError("Could not send the RSET command")); $response=$this->GetLine(); if(GetType($response)!="string") return($this->SetError("Could not get reset deleted messages command response")); if($this->Tokenize($response," ")!="+OK") return($this->SetError("Could not reset deleted messages: ".$this->Tokenize("\r\n"))); $this->must_update=0; return(""); } /* IssueNOOP method - Just pings the server to prevent it auto-close the connection after an idle timeout (tipically 10 minutes). Not very useful for most likely uses of this class. It's just here for protocol support completeness. */ Function IssueNOOP() { if($this->state!="TRANSACTION") return($this->SetError("connection is not in TRANSACTION state")); if($this->PutLine("NOOP")==0) return($this->SetError("Could not send the NOOP command")); $response=$this->GetLine(); if(GetType($response)!="string") return($this->SetError("Could not NOOP command response")); if($this->Tokenize($response," ")!="+OK") return($this->SetError("Could not issue the NOOP command: ".$this->Tokenize("\r\n"))); return(""); } Function &SetConnection($set, &$current_name, &$pop3) { static $connections = array(); if($set>0) { $current_name = strval(count($connections)); $connections[$current_name] = &$pop3; } elseif($set<0) { $connections[$current_name] = ''; $current_name = ''; } elseif(IsSet($connections[$current_name]) && GetType($connections[$current_name])!='string') { $connection = &$connections[$current_name]; return($connection); } return($pop3); } /* GetConnectionName method - Retrieve the name associated to an established POP3 server connection to use as virtual host name for use in POP3 stream wrapper URLs. */ Function GetConnectionName(&$connection_name) { if($this->state!="TRANSACTION") return($this->SetError("cannot get the name of a POP3 connection that was not established and the user has logged in")); if(strlen($this->connection_name) == 0) pop3_class::SetConnection(1, $this->connection_name, $this); $connection_name = $this->connection_name; return(''); } }; class pop3_stream { var $opened = 0; var $report_errors = 1; var $read = 0; var $buffer = ""; var $end_of_message=1; var $previous_connection = 0; var $pop3; Function SetError($error) { if($this->report_errors) trigger_error($error); return(FALSE); } Function ParsePath($path, &$url) { if(!$this->previous_connection) { if(IsSet($url["host"])) $this->pop3->hostname=$url["host"]; if(IsSet($url["port"])) $this->pop3->port=intval($url["port"]); if(IsSet($url["scheme"]) && !strcmp($url["scheme"],"pop3s")) $this->pop3->tls=1; if(!IsSet($url["user"])) return($this->SetError("it was not specified a valid POP3 user")); if(!IsSet($url["pass"])) return($this->SetError("it was not specified a valid POP3 password")); if(!IsSet($url["path"])) return($this->SetError("it was not specified a valid mailbox path")); } if(IsSet($url["query"])) { parse_str($url["query"],$query); if(IsSet($query["debug"])) $this->pop3->debug = intval($query["debug"]); if(IsSet($query["html_debug"])) $this->pop3->html_debug = intval($query["html_debug"]); if(!$this->previous_connection) { if(IsSet($query["tls"])) $this->pop3->tls = intval($query["tls"]); if(IsSet($query["realm"])) $this->pop3->realm = UrlDecode($query["realm"]); if(IsSet($query["workstation"])) $this->pop3->workstation = UrlDecode($query["workstation"]); if(IsSet($query["authentication_mechanism"])) $this->pop3->realm = UrlDecode($query["authentication_mechanism"]); } if(IsSet($query["quit_handshake"])) $this->pop3->quit_handshake = intval($query["quit_handshake"]); } return(TRUE); } Function stream_open($path, $mode, $options, &$opened_path) { $this->report_errors = (($options & STREAM_REPORT_ERRORS) !=0); if(strcmp($mode, "r")) return($this->SetError("the message can only be opened for reading")); $url=parse_url($path); $host = $url['host']; $pop3 = &pop3_class::SetConnection(0, $host, $this->pop3); if(IsSet($pop3)) { $this->pop3 = &$pop3; $this->previous_connection = 1; } else $this->pop3=new pop3_class; if(!$this->ParsePath($path, $url)) return(FALSE); $message=substr($url["path"],1); if(strcmp(intval($message), $message) || $message<=0) return($this->SetError("it was not specified a valid message to retrieve")); if(!$this->previous_connection) { if(strlen($error=$this->pop3->Open())) return($this->SetError($error)); $this->opened = 1; $apop = (IsSet($url["query"]["apop"]) ? intval($url["query"]["apop"]) : 0); if(strlen($error=$this->pop3->Login(UrlDecode($url["user"]), UrlDecode($url["pass"]),$apop))) { $this->stream_close(); return($this->SetError($error)); } } if(strlen($error=$this->pop3->OpenMessage($message,-1))) { $this->stream_close(); return($this->SetError($error)); } $this->end_of_message=FALSE; if($options & STREAM_USE_PATH) $opened_path=$path; $this->read = 0; $this->buffer = ""; return(TRUE); } Function stream_eof() { if($this->read==0) return(FALSE); return($this->end_of_message); } Function stream_read($count) { if($count<=0) return($this->SetError("it was not specified a valid length of the message to read")); if($this->end_of_message) return(""); if(strlen($error=$this->pop3->GetMessage($count, $read, $this->end_of_message))) return($this->SetError($error)); $this->read += strlen($read); return($read); } Function stream_close() { while(!$this->end_of_message) $this->stream_read(8000); if($this->opened) { $this->pop3->Close(); $this->opened = 0; } } }; ##### This section (above) is taken from Manuel Lemos' script pop3.php ##### # # wmSimplifyMime() # Accepts 2 arrays, 1 of headers and 1 of body (as per RetrievePop3()) # Returns an array as per Analyze() from mime_parser on success or false on err # # Return array: # ['Type'] (either 'html' or 'text', depending on type of message) # ['Data'] (data of actual message) # ['Alternative'][n] (array of alternate message types) # ['Type'] (as above, but for alternate) # ['Data'] (as above, but for alternate) # ['Filename'] (?) # ['Attachments'][n] (array of attachments) # ['Type'] ('binary', 'text', etc.) # ['Data'] (content of attachment) # ['Filename'] (filename of attachment) # ['Subject'] (subject of message) # ['Date'] (date of message) # ... function wmSimplifyMime($message_data) { #$message_data = implode("\n", $headers) . "\n\n" . implode("\n", $body); $mime=new mime_parser_class; $mime->mbox = 0; $mime->decode_bodies = 1; $mime->decode_headers = 1; $mime->ignore_syntax_errors = 1; $parameters=array( 'Data'=>$message_data ); if (!$mime->Decode($parameters, $decoded)) { #echo "ERROR decoding TestMime...
\n"; return(false); } else { for ($i=0; $i\n
";
            #var_dump($decoded[$i]);
            #echo "

\n"; if ($mime->Analyze($decoded[$i], $results)) { return($results); } else echo "Analyze error " . $mime->error . "
\n"; } foreach ($mime->warnings as $k->$w) { echo "WARNING[$k]: $w
\n"; } } return(false); } # wmTextMessage() # $SimpleMime - from wmSimplifyMime (from mime_parser_class->Analyze()) # returns text message only (not html, not attachments, etc.) on success # returns false on error function wmTextMessage($SimpleMime) { #echo "Type=$SimpleMime[Type]
\n"; #echo "
".print_r($SimpleMime,true)."

\n"; if ($SimpleMime['Type'] == 'text') return($SimpleMime['Data']); else { foreach ($SimpleMime['Alternative'] as $sm) { #echo "AltType=$sm[Type]
\n"; if ($sm['Type'] == 'text') return($sm['Data']); } } return(false); } # wmAttachment() # Arguments # $SimpleMime - from wmSimplifyMime (from mime_parser_class->Analyze()) # $filename - name of the file whose attachment content you want # - or, given a numeric which does not match any filename, return # attachment #$filename # Return Value # list($type, $data, $filename) of the attachment if success # false if failure function wmAttachment($SimpleMime, $filename) { dbg(4, "wmAttachment($filename): Entering"); # Note that we don't check the numeric solution until after we have checked # the filename solution. Basically filename always trumps, even if it's # an entirely numeric filename. dbg(1, "wmAttachment(): mime follows", $SimpleMime); foreach ($SimpleMime['Attachments'] as $a) { if ($a['FileName'] == $filename) return(array($a['Type'], $a['Data'], $a['FileName'])); } dbg(1, "wmAttachment: Checking for numeric (filename=$filename)"); if (is_numeric($filename) && isset($SimpleMime['Attachments'][$filename])) return(array($SimpleMime['Attachments'][$filename]['Type'], $SimpleMime['Attachments'][$filename]['Data'], $SimpleMime['Attachments'][$filename]['FileName'])); dbg(4, "wmAttachment: Returning FALSE"); return(false); } #### This section (below) is taken from Manuel Lemos' script mime_parser.php ####### Version 1.51 2008/04/15 /* * mime_parser.php * * @(#) $Id: wikimail.php 401 2009-11-14 18:25:29Z root $ * */ define('MIME_PARSER_START', 1); define('MIME_PARSER_HEADER', 2); define('MIME_PARSER_HEADER_VALUE', 3); define('MIME_PARSER_BODY', 4); define('MIME_PARSER_BODY_START', 5); define('MIME_PARSER_BODY_DATA', 6); define('MIME_PARSER_BODY_DONE', 7); define('MIME_PARSER_END', 8); define('MIME_MESSAGE_START', 1); define('MIME_MESSAGE_GET_HEADER_NAME', 2); define('MIME_MESSAGE_GET_HEADER_VALUE', 3); define('MIME_MESSAGE_GET_BODY', 4); define('MIME_MESSAGE_GET_BODY_PART', 5); define('MIME_ADDRESS_START', 1); define('MIME_ADDRESS_FIRST', 2); class mime_parser_class { var $error=''; var $error_position = -1; var $mbox = 0; var $decode_headers = 1; var $decode_bodies = 1; var $extract_addresses = 1; var $address_headers = array( 'from:' => 1, 'to:' => 1, 'cc:' => 1, 'bcc:' => 1, 'return-path:'=>1, 'reply-to:'=>1, 'disposition-notification-to:'=>1 ); var $ignore_syntax_errors=1; var $warnings=array(); /* Private variables */ var $state = MIME_PARSER_START; var $buffer = ''; var $buffer_position = 0; var $offset = 0; var $parts = array(); var $part_position = 0; var $headers = array(); var $body_parser; var $body_parser_state = MIME_PARSER_BODY_DONE; var $body_buffer = ''; var $body_buffer_position = 0; var $body_offset = 0; var $current_header = ''; var $file; var $body_file; var $position = 0; var $body_part_number = 1; var $next_token = ''; /* Private functions */ Function SetError($error) { $this->error = $error; return(0); } Function SetErrorWithContact($error) { return($this->SetError($error.'. Please contact the author Manuel Lemos and send a copy of this message to let him add support for this kind of messages')); } Function SetPositionedError($error, $position) { $this->error_position = $position; return($this->SetError($error)); } Function SetPositionedWarning($error, $position) { if(!$this->ignore_syntax_errors) return($this->SetPositionedError($error, $position)); $this->warnings[$position]=$error; return(1); } Function SetPHPError($error, &$php_error_message) { if(IsSet($php_error_message) && strlen($php_error_message)) $error .= ': '.$php_error_message; return($this->SetError($error)); } Function ResetParserState() { $this->error=''; $this->error_position = -1; $this->state = MIME_PARSER_START; $this->buffer = ''; $this->buffer_position = 0; $this->offset = 0; $this->parts = array(); $this->part_position = 0; $this->headers = array(); $this->body_parser_state = MIME_PARSER_BODY_DONE; $this->body_buffer = ''; $this->body_buffer_position = 0; $this->body_offset = 0; $this->current_header = ''; $this->position = 0; $this->body_part_number = 1; $this->next_token = ''; } Function Tokenize($string,$separator="") { if(!strcmp($separator,"")) { $separator=$string; $string=$this->next_token; } for($character=0;$characternext_token=substr($string,$found+1); return(substr($string,0,$found)); } else { $this->next_token=''; return($string); } } Function ParseStructuredHeader($value, &$type, &$parameters, &$character_sets, &$languages) { $type = strtolower(trim($this->Tokenize($value, ';'))); $p = trim($this->Tokenize('')); $parameters = $character_sets = $languages = array(); while(strlen($p)) { $parameter = trim(strtolower($this->Tokenize($p, '='))); $value = trim($this->Tokenize(';')); if(!strcmp($value[0], '"') && !strcmp($value[strlen($value) - 1], '"')) $value = substr($value, 1, strlen($value) - 2); $p = trim($this->Tokenize('')); if(($l=strlen($parameter)) && !strcmp($parameter[$l - 1],'*')) { $parameter=$this->Tokenize($parameter, '*'); if(IsSet($parameters[$parameter]) && IsSet($character_sets[$parameter])) $value = $parameters[$parameter] . UrlDecode($value); else { $character_sets[$parameter] = strtolower($this->Tokenize($value, '\'')); $languages[$parameter] = $this->Tokenize('\''); $value = UrlDecode($this->Tokenize('')); } } $parameters[$parameter] = $value; } } Function FindStringLineBreak($string, $position, &$break, &$line_break) { if(GetType($line_break=strpos($string, $break="\n", $position))=='integer') { if($line_break>$position && $string[$line_break-1]=="\r") { $line_break--; $break="\r\n"; } return(1); } return(GetType($line_break=strpos($string, $break="\r", $position))=='integer'); } Function FindLineBreak($position, &$break, &$line_break) { if(GetType($line_break=strpos($this->buffer, $break="\r", $position))=='integer') { if(($n = $line_break + 1) < strlen($this->buffer) && $this->buffer[$n]=="\n") $break="\r\n"; return(1); } return(GetType($line_break=strpos($this->buffer, $break="\n", $position))=='integer'); } Function FindBodyLineBreak($position, &$break, &$line_break) { if(GetType($line_break=strpos($this->body_buffer, $break="\r", $position))=='integer') { if(($n = $line_break + 1) < strlen($this->body_buffer) && $this->body_buffer[$n]=="\n") $break="\r\n"; return(1); } return(GetType($line_break=strpos($this->body_buffer, $break="\n", $position))=='integer'); } Function ParseHeaderString($body, &$position, &$headers) { $l = strlen($body); $headers = array(); for(;$position < $l;) { if($this->FindStringLineBreak($body, $position, $break, $line_break)) { $line = substr($body, $position, $line_break - $position); $position = $line_break + strlen($break); } else { $line = substr($body, $position); $position = $l; } if(strlen($line)==0) break; $h = strtolower(strtok($line,':')); $headers[$h] = trim(strtok('')); } } Function ParsePart($end, &$part, &$need_more_data) { $need_more_data = 0; switch($this->state) { case MIME_PARSER_START: $part=array( 'Type'=>'MessageStart', 'Position'=>$this->offset + $this->buffer_position ); $this->state = MIME_PARSER_HEADER; break; case MIME_PARSER_HEADER: if($this->FindLineBreak($this->buffer_position, $break, $line_break)) { $next = $line_break + strlen($break); if(!strcmp($break,"\r") && strlen($this->buffer) == $next && !$end) { $need_more_data = 1; break; } if($line_break==$this->buffer_position) { $part=array( 'Type'=>'BodyStart', 'Position'=>$this->offset + $this->buffer_position ); $this->buffer_position = $next; $this->state = MIME_PARSER_BODY; break; } } if(GetType($colon=strpos($this->buffer, ':', $this->buffer_position))=='integer') { if(GetType($space=strpos(substr($this->buffer, $this->buffer_position, $colon - $this->buffer_position), ' '))=='integer') { if((!$this->mbox || strcmp(strtolower(substr($this->buffer, $this->buffer_position, $space)), 'from')) && !$this->SetPositionedWarning('invalid header name line', $this->buffer_position)) return(0); $next = $this->buffer_position + $space + 1; } else $next = $colon+1; } else { $need_more_data = 1; break; } $part=array( 'Type'=>'HeaderName', 'Name'=>substr($this->buffer, $this->buffer_position, $next - $this->buffer_position), 'Position'=>$this->offset + $this->buffer_position ); $this->buffer_position = $next; $this->state = MIME_PARSER_HEADER_VALUE; break; case MIME_PARSER_HEADER_VALUE: $position = $this->buffer_position; $value = ''; for(;;) { if($this->FindLineBreak($position, $break, $line_break)) { $next = $line_break + strlen($break); $line = substr($this->buffer, $position, $line_break - $position); if(strlen($this->buffer) == $next) { if(!$end) { $need_more_data = 1; break 2; } $value .= $line; $part=array( 'Type'=>'HeaderValue', 'Value'=>$value, 'Position'=>$this->offset + $this->buffer_position ); $this->buffer_position = $next; $this->state = MIME_PARSER_END; break ; } else { $character = $this->buffer[$next]; if(!strcmp($character, ' ') || !strcmp($character, "\t")) { $value .= $line; $position = $next; } else { $value .= $line; $part=array( 'Type'=>'HeaderValue', 'Value'=>$value, 'Position'=>$this->offset + $this->buffer_position ); $this->buffer_position = $next; $this->state = MIME_PARSER_HEADER; break 2; } } } else { if(!$end) { $need_more_data = 1; break; } else { $value .= substr($this->buffer, $position); $part=array( 'Type'=>'HeaderValue', 'Value'=>$value, 'Position'=>$this->offset + $this->buffer_position ); $this->buffer_position = strlen($this->buffer); $this->state = MIME_PARSER_END; break; } } } break; case MIME_PARSER_BODY: if($this->mbox) { $add = 0; $append=''; if($this->FindLineBreak($this->buffer_position, $break, $line_break)) { $next = $line_break + strlen($break); $following = $next + strlen($break); if($following >= strlen($this->buffer) || GetType($line=strpos($this->buffer, $break, $following))!='integer') { if(!$end) { $need_more_data = 1; break; } } $start = strtolower(substr($this->buffer, $next, strlen($break.'from '))); if(!strcmp($break.'from ', $start)) { if($line_break == $this->buffer_position) { $part=array( 'Type'=>'MessageEnd', 'Position'=>$this->offset + $this->buffer_position ); $this->buffer_position = $following; $this->state = MIME_PARSER_START; break; } else $add = strlen($break); $next = $line_break; } else { $start = strtolower(substr($this->buffer, $next, strlen('>from '))); if(!strcmp('>from ', $start)) { $part=array( 'Type'=>'BodyData', 'Data'=>substr($this->buffer, $this->buffer_position, $next - $this->buffer_position), 'Position'=>$this->offset + $this->buffer_position ); $this->buffer_position = $next + 1; break; } } } else { if(!$end) { $need_more_data = 1; break; } $next = strlen($this->buffer); $append="\r\n"; } if($next > $this->buffer_position) { $part=array( 'Type'=>'BodyData', 'Data'=>substr($this->buffer, $this->buffer_position, $next + $add - $this->buffer_position).$append, 'Position'=>$this->offset + $this->buffer_position ); } elseif($end) { $part=array( 'Type'=>'MessageEnd', 'Position'=>$this->offset + $this->buffer_position ); $this->state = MIME_PARSER_END; } $this->buffer_position = $next; } else { if(strlen($this->buffer)-$this->buffer_position) { $data=substr($this->buffer, $this->buffer_position, strlen($this->buffer) - $this->buffer_position); $end_line = (!strcmp(substr($data,-1),"\n") || !strcmp(substr($data,-1),"\r")); if($end && !$end_line) { $data.="\n"; $end_line = 1; } $offset = $this->offset + $this->buffer_position; $this->buffer_position = strlen($this->buffer); $need_more_data = !$end; if(!$end_line) { if(GetType($line_break=strrpos($data, "\n"))=='integer' || GetType($line_break=strrpos($data, "\r"))=='integer') { $line_break++; $this->buffer_position -= strlen($data) - $line_break; $data = substr($data, 0, $line_break); } } $part=array( 'Type'=>'BodyData', 'Data'=>$data, 'Position'=>$offset ); } else { if($end) { $part=array( 'Type'=>'MessageEnd', 'Position'=>$this->offset + $this->buffer_position ); $this->state = MIME_PARSER_END; } else $need_more_data = 1; } } break; default: return($this->SetPositionedError($this->state.' is not a valid parser state', $this->buffer_position)); } return(1); } Function QueueBodyParts() { for(;;) { if(!$this->body_parser->GetPart($part,$end)) return($this->SetError($this->body_parser->error)); if($end) return(1); if(!IsSet($part['Part'])) $part['Part']=$this->headers['Boundary']; $this->parts[]=$part; } } Function ParseParameters($value, &$first, &$parameters, $return) { $first = strtolower(trim(strtok($value, ';'))); $values = trim(strtok('')); $parameters = array(); $return_value = ''; while(strlen($values)) { $parameter = trim(strtolower(strtok($values, '='))); $value = trim(strtok(';')); if(!strcmp($value[0], '"') && !strcmp($value[strlen($value) - 1], '"')) $value = substr($value, 1, strlen($value) - 2); $parameters[$parameter] = $value; if(!strcmp($parameter, $return)) $return_value = $value; $values = trim(strtok('')); } return($return_value); } Function DecodePart($part) { switch($part['Type']) { case 'MessageStart': $this->headers=array(); break; case 'HeaderName': if($this->decode_bodies) $this->current_header = strtolower($part['Name']); break; case 'HeaderValue': if($this->decode_headers) { $value = $part['Value']; $error = ''; for($decoded_header = array(), $position = 0; $positionsubstr($value, $position), 'Encoding'=>'ASCII' ); } } break; } $set = $encoded + 2; if(GetType($method=strpos($value,'?', $set))!='integer') { $error = 'invalid header encoding syntax '.$part['Value']; $error_position = $part['Position'] + $set; break; } $encoding=strtoupper(substr($value, $set, $method - $set)); $method += 1; if(GetType($data=strpos($value,'?', $method))!='integer') { $error = 'invalid header encoding syntax '.$part['Value']; $error_position = $part['Position'] + $set; break; } $start = $data + 1; if(GetType($end=strpos($value,'?=', $start))!='integer') { $error = 'invalid header encoding syntax '.$part['Value']; $error_position = $part['Position'] + $start; break; } if($encoded > $position) { if(count($decoded_header)) $decoded_header[count($decoded_header)-1]['Value'].=substr($value, $position, $encoded - $position); else { $decoded_header[]=array( 'Value'=>substr($value, $position, $encoded - $position), 'Encoding'=>'ASCII' ); } } switch(strtolower(substr($value, $method, $data - $method))) { case 'q': if($end>$start) { for($decoded = '', $position = $start; $position < $end ; ) { switch($value[$position]) { case '=': $h = HexDec($hex = strtolower(substr($value, $position+1, 2))); if($end - $position < 3 || strcmp(sprintf('%02x', $h), $hex)) { $warning = 'the header specified an invalid encoded character'; $warning_position = $part['Position'] + $position + 1; if($this->ignore_syntax_errors) { $this->SetPositionedWarning($warning, $warning_position); $decoded .= '='; $position ++; } else { $error = $warning; $error_position = $warning_position; break 4; } } else { $decoded .= Chr($h); $position += 3; } break; case '_': $decoded .= ' '; $position++; break; default: $decoded .= $value[$position]; $position++; break; } } if(count($decoded_header) && (!strcmp($decoded_header[$last = count($decoded_header)-1]['Encoding'], 'ASCII')) || !strcmp($decoded_header[$last]['Encoding'], $encoding)) { $decoded_header[$last]['Value'].= $decoded; $decoded_header[$last]['Encoding']= $encoding; } else { $decoded_header[]=array( 'Value'=>$decoded, 'Encoding'=>$encoding ); } } break; case 'b': $decoded=base64_decode(substr($value, $start, $end - $start)); if($end <= $start || GetType($decoded) != 'string' || strlen($decoded) == 0) { $warning = 'the header specified an invalid base64 encoded text'; $warning_position = $part['Position'] + $start; if($this->ignore_syntax_errors) $this->SetPositionedWarning($warning, $warning_position); else { $error = $warning; $error_position = $warning_position; break 2; } } if(count($decoded_header) && (!strcmp($decoded_header[$last = count($decoded_header)-1]['Encoding'], 'ASCII')) || !strcmp($decoded_header[$last]['Encoding'], $encoding)) { $decoded_header[$last]['Value'].= $decoded; $decoded_header[$last]['Encoding']= $encoding; } else { $decoded_header[]=array( 'Value'=>$decoded, 'Encoding'=>$encoding ); } break; default: $error = 'the header specified an unsupported encoding method'; $error_position = $part['Position'] + $method; break 2; } $position = $end + 2; } if(strlen($error)==0 && count($decoded_header)) $part['Decoded']=$decoded_header; } if($this->decode_bodies || $this->decode_headers) { switch($this->current_header) { case 'content-type:': $boundary = $this->ParseParameters($part['Value'], $type, $parameters, 'boundary'); $this->headers['Type'] = $type; if($this->decode_headers) { $part['MainValue'] = $type; $part['Parameters'] = $parameters; } if(!strcmp(strtok($type, '/'), 'multipart')) { $this->headers['Multipart'] = 1; if(strlen($boundary)) $this->headers['Boundary'] = $boundary; else return($this->SetPositionedError('multipart content-type header does not specify the boundary parameter', $part['Position'])); } break; case 'content-transfer-encoding:': switch($this->headers['Encoding']=strtolower(trim($part['Value']))) { case 'quoted-printable': $this->headers['QuotedPrintable'] = 1; break; case '7 bit': case '8 bit': if(!$this->SetPositionedWarning('"'.$this->headers['Encoding'].'" is an incorrect content transfer encoding type', $part['Position'])) return(0); case '7bit': case '8bit': case 'binary': break; case 'base64': $this->headers['Base64']=1; break; default: if(!$this->SetPositionedWarning('decoding '.$this->headers['Encoding'].' encoded bodies is not yet supported', $part['Position'])) return(0); } break; } } break; case 'BodyStart': if($this->decode_bodies && IsSet($this->headers['Multipart'])) { $this->body_parser_state = MIME_PARSER_BODY_START; $this->body_buffer = ''; $this->body_buffer_position = 0; } break; case 'MessageEnd': if($this->decode_bodies && IsSet($this->headers['Multipart']) && $this->body_parser_state != MIME_PARSER_BODY_DONE) return($this->SetPositionedError('incomplete message body part', $part['Position'])); break; case 'BodyData': if($this->decode_bodies) { if(strlen($this->body_buffer)==0) { $this->body_buffer = $part['Data']; $this->body_offset = $part['Position']; } else $this->body_buffer .= $part['Data']; if(IsSet($this->headers['Multipart'])) { $boundary = '--'.$this->headers['Boundary']; switch($this->body_parser_state) { case MIME_PARSER_BODY_START: for($position = $this->body_buffer_position; ;) { if(!$this->FindBodyLineBreak($position, $break, $line_break)) return(1); $next = $line_break + strlen($break); if(!strcmp(substr($this->body_buffer, $position, $line_break - $position), $boundary)) { $part=array( 'Type'=>'StartPart', 'Part'=>$this->headers['Boundary'], 'Position'=>$this->body_offset + $next ); $this->parts[]=$part; UnSet($this->body_parser); $this->body_parser = new mime_parser_class; $this->body_parser->decode_bodies = 1; $this->body_parser->decode_headers = $this->decode_headers; $this->body_parser->mbox = 0; $this->body_parser_state = MIME_PARSER_BODY_DATA; $this->body_buffer = substr($this->body_buffer, $next); $this->body_offset += $next; $this->body_buffer_position = 0; break; } else $position = $next; } case MIME_PARSER_BODY_DATA: for($position = $this->body_buffer_position; ;) { if(!$this->FindBodyLineBreak($position, $break, $line_break)) { if($position > 0) { if(!$this->body_parser->Parse(substr($this->body_buffer, 0, $position), 0)) return($this->SetError($this->body_parser->error)); if(!$this->QueueBodyParts()) return(0); } $this->body_buffer = substr($this->body_buffer, $position); $this->body_buffer_position = 0; $this->body_offset += $position; return(1); } $next = $line_break + strlen($break); $line = substr($this->body_buffer, $position, $line_break - $position); if(!strcmp($line, $boundary)) { if(!$this->body_parser->Parse(substr($this->body_buffer, 0, $position), 1)) return($this->SetError($this->body_parser->error)); if(!$this->QueueBodyParts()) return(0); $part=array( 'Type'=>'EndPart', 'Part'=>$this->headers['Boundary'], 'Position'=>$this->body_offset + $position ); $this->parts[] = $part; $part=array( 'Type'=>'StartPart', 'Part'=>$this->headers['Boundary'], 'Position'=>$this->body_offset + $next ); $this->parts[] = $part; UnSet($this->body_parser); $this->body_parser = new mime_parser_class; $this->body_parser->decode_bodies = 1; $this->body_parser->decode_headers = $this->decode_headers; $this->body_parser->mbox = 0; $this->body_buffer = substr($this->body_buffer, $next); $this->body_buffer_position = 0; $this->body_offset += $next; $position=0; continue; } elseif(!strcmp($line, $boundary.'--')) { if(!$this->body_parser->Parse(substr($this->body_buffer, 0, $position), 1)) return($this->SetError($this->body_parser->error)); if(!$this->QueueBodyParts()) return(0); $part=array( 'Type'=>'EndPart', 'Part'=>$this->headers['Boundary'], 'Position'=>$this->body_offset + $position ); $this->body_buffer = substr($this->body_buffer, $next); $this->body_buffer_position = 0; $this->body_offset += $next; $this->body_parser_state = MIME_PARSER_BODY_DONE; break 2; } $position = $next; } break; case MIME_PARSER_BODY_DONE: return(1); default: return($this->SetPositionedError($this->state.' is not a valid body parser state', $this->body_buffer_position)); } } elseif(IsSet($this->headers['QuotedPrintable'])) { for($end = strlen($this->body_buffer), $decoded = '', $position = $this->body_buffer_position; $position < $end; ) { if(GetType($equal = strpos($this->body_buffer, '=', $position))!='integer') { $decoded .= substr($this->body_buffer, $position); $position = $end; break; } $next = $equal + 1; switch($end - $equal) { case 1: $decoded .= substr($this->body_buffer, $position, $equal - $position); $position = $equal; break 2; case 2: $decoded .= substr($this->body_buffer, $position, $equal - $position); if(!strcmp($this->body_buffer[$next],"\n")) $position = $end; else $position = $equal; break 2; } if(!strcmp(substr($this->body_buffer, $next, 2), $break="\r\n") || !strcmp($this->body_buffer[$next], $break="\n") || !strcmp($this->body_buffer[$next], $break="\r")) { $decoded .= substr($this->body_buffer, $position, $equal - $position); $position = $next + strlen($break); continue; } $decoded .= substr($this->body_buffer, $position, $equal - $position); $h = HexDec($hex=strtolower(substr($this->body_buffer, $next, 2))); if(strcmp(sprintf('%02x', $h), $hex)) { if(!$this->SetPositionedWarning('the body specified an invalid quoted-printable encoded character', $this->body_offset + $next)) return(0); $decoded.='='; $position=$next; } else { $decoded .= Chr($h); $position = $equal + 3; } } if(strlen($decoded)==0) { $this->body_buffer_position = $position; return(1); } $part['Data'] = $decoded; $this->body_buffer = substr($this->body_buffer, $position); $this->body_buffer_position = 0; $this->body_offset += $position; } elseif(IsSet($this->headers['Base64'])) { $part['Data'] = base64_decode($this->body_buffer_position ? substr($this->body_buffer,$this->body_buffer_position) : $this->body_buffer); $this->body_offset += strlen($this->body_buffer) - $this->body_buffer_position; $this->body_buffer_position = 0; $this->body_buffer = ''; } else { $part['Data'] = substr($this->body_buffer, $this->body_buffer_position); $this->body_buffer_position = 0; $this->body_buffer = ''; } } break; } $this->parts[]=$part; return(1); } Function DecodeStream($parameters, &$end_of_message, &$decoded) { $end_of_message = 1; $state = MIME_MESSAGE_START; for(;;) { if(!$this->GetPart($part, $end)) return(0); if($end) { if(IsSet($parameters['File'])) { $end_of_data = feof($this->file); if($end_of_data) break; $data = @fread($this->file, 8000); if(GetType($data)!='string') return($this->SetPHPError('could not read the message file', $php_errormsg)); $end_of_data = feof($this->file); } else { $end_of_data=($this->position>=strlen($parameters['Data'])); if($end_of_data) break; $data = substr($parameters['Data'], $this->position); $end_of_data = 1; $this->position = strlen($parameters['Data']); } if(!$this->Parse($data, $end_of_data)) return(0); continue; } $type = $part['Type']; switch($state) { case MIME_MESSAGE_START: switch($type) { case 'MessageStart': $decoded=array( 'Headers'=>array(), 'Parts'=>array() ); $end_of_message = 0; $state = MIME_MESSAGE_GET_HEADER_NAME; continue 3; } break; case MIME_MESSAGE_GET_HEADER_NAME: switch($type) { case 'HeaderName': $header = strtolower($part['Name']); $state = MIME_MESSAGE_GET_HEADER_VALUE; continue 3; case 'BodyStart': $state = MIME_MESSAGE_GET_BODY; $part_number = 0; continue 3; } break; case MIME_MESSAGE_GET_HEADER_VALUE: switch($type) { case 'HeaderValue': $value = trim($part['Value']); if(!IsSet($decoded['Headers'][$header])) { $h = 0; $decoded['Headers'][$header]=$value; if($this->extract_addresses && IsSet($this->address_headers[$header])) $decoded['HeaderPositions'][$header] = $part['Position']; } elseif(GetType($decoded['Headers'][$header])=='string') { $h = 1; $decoded['Headers'][$header]=array($decoded['Headers'][$header], $value); } else { $h = count($decoded['Headers'][$header]); $decoded['Headers'][$header][]=$value; } if(IsSet($part['Decoded']) && (count($part['Decoded'])>1 || strcmp($part['Decoded'][0]['Encoding'],'ASCII') || strcmp($value, trim($part['Decoded'][0]['Value'])))) { $p=$part['Decoded']; $p[0]['Value']=ltrim($p[0]['Value']); $last=count($p)-1; $p[$last]['Value']=rtrim($p[$last]['Value']); $decoded['DecodedHeaders'][$header][$h]=$p; } switch($header) { case 'content-disposition:': $filename='filename'; break; case 'content-type:': if(!IsSet($decoded['FileName'])) { $filename='name'; break; } default: $filename=''; break; } if(strlen($filename)) { $this->ParseStructuredHeader($value, $type, $header_parameters, $character_sets, $languages); if(IsSet($header_parameters[$filename])) { $decoded['FileName']=$header_parameters[$filename]; if(IsSet($character_sets[$filename]) && strlen($character_sets[$filename])) $decoded['FileNameCharacterSet']=$character_sets[$filename]; if(IsSet($character_sets['language']) && strlen($character_sets['language'])) $decoded['FileNameCharacterSet']=$character_sets[$filename]; if(!strcmp($header, 'content-disposition:')) $decoded['FileDisposition']=$type; } } $state = MIME_MESSAGE_GET_HEADER_NAME; continue 3; } break; case MIME_MESSAGE_GET_BODY: switch($type) { case 'BodyData': if(IsSet($parameters['SaveBody'])) { if(!IsSet($decoded['BodyFile'])) { $directory_separator=(defined('DIRECTORY_SEPARATOR') ? DIRECTORY_SEPARATOR : '/'); $path = (strlen($parameters['SaveBody']) ? ($parameters['SaveBody'].(strcmp($parameters['SaveBody'][strlen($parameters['SaveBody'])-1], $directory_separator) ? $directory_separator : '')) : '').strval($this->body_part_number); if(!($this->body_file = fopen($path, 'wb'))) return($this->SetPHPError('could not create file '.$path.' to save the message body part', $php_errormsg)); $decoded['BodyFile'] = $path; $decoded['BodyPart'] = $this->body_part_number; $decoded['BodyLength'] = 0; $this->body_part_number++; } if(strlen($part['Data']) && !fwrite($this->body_file, $part['Data'])) { $this->SetPHPError('could not save the message body part to file '.$decoded['BodyFile'], $php_errormsg); fclose($this->body_file); @unlink($decoded['BodyFile']); return(0); } } elseif(IsSet($parameters['SkipBody'])) { if(!IsSet($decoded['BodyPart'])) { $decoded['BodyPart'] = $this->body_part_number; $decoded['BodyLength'] = 0; $this->body_part_number++; } } else { if(IsSet($decoded['Body'])) $decoded['Body'].=$part['Data']; else { $decoded['Body']=$part['Data']; $decoded['BodyPart'] = $this->body_part_number; $decoded['BodyLength'] = 0; $this->body_part_number++; } } $decoded['BodyLength'] += strlen($part['Data']); continue 3; case 'StartPart': if(!$this->DecodeStream($parameters, $end_of_part, $decoded_part)) return(0); $decoded['Parts'][$part_number]=$decoded_part; $part_number++; $state = MIME_MESSAGE_GET_BODY_PART; continue 3; case 'MessageEnd': if(IsSet($decoded['BodyFile'])) fclose($this->body_file); return(1); } break; case MIME_MESSAGE_GET_BODY_PART: switch($type) { case 'EndPart': $state = MIME_MESSAGE_GET_BODY; continue 3; } break; } return($this->SetError('unexpected decoded message part type '.$type.' in state '.$state)); } return(1); } /* Public functions */ Function Parse($data, $end) { if(strlen($this->error)) return(0); if($this->state==MIME_PARSER_END) return($this->SetError('the parser already reached the end')); $this->buffer .= $data; do { Unset($part); if(!$this->ParsePart($end, $part, $need_more_data)) return(0); if(IsSet($part) && !$this->DecodePart($part)) return(0); } while(!$need_more_data && $this->state!=MIME_PARSER_END); if($end && $this->state!=MIME_PARSER_END) return($this->SetError('reached a premature end of data')); if($this->buffer_position>0) { $this->offset += $this->buffer_position; $this->buffer = substr($this->buffer, $this->buffer_position); $this->buffer_position = 0; } return(1); } Function ParseFile($file) { if(strlen($this->error)) return(0); if(!($stream = @fopen($file, 'r'))) return($this->SetPHPError('Could not open the file '.$file, $php_errormsg)); for($end = 0;!$end;) { if(!($data = @fread($stream, 8000))) { $this->SetPHPError('Could not open the file '.$file, $php_errormsg); fclose($stream); return(0); } $end=feof($stream); if(!$this->Parse($data, $end)) { fclose($stream); return(0); } } fclose($stream); return(1); } Function GetPart(&$part, &$end) { $end = ($this->part_position >= count($this->parts)); if($end) { if($this->part_position) { $this->part_position = 0; $this->parts = array(); } } else { $part = $this->parts[$this->part_position]; $this->part_position ++; } return(1); } Function Decode($parameters, &$decoded) { if(IsSet($parameters['File'])) { if(!($this->file = @fopen($parameters['File'], 'r'))) return($this->SetPHPError('could not open the message file to decode '.$parameters['File'], $php_errormsg)); } elseif(IsSet($parameters['Data'])) $this->position = 0; else return($this->SetError('it was not specified a valid message to decode')); $this->warnings = $decoded = array(); $this->ResetParserState(); $addresses = new rfc822_addresses_class; $addresses->ignore_syntax_errors = $this->ignore_syntax_errors; for($message = 0; ($success = $this->DecodeStream($parameters, $end_of_message, $decoded_message)) && !$end_of_message; $message++) { if($this->extract_addresses) { $headers = $decoded_message['Headers']; $positions = $decoded_message['HeaderPositions']; $th = count($headers); for(Reset($headers), $h = 0; $h<$th; Next($headers), ++$h) { $header = Key($headers); if(IsSet($this->address_headers[$header]) && $this->address_headers[$header]) { $values = (GetType($headers[$header]) == 'array' ? $headers[$header] : array($headers[$header])); $p = (GetType($positions[$header]) == 'array' ? $positions[$header] : array($positions[$header])); $tv = count($values); for($v = 0; $v<$tv; ++$v) { if($addresses->ParseAddressList($values[$v], $a)) { if($v==0) $decoded_message['ExtractedAddresses'][$header] = $a; else { $tl = count($a); for($l = 0; $l<$tl; ++$l) $decoded_message['ExtractedAddresses'][$header][] = $a[$l]; } $tw = count($addresses->warnings); for($w = 0, Reset($addresses->warnings); $w < $tw; Next($addresses->warnings), $w++) { $warning = Key($addresses->warnings); if(!$this->SetPositionedWarning('Address extraction warning from header '.$header.' '.$addresses->warnings[$warning], $warning + $p[$v])) return(0); } } elseif(!$this->SetPositionedWarning('Address extraction error from header '.$header.' '.$addresses->error, $addresses->error_position + $p[$v])) return(0); } } } UnSet($decoded_message['HeaderPositions']); } $decoded[$message]=$decoded_message; } if(IsSet($parameters['File'])) fclose($this->file); return($success); } Function CopyAddresses($message, &$results, $header) { if(!IsSet($message['Headers'][$header])) return; if(!IsSet($message['ExtractedAddresses'][$header])) { $parser = new rfc822_addresses_class; $parser->ignore_syntax_errors = $this->ignore_syntax_errors; $values = (GetType($message['Headers'][$header]) == 'array' ? $message['Headers'][$header] : array($message['Headers'][$header])); $tv = count($values); $addresses = array(); for($v = 0; $v<$tv; ++$v) { if($parser->ParseAddressList($values[$v], $a)) { if($v==0) $addresses = $a; else { $tl = count($a); for($l = 0; $l<$tl; ++$l) $addresses[] = $a[$l]; } } } } else $addresses = $message['ExtractedAddresses'][$header]; if(count($addresses)) $results[ucfirst(substr($header, 0, strlen($header) -1))] = $addresses; } Function ReadMessageBody($message, &$body, $prefix) { if(IsSet($message[$prefix])) $body = $message[$prefix]; elseif(IsSet($message[$prefix.'File'])) { $path = $message[$prefix.'File']; if(!($file = @fopen($path, 'rb'))) return($this->SetPHPError('could not open the message body file '.$path, $php_errormsg)); for($body = '', $end = 0;!$end;) { if(!($data = @fread($file, 8000))) { $this->SetPHPError('Could not open the message body file '.$path, $php_errormsg); fclose($stream); return(0); } $end=feof($file); $body.=$data; } fclose($file); } else $body = ''; return(1); } Function Analyze($message, &$results) { $results = array(); $content_type = (IsSet($message['Headers']['content-type:']) ? $message['Headers']['content-type:'] : 'text/plain'); $disposition = $this->ParseParameters($content_type, $content_type, $parameters, 'disposition'); $type = $this->Tokenize($content_type, '/'); $sub_type = $this->Tokenize(';'); $copy_body = 1; $tolerate_unrecognized = 1; switch($type) { case 'multipart': $tolerate_unrecognized = 0; $copy_body = 0; $lp = count($message['Parts']); if($lp == 0) return($this->SetError($this->decode_bodies ? 'No parts were found in the '.$content_type.' part message' : 'It is not possible to analyze multipart messages without parsing the contained message parts. Please set the decode_bodies variable to 1 before parsing the message')); $parts = array(); for($p = 0; $p < $lp; ++$p) { if(!$this->Analyze($message['Parts'][$p], $parts[$p])) return(0); } switch($sub_type) { case 'alternative': $p = $lp; $results = $parts[--$p]; for(--$p ; $p >=0 ; --$p) $results['Alternative'][] = $parts[$p]; break; case 'related': $results = $parts[0]; for($p = 1; $p < $lp; ++$p) $results['Related'][] = $parts[$p]; break; case 'mixed': $results = $parts[0]; for($p = 1; $p < $lp; ++$p) $results['Attachments'][] = $parts[$p]; break; case 'report': if(IsSet($parameters['report-type'])) { switch($parameters['report-type']) { case 'delivery-status': for($p = 1; $p < $lp; ++$p) { if(!strcmp($parts[$p]['Type'], $parameters['report-type'])) { $results = $parts[$p]; break; } } if(!$this->ReadMessageBody($parts[0], $body, 'Data')) return(0); if(strlen($body)) $results['Response'] = $body; break; } } $results['Type'] = $parameters['report-type']; break; case 'signed': if($lp != 2) return($this->SetError('this '.$content_type.' message does not have just 2 parts')); if(strcmp($parts[1]['Type'], 'signature')) { $this->SetErrorWithContact('this '.$content_type.' message does not contain a signature'); $this->error = ''; } $results = $parts[0]; $results['Signature'] = $parts[1]; break; } break; case 'text': switch($sub_type) { case 'plain': $results['Type'] = 'text'; $results['Description'] = 'Text message'; break; case 'html': $results['Type'] = 'html'; $results['Description'] = 'HTML message'; break; default: $results['Type'] = $type; $results['SubType'] = $sub_type; $results['Description'] = 'Text file in the '.strtoupper($sub_type).' format'; break; } break; case 'video': $results['Type'] = $type; $results['SubType'] = $sub_type; $results['Description'] = 'Video file in the '.strtoupper($sub_type).' format'; break; case 'image': $results['Type'] = $type; $results['SubType'] = $sub_type; $results['Description'] = 'Image file in the '.strtoupper($sub_type).' format'; break; case 'audio': $results['Type'] = $type; $results['SubType'] = $sub_type; $results['Description'] = 'Audio file in the '.strtoupper($sub_type).' format'; break; case 'application': switch($sub_type) { case 'octet-stream': case 'x-msdownload': $results['Type'] = 'binary'; $results['Description'] = 'Binary file'; break; case 'pdf': $results['Type'] = $sub_type; $results['Description'] = 'Document in PDF format'; break; case 'postscript': $results['Type'] = $sub_type; $results['Description'] = 'Document in Postscript format'; break; case 'msword': $results['Type'] = 'ms-word'; $results['Description'] = 'Word processing document in Microsoft Word format'; break; case 'vnd.ms-powerpoint': $results['Type'] = 'ms-powerpoint'; $results['Description'] = 'Presentation in Microsoft PowerPoint format'; break; case 'vnd.ms-excel': $results['Type'] = 'ms-excel'; $results['Description'] = 'Spreadsheet in Microsoft Excel format'; break; case 'zip': case 'x-zip': case 'x-zip-compressed': $results['Type'] = 'zip'; $results['Description'] = 'ZIP archive with compressed files'; break; case 'ms-tnef': $results['Type'] = $sub_type; $results['Description'] = 'Microsoft Exchange data usually sent by Microsoft Outlook'; break; case 'pgp-signature': $results['Type'] = 'signature'; $results['SubType'] = $sub_type; $results['Description'] = 'Message signature for PGP'; break; case 'x-pkcs7-signature': case 'pkcs7-signature': $results['Type'] = 'signature'; $results['SubType'] = $sub_type; $results['Description'] = 'PKCS message signature'; break; case 'vnd.oasis.opendocument.text': $results['Type'] = 'odf-writer'; $results['Description'] = 'Word processing document in ODF text format used by OpenOffice Writer'; break; } break; case 'message': $tolerate_unrecognized = 0; switch($sub_type) { case 'delivery-status': $results['Type'] = $sub_type; $results['Description'] = 'Notification of the status of delivery of a message'; if(!$this->ReadMessageBody($message, $body, 'Body')) return(0); if(($l = strlen($body))) { $position = 0; $this->ParseHeaderString($body, $position, $headers); $recipients = array(); for(;$position<$l;) { $this->ParseHeaderString($body, $position, $headers); if(count($headers)) { $r = count($recipients); if(IsSet($headers['action'])) $recipients[$r]['Action'] = $headers['action']; if(IsSet($headers['status'])) $recipients[$r]['Status'] = $headers['status']; if(IsSet($headers['original-recipient'])) { strtok($headers['original-recipient'], ';'); $recipients[$r]['Address'] = trim(strtok('')); } elseif(IsSet($headers['final-recipient'])) { strtok($headers['final-recipient'], ';'); $recipients[$r]['Address'] = trim(strtok('')); } } } $results['Recipients'] = $recipients; } $copy_body = 0; break; case 'rfc822': $results['Type'] = 'message'; $results['Description'] = 'E-mail message'; break; } break; default: $tolerate_unrecognized = 0; break; } if(!IsSet($results['Type'])) { $this->SetErrorWithContact($content_type.' message parts are not yet recognized'); $results['Type'] = $this->error; $this->error = ''; } if(IsSet($parameters['charset'])) $results['Encoding'] = strtolower($parameters['charset']); if(IsSet($message['Headers']['subject:'])) { if(IsSet($message['DecodedHeaders']['subject:']) && count($message['DecodedHeaders']['subject:']) == 1 && count($message['DecodedHeaders']['subject:'][0]) == 1) { $results['Subject'] = $message['DecodedHeaders']['subject:'][0][0]['Value']; $results['SubjectEncoding'] = strtolower($message['DecodedHeaders']['subject:'][0][0]['Encoding']); } else $results['Subject'] = $message['Headers']['subject:']; } if(IsSet($message['Headers']['date:'])) { if(IsSet($message['DecodedHeaders']['date:']) && count($message['DecodedHeaders']['date:']) == 1 && count($message['DecodedHeaders']['date:'][0]) == 1) $results['Date'] = $message['DecodedHeaders']['date:'][0][0]['Value']; else $results['Date'] = $message['Headers']['date:']; } $l = count($this->address_headers); for(Reset($this->address_headers), $h = 0; $h<$l; Next($this->address_headers), ++$h) $this->CopyAddresses($message, $results, Key($this->address_headers)); if($copy_body) { if(IsSet($message['Body'])) $results['Data'] = $message['Body']; elseif(IsSet($message['BodyFile'])) $results['DataFile'] = $message['BodyFile']; elseif(IsSet($message['BodyLength'])) $results['DataLength'] = $message['BodyLength']; if(IsSet($message['FileName'])) $results['FileName'] = $message['FileName']; if(IsSet($message['FileDisposition'])) $results['FileDisposition'] = $message['FileDisposition']; if(IsSet($message['Headers']['content-id:'])) { $content_id = trim($message['Headers']['content-id:']); $l = strlen($content_id); if(!strcmp($content_id[0], '<') && !strcmp($content_id[$l - 1], '>')) $results['ContentID'] = substr($content_id, 1, $l - 2); } } return(1); } }; #### Below this copied from Manuel Lemos' rfc822_addresses.php #### class rfc822_addresses_class { /* Private variables */ var $v = ''; /* Public variables */ var $error = ''; var $error_position = -1; var $ignore_syntax_errors=1; var $warnings=array(); /* Private functions */ Function SetError($error) { $this->error = $error; return(0); } Function SetPositionedError($error, $position) { $this->error_position = $position; return($this->SetError($error)); } Function SetWarning($warning, $position) { $this->warnings[$position]=$warning; return(1); } Function SetPositionedWarning($error, $position) { if(!$this->ignore_syntax_errors) return($this->SetPositionedError($error, $position)); return($this->SetWarning($error, $position)); } Function QDecode($p, &$value, &$encoding) { $encoding = $charset = null; $s = 0; $decoded = ''; $l = strlen($value); while($s < $l) { if(GetType($q = strpos($value, '=?', $s)) != 'integer') { if($s == 0) return(1); if($s < $l) $decoded .= substr($value, $s); break; } if($s < $q) $decoded .= substr($value, $s, $q - $s); $q += 2; if(GetType($c = strpos($value, '?', $q)) != 'integer' || $q == $c) return($this->SetPositionedWarning('invalid Q-encoding character set', $p + $q)); if(IsSet($charset)) { $another_charset = strtolower(substr($value, $q, $c - $q)); if(strcmp($charset, $another_charset) && strcmp($another_charset, 'ascii')) return($this->SetWarning('it is not possible to decode an encoded value using mixed character sets into a single value', $p + $q)); } else { $charset = strtolower(substr($value, $q, $c - $q)); if(!strcmp($charset, 'ascii')) $charset = null; } ++$c; if(GetType($t = strpos($value, '?', $c)) != 'integer' || $c==$t) return($this->SetPositionedWarning('invalid Q-encoding type', $p + $c)); $type = strtolower(substr($value, $c, $t - $c)); ++$t; if(GetType($e = strpos($value, '?=', $t)) != 'integer') return($this->SetPositionedWarning('invalid Q-encoding encoded data', $p + $e)); switch($type) { case 'q': for($s = $t; $s<$e;) { switch($b = $value[$s]) { case '=': $h = HexDec($hex = strtolower(substr($value, $s + 1, 2))); if($s + 3 > $e || strcmp(sprintf('%02x', $h), $hex)) return($this->SetPositionedWarning('invalid Q-encoding q encoded data', $p + $s)); $decoded .= chr($h); $s += 3; break; case '_': $decoded .= ' '; ++$s; break; default: $decoded .= $b; ++$s; } } break; case 'b': if($e <= $t || strlen($binary = base64_decode($data = substr($value, $t, $e - $t))) == 0 || GetType($binary) != 'string') return($this->SetPositionedWarning('invalid Q-encoding b encoded data', $p + $t)); $decoded .= $binary; $s = $e; break; default: return($this->SetPositionedWarning('Q-encoding '.$type.' is not yet supported', $p + $c)); } $s += 2; } $value = $decoded; $encoding = $charset; return(1); } Function ParseCText(&$p, &$c_text) { $c_text = null; $v = $this->v; if($pv; if($p>strlen($v) || GetType(strchr("\t\r\n \"\\\0", $c = $v[$p])) == 'string') return(1); if(Ord($c) >= 128) { if(!$this->ignore_syntax_errors) return(1); $this->SetPositionedWarning('it was used an unencoded 8 bit character', $p); } $q_text = $c; ++$p; return(1); } Function ParseQuotedPair(&$p, &$quoted_pair) { $quoted_pair = null; $v = $this->v; $l = strlen($v); if($p+1 < $l && !strcmp($v[$p], '\\') && GetType(strchr("\r\n\0", $c = $v[$p + 1])) != 'string' && Ord($c)<128) { $quoted_pair = $c; $p += 2; } return(1); } Function ParseCContent(&$p, &$c_content) { $c_content = null; $c = $p; if(!$this->ParseQuotedPair($c, $content)) return(0); if(!IsSet($content)) { if(!$this->ParseCText($c, $content)) return(0); if(!IsSet($content)) { if(!$this->ParseComment($c, $content)) return(0); if(!IsSet($content)) return(1); } } $c_content = $content; $p = $c; return(1); } Function SkipWhiteSpace(&$p) { $v = $this->v; $l = strlen($v); for(;$p<$l; ++$p) { switch($v[$p]) { case ' ': case "\n": case "\r": case "\t": break; default: return(1); } } return(1); } Function ParseComment(&$p, &$comment) { $comment = null; $v = $this->v; $l = strlen($v); $c = $p; if($c >= $l || strcmp($v[$c], '(')) return(1); ++$c; for(; $c < $l;) { if(!$this->SkipWhiteSpace($c)) return(0); if(!$this->ParseCContent($c, $c_content)) return(0); if(!IsSet($c_content)) break; } if(!$this->SkipWhiteSpace($c)) return(0); if($c >= $l || strcmp($v[$c], ')')) return(1); ++$c; $comment = substr($v, $p, $c - $p); $p = $c; return(1); } Function SkipCommentWhiteSpace(&$p) { $v = $this->v; $l = strlen($v); for(;$p<$l;) { switch($v[$p]) { case ' ': case "\n": case "\r": case "\t": ++$p; break; case '(': if(!$this->ParseComment($p, $comment)) return(0); default: return(1); } } return(1); } Function ParseQContent(&$p, &$q_content) { $q_content = null; $q = $p; if(!$this->ParseQuotedPair($q, $content)) return(0); if(!IsSet($content)) { if(!$this->ParseQText($q, $content)) return(0); if(!IsSet($content)) return(1); } $q_content = $content; $p = $q; return(1); } Function ParseAtom(&$p, &$atom, $dot) { $atom = null; $v = $this->v; $l = strlen($v); $a = $p; if(!$this->SkipCommentWhiteSpace($a)) return(0); for($s = $a;$a < $l;) { if(preg_match('/^([-'.($dot ? '.' : '').'A-Za-z0-9!#$&\'*+\\/=?^_{|}~]+)/', substr($this->v, $a), $m)) $a += strlen($m[1]); elseif(Ord($v[$a]) < 128) break; elseif(!$this->SetPositionedWarning('it was used an unencoded 8 bit character', $a)) return(0); else ++$a; } if($s == $a) return(1); if(!$this->SkipCommentWhiteSpace($a)) return(0); $atom = substr($this->v, $p, $a - $p); $p = $a; return(1); } Function ParseQuotedString(&$p, &$quoted_string) { $quoted_string = null; $v = $this->v; $l = strlen($v); $s = $p; if(!$this->SkipCommentWhiteSpace($s)) return(0); if($s >= $l || strcmp($v[$s], '"')) return(1); ++$s; for($string = '';$s < $l;) { $w = $s; if(!$this->SkipWhiteSpace($s)) return(0); if($w != $s) $string .= substr($v, $w, $s - $w); if(!$this->ParseQContent($s, $q_content)) return(0); if(!IsSet($q_content)) break; $string .= $q_content; } $w = $s; if(!$this->SkipWhiteSpace($s)) return(0); if($w != $s) $string .= substr($v, $w, $s - $w); if($s >= $l || strcmp($v[$s], '"')) return(1); ++$s; if(!$this->SkipCommentWhiteSpace($s)) return(0); $quoted_string = $string; $p = $s; return(1); } Function ParseWord(&$p, &$word) { $word = null; if(!$this->ParseQuotedString($p, $word)) return(0); if(IsSet($word)) return(1); if(!$this->ParseAtom($p, $word, 0)) return(0); return(1); } Function ParseObsPhrase(&$p, &$obs_phrase) { $obs_phrase = null; $v = $this->v; $l = strlen($v); $ph = $p; if(!$this->ParseWord($ph, $word)) return(0); $string = $word; for(;;) { if(!$this->ParseWord($ph, $word)) return(0); if(IsSet($word)) { $string .= $word; continue; } $w = $ph; if(!$this->SkipCommentWhiteSpace($ph)) return(0); if($w != $ph) { $string .= substr($v, $w, $ph - $w); continue; } if($ph >= $l || strcmp($v[$ph], '.')) break; $string .= '.'; ++$ph; } $obs_phrase = $string; $p = $ph; return(1); } Function ParsePhrase(&$p, &$phrase) { $phrase = null; if(!$this->ParseObsPhrase($p, $phrase)) return(0); if(IsSet($phrase)) return(1); $ph = $p; if(!$this->ParseWord($ph, $word)) return(0); $string = $word; for(;;) { if(!$this->ParseWord($ph, $word)) return(0); if(!IsSet($word)) break; $string .= $word; } $phrase = $string; $p = $ph; return(1); } Function ParseAddrSpec(&$p, &$addr_spec) { $addr_spec = null; $v = $this->v; $l = strlen($v); $a = $p; if(!$this->ParseQuotedString($a, $local_part)) return(0); if(!IsSet($local_part)) { if(!$this->ParseAtom($a, $local_part, 1)) return(0); $local_part = trim($local_part); } if($a >= $l || strcmp($v[$a], '@')) return(1); ++$a; if(!$this->ParseAtom($a, $domain, 1)) return(0); if(!IsSet($domain)) return(1); $addr_spec = $local_part.'@'.$domain; $p = $a; return(1); } Function ParseAngleAddr(&$p, &$addr) { $addr = null; $v = $this->v; $l = strlen($v); $a = $p; if(!$this->SkipCommentWhiteSpace($a)) return(0); if($a >= $l || strcmp($v[$a], '<')) return(1); ++$a; if(!$this->ParseAddrSpec($a, $addr_spec)) return(0); if($a >= $l || strcmp($v[$a], '>')) return(1); ++$a; if(!$this->SkipCommentWhiteSpace($a)) return(0); $addr = $addr_spec; $p = $a; return(1); } Function ParseName(&$p, &$address) { $address = null; $a = $p; if(!$this->ParsePhrase($a, $display_name)) return(0); if(IsSet($display_name)) { if(!$this->QDecode($p, $display_name, $encoding)) return(0); $address['name'] = trim($display_name); if(IsSet($encoding)) $address['encoding'] = $encoding; } $p = $a; return(1); } Function ParseNameAddr(&$p, &$address) { $address = null; $a = $p; if(!$this->ParsePhrase($a, $display_name)) return(0); if(!$this->ParseAngleAddr($a, $addr)) return(0); if(!IsSet($addr)) return(1); $address = array('address'=>$addr); if(IsSet($display_name)) { if(!$this->QDecode($p, $display_name, $encoding)) return(0); $address['name'] = trim($display_name); if(IsSet($encoding)) $address['encoding'] = $encoding; } $p = $a; return(1); } Function ParseAddrNameAddr(&$p, &$address) { $address = null; $a = $p; if(!$this->ParseAddrSpec($a, $display_name)) return(0); if(!IsSet($display_name)) return(1); if(!$this->ParseAngleAddr($a, $addr)) return(0); if(!IsSet($addr)) return(1); if(!$this->QDecode($p, $display_name, $encoding)) return(0); $address = array( 'address'=>$addr, 'name' => trim($display_name) ); if(IsSet($encoding)) $address['encoding'] = $encoding; $p = $a; return(1); } Function ParseMailbox(&$p, &$address) { $address = null; if($this->ignore_syntax_errors) { $a = $p; if(!$this->ParseAddrNameAddr($p, $address)) return(0); if(IsSet($address)) return($this->SetPositionedWarning('it was specified an unquoted address as name', $a)); } if(!$this->ParseNameAddr($p, $address)) return(0); if(IsSet($address)) return(1); if(!$this->ParseAddrSpec($p, $addr_spec)) return(0); if(IsSet($addr_spec)) { $address = array('address'=>$addr_spec); return(1); } $a = $p; if($this->ignore_syntax_errors && $this->ParseName($p, $address) && IsSet($address)) return($this->SetPositionedWarning('it was specified a name without an address', $a)); return(1); } Function ParseMailboxGroup(&$p, &$mailbox_group) { $v = $this->v; $l = strlen($v); $g = $p; if(!$this->ParseMailbox($g, $address)) return(0); if(!IsSet($address)) return(1); $addresses = array($address); for(;$g < $l;) { if(strcmp($v[$g], ',')) break; ++$g; if(!$this->ParseMailbox($g, $address)) return(0); if(!IsSet($address)) return(1); $addresses[] = $address; } $mailbox_group = $addresses; $p = $g; return(1); } Function ParseGroup(&$p, &$address) { $address = null; $v = $this->v; $l = strlen($v); $g = $p; if(!$this->ParsePhrase($g, $display_name)) return(0); if(!IsSet($display_name) || $g >= $l || strcmp($v[$g], ':')) return(1); ++$g; if(!$this->ParseMailboxGroup($g, $mailbox_group)) return(0); if(!IsSet($mailbox_group)) { if(!$this->SkipCommentWhiteSpace($g)) return(0); $mailbox_group = array(); } if($g >= $l || strcmp($v[$g], ';')) return(1); $c = ++$g; if($this->SkipCommentWhiteSpace($g) && $g > $c && !$this->SetPositionedWarning('it were used invalid comments after a group of addresses', $c)) return(0); if(!$this->QDecode($p, $display_name, $encoding)) return(0); $address = array( 'name'=>$display_name, 'group'=>$mailbox_group ); if(IsSet($encoding)) $address['encoding'] = $encoding; $p = $g; return(1); } Function ParseAddress(&$p, &$address) { $address = null; if(!$this->ParseGroup($p, $address)) return(0); if(!IsSet($address)) { if(!$this->ParseMailbox($p, $address)) return(0); } return(1); } /* Public functions */ Function ParseAddressList($value, &$addresses) { $this->warnings = array(); $addresses = array(); $this->v = $v = $value; $l = strlen($v); $p = 0; if(!$this->ParseAddress($p, $address)) return(0); if(!IsSet($address)) return($this->SetPositionedError('it was not specified a valid address', $p)); $addresses[] = $address; while($p < $l) { if(strcmp($v[$p], ',')) return($this->SetPositionedError('multiple addresses must be separated by commas: ', $p)); ++$p; if(!$this->ParseAddress($p, $address)) return(0); if(!IsSet($address)) return($this->SetPositionedError('it was not specified a valid address after comma', $p)); $addresses[] = $address; } return(1); } };