RFC822 Email parser
| From: | Richard Heyes | Date: | Sun, 05 Nov 2000 14:53:09 +0000 |
| Subject: | RFC822 Email parser | ||
| Groups: | php.pear | ||
| Request: | Send a blank email to php-pear+get-430@lists.php.net to get a copy of this message | ||
Ok, it's done. Probably. It's rather substantial (600 lines(!)), so where
performance is an issue it probably wouldn't be appropriate. There's
probably bugs too. It returns a slightly different structure than the imap
function, but IMO it's better as it takes into account groups and displays
the addresses in a group nested.
Before I pearify it, any comments or suggestions?
--
Richard Heyes
<?php /*************************************** ** Filename.....: class.rfc822.address.parser.inc ** Last changed.: 26/10/2000 ** Notes........: ***************************************/ class rfc822_parse_adrlist{ var $address; var $addresses; var $default_host; var $structure; var $error; var $index; function rfc822_parse_adrlist($address = NULL, $default_host = NULL){ $this->address = $address; $this->default_host = $default_host; $this->structure = array(); $this->addresses = array(); $this->error = NULL; $this->index = NULL; } /*************************************** ** Starts the whole process. ***************************************/ function go($address = NULL, $default_host = NULL){ $this->address = $address; $this->default_host = $default_host; if($this->split_addresses($this->address) === FALSE OR isset($this->error)) return (array)$this->error; // print_r($this->addresses); exit; for($i=0; $i<count($this->addresses); $i++){ if(($return = $this->validate_address($this->addresses[$i])) === FALSE OR isset($this->error)) return (array)$this->error; $this->structure[] = $return; } $return = $this->structure; // Now clean up. $this->address = $address; $this->default_host = $default_host; $this->structure = array(); $this->addresses = array(); $this->error = NULL; $this->index = NULL; return $return; } /*************************************** ** Splits an address into seperate addresses. ***************************************/ function split_addresses($address = ''){ if($this->is_group($address) == TRUE AND !isset($this->error)){ $split_char = ';'; $is_group = TRUE; }elseif(!isset($this->error)){ $split_char = ','; $is_group = FALSE; }elseif(isset($this->error)) return FALSE; // Starts with a group, so split on semi-colon: $parts = explode($split_char, $address); $string = $this->split_check($parts, $split_char); // If a group... if($is_group === TRUE){ // If $string does not contain a colon outside of // brackets/quotes etc then something's fubar. // First check there's a colon at all: if(strpos($string, ':') === FALSE){ $this->error = 'Invalid address: '.$string; return FALSE; } // Now check it's outside of brackets/quotes: if($this->split_check(explode(':', $string), ':') == FALSE) return FALSE; } // $string now contains the first full address/group. // Add to the addresses array. $this->addresses[] = array( 'address' => trim($string), 'group' => $is_group ); // Remove the now stored address from the initial line, the +1 // is to account for the explode character. $address = trim(substr($address, strlen($string)+1 )); // If the next char is a comma and this was a group, // then there are more addresses, otherwise, if there are // any more chars, then there is another address. if($is_group === TRUE AND substr($address,0,1) == ','){ $address = trim(substr($address,1)); $this->split_addresses($address); return TRUE; }elseif(strlen($address) > 0){ $this->split_addresses($address); return TRUE; }else{ return TRUE; } // If you've got here then something's barfed. return FALSE; } /*************************************** ** Checks for a group at the start of ** the string. ***************************************/ function is_group($address){ // First comma not in quotes, angles or escaped: $parts = explode(',', $address); $string = $this->split_check($parts, ','); /*************************************** ** Now we have the first address, we can ** reliably check for a group by searching ** for a colon that's not escaped or in ** quotes or angle brackets. ***************************************/ if(count($parts = explode(':', $string)) > 1){ $string2 = $this->split_check($parts, ':'); if($string2 === $string) return FALSE; return TRUE; }else return FALSE; } /*************************************** ** A common function that will check ** an exploded string. ** Used by: split_addresses() ** is_group() ** $parts = The exploded string ** $char = The char that was exploded on. ***************************************/ function split_check($parts, $char){ $string = $parts[0]; for($i=0; $i<count($parts); $i++){ if($this->has_unclosed_quotes($string) == TRUE OR $this->has_unclosed_brackets($string, '<>') == TRUE OR $this->has_unclosed_brackets($string, '[]') == TRUE OR substr($string, -1) == '\\'){ if(isset($parts[$i+1])){ $string = $string.$char.$parts[$i+1]; }else{ $this->error = 'Invalid address spec. Unclosed bracket or quotes'; return FALSE; } }else{ $this->index = $i; break; } } return $string; } /*************************************** ** Checks if a string has an unclosed ** quotes or not. ***************************************/ function has_unclosed_quotes($string){ $string = explode('"', $string); $string_cnt = count($string); for($i=0; $i<(count($string)-1); $i++) if(substr($string[$i], -1) == '\\') $string_cnt--; if($string_cnt % 2 === 0) return TRUE; else return FALSE; } /*************************************** ** Checks if a string has an unclosed ** brackets or not. ** IMPORTANT: This function handles ** both angle brackets and square brackets; ***************************************/ function has_unclosed_brackets($string, $chars){ $num_angle_start = substr_count($string, $chars[0]); $num_angle_end = substr_count($string, $chars[1]); $num_angle_start = $this->has_unclosed_brackets_sub($string, $num_angle_start, $chars[0]); $num_angle_end = $this->has_unclosed_brackets_sub($string, $num_angle_end, $chars[1]); if($num_angle_start < $num_angle_end){ $this->error = 'Invalid address spec. Unmatched quote or bracket ('.$chars.')'; return FALSE; }elseif($num_angle_start > $num_angle_end) return TRUE; else return FALSE; } /*************************************** ** Sub function that is used only be ** has_unclosed_angles. ***************************************/ function has_unclosed_brackets_sub($string, $num, $char){ $parts = explode($char, $string); for($i=0; $i<count($parts); $i++){ if(substr($parts[$i],-1) == '\\' OR $this->has_unclosed_quotes($parts[$i])) $num--; if(isset($parts[$i+1])) $parts[$i+1] = $parts[$i].$char.$parts[$i+1]; } return $num; } /*************************************** ** Function to begin checking the address. ***************************************/ function validate_address($address){ $is_group = FALSE; if($address['group'] == TRUE){ $is_group = TRUE; // Get the group part of the name $parts = explode(':', $address['address']); $groupname = $this->split_check($parts, ':'); // And validate the group part of the name. if(!$this->validate_phrase($groupname)){ $this->error = 'Group name did not validate.'; return FALSE; }else{ $structure = new stdClass; $structure->group = $groupname; } $address['address'] = ltrim( substr($address['address'], strlen($groupname.':')) ); } // If a group then split on comma and bung in an array. // Otherwise, Just bung the whole address in an array. if($is_group == TRUE){ while(trim(strlen($address['address'])) > 0){ $parts = explode(',', $address['address']); $addresses[] = $this->split_check($parts, ','); $address['address'] = substr($address['address'], strlen(end($addresses).',')); } }else{ $addresses[] = $address['address']; } // Check that $addresses is set, if address like this: // Groupname:; // Then errors were appearing. if(!isset($addresses)){ $this->error[] = 'Empty group.'; return FALSE; } // Trim those bad boys... for($i=0; $i<count($addresses); $i++) $addresses[$i] = trim($addresses[$i]); // Validate the mailbox. // This could be one of: name <geezer@domain.com> // geezer@domain.com // geezer // // Other combinations/variations are also possible for($i=0; $i<count($addresses); $i++) $addresses[$i] = $this->validate_mailbox($addresses[$i]); $structure->addresses = $addresses; // Determine what to return. if(!isset($structure->group)) return $structure->addresses[0]; else return $structure; } /*************************************** ** Function to validate a phrase. ***************************************/ function validate_phrase($phrase){ // Splits on one or more Tab or space. $parts = preg_split('/[ \\x09]+/', $phrase, 0, PREG_SPLIT_NO_EMPTY); while(count($parts) > 0){ $phrase_parts[] = $this->split_check($parts, ' '); for($i=0; $i < $this->index + 1; $i++) array_shift($parts); } for($i=0; $i<count($phrase_parts); $i++){ // If quoted string: if(substr($phrase_parts[$i],0,1) == '"'){ if(!$this->validate_quoted_string($phrase_parts[$i])) return FALSE; continue; } // Otherwise its an atom: if(!$this->validate_atom($phrase_parts[$i])) return FALSE; } return TRUE; } /*************************************** ** Function to validate an atom which ** from rfc822 is: ** atom = 1*<any CHAR except specials, SPACE and CTLs> ***************************************/ function validate_atom($atom){ // Check for any char from ASCII 0 - ASCII 127 if(!preg_match('/^[\\x00-\\x7E]+$/i', $atom, $matches)) return FALSE; // Check for specials: if( preg_match('/[][()<>@,;\\:". ]/', $atom)) return FALSE; // Check for control characters (ASCII 0-31): if( preg_match('/[\\x00-\\x1F]+/', $atom)) return FALSE; return TRUE; } /*************************************** ** Function to validate quoted string, ** which is: ** quoted-string = <"> *(qtext/quoted-pair) <"> ***************************************/ function validate_quoted_string($qstring){ // Leading and trailing " $qstring = substr($qstring, 1, -1); // Perform check. if(preg_match('/(.)[\x0D\\\\"]/', $qstring, $matches) AND $matches[1] != '\\') return FALSE; return TRUE; } /*************************************** ** Function to validate a mailbox, ** which is: ** mailbox = addr-spec ; simple address ** / phrase route-addr ; name and route-addr ***************************************/ function validate_mailbox($mailbox){ // A couple of defaults. $phrase = ''; $adl = ''; // Check for name + route-addr if(substr($mailbox,-1) == '>' AND substr($mailbox,0,1) != '<'){ $parts = explode('<', $mailbox); $name = $this->split_check($parts, '<'); $phrase = trim($name); $route_addr = trim(substr($mailbox, strlen($name.'<'), -1)); if($this->validate_phrase($phrase) === FALSE OR ($route_addr = $this->validate_route_addr($route_addr)) === FALSE) return FALSE; // Only got addr-spec }else{ // First snip angle brackets if present. if(substr($mailbox,0,1) == '<' AND substr($mailbox,-1) == '>') $addr_spec = substr($mailbox,1,-1); else $addr_spec = $mailbox; if(($addr_spec = $this->validate_addr_spec($addr_spec)) === FALSE) return FALSE; } // Construct the object that will be returned. $mbox = new stdClass(); $mbox->personal = $phrase; if(isset($route_addr)){ $mbox->local_part = $route_addr['local_part']; $mbox->domain = $route_addr['domain']; $mbox->adl = $route_addr['adl']; }else{ $mbox->local_part = $addr_spec['local_part']; $mbox->domain = $addr_spec['domain']; $mbox->adl = $adl; } return $mbox; } /*************************************** ** This function validates a route-addr ** which is: ** route-addr = "<" [route] addr-spec ">" ** ** Angles have already been removed at the ** point of getting to this function. ***************************************/ function validate_route_addr($route_addr){ // Check for colon. if(strpos($route_addr, ':') !== FALSE){ $parts = explode(':', $route_addr); $route = $this->split_check($parts, ':'); }else{ $route = $route_addr; } // If $route is same as $route_addr then the colon // was in quotes or brackets or, of course, non existent. if($route === $route_addr){ unset($route); $addr_spec = $route_addr; if(($addr_spec = $this->validate_addr_spec($addr_spec)) === FALSE) return FALSE; }else{ // Validate route part. if(($route = $this->validate_route($route)) === FALSE) return FALSE; $addr_spec = substr($route_addr, strlen($route.':')); // Validate addr-spec part. if(($addr_spec = $this->validate_addr_spec($addr_spec)) === FALSE) return FALSE; } if(isset($route)) $return['adl'] = $route; else $return['adl'] = ''; $return = array_merge($return, $addr_spec); return $return; } /*************************************** ** Function to validate a route, which is: ** route = 1#("@" domain) ":" ***************************************/ function validate_route($route){ // Split on comma. $domains = explode(',', trim($route)); for($i=0; $i<count($domains); $i++){ $domains[$i] = str_replace('@', '', trim($domains[$i])); if($this->validate_domain($domains[$i]) === FALSE) return FALSE; } return $route; } /*************************************** ** Function to validate a domain, though ** this is not quite what you expect of ** a strict internet domain. ** ** domain = sub-domain *("." sub-domain) ***************************************/ function validate_domain($domain){ // Note the different use of $subdomains and $sub_domains $subdomains = explode('.', $domain); while(count($subdomains) > 0){ $sub_domains[] = $this->split_check($subdomains, '.'); for($i=0; $i < $this->index + 1; $i++) array_shift($subdomains); } for($i=0; $i<count($sub_domains); $i++) if($this->validate_subdomain(trim($sub_domains[$i])) === FALSE) return FALSE; // Managed to get here, so return input. return $domain; } /*************************************** ** Function to validate a subdomain. ** ** subdomain = domain-ref / domain-literal ***************************************/ function validate_subdomain($subdomain){ if(preg_match('|^\[(.*)]$|', $subdomain, $arr)){ if($this->validate_dliteral($arr[1]) === FALSE) return FALSE; }else{ if($this->validate_atom($subdomain) === FALSE) return FALSE; } // Got here, so return successful. return TRUE; } /*************************************** ** Function to validate a domain literal ** ** domain-literal = "[" *(dtext / quoted-pair) "]" ***************************************/ function validate_dliteral($dliteral){ if(preg_match('/(.)[][\x0D\\\\]/', $dliteral, $matches) AND $matches[1] != '\\') return FALSE; return TRUE; } /*************************************** ** Function to validate an addr-spec. ** ** addr-spec = local-part "@" domain ***************************************/ function validate_addr_spec($addr_spec){ $addr_spec = trim($addr_spec); // Split on @ sign if there is one. if(strpos($addr_spec, '@') !== FALSE){ $parts = explode('@', $addr_spec); $local_part = $this->split_check($parts, '@'); $domain = substr($addr_spec, strlen($local_part.'@')); // No @ sign so assume the default domain. }else{ $local_part = $addr_spec; $domain = $this->default_host; } if(($local_part = $this->validate_local_part($local_part)) === FALSE) return FALSE; if(($domain = $this->validate_domain($domain)) === FALSE) return FALSE; // Got here so return successful. return array('local_part' => $local_part, 'domain' => $domain); } /*************************************** ** Function to validate the local part ** of an address. ** ** local-part = word *("." word) ***************************************/ function validate_local_part($local_part){ $parts = explode('.', $local_part); // Split the local_part into words. while(count($parts) > 0){ $words[] = $this->split_check($parts, '.'); for($i=0; $i < $this->index + 1; $i++) array_shift($parts); } // Validate each word. for($i=0; $i<count($words); $i++) if($this->validate_phrase(trim($words[$i])) === FALSE) return FALSE; // Managed to get here, so return the input. return $local_part; } } ?><?php /*************************************** ** Filename.......: index.php ** Project name...: RFC822 Address parser ** Last Modified..: 05/11/00 ***************************************/ include_once('class.rfc822.address.parse.inc'); /*************************************** ** Some example addresses. The last one ** is all the previous together in a "group". ***************************************/ $addresses[] = 'richard.heyes@heyes-computing.net'; $addresses[] = 'Richard <@myroute:spam@heyes-computing.net>'; $addresses[] = 'root'; $addresses[] = '<strumpet@heyes-computing.net>'; $addresses[] = 'Johnny <Johnny@johnnysworld.net>'; $addresses[] = 'Test Emails: '.implode(',', $addresses).';'; $mail = new rfc822_parse_adrlist(); header('Content-Type: text/plain'); print("\r\n\r\n".$addresses[0]."\r\n\r\n"); print_r($mail->go($addresses[0], 'localhost')); print("\r\n\r\n".$addresses[1]."\r\n\r\n\r\n"); print_r($mail->go($addresses[1], 'localhost')); print("\r\n\r\n".$addresses[2]."\r\n\r\n\r\n"); print_r($mail->go($addresses[2], 'localhost')); print("\r\n\r\n".$addresses[3]."\r\n\r\n\r\n"); print_r($mail->go($addresses[3], 'localhost')); print("\r\n\r\n".$addresses[4]."\r\n\r\n\r\n"); print_r($mail->go($addresses[4], 'localhost')); print("\r\n\r\n".$addresses[5]."\r\n\r\n\r\n"); print_r($mail->go($addresses[5], 'localhost')); ?>
<?php /*************************************** ** Filename.....: class.rfc822.address.parser.inc ** Last changed.: 26/10/2000 ** Notes........: ***************************************/ class rfc822_parse_adrlist{ var $address; var $addresses; var $default_host; var $structure; var $error; var $index; function rfc822_parse_adrlist($address = NULL, $default_host = NULL){ $this->address = $address; $this->default_host = $default_host; $this->structure = array(); $this->addresses = array(); $this->error = NULL; $this->index = NULL; } /*************************************** ** Starts the whole process. ***************************************/ function go($address = NULL, $default_host = NULL){ $this->address = $address; $this->default_host = $default_host; if($this->split_addresses($this->address) === FALSE OR isset($this->error)) return (array)$this->error; // print_r($this->addresses); exit; for($i=0; $i<count($this->addresses); $i++){ if(($return = $this->validate_address($this->addresses[$i])) === FALSE OR isset($this->error)) return (array)$this->error; $this->structure[] = $return; } $return = $this->structure; // Now clean up. $this->address = $address; $this->default_host = $default_host; $this->structure = array(); $this->addresses = array(); $this->error = NULL; $this->index = NULL; return $return; } /*************************************** ** Splits an address into seperate addresses. ***************************************/ function split_addresses($address = ''){ if($this->is_group($address) == TRUE AND !isset($this->error)){ $split_char = ';'; $is_group = TRUE; }elseif(!isset($this->error)){ $split_char = ','; $is_group = FALSE; }elseif(isset($this->error)) return FALSE; // Starts with a group, so split on semi-colon: $parts = explode($split_char, $address); $string = $this->split_check($parts, $split_char); // If a group... if($is_group === TRUE){ // If $string does not contain a colon outside of // brackets/quotes etc then something's fubar. // First check there's a colon at all: if(strpos($string, ':') === FALSE){ $this->error = 'Invalid address: '.$string; return FALSE; } // Now check it's outside of brackets/quotes: if($this->split_check(explode(':', $string), ':') == FALSE) return FALSE; } // $string now contains the first full address/group. // Add to the addresses array. $this->addresses[] = array( 'address' => trim($string), 'group' => $is_group ); // Remove the now stored address from the initial line, the +1 // is to account for the explode character. $address = trim(substr($address, strlen($string)+1 )); // If the next char is a comma and this was a group, // then there are more addresses, otherwise, if there are // any more chars, then there is another address. if($is_group === TRUE AND substr($address,0,1) == ','){ $address = trim(substr($address,1)); $this->split_addresses($address); return TRUE; }elseif(strlen($address) > 0){ $this->split_addresses($address); return TRUE; }else{ return TRUE; } // If you've got here then something's barfed. return FALSE; } /*************************************** ** Checks for a group at the start of ** the string. ***************************************/ function is_group($address){ // First comma not in quotes, angles or escaped: $parts = explode(',', $address); $string = $this->split_check($parts, ','); /*************************************** ** Now we have the first address, we can ** reliably check for a group by searching ** for a colon that's not escaped or in ** quotes or angle brackets. ***************************************/ if(count($parts = explode(':', $string)) > 1){ $string2 = $this->split_check($parts, ':'); if($string2 === $string) return FALSE; return TRUE; }else return FALSE; } /*************************************** ** A common function that will check ** an exploded string. ** Used by: split_addresses() ** is_group() ** $parts = The exploded string ** $char = The char that was exploded on. ***************************************/ function split_check($parts, $char){ $string = $parts[0]; for($i=0; $i<count($parts); $i++){ if($this->has_unclosed_quotes($string) == TRUE OR $this->has_unclosed_brackets($string, '<>') == TRUE OR $this->has_unclosed_brackets($string, '[]') == TRUE OR substr($string, -1) == '\\'){ if(isset($parts[$i+1])){ $string = $string.$char.$parts[$i+1]; }else{ $this->error = 'Invalid address spec. Unclosed bracket or quotes'; return FALSE; } }else{ $this->index = $i; break; } } return $string; } /*************************************** ** Checks if a string has an unclosed ** quotes or not. ***************************************/ function has_unclosed_quotes($string){ $string = explode('"', $string); $string_cnt = count($string); for($i=0; $i<(count($string)-1); $i++) if(substr($string[$i], -1) == '\\') $string_cnt--; if($string_cnt % 2 === 0) return TRUE; else return FALSE; } /*************************************** ** Checks if a string has an unclosed ** brackets or not. ** IMPORTANT: This function handles ** both angle brackets and square brackets; ***************************************/ function has_unclosed_brackets($string, $chars){ $num_angle_start = substr_count($string, $chars[0]); $num_angle_end = substr_count($string, $chars[1]); $num_angle_start = $this->has_unclosed_brackets_sub($string, $num_angle_start, $chars[0]); $num_angle_end = $this->has_unclosed_brackets_sub($string, $num_angle_end, $chars[1]); if($num_angle_start < $num_angle_end){ $this->error = 'Invalid address spec. Unmatched quote or bracket ('.$chars.')'; return FALSE; }elseif($num_angle_start > $num_angle_end) return TRUE; else return FALSE; } /*************************************** ** Sub function that is used only be ** has_unclosed_angles. ***************************************/ function has_unclosed_brackets_sub($string, $num, $char){ $parts = explode($char, $string); for($i=0; $i<count($parts); $i++){ if(substr($parts[$i],-1) == '\\' OR $this->has_unclosed_quotes($parts[$i])) $num--; if(isset($parts[$i+1])) $parts[$i+1] = $parts[$i].$char.$parts[$i+1]; } return $num; } /*************************************** ** Function to begin checking the address. ***************************************/ function validate_address($address){ $is_group = FALSE; if($address['group'] == TRUE){ $is_group = TRUE; // Get the group part of the name $parts = explode(':', $address['address']); $groupname = $this->split_check($parts, ':'); // And validate the group part of the name. if(!$this->validate_phrase($groupname)){ $this->error = 'Group name did not validate.'; return FALSE; }else{ $structure = new stdClass; $structure->group = $groupname; } $address['address'] = ltrim( substr($address['address'], strlen($groupname.':')) ); } // If a group then split on comma and bung in an array. // Otherwise, Just bung the whole address in an array. if($is_group == TRUE){ while(trim(strlen($address['address'])) > 0){ $parts = explode(',', $address['address']); $addresses[] = $this->split_check($parts, ','); $address['address'] = substr($address['address'], strlen(end($addresses).',')); } }else{ $addresses[] = $address['address']; } // Check that $addresses is set, if address like this: // Groupname:; // Then errors were appearing. if(!isset($addresses)){ $this->error[] = 'Empty group.'; return FALSE; } // Trim those bad boys... for($i=0; $i<count($addresses); $i++) $addresses[$i] = trim($addresses[$i]); // Validate the mailbox. // This could be one of: name <geezer@domain.com> // geezer@domain.com // geezer // // Other combinations/variations are also possible for($i=0; $i<count($addresses); $i++) $addresses[$i] = $this->validate_mailbox($addresses[$i]); $structure->addresses = $addresses; // Determine what to return. if(!isset($structure->group)) return $structure->addresses[0]; else return $structure; } /*************************************** ** Function to validate a phrase. ***************************************/ function validate_phrase($phrase){ // Splits on one or more Tab or space. $parts = preg_split('/[ \\x09]+/', $phrase, 0, PREG_SPLIT_NO_EMPTY); while(count($parts) > 0){ $phrase_parts[] = $this->split_check($parts, ' '); for($i=0; $i < $this->index + 1; $i++) array_shift($parts); } for($i=0; $i<count($phrase_parts); $i++){ // If quoted string: if(substr($phrase_parts[$i],0,1) == '"'){ if(!$this->validate_quoted_string($phrase_parts[$i])) return FALSE; continue; } // Otherwise its an atom: if(!$this->validate_atom($phrase_parts[$i])) return FALSE; } return TRUE; } /*************************************** ** Function to validate an atom which ** from rfc822 is: ** atom = 1*<any CHAR except specials, SPACE and CTLs> ***************************************/ function validate_atom($atom){ // Check for any char from ASCII 0 - ASCII 127 if(!preg_match('/^[\\x00-\\x7E]+$/i', $atom, $matches)) return FALSE; // Check for specials: if( preg_match('/[][()<>@,;\\:". ]/', $atom)) return FALSE; // Check for control characters (ASCII 0-31): if( preg_match('/[\\x00-\\x1F]+/', $atom)) return FALSE; return TRUE; } /*************************************** ** Function to validate quoted string, ** which is: ** quoted-string = <"> *(qtext/quoted-pair) <"> ***************************************/ function validate_quoted_string($qstring){ // Leading and trailing " $qstring = substr($qstring, 1, -1); // Perform check. if(preg_match('/(.)[\x0D\\\\"]/', $qstring, $matches) AND $matches[1] != '\\') return FALSE; return TRUE; } /*************************************** ** Function to validate a mailbox, ** which is: ** mailbox = addr-spec ; simple address ** / phrase route-addr ; name and route-addr ***************************************/ function validate_mailbox($mailbox){ // A couple of defaults. $phrase = ''; $adl = ''; // Check for name + route-addr if(substr($mailbox,-1) == '>' AND substr($mailbox,0,1) != '<'){ $parts = explode('<', $mailbox); $name = $this->split_check($parts, '<'); $phrase = trim($name); $route_addr = trim(substr($mailbox, strlen($name.'<'), -1)); if($this->validate_phrase($phrase) === FALSE OR ($route_addr = $this->validate_route_addr($route_addr)) === FALSE) return FALSE; // Only got addr-spec }else{ // First snip angle brackets if present. if(substr($mailbox,0,1) == '<' AND substr($mailbox,-1) == '>') $addr_spec = substr($mailbox,1,-1); else $addr_spec = $mailbox; if(($addr_spec = $this->validate_addr_spec($addr_spec)) === FALSE) return FALSE; } // Construct the object that will be returned. $mbox = new stdClass(); $mbox->personal = $phrase; if(isset($route_addr)){ $mbox->local_part = $route_addr['local_part']; $mbox->domain = $route_addr['domain']; $mbox->adl = $route_addr['adl']; }else{ $mbox->local_part = $addr_spec['local_part']; $mbox->domain = $addr_spec['domain']; $mbox->adl = $adl; } return $mbox; } /*************************************** ** This function validates a route-addr ** which is: ** route-addr = "<" [route] addr-spec ">" ** ** Angles have already been removed at the ** point of getting to this function. ***************************************/ function validate_route_addr($route_addr){ // Check for colon. if(strpos($route_addr, ':') !== FALSE){ $parts = explode(':', $route_addr); $route = $this->split_check($parts, ':'); }else{ $route = $route_addr; } // If $route is same as $route_addr then the colon // was in quotes or brackets or, of course, non existent. if($route === $route_addr){ unset($route); $addr_spec = $route_addr; if(($addr_spec = $this->validate_addr_spec($addr_spec)) === FALSE) return FALSE; }else{ // Validate route part. if(($route = $this->validate_route($route)) === FALSE) return FALSE; $addr_spec = substr($route_addr, strlen($route.':')); // Validate addr-spec part. if(($addr_spec = $this->validate_addr_spec($addr_spec)) === FALSE) return FALSE; } if(isset($route)) $return['adl'] = $route; else $return['adl'] = ''; $return = array_merge($return, $addr_spec); return $return; } /*************************************** ** Function to validate a route, which is: ** route = 1#("@" domain) ":" ***************************************/ function validate_route($route){ // Split on comma. $domains = explode(',', trim($route)); for($i=0; $i<count($domains); $i++){ $domains[$i] = str_replace('@', '', trim($domains[$i])); if($this->validate_domain($domains[$i]) === FALSE) return FALSE; } return $route; } /*************************************** ** Function to validate a domain, though ** this is not quite what you expect of ** a strict internet domain. ** ** domain = sub-domain *("." sub-domain) ***************************************/ function validate_domain($domain){ // Note the different use of $subdomains and $sub_domains $subdomains = explode('.', $domain); while(count($subdomains) > 0){ $sub_domains[] = $this->split_check($subdomains, '.'); for($i=0; $i < $this->index + 1; $i++) array_shift($subdomains); } for($i=0; $i<count($sub_domains); $i++) if($this->validate_subdomain(trim($sub_domains[$i])) === FALSE) return FALSE; // Managed to get here, so return input. return $domain; } /*************************************** ** Function to validate a subdomain. ** ** subdomain = domain-ref / domain-literal ***************************************/ function validate_subdomain($subdomain){ if(preg_match('|^\[(.*)]$|', $subdomain, $arr)){ if($this->validate_dliteral($arr[1]) === FALSE) return FALSE; }else{ if($this->validate_atom($subdomain) === FALSE) return FALSE; } // Got here, so return successful. return TRUE; } /*************************************** ** Function to validate a domain literal ** ** domain-literal = "[" *(dtext / quoted-pair) "]" ***************************************/ function validate_dliteral($dliteral){ if(preg_match('/(.)[][\x0D\\\\]/', $dliteral, $matches) AND $matches[1] != '\\') return FALSE; return TRUE; } /*************************************** ** Function to validate an addr-spec. ** ** addr-spec = local-part "@" domain ***************************************/ function validate_addr_spec($addr_spec){ $addr_spec = trim($addr_spec); // Split on @ sign if there is one. if(strpos($addr_spec, '@') !== FALSE){ $parts = explode('@', $addr_spec); $local_part = $this->split_check($parts, '@'); $domain = substr($addr_spec, strlen($local_part.'@')); // No @ sign so assume the default domain. }else{ $local_part = $addr_spec; $domain = $this->default_host; } if(($local_part = $this->validate_local_part($local_part)) === FALSE) return FALSE; if(($domain = $this->validate_domain($domain)) === FALSE) return FALSE; // Got here so return successful. return array('local_part' => $local_part, 'domain' => $domain); } /*************************************** ** Function to validate the local part ** of an address. ** ** local-part = word *("." word) ***************************************/ function validate_local_part($local_part){ $parts = explode('.', $local_part); // Split the local_part into words. while(count($parts) > 0){ $words[] = $this->split_check($parts, '.'); for($i=0; $i < $this->index + 1; $i++) array_shift($parts); } // Validate each word. for($i=0; $i<count($words); $i++) if($this->validate_phrase(trim($words[$i])) === FALSE) return FALSE; // Managed to get here, so return the input. return $local_part; } } ?><?php /*************************************** ** Filename.......: index.php ** Project name...: RFC822 Address parser ** Last Modified..: 05/11/00 ***************************************/ include_once('class.rfc822.address.parse.inc'); /*************************************** ** Some example addresses. The last one ** is all the previous together in a "group". ***************************************/ $addresses[] = 'richard.heyes@heyes-computing.net'; $addresses[] = 'Richard <@myroute:spam@heyes-computing.net>'; $addresses[] = 'root'; $addresses[] = '<strumpet@heyes-computing.net>'; $addresses[] = 'Johnny <Johnny@johnnysworld.net>'; $addresses[] = 'Test Emails: '.implode(',', $addresses).';'; $mail = new rfc822_parse_adrlist(); header('Content-Type: text/plain'); print("\r\n\r\n".$addresses[0]."\r\n\r\n"); print_r($mail->go($addresses[0], 'localhost')); print("\r\n\r\n".$addresses[1]."\r\n\r\n\r\n"); print_r($mail->go($addresses[1], 'localhost')); print("\r\n\r\n".$addresses[2]."\r\n\r\n\r\n"); print_r($mail->go($addresses[2], 'localhost')); print("\r\n\r\n".$addresses[3]."\r\n\r\n\r\n"); print_r($mail->go($addresses[3], 'localhost')); print("\r\n\r\n".$addresses[4]."\r\n\r\n\r\n"); print_r($mail->go($addresses[4], 'localhost')); print("\r\n\r\n".$addresses[5]."\r\n\r\n\r\n"); print_r($mail->go($addresses[5], 'localhost')); ?>