00001 <?php
00033 class Mail_RFC822
00034 {
00039 private $address = '';
00040
00045 private $default_domain = 'localhost';
00046
00051 private $nestGroups = true;
00052
00057 private $validate = true;
00058
00063 private $addresses = array();
00064
00069 private $structure = array();
00070
00075 private $error = null;
00076
00081 private $index = null;
00082
00088 private $num_groups = 0;
00089
00095 private $mailRFC822 = true;
00096
00101 private $limit = null;
00102
00103
00116 function __construct($address = null, $default_domain = null, $nest_groups = null, $validate = null, $limit = null)
00117 {
00118 if (isset($address)) $this->address = $address;
00119 if (isset($default_domain)) $this->default_domain = $default_domain;
00120 if (isset($nest_groups)) $this->nestGroups = $nest_groups;
00121 if (isset($validate)) $this->validate = $validate;
00122 if (isset($limit)) $this->limit = $limit;
00123 }
00124
00125
00138 function parseAddressList($address = null, $default_domain = null, $nest_groups = null, $validate = null, $limit = null)
00139 {
00140
00141 if (!isset($this->mailRFC822)) {
00142 $obj = new Mail_RFC822($address, $default_domain, $nest_groups, $validate, $limit);
00143 return $obj->parseAddressList();
00144 }
00145
00146 if (isset($address)) $this->address = $address;
00147 if (isset($default_domain)) $this->default_domain = $default_domain;
00148 if (isset($nest_groups)) $this->nestGroups = $nest_groups;
00149 if (isset($validate)) $this->validate = $validate;
00150 if (isset($limit)) $this->limit = $limit;
00151
00152 $this->structure = array();
00153 $this->addresses = array();
00154 $this->error = null;
00155 $this->index = null;
00156
00157 while ($this->address = $this->_splitAddresses($this->address)) {
00158 continue;
00159 }
00160
00161 if ($this->address === false || isset($this->error)) {
00162 return false;
00163 }
00164
00165
00166
00167 set_time_limit(30);
00168
00169
00170 for ($i = 0; $i < count($this->addresses); $i++){
00171
00172 if (($return = $this->_validateAddress($this->addresses[$i])) === false
00173 || isset($this->error)) {
00174 return false;
00175 }
00176
00177 if (!$this->nestGroups) {
00178 $this->structure = array_merge($this->structure, $return);
00179 } else {
00180 $this->structure[] = $return;
00181 }
00182 }
00183
00184 return $this->structure;
00185 }
00186
00194 function _splitAddresses($address)
00195 {
00196
00197 if (!empty($this->limit) AND count($this->addresses) == $this->limit) {
00198 return '';
00199 }
00200
00201 if ($this->_isGroup($address) && !isset($this->error)) {
00202 $split_char = ';';
00203 $is_group = true;
00204 } elseif (!isset($this->error)) {
00205 $split_char = ',';
00206 $is_group = false;
00207 } elseif (isset($this->error)) {
00208 return false;
00209 }
00210
00211
00212 $parts = explode($split_char, $address);
00213 $string = $this->_splitCheck($parts, $split_char);
00214
00215
00216 if ($is_group) {
00217
00218
00219
00220
00221 if (strpos($string, ':') === false) {
00222 $this->error = 'Invalid address: ' . $string;
00223 return false;
00224 }
00225
00226
00227 if (!$this->_splitCheck(explode(':', $string), ':'))
00228 return false;
00229
00230
00231 $this->num_groups++;
00232 }
00233
00234
00235
00236 $this->addresses[] = array(
00237 'address' => trim($string),
00238 'group' => $is_group
00239 );
00240
00241
00242
00243 $address = trim(substr($address, strlen($string) + 1));
00244
00245
00246
00247
00248 if ($is_group && substr($address, 0, 1) == ','){
00249 $address = trim(substr($address, 1));
00250 return $address;
00251
00252 } elseif (strlen($address) > 0) {
00253 return $address;
00254
00255 } else {
00256 return '';
00257 }
00258
00259
00260 return false;
00261 }
00262
00270 function _isGroup($address)
00271 {
00272
00273 $parts = explode(',', $address);
00274 $string = $this->_splitCheck($parts, ',');
00275
00276
00277
00278
00279 if (count($parts = explode(':', $string)) > 1) {
00280 $string2 = $this->_splitCheck($parts, ':');
00281 return ($string2 !== $string);
00282 } else {
00283 return false;
00284 }
00285 }
00286
00295 function _splitCheck($parts, $char)
00296 {
00297 $string = $parts[0];
00298
00299 for ($i = 0; $i < count($parts); $i++) {
00300 if ($this->_hasUnclosedQuotes($string)
00301 || $this->_hasUnclosedBrackets($string, '<>')
00302 || $this->_hasUnclosedBrackets($string, '[]')
00303 || $this->_hasUnclosedBrackets($string, '()')
00304 || substr($string, -1) == '\\') {
00305 if (isset($parts[$i + 1])) {
00306 $string = $string . $char . $parts[$i + 1];
00307 } else {
00308 $this->error = 'Invalid address spec. Unclosed bracket or quotes';
00309 return false;
00310 }
00311 } else {
00312 $this->index = $i;
00313 break;
00314 }
00315 }
00316
00317 return $string;
00318 }
00319
00327 function _hasUnclosedQuotes($string)
00328 {
00329 $string = explode('"', $string);
00330 $string_cnt = count($string);
00331
00332 for ($i = 0; $i < (count($string) - 1); $i++)
00333 if (substr($string[$i], -1) == '\\')
00334 $string_cnt--;
00335
00336 return ($string_cnt % 2 === 0);
00337 }
00338
00348 function _hasUnclosedBrackets($string, $chars)
00349 {
00350 $num_angle_start = substr_count($string, $chars[0]);
00351 $num_angle_end = substr_count($string, $chars[1]);
00352
00353 $this->_hasUnclosedBracketsSub($string, $num_angle_start, $chars[0]);
00354 $this->_hasUnclosedBracketsSub($string, $num_angle_end, $chars[1]);
00355
00356 if ($num_angle_start < $num_angle_end) {
00357 $this->error = 'Invalid address spec. Unmatched quote or bracket (' . $chars . ')';
00358 return false;
00359 } else {
00360 return ($num_angle_start > $num_angle_end);
00361 }
00362 }
00363
00373 function _hasUnclosedBracketsSub($string, &$num, $char)
00374 {
00375 $parts = explode($char, $string);
00376 for ($i = 0; $i < count($parts); $i++){
00377 if (substr($parts[$i], -1) == '\\' || $this->_hasUnclosedQuotes($parts[$i]))
00378 $num--;
00379 if (isset($parts[$i + 1]))
00380 $parts[$i + 1] = $parts[$i] . $char . $parts[$i + 1];
00381 }
00382
00383 return $num;
00384 }
00385
00393 function _validateAddress($address)
00394 {
00395 $is_group = false;
00396
00397 if ($address['group']) {
00398 $is_group = true;
00399
00400
00401 $parts = explode(':', $address['address']);
00402 $groupname = $this->_splitCheck($parts, ':');
00403 $structure = array();
00404
00405
00406 if (!$this->_validatePhrase($groupname)){
00407 $this->error = 'Group name did not validate.';
00408 return false;
00409 } else {
00410
00411
00412 if ($this->nestGroups) {
00413 $structure = new stdClass;
00414 $structure->groupname = $groupname;
00415 }
00416 }
00417
00418 $address['address'] = ltrim(substr($address['address'], strlen($groupname . ':')));
00419 }
00420
00421
00422
00423 if ($is_group) {
00424 while (strlen($address['address']) > 0) {
00425 $parts = explode(',', $address['address']);
00426 $addresses[] = $this->_splitCheck($parts, ',');
00427 $address['address'] = trim(substr($address['address'], strlen(end($addresses) . ',')));
00428 }
00429 } else {
00430 $addresses[] = $address['address'];
00431 }
00432
00433
00434
00435
00436 if (!isset($addresses)){
00437 $this->error = 'Empty group.';
00438 return false;
00439 }
00440
00441 for ($i = 0; $i < count($addresses); $i++) {
00442 $addresses[$i] = trim($addresses[$i]);
00443 }
00444
00445
00446
00447
00448
00449
00450 array_walk($addresses, array($this, 'validateMailbox'));
00451
00452
00453 if ($this->nestGroups) {
00454 if ($is_group) {
00455 $structure->addresses = $addresses;
00456 } else {
00457 $structure = $addresses[0];
00458 }
00459
00460
00461 } else {
00462 if ($is_group) {
00463 $structure = array_merge($structure, $addresses);
00464 } else {
00465 $structure = $addresses;
00466 }
00467 }
00468
00469 return $structure;
00470 }
00471
00479 function _validatePhrase($phrase)
00480 {
00481
00482 $parts = preg_split('/[ \\x09]+/', $phrase, -1, PREG_SPLIT_NO_EMPTY);
00483
00484 $phrase_parts = array();
00485 while (count($parts) > 0){
00486 $phrase_parts[] = $this->_splitCheck($parts, ' ');
00487 for ($i = 0; $i < $this->index + 1; $i++)
00488 array_shift($parts);
00489 }
00490
00491 for ($i = 0; $i < count($phrase_parts); $i++) {
00492
00493 if (substr($phrase_parts[$i], 0, 1) == '"') {
00494 if (!$this->_validateQuotedString($phrase_parts[$i]))
00495 return false;
00496 continue;
00497 }
00498
00499
00500 if (!$this->_validateAtom($phrase_parts[$i])) return false;
00501 }
00502
00503 return true;
00504 }
00505
00519 function _validateAtom($atom)
00520 {
00521 if (!$this->validate) {
00522
00523 return true;
00524 }
00525
00526
00527 if (!preg_match('/^[\\x00-\\x7E]+$/i', $atom, $matches)) {
00528 return false;
00529 }
00530
00531
00532 if (preg_match('/[][()<>@,;\\:". ]/', $atom)) {
00533 return false;
00534 }
00535
00536
00537 if (preg_match('/[\\x00-\\x1F]+/', $atom)) {
00538 return false;
00539 }
00540
00541 return true;
00542 }
00543
00552 function _validateQuotedString($qstring)
00553 {
00554
00555 $qstring = substr($qstring, 1, -1);
00556
00557
00558 return !(preg_match('/(.)[\x0D\\\\"]/', $qstring, $matches) && $matches[1] != '\\');
00559 }
00560
00570 function validateMailbox(&$mailbox)
00571 {
00572
00573 $phrase = '';
00574 $comment = '';
00575
00576
00577 $_mailbox = $mailbox;
00578 while (strlen(trim($_mailbox)) > 0) {
00579 $parts = explode('(', $_mailbox);
00580 $before_comment = $this->_splitCheck($parts, '(');
00581 if ($before_comment != $_mailbox) {
00582
00583 $comment = substr(str_replace($before_comment, '', $_mailbox), 1);
00584 $parts = explode(')', $comment);
00585 $comment = $this->_splitCheck($parts, ')');
00586 $comments[] = $comment;
00587
00588
00589 $_mailbox = substr($_mailbox, strpos($_mailbox, $comment)+strlen($comment)+1);
00590 } else {
00591 break;
00592 }
00593 }
00594
00595 for($i=0; $i<count(@$comments); $i++){
00596 $mailbox = str_replace('('.$comments[$i].')', '', $mailbox);
00597 }
00598 $mailbox = trim($mailbox);
00599
00600
00601 if (substr($mailbox, -1) == '>' && substr($mailbox, 0, 1) != '<') {
00602 $parts = explode('<', $mailbox);
00603 $name = $this->_splitCheck($parts, '<');
00604
00605 $phrase = trim($name);
00606 $route_addr = trim(substr($mailbox, strlen($name.'<'), -1));
00607
00608 if ($this->_validatePhrase($phrase) === false || ($route_addr = $this->_validateRouteAddr($route_addr)) === false)
00609 return false;
00610
00611
00612 } else {
00613
00614 if (substr($mailbox,0,1) == '<' && substr($mailbox,-1) == '>')
00615 $addr_spec = substr($mailbox,1,-1);
00616 else
00617 $addr_spec = $mailbox;
00618
00619 if (($addr_spec = $this->_validateAddrSpec($addr_spec)) === false)
00620 return false;
00621 }
00622
00623
00624 $mbox = new stdClass();
00625
00626
00627 $mbox->personal = $phrase;
00628 $mbox->comment = isset($comments) ? $comments : array();
00629
00630 if (isset($route_addr)) {
00631 $mbox->mailbox = $route_addr['local_part'];
00632 $mbox->host = $route_addr['domain'];
00633 $route_addr['adl'] !== '' ? $mbox->adl = $route_addr['adl'] : '';
00634 } else {
00635 $mbox->mailbox = $addr_spec['local_part'];
00636 $mbox->host = $addr_spec['domain'];
00637 }
00638
00639 $mailbox = $mbox;
00640 return true;
00641 }
00642
00654 function _validateRouteAddr($route_addr)
00655 {
00656
00657 if (strpos($route_addr, ':') !== false) {
00658 $parts = explode(':', $route_addr);
00659 $route = $this->_splitCheck($parts, ':');
00660 } else {
00661 $route = $route_addr;
00662 }
00663
00664
00665
00666 if ($route === $route_addr){
00667 unset($route);
00668 $addr_spec = $route_addr;
00669 if (($addr_spec = $this->_validateAddrSpec($addr_spec)) === false) {
00670 return false;
00671 }
00672 } else {
00673
00674 if (($route = $this->_validateRoute($route)) === false) {
00675 return false;
00676 }
00677
00678 $addr_spec = substr($route_addr, strlen($route . ':'));
00679
00680
00681 if (($addr_spec = $this->_validateAddrSpec($addr_spec)) === false) {
00682 return false;
00683 }
00684 }
00685
00686 if (isset($route)) {
00687 $return['adl'] = $route;
00688 } else {
00689 $return['adl'] = '';
00690 }
00691
00692 $return = array_merge($return, $addr_spec);
00693 return $return;
00694 }
00695
00704 function _validateRoute($route)
00705 {
00706
00707 $domains = explode(',', trim($route));
00708
00709 for ($i = 0; $i < count($domains); $i++) {
00710 $domains[$i] = str_replace('@', '', trim($domains[$i]));
00711 if (!$this->_validateDomain($domains[$i])) return false;
00712 }
00713
00714 return $route;
00715 }
00716
00727 function _validateDomain($domain)
00728 {
00729
00730 $subdomains = explode('.', $domain);
00731
00732 while (count($subdomains) > 0) {
00733 $sub_domains[] = $this->_splitCheck($subdomains, '.');
00734 for ($i = 0; $i < $this->index + 1; $i++)
00735 array_shift($subdomains);
00736 }
00737
00738 for ($i = 0; $i < count($sub_domains); $i++) {
00739 if (!$this->_validateSubdomain(trim($sub_domains[$i])))
00740 return false;
00741 }
00742
00743
00744 return $domain;
00745 }
00746
00755 function _validateSubdomain($subdomain)
00756 {
00757 if (preg_match('|^\[(.*)]$|', $subdomain, $arr)){
00758 if (!$this->_validateDliteral($arr[1])) return false;
00759 } else {
00760 if (!$this->_validateAtom($subdomain)) return false;
00761 }
00762
00763
00764 return true;
00765 }
00766
00775 function _validateDliteral($dliteral)
00776 {
00777 return !preg_match('/(.)[][\x0D\\\\]/', $dliteral, $matches) && $matches[1] != '\\';
00778 }
00779
00789 function _validateAddrSpec($addr_spec)
00790 {
00791 $addr_spec = trim($addr_spec);
00792
00793
00794 if (strpos($addr_spec, '@') !== false) {
00795 $parts = explode('@', $addr_spec);
00796 $local_part = $this->_splitCheck($parts, '@');
00797 $domain = substr($addr_spec, strlen($local_part . '@'));
00798
00799
00800 } else {
00801 $local_part = $addr_spec;
00802 $domain = $this->default_domain;
00803 }
00804
00805 if (($local_part = $this->_validateLocalPart($local_part)) === false) return false;
00806 if (($domain = $this->_validateDomain($domain)) === false) return false;
00807
00808
00809 return array('local_part' => $local_part, 'domain' => $domain);
00810 }
00811
00820 function _validateLocalPart($local_part)
00821 {
00822 $parts = explode('.', $local_part);
00823
00824
00825 while (count($parts) > 0){
00826 $words[] = $this->_splitCheck($parts, '.');
00827 for ($i = 0; $i < $this->index + 1; $i++) {
00828 array_shift($parts);
00829 }
00830 }
00831
00832
00833 for ($i = 0; $i < count($words); $i++) {
00834 if ($this->_validatePhrase(trim($words[$i])) === false) return false;
00835 }
00836
00837
00838 return $local_part;
00839 }
00840
00851 function approximateCount($data)
00852 {
00853 return count(preg_split('/(?<!\\\\),/', $data));
00854 }
00855
00869 function isValidInetAddress($data, $strict = false)
00870 {
00871 $regex = $strict ? '/^([.0-9a-z_-]+)@(([0-9a-z-]+\.)+[0-9a-z]{2,4})$/i' : '/^([*+!.&#$|\'\\%\/0-9a-z^_`{}=?~:-]+)@(([0-9a-z-]+\.)+[0-9a-z]{2,4})$/i';
00872 if (preg_match($regex, trim($data), $matches)) {
00873 return array($matches[1], $matches[2]);
00874 } else {
00875 return false;
00876 }
00877 }
00878 }
00879
00880 ?>