Changeset 7688


Ignore:
Timestamp:
12/26/12 16:15:59 (7 years ago)
Author:
cristiano
Message:

Ticket #3209 - ErSincronização? de status e acl de edição

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/library/Mail/Mail/RFC822.php

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