X-Git-Url: https://vcs.fsf.org/?p=squirrelmail.git;a=blobdiff_plain;f=functions%2Fmime.php;h=7a726a2217ba99861eb1211ac08fc650124f6f8a;hp=61ee7357139860568ce2628c0a83ac0f4e20c7e9;hb=ec41cfc7ff0a557b1edf4ee29929003944816fe1;hpb=6ab1bd9e63bd717eef40fcd9b39439c368a546eb diff --git a/functions/mime.php b/functions/mime.php index 61ee7357..7a726a22 100644 --- a/functions/mime.php +++ b/functions/mime.php @@ -24,7 +24,7 @@ class msg_header { $encoding = '', $size = 0, $to = array(), $from = '', $date = '', $cc = array(), $bcc = array(), $reply_to = '', $subject = '', $id = 0, $mailbox = '', $description = '', $filename = '', - $entity_id = 0, $message_id = 0, $name = '', $priority = 3; + $entity_id = 0, $message_id = 0, $name = '', $priority = 3, $type = ''; } class message { @@ -50,7 +50,6 @@ class message { */ function mime_structure ($imap_stream, $header) { - sqimap_messages_flag ($imap_stream, $header->id, $header->id, 'Seen'); $ssid = sqimap_session_id(); $lsid = strlen( $ssid ); $id = $header->id; @@ -69,6 +68,7 @@ function mime_structure ($imap_stream, $header) { // isolate the body structure and remove beginning and end parenthesis $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) { @@ -92,27 +92,84 @@ function mime_structure ($imap_stream, $header) { * to mime_get_elements() */ function mime_parse_structure ($structure, $ent_id) { - + global $mailbox; + $properties = array(); $msg = new message(); if ($structure{0} == '(') { + $old_ent_id = $ent_id; $ent_id = mime_new_element_level($ent_id); $start = $end = -1; do { $start = $end+1; $end = mime_match_parenthesis ($start, $structure); - $element = substr($structure, $start+1, ($end - $start)-1); - $ent_id = mime_increment_id ($ent_id); - $newmsg = mime_parse_structure ($element, $ent_id); - $msg->addEntity ($newmsg); + /* check if we are dealing with a new entity-level */ + $i = strrpos($ent_id,'.'); + if ($i>0) { + $ent = substr($ent_id, $i+1); + } else { + $ent = ''; + } + /* add "forgotten" parent entities (alternative and relative) */ + if ($ent == '0') { + /* new entity levels have information about the type (type1) and + * the properties. This information is situated at the end of the + * structure string like for example (example between the brackets) + * [ "RELATED" ("BOUNDARY" "myboundary" "TYPE" "plain/html") ] + */ + + /* get the involved properties for parsing to mime_get_properties */ + $startprop = strrpos($structure,'('); + $properties_str = substr($structure,$startprop); + $endprop = mime_match_parenthesis ($startprop, $structure); + $propstr = substr($structure, $startprop + 1, ($endprop - $startprop)-1); + /* cut off the used properties */ + if ($startprop) { + $structure_end = substr($structure, $endprop+2); + $structure = trim(substr($structure,0,$startprop)); + } + /* get type1 */ + $pos = strrpos($structure,' '); + if ($structure{$pos+1} =='(') $pos++; + + $type1 = strtolower(substr($structure, $pos+2, (count($structure)-2))); + /* cut off type1 */ + if ($pos && $startprop) { + $structure = trim(substr($structure, 0, $pos)); + } + + /* process the found information */ + $properties = mime_get_props($properties, $properties_str); + if (count($properties)>0) { + $msg->header->entity_id = $old_ent_id; + $msg->header->type0 = 'multipart'; + $msg->header->type1 = $type1; + for ($i=0; $i < count($properties); $i++) { + $msg->header->{$properties[$i]['name']} = $properties[$i]['value']; + } + } + $structure = $structure . ' ' . $structure_end; + } + $element = substr($structure, $start+1, ($end - $start)-1); + $ent_id = mime_increment_id ($ent_id); + $newmsg = mime_parse_structure ($element, $ent_id); + /* set mailbox in case of message/rfc822 entities */ + if (isset($newmsg->header->type0) && isset($newmsg->header->type1)) { + if ($newmsg->header->type0 == 'message' && $newmsg->header->type1 == 'rfc822') { + $newmsg->header->mailbox=$mailbox; + } + } + $msg->addEntity ($newmsg); + } while ($structure{$end+1} == '('); } else { // parse the elements - $msg = mime_get_element ($structure, $msg, $ent_id); + $msg = mime_get_element ($structure, $msg, $ent_id); } return $msg; } + /* Increments the element ID. An element id can look like any of * the following: 1, 1.2, 4.3.2.4.1, etc. This function increments * the last number of the element id, changing 1.2 to 1.3. @@ -156,7 +213,6 @@ function mime_get_element (&$structure, $msg, $ent_id) { $msg->header = new msg_header(); $msg->header->entity_id = $ent_id; $properties = array(); - while (strlen($structure) > 0) { $structure = trim($structure); $char = $structure{0}; @@ -173,6 +229,21 @@ function mime_get_element (&$structure, $msg, $ent_id) { $pos++; } $structure = substr($structure, strlen($text) + 2); + } else if ($char == '{') { + /** + * loop through until we find the matching quote, + * and return that as a string + */ + $pos = 1; + $len = ''; + while (($char = $structure{$pos}) != '}' + && $pos < strlen($structure)) { + $len .= $char; + $pos++; + } + $structure = substr($structure, strlen($len) + 4); + $text = substr($structure, 0, $len); + $structure = substr($structure, $len + 1); } else if ($char == '(') { // comment me $end = mime_match_parenthesis (0, $structure); @@ -252,10 +323,8 @@ function mime_get_element (&$structure, $msg, $ent_id) { $text = ""; } // loop through the additional properties and put those in the various headers - if ($msg->header->type0 != 'message') { - for ($i=0; $i < count($properties); $i++) { - $msg->header->{$properties[$i]['name']} = $properties[$i]['value']; - } + for ($i=0; $i < count($properties); $i++) { + $msg->header->{$properties[$i]['name']} = $properties[$i]['value']; } return $msg; @@ -282,7 +351,6 @@ function mime_get_props ($props, $structure) { while (strlen($structure) > 0) { $structure = trim($structure); $char = $structure{0}; - if ($char == '"') { $pos = 1; $tmp = ''; @@ -302,20 +370,24 @@ function mime_get_props ($props, $structure) { $value .= $char; $pos++; } - $structure = trim(substr($structure, strlen($tmp) + 2)); - + $structure = trim(substr($structure, strlen($value) + 2)); $k = count($props); $props[$k]['name'] = strtolower($tmp); $props[$k]['value'] = $value; + if ($structure != '') { + mime_get_props($props, $structure); + } else { + return $props; + } } 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); + if (! isset($props)) + $props = array(); + $props = mime_get_props($props, $sub); + $structure = substr($structure, strlen($sub) + 2); + return $props; } - return $props; } else if ($char == '(') { $end = mime_match_parenthesis (0, $structure); $sub = substr($structure, 1, $end-1); @@ -339,9 +411,12 @@ function mime_match_parenthesis ($pos, $structure) { $j = strlen( $structure ); - // ignore all extra characters - // If inside of a string, skip string -- Boundary IDs and other - // things can have ) in them. + /* + * ignore all extra characters + * If inside of a quoted string or literal, skip it -- Boundary IDs and other + * things can have ) in them. + */ + if ( $structure{$pos} != '(' ) { return( $j ); } @@ -350,7 +425,7 @@ function mime_match_parenthesis ($pos, $structure) { $pos++; if ($structure{$pos} == ')') { return $pos; - } elseif ($structure{$pos} == '"') { + } elseif ($structure{$pos} == '"') { /* check for quoted string */ $pos++; while ( $structure{$pos} != '"' && $pos < $j ) { @@ -361,6 +436,12 @@ function mime_match_parenthesis ($pos, $structure) { } $pos++; } + } elseif ($structure{$pos} == '{') { /* check for literal */ + $str = substr($structure, $pos); + $pos++; + if (preg_match("/^\{(\d+)\}.*/",$str,$reg)) { + $pos = $pos + strlen($reg[1]) + $reg[1] + 2; + } } elseif ( $structure{$pos} == '(' ) { $pos = mime_match_parenthesis ($pos, $structure); } @@ -369,7 +450,7 @@ function mime_match_parenthesis ($pos, $structure) { return( $pos ); } -function mime_fetch_body($imap_stream, $id, $ent_id ) { +function mime_fetch_body($imap_stream, $id, $ent_id) { /* * do a bit of error correction. If we couldn't find the entity id, just guess @@ -378,13 +459,12 @@ function mime_fetch_body($imap_stream, $id, $ent_id ) { if (!$ent_id) { $ent_id = 1; } - $cmd = "FETCH $id BODY[$ent_id]"; - $data = sqimap_run_command ($imap_stream, $cmd, true, $response, $message); + $data = sqimap_run_command ($imap_stream, $cmd, true, $response, $message); do { - $topline = array_shift( $data ); - } while( $topline && $topline == '*' && !preg_match( '/\\* [0-9] FETCH.*/i', $topline )) ; + $topline = trim(array_shift( $data )); + } while( $topline && $topline[0] == '*' && !preg_match( '/\* [0-9]+ FETCH.*/i', $topline )) ; $wholemessage = implode('', $data); if (ereg('\\{([^\\}]*)\\}', $topline, $regs)) { @@ -395,44 +475,46 @@ function mime_fetch_body($imap_stream, $id, $ent_id ) { */ if ( $ret{0} == '<' ) { $data = sqimap_run_command ($imap_stream, "FETCH $id BODY[$ent_id.MIME]", true, $response, $message); - $base = ''; - $k = 10; - foreach( $data as $d ) { - if ( substr( $d, 0, 13 ) == 'Content-Base:' ) { - $j = strlen( $d ); - $i = 13; - $base = ''; - while ( $i < $j && - ( !isNoSep( $d{$i} ) || $d{$i} == '"' ) ) - $i++; - while ( $i < $j ) { - if ( isNoSep( $d{$i} ) ) - $base .= $d{$i}; - $i++; - } - $k = 0; - } elseif ( $k == 1 && !isnosep( $d{0} ) ) { - $base .= substr( $d, 1 ); - } - $k++; - } - if ( $base <> '' ) { - $ret = "" . $ret; - } + /* BASE within HTML documents is illegal (see w3 spec) +* $base = ''; +* $k = 10; +* foreach( $data as $d ) { +* if ( substr( $d, 0, 13 ) == 'Content-Base:' ) { +* $j = strlen( $d ); +* $i = 13; +* $base = ''; +* while ( $i < $j && +* ( !isNoSep( $d{$i} ) || $d{$i} == '"' ) ) +* $i++; +* while ( $i < $j ) { +* if ( isNoSep( $d{$i} ) ) +* $base .= $d{$i}; +* $i++; +* } +* $k = 0; +* } elseif ( $k == 1 && !isnosep( $d{0} ) ) { +* $base .= substr( $d, 1 ); +* } +* $k++; +* } +* if ( $base <> '' ) { +* $ret = "" . $ret; +* } +* */ } } else if (ereg('"([^"]*)"', $topline, $regs)) { $ret = $regs[1]; } else { global $where, $what, $mailbox, $passed_id, $startMessage; - $par = 'mailbox=' . urlencode($mailbox) . "&passed_id=$passed_id"; + $par = 'mailbox=' . urlencode($mailbox) . "&passed_id=$passed_id"; if (isset($where) && isset($what)) { - $par .= '&where='. urlencode($where) . "&what=" . urlencode($what); + $par .= '&where='. urlencode($where) . "&what=" . urlencode($what); } else { - $par .= "&startMessage=$startMessage&show_more=0"; + $par .= "&startMessage=$startMessage&show_more=0"; } - $par .= '&response=' . urlencode($response) . - '&message=' . urlencode($message). - '&topline=' . urlencode($topline); + $par .= '&response=' . urlencode($response) . + '&message=' . urlencode($message). + '&topline=' . urlencode($topline); echo '
' . '' . @@ -509,7 +591,7 @@ function decodeMime ($imap_stream, &$header) { // This is here for debugging purposese. It will print out a list // of all the entity IDs that are in the $message object. -/* + function listEntities ($message) { if ($message) { if ($message->header->entity_id) @@ -521,19 +603,20 @@ if ($message) { } } } -*/ + /* returns a $message object for a particular entity id */ function getEntity ($message, $ent_id) { if ($message) { - if ($message->header->entity_id == $ent_id && strlen($ent_id) == strlen($message->header->entity_id)) { + if ($message->header->entity_id == $ent_id && strlen($ent_id) == strlen($message->header->entity_id)) + { return $message; } else { for ($i = 0; isset($message->entities[$i]); $i++) { $msg = getEntity ($message->entities[$i], $ent_id); if ($msg) { return $msg; - } + } } } } @@ -543,35 +626,49 @@ function getEntity ($message, $ent_id) { * figures out what entity to display and returns the $message object * for that entity. */ -function findDisplayEntity ($message, $textOnly = 1) { +function findDisplayEntity ($msg, $textOnly = true, $entity = array() ) { global $show_html_default; - $entity = 0; - - if ($message) { - if ( $message->header->type0 == 'multipart' && - ( $message->header->type1 == 'alternative' || - $message->header->type1 == 'related' ) && - $show_html_default && ! $textOnly ) { - $entity = findDisplayEntityHTML($message); - } - - // Show text/plain or text/html -- the first one we find. - if ( $entity == 0 && - $message->header->type0 == 'text' && - ( $message->header->type1 == 'plain' || - $message->header->type1 == 'html' ) && - isset($message->header->entity_id) ) { - $entity = $message->header->entity_id; - } - - $i = 0; - while ($entity == 0 && isset($message->entities[$i]) ) { - $entity = findDisplayEntity($message->entities[$i], $textOnly); - $i++; - } + $found = false; + if ($msg) { + $type = $msg->header->type0.'/'.$msg->header->type1; + if ( $type == 'multipart/alternative') { + $msg = findAlternativeEntity($msg, $textOnly); + if (count($msg->entities) == 0) { + $entity[] = $msg->header->entity_id; + } else { + $found = true; + $entity =findDisplayEntity($msg,$textOnly, $entity); + } + } else if ( $type == 'multipart/related') { + $msgs = findRelatedEntity($msg); + for ($i = 0; $i < count($msgs); $i++) { + $msg = $msgs[$i]; + if (count($msg->entities) == 0) { + $entity[] = $msg->header->entity_id; + } else { + $found = true; + $entity =findDisplayEntity($msg,$textOnly, $entity); + } + } + } else if ( count($entity) == 0 && + $msg->header->type0 == 'text' && + ( $msg->header->type1 == 'plain' || + $msg->header->type1 == 'html' ) && + isset($msg->header->entity_id) ) { + if (count($msg->entities) == 0) { + $entity[] = $msg->header->entity_id; + } + } + $i = 0; + while ( isset($msg->entities[$i]) && count($entity) == 0 && !$found ) { + $entity = findDisplayEntity($msg->entities[$i], $textOnly, $entity); + $i++; + } + } + if ( !isset($entity[0]) ) { + $entity[]=""; } - return( $entity ); } @@ -584,42 +681,173 @@ function findDisplayEntityHTML ($message) { return $message->header->entity_id; } for ($i = 0; isset($message->entities[$i]); $i ++) { + if ( $message->header->type0 == 'message' && + $message->header->type1 == 'rfc822' && + isset($message->header->entity_id)) { + return 0; + } + $entity = findDisplayEntityHTML($message->entities[$i]); if ($entity != 0) { return $entity; } } - + return 0; } +function findAlternativeEntity ($message, $textOnly) { + global $show_html_default; + /* if we are dealing with alternative parts then we choose the best + * viewable message supported by SM. + */ + if ($show_html_default && !$textOnly) { + $alt_order = array ('text/plain','text/html'); + } else { + $alt_order = array ('text/plain'); + } + $best_view = 0; + $ent_id = 0; + $k = 0; + for ($i = 0; $i < count($message->entities); $i ++) { + $type = $message->entities[$i]->header->type0.'/'.$message->entities[$i]->header->type1; + if ($type == 'multipart/related') { + $type = $message->entities[$i]->header->type; + } + for ($j = $k; $j < count($alt_order); $j++) { + if ($alt_order[$j] == $type && $j > $best_view) { + $best_view = $j; + $ent_id = $i; + $k = $j; + } + } + } + return $message->entities[$ent_id]; +} + +function findRelatedEntity ($message) { + $msgs = array(); + for ($i = 0; $i < count($message->entities); $i ++) { + $type = $message->entities[$i]->header->type0.'/'.$message->entities[$i]->header->type1; + if ($message->header->type == $type) { + $msgs[] = $message->entities[$i]; + } + } + return $msgs; +} + +/* + * translateText + * Extracted from strings.php 23/03/2002 + */ + +function translateText(&$body, $wrap_at, $charset) { + global $where, $what; /* from searching */ + global $color; /* color theme */ + + require_once('../functions/url_parser.php'); + + $body_ary = explode("\n", $body); + $PriorQuotes = 0; + for ($i=0; $i < count($body_ary); $i++) { + $line = $body_ary[$i]; + if (strlen($line) - 2 >= $wrap_at) { + sqWordWrap($line, $wrap_at); + } + $line = charset_decode($charset, $line); + $line = str_replace("\t", ' ', $line); + + parseUrl ($line); + + $Quotes = 0; + $pos = 0; + $j = strlen( $line ); + + while ( $pos < $j ) { + if ($line[$pos] == ' ') { + $pos ++; + } else if (strpos($line, '>', $pos) === $pos) { + $pos += 4; + $Quotes ++; + } else { + break; + } + } + + if ($Quotes > 1) { + if (! isset($color[14])) { + $color[14] = '#FF0000'; + } + $line = '' . $line . ''; + } elseif ($Quotes) { + if (! isset($color[13])) { + $color[13] = '#800000'; + } + $line = '' . $line . ''; + } + + $body_ary[$i] = $line; + } + $body = '
' . implode("\n", $body_ary) . '
'; +} + +/* debugfunction for looping through entities and displaying correct entities */ +function listMyEntities ($message) { + +if ($message) { + if ($message->header->entity_id) { + echo "" . $message->header->entity_id . ' : ' . $message->header->type0 . '/' . $message->header->type1 . '
'; + } + if (!($message->header->type0 == 'message' && $message->header->type1 == 'rfc822')) { + if (isset($message->header->boundary) ) { + $ent_id = $message->header->entity_id; + $var = $message->header->boundary; + if ($var !='') + echo "$ent_id boundary = $var
"; + } + if (isset($message->header->type) ) { + $var = $message->header->type; + if ($var !='') + echo "$ent_id type = $var
"; + } + for ($i = 0; $message->entities[$i]; $i++) { + $msg = listMyEntities($message->entities[$i]); + } + + if ($msg ) return $msg; + } +} + +} + + + /* This returns a parsed string called $body. That string can then be displayed as the actual message in the HTML. It contains everything needed, including HTML Tags, Attachments at the bottom, etc. */ -function formatBody($imap_stream, $message, $color, $wrap_at) { +function formatBody($imap_stream, $message, $color, $wrap_at, $ent_num) { // 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 $startMessage, $username, $key, $imapServerAddress, $imapPort, - $show_html_default; - + $show_html_default, $has_unsafe_images, $view_unsafe_images, $sort; + + $has_unsafe_images = 0; + $id = $message->header->id; + $urlmailbox = urlencode($message->header->mailbox); - - // Get the right entity and redefine message to be this entity - // 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 = mime_fetch_body ($imap_stream, $id, $ent_num); + $body = decodeBody($body, $body_message->header->encoding); $hookResults = do_hook("message_body", $body); $body = $hookResults[1]; - // If there are other types that shouldn't be formatted, add // them here if ($body_message->header->type1 == 'html') { @@ -627,20 +855,30 @@ function formatBody($imap_stream, $message, $color, $wrap_at) { $body = strip_tags( $body ); translateText($body, $wrap_at, $body_message->header->charset); } else { - $body = MagicHTML( $body, $id ); + $body = magicHTML( $body, $id, $message ); } } else { translateText($body, $wrap_at, $body_message->header->charset); } - - $body .= "
". _("Download this as a file") ."

