Adding comments.
[squirrelmail.git] / functions / mime.php
index 7e0770077444a8f68acbd587966dacdc9641d467..f89aa54606875663c2d9cbe98ecab6768a147e67 100644 (file)
@@ -3,21 +3,41 @@
 /**
  * mime.php
  *
- * Copyright (c) 1999-2005 The SquirrelMail Project Team
- * Licensed under the GNU GPL. For full terms see the file COPYING.
- *
  * This contains the functions necessary to detect and decode MIME
  * messages.
  *
+ * @copyright © 1999-2007 The SquirrelMail Project Team
+ * @license http://opensource.org/licenses/gpl-license.php GNU Public License
  * @version $Id$
  * @package squirrelmail
  */
 
-/** The typical includes... */
-require_once(SM_PATH . 'functions/imap.php');
-require_once(SM_PATH . 'functions/attachment_common.php');
-/** add sqm_baseuri()*/
-include_once(SM_PATH . 'functions/display_messages.php');
+/**
+ * dependency information
+   functions       dependency
+   mime_structure
+        class/mime/Message.class.php
+            Message::parseStructure
+        functions/page_header.php
+            displayPageHeader
+        functions/display_messages.php
+            plain_error_message
+   mime_fetch_body
+        functions/imap_general.php
+            sqimap_run_command
+   mime_print_body_lines
+
+
+
+functions/imap.php
+functions/attachment_common.php
+functions/display_messages.php
+
+magicHtml => url_parser
+translateText => url_parser
+
+*/
+
 
 /* -------------------------------------------------------------------------- */
 /* MIME DECODING                                                              */
