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

Documentation generated on Mon, 11 Mar 2019 13:55:16 -0400 by phpDocumentor 1.4.4. PEAR Logo Copyright © PHP Group 2004.