Make WordPress Core

Opened 3 months ago

Closed 3 months ago

#65528 closed defect (bug) (duplicate)

Sideload external images on the server via a `url` REST parameter

Reported by: adamsilverstein Owned by: adamsilverstein
Priority: normal Milestone:
Component: Editor Version:
Severity: normal Keywords: has-patch needs-testing has-unit-tests
Cc: Focuses:

Description

Description

The attachments REST endpoint (POST /wp/v2/media) currently only creates an attachment from an uploaded file (the request body or a $_FILES entry). When the block editor uploads an externally-hosted image to the media library, it reads the remote image's bytes in the browser with window.fetch() and posts the resulting blob.

A browser cross-origin fetch is subject to CORS, so it fails for any host that does not send permissive headers, and the failure is silently swallowed. The "Upload to Media Library" toolbar action on an image inserted by URL, and the pre-publish "External media" panel, therefore fail for most external hosts.

This breaks entirely once the editor is cross-origin isolated, which client-side media processing requires (Document-Isolation-Policy: isolate-and-credentialless). In that mode the browser cannot read a cross-origin image's bytes at all.

The fix is to let the server fetch the URL, which is the same primitive that core's media_sideload_image() already relies on. Server-side fetching is not subject to browser CORS, so external uploads work regardless of cross-origin isolation.

Proposed change

Extend WP_REST_Attachments_Controller to accept an optional url parameter on the creatable route:

  • get_endpoint_args_for_item_schema() registers a url arg (string, format uri, sanitized with sanitize_url), alongside the existing generate_sub_sizes and convert_format client-side media arguments.
  • create_item() routes any request that supplies a url through a new create_item_from_url() method, after the existing sub-size and scaling filters have been applied, so those filters continue to govern derivative generation.
  • create_item_from_url():
    • requires the upload_files capability;
    • derives and validates a filename from the URL path before downloading anything, returning a rest_invalid_url (400) error when the URL has no usable filename (for example a query-string-only URL);
    • downloads the remote file with download_url(), which validates the host and blocks requests to private or local addresses;
    • sideloads the file with media_handle_sideload(), cleaning up the temporary file if the sideload fails;
    • returns a 201 response with a Location header pointing at the new attachment.

No existing behavior changes when no url is supplied: the normal uploaded-file path is untouched.

Testing instructions

Automated:

npm run test:php -- --filter 'create_item_from_url' --group restapi
npm run test:php -- --filter 'test_url_registered_as_creatable_arg' --group restapi

Six new tests in tests/phpunit/tests/rest-api/rest-attachments-controller.php cover: sideload without sub-sizes, attachment parenting via the post parameter, download-error propagation, rejection of a URL without a filename, the upload_files capability guard, and registration of the url argument. All pass locally (20 assertions).

Manual:

  1. Enable client-side media processing (so the editor is cross-origin isolated).
  2. Insert an Image block and paste a URL to an externally-hosted image.
  3. Select the block and click "Upload to Media Library" — the image is added to the library and the block updates to the local copy.
  4. Alternatively, add an external image and open the pre-publish panel; the "External media" upload now succeeds.

References

Change History (2)

#1 @adamsilverstein
3 months ago

  • Owner set to adamsilverstein
  • Status newassigned

#2 @adamsilverstein
3 months ago

  • Milestone Awaiting Review
  • Resolutionduplicate
  • Status assignedclosed

Duplicate of #65517.

Closing as a duplicate :)

Note: See TracTickets for help on using tickets.