@@ -2166,6 +2166,123 @@ public function sideload_item_permissions_check( $request ) {
21662166 return $ this ->edit_media_item_permissions_check ( $ request );
21672167 }
21682168
2169+ /**
2170+ * Validates that uploaded image dimensions are appropriate for the specified image size.
2171+ *
2172+ * @since 7.1.0
2173+ *
2174+ * @param int $width Uploaded image width.
2175+ * @param int $height Uploaded image height.
2176+ * @param string $image_size The target image size name.
2177+ * @param int $attachment_id The attachment ID.
2178+ * @return true|WP_Error True if valid, WP_Error if invalid.
2179+ */
2180+ private function validate_image_dimensions ( int $ width , int $ height , string $ image_size , int $ attachment_id ) {
2181+ // All image sizes require positive dimensions.
2182+ if ( $ width <= 0 || $ height <= 0 ) {
2183+ return new WP_Error (
2184+ 'rest_upload_invalid_dimensions ' ,
2185+ __ ( 'Uploaded image must have positive dimensions. ' ),
2186+ array ( 'status ' => 400 )
2187+ );
2188+ }
2189+
2190+ // 'original' size: should match original attachment dimensions.
2191+ if ( 'original ' === $ image_size ) {
2192+ $ metadata = wp_get_attachment_metadata ( $ attachment_id , true );
2193+ if ( is_array ( $ metadata ) && isset ( $ metadata ['width ' ], $ metadata ['height ' ] ) ) {
2194+ $ expected_width = (int ) $ metadata ['width ' ];
2195+ $ expected_height = (int ) $ metadata ['height ' ];
2196+
2197+ if ( $ width !== $ expected_width || $ height !== $ expected_height ) {
2198+ return new WP_Error (
2199+ 'rest_upload_dimension_mismatch ' ,
2200+ sprintf (
2201+ /* translators: 1: Actual width, 2: actual height, 3: expected width, 4: expected height. */
2202+ __ ( 'Uploaded image dimensions (%1$dx%2$d) do not match original image dimensions (%3$dx%4$d). ' ),
2203+ $ width ,
2204+ $ height ,
2205+ $ expected_width ,
2206+ $ expected_height
2207+ ),
2208+ array ( 'status ' => 400 )
2209+ );
2210+ }
2211+ }
2212+ return true ;
2213+ }
2214+
2215+ // 'full' size (PDF thumbnails) and 'scaled': no further constraints.
2216+ if ( in_array ( $ image_size , array ( 'full ' , 'scaled ' ), true ) ) {
2217+ return true ;
2218+ }
2219+
2220+ // Regular image sizes: validate against registered size constraints.
2221+ $ registered_sizes = wp_get_registered_image_subsizes ();
2222+
2223+ if ( ! isset ( $ registered_sizes [ $ image_size ] ) ) {
2224+ return new WP_Error (
2225+ 'rest_upload_unknown_size ' ,
2226+ __ ( 'Unknown image size. ' ),
2227+ array ( 'status ' => 400 )
2228+ );
2229+ }
2230+
2231+ $ size_data = $ registered_sizes [ $ image_size ];
2232+ $ max_width = (int ) $ size_data ['width ' ];
2233+ $ max_height = (int ) $ size_data ['height ' ];
2234+
2235+ // Validate dimensions don't exceed the registered size maximums.
2236+ // Allow 1px tolerance for rounding differences.
2237+ $ tolerance = 1 ;
2238+
2239+ if ( $ this ->dimension_exceeds_max ( $ width , $ max_width , $ tolerance ) ) {
2240+ return new WP_Error (
2241+ 'rest_upload_dimension_mismatch ' ,
2242+ sprintf (
2243+ /* translators: 1: Image size name, 2: maximum width, 3: actual width. */
2244+ __ ( 'Uploaded image width (%3$d) exceeds maximum for "%1$s" size (%2$d). ' ),
2245+ $ image_size ,
2246+ $ max_width ,
2247+ $ width
2248+ ),
2249+ array ( 'status ' => 400 )
2250+ );
2251+ }
2252+
2253+ if ( $ this ->dimension_exceeds_max ( $ height , $ max_height , $ tolerance ) ) {
2254+ return new WP_Error (
2255+ 'rest_upload_dimension_mismatch ' ,
2256+ sprintf (
2257+ /* translators: 1: Image size name, 2: maximum height, 3: actual height. */
2258+ __ ( 'Uploaded image height (%3$d) exceeds maximum for "%1$s" size (%2$d). ' ),
2259+ $ image_size ,
2260+ $ max_height ,
2261+ $ height
2262+ ),
2263+ array ( 'status ' => 400 )
2264+ );
2265+ }
2266+
2267+ return true ;
2268+ }
2269+
2270+ /**
2271+ * Checks whether a dimension exceeds the maximum allowed value.
2272+ *
2273+ * A maximum of zero means the dimension is unconstrained.
2274+ *
2275+ * @since 7.1.0
2276+ *
2277+ * @param int $value The actual dimension in pixels.
2278+ * @param int $max The maximum allowed dimension in pixels. Zero means no constraint.
2279+ * @param int $tolerance Pixel tolerance allowed for rounding differences.
2280+ * @return bool True if the value exceeds the maximum plus tolerance.
2281+ */
2282+ private function dimension_exceeds_max ( int $ value , int $ max , int $ tolerance ): bool {
2283+ return $ max > 0 && $ value > $ max + $ tolerance ;
2284+ }
2285+
21692286 /**
21702287 * Side-loads a media file without creating a new attachment.
21712288 *
@@ -2243,8 +2360,49 @@ public function sideload_item( WP_REST_Request $request ) {
22432360 $ type = $ file ['type ' ];
22442361 $ path = $ file ['file ' ];
22452362
2363+ /** @var non-empty-string $image_size */
22462364 $ image_size = $ request ['image_size ' ];
22472365
2366+ /*
2367+ * Validate raster sub-sizes before storing them. Source-format companion
2368+ * originals (e.g. a HEIC or JXL kept next to its JPEG derivative) are
2369+ * exempt: their dimensions are neither validated nor recorded, and the
2370+ * source format may not be readable by wp_getimagesize() at all.
2371+ */
2372+ if ( self ::IMAGE_SIZE_SOURCE_ORIGINAL !== $ image_size ) {
2373+ /*
2374+ * Read the dimensions up front. A file whose dimensions cannot be
2375+ * read is corrupted or an unsupported format and must be rejected
2376+ * rather than silently stored with zero dimensions.
2377+ */
2378+ $ size = wp_getimagesize ( $ path );
2379+
2380+ if ( ! $ size ) {
2381+ // Clean up the uploaded file.
2382+ wp_delete_file ( $ path );
2383+ return new WP_Error (
2384+ 'rest_upload_invalid_image ' ,
2385+ __ ( 'Could not read image dimensions. The file may be corrupted or an unsupported format. ' ),
2386+ array ( 'status ' => 400 )
2387+ );
2388+ }
2389+
2390+ /*
2391+ * Validate the dimensions match the expected size. An array
2392+ * $image_size represents multiple registered sizes sharing a single
2393+ * file; those are handled by the per-size branch below, so only
2394+ * scalar sizes are validated here.
2395+ */
2396+ if ( ! is_array ( $ image_size ) ) {
2397+ $ validation = $ this ->validate_image_dimensions ( $ size [0 ], $ size [1 ], $ image_size , $ attachment_id );
2398+ if ( is_wp_error ( $ validation ) ) {
2399+ // Clean up the uploaded file.
2400+ wp_delete_file ( $ path );
2401+ return $ validation ;
2402+ }
2403+ }
2404+ }
2405+
22482406 // Build sub-size data to return to the client.
22492407 // The client accumulates these and sends them all to the finalize
22502408 // endpoint, which writes the metadata in a single operation. This
0 commit comments