From 71e91781828d3c3e6e7bc97b8a077b4fc39aa7e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=AE=89=E6=AD=A3=E8=B6=85?= Date: Thu, 9 Apr 2026 23:31:02 +0800 Subject: [PATCH] refactor(app): reuse object tagging validation (#2445) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: 安正超 Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: houseme --- rustfs/src/app/object_usecase.rs | 78 +++++++++++++++++++------------- 1 file changed, 46 insertions(+), 32 deletions(-) diff --git a/rustfs/src/app/object_usecase.rs b/rustfs/src/app/object_usecase.rs index 04d0180d8..e7fc5ea2b 100644 --- a/rustfs/src/app/object_usecase.rs +++ b/rustfs/src/app/object_usecase.rs @@ -950,43 +950,12 @@ impl DefaultObjectUsecase { .. } = req.input.clone(); - if tagging.tag_set.len() > 10 { - error!("Tag set exceeds maximum of 10 tags: {}", tagging.tag_set.len()); - return Err(s3_error!(InvalidTag, "Cannot have more than 10 tags per object")); - } + crate::storage::s3_api::tagging::validate_object_tag_set(&tagging.tag_set)?; let Some(store) = new_object_layer_fn() else { return Err(S3Error::with_message(S3ErrorCode::InternalError, "Not init".to_string())); }; - let mut tag_keys = std::collections::HashSet::with_capacity(tagging.tag_set.len()); - for tag in &tagging.tag_set { - let key = tag.key.as_ref().filter(|k| !k.is_empty()).ok_or_else(|| { - error!("Empty tag key"); - s3_error!(InvalidTag, "Tag key cannot be empty") - })?; - - if key.len() > 128 { - error!("Tag key too long: {} bytes", key.len()); - return Err(s3_error!(InvalidTag, "Tag key is too long, maximum allowed length is 128 characters")); - } - - let value = tag.value.as_ref().ok_or_else(|| { - error!("Null tag value"); - s3_error!(InvalidTag, "Tag value cannot be null") - })?; - - 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")); - } - - if !tag_keys.insert(key) { - error!("Duplicate tag key: {}", key); - return Err(s3_error!(InvalidTag, "Cannot provide multiple Tags with the same key")); - } - } - let tags = encode_tags(tagging.tag_set); debug!("Encoded tags: {}", tags); @@ -3185,6 +3154,51 @@ mod tests { assert_eq!(err.code(), &S3ErrorCode::InvalidStorageClass); } + #[tokio::test] + async fn execute_put_object_tagging_rejects_too_many_tags() { + let tag_set = (0..11) + .map(|index| Tag { + key: Some(format!("k{index}")), + value: Some(format!("v{index}")), + }) + .collect(); + let input = PutObjectTaggingInput::builder() + .bucket("test-bucket".to_string()) + .key("test-key".to_string()) + .tagging(Tagging { tag_set }) + .build() + .unwrap(); + + let req = build_request(input, Method::PUT); + let usecase = DefaultObjectUsecase::without_context(); + + let err = usecase.execute_put_object_tagging(req).await.unwrap_err(); + assert_eq!(err.code(), &S3ErrorCode::InvalidTag); + assert!(err.to_string().contains("Cannot have more than 10 tags per object")); + } + + #[tokio::test] + async fn execute_put_object_tagging_rejects_empty_tag_key_before_store_lookup() { + let input = PutObjectTaggingInput::builder() + .bucket("test-bucket".to_string()) + .key("test-key".to_string()) + .tagging(Tagging { + tag_set: vec![Tag { + key: Some(String::new()), + value: Some("v1".to_string()), + }], + }) + .build() + .unwrap(); + + let req = build_request(input, Method::PUT); + let usecase = DefaultObjectUsecase::without_context(); + + let err = usecase.execute_put_object_tagging(req).await.unwrap_err(); + assert_eq!(err.code(), &S3ErrorCode::InvalidTag); + assert!(err.to_string().contains("Tag key cannot be empty")); + } + #[tokio::test] async fn execute_copy_object_rejects_self_copy_without_replace_directive() { let input = CopyObjectInput::builder()