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 aurlarg (string,formaturi, sanitized withsanitize_url), alongside the existinggenerate_sub_sizesandconvert_formatclient-side media arguments.create_item()routes any request that supplies aurlthrough a newcreate_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_filescapability; - 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
Locationheader pointing at the new attachment.
- requires the
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:
- Enable client-side media processing (so the editor is cross-origin isolated).
- Insert an Image block and paste a URL to an externally-hosted image.
- Select the block and click "Upload to Media Library" — the image is added to the library and the block updates to the local copy.
- Alternatively, add an external image and open the pre-publish panel; the "External media" upload now succeeds.
References
- Core PR: https://github.com/WordPress/wordpress-develop/pull/12268
- Gutenberg PR: https://github.com/WordPress/gutenberg/pull/79409
- Gutenberg issue: https://github.com/WordPress/gutenberg/issues/79407
![(please configure the [header_logo] section in trac.ini)](/chrome/site/your_project_logo.png)
Duplicate of #65517.
Closing as a duplicate :)