1<?php
2/*
3 * rfc822_addresses.php
4 *
5 * @(#) $Id: rfc822_addresses.php,v 1.13 2010/04/08 20:09:23 mlemos Exp $
6 *
7 */
8
9/*
10{metadocument}<?xml version="1.0" encoding="ISO-8859-1" ?>
11<class>
12
13	<package>net.manuellemos.mimeparser</package>
14
15	<version>@(#) $Id: rfc822_addresses.php,v 1.13 2010/04/08 20:09:23 mlemos Exp $</version>
16	<copyright>Copyright � (C) Manuel Lemos 2006 - 2008</copyright>
17	<title>RFC 822 e-mail addresses parser</title>
18	<author>Manuel Lemos</author>
19	<authoraddress>mlemos-at-acm.org</authoraddress>
20
21	<documentation>
22		<idiom>en</idiom>
23		<purpose>Parse e-mail addresses from headers of <link>
24				<url>http://www.ietf.org/rfc/rfc822.txt</url>
25				<data>RFC 822</data>
26			</link> compliant e-mail messages.</purpose>
27		<usage>Use the function <functionlink>ParseAddressList</functionlink>
28			function to retrieve the list of e-mail addresses contained in
29			e-mail message headers like <tt>From</tt>, <tt>To</tt>, <tt>Cc</tt>
30			or <tt>Bcc</tt>.</usage>
31	</documentation>
32
33{/metadocument}
34*/
35
36class rfc822_addresses_class
37{
38	/* Private variables */
39
40	var $v = '';
41
42	/* Public variables */
43
44/*
45{metadocument}
46	<variable>
47		<name>error</name>
48		<type>STRING</type>
49		<value></value>
50		<documentation>
51			<purpose>Store the message that is returned when an error
52				occurs.</purpose>
53			<usage>Check this variable to understand what happened when a call to
54				any of the class functions has failed.<paragraphbreak />
55				This class uses cumulative error handling. This means that if one
56				class functions that may fail is called and this variable was
57				already set to an error message due to a failure in a previous call
58				to the same or other function, the function will also fail and does
59				not do anything.<paragraphbreak />
60				This allows programs using this class to safely call several
61				functions that may fail and only check the failure condition after
62				the last function call.<paragraphbreak />
63				Just set this variable to an empty string to clear the error
64				condition.</usage>
65		</documentation>
66	</variable>
67{/metadocument}
68*/
69	var $error = '';
70
71/*
72{metadocument}
73	<variable>
74		<name>error_position</name>
75		<type>INTEGER</type>
76		<value>-1</value>
77		<documentation>
78			<purpose>Point to the position of the message data or file that
79				refers to the last error that occurred.</purpose>
80			<usage>Check this variable to determine the relevant position of the
81				message when a parsing error occurs.</usage>
82		</documentation>
83	</variable>
84{/metadocument}
85*/
86	var $error_position = -1;
87
88/*
89{metadocument}
90	<variable>
91		<name>ignore_syntax_errors</name>
92		<type>BOOLEAN</type>
93		<value>1</value>
94		<documentation>
95			<purpose>Specify whether the class should ignore syntax errors in
96				malformed addresses.</purpose>
97			<usage>Set this variable to <booleanvalue>0</booleanvalue> if it is
98				necessary to verify whether message data may be corrupted due to
99				to eventual bugs in the program that generated the
100				message.<paragraphbreak />
101				Currently the class only ignores some types of syntax errors.
102				Other syntax errors may still cause the
103				<functionlink>ParseAddressList</functionlink> to fail.</usage>
104		</documentation>
105	</variable>
106{/metadocument}
107*/
108	var $ignore_syntax_errors=1;
109
110/*
111{metadocument}
112	<variable>
113		<name>warnings</name>
114		<type>HASH</type>
115		<value></value>
116		<documentation>
117			<purpose>Return a list of positions of the original message that
118				contain syntax errors.</purpose>
119			<usage>Check this variable to retrieve eventual message syntax
120				errors that were ignored when the
121				<variablelink>ignore_syntax_errors</variablelink> is set to
122				<booleanvalue>1</booleanvalue>.<paragraphbreak />
123				The indexes of this array are the positions of the errors. The
124				array values are the corresponding syntax error messages.</usage>
125		</documentation>
126	</variable>
127{/metadocument}
128*/
129	var $warnings=array();
130
131	/* Private functions */
132
133	Function SetError($error)
134	{
135		$this->error = $error;
136		return(0);
137	}
138
139	Function SetPositionedError($error, $position)
140	{
141		$this->error_position = $position;
142		return($this->SetError($error));
143	}
144
145	Function SetWarning($warning, $position)
146	{
147		$this->warnings[$position]=$warning;
148		return(1);
149	}
150
151	Function SetPositionedWarning($error, $position)
152	{
153		if(!$this->ignore_syntax_errors)
154			return($this->SetPositionedError($error, $position));
155		return($this->SetWarning($error, $position));
156	}
157
158	Function QDecode($p, &$value, &$encoding)
159	{
160		$encoding = $charset = null;
161		$s = 0;
162		$decoded = '';
163		$l = strlen($value);
164		while($s < $l)
165		{
166			if(GetType($q = strpos($value, '=?', $s)) != 'integer')
167			{
168				if($s == 0)
169					return(1);
170				if($s < $l)
171					$decoded .= substr($value, $s);
172				break;
173			}
174			if($s < $q)
175				$decoded .= substr($value, $s, $q - $s);
176			$q += 2;
177			if(GetType($c = strpos($value, '?', $q)) != 'integer'
178			|| $q == $c)
179				return($this->SetPositionedWarning('invalid Q-encoding character set', $p + $q));
180			if(IsSet($charset))
181			{
182				$another_charset = strtolower(substr($value, $q, $c - $q));
183				if(strcmp($charset, $another_charset)
184				&& strcmp($another_charset, 'ascii'))
185					return($this->SetWarning('it is not possible to decode an encoded value using mixed character sets into a single value', $p + $q));
186			}
187			else
188			{
189				$charset = strtolower(substr($value, $q, $c - $q));
190				if(!strcmp($charset, 'ascii'))
191					$charset = null;
192			}
193			++$c;
194			if(GetType($t = strpos($value, '?', $c)) != 'integer'
195			|| $c==$t)
196				return($this->SetPositionedWarning('invalid Q-encoding type', $p + $c));
197			$type = strtolower(substr($value, $c, $t - $c));
198			++$t;
199			if(GetType($e = strpos($value, '?=', $t)) != 'integer')
200				return($this->SetPositionedWarning('invalid Q-encoding encoded data', $p + $e));
201			switch($type)
202			{
203				case 'q':
204					for($s = $t; $s<$e;)
205					{
206						switch($b = $value[$s])
207						{
208							case '=':
209								$h = HexDec($hex = strtolower(substr($value, $s + 1, 2)));
210								if($s + 3 > $e
211								|| strcmp(sprintf('%02x', $h), $hex))
212									return($this->SetPositionedWarning('invalid Q-encoding q encoded data', $p + $s));
213								$decoded .= chr($h);
214								$s += 3;
215								break;
216
217							case '_':
218								$decoded .= ' ';
219								++$s;
220								break;
221
222							default:
223								$decoded .= $b;
224								++$s;
225						}
226					}
227					break;
228
229				case 'b':
230					if($e <= $t
231					|| strlen($binary = base64_decode($data = substr($value, $t, $e - $t))) == 0
232					|| GetType($binary) != 'string')
233						return($this->SetPositionedWarning('invalid Q-encoding b encoded data', $p + $t));
234					$decoded .= $binary;
235					$s = $e;
236					break;
237
238				default:
239					return($this->SetPositionedWarning('Q-encoding '.$type.' is not yet supported', $p + $c));
240			}
241			$s += 2;
242		}
243		$value = $decoded;
244		$encoding = $charset;
245		return(1);
246	}
247
248	Function ParseCText(&$p, &$c_text)
249	{
250		$c_text = null;
251		$v = $this->v;
252		if($p<strlen($v)
253		&& GetType(strchr("\t\r\n ()\\\0", $c = $v[$p])) != 'string'
254		&& Ord($c)<128)
255		{
256			$c_text = $c;
257			++$p;
258		}
259		return(1);
260	}
261
262	Function ParseQText(&$p, &$q_text)
263	{
264		$q_text = null;
265		$v = $this->v;
266		if($p>strlen($v)
267		|| GetType(strchr("\t\r\n \"\\\0", $c = $v[$p])) == 'string')
268			return(1);
269		if(Ord($c) >= 128)
270		{
271			if(!$this->ignore_syntax_errors)
272				return(1);
273			$this->SetPositionedWarning('it was used an unencoded 8 bit character', $p);
274		}
275		$q_text = $c;
276		++$p;
277		return(1);
278	}
279
280	Function ParseQuotedPair(&$p, &$quoted_pair)
281	{
282		$quoted_pair = null;
283		$v = $this->v;
284		$l = strlen($v);
285		if($p+1 < $l
286		&& !strcmp($v[$p], '\\')
287		&& GetType(strchr("\r\n\0", $c = $v[$p + 1])) != 'string'
288		&& Ord($c)<128)
289		{
290			$quoted_pair = $c;
291			$p += 2;
292		}
293		return(1);
294	}
295
296	Function ParseCContent(&$p, &$c_content)
297	{
298		$c_content = null;
299		$c = $p;
300		if(!$this->ParseQuotedPair($c, $content))
301			return(0);
302		if(!IsSet($content))
303		{
304			if(!$this->ParseCText($c, $content))
305				return(0);
306			if(!IsSet($content))
307			{
308				if(!$this->ParseComment($c, $content))
309					return(0);
310				if(!IsSet($content))
311					return(1);
312			}
313		}
314		$c_content = $content;
315		$p = $c;
316		return(1);
317	}
318
319	Function SkipWhiteSpace(&$p)
320	{
321		$v = $this->v;
322		$l = strlen($v);
323		for(;$p<$l; ++$p)
324		{
325			switch($v[$p])
326			{
327				case ' ':
328				case "\n":
329				case "\r":
330				case "\t":
331					break;
332				default:
333					return(1);
334			}
335		}
336		return(1);
337	}
338
339	Function ParseComment(&$p, &$comment)
340	{
341		$comment = null;
342		$v = $this->v;
343		$l = strlen($v);
344		$c = $p;
345		if($c >= $l
346		|| strcmp($v[$c], '('))
347			return(1);
348		++$c;
349		for(; $c < $l;)
350		{
351			if(!$this->SkipWhiteSpace($c))
352				return(0);
353			if(!$this->ParseCContent($c, $c_content))
354				return(0);
355			if(!IsSet($c_content))
356				break;
357		}
358		if(!$this->SkipWhiteSpace($c))
359			return(0);
360		if($c >= $l
361		|| strcmp($v[$c], ')'))
362			return(1);
363		++$c;
364		$comment = substr($v, $p, $c - $p);
365		$p = $c;
366		return(1);
367	}
368
369	Function SkipCommentGetWhiteSpace(&$p, &$space)
370	{
371		$v = $this->v;
372		$l = strlen($v);
373		for($space = '';$p<$l;)
374		{
375			switch($w = $v[$p])
376			{
377				case ' ':
378				case "\n":
379				case "\r":
380				case "\t":
381					++$p;
382					$space .= $w;
383					break;
384				case '(':
385					if(!$this->ParseComment($p, $comment))
386						return(0);
387				default:
388					return(1);
389			}
390		}
391		return(1);
392	}
393
394	Function SkipCommentWhiteSpace(&$p)
395	{
396		$v = $this->v;
397		$l = strlen($v);
398		for(;$p<$l;)
399		{
400			switch($w = $v[$p])
401			{
402				case ' ':
403				case "\n":
404				case "\r":
405				case "\t":
406					++$p;
407					break;
408				case '(':
409					if(!$this->ParseComment($p, $comment))
410						return(0);
411				default:
412					return(1);
413			}
414		}
415		return(1);
416	}
417
418	Function ParseQContent(&$p, &$q_content)
419	{
420		$q_content = null;
421		$q = $p;
422		if(!$this->ParseQuotedPair($q, $content))
423			return(0);
424		if(!IsSet($content))
425		{
426			if(!$this->ParseQText($q, $content))
427				return(0);
428			if(!IsSet($content))
429				return(1);
430		}
431		$q_content = $content;
432		$p = $q;
433		return(1);
434	}
435
436	Function ParseAtom(&$p, &$atom, $dot)
437	{
438		$atom = null;
439		$v = $this->v;
440		$l = strlen($v);
441		$a = $p;
442		if(!$this->SkipCommentGetWhiteSpace($a, $space))
443			return(0);
444		$match = '/^([-'.($dot ? '.' : '').'A-Za-z0-9!#$&\'*+\\/=?^_{|}~]+)/';
445		for($s = $a;$a < $l;)
446		{
447			if(preg_match($match, substr($this->v, $a), $m))
448				$a += strlen($m[1]);
449			elseif(Ord($v[$a]) < 128)
450				break;
451			elseif(!$this->SetPositionedWarning('it was used an unencoded 8 bit character', $a))
452				return(0);
453			else
454				++$a;
455		}
456		if($s == $a)
457			return(1);
458		$atom = $space.substr($this->v, $s, $a - $s);
459		if(!$this->SkipCommentGetWhiteSpace($a, $space))
460			return(0);
461		$atom .= $space;
462		$p = $a;
463		return(1);
464	}
465
466	Function ParseQuotedString(&$p, &$quoted_string)
467	{
468		$quoted_string = null;
469		$v = $this->v;
470		$l = strlen($v);
471		$s = $p;
472		if(!$this->SkipCommentWhiteSpace($s))
473			return(0);
474		if($s >= $l
475		|| strcmp($v[$s], '"'))
476			return(1);
477		++$s;
478		for($string = '';$s < $l;)
479		{
480			$w = $s;
481			if(!$this->SkipWhiteSpace($s))
482				return(0);
483			if($w != $s)
484				$string .= substr($v, $w, $s - $w);
485			if(!$this->ParseQContent($s, $q_content))
486				return(0);
487			if(!IsSet($q_content))
488				break;
489			$string .= $q_content;
490		}
491			$w = $s;
492		if(!$this->SkipWhiteSpace($s))
493			return(0);
494		if($w != $s)
495			$string .= substr($v, $w, $s - $w);
496		if($s >= $l
497		|| strcmp($v[$s], '"'))
498			return(1);
499		++$s;
500		if(!$this->SkipCommentWhiteSpace($s))
501			return(0);
502		$quoted_string = $string;
503		$p = $s;
504		return(1);
505	}
506
507	Function ParseWord(&$p, &$word)
508	{
509		$word = null;
510		if(!$this->ParseQuotedString($p, $word))
511			return(0);
512		if(IsSet($word))
513			return(1);
514		if(!$this->ParseAtom($p, $word, 0))
515			return(0);
516		return(1);
517	}
518
519	Function ParseObsPhrase(&$p, &$obs_phrase)
520	{
521		$obs_phrase = null;
522		$v = $this->v;
523		$l = strlen($v);
524		$ph = $p;
525		if(!$this->ParseWord($ph, $word))
526			return(0);
527		$string = $word;
528		for(;;)
529		{
530			if(!$this->ParseWord($ph, $word))
531				return(0);
532			if(IsSet($word))
533			{
534				$string .= $word;
535				continue;
536			}
537			$w = $ph;
538			if(!$this->SkipCommentGetWhiteSpace($ph, $space))
539				return(0);
540			if($w != $ph)
541			{
542				$string .= $space;
543				continue;
544			}
545			if($ph >= $l
546			|| strcmp($v[$ph], '.'))
547				break;
548			$string .= '.';
549			++$ph;
550		}
551		$obs_phrase = $string;
552		$p = $ph;
553		return(1);
554	}
555
556	Function ParsePhrase(&$p, &$phrase)
557	{
558		$phrase = null;
559		if(!$this->ParseObsPhrase($p, $phrase))
560			return(0);
561		if(IsSet($phrase))
562			return(1);
563		$ph = $p;
564		if(!$this->ParseWord($ph, $word))
565			return(0);
566		$string = $word;
567		for(;;)
568		{
569			if(!$this->ParseWord($ph, $word))
570				return(0);
571			if(!IsSet($word))
572				break;
573			$string .= $word;
574		}
575		$phrase = $string;
576		$p = $ph;
577		return(1);
578	}
579
580	Function ParseAddrSpec(&$p, &$addr_spec)
581	{
582		$addr_spec = null;
583		$v = $this->v;
584		$l = strlen($v);
585		$a = $p;
586		if(!$this->ParseQuotedString($a, $local_part))
587			return(0);
588		if(!IsSet($local_part))
589		{
590			if(!$this->ParseAtom($a, $local_part, 1))
591				return(0);
592			$local_part = trim($local_part);
593		}
594		if($a >= $l
595		|| strcmp($v[$a], '@'))
596			return(1);
597		++$a;
598		if(!$this->ParseAtom($a, $domain, 1))
599			return(0);
600		if(!IsSet($domain))
601			return(1);
602		$addr_spec = $local_part.'@'.trim($domain);
603		$p = $a;
604		return(1);
605	}
606
607	Function ParseAngleAddr(&$p, &$addr)
608	{
609		$addr = null;
610		$v = $this->v;
611		$l = strlen($v);
612		$a = $p;
613		if(!$this->SkipCommentWhiteSpace($a))
614			return(0);
615		if($a >= $l
616		|| strcmp($v[$a], '<'))
617			return(1);
618		++$a;
619		if(!$this->ParseAddrSpec($a, $addr_spec))
620			return(0);
621		if($a >= $l
622		|| strcmp($v[$a], '>'))
623			return(1);
624		++$a;
625		if(!$this->SkipCommentWhiteSpace($a))
626			return(0);
627		$addr = $addr_spec;
628		$p = $a;
629		return(1);
630	}
631
632	Function ParseName(&$p, &$address)
633	{
634		$address = null;
635		$a = $p;
636		if(!$this->ParsePhrase($a, $display_name))
637			return(0);
638		if(IsSet($display_name))
639		{
640			if(!$this->QDecode($p, $display_name, $encoding))
641				return(0);
642			$address['name'] = trim($display_name);
643			if(IsSet($encoding))
644				$address['encoding'] = $encoding;
645		}
646		$p = $a;
647		return(1);
648	}
649
650	Function ParseNameAddr(&$p, &$address)
651	{
652		$address = null;
653		$a = $p;
654		if(!$this->ParsePhrase($a, $display_name))
655			return(0);
656		if(!$this->ParseAngleAddr($a, $addr))
657			return(0);
658		if(!IsSet($addr))
659			return(1);
660		$address = array('address'=>$addr);
661		if(IsSet($display_name))
662		{
663			if(!$this->QDecode($p, $display_name, $encoding))
664				return(0);
665			$address['name'] = trim($display_name);
666			if(IsSet($encoding))
667				$address['encoding'] = $encoding;
668		}
669		$p = $a;
670		return(1);
671	}
672
673	Function ParseAddrNameAddr(&$p, &$address)
674	{
675		$address = null;
676		$a = $p;
677		if(!$this->ParseAddrSpec($a, $display_name))
678			return(0);
679		if(!IsSet($display_name))
680			return(1);
681		if(!$this->ParseAngleAddr($a, $addr))
682			return(0);
683		if(!IsSet($addr))
684			return(1);
685		if(!$this->QDecode($p, $display_name, $encoding))
686			return(0);
687		$address = array(
688			'address'=>$addr,
689			'name' => trim($display_name)
690		);
691		if(IsSet($encoding))
692			$address['encoding'] = $encoding;
693		$p = $a;
694		return(1);
695	}
696
697	Function ParseMailbox(&$p, &$address)
698	{
699		$address = null;
700		if($this->ignore_syntax_errors)
701		{
702			$a = $p;
703			if(!$this->ParseAddrNameAddr($p, $address))
704				return(0);
705			if(IsSet($address))
706				return($this->SetPositionedWarning('it was specified an unquoted address as name', $a));
707		}
708		if(!$this->ParseNameAddr($p, $address))
709			return(0);
710		if(IsSet($address))
711			return(1);
712		if(!$this->ParseAddrSpec($p, $addr_spec))
713			return(0);
714		if(IsSet($addr_spec))
715		{
716			$address = array('address'=>$addr_spec);
717			return(1);
718		}
719		$a = $p;
720		if($this->ignore_syntax_errors
721		&& $this->ParseName($p, $address)
722		&& IsSet($address))
723			return($this->SetPositionedWarning('it was specified a name without an address', $a));
724		return(1);
725	}
726
727	Function ParseMailboxGroup(&$p, &$mailbox_group)
728	{
729		$v = $this->v;
730		$l = strlen($v);
731		$g = $p;
732		if(!$this->ParseMailbox($g, $address))
733			return(0);
734		if(!IsSet($address))
735			return(1);
736		$addresses = array($address);
737		for(;$g < $l;)
738		{
739			if(strcmp($v[$g], ','))
740				break;
741			++$g;
742			if(!$this->ParseMailbox($g, $address))
743				return(0);
744			if(!IsSet($address))
745				return(1);
746			$addresses[] = $address;
747		}
748		$mailbox_group = $addresses;
749		$p = $g;
750		return(1);
751	}
752
753	Function ParseGroup(&$p, &$address)
754	{
755		$address = null;
756		$v = $this->v;
757		$l = strlen($v);
758		$g = $p;
759		if(!$this->ParsePhrase($g, $display_name))
760			return(0);
761		if(!IsSet($display_name)
762		|| $g >= $l
763		|| strcmp($v[$g], ':'))
764			return(1);
765		++$g;
766		if(!$this->ParseMailboxGroup($g, $mailbox_group))
767			return(0);
768		if(!IsSet($mailbox_group))
769		{
770			if(!$this->SkipCommentWhiteSpace($g))
771				return(0);
772			$mailbox_group = array();
773		}
774		if($g >= $l
775		|| strcmp($v[$g], ';'))
776			return(1);
777		$c = ++$g;
778		if($this->SkipCommentWhiteSpace($g)
779		&& $g > $c
780		&& !$this->SetPositionedWarning('it were used invalid comments after a group of addresses', $c))
781			return(0);
782		if(!$this->QDecode($p, $display_name, $encoding))
783			return(0);
784		$address = array(
785			'name'=>$display_name,
786			'group'=>$mailbox_group
787		);
788		if(IsSet($encoding))
789			$address['encoding'] = $encoding;
790		$p = $g;
791		return(1);
792	}
793
794	Function ParseAddress(&$p, &$address)
795	{
796		$address = null;
797		if(!$this->ParseGroup($p, $address))
798			return(0);
799		if(!IsSet($address))
800		{
801			if(!$this->ParseMailbox($p, $address))
802				return(0);
803		}
804		return(1);
805	}
806
807	/* Public functions */
808
809/*
810{metadocument}
811	<function>
812		<name>ParseAddressList</name>
813		<type>BOOLEAN</type>
814		<documentation>
815			<purpose>Parse and extract e-mail addresses eventually from headers
816				of an e-mail message.</purpose>
817			<usage>Pass a string value with a list of e-mail addresses to the
818				<argumentlink>
819					<function>ParseAddressList</function>
820					<argument>value</argument>
821				</argumentlink>. The <argumentlink>
822					<function>ParseAddressList</function>
823					<argument>addresses</argument>
824				</argumentlink> returns the list of e-mail addresses found.</usage>
825			<returnvalue>This function returns <booleanvalue>1</booleanvalue> if
826				the specified value is parsed successfully. Otherwise,
827				check the variables <variablelink>error</variablelink> and
828				<variablelink>error_position</variablelink> to determine what
829				error occurred and the relevant value position.</returnvalue>
830		</documentation>
831		<argument>
832			<name>value</name>
833			<type>STRING</type>
834			<documentation>
835				<purpose>String with a list of e-mail addresses to parse.</purpose>
836			</documentation>
837		</argument>
838		<argument>
839			<name>addresses</name>
840			<type>ARRAY</type>
841			<out />
842			<documentation>
843				<purpose>Return the list of parsed e-mail addresses.
844					Each entry in the list is an associative array.<paragraphbreak />
845					For normal addresses, this associative array has the entry
846					<stringvalue>address</stringvalue> set to the e-mail address.
847					If the address has an associated name, it is stored in the
848					entry <stringvalue>name</stringvalue>.<paragraphbreak />
849					For address groups, there is the entry
850					<stringvalue>name</stringvalue>.
851					The group addresses list are stored in the entry
852					<stringvalue>group</stringvalue> as an array. The structure of
853					the group addresses list array is the same as this addresses
854					list array argument.</purpose>
855			</documentation>
856		</argument>
857		<do>
858{/metadocument}
859*/
860	Function ParseAddressList($value, &$addresses)
861	{
862		$this->warnings = array();
863		$addresses = array();
864		$this->v = $v = $value;
865		$l = strlen($v);
866		$p = 0;
867		if(!$this->ParseAddress($p, $address))
868			return(0);
869		if(!IsSet($address))
870			return($this->SetPositionedError('it was not specified a valid address', $p));
871		$addresses[] = $address;
872		while($p < $l)
873		{
874			if(strcmp($v[$p], ',')
875			&& !$this->SetPositionedWarning('multiple addresses must be separated by commas: ', $p))
876				return(0);
877			++$p;
878			if(!$this->ParseAddress($p, $address))
879				return(0);
880			if(!IsSet($address))
881				return($this->SetPositionedError('it was not specified a valid address after comma', $p));
882			$addresses[] = $address;
883		}
884		return(1);
885	}
886/*
887{metadocument}
888		</do>
889	</function>
890{/metadocument}
891*/
892
893};
894
895/*
896
897{metadocument}
898</class>
899{/metadocument}
900
901*/
902
903?>