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