Upgrader: Perform a MD5 file verification check on the files during upgrade. This ensures that both a Partial upgrade build can be used, and that all the files were copied into place correctly.

Props pento for initial patch. Fixes #18201


git-svn-id: https://develop.svn.wordpress.org/trunk@25540 602fd350-edb4-49c9-b593-d223f7449a82
This commit is contained in:
Dion Hulse 2013-09-21 06:48:20 +00:00
parent 38ef3b8f63
commit eb3bee3ba5
4 changed files with 159 additions and 26 deletions

View File

@ -1111,11 +1111,18 @@ class Core_Upgrader extends WP_Upgrader {
$wp_dir = trailingslashit($wp_filesystem->abspath());
// Pre-cache the checksums for the versions we care about
get_core_checksums( array( $wp_version, $current->version ) );
$no_partial = false;
if ( ! $this->check_files() )
$no_partial = true;
// If partial update is returned from the API, use that, unless we're doing a reinstall.
// If we cross the new_bundled version number, then use the new_bundled zip.
// Don't though if the constant is set to skip bundled items.
// If the API returns a no_content zip, go with it. Finally, default to the full zip.
if ( $current->packages->partial && 'reinstall' != $current->response && $wp_version == $current->partial_version )
if ( $current->packages->partial && 'reinstall' != $current->response && $wp_version == $current->partial_version && ! $no_partial )
$to_download = 'partial';
elseif ( $current->packages->new_bundled && version_compare( $wp_version, $current->new_bundled, '<' )
&& ( ! defined( 'CORE_UPGRADE_SKIP_NEW_BUNDLED' ) || ! CORE_UPGRADE_SKIP_NEW_BUNDLED ) )
@ -1205,6 +1212,21 @@ class Core_Upgrader extends WP_Upgrader {
return false;
}
function check_files() {
global $wp_version;
$checksums = get_core_checksums( $wp_version );
if ( empty( $checksums[ $wp_version ] ) || ! is_array( $checksums[ $wp_version ] ) )
return false;
foreach ( $checksums[ $wp_version ] as $file => $checksum ) {
if ( md5_file( ABSPATH . $file ) !== $checksum )
return false;
}
return true;
}
}
/**

View File

@ -723,17 +723,9 @@ function copy_dir($from, $to, $skip_list = array() ) {
$from = trailingslashit($from);
$to = trailingslashit($to);
$skip_regex = '';
foreach ( (array)$skip_list as $key => $skip_file )
$skip_regex .= preg_quote($skip_file, '!') . '|';
if ( !empty($skip_regex) )
$skip_regex = '!(' . rtrim($skip_regex, '|') . ')$!i';
foreach ( (array) $dirlist as $filename => $fileinfo ) {
if ( !empty($skip_regex) )
if ( preg_match($skip_regex, $from . $filename) )
continue;
if ( in_array( $filename, $skip_list ) )
continue;
if ( 'f' == $fileinfo['type'] ) {
if ( ! $wp_filesystem->copy($from . $filename, $to . $filename, true, FS_CHMOD_FILE) ) {
@ -747,7 +739,15 @@ function copy_dir($from, $to, $skip_list = array() ) {
if ( !$wp_filesystem->mkdir($to . $filename, FS_CHMOD_DIR) )
return new WP_Error('mkdir_failed', __('Could not create directory.'), $to . $filename);
}
$result = copy_dir($from . $filename, $to . $filename, $skip_list);
// generate the $sub_skip_list for the subdirectory as a sub-set of the existing $skip_list
$sub_skip_list = array();
foreach ( $skip_list as $skip_item ) {
if ( 0 === strpos( $skip_item, $filename . '/' ) )
$sub_skip_list[] = preg_replace( '!^' . preg_quote( $filename, '!' ) . '/!i', '', $skip_item );
}
$result = copy_dir($from . $filename, $to . $filename, $sub_skip_list);
if ( is_wp_error($result) )
return $result;
}

View File

@ -688,16 +688,62 @@ function update_core($from, $to) {
elseif ( !$mysql_compat )
return new WP_Error( 'mysql_not_compatible', sprintf( __('The update cannot be installed because WordPress %1$s requires MySQL version %2$s or higher. You are running version %3$s.'), $wp_version, $required_mysql_version, $mysql_version ) );
apply_filters('update_feedback', __('Installing the latest version&#8230;'));
apply_filters( 'update_feedback', __( 'Preparing to install the latest version&#8230;' ) );
// Don't copy wp-content, we'll deal with that below
$skip = array( 'wp-content' );
// Check to see which files don't really need updating - only available for 3.7 and higher
if ( function_exists( 'get_core_checksums' ) ) {
$checksums = get_core_checksums( $wp_version );
if ( ! empty( $checksums[ $wp_version ] ) && is_array( $checksums[ $wp_version ] ) ) {
foreach( $checksums[ $wp_version ] as $file => $checksum ) {
if ( md5_file( ABSPATH . $file ) === $checksum )
$skip[] = $file;
}
}
}
apply_filters( 'update_feedback', __( 'Enabling Maintenance mode&#8230;' ) );
// Create maintenance file to signal that we are upgrading
$maintenance_string = '<?php $upgrading = ' . time() . '; ?>';
$maintenance_file = $to . '.maintenance';
$wp_filesystem->delete($maintenance_file);
$wp_filesystem->put_contents($maintenance_file, $maintenance_string, FS_CHMOD_FILE);
apply_filters( 'update_feedback', __( 'Copying the required files&#8230;' ) );
// Copy new versions of WP files into place.
$result = _copy_dir($from . $distro, $to, array('wp-content') );
$result = _copy_dir( $from . $distro, $to, $skip );
// Check to make sure everything copied correctly, ignoring the contents of wp-content
$skip = array( 'wp-content' );
$failed = array();
if ( ! empty( $checksums[ $wp_version ] ) && is_array( $checksums[ $wp_version ] ) ) {
foreach ( $checksums[ $wp_version ] as $file => $checksum ) {
if ( 0 === strpos( $file, 'wp-content' ) )
continue;
if ( md5_file( ABSPATH . $file ) == $checksum )
$skip[] = $file;
else
$failed[] = $file;
}
}
// Some files didn't copy properly
if ( ! empty( $failed ) ) {
$total_size = 0;
// Find the local version of the working directory
$working_dir_local = str_replace( trailingslashit( $wp_filesystem->wp_content_dir() ), trailingslashit( WP_CONTENT_DIR ), $from . $distro );
foreach ( $failed as $file )
$total_size += filesize( $working_dir_local . '/' . $file );
// If we don't have enough free space, it isn't worth trying again
if ( $total_size >= disk_free_space( ABSPATH ) )
$result = new WP_Error( 'disk_full', __( "There isn't enough free disk space to complete the upgrade." ), $to );
else
$result = _copy_dir( $from . $distro, $to, $skip );
}
// Custom Content Directory needs updating now.
// Copy Languages
@ -795,6 +841,7 @@ function update_core($from, $to) {
else
delete_option('update_core');
apply_filters( 'update_feedback', __( 'Disabling Maintenance mode&#8230;' ) );
// Remove maintenance file, we're done.
$wp_filesystem->delete($maintenance_file);
@ -808,10 +855,12 @@ function update_core($from, $to) {
* Copies a directory from one location to another via the WordPress Filesystem Abstraction.
* Assumes that WP_Filesystem() has already been called and setup.
*
* This is a temporary function for the 3.1 -> 3.2 upgrade only and will be removed in 3.3
* This is a temporary function for the 3.1 -> 3.2 upgrade, as well as for those upgrading to
* 3.7+
*
* @ignore
* @since 3.2.0
* @since 3.7.0 Updated not to use a regular expression for the skip list
* @see copy_dir()
*
* @param string $from source directory
@ -827,17 +876,9 @@ function _copy_dir($from, $to, $skip_list = array() ) {
$from = trailingslashit($from);
$to = trailingslashit($to);
$skip_regex = '';
foreach ( (array)$skip_list as $key => $skip_file )
$skip_regex .= preg_quote($skip_file, '!') . '|';
if ( !empty($skip_regex) )
$skip_regex = '!(' . rtrim($skip_regex, '|') . ')$!i';
foreach ( (array) $dirlist as $filename => $fileinfo ) {
if ( !empty($skip_regex) )
if ( preg_match($skip_regex, $from . $filename) )
continue;
if ( in_array( $filename, $skip_list ) )
continue;
if ( 'f' == $fileinfo['type'] ) {
if ( ! $wp_filesystem->copy($from . $filename, $to . $filename, true, FS_CHMOD_FILE) ) {
@ -851,7 +892,15 @@ function _copy_dir($from, $to, $skip_list = array() ) {
if ( !$wp_filesystem->mkdir($to . $filename, FS_CHMOD_DIR) )
return new WP_Error('mkdir_failed', __('Could not create directory.'), $to . $filename);
}
$result = _copy_dir($from . $filename, $to . $filename, $skip_list);
// generate the $sub_skip_list for the subdirectory as a sub-set of the existing $skip_list
$sub_skip_list = array();
foreach ( $skip_list as $skip_item ) {
if ( 0 === strpos( $skip_item, $filename . '/' ) )
$sub_skip_list[] = preg_replace( '!^' . preg_quote( $filename, '!' ) . '/!i', '', $skip_item );
}
$result = _copy_dir($from . $filename, $to . $filename, $sub_skip_list);
if ( is_wp_error($result) )
return $result;
}

View File

@ -87,6 +87,68 @@ function find_core_auto_update() {
return $auto_update;
}
/**
* Gets and caches the checksums for the given versions of WordPress
*
* @since 3.7.0
*
* @param $version string|array A single version, or an array of versions to fetch
*
* @return bool|array False on failure, otherwise the array of checksums, keyed by version
*/
function get_core_checksums( $version ) {
if ( ! is_array( $version ) )
$version = array( $version );
$return = array();
// Check to see if we have cached copies available, if we do, no need to request them
foreach ( $version as $i => $v ) {
if ( $checksums = get_site_transient( "core_checksums_$v" ) ) {
unset( $version[ $i ] );
$return[ $v ] = $checksums;
}
}
// We had cached copies for all of the versions!
if ( empty( $version ) )
return $return;
$url = 'http://api.wordpress.org/core/checksums/1.0/?' . http_build_query( array( 'version' => $version ), null, '&' );
if ( wp_http_supports( array( 'ssl' ) ) )
$url = set_url_scheme( $url, 'https' );
$options = array(
'timeout' => ( ( defined('DOING_CRON') && DOING_CRON ) ? 30 : 3 ),
);
$response = wp_remote_get( $url, $options );
if ( is_wp_error( $response ) || 200 != wp_remote_retrieve_response_code( $response ) )
return false;
$body = trim( wp_remote_retrieve_body( $response ) );
$body = json_decode( $body, true );
if ( ! is_array( $body ) || ! isset( $body['checksums'] ) || ! is_array( $body['checksums'] ) )
return false;
// Cache the checksums for later
foreach ( $version as $v ) {
set_site_transient( "core_checksums_$v", $body['checksums'][ $v ], HOUR_IN_SECONDS );
$return[ $v ] = $body['checksums'][ $v ];
}
// If the API didn't return anything for a version, explicitly set it's return value to false
foreach ( $return as $v => $r ) {
if ( empty( $r ) )
$return[ $v ] = false;
}
return $return;
}
function dismiss_core_update( $update ) {
$dismissed = get_site_option( 'dismissed_update_core' );
$dismissed[ $update->current . '|' . $update->locale ] = true;