mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-21 11:56:38 +00:00
fix: correct HTTP range suffix handling
This commit is contained in:
+100
-13
@@ -35,7 +35,6 @@ use rustfs_rio::{DecompressReader, HashReader, LimitReader, WarpReader};
|
|||||||
use rustfs_utils::CompressionAlgorithm;
|
use rustfs_utils::CompressionAlgorithm;
|
||||||
use rustfs_utils::http::headers::{AMZ_OBJECT_TAGGING, RESERVED_METADATA_PREFIX_LOWER};
|
use rustfs_utils::http::headers::{AMZ_OBJECT_TAGGING, RESERVED_METADATA_PREFIX_LOWER};
|
||||||
use rustfs_utils::path::decode_dir_object;
|
use rustfs_utils::path::decode_dir_object;
|
||||||
use s3s::dto::ETag;
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
@@ -248,11 +247,16 @@ impl HTTPRangeSpec {
|
|||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut start = 0i64;
|
if part_number == 0 || part_number > oi.parts.len() {
|
||||||
let mut end = -1i64;
|
return None;
|
||||||
for i in 0..oi.parts.len().min(part_number) {
|
}
|
||||||
|
|
||||||
|
let mut start = 0_i64;
|
||||||
|
let mut end = -1_i64;
|
||||||
|
for i in 0..part_number {
|
||||||
|
let part = &oi.parts[i];
|
||||||
start = end + 1;
|
start = end + 1;
|
||||||
end = start + (oi.parts[i].size as i64) - 1
|
end = start + (part.size as i64) - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
Some(HTTPRangeSpec {
|
Some(HTTPRangeSpec {
|
||||||
@@ -267,8 +271,14 @@ impl HTTPRangeSpec {
|
|||||||
|
|
||||||
let mut start = self.start;
|
let mut start = self.start;
|
||||||
if self.is_suffix_length {
|
if self.is_suffix_length {
|
||||||
start = res_size + self.start;
|
let suffix_len = if self.start < 0 {
|
||||||
|
self.start
|
||||||
|
.checked_neg()
|
||||||
|
.ok_or_else(|| Error::other("range value invalid: suffix length overflow"))?
|
||||||
|
} else {
|
||||||
|
self.start
|
||||||
|
};
|
||||||
|
start = res_size - suffix_len;
|
||||||
if start < 0 {
|
if start < 0 {
|
||||||
start = 0;
|
start = 0;
|
||||||
}
|
}
|
||||||
@@ -281,7 +291,13 @@ impl HTTPRangeSpec {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if self.is_suffix_length {
|
if self.is_suffix_length {
|
||||||
let specified_len = self.start; // 假设 h.start 是一个 i64 类型
|
let specified_len = if self.start < 0 {
|
||||||
|
self.start
|
||||||
|
.checked_neg()
|
||||||
|
.ok_or_else(|| Error::other("range value invalid: suffix length overflow"))?
|
||||||
|
} else {
|
||||||
|
self.start
|
||||||
|
};
|
||||||
let mut range_length = specified_len;
|
let mut range_length = specified_len;
|
||||||
|
|
||||||
if specified_len > res_size {
|
if specified_len > res_size {
|
||||||
@@ -459,13 +475,9 @@ pub struct CompletePart {
|
|||||||
|
|
||||||
impl From<s3s::dto::CompletedPart> for CompletePart {
|
impl From<s3s::dto::CompletedPart> for CompletePart {
|
||||||
fn from(value: s3s::dto::CompletedPart) -> Self {
|
fn from(value: s3s::dto::CompletedPart) -> Self {
|
||||||
let etag = value.e_tag.map(|etag| match etag {
|
|
||||||
ETag::Strong(v) => format!("\"{v}\""),
|
|
||||||
ETag::Weak(v) => format!("W/\"{v}\""),
|
|
||||||
});
|
|
||||||
Self {
|
Self {
|
||||||
part_num: value.part_number.unwrap_or_default() as usize,
|
part_num: value.part_number.unwrap_or_default() as usize,
|
||||||
etag,
|
etag: value.e_tag.map(|e| e.value().to_owned()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1566,6 +1578,81 @@ mod tests {
|
|||||||
assert_eq!(length, 10); // end - start + 1 = 14 - 5 + 1 = 10
|
assert_eq!(length, 10); // end - start + 1 = 14 - 5 + 1 = 10
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_http_range_spec_suffix_positive_start() {
|
||||||
|
let range_spec = HTTPRangeSpec {
|
||||||
|
is_suffix_length: true,
|
||||||
|
start: 5,
|
||||||
|
end: -1,
|
||||||
|
};
|
||||||
|
|
||||||
|
let (offset, length) = range_spec.get_offset_length(20).unwrap();
|
||||||
|
assert_eq!(offset, 15);
|
||||||
|
assert_eq!(length, 5);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_http_range_spec_suffix_negative_start() {
|
||||||
|
let range_spec = HTTPRangeSpec {
|
||||||
|
is_suffix_length: true,
|
||||||
|
start: -5,
|
||||||
|
end: -1,
|
||||||
|
};
|
||||||
|
|
||||||
|
let (offset, length) = range_spec.get_offset_length(20).unwrap();
|
||||||
|
assert_eq!(offset, 15);
|
||||||
|
assert_eq!(length, 5);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_http_range_spec_suffix_exceeds_object() {
|
||||||
|
let range_spec = HTTPRangeSpec {
|
||||||
|
is_suffix_length: true,
|
||||||
|
start: 50,
|
||||||
|
end: -1,
|
||||||
|
};
|
||||||
|
|
||||||
|
let (offset, length) = range_spec.get_offset_length(20).unwrap();
|
||||||
|
assert_eq!(offset, 0);
|
||||||
|
assert_eq!(length, 20);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_http_range_spec_from_object_info_valid_and_invalid_parts() {
|
||||||
|
let mut object_info = ObjectInfo::default();
|
||||||
|
object_info.size = 300;
|
||||||
|
object_info.parts = vec![
|
||||||
|
ObjectPartInfo {
|
||||||
|
etag: String::new(),
|
||||||
|
number: 1,
|
||||||
|
size: 100,
|
||||||
|
actual_size: 100,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
ObjectPartInfo {
|
||||||
|
etag: String::new(),
|
||||||
|
number: 2,
|
||||||
|
size: 100,
|
||||||
|
actual_size: 100,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
ObjectPartInfo {
|
||||||
|
etag: String::new(),
|
||||||
|
number: 3,
|
||||||
|
size: 100,
|
||||||
|
actual_size: 100,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
let spec = HTTPRangeSpec::from_object_info(&object_info, 2).unwrap();
|
||||||
|
assert_eq!(spec.start, 100);
|
||||||
|
assert_eq!(spec.end, 199);
|
||||||
|
|
||||||
|
assert!(HTTPRangeSpec::from_object_info(&object_info, 0).is_none());
|
||||||
|
assert!(HTTPRangeSpec::from_object_info(&object_info, 4).is_none());
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_ranged_decompress_reader_zero_length() {
|
async fn test_ranged_decompress_reader_zero_length() {
|
||||||
let original_data = b"Hello, World!";
|
let original_data = b"Hello, World!";
|
||||||
|
|||||||
Reference in New Issue
Block a user