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