This is an automated email from the ASF dual-hosted git repository.
adoroszlai pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ozone.git
The following commit(s) were added to refs/heads/master by this push:
new bfe44d5553b HDDS-15259. PutObject should treat null tag value for
x-amz-tagging header as empty one (#10299)
bfe44d5553b is described below
commit bfe44d5553b38eb18cd350d57d706dbf5fbe0a1d
Author: Gargi Jaiswal <[email protected]>
AuthorDate: Mon May 18 14:42:00 2026 +0530
HDDS-15259. PutObject should treat null tag value for x-amz-tagging header
as empty one (#10299)
---
.../ozone/s3/awssdk/v1/AbstractS3SDKV1Tests.java | 36 ++++++++++++++++++++++
.../ozone/s3/awssdk/v2/AbstractS3SDKV2Tests.java | 34 ++++++++++++++++++++
.../hadoop/ozone/s3/endpoint/EndpointBase.java | 14 +++++++--
.../hadoop/ozone/s3/endpoint/TestObjectPut.java | 26 ++++++++++++----
4 files changed, 102 insertions(+), 8 deletions(-)
diff --git
a/hadoop-ozone/integration-test-s3/src/test/java/org/apache/hadoop/ozone/s3/awssdk/v1/AbstractS3SDKV1Tests.java
b/hadoop-ozone/integration-test-s3/src/test/java/org/apache/hadoop/ozone/s3/awssdk/v1/AbstractS3SDKV1Tests.java
index f1c47df8557..0e5ed3e616e 100644
---
a/hadoop-ozone/integration-test-s3/src/test/java/org/apache/hadoop/ozone/s3/awssdk/v1/AbstractS3SDKV1Tests.java
+++
b/hadoop-ozone/integration-test-s3/src/test/java/org/apache/hadoop/ozone/s3/awssdk/v1/AbstractS3SDKV1Tests.java
@@ -1105,6 +1105,42 @@ public void testGetObject() throws Exception {
}
}
+ static Stream<Arguments> onlyTagKeyCasesV1() {
+ Map<String, String> fooBarEmptyBar = new HashMap<>();
+ fooBarEmptyBar.put("foo", "bar");
+ fooBarEmptyBar.put("bar", "");
+ return Stream.of(
+ Arguments.of(
+ new ObjectTagging(Collections.singletonList(new Tag("tag1",
null))),
+ Collections.singletonMap("tag1", "")),
+ Arguments.of(
+ new ObjectTagging(Arrays.asList(new Tag("foo", "bar"), new
Tag("bar", null))),
+ fooBarEmptyBar)
+ );
+ }
+
+ @ParameterizedTest
+ @MethodSource("onlyTagKeyCasesV1")
+ public void testPutObjectWithOnlyTagKey(ObjectTagging objectTagging,
+ Map<String, String> expectedTags) throws Exception {
+ final String bucketName = getBucketName();
+ final String keyName = getKeyName();
+ final String content = "0123456789";
+ s3Client.createBucket(bucketName);
+
+ try (InputStream is = new
ByteArrayInputStream(content.getBytes(StandardCharsets.UTF_8))) {
+ PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName,
keyName, is, new ObjectMetadata())
+ .withTagging(objectTagging);
+ s3Client.putObject(putObjectRequest);
+ }
+
+ GetObjectTaggingResult taggingResult = s3Client.getObjectTagging(
+ new GetObjectTaggingRequest(bucketName, keyName));
+ Map<String, String> actualTags = taggingResult.getTagSet().stream()
+ .collect(Collectors.toMap(Tag::getKey, Tag::getValue));
+ assertEquals(expectedTags, actualTags);
+ }
+
@Test
public void testHeadObjectReturnsTaggingCount() {
final String bucketName = getBucketName();
diff --git
a/hadoop-ozone/integration-test-s3/src/test/java/org/apache/hadoop/ozone/s3/awssdk/v2/AbstractS3SDKV2Tests.java
b/hadoop-ozone/integration-test-s3/src/test/java/org/apache/hadoop/ozone/s3/awssdk/v2/AbstractS3SDKV2Tests.java
index 7b12bc6f4dd..3a1df68154c 100644
---
a/hadoop-ozone/integration-test-s3/src/test/java/org/apache/hadoop/ozone/s3/awssdk/v2/AbstractS3SDKV2Tests.java
+++
b/hadoop-ozone/integration-test-s3/src/test/java/org/apache/hadoop/ozone/s3/awssdk/v2/AbstractS3SDKV2Tests.java
@@ -244,6 +244,40 @@ public void testPutObject() {
assertEquals("\"37b51d194a7513e45b56f6524f2d51f2\"",
getObjectResponse.eTag());
}
+ static Stream<Arguments> onlyTagKeyCasesV2() {
+ Map<String, String> fooBarEmptyBar = new HashMap<>();
+ fooBarEmptyBar.put("foo", "bar");
+ fooBarEmptyBar.put("bar", "");
+ return Stream.of(
+ Arguments.of("tag1", Collections.singletonMap("tag1", "")),
+ Arguments.of("foo=bar&bar", fooBarEmptyBar)
+ );
+ }
+
+ @ParameterizedTest
+ @MethodSource("onlyTagKeyCasesV2")
+ public void testPutObjectWithOnlyTagKey(String taggingHeader,
+ Map<String, String> expectedTags) {
+ final String bucketName = getBucketName();
+ final String keyName = getKeyName();
+ final String content = "0123456789";
+ s3Client.createBucket(b -> b.bucket(bucketName));
+
+ PutObjectRequest request = PutObjectRequest.builder()
+ .bucket(bucketName)
+ .key(keyName)
+ .tagging(taggingHeader)
+ .build();
+ s3Client.putObject(request, RequestBody.fromString(content));
+
+ Map<String, String> actualTags = s3Client.getObjectTagging(
+ b -> b.bucket(bucketName).key(keyName))
+ .tagSet()
+ .stream()
+ .collect(Collectors.toMap(Tag::key, Tag::value));
+ assertEquals(expectedTags, actualTags);
+ }
+
@Test
public void testGetObjectTaggingReturnsTagsSortedByKey() {
final String bucketName = getBucketName();
diff --git
a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/EndpointBase.java
b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/EndpointBase.java
index 649b14b49cd..e9c7f2c9fa8 100644
---
a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/EndpointBase.java
+++
b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/EndpointBase.java
@@ -363,7 +363,16 @@ protected Map<String, String>
getTaggingFromHeaders(HttpHeaders httpHeaders)
List<NameValuePair> tagPairs = URLEncodedUtils.parse(tagString, UTF_8);
- return validateAndGetTagging(tagPairs, NameValuePair::getName,
NameValuePair::getValue);
+ // Put Object with x-amz-tagging header. A segment with no '='
(e.g."foo=bar&bar") is
+ // typically represented as (key=bar, value=null). AWS S3 treats that as
an empty value for "bar".
+ // We map null → "" here only for this header path.
+ // PutObjectTagging is different: the XML/JSON API requires each Tag to
+ // include a Value element; so a missing Value stays null and fails
validation.
+ return validateAndGetTagging(tagPairs, NameValuePair::getName,
+ pair -> {
+ String v = pair.getValue();
+ return v != null ? v : "";
+ });
}
protected static <KV> Map<String, String> validateAndGetTagging(
@@ -389,7 +398,8 @@ protected static <KV> Map<String, String>
validateAndGetTagging(
}
if (tagValue == null) {
- // For example for query parameter with only value (e.g. "tag1")
+ // Missing tag value is invalid for PutObjectTagging XML/JSON;
x-amz-tagging must
+ // normalize null to "" in getTaggingFromHeaders before calling this
method.
OS3Exception ex = S3ErrorTable.newError(INVALID_TAG, tagKey);
ex.setErrorMessage("Some tag values are not specified, please specify
the tag values");
throw ex;
diff --git
a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestObjectPut.java
b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestObjectPut.java
index 185d126ccec..bcd4cdd9084 100644
---
a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestObjectPut.java
+++
b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestObjectPut.java
@@ -174,13 +174,27 @@ public void testPutObjectWithTags() throws Exception {
assertEquals("value2", tags.get("tag2"));
}
- @Test
- public void testPutObjectWithOnlyTagKey() {
- // Try to send with only the key (no value)
- when(headers.getHeaderString(TAG_HEADER)).thenReturn("tag1");
+ static Stream<Arguments> onlyTagKeyCases() {
+ return Stream.of(
+ Arguments.of("tag1", ImmutableMap.of("tag1", "")),
+ Arguments.of("foo=bar&bar", ImmutableMap.of("foo", "bar", "bar", ""))
+ );
+ }
- OS3Exception ex = assertErrorResponse(INVALID_TAG, () ->
putObject(CONTENT));
- assertThat(ex.getErrorMessage()).contains("Some tag values are not
specified");
+ /**
+ * Put Object with {@code x-amz-tagging} header where key with a null value
is treated as
+ * an empty string value (AWS), e.g. foo=bar&bar, here bar = " ".
+ */
+ @ParameterizedTest
+ @MethodSource("onlyTagKeyCases")
+ void testPutObjectWithOnlyTagKey(String tagHeader,
+ Map<String, String> expectedTags) throws Exception {
+ when(headers.getHeaderString(TAG_HEADER)).thenReturn(tagHeader);
+
+ assertSucceeds(() -> putObject(CONTENT));
+
+ assertThat(bucket.getKey(KEY_NAME).getTags())
+ .containsExactlyInAnyOrderEntriesOf(expectedTags);
}
@Test
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]