Repository: localstack/localstack

Issue:
Uploading to S3 by presigned URL with Content-Md5 throw "The Content-MD5 you specified was invalid"
Hello guys. I use S3 preSigned URL to upload file, but it return an error "The Content-MD5 you specified was invalid".
It happens only when I generate Presigned Url with contentMd5.However it works fine when I annotation the line '.withContentMd5(md5)'.
My file md5 is 32c295626872e465b3d0b590eb8e88a7.

```java
        AmazonS3 s3client = getDefaultS3Client();
        Date expiration = new Date(System.currentTimeMillis() + expiratMillis);
        GeneratePresignedUrlRequest generatePresignedUrlRequest =
                new GeneratePresignedUrlRequest(bucketName, s3FullPath)
                        .withMethod(HttpMethod.PUT)
                        .withExpiration(expiration)
                        .withKey(s3Key)
                        .withContentMd5(md5);
        String presignedUrl =  s3client.generatePresignedUrl(generatePresignedUrlRequest);
 ```


![postman](https://user-images.githubusercontent.com/4321439/60160341-bdfafd80-9827-11e9-8a26-03ec4c4d8faf.png)

Does S3 support checksum when using presigned url?
Could anybody give me a piece of advice to help me solve this? Thank you!


Discussion:

Resolving patch:
diff --git a/localstack/services/dynamodb/dynamodb_listener.py b/localstack/services/dynamodb/dynamodb_listener.py
--- a/localstack/services/dynamodb/dynamodb_listener.py
+++ b/localstack/services/dynamodb/dynamodb_listener.py
@@ -6,7 +6,7 @@
 from binascii import crc32
 from requests.models import Response
 from localstack import config
-from localstack.utils.aws import aws_stack
+from localstack.utils.aws import aws_stack, aws_responses
 from localstack.utils.common import to_bytes, to_str, clone
 from localstack.utils.analytics import event_publisher
 from localstack.services.awslambda import lambda_api
@@ -36,7 +36,10 @@ def __init__(self):
         self._table_ttl_map = {}
 
     def forward_request(self, method, path, data, headers):
-        if path.startswith('/shell'):
+        if path.startswith('/shell') or method == 'GET':
+            if path == '/shell':
+                headers = {'Refresh': '0; url=%s/shell/' % config.TEST_DYNAMODB_URL}
+                return aws_responses.requests_response('', headers=headers)
             return True
         if method == 'OPTIONS':
             return 200
@@ -138,7 +141,7 @@ def forward_request(self, method, path, data, headers):
         return True
 
     def return_response(self, method, path, data, headers, response):
-        if path.startswith('/shell'):
+        if path.startswith('/shell') or method == 'GET':
             return
         data = json.loads(to_str(data))
 


Test patch:
diff --git a/localstack/ext/java/src/test/java/cloud/localstack/S3FeaturesTest.java b/localstack/ext/java/src/test/java/cloud/localstack/S3FeaturesTest.java
--- a/localstack/ext/java/src/test/java/cloud/localstack/S3FeaturesTest.java
+++ b/localstack/ext/java/src/test/java/cloud/localstack/S3FeaturesTest.java
@@ -7,17 +7,29 @@
 import java.nio.charset.StandardCharsets;
 import java.util.*;
 import java.util.stream.*;
+import java.net.*;
 
 import org.junit.Assert;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import org.apache.commons.io.IOUtils;
+import org.apache.http.*;
+import org.apache.http.client.*;
+import org.apache.http.client.methods.*;
+import org.apache.http.entity.*;
+import org.apache.http.impl.client.*;
+
+import com.amazonaws.HttpMethod;
 import com.amazonaws.client.builder.AwsClientBuilder;
 import com.amazonaws.services.s3.*;
 import com.amazonaws.services.s3.model.*;
 import com.amazonaws.services.s3.model.lifecycle.*;
 
+import cloud.localstack.docker.annotation.LocalstackDockerProperties;
+
 @RunWith(LocalstackTestRunner.class)
+@LocalstackDockerProperties(services = {"s3"})
 public class S3FeaturesTest {
 
 	/**
@@ -138,7 +150,6 @@ public void testListNextBatchOfObjects() {
 			objectListing = s3Client.listNextBatchOfObjects(objectListing);
 			someObjList.addAll(mapFilesToSomeObject(objectListing));
 		}
-		System.out.println("someObjList.size() " + someObjList.size());
 		assertEquals(3, someObjList.size());
 	}
 
@@ -148,4 +159,34 @@ private List<Object> mapFilesToSomeObject(ObjectListing objectListing) {
 			.map(S3ObjectSummary::getKey)
 			.collect(Collectors.toList());
 	}
+
+	@Test
+	public void test() throws Exception {
+		AmazonS3 s3client = TestUtils.getClientS3();
+		Date expiration = new Date(System.currentTimeMillis() + 1000*60*5);
+		String bucketName = UUID.randomUUID().toString();
+		String keyName = "presign-test-key";
+		s3client.createBucket(bucketName);
+
+		GeneratePresignedUrlRequest generatePresignedUrlRequest =
+			new GeneratePresignedUrlRequest(bucketName, keyName)
+				.withMethod(HttpMethod.PUT)
+				.withExpiration(expiration)
+				.withKey(keyName);
+		URL presignedUrl = s3client.generatePresignedUrl(generatePresignedUrlRequest);
+
+		// upload content
+		String content = "test content";
+		HttpPut httpPut = new HttpPut(presignedUrl.toString());
+    httpPut.setEntity(new StringEntity(content));
+		CloseableHttpClient httpclient = HttpClients.createDefault();
+		httpclient.execute(httpPut);
+		httpclient.close();
+
+		// download content
+		GetObjectRequest req = new GetObjectRequest(bucketName, keyName);
+		S3Object stream = s3client.getObject(req);
+		String result = IOUtils.toString(stream.getObjectContent());
+		Assert.assertEquals(result, content);
+	}
 }