Source for file class.RFC822.php

Documentation is available at class.RFC822.php

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

Documentation generated on Sat, 24 Mar 2007 10:00:00 +0100 by phpDocumentor 1.3.1