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;
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, -2));
69 $read = trim(substr ($read, 1));
71 if ($debug_mime) echo "<tt>$read</tt><br><br>";
73 $msg = mime_parse_structure ($read);
74 $msg->header
= $header;
78 function mime_parse_structure ($structure, $ent_id) {
80 if ($debug_mime) echo "<font color=008800><tt>START: mime_parse_structure()</tt></font><br>";
82 if (substr($structure, 0, 1) == "(") {
83 $ent_id = mime_new_element_level($ent_id);
86 if ($debug_mime) echo "<font color=008800><tt>Found entity...</tt></font><br>";
88 $end = mime_match_parenthesis ($start, $structure);
90 $element = substr($structure, $start+
1, ($end - $start)-1);
91 $ent_id = mime_increment_id($ent_id);
92 $newmsg = mime_parse_structure ($element, $ent_id);
93 $msg->addEntity ($newmsg);
94 } while (substr($structure, $end+
1, 1) == "(");
97 if ($debug_mime) echo "<br><font color=0000aa><tt>$structure</tt></font><br>";
98 $msg->header
= new msg_header();
99 $msg->header
= mime_get_element (&$structure, $header);
100 $msg->header
->entity_id
= $ent_id;
101 if ($debug_mime) echo "<br>";
104 if ($debug_mime) echo "<font color=008800><tt> END: mime_parse_structure()</tt></font><br>";
107 // Increments the element ID. An element id can look like any of
108 // the following: 1, 1.2, 4.3.2.4.1, etc. This function increments
109 // the last number of the element id, changing 1.2 to 1.3.
110 function mime_increment_id ($id) {
111 if (strpos($id, ".")) {
112 $first = substr($id, 0, strrpos($id, "."));
113 $last = substr($id, strlen($id) - strlen($first));
115 $new = $first . $last;
122 // See comment for mime_increment_id().
123 // This adds another level on to the entity_id changing 1.3 to 1.3.0
124 // NOTE: 1.3.0 is not a valid element ID. It MUST be incremented
125 // before it can be used. I left it this way so as not to have
126 // to make a special case if it is the first entity_id. It
127 // always increments it, and that works fine.
128 function mime_new_element_level ($id) {
136 function mime_get_element (&$structure, $header) {
140 while (strlen($structure) > 0) {
141 $structure = trim($structure);
142 $char = substr($structure, 0, 1);
144 if (substr($structure, 0, 3) == "nil") {
146 $structure = substr($structure, 3);
147 } else if ($char == "\"") {
148 // loop through until we find the matching quote, and return that as a string
150 $char = substr($structure, $pos, 1);
151 while ($char != "\"" && $pos < strlen($structure)) {
154 $char = substr($structure, $pos, 1);
156 $structure = substr($structure, strlen($text) +
2);
157 } else if ($char == "(") {
159 $end = mime_match_parenthesis (0, $structure);
160 $sub = substr($structure, 1, $end-1);
161 $properties = mime_get_props($properties, $sub);
162 $structure = substr($structure, strlen($sub) +
2);
164 // loop through until we find a space or an end parenthesis
166 $char = substr($structure, $pos, 1);
167 while ($char != " " && $char != ")" && $pos < strlen($structure)) {
170 $char = substr($structure, $pos, 1);
172 $structure = substr($structure, strlen($text));
174 if ($debug_mime) echo "<tt>$elem_num : $text</tt><br>";
176 // This is where all the text parts get put into the header
179 $header->type0
= $text;
180 if ($debug_mime) echo "<tt>type0 = $text</tt><br>";
183 $header->type1
= $text;
184 if ($debug_mime) echo "<tt>type1 = $text</tt><br>";
187 $header->encoding
= $text;
188 if ($debug_mime) echo "<tt>encoding = $text</tt><br>";
191 $header->size
= $text;
192 if ($debug_mime) echo "<tt>size = $text</tt><br>";
195 if ($header->type0
== "text" && $elem_num == 8) {
196 $header->num_lines
= $text;
197 if ($debug_mime) echo "<tt>num_lines = $text</tt><br>";
204 // loop through the additional properties and put those in the various headers
205 for ($i=0; $i < count($properties); $i++
) {
206 $header->{$properties[$i]["name"]} = $properties[$i]["value"];
207 if ($debug_mime) echo "<tt>".$properties[$i]["name"]." = " . $properties[$i]["value"] . "</tt><br>";
212 // I did most of the MIME stuff yesterday (June 20, 2000), but I couldn't
213 // figure out how to do this part, so I decided to go to bed. I woke up
214 // in the morning and had a flash of insight. I went to the white-board
215 // and scribbled it out, then spent a bit programming it, and this is the
216 // result. Nothing complicated, but I think my brain was fried yesterday.
218 // This gets properties in a nested parenthesisized list. For example,
219 // this would get passed something like: ("attachment" ("filename" "luke.tar.gz"))
220 // This returns an array called $props with all paired up properties.
221 // It ignores the "attachment" for now, maybe that should change later
222 // down the road. In this case, what is returned is:
223 // $props[0]["name"] = "filename";
224 // $props[0]["value"] = "luke.tar.gz";
225 function mime_get_props ($props, $structure) {
227 while (strlen($structure) > 0) {
228 $structure = trim($structure);
229 $char = substr($structure, 0, 1);
233 $char = substr($structure, $pos, 1);
234 while ($char != "\"" && $pos < strlen($structure)) {
237 $char = substr($structure, $pos, 1);
239 $structure = trim(substr($structure, strlen($tmp) +
2));
240 $char = substr($structure, 0, 1);
244 $char = substr($structure, $pos, 1);
245 while ($char != "\"" && $pos < strlen($structure)) {
248 $char = substr($structure, $pos, 1);
250 $structure = trim(substr($structure, strlen($tmp) +
2));
253 $props[$k]["name"] = $tmp;
254 $props[$k]["value"] = $value;
255 } else if ($char == "(") {
256 $end = mime_match_parenthesis (0, $structure);
257 $sub = substr($structure, 1, $end-1);
258 $props = mime_get_props($props, $sub);
259 $structure = substr($structure, strlen($sub) +
2);
262 } else if ($char == "(") {
263 $end = mime_match_parenthesis (0, $structure);
264 $sub = substr($structure, 1, $end-1);
265 $props = mime_get_props($props, $sub);
266 $structure = substr($structure, strlen($sub) +
2);
273 // Matches parenthesis. It will return the position of the matching
274 // parenthesis in $structure. For instance, if $structure was:
275 // ("text" "plain" ("val1name", "1") nil ... )
277 // then this would return 42 to match up those two.
278 function mime_match_parenthesis ($pos, $structure) {
279 $char = substr($structure, $pos, 1);
281 // ignore all extra characters
282 while ($pos < strlen($structure)) {
284 $char = substr($structure, $pos, 1);
287 } else if ($char == "(") {
288 $pos = mime_match_parenthesis ($pos, $structure);
293 function mime_fetch_body ($imap_stream, $id, $ent_id) {
294 // do a bit of error correction. If we couldn't find the entity id, just guess
295 // that it is the first one. That is usually the case anyway.
296 if (!$ent_id) $ent_id = 1;
298 fputs ($imap_stream, "a001 FETCH $id BODY[$ent_id]\r\n");
299 $topline = fgets ($imap_stream, 1024);
300 $size = substr ($topline, strpos($topline, "{")+
1);
301 $size = substr ($size, 0, strpos($size, "}"));
302 $read = fread ($imap_stream, $size);
306 /* -[ END MIME DECODING ]----------------------------------------------------------- */
310 /** This is the first function called. It decides if this is a multipart
311 message or if it should be handled as a single entity
313 function decodeMime ($body, $header) {
314 global $username, $key, $imapServerAddress, $imapPort;
315 $imap_stream = sqimap_login($username, $key, $imapServerAddress, $imapPort, 0);
316 sqimap_mailbox_select($imap_stream, $header->mailbox
);
318 return mime_structure ($imap_stream, $header);
321 function getEntity ($message, $ent_id) {
323 if ($message->header
->entity_id
== $ent_id) {
326 for ($i = 0; $message->entities
[$i]; $i++
) {
327 $msg = getEntity ($message->entities
[$i], $ent_id);
335 function findDisplayEntity ($message) {
337 if ($message->header
->type0
== "text") {
338 if ($message->header
->type1
== "plain" ||
339 $message->header
->type1
== "html") {
340 return $message->header
->entity_id
;
343 for ($i=0; $message->entities
[$i]; $i++
) {
344 return findDisplayEntity($message->entities
[$i]);
350 /** This returns a parsed string called $body. That string can then
351 be displayed as the actual message in the HTML. It contains
352 everything needed, including HTML Tags, Attachments at the
355 function formatBody($message, $color, $wrap_at) {
356 /** this if statement checks for the entity to show as the
357 primary message. To add more of them, just put them in the
358 order that is their priority.
360 global $username, $key, $imapServerAddress, $imapPort;
363 $id = $message->header
->id
;
364 $urlmailbox = urlencode($message->header
->mailbox
);
366 $imap_stream = sqimap_login($username, $key, $imapServerAddress, $imapPort, 0);
367 sqimap_mailbox_select($imap_stream, $message->header
->mailbox
);
369 $ent_num = findDisplayEntity ($message);
370 $body = mime_fetch_body ($imap_stream, $id, $ent_num);
372 /** If there are other types that shouldn't be formatted, add
374 //if ($->type1 != "html") {
375 $body = translateText($body, $wrap_at, $charset);
378 $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>";
380 /** Display the ATTACHMENTS: message if there's more than one part **/
381 if ($message->entities
) {
382 $body .= "<TABLE WIDTH=100% CELLSPACING=0 CELLPADDING=4 BORDER=0><TR><TD BGCOLOR=\"$color[0]\">";
383 $body .= "<TT><B>ATTACHMENTS:</B></TT>";
384 $body .= "</TD></TR><TR><TD BGCOLOR=\"$color[0]\">";
387 /** make this recurisve at some point **/
388 $body .= formatAttachments ($message, $ent_num, $message->header
->mailbox
, $id);
389 $body .= "</TD></TR></TABLE>";
394 // A recursive function that returns a list of attachments with links
395 // to where to download these attachments
396 function formatAttachments ($message, $ent_id, $mailbox, $id) {
398 if (!$message->entities
) {
399 $type0 = strtolower($message->header
->type0
);
400 $type1 = strtolower($message->header
->type1
);
402 if ($message->header
->entity_id
!= $ent_id) {
403 $filename = $message->header
->filename
;
404 if (trim($filename) == "") {
405 $display_filename = "untitled-$ent_id";
407 $display_filename = $filename;
410 $urlMailbox = urlencode($mailbox);
411 $ent = urlencode($message->header
->entity_id
);
412 $body .= "<TT> <A HREF=\"../src/download.php?passed_id=$id&mailbox=$urlMailbox&passed_ent_id=$ent\">" . $display_filename . "</A> <SMALL>(TYPE: $type0/$type1)</SMALL></TT><BR>";
417 for ($i = 0; $i < count($message->entities
); $i++
) {
418 $body .= formatAttachments ($message->entities
[$i], $ent_id, $mailbox, $id);
426 /** this function decodes the body depending on the encoding type. **/
427 function decodeBody($body, $encoding) {
428 $encoding = strtolower($encoding);
430 if ($encoding == "quoted-printable") {
431 $body = quoted_printable_decode($body);
433 while (ereg("=\n", $body))
434 $body = ereg_replace ("=\n", "", $body);
435 } else if ($encoding == "base64") {
436 $body = base64_decode($body);
439 // All other encodings are returned raw.
444 // This functions decode strings that is encoded according to
445 // RFC1522 (MIME Part Two: Message Header Extensions for Non-ASCII Text).
446 function decodeHeader ($string) {
447 if (eregi('=\?([^?]+)\?(q|b)\?([^?]+)\?=',
449 if (ucfirst($res[2]) == "B") {
450 $replace = base64_decode($res[3]);
452 $replace = ereg_replace("_", " ", $res[3]);
453 $replace = quoted_printable_decode($replace);
456 $replace = charset_decode ($res[1], $replace);
458 $string = eregi_replace
459 ('=\?([^?]+)\?(q|b)\?([^?]+)\?=',
461 // In case there should be more encoding in the string: recurse
462 return (decodeHeader($string));
467 // Encode a string according to RFC 1522 for use in headers if it
468 // contains 8-bit characters
469 function encodeHeader ($string) {
470 global $default_charset;
472 // Encode only if the string contains 8-bit characters
473 if (ereg("[\200-\377]", $string)) {
474 $newstring = "=?$default_charset?Q?";
475 $newstring .= str_replace(" ", "_", $string);
477 while (ereg("([\200-\377])", $newstring, $regs)) {
479 $insert = "=" . bin2hex($replace);
480 $newstring = str_replace($replace, $insert, $newstring);