From 173dad27d1ea5f98774aac2abe7e62085688287a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=AE=89=E6=AD=A3=E8=B6=85?= Date: Sat, 24 Jan 2026 23:02:01 +0800 Subject: [PATCH] fix: preserve exact JSON format in bucket policy GET response (#1598) Co-authored-by: loverustfs --- crates/ecstore/src/bucket/metadata_sys.rs | 23 +++++++++++++++++++++++ rustfs/src/storage/ecfs.rs | 14 +++++++++----- scripts/s3-tests/implemented_tests.txt | 3 ++- scripts/s3-tests/unimplemented_tests.txt | 1 - 4 files changed, 34 insertions(+), 7 deletions(-) diff --git a/crates/ecstore/src/bucket/metadata_sys.rs b/crates/ecstore/src/bucket/metadata_sys.rs index 64c14a5fa..9c54f9944 100644 --- a/crates/ecstore/src/bucket/metadata_sys.rs +++ b/crates/ecstore/src/bucket/metadata_sys.rs @@ -96,6 +96,15 @@ pub async fn get_bucket_policy(bucket: &str) -> Result<(BucketPolicy, OffsetDate bucket_meta_sys.get_bucket_policy(bucket).await } +/// Returns the raw JSON string of the bucket policy as originally stored. +/// This preserves the exact format of the policy document as it was PUT. +pub async fn get_bucket_policy_raw(bucket: &str) -> Result<(String, OffsetDateTime)> { + let bucket_meta_sys_lock = get_bucket_metadata_sys()?; + let bucket_meta_sys = bucket_meta_sys_lock.read().await; + + bucket_meta_sys.get_bucket_policy_raw(bucket).await +} + pub async fn get_quota_config(bucket: &str) -> Result<(BucketQuota, OffsetDateTime)> { let bucket_meta_sys_lock = get_bucket_metadata_sys()?; let bucket_meta_sys = bucket_meta_sys_lock.read().await; @@ -455,6 +464,20 @@ impl BucketMetadataSys { } } + /// Returns the raw JSON string of the bucket policy as originally stored. + /// This preserves the exact format of the policy document as it was PUT. + pub async fn get_bucket_policy_raw(&self, bucket: &str) -> Result<(String, OffsetDateTime)> { + let (bm, _) = self.get_config(bucket).await?; + + if bm.policy_config_json.is_empty() { + Err(Error::ConfigNotFound) + } else { + let policy_str = String::from_utf8(bm.policy_config_json.clone()) + .map_err(|e| Error::other(format!("invalid UTF-8 in policy JSON: {}", e)))?; + Ok((policy_str, bm.policy_config_updated_at)) + } + } + pub async fn get_tagging_config(&self, bucket: &str) -> Result<(Tagging, OffsetDateTime)> { let (bm, _) = self.get_config(bucket).await?; diff --git a/rustfs/src/storage/ecfs.rs b/rustfs/src/storage/ecfs.rs index 4d1845d71..553e54d7c 100644 --- a/rustfs/src/storage/ecfs.rs +++ b/rustfs/src/storage/ecfs.rs @@ -5736,7 +5736,9 @@ impl S3 for FS { .await .map_err(ApiError::from)?; - let cfg = match PolicySys::get(&bucket).await { + // Return the raw policy JSON as originally stored to preserve exact format. + // This ensures GET returns the same document that was PUT, matching S3 behavior. + let (policy_str, _) = match metadata_sys::get_bucket_policy_raw(&bucket).await { Ok(res) => res, Err(err) => { if StorageError::ConfigNotFound == err { @@ -5746,9 +5748,9 @@ impl S3 for FS { } }; - let policies = try_!(serde_json::to_string(&cfg)); - - Ok(S3Response::new(GetBucketPolicyOutput { policy: Some(policies) })) + Ok(S3Response::new(GetBucketPolicyOutput { + policy: Some(policy_str), + })) } async fn put_bucket_policy(&self, req: S3Request) -> S3Result> { @@ -5773,7 +5775,9 @@ impl S3 for FS { return Err(s3_error!(InvalidPolicyDocument)); } - let data = serde_json::to_vec(&cfg).map_err(|e| s3_error!(InternalError, "parse policy failed {:?}", e))?; + // Store the original policy string as-is to preserve the exact JSON format. + // This ensures GET returns the same document that was PUT. + let data = policy.into_bytes(); metadata_sys::update(&bucket, BUCKET_POLICY_CONFIG, data) .await diff --git a/scripts/s3-tests/implemented_tests.txt b/scripts/s3-tests/implemented_tests.txt index 7b5717cae..45908b238 100644 --- a/scripts/s3-tests/implemented_tests.txt +++ b/scripts/s3-tests/implemented_tests.txt @@ -17,7 +17,7 @@ # - Metadata: User-defined metadata # - Conditional GET: If-Match, If-None-Match, If-Modified-Since # -# Total: 119 tests +# Total: 123 tests test_basic_key_count test_bucket_create_naming_bad_short_one @@ -101,6 +101,7 @@ test_bucketv2_notexist test_bucketv2_policy_another_bucket test_get_bucket_policy_status test_get_nonpublicpolicy_principal_bucket_policy_status +test_set_get_del_bucket_policy test_get_object_ifmatch_good test_get_object_ifmodifiedsince_good test_get_object_ifunmodifiedsince_failed diff --git a/scripts/s3-tests/unimplemented_tests.txt b/scripts/s3-tests/unimplemented_tests.txt index 0e07c386a..e5833e056 100644 --- a/scripts/s3-tests/unimplemented_tests.txt +++ b/scripts/s3-tests/unimplemented_tests.txt @@ -173,7 +173,6 @@ test_put_excess_tags test_put_excess_val_tags test_put_get_delete_public_block test_put_public_block -test_set_get_del_bucket_policy # Object attributes and torrent tests test_create_bucket_no_ownership_controls