home *** CD-ROM | disk | FTP | other *** search
/ PC World 2003 March / PCWorld_2003-03_cd.bin / Software / Vyzkuste / phptriad / phptriad2-2-1.exe / php / pear / Mail / RFC822.php < prev    next >
Encoding:
PHP Script  |  2001-11-13  |  28.2 KB  |  834 lines

  1. <?php
  2. //
  3. // +----------------------------------------------------------------------+
  4. // | PHP version 4.0                                                      |
  5. // +----------------------------------------------------------------------+
  6. // | Copyright (c) 1997-2001 The PHP Group                                |
  7. // +----------------------------------------------------------------------+
  8. // | This source file is subject to version 2.02 of the PHP license,      |
  9. // | that is bundled with this package in the file LICENSE, and is        |
  10. // | available at through the world-wide-web at                           |
  11. // | http://www.php.net/license/2_02.txt.                                 |
  12. // | If you did not receive a copy of the PHP license and are unable to   |
  13. // | obtain it through the world-wide-web, please send a note to          |
  14. // | license@php.net so we can mail you a copy immediately.               |
  15. // +----------------------------------------------------------------------+
  16. // | Authors: Richard Heyes <richard@phpguru.org>                         |
  17. // |          Chuck Hagenbuch <chuck@horde.org>                           |
  18. // +----------------------------------------------------------------------+
  19.  
  20. require_once ('PEAR.php');
  21.  
  22. /**
  23. * RFC 822 Email address list validation Utility
  24. *
  25. * What is it?
  26. *
  27. * This class will take an address string, and parse it into it's consituent
  28. * parts, be that either addresses, groups, or combinations. Nested groups
  29. * are not supported. The structure it returns is pretty straight forward,
  30. * and is similar to that provided by the imap_rfc822_parse_adrlist(). Use
  31. * print_r() to view the structure.
  32. *
  33. * How do I use it?
  34. *
  35. * $address_string = 'My Group: "Richard Heyes" <richard@localhost> (A comment), ted@example.com (Ted Bloggs), Barney;';
  36. * $structure = Mail_RFC822::parseAddressList($address_string, 'example.com', TRUE)
  37. * print_r($structure);
  38. *
  39. * @author  Richard Heyes <richard@phpguru.org>
  40. * @author  Chuck Hagenbuch <chuck@horde.org>
  41. * @version $Revision: 1.9.2.2 $
  42. * @package Mail
  43. */
  44.  
  45. class Mail_RFC822 extends PEAR{
  46.  
  47.     /**
  48.      * The address being parsed by the RFC822 object.
  49.      * @var string $address
  50.      */
  51.     var $address = '';
  52.  
  53.     /**
  54.      * The default domain to use for unqualified addresses.
  55.      * @var string $default_domain
  56.      */
  57.     var $default_domain = 'localhost';
  58.  
  59.     /**
  60.      * Should we return a nested array showing groups, or flatten everything?
  61.      * @var boolean $nestGroups
  62.      */
  63.     var $nestGroups = true;
  64.  
  65.     /**
  66.      * Whether or not to validate atoms for non-ascii characters.
  67.      * @var boolean $validate
  68.      */
  69.     var $validate = true;
  70.  
  71.     /**
  72.      * The array of raw addresses built up as we parse.
  73.      * @var array $addresses
  74.      */
  75.     var $addresses = array();
  76.  
  77.     /**
  78.      * The final array of parsed address information that we build up.
  79.      * @var array $structure
  80.      */
  81.     var $structure = array();
  82.  
  83.     /**
  84.      * The current error message, if any.
  85.      * @var string $error
  86.      */
  87.     var $error = null;
  88.  
  89.     /**
  90.      * An internal counter/pointer.
  91.      * @var integer $index
  92.      */
  93.     var $index = null;
  94.  
  95.     /**
  96.      * The number of groups that have been found in the address list.
  97.      * @var integer $num_groups
  98.      * @access public
  99.      */
  100.     var $num_groups = 0;
  101.  
  102.     /**
  103.      * A variable so that we can tell whether or not we're inside a
  104.      * Mail_RFC822 object.
  105.      * @var boolean $mailRFC822
  106.      */
  107.     var $mailRFC822 = true;
  108.  
  109.  
  110.     /**
  111.      * Sets up the object. The address must either be set here or when
  112.      * calling parseAddressList(). One or the other.
  113.      *
  114.      * @access public
  115.      * @param string  $address         The address(es) to validate.
  116.      * @param string  $default_domain  Default domain/host etc. If not supplied, will be set to localhost.
  117.      * @param boolean $nest_groups     Whether to return the structure with groups nested for easier viewing.
  118.      * @param boolean $validate        Whether to validate atoms. Turn this off if you need to run addresses through before encoding the personal names, for instance.
  119.      * 
  120.      * @return object Mail_RFC822 A new Mail_RFC822 object.
  121.      */
  122.     function Mail_RFC822($address = null, $default_domain = null, $nest_groups = null, $validate = null)
  123.     {
  124.         if (isset($address))        $this->address        = $address;
  125.         if (isset($default_domain)) $this->default_domain = $default_domain;
  126.         if (isset($nest_groups))    $this->nestGroups     = $nest_groups;
  127.         if (isset($validate))       $this->validate       = $validate;
  128.     }
  129.  
  130.  
  131.     /**
  132.      * Starts the whole process. The address must either be set here
  133.      * or when creating the object. One or the other.
  134.      *
  135.      * @access public
  136.      * @param string  $address         The address(es) to validate.
  137.      * @param string  $default_domain  Default domain/host etc.
  138.      * @param boolean $nest_groups     Whether to return the structure with groups nested for easier viewing.
  139.      * @param boolean $validate        Whether to validate atoms. Turn this off if you need to run addresses through before encoding the personal names, for instance.
  140.      * 
  141.      * @return array A structured array of addresses.
  142.      */
  143.     function parseAddressList($address = null, $default_domain = null, $nest_groups = null, $validate = null)
  144.     {
  145.  
  146.         if (!isset($this->mailRFC822)) {
  147.             $obj = new Mail_RFC822($address, $default_domain, $nest_groups, $validate);
  148.             return $obj->parseAddressList();
  149.         }
  150.  
  151.         if (isset($address))        $this->address        = $address;
  152.         if (isset($default_domain)) $this->default_domain = $default_domain;
  153.         if (isset($nest_groups))    $this->nestGroups     = $nest_groups;
  154.         if (isset($validate))       $this->validate       = $validate;
  155.  
  156.         $this->structure  = array();
  157.         $this->addresses  = array();
  158.         $this->error      = null;
  159.         $this->index      = null;
  160.  
  161.         if (!$this->_splitAddresses($this->address) || isset($this->error)) {
  162.             return $this->raiseError($this->error);
  163.         }
  164.  
  165.         for ($i = 0; $i < count($this->addresses); $i++){
  166.             if (($return = $this->_validateAddress($this->addresses[$i])) === false
  167.                 || isset($this->error)) {
  168.                 return $this->raiseError($this->error);
  169.             }
  170.             
  171.             if (!$this->nestGroups) {
  172.                 $this->structure = array_merge($this->structure, $return);
  173.             } else {
  174.                 $this->structure[] = $return;
  175.             }
  176.         }
  177.         
  178.         return $this->structure;
  179.     }
  180.  
  181.     /**
  182.      * Splits an address into seperate addresses.
  183.      * 
  184.      * @access private
  185.      * @param string $address The addresses to split.
  186.      * @return boolean Success or failure.
  187.      */
  188.     function _splitAddresses($address = '')
  189.     {
  190.  
  191.         if ($this->_isGroup($address) && !isset($this->error)) {
  192.             $split_char = ';';
  193.             $is_group   = true;
  194.         } elseif (!isset($this->error)) {
  195.             $split_char = ',';
  196.             $is_group   = false;
  197.         } elseif (isset($this->error)) {
  198.             return false;
  199.         }
  200.  
  201.         // Split the string based on the above ten or so lines.
  202.         $parts  = explode($split_char, $address);
  203.         $string = $this->_splitCheck($parts, $split_char);
  204.  
  205.         // If a group...
  206.         if ($is_group) {
  207.             // If $string does not contain a colon outside of
  208.             // brackets/quotes etc then something's fubar.
  209.  
  210.             // First check there's a colon at all:
  211.             if (strpos($string, ':') === false) {
  212.                 $this->error = 'Invalid address: ' . $string;
  213.                 return false;
  214.             }
  215.  
  216.             // Now check it's outside of brackets/quotes:
  217.             if (!$this->_splitCheck(explode(':', $string), ':'))
  218.                 return false;
  219.  
  220.             // We must have a group at this point, so increase the counter:
  221.             $this->num_groups++;
  222.         }
  223.  
  224.         // $string now contains the first full address/group.
  225.         // Add to the addresses array.
  226.         $this->addresses[] = array(
  227.                                    'address' => trim($string),
  228.                                    'group'   => $is_group
  229.                                    );
  230.  
  231.         // Remove the now stored address from the initial line, the +1
  232.         // is to account for the explode character.
  233.         $address = trim(substr($address, strlen($string) + 1));
  234.  
  235.         // If the next char is a comma and this was a group, then
  236.         // there are more addresses, otherwise, if there are any more
  237.         // chars, then there is another address.
  238.         if ($is_group && substr($address, 0, 1) == ','){
  239.             $address = trim(substr($address, 1));
  240.             $this->_splitAddresses($address);
  241.             return true;
  242.  
  243.         } elseif (strlen($address) > 0) {
  244.             $this->_splitAddresses($address);
  245.             return true;
  246.         } else {
  247.             return true;
  248.         }
  249.  
  250.         // If you got here then something's off
  251.         return false;
  252.     }
  253.  
  254.     /**
  255.      * Checks for a group at the start of the string.
  256.      * 
  257.      * @access private
  258.      * @param string $address The address to check.
  259.      * @return boolean Whether or not there is a group at the start of the string.
  260.      */
  261.     function _isGroup($address)
  262.     {
  263.         // First comma not in quotes, angles or escaped:
  264.         $parts  = explode(',', $address);
  265.         $string = $this->_splitCheck($parts, ',');
  266.  
  267.         // Now we have the first address, we can reliably check for a
  268.         // group by searching for a colon that's not escaped or in
  269.         // quotes or angle brackets.
  270.         if (count($parts = explode(':', $string)) > 1) {
  271.             $string2 = $this->_splitCheck($parts, ':');
  272.             return ($string2 !== $string);
  273.         } else {
  274.             return false;
  275.         }
  276.     }
  277.  
  278.     /**
  279.      * A common function that will check an exploded string.
  280.      * 
  281.      * @access private
  282.      * @param array $parts The exloded string.
  283.      * @param string $char  The char that was exploded on.
  284.      * @return mixed False if the string contains unclosed quotes/brackets, or the string on success.
  285.      */
  286.     function _splitCheck($parts, $char)
  287.     {
  288.         $string = $parts[0];
  289.  
  290.         for ($i = 0; $i < count($parts); $i++) {
  291.             if ($this->_hasUnclosedQuotes($string)
  292.                 || $this->_hasUnclosedBrackets($string, '<>')
  293.                 || $this->_hasUnclosedBrackets($string, '[]')
  294.                 || $this->_hasUnclosedBrackets($string, '()')
  295.                 || substr($string, -1) == '\\') {
  296.                 if (isset($parts[$i + 1])) {
  297.                     $string = $string . $char . $parts[$i + 1];
  298.                 } else {
  299.                     $this->error = 'Invalid address spec. Unclosed bracket or quotes';
  300.                     return false;
  301.                 }
  302.             } else {
  303.                 $this->index = $i;
  304.                 break;
  305.             }
  306.         }
  307.  
  308.         return $string;
  309.     }
  310.  
  311.     /**
  312.      * Checks if a string has an unclosed quotes or not.
  313.      * 
  314.      * @access private
  315.      * @param string $string The string to check.
  316.      * @return boolean True if there are unclosed quotes inside the string, false otherwise.
  317.      */
  318.     function _hasUnclosedQuotes($string)
  319.     {
  320.         $string     = explode('"', $string);
  321.         $string_cnt = count($string);
  322.  
  323.         for ($i = 0; $i < (count($string) - 1); $i++)
  324.             if (substr($string[$i], -1) == '\\')
  325.                 $string_cnt--;
  326.  
  327.         return ($string_cnt % 2 === 0);
  328.     }
  329.  
  330.     /**
  331.      * Checks if a string has an unclosed brackets or not. IMPORTANT:
  332.      * This function handles both angle brackets and square brackets;
  333.      * 
  334.      * @access private
  335.      * @param string $string The string to check.
  336.      * @param string $chars  The characters to check for.
  337.      * @return boolean True if there are unclosed brackets inside the string, false otherwise.
  338.      */
  339.     function _hasUnclosedBrackets($string, $chars)
  340.     {
  341.         $num_angle_start = substr_count($string, $chars[0]);
  342.         $num_angle_end   = substr_count($string, $chars[1]);
  343.  
  344.         $this->_hasUnclosedBracketsSub($string, $num_angle_start, $chars[0]);
  345.         $this->_hasUnclosedBracketsSub($string, $num_angle_end, $chars[1]);
  346.  
  347.         if ($num_angle_start < $num_angle_end) {
  348.             $this->error = 'Invalid address spec. Unmatched quote or bracket (' . $chars . ')';
  349.             return false;
  350.         } else {
  351.             return ($num_angle_start > $num_angle_end);
  352.         }
  353.     }
  354.  
  355.     /**
  356.      * Sub function that is used only by hasUnclosedBrackets().
  357.      * 
  358.      * @access private
  359.      * @param string $string The string to check.
  360.      * @param integer &$num    The number of occurences.
  361.      * @param string $char   The character to count.
  362.      * @return integer The number of occurences of $char in $string, adjusted for backslashes.
  363.      */
  364.     function _hasUnclosedBracketsSub($string, &$num, $char)
  365.     {
  366.         $parts = explode($char, $string);
  367.         for ($i = 0; $i < count($parts); $i++){
  368.             if (substr($parts[$i], -1) == '\\' || $this->_hasUnclosedQuotes($parts[$i]))
  369.                 $num--;
  370.             if (isset($parts[$i + 1]))
  371.                 $parts[$i + 1] = $parts[$i] . $char . $parts[$i + 1];
  372.         }
  373.         
  374.         return $num;
  375.     }
  376.  
  377.     /**
  378.      * Function to begin checking the address.
  379.      *
  380.      * @access private
  381.      * @param string $address The address to validate.
  382.      * @return mixed False on failure, or a structured array of address information on success.
  383.      */
  384.     function _validateAddress($address)
  385.     {
  386.         $is_group = false;
  387.  
  388.         if ($address['group']) {
  389.             $is_group = true;
  390.  
  391.             // Get the group part of the name
  392.             $parts     = explode(':', $address['address']);
  393.             $groupname = $this->_splitCheck($parts, ':');
  394.             $structure = array();
  395.  
  396.             // And validate the group part of the name.
  397.             if (!$this->_validatePhrase($groupname)){
  398.                 $this->error = 'Group name did not validate.';
  399.                 return false;
  400.             } else {
  401.                 // Don't include groups if we are not nesting
  402.                 // them. This avoids returning invalid addresses.
  403.                 if ($this->nestGroups) {
  404.                     $structure = new stdClass;
  405.                     $structure->groupname = $groupname;
  406.                 }
  407.             }
  408.  
  409.             $address['address'] = ltrim(substr($address['address'], strlen($groupname . ':')));
  410.         }
  411.  
  412.         // If a group then split on comma and put into an array.
  413.         // Otherwise, Just put the whole address in an array.
  414.         if ($is_group) {
  415.             while (strlen($address['address']) > 0) {
  416.                 $parts       = explode(',', $address['address']);
  417.                 $addresses[] = $this->_splitCheck($parts, ',');
  418.                 $address['address'] = trim(substr($address['address'], strlen(end($addresses) . ',')));
  419.             }
  420.         } else {
  421.             $addresses[] = $address['address'];
  422.         }
  423.  
  424.         // Check that $addresses is set, if address like this:
  425.         // Groupname:;
  426.         // Then errors were appearing.
  427.         if (!isset($addresses)){
  428.             $this->error = 'Empty group.';
  429.             return false;
  430.         }
  431.  
  432.         for ($i = 0; $i < count($addresses); $i++) {
  433.             $addresses[$i] = trim($addresses[$i]);
  434.         }
  435.  
  436.         // Validate each mailbox.
  437.         // Format could be one of: name <geezer@domain.com>
  438.         //                         geezer@domain.com
  439.         //                         geezer
  440.         // ... or any other format valid by RFC 822.
  441.         array_walk($addresses, array($this, 'validateMailbox'));
  442.  
  443.         // Nested format
  444.         if ($this->nestGroups) {
  445.             if ($is_group) {
  446.                 $structure->addresses = $addresses;
  447.             } else {
  448.                 $structure = $addresses[0];
  449.             }
  450.  
  451.         // Flat format
  452.         } else {
  453.             if ($is_group) {
  454.                 $structure = array_merge($structure, $addresses);
  455.             } else {
  456.                 $structure = $addresses;
  457.             }
  458.         }
  459.  
  460.         return $structure;
  461.     }
  462.  
  463.     /**
  464.      * Function to validate a phrase.
  465.      *
  466.      * @access private
  467.      * @param string $phrase The phrase to check.
  468.      * @return boolean Success or failure.
  469.      */
  470.     function _validatePhrase($phrase)
  471.     {
  472.         // Splits on one or more Tab or space.
  473.         $parts = preg_split('/[ \\x09]+/', $phrase, -1, PREG_SPLIT_NO_EMPTY);
  474.  
  475.         $phrase_parts = array();
  476.         while (count($parts) > 0){
  477.             $phrase_parts[] = $this->_splitCheck($parts, ' ');
  478.             for ($i = 0; $i < $this->index + 1; $i++)
  479.                 array_shift($parts);
  480.         }
  481.  
  482.         for ($i = 0; $i < count($phrase_parts); $i++) {
  483.             // If quoted string:
  484.             if (substr($phrase_parts[$i], 0, 1) == '"') {
  485.                 if (!$this->_validateQuotedString($phrase_parts[$i]))
  486.                     return false;
  487.                 continue;
  488.             }
  489.  
  490.             // Otherwise it's an atom:
  491.             if (!$this->_validateAtom($phrase_parts[$i])) return false;
  492.         }
  493.  
  494.         return true;
  495.     }
  496.  
  497.     /**
  498.      * Function to validate an atom which from rfc822 is:
  499.      * atom = 1*<any CHAR except specials, SPACE and CTLs>
  500.      * 
  501.      * If validation ($this->validate) has been turned off, then
  502.      * validateAtom() doesn't actually check anything. This is so that you
  503.      * can split a list of addresses up before encoding personal names
  504.      * (umlauts, etc.), for example.
  505.      * 
  506.      * @access private
  507.      * @param string $atom The string to check.
  508.      * @return boolean Success or failure.
  509.      */
  510.     function _validateAtom($atom)
  511.     {
  512.         if (!$this->validate) {
  513.             // Validation has been turned off; assume the atom is okay.
  514.             return true;
  515.         }
  516.  
  517.         // Check for any char from ASCII 0 - ASCII 127
  518.         if (!preg_match('/^[\\x00-\\x7E]+$/i', $atom, $matches)) {
  519.             return false;
  520.         }
  521.  
  522.         // Check for specials:
  523.         if (preg_match('/[][()<>@,;\\:". ]/', $atom)) {
  524.             return false;
  525.         }
  526.  
  527.         // Check for control characters (ASCII 0-31):
  528.         if (preg_match('/[\\x00-\\x1F]+/', $atom)) {
  529.             return false;
  530.         }
  531.  
  532.         return true;
  533.     }
  534.  
  535.     /**
  536.      * Function to validate quoted string, which is:
  537.      * quoted-string = <"> *(qtext/quoted-pair) <">
  538.      * 
  539.      * @access private
  540.      * @param string $qstring The string to check
  541.      * @return boolean Success or failure.
  542.      */
  543.     function _validateQuotedString($qstring)
  544.     {
  545.         // Leading and trailing "
  546.         $qstring = substr($qstring, 1, -1);
  547.  
  548.         // Perform check.
  549.         return !(preg_match('/(.)[\x0D\\\\"]/', $qstring, $matches) && $matches[1] != '\\');
  550.     }
  551.  
  552.     /**
  553.      * Function to validate a mailbox, which is:
  554.      * mailbox =   addr-spec         ; simple address
  555.      *           / phrase route-addr ; name and route-addr
  556.      * 
  557.      * @access public
  558.      * @param string &$mailbox The string to check.
  559.      * @return boolean Success or failure.
  560.      */
  561.     function validateMailbox(&$mailbox)
  562.     {
  563.         // A couple of defaults.
  564.         $phrase  = '';
  565.         $comment = '';
  566.  
  567.         // Catch any RFC822 comments and store them separately
  568.         $_mailbox = $mailbox;
  569.         while (strlen(trim($_mailbox)) > 0) {
  570.             $parts = explode('(', $_mailbox);
  571.             $before_comment = $this->_splitCheck($parts, '(');
  572.             if ($before_comment != $_mailbox) {
  573.                 // First char should be a (
  574.                 $comment    = substr(str_replace($before_comment, '', $_mailbox), 1);
  575.                 $parts      = explode(')', $comment);
  576.                 $comment    = $this->_splitCheck($parts, ')');
  577.                 $comments[] = $comment;
  578.  
  579.                 // +1 is for the trailing )
  580.                 $_mailbox   = substr($_mailbox, strpos($_mailbox, $comment)+strlen($comment)+1);
  581.             } else {
  582.                 break;
  583.             }
  584.         }
  585.  
  586.         for($i=0; $i<count(@$comments); $i++){
  587.             $mailbox = str_replace('('.$comments[$i].')', '', $mailbox);
  588.         }
  589.         $mailbox = trim($mailbox);
  590.  
  591.         // Check for name + route-addr
  592.         if (substr($mailbox, -1) == '>' && substr($mailbox, 0, 1) != '<') {
  593.             $parts  = explode('<', $mailbox);
  594.             $name   = $this->_splitCheck($parts, '<');
  595.  
  596.             $phrase     = trim($name);
  597.             $route_addr = trim(substr($mailbox, strlen($name.'<'), -1));
  598.  
  599.             if ($this->_validatePhrase($phrase) === false || ($route_addr = $this->_validateRouteAddr($route_addr)) === false)
  600.                 return false;
  601.  
  602.         // Only got addr-spec
  603.         } else {
  604.             // First snip angle brackets if present.
  605.             if (substr($mailbox,0,1) == '<' && substr($mailbox,-1) == '>')
  606.                 $addr_spec = substr($mailbox,1,-1);
  607.             else
  608.                 $addr_spec = $mailbox;
  609.  
  610.             if (($addr_spec = $this->_validateAddrSpec($addr_spec)) === false)
  611.                 return false;
  612.         }
  613.  
  614.         // Construct the object that will be returned.
  615.         $mbox = new stdClass();
  616.  
  617.         // Add the phrase (even if empty) and comments
  618.         $mbox->personal = $phrase;
  619.         $mbox->comment  = isset($comments) ? $comments : array();
  620.  
  621.         if (isset($route_addr)) {
  622.             $mbox->mailbox = $route_addr['local_part'];
  623.             $mbox->host    = $route_addr['domain'];
  624.             $route_addr['adl'] !== '' ? $mbox->adl = $route_addr['adl'] : '';
  625.         } else {
  626.             $mbox->mailbox = $addr_spec['local_part'];
  627.             $mbox->host    = $addr_spec['domain'];
  628.         }
  629.  
  630.         $mailbox = $mbox;
  631.         return true;
  632.     }
  633.  
  634.     /**
  635.      * This function validates a route-addr which is:
  636.      * route-addr = "<" [route] addr-spec ">"
  637.      *
  638.      * Angle brackets have already been removed at the point of
  639.      * getting to this function.
  640.      * 
  641.      * @access private
  642.      * @param string $route_addr The string to check.
  643.      * @return mixed False on failure, or an array containing validated address/route information on success.
  644.      */
  645.     function _validateRouteAddr($route_addr)
  646.     {
  647.         // Check for colon.
  648.         if (strpos($route_addr, ':') !== false) {
  649.             $parts = explode(':', $route_addr);
  650.             $route = $this->_splitCheck($parts, ':');
  651.         } else {
  652.             $route = $route_addr;
  653.         }
  654.  
  655.         // If $route is same as $route_addr then the colon was in
  656.         // quotes or brackets or, of course, non existent.
  657.         if ($route === $route_addr){
  658.             unset($route);
  659.             $addr_spec = $route_addr;
  660.             if (($addr_spec = $this->_validateAddrSpec($addr_spec)) === false) {
  661.                 return false;
  662.             }
  663.         } else {
  664.             // Validate route part.
  665.             if (($route = $this->_validateRoute($route)) === false) {
  666.                 return false;
  667.             }
  668.  
  669.             $addr_spec = substr($route_addr, strlen($route . ':'));
  670.  
  671.             // Validate addr-spec part.
  672.             if (($addr_spec = $this->_validateAddrSpec($addr_spec)) === false) {
  673.                 return false;
  674.             }
  675.         }
  676.  
  677.         if (isset($route)) {
  678.             $return['adl'] = $route;
  679.         } else {
  680.             $return['adl'] = '';
  681.         }
  682.  
  683.         $return = array_merge($return, $addr_spec);
  684.         return $return;
  685.     }
  686.  
  687.     /**
  688.      * Function to validate a route, which is:
  689.      * route = 1#("@" domain) ":"
  690.      * 
  691.      * @access private
  692.      * @param string $route The string to check.
  693.      * @return mixed False on failure, or the validated $route on success.
  694.      */
  695.     function _validateRoute($route)
  696.     {
  697.         // Split on comma.
  698.         $domains = explode(',', trim($route));
  699.  
  700.         for ($i = 0; $i < count($domains); $i++) {
  701.             $domains[$i] = str_replace('@', '', trim($domains[$i]));
  702.             if (!$this->_validateDomain($domains[$i])) return false;
  703.         }
  704.  
  705.         return $route;
  706.     }
  707.  
  708.     /**
  709.      * Function to validate a domain, though this is not quite what
  710.      * you expect of a strict internet domain.
  711.      *
  712.      * domain = sub-domain *("." sub-domain)
  713.      * 
  714.      * @access private
  715.      * @param string $domain The string to check.
  716.      * @return mixed False on failure, or the validated domain on success.
  717.      */
  718.     function _validateDomain($domain)
  719.     {
  720.         // Note the different use of $subdomains and $sub_domains                        
  721.         $subdomains = explode('.', $domain);
  722.  
  723.         while (count($subdomains) > 0) {
  724.             $sub_domains[] = $this->_splitCheck($subdomains, '.');
  725.             for ($i = 0; $i < $this->index + 1; $i++)
  726.                 array_shift($subdomains);
  727.         }
  728.  
  729.         for ($i = 0; $i < count($sub_domains); $i++) {
  730.             if (!$this->_validateSubdomain(trim($sub_domains[$i])))
  731.                 return false;
  732.         }
  733.  
  734.         // Managed to get here, so return input.
  735.         return $domain;
  736.     }
  737.  
  738.     /**
  739.      * Function to validate a subdomain:
  740.      *   subdomain = domain-ref / domain-literal
  741.      * 
  742.      * @access private
  743.      * @param string $subdomain The string to check.
  744.      * @return boolean Success or failure.
  745.      */
  746.     function _validateSubdomain($subdomain)
  747.     {
  748.         if (preg_match('|^\[(.*)]$|', $subdomain, $arr)){
  749.             if (!$this->_validateDliteral($arr[1])) return false;
  750.         } else {
  751.             if (!$this->_validateAtom($subdomain)) return false;
  752.         }
  753.  
  754.         // Got here, so return successful.
  755.         return true;
  756.     }
  757.  
  758.     /**
  759.      * Function to validate a domain literal:
  760.      *   domain-literal =  "[" *(dtext / quoted-pair) "]"
  761.      * 
  762.      * @access private
  763.      * @param string $dliteral The string to check.
  764.      * @return boolean Success or failure.
  765.      */
  766.     function _validateDliteral($dliteral)
  767.     {
  768.         return !preg_match('/(.)[][\x0D\\\\]/', $dliteral, $matches) && $matches[1] != '\\';
  769.     }
  770.  
  771.     /**
  772.      * Function to validate an addr-spec.
  773.      *
  774.      * addr-spec = local-part "@" domain
  775.      * 
  776.      * @access private
  777.      * @param string $addr_spec The string to check.
  778.      * @return mixed False on failure, or the validated addr-spec on success.
  779.      */
  780.     function _validateAddrSpec($addr_spec)
  781.     {
  782.         $addr_spec = trim($addr_spec);
  783.  
  784.         // Split on @ sign if there is one.
  785.         if (strpos($addr_spec, '@') !== false) {
  786.             $parts      = explode('@', $addr_spec);
  787.             $local_part = $this->_splitCheck($parts, '@');
  788.             $domain     = substr($addr_spec, strlen($local_part . '@'));
  789.  
  790.         // No @ sign so assume the default domain.
  791.         } else {
  792.             $local_part = $addr_spec;
  793.             $domain     = $this->default_domain;
  794.         }
  795.  
  796.         if (($local_part = $this->_validateLocalPart($local_part)) === false) return false;
  797.         if (($domain     = $this->_validateDomain($domain)) === false) return false;
  798.         
  799.         // Got here so return successful.
  800.         return array('local_part' => $local_part, 'domain' => $domain);
  801.     }
  802.  
  803.     /**
  804.      * Function to validate the local part of an address:
  805.      *   local-part = word *("." word)
  806.      * 
  807.      * @access private
  808.      * @param string $local_part
  809.      * @return mixed False on failure, or the validated local part on success.
  810.      */
  811.     function _validateLocalPart($local_part)
  812.     {
  813.         $parts = explode('.', $local_part);
  814.  
  815.         // Split the local_part into words.
  816.         while (count($parts) > 0){
  817.             $words[] = $this->_splitCheck($parts, '.');
  818.             for ($i = 0; $i < $this->index + 1; $i++) {
  819.                 array_shift($parts);
  820.             }
  821.         }
  822.  
  823.         // Validate each word.
  824.         for ($i = 0; $i < count($words); $i++) {
  825.             if ($this->_validatePhrase(trim($words[$i])) === false) return false;
  826.         }
  827.  
  828.         // Managed to get here, so return the input.
  829.         return $local_part;
  830.     }
  831.  
  832. }
  833. ?>
  834.