From 0c4c3b9aed7f2d47b29396ecdfc1801312b37c86 Mon Sep 17 00:00:00 2001 From: Jun Pataleta Date: Thu, 13 Aug 2015 17:35:06 +0800 Subject: [PATCH] MDL-44828 filelib: Fix open error for force-downloaded files in mobile Allow guessing of file's MIME type by default. Fallback to application/octet-stream if MIME type cannot be determined. --- lib/filelib.php | 55 +++++++++++++++++++++++++------------- lib/tests/filelib_test.php | 24 +++++++++++++++++ 2 files changed, 61 insertions(+), 18 deletions(-) diff --git a/lib/filelib.php b/lib/filelib.php index 6375faf4ee8..4da13f28dc7 100644 --- a/lib/filelib.php +++ b/lib/filelib.php @@ -1390,6 +1390,29 @@ function &get_mimetypes_array() { return core_filetypes::get_types(); } +/** + * Determine a file's MIME type based on the given filename using the function mimeinfo. + * + * This function retrieves a file's MIME type for a file that will be sent to the user. + * This should only be used for file-sending purposes just like in send_stored_file, send_file, and send_temp_file. + * Should the file's MIME type cannot be determined by mimeinfo, it will return 'application/octet-stream' as a default + * MIME type which should tell the browser "I don't know what type of file this is, so just download it.". + * + * @param string $filename The file's filename. + * @return string The file's MIME type or 'application/octet-stream' if it cannot be determined. + */ +function get_mimetype_for_sending($filename = '') { + // Guess the file's MIME type using mimeinfo. + $mimetype = mimeinfo('type', $filename); + + // Use octet-stream as fallback if MIME type cannot be determined by mimeinfo. + if (!$mimetype || $mimetype === 'document/unknown') { + $mimetype = 'application/octet-stream'; + } + + return $mimetype; +} + /** * Obtains information about a filetype based on its extension. Will * use a default if no information is present about that particular @@ -1988,12 +2011,8 @@ function readstring_accel($string, $mimetype, $accelerate) { function send_temp_file($path, $filename, $pathisstring=false) { global $CFG; - if (core_useragent::is_firefox()) { - // only FF is known to correctly save to disk before opening... - $mimetype = mimeinfo('type', $filename); - } else { - $mimetype = 'application/x-forcedownload'; - } + // Guess the file's MIME type. + $mimetype = get_mimetype_for_sending($filename); // close session - not needed anymore \core\session\manager::write_close(); @@ -2076,12 +2095,10 @@ function send_file($path, $filename, $lifetime = null , $filter=0, $pathisstring \core\session\manager::write_close(); // Unlock session during file serving. - // Use given MIME type if specified, otherwise guess it using mimeinfo. - // IE, Konqueror and Opera open html file directly in browser from web even when directed to save it to disk :-O - // only Firefox saves all files locally before opening when content-disposition: attachment stated - $isFF = core_useragent::is_firefox(); // only FF properly tested - $mimetype = ($forcedownload and !$isFF) ? 'application/x-forcedownload' : - ($mimetype ? $mimetype : mimeinfo('type', $filename)); + // Use given MIME type if specified, otherwise guess it. + if (!$mimetype || $mimetype === 'document/unknown') { + $mimetype = get_mimetype_for_sending($filename); + } // if user is using IE, urlencode the filename so that multibyte file name will show up correctly on popup if (core_useragent::is_ie()) { @@ -2254,13 +2271,15 @@ function send_stored_file($stored_file, $lifetime=null, $filter=0, $forcedownloa \core\session\manager::write_close(); // Unlock session during file serving. - // Use given MIME type if specified, otherwise guess it using mimeinfo. - // IE, Konqueror and Opera open html file directly in browser from web even when directed to save it to disk :-O - // only Firefox saves all files locally before opening when content-disposition: attachment stated $filename = is_null($filename) ? $stored_file->get_filename() : $filename; - $isFF = core_useragent::is_firefox(); // only FF properly tested - $mimetype = ($forcedownload and !$isFF) ? 'application/x-forcedownload' : - ($stored_file->get_mimetype() ? $stored_file->get_mimetype() : mimeinfo('type', $filename)); + + // Use given MIME type if specified. + $mimetype = $stored_file->get_mimetype(); + + // Otherwise guess it. + if (!$mimetype || $mimetype === 'document/unknown') { + $mimetype = get_mimetype_for_sending($filename); + } // if user is using IE, urlencode the filename so that multibyte file name will show up correctly on popup if (core_useragent::is_ie()) { diff --git a/lib/tests/filelib_test.php b/lib/tests/filelib_test.php index b32c328ea31..555a45cb235 100644 --- a/lib/tests/filelib_test.php +++ b/lib/tests/filelib_test.php @@ -879,6 +879,30 @@ EOF; $this->assertEquals(true, $mimeinfo['txt']['defaulticon']); } + /** + * Tests for get_mimetype_for_sending function. + */ + public function test_get_mimetype_for_sending() { + // Without argument. + $this->assertEquals('application/octet-stream', get_mimetype_for_sending()); + + // Argument is null. + $this->assertEquals('application/octet-stream', get_mimetype_for_sending(null)); + + // Filename having no extension. + $this->assertEquals('application/octet-stream', get_mimetype_for_sending('filenamewithoutextension')); + + // Test using the extensions listed from the get_mimetypes_array function. + $mimetypes = get_mimetypes_array(); + foreach ($mimetypes as $ext => $info) { + if ($ext === 'xxx') { + $this->assertEquals('application/octet-stream', get_mimetype_for_sending('SampleFile.' . $ext)); + } else { + $this->assertEquals($info['type'], get_mimetype_for_sending('SampleFile.' . $ext)); + } + } + } + /** * Test curl agent settings. */ -- 2.43.0