4 ** This contains the functions necessary to detect and decode MIME
12 if (!isset($i18n_php))
13 include "../functions/i18n.php";
14 if (!isset($imap_php))
15 include "../functions/imap.php";
16 if (!isset($config_php))
17 include "../config/config.php";
20 /** Setting up the object that has the structure for the message **/
23 /** msg_header contains generic variables for values that **/
24 /** could be in a header. **/
26 var $type0, $type1, $boundary, $charset, $encoding;
27 var $to, $from, $date, $cc, $bcc, $reply_to, $subject;
28 var $id, $mailbox, $description;
33 /** message is the object that contains messages. It is a recursive
34 object in that through the $entities variable, it can contain
35 more objects of type message. See documentation in mime.txt for
36 a better description of how this works.
41 function addEntity ($msg) {
42 $this->entities
[count($this->entities
)] = $msg;
48 /* --------------------------------------------------------------------------------- */
50 /* --------------------------------------------------------------------------------- */
52 /** This function gets the structure of a message and stores it in the "message" class.
53 It will return this object for use with all relevant header information and
54 fully parsed into the standard "message" object format.
56 function mime_structure ($imap_stream, $header) {
58 sqimap_messages_flag ($imap_stream, $header->id
, $header->id
, "Seen");
61 fputs ($imap_stream, "a001 FETCH $id BODYSTRUCTURE\r\n");
62 $read = fgets ($imap_stream, 10000);
63 $read = strtolower($read);
65 if ($debug_mime) echo "<tt>$read</tt><br><br>";
66 // isolate the body structure and remove beginning and end parenthesis
67 $read = trim(substr ($read, strpos($read, "bodystructure") +
13));
68 $read = trim(substr ($read, 0, -1));
69 $end = mime_match_parenthesis(0, $read);
70 while ($end == strlen($read)-1) {
71 $read = trim(substr ($read, 0, -1));
72 $read = trim(substr ($read, 1));
73 $end = mime_match_parenthesis(0, $read);
76 if ($debug_mime) echo "<tt>$read</tt><br><br>";
78 $msg = mime_parse_structure ($read);
79 $msg->header
= $header;
83 function mime_parse_structure ($structure, $ent_id) {
85 if ($debug_mime) echo "<font color=008800><tt>START: mime_parse_structure()</tt></font><br>";
87 if (substr($structure, 0, 1) == "(") {
88 $ent_id = mime_new_element_level($ent_id);
90 if ($debug_mime) echo "<br><font color=0000aa><tt>$structure</tt></font><br>";
92 if ($debug_mime) echo "<font color=008800><tt>Found entity...</tt></font><br>";
94 $end = mime_match_parenthesis ($start, $structure);
96 $element = substr($structure, $start+
1, ($end - $start)-1);
97 $ent_id = mime_increment_id ($ent_id);
98 $newmsg = mime_parse_structure ($element, $ent_id);
99 $msg->addEntity ($newmsg);
100 } while (substr($structure, $end+
1, 1) == "(");
102 // parse the elements
103 if ($debug_mime) echo "<br><font color=0000aa><tt>$structure</tt></font><br>";
104 $msg = mime_get_element (&$structure, $msg, $ent_id);
105 if ($debug_mime) echo "<br>";
108 if ($debug_mime) echo "<font color=008800><tt> END: mime_parse_structure()</tt></font><br>";
111 // Increments the element ID. An element id can look like any of
112 // the following: 1, 1.2, 4.3.2.4.1, etc. This function increments
113 // the last number of the element id, changing 1.2 to 1.3.
114 function mime_increment_id ($id) {
116 if (strpos($id, ".")) {
117 $first = substr($id, 0, strrpos($id, "."));
118 $last = substr($id, strrpos($id, ".")+
1);
120 $new = $first . "." .$last;
124 if ($debug_mime) echo "<b>INCREMENT: $new</b><br>";
128 // See comment for mime_increment_id().
129 // This adds another level on to the entity_id changing 1.3 to 1.3.0
130 // NOTE: 1.3.0 is not a valid element ID. It MUST be incremented
131 // before it can be used. I left it this way so as not to have
132 // to make a special case if it is the first entity_id. It
133 // always increments it, and that works fine.
134 function mime_new_element_level ($id) {
136 else $id = $id . ".0";
141 function mime_get_element (&$structure, $msg, $ent_id) {
144 $msg->header
= new msg_header();
145 $msg->header
->entity_id
= $ent_id;
147 while (strlen($structure) > 0) {
148 $structure = trim($structure);
149 $char = substr($structure, 0, 1);
151 if (substr($structure, 0, 3) == "nil") {
153 $structure = substr($structure, 3);
154 } else if ($char == "\"") {
155 // loop through until we find the matching quote, and return that as a string
157 $char = substr($structure, $pos, 1);
158 while ($char != "\"" && $pos < strlen($structure)) {
161 $char = substr($structure, $pos, 1);
163 $structure = substr($structure, strlen($text) +
2);
164 } else if ($char == "(") {
166 $end = mime_match_parenthesis (0, $structure);
167 $sub = substr($structure, 1, $end-1);
168 $properties = mime_get_props($properties, $sub);
169 $structure = substr($structure, strlen($sub) +
2);
171 // loop through until we find a space or an end parenthesis
173 $char = substr($structure, $pos, 1);
174 while ($char != " " && $char != ")" && $pos < strlen($structure)) {
177 $char = substr($structure, $pos, 1);
179 $structure = substr($structure, strlen($text));
181 if ($debug_mime) echo "<tt>$elem_num : $text</tt><br>";
183 // This is where all the text parts get put into the header
186 $msg->header
->type0
= $text;
187 if ($debug_mime) echo "<tt>type0 = $text</tt><br>";
190 $msg->header
->type1
= $text;
191 if ($debug_mime) echo "<tt>type1 = $text</tt><br>";
194 $msg->header
->description
= $text;
195 if ($debug_mime) echo "<tt>description = $text</tt><br>";
198 $msg->header
->encoding
= $text;
199 if ($debug_mime) echo "<tt>encoding = $text</tt><br>";
202 $msg->header
->size
= $text;
203 if ($debug_mime) echo "<tt>size = $text</tt><br>";
206 if ($msg->header
->type0
== "text" && $elem_num == 8) {
207 $msg->header
->num_lines
= $text;
208 if ($debug_mime) echo "<tt>num_lines = $text</tt><br>";
209 } else if ($msg->header
->type0
== "message" && $msg->header
->type1
== "rfc822" && $elem_num == 8) {
210 // This is an encapsulated message, so lets start all over again and
211 // parse this message adding it on to the existing one.
212 $structure = trim($structure);
213 if (substr($structure, 0, 1) == "(") {
214 $e = mime_match_parenthesis (0, $structure);
215 $structure = substr($structure, 0, $e);
216 $structure = substr($structure, 1);
217 $m = mime_parse_structure($structure, $msg->header
->entity_id
);
218 if (substr($structure, 1, 1) != "(")
219 $m->header
->entity_id
= mime_increment_id(mime_new_element_level($ent_id));
221 for ($i=0; $i < count($m->entities
); $i++
) {
222 //echo "<big>TYPE: $i - ".$m->entities[$i]->header->type0." - ".$m->entities[$i]->header->type1."</big><br>";
223 $msg->addEntity($m->entities
[$i]);
226 //echo "<big>TYPE: ".$m->header->type0." - ".$m->header->type1."</big><br>";
237 // loop through the additional properties and put those in the various headers
238 if ($msg->header
->type0
!= "message") {
239 for ($i=0; $i < count($properties); $i++
) {
240 $msg->header
->{$properties[$i]["name"]} = $properties[$i]["value"];
241 if ($debug_mime) echo "<tt>".$properties[$i]["name"]." = " . $properties[$i]["value"] . "</tt><br>";
247 // I did most of the MIME stuff yesterday (June 20, 2000), but I couldn't
248 // figure out how to do this part, so I decided to go to bed. I woke up
249 // in the morning and had a flash of insight. I went to the white-board
250 // and scribbled it out, then spent a bit programming it, and this is the
251 // result. Nothing complicated, but I think my brain was fried yesterday.
253 // This gets properties in a nested parenthesisized list. For example,
254 // this would get passed something like: ("attachment" ("filename" "luke.tar.gz"))
255 // This returns an array called $props with all paired up properties.
256 // It ignores the "attachment" for now, maybe that should change later
257 // down the road. In this case, what is returned is:
258 // $props[0]["name"] = "filename";
259 // $props[0]["value"] = "luke.tar.gz";
260 function mime_get_props ($props, $structure) {
262 while (strlen($structure) > 0) {
263 $structure = trim($structure);
264 $char = substr($structure, 0, 1);
268 $char = substr($structure, $pos, 1);
269 while ($char != "\"" && $pos < strlen($structure)) {
272 $char = substr($structure, $pos, 1);
274 $structure = trim(substr($structure, strlen($tmp) +
2));
275 $char = substr($structure, 0, 1);
279 $char = substr($structure, $pos, 1);
280 while ($char != "\"" && $pos < strlen($structure)) {
283 $char = substr($structure, $pos, 1);
285 $structure = trim(substr($structure, strlen($tmp) +
2));
288 $props[$k]["name"] = $tmp;
289 $props[$k]["value"] = $value;
290 } else if ($char == "(") {
291 $end = mime_match_parenthesis (0, $structure);
292 $sub = substr($structure, 1, $end-1);
293 $props = mime_get_props($props, $sub);
294 $structure = substr($structure, strlen($sub) +
2);
297 } else if ($char == "(") {
298 $end = mime_match_parenthesis (0, $structure);
299 $sub = substr($structure, 1, $end-1);
300 $props = mime_get_props($props, $sub);
301 $structure = substr($structure, strlen($sub) +
2);
309 // Matches parenthesis. It will return the position of the matching
310 // parenthesis in $structure. For instance, if $structure was:
311 // ("text" "plain" ("val1name", "1") nil ... )
313 // then this would return 42 to match up those two.
314 function mime_match_parenthesis ($pos, $structure) {
315 $char = substr($structure, $pos, 1);
317 // ignore all extra characters
318 while ($pos < strlen($structure)) {
320 $char = substr($structure, $pos, 1);
323 } else if ($char == "(") {
324 $pos = mime_match_parenthesis ($pos, $structure);
329 function mime_fetch_body ($imap_stream, $id, $ent_id) {
330 // do a bit of error correction. If we couldn't find the entity id, just guess
331 // that it is the first one. That is usually the case anyway.
332 if (!$ent_id) $ent_id = 1;
334 fputs ($imap_stream, "a001 FETCH $id BODY[$ent_id]\r\n");
335 $topline = fgets ($imap_stream, 1024);
336 $size = substr ($topline, strpos($topline, "{")+
1);
337 $size = substr ($size, 0, strpos($size, "}"));
338 $read = fread ($imap_stream, $size);
342 /* -[ END MIME DECODING ]----------------------------------------------------------- */
346 /** This is the first function called. It decides if this is a multipart
347 message or if it should be handled as a single entity
349 function decodeMime ($body, $header) {
350 global $username, $key, $imapServerAddress, $imapPort;
351 $imap_stream = sqimap_login($username, $key, $imapServerAddress, $imapPort, 0);
352 sqimap_mailbox_select($imap_stream, $header->mailbox
);
354 return mime_structure ($imap_stream, $header);
357 function listEntities ($message) {
359 if ($message->header
->entity_id
)
360 echo "<tt>" . $message->header
->entity_id
. " : " . $message->header
->type0
. "/" . $message->header
->type1
. "<br>";
361 for ($i = 0; $message->entities
[$i]; $i++
) {
362 $msg = listEntities($message->entities
[$i], $ent_id);
369 function getEntity ($message, $ent_id) {
371 if ($message->header
->entity_id
== $ent_id && strlen($ent_id) == strlen($message->header
->entity_id
)) {
374 for ($i = 0; $message->entities
[$i]; $i++
) {
375 $msg = getEntity ($message->entities
[$i], $ent_id);
383 function findDisplayEntity ($message) {
385 if ($message->header
->type0
== "text") {
386 if ($message->header
->type1
== "plain" ||
387 $message->header
->type1
== "html") {
388 return $message->header
->entity_id
;
391 for ($i=0; $message->entities
[$i]; $i++
) {
392 return findDisplayEntity($message->entities
[$i]);
398 /** This returns a parsed string called $body. That string can then
399 be displayed as the actual message in the HTML. It contains
400 everything needed, including HTML Tags, Attachments at the
403 function formatBody($message, $color, $wrap_at) {
404 /** this if statement checks for the entity to show as the
405 primary message. To add more of them, just put them in the
406 order that is their priority.
408 global $username, $key, $imapServerAddress, $imapPort;
411 $id = $message->header
->id
;
412 $urlmailbox = urlencode($message->header
->mailbox
);
414 $imap_stream = sqimap_login($username, $key, $imapServerAddress, $imapPort, 0);
415 sqimap_mailbox_select($imap_stream, $message->header
->mailbox
);
417 $ent_num = findDisplayEntity ($message);
418 $body = mime_fetch_body ($imap_stream, $id, $ent_num);
420 /** If there are other types that shouldn't be formatted, add
422 //if ($->type1 != "html") {
423 $body = translateText($body, $wrap_at, $charset);
426 $body .= "<BR><SMALL><CENTER><A HREF=\"../src/download.php?absolute_dl=true&passed_id=$id&passed_ent_id=$ent_num&mailbox=$urlmailbox\">". _("Download this as a file") ."</A></CENTER><BR></SMALL>";
428 /** Display the ATTACHMENTS: message if there's more than one part **/
429 if ($message->entities
) {
430 $body .= "<TABLE WIDTH=100% CELLSPACING=0 CELLPADDING=4 BORDER=0><TR><TD BGCOLOR=\"$color[0]\">";
431 $body .= "<TT><B>ATTACHMENTS:</B></TT>";
432 $body .= "</TD></TR><TR><TD BGCOLOR=\"$color[0]\">";
435 /** make this recurisve at some point **/
436 $body .= formatAttachments ($message, $ent_num, $message->header
->mailbox
, $id);
437 $body .= "</TD></TR></TABLE>";
442 // A recursive function that returns a list of attachments with links
443 // to where to download these attachments
444 function formatAttachments ($message, $ent_id, $mailbox, $id) {
446 if (!$message->entities
) {
447 $type0 = strtolower($message->header
->type0
);
448 $type1 = strtolower($message->header
->type1
);
450 if ($message->header
->entity_id
!= $ent_id) {
451 $filename = $message->header
->filename
;
452 if (trim($filename) == "") {
453 $display_filename = "untitled-".$message->header
->entity_id
;
455 $display_filename = $filename;
458 $urlMailbox = urlencode($mailbox);
459 $ent = urlencode($message->header
->entity_id
);
460 $body .= "<TT> <A HREF=\"../src/download.php?passed_id=$id&mailbox=$urlMailbox&passed_ent_id=$ent\">" . $display_filename . "</A> (TYPE: $type0/$type1)";
461 if ($message->header
->description
)
462 $body .= " <b>" . htmlspecialchars($message->header
->description
)."</b>";
463 $body .= "</TT><BR>";
468 for ($i = 0; $i < count($message->entities
); $i++
) {
469 $body .= formatAttachments ($message->entities
[$i], $ent_id, $mailbox, $id);
477 /** this function decodes the body depending on the encoding type. **/
478 function decodeBody($body, $encoding) {
479 $encoding = strtolower($encoding);
481 if ($encoding == "quoted-printable") {
482 $body = quoted_printable_decode($body);
484 while (ereg("=\n", $body))
485 $body = ereg_replace ("=\n", "", $body);
486 } else if ($encoding == "base64") {
487 $body = base64_decode($body);
490 // All other encodings are returned raw.
495 // This functions decode strings that is encoded according to
496 // RFC1522 (MIME Part Two: Message Header Extensions for Non-ASCII Text).
497 function decodeHeader ($string) {
498 if (eregi('=\?([^?]+)\?(q|b)\?([^?]+)\?=',
500 if (ucfirst($res[2]) == "B") {
501 $replace = base64_decode($res[3]);
503 $replace = ereg_replace("_", " ", $res[3]);
504 $replace = quoted_printable_decode($replace);
507 $replace = charset_decode ($res[1], $replace);
509 $string = eregi_replace
510 ('=\?([^?]+)\?(q|b)\?([^?]+)\?=',
512 // In case there should be more encoding in the string: recurse
513 return (decodeHeader($string));
518 // Encode a string according to RFC 1522 for use in headers if it
519 // contains 8-bit characters
520 function encodeHeader ($string) {
521 global $default_charset;
523 // Encode only if the string contains 8-bit characters
524 if (ereg("[\200-\377]", $string)) {
525 $newstring = "=?$default_charset?Q?";
526 $newstring .= str_replace(" ", "_", $string);
528 while (ereg("([\200-\377])", $newstring, $regs)) {
530 $insert = "=" . bin2hex($replace);
531 $newstring = str_replace($replace, $insert, $newstring);