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