Skip to content

Commit d105e7a

Browse files
committed
Throw Crc32MismatchException when response content is absent but x-amz-crc32 is present and is non-zero, instead of silently returning an empty response.
1 parent 60291a4 commit d105e7a

5 files changed

Lines changed: 95 additions & 2 deletions

File tree

core/sdk-core/src/main/java/software/amazon/awssdk/core/http/Crc32Validation.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import java.util.Optional;
2121
import java.util.zip.GZIPInputStream;
2222
import software.amazon.awssdk.annotations.SdkProtectedApi;
23+
import software.amazon.awssdk.core.exception.Crc32MismatchException;
2324
import software.amazon.awssdk.core.internal.util.Crc32ChecksumValidatingInputStream;
2425
import software.amazon.awssdk.http.AbortableInputStream;
2526
import software.amazon.awssdk.http.SdkHttpFullResponse;
@@ -37,6 +38,16 @@ public static SdkHttpFullResponse validate(boolean calculateCrc32FromCompressedD
3738
SdkHttpFullResponse httpResponse) {
3839

3940
if (!httpResponse.content().isPresent()) {
41+
// CRC32 of zero bytes is 0, so a 0 header is a valid match for an empty body.
42+
// A non-zero header with no content means the Crc32 mismatch error.
43+
Optional<Long> expectedChecksum = getCrc32Checksum(httpResponse);
44+
if (expectedChecksum.isPresent() && expectedChecksum.get() != 0L) {
45+
throw Crc32MismatchException.builder()
46+
.message(String.format("Expected %d as the Crc32 checksum but the response "
47+
+ "had no content",
48+
expectedChecksum.get()))
49+
.build();
50+
}
4051
return httpResponse;
4152
}
4253

core/sdk-core/src/test/java/software/amazon/awssdk/core/http/Crc32ValidationTest.java

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
package software.amazon.awssdk.core.http;
1717

1818
import static org.assertj.core.api.Assertions.assertThat;
19+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
1920

2021
import java.io.IOException;
2122
import java.io.InputStream;
@@ -27,6 +28,7 @@
2728
import org.junit.runner.RunWith;
2829
import org.mockito.junit.MockitoJUnitRunner;
2930
import org.unitils.util.ReflectionUtils;
31+
import software.amazon.awssdk.core.exception.Crc32MismatchException;
3032
import software.amazon.awssdk.core.internal.util.Crc32ChecksumValidatingInputStream;
3133
import software.amazon.awssdk.http.AbortableInputStream;
3234
import software.amazon.awssdk.http.SdkHttpFullResponse;
@@ -109,18 +111,41 @@ public void adapt_InvalidGzipContent_ThrowsException() throws UnsupportedEncodin
109111
}
110112

