Mail
[ class tree: Mail ] [ index: Mail ] [ all elements ]

Source for file RFC822.php

Documentation is available at RFC822.php

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

Documentation generated on Mon, 11 Mar 2019 10:14:57 -0400 by phpDocumentor 1.4.4. PEAR Logo Copyright © PHP Group 2004.