* This contains the functions necessary to detect and decode MIME
* messages.
*
- * @copyright © 1999-2006 The SquirrelMail Project Team
+ * @copyright 1999-2009 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 */
$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':
if (strtolower($flag) == '\\flagged') {
$msg->is_flagged = true;
}
+ else if (strtolower($flag) == '$forwarded') {
+ $msg->is_forwarded = true;
+ }
break;
case 'M':
if (strtolower($flag) == '$mdnsent') {
} while($topline && ($topline[0] == '*') && !preg_match('/\* [0-9]+ FETCH.*/i', $topline)) ;
$wholemessage = implode('', $data);
- if (ereg('\\{([^\\}]*)\\}', $topline, $regs)) {
+ if (preg_match('/\{([^\}]*)\}/', $topline, $regs)) {
$ret = substr($wholemessage, 0, $regs[1]);
/* There is some information in the content info header that could be important
* in order to parse html messages. Let's get them here.
// if ($ret{0} == '<') {
// $data = sqimap_run_command ($imap_stream, "FETCH $id BODY[$ent_id.MIME]", true, $response, $message, TRUE);
// }
- } else if (ereg('"([^"]*)"', $topline, $regs)) {
+ } else if (preg_match('/"([^"]*)"/', $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) . '&passed_id=' . $passed_id;
return $ret;
}
-function mime_print_body_lines ($imap_stream, $id, $ent_id=1, $encoding, $rStream='php://stdout') {
+function mime_print_body_lines ($imap_stream, $id, $ent_id=1, $encoding, $rStream='php://stdout', $force_crlf='') {
/* Don't kill the connection if the browser is over a dialup
* and it would take over 30 seconds to download it.
} else {
$body = mime_fetch_body ($imap_stream, $id, $ent_id);
if (is_resource($rStream)) {
- fputs($rStream,decodeBody($body,$encoding));
+ fputs($rStream,decodeBody($body, $encoding, $force_crlf));
} else {
- echo decodeBody($body, $encoding);
+ echo decodeBody($body, $encoding, $force_crlf);
}
}
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++) {
- $line = $body_ary[$i];
+ $line = rtrim($body_ary[$i],"\r");
+
if (strlen($line) - 2 >= $wrap_at) {
sqWordWrap($line, $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;
* @param string $ent_num (since 1.3.0) message part id
* @param integer $id (since 1.3.0) message id
* @param string $mailbox (since 1.3.0) imap folder name
- * @param boolean $clean (since 1.5.1) Do not output stuff that's irrelevant for the printable version.
* @return string html formated message text
*/
-function formatBody($imap_stream, $message, $color, $wrap_at, $ent_num, $id, $mailbox='INBOX', $clean=FALSE) {
+function formatBody($imap_stream, $message, $color, $wrap_at, $ent_num, $id, $mailbox='INBOX') {
/* 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, $languages, $squirrelmail_language,
$show_html_default, $sort, $has_unsafe_images, $passed_ent_id,
- $use_iframe, $iframe_height, $download_and_unsafe_link;
+ $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( !sqgetGlobalVar('view_unsafe_images', $view_unsafe_images, SQ_GET) ) {
- $view_unsafe_images = false;
- }
+ // If there's no "view_unsafe_images" variable in the URL, turn unsafe
+ // images off by default.
+ sqgetGlobalVar('view_unsafe_images', $view_unsafe_images, SQ_GET, FALSE);
$body = '';
$urlmailbox = urlencode($mailbox);
$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.
$body = trim($body);
translateText($body, $wrap_at,
$body_message->header->getParameter('charset'));
- } elseif ($use_iframe && ! $clean) {
- // $clean is used to remove iframe in printable view.
-
+ } elseif ($use_iframe) {
/**
* If we don't add html message between iframe tags,
* we must detect unsafe images and modify $has_unsafe_images.
. '&ent_id=' . $ent_num
. '&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>";
+ global $oTemplate;
+ $oTemplate->assign('iframe_url', $iframeurl);
+ $oTemplate->assign('iframe_height', $iframe_height);
+ $oTemplate->assign('html_body', $html_body);
- // if browser can't render iframe, it renders html message.
- $body.= $html_body;
-
- // close iframe
- $body.="</iframe></div>\n";
+ $body = $oTemplate->fetch('read_html_iframe.tpl');
} else {
// old way of html rendering
- $body = magicHTML($body, $id, $message, $mailbox);
/**
* 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.
*/
- $body = charset_decode($body_message->header->getParameter('charset'),$body,false,true);
+ $charset = $body_message->header->getParameter('charset');
+ if (!empty($charset)) {
+ $body = charset_decode($charset,$body,false,true);
+ }
+ $body = magicHTML($body, $id, $message, $mailbox);
}
} else {
translateText($body, $wrap_at,
$body_message->header->getParameter('charset'));
}
- // if this is the clean display (i.e. printer friendly), stop here.
- if ( $clean ) {
- 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 . '&ent_id='.$ent_num.
'&mailbox=' . $urlmailbox .'&sort=' . $sort .
'&startMessage=' . $startMessage . '&show_more=0';
if (isset($passed_ent_id)) {
$link .= '&passed_ent_id='.$passed_ent_id;
}
- $download_and_unsafe_link .= ' | <a href="download.php?absolute_dl=true&' .
- $link . '">' . _("Download this as a file") . '</a>';
+ $download_href = SM_PATH . 'src/download.php?absolute_dl=true&' . $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 {
$text = '';
}
}
+
+ // Only create a link for unsafe images if there's need for one. If so:
+ // add it to the global variable.
if($text != '') {
- $download_and_unsafe_link .= ' | <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);
}
}
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.
*
+ * @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&passed_id=$id&mailbox=$urlMailbox&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 ) == '') {
} 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);
if ($where && $what) {
$defaultlink .= '&where='. urlencode($where).'&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 .= '&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> </td>' .
- '<td><small><b>' . show_readable_size($header->size) .
- '</b> </small></td>' .
- '<td><small>[ '.htmlspecialchars($type0).'/'.htmlspecialchars($type1).' ] </small></td>' .
- '<td><small>';
- $attachments .= '<b>' . $description . '</b>';
- $attachments .= '</small></td><td><small> ';
-
- $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 .= ' | ';
- }
- $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
}
/**
- * Decodes encoded message body
+ * Decodes encoded string (usually message body)
+ *
+ * This function decodes a string (usually the message body)
+ * depending on the encoding type. Currently quoted-printable
+ * and base64 encodings are supported.
+ *
+ * The decode_body hook was added to this function in 1.4.2/1.5.0.
+ * The $force_crlf parameter was added in 1.5.2.
+ *
+ * @param string $string The encoded string
+ * @param string $encoding used encoding
+ * @param string $force_crlf Whether or not to force CRLF or LF
+ * line endings (or to leave as is).
+ * If given as "LF", line endings will
+ * all be converted to LF; if "CRLF",
+ * line endings will all be converted
+ * to CRLF. If given as an empty value,
+ * the global $force_crlf_default will
+ * be consulted (it can be specified in
+ * config/config_local.php). Otherwise,
+ * any other value will cause the string
+ * to be left alone. Note that this will
+ * be overridden to "LF" if not using at
+ * least PHP version 4.3.0. (OPTIONAL;
+ * default is empty - consult global
+ * default value)
+ *
+ * @return string The decoded string
*
- * 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) {
+function decodeBody($string, $encoding, $force_crlf='') {
+
+ global $force_crlf_default;
+ if (empty($force_crlf)) $force_crlf = $force_crlf_default;
+ $force_crlf = strtoupper($force_crlf);
+
+ // must force line endings to LF due to broken
+ // quoted_printable_decode() in PHP versions
+ // before 4.3.0 (see below)
+ //
+ if (!check_php_version(4, 3, 0) || $force_crlf == 'LF')
+ $string = str_replace("\r\n", "\n", $string);
+ else if ($force_crlf == 'CRLF')
+ $string = str_replace("\n", "\r\n", $string);
- $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
+ // plugins get first shot at decoding the string
//
if (!empty($encoding_handler) && function_exists($encoding_handler)) {
- $body = $encoding_handler('decode', $body);
+ $string = $encoding_handler('decode', $string);
} 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);
+
+ // 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 is PHP 4.0.4+
+ // and the above call to: str_replace("\r\n", "\n", $string);
+ //
+ $string = quoted_printable_decode($string);
+
} elseif ($encoding == 'base64') {
- $body = base64_decode($body);
+ $string = base64_decode($string);
}
// All other encodings are returned raw.
- return $body;
+ return $string;
}
/**
* 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);
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 .= ' ';
} else {
$ret .= ' ';
}
$iLastMatch = $i;
$j = $i;
- if ($htmlsave) {
+ if ($htmlsafe) {
$ret .= htmlspecialchars($res[1]);
} else {
$ret .= $res[1];
/* 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;
/* 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);
}
}
$encoded = true;
}
if (!$encoded) {
- if ($htmlsave) {
+ if ($htmlsafe) {
$ret .= ' ';
} else {
$ret .= ' ';
}
}
- if (!$encoded && $htmlsave) {
+ if (!$encoded && $htmlsafe) {
$ret .= htmlspecialchars($chunk);
} else {
$ret .= $chunk;
}
/* remove the first added space */
if ($ret) {
- if ($htmlsave) {
+ if ($htmlsafe) {
$ret = substr($ret,5);
} else {
$ret = substr($ret,1);
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, '/\�*(\d+);*/s');
}
}
+/**
+ * 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('ʟ', 'ʟ' ,/* L UNICODE IPA Extension */
+ 'ʀ', 'ʀ' ,/* R UNICODE IPA Extension */
+ 'ɴ', 'ɴ' ,/* N UNICODE IPA Extension */
+ 'E', 'E' ,/* Unicode FULLWIDTH LATIN CAPITAL LETTER E */
+ 'e', 'e' ,/* Unicode FULLWIDTH LATIN SMALL LETTER E */
+ 'X', 'X',/* Unicode FULLWIDTH LATIN CAPITAL LETTER X */
+ 'x', 'x',/* Unicode FULLWIDTH LATIN SMALL LETTER X */
+ 'P', 'P',/* Unicode FULLWIDTH LATIN CAPITAL LETTER P */
+ 'p', 'p',/* Unicode FULLWIDTH LATIN SMALL LETTER P */
+ 'R', 'R',/* Unicode FULLWIDTH LATIN CAPITAL LETTER R */
+ 'r', 'r',/* Unicode FULLWIDTH LATIN SMALL LETTER R */
+ 'S', 'S',/* Unicode FULLWIDTH LATIN CAPITAL LETTER S */
+ 's', 's',/* Unicode FULLWIDTH LATIN SMALL LETTER S */
+ 'I', 'I',/* Unicode FULLWIDTH LATIN CAPITAL LETTER I */
+ 'i', 'i',/* Unicode FULLWIDTH LATIN SMALL LETTER I */
+ 'O', 'O',/* Unicode FULLWIDTH LATIN CAPITAL LETTER O */
+ 'o', 'o',/* Unicode FULLWIDTH LATIN SMALL LETTER O */
+ 'N', 'N',/* Unicode FULLWIDTH LATIN CAPITAL LETTER N */
+ 'n', 'n',/* Unicode FULLWIDTH LATIN SMALL LETTER N */
+ 'L', 'L',/* Unicode FULLWIDTH LATIN CAPITAL LETTER L */
+ 'l', 'l',/* Unicode FULLWIDTH LATIN SMALL LETTER L */
+ 'U', 'U',/* Unicode FULLWIDTH LATIN CAPITAL LETTER U */
+ 'u', 'u',/* Unicode FULLWIDTH LATIN SMALL LETTER U */
+ 'ⁿ', 'ⁿ' ,/* 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
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();
}
}
}
+ /**
+ * 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ʀessioɴ( alert('XSS') ) }">
+ $attvalue = "idiocy";
+ $attary{$attname} = $attvalue;
+ }
sq_unspace($attvalue);
/**
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"';
+ 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);
+ }
+ }
}
-
/**
- * Turn cid: urls into http-friendly ones.
+ * Use white list based filtering on attributes which can contain url's
*/
- if (preg_match("/^[\'\"]\s*cid:/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;
}
-
- /**
- * "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.
- */
- if (preg_match("/^[\'\"]\s*outbind:\/\//si", $attvalue)) {
- $attary{$attname} = sq_cid2http($message, $id, $attvalue, $mailbox);
- }
-
}
/**
* See if we need to append any attributes to this tag.
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" variable in the URL, turn unsafe
+ // images off by default.
+ sqgetGlobalVar('view_unsafe_images', $view_unsafe_images, SQ_GET, 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'])) {
+
+ // No one has been able to show that image URIs
+ // can be exploited, so for now, no restrictions
+ // are made at all. If this proves to be a problem,
+ // the commented-out code below can be of help.
+ // (One consideration is that I see nothing in this
+ // function that specifically says that we will
+ // only ever arrive here when inspecting an image
+ // tag, although that does seem to be the end
+ // result - e.g., <script src="..."> where malicious
+ // image URIs are in fact a problem are already
+ // filtered out elsewhere.
+ /* ---------------------------------
+ // validate image extension.
+ $ext = strtolower(substr($aUrl['path'],strrpos($aUrl['path'],'.')));
+ if (!in_array($ext,array('.jpeg','.jpg','xjpeg','.gif','.bmp','.jpe','.png','.xbm'))) {
+ // If URI is to something other than
+ // a regular image file, get the contents
+ // and try to see if it is an image.
+ // Don't use Fileinfo (finfo_file()) because
+ // we'd need to make the admin configure the
+ // location of the magic.mime file (FIXME: add finfo_file() support later?)
+ //
+ $mime_type = '';
+ if (function_exists('mime_content_type')
+ && ($FILE = @fopen($attvalue, 'rb', FALSE))) {
+
+ // fetch file
+ //
+ $file_contents = '';
+ while (!feof($FILE)) {
+ $file_contents .= fread($FILE, 8192);
+ }
+ fclose($FILE);
+
+ // store file locally
+ //
+ global $attachment_dir, $username;
+ $hashed_attachment_dir = getHashedDir($username, $attachment_dir);
+ $localfilename = GenerateRandomString(32, '', 7);
+ $full_localfilename = "$hashed_attachment_dir/$localfilename";
+ while (file_exists($full_localfilename)) {
+ $localfilename = GenerateRandomString(32, '', 7);
+ $full_localfilename = "$hashed_attachment_dir/$localfilename";
+ }
+ $FILE = fopen("$hashed_attachment_dir/$localfilename", 'wb');
+ fwrite($FILE, $file_contents);
+ fclose($FILE);
+
+ // get mime type and remove file
+ //
+ $mime_type = mime_content_type("$hashed_attachment_dir/$localfilename");
+ unlink("$hashed_attachment_dir/$localfilename");
+ }
+ // debug: echo "$attvalue FILE TYPE IS $mime_type<HR>";
+ if (substr(strtolower($mime_type), 0, 5) != 'image') {
+ $attvalue = $sQuote . SM_PATH . 'images/blank.png'. $sQuote;
+ }
+ }
+ --------------------------------- */
+ } else {
+ $attvalue = $sQuote . SM_PATH . 'images/blank.png'. $sQuote;
+ }
+ }
+ } else {
+ $attvalue = $sQuote . $attvalue . $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 = $sQuote . sq_cid2http($message, $id, $attvalue, $mailbox) . $sQuote;
+ break;
+ case 'cid':
+ /**
+ * Turn cid: urls into http-friendly ones.
+ */
+ $attvalue = $sQuote . sq_cid2http($message, $id, $attvalue, $mailbox) . $sQuote;
+ 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.
* @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");
/**
*/
// $content = preg_replace("|url\s*\(\s*([\'\"])\s*\S+script\s*:.*?([\'\"])\s*\)|si",
// "url(\\1$secremoveimg\\2)", $content);
- // remove NUL
- $content = str_replace("\0", "", $content);
- // translate ur\l and variations (IE parses that)
- $content = preg_replace("/(\\\\)?u(\\\\)?r(\\\\)?l(\\\\)?/i", 'url', $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);
+ // 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);
+ }
- } else {
- $content = preg_replace('/url/i',"u\0r\0l",$content);
- }
- break;
- /**
- * Fix urls that refer to cid:
- */
- case 'cid':
- $cidurl = 'cid:'. $matches[2];
- $httpurl = sq_cid2http($message, $id, $cidurl, $mailbox);
- // escape parentheses that can modify the regular expression
- $cidurl = str_replace(array('(',')'),array('\\(','\\)'),$cidurl);
- $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;
+ // 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;
}
+ $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);
/**
* Fix stupid css declarations which lead to vulnerabilities
* in IE.
+ *
+ * Also remove "position" attribute, as it can easily be set
+ * to "fixed" or "absolute" with "left" and "top" attributes
+ * of zero, taking over the whole content frame. It can also
+ * be set to relative and move itself anywhere it wants to,
+ * displaying content in areas it shouldn't be allowed to touch.
*/
$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',
+ '/position/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
$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 = '';
/**
}
if (!empty($linkurl)) {
- $httpurl = $quotchar . SM_PATH . 'src/download.php?absolute_dl=true&' .
+ $httpurl = $quotchar . sqm_baseuri() . 'src/download.php?absolute_dl=true&' .
"passed_id=$id&mailbox=" . urlencode($mailbox) .
'&ent_id=' . $linkurl . $quotchar;
} else {
*/
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(
"/binding/i",
"/behaviou*r/i",
"/include-source/i",
- "/position\s*:\s*absolute/i",
+
+ // position:relative can also be exploited
+ // to put content outside of email body area
+ // and position:fixed is similarly exploitable
+ // as position:absolute, so we'll remove it
+ // altogether....
+ //
+ // Does this screw up legitimate HTML messages?
+ // If so, the only fix I see is to allow position
+ // attributes (any values? I think we still have
+ // to block static and fixed) only if $use_iframe
+ // is enabled (1.5.0+)
+ //
+ // was: "/position\s*:\s*absolute/i",
+ //
+ "/position\s*:/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"
+ "/(.*)\s*:\s*url\s*\(\s*([\'\"]*)\s*\S+script\s*:.*([\'\"]*)\s*\)/si",
),
Array(
"",
"idiocy",
"idiocy",
"idiocy",
- "",
+ "idiocy",
"url",
"url(\\1#\\1)",
"url(\\1#\\1)",
)
)
);
- if( !sqgetGlobalVar('view_unsafe_images', $view_unsafe_images, SQ_GET) ) {
- $view_unsafe_images = false;
- }
+
+ // If there's no "view_unsafe_images" variable in the URL, turn unsafe
+ // images off by default.
+ sqgetGlobalVar('view_unsafe_images', $view_unsafe_images, SQ_GET, FALSE);
+
if (!$view_unsafe_images){
/**
* Remove any references to http/https if view_unsafe_images set
$id,
$mailbox
);
- if (preg_match("|$secremoveimg|i", $trusted)){
+ if (strpos($trusted,$secremoveimg)){
$has_unsafe_images = true;
}
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];
$filename =
call_user_func($languages[$squirrelmail_language]['XTRA_CODE'] . '_downloadfilename', $filename, $HTTP_USER_AGENT);
} else {
- $filename = ereg_replace('[\\/:\*\?"<>\|;]', '_', str_replace(' ', ' ', $filename));
+ $filename = preg_replace('/[\\\\\/:*?"<>|;]/', '_', str_replace(' ', ' ', $filename));
}
// A Pox on Microsoft and it's Internet Explorer!
// 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\"");
}
} // end fn SendDownloadHeaders
-
-?>