From 53ffdf909890db54fb411aa927279d097675c92b Mon Sep 17 00:00:00 2001 From: Eirik Jakobsen Date: Sun, 16 Dec 2018 01:50:20 +0100 Subject: [PATCH] Fixes issue where headers were treated as case sensitive after deserialization. --- .../proxy/AwsProxyExceptionHandler.java | 9 +-- .../servlet/AwsHttpServletResponse.java | 8 +- .../testutils/AwsProxyRequestBuilder.java | 16 ++-- .../proxy/model/AwsProxyRequest.java | 8 +- .../proxy/model/AwsProxyResponse.java | 12 +-- .../serverless/proxy/model/Headers.java | 10 +++ .../servlet/AwsHttpServletResponseTest.java | 7 +- .../proxy/model/AwsProxyRequestTest.java | 79 +++++++++++++++++++ .../proxy/model/MultiValuedTreeMapTest.java | 2 +- .../spring/springbootapp/TestApplication.java | 2 +- 10 files changed, 117 insertions(+), 36 deletions(-) create mode 100644 aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/model/Headers.java create mode 100644 aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/model/AwsProxyRequestTest.java diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/AwsProxyExceptionHandler.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/AwsProxyExceptionHandler.java index 75bf3c6ed..d2b96d8e2 100644 --- a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/AwsProxyExceptionHandler.java +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/AwsProxyExceptionHandler.java @@ -16,12 +16,9 @@ import com.amazonaws.serverless.proxy.internal.LambdaContainerHandler; import com.amazonaws.serverless.proxy.model.AwsProxyResponse; import com.amazonaws.serverless.proxy.model.ErrorModel; -import com.amazonaws.serverless.proxy.model.MultiValuedTreeMap; +import com.amazonaws.serverless.proxy.model.Headers; import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonValueFormat; -import com.fasterxml.jackson.databind.ser.std.JsonValueSerializer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -30,8 +27,6 @@ import java.io.IOException; import java.io.OutputStream; -import java.util.HashMap; -import java.util.Map; /** * Default implementation of the ExceptionHandler object that returns AwsProxyResponse objects. @@ -58,7 +53,7 @@ public class AwsProxyExceptionHandler // Variables - Private - Static //------------------------------------------------------------- - private static MultiValuedTreeMap headers = new MultiValuedTreeMap<>(String.CASE_INSENSITIVE_ORDER); + private static Headers headers = new Headers(); //------------------------------------------------------------- // Constructors diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpServletResponse.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpServletResponse.java index 9ae65c28a..3ea2deda5 100644 --- a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpServletResponse.java +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpServletResponse.java @@ -13,7 +13,7 @@ package com.amazonaws.serverless.proxy.internal.servlet; import com.amazonaws.serverless.proxy.internal.SecurityUtils; -import com.amazonaws.serverless.proxy.model.MultiValuedTreeMap; +import com.amazonaws.serverless.proxy.model.Headers; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import org.slf4j.Logger; @@ -52,7 +52,7 @@ public class AwsHttpServletResponse // Variables - Private //------------------------------------------------------------- - private MultiValuedTreeMap headers = new MultiValuedTreeMap<>(String.CASE_INSENSITIVE_ORDER); + private Headers headers = new Headers(); private int statusCode; private String statusMessage; private String responseBody; @@ -404,7 +404,7 @@ public boolean isCommitted() { @Override public void reset() { - headers = new MultiValuedTreeMap<>(); + headers = new Headers(); responseBody = null; writer = null; bodyOutputStream = new ByteArrayOutputStream(); @@ -439,7 +439,7 @@ byte[] getAwsResponseBodyBytes() { } - MultiValuedTreeMap getAwsResponseHeaders() { + Headers getAwsResponseHeaders() { return headers; } diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/testutils/AwsProxyRequestBuilder.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/testutils/AwsProxyRequestBuilder.java index 01924c063..d2a9b0503 100644 --- a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/testutils/AwsProxyRequestBuilder.java +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/testutils/AwsProxyRequestBuilder.java @@ -18,10 +18,10 @@ import com.amazonaws.serverless.proxy.model.ApiGatewayRequestIdentity; import com.amazonaws.serverless.proxy.model.AwsProxyRequest; import com.amazonaws.serverless.proxy.model.CognitoAuthorizerClaims; +import com.amazonaws.serverless.proxy.model.Headers; import com.amazonaws.serverless.proxy.model.MultiValuedTreeMap; import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import org.apache.commons.io.IOUtils; @@ -32,10 +32,8 @@ import java.io.File; import java.io.IOException; import java.io.InputStream; -import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.util.Base64; -import java.util.HashMap; import java.util.UUID; @@ -67,7 +65,7 @@ public AwsProxyRequestBuilder(String path) { public AwsProxyRequestBuilder(String path, String httpMethod) { this.request = new AwsProxyRequest(); - this.request.setMultiValueHeaders(new MultiValuedTreeMap<>(String.CASE_INSENSITIVE_ORDER)); // avoid NPE + this.request.setMultiValueHeaders(new Headers()); // avoid NPE this.request.setHttpMethod(httpMethod); this.request.setPath(path); this.request.setMultiValueQueryStringParameters(new MultiValuedTreeMap<>()); @@ -111,7 +109,7 @@ public AwsProxyRequestBuilder json() { public AwsProxyRequestBuilder form(String key, String value) { if (request.getMultiValueHeaders() == null) { - request.setMultiValueHeaders(new MultiValuedTreeMap<>(String.CASE_INSENSITIVE_ORDER)); + request.setMultiValueHeaders(new Headers()); } request.getMultiValueHeaders().add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED); String body = request.getBody(); @@ -126,7 +124,7 @@ public AwsProxyRequestBuilder form(String key, String value) { public AwsProxyRequestBuilder header(String key, String value) { if (this.request.getMultiValueHeaders() == null) { - this.request.setMultiValueHeaders(new MultiValuedTreeMap<>(String.CASE_INSENSITIVE_ORDER)); + this.request.setMultiValueHeaders(new Headers()); } this.request.getMultiValueHeaders().add(key, value); @@ -223,7 +221,7 @@ public AwsProxyRequestBuilder cognitoIdentity(String identityId, String identity public AwsProxyRequestBuilder cookie(String name, String value) { if (request.getMultiValueHeaders() == null) { - request.setMultiValueHeaders(new MultiValuedTreeMap<>(String.CASE_INSENSITIVE_ORDER)); + request.setMultiValueHeaders(new Headers()); } String cookies = request.getMultiValueHeaders().getFirst(HttpHeaders.COOKIE); @@ -238,7 +236,7 @@ public AwsProxyRequestBuilder cookie(String name, String value) { public AwsProxyRequestBuilder scheme(String scheme) { if (request.getMultiValueHeaders() == null) { - request.setMultiValueHeaders(new MultiValuedTreeMap<>(String.CASE_INSENSITIVE_ORDER)); + request.setMultiValueHeaders(new Headers()); } request.getMultiValueHeaders().putSingle("CloudFront-Forwarded-Proto", scheme); @@ -247,7 +245,7 @@ public AwsProxyRequestBuilder scheme(String scheme) { public AwsProxyRequestBuilder serverName(String serverName) { if (request.getMultiValueHeaders() == null) { - request.setMultiValueHeaders(new MultiValuedTreeMap<>(String.CASE_INSENSITIVE_ORDER)); + request.setMultiValueHeaders(new Headers()); } request.getMultiValueHeaders().putSingle("Host", serverName); diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/model/AwsProxyRequest.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/model/AwsProxyRequest.java index 61aa78768..a607595bf 100644 --- a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/model/AwsProxyRequest.java +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/model/AwsProxyRequest.java @@ -32,7 +32,7 @@ public class AwsProxyRequest { private String resource; private ApiGatewayRequestContext requestContext; private MultiValuedTreeMap multiValueQueryStringParameters; - private MultiValuedTreeMap multiValueHeaders; + private Headers multiValueHeaders; private Map pathParameters; private String httpMethod; private Map stageVariables; @@ -40,7 +40,7 @@ public class AwsProxyRequest { private boolean isBase64Encoded; public AwsProxyRequest() { - multiValueHeaders = new MultiValuedTreeMap<>(String.CASE_INSENSITIVE_ORDER); + multiValueHeaders = new Headers(); multiValueQueryStringParameters = new MultiValuedTreeMap<>(); pathParameters = new HashMap<>(); stageVariables = new HashMap<>(); @@ -110,12 +110,12 @@ public void setMultiValueQueryStringParameters( this.multiValueQueryStringParameters = multiValueQueryStringParameters; } - public MultiValuedTreeMap getMultiValueHeaders() { + public Headers getMultiValueHeaders() { return multiValueHeaders; } - public void setMultiValueHeaders(MultiValuedTreeMap multiValueHeaders) { + public void setMultiValueHeaders(Headers multiValueHeaders) { this.multiValueHeaders = multiValueHeaders; } diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/model/AwsProxyResponse.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/model/AwsProxyResponse.java index f2e3a63a0..74118bfa8 100644 --- a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/model/AwsProxyResponse.java +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/model/AwsProxyResponse.java @@ -22,7 +22,7 @@ public class AwsProxyResponse { //------------------------------------------------------------- private int statusCode; - private MultiValuedTreeMap multiValueHeaders; + private Headers multiValueHeaders; private String body; private boolean isBase64Encoded; @@ -41,12 +41,12 @@ public AwsProxyResponse(int statusCode) { } - public AwsProxyResponse(int statusCode, MultiValuedTreeMap headers) { + public AwsProxyResponse(int statusCode, Headers headers) { this(statusCode, headers, null); } - public AwsProxyResponse(int statusCode, MultiValuedTreeMap headers, String body) { + public AwsProxyResponse(int statusCode, Headers headers, String body) { this.statusCode = statusCode; this.multiValueHeaders = headers; this.body = body; @@ -59,7 +59,7 @@ public AwsProxyResponse(int statusCode, MultiValuedTreeMap heade public void addHeader(String key, String value) { if (this.multiValueHeaders == null) { - this.multiValueHeaders = new MultiValuedTreeMap(); + this.multiValueHeaders = new Headers(); } this.multiValueHeaders.add(key, value); @@ -80,12 +80,12 @@ public void setStatusCode(int statusCode) { } - public MultiValuedTreeMap getMultiValueHeaders() { + public Headers getMultiValueHeaders() { return multiValueHeaders; } - public void setMultiValueHeaders(MultiValuedTreeMap multiValueHeaders) { + public void setMultiValueHeaders(Headers multiValueHeaders) { this.multiValueHeaders = multiValueHeaders; } diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/model/Headers.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/model/Headers.java new file mode 100644 index 000000000..0de0d204a --- /dev/null +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/model/Headers.java @@ -0,0 +1,10 @@ +package com.amazonaws.serverless.proxy.model; + +public class Headers extends MultiValuedTreeMap { + + private static final long serialVersionUID = 42L; + + public Headers() { + super(String.CASE_INSENSITIVE_ORDER); + } +} diff --git a/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpServletResponseTest.java b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpServletResponseTest.java index 426e0f314..d9a4e742b 100644 --- a/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpServletResponseTest.java +++ b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpServletResponseTest.java @@ -1,7 +1,7 @@ package com.amazonaws.serverless.proxy.internal.servlet; -import com.amazonaws.serverless.proxy.model.MultiValuedTreeMap; +import com.amazonaws.serverless.proxy.model.Headers; import org.junit.Test; @@ -15,7 +15,6 @@ import java.text.SimpleDateFormat; import java.time.Instant; import java.util.Calendar; -import java.util.Map; import java.util.TimeZone; import java.util.concurrent.CountDownLatch; import java.util.regex.Matcher; @@ -154,7 +153,7 @@ public void responseHeaders_getAwsResponseHeaders_expectLatestHeader() { resp.addHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON); resp.addHeader("content-type", "application/xml"); - MultiValuedTreeMap awsResp = resp.getAwsResponseHeaders(); + Headers awsResp = resp.getAwsResponseHeaders(); assertEquals(1, awsResp.size()); assertEquals("application/xml", awsResp.getFirst(HttpHeaders.CONTENT_TYPE)); } @@ -165,7 +164,7 @@ public void responseHeaders_getAwsResponseHeaders_expectedMultpleCookieHeaders() resp.addCookie(new Cookie(COOKIE_NAME, COOKIE_VALUE)); resp.addCookie(new Cookie("Second", "test")); - MultiValuedTreeMap awsResp = resp.getAwsResponseHeaders(); + Headers awsResp = resp.getAwsResponseHeaders(); assertEquals(1, awsResp.size()); assertEquals(2, awsResp.get(HttpHeaders.SET_COOKIE).size()); } diff --git a/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/model/AwsProxyRequestTest.java b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/model/AwsProxyRequestTest.java new file mode 100644 index 000000000..6763bbda5 --- /dev/null +++ b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/model/AwsProxyRequestTest.java @@ -0,0 +1,79 @@ +package com.amazonaws.serverless.proxy.model; + +import com.amazonaws.serverless.proxy.internal.testutils.AwsProxyRequestBuilder; +import org.junit.Test; + +import java.io.IOException; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +public class AwsProxyRequestTest { + private static final String CUSTOM_HEADER_KEY_LOWER_CASE = "custom-header"; + private static final String CUSTOM_HEADER_VALUE = "123456"; + public static final String REQUEST_JSON = "{\n" + + " \"resource\": \"/api/{proxy+}\",\n" + + " \"path\": \"/api/endpoint\",\n" + + " \"httpMethod\": \"OPTIONS\",\n" + + " \"headers\": {\n" + + " \"Accept\": \"*/*\",\n" + + " \"User-Agent\": \"PostmanRuntime/7.1.1\",\n" + + " \"" + CUSTOM_HEADER_KEY_LOWER_CASE +"\":" + "\"" + CUSTOM_HEADER_VALUE + "\"\n" + + " },\n" + + " \"multiValueHeaders\": {\n" + + " \"Accept\": [\n" + + " \"*/*\"\n" + + " ],\n" + + " \"User-Agent\": [\n" + + " \"PostmanRuntime/7.1.1\"\n" + + " ],\n" + + " \"" + CUSTOM_HEADER_KEY_LOWER_CASE + "\": [\n" + + " \"" + CUSTOM_HEADER_VALUE + "\"\n" + + " ]\n" + + " },\n" + + " \"queryStringParameters\": null,\n" + + " \"multiValueQueryStringParameters\": null,\n" + + " \"pathParameters\": {\n" + + " \"proxy\": \"endpoint\"\n" + + " },\n" + + " \"stageVariables\": null,\n" + + " \"requestContext\": {\n" + + " \"resourceId\": null,\n" + + " \"resourcePath\": \"/api/{proxy+}\",\n" + + " \"httpMethod\": \"OPTIONS\",\n" + + " \"extendedRequestId\": null,\n" + + " \"requestTime\": \"15/Dec/2018:20:37:47 +0000\",\n" + + " \"path\": \"/api/endpoint\",\n" + + " \"accountId\": null,\n" + + " \"protocol\": \"HTTP/1.1\",\n" + + " \"stage\": \"stage_name\",\n" + + " \"domainPrefix\": null,\n" + + " \"requestTimeEpoch\": 1544906267828,\n" + + " \"requestId\": null,\n" + + " \"identity\": {\n" + + " \"cognitoIdentityPoolId\": null,\n" + + " \"accountId\": null,\n" + + " \"cognitoIdentityId\": null,\n" + + " \"caller\": null,\n" + + " \"sourceIp\": \"54.240.196.171\",\n" + + " \"accessKey\": null,\n" + + " \"cognitoAuthenticationType\": null,\n" + + " \"cognitoAuthenticationProvider\": null,\n" + + " \"userArn\": null,\n" + + " \"userAgent\": \"PostmanRuntime/7.1.1\",\n" + + " \"user\": null\n" + + " },\n" + + " \"domainName\": \"https://apiId.execute-api.eu-central-1.amazonaws.com/\",\n" + + " \"apiId\": \"apiId\"\n" + + " },\n" + + " \"body\": null,\n" + + " \"isBase64Encoded\": false\n" + + "}"; + + @Test + public void deserialize_multiValuedHeaders_caseInsensitive() throws IOException { + AwsProxyRequest req = new AwsProxyRequestBuilder().fromJsonString(REQUEST_JSON).build(); + assertNotNull(req.getMultiValueHeaders().get(CUSTOM_HEADER_KEY_LOWER_CASE.toUpperCase())); + assertEquals(CUSTOM_HEADER_VALUE, req.getMultiValueHeaders().get(CUSTOM_HEADER_KEY_LOWER_CASE.toUpperCase()).get(0)); + } +} diff --git a/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/model/MultiValuedTreeMapTest.java b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/model/MultiValuedTreeMapTest.java index 3e215850a..edc6d2810 100644 --- a/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/model/MultiValuedTreeMapTest.java +++ b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/model/MultiValuedTreeMapTest.java @@ -29,7 +29,7 @@ public void add_sameNameCaseSensitive_expectBothValues() { @Test public void add_sameNameCaseInsensitive_expectOneValue() { - MultiValuedTreeMap map = new MultiValuedTreeMap<>(String.CASE_INSENSITIVE_ORDER); + Headers map = new Headers(); map.add("Test", "test"); assertNotNull(map.get("Test")); assertNotNull(map.get("test")); diff --git a/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/springbootapp/TestApplication.java b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/springbootapp/TestApplication.java index 2ebe5f037..d70aac98a 100644 --- a/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/springbootapp/TestApplication.java +++ b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/springbootapp/TestApplication.java @@ -12,5 +12,5 @@ @SpringBootApplication @ComponentScan(basePackages = "com.amazonaws.serverless.proxy.spring.springbootapp") public class TestApplication extends SpringBootServletInitializer { - + }