This is an automated email from the ASF dual-hosted git repository.

Gargi-jais11 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 b23218a8cff HDDS-16251. Preserve signed empty Content-Type during 
preprocessing (#11088).
b23218a8cff is described below

commit b23218a8cff1bd7b84c44df2a132761e0f39b525
Author: Shuo Huang <[email protected]>
AuthorDate: Fri Aug 28 14:49:28 2026 +0800

    HDDS-16251. Preserve signed empty Content-Type during preprocessing 
(#11088).
---
 .../ozone/s3/awssdk/v1/AbstractS3SDKV1Tests.java   | 18 +++++++++++
 .../ozone/s3/awssdk/v2/AbstractS3SDKV2Tests.java   | 18 +++++++++++
 .../hadoop/ozone/s3/EmptyContentTypeFilter.java    | 30 ++++++++++++-------
 .../ozone/s3/TestEmptyContentTypeFilter.java       | 35 ++++++++++++++++++++++
 .../s3/signature/TestAWSSignatureProcessor.java    | 14 +++++++++
 5 files changed, 105 insertions(+), 10 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 b52ed18b511..615c3bc8083 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
@@ -572,6 +572,24 @@ public void testPutObject() {
     assertEquals("37b51d194a7513e45b56f6524f2d51f2", 
putObjectResult.getETag());
   }
 
+  @Test
+  public void testPutObjectWithEmptyContentType() {
+    final String bucketName = getBucketName();
+    final String keyName = getKeyName();
+    final String content = "bar";
+    s3Client.createBucket(bucketName);
+
+    ObjectMetadata metadata = new ObjectMetadata();
+    metadata.setContentType("");
+    InputStream inputStream = new ByteArrayInputStream(
+        content.getBytes(StandardCharsets.UTF_8));
+
+    PutObjectResult putObjectResult = s3Client.putObject(
+        bucketName, keyName, inputStream, metadata);
+    assertEquals("37b51d194a7513e45b56f6524f2d51f2",
+        putObjectResult.getETag());
+  }
+
   @Test
   public void testPutObjectIfNoneMatch() {
     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 00224584f7a..63c0607f0d6 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
@@ -296,6 +296,24 @@ public void testPutObject() {
     assertEquals("\"37b51d194a7513e45b56f6524f2d51f2\"", 
getObjectResponse.eTag());
   }
 
+  @Test
+  public void testPutObjectWithEmptyContentType() {
+    final String bucketName = getBucketName();
+    final String keyName = getKeyName();
+    final String content = "bar";
+    s3Client.createBucket(b -> b.bucket(bucketName));
+
+    PutObjectResponse putObjectResponse = s3Client.putObject(b -> b
+            .bucket(bucketName)
+            .key(keyName)
+            .overrideConfiguration(c ->
+                c.putHeader("Content-Type", "")),
+        RequestBody.fromString(content));
+
+    assertEquals("\"37b51d194a7513e45b56f6524f2d51f2\"",
+        putObjectResponse.eTag());
+  }
+
   static Stream<Arguments> onlyTagKeyCasesV2() {
     Map<String, String> fooBarEmptyBar = new HashMap<>();
     fooBarEmptyBar.put("foo", "bar");
diff --git 
a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/EmptyContentTypeFilter.java
 
b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/EmptyContentTypeFilter.java
index dcd632545d1..a2177cc2f4e 100644
--- 
a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/EmptyContentTypeFilter.java
+++ 
b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/EmptyContentTypeFilter.java
@@ -18,6 +18,7 @@
 package org.apache.hadoop.ozone.s3;
 
 import java.io.IOException;
+import java.util.Collections;
 import java.util.Enumeration;
 import java.util.NoSuchElementException;
 import javax.servlet.Filter;
@@ -56,6 +57,9 @@ public String getHeader(String name) {
           if (name.equalsIgnoreCase("Content-Type")) {
             return null;
           }
+          if (name.equalsIgnoreCase(HeaderPreprocessor.ORIGINAL_CONTENT_TYPE)) 
{
+            return "";
+          }
           return super.getHeader(name);
         }
 
@@ -64,6 +68,9 @@ public Enumeration<String> getHeaders(String name) {
           if ("Content-Type".equalsIgnoreCase(name)) {
             return null;
           }
+          if (HeaderPreprocessor.ORIGINAL_CONTENT_TYPE.equalsIgnoreCase(name)) 
{
+            return Collections.enumeration(Collections.singletonList(""));
+          }
           return super.getHeaders(name);
         }
 
@@ -84,8 +91,8 @@ public void destroy() {
   }
 
   /**
-   * Enumeration Wrapper which removes Content-Type from the original
-   * enumeration.
+   * Enumeration Wrapper which replaces Content-Type with the internal header
+   * used to preserve its original value.
    */
   public static class EnumerationWrapper implements Enumeration<String> {
 
@@ -93,24 +100,27 @@ public static class EnumerationWrapper implements 
Enumeration<String> {
 
     private String nextElement;
 
+    private boolean contentTypeReplaced;
+
     public EnumerationWrapper(Enumeration<String> original) {
       this.original = original;
       step();
     }
 
     private void step() {
-      if (original.hasMoreElements()) {
+      while (original.hasMoreElements()) {
         nextElement = original.nextElement();
-      } else {
-        nextElement = null;
-      }
-      if ("Content-Type".equalsIgnoreCase(nextElement)) {
-        if (original.hasMoreElements()) {
-          nextElement = original.nextElement();
+        if ("Content-Type".equalsIgnoreCase(nextElement)) {
+          if (!contentTypeReplaced) {
+            nextElement = HeaderPreprocessor.ORIGINAL_CONTENT_TYPE;
+            contentTypeReplaced = true;
+            return;
+          }
         } else {
-          nextElement = null;
+          return;
         }
       }
+      nextElement = null;
     }
 
     @Override
diff --git 
a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/TestEmptyContentTypeFilter.java
 
b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/TestEmptyContentTypeFilter.java
index ab9f1dfb0e2..1b5479f850b 100644
--- 
a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/TestEmptyContentTypeFilter.java
+++ 
b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/TestEmptyContentTypeFilter.java
@@ -19,9 +19,15 @@
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNull;
 import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
 
+import java.util.Collections;
 import java.util.Vector;
+import java.util.concurrent.atomic.AtomicReference;
+import javax.servlet.http.HttpServletRequest;
 import org.apache.hadoop.ozone.s3.EmptyContentTypeFilter.EnumerationWrapper;
 import org.junit.jupiter.api.Test;
 
@@ -42,6 +48,9 @@ public void enumerationWithContentType() {
         new EnumerationWrapper(values.elements());
 
     assertTrue(enumerationWrapper.hasMoreElements());
+    assertEquals(HeaderPreprocessor.ORIGINAL_CONTENT_TYPE,
+        enumerationWrapper.nextElement());
+    assertTrue(enumerationWrapper.hasMoreElements());
     assertEquals("1", enumerationWrapper.nextElement());
     assertTrue(enumerationWrapper.hasMoreElements());
     assertEquals("2", enumerationWrapper.nextElement());
@@ -56,7 +65,33 @@ public void enumerationWithOneContentType() {
     final EnumerationWrapper enumerationWrapper =
         new EnumerationWrapper(values.elements());
 
+    assertTrue(enumerationWrapper.hasMoreElements());
+    assertEquals(HeaderPreprocessor.ORIGINAL_CONTENT_TYPE,
+        enumerationWrapper.nextElement());
     assertFalse(enumerationWrapper.hasMoreElements());
   }
 
+  @Test
+  public void preserveEmptyContentType() throws Exception {
+    HttpServletRequest request = mock(HttpServletRequest.class);
+    when(request.getContentType()).thenReturn("");
+    when(request.getHeaderNames()).thenReturn(
+        
Collections.enumeration(Collections.singletonList(HeaderPreprocessor.CONTENT_TYPE)));
+
+    AtomicReference<HttpServletRequest> wrappedRequest =
+        new AtomicReference<>();
+    new EmptyContentTypeFilter().doFilter(request, null,
+        (filteredRequest, response) -> wrappedRequest.set(
+            (HttpServletRequest) filteredRequest));
+
+    assertNull(wrappedRequest.get().getContentType());
+    
assertNull(wrappedRequest.get().getHeader(HeaderPreprocessor.CONTENT_TYPE));
+    assertEquals("", 
wrappedRequest.get().getHeader(HeaderPreprocessor.ORIGINAL_CONTENT_TYPE));
+    assertEquals(Collections.singletonList(""),
+        
Collections.list(wrappedRequest.get().getHeaders(HeaderPreprocessor.ORIGINAL_CONTENT_TYPE)));
+    assertEquals(Collections.singletonList(
+        HeaderPreprocessor.ORIGINAL_CONTENT_TYPE), Collections.list(
+        wrappedRequest.get().getHeaderNames()));
+  }
+
 }
diff --git 
a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/signature/TestAWSSignatureProcessor.java
 
b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/signature/TestAWSSignatureProcessor.java
index 2e2e259d112..7f9681054d2 100644
--- 
a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/signature/TestAWSSignatureProcessor.java
+++ 
b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/signature/TestAWSSignatureProcessor.java
@@ -20,6 +20,7 @@
 import static 
org.apache.hadoop.ozone.s3.endpoint.EndpointTestUtils.assertErrorResponse;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
@@ -27,6 +28,7 @@
 import javax.ws.rs.core.MultivaluedHashMap;
 import javax.ws.rs.core.MultivaluedMap;
 import javax.ws.rs.core.UriInfo;
+import org.apache.hadoop.ozone.s3.HeaderPreprocessor;
 import org.apache.hadoop.ozone.s3.exception.S3ErrorTable;
 import org.junit.jupiter.api.Test;
 
@@ -46,6 +48,18 @@ public void 
testLowerCaseHeaderMapRemovesKeysCaseInsensitively() {
     assertFalse(headers.containsKey("authorization"));
   }
 
+  @Test
+  public void testRestoreEmptyContentType() {
+    MultivaluedMap<String, String> rawHeaders = new MultivaluedHashMap<>();
+    rawHeaders.putSingle(HeaderPreprocessor.ORIGINAL_CONTENT_TYPE, "");
+
+    AWSSignatureProcessor.LowerCaseKeyStringMap headers =
+        AWSSignatureProcessor.LowerCaseKeyStringMap.fromHeaderMap(rawHeaders);
+
+    assertTrue(headers.containsKey(HeaderPreprocessor.CONTENT_TYPE));
+    assertEquals("", headers.get(HeaderPreprocessor.CONTENT_TYPE));
+  }
+
   @Test
   public void testOutOfRangeExpiresPreSignedUrlReturns403() throws Exception {
     // A pre-signed URL whose X-Amz-Expires is out of range must be rejected


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to