* This contains the functions necessary to detect and decode MIME
* messages.
*
- * @copyright © 1999-2007 The SquirrelMail Project Team
+ * @copyright 1999-2021 The SquirrelMail Project Team
* @license http://opensource.org/licenses/gpl-license.php GNU Public License
* @version $Id$
* @package squirrelmail
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>';
+ $errormessage .= '<pre>' . sm_encode_html_special_chars($read) . '</pre>';
plain_error_message( $errormessage );
echo '</body></html>';
exit;
if (strtolower($flag) == '\\flagged') {
$msg->is_flagged = true;
}
+ else if (strtolower($flag) == '$forwarded') {
+ $msg->is_forwarded = true;
+ }
break;
case 'M':
if (strtolower($flag) == '$mdnsent') {
$data = sqimap_run_command ($imap_stream, $cmd, true, $response, $message, TRUE);
do {
$topline = trim(array_shift($data));
- } while($topline && ($topline[0] == '*') && !preg_match('/\* [0-9]+ FETCH.*/i', $topline)) ;
+ } while($topline && ($topline[0] == '*') && !preg_match('/\* [0-9]+ FETCH .*BODY.*/i', $topline)) ;
+ // Matching with "BODY" above is difficult: in most cases "FETCH \(BODY" would work
+ // but some servers may put other things in the same result, perhaps something such
+ // as "* 23 FETCH (FLAGS (\Seen) BODY[1] {174}". There is some small chance that
+ // if the character sequence "BODY" appears in a response where it isn't actually
+ // a FETCH response data item name, the current regex will break things. The better
+ // way to do this would be to parse the response correctly and not use a regex.
$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;
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);
}
}
$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);
}
* @param integer $id message id
*/
function buildAttachmentArray($message, $exclude_id, $mailbox, $id) {
- global $where, $what, $startMessage, $color, $passed_ent_id, $base_uri;
+ global $where, $what, $startMessage, $color, $passed_ent_id,
+ $base_uri, $block_svg_download;
$att_ar = $message->getAttachments($exclude_id);
$urlMailbox = urlencode($mailbox);
$header = $att->header;
$type0 = strtolower($header->type0);
$type1 = strtolower($header->type1);
+ if ($block_svg_download && strpos($type1, 'svg') === 0)
+ continue;
+
$name = '';
$links = array();
$links['download link']['text'] = _("Download");
}
/* 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. Finally, a hook for ALL attachment
- * types is run as well.
+ * It also allows plugins to run if there's a rule for a more
+ * generic type. Finally, a hook for ALL attachment types is
+ * run as well.
*/
// First remember the default link.
$defaultlink_orig = $defaultlink;
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);
+ $temp = array(&$links, &$startMessage, &$id, &$urlMailbox, &$ent,
+ &$defaultlink, &$display_filename, &$where, &$what,
+ &$type0, &$type1);
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);
+ /* 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,
+ &$type0, &$type1);
+ // 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 $type0/*", $temp);
/* 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);
+ &$defaultlink, &$display_filename, &$where, &$what,
+ &$type0, &$type1);
// Do not let a generic plugin change the default link if a more
// specialized one already did it...
if ($defaultlink != $defaultlink_orig) {
$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);
+
+ // base64 encoded file sizes are misleading, so approximate real size
+ if (!empty($header->encoding) && strtolower($header->encoding) == 'base64')
+ $this_attachment['Size'] = $header->size / 4 * 3;
+ else
+ $this_attachment['Size'] = $header->size;
+
+ $this_attachment['ContentType'] = sm_encode_html_special_chars($type0 .'/'. $type1);
$this_attachment['OtherLinks'] = array();
foreach ($links as $val) {
- if ($val['text']==_("Download") || $val['text'] == _("View"))
+ if ($val['text']==_("Download")) {
+ $this_attachment['DownloadHREF'] = $val['href'];
+ continue;
+ }
+ if ($val['text']==_("View")) {
+ $this_attachment['ViewHREF'] = $val['href'];
continue;
- if (empty($val['text']) && empty($val['extra']))
+ }
+
+ // This makes no sense - If 'text' and 'extra' are just concatenated,
+ // there is no point in having 'extra'.... I am going to assume this
+ // was a mistake and am changing 'extra' to be what I think it was
+ // meant to be: additional tag attributes. However, I'm not checking
+ // extensively for plugins that were using this the wrong way (but why would they?)
+ if (empty($val['text']))
continue;
$temp = array();
$temp['HREF'] = $val['href'];
- $temp['Text'] = (empty($val['text']) ? '' : $val['text']) . (empty($val['extra']) ? '' : $val['extra']);
+ $temp['Text'] = $val['text'];
+ $temp['Extra'] = (empty($val['extra']) ? '' : $val['extra']);
$this_attachment['OtherLinks'][] = $temp;
}
$attachments[] = $this_attachment;
}
/**
- * 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('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;
}
/**
* @return string decoded header string
*/
function decodeHeader ($string, $utfencode=true,$htmlsafe=true,$decide=false) {
- global $languages, $squirrelmail_language,$default_charset;
+ global $languages, $squirrelmail_language,$default_charset, $fix_broken_base64_encoded_messages;
if (is_array($string)) {
$string = implode("\n", $string);
}
$iLastMatch = -2;
$encoded = true;
+// FIXME: spaces are allowed inside quoted-printable encoding, but the following line will bust up any such encoded strings
$aString = explode(' ',$string);
$ret = '';
foreach ($aString as $chunk) {
$iLastMatch = $i;
$j = $i;
if ($htmlsafe) {
- $ret .= htmlspecialchars($res[1]);
+ $ret .= sm_encode_html_special_chars($res[1]);
} else {
$ret .= $res[1];
}
switch ($encoding)
{
case 'B':
+ // fix broken base64-encoded strings (remove end = padding,
+ // change any = to + in middle of string, add padding back
+ // to the end)
+ if ($fix_broken_base64_encoded_messages) {
+ $encoded_string_minus_padding = strtr(rtrim($res[4], '='), '=', '+');
+ $res[4] = str_pad($encoded_string_minus_padding, strlen($res[4]), '=');
+ }
$replace = base64_decode($res[4]);
if ($utfencode) {
if ($can_be_encoded) {
}
} else {
if ($htmlsafe) {
- $replace = htmlspecialchars($replace);
+ $replace = sm_encode_html_special_chars($replace);
}
$ret.= $replace;
}
break;
case 'Q':
$replace = str_replace('_', ' ', $res[4]);
- $replace = preg_replace('/=([0-9a-f]{2})/ie', 'chr(hexdec("\1"))',
+ $replace = preg_replace_callback('/=([0-9a-f]{2})/i',
+ create_function ('$matches', 'return chr(hexdec($matches[1]));'),
$replace);
if ($utfencode) {
if ($can_be_encoded) {
}
} else {
if ($htmlsafe) {
- $replace = htmlspecialchars($replace);
+ $replace = sm_encode_html_special_chars($replace);
}
}
$ret .= $replace;
}
if (!$encoded && $htmlsafe) {
- $ret .= htmlspecialchars($chunk);
+ $ret .= sm_encode_html_special_chars($chunk);
} else {
$ret .= $chunk;
}
for($i = 0; $i < $j; ++$i) {
switch($string{$i})
{
+ case '"':
case '=':
case '<':
case '>':
$fulltag = '<' . $tagname;
if (is_array($attary) && sizeof($attary)){
$atts = Array();
- while (list($attname, $attvalue) = each($attary)){
+ foreach ($attary as $attname => $attvalue){
array_push($atts, "$attname=$attvalue");
}
$fulltag .= ' ' . join(" ", $atts);
function sq_skipspace($body, $offset){
$me = 'sq_skipspace';
preg_match('/^(\s*)/s', substr($body, $offset), $matches);
- if (sizeof($matches{1})){
- $count = strlen($matches{1});
- $offset += $count;
+ if (!empty($matches[1])){
+ $offset += strlen($matches[1]);
}
return $offset;
}
$mailbox
){
$me = 'sq_fixatts';
- while (list($attname, $attvalue) = each($attary)){
+ foreach ($attary as $attname => $attvalue){
/**
* See if this attribute should be removed.
*/
/**
* Use white list based filtering on attributes which can contain url's
*/
- else if ($attname == 'href' || $attname == 'src' || $attname == 'background') {
+ else if ($attname == 'href' || $attname == 'xlink:href' || $attname == 'src'
+ || $attname == 'poster' || $attname == 'formaction'
+ || $attname == 'background' || $attname == 'action') {
sq_fix_url($attname, $attvalue, $message, $id, $mailbox);
$attary{$attname} = $attvalue;
}
// images off by default.
sqgetGlobalVar('view_unsafe_images', $view_unsafe_images, SQ_GET, FALSE);
- $secremoveimg = '../images/' . _("sec_remove_eng.png");
+ global $use_transparent_security_image;
+ if ($use_transparent_security_image) $secremoveimg = '../images/spacer.png';
+ else $secremoveimg = '../images/' . _("sec_remove_eng.png");
/**
* Replace empty src tags with the blank image. src is only used
break;
}
} else {
- if (!(isset($aUrl['path']) && $aUrl['path'] == $secremoveimg)) {
+ if (!isset($aUrl['path']) || $aUrl['path'] != $secremoveimg) {
// parse_url did not lead to satisfying result
$attvalue = $sQuote . SM_PATH . 'images/blank.png' . $sQuote;
}
* 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("|body(\s*\{.*?\})|si", ".bodyclass\\1", $content);
+ // Nah, this is even better - try to preface all CSS selectors with
+ // our <div> class ID "bodyclass" then correct generic "body" selectors
+ // TODO: this works pretty good but breaks stuff like this:
+ // @media print { body { font-size: 10pt; } }
+ // but there isn't an easy way to make this regex skip @media
+ // definitions... though lots of the ones in the wild will be
+ // correctly handled because they tend to end with a parenthesis, like:
+ // @media screen and (max-width:480px) { ...
+ $content = preg_replace('/([a-z0-9._-][a-z0-9 >+~|:._-]*\s*(?:,|{.*?}))/si', '.bodyclass $1', $content);
+ $content = str_replace('.bodyclass body', '.bodyclass', $content);
+
+ global $use_transparent_security_image;
+ if ($use_transparent_security_image) $secremoveimg = '../images/spacer.png';
+ else $secremoveimg = '../images/' . _("sec_remove_eng.png");
+
/**
* Fix url('blah') declarations.
*/
/**
* 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('/\/\*.*\*\//',
+ $match = Array('/\/\*.*\*\//', // removes /* blah blah */
'/expression/i',
'/behaviou*r/i',
'/binding/i',
'/include-source/i',
'/javascript/i',
- '/script/i');
- $replace = Array('','idiocy', 'idiocy', 'idiocy', 'idiocy', 'idiocy', 'idiocy');
+ '/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
}
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 {
* @param $add_attr_to_tag see description above
* @param $message message object
* @param $id message id
+ * @param $recursively_called boolean flag for recursive calls into this function (optional; default FALSE)
* @return sanitized html safe to show on your pages.
*/
function sq_sanitize($body,
$add_attr_to_tag,
$message,
$id,
- $mailbox
+ $mailbox,
+ $recursively_called=FALSE
){
$me = 'sq_sanitize';
+
+ /**
+ * See if tag_list is of tags to remove or tags to allow.
+ * false means remove these tags
+ * true means allow these tags
+ */
+ $orig_tag_list = $tag_list;
$rm_tags = array_shift($tag_list);
+
/**
* Normalize rm_tags and rm_tags_with_content.
*/
@array_walk($tag_list, 'sq_casenormalize');
@array_walk($rm_tags_with_content, 'sq_casenormalize');
@array_walk($self_closing_tags, 'sq_casenormalize');
- /**
- * See if tag_list is of tags to remove or tags to allow.
- * false means remove these tags
- * true means allow these tags
- */
+
$curpos = 0;
$open_tags = Array();
$trusted = "\n<!-- begin sanitized html -->\n";
while (($curtag = sq_getnxtag($body, $curpos)) != FALSE){
list($tagname, $attary, $tagtype, $lt, $gt) = $curtag;
+
+ /**
+ * RCDATA and RAWTEXT tags are handled differently:
+ * next instance of closing tag is used, whether or not
+ * the HTML is well formed before that
+ */
+ global $rcdata_rawtext_tags;
+ if (!$recursively_called
+ && in_array($tagname, $rcdata_rawtext_tags)
+ && $tagtype === 1){
+ $closing_tag = false;
+ $closing_tag_offset = $curpos;
+ // seek out the closing tag for the current RCDATA/RAWTEXT tag
+ while (1) {
+ // first we need to move forward to next available closing tag
+ // (intentionally leave off the closing > and let sq_getnxtag() validate a proper tag syntax)
+ $next_tag = sq_findnxreg($body, $closing_tag_offset, "</\s*$tagname");
+ if ($next_tag === false) {
+ $closing_tag = false;
+ break;
+ }
+ // but then we have to make sure it's a well-formed tag
+ $closing_tag = sq_getnxtag($body, $next_tag[0]);
+ if ($closing_tag === false)
+ break;
+ else if ($closing_tag[0] !== false
+ // these should be redundant
+ && $closing_tag[0] === $tagname && $closing_tag[2] === 2) {
+ $trusted .= sq_sanitize(substr($body, $curpos, $closing_tag[4] - $curpos + 1),
+ $orig_tag_list, $rm_tags_with_content, $self_closing_tags,
+ $force_tag_closing, $rm_attnames, $bad_attvals, $add_attr_to_tag,
+ $message, $id, $mailbox, true);
+ $curpos = $closing_tag[4] + 1;
+ continue 2;
+ }
+ $closing_tag_offset = $next_tag[0] + 1;
+ }
+ if ($closing_tag === false)
+ { /* no-op... there was no closing tag for this RCDATA/RAWTEXT tag - we could probably set $curpos to the end of $body, but this HTML is malformed anyway and should just fall apart on its own */ }
+ }
+
$free_content = substr($body, $curpos, $lt-$curpos);
/**
* Take care of <style>
list($free_content, $curpos) =
sq_fixstyle($body, $gt+1, $message, $id, $mailbox);
if ($free_content != FALSE){
+ if ( !empty($attary) ) {
+ $attary = sq_fixatts($tagname,
+ $attary,
+ $rm_attnames,
+ $bad_attvals,
+ $add_attr_to_tag,
+ $message,
+ $id,
+ $mailbox
+ );
+ }
$trusted .= sq_tagprint($tagname, $attary, $tagtype);
$trusted .= $free_content;
$trusted .= sq_tagprint($tagname, false, 2);
// require_once(SM_PATH . 'functions/url_parser.php'); // for $MailTo_PReg_Match
global $attachment_common_show_images, $view_unsafe_images,
- $has_unsafe_images;
+ $has_unsafe_images, $allow_svg_display, $rcdata_rawtext_tags,
+ $remove_rcdata_rawtext_tags_and_content;
+
+ $rcdata_rawtext_tags = array(
+ "noscript",
+ "noframes",
+ "noembed",
+ "textarea",
+ // also "title", "xmp", "script", "iframe", "plaintext" which we already remove below
+ );
+
/**
* Don't display attached images in HTML mode.
*
*/
$attachment_common_show_images = false;
$tag_list = Array(
- false,
- "object",
+ false, // remove these tags
"meta",
"html",
"head",
"frame",
"iframe",
"plaintext",
- "marquee"
+ "marquee",
);
$rm_tags_with_content = Array(
"script",
+ "object",
"applet",
"embed",
"title",
"frameset",
"xmp",
- "xml"
+ "xml",
);
+ if (!$allow_svg_display)
+ $rm_tags_with_content[] = 'svg';
+ /**
+ * SquirrelMail will parse RCDATA and RAWTEXT tags and handle them as the special
+ * case that they are, but if you prefer to remove them and their contents entirely
+ * (in most cases, should be a safe thing with minimal impact), you can add the
+ * following to config/config_local.php
+ * $remove_rcdata_rawtext_tags_and_content = TRUE;
+ */
+ if ($remove_rcdata_rawtext_tags_and_content)
+ $rm_tags_with_content = array_merge($rm_tags_with_content, $rcdata_rawtext_tags);
$self_closing_tags = Array(
"img",
"br",
"hr",
"input",
- "outbind"
+ "outbind",
);
$force_tag_closing = true;
"/^on.*/i",
"/^dynsrc/i",
"/^data.*/i",
- "/^lowsrc.*/i"
+ "/^lowsrc.*/i",
)
);
- $secremoveimg = "../images/" . _("sec_remove_eng.png");
+ global $use_transparent_security_image;
+ if ($use_transparent_security_image) $secremoveimg = '../images/spacer.png';
+ else $secremoveimg = '../images/' . _("sec_remove_eng.png");
+
$bad_attvals = Array(
"/.*/" =>
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(
"",
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) ;
+ // some mailers (Microsoft, surprise surprise) produce mailto strings without being
+ // inside an anchor (link) tag, so we have to make sure the regex looks for the
+ // quote before mailto, and we'll also try to convert the non-links back into links
+ $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];
+ $mailto_params = $regs[11][$i];
+
+ // get rid of any leading quote we may have captured but don't care about
+ //
+ $mailto_before = ltrim($mailto_before, '"\'');
+
// get rid of any tailing quote since we have to add send_to to the end
//
- if (substr($mailto_before, strlen($mailto_before) - 1) == '"')
- $mailto_before = substr($mailto_before, 0, strlen($mailto_before) - 1);
- if (substr($mailto_params, strlen($mailto_params) - 1) == '"')
- $mailto_params = substr($mailto_params, 0, strlen($mailto_params) - 1);
+ $mailto_before = rtrim($mailto_before, '"\'');
+ $mailto_params = rtrim($mailto_params, '"\'');
- if ($regs[1][$i]) { //if there is an email addr before '?', we need to merge it with the params
- $to = 'to=' . $regs[1][$i];
+ if ($regs[2][$i]) { //if there is an email addr before '?', we need to merge it with the params
+ $to = 'to=' . $regs[2][$i];
if (strpos($mailto_params, 'to=') > -1) //already a 'to='
$mailto_params = str_replace('to=', $to . '%2C%20', $mailto_params);
else {
// remove <a href=" and anything after the next quote (we only
// need the uri, not the link HTML) in compose uri
//
- $comp_uri = substr($comp_uri, 9);
- $comp_uri = substr($comp_uri, 0, strpos($comp_uri, '"', 1));
+ // but only do this if the original mailto was in a real anchor tag
+ //
+ if (!empty($regs[1][$i])) {
+ $comp_uri = substr($comp_uri, 9);
+ $comp_uri = substr($comp_uri, 0, strpos($comp_uri, '"', 1));
+ }
$trusted = str_replace($mailto_before, $comp_uri, $trusted);
}
}
$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!
$filename=rawurlencode($filename);
header ("Pragma: public");
header ("Cache-Control: no-store, max-age=0, no-cache, must-revalidate"); // HTTP/1.1
- header ("Cache-Control: post-check=0, pre-check=0", false);
+ // does nothing - see: https://blogs.msdn.microsoft.com/ieinternals/2009/07/20/internet-explorers-cache-control-extensions/
+ // header ("Cache-Control: post-check=0, pre-check=0", false);
header ("Cache-Control: private");
//set the inline header for IE, we'll add the attachment header later if we need it