download_url()WP 2.5.0

Uploads a file (URL) to a temporary PHP directory. Works based on the WordPress HTTP API.

Keep in mind that when calling the function, the file should always be deleted using unlink().

On the front-end, the following files are required for the function to work:

require_once ABSPATH . 'wp-admin/includes/file.php';

Returns

String|WP_Error. The name of the file (absolute path to the file on the server) if the file is successfully uploaded. WP_Error object in case of an error.

Usage

download_url( $url, $timeout );
$url(string) (required)
URL of the file to be uploaded to the temporary directory.
$timeout(number)
Time after which the function will stop uploading the file.
Default: 300
$signature_verification(true/false) (WP 5.2)

Whether to verify the signature of the uploaded file.

true means that the signature should be verified only if the file is uploaded from hosts wordpress.org, downloads.wordpress.org, s.w.org. The list of hosts can be extended via the hook wp_signature_hosts. The signature is obtained from the HTTP header x-content-signature or from a special file with the extension .sig. After obtaining, the content of the uploaded file is compared with the signature of the file using the function verify_file_signature().
Default: false

Examples

0

#1 Example of loading a file into a temporary PHP directory

In this example, we'll upload the WordPress logo and check the upload for errors. We'll also delete the temporary file on the server after processing.

// If the function it's not available, require it.
require_once ABSPATH . 'wp-admin/includes/file.php';

$sourse_url = 'https://s.w.org/style/images/wp-header-logo.png';
$dest_path = wp_upload_dir()['basedir'] . '/wp-header-logo.png'; // wp-content/uploads/myfile.ext

$tmp_file = download_url( $sourse_url );

if( is_wp_error( $tmp_file ) ){
	echo $tmp_file->get_error_message();
}
else {
	// Copies the file to the final destination and deletes temporary file.
	copy( $tmp_file, $dest_path );
	echo "Copied to: $dest_path";
}

// delete the temporary file
@ unlink( $tmp_file );

Changelog

Since 2.5.0 Introduced.
Since 5.2.0 Signature Verification with SoftFail was added.
Since 5.9.0 Support for Content-Disposition filename was added.

download_url() code WP 6.9.1

function download_url( $url, $timeout = 300, $signature_verification = false ) {
	// WARNING: The file is not automatically deleted, the script must delete or move the file.
	if ( ! $url ) {
		return new WP_Error( 'http_no_url', __( 'No URL Provided.' ) );
	}

	$url_path     = parse_url( $url, PHP_URL_PATH );
	$url_filename = '';
	if ( is_string( $url_path ) && '' !== $url_path ) {
		$url_filename = basename( $url_path );
	}

	$tmpfname = wp_tempnam( $url_filename );
	if ( ! $tmpfname ) {
		return new WP_Error( 'http_no_file', __( 'Could not create temporary file.' ) );
	}

	$response = wp_safe_remote_get(
		$url,
		array(
			'timeout'  => $timeout,
			'stream'   => true,
			'filename' => $tmpfname,
		)
	);

	if ( is_wp_error( $response ) ) {
		unlink( $tmpfname );
		return $response;
	}

	$response_code = wp_remote_retrieve_response_code( $response );

	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()`.
			 *
			 * @since 5.1.0
			 *
			 * @see 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 );
			fclose( $tmpf );
		}

		unlink( $tmpfname );

		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 ) );
		} else {
			$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 );
			}
		}
	}

	$mime_type = wp_remote_retrieve_header( $response, 'content-type' );
	if ( $mime_type && 'tmp' === pathinfo( $tmpfname, PATHINFO_EXTENSION ) ) {
		$valid_mime_types = array_flip( get_allowed_mime_types() );
		if ( ! empty( $valid_mime_types[ $mime_type ] ) ) {
			$extensions     = explode( '|', $valid_mime_types[ $mime_type ] );
			$new_image_name = substr( $tmpfname, 0, -4 ) . ".{$extensions[0]}";
			if ( 0 === validate_file( $new_image_name ) ) {
				if ( rename( $tmpfname, $new_image_name ) ) {
					$tmpfname = $new_image_name;
				}

				if ( ( $tmpfname !== $new_image_name ) && file_exists( $new_image_name ) ) {
					unlink( $new_image_name );
				}
			}
		}
	}

	$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;
		}
	}

	// 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.
		 *
		 * @since 5.2.0
		 *
		 * @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' );

		if ( ! $signature ) {
			/*
			 * Retrieve signatures from a file if the header wasn't included.
			 * WordPress.org stores signatures at $package_url.sig.
			 */

			$signature_url = false;

			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.
			 *
			 * @since 5.2.0
			 *
			 * @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 );

			if ( $signature_url ) {
				$signature_request = wp_safe_remote_get(
					$signature_url,
					array(
						'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 ) );
				}
			}
		}

		// Perform the checks.
		$signature_verification = verify_file_signature( $tmpfname, $signature, $url_filename );
	}

	if ( is_wp_error( $signature_verification ) ) {
		if (
			/**
			 * Filters whether Signature Verification failures should be allowed to soft fail.
			 *
			 * WARNING: This may be removed from a future release.
			 *
			 * @since 5.2.0
			 *
			 * @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' );
		} else {
			// Hard-fail.
			unlink( $tmpfname );
		}

		return $signature_verification;
	}

	return $tmpfname;
}