-
Notifications
You must be signed in to change notification settings - Fork 3.3k
REST API: Add dimension validation to sideload endpoint #11100
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: trunk
Are you sure you want to change the base?
Changes from all commits
0229c11
3832e25
0dcab83
2fe2162
133ec0f
a868b57
b191ba8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -70,6 +70,8 @@ public function register_routes() { | |
| $valid_image_sizes[] = 'original'; | ||
| // Used for PDF thumbnails. | ||
| $valid_image_sizes[] = 'full'; | ||
| // Client-side big image threshold: sideload the scaled version. | ||
| $valid_image_sizes[] = 'scaled'; | ||
|
|
||
| register_rest_route( | ||
| $this->namespace, | ||
|
|
@@ -1966,6 +1968,114 @@ 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 ) { | ||
| // '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: Expected width, 2: expected height, 3: actual width, 4: actual height. */ | ||
| __( 'Uploaded image dimensions (%3$dx%4$d) do not match original image dimensions (%1$dx%2$d).' ), | ||
| $expected_width, | ||
| $expected_height, | ||
| $width, | ||
| $height | ||
| ), | ||
| array( 'status' => 400 ) | ||
| ); | ||
| } | ||
| } | ||
| return true; | ||
| } | ||
|
|
||
| // 'full' size (PDF thumbnails) and 'scaled': dimensions must be positive. | ||
| if ( 'full' === $image_size || 'scaled' === $image_size ) { | ||
| if ( $width <= 0 || $height <= 0 ) { | ||
| return new WP_Error( | ||
| 'rest_upload_invalid_dimensions', | ||
| __( 'Uploaded image must have positive dimensions.' ), | ||
| array( 'status' => 400 ) | ||
| ); | ||
| } | ||
| return true; | ||
| } | ||
|
Comment on lines
+2008
to
+2018
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there any valid case where negative dimensions are actually allowed? I see that empty could be allowable for SVGs, but I can't see any case where a negative integer is a valid dimension. |
||
|
|
||
| // 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']; | ||
|
|
||
| // Dimensions must be positive. | ||
| if ( $width <= 0 || $height <= 0 ) { | ||
| return new WP_Error( | ||
| 'rest_upload_invalid_dimensions', | ||
| __( 'Uploaded image must have positive dimensions.' ), | ||
| array( 'status' => 400 ) | ||
| ); | ||
|
Comment on lines
+2035
to
+2041
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. see above, you're checking this here, if it is a valid case for having 2 checks, maybe this could be refactored into a function that performs the check, so you don't repeat yourself? |
||
| } | ||
|
|
||
| // 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 +2155,18 @@ public function sideload_item( WP_REST_Request $request ) { | |
|
|
||
| $image_size = $request['image_size']; | ||
|
|
||
| $size = wp_getimagesize( $path ); | ||
|
|
||
| // Validate dimensions match expected size. | ||
| if ( $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 +2175,30 @@ 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' => 400 ) | ||
| ); | ||
| } | ||
|
|
||
| $metadata['original_image'] = wp_basename( $current_file ); | ||
|
|
||
| // Update the attached file to point to the scaled version. | ||
| update_attached_file( $attachment_id, $path ); | ||
|
|
||
| $metadata['width'] = $size ? $size[0] : 0; | ||
| $metadata['height'] = $size ? $size[1] : 0; | ||
| $metadata['filesize'] = wp_filesize( $path ); | ||
| $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, | ||
|
|
@@ -2123,7 +2264,7 @@ private static function filter_wp_unique_filename( $filename, $dir, $number, $at | |
| } | ||
|
|
||
| $matches = array(); | ||
| if ( preg_match( '/(.*)(-\d+x\d+)-' . $number . '$/', $name, $matches ) ) { | ||
| 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; | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any deeper reasoning why you chose to switch order in the printf template over the order of how they are in the
sprintf()here?In my eyes it would be simpler to have them (by default) in the same order as they are displayed, as this would make things easier to read and reduce cognitive load.