See Release Notes
Long Term Support Release
Differences Between: [Versions 310 and 401] [Versions 311 and 401] [Versions 39 and 401] [Versions 400 and 401]
1 <?php 2 /** 3 * Copyright (c) 2001-2010, Richard Heyes 4 * Copyright 2011-2017 Horde LLC (http://www.horde.org/) 5 * All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 11 * o Redistributions of source code must retain the above copyright 12 * notice, this list of conditions and the following disclaimer. 13 * o Redistributions in binary form must reproduce the above copyright 14 * notice, this list of conditions and the following disclaimer in the 15 * documentation and/or other materials provided with the distribution. 16 * o The names of the authors may not be used to endorse or promote 17 * products derived from this software without specific prior written 18 * permission. 19 * 20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 23 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 24 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 25 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 26 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 27 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 28 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 * 32 * RFC822 parsing code adapted from message-address.c and rfc822-parser.c 33 * (Dovecot 2.1rc5) 34 * Original code released under LGPL-2.1 35 * Copyright (c) 2002-2011 Timo Sirainen <tss@iki.fi> 36 * 37 * @category Horde 38 * @copyright 2001-2010 Richard Heyes 39 * @copyright 2002-2011 Timo Sirainen 40 * @copyright 2011-2017 Horde LLC 41 * @license http://www.horde.org/licenses/bsd New BSD License 42 * @package Mail 43 */ 44 45 /** 46 * RFC 822/2822/3490/5322 Email parser/validator. 47 * 48 * @author Richard Heyes <richard@phpguru.org> 49 * @author Chuck Hagenbuch <chuck@horde.org> 50 * @author Michael Slusarz <slusarz@horde.org> 51 * @author Timo Sirainen <tss@iki.fi> 52 * @category Horde 53 * @copyright 2001-2010 Richard Heyes 54 * @copyright 2002-2011 Timo Sirainen 55 * @copyright 2011-2017 Horde LLC 56 * @license http://www.horde.org/licenses/bsd New BSD License 57 * @package Mail 58 */ 59 class Horde_Mail_Rfc822 60 { 61 /** 62 * Valid atext characters. 63 * 64 * @deprecated 65 * @since 2.0.3 66 */ 67 const ATEXT = '!#$%&\'*+-./0123456789=?ABCDEFGHIJKLMNOPQRSTUVWXYZ^_`abcdefghijklmnopqrstuvwxyz{|}~'; 68 69 /** 70 * Excluded (in ASCII decimal): 0-8, 10-31, 34, 40-41, 44, 58-60, 62, 64, 71 * 91-93, 127 72 * 73 * @since 2.0.3 74 */ 75 const ENCODE_FILTER = "\0\1\2\3\4\5\6\7\10\12\13\14\15\16\17\20\21\22\23\24\25\26\27\30\31\32\33\34\35\36\37\"(),:;<>@[\\]\177"; 76 77 /** 78 * The address string to parse. 79 * 80 * @var string 81 */ 82 protected $_data; 83 84 /** 85 * Length of the address string. 86 * 87 * @var integer 88 */ 89 protected $_datalen; 90 91 /** 92 * Comment cache. 93 * 94 * @var string 95 */ 96 protected $_comments = array(); 97 98 /** 99 * List object to return in parseAddressList(). 100 * 101 * @var Horde_Mail_Rfc822_List 102 */ 103 protected $_listob; 104 105 /** 106 * Configuration parameters. 107 * 108 * @var array 109 */ 110 protected $_params = array(); 111 112 /** 113 * Data pointer. 114 * 115 * @var integer 116 */ 117 protected $_ptr; 118 119 /** 120 * Starts the whole process. 121 * 122 * @param mixed $address The address(es) to validate. Either a string, 123 * a Horde_Mail_Rfc822_Object, or an array of 124 * strings and/or Horde_Mail_Rfc822_Objects. 125 * @param array $params Optional parameters: 126 * - default_domain: (string) Default domain/host. 127 * DEFAULT: None 128 * - group: (boolean) Return a GroupList object instead of a List object? 129 * DEFAULT: false 130 * - limit: (integer) Stop processing after this many addresses. 131 * DEFAULT: No limit (0) 132 * - validate: (mixed) Strict validation of personal part data? If 133 * false, attempts to allow non-ASCII characters and 134 * non-quoted strings in the personal data, and will 135 * silently abort if an unparseable address is found. 136 * If true, does strict RFC 5322 (ASCII-only) parsing. If 137 * 'eai' (@since 2.5.0), allows RFC 6532 (EAI/UTF-8) 138 * addresses. 139 * DEFAULT: false 140 * 141 * @return Horde_Mail_Rfc822_List A list object. 142 * 143 * @throws Horde_Mail_Exception 144 */ 145 public function parseAddressList($address, array $params = array()) 146 { 147 if ($address instanceof Horde_Mail_Rfc822_List) { 148 return $address; 149 } 150 151 if (empty($params['limit'])) { 152 $params['limit'] = -1; 153 } 154 155 $this->_params = array_merge(array( 156 'default_domain' => null, 157 'validate' => false 158 ), $params); 159 160 $this->_listob = empty($this->_params['group']) 161 ? new Horde_Mail_Rfc822_List() 162 : new Horde_Mail_Rfc822_GroupList(); 163 164 if (!is_array($address)) { 165 $address = array($address); 166 } 167 168 $tmp = array(); 169 foreach ($address as $val) { 170 if ($val instanceof Horde_Mail_Rfc822_Object) { 171 $this->_listob->add($val); 172 } else { 173 $tmp[] = rtrim(trim($val), ','); 174 } 175 } 176 177 if (!empty($tmp)) { 178 $this->_data = implode(',', $tmp); 179 $this->_datalen = strlen($this->_data); 180 $this->_ptr = 0; 181 182 $this->_parseAddressList(); 183 } 184 185 $ret = $this->_listob; 186 unset($this->_listob); 187 188 return $ret; 189 } 190 191 /** 192 * Quotes and escapes the given string if necessary using rules contained 193 * in RFC 2822 [3.2.5]. 194 * 195 * @param string $str The string to be quoted and escaped. 196 * @param string $type Either 'address', 'comment' (@since 2.6.0), or 197 * 'personal'. 198 * 199 * @return string The correctly quoted and escaped string. 200 */ 201 public function encode($str, $type = 'address') 202 { 203 switch ($type) { 204 case 'comment': 205 // RFC 5322 [3.2.2]: Filter out non-printable US-ASCII and ( ) \ 206 $filter = "\0\1\2\3\4\5\6\7\10\12\13\14\15\16\17\20\21\22\23\24\25\26\27\30\31\32\33\34\35\36\37\50\51\134\177"; 207 break; 208 209 case 'personal': 210 // RFC 2822 [3.4]: Period not allowed in display name 211 $filter = self::ENCODE_FILTER . '.'; 212 break; 213 214 case 'address': 215 default: 216 // RFC 2822 [3.4.1]: (HTAB, SPACE) not allowed in address 217 $filter = self::ENCODE_FILTER . "\11\40"; 218 break; 219 } 220 221 // Strip double quotes if they are around the string already. 222 // If quoted, we know that the contents are already escaped, so 223 // unescape now. 224 $str = trim($str); 225 if ($str && ($str[0] === '"') && (substr($str, -1) === '"')) { 226 $str = stripslashes(substr($str, 1, -1)); 227 } 228 229 return (strcspn($str, $filter) != strlen($str)) 230 ? '"' . addcslashes($str, '\\"') . '"' 231 : $str; 232 } 233 234 /** 235 * If an email address has no personal information, get rid of any angle 236 * brackets (<>) around it. 237 * 238 * @param string $address The address to trim. 239 * 240 * @return string The trimmed address. 241 */ 242 public function trimAddress($address) 243 { 244 $address = trim($address); 245 246 return (($address[0] == '<') && (substr($address, -1) == '>')) 247 ? substr($address, 1, -1) 248 : $address; 249 } 250 251 /* RFC 822 parsing methods. */ 252 253 /** 254 * address-list = (address *("," address)) / obs-addr-list 255 */ 256 protected function _parseAddressList() 257 { 258 $limit = $this->_params['limit']; 259 260 while (($this->_curr() !== false) && ($limit-- !== 0)) { 261 try { 262 $this->_parseAddress(); 263 } catch (Horde_Mail_Exception $e) { 264 if ($this->_params['validate']) { 265 throw $e; 266 } 267 ++$this->_ptr; 268 } 269 270 switch ($this->_curr()) { 271 case ',': 272 $this->_rfc822SkipLwsp(true); 273 break; 274 275 case false: 276 // No-op 277 break; 278 279 default: 280 if ($this->_params['validate']) { 281 throw new Horde_Mail_Exception('Error when parsing address list.'); 282 } 283 break; 284 } 285 } 286 } 287 288 /** 289 * address = mailbox / group 290 */ 291 protected function _parseAddress() 292 { 293 $start = $this->_ptr; 294 if (!$this->_parseGroup()) { 295 $this->_ptr = $start; 296 if ($mbox = $this->_parseMailbox()) { 297 $this->_listob->add($mbox); 298 } 299 } 300 } 301 302 /** 303 * group = display-name ":" [mailbox-list / CFWS] ";" [CFWS] 304 * display-name = phrase 305 * 306 * @return boolean True if a group was parsed. 307 * 308 * @throws Horde_Mail_Exception 309 */ 310 protected function _parseGroup() 311 { 312 $this->_rfc822ParsePhrase($groupname); 313 314 if ($this->_curr(true) != ':') { 315 return false; 316 } 317 318 $addresses = new Horde_Mail_Rfc822_GroupList(); 319 320 $this->_rfc822SkipLwsp(); 321 322 while (($chr = $this->_curr()) !== false) { 323 if ($chr == ';') { 324 ++$this->_ptr; 325 326 if (count($addresses)) { 327 $this->_listob->add(new Horde_Mail_Rfc822_Group($groupname, $addresses)); 328 } 329 330 return true; 331 } 332 333 /* mailbox-list = (mailbox *("," mailbox)) / obs-mbox-list */ 334 $addresses->add($this->_parseMailbox()); 335 336 switch ($this->_curr()) { 337 case ',': 338 $this->_rfc822SkipLwsp(true); 339 break; 340 341 case ';': 342 // No-op 343 break; 344 345 default: 346 break 2; 347 } 348 } 349 350 throw new Horde_Mail_Exception('Error when parsing group.'); 351 } 352 353 /** 354 * mailbox = name-addr / addr-spec 355 * 356 * @return mixed Mailbox object if mailbox was parsed, or false. 357 */ 358 protected function _parseMailbox() 359 { 360 $this->_comments = array(); 361 $start = $this->_ptr; 362 363 if (!($ob = $this->_parseNameAddr())) { 364 $this->_comments = array(); 365 $this->_ptr = $start; 366 $ob = $this->_parseAddrSpec(); 367 } 368 369 if ($ob) { 370 $ob->comment = $this->_comments; 371 } 372 373 return $ob; 374 } 375 376 /** 377 * name-addr = [display-name] angle-addr 378 * display-name = phrase 379 * 380 * @return mixed Mailbox object, or false. 381 */ 382 protected function _parseNameAddr() 383 { 384 $this->_rfc822ParsePhrase($personal); 385 386 if ($ob = $this->_parseAngleAddr()) { 387 $ob->personal = $personal; 388 return $ob; 389 } 390 391 return false; 392 } 393 394 /** 395 * addr-spec = local-part "@" domain 396 * 397 * @return mixed Mailbox object. 398 * 399 * @throws Horde_Mail_Exception 400 */ 401 protected function _parseAddrSpec() 402 { 403 $ob = new Horde_Mail_Rfc822_Address(); 404 $ob->mailbox = $this->_parseLocalPart(); 405 406 if ($this->_curr() == '@') { 407 try { 408 $this->_rfc822ParseDomain($host); 409 if (!empty($host)) { 410 $ob->host = $host; 411 } 412 } catch (Horde_Mail_Exception $e) { 413 if (!empty($this->_params['validate'])) { 414 throw $e; 415 } 416 } 417 } 418 419 if (is_null($ob->host)) { 420 if (!is_null($this->_params['default_domain'])) { 421 $ob->host = $this->_params['default_domain']; 422 } elseif (!empty($this->_params['validate'])) { 423 throw new Horde_Mail_Exception('Address is missing domain.'); 424 } 425 } 426 427 return $ob; 428 } 429 430 /** 431 * local-part = dot-atom / quoted-string / obs-local-part 432 * obs-local-part = word *("." word) 433 * 434 * @return string The local part. 435 * 436 * @throws Horde_Mail_Exception 437 */ 438 protected function _parseLocalPart() 439 { 440 if (($curr = $this->_curr()) === false) { 441 throw new Horde_Mail_Exception('Error when parsing local part.'); 442 } 443 444 if ($curr == '"') { 445 $this->_rfc822ParseQuotedString($str); 446 } else { 447 $this->_rfc822ParseDotAtom($str, ',;@'); 448 } 449 450 return $str; 451 } 452 453 /** 454 * "<" [ "@" route ":" ] local-part "@" domain ">" 455 * 456 * @return mixed Mailbox object, or false. 457 * 458 * @throws Horde_Mail_Exception 459 */ 460 protected function _parseAngleAddr() 461 { 462 if ($this->_curr() != '<') { 463 return false; 464 } 465 466 $this->_rfc822SkipLwsp(true); 467 468 if ($this->_curr() == '@') { 469 // Route information is ignored. 470 $this->_parseDomainList(); 471 if ($this->_curr() != ':') { 472 throw new Horde_Mail_Exception('Invalid route.'); 473 } 474 475 $this->_rfc822SkipLwsp(true); 476 } 477 478 $ob = $this->_parseAddrSpec(); 479 480 if ($this->_curr() != '>') { 481 throw new Horde_Mail_Exception('Error when parsing angle address.'); 482 } 483 484 $this->_rfc822SkipLwsp(true); 485 486 return $ob; 487 } 488 489 /** 490 * obs-domain-list = "@" domain *(*(CFWS / "," ) [CFWS] "@" domain) 491 * 492 * @return array Routes. 493 * 494 * @throws Horde_Mail_Exception 495 */ 496 protected function _parseDomainList() 497 { 498 $route = array(); 499 500 while ($this->_curr() !== false) { 501 $this->_rfc822ParseDomain($str); 502 $route[] = '@' . $str; 503 504 $this->_rfc822SkipLwsp(); 505 if ($this->_curr() != ',') { 506 return $route; 507 } 508 ++$this->_ptr; 509 } 510 511 throw new Horde_Mail_Exception('Invalid domain list.'); 512 } 513 514 /* RFC 822 parsing methods. */ 515 516 /** 517 * phrase = 1*word / obs-phrase 518 * word = atom / quoted-string 519 * obs-phrase = word *(word / "." / CFWS) 520 * 521 * @param string &$phrase The phrase data. 522 * 523 * @throws Horde_Mail_Exception 524 */ 525 protected function _rfc822ParsePhrase(&$phrase) 526 { 527 $curr = $this->_curr(); 528 if (($curr === false) || ($curr == '.')) { 529 throw new Horde_Mail_Exception('Error when parsing a group.'); 530 } 531 532 do { 533 if ($curr == '"') { 534 $this->_rfc822ParseQuotedString($phrase); 535 } else { 536 $this->_rfc822ParseAtomOrDot($phrase); 537 } 538 539 $curr = $this->_curr(); 540 if (($curr != '"') && 541 ($curr != '.') && 542 !$this->_rfc822IsAtext($curr)) { 543 break; 544 } 545 546 $phrase .= ' '; 547 } while ($this->_ptr < $this->_datalen); 548 549 $this->_rfc822SkipLwsp(); 550 } 551 552 /** 553 * @param string &$phrase The quoted string data. 554 * 555 * @throws Horde_Mail_Exception 556 */ 557 protected function _rfc822ParseQuotedString(&$str) 558 { 559 if ($this->_curr(true) != '"') { 560 throw new Horde_Mail_Exception('Error when parsing a quoted string.'); 561 } 562 563 while (($chr = $this->_curr(true)) !== false) { 564 switch ($chr) { 565 case '"': 566 $this->_rfc822SkipLwsp(); 567 return; 568 569 case "\n": 570 /* Folding whitespace, remove the (CR)LF. */ 571 if (substr($str, -1) == "\r") { 572 $str = substr($str, 0, -1); 573 } 574 continue 2; 575 576 case '\\': 577 if (($chr = $this->_curr(true)) === false) { 578 break 2; 579 } 580 break; 581 } 582 583 $str .= $chr; 584 } 585 586 /* Missing trailing '"', or partial quoted character. */ 587 throw new Horde_Mail_Exception('Error when parsing a quoted string.'); 588 } 589 590 /** 591 * dot-atom = [CFWS] dot-atom-text [CFWS] 592 * dot-atom-text = 1*atext *("." 1*atext) 593 * 594 * atext = ; Any character except controls, SP, and specials. 595 * 596 * For RFC-822 compatibility allow LWSP around '.'. 597 * 598 * 599 * @param string &$str The atom/dot data. 600 * @param string $validate Use these characters as delimiter. 601 * 602 * @throws Horde_Mail_Exception 603 */ 604 protected function _rfc822ParseDotAtom(&$str, $validate = null) 605 { 606 $valid = false; 607 608 while ($this->_ptr < $this->_datalen) { 609 $chr = $this->_data[$this->_ptr]; 610 611 /* TODO: Optimize by duplicating rfc822IsAtext code here */ 612 if ($this->_rfc822IsAtext($chr, $validate)) { 613 $str .= $chr; 614 ++$this->_ptr; 615 } elseif (!$valid) { 616 throw new Horde_Mail_Exception('Error when parsing dot-atom.'); 617 } else { 618 $this->_rfc822SkipLwsp(); 619 620 if ($this->_curr() != '.') { 621 return; 622 } 623 $str .= $chr; 624 625 $this->_rfc822SkipLwsp(true); 626 } 627 628 $valid = true; 629 } 630 } 631 632 /** 633 * atom = [CFWS] 1*atext [CFWS] 634 * atext = ; Any character except controls, SP, and specials. 635 * 636 * This method doesn't just silently skip over WS. 637 * 638 * @param string &$str The atom/dot data. 639 * 640 * @throws Horde_Mail_Exception 641 */ 642 protected function _rfc822ParseAtomOrDot(&$str) 643 { 644 while ($this->_ptr < $this->_datalen) { 645 $chr = $this->_data[$this->_ptr]; 646 if (($chr != '.') && 647 /* TODO: Optimize by duplicating rfc822IsAtext code here */ 648 !$this->_rfc822IsAtext($chr, ',<:')) { 649 $this->_rfc822SkipLwsp(); 650 if (!$this->_params['validate'] && $str !== null) { 651 $str = trim($str); 652 } 653 return; 654 } 655 656 $str .= $chr; 657 ++$this->_ptr; 658 } 659 } 660 661 /** 662 * domain = dot-atom / domain-literal / obs-domain 663 * domain-literal = [CFWS] "[" *([FWS] dcontent) [FWS] "]" [CFWS] 664 * obs-domain = atom *("." atom) 665 * 666 * @param string &$str The domain string. 667 * 668 * @throws Horde_Mail_Exception 669 */ 670 protected function _rfc822ParseDomain(&$str) 671 { 672 if ($this->_curr(true) != '@') { 673 throw new Horde_Mail_Exception('Error when parsing domain.'); 674 } 675 676 $this->_rfc822SkipLwsp(); 677 678 if ($this->_curr() == '[') { 679 $this->_rfc822ParseDomainLiteral($str); 680 } else { 681 $this->_rfc822ParseDotAtom($str, ';,> '); 682 } 683 } 684 685 /** 686 * domain-literal = [CFWS] "[" *([FWS] dcontent) [FWS] "]" [CFWS] 687 * dcontent = dtext / quoted-pair 688 * dtext = NO-WS-CTL / ; Non white space controls 689 * %d33-90 / ; The rest of the US-ASCII 690 * %d94-126 ; characters not including "[", 691 * ; "]", or "\" 692 * 693 * @param string &$str The domain string. 694 * 695 * @throws Horde_Mail_Exception 696 */ 697 protected function _rfc822ParseDomainLiteral(&$str) 698 { 699 if ($this->_curr(true) != '[') { 700 throw new Horde_Mail_Exception('Error parsing domain literal.'); 701 } 702 703 while (($chr = $this->_curr(true)) !== false) { 704 switch ($chr) { 705 case '\\': 706 if (($chr = $this->_curr(true)) === false) { 707 break 2; 708 } 709 break; 710 711 case ']': 712 $this->_rfc822SkipLwsp(); 713 return; 714 } 715 716 $str .= $chr; 717 } 718 719 throw new Horde_Mail_Exception('Error parsing domain literal.'); 720 } 721 722 /** 723 * @param boolean $advance Advance cursor? 724 * 725 * @throws Horde_Mail_Exception 726 */ 727 protected function _rfc822SkipLwsp($advance = false) 728 { 729 if ($advance) { 730 ++$this->_ptr; 731 } 732 733 while (($chr = $this->_curr()) !== false) { 734 switch ($chr) { 735 case ' ': 736 case "\n": 737 case "\r": 738 case "\t": 739 ++$this->_ptr; 740 continue 2; 741 742 case '(': 743 $this->_rfc822SkipComment(); 744 break; 745 746 default: 747 return; 748 } 749 } 750 } 751 752 /** 753 * @throws Horde_Mail_Exception 754 */ 755 protected function _rfc822SkipComment() 756 { 757 if ($this->_curr(true) != '(') { 758 throw new Horde_Mail_Exception('Error when parsing a comment.'); 759 } 760 761 $comment = ''; 762 $level = 1; 763 764 while (($chr = $this->_curr(true)) !== false) { 765 switch ($chr) { 766 case '(': 767 ++$level; 768 continue 2; 769 770 case ')': 771 if (--$level == 0) { 772 $this->_comments[] = $comment; 773 return; 774 } 775 break; 776 777 case '\\': 778 if (($chr = $this->_curr(true)) === false) { 779 break 2; 780 } 781 break; 782 } 783 784 $comment .= $chr; 785 } 786 787 throw new Horde_Mail_Exception('Error when parsing a comment.'); 788 } 789 790 /** 791 * Check if data is an atom. 792 * 793 * @param string $chr The character to check. 794 * @param string $validate If in non-validate mode, use these characters 795 * as the non-atom delimiters. 796 * 797 * @return boolean True if a valid atom. 798 */ 799 protected function _rfc822IsAtext($chr, $validate = null) 800 { 801 if (!$this->_params['validate'] && !is_null($validate)) { 802 return strcspn($chr, $validate); 803 } 804 805 $ord = ord($chr); 806 807 /* UTF-8 characters check. */ 808 if ($ord > 127) { 809 return ($this->_params['validate'] === 'eai'); 810 } 811 812 /* Check for DISALLOWED characters under both RFCs 5322 and 6532. */ 813 814 /* Unprintable characters && [SPACE] */ 815 if ($ord <= 32) { 816 return false; 817 } 818 819 /* "(),:;<>@[\] [DEL] */ 820 switch ($ord) { 821 case 34: 822 case 40: 823 case 41: 824 case 44: 825 case 58: 826 case 59: 827 case 60: 828 case 62: 829 case 64: 830 case 91: 831 case 92: 832 case 93: 833 case 127: 834 return false; 835 } 836 837 return true; 838 } 839 840 /* Helper methods. */ 841 842 /** 843 * Return current character. 844 * 845 * @param boolean $advance If true, advance the cursor. 846 * 847 * @return string The current character (false if EOF reached). 848 */ 849 protected function _curr($advance = false) 850 { 851 return ($this->_ptr >= $this->_datalen) 852 ? false 853 : $this->_data[$advance ? $this->_ptr++ : $this->_ptr]; 854 } 855 856 /* Other public methods. */ 857 858 /** 859 * Returns an approximate count of how many addresses are in the string. 860 * This is APPROXIMATE as it only splits based on a comma which has no 861 * preceding backslash. 862 * 863 * @param string $data Addresses to count. 864 * 865 * @return integer Approximate count. 866 */ 867 public function approximateCount($data) 868 { 869 return count(preg_split('/(?<!\\\\),/', $data)); 870 } 871 872 /** 873 * Validates whether an email is of the common internet form: 874 * <user>@<domain>. This can be sufficient for most people. 875 * 876 * Optional stricter mode can be utilized which restricts mailbox 877 * characters allowed to: alphanumeric, full stop, hyphen, and underscore. 878 * 879 * @param string $data Address to check. 880 * @param boolean $strict Strict check? 881 * 882 * @return mixed False if it fails, an indexed array username/domain if 883 * it matches. 884 */ 885 public function isValidInetAddress($data, $strict = false) 886 { 887 $regex = $strict 888 ? '/^([.0-9a-z_+-]+)@(([0-9a-z-]+\.)+[0-9a-z]{2,})$/i' 889 : '/^([*+!.&#$|\'\\%\/0-9a-z^_`{}=?~:-]+)@(([0-9a-z-]+\.)+[0-9a-z]{2,})$/i'; 890 891 return preg_match($regex, trim($data), $matches) 892 ? array($matches[1], $matches[2]) 893 : false; 894 } 895 896 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body