Skip to content

Commit a65355a

Browse files
authored
Fixed an issue in sync clients where empty response payloads could cause a null pointer exception. (#3371)
Before this change, we passed null to response transformers when the HTTP client returned empty. Our built-in response transformers expected non-null, so this would fail. This change passes an empty body to response transformers, instead.
1 parent 26bb6dc commit a65355a

File tree

6 files changed

+71
-15
lines changed

6 files changed

+71
-15
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"type": "bugfix",
3+
"category": "AWS SDK for Java v2",
4+
"contributor": "",
5+
"description": "Fixed an issue in sync clients where empty response payloads could cause a null pointer exception."
6+
}

core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/handler/BaseSyncClientHandler.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,7 @@ private HttpResponseHandlerAdapter(HttpResponseHandler<OutputT> httpResponseHand
203203
@Override
204204
public ReturnT handle(SdkHttpFullResponse response, ExecutionAttributes executionAttributes) throws Exception {
205205
OutputT resp = httpResponseHandler.handle(response, executionAttributes);
206-
return transformResponse(resp, response.content().orElse(null));
206+
return transformResponse(resp, response.content().orElseGet(AbortableInputStream::createEmpty));
207207
}
208208

209209
@Override

core/sdk-core/src/main/java/software/amazon/awssdk/core/sync/ResponseTransformer.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,7 @@ static <ResponseT> ResponseTransformer<ResponseT, ResponseBytes<ResponseT>> toBy
172172
return (response, inputStream) -> {
173173
try {
174174
InterruptMonitor.checkInterrupted();
175-
return ResponseBytes.fromByteArray(response, IoUtils.toByteArray(inputStream));
175+
return ResponseBytes.fromByteArrayUnsafe(response, IoUtils.toByteArray(inputStream));
176176
} catch (IOException e) {
177177
throw RetryableException.builder().message("Failed to read response.").cause(e).build();
178178
}

http-client-spi/src/main/java/software/amazon/awssdk/http/AbortableInputStream.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
import static software.amazon.awssdk.utils.Validate.paramNotNull;
1919

20+
import java.io.ByteArrayInputStream;
2021
import java.io.FilterInputStream;
2122
import java.io.InputStream;
2223
import software.amazon.awssdk.annotations.SdkProtectedApi;
@@ -61,6 +62,10 @@ public static AbortableInputStream create(InputStream delegate) {
6162
return new AbortableInputStream(delegate, () -> { });
6263
}
6364

65+
public static AbortableInputStream createEmpty() {
66+
return create(new ByteArrayInputStream(new byte[0]));
67+
}
68+
6469
@Override
6570
public void abort() {
6671
abortable.abort();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License").
5+
* You may not use this file except in compliance with the License.
6+
* A copy of the License is located at
7+
*
8+
* http://aws.amazon.com/apache2.0
9+
*
10+
* or in the "license" file accompanying this file. This file is distributed
11+
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12+
* express or implied. See the License for the specific language governing
13+
* permissions and limitations under the License.
14+
*/
15+
16+
package software.amazon.awssdk.http.urlconnection;
17+
18+
import static org.assertj.core.api.Assertions.assertThat;
19+
import static software.amazon.awssdk.testutils.service.S3BucketUtils.temporaryBucketName;
20+
21+
import org.junit.AfterClass;
22+
import org.junit.BeforeClass;
23+
import org.junit.Test;
24+
import software.amazon.awssdk.core.sync.RequestBody;
25+
import software.amazon.awssdk.services.s3.S3Client;
26+
27+
public class EmptyFileS3IntegrationTest extends UrlHttpConnectionS3IntegrationTestBase {
28+
private static final String BUCKET = temporaryBucketName(EmptyFileS3IntegrationTest.class);
29+
30+
@BeforeClass
31+
public static void setup() {
32+
createBucket(BUCKET);
33+
}
34+
35+
@AfterClass
36+
public static void cleanup() {
37+
deleteBucketAndAllContents(BUCKET);
38+
}
39+
40+
@Test
41+
public void s3EmptyFileGetAsBytesWorksWithoutChecksumValidationEnabled() {
42+
try (S3Client s3 = s3ClientBuilder().serviceConfiguration(c -> c.checksumValidationEnabled(false))
43+
.build()) {
44+
s3.putObject(r -> r.bucket(BUCKET).key("x"), RequestBody.empty());
45+
assertThat(s3.getObjectAsBytes(r -> r.bucket(BUCKET).key("x")).asUtf8String()).isEmpty();
46+
}
47+
}
48+
49+
@Test
50+
public void s3EmptyFileContentLengthIsCorrectWithoutChecksumValidationEnabled() {
51+
try (S3Client s3 = s3ClientBuilder().serviceConfiguration(c -> c.checksumValidationEnabled(false))
52+
.build()) {
53+
s3.putObject(r -> r.bucket(BUCKET).key("x"), RequestBody.empty());
54+
assertThat(s3.getObject(r -> r.bucket(BUCKET).key("x")).response().contentLength()).isEqualTo(0);
55+
}
56+
}
57+
}

http-clients/url-connection-client/src/it/java/software/amazon/awssdk/http/urlconnection/UrlHttpConnectionS3IntegrationTestBase.java

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -15,32 +15,19 @@
1515

1616
package software.amazon.awssdk.http.urlconnection;
1717

18-
import static org.assertj.core.api.Assertions.assertThat;
19-
2018
import java.util.Iterator;
2119
import java.util.List;
2220
import org.junit.BeforeClass;
23-
import software.amazon.awssdk.core.ClientType;
24-
import software.amazon.awssdk.core.interceptor.Context;
25-
import software.amazon.awssdk.core.interceptor.ExecutionAttributes;
26-
import software.amazon.awssdk.core.interceptor.ExecutionInterceptor;
27-
import software.amazon.awssdk.http.apache.ApacheHttpClient;
2821
import software.amazon.awssdk.regions.Region;
29-
import software.amazon.awssdk.services.s3.S3AsyncClient;
30-
import software.amazon.awssdk.services.s3.S3AsyncClientBuilder;
3122
import software.amazon.awssdk.services.s3.S3Client;
3223
import software.amazon.awssdk.services.s3.S3ClientBuilder;
33-
import software.amazon.awssdk.services.s3.model.BucketLocationConstraint;
34-
import software.amazon.awssdk.services.s3.model.CreateBucketConfiguration;
35-
import software.amazon.awssdk.services.s3.model.CreateBucketRequest;
3624
import software.amazon.awssdk.services.s3.model.DeleteBucketRequest;
3725
import software.amazon.awssdk.services.s3.model.DeleteObjectRequest;
3826
import software.amazon.awssdk.services.s3.model.ListObjectVersionsRequest;
3927
import software.amazon.awssdk.services.s3.model.ListObjectVersionsResponse;
4028
import software.amazon.awssdk.services.s3.model.ListObjectsRequest;
4129
import software.amazon.awssdk.services.s3.model.ListObjectsResponse;
4230
import software.amazon.awssdk.services.s3.model.NoSuchBucketException;
43-
import software.amazon.awssdk.services.s3.model.S3Exception;
4431
import software.amazon.awssdk.services.s3.model.S3Object;
4532
import software.amazon.awssdk.testutils.Waiter;
4633
import software.amazon.awssdk.testutils.service.AwsTestBase;
@@ -79,6 +66,7 @@ protected static void createBucket(String bucket) {
7966
Waiter.run(() -> s3.createBucket(r -> r.bucket(bucket)))
8067
.ignoringException(NoSuchBucketException.class)
8168
.orFail();
69+
s3.waiter().waitUntilBucketExists(r -> r.bucket(bucket));
8270
}
8371

8472
protected static void deleteBucketAndAllContents(String bucketName) {

0 commit comments

Comments
 (0)