From 86bb4f5bf2c3ec79bc518ba5cc2fc59a34942aa4 Mon Sep 17 00:00:00 2001 From: John Okely Date: Thu, 30 Mar 2017 12:14:52 +0800 Subject: [PATCH] MDL-53240 filetypes: Add functions to be used for input validation --- lib/form/classes/filetypes_util.php | 60 +++++++++++++ lib/form/tests/filetypes_util_test.php | 119 +++++++++++++++++++++++++ 2 files changed, 179 insertions(+) diff --git a/lib/form/classes/filetypes_util.php b/lib/form/classes/filetypes_util.php index 7d6f1369837..82a56c49659 100644 --- a/lib/form/classes/filetypes_util.php +++ b/lib/form/classes/filetypes_util.php @@ -432,4 +432,64 @@ class filetypes_util { return !empty($intersection); } + + /** + * Is the given filename of an allowed file type? + * + * Empty whitelist is interpretted as "any file type is allowed" rather + * than "no file can be uploaded". + * + * @param string $filename the file name + * @param string|array $whitelist list of allowed file extensions + * @return boolean True if the file type is allowed, false if not + */ + public function is_allowed_file_type($filename, $whitelist) { + + $allowedextensions = $this->expand($whitelist); + + if (empty($allowedextensions) || $allowedextensions == ['*']) { + return true; + } + + $haystack = strrev(trim(strtolower($filename))); + + foreach ($allowedextensions as $extension) { + if (strpos($haystack, strrev($extension)) === 0) { + // The file name ends with the extension. + return true; + } + } + + return false; + } + + /** + * Returns file types from the list that are not recognized + * + * @param string|array $types list of user-defined file types + * @return array A list of unknown file types. + */ + public function get_unknown_file_types($types) { + $unknown = []; + + foreach ($this->normalize_file_types($types) as $type) { + if ($this->is_filetype_group($type)) { + // The type is a group that exists. + } else if ($this->looks_like_mimetype($type)) { + // If there's no extension associated with that mimetype, we consider it unknown. + if (empty(file_get_typegroup('extension', [$type]))) { + $unknown[$type] = true; + } + } else { + $coretypes = core_filetypes::get_types(); + $typecleaned = str_replace(".", "", $type); + if (empty($coretypes[$typecleaned])) { + // If there's no extension, it doesn't exist. + $unknown[$type] = true; + } + } + } + + return array_keys($unknown); + } } diff --git a/lib/form/tests/filetypes_util_test.php b/lib/form/tests/filetypes_util_test.php index 91e17cf9aeb..45dec2f9500 100644 --- a/lib/form/tests/filetypes_util_test.php +++ b/lib/form/tests/filetypes_util_test.php @@ -306,4 +306,123 @@ class filetypes_util_testcase extends advanced_testcase { } } } + + /** + * Data provider for testing test_is_allowed_file_type. + * + * @return array + */ + public function is_allowed_file_type_provider() { + return [ + 'Filetype not in extension whitelist' => [ + 'filename' => 'test.xml', + 'whitelist' => '.png .jpg', + 'expected' => false + ], + 'Filetype not in mimetype whitelist' => [ + 'filename' => 'test.xml', + 'whitelist' => 'image/png', + 'expected' => false + ], + 'Filetype not in group whitelist' => [ + 'filename' => 'test.xml', + 'whitelist' => 'web_file', + 'expected' => false + ], + 'Filetype in whitelist as extension' => [ + 'filename' => 'test.xml', + 'whitelist' => 'xml', + 'expected' => true + ], + 'Empty whitelist should allow all' => [ + 'filename' => 'test.xml', + 'whitelist' => '', + 'expected' => true + ], + 'Filetype in whitelist but later on' => [ + 'filename' => 'test.xml', + 'whitelist' => 'gif;jpeg,image/png xml xlsx', + 'expected' => true + ], + 'Filetype in whitelist as mimetype' => [ + 'filename' => 'test.xml', + 'whitelist' => 'image/png application/xml', + 'expected' => true + ], + 'Filetype in whitelist as group' => [ + 'filename' => 'test.html', + 'whitelist' => 'video,web_file', + 'expected' => true + ], + ]; + } + + /** + * Test is_allowed_file_type(). + * @dataProvider is_allowed_file_type_provider + * @param string $filename The filename to check + * @param string $whitelist The space , or ; separated list of types supported + * @param boolean $expected The expected result. True if the file is allowed, false if not. + */ + public function test_is_allowed_file_type($filename, $whitelist, $expected) { + $util = new filetypes_util(); + $this->assertSame($expected, $util->is_allowed_file_type($filename, $whitelist)); + } + + /** + * Data provider for testing test_get_unknown_file_types. + * + * @return array + */ + public function get_unknown_file_types_provider() { + return [ + 'Unknown extension' => [ + 'filetypes' => '.rat', + 'expected' => ['.rat'] + ], + 'Multiple unknown extensions' => [ + 'filetypes' => '.ricefield .rat', + 'expected' => ['.ricefield', '.rat'] + ], + 'Existant extension' => [ + 'filetypes' => '.xml', + 'expected' => [] + ], + 'Existant group' => [ + 'filetypes' => 'web_file', + 'expected' => [] + ], + 'Nonexistant mimetypes' => [ + 'filetypes' => 'ricefield/rat', + 'expected' => ['ricefield/rat'] + ], + 'Existant mimetype' => [ + 'filetypes' => 'application/xml', + 'expected' => [] + ], + 'Multiple unknown mimetypes' => [ + 'filetypes' => 'ricefield/rat cam/ball', + 'expected' => ['ricefield/rat', 'cam/ball'] + ], + 'Strange characters in unknown extension/group' => [ + 'filetypes' => '©ç√√ß∂å√©åß©√', + 'expected' => ['.©ç√√ß∂å√©åß©√'] + ], + 'Some existant some not' => [ + 'filetypes' => '.txt application/xml web_file ©ç√√ß∂å√©åß©√ .png ricefield/rat document', + 'expected' => ['.©ç√√ß∂å√©åß©√', 'ricefield/rat'] + ], + ]; + } + + /** + * Test get_unknown_file_types(). + * @dataProvider get_unknown_file_types_provider + * @param string $filetypes The filetypes to check + * @param array $expected The expected result. The list of non existant file types. + */ + public function test_get_unknown_file_types($filetypes, $expected) { + $util = new filetypes_util(); + $this->assertSame($expected, $util->get_unknown_file_types($filetypes)); + } }