mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-24 13:16:28 +00:00
fix(s3): return InvalidRange when CopySourceRange exceeds source object size (#2029)
This commit is contained in:
@@ -38,7 +38,7 @@ use rustfs_ecstore::compress::is_compressible;
|
|||||||
use rustfs_ecstore::error::{StorageError, is_err_object_not_found, is_err_version_not_found};
|
use rustfs_ecstore::error::{StorageError, is_err_object_not_found, is_err_version_not_found};
|
||||||
use rustfs_ecstore::new_object_layer_fn;
|
use rustfs_ecstore::new_object_layer_fn;
|
||||||
use rustfs_ecstore::set_disk::{MAX_PARTS_COUNT, is_valid_storage_class};
|
use rustfs_ecstore::set_disk::{MAX_PARTS_COUNT, is_valid_storage_class};
|
||||||
use rustfs_ecstore::store_api::{CompletePart, MultipartUploadResult, ObjectIO, ObjectOptions, PutObjReader};
|
use rustfs_ecstore::store_api::{CompletePart, HTTPRangeSpec, MultipartUploadResult, ObjectIO, ObjectOptions, PutObjReader};
|
||||||
use rustfs_ecstore::store_api::{MultipartOperations, ObjectOperations};
|
use rustfs_ecstore::store_api::{MultipartOperations, ObjectOperations};
|
||||||
use rustfs_filemeta::{ReplicationStatusType, ReplicationType};
|
use rustfs_filemeta::{ReplicationStatusType, ReplicationType};
|
||||||
use rustfs_rio::{CompressReader, HashReader, Reader, WarpReader};
|
use rustfs_rio::{CompressReader, HashReader, Reader, WarpReader};
|
||||||
@@ -58,6 +58,18 @@ use tokio::sync::RwLock;
|
|||||||
use tokio_util::io::StreamReader;
|
use tokio_util::io::StreamReader;
|
||||||
use tracing::{info, instrument, warn};
|
use tracing::{info, instrument, warn};
|
||||||
|
|
||||||
|
/// Returns InvalidRange error if CopySourceRange end exceeds the source object size.
|
||||||
|
/// Used by execute_upload_part_copy to reject out-of-bounds ranges per S3 spec.
|
||||||
|
fn validate_copy_source_range_not_exceeds(range_spec: &HTTPRangeSpec, object_size: i64) -> S3Result<()> {
|
||||||
|
if range_spec.end >= object_size {
|
||||||
|
return Err(S3Error::with_message(
|
||||||
|
S3ErrorCode::InvalidRange,
|
||||||
|
"The requested range is not satisfiable".to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Default)]
|
#[derive(Clone, Default)]
|
||||||
pub struct DefaultMultipartUsecase {
|
pub struct DefaultMultipartUsecase {
|
||||||
context: Option<Arc<AppContext>>,
|
context: Option<Arc<AppContext>>,
|
||||||
@@ -973,6 +985,8 @@ impl DefaultMultipartUsecase {
|
|||||||
_ => src_info.size,
|
_ => src_info.size,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
validate_copy_source_range_not_exceeds(range_spec, validation_size)?;
|
||||||
|
|
||||||
range_spec
|
range_spec
|
||||||
.get_offset_length(validation_size)
|
.get_offset_length(validation_size)
|
||||||
.map_err(|e| S3Error::with_message(S3ErrorCode::InvalidRange, e.to_string()))?
|
.map_err(|e| S3Error::with_message(S3ErrorCode::InvalidRange, e.to_string()))?
|
||||||
@@ -1217,6 +1231,44 @@ mod tests {
|
|||||||
assert_eq!(err.code(), &S3ErrorCode::InternalError);
|
assert_eq!(err.code(), &S3ErrorCode::InternalError);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_validate_copy_source_range_not_exceeds_returns_invalid_range_when_range_exceeds() {
|
||||||
|
use super::validate_copy_source_range_not_exceeds;
|
||||||
|
|
||||||
|
let range_exceeds = HTTPRangeSpec {
|
||||||
|
is_suffix_length: false,
|
||||||
|
start: 0,
|
||||||
|
end: 21,
|
||||||
|
};
|
||||||
|
let err = validate_copy_source_range_not_exceeds(&range_exceeds, 5).unwrap_err();
|
||||||
|
assert_eq!(err.code(), &S3ErrorCode::InvalidRange);
|
||||||
|
assert!(err.to_string().contains("not satisfiable"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_validate_copy_source_range_not_exceeds_ok_when_range_valid() {
|
||||||
|
use super::validate_copy_source_range_not_exceeds;
|
||||||
|
|
||||||
|
let range_valid = HTTPRangeSpec {
|
||||||
|
is_suffix_length: false,
|
||||||
|
start: 0,
|
||||||
|
end: 4,
|
||||||
|
};
|
||||||
|
assert!(validate_copy_source_range_not_exceeds(&range_valid, 5).is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_validate_copy_source_range_not_exceeds_ok_for_suffix_range() {
|
||||||
|
use super::validate_copy_source_range_not_exceeds;
|
||||||
|
|
||||||
|
let range_suffix = HTTPRangeSpec {
|
||||||
|
is_suffix_length: true,
|
||||||
|
start: -5,
|
||||||
|
end: -1,
|
||||||
|
};
|
||||||
|
assert!(validate_copy_source_range_not_exceeds(&range_suffix, 5).is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn execute_upload_part_rejects_missing_body() {
|
async fn execute_upload_part_rejects_missing_body() {
|
||||||
let input = UploadPartInput::builder()
|
let input = UploadPartInput::builder()
|
||||||
|
|||||||
@@ -34,8 +34,9 @@
|
|||||||
# - CORS: Bucket-level CORS configuration, preflight, origin matching
|
# - CORS: Bucket-level CORS configuration, preflight, origin matching
|
||||||
# - HTTP: 100-continue, multipart abort, tagging in POST Object
|
# - HTTP: 100-continue, multipart abort, tagging in POST Object
|
||||||
# - DeleteObject: Proper NoSuchBucket for deleted buckets
|
# - DeleteObject: Proper NoSuchBucket for deleted buckets
|
||||||
|
# - Multipart Copy: InvalidRange when CopySourceRange exceeds source size
|
||||||
#
|
#
|
||||||
# Total: 393 tests
|
# Total: 394 tests
|
||||||
|
|
||||||
test_basic_key_count
|
test_basic_key_count
|
||||||
test_bucket_create_naming_bad_short_one
|
test_bucket_create_naming_bad_short_one
|
||||||
@@ -486,3 +487,6 @@ test_100_continue
|
|||||||
test_abort_multipart_upload_not_found
|
test_abort_multipart_upload_not_found
|
||||||
test_post_object_tags_authenticated_request
|
test_post_object_tags_authenticated_request
|
||||||
test_object_delete_key_bucket_gone
|
test_object_delete_key_bucket_gone
|
||||||
|
|
||||||
|
# Multipart copy range validation
|
||||||
|
test_multipart_copy_invalid_range
|
||||||
|
|||||||
@@ -224,7 +224,6 @@ test_list_buckets_paginated
|
|||||||
test_list_multipart_upload
|
test_list_multipart_upload
|
||||||
test_list_multipart_upload_owner
|
test_list_multipart_upload_owner
|
||||||
test_multipart_checksum_sha256
|
test_multipart_checksum_sha256
|
||||||
test_multipart_copy_invalid_range
|
|
||||||
test_multipart_copy_multiple_sizes
|
test_multipart_copy_multiple_sizes
|
||||||
test_multipart_copy_versioned
|
test_multipart_copy_versioned
|
||||||
test_multipart_get_part
|
test_multipart_get_part
|
||||||
|
|||||||
Reference in New Issue
Block a user