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