From af92cb606c9e3cc9e885975be82057caec006e7d Mon Sep 17 00:00:00 2001 From: Seamus Lee Date: Fri, 3 May 2019 14:53:48 +1000 Subject: [PATCH] Add whitelist back in and validate extension of file is permtted for the mime-type supplied and use mime-type from db if supplied with an fid and eid Switch to different libary that is php5.6 compatable --- CRM/Core/Page/File.php | 20 ++++++------ CRM/Utils/File.php | 26 +++++++++++++--- composer.json | 3 +- composer.lock | 46 +++++++++++++++++++++++++++- settings/Core.setting.php | 14 +++++++++ tests/phpunit/CRM/Utils/FileTest.php | 35 +++++++++++++++++++++ 6 files changed, 127 insertions(+), 17 deletions(-) diff --git a/CRM/Core/Page/File.php b/CRM/Core/Page/File.php index 57ffc9b8ec..e32f90f79f 100644 --- a/CRM/Core/Page/File.php +++ b/CRM/Core/Page/File.php @@ -68,22 +68,22 @@ class CRM_Core_Page_File extends CRM_Core_Page { $mimeType = ''; $path = CRM_Core_Config::singleton()->customFileUploadDir . $fileName; } - $passedInMimeType = CRM_Utils_Request::retrieveValue('mime-type', 'String', $mimeType, FALSE); if (!$path) { CRM_Core_Error::statusBounce('Could not retrieve the file'); } - if (!empty($mimeType) && !empty($passedInMimeType)) { - if ($passedInMimeType != $mimeType) { - throw new CRM_Core_Exception("Supplied Mime Type does not match file Mime Type"); + + if (empty($mimeType)) { + $passedInMimeType = CRM_Utils_Request::retrieveValue('mime-type', 'String', $mimeType, FALSE); + if (!in_array($passedInMimeType, explode(',', Civi::settings()->get('requestableMimeTypes')))) { + throw new CRM_Core_Exception("Supplied mime-type is not accepted"); } - } - elseif (!empty($passedInMimeType)) { - $testMimeType = CRM_Utils_File::getMimeType($path); - if ($testMimeType != $passedInMimeType) { - throw new CRM_Core_Exception("Supplied Mime Type does not match file Mime Type"); + $extension = CRM_Utils_File::getExtensionFromPath($path); + $candidateExtensions = CRM_Utils_File::getAcceptableExtensionsForMimeType($passedInMimeType); + if (!in_array($extension, $candidateExtensions)) { + throw new CRM_Core_Exception("Supplied mime-type does not match file extension"); } - // Now that we have ensured that the mime-type matches to what we believe is the mime-type of the file + // Now that we have validated mime-type supplied as much as possible lets now set the MimeType variable/ $mimeType = $passedInMimeType; } diff --git a/CRM/Utils/File.php b/CRM/Utils/File.php index cf3a277bb4..efd0b79c61 100644 --- a/CRM/Utils/File.php +++ b/CRM/Utils/File.php @@ -1067,12 +1067,28 @@ HTACCESS; } /** - * Get the Mime-Type of a file based on the url path - * @param string $path full filename path - * @return string|bool + * Get the extensions that this MimeTpe is for + * @param string $mimeType the mime-type we want extensions for + * @return array + */ + public static function getAcceptableExtensionsForMimeType($mimeType = NULL) { + $mapping = \MimeType\Mapping::$types; + $extensions = []; + foreach ($mapping as $extension => $type) { + if ($mimeType == $type) { + $extensions[] = $extension; + } + } + return $extensions; + } + + /** + * Get the extension of a file based on its path + * @param string $path path of the file to query + * @return string */ - public function getMimeType($path = NULL) { - return mime_content_type($path); + public static function getExtensionFromPath($path) { + return pathinfo($path, PATHINFO_EXTENSION); } } diff --git a/composer.json b/composer.json index fb0a915494..bdbf553c4a 100644 --- a/composer.json +++ b/composer.json @@ -61,7 +61,8 @@ "psr/simple-cache": "~1.0.1", "cweagans/composer-patches": "~1.0", "pear/log": "1.13.1", - "ezyang/htmlpurifier": "4.10" + "ezyang/htmlpurifier": "4.10", + "katzien/php-mime-type": "2.1.0" }, "scripts": { "post-install-cmd": [ diff --git a/composer.lock b/composer.lock index c8c7a65596..b0f5e5e881 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "93a9f686f7eb00fb9d766d262eedb09b", + "content-hash": "2a06373b9174ae3aa2bfb820e2e5a35e", "packages": [ { "name": "civicrm/civicrm-cxn-rpc", @@ -454,6 +454,50 @@ ], "time": "2017-03-20T17:10:46+00:00" }, + { + "name": "katzien/php-mime-type", + "version": "2.1.0", + "source": { + "type": "git", + "url": "https://github.com/katzien/PhpMimeType.git", + "reference": "159dfbdcd5906442f3dad89951127f0b9dfa3b78" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/katzien/PhpMimeType/zipball/159dfbdcd5906442f3dad89951127f0b9dfa3b78", + "reference": "159dfbdcd5906442f3dad89951127f0b9dfa3b78", + "shasum": "" + }, + "require": { + "php": ">=5.6" + }, + "require-dev": { + "phpunit/phpunit": "5.*", + "satooshi/php-coveralls": "1.*" + }, + "type": "library", + "autoload": { + "psr-4": { + "MimeType\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Kat Zien" + } + ], + "description": "A PHP library to detect the mime type of files.", + "homepage": "https://github.com/katzien/PhpMimeType", + "keywords": [ + "mimetype", + "php" + ], + "time": "2017-03-23T02:05:33+00:00" + }, { "name": "marcj/topsort", "version": "1.1.0", diff --git a/settings/Core.setting.php b/settings/Core.setting.php index 27af8a8f5b..cc69e3f982 100644 --- a/settings/Core.setting.php +++ b/settings/Core.setting.php @@ -1052,4 +1052,18 @@ return [ 'help_text' => NULL, 'validate_callback' => 'CRM_Utils_Rule::color', ], + 'requestableMimeTypes' => [ + 'group_name' => 'CiviCRM Preferences', + 'group' => 'core', + 'name' => 'requestableMimeTypes', + 'type' => 'String', + 'html_type' => 'Text', + 'default' => 'image/jpeg,image/pjpeg,image/gif,image/x-png,image/png,image/jpg,text/html,application/pdf', + 'add' => '5.13', + 'title' => ts('Mime Types that can be passed as URL params'), + 'is_domain' => 1, + 'is_contact' => 0, + 'description' => ts('Acceptable Mime Types that can be used as part of file urls'), + 'help_text' => NULL, + ], ]; diff --git a/tests/phpunit/CRM/Utils/FileTest.php b/tests/phpunit/CRM/Utils/FileTest.php index f52784ae4f..3d5c6a0a69 100644 --- a/tests/phpunit/CRM/Utils/FileTest.php +++ b/tests/phpunit/CRM/Utils/FileTest.php @@ -95,4 +95,39 @@ class CRM_Utils_FileTest extends CiviUnitTestCase { $this->assertEquals($expectedResult, CRM_Utils_File::isValidFileName($fileName)); } + public function pathToFileExtension() { + $cases = []; + $cases[] = ['/evil.pdf', 'pdf']; + $cases[] = ['/helloworld.jpg', 'jpg']; + $cases[] = ['/smartwatch_1736683_1280_9af3657015e8660cc234eb1601da871.jpg', 'jpg']; + return $cases; + } + + /** + * Test returning appropriate file extension + * @dataProvider pathToFileExtension + * @param string $path + * @param string $expectedExtension + */ + public function testPathToExtension($path, $expectedExtension) { + $this->assertEquals($expectedExtension, CRM_Utils_File::getExtensionFromPath($path)); + } + + public function mimeTypeToExtension() { + $cases = []; + $cases[] = ['text/plain', ['txt', 'text', 'conf', 'def', 'list', 'log', 'in']]; + $cases[] = ['image/jpeg', ['jpeg','jpg', 'jpe']]; + $cases[] = ['image/png', ['png']]; + return $cases; + } + + /** + * @dataProvider mimeTypeToExtension + * @param stirng $mimeType + * @param array $expectedExtensions + */ + public function testMimeTypeToExtension($mimeType, $expectedExtensions) { + $this->assertEquals($expectedExtensions, CRM_Utils_File::getAcceptableExtensionsForMimeType($mimeType)); + } + } -- 2.25.1