diff --git a/crates/ecstore/src/bucket/tagging/mod.rs b/crates/ecstore/src/bucket/tagging/mod.rs index 9c5f16be3..48e391a87 100644 --- a/crates/ecstore/src/bucket/tagging/mod.rs +++ b/crates/ecstore/src/bucket/tagging/mod.rs @@ -13,6 +13,7 @@ // limitations under the License. use s3s::dto::Tag; +use std::cmp::Ordering; use std::collections::HashMap; use url::form_urlencoded; @@ -22,7 +23,7 @@ pub fn decode_tags(tags: &str) -> Vec { let mut list = Vec::new(); for (k, v) in values { - if k.is_empty() || v.is_empty() { + if k.is_empty() { continue; } @@ -31,7 +32,13 @@ pub fn decode_tags(tags: &str) -> Vec { value: Some(v.to_string()), }); } - + // use pattern matching instead of unwrap(), no panic even if the key becomes None later + list.sort_by(|a, b| match (a.key.as_ref(), b.key.as_ref()) { + (Some(a_k), Some(b_k)) => a_k.cmp(b_k), + (Some(_), None) => Ordering::Greater, + (None, Some(_)) => Ordering::Less, + (None, None) => Ordering::Equal, + }); list } @@ -39,7 +46,7 @@ pub fn decode_tags_to_map(tags: &str) -> HashMap { let mut list = HashMap::new(); for (k, v) in form_urlencoded::parse(tags.as_bytes()) { - if k.is_empty() || v.is_empty() { + if k.is_empty() { continue; } diff --git a/rustfs/src/auth.rs b/rustfs/src/auth.rs index 15881c877..59e919cbe 100644 --- a/rustfs/src/auth.rs +++ b/rustfs/src/auth.rs @@ -146,7 +146,10 @@ impl S3Auth for IAMAuth { tracing::warn!("get_secret_key failed: iam not initialized, access_key: {access_key}"); } - Err(s3_error!(UnauthorizedAccess, "Your account is not signed up2, access_key: {access_key}")) + Err(s3_error!( + InvalidAccessKeyId, + "The Access Key Id you provided does not exist in our records." + )) } } diff --git a/rustfs/src/storage/ecfs.rs b/rustfs/src/storage/ecfs.rs index 390165cf8..578af2532 100644 --- a/rustfs/src/storage/ecfs.rs +++ b/rustfs/src/storage/ecfs.rs @@ -4385,7 +4385,7 @@ impl S3 for FS { if let Err(err) = cfg.is_valid() { warn!("put_bucket_policy err input {:?}, {:?}", &policy, err); - return Err(s3_error!(InvalidPolicyDocument)); + return Err(s3_error!(MalformedPolicy)); } let data = serde_json::to_vec(&cfg).map_err(|e| s3_error!(InternalError, "parse policy failed {:?}", e))?; @@ -5206,16 +5206,13 @@ impl S3 for FS { return Err(s3_error!(InvalidTag, "Tag key is too long, maximum allowed length is 128 characters")); } + // allow to set the value of a tag to an empty string, but cannot set it to a null value. + // Reference:https://docs.aws.amazon.com/zh_cn/AWSEC2/latest/UserGuide/Using_Tags.html#tag-restrictions let value = tag.value.as_ref().ok_or_else(|| { error!("Null tag value"); s3_error!(InvalidTag, "Tag value cannot be null") })?; - if value.is_empty() { - error!("Empty tag value"); - return Err(s3_error!(InvalidTag, "Tag value cannot be empty")); - } - if value.len() > 256 { error!("Tag value too long: {} bytes", value.len()); return Err(s3_error!(InvalidTag, "Tag value is too long, maximum allowed length is 256 characters")); diff --git a/rustfs/src/storage/options.rs b/rustfs/src/storage/options.rs index 6d0d57218..77dc82cbf 100644 --- a/rustfs/src/storage/options.rs +++ b/rustfs/src/storage/options.rs @@ -367,11 +367,11 @@ pub(crate) fn filter_object_metadata(metadata: &HashMap) -> Opti let mut filtered_metadata = HashMap::new(); for (k, v) in metadata { + let lower_key = k.to_ascii_lowercase(); // Skip internal/reserved metadata - if k.starts_with(RESERVED_METADATA_PREFIX_LOWER) { + if lower_key.starts_with(RESERVED_METADATA_PREFIX_LOWER) { continue; } - // Skip empty object lock values if v.is_empty() && (k == &X_AMZ_OBJECT_LOCK_MODE.to_string() || k == &X_AMZ_OBJECT_LOCK_RETAIN_UNTIL_DATE.to_string()) { continue; @@ -382,8 +382,6 @@ pub(crate) fn filter_object_metadata(metadata: &HashMap) -> Opti continue; } - let lower_key = k.to_ascii_lowercase(); - // Skip standard HTTP headers (they are returned as separate headers, not metadata) if EXCLUDED_HEADERS.contains(&lower_key.as_str()) { continue;