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

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