X-Git-Url: https://vcs.fsf.org/?p=squirrelmail.git;a=blobdiff_plain;f=functions%2Fmime.php;h=278c0d95f429ab9b3968ae07ed05641b45ca3599;hp=701cea85127cb969e976766524f107b4cc66bfc8;hb=358f007e212ad7428914c505f44f9ac192465224;hpb=e4a256af2e1a80cddbbbd200ed68f7ea43c89a60 diff --git a/functions/mime.php b/functions/mime.php index 701cea85..278c0d95 100644 --- a/functions/mime.php +++ b/functions/mime.php @@ -4,18 +4,17 @@ ** This contains the functions necessary to detect and decode MIME ** messages. ** + ** $Id$ **/ + + if (defined('mime_php')) + return; + define('mime_php', true); + global $debug_mime; $debug_mime = false; - $mime_php = true; - - if (!isset($i18n_php)) - include "../functions/i18n.php"; - if (!isset($imap_php)) - include "../functions/imap.php"; - if (!isset($config_php)) - include "../config/config.php"; - + + include "../functions/imap.php"; /** Setting up the objects that have the structure for the message **/ @@ -23,10 +22,11 @@ /** msg_header contains generic variables for values that **/ /** could be in a header. **/ - var $type0, $type1, $boundary, $charset, $encoding; - var $to, $from, $date, $cc, $bcc, $reply_to, $subject; - var $id, $mailbox, $description; - var $entity_id, $message_id, $charset; + var $type0 = '', $type1 = '', $boundary = '', $charset = ''; + var $encoding = '', $size = 0, $to = array(), $from = '', $date = ''; + var $cc = array(), $bcc = array(), $reply_to = '', $subject = ''; + var $id = 0, $mailbox = '', $description = '', $filename = ''; + var $entity_id = 0, $message_id = 0, $name = ''; } class message { @@ -35,11 +35,11 @@ more objects of type message. See documentation in mime.txt for a better description of how this works. **/ - var $header; - var $entities; + var $header = ''; + var $entities = array(); function addEntity ($msg) { - $this->entities[count($this->entities)] = $msg; + $this->entities[] = $msg; } } @@ -58,13 +58,22 @@ $id = $header->id; fputs ($imap_stream, "a001 FETCH $id BODYSTRUCTURE\r\n"); + // + // This should use sqimap_read_data instead of reading it itself + // $read = fgets ($imap_stream, 10000); - $endline = fgets($imap_stream, 1024); - $read = strtolower($read); + $response = substr($read, 0, 4); + $bodystructure = ""; + while ($response != "a001") { + $bodystructure .= $read; + $read = fgets ($imap_stream, 10000); + $response = substr($read, 0, 4); + } + $read = $bodystructure; - if ($debug_mime) echo "$read

"; + if ($debug_mime) echo "$read

\n"; // isolate the body structure and remove beginning and end parenthesis - $read = trim(substr ($read, strpos($read, "bodystructure") + 13)); + $read = trim(substr ($read, strpos(strtolower($read), "bodystructure") + 13)); $read = trim(substr ($read, 0, -1)); $end = mime_match_parenthesis(0, $read); while ($end == strlen($read)-1) { @@ -73,7 +82,7 @@ $end = mime_match_parenthesis(0, $read); } - if ($debug_mime) echo "$read

"; + if ($debug_mime) echo "$read

