Skip to content

Commit

Permalink
Fix #11075: Add Content-Disposition workaround for Internet Explorer/…
Browse files Browse the repository at this point in the history
…Chrome

Internet Explorer and Chrome don't support RFC2231 and also ignore the
fallback method currently implemented in MantisBT. See
http://greenbytes.de/tech/tc2231/#attfnboth2 for the current method.

We can however use another method to display UTF8 filenames to IE and
Chrome. This workaround is actually in breach in RFC2231. See
http://greenbytes.de/tech/tc2231/#attwithfnrawpctenclong for details. We
use this method when the user agent is determined to be IE or Chrome.
Otherwise we just keep using the original RFC2231 fallback technique
mentioned above.

It also appears that urlencode() is the wrong method to use for encoding
filenames. Browsers seem to expect %20 as a space instead of +. Thus we
should use rawurlencode() instead for the old method of encoding URLs.
RFC2231 actually contains examples with %20 being used.

Some minor cleanups were also performed in relation to sending the
Content-Disposition header and also performing browser checks.
  • Loading branch information
davidhicks committed Oct 27, 2009
1 parent ccd99a0 commit 07c8da0
Show file tree
Hide file tree
Showing 4 changed files with 79 additions and 30 deletions.
12 changes: 9 additions & 3 deletions core/file_api.php
Expand Up @@ -835,13 +835,19 @@ function file_get_extension( $p_filename ) {
$t_extension = '';
$t_basename = $p_filename;
if( utf8_strpos( $t_basename, '/' ) !== false ) {
$t_basename = end( explode( '/', $t_basename ) );
// Note that we can't use end(explode(...)) on a single line because
// end() expects a reference to a variable and thus we first need to
// copy the result of explode() into a variable that end() can modify.
$t_components = explode( '/', $t_basename );
$t_basename = end( $t_components );
}
if( utf8_strpos( $t_basename, '\\' ) !== false ) {
$t_basename = end( explode( '\\', $t_basename ) );
$t_components = explode( '\\', $t_basename );
$t_basename = end( $t_components );
}
if( utf8_strpos( $t_basename, '.' ) !== false ) {
$t_extension = end( explode( '.', $t_basename ) );
$t_components = explode( '\\', $t_basename );
$t_extension = end( $t_components );
}
return $t_extension;
}
78 changes: 64 additions & 14 deletions core/http_api.php
Expand Up @@ -22,32 +22,82 @@
* @link http://www.mantisbt.org
*/

/**
* Check to see if the client is using Microsoft Internet Explorer so we can
* enable quirks and hacky non-standards-compliant workarounds.
* @return boolean True if Internet Explorer is detected as the user agent
*/
function is_browser_internet_explorer() {
$t_user_agent = isset( $_SERVER['HTTP_USER_AGENT'] ) ? $_SERVER['HTTP_USER_AGENT'] : 'none';

if ( strpos( $t_user_agent, 'MSIE' ) ) {
return true;
}

return false;
}

/**
* Checks to see if the client is using Google Chrome so we can enable quirks
* and hacky non-standards-compliant workarounds.
* @return boolean True if Chrome is detected as the user agent
*/
function is_browser_chrome() {
$t_user_agent = isset( $_SERVER['HTTP_USER_AGENT'] ) ? $_SERVER['HTTP_USER_AGENT'] : 'none';

if ( strpos( $t_user_agent, 'Chrome/' ) ) {
return true;
}

return false;
}

/**
* Send a Content-Disposition header. This is more complex than it sounds
* because only a few browsers properly support RFC2231. For those browsers
* which are behind the times or are otherwise broken, we need to use
* some hacky workarounds to get them to work 'nicely' with attachments and
* inline files. See http://greenbytes.de/tech/tc2231/ for full reasoning.
* @param string Filename
* @param boolean Display file inline (optional, default = treat as attachment)
*/
function http_content_disposition_header( $p_filename, $p_inline = false ) {
if ( !headers_sent() ) {
$t_encoded_filename = rawurlencode( $p_filename );
$t_disposition = '';
if ( !$p_inline ) {
$t_disposition = 'attachment;';
}
if ( is_browser_internet_explorer() || is_browser_chrome() ) {
// Internet Explorer does not support RFC2231 however it does
// incorrectly decode URL encoded filenames and we can use this to
// get UTF8 filenames to work with the file download dialog. Chrome
// behaves in the same was as Internet Explorer in this respect.
// See http://greenbytes.de/tech/tc2231/#attwithfnrawpctenclong
header( 'Content-Disposition:' . $t_disposition . ' filename="' . $t_encoded_filename . '"' );
} else {
// For most other browsers, we can use this technique:
// http://greenbytes.de/tech/tc2231/#attfnboth2
header( 'Content-Disposition:' . $t_disposition . ' filename*=UTF-8\'\'' . $t_encoded_filename . '; filename="' . $t_encoded_filename . '"' );
}
}
}

