[ Index ]

PHP Cross Reference of Unnamed Project

title

Body

[close]

/lib/horde/framework/Horde/Mail/ -> Rfc822.php (source)

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


Generated: Thu Aug 11 10:00:09 2016 Cross-referenced by PHPXref 0.7.1