111113
@Test
112-
public void adapt_ResponseWithCrc32Header_And_NoContent_DoesNotThrowNPE() throws UnsupportedEncodingException {
114+
public void adapt_ResponseWithNonZeroCrc32Header_AndNoContent_ThrowsCrc32Mismatch() {
113115
SdkHttpFullResponse httpResponse = SdkHttpFullResponse.builder()
114116
.statusCode(200)
115117
.putHeader("x-amz-crc32", "1234")
116118
.build();
117119

120+
assertThatThrownBy(() -> adapt(httpResponse))
121+
.isInstanceOf(Crc32MismatchException.class)
122+
.hasMessageContaining("1234")
123+
.hasMessageContaining("no content");
124+
}
125+
126+
@Test
127+
public void adapt_ResponseWithZeroCrc32Header_AndNoContent_PassesThrough() {
128+
SdkHttpFullResponse httpResponse = SdkHttpFullResponse.builder()
129+
.statusCode(200)
130+
.putHeader("x-amz-crc32", "0")
131+
.build();
132+
118133
SdkHttpFullResponse adapted = adapt(httpResponse);
119134
assertThat(adapted.content().isPresent()).isFalse();
120135
}
121136

122137
@Test
123-
public void adapt_ResponseGzipEncoding_And_NoContent_DoesNotThrowNPE() throws IOException {
138+
public void adapt_ResponseWithoutCrc32Header_AndNoContent_PassesThrough() {
139+
SdkHttpFullResponse httpResponse = SdkHttpFullResponse.builder()
140+
.statusCode(200)
141+
.build();
142+
143+
SdkHttpFullResponse adapted = adapt(httpResponse);
144+
assertThat(adapted.content().isPresent()).isFalse();
145+
}
146+
147+
@Test
148+
public void adapt_ResponseGzipEncoding_AndNoContent_PassesThrough() throws IOException {
124149
SdkHttpFullResponse httpResponse = SdkHttpFullResponse.builder()
125150
.statusCode(200)
126151
.putHeader("Content-Encoding", "gzip")
@@ -130,6 +155,18 @@ public void adapt_ResponseGzipEncoding_And_NoContent_DoesNotThrowNPE() throws IO
130155
assertThat(adapted.content().isPresent()).isFalse();
131156
}
132157

158+
@Test
159+
public void adapt_ResponseGzip_NonZeroCrc32_AndNoContent_ThrowsCrc32Mismatch() {
160+
SdkHttpFullResponse httpResponse = SdkHttpFullResponse.builder()
161+
.statusCode(200)
162+
.putHeader("Content-Encoding", "gzip")
163+
.putHeader("x-amz-crc32", "1234")
164+
.build();
165+
166+
assertThatThrownBy(() -> adapt(httpResponse))
167+
.isInstanceOf(Crc32MismatchException.class);
168+
}
169+
133170
private SdkHttpFullResponse adapt(SdkHttpFullResponse httpResponse) {
134171
return Crc32Validation.validate(false, httpResponse);
135172
}

test/protocol-tests/src/test/java/software/amazon/awssdk/protocol/tests/crc32/AwsJsonAsyncCrc32ChecksumTests.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,4 +166,15 @@ public void useGzipFalse_WhenCrc32IsInvalid_ThrowException() throws Exception {
166166
assertThatThrownBy(() -> jsonRpcAsync.allTypes(AllTypesRequest.builder().build()).get())
167167
.hasRootCauseInstanceOf(Crc32MismatchException.class);
168168
}
169+
170+
@Test
171+
public void emptyBody_WhenCrc32HeaderIsNonZero_ThrowsCrc32Mismatch() {
172+
stubFor(post(urlEqualTo("/")).willReturn(aResponse()
173+
.withStatus(200)
174+
.withHeader("x-amz-crc32", JSON_BODY_Crc32_CHECKSUM)
175+
.withHeader("Content-Length", "0")));
176+
177+
assertThatThrownBy(() -> jsonRpcAsync.allTypes(AllTypesRequest.builder().build()).get())
178+
.hasRootCauseInstanceOf(Crc32MismatchException.class);
179+
}
169180
}

test/protocol-tests/src/test/java/software/amazon/awssdk/protocol/tests/crc32/AwsJsonCrc32ChecksumTests.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import org.junit.Test;
3030
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
3131
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
32+
import software.amazon.awssdk.core.exception.Crc32MismatchException;
3233
import software.amazon.awssdk.core.exception.SdkClientException;
3334
import software.amazon.awssdk.regions.Region;
3435
import software.amazon.awssdk.services.protocoljsonrpc.ProtocolJsonRpcClient;
@@ -183,4 +184,20 @@ public void useGzipFalse_WhenCrc32IsInvalid_ThrowException() {
183184

184185
jsonRpc.allTypes(AllTypesRequest.builder().build());
185186
}
187+
188+
@Test(expected = Crc32MismatchException.class)
189+
public void emptyBody_WhenCrc32HeaderIsNonZero_ThrowsCrc32Mismatch() {
190+
stubFor(post(urlEqualTo("/")).willReturn(aResponse()
191+
.withStatus(200)
192+
.withHeader("x-amz-crc32", JSON_BODY_Crc32_CHECKSUM)
193+
.withHeader("Content-Length", "0")));
194+
195+
ProtocolJsonRpcClient jsonRpc = ProtocolJsonRpcClient.builder()
196+
.credentialsProvider(FAKE_CREDENTIALS_PROVIDER)
197+
.region(Region.US_EAST_1)
198+
.endpointOverride(URI.create("http://localhost:" + mockServer.port()))
199+
.build();
200+
201+
jsonRpc.allTypes(AllTypesRequest.builder().build());
202+
}
186203
}

test/protocol-tests/src/test/java/software/amazon/awssdk/protocol/tests/crc32/RestJsonCrc32ChecksumTests.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
3131
import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
3232
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
33+
import software.amazon.awssdk.core.exception.Crc32MismatchException;
3334
import software.amazon.awssdk.core.exception.SdkClientException;
3435
import software.amazon.awssdk.regions.Region;
3536
import software.amazon.awssdk.services.protocolrestjson.ProtocolRestJsonClient;
@@ -153,4 +154,20 @@ public void useGzipFalse_WhenCrc32IsInvalid_ThrowException() {
153154

154155
client.allTypes(AllTypesRequest.builder().build());
155156
}
157+
158+
@Test(expected = Crc32MismatchException.class)
159+
public void emptyBody_WhenCrc32HeaderIsNonZero_ThrowsCrc32Mismatch() {
160+
stubFor(post(urlEqualTo(RESOURCE_PATH)).willReturn(aResponse()
161+
.withStatus(200)
162+
.withHeader("x-amz-crc32", JSON_BODY_Crc32_CHECKSUM)
163+
.withHeader("Content-Length", "0")));
164+
165+
ProtocolRestJsonClient client = ProtocolRestJsonClient.builder()
166+
.credentialsProvider(FAKE_CREDENTIALS_PROVIDER)
167+
.region(Region.US_EAST_1)
168+
.endpointOverride(URI.create("http://localhost:" + mockServer.port()))
169+
.build();
170+
171+
client.allTypes(AllTypesRequest.builder().build());
172+
}
156173
}

0 commit comments

Comments
 (0)