/**
* Set caching headers that will allow or prevent browser caching.
* @param boolean Allow caching
*/
function http_caching_headers( $p_allow_caching=false ) {
global $g_allow_browser_cache;

// Basic browser detection
$t_user_agent = isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : 'none';

$t_browser_name = 'Normal';
if ( strpos( $t_user_agent, 'MSIE' ) ) {
$t_browser_name = 'IE';
}

// Headers to prevent caching
// with option to bypass if running from script
// with option to bypass if running from script
if ( !headers_sent() ) {
if ( $p_allow_caching || ( isset( $g_allow_browser_cache ) && ON == $g_allow_browser_cache ) ) {
switch ( $t_browser_name ) {
case 'IE':
if ( is_browser_internet_explorer() ) {
header( 'Cache-Control: private, proxy-revalidate' );
break;
default:
} else {
header( 'Cache-Control: private, must-revalidate' );
break;
}
} else {
header( 'Cache-Control: no-store, no-cache, must-revalidate' );
Expand Down
12 changes: 4 additions & 8 deletions file_download.php
Expand Up @@ -95,25 +95,21 @@
header( 'Pragma: public' );

$t_filename = file_get_display_name( $v_filename );
$t_show_inline = false;
$t_inline_files = explode(',', config_get('inline_file_exts', 'gif'));
if ( in_array( utf8_strtolower( file_get_extension($t_filename) ), $t_inline_files ) ) {
$t_disposition = ''; //'inline;';
} else {
$t_disposition = ' attachment;';
$t_show_inline = true;
}

# The following header has undefined behaviour but needs to be used to
# allow a fallback for old browsers that don't support RFC2231.
# See http://greenbytes.de/tech/tc2231/ for more information.
header( 'Content-Disposition:' . $t_disposition . ' filename*=UTF-8\'\'' . urlencode( $t_filename ) . '; filename="' . urlencode( $t_filename ) . '"' );
http_content_disposition_header( $t_filename, $t_show_inline );

header( 'Last-Modified: ' . gmdate( 'D, d M Y H:i:s \G\M\T', $v_date_added ) );

# To fix an IE bug which causes problems when downloading
# attached files via HTTPS, we disable the "Pragma: no-cache"
# command when IE is used over HTTPS.
global $g_allow_file_cache;
if ( ( isset( $_SERVER["HTTPS"] ) && ( "on" == utf8_strtolower( $_SERVER["HTTPS"] ) ) ) && preg_match( "/MSIE/", $_SERVER["HTTP_USER_AGENT"] ) ) {
if ( ( isset( $_SERVER["HTTPS"] ) && ( "on" == utf8_strtolower( $_SERVER["HTTPS"] ) ) ) && is_browser_internet_explorer() ) {
# Suppress "Pragma: no-cache" header.
} else {
if ( !isset( $g_allow_file_cache ) ) {
Expand Down
7 changes: 2 additions & 5 deletions print_all_bug_page_word.php
Expand Up @@ -49,17 +49,14 @@
if ( $f_type_page != 'html' ) {
$t_export_title = helper_get_default_export_filename( '' );
$t_export_title = preg_replace( '/[\/:*?"<>|]/', '', $t_export_title );
$t_export_title .= '.doc';

# Make sure that IE can download the attachments under https.
header( 'Pragma: public' );

header( 'Content-Type: application/msword' );

if ( preg_match( "/MSIE/", $_SERVER["HTTP_USER_AGENT"] ) ) {
header( 'Content-Disposition: attachment; filename="' . urlencode( $t_export_title ) . '.doc"' );
} else {
header( 'Content-Disposition: attachment; filename="' . $t_export_title . '.doc"' );
}
http_content_disposition_header( $t_export_title );
}

# This is where we used to do the entire actual filter ourselves
Expand Down

0 comments on commit 07c8da0

Please sign in to comment.