\n"; $msg = mime_parse_structure ($read, 0); $msg->header = $header; @@ -89,7 +98,7 @@ // to mime_get_elements() function mime_parse_structure ($structure, $ent_id) { global $debug_mime; - if ($debug_mime) echo "START: mime_parse_structure()
"; + if ($debug_mime) echo "START: mime_parse_structure()
\n"; $msg = new message(); if (substr($structure, 0, 1) == "(") { $ent_id = mime_new_element_level($ent_id); @@ -108,11 +117,11 @@ } else { // parse the elements if ($debug_mime) echo "
$structure
"; - $msg = mime_get_element (&$structure, $msg, $ent_id); + $msg = mime_get_element ($structure, $msg, $ent_id); if ($debug_mime) echo "
"; } - return $msg; if ($debug_mime) echo "  END: mime_parse_structure()
"; + return $msg; } // Increments the element ID. An element id can look like any of @@ -150,18 +159,20 @@ $elem_num = 1; $msg->header = new msg_header(); $msg->header->entity_id = $ent_id; + $properties = array(); while (strlen($structure) > 0) { $structure = trim($structure); $char = substr($structure, 0, 1); - if (substr($structure, 0, 3) == "nil") { + if (strtolower(substr($structure, 0, 3)) == "nil") { $text = ""; $structure = substr($structure, 3); } else if ($char == "\"") { // loop through until we find the matching quote, and return that as a string $pos = 1; $char = substr($structure, $pos, 1); + $text = ""; while ($char != "\"" && $pos < strlen($structure)) { $text .= $char; $pos++; @@ -178,6 +189,7 @@ // loop through until we find a space or an end parenthesis $pos = 0; $char = substr($structure, $pos, 1); + $text = ""; while ($char != " " && $char != ")" && $pos < strlen($structure)) { $text .= $char; $pos++; @@ -190,20 +202,20 @@ // This is where all the text parts get put into the header switch ($elem_num) { case 1: - $msg->header->type0 = $text; - if ($debug_mime) echo "type0 = $text
"; + $msg->header->type0 = strtolower($text); + if ($debug_mime) echo "type0 = ".strtolower($text)."
"; break; case 2: - $msg->header->type1 = $text; - if ($debug_mime) echo "type1 = $text
"; + $msg->header->type1 = strtolower($text); + if ($debug_mime) echo "type1 = ".strtolower($text)."
"; break; case 5: $msg->header->description = $text; if ($debug_mime) echo "description = $text
"; break; case 6: - $msg->header->encoding = $text; - if ($debug_mime) echo "encoding = $text
"; + $msg->header->encoding = strtolower($text); + if ($debug_mime) echo "encoding = ".strtolower($text)."
"; break; case 7: $msg->header->size = $text; @@ -282,6 +294,7 @@ if ($char == "\"") { $pos = 1; $char = substr($structure, $pos, 1); + $tmp = ""; while ($char != "\"" && $pos < strlen($structure)) { $tmp .= $char; $pos++; @@ -293,6 +306,7 @@ if ($char == "\"") { $pos = 1; $char = substr($structure, $pos, 1); + $value = ""; while ($char != "\"" && $pos < strlen($structure)) { $value .= $char; $pos++; @@ -301,11 +315,13 @@ $structure = trim(substr($structure, strlen($tmp) + 2)); $k = count($props); - $props[$k]["name"] = $tmp; + $props[$k]["name"] = strtolower($tmp); $props[$k]["value"] = $value; } else if ($char == "(") { $end = mime_match_parenthesis (0, $structure); $sub = substr($structure, 1, $end-1); + if (! isset($props)) + $props = array(); $props = mime_get_props($props, $sub); $structure = substr($structure, strlen($sub) + 2); } @@ -331,15 +347,31 @@ $char = substr($structure, $pos, 1); // ignore all extra characters + // If inside of a string, skip string -- Boundary IDs and other + // things can have ) in them. + if ($char != '(') + return strlen($structure); while ($pos < strlen($structure)) { $pos++; $char = substr($structure, $pos, 1); if ($char == ")") { return $pos; + } else if ($char == '"') { + $pos ++; + while (substr($structure, $pos, 1) != '"' && + $pos < strlen($structure)) { + if (substr($structure, $pos, 2) == '\\"') + $pos ++; + elseif (substr($structure, $pos, 2) == '\\\\') + $pos ++; + $pos ++; + } } else if ($char == "(") { $pos = mime_match_parenthesis ($pos, $structure); } } + echo "Error decoding mime structure. Report this as a bug!
\n"; + return $pos; } function mime_fetch_body ($imap_stream, $id, $ent_id) { @@ -347,12 +379,63 @@ // that it is the first one. That is usually the case anyway. if (!$ent_id) $ent_id = 1; + fputs ($imap_stream, "a010 FETCH $id BODY[$ent_id]\r\n"); + $data = sqimap_read_data ($imap_stream, 'a010', true, $response, $message); + $topline = array_shift($data); + while (! ereg('\\* [0-9]+ FETCH ', $topline) && $data) + $topline = array_shift($data); + $wholemessage = implode('', $data); + + if (ereg('\\{([^\\}]*)\\}', $topline, $regs)) { + return substr($wholemessage, 0, $regs[1]); + } + else if (ereg('"([^"]*)"', $topline, $regs)) { + return $regs[1]; + } + + $str = "Body retrival error. Please report this bug!\n"; + $str .= "Response: $response\n"; + $str .= "Message: $message\n"; + $str .= "FETCH line: $topline"; + $str .= "---------------\n$wholemessage"; + foreach ($data as $d) + { + $str .= htmlspecialchars($d) . "\n"; + } + return $str; + } + + function mime_print_body_lines ($imap_stream, $id, $ent_id, $encoding) { + // do a bit of error correction. If we couldn't find the entity id, just guess + // that it is the first one. That is usually the case anyway. + if (!$ent_id) $ent_id = 1; + + // Don't kill the connection if the browser is over a dialup + // and it would take over 30 seconds to download it. + set_time_limit(0); + fputs ($imap_stream, "a001 FETCH $id BODY[$ent_id]\r\n"); - $topline = fgets ($imap_stream, 1024); - $size = substr ($topline, strpos($topline, "{")+1); - $size = substr ($size, 0, strpos($size, "}")); - $read = fread ($imap_stream, $size); - return $read; + $cnt = 0; + $continue = true; + $read = fgets ($imap_stream,4096); + // This could be bad -- if the section has 'a001 OK' + // or similar, it will kill the download. + while (!ereg("^a001 (OK|BAD|NO)(.*)$", $read, $regs)) { + if (trim($read) == ")==") { + $read1 = $read; + $read = fgets ($imap_stream,4096); + if (ereg("^a001 (OK|BAD|NO)(.*)$", $read, $regs)) { + return; + } else { + echo decodeBody($read1, $encoding); + echo decodeBody($read, $encoding); + } + } else if ($cnt) { + echo decodeBody($read, $encoding); + } + $read = fgets ($imap_stream,4096); + $cnt++; + } } /* -[ END MIME DECODING ]----------------------------------------------------------- */ @@ -362,7 +445,7 @@ /** This is the first function called. It decides if this is a multipart message or if it should be handled as a single entity **/ - function decodeMime ($imap_stream, $body, $header) { + function decodeMime ($imap_stream, &$header) { global $username, $key, $imapServerAddress, $imapPort; return mime_structure ($imap_stream, $header); } @@ -387,7 +470,7 @@ if ($message->header->entity_id == $ent_id && strlen($ent_id) == strlen($message->header->entity_id)) { return $message; } else { - for ($i = 0; $message->entities[$i]; $i++) { + for ($i = 0; isset($message->entities[$i]); $i++) { $msg = getEntity ($message->entities[$i], $ent_id); if ($msg) return $msg; @@ -398,19 +481,49 @@ // figures out what entity to display and returns the $message object // for that entity. - function findDisplayEntity ($message) { - if ($message) { - if ($message->header->type0 == "text") { - if ($message->header->type1 == "plain" || - $message->header->type1 == "html") { - return $message->header->entity_id; - } - } else { - for ($i=0; $message->entities[$i]; $i++) { - return findDisplayEntity($message->entities[$i]); - } - } + function findDisplayEntity ($message, $textOnly = 1) + { + global $show_html_default; + + if (! $message) + return 0; + + if ($message->header->type0 == "multipart" && + $message->header->type1 == "alternative" && + $show_html_default && ! $textOnly) { + $entity = findDisplayEntityHTML($message); + if ($entity != 0) + return $entity; + } + + // Show text/plain or text/html -- the first one we find. + if ($message->header->type0 == 'text' && + ($message->header->type1 == 'plain' || + $message->header->type1 == 'html') && + isset($message->header->entity_id)) + return $message->header->entity_id; + + for ($i=0; isset($message->entities[$i]); $i++) { + $entity = findDisplayEntity($message->entities[$i], $textOnly); + if ($entity != 0) + return $entity; } + + return 0; + } + + // Shows the HTML version + function findDisplayEntityHTML ($message) { + if ($message->header->type0 == 'text' && + $message->header->type1 == 'html' && + isset($message->header->entity_id)) + return $message->header->entity_id; + for ($i = 0; isset($message->entities[$i]); $i ++) { + $entity = findDisplayEntityHTML($message->entities[$i]); + if ($entity != 0) + return $entity; + } + return 0; } /** This returns a parsed string called $body. That string can then @@ -422,36 +535,37 @@ // this if statement checks for the entity to show as the // primary message. To add more of them, just put them in the // order that is their priority. - global $username, $key, $imapServerAddress, $imapPort; + global $startMessage, $username, $key, $imapServerAddress, $imapPort; $id = $message->header->id; $urlmailbox = urlencode($message->header->mailbox); // Get the right entity and redefine message to be this entity - $ent_num = findDisplayEntity ($message); - $message = getEntity($message, $ent_num); - - $body = mime_fetch_body ($imap_stream, $id, $ent_num); - $body = decodeBody($body, $message->header->encoding); - - // If there are other types that shouldn't be formatted, add - // them here - if ($message->header->type1 != "html") { - $body = translateText($body, $wrap_at, $message->header->charset); - } - - $body .= "
". _("Download this as a file") ."

"; - - /** Display the ATTACHMENTS: message if there's more than one part **/ - if ($message->entities) { - $body .= "
"; - $body .= "ATTACHMENTS:"; - $body .= "
"; - $num = 0; - - /** make this recurisve at some point **/ - $body .= formatAttachments ($message, $ent_num, $message->header->mailbox, $id); + // Pass the 0 to mean that we want the 'best' viewable one + $ent_num = findDisplayEntity ($message, 0); + $body_message = getEntity($message, $ent_num); + if (($body_message->header->type0 == "text") || + ($body_message->header->type0 == "rfc822")) { + + $body = mime_fetch_body ($imap_stream, $id, $ent_num); + $body = decodeBody($body, $body_message->header->encoding); + + // If there are other types that shouldn't be formatted, add + // them here + if ($body_message->header->type1 != "html") { + translateText($body, $wrap_at, $body_message->header->charset); + } + + $body .= "
". _("Download this as a file") ."

"; + + /** Display the ATTACHMENTS: message if there's more than one part **/ $body .= "
"; + if (isset($message->entities[0])) { + $body .= formatAttachments ($message, $ent_num, $message->header->mailbox, $id); + } + $body .= ""; + } else { + $body = formatAttachments ($message, -1, $message->header->mailbox, $id); } return $body; } @@ -459,32 +573,95 @@ // A recursive function that returns a list of attachments with links // to where to download these attachments function formatAttachments ($message, $ent_id, $mailbox, $id) { + global $where, $what; + global $startMessage, $color; + static $ShownHTML = 0; + + $body = ""; + if ($ShownHTML == 0) + { + $ShownHTML = 1; + + $body .= "\n"; + $body .= "
\n"; + $body .= _("Attachments") . ':'; + $body .= "
\n"; + + $body .= "\n"; + + $body .= formatAttachments ($message, $ent_id, $mailbox, $id); + + $body .= "
"; + + return $body; + } + if ($message) { if (!$message->entities) { $type0 = strtolower($message->header->type0); $type1 = strtolower($message->header->type1); + $name = decodeHeader($message->header->name); if ($message->header->entity_id != $ent_id) { - $filename = $message->header->filename; + $filename = decodeHeader($message->header->filename); if (trim($filename) == "") { - $display_filename = "untitled-".$message->header->entity_id; + if (trim($name) == "") { + $display_filename = "untitled-".$message->header->entity_id; + } else { + $display_filename = $name; + $filename = $name; + } } else { $display_filename = $filename; } $urlMailbox = urlencode($mailbox); $ent = urlencode($message->header->entity_id); - $body .= "   " . $display_filename . "  (TYPE: $type0/$type1)"; + + $DefaultLink = + "../src/download.php?startMessage=$startMessage&passed_id=$id&mailbox=$urlMailbox&passed_ent_id=$ent"; + if ($where && $what) + $DefaultLink .= '&where=' . urlencode($where) . '&what=' . urlencode($what); + $Links['download link']['text'] = _("download"); + $Links['download link']['href'] = + "../src/download.php?absolute_dl=true&passed_id=$id&mailbox=$urlMailbox&passed_ent_id=$ent"; + $ImageURL = ''; + + $HookResults = do_hook("attachment $type0/$type1", $Links, + $startMessage, $id, $urlMailbox, $ent, $DefaultLink, + $display_filename, $where, $what); + + $Links = $HookResults[1]; + $DefaultLink = $HookResults[6]; + + $body .= '  '; + $body .= "$display_filename "; + $body .= '' . show_readable_size($message->header->size) . + '  '; + $body .= "[ $type0/$type1 ] "; + $body .= ''; if ($message->header->description) - $body .= "  " . htmlspecialchars($message->header->description).""; - if ($message->header->type0 == "image" && - ($message->header->type1 == "jpg" || - $message->header->type1 == "jpeg" || - $message->header->type1 == "gif" || - $message->header->type1 == "png")) - $body .= " ("._("view").")\n"; - $body .= "
"; - $num++; + $body .= '' . htmlspecialchars($message->header->description) . ''; + $body .= '
 '; + + + $SkipSpaces = 1; + foreach ($Links as $Val) + { + if ($SkipSpaces) + { + $SkipSpaces = 0; + } + else + { + $body .= '  |  '; + } + $body .= '' . $Val['text'] . ''; + } + + unset($Links); + + $body .= "\n"; } return $body; } else { @@ -499,8 +676,11 @@ /** this function decodes the body depending on the encoding type. **/ function decodeBody($body, $encoding) { + $body = str_replace("\r\n", "\n", $body); $encoding = strtolower($encoding); + global $show_html_default; + if ($encoding == "quoted-printable") { $body = quoted_printable_decode($body); @@ -510,6 +690,13 @@ $body = base64_decode($body); } + if (!$show_html_default) { + $body = str_replace('<', '<', $body); +// $body = str_replace('>', '>', $body); +// Both this and $body = htmlspecialchars($body); mess up inline +// quoting :-( Anyway, just replacing < gets the job done. + } + // All other encodings are returned raw. return $body; } @@ -518,20 +705,26 @@ // This functions decode strings that is encoded according to // RFC1522 (MIME Part Two: Message Header Extensions for Non-ASCII Text). function decodeHeader ($string) { - if (eregi('=\?([^?]+)\?(q|b)\?([^?]+)\?=', + if (eregi('=\\?([^?]+)\\?(q|b)\\?([^?]+)\\?=', $string, $res)) { if (ucfirst($res[2]) == "B") { $replace = base64_decode($res[3]); } else { $replace = ereg_replace("_", " ", $res[3]); + // Convert lowercase Quoted Printable to uppercase for + // quoted_printable_decode to understand it. + while (ereg("(=(([0-9][abcdef])|([abcdef][0-9])|([abcdef][abcdef])))", $replace, $res)) { + $replace = str_replace($res[1], strtoupper($res[1]), $replace); + } $replace = quoted_printable_decode($replace); } $replace = charset_decode ($res[1], $replace); - $string = eregi_replace - ('=\?([^?]+)\?(q|b)\?([^?]+)\?=', + // Remove the name of the character set. + $string = eregi_replace ('=\\?([^?]+)\\?(q|b)\\?([^?]+)\\?=', $replace, $string); + // In case there should be more encoding in the string: recurse return (decodeHeader($string)); } else @@ -543,30 +736,40 @@ // be encoded. function encodeHeader ($string) { global $default_charset; - - // Encode only if the string contains 8-bit characters or =? - if (ereg("([\200-\377])|=\\?", $string)) { - $newstring = "=?$default_charset?Q?"; - - // First the special characters - $string = str_replace("=", "=3D", $string); - $string = str_replace("?", "=3F", $string); - $string = str_replace("_", "=5F", $string); - $string = str_replace(" ", "_", $string); - - - while (ereg("([\200-\377])", $string, $regs)) { - $replace = $regs[1]; - $insert = "=" . strtoupper(bin2hex($replace)); - $string = str_replace($replace, $insert, $string); - } - - $newstring = "=?$default_charset?Q?".$string."?="; - - return $newstring; - } - - return $string; - } + + // Encode only if the string contains 8-bit characters or =? + $j = strlen( $string ); + $l = FALSE; // Must be encoded ? + $ret = ''; + for( $i=0; $i < $j; ++$i) { + switch( $string{$i} ) { + case '=': + $ret .= '=3D'; + break; + case '?': + $l = TRUE; + $ret .= '=3F'; + break; + case '_': + $ret .= '=5F'; + break; + case ' ': + $ret .= '_'; + break; + default: + $k = ord( $string{$i} ); + if( $k > 126 ) { + $ret .= sprintf("=%02X", $k); + $l = TRUE; + } else + $ret .= $string{$i}; + } + } + + if( $l ) + $string = "=?$default_charset?Q?$ret?="; + + return( $string ); + } ?>