mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-24 05:06:28 +00:00
fix: implement handling for "aws-chunked" Content-Encoding (#2009)
Co-authored-by: 安正超 <anzhengchao@gmail.com>
This commit is contained in:
@@ -82,4 +82,60 @@ mod tests {
|
|||||||
|
|
||||||
env.stop_server();
|
env.stop_server();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Issue #1857: Content-Encoding "aws-chunked" is used by SigV4 streaming clients and must
|
||||||
|
/// not be stored or returned. Upload with aws-chunked and verify GET/HEAD do not return it.
|
||||||
|
#[tokio::test]
|
||||||
|
#[serial]
|
||||||
|
async fn test_content_encoding_aws_chunked_not_returned_issue_1857() {
|
||||||
|
init_logging();
|
||||||
|
info!("Issue #1857: aws-chunked must not be persisted or returned");
|
||||||
|
|
||||||
|
let mut env = RustFSTestEnvironment::new().await.expect("Failed to create test environment");
|
||||||
|
env.start_rustfs_server(vec![]).await.expect("Failed to start RustFS");
|
||||||
|
|
||||||
|
let client = env.create_s3_client();
|
||||||
|
let bucket = "content-encoding-aws-chunked-test";
|
||||||
|
let key = "streamed/object.bin";
|
||||||
|
let content = b"streaming upload body";
|
||||||
|
|
||||||
|
client
|
||||||
|
.create_bucket()
|
||||||
|
.bucket(bucket)
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.expect("Failed to create bucket");
|
||||||
|
|
||||||
|
client
|
||||||
|
.put_object()
|
||||||
|
.bucket(bucket)
|
||||||
|
.key(key)
|
||||||
|
.content_encoding("aws-chunked")
|
||||||
|
.body(ByteStream::from_static(content))
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.expect("PUT failed");
|
||||||
|
|
||||||
|
let get_resp = client.get_object().bucket(bucket).key(key).send().await.expect("GET failed");
|
||||||
|
assert_eq!(
|
||||||
|
get_resp.content_encoding(),
|
||||||
|
None,
|
||||||
|
"GET must not return Content-Encoding when only aws-chunked was sent (Issue #1857)"
|
||||||
|
);
|
||||||
|
|
||||||
|
let head_resp = client
|
||||||
|
.head_object()
|
||||||
|
.bucket(bucket)
|
||||||
|
.key(key)
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.expect("HEAD failed");
|
||||||
|
assert_eq!(
|
||||||
|
head_resp.content_encoding(),
|
||||||
|
None,
|
||||||
|
"HEAD must not return Content-Encoding when only aws-chunked was sent (Issue #1857)"
|
||||||
|
);
|
||||||
|
|
||||||
|
env.stop_server();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -345,6 +345,25 @@ pub fn extract_metadata_from_mime(headers: &HeaderMap<HeaderValue>, metadata: &m
|
|||||||
extract_metadata_from_mime_with_object_name(headers, metadata, false, None);
|
extract_metadata_from_mime_with_object_name(headers, metadata, false, None);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Normalizes Content-Encoding for storage per AWS S3 behavior: "aws-chunked" is a
|
||||||
|
/// request-side transfer encoding for SigV4 streaming and must not be stored or returned.
|
||||||
|
/// If the only value is "aws-chunked", returns None (do not persist). Otherwise returns
|
||||||
|
/// the value with "aws-chunked" stripped, or None if nothing remains.
|
||||||
|
fn normalize_content_encoding_for_storage(value: &str) -> Option<String> {
|
||||||
|
let trimmed = value.trim();
|
||||||
|
if trimmed.is_empty() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let normalized: String = trimmed
|
||||||
|
.split(',')
|
||||||
|
.map(|s| s.trim())
|
||||||
|
.filter(|s| !s.eq_ignore_ascii_case("aws-chunked"))
|
||||||
|
.filter(|s| !s.is_empty())
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join(", ");
|
||||||
|
if normalized.is_empty() { None } else { Some(normalized) }
|
||||||
|
}
|
||||||
|
|
||||||
/// Extracts metadata from headers and returns it as a HashMap with object name for MIME type detection.
|
/// Extracts metadata from headers and returns it as a HashMap with object name for MIME type detection.
|
||||||
pub fn extract_metadata_from_mime_with_object_name(
|
pub fn extract_metadata_from_mime_with_object_name(
|
||||||
headers: &HeaderMap<HeaderValue>,
|
headers: &HeaderMap<HeaderValue>,
|
||||||
@@ -373,7 +392,14 @@ pub fn extract_metadata_from_mime_with_object_name(
|
|||||||
|
|
||||||
for hd in SUPPORTED_HEADERS.iter() {
|
for hd in SUPPORTED_HEADERS.iter() {
|
||||||
if k.as_str() == *hd {
|
if k.as_str() == *hd {
|
||||||
metadata.insert(k.to_string(), String::from_utf8_lossy(v.as_bytes()).to_string());
|
let raw = String::from_utf8_lossy(v.as_bytes()).to_string();
|
||||||
|
if *hd == "content-encoding" {
|
||||||
|
if let Some(normalized) = normalize_content_encoding_for_storage(&raw) {
|
||||||
|
metadata.insert(k.to_string(), normalized);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
metadata.insert(k.to_string(), raw);
|
||||||
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1051,6 +1077,48 @@ mod tests {
|
|||||||
assert_eq!(metadata.get("x-amz-replication-status"), Some(&"COMPLETED".to_string()));
|
assert_eq!(metadata.get("x-amz-replication-status"), Some(&"COMPLETED".to_string()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Issue #1857: SigV4 streaming sends Content-Encoding: aws-chunked. Per AWS S3,
|
||||||
|
/// this is a request-side transfer encoding and must not be stored or returned.
|
||||||
|
/// This test verifies: (1) "aws-chunked" alone is not persisted;
|
||||||
|
/// (2) when combined with real encoding (e.g. gzip), only the real encoding is stored;
|
||||||
|
/// (3) case-insensitive stripping of aws-chunked.
|
||||||
|
#[test]
|
||||||
|
fn test_content_encoding_aws_chunked_not_persisted_issue_1857() {
|
||||||
|
let cases: &[(&str, Option<&str>)] = &[
|
||||||
|
("aws-chunked", None),
|
||||||
|
("AWS-CHUNKED", None),
|
||||||
|
("aws-chunked ", None),
|
||||||
|
("gzip, aws-chunked", Some("gzip")),
|
||||||
|
("aws-chunked, gzip", Some("gzip")),
|
||||||
|
("gzip", Some("gzip")),
|
||||||
|
("zstd", Some("zstd")),
|
||||||
|
];
|
||||||
|
|
||||||
|
for (header_value, expected) in cases {
|
||||||
|
let mut headers = HeaderMap::new();
|
||||||
|
headers.insert("content-encoding", HeaderValue::from_static(header_value));
|
||||||
|
|
||||||
|
let mut metadata = HashMap::new();
|
||||||
|
extract_metadata_from_mime(&headers, &mut metadata);
|
||||||
|
|
||||||
|
match expected {
|
||||||
|
None => assert!(
|
||||||
|
!metadata.contains_key("content-encoding"),
|
||||||
|
"content-encoding {:?} should not be persisted, got metadata keys: {:?}",
|
||||||
|
header_value,
|
||||||
|
metadata.keys().collect::<Vec<_>>()
|
||||||
|
),
|
||||||
|
Some(exp) => assert_eq!(
|
||||||
|
metadata.get("content-encoding"),
|
||||||
|
Some(&exp.to_string()),
|
||||||
|
"content-encoding {:?} should be normalized to {:?}",
|
||||||
|
header_value,
|
||||||
|
exp
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_extract_metadata_from_mime_default_content_type() {
|
fn test_extract_metadata_from_mime_default_content_type() {
|
||||||
let headers = HeaderMap::new();
|
let headers = HeaderMap::new();
|
||||||
|
|||||||
Reference in New Issue
Block a user