: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
* @type string $name The original name of the file on the client machine.
* @type string $type The mime type of the file, if the browser provided this information.
* @type string $tmp_name The temporary filename of the file in which the uploaded file was stored on the server.
* @type int $size The size, in bytes, of the uploaded file.
* @type int $error The error code associated with this file upload.
* @param string $new_file Filename of the newly-uploaded file.
* @param string $type Mime type of the newly-uploaded file.
$move_new_file = apply_filters( 'pre_move_uploaded_file', null, $file, $new_file, $type );
if ( null === $move_new_file ) {
if ( 'wp_handle_upload' === $action ) {
$move_new_file = @move_uploaded_file( $file['tmp_name'], $new_file );
// Use copy and unlink because rename breaks streams.
// phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
$move_new_file = @copy( $file['tmp_name'], $new_file );
unlink( $file['tmp_name'] );
if ( false === $move_new_file ) {
if ( str_starts_with( $uploads['basedir'], ABSPATH ) ) {
$error_path = str_replace( ABSPATH, '', $uploads['basedir'] ) . $uploads['subdir'];
$error_path = basename( $uploads['basedir'] ) . $uploads['subdir'];
return $upload_error_handler(
/* translators: %s: Destination file path. */
__( 'The uploaded file could not be moved to %s.' ),
// Set correct file permissions.
$stat = stat( dirname( $new_file ) );
$perms = $stat['mode'] & 0000666;
chmod( $new_file, $perms );
$url = $uploads['url'] . "/$filename";
clean_dirsize_cache( $new_file );
* Filters the data array for the uploaded file.
* @type string $file Filename of the newly-uploaded file.
* @type string $url URL of the newly-uploaded file.
* @type string $type Mime type of the newly-uploaded file.
* @param string $context The type of upload action. Values include 'upload' or 'sideload'.
'wp_handle_sideload' === $action ? 'sideload' : 'upload'
* Wrapper for _wp_handle_upload().
* Passes the {@see 'wp_handle_upload'} action.
* @see _wp_handle_upload()
* @param array $file Reference to a single element of `$_FILES`.
* Call the function once for each uploaded file.
* See _wp_handle_upload() for accepted values.
* @param array|false $overrides Optional. An associative array of names => values
* to override default variables. Default false.
* See _wp_handle_upload() for accepted values.
* @param string|null $time Optional. Time formatted in 'yyyy/mm'. Default null.
* @return array See _wp_handle_upload() for return value.
function wp_handle_upload( &$file, $overrides = false, $time = null ) {
* $_POST['action'] must be set and its value must equal $overrides['action']
$action = 'wp_handle_upload';
if ( isset( $overrides['action'] ) ) {
$action = $overrides['action'];
return _wp_handle_upload( $file, $overrides, $time, $action );
* Wrapper for _wp_handle_upload().
* Passes the {@see 'wp_handle_sideload'} action.
* @see _wp_handle_upload()
* @param array $file Reference to a single element of `$_FILES`.
* Call the function once for each uploaded file.
* See _wp_handle_upload() for accepted values.
* @param array|false $overrides Optional. An associative array of names => values
* to override default variables. Default false.
* See _wp_handle_upload() for accepted values.
* @param string|null $time Optional. Time formatted in 'yyyy/mm'. Default null.
* @return array See _wp_handle_upload() for return value.
function wp_handle_sideload( &$file, $overrides = false, $time = null ) {
* $_POST['action'] must be set and its value must equal $overrides['action']
$action = 'wp_handle_sideload';
if ( isset( $overrides['action'] ) ) {
$action = $overrides['action'];
return _wp_handle_upload( $file, $overrides, $time, $action );
* Downloads a URL to a local temporary file using the WordPress HTTP API.
* Please note that the calling function must delete or move the file.
* @since 5.2.0 Signature Verification with SoftFail was added.
* @since 5.9.0 Support for Content-Disposition filename was added.
* @param string $url The URL of the file to download.
* @param int $timeout The timeout for the request to download the file.
* @param bool $signature_verification Whether to perform Signature Verification.
* @return string|WP_Error Filename on success, WP_Error on failure.
function download_url( $url, $timeout = 300, $signature_verification = false ) {
// WARNING: The file is not automatically deleted, the script must delete or move the file.
return new WP_Error( 'http_no_url', __( 'No URL Provided.' ) );
$url_path = parse_url( $url, PHP_URL_PATH );
if ( is_string( $url_path ) && '' !== $url_path ) {
$url_filename = basename( $url_path );
$tmpfname = wp_tempnam( $url_filename );
return new WP_Error( 'http_no_file', __( 'Could not create temporary file.' ) );
$response = wp_safe_remote_get(
if ( is_wp_error( $response ) ) {
$response_code = wp_remote_retrieve_response_code( $response );
if ( 200 !== $response_code ) {
'code' => $response_code,
// Retrieve a sample of the response body for debugging purposes.
$tmpf = fopen( $tmpfname, 'rb' );
* Filters the maximum error response body size in `download_url()`.
* @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 );
return new WP_Error( 'http_404', trim( wp_remote_retrieve_response_message( $response ) ), $data );
$content_disposition = wp_remote_retrieve_header( $response, 'Content-Disposition' );
if ( $content_disposition ) {
$content_disposition = strtolower( $content_disposition );
if ( str_starts_with( $content_disposition, 'attachment; filename=' ) ) {
$tmpfname_disposition = sanitize_file_name( substr( $content_disposition, 21 ) );
$tmpfname_disposition = '';
// Potential file name must be valid string.
if ( $tmpfname_disposition && is_string( $tmpfname_disposition )
&& ( 0 === validate_file( $tmpfname_disposition ) )
$tmpfname_disposition = dirname( $tmpfname ) . '/' . $tmpfname_disposition;
if ( rename( $tmpfname, $tmpfname_disposition ) ) {
$tmpfname = $tmpfname_disposition;
if ( ( $tmpfname !== $tmpfname_disposition ) && file_exists( $tmpfname_disposition ) ) {
unlink( $tmpfname_disposition );
$content_md5 = wp_remote_retrieve_header( $response, 'Content-MD5' );
$md5_check = verify_file_md5( $tmpfname, $content_md5 );
if ( is_wp_error( $md5_check ) ) {
// If the caller expects signature verification to occur, check to see if this URL supports it.
if ( $signature_verification ) {
* Filters the list of hosts which should have Signature Verification attempted on.
* @param string[] $hostnames List of hostnames.
$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 validation if supported.
if ( $signature_verification ) {
$signature = wp_remote_retrieve_header( $response, 'X-Content-Signature' );
* Retrieve signatures from a file if the header wasn't included.
* WordPress.org stores signatures at $package_url.sig.
if ( is_string( $url_path ) && ( str_ends_with( $url_path, '.zip' ) || str_ends_with( $url_path, '.tar.gz' ) ) ) {
$signature_url = str_replace( $url_path, $url_path . '.sig', $url );
* Filters the URL where the signature for a file is located.
* @param false|string $signature_url The URL where signatures can be found for a file, or false if none are known.
* @param string $url The URL being verified.
$signature_url = apply_filters( 'wp_signature_url', $signature_url, $url );
$signature_request = wp_safe_remote_get(
'limit_response_size' => 10 * KB_IN_BYTES, // 10KB should be large enough for quite a few signatures.
if ( ! is_wp_error( $signature_request ) && 200 === wp_remote_retrieve_response_code( $signature_request ) ) {
$signature = explode( "\n", wp_remote_retrieve_body( $signature_request ) );
$signature_verification = verify_file_signature( $tmpfname, $signature, $url_filename );
if ( is_wp_error( $signature_verification ) ) {
* Filters whether Signature Verification failures should be allowed to soft fail.
* WARNING: This may be removed from a future release.
* @param bool $signature_softfail If a softfail is allowed.
* @param string $url The url being accessed.
apply_filters( 'wp_signature_softfail', true, $url )
$signature_verification->add_data( $tmpfname, 'softfail-filename' );
return $signature_verification;
* Calculates and compares the MD5 of a file to its expected value.
* @param string $filename The filename to check the MD5 of.
* @param string $expected_md5 The expected MD5 of the file, either a base64-encoded raw md5,
* @return bool|WP_Error True on success, false when the MD5 format is unknown/unexpected,
function verify_file_md5( $filename, $expected_md5 ) {
if ( 32 === strlen( $expected_md5 ) ) {
$expected_raw_md5 = pack( 'H*', $expected_md5 );
} elseif ( 24 === strlen( $expected_md5 ) ) {
$expected_raw_md5 = base64_decode( $expected_md5 );
return false; // Unknown format.
$file_md5 = md5_file( $filename, true );
if ( $file_md5 === $expected_raw_md5 ) {
/* translators: 1: File checksum, 2: Expected checksum value. */
__( 'The checksum of the file (%1$s) does not match the expected checksum value (%2$s).' ),
bin2hex( $expected_raw_md5 )
* Verifies the contents of a file against its ED25519 signature.
* @param string $filename The file to validate.
* @param string|array $signatures A Signature provided for the file.
* @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.
function verify_file_signature( $filename, $signatures, $filename_for_errors = false ) {
if ( ! $filename_for_errors ) {
$filename_for_errors = wp_basename( $filename );
// Check we can process signatures.
if ( ! function_exists( 'sodium_crypto_sign_verify_detached' ) || ! in_array( 'sha384', array_map( 'strtolower', hash_algos() ), true ) ) {
'signature_verification_unsupported',
/* translators: %s: The filename of the package. */
__( 'The authenticity of %s could not be verified as signature verification is unavailable on this system.' ),
'<span class="code">' . esc_html( $filename_for_errors ) . '</span>'
( ! function_exists( 'sodium_crypto_sign_verify_detached' ) ? 'sodium_crypto_sign_verify_detached' : 'sha384' )
// Check for an edge-case affecting PHP Maths abilities.
! extension_loaded( 'sodium' ) &&
in_array( PHP_VERSION_ID, array( 70200, 70201, 70202 ), true ) &&
extension_loaded( 'opcache' )
* 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
'signature_verification_unsupported',
/* translators: %s: The filename of the package. */
__( 'The authenticity of %s could not be verified as signature verification is unavailable on this system.' ),
'<span class="code">' . esc_html( $filename_for_errors ) . '</span>'
'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.
if ( ! extension_loaded( 'sodium' ) && ! ParagonIE_Sodium_Compat::polyfill_is_fast() ) {
$sodium_compat_is_fast = false;
// Allow for an old version of Sodium_Compat being loaded before the bundled WordPress one.
if ( method_exists( 'ParagonIE_Sodium_Compat', 'runtime_speed_test' ) ) {
* Run `ParagonIE_Sodium_Compat::runtime_speed_test()` in optimized integer mode,
* as that's what WordPress utilizes during signing verifications.
// phpcs:disable WordPress.NamingConventions.ValidVariableName
$old_fastMult = ParagonIE_Sodium_Compat::$fastMult;
ParagonIE_Sodium_Compat::$fastMult = true;
$sodium_compat_is_fast = ParagonIE_Sodium_Compat::runtime_speed_test( 100, 10 );
ParagonIE_Sodium_Compat::$fastMult = $old_fastMult;
* This cannot be performed in a reasonable amount of time.
* https://github.com/paragonie/sodium_compat#help-sodium_compat-is-slow-how-can-i-make-it-fast
if ( ! $sodium_compat_is_fast ) {
'signature_verification_unsupported',
/* translators: %s: The filename of the package. */
__( 'The authenticity of %s could not be verified as signature verification is unavailable on this system.' ),
'<span class="code">' . esc_html( $filename_for_errors ) . '</span>'
'sodium' => defined( 'SODIUM_LIBRARY_VERSION' ) ? SODIUM_LIBRARY_VERSION : ( defined( 'ParagonIE_Sodium_Compat::VERSION_STRING' ) ? ParagonIE_Sodium_Compat::VERSION_STRING : false ),
'polyfill_is_fast' => false,
'max_execution_time' => ini_get( 'max_execution_time' ),
'signature_verification_no_signature',
/* translators: %s: The filename of the package. */
__( 'The authenticity of %s could not be verified as no signature was found.' ),
'<span class="code">' . esc_html( $filename_for_errors ) . '</span>'
'filename' => $filename_for_errors,
$trusted_keys = wp_trusted_keys();
$file_hash = hash_file( 'sha384', $filename, true );
mbstring_binary_safe_encoding();
foreach ( (array) $signatures as $signature ) {
$signature_raw = base64_decode( $signature );
// Ensure only valid-length signatures are considered.
if ( SODIUM_CRYPTO_SIGN_BYTES !== strlen( $signature_raw ) ) {
foreach ( (array) $trusted_keys as $key ) {