From 6b75106a75f03b797531275d31390e02303bb4d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Mudr=C3=A1k?= Date: Wed, 7 Nov 2012 16:29:07 +0100 Subject: [PATCH] MDL-35238 Compare the ZIP package content hash with the expected value The expected value is returned as a part of available update info (requires API version 1.1). --- lib/pluginlib.php | 9 ++++++++- mdeploy.php | 19 ++++++++++++++++++- mdeploytest.php | 11 +++++++++++ 3 files changed, 37 insertions(+), 2 deletions(-) diff --git a/lib/pluginlib.php b/lib/pluginlib.php index e3fd1f3448bd0..4d69ffe60ae7b 100644 --- a/lib/pluginlib.php +++ b/lib/pluginlib.php @@ -1407,6 +1407,8 @@ class available_update_info { public $url = null; /** @var string|null optional URL of a ZIP package that can be downloaded and installed */ public $download = null; + /** @var string|null of self::download is set, then this must be the MD5 hash of the ZIP */ + public $downloadmd5 = null; /** * Creates new instance of the class @@ -1529,7 +1531,7 @@ public function initialized() { * * All instances of {@link available_update_info} class always provide at least the * component name and component version. Additionally, we also need the URL to download - * the ZIP package from. + * the ZIP package from and MD5 hash of the ZIP's content. * * @param available_update_info $info * @return bool @@ -1540,6 +1542,10 @@ public function can_deploy(available_update_info $info) { return false; } + if (empty($info->downloadmd5)) { + return false; + } + return true; } @@ -1626,6 +1632,7 @@ public function make_execution_widget(available_update_info $info) { 'name' => $pluginname, 'typeroot' => $pluginrootpaths[$plugintype], 'package' => $info->download, + 'md5' => $info->downloadmd5, 'dataroot' => $CFG->dataroot, 'dirroot' => $CFG->dirroot, 'passfile' => $passfile, diff --git a/mdeploy.php b/mdeploy.php index 68a447f939aaa..69b7b06d215cf 100644 --- a/mdeploy.php +++ b/mdeploy.php @@ -45,6 +45,7 @@ class download_file_exception extends Exception {} class backup_folder_exception extends Exception {} class zip_exception extends Exception {} class filesystem_exception extends Exception {} +class checksum_exception extends Exception {} // Various support classes ///////////////////////////////////////////////////// @@ -118,6 +119,7 @@ class input_manager extends singleton_pattern { const TYPE_RAW = 'raw'; // Raw value, keep as is const TYPE_URL = 'url'; // URL to a file const TYPE_PLUGIN = 'plugin'; // Plugin name + const TYPE_MD5 = 'md5'; // MD5 hash /** @var input_cli_provider|input_http_provider the provider of the input */ protected $inputprovider = null; @@ -173,6 +175,7 @@ public function get_option_info($name=null) { array('d', 'dataroot', input_manager::TYPE_PATH, 'Full path to the dataroot (moodledata) directory'), array('h', 'help', input_manager::TYPE_FLAG, 'Prints usage information'), array('i', 'install', input_manager::TYPE_FLAG, 'Installation mode'), + array('m', 'md5', input_manager::TYPE_MD5, 'Expected MD5 hash of the ZIP package to deploy'), array('n', 'name', input_manager::TYPE_PLUGIN, 'Plugin name (the name of its folder)'), array('p', 'package', input_manager::TYPE_URL, 'URL to the ZIP package to deploy'), array('r', 'typeroot', input_manager::TYPE_PATH, 'Full path of the container for this plugin type'), @@ -288,6 +291,12 @@ public function cast_value($raw, $type) { } return $raw; + case input_manager::TYPE_MD5: + if (!preg_match('/^[a-f0-9]{32}$/', $raw)) { + throw new invalid_option_exception('Invalid MD5 hash format'); + } + return $raw; + default: throw new invalid_coding_exception('Unknown option type.'); @@ -654,9 +663,17 @@ public function execute() { } else { $this->log('cURL error ' . $this->curlerrno . ' ' . $this->curlerror); $this->log('Unable to download the file'); + throw new download_file_exception('Unable to download the ZIP package'); } - // Compare MD5 checksum of the ZIP file - TODO + // Compare MD5 checksum of the ZIP file + $md5remote = $this->input->get_option('md5'); + $md5local = md5_file($target); + + if ($md5local !== $md5remote) { + $this->log('MD5 checksum failed. Expected: '.$md5remote.' Got: '.$md5local); + throw new checksum_exception('MD5 checksum failed'); + } // Backup the current version of the plugin $plugintyperoot = $this->input->get_option('typeroot'); diff --git a/mdeploytest.php b/mdeploytest.php index f183509580968..1cbfb91abcada 100644 --- a/mdeploytest.php +++ b/mdeploytest.php @@ -135,6 +135,8 @@ public function data_for_cast_value() { 'https://moodle.org/plugins/download.php/1292/mod_stampcoll_moodle23_2012062201.zip' ), array('file:///etc/passwd', input_manager::TYPE_URL, ''), + + array('5e8d2ea4f50d154730100b1645fbad67', input_manager::TYPE_MD5, '5e8d2ea4f50d154730100b1645fbad67'), ); } @@ -155,6 +157,15 @@ public function test_cast_object_argument() { $input->cast_value($o, input_manager::TYPE_INT); // must throw exception } + /** + * @expectedException invalid_option_exception + */ + public function test_cast_invalid_md5_value() { + $input = testable_input_manager::instance(); + $invalid = 'this is not a valid md5 hash'; + $input->cast_value($invalid, input_manager::TYPE_MD5); // must throw exception + } + public function test_has_option() { $provider = input_fake_provider::instance();