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