fix: restore s3 compatibility regressions and CI coverage (#1793)

This commit is contained in:
安正超
2026-02-13 12:26:52 +08:00
committed by GitHub
parent 921cfb849c
commit 2fc36bb52e
5 changed files with 291 additions and 25 deletions
+3 -1
View File
@@ -4265,7 +4265,9 @@ impl S3 for FS {
return Err(s3_error!(MalformedPolicy));
}
let data = serde_json::to_vec(&cfg).map_err(|e| s3_error!(InternalError, "parse policy failed {:?}", e))?;
// Preserve the original JSON text so GetBucketPolicy can return byte-for-byte content.
// s3-tests expects exact string round-trip equality.
let data = policy.into_bytes();
metadata_sys::update(&bucket, BUCKET_POLICY_CONFIG, data)
.await
+29
View File
@@ -27,6 +27,7 @@ mod tests {
use rustfs_config::MI_B;
use rustfs_ecstore::set_disk::DEFAULT_READ_BUFFER_SIZE;
use rustfs_ecstore::store_api::ObjectInfo;
use rustfs_policy::policy::{BucketPolicy, Validator};
use rustfs_utils::http::{AMZ_OBJECT_LOCK_LEGAL_HOLD_LOWER, RESERVED_METADATA_PREFIX_LOWER};
use rustfs_zip::CompressionFormat;
use s3s::dto::{
@@ -1012,6 +1013,34 @@ mod tests {
assert_eq!(filtered_version_marker.unwrap(), "null");
}
#[test]
fn test_bucket_policy_round_trip_preserves_original_json_text() {
let policy = r#"{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": {"AWS": "*"},
"Action": "s3:ListBucket",
"Resource": [
"arn:aws:s3:::example-bucket",
"arn:aws:s3:::example-bucket/*"
]
}]
}"#;
let parsed: BucketPolicy = serde_json::from_str(policy).unwrap();
assert!(parsed.is_valid().is_ok());
// Normalized serialization can differ (for example, Action becomes an array).
let normalized = serde_json::to_string(&parsed).unwrap();
assert_ne!(normalized, policy);
// Stored raw policy bytes must preserve exact text for GetBucketPolicy round trip.
let stored = policy.as_bytes().to_vec();
let round_trip = String::from_utf8(stored).unwrap();
assert_eq!(round_trip, policy);
}
#[test]
fn test_matches_origin_pattern_exact_match() {
// Test exact match
+30 -7
View File
@@ -392,13 +392,9 @@ pub fn extract_metadata_from_mime_with_object_name(
pub(crate) fn filter_object_metadata(metadata: &HashMap<String, String>) -> Option<HashMap<String, String>> {
// HTTP headers that should NOT be returned in the Metadata field.
// These headers are returned as separate response headers, not user metadata.
//
// Note: content-type and content-disposition are intentionally NOT excluded here.
// They remain in the filtered metadata so they can continue to be exposed via
// x-amz-meta-* style user metadata for backward compatibility, while the HEAD
// implementation also mirrors their values into the standard Content-Type and
// Content-Disposition response headers where appropriate.
const EXCLUDED_HEADERS: &[&str] = &[
"content-type",
"content-disposition",
"content-encoding",
"content-language",
"cache-control",
@@ -437,7 +433,6 @@ pub(crate) fn filter_object_metadata(metadata: &HashMap<String, String>) -> Opti
}
// Skip excluded HTTP headers (they are returned as separate headers, not metadata)
// Note: content-type and content-disposition are NOT excluded and will be treated as user metadata
if EXCLUDED_HEADERS.contains(&lower_key.as_str()) {
continue;
}
@@ -1209,6 +1204,34 @@ mod tests {
assert_eq!(metadata.get("content-type"), Some(&"custom/type".to_string()));
}
#[test]
fn test_filter_object_metadata_excludes_standard_headers() {
let mut metadata = HashMap::new();
metadata.insert("content-type".to_string(), "application/octet-stream".to_string());
metadata.insert("content-disposition".to_string(), "inline".to_string());
metadata.insert("cache-control".to_string(), "no-cache".to_string());
metadata.insert("x-amz-storage-class".to_string(), "STANDARD".to_string());
metadata.insert("custom-key".to_string(), "custom-value".to_string());
let filtered = filter_object_metadata(&metadata).unwrap();
assert_eq!(filtered.len(), 1);
assert_eq!(filtered.get("custom-key"), Some(&"custom-value".to_string()));
assert!(!filtered.contains_key("content-type"));
assert!(!filtered.contains_key("content-disposition"));
assert!(!filtered.contains_key("cache-control"));
assert!(!filtered.contains_key("x-amz-storage-class"));
}
#[test]
fn test_filter_object_metadata_returns_none_for_only_content_type() {
let mut metadata = HashMap::new();
metadata.insert("content-type".to_string(), "application/octet-stream".to_string());
let filtered = filter_object_metadata(&metadata);
assert!(filtered.is_none(), "content-type must not be exposed as user metadata");
}
#[test]
fn test_detect_content_type_from_object_name() {
// Test Parquet files (our custom handling)