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

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