moved parse header stuff to the mime-class and some rewriting to follow
[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 * rdc822_header class
17 * input: header_string or array
18 */
19
20 class rfc822_header {
21 var $date = '',
22 $subject = '',
23 $from = array(),
24 $sender = '',
25 $reply_to = array(),
26 $to = array(),
27 $cc = array(),
28 $bcc = array(),
29 $in_reply_to = '',
30 $message_id = '',
31 $mime = false,
32 $content_type = '',
33 $disposition = '',
34 $xmailer = '',
35 $priority = 3,
36 $dnt = '',
37 $mlist = array(),
38 $optional_headers = array(); /* only needed for
39 constructing headers in smtp.php */
40
41 function parseHeader($hdr) {
42 if (is_array($hdr)) {
43 $hdr = implode('',$hdr);
44 }
45 /* first we unfold the header */
46 $hdr = str_replace(array("\r\n\t","\r\n\s"),array('',''),$hdr);
47 /*
48 * now we can make a new header array with each element representing
49 * a headerline
50 */
51 $hdr = explode("\r\n" , $hdr);
52 foreach ($hdr as $line) {
53 $pos = strpos($line,':');
54 if ($pos > 0) {
55 $field = substr($line,0,$pos);
56 $value = trim(substr($line,$pos+1));
57 $value = $this->stripComments($value);
58 $this->parseField($field,$value);
59 }
60 }
61 if ($this->content_type == '') {
62 $this->parseContentType('text/plain; charset=us-ascii');
63 }
64 }
65
66 function stripComments($value) {
67 $cnt = strlen($value);
68 $s = '';
69 $i = 0;
70 while ($i < $cnt) {
71 switch ($value{$i})
72 {
73 case ('"'):
74 $s .= '"';
75 $i++;
76 while ($value{$i} != '"') {
77 if ($value{$i} == '\\') {
78 $s .= '\\';
79 $i++;
80 }
81 $s .= $value{$i};
82 $i++;
83 }
84 $s .= $value{$i};
85 break;
86 case ('('):
87 while ($value{$i} != ')') {
88 if ($value{$i} == '\\') {
89 $i++;
90 }
91 $i++;
92 }
93 break;
94 default:
95 $s .= $value{$i};
96 break;
97 }
98 $i++;
99 }
100 return $s;
101 }
102
103 function parseField($field,$value) {
104 $field = strtolower($field);
105 switch($field)
106 {
107 case ('date'):
108 $d = strtr($value, array(' ' => ' '));
109 $d = explode(' ', $d);
110 $this->date = getTimeStamp($d);
111 break;
112 case ('subject'):
113 $this->subject = $value;
114 break;
115 case ('from'):
116 $this->from = $this->parseAddress($value,true);
117 break;
118 case ('sender'):
119 $this->sender = $this->parseAddress($value);
120 break;
121 case ('reply-to'):
122 $this->reply_to = $this->parseAddress($value, true);
123 break;
124 case ('to'):
125 $this->to = $this->parseAddress($value, true);
126 break;
127 case ('cc'):
128 $this->cc = $this->parseAddress($value, true);
129 break;
130 case ('bcc'):
131 $this->bcc = $this->parseAddress($value, true);
132 break;
133 case ('in-reply-to'):
134 $this->in_reply_to = $value;
135 break;
136 case ('message_id'):
137 $this->message_id = $value;
138 break;
139 case ('disposition-notification-to'):
140 $this->dnt = $this->parseAddress($value);
141 break;
142 case ('mime-Version'):
143 $value = str_replace(' ','',$value);
144 if ($value == '1.0') {
145 $this->mime = true;
146 }
147 break;
148 case ('content-type'):
149 $this->parseContentType($value);
150 break;
151 case ('content-disposition'):
152 $this->parseDisposition($value);
153 break;
154 case ('x-mailer'):
155 $this->xmailer = $value;
156 break;
157 case ('x-priority'):
158 $this->priority = $value;
159 break;
160 case ('list-post'):
161 $this->mlist('post',$value);
162 break;
163 case ('list-reply'):
164 $this->mlist('reply',$value);
165 break;
166 case ('list-subscribe'):
167 $this->mlist('subscribe',$value);
168 break;
169 case ('list-unsubscribe'):
170 $this->mlist('unsubscribe',$value);
171 break;
172 case ('list-archive'):
173 $this->mlist('archive',$value);
174 break;
175 case ('list-owner'):
176 $this->mlist('owner',$value);
177 break;
178 case ('list-help'):
179 $this->mlist('help',$value);
180 break;
181 case ('list-id'):
182 $this->mlist('id',$value);
183 break;
184 default:
185 break;
186 }
187 }
188
189 function parseAddress($address, $ar=false, $addr_ar = array(), $group = '') {
190 $pos = 0;
191 $j = strlen( $address );
192 $name = '';
193 $addr = '';
194 while ( $pos < $j ) {
195 switch ($address{$pos})
196 {
197 case ('"'): /* get the personal name */
198 $pos++;
199 if ($address{$pos} == '"') {
200 $pos++;
201 } else {
202 while ( $pos < $j && $address{$pos} != '"') {
203 if (substr($address, $pos, 2) == '\\"') {
204 $name .= $address{$pos};
205 $pos++;
206 } elseif (substr($address, $pos, 2) == '\\\\') {
207 $name .= $address{$pos};
208 $pos++;
209 }
210 $name .= $address{$pos};
211 $pos++;
212 }
213 }
214 $pos++;
215 break;
216 case ('<'): /* get email address */
217 $addr_start=$pos;
218 $pos++;
219 while ( $pos < $j && $address{$pos} != '>' ) {
220 $addr .= $address{$pos};
221 $pos++;
222 }
223 $pos++;
224 break;
225 case ('('): /* rip off comments */
226 $addr_start=$pos;
227 $pos++;
228 while ( $pos < $j && $address{$pos} != ')' ) {
229 $addr .= $address{$pos};
230 $pos++;
231 }
232 $address_start = substr($address,0,$addr_start);
233 $address_end = substr($address,$pos+1);
234 $address = $address_start . $address_end;
235 $j = strlen( $address );
236 $pos = $addr_start;
237 $pos++;
238 break;
239 case (','): /* we reached a delimiter */
240 if ($addr == '') {
241 $addr = substr($address,0,$pos);
242 } elseif ($name == '') {
243 $name = substr($address,0,$addr_start);
244 }
245
246 $at = strpos($addr, '@');
247 $addr_structure = new address_structure();
248 $addr_structure->personal = $name;
249 $addr_structure->group = $group;
250 if ($at) {
251 $addr_structure->mailbox = substr($addr,0,$at);
252 $addr_structure->host = substr($addr,$at+1);
253 } else {
254 $addr_structure->mailbox = $addr;
255 }
256 $address = trim(substr($address,$pos+1));
257 $j = strlen( $address );
258 $pos = 0;
259 $name = '';
260 $addr = '';
261 $addr_ar[] = $addr_structure;
262 break;
263 case (':'): /* process the group addresses */
264 /* group marker */
265 $group = substr($address,0,$pos);
266 $address = substr($address,$pos+1);
267 $result = $this->parseAddress($address, $ar, $addr_ar, $group);
268 $addr_ar = $result[0];
269 $pos = $result[1];
270 $address = substr($address,$pos);
271 $j = strlen( $address );
272 $group = '';
273 $pos++;
274 break;
275 case (';'):
276 if ($group) {
277 $address = substr($address, 0, $pos-1);
278 }
279 $pos++;
280 break;
281 default:
282 $pos++;
283 break;
284 }
285
286 }
287 if ($addr == '') {
288 $addr = substr($address,0,$pos);
289 } elseif ($name == '') {
290 $name = substr($address,0,$addr_start);
291 }
292 $at = strpos($addr, '@');
293 $addr_structure = new address_structure();
294 $addr_structure->group = $group;
295 if ($at) {
296 $addr_structure->mailbox = trim(substr($addr,0,$at));
297 $addr_structure->host = trim(substr($addr,$at+1));
298 } else {
299 $addr_structure->mailbox = trim($addr);
300 }
301 if ($group && $addr == '') { /* no addresses found in group */
302 $name = "$group: Undisclosed recipients;";
303 $addr_structure->personal = $name;
304 $addr_ar[] = $addr_structure;
305 return (array($addr_ar,$pos+1));
306 } else {
307 $addr_structure->personal = $name;
308 if ($name || $addr) {
309 $addr_ar[] = $addr_structure;
310 }
311 }
312 if ($ar) {
313 return ($addr_ar);
314 } else {
315 return ($addr_ar[0]);
316 }
317 }
318
319 function parseContentType($value) {
320 $pos = strpos($value,';');
321 $props = '';
322 if ($pos > 0) {
323 $type = trim(substr($value,0,$pos));
324 $props = trim(substr($type,$pos+1));
325 } else {
326 $type = $value;
327 }
328 $content_type = new content_type($type);
329 if ($props) {
330 $properties = $this->parseProperties($props);
331 if (!isset($properties['charset'])) {
332 $properties['charset'] = 'us-ascii';
333 }
334 $content_type->properties = $this->parseProperties($props);
335 }
336 $this->content_type = $content_type;
337 }
338
339 function parseProperties($value) {
340 $propArray = explode(';',$value);
341 $propResultArray = array();
342 foreach ($propArray as $prop) {
343 $prop = trim($prop);
344 $pos = strpos($prop,'=');
345 if ($pos>0) {
346 $key = trim(substr($prop,0,$pos));
347 $val = trim(substr($prop,$pos+1));
348 if ($val{0} == '"') {
349 $val = substr($val,1,-1);
350 }
351 $propResultArray[$key] = $val;
352 }
353 }
354 return $propResultArray;
355 }
356
357 function parseDisposition($value) {
358 $pos = strpos($value,';');
359 $props = '';
360 if ($pos > 0) {
361 $name = trim(substr($value,0,$pos));
362 $props = trim(substr($type,$pos+1));
363 } else {
364 $name = $value;
365 }
366 $props_a = $this->parseProperties($props);
367 $disp = new disposition($name);
368 $disp->properties = $props_a;
369 $this->disposition = $disp;
370 }
371
372 function mlist($field, $value) {
373 $res_a = array();
374 $value_a = explode(',',$value);
375 foreach ($value_a as $val) {
376 $val = trim($val);
377 if ($val{0} == '<') {
378 $val = substr($val,1,-1);
379 }
380 if (substr($val,0,7) == 'mailto:') {
381 $res_a['mailto'] = substr($val,7);
382 } else {
383 $res_a['href'] = $val;
384 }
385 }
386 $this->mlist[$field] = $res_a;
387 }
388
389 /*
390 * function to get the addres strings out of the header.
391 * Arguments: string or array of strings !
392 * example1: header->getAddr_s('to').
393 * example2: header->getAddr_s(array('to','cc','bcc'))
394 */
395 function getAddr_s($arr, $separator=', ') {
396 if (is_array($arr)) {
397 $s = '';
398 foreach($arr as $arg ) {
399 $result = $this->getAddr_s($arg);
400 if ($result) {
401 $s .= $separator . $result;
402 }
403 }
404 if ($s) $s = substr($s,2);
405 return $s;
406 } else {
407 $s = '';
408 eval('$addr = $this->'.$arr.';') ;
409 if (is_array($addr)) {
410 foreach ($addr as $addr_o) {
411 if (is_object($addr_o)) {
412 $s .= $addr_o->getAddress() . $separator;
413 }
414 }
415 $s = substr($s,0,-strlen($separator));
416 } else {
417 if (is_object($addr)) {
418 $s .= $addr->getAddress();
419 }
420 }
421 return $s;
422 }
423 }
424
425 function getAddr_a($arg, $excl_arr=array(), $arr = array()) {
426 if (is_array($arg)) {
427 foreach($arg as $argument ) {
428 $arr = $this->getAddr_a($argument, $excl_arr, $arr);
429 }
430 return $arr;
431 } else {
432 eval('$addr = $this->'.$arg.';') ;
433 if (is_array($addr)) {
434 foreach ($addr as $addr_o) {
435 if (is_object($addr_o)) {
436 if (isset($addr_o->host) && $addr_o->host !='') {
437 $email = $addr_o->mailbox.'@'.$addr_o->host;
438 } else {
439 $email = $addr_o->mailbox;
440 }
441 $email = strtolower($email);
442 if ($email && !isset($arr[$email]) && !isset($excl_arr[$email])) {
443 $arr[$email] = $addr_o->personal;
444 }
445 }
446 }
447 } else {
448 if (is_object($addr)) {
449 if (isset($addr->host)) {
450 $email = $addr->mailbox.'@'.$addr->host;
451 } else {
452 $email = $addr->mailbox;
453 }
454 $email = strtolower($email);
455 if ($email && !isset($arr[$email]) && !isset($excl_arr[$email])) {
456 $arr[$email] = $addr->personal;
457 }
458 }
459 }
460 return $arr;
461 }
462 }
463
464 function getContentType($type0, $type1) {
465 $type0 = $this->content_type->type0;
466 $type1 = $this->content_type->type1;
467 return $this->content_type->properties;
468 }
469 }
470
471 class msg_header {
472 /** msg_header contains all variables available in a bodystructure **/
473 /** entity like described in rfc2060 **/
474
475 var $type0 = '',
476 $type1 = '',
477 $parameters = array(),
478 $id = 0,
479 $description = '',
480 $encoding='',
481 $size = 0,
482 $md5='',
483 $disposition = '',
484 $language='';
485
486 /*
487 * returns addres_list of supplied argument
488 * arguments: array('to', 'from', ...) or just a string like 'to'.
489 * result: string: address1, addres2, ....
490 */
491
492 function setVar($var, $value) {
493 $this->{$var} = $value;
494 }
495
496 function getParameter($par) {
497 $value = strtolower($par);
498 if (isset($this->parameters[$par])) {
499 return $this->parameters[$par];
500 }
501 return '';
502 }
503
504 function setParameter($parameter, $value) {
505 $this->parameters[strtolower($parameter)] = $value;
506 }
507 }
508
509
510
511 class address_structure {
512 var $personal = '', $adl = '', $mailbox = '', $host = '', $group = '';
513
514 function getAddress($full=true) {
515 if (is_object($this)) {
516 if (isset($this->host) && $this->host !='') {
517 $email = $this->mailbox.'@'.$this->host;
518 } else {
519 $email = $this->mailbox;
520 }
521 if (trim($this->personal) !='') {
522 if ($email) {
523 $addr = '"' . $this->personal . '" <' .$email.'>';
524 } else {
525 $addr = $this->personal;
526 }
527 $best_dpl = $this->personal;
528 } else {
529 $addr = $email;
530 $best_dpl = $email;
531 }
532 if ($full) {
533 return $addr;
534 } else {
535 return $best_dpl;
536 }
537 } else return '';
538 }
539 }
540
541 class message {
542 /** message is the object that contains messages. It is a recursive
543 object in that through the $entities variable, it can contain
544 more objects of type message. See documentation in mime.txt for
545 a better description of how this works.
546 **/
547 var $rfc822_header = '',
548 $mime_header = '',
549 $flags = '',
550 $type0='',
551 $type1='',
552 $entities = array(),
553 $parent_ent, $entity,
554 $parent = '', $decoded_body='',
555 $is_seen = 0, $is_answered = 0, $is_deleted = 0, $is_flagged = 0,
556 $is_mdnsent = 0,
557 $body_part = '';
558
559 function setEnt($ent) {
560 $this->entity_id= $ent;
561 }
562
563 function addEntity ($msg) {
564 $msg->parent = &$this;
565 $this->entities[] = $msg;
566 }
567
568 function addRFC822Header($read) {
569 $header = new msg_header();
570 $this->header = sqimap_parse_RFC822Header($read,$header);
571 }
572
573 function getEntity($ent) {
574
575 $cur_ent = $this->entity_id;
576 $msg = $this;
577 if ($cur_ent == '' || $cur_ent == '0') {
578 $cur_ent_a = array();
579 } else {
580 $cur_ent_a = explode('.',$this->entity_id);
581 }
582 $ent_a = explode('.',$ent);
583
584 $cnt = count($ent_a);
585
586 for ($i=0;$i<$cnt -1;$i++) {
587 if (isset($cur_ent_a[$i]) && $cur_ent_a[$i] != $ent_a[$i]) {
588 $msg = $msg->parent;
589 $cur_ent_a = explode('.',$msg->entity_id);
590 $i--;
591 } else if (!isset($cur_ent_a[$i])) {
592 if (isset($msg->entities[($ent_a[$i]-1)])) {
593 $msg = $msg->entities[($ent_a[$i]-1)];
594 } else {
595 $msg = $msg->entities[0];
596 }
597 }
598 if ($msg->type0 == 'message' && $msg->type1 == 'rfc822') {
599 /*this is a header for a message/rfc822 entity */
600 $msg = $msg->entities[0];
601 }
602 }
603
604 if ($msg->type0 == 'message' && $msg->type1 == 'rfc822') {
605 /*this is a header for a message/rfc822 entity */
606 $msg = $msg->entities[0];
607 }
608
609 if (isset($msg->entities[($ent_a[$cnt-1])-1])) {
610 $msg = $msg->entities[($ent_a[$cnt-1]-1)];
611 }
612
613 return $msg;
614 }
615
616 function setBody($s) {
617 $this->body_part = $s;
618 }
619
620 function clean_up() {
621 $msg = $this;
622 $msg->body_part = '';
623 $i=0;
624 while ( isset($msg->entities[$i])) {
625 $msg->entities[$i]->clean_up();
626 $i++;
627 }
628 }
629
630 function getMailbox() {
631 $msg = $this;
632 while (is_object($msg->parent)) {
633 $msg = $msg->parent;
634 }
635 return $msg->mailbox;
636 }
637
638 /*
639 * Bodystructure parser, a recursive function for generating the
640 * entity-tree with all the mime-parts.
641 *
642 * It follows RFC2060 and stores all the described fields in the
643 * message object.
644 *
645 * Question/Bugs:
646 *
647 * Ask for me (Marc Groot Koerkamp, stekkel@users.sourceforge.net.
648 *
649 */
650 function &parseStructure($read, $i=0, $message = false) {
651 $arg_no = 0;
652 $arg_a = array();
653 $cnt = strlen($read);
654 while ($i < $cnt) {
655 $char = strtoupper($read{$i});
656 switch ($char) {
657 case '(':
658 if ($arg_no == 0 ) {
659 if (!isset($msg)) {
660 $msg = new message();
661 $hdr = new msg_header();
662 $hdr->type0 = 'text';
663 $hdr->type1 = 'plain';
664 $hdr->encoding = 'us-ascii';
665
666 if ($this->type0 == 'message' && $this->type1 == 'rfc822') {
667 $msg->entity_id = $this->entity_id .'.0'; /* header of message/rfc822 */
668 } else if (isset($this->entity_id) && $this->entity_id !='') {
669 $ent_no = count($this->entities)+1;
670 $par_ent = substr($this->entity_id,-2);
671 if ($par_ent{0} == '.') {
672 $par_ent = $par_ent{1};
673 }
674 if ($par_ent == '0') {
675 $ent_no = count($this->entities)+1;
676 if ($ent_no > 0) {
677 $ent = substr($this->entity_id,0,strrpos($this->entity_id,'.'));
678 if ($ent) {
679 $ent = $ent . ".$ent_no";
680 } else {
681 $ent = $ent_no;
682 }
683 $msg->entity_id = $ent;
684 } else {
685 $msg->entity_id = $ent_no;
686 }
687 } else {
688 $ent = $this->entity_id . ".$ent_no";
689 $msg->entity_id = $ent;
690 }
691 } else {
692 $msg->entity_id = '0';
693 }
694 } else {
695 $msg->header->type0 = 'multipart';
696 $msg->type0 = 'multipart';
697 while ($read{$i} == '(') {
698 $msg->addEntity($msg->parseStructure($read,&$i));
699 }
700 }
701 } else {
702 switch ($arg_no)
703 {
704 case 1:
705 /* multipart properties */
706 $i++;
707 $arg_a[] = $this->parseProperties($read,&$i);
708 $arg_no++;
709 break;
710 case 2:
711 if (isset($msg->type0) && $msg->type0 == 'multipart') {
712 $i++;
713 $arg_a[]= $msg->parseDisposition($read,&$i);
714 } else { /* properties */
715 /* properties */
716 $arg_a[] = $msg->parseProperties($read,&$i);
717 }
718 $arg_no++;
719 break;
720 case 3:
721 if (isset($msg->type0) && $msg->type0 == 'multipart') {
722 $i++;
723 $arg_a[]= $msg->parseLanguage($read,&$i);
724 }
725 case 7:
726 if ($arg_a[0] == 'message' && $arg_a[1] == 'rfc822') {
727
728 $msg->header->type0 = $arg_a[0];
729 $msg->type0 = $arg_a[0];
730
731 $msg->header->type1 = $arg_a[1];
732 $msg->type1 = $arg_a[1];
733 $rfc822_hdr = new rfc822_header();
734 $msg->parseEnvelope($read,&$i,&$rfc822_hdr);
735 $msg->rfc822_header = $rfc822_hdr;
736 $i++;
737 while ($i < $cnt && $read{$i} != '(') {
738 $i++;
739 }
740 $msg->addEntity($msg->parseStructure($read,&$i));
741 }
742 break;
743 case 8:
744 $i++;
745 $arg_a[] = $msg->parseDisposition($read,&$i);
746 $arg_no++;
747 break;
748 case 9:
749 if ($arg_a[0] == 'text' ||
750 ($arg_a[0] == 'message' && $arg_a[1] == 'rfc822')) {
751 $i++;
752 $arg_a[] = $msg->parseDisposition($read,&$i);
753 } else {
754 $i++;
755 $arg_a[] = $msg->parseLanguage($read,&$i);
756 }
757 $arg_no++;
758 break;
759 case 10:
760 if ($arg_a[0] == 'text' ||
761 ($arg_a[0] == 'message' && $arg_a[1] == 'rfc822')) {
762 $i++;
763 $arg_a[] = $msg->parseLanguage($read,&$i);
764 } else {
765 $msg->parseParenthesis($read,&$i);
766 $arg_a[] = ''; /* not yet desribed in rfc2060 */
767 }
768 $arg_no++;
769 break;
770 default:
771 /* unknown argument, skip this part */
772 $msg->parseParenthesis($read,&$i);
773 $arg_a[] = '';
774 $arg_no++;
775 break;
776 } /* switch */
777 }
778 break;
779 case '"':
780 /* inside an entity -> start processing */
781 $debug = substr($read,$i,20);
782 $arg_s = $msg->parseQuote($read,&$i);
783 $arg_no++;
784 if ($arg_no < 3) $arg_s = strtolower($arg_s); /* type0 and type1 */
785 $arg_a[] = $arg_s;
786 break;
787 case 'n':
788 case 'N':
789 /* probably NIL argument */
790 if (strtoupper(substr($read,$i,4)) == 'NIL ' ||
791 strtoupper(substr($read,$i,4)) == 'NIL)') {
792 $arg_a[] = '';
793 $arg_no++;
794 $i = $i+2;
795 }
796 break;
797 case '{':
798 /* process the literal value */
799 $arg_a[] = $msg->parseLiteral($read,&$i);
800 $arg_no++;
801 break;
802 case (is_numeric($read{$i}) ):
803 /* process integers */
804 if ($read{$i} == ' ') break;
805 $arg_s = $read{$i};;
806 $i++;
807 while (preg_match('/^[0-9]{1}$/',$read{$i})) {
808 $arg_s .= $read{$i};
809 $i++;
810 }
811 $arg_no++;
812 $arg_a[] = $arg_s;
813 break;
814 case ')':
815 if (isset($msg->type0) && $msg->type0 == 'multipart') {
816 $multipart = true;
817 } else {
818 $multipart = false;
819 }
820 if (!$multipart) {
821 if ($arg_a[0] == 'text' ||
822 ($arg_a[0] == 'message' && $arg_a[1] == 'rfc822')) {
823 $shifted_args = true;
824 } else {
825 $shifted_args = false;
826 }
827 $hdr->type0 = $arg_a[0];
828 $hdr->type1 = $arg_a[1];
829
830 $msg->type0 = $arg_a[0];
831 $msg->type1 = $arg_a[1];
832
833 $arr = $arg_a[2];
834 if (is_array($arr)) {
835 $hdr->parameters = $arg_a[2];
836 }
837 $hdr->id = str_replace( '<', '', str_replace( '>', '', $arg_a[3] ) );
838 $hdr->description = $arg_a[4];
839 $hdr->encoding = strtolower($arg_a[5]);
840 $hdr->entity_id = $msg->entity_id;
841 $hdr->size = $arg_a[6];
842 if ($shifted_args) {
843 $hdr->lines = $arg_a[7];
844 if (isset($arg_a[8])) {
845 $hdr->md5 = $arg_a[8];
846 }
847 if (isset($arg_a[9])) {
848 $hdr->disposition = $arg_a[9];
849 }
850 if (isset($arg_a[10])) {
851 $hdr->language = $arg_a[10];
852 }
853 } else {
854 if (isset($arg_a[7])) {
855 $hdr->md5 = $arg_a[7];
856 }
857 if (isset($arg_a[8])) {
858 $hdr->disposition = $arg_a[8];
859 }
860 if (isset($arg_a[9])) {
861 $hdr->language = $arg_a[9];
862 }
863 }
864 $msg->header = $hdr;
865 $arg_no = 0;
866 $i++;
867 if (substr($msg->entity_id,-2) == '.0' && $msg->type0 !='multipart') {
868 $msg->entity_id++;
869 }
870 return $msg;
871 } else {
872 $hdr->type0 = 'multipart';
873 $hdr->type1 = $arg_a[0];
874
875 $msg->type0 = 'multipart';
876 $msg->type1 = $arg_a[0];
877 if (is_array($arg_a[1])) {
878 $hdr->parameters = $arg_a[1];
879 }
880 if (isset($arg_a[2])) {
881 $hdr->disposition = $arg_a[2];
882 }
883 if (isset($arg_a[3])) {
884 $hdr->language = $arg_a[3];
885 }
886 $msg->header = $hdr;
887 return $msg;
888 }
889 default:
890 break;
891 } /* switch */
892 $i++;
893 } /* while */
894 } /* parsestructure */
895
896 function parseProperties($read, $i) {
897 $properties = array();
898 $arg_s = '';
899 $prop_name = '';
900 while ($read{$i} != ')') {
901 if ($read{$i} == '"') {
902 $arg_s = $this->parseQuote($read,&$i);
903 } else if ($read{$i} == '{') {
904 $arg_s = $this->parseLiteral($read,&$i);
905 }
906 if ($prop_name == '' && $arg_s) {
907 $prop_name = strtolower($arg_s);
908 $properties[$prop_name] = '';
909 $arg_s = '';
910 } elseif ($prop_name != '' && $arg_s != '') {
911 $properties[$prop_name] = $arg_s;
912 $prop_name = '';
913 $arg_s = '';
914 }
915 $i++;
916 }
917 return $properties;
918 }
919
920 function parseEnvelope($read, $i, $hdr) {
921 $arg_no = 0;
922 $arg_a = array();
923 $cnt = strlen($read);
924 while ($i< $cnt && $read{$i} != ')') {
925 $i++;
926 $char = strtoupper($read{$i});
927 switch ($char)
928 {
929 case '"':
930 $arg_a[] = $this->parseQuote($read,&$i);
931 $arg_no++;
932 break;
933 case '{':
934 $arg_a[] = $this->parseLiteral($read,&$i);
935 $arg_no++;
936 break;
937 case 'N':
938 /* probably NIL argument */
939 if (strtoupper(substr($read,$i,3)) == 'NIL') {
940 $arg_a[] = '';
941 $arg_no++;
942 $i = $i+2;
943 }
944 break;
945 case '(':
946 /* Address structure
947 * With group support.
948 * Note: Group support is useless on SMTP connections
949 * because the protocol doesn't support it
950 */
951 $addr_a = array();
952 $group = '';
953 $a=0;
954 while ($i < $cnt && $read{$i} != ')') {
955 if ($read{$i} == '(') {
956 $addr = $this->parseAddress($read,&$i);
957 if ($addr->host == '' && $addr->mailbox != '') {
958 /* start of group */
959 $group = $addr->mailbox;
960 $group_addr = $addr;
961 $j = $a;
962 } elseif ($group && $addr->host == '' && $addr->mailbox == '') {
963 /* end group */
964 if ($a == $j+1) { /* no group members */
965 $group_addr->group = $group;
966 $group_addr->mailbox = '';
967 $group_addr->personal = "$group: Undisclosed recipients;";
968 $addr_a[] = $group_addr;
969 $group ='';
970 }
971 } else {
972 $addr->group = $group;
973 $addr_a[] = $addr;
974 }
975 $a++;
976 }
977 $i++;
978 }
979 $arg_a[] = $addr_a;
980 break;
981 default:
982 break;
983 }
984 $i++;
985 }
986 if (count($arg_a) > 9) {
987 /* argument 1: date */
988 $d = strtr($arg_a[0], array(' ' => ' '));
989 $d = explode(' ', $d);
990 $hdr->date = getTimeStamp($d);
991 /* argument 2: subject */
992 if (!trim($arg_a[1])) {
993 $arg_a[1]= _("(no subject)");
994 }
995 $hdr->subject = $arg_a[1];
996 /* argument 3: from */
997 $hdr->from = $arg_a[2][0];
998 /* argument 4: sender */
999 $hdr->sender = $arg_a[3][0];
1000 /* argument 5: reply-to */
1001 $hdr->replyto = $arg_a[4][0];
1002 /* argument 6: to */
1003 $hdr->to = $arg_a[5];
1004 /* argument 7: cc */
1005 $hdr->cc = $arg_a[6];
1006 /* argument 8: bcc */
1007 $hdr->bcc = $arg_a[7];
1008 /* argument 9: in-reply-to */
1009 $hdr->inreplyto = $arg_a[8];
1010 /* argument 10: message-id */
1011 $hdr->message_id = $arg_a[9];
1012 }
1013 }
1014
1015 function parseLiteral($read, $i) {
1016 $lit_cnt = '';
1017 $i++;
1018 while ($read{$i} != '}') {
1019 $lit_cnt .= $read{$i};
1020 $i++;
1021 }
1022 $lit_cnt +=2; /* add the { and } characters */
1023 $s = '';
1024 for ($j = 0; $j < $lit_cnt; $j++) {
1025 $i++;
1026 $s .= $read{$i};
1027 }
1028 return $s;
1029 }
1030
1031 function parseQuote($read, $i) {
1032 $i++;
1033 $s = '';
1034 while ($read{$i} != '"') {
1035 if ($read{$i} == '\\') {
1036 $i++;
1037 }
1038 $s .= $read{$i};
1039 $i++;
1040 }
1041 return $s;
1042 }
1043
1044 function parseAddress($read, $i) {
1045 $arg_a = array();
1046 while ($read{$i} != ')' ) { //&& $i < count($read)) {
1047 $char = strtoupper($read{$i});
1048 switch ($char)
1049 {
1050 case '"':
1051 $arg_a[] = $this->parseQuote($read,&$i);
1052 break;
1053 case '{':
1054 $arg_a[] = $this->parseLiteral($read,&$i);
1055 break;
1056 case 'n':
1057 case 'N':
1058 if (strtoupper(substr($read,$i,3)) == 'NIL') {
1059 $arg_a[] = '';
1060 $i = $i+2;
1061 }
1062 break;
1063 default:
1064 break;
1065 }
1066 $i++;
1067 }
1068 if (count($arg_a) == 4) {
1069 $adr = new address_structure();
1070 $adr->personal = $arg_a[0];
1071 $adr->adl = $arg_a[1];
1072 $adr->mailbox = $arg_a[2];
1073 $adr->host = $arg_a[3];
1074 } else {
1075 $adr = '';
1076 }
1077 return $adr;
1078 }
1079
1080 function parseDisposition($read,&$i) {
1081 $arg_a = array();
1082 while ($read{$i} != ')') {
1083 switch ($read{$i})
1084 {
1085 case '"':
1086 $arg_a[] = $this->parseQuote($read,&$i);
1087 break;
1088 case '{':
1089 $arg_a[] = $this->parseLiteral($read,&$i);
1090 break;
1091 case '(':
1092 $arg_a[] = $this->parseProperties($read,&$i);
1093 break;
1094 default:
1095 break;
1096 }
1097 $i++;
1098 }
1099 if (isset($arg_a[0])) {
1100 $disp = new disposition($arg_a[0]);
1101 if (isset($arg_a[1])) {
1102 $disp->properties = $arg_a[1];
1103 }
1104 }
1105 if (is_object($disp)) {
1106 return $disp;
1107 }
1108 }
1109
1110 function parseLanguage($read,&$i) {
1111 /* no idea how to process this one without examples */
1112 $arg_a = array();
1113 while ($read{$i} != ')') {
1114 switch ($read{$i})
1115 {
1116 case '"':
1117 $arg_a[] = $this->parseQuote($read,&$i);
1118 break;
1119 case '{':
1120 $arg_a[] = $this->parseLiteral($read,&$i);
1121 break;
1122 case '(':
1123 $arg_a[] = $this->parseProperties($read,&$i);
1124 break;
1125 default:
1126 break;
1127 }
1128 $i++;
1129 }
1130 if (isset($arg_a[0])) {
1131 $lang = new language($arg_a[0]);
1132 if (isset($arg_a[1])) {
1133 $lang->properties = $arg_a[1];
1134 }
1135 }
1136 if (is_object($lang)) {
1137 return $lang;
1138 } else {
1139 return '';
1140 }
1141 }
1142
1143 function parseParenthesis($read,&$i) {
1144 while ($read{$i} != ')') {
1145 switch ($read{$i})
1146 {
1147 case '"':
1148 $this->parseQuote($read,&$i);
1149 break;
1150 case '{':
1151 $this->parseLiteral($read,&$i);
1152 break;
1153 case '(':
1154 $this->parseParenthesis($read,&$i);
1155 break;
1156 default:
1157 break;
1158 }
1159 $i++;
1160 }
1161 }
1162
1163 function findDisplayEntity ($entity = array(), $alt_order = array('text/plain','text/html')) {
1164 $found = false;
1165 $type = $this->type0.'/'.$this->type1;
1166 if ( $type == 'multipart/alternative') {
1167 $msg = $this->findAlternativeEntity($alt_order);
1168 if (count($msg->entities) == 0) {
1169 $entity[] = $msg->entity_id;
1170 } else {
1171 $msg->findDisplayEntity(&$entity, $alt_order);
1172 }
1173 $found = true;
1174 } else if ( $type == 'multipart/related') {
1175 $msgs = $this->findRelatedEntity();
1176 for ($i = 0; $i < count($msgs); $i++) {
1177 $msg = $msgs[$i];
1178 if (count($msg->entities) == 0) {
1179 $entity[] = $msg->entity_id;
1180 } else {
1181 $msg->findDisplayEntity(&$entity,$alt_order);
1182 }
1183 $found = true;
1184 }
1185 } else if ( $this->type0 == 'text' &&
1186 ( $this->type1 == 'plain' ||
1187 $this->type1 == 'html' ||
1188 $this->type1 == 'message') &&
1189 isset($this->entity_id) ) {
1190 if (count($this->entities) == 0) {
1191 if (strtolower($this->header->disposition->name) != 'attachment') {
1192 $entity[] = $this->entity_id;
1193 }
1194 }
1195 }
1196 $i = 0;
1197 while ( isset($this->entities[$i]) && !$found &&
1198 (strtolower($this->entities[$i]->header->disposition->name)
1199 != 'attachment') &&
1200 ($this->entities[$i]->type0 != 'message' &&
1201 $this->entities[$i]->type1 != 'rfc822' )
1202 )
1203 {
1204 $this->entities[$i]->findDisplayEntity(&$entity, $alt_order);
1205 $i++;
1206 }
1207
1208 if ( !isset($entity[0]) ) {
1209 $entity[]="";
1210 }
1211 return( $entity );
1212 }
1213
1214 function findAlternativeEntity ($alt_order) {
1215 /* if we are dealing with alternative parts then we choose the best
1216 * viewable message supported by SM.
1217 */
1218 $best_view = 0;
1219 $ent_id = 0;
1220 $k = 0;
1221 for ($i = 0; $i < count($this->entities); $i ++) {
1222 $type = $this->entities[$i]->header->type0.'/'.$this->entities[$i]->header->type1;
1223 if ($type == 'multipart/related') {
1224 $type = $this->entities[$i]->header->getParameter('type');
1225 }
1226 for ($j = $k; $j < count($alt_order); $j++) {
1227 if ($alt_order[$j] == $type && $j > $best_view) {
1228 $best_view = $j;
1229 $ent_id = $i;
1230 $k = $j;
1231 }
1232 }
1233 }
1234 return $this->entities[$ent_id];
1235 }
1236
1237 function findRelatedEntity () {
1238 $msgs = array();
1239 for ($i = 0; $i < count($this->entities); $i ++) {
1240 $type = $this->entities[$i]->header->type0.'/'.$this->entities[$i]->header->type1;
1241 if ($this->header->getParameter('type') == $type) {
1242 $msgs[] = $this->entities[$i];
1243 }
1244 }
1245 return $msgs;
1246 }
1247
1248 function getAttachments($exclude_id=array(), $result = array()) {
1249 if ($this->type0 == 'message' && $this->type1 == 'rfc822') {
1250 $this = $this->entities[0];
1251 }
1252 if (count($this->entities)) {
1253 foreach ($this->entities as $entity) {
1254 $exclude = false;
1255 foreach ($exclude_id as $excl) {
1256 if ($entity->entity_id == $excl) {
1257 $exclude = true;
1258 }
1259 }
1260 if (!$exclude) {
1261 if ($entity->type0 == 'multipart' &&
1262 $entity->type1 != 'related') {
1263 $result = $entity->getAttachments($exclude_id, $result);
1264 } else if ($entity->type0 != 'multipart') {
1265 $result[] = $entity;
1266 }
1267 }
1268 }
1269 } else {
1270 $exclude = false;
1271 foreach ($exclude_id as $excl) {
1272 if ($this->entity_id == $excl) {
1273 $exclude = true;
1274 }
1275 }
1276 if (!$exclude) {
1277 $result[] = $this;
1278 }
1279 }
1280 return $result;
1281 }
1282
1283 }
1284
1285 class disposition {
1286 function disposition($name) {
1287 $this->name = $name;
1288 $this->properties = array();
1289 }
1290 }
1291
1292 class language {
1293 function language($name) {
1294 $this->name = $name;
1295 $this->properties = array();
1296 }
1297 }
1298
1299 class content_type {
1300 var $type0='text',
1301 $type1='plain',
1302 $properties='';
1303 function content_type($type) {
1304 $pos = strpos($type,'/');
1305 if ($pos > 0) {
1306 $this->type0 = substr($type,0,$pos);
1307 $this->type1 = substr($type,$pos+1);
1308 } else {
1309 $this->type0 = $type;
1310 }
1311 $this->properties = array();
1312 }
1313 }
1314
1315 ?>