From 483513ecfc0305bd32d3225ca8821dfe17e9daf1 Mon Sep 17 00:00:00 2001 From: Jonathan Desrosiers <359867+desrosj@users.noreply.github.com> Date: Mon, 9 Nov 2026 14:57:12 -0500 Subject: [PATCH] Tests: Demonstrate multisite rejecting sideloaded sub-sizes by file type. On the classic path, the image editor writes sub-sizes to disk directly, so a network that does not allow WebP uploads still gets WebP sub-sizes when `image_editor_output_format` asks for them. With client-side media processing, the browser transcodes each sub-size to that format and sideloads it, and the sideload is checked against the network's allowed upload file types, so it is rejected with "Sorry, you are not allowed to upload this file type." Networks created before WordPress 5.8 have no `webp` in `upload_filetypes`, and upgrades never add it. The first test is the classic baseline and passes. The second fails until the sideload endpoint allows the attachment's own output format. Co-Authored-By: Claude Opus 5.5 --- .../rest-api/rest-attachments-controller.php | 98 +++++++++++++++++++ 1 file changed, 98 insertions(+) diff --git a/tests/phpunit/tests/rest-api/rest-attachments-controller.php b/tests/phpunit/tests/rest-api/rest-attachments-controller.php index a8499a34ed274..3869f9171cc22 100644 --- a/tests/phpunit/tests/rest-api/rest-attachments-controller.php +++ b/tests/phpunit/tests/rest-api/rest-attachments-controller.php @@ -2854,6 +2854,104 @@ public function test_create_item_with_data_exceeds_multisite_site_upload_space() $this->assertErrorResponse( 'rest_upload_limited_space', $response, 400 ); } + /** + * Tests that sub-sizes generated on the server are not limited to the + * network's allowed upload file types. + * + * The image editor writes sub-sizes straight to disk, so a network that + * does not allow WebP uploads still gets WebP sub-sizes when an + * `image_editor_output_format` filter asks for them. This is the + * baseline for test_sideload_sub_size_in_output_format_not_allowed_by_network(). + * + * @group multisite + * @group ms-required + * @covers ::wp_generate_attachment_metadata + */ + public function test_server_sub_sizes_in_output_format_not_allowed_by_network(): void { + if ( ! wp_image_editor_supports( array( 'mime_type' => 'image/webp' ) ) ) { + $this->markTestSkipped( 'This test requires an image editor that can save WebP.' ); + } + + /* + * Networks created before WordPress 5.8 have no `webp` in this list, + * and upgrades never add it. This is also the list + * check_upload_mimes() falls back to. + */ + update_site_option( 'upload_filetypes', 'jpg jpeg png gif' ); + $this->assertNotContains( 'image/webp', get_allowed_mime_types(), 'The network should not allow WebP uploads.' ); + + add_filter( 'image_editor_output_format', array( $this, 'filter_output_format_jpeg_to_webp' ) ); + + $attachment_id = self::factory()->attachment->create_upload_object( DIR_TESTDATA . '/images/canola.jpg' ); + $metadata = wp_get_attachment_metadata( $attachment_id ); + + $this->assertArrayHasKey( 'thumbnail', $metadata['sizes'], 'The server should generate a thumbnail.' ); + $this->assertSame( 'image/webp', $metadata['sizes']['thumbnail']['mime-type'], 'The thumbnail should be a WebP.' ); + } + + /** + * Tests that a client-generated sub-size can be sideloaded in an output + * format the network does not allow for upload. + * + * With client-side media processing, the browser transcodes each sub-size + * to the `image_output_format` returned by the upload, then sideloads it. + * The server generates the same sub-size itself on the classic path (see + * test_server_sub_sizes_in_output_format_not_allowed_by_network()), so the + * sideload should not be rejected for its file type. + * + * @group multisite + * @group ms-required + * @covers WP_REST_Attachments_Controller::sideload_item + */ + public function test_sideload_sub_size_in_output_format_not_allowed_by_network(): void { + $this->enable_client_side_media_processing(); + + wp_set_current_user( self::$author_id ); + + // See test_server_sub_sizes_in_output_format_not_allowed_by_network(). + update_site_option( 'upload_filetypes', 'jpg jpeg png gif' ); + $this->assertNotContains( 'image/webp', get_allowed_mime_types(), 'The network should not allow WebP uploads.' ); + + add_filter( 'image_editor_output_format', array( $this, 'filter_output_format_jpeg_to_webp' ) ); + + $request = new WP_REST_Request( 'POST', '/wp/v2/media' ); + $request->set_header( 'Content-Type', 'image/jpeg' ); + $request->set_header( 'Content-Disposition', 'attachment; filename=canola.jpg' ); + $request->set_param( 'generate_sub_sizes', false ); + $request->set_body( file_get_contents( self::$test_file ) ); + $response = rest_get_server()->dispatch( $request ); + + $this->assertSame( 201, $response->get_status(), 'The original upload should succeed.' ); + + $attachment_id = $response->get_data()['id']; + + // A WebP thumbnail, as the client would sideload it. + $request = new WP_REST_Request( 'POST', "/wp/v2/media/{$attachment_id}/sideload" ); + $request->set_header( 'Content-Type', 'image/webp' ); + $request->set_header( 'Content-Disposition', 'attachment; filename=canola-50x50.webp' ); + $request->set_param( 'image_size', 'thumbnail' ); + $request->set_param( 'convert_format', false ); + $request->set_body( file_get_contents( DIR_TESTDATA . '/images/test-image.webp' ) ); + $response = rest_get_server()->dispatch( $request ); + + $this->assertSame( + 200, + $response->get_status(), + 'Sideloading a WebP sub-size should succeed. Response: ' . wp_json_encode( $response->get_data() ) + ); + } + + /** + * Maps JPEG output to WebP, as image format plugins do. + * + * @param array $formats Output formats keyed by source mime type. + * @return array The filtered output formats. + */ + public function filter_output_format_jpeg_to_webp( $formats ) { + $formats['image/jpeg'] = 'image/webp'; + return $formats; + } + /** * Ensure the `rest_after_insert_attachment` and `rest_insert_attachment` hooks only fire * once when attachments are created.