fix for trailing space in personal address names
[squirrelmail.git] / class / mime.class.php
1 <?php
2
3 /**
4 * mime.class
5 *
6 * Copyright (c) 2002 The SquirrelMail Project Team
7 * Licensed under the GNU GPL. For full terms see the file COPYING.
8 *
9 *
10 * This contains functions needed to handle mime messages.
11 *
12 * $Id$
13 */
14
15
16
17 /*
18 * rdc822_header class
19 * input: header_string or array
20 */
21 class rfc822_header
22 {
23 var $date = '',
24 $subject = '',
25 $from = array(),
26 $sender = '',
27 $reply_to = array(),
28 $to = array(),
29 $cc = array(),
30 $bcc = array(),
31 $in_reply_to = '',
32 $message_id = '',
33 $mime = false,
34 $content_type = '',
35 $disposition = '',
36 $xmailer = '',
37 $priority = 3,
38 $dnt = '',
39 $mlist = array(),
40 $more_headers = array(); /* only needed for constructing headers
41 in smtp.php */
42
43 function parseHeader($hdr)
44 {
45 if (is_array($hdr))
46 {
47 $hdr = implode('',$hdr);
48 }
49 /* first we unfold the header */
50 $hdr = trim(str_replace(array("\r\n\t","\r\n "),array('',''),$hdr));
51 /*
52 * now we can make a new header array with each element representing
53 * a headerline
54 */
55 $hdr = explode("\r\n" , $hdr);
56 foreach ($hdr as $line)
57 {
58 $pos = strpos($line,':');
59 if ($pos > 0)
60 {
61 $field = substr($line,0,$pos);
62 $value = trim(substr($line,$pos+1));
63 $value = $this->stripComments($value);
64 $this->parseField($field,$value);
65 }
66 }
67 if ($this->content_type == '')
68 {
69 $this->parseContentType('text/plain; charset=us-ascii');
70 }
71 }
72
73 function stripComments($value)
74 {
75 $cnt = strlen($value);
76 $s = '';
77 $i = 0;
78 while ($i < $cnt)
79 {
80 switch ($value{$i})
81 {
82 case ('"'):
83 $s .= '"';
84 $i++;
85 while ($value{$i} != '"')
86 {
87 if ($value{$i} == '\\')
88 {
89 $s .= '\\';
90 $i++;
91 }
92 $s .= $value{$i};
93 $i++;
94 if ($i > $cnt) break;
95 }
96 $s .= $value{$i};
97 break;
98 case ('('):
99 while ($value{$i} != ')')
100 {
101 if ($value{$i} == '\\')
102 {
103 $i++;
104 }
105 $i++;
106 }
107 break;
108 default:
109 $s .= $value{$i};
110 break;
111 }
112 $i++;
113 }
114 return $s;
115 }
116
117 function parseField($field,$value)
118 {
119 $field = strtolower($field);
120 switch($field)
121 {
122 case ('date'):
123 $d = strtr($value, array(' ' => ' '));
124 $d = explode(' ', $d);
125 $this->date = getTimeStamp($d);
126 break;
127 case ('subject'):
128 $this->subject = $value;
129 break;
130 case ('from'):
131 $this->from = $this->parseAddress($value,true);
132 break;
133 case ('sender'):
134 $this->sender = $this->parseAddress($value);
135 break;
136 case ('reply-to'):
137 $this->reply_to = $this->parseAddress($value, true);
138 break;
139 case ('to'):
140 $this->to = $this->parseAddress($value, true);
141 break;
142 case ('cc'):
143 $this->cc = $this->parseAddress($value, true);
144 break;
145 case ('bcc'):
146 $this->bcc = $this->parseAddress($value, true);
147 break;
148 case ('in-reply-to'):
149 $this->in_reply_to = $value;
150 break;
151 case ('message_id'):
152 $this->message_id = $value;
153 break;
154 case ('disposition-notification-to'):
155 $this->dnt = $this->parseAddress($value);
156 break;
157 case ('mime-Version'):
158 $value = str_replace(' ','',$value);
159 if ($value == '1.0')
160 {
161 $this->mime = true;
162 }
163 break;
164 case ('content-type'):
165 $this->parseContentType($value);
166 break;
167 case ('content-disposition'):
168 $this->parseDisposition($value);
169 break;
170 case ('x-mailer'):
171 $this->xmailer = $value;
172 break;
173 case ('x-priority'):
174 $this->priority = $value;
175 break;
176 case ('list-post'):
177 $this->mlist('post',$value);
178 break;
179 case ('list-reply'):
180 $this->mlist('reply',$value);
181 break;
182 case ('list-subscribe'):
183 $this->mlist('subscribe',$value);
184 break;
185 case ('list-unsubscribe'):
186 $this->mlist('unsubscribe',$value);
187 break;
188 case ('list-archive'):
189 $this->mlist('archive',$value);
190 break;
191 case ('list-owner'):
192 $this->mlist('owner',$value);
193 break;
194 case ('list-help'):
195 $this->mlist('help',$value);
196 break;
197 case ('list-id'):
198 $this->mlist('id',$value);
199 break;
200 default:
201 break;
202 }
203 }
204
205 function parseAddress($address, $ar=false, $addr_ar = array(), $group = '')
206 {
207 $pos = 0;
208 $j = strlen( $address );
209 $name = '';
210 $addr = '';
211 while ( $pos < $j ) {
212 switch ($address{$pos})
213 {
214 case ('"'): /* get the personal name */
215 $pos++;
216 if ($address{$pos} == '"')
217 {
218 $pos++;
219 } else
220 {
221 while ( $pos < $j && $address{$pos} != '"')
222 {
223 if (substr($address, $pos, 2) == '\\"')
224 {
225 $name .= $address{$pos};
226 $pos++;
227 } elseif (substr($address, $pos, 2) == '\\\\')
228 {
229 $name .= $address{$pos};
230 $pos++;
231 }
232 $name .= $address{$pos};
233 $pos++;
234 }
235 }
236 $pos++;
237 break;
238 case ('<'): /* get email address */
239 $addr_start=$pos;
240 $pos++;
241 while ( $pos < $j && $address{$pos} != '>' )
242 {
243 $addr .= $address{$pos};
244 $pos++;
245 }
246 $pos++;
247 break;
248 case ('('): /* rip off comments */
249 $addr_start=$pos;
250 $pos++;
251 while ( $pos < $j && $address{$pos} != ')' )
252 {
253 $addr .= $address{$pos};
254 $pos++;
255 }
256 $address_start = substr($address,0,$addr_start);
257 $address_end = substr($address,$pos+1);
258 $address = $address_start . $address_end;
259 $j = strlen( $address );
260 $pos = $addr_start;
261 $pos++;
262 break;
263 case (','): /* we reached a delimiter */
264 if ($addr == '')
265 {
266 $addr = substr($address,0,$pos);
267 } elseif ($name == '') {
268 $name = trim(substr($address,0,$addr_start));
269 }
270
271 $at = strpos($addr, '@');
272 $addr_structure = new address_structure();
273 $addr_structure->personal = $name;
274 $addr_structure->group = $group;
275 if ($at)
276 {
277 $addr_structure->mailbox = substr($addr,0,$at);
278 $addr_structure->host = substr($addr,$at+1);
279 } else
280 {
281 $addr_structure->mailbox = $addr;
282 }
283 $address = trim(substr($address,$pos+1));
284 $j = strlen( $address );
285 $pos = 0;
286 $name = '';
287 $addr = '';
288 $addr_ar[] = $addr_structure;
289 break;
290 case (':'): /* process the group addresses */
291 /* group marker */
292 $group = substr($address,0,$pos);
293 $address = substr($address,$pos+1);
294 $result = $this->parseAddress($address, $ar, $addr_ar, $group);
295 $addr_ar = $result[0];
296 $pos = $result[1];
297 $address = substr($address,$pos);
298 $j = strlen( $address );
299 $group = '';
300 $pos++;
301 break;
302 case (';'):
303 if ($group)
304 {
305 $address = substr($address, 0, $pos-1);
306 }
307 $pos++;
308 break;
309 default:
310 $pos++;
311 break;
312 }
313
314 }
315 if ($addr == '')
316 {
317 $addr = substr($address,0,$pos);
318 } elseif ($name == '')
319 {
320 $name = trim(substr($address,0,$addr_start));
321 }
322 $at = strpos($addr, '@');
323 $addr_structure = new address_structure();
324 $addr_structure->group = $group;
325 if ($at)
326 {
327 $addr_structure->mailbox = trim(substr($addr,0,$at));
328 $addr_structure->host = trim(substr($addr,$at+1));
329 } else
330 {
331 $addr_structure->mailbox = trim($addr);
332 }
333 if ($group && $addr == '') /* no addresses found in group */
334 {
335 $name = "$group: Undisclosed recipients;";
336 $addr_structure->personal = $name;
337 $addr_ar[] = $addr_structure;
338 return (array($addr_ar,$pos+1));
339 } else
340 {
341 $addr_structure->personal = $name;
342 if ($name || $addr)
343 {
344 $addr_ar[] = $addr_structure;
345 }
346 }
347 if ($ar)
348 {
349 return ($addr_ar);
350 } else
351 {
352 return ($addr_ar[0]);
353 }
354 }
355
356 function parseContentType($value)
357 {
358 $pos = strpos($value,';');
359 $props = '';
360 if ($pos > 0)
361 {
362 $type = trim(substr($value,0,$pos));
363 $props = trim(substr($type,$pos+1));
364 } else
365 {
366 $type = $value;
367 }
368 $content_type = new content_type($type);
369 if ($props)
370 {
371 $properties = $this->parseProperties($props);
372 if (!isset($properties['charset']))
373 {
374 $properties['charset'] = 'us-ascii';
375 }
376 $content_type->properties = $this->parseProperties($props);
377 }
378 $this->content_type = $content_type;
379 }
380
381 function parseProperties($value)
382 {
383 $propArray = explode(';',$value);
384 $propResultArray = array();
385 foreach ($propArray as $prop)
386 {
387 $prop = trim($prop);
388 $pos = strpos($prop,'=');
389 if ($pos>0)
390 {
391 $key = trim(substr($prop,0,$pos));
392 $val = trim(substr($prop,$pos+1));
393 if ($val{0} == '"')
394 {
395 $val = substr($val,1,-1);
396 }
397 $propResultArray[$key] = $val;
398 }
399 }
400 return $propResultArray;
401 }
402
403 function parseDisposition($value)
404 {
405 $pos = strpos($value,';');
406 $props = '';
407 if ($pos > 0)
408 {
409 $name = trim(substr($value,0,$pos));
410 $props = trim(substr($type,$pos+1));
411 } else
412 {
413 $name = $value;
414 }
415 $props_a = $this->parseProperties($props);
416 $disp = new disposition($name);
417 $disp->properties = $props_a;
418 $this->disposition = $disp;
419 }
420
421 function mlist($field, $value)
422 {
423 $res_a = array();
424 $value_a = explode(',',$value);
425 foreach ($value_a as $val) {
426 $val = trim($val);
427 if ($val{0} == '<')
428 {
429 $val = substr($val,1,-1);
430 }
431 if (substr($val,0,7) == 'mailto:')
432 {
433 $res_a['mailto'] = substr($val,7);
434 } else
435 {
436 $res_a['href'] = $val;
437 }
438 }
439 $this->mlist[$field] = $res_a;
440 }
441
442 /*
443 * function to get the addres strings out of the header.
444 * Arguments: string or array of strings !
445 * example1: header->getAddr_s('to').
446 * example2: header->getAddr_s(array('to','cc','bcc'))
447 */
448 function getAddr_s($arr, $separator=', ')
449 {
450 if (is_array($arr))
451 {
452 $s = '';
453 foreach($arr as $arg )
454 {
455 $result = $this->getAddr_s($arg);
456 if ($result)
457 {
458 $s .= $separator . $result;
459 }
460 }
461 if ($s) $s = substr($s,2);
462 return $s;
463 } else
464 {
465 $s = '';
466 eval('$addr = $this->'.$arr.';') ;
467 if (is_array($addr))
468 {
469 foreach ($addr as $addr_o)
470 {
471 if (is_object($addr_o))
472 {
473 $s .= $addr_o->getAddress() . $separator;
474 }
475 }
476 $s = substr($s,0,-strlen($separator));
477 } else
478 {
479 if (is_object($addr))
480 {
481 $s .= $addr->getAddress();
482 }
483 }
484 return $s;
485 }
486 }
487
488 function getAddr_a($arg, $excl_arr=array(), $arr = array())
489 {
490 if (is_array($arg))
491 {
492 foreach($arg as $argument )
493 {
494 $arr = $this->getAddr_a($argument, $excl_arr, $arr);
495 }
496 return $arr;
497 } else
498 {
499 eval('$addr = $this->'.$arg.';') ;
500 if (is_array($addr))
501 {
502 foreach ($addr as $addr_o)
503 {
504 if (is_object($addr_o))
505 {
506 if (isset($addr_o->host) && $addr_o->host !='')
507 {
508 $email = $addr_o->mailbox.'@'.$addr_o->host;
509 } else
510 {
511 $email = $addr_o->mailbox;
512 }
513 $email = strtolower($email);
514 if ($email && !isset($arr[$email]) && !isset($excl_arr[$email]))
515 {
516 $arr[$email] = $addr_o->personal;
517 }
518 }
519 }
520 } else
521 {
522 if (is_object($addr))
523 {
524 if (isset($addr->host))
525 {
526 $email = $addr->mailbox.'@'.$addr->host;
527 } else
528 {
529 $email = $addr->mailbox;
530 }
531 $email = strtolower($email);
532 if ($email && !isset($arr[$email]) && !isset($excl_arr[$email]))
533 {
534 $arr[$email] = $addr->personal;
535 }
536 }
537 }
538 return $arr;
539 }
540 }
541
542 function getContentType($type0, $type1)
543 {
544 $type0 = $this->content_type->type0;
545 $type1 = $this->content_type->type1;
546 return $this->content_type->properties;
547 }
548 }
549
550 class msg_header
551 {
552 /** msg_header contains all variables available in a bodystructure **/
553 /** entity like described in rfc2060 **/
554
555 var $type0 = '',
556 $type1 = '',
557 $parameters = array(),
558 $id = 0,
559 $description = '',
560 $encoding='',
561 $size = 0,
562 $md5='',
563 $disposition = '',
564 $language='';
565
566 /*
567 * returns addres_list of supplied argument
568 * arguments: array('to', 'from', ...) or just a string like 'to'.
569 * result: string: address1, addres2, ....
570 */
571
572 function setVar($var, $value)
573 {
574 $this->{$var} = $value;
575 }
576
577 function getParameter($par)
578 {
579 $value = strtolower($par);
580 if (isset($this->parameters[$par]))
581 {
582 return $this->parameters[$par];
583 }
584 return '';
585 }
586
587 function setParameter($parameter, $value)
588 {
589 $this->parameters[strtolower($parameter)] = $value;
590 }
591 }
592
593
594
595 class address_structure
596 {
597 var $personal = '', $adl = '', $mailbox = '', $host = '', $group = '';
598
599 function getAddress($full=true)
600 {
601 if (is_object($this))
602 {
603 if (isset($this->host) && $this->host !='')
604 {
605 $email = $this->mailbox.'@'.$this->host;
606 } else
607 {
608 $email = $this->mailbox;
609 }
610 if (trim($this->personal) !='')
611 {
612 if ($email)
613 {
614 $addr = '"' . $this->personal . '" <' .$email.'>';
615 } else
616 {
617 $addr = $this->personal;
618 }
619 $best_dpl = $this->personal;
620 } else
621 {
622 $addr = $email;
623 $best_dpl = $email;
624 }
625 if ($full)
626 {
627 return $addr;
628 } else
629 {
630 return $best_dpl;
631 }
632 } else return '';
633 }
634 }
635
636 class message
637 {
638 /** message is the object that contains messages. It is a recursive
639 object in that through the $entities variable, it can contain
640 more objects of type message. See documentation in mime.txt for
641 a better description of how this works.
642 **/
643 var $rfc822_header = '',
644 $mime_header = '',
645 $flags = '',
646 $type0='',
647 $type1='',
648 $entities = array(),
649 $parent_ent, $entity,
650 $parent = '', $decoded_body='',
651 $is_seen = 0, $is_answered = 0, $is_deleted = 0, $is_flagged = 0,
652 $is_mdnsent = 0,
653 $body_part = '',
654 $offset = 0, /* for fetching body parts out of raw messages */
655 $length = 0; /* for fetching body parts out of raw messages */
656
657 function setEnt($ent)
658 {
659 $this->entity_id= $ent;
660 }
661
662 function addEntity ($msg)
663 {
664 $msg->parent = &$this;
665 $this->entities[] = $msg;
666 }
667
668 function addRFC822Header($read)
669 {
670 $header = new rfc822_header();
671 $this->rfc822_header = $header->parseHeader($read);
672 }
673
674 function getEntity($ent)
675 {
676 $cur_ent = $this->entity_id;
677 $msg = $this;
678 if ($cur_ent == '' || $cur_ent == '0')
679 {
680 $cur_ent_a = array();
681 } else
682 {
683 $cur_ent_a = explode('.',$this->entity_id);
684 }
685 $ent_a = explode('.',$ent);
686
687 $cnt = count($ent_a);
688
689 for ($i=0;$i<$cnt -1;$i++)
690 {
691 if (isset($cur_ent_a[$i]) && $cur_ent_a[$i] != $ent_a[$i])
692 {
693 $msg = $msg->parent;
694 $cur_ent_a = explode('.',$msg->entity_id);
695 $i--;
696 } else if (!isset($cur_ent_a[$i]))
697 {
698 if (isset($msg->entities[($ent_a[$i]-1)]))
699 {
700 $msg = $msg->entities[($ent_a[$i]-1)];
701 } else {
702 $msg = $msg->entities[0];
703 }
704 }
705 if ($msg->type0 == 'message' && $msg->type1 == 'rfc822')
706 {
707 /*this is a header for a message/rfc822 entity */
708 $msg = $msg->entities[0];
709 }
710 }
711
712 if ($msg->type0 == 'message' && $msg->type1 == 'rfc822')
713 {
714 /*this is a header for a message/rfc822 entity */
715 $msg = $msg->entities[0];
716 }
717
718 if (isset($msg->entities[($ent_a[$cnt-1])-1]))
719 {
720 $msg = $msg->entities[($ent_a[$cnt-1]-1)];
721 }
722
723 return $msg;
724 }
725
726 function setBody($s)
727 {
728 $this->body_part = $s;
729 }
730
731 function clean_up()
732 {
733 $msg = $this;
734 $msg->body_part = '';
735 $i=0;
736 while ( isset($msg->entities[$i]))
737 {
738 $msg->entities[$i]->clean_up();
739 $i++;
740 }
741 }
742
743 function getMailbox()
744 {
745 $msg = $this;
746 while (is_object($msg->parent))
747 {
748 $msg = $msg->parent;
749 }
750 return $msg->mailbox;
751 }
752
753 function calcEntity($msg)
754 {
755 if ($this->type0 == 'message' && $this->type1 == 'rfc822')
756 {
757 $msg->entity_id = $this->entity_id .'.0'; /* header of message/rfc822 */
758 } else if (isset($this->entity_id) && $this->entity_id !='')
759 {
760 $ent_no = count($this->entities)+1;
761 $par_ent = substr($this->entity_id,-2);
762 if ($par_ent{0} == '.')
763 {
764 $par_ent = $par_ent{1};
765 }
766 if ($par_ent == '0')
767 {
768 $ent_no = count($this->entities)+1;
769 if ($ent_no > 0)
770 {
771 $ent = substr($this->entity_id,0,strrpos($this->entity_id,'.'));
772 if ($ent)
773 {
774 $ent = $ent . ".$ent_no";
775 } else
776 {
777 $ent = $ent_no;
778 }
779 $msg->entity_id = $ent;
780 } else
781 {
782 $msg->entity_id = $ent_no;
783 }
784 } else
785 {
786 $ent = $this->entity_id . ".$ent_no";
787 $msg->entity_id = $ent;
788 }
789 } else
790 {
791 $msg->entity_id = '0';
792 }
793 return $msg->entity_id;
794 }
795
796
797 /*
798 * Bodystructure parser, a recursive function for generating the
799 * entity-tree with all the mime-parts.
800 *
801 * It follows RFC2060 and stores all the described fields in the
802 * message object.
803 *
804 * Question/Bugs:
805 *
806 * Ask for me (Marc Groot Koerkamp, stekkel@users.sourceforge.net.
807 *
808 */
809 function parseStructure($read, $i=0)
810 {
811 $arg_no = 0;
812 $arg_a = array();
813 $cnt = strlen($read);
814 while ($i < $cnt)
815 {
816 $char = strtoupper($read{$i});
817 switch ($char)
818 {
819 case '(':
820 if ($arg_no == 0 )
821 {
822 if (!isset($msg))
823 {
824 $msg = new message();
825 $hdr = new msg_header();
826 $hdr->type0 = 'text';
827 $hdr->type1 = 'plain';
828 $hdr->encoding = 'us-ascii';
829 $msg->entity_id = $this->calcEntity($msg);
830 } else
831 {
832 $msg->header->type0 = 'multipart';
833 $msg->type0 = 'multipart';
834 while ($read{$i} == '(')
835 {
836 $res = $msg->parseStructure($read,$i);
837 $i = $res[1];
838 $msg->addEntity($res[0]);
839 }
840 }
841 } else
842 {
843 switch ($arg_no)
844 {
845 case 1:
846 /* multipart properties */
847 $i++;
848 $res = $this->parseProperties($read,$i);
849 $arg_a[] = $res[0];
850 $i = $res[1];
851 $arg_no++;
852 break;
853 case 2:
854 if (isset($msg->type0) && $msg->type0 == 'multipart')
855 {
856 $i++;
857 $res = $msg->parseDisposition($read,$i);
858 $arg_a[] = $res[0];
859 $i = $res[1];
860 } else /* properties */
861 {
862 $res = $msg->parseProperties($read,$i);
863 $arg_a[] = $res[0];
864 $i = $res[1];
865 }
866 $arg_no++;
867 break;
868 case 3:
869 if (isset($msg->type0) && $msg->type0 == 'multipart')
870 {
871 $i++;
872 $res= $msg->parseLanguage($read,$i);
873 $arg_a[] = $res[0];
874 $i = $res[1];
875 }
876 case 7:
877 if ($arg_a[0] == 'message' && $arg_a[1] == 'rfc822')
878 {
879 $msg->header->type0 = $arg_a[0];
880 $msg->type0 = $arg_a[0];
881 $msg->header->type1 = $arg_a[1];
882 $msg->type1 = $arg_a[1];
883 $rfc822_hdr = new rfc822_header();
884 $res = $msg->parseEnvelope($read,$i,$rfc822_hdr);
885 $i = $res[1];
886 $msg->rfc822_header = $res[0];
887 $i++;
888 while ($i < $cnt && $read{$i} != '(')
889 {
890 $i++;
891 }
892 $res = $msg->parseStructure($read,$i);
893 $i = $res[1];
894 $msg->addEntity($res[0]);
895 }
896 break;
897 case 8:
898 $i++;
899 $res = $msg->parseDisposition($read,$i);
900 $arg_a[] = $res[0];
901 $i = $res[1];
902 $arg_no++;
903 break;
904 case 9:
905 if ($arg_a[0] == 'text' ||
906 ($arg_a[0] == 'message' && $arg_a[1] == 'rfc822'))
907 {
908 $i++;
909 $res = $msg->parseDisposition($read,$i);
910 $arg_a[] = $res[0];
911 $i = $res[1];
912 } else
913 {
914 $i++;
915 $res = $msg->parseLanguage($read,$i);
916 $arg_a[] = $res[0];
917 $i = $res[1];
918 }
919 $arg_no++;
920 break;
921 case 10:
922 if ($arg_a[0] == 'text' ||
923 ($arg_a[0] == 'message' && $arg_a[1] == 'rfc822'))
924 {
925 $i++;
926 $res = $msg->parseLanguage($read,$i);
927 $arg_a[] = $res[0];
928 $i = $res[1];
929 } else
930 {
931 $i = $msg->parseParenthesis($read,$i);
932 $arg_a[] = ''; /* not yet desribed in rfc2060 */
933 }
934 $arg_no++;
935 break;
936 default:
937 /* unknown argument, skip this part */
938 $i = $msg->parseParenthesis($read,$i);
939 $arg_a[] = '';
940 $arg_no++;
941 break;
942 } /* switch */
943 }
944 break;
945 case '"':
946 /* inside an entity -> start processing */
947 $debug = substr($read,$i,20);
948 $res = $msg->parseQuote($read,$i);
949 $arg_s = $res[0];
950 $i = $res[1];
951 $arg_no++;
952 if ($arg_no < 3) $arg_s = strtolower($arg_s); /* type0 and type1 */
953 $arg_a[] = $arg_s;
954 break;
955 case 'n':
956 case 'N':
957 /* probably NIL argument */
958 if (strtoupper(substr($read,$i,4)) == 'NIL ' ||
959 strtoupper(substr($read,$i,4)) == 'NIL)')
960 {
961 $arg_a[] = '';
962 $arg_no++;
963 $i = $i+2;
964 }
965 break;
966 case '{':
967 /* process the literal value */
968 $res = $msg->parseLiteral($read,$i);
969 $arg_s = $res[0];
970 $i = $res[1];
971 $arg_no++;
972 break;
973 case (is_numeric($read{$i}) ):
974 /* process integers */
975 if ($read{$i} == ' ') break;
976 $arg_s = $read{$i};;
977 $i++;
978 while (preg_match('/^[0-9]{1}$/',$read{$i}))
979 {
980 $arg_s .= $read{$i};
981 $i++;
982 }
983 $arg_no++;
984 $arg_a[] = $arg_s;
985 break;
986 case ')':
987 if (isset($msg->type0) && $msg->type0 == 'multipart')
988 {
989 $multipart = true;
990 } else
991 {
992 $multipart = false;
993 }
994 if (!$multipart)
995 {
996 if ($arg_a[0] == 'text' ||
997 ($arg_a[0] == 'message' && $arg_a[1] == 'rfc822'))
998 {
999 $shifted_args = true;
1000 } else
1001 {
1002 $shifted_args = false;
1003 }
1004 $hdr->type0 = $arg_a[0];
1005 $hdr->type1 = $arg_a[1];
1006
1007 $msg->type0 = $arg_a[0];
1008 $msg->type1 = $arg_a[1];
1009
1010 $arr = $arg_a[2];
1011 if (is_array($arr))
1012 {
1013 $hdr->parameters = $arg_a[2];
1014 }
1015 $hdr->id = str_replace( '<', '', str_replace( '>', '', $arg_a[3] ) );
1016 $hdr->description = $arg_a[4];
1017 $hdr->encoding = strtolower($arg_a[5]);
1018 $hdr->entity_id = $msg->entity_id;
1019 $hdr->size = $arg_a[6];
1020 if ($shifted_args)
1021 {
1022 $hdr->lines = $arg_a[7];
1023 if (isset($arg_a[8]))
1024 {
1025 $hdr->md5 = $arg_a[8];
1026 }
1027 if (isset($arg_a[9]))
1028 {
1029 $hdr->disposition = $arg_a[9];
1030 }
1031 if (isset($arg_a[10]))
1032 {
1033 $hdr->language = $arg_a[10];
1034 }
1035 } else
1036 {
1037 if (isset($arg_a[7]))
1038 {
1039 $hdr->md5 = $arg_a[7];
1040 }
1041 if (isset($arg_a[8]))
1042 {
1043 $hdr->disposition = $arg_a[8];
1044 }
1045 if (isset($arg_a[9]))
1046 {
1047 $hdr->language = $arg_a[9];
1048 }
1049 }
1050 $msg->header = $hdr;
1051 $arg_no = 0;
1052 $i++;
1053 if (substr($msg->entity_id,-2) == '.0' && $msg->type0 !='multipart')
1054 {
1055 $msg->entity_id++;
1056 }
1057 return (array($msg, $i));
1058 } else
1059 {
1060 $hdr->type0 = 'multipart';
1061 $hdr->type1 = $arg_a[0];
1062 $msg->type0 = 'multipart';
1063 $msg->type1 = $arg_a[0];
1064 if (is_array($arg_a[1]))
1065 {
1066 $hdr->parameters = $arg_a[1];
1067 }
1068 if (isset($arg_a[2]))
1069 {
1070 $hdr->disposition = $arg_a[2];
1071 }
1072 if (isset($arg_a[3]))
1073 {
1074 $hdr->language = $arg_a[3];
1075 }
1076 $msg->header = $hdr;
1077 return (array($msg, $i));
1078 }
1079 default:
1080 break;
1081 } /* switch */
1082 $i++;
1083 } /* while */
1084 } /* parsestructure */
1085
1086 function parseProperties($read, $i)
1087 {
1088 $properties = array();
1089 $arg_s = '';
1090 $prop_name = '';
1091 while ($read{$i} != ')')
1092 {
1093 if ($read{$i} == '"')
1094 {
1095 $res = $this->parseQuote($read,$i);
1096 $arg_s = $res[0];
1097 $i = $res[1];
1098 } else if ($read{$i} == '{')
1099 {
1100 $res = $this->parseLiteral($read,$i);
1101 $arg_s = $res[0];
1102 $i = $res[1];
1103 }
1104 if ($prop_name == '' && $arg_s)
1105 {
1106 $prop_name = strtolower($arg_s);
1107 $properties[$prop_name] = '';
1108 $arg_s = '';
1109 } elseif ($prop_name != '' && $arg_s != '')
1110 {
1111 $properties[$prop_name] = $arg_s;
1112 $prop_name = '';
1113 $arg_s = '';
1114 }
1115 $i++;
1116 }
1117 return (array($properties, $i));
1118 }
1119
1120 function parseEnvelope($read, $i, $hdr)
1121 {
1122 $arg_no = 0;
1123 $arg_a = array();
1124 $cnt = strlen($read);
1125 while ($i< $cnt && $read{$i} != ')')
1126 {
1127 $i++;
1128 $char = strtoupper($read{$i});
1129 switch ($char)
1130 {
1131 case '"':
1132 $res = $this->parseQuote($read,$i);
1133 $arg_a[] = $res[0];
1134 $i = $res[1];
1135 $arg_no++;
1136 break;
1137 case '{':
1138 $res = $this->parseLiteral($read,$i);
1139 $arg_a[] = $res[0];
1140 $i = $res[1];
1141 $arg_no++;
1142 break;
1143 case 'N':
1144 /* probably NIL argument */
1145 if (strtoupper(substr($read,$i,3)) == 'NIL') {
1146 $arg_a[] = '';
1147 $arg_no++;
1148 $i = $i+2;
1149 }
1150 break;
1151 case '(':
1152 /* Address structure
1153 * With group support.
1154 * Note: Group support is useless on SMTP connections
1155 * because the protocol doesn't support it
1156 */
1157 $addr_a = array();
1158 $group = '';
1159 $a=0;
1160 while ($i < $cnt && $read{$i} != ')')
1161 {
1162 if ($read{$i} == '(')
1163 {
1164 $res = $this->parseAddress($read,$i);
1165 $addr = $res[0];
1166 $i = $res[1];
1167 if ($addr->host == '' && $addr->mailbox != '')
1168 {
1169 /* start of group */
1170 $group = $addr->mailbox;
1171 $group_addr = $addr;
1172 $j = $a;
1173 } elseif ($group && $addr->host == '' && $addr->mailbox == '')
1174 {
1175 /* end group */
1176 if ($a == $j+1) /* no group members */
1177 {
1178 $group_addr->group = $group;
1179 $group_addr->mailbox = '';
1180 $group_addr->personal = "$group: Undisclosed recipients;";
1181 $addr_a[] = $group_addr;
1182 $group ='';
1183 }
1184 } else
1185 {
1186 $addr->group = $group;
1187 $addr_a[] = $addr;
1188 }
1189 $a++;
1190 }
1191 $i++;
1192 }
1193 $arg_a[] = $addr_a;
1194 break;
1195 default:
1196 break;
1197 }
1198 $i++;
1199 }
1200 if (count($arg_a) > 9)
1201 {
1202 /* argument 1: date */
1203 $d = strtr($arg_a[0], array(' ' => ' '));
1204 $d = explode(' ', $d);
1205 $hdr->date = getTimeStamp($d);
1206 /* argument 2: subject */
1207 if (!trim($arg_a[1]))
1208 {
1209 $arg_a[1]= _("(no subject)");
1210 }
1211 $hdr->subject = $arg_a[1];
1212 /* argument 3: from */
1213 $hdr->from = $arg_a[2][0];
1214 /* argument 4: sender */
1215 $hdr->sender = $arg_a[3][0];
1216 /* argument 5: reply-to */
1217 $hdr->replyto = $arg_a[4][0];
1218 /* argument 6: to */
1219 $hdr->to = $arg_a[5];
1220 /* argument 7: cc */
1221 $hdr->cc = $arg_a[6];
1222 /* argument 8: bcc */
1223 $hdr->bcc = $arg_a[7];
1224 /* argument 9: in-reply-to */
1225 $hdr->inreplyto = $arg_a[8];
1226 /* argument 10: message-id */
1227 $hdr->message_id = $arg_a[9];
1228 }
1229 return (array($hdr,$i));
1230 }
1231
1232 function parseLiteral($read, $i)
1233 {
1234 $lit_cnt = '';
1235 $i++;
1236 while ($read{$i} != '}')
1237 {
1238 $lit_cnt .= $read{$i};
1239 $i++;
1240 }
1241 $lit_cnt +=2; /* add the { and } characters */
1242 $s = '';
1243 for ($j = 0; $j < $lit_cnt; $j++)
1244 {
1245 $i++;
1246 $s .= $read{$i};
1247 }
1248 return (array($s, $i));
1249 }
1250
1251 function parseQuote($read, $i)
1252 {
1253 $i++;
1254 $s = '';
1255 while ($read{$i} != '"')
1256 {
1257 if ($read{$i} == '\\')
1258 {
1259 $i++;
1260 }
1261 $s .= $read{$i};
1262 $i++;
1263 }
1264 return (array($s, $i));
1265 }
1266
1267 function parseAddress($read, $i)
1268 {
1269 $arg_a = array();
1270 while ($read{$i} != ')' )
1271 {
1272 $char = strtoupper($read{$i});
1273 switch ($char)
1274 {
1275 case '"':
1276 $res = $this->parseQuote($read,$i);
1277 $arg_a[] = $res[0];
1278 $i = $res[1];
1279 break;
1280 case '{':
1281 $res = $this->parseLiteral($read,$i);
1282 $arg_a[] = $res[0];
1283 $i = $res[1];
1284 break;
1285 case 'n':
1286 case 'N':
1287 if (strtoupper(substr($read,$i,3)) == 'NIL') {
1288 $arg_a[] = '';
1289 $i = $i+2;
1290 }
1291 break;
1292 default:
1293 break;
1294 }
1295 $i++;
1296 }
1297 if (count($arg_a) == 4)
1298 {
1299 $adr = new address_structure();
1300 $adr->personal = $arg_a[0];
1301 $adr->adl = $arg_a[1];
1302 $adr->mailbox = $arg_a[2];
1303 $adr->host = $arg_a[3];
1304 } else
1305 {
1306 $adr = '';
1307 }
1308 return (array($adr,$i));
1309 }
1310
1311 function parseDisposition($read,$i)
1312 {
1313 $arg_a = array();
1314 while ($read{$i} != ')')
1315 {
1316 switch ($read{$i})
1317 {
1318 case '"':
1319 $res = $this->parseQuote($read,$i);
1320 $arg_a[] = $res[0];
1321 $i = $res[1];
1322 break;
1323 case '{':
1324 $res = $this->parseLiteral($read,$i);
1325 $arg_a[] = $res[0];
1326 $i = $res[1];
1327 break;
1328 case '(':
1329 $res = $this->parseProperties($read,$i);
1330 $arg_a[] = $res[0];
1331 $i = $res[1];
1332 break;
1333 default:
1334 break;
1335 }
1336 $i++;
1337 }
1338 if (isset($arg_a[0]))
1339 {
1340 $disp = new disposition($arg_a[0]);
1341 if (isset($arg_a[1]))
1342 {
1343 $disp->properties = $arg_a[1];
1344 }
1345 }
1346 if (is_object($disp))
1347 {
1348 return (array($disp, $i));
1349 } else
1350 {
1351 return (array('',$i));
1352 }
1353 }
1354
1355 function parseLanguage($read,$i)
1356 {
1357 /* no idea how to process this one without examples */
1358 $arg_a = array();
1359 while ($read{$i} != ')')
1360 {
1361 switch ($read{$i})
1362 {
1363 case '"':
1364 $res = $this->parseQuote($read,$i);
1365 $arg_a[] = $res[0];
1366 $i = $res[1];
1367 break;
1368 case '{':
1369 $res = $this->parseLiteral($read,$i);
1370 $arg_a[] = $res[0];
1371 $i = $res[1];
1372 break;
1373 case '(':
1374 $res = $this->parseProperties($read,$i);
1375 $arg_a[] = $res[0];
1376 $i = $res[1];
1377 break;
1378 default:
1379 break;
1380 }
1381 $i++;
1382 }
1383 if (isset($arg_a[0]))
1384 {
1385 $lang = new language($arg_a[0]);
1386 if (isset($arg_a[1]))
1387 {
1388 $lang->properties = $arg_a[1];
1389 }
1390 }
1391 if (is_object($lang))
1392 {
1393 return (array($lang, $i));
1394 } else
1395 {
1396 return (array('', $i));
1397 }
1398 }
1399
1400 function parseParenthesis($read,$i)
1401 {
1402 while ($read{$i} != ')')
1403 {
1404 switch ($read{$i})
1405 {
1406 case '"':
1407 $res = $this->parseQuote($read,$i);
1408 $i = $res[1];
1409 break;
1410 case '{':
1411 $res = $this->parseLiteral($read,$i);
1412 $i = $res[1];
1413 break;
1414 case '(':
1415 $res = $this->parseParenthesis($read,$i);
1416 $i = $res[1];
1417 break;
1418 default:
1419 break;
1420 }
1421 $i++;
1422 }
1423 return $i;
1424 }
1425
1426 /* function to fill the message structure in case the bodystructure
1427 isn't available NOT FINISHED YET
1428 */
1429 function parseMessage($read, $type0, $type1)
1430 {
1431 switch ($type0)
1432 {
1433 case 'message':
1434 $rfc822_header = true;
1435 $mime_header = false;
1436 break;
1437 case 'multipart':
1438 $mime_header = true;
1439 $rfc822_header = false;
1440 break;
1441 default:
1442 return $read;
1443 }
1444
1445 for ($i=1; $i < $count; $i++)
1446 {
1447 $line = trim($body[$i]);
1448 if ( ( $mime_header || $rfc822_header) &&
1449 (preg_match("/^.*boundary=\"?(.+(?=\")|.+).*/i",$line,$reg)) )
1450 {
1451 $bnd = $reg[1];
1452 $bndreg = $bnd;
1453 $bndreg = str_replace("\\","\\\\",$bndreg);
1454 $bndreg = str_replace("?","\\?",$bndreg);
1455 $bndreg = str_replace("+","\\+",$bndreg);
1456 $bndreg = str_replace(".","\\.",$bndreg);
1457 $bndreg = str_replace("/","\\/",$bndreg);
1458 $bndreg = str_replace("-","\\-",$bndreg);
1459 $bndreg = str_replace("(","\\(",$bndreg);
1460 $bndreg = str_replace(")","\\)",$bndreg);
1461 } elseif ( $rfc822_header && $line == '' )
1462 {
1463 $rfc822_header = false;
1464 if ($msg->type0 == 'multipart')
1465 {
1466 $mime_header = true;
1467 }
1468 }
1469
1470 if (($line{0} == '-' || $rfc822_header) && isset($boundaries[0]))
1471 {
1472 $cnt=count($boundaries)-1;
1473 $bnd = $boundaries[$cnt]['bnd'];
1474 $bndreg = $boundaries[$cnt]['bndreg'];
1475
1476 $regstr = '/^--'."($bndreg)".".*".'/';
1477 if (preg_match($regstr,$line,$reg) )
1478 {
1479 $bndlen = strlen($reg[1]);
1480 $bndend = false;
1481 if (strlen($line) > ($bndlen + 3))
1482 {
1483 if ($line{$bndlen+2} == '-' && $line{$bndlen+3} == '-')
1484 $bndend = true;
1485 }
1486 if ($bndend)
1487 {
1488 /* calc offset and return $msg */
1489 // $entStr = CalcEntity("$entStr",-1);
1490 array_pop($boundaries);
1491 $mime_header = true;
1492 $bnd_end = true;
1493 } else
1494 {
1495 $mime_header = true;
1496 $bnd_end = false;
1497 // $entStr = CalcEntity("$entStr",0);
1498 $content_indx++;
1499 }
1500 } else
1501 {
1502 if ($header)
1503 {
1504 }
1505 }
1506 }
1507 }
1508 }
1509
1510 function findDisplayEntity ($entity = array(), $alt_order = array('text/plain','text/html'))
1511 {
1512 $found = false;
1513 $type = $this->type0.'/'.$this->type1;
1514 if ( $type == 'multipart/alternative')
1515 {
1516 $msg = $this->findAlternativeEntity($alt_order);
1517 if (count($msg->entities) == 0)
1518 {
1519 $entity[] = $msg->entity_id;
1520 } else
1521 {
1522 $entity = $msg->findDisplayEntity($entity, $alt_order);
1523 }
1524 $found = true;
1525 } else if ( $type == 'multipart/related')
1526 {
1527 $msgs = $this->findRelatedEntity();
1528 for ($i = 0; $i < count($msgs); $i++)
1529 {
1530 $msg = $msgs[$i];
1531 if (count($msg->entities) == 0)
1532 {
1533 $entity[] = $msg->entity_id;
1534 } else
1535 {
1536 $entity = $msg->findDisplayEntity($entity,$alt_order);
1537 }
1538 $found = true;
1539 }
1540 } else if ( $this->type0 == 'text' &&
1541 ( $this->type1 == 'plain' ||
1542 $this->type1 == 'html' ||
1543 $this->type1 == 'message') &&
1544 isset($this->entity_id) )
1545 {
1546 if (count($this->entities) == 0)
1547 {
1548 if (strtolower($this->header->disposition->name) != 'attachment')
1549 {
1550 $entity[] = $this->entity_id;
1551 }
1552 }
1553 }
1554 $i = 0;
1555 while ( isset($this->entities[$i]) && !$found &&
1556 (strtolower($this->entities[$i]->header->disposition->name)
1557 != 'attachment') &&
1558 ($this->entities[$i]->type0 != 'message' &&
1559 $this->entities[$i]->type1 != 'rfc822' )
1560 )
1561 {
1562 $entity = $this->entities[$i]->findDisplayEntity($entity, $alt_order);
1563 $i++;
1564 }
1565 return( $entity );
1566 }
1567
1568 function findAlternativeEntity ($alt_order)
1569 {
1570 /* if we are dealing with alternative parts then we choose the best
1571 * viewable message supported by SM.
1572 */
1573 $best_view = 0;
1574 $ent_id = 0;
1575 $k = 0;
1576 for ($i = 0; $i < count($this->entities); $i ++)
1577 {
1578 $type = $this->entities[$i]->header->type0.'/'.$this->entities[$i]->header->type1;
1579 if ($type == 'multipart/related')
1580 {
1581 $type = $this->entities[$i]->header->getParameter('type');
1582 }
1583 for ($j = $k; $j < count($alt_order); $j++)
1584 {
1585 if ($alt_order[$j] == $type && $j > $best_view)
1586 {
1587 $best_view = $j;
1588 $ent_id = $i;
1589 $k = $j;
1590 }
1591 }
1592 }
1593 return $this->entities[$ent_id];
1594 }
1595
1596 function findRelatedEntity ()
1597 {
1598 $msgs = array();
1599 for ($i = 0; $i < count($this->entities); $i ++)
1600 {
1601 $type = $this->entities[$i]->header->type0.'/'.$this->entities[$i]->header->type1;
1602 if ($this->header->getParameter('type') == $type)
1603 {
1604 $msgs[] = $this->entities[$i];
1605 }
1606 }
1607 return $msgs;
1608 }
1609
1610 function getAttachments($exclude_id=array(), $result = array())
1611 {
1612 if ($this->type0 == 'message' && $this->type1 == 'rfc822')
1613 {
1614 $this = $this->entities[0];
1615 }
1616 if (count($this->entities))
1617 {
1618 foreach ($this->entities as $entity)
1619 {
1620 $exclude = false;
1621 foreach ($exclude_id as $excl)
1622 {
1623 if ($entity->entity_id == $excl)
1624 {
1625 $exclude = true;
1626 }
1627 }
1628 if (!$exclude)
1629 {
1630 if ($entity->type0 == 'multipart' &&
1631 $entity->type1 != 'related')
1632 {
1633 $result = $entity->getAttachments($exclude_id, $result);
1634 } else if ($entity->type0 != 'multipart')
1635 {
1636 $result[] = $entity;
1637 }
1638 }
1639 }
1640 } else
1641 {
1642 $exclude = false;
1643 foreach ($exclude_id as $excl)
1644 {
1645 if ($this->entity_id == $excl)
1646 {
1647 $exclude = true;
1648 }
1649 }
1650 if (!$exclude)
1651 {
1652 $result[] = $this;
1653 }
1654 }
1655 return $result;
1656 }
1657 }
1658
1659 class smime_message
1660 {
1661 }
1662
1663 class disposition
1664 {
1665 function disposition($name)
1666 {
1667 $this->name = $name;
1668 $this->properties = array();
1669 }
1670 }
1671
1672 class language
1673 {
1674 function language($name)
1675 {
1676 $this->name = $name;
1677 $this->properties = array();
1678 }
1679 }
1680
1681 class content_type
1682 {
1683 var $type0='text',
1684 $type1='plain',
1685 $properties='';
1686 function content_type($type)
1687 {
1688 $pos = strpos($type,'/');
1689 if ($pos > 0)
1690 {
1691 $this->type0 = substr($type,0,$pos);
1692 $this->type1 = substr($type,$pos+1);
1693 } else
1694 {
1695 $this->type0 = $type;
1696 }
1697 $this->properties = array();
1698 }
1699 }
1700
1701 ?>