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 dcd632545d1e..a2177cc2f4ef 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 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 { @@ -93,24 +100,27 @@ public static class EnumerationWrapper implements Enumeration { private String nextElement; + private boolean contentTypeReplaced; + public EnumerationWrapper(Enumeration 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 ab9f1dfb0e28..1b5479f850b8 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 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 2e2e259d1128..7f9681054d29 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 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