Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 18 additions & 10 deletions src/wp-admin/includes/media.php
Original file line number Diff line number Diff line change
Expand Up @@ -1056,24 +1056,32 @@ function media_sideload_image( $file, $post_id = 0, $desc = null, $return_type =
$allowed_extensions = apply_filters( 'image_sideload_extensions', $allowed_extensions, $file );
$allowed_extensions = array_map( 'preg_quote', $allowed_extensions );

// Download file to temp location.
$tmp = download_url( $file );

// If error storing temporarily, return the error.
if ( is_wp_error( $tmp ) ) {
return $tmp;
}

// Set variables for storage, fix file filename for query strings.
preg_match( '/[^\?]+\.(' . implode( '|', $allowed_extensions ) . ')\b/i', $file, $matches );

// If the URL has no recognizable extension, fall back to the temp filename.
// download_url() may have added one based on the Content-Type header.
if ( ! $matches ) {
return new WP_Error( 'image_sideload_failed', __( 'Invalid image URL.' ) );
preg_match( '/[^\?]+\.(' . implode( '|', $allowed_extensions ) . ')\b/i', $tmp, $matches );
}

$file_array = array();
$file_array['name'] = wp_basename( $matches[0] );

// Download file to temp location.
$file_array['tmp_name'] = download_url( $file );

// If error storing temporarily, return the error.
if ( is_wp_error( $file_array['tmp_name'] ) ) {
return $file_array['tmp_name'];
if ( ! $matches ) {
@unlink( $tmp );
return new WP_Error( 'image_sideload_failed', __( 'Invalid image URL.' ) );
}

$file_array = array();
$file_array['name'] = wp_basename( $matches[0] );
$file_array['tmp_name'] = $tmp;

// Do the validation and storage stuff.
$id = media_handle_sideload( $file_array, $post_id, $desc );

Expand Down
99 changes: 99 additions & 0 deletions tests/phpunit/tests/media/mediaSideloadImage.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
<?php

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

/**
* Tests for the `media_sideload_image()` function.
*
* @group media
* @covers ::media_sideload_image
*/
class Tests_Media_MediaSideloadImage extends WP_UnitTestCase {

public function tear_down() {
$this->remove_added_uploads();
parent::tear_down();
}

/**
* Mocks an HTTP response returning a JPEG Content-Type for use with the `pre_http_request` filter.
*
* @param false|array|WP_Error $preempt Whether to preempt an HTTP request.
* @param array $args HTTP request arguments.
* @return array Mocked HTTP response.
*/
public function mock_jpeg_http_response( $preempt, $args ) {
if ( ! empty( $args['filename'] ) ) {
copy( DIR_TESTDATA . '/images/test-image.jpg', $args['filename'] );
}

return array(
'headers' => array( 'content-type' => 'image/jpeg' ),
'body' => '',
'response' => array(
'code' => 200,
'message' => 'OK',
),
'cookies' => array(),
'filename' => $args['filename'] ?? '',
);
}

/**
* Mocks an HTTP response returning an HTML Content-Type for use with the `pre_http_request` filter.
*
* @param false|array|WP_Error $preempt Whether to preempt an HTTP request.
* @param array $args HTTP request arguments.
* @return array Mocked HTTP response.
*/
public function mock_html_http_response( $preempt, $args ) {
if ( ! empty( $args['filename'] ) ) {
copy( DIR_TESTDATA . '/images/test-image.jpg', $args['filename'] );
}

return array(
'headers' => array( 'content-type' => 'text/html' ),
'body' => '',
'response' => array(
'code' => 200,
'message' => 'OK',
),
'cookies' => array(),
'filename' => $args['filename'] ?? '',
);
}

/**
* Tests that an image at a URL without a file extension is sideloaded when the server returns a valid image Content-Type.
*
* @ticket 18730
*/
public function test_sideload_extensionless_url_succeeds_when_content_type_is_valid_image() {
add_filter( 'pre_http_request', array( $this, 'mock_jpeg_http_response' ), 10, 2 );

$result = media_sideload_image( 'http://' . WP_TESTS_DOMAIN . '/photo/1280/10464566223/1/tumblr_lrum2xzkpC1r3z8e3', 0, null, 'id' );

remove_filter( 'pre_http_request', array( $this, 'mock_jpeg_http_response' ), 10 );

$this->assertNotWPError( $result );
$this->assertIsInt( $result );
$this->assertGreaterThan( 0, $result );
}

/**
* Tests that sideloading an extensionless URL fails when the server returns a non-image Content-Type.
*
* @ticket 18730
*/
public function test_sideload_extensionless_url_fails_when_content_type_is_not_image() {
add_filter( 'pre_http_request', array( $this, 'mock_html_http_response' ), 10, 2 );

$result = media_sideload_image( 'http://' . WP_TESTS_DOMAIN . '/dynamic-resource', 0, null, 'id' );

remove_filter( 'pre_http_request', array( $this, 'mock_html_http_response' ), 10 );

$this->assertWPError( $result );
$this->assertSame( 'image_sideload_failed', $result->get_error_code() );
}
}
Loading