Skip to content

Commit 8bdc72b

Browse files
raphkimpalpatim
authored andcommitted
Added local testing support for S3 (#1097)
* Added local testing support for S3
1 parent 752f2f2 commit 8bdc72b

File tree

9 files changed

+315
-12
lines changed

9 files changed

+315
-12
lines changed

aws-android-sdk-s3/pom.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,12 @@
3838
<version>1.10.5</version>
3939
<scope>test</scope>
4040
</dependency>
41+
<dependency>
42+
<groupId>org.robolectric</groupId>
43+
<artifactId>robolectric</artifactId>
44+
<version>3.4</version>
45+
<scope>test</scope>
46+
</dependency>
4147
<dependency>
4248
<groupId>org.hamcrest</groupId>
4349
<artifactId>hamcrest-all</artifactId>

aws-android-sdk-s3/src/main/java/com/amazonaws/mobileconnectors/s3/transferutility/TransferUtility.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
import android.database.Cursor;
2424
import android.net.ConnectivityManager;
2525
import android.net.Uri;
26+
import com.amazonaws.services.s3.S3ClientOptions;
27+
import com.amazonaws.services.s3.internal.Constants;
2628
import org.json.JSONObject;
2729

2830
import com.amazonaws.AmazonWebServiceRequest;
@@ -264,6 +266,23 @@ public TransferUtility build() {
264266
this.s3.setRegion(Region.getRegion(tuConfig.getString("Region")));
265267
this.defaultBucket = tuConfig.getString("Bucket");
266268

269+
// Checks if awsconfiguration.json has local testing flag to dangerously connect to HTTP endpoint.
270+
// Defaults to false unless specified.
271+
final boolean canConnectToHTTPEndpoint = tuConfig.has(Constants.LOCAL_TESTING_FLAG_NAME) ?
272+
tuConfig.getBoolean(Constants.LOCAL_TESTING_FLAG_NAME) : false;
273+
274+
// Mutates AmazonS3Client object to have local endpoint
275+
if (canConnectToHTTPEndpoint) {
276+
this.s3.setEndpoint(Constants.LOCAL_TESTING_ENDPOINT);
277+
this.s3.setS3ClientOptions(S3ClientOptions.builder()
278+
// Prevents reformatting host address to accommodate AWS service hostname pattern
279+
.setPathStyleAccess(true)
280+
// Skips data integrity check after each transfer because correct
281+
// hashing algorithm isn't yet implemented in local storage server
282+
.skipContentMd5Check(true)
283+
.build());
284+
}
285+
267286
TransferUtility.setUserAgentFromConfig(this.awsConfig.getUserAgent());
268287
} catch (Exception e) {
269288
throw new IllegalArgumentException("Failed to read S3TransferUtility "

aws-android-sdk-s3/src/main/java/com/amazonaws/services/s3/AmazonS3Client.java

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,7 @@ public class AmazonS3Client extends AmazonWebServiceClient implements AmazonS3 {
201201
private static final RequestPaymentConfigurationXmlFactory requestPaymentConfigurationXmlFactory = new RequestPaymentConfigurationXmlFactory();
202202

203203
/** S3 specific client configuration options */
204-
private S3ClientOptions clientOptions = new S3ClientOptions();
204+
protected S3ClientOptions clientOptions = new S3ClientOptions();
205205

206206
/** Provider for AWS credentials. */
207207
private final AWSCredentialsProvider awsCredentialsProvider;
@@ -1624,12 +1624,13 @@ public S3Object getObject(GetObjectRequest getObjectRequest)
16241624
ProgressEvent.STARTED_EVENT_CODE);
16251625
}
16261626

1627+
16271628
// The Etag header contains a server-side MD5 of the object. If
16281629
// we're downloading the whole object, by default we wrap the
16291630
// stream in a validator that calculates an MD5 of the downloaded
16301631
// bytes and complains if what we received doesn't match the Etag.
1631-
if (!ServiceUtils.skipMd5CheckPerRequest(getObjectRequest)
1632-
&& !ServiceUtils.skipMd5CheckPerResponse(s3Object.getObjectMetadata())) {
1632+
if (!ServiceUtils.skipMd5CheckPerRequest(getObjectRequest, clientOptions)
1633+
&& !ServiceUtils.skipMd5CheckPerResponse(s3Object.getObjectMetadata(), clientOptions)) {
16331634
byte[] serverSideHash = null;
16341635
final String etag = s3Object.getObjectMetadata().getETag();
16351636
if (etag != null && ServiceUtils.isMultipartUploadETag(etag) == false) {
@@ -1706,7 +1707,7 @@ public S3Object getS3ObjectStream() {
17061707

17071708
@Override
17081709
public boolean needIntegrityCheck() {
1709-
return !ServiceUtils.skipMd5CheckPerRequest(getObjectRequest);
1710+
return !ServiceUtils.skipMd5CheckPerRequest(getObjectRequest, clientOptions);
17101711
}
17111712

17121713
}, mode);
@@ -1810,7 +1811,7 @@ public PutObjectResult putObject(PutObjectRequest putObjectRequest)
18101811
assertParameterNotNull(key, "The key parameter must be specified when uploading an object");
18111812

18121813
final boolean skipContentMd5Check = ServiceUtils
1813-
.skipMd5CheckPerRequest(putObjectRequest);
1814+
.skipMd5CheckPerRequest(putObjectRequest, clientOptions);
18141815

18151816
// If a file is specified for upload, we need to pull some additional
18161817
// information from it to auto-configure a few options
@@ -3854,7 +3855,7 @@ public UploadPartResult uploadPart(UploadPartRequest uploadPartRequest)
38543855

38553856
MD5DigestCalculatingInputStream md5DigestStream = null;
38563857
if (uploadPartRequest.getMd5Digest() == null
3857-
&& !ServiceUtils.skipMd5CheckPerRequest(uploadPartRequest)) {
3858+
&& !ServiceUtils.skipMd5CheckPerRequest(uploadPartRequest, clientOptions)) {
38583859
/*
38593860
* If the user hasn't set the content MD5, then we don't want to
38603861
* buffer the whole stream in memory just to calculate it. Instead,
@@ -3885,7 +3886,7 @@ public UploadPartResult uploadPart(UploadPartRequest uploadPartRequest)
38853886
key);
38863887

38873888
if (metadata != null && md5DigestStream != null
3888-
&& !ServiceUtils.skipMd5CheckPerResponse(metadata)) {
3889+
&& !ServiceUtils.skipMd5CheckPerResponse(metadata, clientOptions)) {
38893890
final byte[] clientSideHash = md5DigestStream.getMd5Digest();
38903891
final byte[] serverSideHash = BinaryUtils.fromHex(metadata.getETag());
38913892

aws-android-sdk-s3/src/main/java/com/amazonaws/services/s3/S3ClientOptions.java

Lines changed: 66 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
*/
2020
public class S3ClientOptions {
2121

22+
/** The default setting for skipping content MD5 check */
23+
public static final boolean DEFAULT_SKIP_CONTENT_MD5_CHECK = false;
2224
/** The default setting for use of path-style access */
2325
public static final boolean DEFAULT_PATH_STYLE_ACCESS = false;
2426
/** The default setting for use of chunked encoding */
@@ -30,6 +32,8 @@ public class S3ClientOptions {
3032
/** S3 dualstack endpoint is by default not enabled */
3133
public static final boolean DEFAULT_DUALSTACK_ENABLED = false;
3234

35+
/** Flag for skipping content MD5 check for local testing */
36+
private boolean skipContentMd5Check;
3337
/** Flag for use of path-style access */
3438
private boolean pathStyleAccess;
3539
private final boolean chunkedEncodingDisabled;
@@ -48,6 +52,7 @@ public static Builder builder() {
4852
* Builder class for S3ClientOptions.
4953
*/
5054
public static final class Builder {
55+
private boolean skipContentMd5Check = DEFAULT_SKIP_CONTENT_MD5_CHECK;
5156
private boolean pathStyleAccess = DEFAULT_PATH_STYLE_ACCESS;
5257
/** Flag for user of chunked encoding */
5358
private boolean chunkedEncodingDisabled = DEFAULT_CHUNKED_ENCODING_DISABLED;
@@ -63,13 +68,34 @@ private Builder() {
6368
* @return S3ClientOptions instance.
6469
*/
6570
public S3ClientOptions build() {
66-
return new S3ClientOptions(pathStyleAccess,
71+
return new S3ClientOptions(skipContentMd5Check,
72+
pathStyleAccess,
6773
chunkedEncodingDisabled,
6874
accelerateModeEnabled,
6975
payloadSigningEnabled,
7076
dualstackEnabled);
7177
}
7278

79+
/**
80+
* <p>
81+
* Configures the client to skip content MD5 check for all requests
82+
* and responses.
83+
* </p>
84+
* <p>
85+
* Setting this flag will allow the client to disregard data transfer
86+
* integrity check by bypassing content MD5 check for all requests
87+
* and responses.
88+
* </p>
89+
*
90+
* @param skipContentMd5Check True to always skip content MD5 check.
91+
* @return this Builder instance that can be used for method chaining
92+
*/
93+
@SuppressWarnings("checkstyle:hiddenfield")
94+
public Builder skipContentMd5Check(boolean skipContentMd5Check) {
95+
this.skipContentMd5Check = skipContentMd5Check;
96+
return this;
97+
}
98+
7399
/**
74100
* <p>
75101
* Configures the client to use path-style access for all requests.
@@ -186,6 +212,7 @@ public Builder enableDualstack() {
186212
*/
187213
@Deprecated
188214
public S3ClientOptions() {
215+
this.skipContentMd5Check = DEFAULT_SKIP_CONTENT_MD5_CHECK;
189216
this.pathStyleAccess = DEFAULT_PATH_STYLE_ACCESS;
190217
this.chunkedEncodingDisabled = DEFAULT_CHUNKED_ENCODING_DISABLED;
191218
this.accelerateModeEnabled = DEFAULT_ACCELERATE_MODE_ENABLED;
@@ -200,25 +227,45 @@ public S3ClientOptions() {
200227
*/
201228
@Deprecated
202229
public S3ClientOptions(S3ClientOptions other) {
230+
this.skipContentMd5Check = other.skipContentMd5Check;
203231
this.pathStyleAccess = other.pathStyleAccess;
204232
this.chunkedEncodingDisabled = other.chunkedEncodingDisabled;
205233
this.accelerateModeEnabled = other.accelerateModeEnabled;
206234
this.payloadSigningEnabled = other.payloadSigningEnabled;
207235
this.dualstackEnabled = other.dualstackEnabled;
208236
}
209237

210-
private S3ClientOptions(boolean pathStyleAccess,
238+
private S3ClientOptions(boolean skipContentMd5Check,
239+
boolean pathStyleAccess,
211240
boolean chunkedEncodingDisabled,
212241
boolean accelerateModeEnabled,
213242
boolean payloadSigningEnabled,
214243
boolean dualstackEnabled) {
244+
this.skipContentMd5Check = skipContentMd5Check;
215245
this.pathStyleAccess = pathStyleAccess;
216246
this.chunkedEncodingDisabled = chunkedEncodingDisabled;
217247
this.accelerateModeEnabled = accelerateModeEnabled;
218248
this.payloadSigningEnabled = payloadSigningEnabled;
219249
this.dualstackEnabled = dualstackEnabled;
220250
}
221251

252+
/**
253+
* <p>
254+
* Returns whether the client skips content MD5 check for all requests
255+
* and responses.
256+
* </p>
257+
* <p>
258+
* Setting this flag will allow the client to disregard data transfer
259+
* integrity check by bypassing content MD5 check for all requests and
260+
* responses.
261+
* </p>
262+
*
263+
* @return True if the client should always skip content MD5 check
264+
*/
265+
public boolean isContentMd5CheckSkipped() {
266+
return skipContentMd5Check;
267+
}
268+
222269
/**
223270
* <p>
224271
* Returns whether the client uses path-style access for all requests.
@@ -235,7 +282,7 @@ private S3ClientOptions(boolean pathStyleAccess,
235282
* this flag will result in path-style access being used for all requests.
236283
* </p>
237284
*
238-
* @return True is the client should always use path-style access
285+
* @return True if the client should always use path-style access
239286
*/
240287
public boolean isPathStyleAccess() {
241288
return pathStyleAccess;
@@ -305,6 +352,22 @@ public boolean isPayloadSigningEnabled() {
305352
return payloadSigningEnabled;
306353
}
307354

355+
/**
356+
* <p>
357+
* Returns whether the client skips content MD5 check for all requests
358+
* and responses.
359+
* </p>
360+
* <p>
361+
* Setting this flag will allow the client to disregard data transfer
362+
* integrity check by bypassing content MD5 check for all requests and
363+
* responses.
364+
* </p>
365+
*
366+
* @param skipContentMd5Check True to always skip content MD5 check
367+
*/
368+
public void skipContentMd5Check(boolean skipContentMd5Check) {
369+
this.skipContentMd5Check = skipContentMd5Check;
370+
}
308371

309372
/**
310373
* <p>

aws-android-sdk-s3/src/main/java/com/amazonaws/services/s3/internal/Constants.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,16 @@ public class Constants {
3939
/** Service hostname for accessing dualstack accelerated S3 buckets */
4040
public static final String S3_ACCELERATE_DUALSTACK_HOSTNAME = "s3-accelerate.dualstack.amazonaws.com";
4141

42+
/** Parameter name to indicate permission to dangerously connect to HTTP endpoint */
43+
public static final String LOCAL_TESTING_FLAG_NAME = "DangerouslyConnectToHTTPEndpointForTesting";
44+
45+
/**
46+
* Default endpoint for the local S3 storage testing
47+
*
48+
* Android emulator uses 10.0.2.2 to connect to localhost instead of 127.0.0.1 (reserved for emulator itself)
49+
*/
50+
public static final String LOCAL_TESTING_ENDPOINT = "http://10.0.2.2:20005";
51+
4252
/** Dualstack qualifier for S3 */
4353
public static final String S3_DUALSTACK_QUALIFIER = "dualstack";
4454

aws-android-sdk-s3/src/main/java/com/amazonaws/services/s3/internal/ServiceUtils.java

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import com.amazonaws.AmazonWebServiceRequest;
2525
import com.amazonaws.Request;
2626
import com.amazonaws.services.s3.AmazonS3Client;
27+
import com.amazonaws.services.s3.S3ClientOptions;
2728
import com.amazonaws.services.s3.model.GetObjectRequest;
2829
import com.amazonaws.services.s3.model.ObjectMetadata;
2930
import com.amazonaws.services.s3.model.PutObjectRequest;
@@ -419,6 +420,29 @@ public static S3Object retryableDownloadS3ObjectToFile(File file,
419420
* check on the requested object content.
420421
*/
421422
public static boolean skipMd5CheckPerResponse(ObjectMetadata metadata) {
423+
return skipMd5CheckPerResponse(metadata, null);
424+
}
425+
426+
/**
427+
* Based on the given metadata of an S3 response, Returns whether the
428+
* specified request should skip MD5 check on the requested object content.
429+
* Specifically, MD5 check should be skipped if either SSE-KMS or SSE-C is
430+
* involved.
431+
* <p>
432+
* The reason is that when SSE-KMS or SSE-C is involved, the MD5 returned
433+
* from the server side is the MD5 of the ciphertext, which will by
434+
* definition mismatch the MD5 on the client side which is computed based on
435+
* the plaintext.
436+
* @param metadata the ObjectMetadata of an S3 response.
437+
* @param clientOptions the S3 client options to see if check can be skipped
438+
* @return true if the specified response should skip MD5
439+
* check on the requested object content.
440+
*/
441+
public static boolean skipMd5CheckPerResponse(ObjectMetadata metadata, S3ClientOptions clientOptions) {
442+
if (clientOptions != null && clientOptions.isContentMd5CheckSkipped()) {
443+
return true;
444+
}
445+
422446
if (metadata == null) {
423447
return false;
424448
}
@@ -436,6 +460,22 @@ public static boolean skipMd5CheckPerResponse(ObjectMetadata metadata) {
436460
* check on the requested object content.
437461
*/
438462
public static boolean skipMd5CheckPerRequest(AmazonWebServiceRequest request) {
463+
return skipMd5CheckPerRequest(request, null);
464+
}
465+
466+
/**
467+
* Returns whether the specified request should skip MD5 check on the
468+
* requested object content.
469+
* @param request the AmazonWebServiceRequest.
470+
* @param clientOptions the S3 client options to see if check can be skipped
471+
* @return true if the specified request should skip MD5
472+
* check on the requested object content.
473+
*/
474+
public static boolean skipMd5CheckPerRequest(AmazonWebServiceRequest request, S3ClientOptions clientOptions) {
475+
if (clientOptions != null && clientOptions.isContentMd5CheckSkipped()) {
476+
return true;
477+
}
478+
439479
if (System.getProperty("com.amazonaws.services.s3.disableGetObjectMD5Validation") != null) {
440480
return true;
441481
}

aws-android-sdk-s3/src/test/java/com/amazonaws/services/s3/S3ClientOptionsTest.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,14 @@
2222
public class S3ClientOptionsTest {
2323
@Test
2424
public void testBuilder() {
25-
S3ClientOptions options = S3ClientOptions.builder().setAccelerateModeEnabled(true)
26-
.setPathStyleAccess(true).build();
25+
S3ClientOptions options = S3ClientOptions.builder()
26+
.setAccelerateModeEnabled(true)
27+
.setPathStyleAccess(true)
28+
.skipContentMd5Check(true)
29+
.build();
2730
assertTrue(options.isAccelerateModeEnabled());
2831
assertTrue(options.isPathStyleAccess());
32+
assertTrue(options.isContentMd5CheckSkipped());
2933
}
3034
}
3135

0 commit comments

Comments
 (0)