Make WordPress Core

Changeset 59902


Ignore:
Timestamp:
03/03/2025 12:17:08 AM (8 weeks ago)
Author:
joedolson
Message:

Media: Allow uploading images from URLs without extensions.

Enable download_url() to fetch and verify file types if the URL does not contain a file extension. This allows URL downloads to handle media endpoints like istockphoto.com that use file IDs and formatting arguments to deliver images.

Props masteradhoc, mitogh, joedolson, hellofromTonya, antpb, audrasjb, navi161, dmsnell.
Fixes #54738.

Location:
trunk
Files:
2 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-admin/includes/file.php

    r59789 r59902  
    12421242    }
    12431243
     1244    $mime_type = wp_remote_retrieve_header( $response, 'content-type' );
     1245    if ( $mime_type && 'tmp' === pathinfo( $tmpfname, PATHINFO_EXTENSION ) ) {
     1246        $valid_mime_types = array_flip( get_allowed_mime_types() );
     1247        if ( ! empty( $valid_mime_types[ $mime_type ] ) ) {
     1248            $extensions     = explode( '|', $valid_mime_types[ $mime_type ] );
     1249            $new_image_name = substr( $tmpfname, 0, -4 ) . ".{$extensions[0]}";
     1250            if ( 0 === validate_file( $new_image_name ) ) {
     1251                if ( rename( $tmpfname, $new_image_name ) ) {
     1252                    $tmpfname = $new_image_name;
     1253                }
     1254
     1255                if ( ( $tmpfname !== $new_image_name ) && file_exists( $new_image_name ) ) {
     1256                    unlink( $new_image_name );
     1257                }
     1258            }
     1259        }
     1260    }
     1261
    12441262    $content_md5 = wp_remote_retrieve_header( $response, 'Content-MD5' );
    12451263
  • trunk/tests/phpunit/tests/admin/includesFile.php

    r59604 r59902  
    390390        return $response;
    391391    }
     392
     393    /**
     394     * Test that `download_url()` properly handles setting the file name when set using
     395     * the content type header on URLs with no file extension.
     396     *
     397     * @dataProvider data_download_url_should_use_the_content_type_header_to_set_extension_of_a_file_if_extension_was_not_determined
     398     *
     399     * @covers ::download_url
     400     * @ticket 54738
     401     *
     402     * @param string $filter A callback containing a fake Content-Type header.
     403     * @param string $ext The expected file extension to match.
     404     */
     405    public function test_download_url_should_use_the_content_type_header_to_set_extension_of_a_file_if_extension_was_not_determined( $filter, $extension ) {
     406        add_filter( 'pre_http_request', $filter );
     407
     408        $filename = download_url( 'url_with_content_type_header' );
     409        $this->assertStringEndsWith( $extension, $filename );
     410        $this->assertFileExists( $filename );
     411        $this->unlink( $filename );
     412    }
     413
     414    /**
     415     * Data provider for test_download_url_should_use_the_content_type_header_to_set_extension_of_a_file_if_extension_was_not_determined
     416     *
     417     * @see test_download_url_should_use_the_content_type_header_to_set_extension_of_a_file_if_extension_was_not_determined()
     418     * @test
     419     * @ticket 54738
     420     *
     421     * @return Generator
     422     */
     423    public function data_download_url_should_use_the_content_type_header_to_set_extension_of_a_file_if_extension_was_not_determined() {
     424        yield 'Content-Type header in the response' => array(
     425            function () {
     426                return array(
     427                    'response' => array(
     428                        'code' => 200,
     429                    ),
     430                    'headers'  => array(
     431                        'content-type' => 'image/jpeg',
     432                    ),
     433                );
     434            },
     435            '.jpg',
     436        );
     437
     438        yield 'Invalid Content-Type header' => array(
     439            function () {
     440                return array(
     441                    'response' => array(
     442                        'code' => 200,
     443                    ),
     444                    'headers'  => array(
     445                        'content-type' => '../../filename-from-content-disposition-header.txt',
     446                    ),
     447                );
     448            },
     449            '.tmp',
     450        );
     451
     452        yield 'Valid content type but not supported mime type' => array(
     453            function () {
     454                return array(
     455                    'response' => array(
     456                        'code' => 200,
     457                    ),
     458                    'headers'  => array(
     459                        'content-type' => 'image/x-xbm',
     460                    ),
     461                );
     462            },
     463            '.tmp',
     464        );
     465    }
    392466}
Note: See TracChangeset for help on using the changeset viewer.