diff --git a/src/wp-includes/rest-api/endpoints/class-wp-rest-attachments-controller.php b/src/wp-includes/rest-api/endpoints/class-wp-rest-attachments-controller.php index 1a135ba546779..e91eb4c694b4b 100644 --- a/src/wp-includes/rest-api/endpoints/class-wp-rest-attachments-controller.php +++ b/src/wp-includes/rest-api/endpoints/class-wp-rest-attachments-controller.php @@ -1966,6 +1966,107 @@ public function sideload_item_permissions_check( $request ) { return $this->edit_media_item_permissions_check( $request ); } + /** + * Validates that uploaded image dimensions are appropriate for the specified image size. + * + * @since 7.0.0 + * + * @param int $width Uploaded image width. + * @param int $height Uploaded image height. + * @param string $image_size The target image size name. + * @param int $attachment_id The attachment ID. + * @return true|WP_Error True if valid, WP_Error if invalid. + */ + private function validate_image_dimensions( int $width, int $height, string $image_size, int $attachment_id ) { + // All image sizes require positive dimensions. + if ( $width <= 0 || $height <= 0 ) { + return new WP_Error( + 'rest_upload_invalid_dimensions', + __( 'Uploaded image must have positive dimensions.' ), + array( 'status' => 400 ) + ); + } + + // 'original' size: should match original attachment dimensions. + if ( 'original' === $image_size ) { + $metadata = wp_get_attachment_metadata( $attachment_id, true ); + if ( is_array( $metadata ) && isset( $metadata['width'], $metadata['height'] ) ) { + $expected_width = (int) $metadata['width']; + $expected_height = (int) $metadata['height']; + + if ( $width !== $expected_width || $height !== $expected_height ) { + return new WP_Error( + 'rest_upload_dimension_mismatch', + sprintf( + /* translators: 1: Actual width, 2: actual height, 3: expected width, 4: expected height. */ + __( 'Uploaded image dimensions (%1$dx%2$d) do not match original image dimensions (%3$dx%4$d).' ), + $width, + $height, + $expected_width, + $expected_height + ), + array( 'status' => 400 ) + ); + } + } + return true; + } + + // 'full' size (PDF thumbnails) and 'scaled': no further constraints. + if ( 'full' === $image_size || 'scaled' === $image_size ) { + return true; + } + + // Regular image sizes: validate against registered size constraints. + $registered_sizes = wp_get_registered_image_subsizes(); + + if ( ! isset( $registered_sizes[ $image_size ] ) ) { + return new WP_Error( + 'rest_upload_unknown_size', + __( 'Unknown image size.' ), + array( 'status' => 400 ) + ); + } + + $size_data = $registered_sizes[ $image_size ]; + $max_width = (int) $size_data['width']; + $max_height = (int) $size_data['height']; + + // Validate dimensions don't exceed the registered size maximums. + // Allow 1px tolerance for rounding differences. + $tolerance = 1; + + if ( $max_width > 0 && $width > $max_width + $tolerance ) { + return new WP_Error( + 'rest_upload_dimension_mismatch', + sprintf( + /* translators: 1: Image size name, 2: maximum width, 3: actual width. */ + __( 'Uploaded image width (%3$d) exceeds maximum for "%1$s" size (%2$d).' ), + $image_size, + $max_width, + $width + ), + array( 'status' => 400 ) + ); + } + + if ( $max_height > 0 && $height > $max_height + $tolerance ) { + return new WP_Error( + 'rest_upload_dimension_mismatch', + sprintf( + /* translators: 1: Image size name, 2: maximum height, 3: actual height. */ + __( 'Uploaded image height (%3$d) exceeds maximum for "%1$s" size (%2$d).' ), + $image_size, + $max_height, + $height + ), + array( 'status' => 400 ) + ); + } + + return true; + } + /** * Side-loads a media file without creating a new attachment. * @@ -2045,6 +2146,18 @@ public function sideload_item( WP_REST_Request $request ) { $image_size = $request['image_size']; + $size = wp_getimagesize( $path ); + + // Validate dimensions match expected size. + if ( is_array( $size ) ) { + $validation = $this->validate_image_dimensions( $size[0], $size[1], $image_size, $attachment_id ); + if ( is_wp_error( $validation ) ) { + // Clean up the uploaded file. + wp_delete_file( $path ); + return $validation; + } + } + $metadata = wp_get_attachment_metadata( $attachment_id, true ); if ( ! $metadata ) { @@ -2053,11 +2166,51 @@ public function sideload_item( WP_REST_Request $request ) { if ( 'original' === $image_size ) { $metadata['original_image'] = wp_basename( $path ); + } elseif ( 'scaled' === $image_size ) { + // The current attached file is the original; record it as original_image. + $current_file = get_attached_file( $attachment_id, true ); + + if ( ! $current_file ) { + return new WP_Error( + 'rest_sideload_no_attached_file', + __( 'Unable to retrieve the attached file for this attachment.' ), + array( 'status' => 404 ) + ); + } + + $metadata['original_image'] = wp_basename( $current_file ); + + // Validate the scaled image before updating the attached file. + $size = wp_getimagesize( $path ); + $filesize = wp_filesize( $path ); + + if ( ! $size || ! $filesize ) { + return new WP_Error( + 'rest_sideload_invalid_image', + __( 'Unable to read the scaled image file.' ), + array( 'status' => 500 ) + ); + } + + // Update the attached file to point to the scaled version. + if ( + get_post_meta( $attachment_id, '_wp_attached_file', true ) !== $path && + ! update_attached_file( $attachment_id, $path ) + ) { + return new WP_Error( + 'rest_sideload_update_attached_file_failed', + __( 'Unable to update the attached file for this attachment.' ), + array( 'status' => 500 ) + ); + } + + $metadata['width'] = $size[0]; + $metadata['height'] = $size[1]; + $metadata['filesize'] = $filesize; + $metadata['file'] = _wp_relative_upload_path( $path ); } else { $metadata['sizes'] = $metadata['sizes'] ?? array(); - $size = wp_getimagesize( $path ); - $metadata['sizes'][ $image_size ] = array( 'width' => $size ? $size[0] : 0, 'height' => $size ? $size[1] : 0, @@ -2110,7 +2263,7 @@ public function sideload_item( WP_REST_Request $request ) { * @return string Filtered file name. */ private static function filter_wp_unique_filename( $filename, $dir, $number, $attachment_filename ) { - if ( empty( $number ) || ! $attachment_filename ) { + if ( ! is_int( $number ) || ! $attachment_filename ) { return $filename; } @@ -2123,8 +2276,8 @@ private static function filter_wp_unique_filename( $filename, $dir, $number, $at } $matches = array(); - if ( preg_match( '/(.*)(-\d+x\d+)-' . $number . '$/', $name, $matches ) ) { - $filename_without_suffix = $matches[1] . $matches[2] . ".$ext"; + if ( preg_match( '/(.*)-(\d+x\d+|scaled)-' . $number . '$/', $name, $matches ) ) { + $filename_without_suffix = $matches[1] . '-' . $matches[2] . ".$ext"; if ( $matches[1] === $orig_name && ! file_exists( "$dir/$filename_without_suffix" ) ) { return $filename_without_suffix; }