From de3e13f98701a1027c82087351fa0520e4fed64f Mon Sep 17 00:00:00 2001 From: Anton Chudinovskikh Date: Tue, 15 Oct 2019 13:35:05 +0100 Subject: [PATCH] Do not drop content-type/content-encoding headers from http entity --- .gitignore | 2 + .../AWSRequestSigningApacheInterceptor.java | 30 +++-- ...gnableRequestContentChangeInterceptor.java | 93 +++++++++++++++ ...WSRequestSigningApacheInterceptorTest.java | 109 ++++++++++++++---- ...leRequestContentChangeInterceptorTest.java | 21 ++++ 5 files changed, 224 insertions(+), 31 deletions(-) create mode 100644 .gitignore create mode 100644 src/main/java/com/amazonaws/http/SignableRequestContentChangeInterceptor.java create mode 100644 src/test/java/com/amazonaws/http/SignableRequestContentChangeInterceptorTest.java diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..96ef862 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +target/ +.idea/ diff --git a/src/main/java/com/amazonaws/http/AWSRequestSigningApacheInterceptor.java b/src/main/java/com/amazonaws/http/AWSRequestSigningApacheInterceptor.java index 6337eeb..51f1945 100644 --- a/src/main/java/com/amazonaws/http/AWSRequestSigningApacheInterceptor.java +++ b/src/main/java/com/amazonaws/http/AWSRequestSigningApacheInterceptor.java @@ -18,6 +18,7 @@ import org.apache.http.Header; import org.apache.http.HttpEntityEnclosingRequest; import org.apache.http.HttpException; +import org.apache.http.HttpHeaders; import org.apache.http.HttpHost; import org.apache.http.HttpRequest; import org.apache.http.HttpRequestInterceptor; @@ -59,14 +60,13 @@ public class AWSRequestSigningApacheInterceptor implements HttpRequestIntercepto private final AWSCredentialsProvider awsCredentialsProvider; /** - * - * @param service service that we're connecting to - * @param signer particular signer implementation + * @param service service that we're connecting to + * @param signer particular signer implementation * @param awsCredentialsProvider source of AWS credentials for signing */ public AWSRequestSigningApacheInterceptor(final String service, - final Signer signer, - final AWSCredentialsProvider awsCredentialsProvider) { + final Signer signer, + final AWSCredentialsProvider awsCredentialsProvider) { this.service = service; this.signer = signer; this.awsCredentialsProvider = awsCredentialsProvider; @@ -82,7 +82,7 @@ public void process(final HttpRequest request, final HttpContext context) try { uriBuilder = new URIBuilder(request.getRequestLine().getUri()); } catch (URISyntaxException e) { - throw new IOException("Invalid URI" , e); + throw new IOException("Invalid URI", e); } // Copy Apache HttpRequest to AWS DefaultRequest @@ -98,7 +98,7 @@ public void process(final HttpRequest request, final HttpContext context) try { signableRequest.setResourcePath(uriBuilder.build().getRawPath()); } catch (URISyntaxException e) { - throw new IOException("Invalid URI" , e); + throw new IOException("Invalid URI", e); } if (request instanceof HttpEntityEnclosingRequest) { @@ -111,24 +111,34 @@ public void process(final HttpRequest request, final HttpContext context) signableRequest.setParameters(nvpToMapParams(uriBuilder.getQueryParams())); signableRequest.setHeaders(headerArrayToMap(request.getAllHeaders())); + SignableRequestContentChangeInterceptor wrappedSignableRequest = + new SignableRequestContentChangeInterceptor<>(signableRequest); + // Sign it - signer.sign(signableRequest, awsCredentialsProvider.getCredentials()); + signer.sign(wrappedSignableRequest, awsCredentialsProvider.getCredentials()); // Now copy everything back request.setHeaders(mapToHeaderArray(signableRequest.getHeaders())); - if (request instanceof HttpEntityEnclosingRequest) { + // Replace http entity only if content got changed + if (wrappedSignableRequest.isContentChanged() && + request instanceof HttpEntityEnclosingRequest) { HttpEntityEnclosingRequest httpEntityEnclosingRequest = (HttpEntityEnclosingRequest) request; if (httpEntityEnclosingRequest.getEntity() != null) { BasicHttpEntity basicHttpEntity = new BasicHttpEntity(); basicHttpEntity.setContent(signableRequest.getContent()); + if (request.getFirstHeader(HttpHeaders.CONTENT_TYPE) != null) { + basicHttpEntity.setContentType(request.getFirstHeader(HttpHeaders.CONTENT_TYPE)); + } + if (request.getFirstHeader(HttpHeaders.CONTENT_ENCODING) != null) { + basicHttpEntity.setContentEncoding(request.getFirstHeader(HttpHeaders.CONTENT_ENCODING)); + } httpEntityEnclosingRequest.setEntity(basicHttpEntity); } } } /** - * * @param params list of HTTP query params as NameValuePairs * @return a multimap of HTTP query params */ diff --git a/src/main/java/com/amazonaws/http/SignableRequestContentChangeInterceptor.java b/src/main/java/com/amazonaws/http/SignableRequestContentChangeInterceptor.java new file mode 100644 index 0000000..89a2754 --- /dev/null +++ b/src/main/java/com/amazonaws/http/SignableRequestContentChangeInterceptor.java @@ -0,0 +1,93 @@ +package com.amazonaws.http; + +import com.amazonaws.ReadLimitInfo; +import com.amazonaws.SignableRequest; + +import java.io.InputStream; +import java.net.URI; +import java.util.List; +import java.util.Map; + +/** + * Wrapper for SignableRequest which intercepts if the request content is changed. + * + * @param + */ +public class SignableRequestContentChangeInterceptor implements SignableRequest { + private final SignableRequest original; + private boolean contentChanged = false; + + public SignableRequestContentChangeInterceptor(final SignableRequest original) { + this.original = original; + } + + @Override + public void addHeader(final String name, final String value) { + original.addHeader(name, value); + } + + @Override + public void addParameter(final String name, final String value) { + original.addParameter(name, value); + } + + @Override + public void setContent(final InputStream inputStream) { + original.setContent(inputStream); + contentChanged = true; + } + + @Override + public Map getHeaders() { + return original.getHeaders(); + } + + @Override + public String getResourcePath() { + return original.getResourcePath(); + } + + @Override + public Map> getParameters() { + return original.getParameters(); + } + + @Override + public URI getEndpoint() { + return original.getEndpoint(); + } + + @Override + public HttpMethodName getHttpMethod() { + return original.getHttpMethod(); + } + + @Override + public int getTimeOffset() { + return original.getTimeOffset(); + } + + @Override + public InputStream getContent() { + return original.getContent(); + } + + @Override + public InputStream getContentUnwrapped() { + return original.getContentUnwrapped(); + } + + @Override + public ReadLimitInfo getReadLimitInfo() { + return original.getReadLimitInfo(); + } + + @Override + public Object getOriginalRequestObject() { + return original.getOriginalRequestObject(); + } + + public boolean isContentChanged() { + return contentChanged; + } +} diff --git a/src/test/java/com/amazonaws/http/AWSRequestSigningApacheInterceptorTest.java b/src/test/java/com/amazonaws/http/AWSRequestSigningApacheInterceptorTest.java index 75f546c..4111d97 100644 --- a/src/test/java/com/amazonaws/http/AWSRequestSigningApacheInterceptorTest.java +++ b/src/test/java/com/amazonaws/http/AWSRequestSigningApacheInterceptorTest.java @@ -18,9 +18,13 @@ import com.amazonaws.auth.AWSStaticCredentialsProvider; import com.amazonaws.auth.AnonymousAWSCredentials; import com.amazonaws.auth.Signer; +import com.amazonaws.util.IOUtils; import org.apache.http.HttpEntityEnclosingRequest; import org.apache.http.HttpHost; import org.apache.http.HttpRequest; +import org.apache.http.entity.BasicHttpEntity; +import org.apache.http.entity.ByteArrayEntity; +import org.apache.http.entity.ContentType; import org.apache.http.entity.StringEntity; import org.apache.http.message.BasicHttpEntityEnclosingRequest; import org.apache.http.message.BasicHttpRequest; @@ -28,20 +32,23 @@ import org.apache.http.protocol.HttpCoreContext; import org.junit.Test; +import java.io.ByteArrayInputStream; import java.io.IOException; +import java.io.InputStream; +import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; public class AWSRequestSigningApacheInterceptorTest { - private static AWSRequestSigningApacheInterceptor createInterceptor() { + private static AWSRequestSigningApacheInterceptor createInterceptor(Signer signer) { AWSCredentialsProvider anonymousCredentialsProvider = new AWSStaticCredentialsProvider(new AnonymousAWSCredentials()); return new AWSRequestSigningApacheInterceptor("servicename", - new AddHeaderSigner("Signature", "wuzzle"), + signer, anonymousCredentialsProvider); - } @Test @@ -55,7 +62,8 @@ public void testSimpleSigner() throws Exception { HttpCoreContext context = new HttpCoreContext(); context.setTargetHost(HttpHost.create("localhost")); - createInterceptor().process(request, context); + createInterceptor(new AddHeaderSigner("Signature", "wuzzle")) + .process(request, context); assertEquals("bar", request.getFirstHeader("foo").getValue()); assertEquals("wuzzle", request.getFirstHeader("Signature").getValue()); @@ -64,32 +72,61 @@ public void testSimpleSigner() throws Exception { @Test(expected = IOException.class) public void testBadRequest() throws Exception { - HttpRequest badRequest = new BasicHttpRequest("GET", "?#!@*%"); - createInterceptor().process(badRequest, new BasicHttpContext()); + createInterceptor(new AddHeaderSigner("Signature", "wuzzle")) + .process(badRequest, new BasicHttpContext()); } - private static class AddHeaderSigner implements Signer { - private final String name; - private final String value; + @Test + public void testHttpEntityIsCorrect() throws Exception { + HttpEntityEnclosingRequest request = + new BasicHttpEntityEnclosingRequest("GET", "/query?a=b"); + BasicHttpEntity httpEntity = new BasicHttpEntity(); + InputStream content = new ByteArrayInputStream(new byte[]{0, 1}); + httpEntity.setContent(content); + httpEntity.setContentEncoding("gzip"); + request.setEntity(httpEntity); - private AddHeaderSigner(String name, String value) { - this.name = name; - this.value = value; - } + HttpCoreContext context = new HttpCoreContext(); + context.setTargetHost(HttpHost.create("localhost")); + createInterceptor(new AddHeaderSigner("Signature", "wuzzle")) + .process(request, context); - @Override - public void sign(SignableRequest request, AWSCredentials credentials) { - request.addHeader(name, value); - request.addHeader("resourcePath", request.getResourcePath()); - } + assertNotNull(request.getEntity()); + assertNotNull(request.getEntity().getContentEncoding()); + assertEquals("gzip", request.getEntity().getContentEncoding().getValue()); + assertEquals(content, request.getEntity().getContent()); + } + + @Test + public void testReplaceContentSigner() throws Exception { + HttpEntityEnclosingRequest request = + new BasicHttpEntityEnclosingRequest("GET", "/query?a=b"); + ByteArrayEntity byteArrayEntity = new ByteArrayEntity("I'm an entity".getBytes(), + ContentType.TEXT_PLAIN); + request.setEntity(byteArrayEntity); + request.addHeader("foo", "bar"); + request.addHeader("content-length", "0"); + request.addHeader(byteArrayEntity.getContentType()); + + HttpCoreContext context = new HttpCoreContext(); + context.setTargetHost(HttpHost.create("localhost")); + + createInterceptor(new ReplaceContentSigner("new content")) + .process(request, context); + assertNotNull(request.getEntity().getContentType()); + assertEquals(ContentType.TEXT_PLAIN.toString(), + request.getEntity().getContentType().getValue()); + assertArrayEquals("new content".getBytes(), + IOUtils.toByteArray(request.getEntity().getContent())); } @Test public void testEncodedUriSigner() throws Exception { HttpEntityEnclosingRequest request = - new BasicHttpEntityEnclosingRequest("GET", "/foo-2017-02-25%2Cfoo-2017-02-26/_search?a=b"); + new BasicHttpEntityEnclosingRequest("GET", + "/foo-2017-02-25%2Cfoo-2017-02-26/_search?a=b"); request.setEntity(new StringEntity("I'm an entity")); request.addHeader("foo", "bar"); request.addHeader("content-length", "0"); @@ -97,12 +134,42 @@ public void testEncodedUriSigner() throws Exception { HttpCoreContext context = new HttpCoreContext(); context.setTargetHost(HttpHost.create("localhost")); - createInterceptor().process(request, context); + createInterceptor(new AddHeaderSigner("Signature", "wuzzle")) + .process(request, context); assertEquals("bar", request.getFirstHeader("foo").getValue()); assertEquals("wuzzle", request.getFirstHeader("Signature").getValue()); assertNull(request.getFirstHeader("content-length")); - assertEquals("/foo-2017-02-25%2Cfoo-2017-02-26/_search", request.getFirstHeader("resourcePath").getValue()); + assertEquals("/foo-2017-02-25%2Cfoo-2017-02-26/_search", + request.getFirstHeader("resourcePath").getValue()); + } + + private static class AddHeaderSigner implements Signer { + private final String name; + private final String value; + + private AddHeaderSigner(String name, String value) { + this.name = name; + this.value = value; + } + + @Override + public void sign(SignableRequest request, AWSCredentials credentials) { + request.addHeader(name, value); + request.addHeader("resourcePath", request.getResourcePath()); + } } + private static class ReplaceContentSigner implements Signer { + private final String content; + + public ReplaceContentSigner(final String content) { + this.content = content; + } + + @Override + public void sign(SignableRequest request, AWSCredentials credentials) { + request.setContent(new ByteArrayInputStream(content.getBytes())); + } + } } \ No newline at end of file diff --git a/src/test/java/com/amazonaws/http/SignableRequestContentChangeInterceptorTest.java b/src/test/java/com/amazonaws/http/SignableRequestContentChangeInterceptorTest.java new file mode 100644 index 0000000..7ed32a1 --- /dev/null +++ b/src/test/java/com/amazonaws/http/SignableRequestContentChangeInterceptorTest.java @@ -0,0 +1,21 @@ +package com.amazonaws.http; + +import com.amazonaws.DefaultRequest; +import com.amazonaws.SignableRequest; +import org.junit.Assert; +import org.junit.Test; + +import java.io.ByteArrayInputStream; + +public class SignableRequestContentChangeInterceptorTest { + @Test + public void testContentChangeDetected() { + SignableRequest signableRequest = new DefaultRequest<>("test-service"); + signableRequest.setContent(new ByteArrayInputStream(new byte[]{0, 1})); + SignableRequestContentChangeInterceptor wrapped = + new SignableRequestContentChangeInterceptor<>(signableRequest); + Assert.assertFalse(wrapped.isContentChanged()); + wrapped.setContent(new ByteArrayInputStream(new byte[]{1, 2})); + Assert.assertTrue(wrapped.isContentChanged()); + } +} \ No newline at end of file