PageRenderTime 52ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 0ms

/photoalbum/class_mail/RFC822.php

https://bitbucket.org/ladasoukup/sb-photoalbum
PHP | 872 lines | 542 code | 80 blank | 250 comment | 80 complexity | 44e95c249b085f2f3153f654952609cb MD5 | raw file
  1. <?php
  2. /**
  3. * RFC 822 Email address list validation Utility
  4. *
  5. * What is it?
  6. *
  7. * This class will take an address string, and parse it into it's consituent
  8. * parts, be that either addresses, groups, or combinations. Nested groups
  9. * are not supported. The structure it returns is pretty straight forward,
  10. * and is similar to that provided by the imap_rfc822_parse_adrlist(). Use
  11. * print_r() to view the structure.
  12. *
  13. * How do I use it?
  14. *
  15. * $address_string = 'My Group: "Richard Heyes" <richard@localhost> (A comment), ted@example.com (Ted Bloggs), Barney;';
  16. * $structure = Mail_RFC822::parseAddressList($address_string, 'example.com', TRUE)
  17. * print_r($structure);
  18. *
  19. * @author Richard Heyes <richard@phpguru.org>
  20. * @author Chuck Hagenbuch <chuck@horde.org>
  21. * @version $Revision: 1.1 $
  22. * @package Mail
  23. */
  24. class Mail_RFC822
  25. {
  26. /**
  27. * The address being parsed by the RFC822 object.
  28. * @private string $address
  29. */
  30. private $address = '';
  31. /**
  32. * The default domain to use for unqualified addresses.
  33. * @private string $default_domain
  34. */
  35. private $default_domain = 'localhost';
  36. /**
  37. * Should we return a nested array showing groups, or flatten everything?
  38. * @private boolean $nestGroups
  39. */
  40. private $nestGroups = true;
  41. /**
  42. * Whether or not to validate atoms for non-ascii characters.
  43. * @private boolean $validate
  44. */
  45. private $validate = true;
  46. /**
  47. * The array of raw addresses built up as we parse.
  48. * @private array $addresses
  49. */
  50. private $addresses = array();
  51. /**
  52. * The final array of parsed address information that we build up.
  53. * @private array $structure
  54. */
  55. private $structure = array();
  56. /**
  57. * The current error message, if any.
  58. * @private string $error
  59. */
  60. private $error = null;
  61. /**
  62. * An internal counter/pointer.
  63. * @private integer $index
  64. */
  65. private $index = null;
  66. /**
  67. * The number of groups that have been found in the address list.
  68. * @private integer $num_groups
  69. * @access public
  70. */
  71. private $num_groups = 0;
  72. /**
  73. * A variable so that we can tell whether or not we're inside a
  74. * Mail_RFC822 object.
  75. * @private boolean $mailRFC822
  76. */
  77. private $mailRFC822 = true;
  78. /**
  79. * A limit after which processing stops
  80. * @private int $limit
  81. */
  82. private $limit = null;
  83. /**
  84. * Sets up the object. The address must either be set here or when
  85. * calling parseAddressList(). One or the other.
  86. *
  87. * @access public
  88. * @param string $address The address(es) to validate.
  89. * @param string $default_domain Default domain/host etc. If not supplied, will be set to localhost.
  90. * @param boolean $nest_groups Whether to return the structure with groups nested for easier viewing.
  91. * @param boolean $validate Whether to validate atoms. Turn this off if you need to run addresses through before encoding the personal names, for instance.
  92. *
  93. * @return object Mail_RFC822 A new Mail_RFC822 object.
  94. */
  95. function __construct($address = null, $default_domain = null, $nest_groups = null, $validate = null, $limit = null)
  96. {
  97. if (isset($address)) $this->address = $address;
  98. if (isset($default_domain)) $this->default_domain = $default_domain;
  99. if (isset($nest_groups)) $this->nestGroups = $nest_groups;
  100. if (isset($validate)) $this->validate = $validate;
  101. if (isset($limit)) $this->limit = $limit;
  102. }
  103. /**
  104. * Starts the whole process. The address must either be set here
  105. * or when creating the object. One or the other.
  106. *
  107. * @access public
  108. * @param string $address The address(es) to validate.
  109. * @param string $default_domain Default domain/host etc.
  110. * @param boolean $nest_groups Whether to return the structure with groups nested for easier viewing.
  111. * @param boolean $validate Whether to validate atoms. Turn this off if you need to run addresses through before encoding the personal names, for instance.
  112. *
  113. * @return array A structured array of addresses.
  114. */
  115. function parseAddressList($address = null, $default_domain = null, $nest_groups = null, $validate = null, $limit = null)
  116. {
  117. if (!isset($this->mailRFC822)) {
  118. $obj = new Mail_RFC822($address, $default_domain, $nest_groups, $validate, $limit);
  119. return $obj->parseAddressList();
  120. }
  121. if (isset($address)) $this->address = $address;
  122. if (isset($default_domain)) $this->default_domain = $default_domain;
  123. if (isset($nest_groups)) $this->nestGroups = $nest_groups;
  124. if (isset($validate)) $this->validate = $validate;
  125. if (isset($limit)) $this->limit = $limit;
  126. $this->structure = array();
  127. $this->addresses = array();
  128. $this->error = null;
  129. $this->index = null;
  130. while ($this->address = $this->_splitAddresses($this->address)) {
  131. continue;
  132. }
  133. if ($this->address === false || isset($this->error)) {
  134. return false;
  135. }
  136. // Reset timer since large amounts of addresses can take a long time to
  137. // get here
  138. set_time_limit(30);
  139. // Loop through all the addresses
  140. for ($i = 0; $i < count($this->addresses); $i++){
  141. if (($return = $this->_validateAddress($this->addresses[$i])) === false
  142. || isset($this->error)) {
  143. return false;
  144. }
  145. if (!$this->nestGroups) {
  146. $this->structure = array_merge($this->structure, $return);
  147. } else {
  148. $this->structure[] = $return;
  149. }
  150. }
  151. return $this->structure;
  152. }
  153. /**
  154. * Splits an address into seperate addresses.
  155. *
  156. * @access private
  157. * @param string $address The addresses to split.
  158. * @return boolean Success or failure.
  159. */
  160. function _splitAddresses($address)
  161. {
  162. if (!empty($this->limit) AND count($this->addresses) == $this->limit) {
  163. return '';
  164. }
  165. if ($this->_isGroup($address) && !isset($this->error)) {
  166. $split_char = ';';
  167. $is_group = true;
  168. } elseif (!isset($this->error)) {
  169. $split_char = ',';
  170. $is_group = false;
  171. } elseif (isset($this->error)) {
  172. return false;
  173. }
  174. // Split the string based on the above ten or so lines.
  175. $parts = explode($split_char, $address);
  176. $string = $this->_splitCheck($parts, $split_char);
  177. // If a group...
  178. if ($is_group) {
  179. // If $string does not contain a colon outside of
  180. // brackets/quotes etc then something's fubar.
  181. // First check there's a colon at all:
  182. if (strpos($string, ':') === false) {
  183. $this->error = 'Invalid address: ' . $string;
  184. return false;
  185. }
  186. // Now check it's outside of brackets/quotes:
  187. if (!$this->_splitCheck(explode(':', $string), ':'))
  188. return false;
  189. // We must have a group at this point, so increase the counter:
  190. $this->num_groups++;
  191. }
  192. // $string now contains the first full address/group.
  193. // Add to the addresses array.
  194. $this->addresses[] = array(
  195. 'address' => trim($string),
  196. 'group' => $is_group
  197. );
  198. // Remove the now stored address from the initial line, the +1
  199. // is to account for the explode character.
  200. $address = trim(substr($address, strlen($string) + 1));
  201. // If the next char is a comma and this was a group, then
  202. // there are more addresses, otherwise, if there are any more
  203. // chars, then there is another address.
  204. if ($is_group && substr($address, 0, 1) == ','){
  205. $address = trim(substr($address, 1));
  206. return $address;
  207. } elseif (strlen($address) > 0) {
  208. return $address;
  209. } else {
  210. return '';
  211. }
  212. // If you got here then something's off
  213. return false;
  214. }
  215. /**
  216. * Checks for a group at the start of the string.
  217. *
  218. * @access private
  219. * @param string $address The address to check.
  220. * @return boolean Whether or not there is a group at the start of the string.
  221. */
  222. function _isGroup($address)
  223. {
  224. // First comma not in quotes, angles or escaped:
  225. $parts = explode(',', $address);
  226. $string = $this->_splitCheck($parts, ',');
  227. // Now we have the first address, we can reliably check for a
  228. // group by searching for a colon that's not escaped or in
  229. // quotes or angle brackets.
  230. if (count($parts = explode(':', $string)) > 1) {
  231. $string2 = $this->_splitCheck($parts, ':');
  232. return ($string2 !== $string);
  233. } else {
  234. return false;
  235. }
  236. }
  237. /**
  238. * A common function that will check an exploded string.
  239. *
  240. * @access private
  241. * @param array $parts The exloded string.
  242. * @param string $char The char that was exploded on.
  243. * @return mixed False if the string contains unclosed quotes/brackets, or the string on success.
  244. */
  245. function _splitCheck($parts, $char)
  246. {
  247. $string = $parts[0];
  248. for ($i = 0; $i < count($parts); $i++) {
  249. if ($this->_hasUnclosedQuotes($string)
  250. || $this->_hasUnclosedBrackets($string, '<>')
  251. || $this->_hasUnclosedBrackets($string, '[]')
  252. || $this->_hasUnclosedBrackets($string, '()')
  253. || substr($string, -1) == '\\') {
  254. if (isset($parts[$i + 1])) {
  255. $string = $string . $char . $parts[$i + 1];
  256. } else {
  257. $this->error = 'Invalid address spec. Unclosed bracket or quotes';
  258. return false;
  259. }
  260. } else {
  261. $this->index = $i;
  262. break;
  263. }
  264. }
  265. return $string;
  266. }
  267. /**
  268. * Checks if a string has an unclosed quotes or not.
  269. *
  270. * @access private
  271. * @param string $string The string to check.
  272. * @return boolean True if there are unclosed quotes inside the string, false otherwise.
  273. */
  274. function _hasUnclosedQuotes($string)
  275. {
  276. $string = explode('"', $string);
  277. $string_cnt = count($string);
  278. for ($i = 0; $i < (count($string) - 1); $i++)
  279. if (substr($string[$i], -1) == '\\')
  280. $string_cnt--;
  281. return ($string_cnt % 2 === 0);
  282. }
  283. /**
  284. * Checks if a string has an unclosed brackets or not. IMPORTANT:
  285. * This function handles both angle brackets and square brackets;
  286. *
  287. * @access private
  288. * @param string $string The string to check.
  289. * @param string $chars The characters to check for.
  290. * @return boolean True if there are unclosed brackets inside the string, false otherwise.
  291. */
  292. function _hasUnclosedBrackets($string, $chars)
  293. {
  294. $num_angle_start = substr_count($string, $chars[0]);
  295. $num_angle_end = substr_count($string, $chars[1]);
  296. $this->_hasUnclosedBracketsSub($string, $num_angle_start, $chars[0]);
  297. $this->_hasUnclosedBracketsSub($string, $num_angle_end, $chars[1]);
  298. if ($num_angle_start < $num_angle_end) {
  299. $this->error = 'Invalid address spec. Unmatched quote or bracket (' . $chars . ')';
  300. return false;
  301. } else {
  302. return ($num_angle_start > $num_angle_end);
  303. }
  304. }
  305. /**
  306. * Sub function that is used only by hasUnclosedBrackets().
  307. *
  308. * @access private
  309. * @param string $string The string to check.
  310. * @param integer &$num The number of occurences.
  311. * @param string $char The character to count.
  312. * @return integer The number of occurences of $char in $string, adjusted for backslashes.
  313. */
  314. function _hasUnclosedBracketsSub($string, &$num, $char)
  315. {
  316. $parts = explode($char, $string);
  317. for ($i = 0; $i < count($parts); $i++){
  318. if (substr($parts[$i], -1) == '\\' || $this->_hasUnclosedQuotes($parts[$i]))
  319. $num--;
  320. if (isset($parts[$i + 1]))
  321. $parts[$i + 1] = $parts[$i] . $char . $parts[$i + 1];
  322. }
  323. return $num;
  324. }
  325. /**
  326. * Function to begin checking the address.
  327. *
  328. * @access private
  329. * @param string $address The address to validate.
  330. * @return mixed False on failure, or a structured array of address information on success.
  331. */
  332. function _validateAddress($address)
  333. {
  334. $is_group = false;
  335. if ($address['group']) {
  336. $is_group = true;
  337. // Get the group part of the name
  338. $parts = explode(':', $address['address']);
  339. $groupname = $this->_splitCheck($parts, ':');
  340. $structure = array();
  341. // And validate the group part of the name.
  342. if (!$this->_validatePhrase($groupname)){
  343. $this->error = 'Group name did not validate.';
  344. return false;
  345. } else {
  346. // Don't include groups if we are not nesting
  347. // them. This avoids returning invalid addresses.
  348. if ($this->nestGroups) {
  349. $structure = new stdClass;
  350. $structure->groupname = $groupname;
  351. }
  352. }
  353. $address['address'] = ltrim(substr($address['address'], strlen($groupname . ':')));
  354. }
  355. // If a group then split on comma and put into an array.
  356. // Otherwise, Just put the whole address in an array.
  357. if ($is_group) {
  358. while (strlen($address['address']) > 0) {
  359. $parts = explode(',', $address['address']);
  360. $addresses[] = $this->_splitCheck($parts, ',');
  361. $address['address'] = trim(substr($address['address'], strlen(end($addresses) . ',')));
  362. }
  363. } else {
  364. $addresses[] = $address['address'];
  365. }
  366. // Check that $addresses is set, if address like this:
  367. // Groupname:;
  368. // Then errors were appearing.
  369. if (!isset($addresses)){
  370. $this->error = 'Empty group.';
  371. return false;
  372. }
  373. for ($i = 0; $i < count($addresses); $i++) {
  374. $addresses[$i] = trim($addresses[$i]);
  375. }
  376. // Validate each mailbox.
  377. // Format could be one of: name <geezer@domain.com>
  378. // geezer@domain.com
  379. // geezer
  380. // ... or any other format valid by RFC 822.
  381. array_walk($addresses, array($this, 'validateMailbox'));
  382. // Nested format
  383. if ($this->nestGroups) {
  384. if ($is_group) {
  385. $structure->addresses = $addresses;
  386. } else {
  387. $structure = $addresses[0];
  388. }
  389. // Flat format
  390. } else {
  391. if ($is_group) {
  392. $structure = array_merge($structure, $addresses);
  393. } else {
  394. $structure = $addresses;
  395. }
  396. }
  397. return $structure;
  398. }
  399. /**
  400. * Function to validate a phrase.
  401. *
  402. * @access private
  403. * @param string $phrase The phrase to check.
  404. * @return boolean Success or failure.
  405. */
  406. function _validatePhrase($phrase)
  407. {
  408. // Splits on one or more Tab or space.
  409. $parts = preg_split('/[ \\x09]+/', $phrase, -1, PREG_SPLIT_NO_EMPTY);
  410. $phrase_parts = array();
  411. while (count($parts) > 0){
  412. $phrase_parts[] = $this->_splitCheck($parts, ' ');
  413. for ($i = 0; $i < $this->index + 1; $i++)
  414. array_shift($parts);
  415. }
  416. for ($i = 0; $i < count($phrase_parts); $i++) {
  417. // If quoted string:
  418. if (substr($phrase_parts[$i], 0, 1) == '"') {
  419. if (!$this->_validateQuotedString($phrase_parts[$i]))
  420. return false;
  421. continue;
  422. }
  423. // Otherwise it's an atom:
  424. if (!$this->_validateAtom($phrase_parts[$i])) return false;
  425. }
  426. return true;
  427. }
  428. /**
  429. * Function to validate an atom which from rfc822 is:
  430. * atom = 1*<any CHAR except specials, SPACE and CTLs>
  431. *
  432. * If validation ($this->validate) has been turned off, then
  433. * validateAtom() doesn't actually check anything. This is so that you
  434. * can split a list of addresses up before encoding personal names
  435. * (umlauts, etc.), for example.
  436. *
  437. * @access private
  438. * @param string $atom The string to check.
  439. * @return boolean Success or failure.
  440. */
  441. function _validateAtom($atom)
  442. {
  443. if (!$this->validate) {
  444. // Validation has been turned off; assume the atom is okay.
  445. return true;
  446. }
  447. // Check for any char from ASCII 0 - ASCII 127
  448. if (!preg_match('/^[\\x00-\\x7E]+$/i', $atom, $matches)) {
  449. return false;
  450. }
  451. // Check for specials:
  452. if (preg_match('/[][()<>@,;\\:". ]/', $atom)) {
  453. return false;
  454. }
  455. // Check for control characters (ASCII 0-31):
  456. if (preg_match('/[\\x00-\\x1F]+/', $atom)) {
  457. return false;
  458. }
  459. return true;
  460. }
  461. /**
  462. * Function to validate quoted string, which is:
  463. * quoted-string = <"> *(qtext/quoted-pair) <">
  464. *
  465. * @access private
  466. * @param string $qstring The string to check
  467. * @return boolean Success or failure.
  468. */
  469. function _validateQuotedString($qstring)
  470. {
  471. // Leading and trailing "
  472. $qstring = substr($qstring, 1, -1);
  473. // Perform check.
  474. return !(preg_match('/(.)[\x0D\\\\"]/', $qstring, $matches) && $matches[1] != '\\');
  475. }
  476. /**
  477. * Function to validate a mailbox, which is:
  478. * mailbox = addr-spec ; simple address
  479. * / phrase route-addr ; name and route-addr
  480. *
  481. * @access public
  482. * @param string &$mailbox The string to check.
  483. * @return boolean Success or failure.
  484. */
  485. function validateMailbox(&$mailbox)
  486. {
  487. // A couple of defaults.
  488. $phrase = '';
  489. $comment = '';
  490. // Catch any RFC822 comments and store them separately
  491. $_mailbox = $mailbox;
  492. while (strlen(trim($_mailbox)) > 0) {
  493. $parts = explode('(', $_mailbox);
  494. $before_comment = $this->_splitCheck($parts, '(');
  495. if ($before_comment != $_mailbox) {
  496. // First char should be a (
  497. $comment = substr(str_replace($before_comment, '', $_mailbox), 1);
  498. $parts = explode(')', $comment);
  499. $comment = $this->_splitCheck($parts, ')');
  500. $comments[] = $comment;
  501. // +1 is for the trailing )
  502. $_mailbox = substr($_mailbox, strpos($_mailbox, $comment)+strlen($comment)+1);
  503. } else {
  504. break;
  505. }
  506. }
  507. for($i=0; $i<count(@$comments); $i++){
  508. $mailbox = str_replace('('.$comments[$i].')', '', $mailbox);
  509. }
  510. $mailbox = trim($mailbox);
  511. // Check for name + route-addr
  512. if (substr($mailbox, -1) == '>' && substr($mailbox, 0, 1) != '<') {
  513. $parts = explode('<', $mailbox);
  514. $name = $this->_splitCheck($parts, '<');
  515. $phrase = trim($name);
  516. $route_addr = trim(substr($mailbox, strlen($name.'<'), -1));
  517. if ($this->_validatePhrase($phrase) === false || ($route_addr = $this->_validateRouteAddr($route_addr)) === false)
  518. return false;
  519. // Only got addr-spec
  520. } else {
  521. // First snip angle brackets if present.
  522. if (substr($mailbox,0,1) == '<' && substr($mailbox,-1) == '>')
  523. $addr_spec = substr($mailbox,1,-1);
  524. else
  525. $addr_spec = $mailbox;
  526. if (($addr_spec = $this->_validateAddrSpec($addr_spec)) === false)
  527. return false;
  528. }
  529. // Construct the object that will be returned.
  530. $mbox = new stdClass();
  531. // Add the phrase (even if empty) and comments
  532. $mbox->personal = $phrase;
  533. $mbox->comment = isset($comments) ? $comments : array();
  534. if (isset($route_addr)) {
  535. $mbox->mailbox = $route_addr['local_part'];
  536. $mbox->host = $route_addr['domain'];
  537. $route_addr['adl'] !== '' ? $mbox->adl = $route_addr['adl'] : '';
  538. } else {
  539. $mbox->mailbox = $addr_spec['local_part'];
  540. $mbox->host = $addr_spec['domain'];
  541. }
  542. $mailbox = $mbox;
  543. return true;
  544. }
  545. /**
  546. * This function validates a route-addr which is:
  547. * route-addr = "<" [route] addr-spec ">"
  548. *
  549. * Angle brackets have already been removed at the point of
  550. * getting to this function.
  551. *
  552. * @access private
  553. * @param string $route_addr The string to check.
  554. * @return mixed False on failure, or an array containing validated address/route information on success.
  555. */
  556. function _validateRouteAddr($route_addr)
  557. {
  558. // Check for colon.
  559. if (strpos($route_addr, ':') !== false) {
  560. $parts = explode(':', $route_addr);
  561. $route = $this->_splitCheck($parts, ':');
  562. } else {
  563. $route = $route_addr;
  564. }
  565. // If $route is same as $route_addr then the colon was in
  566. // quotes or brackets or, of course, non existent.
  567. if ($route === $route_addr){
  568. unset($route);
  569. $addr_spec = $route_addr;
  570. if (($addr_spec = $this->_validateAddrSpec($addr_spec)) === false) {
  571. return false;
  572. }
  573. } else {
  574. // Validate route part.
  575. if (($route = $this->_validateRoute($route)) === false) {
  576. return false;
  577. }
  578. $addr_spec = substr($route_addr, strlen($route . ':'));
  579. // Validate addr-spec part.
  580. if (($addr_spec = $this->_validateAddrSpec($addr_spec)) === false) {
  581. return false;
  582. }
  583. }
  584. if (isset($route)) {
  585. $return['adl'] = $route;
  586. } else {
  587. $return['adl'] = '';
  588. }
  589. $return = array_merge($return, $addr_spec);
  590. return $return;
  591. }
  592. /**
  593. * Function to validate a route, which is:
  594. * route = 1#("@" domain) ":"
  595. *
  596. * @access private
  597. * @param string $route The string to check.
  598. * @return mixed False on failure, or the validated $route on success.
  599. */
  600. function _validateRoute($route)
  601. {
  602. // Split on comma.
  603. $domains = explode(',', trim($route));
  604. for ($i = 0; $i < count($domains); $i++) {
  605. $domains[$i] = str_replace('@', '', trim($domains[$i]));
  606. if (!$this->_validateDomain($domains[$i])) return false;
  607. }
  608. return $route;
  609. }
  610. /**
  611. * Function to validate a domain, though this is not quite what
  612. * you expect of a strict internet domain.
  613. *
  614. * domain = sub-domain *("." sub-domain)
  615. *
  616. * @access private
  617. * @param string $domain The string to check.
  618. * @return mixed False on failure, or the validated domain on success.
  619. */
  620. function _validateDomain($domain)
  621. {
  622. // Note the different use of $subdomains and $sub_domains
  623. $subdomains = explode('.', $domain);
  624. while (count($subdomains) > 0) {
  625. $sub_domains[] = $this->_splitCheck($subdomains, '.');
  626. for ($i = 0; $i < $this->index + 1; $i++)
  627. array_shift($subdomains);
  628. }
  629. for ($i = 0; $i < count($sub_domains); $i++) {
  630. if (!$this->_validateSubdomain(trim($sub_domains[$i])))
  631. return false;
  632. }
  633. // Managed to get here, so return input.
  634. return $domain;
  635. }
  636. /**
  637. * Function to validate a subdomain:
  638. * subdomain = domain-ref / domain-literal
  639. *
  640. * @access private
  641. * @param string $subdomain The string to check.
  642. * @return boolean Success or failure.
  643. */
  644. function _validateSubdomain($subdomain)
  645. {
  646. if (preg_match('|^\[(.*)]$|', $subdomain, $arr)){
  647. if (!$this->_validateDliteral($arr[1])) return false;
  648. } else {
  649. if (!$this->_validateAtom($subdomain)) return false;
  650. }
  651. // Got here, so return successful.
  652. return true;
  653. }
  654. /**
  655. * Function to validate a domain literal:
  656. * domain-literal = "[" *(dtext / quoted-pair) "]"
  657. *
  658. * @access private
  659. * @param string $dliteral The string to check.
  660. * @return boolean Success or failure.
  661. */
  662. function _validateDliteral($dliteral)
  663. {
  664. return !preg_match('/(.)[][\x0D\\\\]/', $dliteral, $matches) && $matches[1] != '\\';
  665. }
  666. /**
  667. * Function to validate an addr-spec.
  668. *
  669. * addr-spec = local-part "@" domain
  670. *
  671. * @access private
  672. * @param string $addr_spec The string to check.
  673. * @return mixed False on failure, or the validated addr-spec on success.
  674. */
  675. function _validateAddrSpec($addr_spec)
  676. {
  677. $addr_spec = trim($addr_spec);
  678. // Split on @ sign if there is one.
  679. if (strpos($addr_spec, '@') !== false) {
  680. $parts = explode('@', $addr_spec);
  681. $local_part = $this->_splitCheck($parts, '@');
  682. $domain = substr($addr_spec, strlen($local_part . '@'));
  683. // No @ sign so assume the default domain.
  684. } else {
  685. $local_part = $addr_spec;
  686. $domain = $this->default_domain;
  687. }
  688. if (($local_part = $this->_validateLocalPart($local_part)) === false) return false;
  689. if (($domain = $this->_validateDomain($domain)) === false) return false;
  690. // Got here so return successful.
  691. return array('local_part' => $local_part, 'domain' => $domain);
  692. }
  693. /**
  694. * Function to validate the local part of an address:
  695. * local-part = word *("." word)
  696. *
  697. * @access private
  698. * @param string $local_part
  699. * @return mixed False on failure, or the validated local part on success.
  700. */
  701. function _validateLocalPart($local_part)
  702. {
  703. $parts = explode('.', $local_part);
  704. // Split the local_part into words.
  705. while (count($parts) > 0){
  706. $words[] = $this->_splitCheck($parts, '.');
  707. for ($i = 0; $i < $this->index + 1; $i++) {
  708. array_shift($parts);
  709. }
  710. }
  711. // Validate each word.
  712. for ($i = 0; $i < count($words); $i++) {
  713. if ($this->_validatePhrase(trim($words[$i])) === false) return false;
  714. }
  715. // Managed to get here, so return the input.
  716. return $local_part;
  717. }
  718. /**
  719. * Returns an approximate count of how many addresses are
  720. * in the given string. This is APPROXIMATE as it only splits
  721. * based on a comma which has no preceding backslash. Could be
  722. * useful as large amounts of addresses will end up producing
  723. * *large* structures when used with parseAddressList().
  724. *
  725. * @param string $data Addresses to count
  726. * @return int Approximate count
  727. */
  728. function approximateCount($data)
  729. {
  730. return count(preg_split('/(?<!\\\\),/', $data));
  731. }
  732. /**
  733. * This is a email validating function seperate to the rest
  734. * of the class. It simply validates whether an email is of
  735. * the common internet form: <user>@<domain>. This can be
  736. * sufficient for most people. Optional stricter mode can
  737. * be utilised which restricts mailbox characters allowed
  738. * to alphanumeric, full stop, hyphen and underscore.
  739. *
  740. * @param string $data Address to check
  741. * @param boolean $strict Optional stricter mode
  742. * @return mixed False if it fails, an indexed array
  743. * username/domain if it matches
  744. */
  745. function isValidInetAddress($data, $strict = false)
  746. {
  747. $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';
  748. if (preg_match($regex, trim($data), $matches)) {
  749. return array($matches[1], $matches[2]);
  750. } else {
  751. return false;
  752. }
  753. }
  754. }
  755. ?>