diff --git a/wp-admin/includes/class-wp-upgrader-skins.php b/wp-admin/includes/class-wp-upgrader-skins.php index 03fffa8af9..9cd665fe4d 100644 --- a/wp-admin/includes/class-wp-upgrader-skins.php +++ b/wp-admin/includes/class-wp-upgrader-skins.php @@ -40,12 +40,18 @@ class WP_Upgrader_Skin { $this->result = $result; } - public function request_filesystem_credentials($error = false) { + public function request_filesystem_credentials( $error = false, $context = false, $allow_relaxed_file_ownership = false ) { $url = $this->options['url']; - $context = $this->options['context']; - if ( !empty($this->options['nonce']) ) + if ( ! $context ) { + $context = $this->options['context']; + } + if ( !empty($this->options['nonce']) ) { $url = wp_nonce_url($url, $this->options['nonce']); - return request_filesystem_credentials($url, '', $error, $context); //Possible to bring inline, Leaving as is for now. + } + + $extra_fields = array(); + + return request_filesystem_credentials( $url, '', $error, $context, $extra_fields, $allow_relaxed_file_ownership ); } public function header() { @@ -699,13 +705,14 @@ class Language_Pack_Upgrader_Skin extends WP_Upgrader_Skin { class Automatic_Upgrader_Skin extends WP_Upgrader_Skin { protected $messages = array(); - public function request_filesystem_credentials( $error = false, $context = '' ) { - if ( $context ) + public function request_filesystem_credentials( $error = false, $context = '', $allow_relaxed_file_ownership = false ) { + if ( $context ) { $this->options['context'] = $context; + } // TODO: fix up request_filesystem_credentials(), or split it, to allow us to request a no-output version // This will output a credentials form in event of failure, We don't want that, so just hide with a buffer ob_start(); - $result = parent::request_filesystem_credentials( $error ); + $result = parent::request_filesystem_credentials( $error, $context, $allow_relaxed_file_ownership ); ob_end_clean(); return $result; } diff --git a/wp-admin/includes/class-wp-upgrader.php b/wp-admin/includes/class-wp-upgrader.php index ccd2931f58..3a3cd69dc0 100644 --- a/wp-admin/includes/class-wp-upgrader.php +++ b/wp-admin/includes/class-wp-upgrader.php @@ -61,17 +61,19 @@ class WP_Upgrader { $this->strings['maintenance_end'] = __('Disabling Maintenance mode…'); } - public function fs_connect( $directories = array() ) { + public function fs_connect( $directories = array(), $allow_relaxed_file_ownership = false ) { global $wp_filesystem; - if ( false === ($credentials = $this->skin->request_filesystem_credentials()) ) + if ( false === ( $credentials = $this->skin->request_filesystem_credentials( false, $directories[0], $allow_relaxed_file_ownership ) ) ) { return false; + } - if ( ! WP_Filesystem($credentials) ) { + if ( ! WP_Filesystem( $credentials, $directories[0], $allow_relaxed_file_ownership ) ) { $error = true; if ( is_object($wp_filesystem) && $wp_filesystem->errors->get_error_code() ) $error = $wp_filesystem->errors; - $this->skin->request_filesystem_credentials($error); //Failed to connect, Error and request again + // Failed to connect, Error and request again + $this->skin->request_filesystem_credentials( $error, $directories[0], $allow_relaxed_file_ownership ); return false; } @@ -1456,6 +1458,7 @@ class Core_Upgrader extends WP_Upgrader { 'pre_check_md5' => true, 'attempt_rollback' => false, 'do_rollback' => false, + 'allow_relaxed_file_ownership' => false, ); $parsed_args = wp_parse_args( $args, $defaults ); @@ -1466,7 +1469,7 @@ class Core_Upgrader extends WP_Upgrader { if ( !isset( $current->response ) || $current->response == 'latest' ) return new WP_Error('up_to_date', $this->strings['up_to_date']); - $res = $this->fs_connect( array(ABSPATH, WP_CONTENT_DIR) ); + $res = $this->fs_connect( array( ABSPATH, WP_CONTENT_DIR ), $parsed_args['allow_relaxed_file_ownership'] ); if ( ! $res || is_wp_error( $res ) ) { return $res; } @@ -1911,8 +1914,14 @@ class WP_Automatic_Updater { if ( $this->is_disabled() ) return false; + // Only relax the filesystem checks when the update doesn't include new files + $allow_relaxed_file_ownership = false; + if ( 'core' == $type && isset( $item->new_files ) && ! $item->new_files ) { + $allow_relaxed_file_ownership = true; + } + // If we can't do an auto core update, we may still be able to email the user. - if ( ! $skin->request_filesystem_credentials( false, $context ) || $this->is_vcs_checkout( $context ) ) { + if ( ! $skin->request_filesystem_credentials( false, $context, $allow_relaxed_file_ownership ) || $this->is_vcs_checkout( $context ) ) { if ( 'core' == $type ) $this->send_core_update_notification_email( $item ); return false; @@ -2072,6 +2081,11 @@ class WP_Automatic_Updater { break; } + $allow_relaxed_file_ownership = false; + if ( 'core' == $type && isset( $item->new_files ) && ! $item->new_files ) { + $allow_relaxed_file_ownership = true; + } + // Boom, This sites about to get a whole new splash of paint! $upgrade_result = $upgrader->upgrade( $upgrader_item, array( 'clear_update_cache' => false, @@ -2079,6 +2093,9 @@ class WP_Automatic_Updater { 'pre_check_md5' => false, // Only available for core updates. 'attempt_rollback' => true, + // Allow relaxed file ownership in some scenarios + 'allow_relaxed_file_ownership' => $allow_relaxed_file_ownership, + ) ); // If the filesystem is unavailable, false is returned. diff --git a/wp-admin/includes/file.php b/wp-admin/includes/file.php index 6bb05f0ad8..1dfca20573 100644 --- a/wp-admin/includes/file.php +++ b/wp-admin/includes/file.php @@ -809,14 +809,15 @@ function copy_dir($from, $to, $skip_list = array() ) { * * @param array $args (optional) Connection args, These are passed directly to the WP_Filesystem_*() classes. * @param string $context (optional) Context for get_filesystem_method(), See function declaration for more information. + * @param bool $allow_relaxed_file_ownership Whether to allow Group/World writable. * @return null|boolean false on failure, true on success */ -function WP_Filesystem( $args = false, $context = false ) { +function WP_Filesystem( $args = false, $context = false, $allow_relaxed_file_ownership = false ) { global $wp_filesystem; require_once(ABSPATH . 'wp-admin/includes/class-wp-filesystem-base.php'); - $method = get_filesystem_method($args, $context); + $method = get_filesystem_method( $args, $context, $allow_relaxed_file_ownership ); if ( ! $method ) return false; @@ -879,25 +880,46 @@ function WP_Filesystem( $args = false, $context = false ) { * * @param array $args Connection details. * @param string $context Full path to the directory that is tested for being writable. + * @param bool $allow_relaxed_file_ownership Whether to allow Group/World writable. * @return string The transport to use, see description for valid return values. */ -function get_filesystem_method($args = array(), $context = false) { +function get_filesystem_method( $args = array(), $context = false, $allow_relaxed_file_ownership = false ) { $method = defined('FS_METHOD') ? FS_METHOD : false; // Please ensure that this is either 'direct', 'ssh2', 'ftpext' or 'ftpsockets' - if ( ! $method && function_exists('getmyuid') && function_exists('fileowner') ){ - if ( !$context ) - $context = WP_CONTENT_DIR; + if ( ! $context ) { + $context = WP_CONTENT_DIR; + } - // If the directory doesn't exist (wp-content/languages) then use the parent directory as we'll create it. - if ( WP_LANG_DIR == $context && ! is_dir( $context ) ) - $context = dirname( $context ); + // If the directory doesn't exist (wp-content/languages) then use the parent directory as we'll create it. + if ( WP_LANG_DIR == $context && ! is_dir( $context ) ) { + $context = dirname( $context ); + } + + $context = trailingslashit( $context ); + + if ( ! $method ) { - $context = trailingslashit($context); $temp_file_name = $context . 'temp-write-test-' . time(); $temp_handle = @fopen($temp_file_name, 'w'); if ( $temp_handle ) { - if ( getmyuid() == @fileowner($temp_file_name) ) + + // Attempt to determine the file owner of the WordPress files, and that of newly created files + $wp_file_owner = $temp_file_owner = false; + if ( function_exists('fileowner') ) { + $wp_file_owner = @fileowner( __FILE__ ); + $temp_file_owner = @fileowner( $temp_file_name ); + } + + if ( $wp_file_owner !== false && $wp_file_owner === $temp_file_owner ) { + // WordPress is creating files as the same owner as the WordPress files, + // this means it's safe to modify & create new files via PHP. $method = 'direct'; + } else if ( $allow_relaxed_file_ownership ) { + // The $context directory is writable, and $allow_relaxed_file_ownership is set, this means we can modify files + // safely in this directory. This mode doesn't create new files, only alter existing ones. + $method = 'direct'; + } + @fclose($temp_handle); @unlink($temp_file_name); } @@ -912,10 +934,12 @@ function get_filesystem_method($args = array(), $context = false) { * * @since 2.6.0 * - * @param string $method Filesystem method to return. - * @param array $args An array of connection details for the method. + * @param string $method Filesystem method to return. + * @param array $args An array of connection details for the method. + * @param string $context Full path to the directory that is tested for being writable. + * @param bool $allow_relaxed_file_ownership Whether to allow Group/World writable. */ - return apply_filters( 'filesystem_method', $method, $args ); + return apply_filters( 'filesystem_method', $method, $args, $context, $allow_relaxed_file_ownership ); } /** @@ -933,9 +957,10 @@ function get_filesystem_method($args = array(), $context = false) { * @param boolean $error if the current request has failed to connect * @param string $context The directory which is needed access to, The write-test will be performed on this directory by get_filesystem_method() * @param string $extra_fields Extra POST fields which should be checked for to be included in the post. + * @param bool $allow_relaxed_file_ownership Whether to allow Group/World writable. * @return boolean False on failure. True on success. */ -function request_filesystem_credentials($form_post, $type = '', $error = false, $context = false, $extra_fields = null) { +function request_filesystem_credentials($form_post, $type = '', $error = false, $context = false, $extra_fields = null, $allow_relaxed_file_ownership = false ) { /** * Filter the filesystem credentials form output. @@ -952,14 +977,16 @@ function request_filesystem_credentials($form_post, $type = '', $error = false, * Default false. * @param string $context Full path to the directory that is tested for * being writable. + * @param bool $allow_relaxed_file_ownership Whether to allow Group/World writable. * @param array $extra_fields Extra POST fields. */ - $req_cred = apply_filters( 'request_filesystem_credentials', '', $form_post, $type, $error, $context, $extra_fields ); + $req_cred = apply_filters( 'request_filesystem_credentials', '', $form_post, $type, $error, $context, $extra_fields, $allow_relaxed_file_ownership ); if ( '' !== $req_cred ) return $req_cred; - if ( empty($type) ) - $type = get_filesystem_method(array(), $context); + if ( empty($type) ) { + $type = get_filesystem_method( array(), $context, $allow_relaxed_file_ownership ); + } if ( 'direct' == $type ) return true; diff --git a/wp-admin/update-core.php b/wp-admin/update-core.php index 6bab1ca973..ec59a92329 100644 --- a/wp-admin/update-core.php +++ b/wp-admin/update-core.php @@ -381,19 +381,23 @@ function do_core_upgrade( $reinstall = false ) { if ( !$update ) return; + // Allow relaxed file ownership writes for User-initiated upgrades when the API specifies + // that it's safe to do so. This only happens when there are no new files to create. + $allow_relaxed_file_ownership = ! $reinstall && isset( $update->new_files ) && ! $update->new_files; + ?>

'; return; } - if ( ! WP_Filesystem( $credentials, ABSPATH ) ) { + if ( ! WP_Filesystem( $credentials, ABSPATH, $allow_relaxed_file_ownership ) ) { // Failed to connect, Error and request again - request_filesystem_credentials( $url, '', true, ABSPATH ); + request_filesystem_credentials( $url, '', true, ABSPATH, array(), $allow_relaxed_file_ownership ); echo '
'; return; } @@ -411,7 +415,9 @@ function do_core_upgrade( $reinstall = false ) { add_filter( 'update_feedback', 'show_message' ); $upgrader = new Core_Upgrader(); - $result = $upgrader->upgrade( $update ); + $result = $upgrader->upgrade( $update, array( + 'allow_relaxed_file_ownership' => $allow_relaxed_file_ownership + ) ); if ( is_wp_error($result) ) { show_message($result); diff --git a/wp-includes/update.php b/wp-includes/update.php index 05a152adb9..bb3b100935 100644 --- a/wp-includes/update.php +++ b/wp-includes/update.php @@ -142,7 +142,7 @@ function wp_version_check( $extra_stats = array(), $force_check = false ) { $offer[ $offer_key ] = esc_html( $value ); } $offer = (object) array_intersect_key( $offer, array_fill_keys( array( 'response', 'download', 'locale', - 'packages', 'current', 'version', 'php_version', 'mysql_version', 'new_bundled', 'partial_version', 'notify_email', 'support_email' ), '' ) ); + 'packages', 'current', 'version', 'php_version', 'mysql_version', 'new_bundled', 'partial_version', 'notify_email', 'support_email', 'new_files' ), '' ) ); } $updates = new stdClass();