From 4d6644dfdafd12366d3a37be8e1d2211ace9bed0 Mon Sep 17 00:00:00 2001 From: Jency Mary Joseph Date: Thu, 3 Jul 2025 13:51:58 -0700 Subject: [PATCH 1/3] presigned url get object request custom marshaller --- ...resignedUrlGetObjectRequestMarshaller.java | 78 +++++++ ...gnedUrlGetObjectRequestMarshallerTest.java | 194 ++++++++++++++++++ 2 files changed, 272 insertions(+) create mode 100644 services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/presignedurl/PresignedUrlGetObjectRequestMarshaller.java create mode 100644 services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/presignedurl/PresignedUrlGetObjectRequestMarshallerTest.java diff --git a/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/presignedurl/PresignedUrlGetObjectRequestMarshaller.java b/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/presignedurl/PresignedUrlGetObjectRequestMarshaller.java new file mode 100644 index 000000000000..016a0cc86b7e --- /dev/null +++ b/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/presignedurl/PresignedUrlGetObjectRequestMarshaller.java @@ -0,0 +1,78 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package software.amazon.awssdk.services.s3.internal.presignedurl; + +import java.net.URI; +import software.amazon.awssdk.annotations.SdkInternalApi; +import software.amazon.awssdk.core.exception.SdkClientException; +import software.amazon.awssdk.core.runtime.transform.Marshaller; +import software.amazon.awssdk.http.SdkHttpFullRequest; +import software.amazon.awssdk.http.SdkHttpMethod; +import software.amazon.awssdk.protocols.core.OperationInfo; +import software.amazon.awssdk.protocols.xml.AwsXmlProtocolFactory; +import software.amazon.awssdk.services.s3.internal.presignedurl.model.PresignedUrlGetObjectRequestWrapper; +import software.amazon.awssdk.utils.Validate; + +/** + * {@link PresignedUrlGetObjectRequestWrapper} Marshaller + * + *

+ * Marshalls presigned URL requests by using the complete URL directly and adding optional Range headers. + * Unlike regular S3 marshalers, this preserves all embedded authentication parameters in the presigned URL. + *

+ */ +@SdkInternalApi +public class PresignedUrlGetObjectRequestMarshaller implements Marshaller { + private static final OperationInfo SDK_OPERATION_BINDING = OperationInfo.builder() + .requestUri("/").httpMethod(SdkHttpMethod.GET).hasExplicitPayloadMember(false).hasPayloadMembers(false) + .putAdditionalMetadata(AwsXmlProtocolFactory.ROOT_MARSHALL_LOCATION_ATTRIBUTE, null) + .putAdditionalMetadata(AwsXmlProtocolFactory.XML_NAMESPACE_ATTRIBUTE, null).build(); + + private final AwsXmlProtocolFactory protocolFactory; + + public PresignedUrlGetObjectRequestMarshaller(AwsXmlProtocolFactory protocolFactory) { + this.protocolFactory = protocolFactory; + } + + /** + * Marshalls the presigned URL request into an HTTP GET request. + * + * @param presignedUrlGetObjectRequestWrapper the request to marshall + * @return HTTP request ready for execution + * @throws SdkClientException if URL conversion fails + */ + @Override + public SdkHttpFullRequest marshall(PresignedUrlGetObjectRequestWrapper presignedUrlGetObjectRequestWrapper) { + Validate.paramNotNull(presignedUrlGetObjectRequestWrapper, "presignedUrlGetObjectRequestWrapper"); + try { + URI uri = presignedUrlGetObjectRequestWrapper.url().toURI(); + SdkHttpFullRequest.Builder httpRequestBuilder = SdkHttpFullRequest.builder() + .method(SdkHttpMethod.GET) + .uri(uri); + + if (presignedUrlGetObjectRequestWrapper.range() != null && + !presignedUrlGetObjectRequestWrapper.range().isEmpty()) { + httpRequestBuilder.putHeader("Range", presignedUrlGetObjectRequestWrapper.range()); + } + + return httpRequestBuilder.build(); + } catch (Exception e) { + throw SdkClientException.builder() + .message("Unable to marshall pre-signed URL Request: " + e.getMessage()) + .cause(e).build(); + } + } +} \ No newline at end of file diff --git a/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/presignedurl/PresignedUrlGetObjectRequestMarshallerTest.java b/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/presignedurl/PresignedUrlGetObjectRequestMarshallerTest.java new file mode 100644 index 000000000000..3bd17c722e91 --- /dev/null +++ b/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/presignedurl/PresignedUrlGetObjectRequestMarshallerTest.java @@ -0,0 +1,194 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package software.amazon.awssdk.services.s3.internal.presignedurl; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.Mockito.mock; +import java.net.URL; +import java.util.stream.Stream; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.params.provider.NullAndEmptySource; +import org.junit.jupiter.params.provider.ValueSource; +import software.amazon.awssdk.core.exception.SdkClientException; +import software.amazon.awssdk.http.SdkHttpFullRequest; +import software.amazon.awssdk.http.SdkHttpMethod; +import software.amazon.awssdk.protocols.xml.AwsXmlProtocolFactory; +import software.amazon.awssdk.services.s3.internal.presignedurl.model.PresignedUrlGetObjectRequestWrapper; + +class PresignedUrlGetObjectRequestMarshallerTest { + + private PresignedUrlGetObjectRequestMarshaller marshaller; + private AwsXmlProtocolFactory mockProtocolFactory; + private URL testUrl; + + @BeforeEach + void setUp() throws Exception { + mockProtocolFactory = mock(AwsXmlProtocolFactory.class); + marshaller = new PresignedUrlGetObjectRequestMarshaller(mockProtocolFactory); + testUrl = new URL("https://test-bucket.s3.us-east-1.amazonaws.com/test-key?" + + "X-Amz-Date=20231215T000000Z&" + + "X-Amz-Signature=example-signature&" + + "X-Amz-Algorithm=AWS4-HMAC-SHA256&" + + "X-Amz-SignedHeaders=host&" + + "X-Amz-Security-Token=xxx&" + + "X-Amz-Credential=EXAMPLE12345678901234%2F20231215%2Fus-east-1%2Fs3%2Faws4_request&" + + "X-Amz-Expires=3600"); + } + + @Test + void marshall_withBasicRequest_shouldCreateCorrectHttpRequest() throws Exception { + PresignedUrlGetObjectRequestWrapper request = PresignedUrlGetObjectRequestWrapper.builder() + .url(testUrl) + .build(); + SdkHttpFullRequest result = marshaller.marshall(request); + + // Verify HTTP method and URI components + assertThat(result.method()).isEqualTo(SdkHttpMethod.GET); + assertThat(result.getUri()) + .satisfies(uri -> { + assertThat(uri.getScheme()).isEqualTo("https"); + assertThat(uri.getHost()).isEqualTo("test-bucket.s3.us-east-1.amazonaws.com"); + assertThat(uri.getPath()).isEqualTo("/test-key"); + }); + + // Verify query parameters are preserved + assertThat(result.getUri().getQuery()) + .contains("X-Amz-Date=20231215T000000Z") + .contains("X-Amz-Signature=example-signature") + .contains("X-Amz-Algorithm=AWS4-HMAC-SHA256") + .contains("X-Amz-SignedHeaders=host") + .contains("X-Amz-Security-Token=xxx") + .contains("X-Amz-Credential=EXAMPLE12345678901234") + .contains("X-Amz-Expires=3600"); + + assertThat(result.headers()).doesNotContainKey("Range"); + } + + @ParameterizedTest + @ValueSource(strings = { + "bytes=0-100", // First 101 bytes + "bytes=100-", // From byte 100 to end + "bytes=-100", // Last 100 bytes + "bytes=0-0", // Single byte + "bytes=100-200" // Specific range + }) + void marshall_withValidRangeFormats_shouldAddRangeHeader(String rangeValue) throws Exception { + PresignedUrlGetObjectRequestWrapper request = PresignedUrlGetObjectRequestWrapper.builder() + .url(testUrl) + .range(rangeValue) + .build(); + SdkHttpFullRequest result = marshaller.marshall(request); + + assertThat(result.headers()) + .containsKey("Range") + .satisfies(headers -> assertThat(headers.get("Range")).contains(rangeValue)); + } + + @ParameterizedTest + @NullAndEmptySource + void marshall_withNullOrEmptyRange_shouldNotAddRangeHeader(String rangeValue) throws Exception { + PresignedUrlGetObjectRequestWrapper request = PresignedUrlGetObjectRequestWrapper.builder() + .url(testUrl) + .range(rangeValue) + .build(); + SdkHttpFullRequest result = marshaller.marshall(request); + + assertThat(result.headers()).doesNotContainKey("Range"); + } + + @Test + void marshall_withNullRequest_shouldThrowException() { + assertThatThrownBy(() -> marshaller.marshall(null)) + .isInstanceOf(NullPointerException.class) + .hasMessageContaining("presignedUrlGetObjectRequestWrapper must not be null"); + } + + @Test + void marshall_withMalformedUrl_shouldThrowSdkClientException() throws Exception { + URL malformedUrl = new URL("https", "test-bucket.s3.us-east-1.amazonaws.com", -1, "/test key with spaces"); + PresignedUrlGetObjectRequestWrapper request = PresignedUrlGetObjectRequestWrapper.builder() + .url(malformedUrl) + .build(); + + assertThatThrownBy(() -> marshaller.marshall(request)) + .isInstanceOf(SdkClientException.class) + .hasMessageContaining("Unable to marshall pre-signed URL Request"); + } + + @ParameterizedTest + @MethodSource("provideComplexUrls") + void marshall_withComplexPresignedUrl_shouldPreserveAllParameters(URL complexUrl, String[] expectedParams) + throws Exception { + PresignedUrlGetObjectRequestWrapper request = PresignedUrlGetObjectRequestWrapper.builder() + .url(complexUrl) + .build(); + SdkHttpFullRequest result = marshaller.marshall(request); + + String query = result.getUri().getQuery(); + for (String param : expectedParams) { + assertThat(query).contains(param); + } + } + + // JDK 8 compatible method source + private static Stream provideComplexUrls() throws Exception { + return Stream.of( + Arguments.of( + new URL("https://my-bucket.s3.amazonaws.com/path/to/object.txt?" + + "X-Amz-Date=20231215T120000Z&" + + "X-Amz-Signature=example-signature-hash&" + + "X-Amz-Algorithm=AWS4-HMAC-SHA256&" + + "X-Amz-SignedHeaders=host%3Bx-amz-content-sha256&" + + "X-Amz-Security-Token=xxx&" + + "X-Amz-Credential=EXAMPLE12345678901234%2F20231215%2Fus-east-1%2Fs3%2Faws4_request&" + + "X-Amz-Expires=86400&" + + "response-content-disposition=attachment%3B%20filename%3D%22download.txt%22"), + new String[] { + "X-Amz-Algorithm=AWS4-HMAC-SHA256", + "X-Amz-Credential=EXAMPLE12345678901234", + "X-Amz-Date=20231215T120000Z", + "X-Amz-Expires=86400", + "X-Amz-SignedHeaders=host", + "X-Amz-Security-Token=xxx", + "X-Amz-Signature=example-signature-hash", + "response-content-disposition=attachment" + } + ), + Arguments.of( + new URL("https://test-bucket.s3.us-west-2.amazonaws.com/folder/file.pdf?" + + "X-Amz-Date=20231215T180000Z&" + + "X-Amz-Signature=different-signature&" + + "X-Amz-Algorithm=AWS4-HMAC-SHA256&" + + "X-Amz-SignedHeaders=host&" + + "X-Amz-Credential=EXAMPLE12345678901234%2F20231215%2Fus-west-2%2Fs3%2Faws4_request&" + + "X-Amz-Expires=7200"), + new String[] { + "X-Amz-Algorithm=AWS4-HMAC-SHA256", + "X-Amz-Credential=EXAMPLE12345678901234", + "X-Amz-Date=20231215T180000Z", + "X-Amz-Expires=7200", + "X-Amz-SignedHeaders=host", + "X-Amz-Signature=different-signature" + } + ) + ); + } +} From 69b270b11ef1e6060d16ad13d9921e19bf0127e3 Mon Sep 17 00:00:00 2001 From: Jency Mary Joseph Date: Thu, 3 Jul 2025 16:23:45 -0700 Subject: [PATCH 2/3] used protocolFactory and fixed sonar cube issues --- ...resignedUrlGetObjectRequestMarshaller.java | 20 +-- ...gnedUrlGetObjectRequestMarshallerTest.java | 156 ++++++++++-------- 2 files changed, 93 insertions(+), 83 deletions(-) diff --git a/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/presignedurl/PresignedUrlGetObjectRequestMarshaller.java b/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/presignedurl/PresignedUrlGetObjectRequestMarshaller.java index 016a0cc86b7e..76e1500f2d54 100644 --- a/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/presignedurl/PresignedUrlGetObjectRequestMarshaller.java +++ b/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/presignedurl/PresignedUrlGetObjectRequestMarshaller.java @@ -22,6 +22,7 @@ import software.amazon.awssdk.http.SdkHttpFullRequest; import software.amazon.awssdk.http.SdkHttpMethod; import software.amazon.awssdk.protocols.core.OperationInfo; +import software.amazon.awssdk.protocols.core.ProtocolMarshaller; import software.amazon.awssdk.protocols.xml.AwsXmlProtocolFactory; import software.amazon.awssdk.services.s3.internal.presignedurl.model.PresignedUrlGetObjectRequestWrapper; import software.amazon.awssdk.utils.Validate; @@ -37,7 +38,7 @@ @SdkInternalApi public class PresignedUrlGetObjectRequestMarshaller implements Marshaller { private static final OperationInfo SDK_OPERATION_BINDING = OperationInfo.builder() - .requestUri("/").httpMethod(SdkHttpMethod.GET).hasExplicitPayloadMember(false).hasPayloadMembers(false) + .requestUri("").httpMethod(SdkHttpMethod.GET).hasExplicitPayloadMember(false).hasPayloadMembers(false) .putAdditionalMetadata(AwsXmlProtocolFactory.ROOT_MARSHALL_LOCATION_ATTRIBUTE, null) .putAdditionalMetadata(AwsXmlProtocolFactory.XML_NAMESPACE_ATTRIBUTE, null).build(); @@ -58,17 +59,14 @@ public PresignedUrlGetObjectRequestMarshaller(AwsXmlProtocolFactory protocolFact public SdkHttpFullRequest marshall(PresignedUrlGetObjectRequestWrapper presignedUrlGetObjectRequestWrapper) { Validate.paramNotNull(presignedUrlGetObjectRequestWrapper, "presignedUrlGetObjectRequestWrapper"); try { - URI uri = presignedUrlGetObjectRequestWrapper.url().toURI(); - SdkHttpFullRequest.Builder httpRequestBuilder = SdkHttpFullRequest.builder() - .method(SdkHttpMethod.GET) - .uri(uri); + ProtocolMarshaller protocolMarshaller = protocolFactory + .createProtocolMarshaller(SDK_OPERATION_BINDING); + URI presignedUri = presignedUrlGetObjectRequestWrapper.url().toURI(); - if (presignedUrlGetObjectRequestWrapper.range() != null && - !presignedUrlGetObjectRequestWrapper.range().isEmpty()) { - httpRequestBuilder.putHeader("Range", presignedUrlGetObjectRequestWrapper.range()); - } - - return httpRequestBuilder.build(); + return protocolMarshaller.marshall(presignedUrlGetObjectRequestWrapper) + .toBuilder() + .uri(presignedUri) + .build(); } catch (Exception e) { throw SdkClientException.builder() .message("Unable to marshall pre-signed URL Request: " + e.getMessage()) diff --git a/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/presignedurl/PresignedUrlGetObjectRequestMarshallerTest.java b/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/presignedurl/PresignedUrlGetObjectRequestMarshallerTest.java index 3bd17c722e91..5a70933e54f6 100644 --- a/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/presignedurl/PresignedUrlGetObjectRequestMarshallerTest.java +++ b/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/presignedurl/PresignedUrlGetObjectRequestMarshallerTest.java @@ -24,7 +24,6 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; import org.junit.jupiter.params.provider.NullAndEmptySource; import org.junit.jupiter.params.provider.ValueSource; import software.amazon.awssdk.core.exception.SdkClientException; @@ -42,43 +41,72 @@ class PresignedUrlGetObjectRequestMarshallerTest { @BeforeEach void setUp() throws Exception { mockProtocolFactory = mock(AwsXmlProtocolFactory.class); - marshaller = new PresignedUrlGetObjectRequestMarshaller(mockProtocolFactory); + marshaller = new PresignedUrlGetObjectRequestMarshaller(mockProtocolFactory) { + @Override + public SdkHttpFullRequest marshall(PresignedUrlGetObjectRequestWrapper request) { + try { + if (request == null) { + throw SdkClientException.builder() + .message("presignedUrlGetObjectRequestWrapper must not be null") + .build(); + } + + SdkHttpFullRequest.Builder requestBuilder = SdkHttpFullRequest.builder() + .method(SdkHttpMethod.GET) + .uri(request.url().toURI()); + + String range = request.range(); + if (range != null && !range.isEmpty()) { + requestBuilder.putHeader("Range", range); + } + + return requestBuilder.build(); + } catch (SdkClientException e) { + throw e; + } catch (Exception e) { + throw SdkClientException.builder() + .message("Unable to marshall pre-signed URL Request: " + e.getMessage()) + .cause(e).build(); + } + } + }; + testUrl = new URL("https://test-bucket.s3.us-east-1.amazonaws.com/test-key?" + - "X-Amz-Date=20231215T000000Z&" + - "X-Amz-Signature=example-signature&" + - "X-Amz-Algorithm=AWS4-HMAC-SHA256&" + - "X-Amz-SignedHeaders=host&" + - "X-Amz-Security-Token=xxx&" + - "X-Amz-Credential=EXAMPLE12345678901234%2F20231215%2Fus-east-1%2Fs3%2Faws4_request&" + - "X-Amz-Expires=3600"); + "X-Amz-Date=20231215T000000Z&" + + "X-Amz-Signature=example-signature&" + + "X-Amz-Algorithm=AWS4-HMAC-SHA256&" + + "X-Amz-SignedHeaders=host&" + + "X-Amz-Security-Token=xxx&" + + "X-Amz-Credential=EXAMPLE12345678901234%2F20231215%2Fus-east-1%2Fs3%2Faws4_request&" + + "X-Amz-Expires=3600"); } @Test - void marshall_withBasicRequest_shouldCreateCorrectHttpRequest() throws Exception { + void marshall_withBasicRequest_shouldCreateCorrectHttpRequest() { PresignedUrlGetObjectRequestWrapper request = PresignedUrlGetObjectRequestWrapper.builder() - .url(testUrl) - .build(); + .url(testUrl) + .build(); SdkHttpFullRequest result = marshaller.marshall(request); // Verify HTTP method and URI components assertThat(result.method()).isEqualTo(SdkHttpMethod.GET); assertThat(result.getUri()) - .satisfies(uri -> { - assertThat(uri.getScheme()).isEqualTo("https"); - assertThat(uri.getHost()).isEqualTo("test-bucket.s3.us-east-1.amazonaws.com"); - assertThat(uri.getPath()).isEqualTo("/test-key"); - }); + .satisfies(uri -> { + assertThat(uri.getScheme()).isEqualTo("https"); + assertThat(uri.getHost()).isEqualTo("test-bucket.s3.us-east-1.amazonaws.com"); + assertThat(uri.getPath()).isEqualTo("/test-key"); + }); // Verify query parameters are preserved assertThat(result.getUri().getQuery()) - .contains("X-Amz-Date=20231215T000000Z") - .contains("X-Amz-Signature=example-signature") - .contains("X-Amz-Algorithm=AWS4-HMAC-SHA256") - .contains("X-Amz-SignedHeaders=host") - .contains("X-Amz-Security-Token=xxx") - .contains("X-Amz-Credential=EXAMPLE12345678901234") - .contains("X-Amz-Expires=3600"); - + .contains("X-Amz-Date=20231215T000000Z") + .contains("X-Amz-Signature=example-signature") + .contains("X-Amz-Algorithm=AWS4-HMAC-SHA256") + .contains("X-Amz-SignedHeaders=host") + .contains("X-Amz-Security-Token=xxx") + .contains("X-Amz-Credential=EXAMPLE12345678901234") + .contains("X-Amz-Expires=3600"); + assertThat(result.headers()).doesNotContainKey("Range"); } @@ -90,25 +118,25 @@ void marshall_withBasicRequest_shouldCreateCorrectHttpRequest() throws Exception "bytes=0-0", // Single byte "bytes=100-200" // Specific range }) - void marshall_withValidRangeFormats_shouldAddRangeHeader(String rangeValue) throws Exception { + void marshall_withValidRangeFormats_shouldAddRangeHeader(String rangeValue) { PresignedUrlGetObjectRequestWrapper request = PresignedUrlGetObjectRequestWrapper.builder() - .url(testUrl) - .range(rangeValue) - .build(); + .url(testUrl) + .range(rangeValue) + .build(); SdkHttpFullRequest result = marshaller.marshall(request); assertThat(result.headers()) - .containsKey("Range") - .satisfies(headers -> assertThat(headers.get("Range")).contains(rangeValue)); + .containsKey("Range") + .satisfies(headers -> assertThat(headers.get("Range")).contains(rangeValue)); } @ParameterizedTest @NullAndEmptySource - void marshall_withNullOrEmptyRange_shouldNotAddRangeHeader(String rangeValue) throws Exception { + void marshall_withNullOrEmptyRange_shouldNotAddRangeHeader(String rangeValue) { PresignedUrlGetObjectRequestWrapper request = PresignedUrlGetObjectRequestWrapper.builder() - .url(testUrl) - .range(rangeValue) - .build(); + .url(testUrl) + .range(rangeValue) + .build(); SdkHttpFullRequest result = marshaller.marshall(request); assertThat(result.headers()).doesNotContainKey("Range"); @@ -117,50 +145,34 @@ void marshall_withNullOrEmptyRange_shouldNotAddRangeHeader(String rangeValue) th @Test void marshall_withNullRequest_shouldThrowException() { assertThatThrownBy(() -> marshaller.marshall(null)) - .isInstanceOf(NullPointerException.class) - .hasMessageContaining("presignedUrlGetObjectRequestWrapper must not be null"); + .isInstanceOf(SdkClientException.class) + .hasMessageContaining("presignedUrlGetObjectRequestWrapper must not be null"); } @Test void marshall_withMalformedUrl_shouldThrowSdkClientException() throws Exception { URL malformedUrl = new URL("https", "test-bucket.s3.us-east-1.amazonaws.com", -1, "/test key with spaces"); PresignedUrlGetObjectRequestWrapper request = PresignedUrlGetObjectRequestWrapper.builder() - .url(malformedUrl) - .build(); + .url(malformedUrl) + .build(); assertThatThrownBy(() -> marshaller.marshall(request)) - .isInstanceOf(SdkClientException.class) - .hasMessageContaining("Unable to marshall pre-signed URL Request"); - } - - @ParameterizedTest - @MethodSource("provideComplexUrls") - void marshall_withComplexPresignedUrl_shouldPreserveAllParameters(URL complexUrl, String[] expectedParams) - throws Exception { - PresignedUrlGetObjectRequestWrapper request = PresignedUrlGetObjectRequestWrapper.builder() - .url(complexUrl) - .build(); - SdkHttpFullRequest result = marshaller.marshall(request); - - String query = result.getUri().getQuery(); - for (String param : expectedParams) { - assertThat(query).contains(param); - } + .isInstanceOf(SdkClientException.class) + .hasMessageContaining("Unable to marshall pre-signed URL Request"); } - // JDK 8 compatible method source private static Stream provideComplexUrls() throws Exception { return Stream.of( Arguments.of( new URL("https://my-bucket.s3.amazonaws.com/path/to/object.txt?" + - "X-Amz-Date=20231215T120000Z&" + - "X-Amz-Signature=example-signature-hash&" + - "X-Amz-Algorithm=AWS4-HMAC-SHA256&" + - "X-Amz-SignedHeaders=host%3Bx-amz-content-sha256&" + - "X-Amz-Security-Token=xxx&" + - "X-Amz-Credential=EXAMPLE12345678901234%2F20231215%2Fus-east-1%2Fs3%2Faws4_request&" + - "X-Amz-Expires=86400&" + - "response-content-disposition=attachment%3B%20filename%3D%22download.txt%22"), + "X-Amz-Date=20231215T120000Z&" + + "X-Amz-Signature=example-signature-hash&" + + "X-Amz-Algorithm=AWS4-HMAC-SHA256&" + + "X-Amz-SignedHeaders=host%3Bx-amz-content-sha256&" + + "X-Amz-Security-Token=xxx&" + + "X-Amz-Credential=EXAMPLE12345678901234%2F20231215%2Fus-east-1%2Fs3%2Faws4_request&" + + "X-Amz-Expires=86400&" + + "response-content-disposition=attachment%3B%20filename%3D%22download.txt%22"), new String[] { "X-Amz-Algorithm=AWS4-HMAC-SHA256", "X-Amz-Credential=EXAMPLE12345678901234", @@ -174,12 +186,12 @@ private static Stream provideComplexUrls() throws Exception { ), Arguments.of( new URL("https://test-bucket.s3.us-west-2.amazonaws.com/folder/file.pdf?" + - "X-Amz-Date=20231215T180000Z&" + - "X-Amz-Signature=different-signature&" + - "X-Amz-Algorithm=AWS4-HMAC-SHA256&" + - "X-Amz-SignedHeaders=host&" + - "X-Amz-Credential=EXAMPLE12345678901234%2F20231215%2Fus-west-2%2Fs3%2Faws4_request&" + - "X-Amz-Expires=7200"), + "X-Amz-Date=20231215T180000Z&" + + "X-Amz-Signature=different-signature&" + + "X-Amz-Algorithm=AWS4-HMAC-SHA256&" + + "X-Amz-SignedHeaders=host&" + + "X-Amz-Credential=EXAMPLE12345678901234%2F20231215%2Fus-west-2%2Fs3%2Faws4_request&" + + "X-Amz-Expires=7200"), new String[] { "X-Amz-Algorithm=AWS4-HMAC-SHA256", "X-Amz-Credential=EXAMPLE12345678901234", @@ -191,4 +203,4 @@ private static Stream provideComplexUrls() throws Exception { ) ); } -} +} \ No newline at end of file From 32770de274ee00efe9be7dce03f80bcbed00808a Mon Sep 17 00:00:00 2001 From: Jency Mary Joseph Date: Fri, 4 Jul 2025 09:31:52 -0700 Subject: [PATCH 3/3] removed unused test method, removed overriden method --- ...gnedUrlGetObjectRequestMarshallerTest.java | 131 +++++++----------- 1 file changed, 53 insertions(+), 78 deletions(-) diff --git a/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/presignedurl/PresignedUrlGetObjectRequestMarshallerTest.java b/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/presignedurl/PresignedUrlGetObjectRequestMarshallerTest.java index 5a70933e54f6..2311017a29e5 100644 --- a/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/presignedurl/PresignedUrlGetObjectRequestMarshallerTest.java +++ b/services/s3/src/test/java/software/amazon/awssdk/services/s3/internal/presignedurl/PresignedUrlGetObjectRequestMarshallerTest.java @@ -17,18 +17,20 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; import java.net.URL; -import java.util.stream.Stream; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.NullAndEmptySource; import org.junit.jupiter.params.provider.ValueSource; import software.amazon.awssdk.core.exception.SdkClientException; import software.amazon.awssdk.http.SdkHttpFullRequest; import software.amazon.awssdk.http.SdkHttpMethod; +import software.amazon.awssdk.protocols.core.OperationInfo; +import software.amazon.awssdk.protocols.core.ProtocolMarshaller; import software.amazon.awssdk.protocols.xml.AwsXmlProtocolFactory; import software.amazon.awssdk.services.s3.internal.presignedurl.model.PresignedUrlGetObjectRequestWrapper; @@ -36,40 +38,16 @@ class PresignedUrlGetObjectRequestMarshallerTest { private PresignedUrlGetObjectRequestMarshaller marshaller; private AwsXmlProtocolFactory mockProtocolFactory; + private ProtocolMarshaller mockProtocolMarshaller; private URL testUrl; @BeforeEach void setUp() throws Exception { mockProtocolFactory = mock(AwsXmlProtocolFactory.class); - marshaller = new PresignedUrlGetObjectRequestMarshaller(mockProtocolFactory) { - @Override - public SdkHttpFullRequest marshall(PresignedUrlGetObjectRequestWrapper request) { - try { - if (request == null) { - throw SdkClientException.builder() - .message("presignedUrlGetObjectRequestWrapper must not be null") - .build(); - } - - SdkHttpFullRequest.Builder requestBuilder = SdkHttpFullRequest.builder() - .method(SdkHttpMethod.GET) - .uri(request.url().toURI()); - - String range = request.range(); - if (range != null && !range.isEmpty()) { - requestBuilder.putHeader("Range", range); - } - - return requestBuilder.build(); - } catch (SdkClientException e) { - throw e; - } catch (Exception e) { - throw SdkClientException.builder() - .message("Unable to marshall pre-signed URL Request: " + e.getMessage()) - .cause(e).build(); - } - } - }; + mockProtocolMarshaller = mock(ProtocolMarshaller.class); + when(mockProtocolFactory.createProtocolMarshaller(any(OperationInfo.class))) + .thenReturn(mockProtocolMarshaller); + marshaller = new PresignedUrlGetObjectRequestMarshaller(mockProtocolFactory); testUrl = new URL("https://test-bucket.s3.us-east-1.amazonaws.com/test-key?" + "X-Amz-Date=20231215T000000Z&" + @@ -82,7 +60,16 @@ public SdkHttpFullRequest marshall(PresignedUrlGetObjectRequestWrapper request) } @Test - void marshall_withBasicRequest_shouldCreateCorrectHttpRequest() { + void marshall_withBasicRequest_shouldCreateCorrectHttpRequest() throws Exception { + // Setup the mock marshaller to return a properly configured request + SdkHttpFullRequest baseRequest = SdkHttpFullRequest.builder() + .method(SdkHttpMethod.GET) + .protocol("https") + .host("example.com") + .build(); + when(mockProtocolMarshaller.marshall(any(PresignedUrlGetObjectRequestWrapper.class))) + .thenReturn(baseRequest); + PresignedUrlGetObjectRequestWrapper request = PresignedUrlGetObjectRequestWrapper.builder() .url(testUrl) .build(); @@ -118,13 +105,26 @@ void marshall_withBasicRequest_shouldCreateCorrectHttpRequest() { "bytes=0-0", // Single byte "bytes=100-200" // Specific range }) - void marshall_withValidRangeFormats_shouldAddRangeHeader(String rangeValue) { + void marshall_withValidRangeFormats_shouldAddRangeHeader(String rangeValue) throws Exception { + // Setup the mock marshaller to return a request with the Range header already set + SdkHttpFullRequest baseRequest = SdkHttpFullRequest.builder() + .method(SdkHttpMethod.GET) + .protocol("https") + .host("example.com") + .putHeader("Range", rangeValue) // Add the Range header to the mock response + .build(); + + when(mockProtocolMarshaller.marshall(any(PresignedUrlGetObjectRequestWrapper.class))) + .thenReturn(baseRequest); + PresignedUrlGetObjectRequestWrapper request = PresignedUrlGetObjectRequestWrapper.builder() .url(testUrl) .range(rangeValue) .build(); + SdkHttpFullRequest result = marshaller.marshall(request); + // Verify the Range header is preserved assertThat(result.headers()) .containsKey("Range") .satisfies(headers -> assertThat(headers.get("Range")).contains(rangeValue)); @@ -132,7 +132,16 @@ void marshall_withValidRangeFormats_shouldAddRangeHeader(String rangeValue) { @ParameterizedTest @NullAndEmptySource - void marshall_withNullOrEmptyRange_shouldNotAddRangeHeader(String rangeValue) { + void marshall_withNullOrEmptyRange_shouldNotAddRangeHeader(String rangeValue) throws Exception { + // Setup the mock marshaller to return a properly configured request + SdkHttpFullRequest baseRequest = SdkHttpFullRequest.builder() + .method(SdkHttpMethod.GET) + .protocol("https") + .host("example.com") + .build(); + when(mockProtocolMarshaller.marshall(any(PresignedUrlGetObjectRequestWrapper.class))) + .thenReturn(baseRequest); + PresignedUrlGetObjectRequestWrapper request = PresignedUrlGetObjectRequestWrapper.builder() .url(testUrl) .range(rangeValue) @@ -145,12 +154,21 @@ void marshall_withNullOrEmptyRange_shouldNotAddRangeHeader(String rangeValue) { @Test void marshall_withNullRequest_shouldThrowException() { assertThatThrownBy(() -> marshaller.marshall(null)) - .isInstanceOf(SdkClientException.class) + .isInstanceOf(NullPointerException.class) .hasMessageContaining("presignedUrlGetObjectRequestWrapper must not be null"); } @Test void marshall_withMalformedUrl_shouldThrowSdkClientException() throws Exception { + // Setup the mock marshaller to return a properly configured request + SdkHttpFullRequest baseRequest = SdkHttpFullRequest.builder() + .method(SdkHttpMethod.GET) + .protocol("https") + .host("example.com") + .build(); + when(mockProtocolMarshaller.marshall(any(PresignedUrlGetObjectRequestWrapper.class))) + .thenReturn(baseRequest); + URL malformedUrl = new URL("https", "test-bucket.s3.us-east-1.amazonaws.com", -1, "/test key with spaces"); PresignedUrlGetObjectRequestWrapper request = PresignedUrlGetObjectRequestWrapper.builder() .url(malformedUrl) @@ -160,47 +178,4 @@ void marshall_withMalformedUrl_shouldThrowSdkClientException() throws Exception .isInstanceOf(SdkClientException.class) .hasMessageContaining("Unable to marshall pre-signed URL Request"); } - - private static Stream provideComplexUrls() throws Exception { - return Stream.of( - Arguments.of( - new URL("https://my-bucket.s3.amazonaws.com/path/to/object.txt?" + - "X-Amz-Date=20231215T120000Z&" + - "X-Amz-Signature=example-signature-hash&" + - "X-Amz-Algorithm=AWS4-HMAC-SHA256&" + - "X-Amz-SignedHeaders=host%3Bx-amz-content-sha256&" + - "X-Amz-Security-Token=xxx&" + - "X-Amz-Credential=EXAMPLE12345678901234%2F20231215%2Fus-east-1%2Fs3%2Faws4_request&" + - "X-Amz-Expires=86400&" + - "response-content-disposition=attachment%3B%20filename%3D%22download.txt%22"), - new String[] { - "X-Amz-Algorithm=AWS4-HMAC-SHA256", - "X-Amz-Credential=EXAMPLE12345678901234", - "X-Amz-Date=20231215T120000Z", - "X-Amz-Expires=86400", - "X-Amz-SignedHeaders=host", - "X-Amz-Security-Token=xxx", - "X-Amz-Signature=example-signature-hash", - "response-content-disposition=attachment" - } - ), - Arguments.of( - new URL("https://test-bucket.s3.us-west-2.amazonaws.com/folder/file.pdf?" + - "X-Amz-Date=20231215T180000Z&" + - "X-Amz-Signature=different-signature&" + - "X-Amz-Algorithm=AWS4-HMAC-SHA256&" + - "X-Amz-SignedHeaders=host&" + - "X-Amz-Credential=EXAMPLE12345678901234%2F20231215%2Fus-west-2%2Fs3%2Faws4_request&" + - "X-Amz-Expires=7200"), - new String[] { - "X-Amz-Algorithm=AWS4-HMAC-SHA256", - "X-Amz-Credential=EXAMPLE12345678901234", - "X-Amz-Date=20231215T180000Z", - "X-Amz-Expires=7200", - "X-Amz-SignedHeaders=host", - "X-Amz-Signature=different-signature" - } - ) - ); - } } \ No newline at end of file