Index: src/wp-admin/includes/file.php
--- src/wp-admin/includes/file.php
+++ src/wp-admin/includes/file.php
@@ -1182,6 +1182,20 @@
 		return new WP_Error( 'http_404', trim( wp_remote_retrieve_response_message( $response ) ), $data );
 	}

+	$content_disposition = wp_remote_retrieve_header( $response, 'content-disposition' );
+
+	if ( preg_match( '/filename\*?=("?)([^ ]+)\1/', $content_disposition, $matches ) ) {
+		$tmpfname_disposition = wp_tempnam( $matches[2] );
+
+		if ( $tmpfname_disposition && 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' );

 	if ( $content_md5 ) {
Index: tests/phpunit/tests/admin/includesFile.php
--- tests/phpunit/tests/admin/includesFile.php
+++ tests/phpunit/tests/admin/includesFile.php
@@ -79,6 +79,74 @@
 	}

 	/**
+	 * @ticket 38231
+	 * @dataProvider provider_for_download_url_filename_from_content_disposition_header
+	 */
+	public function test_download_url_filename_from_content_disposition_header( $filter ) {
+		add_filter( 'pre_http_request', array( $this, $filter ), 10, 3 );
+
+		$filename = download_url( 'url_with_content_disposition_header' );
+		$this->assertContains( 'filename-from-content-disposition-header', $filename );
+		$this->assertFileExists( $filename );
+		$this->unlink( $filename );
+
+		remove_filter( 'pre_http_request', array( $this, $filter ) );
+	}
+
+	public function provider_for_download_url_filename_from_content_disposition_header() {
+		return array(
+			array( '_fake_download_url_with_content_disposition_header' ),
+			array( '_fake_download_url_with_content_disposition_header_without_quotes' ),
+			array( '_fake_download_url_with_content_disposition_header_with_asterisk' ),
+			array( '_fake_download_url_with_content_disposition_header_with_path_traversal' ),
+		);
+	}
+
+	public function _fake_download_url_with_content_disposition_header( $response, $args, $url ) {
+		return array(
+			'response' => array(
+				'code' => 200,
+			),
+			'headers'  => array(
+				'Content-Disposition' => 'filename="filename-from-content-disposition-header.txt"',
+			),
+		);
+	}
+
+	public function _fake_download_url_with_content_disposition_header_without_quotes( $response, $args, $url ) {
+		return array(
+			'response' => array(
+				'code' => 200,
+			),
+			'headers'  => array(
+				'Content-Disposition' => 'filename=filename-from-content-disposition-header.txt',
+			),
+		);
+	}
+
+	public function _fake_download_url_with_content_disposition_header_with_asterisk( $response, $args, $url ) {
+		return array(
+			'response' => array(
+				'code' => 200,
+			),
+			'headers'  => array(
+				'Content-Disposition' => 'filename*="filename-from-content-disposition-header.txt"',
+			),
+		);
+	}
+
+	public function _fake_download_url_with_content_disposition_header_with_path_traversal( $response, $args, $url ) {
+		return array(
+			'response' => array(
+				'code' => 200,
+			),
+			'headers'  => array(
+				'Content-Disposition' => 'filename="../../filename-from-content-disposition-header.txt"',
+			),
+		);
+	}
+
+	/**
 	 * Verify that a WP_Error object is returned when invalid input is passed as the `$url` parameter.
 	 *
 	 * @covers ::download_url
