From 0369f3f80874c5f61f29f4af400c36f879e21ec1 Mon Sep 17 00:00:00 2001 From: Angie Byron Date: Mon, 6 Dec 2010 06:50:08 +0000 Subject: #936490 by dww, tstoeckler, haydeniv, Bojhan: Fixed Update module should verify downloaded tarballs and propagate errors correctly --- modules/update/update.api.php | 8 +++-- modules/update/update.manager.inc | 37 ++++++++++--------- modules/update/update.module | 75 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 101 insertions(+), 19 deletions(-) (limited to 'modules/update') diff --git a/modules/update/update.api.php b/modules/update/update.api.php index b02b22300..ef78cbf42 100644 --- a/modules/update/update.api.php +++ b/modules/update/update.api.php @@ -115,16 +115,18 @@ function hook_update_status_alter(&$projects) { * The directory that the archive was extracted into. * * @return - * If there is a problem, return any non-null value. If there is no problem, - * don't return anything (null). + * If there are any problems, return an array of error messages. If there are + * no problems, return an empty array. * * @see update_manager_archive_verify() */ function hook_verify_update_archive($project, $archive_file, $directory) { + $errors = array(); if (!file_exists($directory)) { - return TRUE; + $errors[] = t('The %directory does not exist.', array('%directory' => $directory)); } // Add other checks on the archive integrity here. + return $errors; } /** diff --git a/modules/update/update.manager.inc b/modules/update/update.manager.inc index d149027bd..7649852bf 100644 --- a/modules/update/update.manager.inc +++ b/modules/update/update.manager.inc @@ -557,11 +557,16 @@ function update_manager_install_form_submit($form, &$form_state) { // Unfortunately, we can only use the directory name for this. :( $project = drupal_substr($files[0], 0, -1); - try { - update_manager_archive_verify($project, $local_cache, $directory); - } - catch (Exception $e) { - form_set_error($field, $e->getMessage()); + $archive_errors = update_manager_archive_verify($project, $local_cache, $directory); + if (!empty($archive_errors)) { + form_set_error($field, array_shift($archive_errors)); + // @todo: Fix me in D8: We need a way to set multiple errors on the same + // form element and have all of them appear! + if (!empty($archive_errors)) { + foreach ($archive_errors as $error) { + drupal_set_message($error, 'error'); + } + } return; } @@ -682,15 +687,13 @@ function update_manager_archive_extract($file, $directory) { * @param string $directory * The directory that the archive was extracted into. * - * @return void - * @throws Exception on failure. + * @return array + * An array of error messages to display if the archive was invalid. If + * there are no errors, it will be an empty array. * */ function update_manager_archive_verify($project, $archive_file, $directory) { - $failures = module_invoke_all('verify_update_archive', $project, $archive_file, $directory); - if (!empty($failures)) { - throw new Exception(t('Unable to extract %file', array('%file' => $archive_file))); - } + return module_invoke_all('verify_update_archive', $project, $archive_file, $directory); } /** @@ -770,11 +773,13 @@ function update_manager_batch_project_get($project, $url, &$context) { } // Verify it. - try { - update_manager_archive_verify($project, $local_cache, $extract_directory); - } - catch (Exception $e) { - $context['results']['errors'][$project] = $e->getMessage(); + $archive_errors = update_manager_archive_verify($project, $local_cache, $extract_directory); + if (!empty($archive_errors)) { + // We just need to make sure our array keys don't collide, so use the + // numeric keys from the $archive_errors array. + foreach ($archive_errors as $key => $error) { + $context['results']['errors']["$project-$key"] = $error; + } return; } diff --git a/modules/update/update.module b/modules/update/update.module index ee0e91358..9f2764c28 100644 --- a/modules/update/update.module +++ b/modules/update/update.module @@ -640,6 +640,81 @@ function theme_update_last_check($variables) { return $output; } +/** + * Implements hook_verify_update_archive(). + * + * First, we ensure that the archive isn't a copy of Drupal core, which the + * Update manager does not yet support. @see http://drupal.org/node/606592 + * + * Then, we make sure that every module included in the archive has an info + * file. + * + * Finally, we check that all the .info files claim the code is compatible + * with the current version of Drupal core. + * + * @see drupal_system_listing() + * @see _system_rebuild_module_data() + */ +function update_verify_update_archive($project, $archive_file, $directory) { + $errors = array(); + + // Make sure this isn't a tarball of Drupal core. + if ( + file_exists("$directory/$project/index.php") + && file_exists("$directory/$project/update.php") + && file_exists("$directory/$project/includes/bootstrap.inc") + && file_exists("$directory/$project/modules/node/node.module") + && file_exists("$directory/$project/modules/system/system.module") + ) { + return array( + 'no-core' => t('Automatic updating of Drupal core is not supported. See the upgrade guide for information on how to update Drupal core manually.', array('@upgrade-guide' => 'http://drupal.org/upgrade')), + ); + } + + // Look for any .module file that doesn't have a corresponding .info file. + $missing_info = array(); + $files = file_scan_directory("$directory/$project", '/^' . DRUPAL_PHP_FUNCTION_PATTERN . '\.module$/', array('key' => 'name', 'min_depth' => 0)); + foreach ($files as $key => $file) { + // If it has no info file, set an error. + $info_file = dirname($file->uri) . '/' . $file->name . '.info'; + if (!file_exists($info_file)) { + $missing_info[] = $file->filename; + } + } + if (!empty($missing_info)) { + $errors[] = format_plural( + count($missing_info), + '%archive_file contains %names which is missing an info file.', + '%archive_file contains the following modules which are missing info files: %names', + array('%archive_file' => basename($archive_file), '%names' => implode(', ', $missing_info)) + ); + } + + // Parse all the .info files and make sure they're compatible with this + // version of Drupal core. + $incompatible = array(); + $files = file_scan_directory("$directory/$project", '/^' . DRUPAL_PHP_FUNCTION_PATTERN . '\.info/', array('key' => 'name', 'min_depth' => 0)); + foreach ($files as $key => $file) { + // Get the .info file for the module or theme this file belongs to. + $info = drupal_parse_info_file($file->uri); + + // If the module or theme is incompatible with Drupal core, set an error. + if (empty($info['core']) || $info['core'] != DRUPAL_CORE_COMPATIBILITY) { + $incompatible[] = $info['name']; + } + } + if (!empty($incompatible)) { + $errors[] = format_plural( + count($incompatible), + '%archive_file contains a version of %names that is not compatible with Drupal !version.', + '%archive_file contains versions of modules or themes that are not compatible with Drupal !version: %names', + array('!version' => DRUPAL_CORE_COMPATIBILITY, '%archive_file' => basename($archive_file), '%names' => implode(', ', $incompatible)) + ); + } + + return $errors; +} + /** * @defgroup update_status_cache Private update status cache system * @{ -- cgit v1.2.3