@@ -37,20 +57,20 @@ function mime_structure ($bodystructure, $flags=array()) {
     $read = trim(substr ($read, 0, -1));
     $i = 0;
     $msg = Message::parseStructure($read,$i);
+
     if (!is_object($msg)) {
-        include_once(SM_PATH . 'functions/display_messages.php');
         global $color, $mailbox;
-        /* removed urldecode because $_GET is auto urldecoded ??? */
         displayPageHeader( $color, $mailbox );
         $errormessage  = _("SquirrelMail could not decode the bodystructure of the message");
         $errormessage .= '<br />'._("The bodystructure provided by your IMAP server:").'<br /><br />';
         $errormessage .= '<pre>' . htmlspecialchars($read) . '</pre>';
-        plain_error_message( $errormessage, $color );
+        plain_error_message( $errormessage );
         echo '</body></html>';
         exit;
     }
     if (count($flags)) {
         foreach ($flags as $flag) {
+//FIXME: please document why it is we have to check the first char of the flag but we then go ahead and do a full string comparison anyway.  Is this a speed enhancement?  If not, let's keep it simple and just compare the full string and forget the switch block.
             $char = strtoupper($flag{1});
             switch ($char) {
                 case 'S':
@@ -127,6 +147,8 @@ function mime_fetch_body($imap_stream, $id, $ent_id=1, $fetch_size=0) {
 //        }
     } else if (ereg('"([^"]*)"', $topline, $regs)) {
         $ret = $regs[1];
+    } else if ((stristr($topline, 'nil') !== false) && (empty($wholemessage))) {
+        $ret = $wholemessage;
     } else {
         global $where, $what, $mailbox, $passed_id, $startMessage;
         $par = 'mailbox=' . urlencode($mailbox) . '&amp;passed_id=' . $passed_id;
@@ -265,7 +287,7 @@ function translateText(&$body, $wrap_at, $charset) {
     global $where, $what;   /* from searching */
     global $color;          /* color theme */
 
-    require_once(SM_PATH . 'functions/url_parser.php');
+    // require_once(SM_PATH . 'functions/url_parser.php');
 
     $body_ary = explode("\n", $body);
     for ($i=0; $i < count($body_ary); $i++) {
@@ -294,15 +316,9 @@ function translateText(&$body, $wrap_at, $charset) {
         }
 
         if ($quotes % 2) {
-            if (!isset($color[13])) {
-                $color[13] = '#800000';
-            }
-            $line = '<font color="' . $color[13] . '">' . $line . '</font>';
+            $line = '<span class="quote1">' . $line . '</span>';
         } elseif ($quotes) {
-            if (!isset($color[14])) {
-                $color[14] = '#FF0000';
-            }
-            $line = '<font color="' . $color[14] . '">' . $line . '</font>';
+            $line = '<span class="quote2">' . $line . '</span>';
         }
 
         $body_ary[$i] = $line;
@@ -335,11 +351,17 @@ function formatBody($imap_stream, $message, $color, $wrap_at, $ent_num, $id, $ma
      * order that is their priority.
      */
     global $startMessage, $languages, $squirrelmail_language,
-           $show_html_default, $sort, $has_unsafe_images, $passed_ent_id, $use_iframe,$iframe_height;
+           $show_html_default, $sort, $has_unsafe_images, $passed_ent_id,
+           $use_iframe, $iframe_height, $download_and_unsafe_link,
+           $download_href, $unsafe_image_toggle_href, $unsafe_image_toggle_text,
+           $oTemplate, $nbsp;
 
     // workaround for not updated config.php
     if (! isset($use_iframe)) $use_iframe = false;
 
+    // If there's no "view_unsafe_images" setting in the user's preferences,
+    // turn unsafe images off by default.
+    // FIXME: Check if the UIR plugin is enabled. If it's not, no unsafe images should be displayed regardless of the user's preferences. This test is done in several places in the code and they should all be fixed in the same way.
     if( !sqgetGlobalVar('view_unsafe_images', $view_unsafe_images, SQ_GET) ) {
         $view_unsafe_images = false;
     }
@@ -358,8 +380,9 @@ function formatBody($imap_stream, $message, $color, $wrap_at, $ent_num, $id, $ma
                 $body = call_user_func($languages[$squirrelmail_language]['XTRA_CODE'] . '_decode',$body);
             }
         }
-        $hookResults = do_hook("message_body", $body);
-        $body = $hookResults[1];
+
+        /* As of 1.5.2, $body is passed (and modified) by reference */
+        do_hook('message_body', $body);
 
         /* If there are other types that shouldn't be formatted, add
          * them here.
@@ -388,7 +411,9 @@ function formatBody($imap_stream, $message, $color, $wrap_at, $ent_num, $id, $ma
                  * If we don't add html message between iframe tags,
                  * we must detect unsafe images and modify $has_unsafe_images.
                  */
-                $html_body =  magicHTML($body, $id, $message, $mailbox);
+                $html_body = magicHTML($body, $id, $message, $mailbox);
+                // Convert character set in order to display html mails in different character set
+                $html_body = charset_decode($body_message->header->getParameter('charset'),$html_body,false,true);
 
                 // creating iframe url
                 $iframeurl=sqm_baseuri().'src/view_html.php?'
@@ -397,35 +422,23 @@ function formatBody($imap_stream, $message, $color, $wrap_at, $ent_num, $id, $ma
                     . '&amp;ent_id=' . $ent_num
                     . '&amp;view_unsafe_images=' . (int) $view_unsafe_images;
 
-                // adding warning message
-                $body = html_tag('div',_("Viewing HTML formatted email"),'center');
-
-                /**
-                 * height can't be set to 100%, because it does not work as expected when
-                 * iframe is inside the table. Browsers do not create full height objects
-                 * even when iframe is not nested. Maybe there is some way to get full size
-                 * with CSS. Tested in firefox 1.02 and opera 7.53
-                 *
-                 * width="100%" does not work as expected, when table width is not set (automatic)
-                 *
-                 * tokul: I think <iframe> are safer sandbox than <object>. Objects might
-                 * need special handling for IE and IE6SP2.
-                 */
-                $body.= "<div><iframe name=\"message_frame\" width=\"100%\" height=\"$iframe_height\" src=\"$iframeurl\""
-                    .' frameborder="1" marginwidth="0" marginheight="0" scrolling="auto">' . "\n";
-
-                // Message for browsers without iframe support
-                //$body.= _("Your browser does not support inline frames.
-                // You can view HTML formated message by following below link.");
-                //$body.= "<br /><a href=\"$iframeurl\">"._("View HTML Message")."</a>";
-
-                // if browser can't render iframe, it renders html message.
-                $body.= $html_body;
+                global $oTemplate;
+                $oTemplate->assign('iframe_url', $iframeurl);
+                $oTemplate->assign('iframe_height', $iframe_height);
+                $oTemplate->assign('html_body', $html_body);
 
-                // close iframe
-                $body.="</iframe></div>\n";
+                $body = $oTemplate->fetch('read_html_iframe.tpl');
             } else {
                 // old way of html rendering
+                /**
+                 * convert character set. charset_decode does not remove html special chars
+                 * applied by magicHTML functions and does not sanitize them second time if
+                 * fourth argument is true.
+                 */
+                $charset = $body_message->header->getParameter('charset');
+                if (!empty($charset)) {
+                    $body = charset_decode($charset,$body,false,true);
+                }
                 $body = magicHTML($body, $id, $message, $mailbox);
             }
         } else {
@@ -438,14 +451,37 @@ function formatBody($imap_stream, $message, $color, $wrap_at, $ent_num, $id, $ma
             return $body;
         }
 
+        /*
+         * Previously the links for downloading and unsafe images were printed
+         * under the mail. By putting the links in a global variable we can
+         * print it in the toolbar where it belongs. Since the original code was
+         * in this place it's left here. It might be possible to move it to some
+         * other place if that makes sense. The possibility to do so has not
+         * been evaluated yet.
+         */
+
+        // Initialize the global variable to an empty string.
+        // FIXME: To have $download_and_unsafe_link as a global variable might not be needed since the use of separate variables ($download_href, $unsafe_image_toggle_href, and $unsafe_image_toggle_text) for the templates was introduced.
+        $download_and_unsafe_link = '';
+
+        // Prepare and build a link for downloading the mail.
         $link = 'passed_id=' . $id . '&amp;ent_id='.$ent_num.
             '&amp;mailbox=' . $urlmailbox .'&amp;sort=' . $sort .
             '&amp;startMessage=' . $startMessage . '&amp;show_more=0';
         if (isset($passed_ent_id)) {
             $link .= '&amp;passed_ent_id='.$passed_ent_id;
         }
-        $body .= '<center><small><a href="download.php?absolute_dl=true&amp;' .
-            $link . '">' . _("Download this as a file") .  '</a>';
+        $download_href = SM_PATH . 'src/download.php?absolute_dl=true&amp;' . $link;
+
+        // Always add the link for downloading the mail as a file to the global
+        // variable.
+        $download_and_unsafe_link .= "$nbsp|$nbsp" 
+            . create_hyperlink($download_href, _("Download this as a file"));
+
+        // Find out the right text to use in the link depending on the
+        // circumstances. If the unsafe images are displayed the link should
+        // hide them, if they aren't displayed the link should only appear if
+        // the mail really contains unsafe images.
         if ($view_unsafe_images) {
             $text = _("Hide Unsafe Images");
         } else {
@@ -456,51 +492,50 @@ function formatBody($imap_stream, $message, $color, $wrap_at, $ent_num, $id, $ma
                 $text = '';
             }
         }
+
+        // Only create a link for unsafe images if there's need for one. If so:
+        // add it to the global variable.
         if($text != '') {
-            $body .= '&nbsp;|&nbsp;<a href="read_body.php?' . $link . '">' . $text . '</a>';
+            $unsafe_image_toggle_href = SM_PATH . 'src/read_body.php?'.$link;
+            $unsafe_image_toggle_text = $text;
+            $download_and_unsafe_link .= "$nbsp|$nbsp"
+                . create_hyperlink($unsafe_image_toggle_href, $text);
         }
-        $body .= '</small></center><br />' . "\n";
     }
     return $body;
 }
 
 /**
- * Displays attachment links and information
- *
- * Since 1.3.0 function is not included in formatBody() call.
- *
- * Since 1.0.2 uses attachment $type0/$type1 hook.
- * Since 1.2.5 uses attachment $type0/* hook.
- * Since 1.5.0 uses attachments_bottom hook.
+ * Generate attachments array for passing to templates.  Separated from
+ * formatAttachments() below so that the same array can be given to the
+ * print-friendly version.
  *
+ * @since 1.5.2
  * @param object $message SquirrelMail message object
  * @param array $exclude_id message parts that are not attachments.
  * @param string $mailbox mailbox name
  * @param integer $id message id
- * @return string html formated attachment information.
  */
-function formatAttachments($message, $exclude_id, $mailbox, $id) {
-    global $where, $what, $startMessage, $color, $passed_ent_id;
+function buildAttachmentArray($message, $exclude_id, $mailbox, $id) {
+    global $where, $what, $startMessage, $color, $passed_ent_id, $base_uri;
 
     $att_ar = $message->getAttachments($exclude_id);
-
-    if (!count($att_ar)) return '';
-
-    $attachments = '';
-
     $urlMailbox = urlencode($mailbox);
 
+    $attachments = array();
     foreach ($att_ar as $att) {
         $ent = $att->entity_id;
         $header = $att->header;
         $type0 = strtolower($header->type0);
         $type1 = strtolower($header->type1);
         $name = '';
+        $links = array();
         $links['download link']['text'] = _("Download");
-        $links['download link']['href'] = sqm_baseuri() .
+        $links['download link']['href'] = $base_uri .
             "src/download.php?absolute_dl=true&amp;passed_id=$id&amp;mailbox=$urlMailbox&amp;ent_id=$ent";
+
         if ($type0 =='message' && $type1 == 'rfc822') {
-            $default_page = sqm_baseuri() . 'src/read_body.php';
+            $default_page = $base_uri  . 'src/read_body.php';
             $rfc822_header = $att->rfc822_header;
             $filename = $rfc822_header->subject;
             if (trim( $filename ) == '') {
@@ -509,12 +544,19 @@ function formatAttachments($message, $exclude_id, $mailbox, $id) {
             $from_o = $rfc822_header->from;
             if (is_object($from_o)) {
                 $from_name = decodeHeader($from_o->getAddress(false));
+            } elseif (is_array($from_o) && count($from_o) && is_object($from_o[0])) {
+                // something weird happens when a digest message is opened and you return to the digest
+                // now the from object is part of an array. Probably the parseHeader call overwrites the info
+                // retrieved from the bodystructure in a different way. We need to fix this later.
+                // possible starting point, do not fetch header we already have and inspect how
+                // the rfc822_header object behaves.
+                $from_name = decodeHeader($from_o[0]->getAddress(false));
             } else {
                 $from_name = _("Unknown sender");
             }
-            $description = $from_name;
+            $description = _("From").': '.$from_name;
         } else {
-            $default_page = sqm_baseuri() . 'src/download.php';
+            $default_page = $base_uri  . 'src/download.php';
             $filename = $att->getFilename();
             if ($header->description) {
                 $description = decodeHeader($header->description);
@@ -535,53 +577,105 @@ function formatAttachments($message, $exclude_id, $mailbox, $id) {
         if ($where && $what) {
             $defaultlink .= '&amp;where='. urlencode($where).'&amp;what='.urlencode($what);
         }
+        // IE does make use of mime content sniffing. Forcing a download
+        // prohibit execution of XSS inside an application/octet-stream attachment
+        if ($type0 == 'application' && $type1 == 'octet-stream') {
+            $defaultlink .= '&amp;absolute_dl=true';
+        }
 
         /* This executes the attachment hook with a specific MIME-type.
          * If that doesn't have results, it tries if there's a rule
-         * for a more generic type.
+         * for a more generic type. Finally, a hook for ALL attachment
+         * types is run as well.
          */
-        $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);
+        // First remember the default link.
+        $defaultlink_orig = $defaultlink;
+
+        /* The API for this hook has changed as of 1.5.2 so that all plugin
+           arguments are passed in an array instead of each their own plugin
+           argument, and arguments are passed by reference, so instead of
+           returning any changes, changes should simply be made to the original
+           arguments themselves. */
+        $temp = array(&$links, &$startMessage, &$id, &$urlMailbox, &$ent, 
+                    &$defaultlink, &$display_filename, &$where, &$what);
+        do_hook("attachment $type0/$type1", $temp);
+        if(count($links) <= 1 && $defaultlink == $defaultlink_orig) {
+            /* The API for this hook has changed as of 1.5.2 so that all plugin
+               arguments are passed in an array instead of each their own plugin
+               argument, and arguments are passed by reference, so instead of
+               returning any changes, changes should simply be made to the original
+               arguments themselves. */
+            $temp = array(&$links, &$startMessage, &$id, &$urlMailbox, &$ent, 
+                          &$defaultlink, &$display_filename, &$where, &$what);
+            do_hook("attachment $type0/*", $temp);
         }
-
-        $links = $hookresults[1];
-        $defaultlink = $hookresults[6];
-
-        $attachments .= '<tr><td>' .
-            '<a href="'.$defaultlink.'">'.decodeHeader($display_filename).'</a>&nbsp;</td>' .
-            '<td><small><b>' . show_readable_size($header->size) .
-            '</b>&nbsp;&nbsp;</small></td>' .
-            '<td><small>[ '.htmlspecialchars($type0).'/'.htmlspecialchars($type1).' ]&nbsp;</small></td>' .
-            '<td><small>';
-        $attachments .= '<b>' . $description . '</b>';
-        $attachments .= '</small></td><td><small>&nbsp;';
-
-        $skipspaces = 1;
+        /* The API for this hook has changed as of 1.5.2 so that all plugin
+           arguments are passed in an array instead of each their own plugin
+           argument, and arguments are passed by reference, so instead of
+           returning any changes, changes should simply be made to the original
+           arguments themselves. */
+        $temp = array(&$links, &$startMessage, &$id, &$urlMailbox, &$ent, 
+                      &$defaultlink, &$display_filename, &$where, &$what);
+        // Do not let a generic plugin change the default link if a more
+        // specialized one already did it...
+        if ($defaultlink != $defaultlink_orig) {
+            $dummy = '';
+            $temp[5] = &$dummy;
+        }
+        do_hook("attachment */*", $temp);
+
+        $this_attachment = array();
+        $this_attachment['Name'] = decodeHeader($display_filename);
+        $this_attachment['Description'] = $description;
+        $this_attachment['DefaultHREF'] = $defaultlink;
+        $this_attachment['DownloadHREF'] = $links['download link']['href'];
+        $this_attachment['ViewHREF'] = isset($links['attachment_common']) ? $links['attachment_common']['href'] : '';
+        $this_attachment['Size'] = $header->size;
+        $this_attachment['ContentType'] = htmlspecialchars($type0 .'/'. $type1);
+        $this_attachment['OtherLinks'] = array();
         foreach ($links as $val) {
-            if ($skipspaces) {
-                $skipspaces = 0;
-            } else {
-                $attachments .= '&nbsp;&nbsp;|&nbsp;&nbsp;';
-            }
-            $attachments .= '<a href="' . $val['href'] . '">'
-                . (isset($val['text']) && !empty($val['text']) ? $val['text'] : '')
-                . (isset($val['extra']) && !empty($val['extra']) ? $val['extra'] : '')
-                . '</a>';
+            if ($val['text']==_("Download") || $val['text'] == _("View"))
+                continue;
+            if (empty($val['text']) && empty($val['extra']))
+                continue;
+
+            $temp = array();
+            $temp['HREF'] = $val['href'];
+            $temp['Text'] = (empty($val['text']) ? '' : $val['text']) . (empty($val['extra']) ? '' : $val['extra']);
+            $this_attachment['OtherLinks'][] = $temp;
         }
+        $attachments[] = $this_attachment;
+
         unset($links);
-        $attachments .= "</td></tr>\n";
     }
-    $attachmentadd = do_hook_function('attachments_bottom',$attachments);
-    if ($attachmentadd != '')
-        $attachments = $attachmentadd;
+
     return $attachments;
 }
 
+/**
+ * Displays attachment links and information
+ *
+ * Since 1.3.0 function is not included in formatBody() call.
+ *
+ * Since 1.0.2 uses attachment $type0/$type1 hook.
+ * Since 1.2.5 uses attachment $type0/* hook.
+ * Since 1.5.0 uses attachments_bottom hook.
+ * Since 1.5.2 uses templates and does *not* return a value.
+ *
+ * @param object $message SquirrelMail message object
+ * @param array $exclude_id message parts that are not attachments.
+ * @param string $mailbox mailbox name
+ * @param integer $id message id
+ */
+function formatAttachments($message, $exclude_id, $mailbox, $id) {
+    global $oTemplate;
+
+    $attach = buildAttachmentArray($message, $exclude_id, $mailbox, $id);
+
+    $oTemplate->assign('attachments', $attach);
+    $oTemplate->display('read_attachments.tpl');
+}
+
 function sqimap_base64_decode(&$string) {
 
     // Base64 encoded data goes in pairs of 4 bytes. To achieve on the
@@ -606,15 +700,23 @@ function sqimap_base64_decode(&$string) {
     return $sStringRem;
 }
 
-
-/* This function decodes the body depending on the encoding type. */
+/**
+ * Decodes encoded message body
+ *
+ * This function decodes the body depending on the encoding type.
+ * Currently quoted-printable and base64 encodings are supported.
+ * decode_body hook was added to this function in 1.4.2/1.5.0
+ * @param string $body encoded message body
+ * @param string $encoding used encoding
+ * @return string decoded string
+ * @since 1.0
+ */
 function decodeBody($body, $encoding) {
-    global $show_html_default;
 
     $body = str_replace("\r\n", "\n", $body);
     $encoding = strtolower($encoding);
 
-    $encoding_handler = do_hook_function('decode_body', $encoding);
+    $encoding_handler = do_hook('decode_body', $encoding);
 
 
     // plugins get first shot at decoding the body
@@ -622,15 +724,16 @@ function decodeBody($body, $encoding) {
     if (!empty($encoding_handler) && function_exists($encoding_handler)) {
         $body = $encoding_handler('decode', $body);
 
-    } else if ($encoding == 'quoted-printable' ||
+    } elseif ($encoding == 'quoted-printable' ||
             $encoding == 'quoted_printable') {
+        /**
+         * quoted_printable_decode() function is broken in older
+         * php versions. Text with \r\n decoding was fixed only
+         * in php 4.3.0. Minimal code requirement 4.0.4 +
+         * str_replace("\r\n", "\n", $body); call.
+         */
         $body = quoted_printable_decode($body);
-
-        while (ereg("=\n", $body)) {
-            $body = ereg_replace ("=\n", '', $body);
-        }
-
-    } else if ($encoding == 'base64') {
+    } elseif ($encoding == 'base64') {
         $body = base64_decode($body);
     }
 
@@ -641,17 +744,17 @@ function decodeBody($body, $encoding) {
 /**
  * Decodes headers
  *
- * This functions decode strings that is encoded according to
+ * This function decodes strings that are encoded according to
  * RFC1522 (MIME Part Two: Message Header Extensions for Non-ASCII Text).
  * Patched by Christian Schmidt <christian@ostenfeld.dk>  23/03/2002
  *
  * @param string $string header string that has to be made readable
  * @param boolean $utfencode change message in order to be readable on user's charset. defaults to true
- * @param boolean $htmlsave preserve spaces and sanitize html special characters. defaults to true
+ * @param boolean $htmlsafe preserve spaces and sanitize html special characters. defaults to true
  * @param boolean $decide decide if string can be utfencoded. defaults to false
  * @return string decoded header string
  */
-function decodeHeader ($string, $utfencode=true,$htmlsave=true,$decide=false) {
+function decodeHeader ($string, $utfencode=true,$htmlsafe=true,$decide=false) {
     global $languages, $squirrelmail_language,$default_charset;
     if (is_array($string)) {
         $string = implode("\n", $string);
@@ -683,7 +786,7 @@ function decodeHeader ($string, $utfencode=true,$htmlsave=true,$decide=false) {
         while ($match = preg_match('/^(.*)=\?([^?]*)\?(Q|B)\?([^?]*)\?=(.*)$/Ui',$chunk,$res)) {
             /* if the last chunk isn't an encoded string then put back the space, otherwise don't */
             if ($iLastMatch !== $j) {
-                if ($htmlsave) {
+                if ($htmlsafe) {
                     $ret .= '&#32;';
                 } else {
                     $ret .= ' ';
@@ -691,7 +794,7 @@ function decodeHeader ($string, $utfencode=true,$htmlsave=true,$decide=false) {
             }
             $iLastMatch = $i;
             $j = $i;
-            if ($htmlsave) {
+            if ($htmlsafe) {
                 $ret .= htmlspecialchars($res[1]);
             } else {
                 $ret .= $res[1];
@@ -714,13 +817,13 @@ function decodeHeader ($string, $utfencode=true,$htmlsave=true,$decide=false) {
                             /* convert string to different charset,
                              * if functions asks for it (usually in compose)
                              */
-                            $ret .= charset_convert($res[2],$replace,$default_charset);
+                            $ret .= charset_convert($res[2],$replace,$default_charset,$htmlsafe);
                         } else {
                             // convert string to html codes in order to display it
                             $ret .= charset_decode($res[2],$replace);
                         }
                     } else {
-                        if ($htmlsave) {
+                        if ($htmlsafe) {
                             $replace = htmlspecialchars($replace);
                         }
                         $ret.= $replace;
@@ -735,13 +838,13 @@ function decodeHeader ($string, $utfencode=true,$htmlsave=true,$decide=false) {
                             /* convert string to different charset,
                              * if functions asks for it (usually in compose)
                              */
-                            $replace = charset_convert($res[2], $replace,$default_charset);
+                            $replace = charset_convert($res[2], $replace,$default_charset,$htmlsafe);
                         } else {
                             // convert string to html codes in order to display it
                             $replace = charset_decode($res[2], $replace);
                         }
                     } else {
-                        if ($htmlsave) {
+                        if ($htmlsafe) {
                             $replace = htmlspecialchars($replace);
                         }
                     }
@@ -754,14 +857,14 @@ function decodeHeader ($string, $utfencode=true,$htmlsave=true,$decide=false) {
             $encoded = true;
         }
         if (!$encoded) {
-            if ($htmlsave) {
+            if ($htmlsafe) {
                 $ret .= '&#32;';
             } else {
                 $ret .= ' ';
             }
         }
 
-        if (!$encoded && $htmlsave) {
+        if (!$encoded && $htmlsafe) {
             $ret .= htmlspecialchars($chunk);
         } else {
             $ret .= $chunk;
@@ -770,7 +873,7 @@ function decodeHeader ($string, $utfencode=true,$htmlsave=true,$decide=false) {
     }
     /* remove the first added space */
     if ($ret) {
-        if ($htmlsave) {
+        if ($htmlsafe) {
             $ret = substr($ret,5);
         } else {
             $ret = substr($ret,1);
@@ -785,13 +888,13 @@ function decodeHeader ($string, $utfencode=true,$htmlsave=true,$decide=false) {
  *
  * Function uses XTRA_CODE _encodeheader function, if such function exists.
  *
- * Function uses Q encoding by default and encodes a string according to RFC 
- * 1522 for use in headers if it contains 8-bit characters or anything that 
+ * Function uses Q encoding by default and encodes a string according to RFC
+ * 1522 for use in headers if it contains 8-bit characters or anything that
  * looks like it should be encoded.
  *
- * Function switches to B encoding and encodeHeaderBase64() function, if 
- * string is 8bit and multibyte character set supported by mbstring extension 
- * is used. It can cause E_USER_NOTICE errors, if interface is used with 
+ * Function switches to B encoding and encodeHeaderBase64() function, if
+ * string is 8bit and multibyte character set supported by mbstring extension
+ * is used. It can cause E_USER_NOTICE errors, if interface is used with
  * multibyte character set unsupported by mbstring extension.
  *
  * @param string $string header string, that has to be encoded
@@ -935,27 +1038,27 @@ function encodeHeader ($string) {
 /**
  * Encodes string according to rfc2047 B encoding header formating rules
  *
- * It is recommended way to encode headers with character sets that store 
+ * It is recommended way to encode headers with character sets that store
  * symbols in more than one byte.
  *
  * Function requires mbstring support. If required mbstring functions are missing,
  * function returns false and sets E_USER_WARNING level error message.
  *
- * Minimal requirements - php 4.0.6 with mbstring extension. Please note, 
- * that mbstring functions will generate E_WARNING errors, if unsupported 
+ * Minimal requirements - php 4.0.6 with mbstring extension. Please note,
+ * that mbstring functions will generate E_WARNING errors, if unsupported
  * character set is used. mb_encode_mimeheader function provided by php
  * mbstring extension is not used in order to get better control of header
  * encoding.
  *
- * Used php code functions - function_exists(), trigger_error(), strlen() 
- * (is used with charset names and base64 strings). Used php mbstring 
+ * Used php code functions - function_exists(), trigger_error(), strlen()
+ * (is used with charset names and base64 strings). Used php mbstring
  * functions - mb_strlen and mb_substr.
  *
- * Related documents: rfc 2045 (BASE64 encoding), rfc 2047 (mime header 
+ * Related documents: rfc 2045 (BASE64 encoding), rfc 2047 (mime header
  * encoding), rfc 2822 (header folding)
  *
  * @param string $string header string that must be encoded
- * @param string $charset character set. Must be supported by mbstring extension. 
+ * @param string $charset character set. Must be supported by mbstring extension.
  * Use sq_mb_list_encodings() to detect supported charsets.
  * @return string string encoded according to rfc2047 B encoding formating rules
  * @since 1.5.1
@@ -1081,6 +1184,11 @@ function sq_defang(&$attvalue){
         return;
     }
     $m = false;
+    // before deent, translate the dangerous unicode characters and ... to safe values
+    // otherwise the regular expressions do not match.
+
+
+
     do {
         $m = false;
         $m = $m || sq_deent($attvalue, '/\&#0*(\d+);*/s');
@@ -1106,6 +1214,83 @@ function sq_unspace(&$attvalue){
     }
 }
 
+/**
+ * Translate all dangerous Unicode or Shift_JIS characters which are accepted by
+ * IE as regular characters.
+ *
+ * @param  attvalue  The attribute value before dangerous characters are translated.
+ * @return attvalue  Nothing, modifies a reference value.
+ * @author Marc Groot Koerkamp.
+ */
+function sq_fixIE_idiocy(&$attvalue) {
+    // remove NUL
+    $attvalue = str_replace("\0", "", $attvalue);
+    // remove comments
+    $attvalue = preg_replace("/(\/\*.*?\*\/)/","",$attvalue);
+
+    // IE has the evil habit of accepting every possible value for the attribute expression.
+    // The table below contains characters which are parsed by IE if they are used in the "expression"
+    // attribute value.
+    $aDangerousCharsReplacementTable = array(
+                        array('&#x029F;', '&#0671;' ,/* L UNICODE IPA Extension */
+                              '&#x0280;', '&#0640;' ,/* R UNICODE IPA Extension */
+                              '&#x0274;', '&#0628;' ,/* N UNICODE IPA Extension */
+                              '&#xFF25;', '&#65317;' ,/* Unicode FULLWIDTH LATIN CAPITAL LETTER E */
+                              '&#xFF45;', '&#65349;' ,/* Unicode FULLWIDTH LATIN SMALL LETTER E */
+                              '&#xFF38;', '&#65336;',/* Unicode FULLWIDTH LATIN CAPITAL LETTER X */
+                              '&#xFF58;', '&#65368;',/* Unicode FULLWIDTH LATIN SMALL LETTER X */
+                              '&#xFF30;', '&#65328;',/* Unicode FULLWIDTH LATIN CAPITAL LETTER P */
+                              '&#xFF50;', '&#65360;',/* Unicode FULLWIDTH LATIN SMALL LETTER P */
+                              '&#xFF32;', '&#65330;',/* Unicode FULLWIDTH LATIN CAPITAL LETTER R */
+                              '&#xFF52;', '&#65362;',/* Unicode FULLWIDTH LATIN SMALL LETTER R */
+                              '&#xFF33;', '&#65331;',/* Unicode FULLWIDTH LATIN CAPITAL LETTER S */
+                              '&#xFF53;', '&#65363;',/* Unicode FULLWIDTH LATIN SMALL LETTER S */
+                              '&#xFF29;', '&#65321;',/* Unicode FULLWIDTH LATIN CAPITAL LETTER I */
+                              '&#xFF49;', '&#65353;',/* Unicode FULLWIDTH LATIN SMALL LETTER I */
+                              '&#xFF2F;', '&#65327;',/* Unicode FULLWIDTH LATIN CAPITAL LETTER O */
+                              '&#xFF4F;', '&#65359;',/* Unicode FULLWIDTH LATIN SMALL LETTER O */
+                              '&#xFF2E;', '&#65326;',/* Unicode FULLWIDTH LATIN CAPITAL LETTER N */
+                              '&#xFF4E;', '&#65358;',/* Unicode FULLWIDTH LATIN SMALL LETTER N */
+                              '&#xFF2C;', '&#65324;',/* Unicode FULLWIDTH LATIN CAPITAL LETTER L */
+                              '&#xFF4C;', '&#65356;',/* Unicode FULLWIDTH LATIN SMALL LETTER L */
+                              '&#xFF35;', '&#65333;',/* Unicode FULLWIDTH LATIN CAPITAL LETTER U */
+                              '&#xFF55;', '&#65365;',/* Unicode FULLWIDTH LATIN SMALL LETTER U */
+                              '&#x207F;', '&#8319;' ,/* Unicode SUPERSCRIPT LATIN SMALL LETTER N */
+                              "\xEF\xBC\xA5", /* Shift JIS FULLWIDTH LATIN CAPITAL LETTER E */   // in unicode this is some Chinese char range
+                              "\xEF\xBD\x85", /* Shift JIS FULLWIDTH LATIN SMALL LETTER E */
+                              "\xEF\xBC\xB8", /* Shift JIS FULLWIDTH LATIN CAPITAL LETTER X */
+                              "\xEF\xBD\x98", /* Shift JIS FULLWIDTH LATIN SMALL LETTER X */
+                              "\xEF\xBC\xB0", /* Shift JIS FULLWIDTH LATIN CAPITAL LETTER P */
+                              "\xEF\xBD\x90", /* Shift JIS FULLWIDTH LATIN SMALL LETTER P */
+                              "\xEF\xBC\xB2", /* Shift JIS FULLWIDTH LATIN CAPITAL LETTER R */
+                              "\xEF\xBD\x92", /* Shift JIS FULLWIDTH LATIN SMALL LETTER R */
+                              "\xEF\xBC\xB3", /* Shift JIS FULLWIDTH LATIN CAPITAL LETTER S */
+                              "\xEF\xBD\x93", /* Shift JIS FULLWIDTH LATIN SMALL LETTER S */
+                              "\xEF\xBC\xA9", /* Shift JIS FULLWIDTH LATIN CAPITAL LETTER I */
+                              "\xEF\xBD\x89", /* Shift JIS FULLWIDTH LATIN SMALL LETTER I */
+                              "\xEF\xBC\xAF", /* Shift JIS FULLWIDTH LATIN CAPITAL LETTER O */
+                              "\xEF\xBD\x8F", /* Shift JIS FULLWIDTH LATIN SMALL LETTER O */
+                              "\xEF\xBC\xAE", /* Shift JIS FULLWIDTH LATIN CAPITAL LETTER N */
+                              "\xEF\xBD\x8E", /* Shift JIS FULLWIDTH LATIN SMALL LETTER N */
+                              "\xEF\xBC\xAC", /* Shift JIS FULLWIDTH LATIN CAPITAL LETTER L */
+                              "\xEF\xBD\x8C", /* Shift JIS FULLWIDTH LATIN SMALL LETTER L */
+                              "\xEF\xBC\xB5", /* Shift JIS FULLWIDTH LATIN CAPITAL LETTER U */
+                              "\xEF\xBD\x95", /* Shift JIS FULLWIDTH LATIN SMALL LETTER U */
+                              "\xE2\x81\xBF", /* Shift JIS FULLWIDTH SUPERSCRIPT N */
+                              "\xCA\x9F", /* L UNICODE IPA Extension */
+                              "\xCA\x80", /* R UNICODE IPA Extension */
+                              "\xC9\xB4"),  /* N UNICODE IPA Extension */
+                       array('l', 'l', 'r','r','n','n',
+                             'E','E','e','e','X','X','x','x','P','P','p','p','R','R','r','r','S','S','s','s','I','I',
+                             'i','i','O','O','o','o','N','N','n','n','L','L','l','l','U','U','u','u','n','n',
+                             'E','e','X','x','P','p','R','r','S','s','I','i','O','o','N','n','L','l','U','u','n','l','r','n'));
+    $attvalue = str_replace($aDangerousCharsReplacementTable[0],$aDangerousCharsReplacementTable[1],$attvalue);
+
+    // Escapes are useful for special characters like "{}[]()'&. In other cases they are
+    // used for XSS.
+    $attvalue = preg_replace("/(\\\\)([a-zA-Z]{1})/",'$2',$attvalue);
+}
+
 /**
  * This function returns the final tag out of the tag name, an array
  * of attributes, and the type of the tag. This function is called by
@@ -1519,6 +1704,8 @@ function sq_getnxtag($body, $offset){
 function sq_deent(&$attvalue, $regex, $hex=false){
     $me = 'sq_deent';
     $ret_match = false;
+    // remove comments
+    //$attvalue = preg_replace("/(\/\*.*\*\/)/","",$attvalue);
     preg_match_all($regex, $attvalue, $matches);
     if (is_array($matches) && sizeof($matches[0]) > 0){
         $repl = Array();
@@ -1572,10 +1759,23 @@ function sq_fixatts($tagname,
                 }
             }
         }
+        /**
+         * Workaround for IE quirks
+         */
+        sq_fixIE_idiocy($attvalue);
+
         /**
          * Remove any backslashes, entities, and extraneous whitespace.
          */
+
+        $oldattvalue = $attvalue;
         sq_defang($attvalue);
+        if ($attname == 'style' && $attvalue !== $oldattvalue) {
+            // entities are used in the attribute value. In 99% of the cases it's there as XSS
+            // i.e.<div style="{ left:exp&#x0280;essio&#x0274;( alert('XSS') ) }">
+            $attvalue = "idiocy";
+            $attary{$attname} = $attvalue;
+        }
         sq_unspace($attvalue);
 
         /**
@@ -1598,38 +1798,34 @@ function sq_fixatts($tagname,
                             preg_replace($valmatch, $valrepl, $attvalue);
                         if ($newvalue != $attvalue){
                             $attary{$attname} = $newvalue;
+                            $attvalue = $newvalue;
                         }
                     }
                 }
             }
         }
-
-        /**
-         * Replace empty src tags with the blank image.  src is only used
-         * for frames, images, and image inputs.  Doing a replace should
-         * not affect them working as should be, however it will stop
-         * IE from being kicked off when src for img tags are not set
-         */
-        if (($attname == 'src') && ($attvalue == '""')) {
-            $attary{$attname} = '"' . SM_PATH . 'images/blank.png"';
-        }
-
-        /**
-         * Turn cid: urls into http-friendly ones.
-         */
-        if (preg_match("/^[\'\"]\s*cid:/si", $attvalue)){
-            $attary{$attname} = sq_cid2http($message, $id, $attvalue, $mailbox);
+        if ($attname == 'style') {
+            if (preg_match('/[\0-\37\200-\377]+/',$attvalue)) {
+                // 8bit and control characters in style attribute values can be used for XSS, remove them
+                $attary{$attname} = '"disallowed character"';
+            }
+            preg_match_all("/url\s*\((.+)\)/si",$attvalue,$aMatch);
+            if (count($aMatch)) {
+                foreach($aMatch[1] as $sMatch) {
+                    // url value
+                    $urlvalue = $sMatch;
+                    sq_fix_url($attname, $urlvalue, $message, $id, $mailbox,"'");
+                    $attary{$attname} = str_replace($sMatch,$urlvalue,$attvalue);
+                }
+            }
         }
-
         /**
-         * "Hack" fix for Outlook using propriatary outbind:// protocol in img tags.
-         * One day MS might actually make it match something useful, for now, falling
-         * back to using cid2http, so we can grab the blank.png.
+         * Use white list based filtering on attributes which can contain url's
          */
-        if (preg_match("/^[\'\"]\s*outbind:\/\//si", $attvalue)) {
-            $attary{$attname} = sq_cid2http($message, $id, $attvalue, $mailbox);
+        else if ($attname == 'href' || $attname == 'src' || $attname == 'background') {
+            sq_fix_url($attname, $attvalue, $message, $id, $mailbox);
+            $attary{$attname} = $attvalue;
         }
-
     }
     /**
      * See if we need to append any attributes to this tag.
@@ -1642,6 +1838,102 @@ function sq_fixatts($tagname,
     return $attary;
 }
 
+/**
+ * This function filters url's
+ *
+ * @param  $attvalue        String with attribute value to filter
+ * @param  $message         message object
+ * @param  $id               message id
+ * @param  $mailbox         mailbox
+ * @param  $sQuote          quoting characters around url's
+ */
+function sq_fix_url($attname, &$attvalue, $message, $id, $mailbox,$sQuote = '"') {
+    $attvalue = trim($attvalue);
+    if ($attvalue && ($attvalue[0] =='"'|| $attvalue[0] == "'")) {
+        // remove the double quotes
+        $sQuote = $attvalue[0];
+        $attvalue = trim(substr($attvalue,1,-1));
+    }
+
+    // If there's no "view_unsafe_images" setting in the user's preferences,
+    // turn unsafe images off by default.
+    // FIXME: Check if the UIR plugin is enabled. If it's not, no unsafe images should be displayed regardless of the user's preferences. This test is done in several places in the code and they should all be fixed in the same way.
+    if( !sqgetGlobalVar('view_unsafe_images', $view_unsafe_images, SQ_GET) ) {
+        $view_unsafe_images = false;
+    }
+    $secremoveimg = '../images/' . _("sec_remove_eng.png");
+
+    /**
+     * Replace empty src tags with the blank image.  src is only used
+     * for frames, images, and image inputs.  Doing a replace should
+     * not affect them working as should be, however it will stop
+     * IE from being kicked off when src for img tags are not set
+     */
+    if ($attvalue == '') {
+        $attvalue = '"' . SM_PATH . 'images/blank.png"';
+    } else {
+        // first, disallow 8 bit characters and control characters
+        if (preg_match('/[\0-\37\200-\377]+/',$attvalue)) {
+            switch ($attname) {
+                case 'href':
+                    $attvalue = $sQuote . 'http://invalid-stuff-detected.example.com' . $sQuote;
+                    break;
+                default:
+                    $attvalue = $sQuote . SM_PATH . 'images/blank.png'. $sQuote;
+                    break;
+            }
+        } else {
+            $aUrl = parse_url($attvalue);
+            if (isset($aUrl['scheme'])) {
+                switch(strtolower($aUrl['scheme'])) {
+                    case 'mailto':
+                    case 'http':
+                    case 'https':
+                    case 'ftp':
+                        if ($attname != 'href') {
+                            if ($view_unsafe_images == false) {
+                                $attvalue = $sQuote . $secremoveimg . $sQuote;
+                            } else {
+                                if (isset($aUrl['path'])) {
+                                    // validate image extension.
+                                    $ext = strtolower(substr($aUrl['path'],strrpos($aUrl['path'],'.')));
+                                    if (!in_array($ext,array('.jpeg','.jpg','xjpeg','.gif','.bmp','.jpe','.png','.xbm'))) {
+                                        $attvalue = $sQuote . SM_PATH . 'images/blank.png'. $sQuote;
+                                    }
+                                } else {
+                                    $attvalue = $sQuote . SM_PATH . 'images/blank.png'. $sQuote;
+                                }
+                            }
+                        }
+                        break;
+                    case 'outbind':
+                        /**
+                         * "Hack" fix for Outlook using propriatary outbind:// protocol in img tags.
+                         * One day MS might actually make it match something useful, for now, falling
+                         * back to using cid2http, so we can grab the blank.png.
+                         */
+                        $attvalue = sq_cid2http($message, $id, $attvalue, $mailbox);
+                        break;
+                    case 'cid':
+                        /**
+                            * Turn cid: urls into http-friendly ones.
+                            */
+                        $attvalue = sq_cid2http($message, $id, $attvalue, $mailbox);
+                        break;
+                    default:
+                        $attvalue = $sQuote . SM_PATH . 'images/blank.png' . $sQuote;
+                        break;
+                }
+            } else {
+                if (!(isset($aUrl['path']) && $aUrl['path'] == $secremoveimg)) {
+                    // parse_url did not lead to satisfying result
+                    $attvalue = $sQuote . SM_PATH . 'images/blank.png' . $sQuote;
+                }
+            }
+        }
+    }
+}
+
 /**
  * This function edits the style definition to make them friendly and
  * usable in SquirrelMail.
@@ -1653,20 +1945,77 @@ function sq_fixatts($tagname,
  * @return           a string with edited content.
  */
 function sq_fixstyle($body, $pos, $message, $id, $mailbox){
-    global $view_unsafe_images;
     $me = 'sq_fixstyle';
-    $ret = sq_findnxreg($body, $pos, '</\s*style\s*>');
-    if ($ret == FALSE){
+    // workaround for </style> in between comments
+    $iCurrentPos = $pos;
+    $content = '';
+    $sToken = '';
+    $bSucces = false;
+    $bEndTag = false;
+    for ($i=$pos,$iCount=strlen($body);$i<$iCount;++$i) {
+        $char = $body{$i};
+        switch ($char) {
+            case '<':
+                $sToken = $char;
+                break;
+            case '/':
+                 if ($sToken == '<') {
+                    $sToken .= $char;
+                    $bEndTag = true;
+                 } else {
+                    $content .= $char;
+                 }
+                 break;
+            case '>':
+                 if ($bEndTag) {
+                    $sToken .= $char;
+                    if (preg_match('/\<\/\s*style\s*\>/i',$sToken,$aMatch)) {
+                        $newpos = $i + 1;
+                        $bSucces = true;
+                        break 2;
+                    } else {
+                        $content .= $sToken;
+                    }
+                    $bEndTag = false;
+                 } else {
+                    $content .= $char;
+                 }
+                 break;
+            case '!':
+                if ($sToken == '<') {
+                    // possible comment
+                    if (isset($body{$i+2}) && substr($body,$i,3) == '!--') {
+                        $i = strpos($body,'-->',$i+3);
+                        if ($i === false) { // no end comment
+                            $i = strlen($body);
+                        }
+                        $sToken = '';
+                    }
+                } else {
+                    $content .= $char;
+                }
+                break;
+            default:
+                if ($bEndTag) {
+                    $sToken .= $char;
+                } else {
+                    $content .= $char;
+                }
+                break;
+        }
+    }
+    if ($bSucces == FALSE){
         return array(FALSE, strlen($body));
     }
-    $newpos = $ret[0] + strlen($ret[2]);
-    $content = $ret[1];
+
+
+
     /**
-    * 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 <div>
-    */
+     * 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 <div>
+     */
     $content = preg_replace("|body(\s*\{.*?\})|si", ".bodyclass\\1", $content);
     $secremoveimg = '../images/' . _("sec_remove_eng.png");
     /**
@@ -1674,49 +2023,38 @@ function sq_fixstyle($body, $pos, $message, $id, $mailbox){
     */
     //   $content = preg_replace("|url\s*\(\s*([\'\"])\s*\S+script\s*:.*?([\'\"])\s*\)|si",
     //                           "url(\\1$secremoveimg\\2)", $content);
-    // remove NUL
-    $content = str_replace("\0", "", $content);
-    // NB I insert NUL characters to keep to avoid an infinite loop. They are removed after the loop.
-    while (preg_match("/url\s*\(\s*[\'\"]?([^:]+):(.*)?[\'\"]?\s*\)/si", $content, $matches)) {
-        $sProto = strtolower($matches[1]);
-        switch ($sProto) {
-          /**
-           * Fix url('https*://.*) declarations but only if $view_unsafe_images
-           * is false.
-           */
-          case 'https':
-          case 'http':
-            if (!$view_unsafe_images){
-                $sExpr = "/url\s*\(\s*([\'\"])\s*$sProto*:.*?([\'\"])\s*\)/si";
-                $content = preg_replace($sExpr, "u\0r\0l(\\1$secremoveimg\\2)", $content);
-            }
-            break;
-          /**
-           * Fix urls that refer to cid:
-           */
-          case 'cid':
-            $cidurl = 'cid:'. $matches[2];
-            $httpurl = sq_cid2http($message, $id, $cidurl, $mailbox);
-            $content = preg_replace("|url\s*\(\s*$cidurl\s*\)|si",
-                                "u\0r\0l($httpurl)", $content);
-            break;
-          default:
-            /**
-             * replace url with protocol other then the white list
-             * http,https and cid by an empty string.
-             */
-            $content = preg_replace("/url\s*\(\s*[\'\"]?([^:]+):(.*)?[\'\"]?\s*\)/si",
-                                "", $content);
-            break;
+
+    // first check for 8bit sequences and disallowed control characters
+    if (preg_match('/[\16-\37\200-\377]+/',$content)) {
+        $content = '<!-- style block removed by html filter due to presence of 8bit characters -->';
+        return array($content, $newpos);
+    }
+
+    // IE Sucks hard. We have a special function for it.
+    sq_fixIE_idiocy($content);
+
+    // remove @import line
+    $content = preg_replace("/^\s*(@import.*)$/mi","\n<!-- @import rules forbidden -->\n",$content);
+
+    // translate ur\l and variations (IE parses that)
+    // TODO check if the sq_fixIE_idiocy function already handles this.
+    $content = preg_replace("/(\\\\)?u(\\\\)?r(\\\\)?l(\\\\)?/i", 'url', $content);
+    preg_match_all("/url\s*\((.+)\)/si",$content,$aMatch);
+    if (count($aMatch)) {
+        $aValue = $aReplace = array();
+        foreach($aMatch[1] as $sMatch) {
+            // url value
+            $urlvalue = $sMatch;
+            sq_fix_url('style',$urlvalue, $message, $id, $mailbox,"'");
+            $aValue[] = $sMatch;
+            $aReplace[] = $urlvalue;
         }
-        break;
+        $content = str_replace($aValue,$aReplace,$content);
     }
-    // remove NUL
-    $content = str_replace("\0", "", $content);
 
-   /**
-    * Remove any backslashes, entities, and extraneous whitespace.
-    */
+    /**
+     * Remove any backslashes, entities, and extraneous whitespace.
+     */
     $contentTemp = $content;
     sq_defang($contentTemp);
     sq_unspace($contentTemp);
@@ -1725,11 +2063,14 @@ function sq_fixstyle($body, $pos, $message, $id, $mailbox){
      * Fix stupid css declarations which lead to vulnerabilities
      * in IE.
      */
-    $match   = Array('/expression/i',
+    $match   = Array('/\/\*.*\*\//',
+                    '/expression/i',
                     '/behaviou*r/i',
                     '/binding/i',
-                    '/include-source/i');
-    $replace = Array('idiocy', 'idiocy', 'idiocy', 'idiocy');
+                    '/include-source/i',
+                    '/javascript/i',
+                    '/script/i');
+    $replace = Array('','idiocy', 'idiocy', 'idiocy', 'idiocy', 'idiocy', 'idiocy');
     $contentNew = preg_replace($match, $replace, $contentTemp);
     if ($contentNew !== $contentTemp) {
         // insecure css declarations are used. From now on we don't care
@@ -1767,8 +2108,8 @@ function sq_cid2http($message, $id, $cidurl, $mailbox){
     $cidurl = preg_replace($match_str, $str_rep, $cidurl);
 
     $linkurl = find_ent_id($cidurl, $message);
-    /* in case of non-save cid links $httpurl should be replaced by a sort of
-       unsave link image */
+    /* in case of non-safe cid links $httpurl should be replaced by a sort of
+       unsafe link image */
     $httpurl = '';
 
     /**
@@ -2041,12 +2382,14 @@ function sq_sanitize($body,
  */
 function magicHTML($body, $id, $message, $mailbox = 'INBOX', $take_mailto_links =true) {
 
-    require_once(SM_PATH . 'functions/url_parser.php');  // for $MailTo_PReg_Match
+    // require_once(SM_PATH . 'functions/url_parser.php');  // for $MailTo_PReg_Match
 
     global $attachment_common_show_images, $view_unsafe_images,
            $has_unsafe_images;
     /**
      * Don't display attached images in HTML mode.
+     *
+     * SB: why?
      */
     $attachment_common_show_images = false;
     $tag_list = Array(
@@ -2109,7 +2452,6 @@ function magicHTML($body, $id, $message, $mailbox = 'INBOX', $take_mailto_links
                         "\\1$secremoveimg\\2",
                         "\\1$secremoveimg\\2",
                         "\\1$secremoveimg\\2",
-                        "\\1$secremoveimg\\2"
                         )
                     ),
                 "/^href|action/i" =>
@@ -2120,7 +2462,6 @@ function magicHTML($body, $id, $message, $mailbox = 'INBOX', $take_mailto_links
                         "/^([\'\"])\s*about\s*:.*([\'\"])/si"
                         ),
                     Array(
-                        "\\1#\\1",
                         "\\1#\\1",
                         "\\1#\\1",
                         "\\1#\\1"
@@ -2129,24 +2470,26 @@ function magicHTML($body, $id, $message, $mailbox = 'INBOX', $take_mailto_links
         "/^style/i" =>
             Array(
                 Array(
+                    "/\/\*.*\*\//",
                     "/expression/i",
                     "/binding/i",
                     "/behaviou*r/i",
                     "/include-source/i",
                     "/position\s*:\s*absolute/i",
+                    "/(\\\\)?u(\\\\)?r(\\\\)?l(\\\\)?/i",
                     "/url\s*\(\s*([\'\"])\s*\S+script\s*:.*([\'\"])\s*\)/si",
                     "/url\s*\(\s*([\'\"])\s*mocha\s*:.*([\'\"])\s*\)/si",
                     "/url\s*\(\s*([\'\"])\s*about\s*:.*([\'\"])\s*\)/si",
                     "/(.*)\s*:\s*url\s*\(\s*([\'\"]*)\s*\S+script\s*:.*([\'\"]*)\s*\)/si"
                     ),
                 Array(
+                    "",
                     "idiocy",
                     "idiocy",
                     "idiocy",
                     "idiocy",
-                    "",
-                    "url(\\1#\\1)",
-                    "url(\\1#\\1)",
+                    "idiocy",
+                    "url",
                     "url(\\1#\\1)",
                     "url(\\1#\\1)",
                     "url(\\1#\\1)",
@@ -2155,9 +2498,14 @@ function magicHTML($body, $id, $message, $mailbox = 'INBOX', $take_mailto_links
                 )
             )
         );
+
+    // If there's no "view_unsafe_images" setting in the user's preferences,
+    // turn unsafe images off by default.
+    // FIXME: Check if the UIR plugin is enabled. If it's not, no unsafe images should be displayed regardless of the user's preferences. This test is done in several places in the code and they should all be fixed in the same way.
     if( !sqgetGlobalVar('view_unsafe_images', $view_unsafe_images, SQ_GET) ) {
         $view_unsafe_images = false;
     }
+
     if (!$view_unsafe_images){
         /**
          * Remove any references to http/https if view_unsafe_images set
@@ -2168,7 +2516,7 @@ function magicHTML($body, $id, $message, $mailbox = 'INBOX', $take_mailto_links
         array_push($bad_attvals{'/.*/'}{'/^src|background/i'}[1],
                 "\\1$secremoveimg\\1");
         array_push($bad_attvals{'/.*/'}{'/^style/i'}[0],
-                '/url\(([\'\"])\s*https*:.*([\'\"])\)/si');
+                '/url\([\'\"]?https?:[^\)]*[\'\"]?\)/si');
         array_push($bad_attvals{'/.*/'}{'/^style/i'}[1],
                 "url(\\1$secremoveimg\\1)");
     }
@@ -2191,7 +2539,7 @@ function magicHTML($body, $id, $message, $mailbox = 'INBOX', $take_mailto_links
                            $id,
                            $mailbox
                            );
-    if (preg_match("|$secremoveimg|i", $trusted)){
+    if (strpos($trusted,$secremoveimg)){
         $has_unsafe_images = true;
     }
 
@@ -2203,7 +2551,7 @@ function magicHTML($body, $id, $message, $mailbox = 'INBOX', $take_mailto_links
     if ($take_mailto_links) {
         // parseUrl($trusted);   // this even parses URLs inside of tags... too aggressive
         global $MailTo_PReg_Match;
-        $MailTo_PReg_Match = '/mailto:' . substr($MailTo_PReg_Match, 1);
+        $MailTo_PReg_Match = '/mailto:' . substr($MailTo_PReg_Match, 1) ;
         if ((preg_match_all($MailTo_PReg_Match, $trusted, $regs)) && ($regs[0][0] != '')) {
             foreach ($regs[0] as $i => $mailto_before) {
                 $mailto_params = $regs[10][$i];
@@ -2266,18 +2614,18 @@ function magicHTML($body, $id, $message, $mailbox = 'INBOX', $take_mailto_links
  */
 function SendDownloadHeaders($type0, $type1, $filename, $force, $filesize=0) {
     global $languages, $squirrelmail_language;
-    $isIE = $isIE6 = 0;
+    $isIE = $isIE6plus = false;
 
     sqgetGlobalVar('HTTP_USER_AGENT', $HTTP_USER_AGENT, SQ_SERVER);
 
     if (strstr($HTTP_USER_AGENT, 'compatible; MSIE ') !== false &&
             strstr($HTTP_USER_AGENT, 'Opera') === false) {
-        $isIE = 1;
+        $isIE = true;
     }
 
-    if (strstr($HTTP_USER_AGENT, 'compatible; MSIE 6') !== false &&
-            strstr($HTTP_USER_AGENT, 'Opera') === false) {
-        $isIE6 = 1;
+    if (preg_match('/compatible; MSIE ([0-9]+)/', $HTTP_USER_AGENT, $match) &&
+        ((int)$match[1]) >= 6 && strstr($HTTP_USER_AGENT, 'Opera') === false) {
+        $isIE6plus = true;
     }
 
     if (isset($languages[$squirrelmail_language]['XTRA_CODE']) &&
@@ -2334,17 +2682,20 @@ function SendDownloadHeaders($type0, $type1, $filename, $force, $filesize=0) {
         // "attachment"... does it apply to inline too?
         header ("Content-Disposition: attachment; filename=\"$filename\"");
 
-        if ($isIE && !$isIE6) {
+        if ($isIE && !$isIE6plus) {
             // This combination seems to work mostly.  IE 5.5 SP 1 has
             // known issues (see the Microsoft Knowledge Base)
 
             // This works for most types, but doesn't work with Word files
             header ("Content-Type: application/download; name=\"$filename\"");
-
+            header ("Content-Type: application/force-download; name=\"$filename\"");
             // These are spares, just in case.  :-)
             //header("Content-Type: $type0/$type1; name=\"$filename\"");
             //header("Content-Type: application/x-msdownload; name=\"$filename\"");
             //header("Content-Type: application/octet-stream; name=\"$filename\"");
+        } else if ($isIE) {
+             // This is to prevent IE for MIME sniffing and auto open a file in IE
+             header ("Content-Type: application/force-download; name=\"$filename\"");
         } else {
             // another application/octet-stream forces download for Netscape
             header ("Content-Type: application/octet-stream; name=\"$filename\"");
@@ -2357,5 +2708,3 @@ function SendDownloadHeaders($type0, $type1, $filename, $force, $filesize=0) {
     }
 
 }  // end fn SendDownloadHeaders
-
-?>