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