"; + $body .= "
". _("Download this as a file") ."

"; + if ($has_unsafe_images) { + if ($view_unsafe_images) { + $body .= "
". _("Hide Unsafe Images") ."

\n"; + } else { + $body .= "
". _("View Unsafe Images") ."

\n"; + } + } /** 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); + if (isset($message->entities[1])) { + /* Header-type alternative means we choose the best one to display + so don't show the alternatives as attachment. Header-type related + means that the attachments are already part of the related message. + */ + if ($message->header->type1 !='related' && $message->header->type1 !='alternative') { + $body .= formatAttachments ($message, $ent_num, $message->header->mailbox, $id); + } } - $body .= ""; } else { $body = formatAttachments ($message, -1, $message->header->mailbox, $id); } @@ -669,8 +907,83 @@ function formatAttachments($message, $ent_id, $mailbox, $id) { ""; } else if ($message) { + $header = $message->header; + $type0 = strtolower($header->type0); + $type1 = strtolower($header->type1); + $name = ''; + if (isset($header->name)) { + $name = decodeHeader($header->name); + } + if ($type0 =='message' && $type1 == 'rfc822') { + + $filename = decodeHeader($message->header->filename); + if (trim($filename) == '') { + if (trim($name) == '') { + $display_filename = 'untitled-[' . $message->header->entity_id . ']' ; + } else { + $display_filename = $name; + $filename = $name; + } + } else { + $display_filename = $filename; + } - if (!$message->entities) { + $urlMailbox = urlencode($mailbox); + $ent = urlencode($message->header->entity_id); + + $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 = ''; + + /* this executes the attachment hook with a specific MIME-type. + * if that doens't have results, it tries if there's a rule + * for a more generic type. */ + $HookResults = do_hook("attachment $type0/$type1", $Links, + $startMessage, $id, $urlMailbox, $ent, $DefaultLink, $display_filename, $where, $what); + if(count($HookResults[1]) <= 1) { + $HookResults = do_hook("attachment $type0/*", $Links, + $startMessage, $id, $urlMailbox, $ent, $DefaultLink, + $display_filename, $where, $what); + } + + $Links = $HookResults[1]; + $DefaultLink = $HookResults[6]; + + $body .= '  ' . + "$display_filename " . + '' . show_readable_size($message->header->size) . + '  ' . + "[ $type0/$type1 ] " . + ''; + if ($message->header->description) { + $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 ); + + } elseif (!$message->entities) { $type0 = strtolower($message->header->type0); $type1 = strtolower($message->header->type1); @@ -697,13 +1010,13 @@ function formatAttachments($message, $ent_id, $mailbox, $id) { $ent = urlencode($message->header->entity_id); $DefaultLink = - "../src/download.php?startMessage=$startMessage&passed_id=$id&mailbox=$urlMailbox&passed_ent_id=$ent"; + "../src/download.php?startMessage=$startMessage&passed_id=$id&mailbox=$urlMailbox&passed_ent_id=$ent"; if ($where && $what) { - $DefaultLink .= '&where=' . urlencode($where) . '&what=' . urlencode($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"; + "../src/download.php?absolute_dl=true&passed_id=$id&mailbox=$urlMailbox&passed_ent_id=$ent"; $ImageURL = ''; /* this executes the attachment hook with a specific MIME-type. @@ -764,7 +1077,8 @@ function decodeBody($body, $encoding) { global $show_html_default; - if ($encoding == 'quoted-printable') { + if ($encoding == 'quoted-printable' || + $encoding == 'quoted_printable') { $body = quoted_printable_decode($body); @@ -782,42 +1096,37 @@ function decodeBody($body, $encoding) { /* * This functions decode strings that is encoded according to * RFC1522 (MIME Part Two: Message Header Extensions for Non-ASCII Text). + * Patched by Christian Schmidt 23/03/2002 */ function decodeHeader ($string, $utfencode=true) { + if (is_array($string)) { + $string = implode("\n", $string); + } + $i = 0; + while (preg_match('/^(.{' . $i . '})(.*)=\?([^?]*)\?(Q|B)\?([^?]*)\?=/Ui', + $string, $res)) { + $prefix = $res[1]; + // Ignore white-space between consecutive encoded-words + if (strspn($res[2], " \t") != strlen($res[2])) { + $prefix .= $res[2]; + } -if ( is_array( $string ) ) { - $string = implode("\n", $string ); -} - -if (eregi('=\\?([^?]+)\\?(q|b)\\?([^?]+)\\?=', - $string, $res)) { - if (ucfirst($res[2]) == 'B') { - $replace = base64_decode($res[3]); - } else { - $replace = str_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); + if (ucfirst($res[4]) == 'B') { + $replace = base64_decode($res[5]); + } else { + $replace = str_replace('_', ' ', $res[5]); + $replace = preg_replace('/=([0-9a-f]{2})/ie', 'chr(hexdec("\1"))', + $replace); + /* Only encode into entities by default. Some places + don't need the encoding, like the compose form. */ + if ($utfencode) { + $replace = charset_decode($res[3], $replace); + } } - $replace = quoted_printable_decode($replace); + $string = $prefix . $replace . substr($string, strlen($res[0])); + $i = strlen($prefix) + strlen($replace); } - /* Only encode into entities by default. Some places - don't need the encoding, like the compose form. */ - if ($utfencode){ - $replace = charset_decode ($res[1], $replace); - } - - // 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 - $string = decodeHeader($string); -} - -return ($string); + return( $string ); } /* @@ -827,7 +1136,7 @@ return ($string); */ function encodeHeader ($string) { global $default_charset; - + // Encode only if the string contains 8-bit characters or =? $j = strlen( $string ); $l = strstr($string, '=?'); // Must be encoded ? @@ -855,7 +1164,7 @@ function encodeHeader ($string) { $ret .= $string{$i}; } } - + if ( $l ) { $string = "=?$default_charset?Q?$ret?="; } @@ -863,391 +1172,965 @@ function encodeHeader ($string) { return( $string ); } -/* - Strips dangerous tags from html messages. -*/ -function MagicHTML( $body, $id ) { - - global $message, $HTTP_SERVER_VARS, - $attachment_common_show_images; - - $attachment_common_show_images = - FALSE; // Don't display attached images in HTML mode - $j = strlen( $body ); // Legnth of the HTML - $ret = ''; // Returned string - $bgcolor = '#ffffff'; // Background style color (defaults to white) - $textcolor = '#000000'; // Foreground style color (defaults to black) - $leftmargin = ''; // Left margin style - $title = ''; // HTML title if any +/* This function trys to locate the entity_id of a specific mime element */ - $i = 0; - while ( $i < $j ) { - if ( $body{$i} == '<' ) { - $pos = $i + 1; - $tag = ''; - while ($body{$pos} == ' ' || $body{$pos} == "\t" || - $body{$pos} == "\n") { - $pos ++; - } - while (strlen($tag) < 4 && $body{$pos} != ' ' && - $body{$pos} != "\t" && $body{$pos} != "\n") { - $tag .= $body{$pos}; - $pos ++; - } - switch( strtoupper( $tag ) ) { - // Strips the entire tag and contents - case 'APPL': - case 'EMBB': - case 'FRAM': - case 'SCRI': - case 'OBJE': - $etg = '/' . $tag; - while ( $body{$i+1}.$body{$i+2}.$body{$i+3}.$body{$i+4}.$body{$i+5} <> $etg && - $i < $j ) $i++; - while ( $i < $j && $body{++$i} <> '>' ); - // $ret .= ""; - break; - // Substitute Title - case 'TITL': - $i += 5; - while ( $body{$i} <> '>' && // - $i < $j ) - $i++; - $i++; - $title = ''; - while ( $body{$i} <> '<' && // - $i < $j ) { - $title .= $body{$i}; - $i++; - } - $i += 7; - break; - // Destroy these tags - case 'HTML': - case 'HEAD': - case '/HTM': - case '/HEA': - case '!DOC': - case 'META': - //case 'DIV ': - //case '/DIV': - case '!-- ': - $i += 4; - while ( $body{$i} <> '>' && - $i < $j ) - $i++; - // $i++; - break; - case 'STYL': - $i += 5; - while ( $body{$i} <> '>' && // - $i < $j ) - $i++; - $i++; - // We parse the style to look for interesting stuff - $styleblk = ''; - while ( $body{$i} <> '>' && - $i < $j ) { - // First we get the name of the style - $style = ''; - while ( $body{$i} <> '>' && - $body{$i} <> '<' && - $body{$i} <> '{' && - $i < $j ) { - if ( isnoSep( $body{$i} ) ) - $style .= $body{$i}; - $i++; - } - stripComments( $i, $j, $body ); - $style = strtoupper( trim( $style ) ); - if ( $style == 'BODY' ) { - // Next we look into the definitions of the body style - while ( $body{$i} <> '>' && - $body{$i} <> '}' && - $i < $j ) { - // We look for the background color if any. - if ( substr( $body, $i, 17 ) == 'BACKGROUND-COLOR:' ) { - $i += 17; - $bgcolor = getStyleData( $i, $j, $body ); - } elseif ( substr( $body, $i, 12 ) == 'MARGIN-LEFT:' ) { - $i += 12; - $leftmargin = getStyleData( $i, $j, $body ); - } - $i++; - } - } else { - // Other style are mantained - $styleblk .= "$style "; - while ( $body{$i} <> '>' && - $body{$i} <> '<' && - $body{$i} <> '}' && - $i < $j ) { - $styleblk .= $body{$i}; - $i++; - } - $styleblk .= $body{$i}; - } - stripComments( $i, $j, $body ); - if ( $body{$i} <> '>' ) - $i++; - } - if ( $styleblk <> '' ) - $ret .= " + * @return a string with edited content. + */ +function sq_fixstyle($message, $id, $content){ + global $view_unsafe_images; + $me = "sq_fixstyle"; + /** + * First look for general BODY style declaration, which would be + * like so: + * body {background: blah-blah} + * and change it to .bodyclass so we can just assign it to a
+ */ + $content = preg_replace("|body(\s*\{.*?\})|si", ".bodyclass\\1", $content); + $secremoveimg = "../images/" . _("sec_remove_eng.png"); + /** + * Fix url('blah') declarations. + */ + $content = preg_replace("|url\(([\'\"])\s*\S+script\s*:.*?([\'\"])\)|si", + "url(\\1$secremoveimg\\2)", $content); + /** + * Fix url('https*://.*) declarations but only if $view_unsafe_images + * is false. + */ + if (!$view_unsafe_images){ + $content = preg_replace("|url\(([\'\"])\s*https*:.*?([\'\"])\)|si", + "url(\\1$secremoveimg\\2)", $content); + } + + /** + * Fix urls that refer to cid: + */ + while (preg_match("|url\(([\'\"]\s*cid:.*?[\'\"])\)|si", $content, + $matches)){ + $cidurl = $matches{1}; + $httpurl = sq_cid2http($message, $id, $cidurl); + $content = preg_replace("|url\($cidurl\)|si", + "url($httpurl)", $content); + } -/* This function trys to locate the entity_id of a specific mime element */ + /** + * Fix stupid css declarations which lead to vulnerabilities + * in IE. + */ + $match = Array('/expression/si', + '/behaviou*r/si', + '/binding/si'); + $replace = Array('idiocy', 'idiocy', 'idiocy'); + $content = preg_replace($match, $replace, $content); + return $content; +} -function find_ent_id( $id, $message ) { +/** + * This function converts cid: url's into the ones that can be viewed in + * the browser. + * + * @param $message the message object + * @param $id the message id + * @param $cidurl the cid: url. + * @return a string with a http-friendly url + */ +function sq_cid2http($message, $id, $cidurl){ + /** + * Get rid of quotes. + */ + $quotchar = substr($cidurl, 0, 1); + $cidurl = str_replace($quotchar, "", $cidurl); + $cidurl = substr(trim($cidurl), 4); + $httpurl = $quotchar . "../src/download.php?absolute_dl=true&" . + "passed_id=$id&mailbox=" . urlencode($message->header->mailbox) . + "&passed_ent_id=" . find_ent_id($cidurl, $message) . $quotchar; + return $httpurl; +} - $ret = ''; - for ($i=0; $ret == '' && $i < count($message->entities); $i++) { +/** + * This function changes the tag into a
tag since we + * can't really have a body-within-body. + * + * @param $attary an array of attributes and values of + * @return a modified array of attributes to be set for
+ */ +function sq_body2div($attary){ + $me = "sq_body2div"; + $divattary = Array("class"=>"'bodyclass'"); + $bgcolor="#ffffff"; + $text="#000000"; + $styledef=""; + if (is_array($attary) && sizeof($attary) > 0){ + foreach ($attary as $attname=>$attvalue){ + $quotchar = substr($attvalue, 0, 1); + $attvalue = str_replace($quotchar, "", $attvalue); + switch ($attname){ + case "background": + $styledef .= "background-image: url('$attvalue'); "; + break; + case "bgcolor": + $styledef .= "background-color: $attvalue; "; + break; + case "text": + $styledef .= "color: $attvalue; "; + } + } + if (strlen($styledef) > 0){ + $divattary{"style"} = "\"$styledef\""; + } + } + return $divattary; +} - if ( $message->entities[$i]->header->entity_id == '' ) { - $ret = find_ent_id( $id, $message->entities[$i] ); +/** + * This is the main function and the one you should actually be calling. + * There are several variables you should be aware of an which need + * special description. + * + * Since the description is quite lengthy, see it here: + * http://www.mricon.com/html/phpfilter.html + * + * @param $body the string with HTML you wish to filter + * @param $tag_list see description above + * @param $rm_tags_with_content see description above + * @param $self_closing_tags see description above + * @param $force_tag_closing see description above + * @param $rm_attnames see description above + * @param $bad_attvals see description above + * @param $add_attr_to_tag see description above + * @param $message message object + * @param $id message id + * @return sanitized html safe to show on your pages. + */ +function sq_sanitize($body, + $tag_list, + $rm_tags_with_content, + $self_closing_tags, + $force_tag_closing, + $rm_attnames, + $bad_attvals, + $add_attr_to_tag, + $message, + $id + ){ + $me = "sq_sanitize"; + /** + * Normalize rm_tags and rm_tags_with_content. + */ + @array_walk($rm_tags, 'sq_casenormalize'); + @array_walk($rm_tags_with_content, 'sq_casenormalize'); + @array_walk($self_closing_tags, 'sq_casenormalize'); + /** + * See if tag_list is of tags to remove or tags to allow. + * false means remove these tags + * true means allow these tags + */ + $rm_tags = array_shift($tag_list); + $curpos = 0; + $open_tags = Array(); + $trusted = "\n"; + $skip_content = false; + /** + * Take care of netscape's stupid javascript entities like + * &{alert('boo')}; + */ + $body = preg_replace("/&(\{.*?\};)/si", "&\\1", $body); + + while (($curtag=sq_getnxtag($body, $curpos)) != FALSE){ + list($tagname, $attary, $tagtype, $lt, $gt) = $curtag; + $free_content = substr($body, $curpos, $lt-$curpos); + /** + * Take care of . Edit the + * content before we apply it. + */ + $free_content = sq_fixstyle($message, $id, $free_content); + } + if ($skip_content == false){ + $trusted .= $free_content; } else { - if ( strcasecmp( $message->entities[$i]->header->id, $id ) == 0 ) - $ret = $message->entities[$i]->header->entity_id; } - + if ($tagname != FALSE){ + if ($tagtype == 2){ + if ($skip_content == $tagname){ + /** + * Got to the end of tag we needed to remove. + */ + $tagname = false; + $skip_content = false; + } else { + if ($skip_content == false){ + if ($tagname == "body"){ + $tagname = "div"; + } else { + if (isset($open_tags{$tagname}) && + $open_tags{$tagname} > 0){ + $open_tags{$tagname}--; + } else { + $tagname = false; + } + } + } else { + } + } + } else { + /** + * $rm_tags_with_content + */ + if ($skip_content == false){ + /** + * See if this is a self-closing type and change + * tagtype appropriately. + */ + if ($tagtype == 1 + && in_array($tagname, $self_closing_tags)){ + $tagtype=3; + } + /** + * See if we should skip this tag and any content + * inside it. + */ + if ($tagtype == 1 && + in_array($tagname, $rm_tags_with_content)){ + $skip_content = $tagname; + } else { + if (($rm_tags == false + && in_array($tagname, $tag_list)) || + ($rm_tags == true && + !in_array($tagname, $tag_list))){ + $tagname = false; + } else { + if ($tagtype == 1){ + if (isset($open_tags{$tagname})){ + $open_tags{$tagname}++; + } else { + $open_tags{$tagname}=1; + } + } + /** + * This is where we run other checks. + */ + if (is_array($attary) && sizeof($attary) > 0){ + $attary = sq_fixatts($tagname, + $attary, + $rm_attnames, + $bad_attvals, + $add_attr_to_tag, + $message, + $id + ); + } + /** + * Convert body into div. + */ + if ($tagname == "body"){ + $tagname = "div"; + $attary = sq_body2div($attary, $message, $id); + } + } + } + } else { + } + } + if ($tagname != false && $skip_content == false){ + $trusted .= sq_tagprint($tagname, $attary, $tagtype); + } + } else { + } + $curpos = $gt+1; + } + $trusted .= substr($body, $curpos, strlen($body)-$curpos); + if ($force_tag_closing == true){ + foreach ($open_tags as $tagname=>$opentimes){ + while ($opentimes > 0){ + $trusted .= ''; + $opentimes--; + } + } + $trusted .= "\n"; } + $trusted .= "\n"; + return $trusted; +} - return( $ret ); +/** + * This is a wrapper function to call html sanitizing routines. + * + * @param $body the body of the message + * @param $id the id of the message + * @return a string with html safe to display in the browser. + */ +function magicHTML($body, $id, $message){ + global $attachment_common_show_images, $view_unsafe_images, + $has_unsafe_images; + /** + * Don't display attached images in HTML mode. + */ + $attachment_common_show_images = false; + $tag_list = Array( + false, + "object", + "meta", + "html", + "head", + "base" + ); + + $rm_tags_with_content = Array( + "script", + "applet", + "embed", + "title" + ); + + $self_closing_tags = Array( + "img", + "br", + "hr", + "input" + ); + + $force_tag_closing = false; + + $rm_attnames = Array( + "/.*/" => + Array( + "/target/si", + "/^on.*/si", + "/^dynsrc/si", + "/^data.*/si" + ) + ); + + $secremoveimg = "../images/" . _("sec_remove_eng.png"); + $bad_attvals = Array( + "/.*/" => + Array( + "/^src|background|href|action/i" => + Array( + Array( + "|^([\'\"])\s*\.\./.*([\'\"])|si", + "/^([\'\"])\s*\S+script\s*:.*([\'\"])/si", + "/^([\'\"])\s*mocha\s*:*.*([\'\"])/si", + "/^([\'\"])\s*about\s*:.*([\'\"])/si" + ), + Array( + "\\1$secremoveimg\\2", + "\\1$secremoveimg\\2", + "\\1$secremoveimg\\2", + "\\1$secremoveimg\\2" + ) + ), + "/^style/si" => + Array( + Array( + "/expression/si", + "/binding/si", + "/behaviou*r/si", + "|url\(([\'\"])\s*\.\./.*([\'\"])\)|si", + "/url\(([\'\"])\s*\S+script:.*([\'\"])\)/si" + ), + Array( + "idiocy", + "idiocy", + "idiocy", + "url(\\1$secremoveimg\\2)", + "url(\\1$secremoveimg\\2)" + ) + ) + ) + ); + if (!$view_unsafe_images){ + /** + * Remove any references to http/https if view_unsafe_images set + * to false. + */ + array_push($bad_attvals{'/.*/'}{'/^src|background|href|action/i'}[0], + '/^([\'\"])\s*https*:.*([\'\"])/si'); + array_push($bad_attvals{'/.*/'}{'/^src|background|href|action/i'}[1], + "\\1$secremoveimg\\2"); + array_push($bad_attvals{'/.*/'}{'/^style/si'}[0], + '/url\(([\'\"])\s*https*:.*([\'\"])\)/si'); + array_push($bad_attvals{'/.*/'}{'/^style/si'}[1], + "url(\\1$secremoveimg\\2)"); + } + $add_attr_to_tag = Array( + "/^a$/si" => Array('target'=>'"_new"') + ); + $trusted = sq_sanitize($body, + $tag_list, + $rm_tags_with_content, + $self_closing_tags, + $force_tag_closing, + $rm_attnames, + $bad_attvals, + $add_attr_to_tag, + $message, + $id + ); + if (preg_match("|$secremoveimg|si", $trusted)){ + $has_unsafe_images = true; + } + return $trusted; } -?> +?> \ No newline at end of file