Skip to content
Open
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
0229c11
REST API: Add 'scaled' to sideload route image_size enum
adamsilverstein Feb 23, 2026
3832e25
Merge branch 'trunk' into add-scaled-to-sideload-route
adamsilverstein Feb 26, 2026
0dcab83
REST API: Update auto-generated JS fixture for sideload route.
adamsilverstein Feb 26, 2026
2fe2162
Tests: Add unit tests for scaled image sideloading via REST API.
adamsilverstein Feb 26, 2026
133ec0f
REST API: Validate get_attached_file() in sideload
adamsilverstein Feb 26, 2026
a868b57
Tests: Fix expected error code in sideload auth test
adamsilverstein Feb 26, 2026
e5baa99
fix ticket number for test annotations
adamsilverstein Mar 2, 2026
5405e38
Merge branch 'trunk' into add-scaled-to-sideload-route
adamsilverstein Mar 2, 2026
02a9794
Merge branch 'trunk' into add-scaled-to-sideload-route
adamsilverstein Mar 3, 2026
458d94c
Add value assertion for metadata file key
adamsilverstein Mar 3, 2026
978ac79
Extract image_size key into variable
adamsilverstein Mar 3, 2026
eeca10e
Add test for scaled filename numeric suffix on conflict
adamsilverstein Mar 3, 2026
ae029f1
Cast $number to int in regex for safety
adamsilverstein Mar 3, 2026
a484e07
Apply suggestion from @westonruter
adamsilverstein Mar 3, 2026
fbe7458
Apply suggestion from @westonruter
adamsilverstein Mar 3, 2026
b30edb6
Handle update_attached_file failure in sideload
adamsilverstein Mar 3, 2026
56c05e4
Use is_int check instead of empty for $number
adamsilverstein Mar 3, 2026
d558ceb
Validate scaled image before updating attached file
adamsilverstein Mar 3, 2026
851f14a
Improve regex grouping in filter_wp_unique_filename.
adamsilverstein Mar 3, 2026
6a95756
REST API: Add dimension validation to sideload endpoint.
adamsilverstein Mar 1, 2026
2cb2454
REST API: Refactor dimension validation in sideload endpoint.
adamsilverstein Mar 2, 2026
5474fcd
Update src/wp-includes/rest-api/endpoints/class-wp-rest-attachments-c…
adamsilverstein Mar 3, 2026
394bec7
Merge branch 'trunk' into add-dimension-validation-to-sideload
adamsilverstein Mar 3, 2026
0cec0b0
revert unrelated
adamsilverstein Mar 3, 2026
2566f7c
revert dupicate change
adamsilverstein Mar 3, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
Expand Down Expand Up @@ -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 ) {
Expand All @@ -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,
Expand Down Expand Up @@ -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;
}

Expand All @@ -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;
}
Expand Down
Loading