diff -r 34716fd837a4 -r be944660c56a wp/wp-admin/includes/file.php --- a/wp/wp-admin/includes/file.php Tue Dec 15 15:52:01 2020 +0100 +++ b/wp/wp-admin/includes/file.php Wed Sep 21 18:19:35 2022 +0200 @@ -130,7 +130,7 @@ * @param string $folder Optional. Full path to folder. Default empty. * @param int $levels Optional. Levels of folders to follow, Default 100 (PHP Loop limit). * @param string[] $exclusions Optional. List of folders and files to skip. - * @return bool|string[] False on failure, else array of files. + * @return string[]|false Array of files on success, false on failure. */ function list_files( $folder = '', $levels = 100, $exclusions = array() ) { if ( empty( $folder ) ) { @@ -146,6 +146,7 @@ $files = array(); $dir = @opendir( $folder ); + if ( $dir ) { while ( ( $file = readdir( $dir ) ) !== false ) { // Skip current and parent folder links. @@ -369,23 +370,26 @@ if ( empty( $args['file'] ) ) { return new WP_Error( 'missing_file' ); } - $file = $args['file']; - if ( 0 !== validate_file( $file ) ) { + + if ( 0 !== validate_file( $args['file'] ) ) { return new WP_Error( 'bad_file' ); } if ( ! isset( $args['newcontent'] ) ) { return new WP_Error( 'missing_content' ); } - $content = $args['newcontent']; if ( ! isset( $args['nonce'] ) ) { return new WP_Error( 'missing_nonce' ); } + $file = $args['file']; + $content = $args['newcontent']; + $plugin = null; $theme = null; $real_file = null; + if ( ! empty( $args['plugin'] ) ) { $plugin = $args['plugin']; @@ -417,6 +421,7 @@ } elseif ( ! empty( $args['theme'] ) ) { $stylesheet = $args['theme']; + if ( 0 !== validate_file( $stylesheet ) ) { return new WP_Error( 'bad_theme_path' ); } @@ -489,17 +494,19 @@ $previous_content = file_get_contents( $real_file ); - if ( ! is_writeable( $real_file ) ) { + if ( ! is_writable( $real_file ) ) { return new WP_Error( 'file_not_writable' ); } $f = fopen( $real_file, 'w+' ); + if ( false === $f ) { return new WP_Error( 'file_not_writable' ); } $written = fwrite( $f, $content ); fclose( $f ); + if ( false === $written ) { return new WP_Error( 'unable_to_write', __( 'Unable to write to file.' ) ); } @@ -510,7 +517,7 @@ $scrape_key = md5( rand() ); $transient = 'scrape_key_' . $scrape_key; - $scrape_nonce = strval( rand() ); + $scrape_nonce = (string) rand(); // It shouldn't take more than 60 seconds to make the two loopback requests. set_transient( $transient, $scrape_nonce, 60 ); @@ -575,6 +582,7 @@ ); $result = null; + if ( false === $scrape_result_position ) { $result = $loopback_request_failure; } else { @@ -609,7 +617,6 @@ delete_transient( $transient ); if ( true !== $result ) { - // Roll-back file change. file_put_contents( $real_file, $previous_content ); wp_opcache_invalidate( $real_file, true ); @@ -620,6 +627,7 @@ $message = $result['message']; unset( $result['message'] ); } + return new WP_Error( 'php_error', $message, $result ); } } @@ -671,9 +679,11 @@ $temp_filename = $dir . wp_unique_filename( $dir, $temp_filename ); $fp = @fopen( $temp_filename, 'x' ); + if ( ! $fp && is_writable( $dir ) && file_exists( $temp_filename ) ) { return wp_tempnam( $filename, $dir ); } + if ( $fp ) { fclose( $fp ); } @@ -725,8 +735,20 @@ * * @param string[] $file Reference to a single element of `$_FILES`. * Call the function once for each uploaded file. - * @param string[]|false $overrides An associative array of names => values - * to override default variables. Default false. + * @param array|false $overrides { + * An array of override parameters for this file, or boolean false if none are provided. + * + * @type callable $upload_error_handler Function to call when there is an error during the upload process. + * @see wp_handle_upload_error(). + * @type callable $unique_filename_callback Function to call when determining a unique file name for the file. + * @see wp_unique_filename(). + * @type string[] $upload_error_strings The strings that describe the error indicated in + * `$_FILES[{form field}]['error']`. + * @type bool $test_form Whether to test that the `$_POST['action']` parameter is as expected. + * @type bool $test_size Whether to test that the file size is greater than zero bytes. + * @type bool $test_type Whether to test that the mime type of the file is as expected. + * @type string[] $mimes Array of allowed mime types keyed by their file extension regex. + * } * @param string $time Time formatted in 'yyyy/mm'. * @param string $action Expected value for `$_POST['action']`. * @return string[] On success, returns an associative array of file attributes. @@ -746,13 +768,36 @@ * * The dynamic portion of the hook name, `$action`, refers to the post action. * + * Possible hook names include: + * + * - `wp_handle_sideload_prefilter` + * - `wp_handle_upload_prefilter` + * * @since 2.9.0 as 'wp_handle_upload_prefilter'. * @since 4.0.0 Converted to a dynamic hook with `$action`. * - * @param string[] $file An array of data for a single file. + * @param string[] $file An array of data for the file. Reference to a single element of `$_FILES`. */ $file = apply_filters( "{$action}_prefilter", $file ); + /** + * Filters the override parameters for a file before it is uploaded to WordPress. + * + * The dynamic portion of the hook name, `$action`, refers to the post action. + * + * Possible hook names include: + * + * - `wp_handle_sideload_overrides` + * - `wp_handle_upload_overrides` + * + * @since 5.7.0 + * + * @param array|false $overrides An array of override parameters for this file. Boolean false if none are + * provided. @see _wp_handle_upload(). + * @param string[] $file An array of data for the file. Reference to a single element of `$_FILES`. + */ + $overrides = apply_filters( "{$action}_overrides", $overrides, $file ); + // You may define your own function and pass the name in $overrides['upload_error_handler']. $upload_error_handler = 'wp_handle_upload_error'; if ( isset( $overrides['upload_error_handler'] ) ) { @@ -811,9 +856,10 @@ $mimes = isset( $overrides['mimes'] ) ? $overrides['mimes'] : false; // A correct form post will pass this test. - if ( $test_form && ( ! isset( $_POST['action'] ) || ( $_POST['action'] != $action ) ) ) { + if ( $test_form && ( ! isset( $_POST['action'] ) || $_POST['action'] !== $action ) ) { return call_user_func_array( $upload_error_handler, array( &$file, __( 'Invalid form submission.' ) ) ); } + // A successful upload will pass this test. It makes no sense to override this one. if ( isset( $file['error'] ) && $file['error'] > 0 ) { return call_user_func_array( $upload_error_handler, array( &$file, $upload_error_strings[ $file['error'] ] ) ); @@ -839,6 +885,7 @@ 'upload_max_filesize' ); } + return call_user_func_array( $upload_error_handler, array( &$file, $error_msg ) ); } @@ -853,9 +900,11 @@ if ( $proper_filename ) { $file['name'] = $proper_filename; } + if ( ( ! $type || ! $ext ) && ! current_user_can( 'unfiltered_upload' ) ) { return call_user_func_array( $upload_error_handler, array( &$file, __( 'Sorry, this file type is not permitted for security reasons.' ) ) ); } + if ( ! $type ) { $type = $file['type']; } @@ -908,6 +957,7 @@ } else { $error_path = basename( $uploads['basedir'] ) . $uploads['subdir']; } + return $upload_error_handler( $file, sprintf( @@ -928,7 +978,7 @@ $url = $uploads['url'] . "/$filename"; if ( is_multisite() ) { - delete_transient( 'dirsize_cache' ); + clean_dirsize_cache( $new_file ); } /** @@ -965,11 +1015,11 @@ * * @see _wp_handle_upload() * - * @param array $file Reference to a single element of `$_FILES`. - * Call the function once for each uploaded file. - * @param array|bool $overrides Optional. An associative array of names => values - * to override default variables. Default false. - * @param string $time Optional. Time formatted in 'yyyy/mm'. Default null. + * @param array $file Reference to a single element of `$_FILES`. + * Call the function once for each uploaded file. + * @param array|false $overrides Optional. An associative array of names => values + * to override default variables. Default false. + * @param string $time Optional. Time formatted in 'yyyy/mm'. Default null. * @return array On success, returns an associative array of file attributes. * On failure, returns `$overrides['upload_error_handler']( &$file, $message )` * or `array( 'error' => $message )`. @@ -996,11 +1046,11 @@ * * @see _wp_handle_upload() * - * @param array $file Reference to a single element of `$_FILES`. - * Call the function once for each uploaded file. - * @param array|bool $overrides Optional. An associative array of names => values - * to override default variables. Default false. - * @param string $time Optional. Time formatted in 'yyyy/mm'. Default null. + * @param array $file Reference to a single element of `$_FILES`. + * Call the function once for each uploaded file. + * @param array|false $overrides Optional. An associative array of names => values + * to override default variables. Default false. + * @param string $time Optional. Time formatted in 'yyyy/mm'. Default null. * @return array On success, returns an associative array of file attributes. * On failure, returns `$overrides['upload_error_handler']( &$file, $message )` * or `array( 'error' => $message )`. @@ -1043,7 +1093,7 @@ $tmpfname = wp_tempnam( $url_filename ); if ( ! $tmpfname ) { - return new WP_Error( 'http_no_file', __( 'Could not create Temporary file.' ) ); + return new WP_Error( 'http_no_file', __( 'Could not create temporary file.' ) ); } $response = wp_safe_remote_get( @@ -1062,13 +1112,14 @@ $response_code = wp_remote_retrieve_response_code( $response ); - if ( 200 != $response_code ) { + if ( 200 !== $response_code ) { $data = array( 'code' => $response_code, ); // Retrieve a sample of the response body for debugging purposes. $tmpf = fopen( $tmpfname, 'rb' ); + if ( $tmpf ) { /** * Filters the maximum error response body size in `download_url()`. @@ -1080,17 +1131,21 @@ * @param int $size The maximum error response body size. Default 1 KB. */ $response_size = apply_filters( 'download_url_error_max_body_size', KB_IN_BYTES ); - $data['body'] = fread( $tmpf, $response_size ); + + $data['body'] = fread( $tmpf, $response_size ); fclose( $tmpf ); } unlink( $tmpfname ); + return new WP_Error( 'http_404', trim( wp_remote_retrieve_response_message( $response ) ), $data ); } $content_md5 = wp_remote_retrieve_header( $response, 'content-md5' ); + if ( $content_md5 ) { $md5_check = verify_file_md5( $tmpfname, $content_md5 ); + if ( is_wp_error( $md5_check ) ) { unlink( $tmpfname ); return $md5_check; @@ -1106,13 +1161,15 @@ * * @param string[] $hostnames List of hostnames. */ - $signed_hostnames = apply_filters( 'wp_signature_hosts', array( 'wordpress.org', 'downloads.wordpress.org', 's.w.org' ) ); + $signed_hostnames = apply_filters( 'wp_signature_hosts', array( 'wordpress.org', 'downloads.wordpress.org', 's.w.org' ) ); + $signature_verification = in_array( parse_url( $url, PHP_URL_HOST ), $signed_hostnames, true ); } // Perform signature valiation if supported. if ( $signature_verification ) { $signature = wp_remote_retrieve_header( $response, 'x-content-signature' ); + if ( ! $signature ) { // Retrieve signatures from a file if the header wasn't included. // WordPress.org stores signatures at $package_url.sig. @@ -1125,7 +1182,7 @@ } /** - * Filter the URL where the signature for a file is located. + * Filters the URL where the signature for a file is located. * * @since 5.2.0 * @@ -1190,9 +1247,9 @@ * WP_Error on failure. */ function verify_file_md5( $filename, $expected_md5 ) { - if ( 32 == strlen( $expected_md5 ) ) { + if ( 32 === strlen( $expected_md5 ) ) { $expected_raw_md5 = pack( 'H*', $expected_md5 ); - } elseif ( 24 == strlen( $expected_md5 ) ) { + } elseif ( 24 === strlen( $expected_md5 ) ) { $expected_raw_md5 = base64_decode( $expected_md5 ); } else { return false; // Unknown format. @@ -1222,7 +1279,7 @@ * * @param string $filename The file to validate. * @param string|array $signatures A Signature provided for the file. - * @param string $filename_for_errors A friendly filename for errors. Optional. + * @param string|false $filename_for_errors Optional. A friendly filename for errors. * @return bool|WP_Error True on success, false if verification not attempted, * or WP_Error describing an error condition. */ @@ -1252,7 +1309,6 @@ ) { // Sodium_Compat isn't compatible with PHP 7.2.0~7.2.2 due to a bug in the PHP Opcache extension, bail early as it'll fail. // https://bugs.php.net/bug.php?id=75938 - return new WP_Error( 'signature_verification_unsupported', sprintf( @@ -1266,7 +1322,6 @@ 'sodium' => defined( 'SODIUM_LIBRARY_VERSION' ) ? SODIUM_LIBRARY_VERSION : ( defined( 'ParagonIE_Sodium_Compat::VERSION_STRING' ) ? ParagonIE_Sodium_Compat::VERSION_STRING : false ), ) ); - } // Verify runtime speed of Sodium_Compat is acceptable. @@ -1394,7 +1449,7 @@ // TODO: Add key #2 with longer expiration. /** - * Filter the valid signing keys used to verify the contents of files. + * Filters the valid signing keys used to verify the contents of files. * * @since 5.2.0 * @@ -1501,6 +1556,7 @@ $z = new ZipArchive(); $zopen = $z->open( $file, ZIPARCHIVE::CHECKCONS ); + if ( true !== $zopen ) { return new WP_Error( 'incompatible_archive', __( 'Incompatible Archive.' ), array( 'ziparchive_error' => $zopen ) ); } @@ -1509,6 +1565,7 @@ for ( $i = 0; $i < $z->numFiles; $i++ ) { $info = $z->statIndex( $i ); + if ( ! $info ) { return new WP_Error( 'stat_failed_ziparchive', __( 'Could not retrieve file from archive.' ) ); } @@ -1542,27 +1599,39 @@ */ if ( wp_doing_cron() ) { $available_space = @disk_free_space( WP_CONTENT_DIR ); + if ( $available_space && ( $uncompressed_size * 2.1 ) > $available_space ) { - return new WP_Error( 'disk_full_unzip_file', __( 'Could not copy files. You may have run out of disk space.' ), compact( 'uncompressed_size', 'available_space' ) ); + return new WP_Error( + 'disk_full_unzip_file', + __( 'Could not copy files. You may have run out of disk space.' ), + compact( 'uncompressed_size', 'available_space' ) + ); } } $needed_dirs = array_unique( $needed_dirs ); + foreach ( $needed_dirs as $dir ) { // Check the parent folders of the folders all exist within the creation array. - if ( untrailingslashit( $to ) == $dir ) { // Skip over the working directory, we know this exists (or will exist). + if ( untrailingslashit( $to ) === $dir ) { // Skip over the working directory, we know this exists (or will exist). continue; } + if ( strpos( $dir, $to ) === false ) { // If the directory is not within the working directory, skip it. continue; } $parent_folder = dirname( $dir ); - while ( ! empty( $parent_folder ) && untrailingslashit( $to ) != $parent_folder && ! in_array( $parent_folder, $needed_dirs, true ) ) { + + while ( ! empty( $parent_folder ) + && untrailingslashit( $to ) !== $parent_folder + && ! in_array( $parent_folder, $needed_dirs, true ) + ) { $needed_dirs[] = $parent_folder; $parent_folder = dirname( $parent_folder ); } } + asort( $needed_dirs ); // Create those directories if need be: @@ -1576,6 +1645,7 @@ for ( $i = 0; $i < $z->numFiles; $i++ ) { $info = $z->statIndex( $i ); + if ( ! $info ) { return new WP_Error( 'stat_failed_ziparchive', __( 'Could not retrieve file from archive.' ) ); } @@ -1594,6 +1664,7 @@ } $contents = $z->getFromIndex( $i ); + if ( false === $contents ) { return new WP_Error( 'extract_failed_ziparchive', __( 'Could not extract file from archive.' ), $info['name'] ); } @@ -1669,27 +1740,39 @@ */ if ( wp_doing_cron() ) { $available_space = @disk_free_space( WP_CONTENT_DIR ); + if ( $available_space && ( $uncompressed_size * 2.1 ) > $available_space ) { - return new WP_Error( 'disk_full_unzip_file', __( 'Could not copy files. You may have run out of disk space.' ), compact( 'uncompressed_size', 'available_space' ) ); + return new WP_Error( + 'disk_full_unzip_file', + __( 'Could not copy files. You may have run out of disk space.' ), + compact( 'uncompressed_size', 'available_space' ) + ); } } $needed_dirs = array_unique( $needed_dirs ); + foreach ( $needed_dirs as $dir ) { // Check the parent folders of the folders all exist within the creation array. - if ( untrailingslashit( $to ) == $dir ) { // Skip over the working directory, we know this exists (or will exist). + if ( untrailingslashit( $to ) === $dir ) { // Skip over the working directory, we know this exists (or will exist). continue; } + if ( strpos( $dir, $to ) === false ) { // If the directory is not within the working directory, skip it. continue; } $parent_folder = dirname( $dir ); - while ( ! empty( $parent_folder ) && untrailingslashit( $to ) != $parent_folder && ! in_array( $parent_folder, $needed_dirs, true ) ) { + + while ( ! empty( $parent_folder ) + && untrailingslashit( $to ) !== $parent_folder + && ! in_array( $parent_folder, $needed_dirs, true ) + ) { $needed_dirs[] = $parent_folder; $parent_folder = dirname( $parent_folder ); } } + asort( $needed_dirs ); // Create those directories if need be: @@ -1720,6 +1803,7 @@ return new WP_Error( 'copy_failed_pclzip', __( 'Could not copy file.' ), $file['filename'] ); } } + return true; } @@ -1743,6 +1827,10 @@ $dirlist = $wp_filesystem->dirlist( $from ); + if ( false === $dirlist ) { + return new WP_Error( 'dirlist_failed_copy_dir', __( 'Directory listing failed.' ), basename( $to ) ); + } + $from = trailingslashit( $from ); $to = trailingslashit( $to ); @@ -1755,6 +1843,7 @@ if ( ! $wp_filesystem->copy( $from . $filename, $to . $filename, true, FS_CHMOD_FILE ) ) { // If copy failed, chmod file to 0644 and try again. $wp_filesystem->chmod( $to . $filename, FS_CHMOD_FILE ); + if ( ! $wp_filesystem->copy( $from . $filename, $to . $filename, true, FS_CHMOD_FILE ) ) { return new WP_Error( 'copy_failed_copy_dir', __( 'Could not copy file.' ), $to . $filename ); } @@ -1770,6 +1859,7 @@ // 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 ); @@ -1777,6 +1867,7 @@ } $result = copy_dir( $from . $filename, $to . $filename, $sub_skip_list ); + if ( is_wp_error( $result ) ) { return $result; } @@ -1908,7 +1999,7 @@ } // 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 ) ) { + if ( WP_LANG_DIR === $context && ! is_dir( $context ) ) { $context = dirname( $context ); } @@ -2029,6 +2120,7 @@ * @param bool $allow_relaxed_file_ownership Whether to allow Group/World writable. */ $req_cred = apply_filters( 'request_filesystem_credentials', '', $form_post, $type, $error, $context, $extra_fields, $allow_relaxed_file_ownership ); + if ( '' !== $req_cred ) { return $req_cred; } @@ -2067,14 +2159,25 @@ ); } - // If defined, set it to that. Else, if POST'd, set it to that. If not, set it to whatever it previously was (saved details in option). - $credentials['hostname'] = defined( 'FTP_HOST' ) ? FTP_HOST : ( ! empty( $submitted_form['hostname'] ) ? $submitted_form['hostname'] : $credentials['hostname'] ); - $credentials['username'] = defined( 'FTP_USER' ) ? FTP_USER : ( ! empty( $submitted_form['username'] ) ? $submitted_form['username'] : $credentials['username'] ); - $credentials['password'] = defined( 'FTP_PASS' ) ? FTP_PASS : ( ! empty( $submitted_form['password'] ) ? $submitted_form['password'] : '' ); + $ftp_constants = array( + 'hostname' => 'FTP_HOST', + 'username' => 'FTP_USER', + 'password' => 'FTP_PASS', + 'public_key' => 'FTP_PUBKEY', + 'private_key' => 'FTP_PRIKEY', + ); - // Check to see if we are setting the public/private keys for ssh. - $credentials['public_key'] = defined( 'FTP_PUBKEY' ) ? FTP_PUBKEY : ( ! empty( $submitted_form['public_key'] ) ? $submitted_form['public_key'] : '' ); - $credentials['private_key'] = defined( 'FTP_PRIKEY' ) ? FTP_PRIKEY : ( ! empty( $submitted_form['private_key'] ) ? $submitted_form['private_key'] : '' ); + // If defined, set it to that. Else, if POST'd, set it to that. If not, set it to an empty string. + // Otherwise, keep it as it previously was (saved details in option). + foreach ( $ftp_constants as $key => $constant ) { + if ( defined( $constant ) ) { + $credentials[ $key ] = constant( $constant ); + } elseif ( ! empty( $submitted_form[ $key ] ) ) { + $credentials[ $key ] = $submitted_form[ $key ]; + } elseif ( ! isset( $credentials[ $key ] ) ) { + $credentials[ $key ] = ''; + } + } // Sanitize the hostname, some people might pass in odd data. $credentials['hostname'] = preg_replace( '|\w+://|', '', $credentials['hostname'] ); // Strip any schemes off. @@ -2097,9 +2200,10 @@ } elseif ( ! isset( $credentials['connection_type'] ) ) { // All else fails (and it's not defaulted to something else saved), default to FTP. $credentials['connection_type'] = 'ftp'; } + if ( ! $error - && ( ( ! empty( $credentials['password'] ) && ! empty( $credentials['username'] ) && ! empty( $credentials['hostname'] ) ) - || ( 'ssh' === $credentials['connection_type'] && ! empty( $credentials['public_key'] ) && ! empty( $credentials['private_key'] ) ) + && ( ! empty( $credentials['hostname'] ) && ! empty( $credentials['username'] ) && ! empty( $credentials['password'] ) + || 'ssh' === $credentials['connection_type'] && ! empty( $credentials['public_key'] ) && ! empty( $credentials['private_key'] ) ) ) { $stored_credentials = $credentials; @@ -2108,7 +2212,12 @@ $stored_credentials['hostname'] .= ':' . $stored_credentials['port']; } - unset( $stored_credentials['password'], $stored_credentials['port'], $stored_credentials['private_key'], $stored_credentials['public_key'] ); + unset( + $stored_credentials['password'], + $stored_credentials['port'], + $stored_credentials['private_key'], + $stored_credentials['public_key'] + ); if ( ! wp_installing() ) { update_option( 'ftp_credentials', $stored_credentials ); @@ -2116,6 +2225,7 @@ return $credentials; } + $hostname = isset( $credentials['hostname'] ) ? $credentials['hostname'] : ''; $username = isset( $credentials['username'] ) ? $credentials['username'] : ''; $public_key = isset( $credentials['public_key'] ) ? $credentials['public_key'] : ''; @@ -2156,7 +2266,6 @@ * @param string $context Full path to the directory that is tested for being writable. */ $types = apply_filters( 'fs_ftp_connection_types', $types, $credentials, $type, $error, $context ); - ?>
@@ -2213,12 +2322,10 @@
@@ -2262,6 +2369,12 @@ echo ''; } } + + // Make sure the `submit_button()` function is available during the REST API call + // from WP_Site_Health_Auto_Updates::test_check_wp_filesystem_method(). + if ( ! function_exists( 'submit_button' ) ) { + require_once ABSPATH . '/wp-admin/includes/template.php'; + } ?>