From e48449c046bd4dc6c59c9539f6288a0272ba8937 Mon Sep 17 00:00:00 2001 From: Damien Metzler Date: Thu, 18 Apr 2019 11:58:36 -0700 Subject: [PATCH 01/14] expose the Jersey injection manager to be able to use it outside of Jersey resources --- .../jersey/JerseyLambdaContainerHandler.java | 9 +++ .../proxy/jersey/JerseyInjectionTest.java | 59 +++++++++++++++++++ 2 files changed, 68 insertions(+) create mode 100644 aws-serverless-java-container-jersey/src/test/java/com/amazonaws/serverless/proxy/jersey/JerseyInjectionTest.java diff --git a/aws-serverless-java-container-jersey/src/main/java/com/amazonaws/serverless/proxy/jersey/JerseyLambdaContainerHandler.java b/aws-serverless-java-container-jersey/src/main/java/com/amazonaws/serverless/proxy/jersey/JerseyLambdaContainerHandler.java index 576d2b66c..dd9166f7d 100644 --- a/aws-serverless-java-container-jersey/src/main/java/com/amazonaws/serverless/proxy/jersey/JerseyLambdaContainerHandler.java +++ b/aws-serverless-java-container-jersey/src/main/java/com/amazonaws/serverless/proxy/jersey/JerseyLambdaContainerHandler.java @@ -34,6 +34,7 @@ import com.amazonaws.services.lambda.runtime.Context; import org.glassfish.jersey.internal.inject.AbstractBinder; +import org.glassfish.jersey.internal.inject.InjectionManager; import org.glassfish.jersey.process.internal.RequestScoped; import org.glassfish.jersey.server.ResourceConfig; @@ -196,4 +197,12 @@ public void initialize() { Timer.stop("JERSEY_COLD_START_INIT"); initialized = true; } + + + public InjectionManager getInjectionManager() { + if (!initialized) { + initialize(); + } + return jerseyFilter.getApplicationHandler().getInjectionManager(); + } } diff --git a/aws-serverless-java-container-jersey/src/test/java/com/amazonaws/serverless/proxy/jersey/JerseyInjectionTest.java b/aws-serverless-java-container-jersey/src/test/java/com/amazonaws/serverless/proxy/jersey/JerseyInjectionTest.java new file mode 100644 index 000000000..0af2785ee --- /dev/null +++ b/aws-serverless-java-container-jersey/src/test/java/com/amazonaws/serverless/proxy/jersey/JerseyInjectionTest.java @@ -0,0 +1,59 @@ +/* + * Copyright 2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance + * with the License. A copy of the License is located at + * + * http://aws.amazon.com/apache2.0/ + * + * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES + * OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions + * and limitations under the License. + */ +package com.amazonaws.serverless.proxy.jersey; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import javax.inject.Singleton; + +import org.glassfish.jersey.internal.inject.AbstractBinder; +import org.glassfish.jersey.media.multipart.MultiPartFeature; +import org.glassfish.jersey.server.ResourceConfig; +import org.junit.Test; + +import com.amazonaws.serverless.proxy.model.AwsProxyRequest; +import com.amazonaws.serverless.proxy.model.AwsProxyResponse; + +/** + * Test that one can access the Jersey injection manager + */ +public class JerseyInjectionTest { + + // Test ressource binder + private static class ResourceBinder extends AbstractBinder { + + @Override + protected void configure() { + bind(new JerseyInjectionTest()).to(JerseyInjectionTest.class).in(Singleton.class); + } + + } + + private static ResourceConfig app = new ResourceConfig().register(MultiPartFeature.class) + .register(new ResourceBinder()); + + private static JerseyLambdaContainerHandler handler = JerseyLambdaContainerHandler.getAwsProxyHandler( + app); + + @Test + public void can_get_injected_resources() throws Exception { + + JerseyInjectionTest instance1 = handler.getInjectionManager().getInstance(JerseyInjectionTest.class); + assertNotNull(instance1); + + JerseyInjectionTest instance2 = handler.getInjectionManager().getInstance(JerseyInjectionTest.class); + assertEquals(instance1, instance2); + + } +} From 2a4de7fa06d2ad0aef53444f0b6b80287c016b47 Mon Sep 17 00:00:00 2001 From: sapessi Date: Thu, 20 Jun 2019 08:41:12 -0700 Subject: [PATCH 02/14] Bump jackson version to 2.9.9 to address security alerts --- aws-serverless-java-container-core/pom.xml | 2 +- aws-serverless-java-container-jersey/pom.xml | 2 +- aws-serverless-java-container-spring/pom.xml | 2 +- aws-serverless-java-container-struts2/pom.xml | 2 +- .../src/main/resources/archetype-resources/pom.xml | 2 +- .../src/main/resources/archetype-resources/pom.xml | 2 +- .../src/main/resources/archetype-resources/pom.xml | 2 +- samples/jersey/pet-store/pom.xml | 2 +- samples/spark/pet-store/pom.xml | 2 +- samples/struts/pet-store/pom.xml | 2 +- 10 files changed, 10 insertions(+), 10 deletions(-) diff --git a/aws-serverless-java-container-core/pom.xml b/aws-serverless-java-container-core/pom.xml index 02a535a35..82d218f9d 100644 --- a/aws-serverless-java-container-core/pom.xml +++ b/aws-serverless-java-container-core/pom.xml @@ -16,7 +16,7 @@ - 2.9.8 + 2.9.9 2.1 3.1.0 diff --git a/aws-serverless-java-container-jersey/pom.xml b/aws-serverless-java-container-jersey/pom.xml index b278936f0..f92c9d702 100644 --- a/aws-serverless-java-container-jersey/pom.xml +++ b/aws-serverless-java-container-jersey/pom.xml @@ -60,7 +60,7 @@ com.fasterxml.jackson.core jackson-databind - 2.9.8 + 2.9.9 true test diff --git a/aws-serverless-java-container-spring/pom.xml b/aws-serverless-java-container-spring/pom.xml index 22ce578bd..bbab50a57 100644 --- a/aws-serverless-java-container-spring/pom.xml +++ b/aws-serverless-java-container-spring/pom.xml @@ -19,7 +19,7 @@ 5.1.1.RELEASE 1.5.17.RELEASE 5.1.1.RELEASE - 2.9.8 + 2.9.9 diff --git a/aws-serverless-java-container-struts2/pom.xml b/aws-serverless-java-container-struts2/pom.xml index 49942915c..f981e0876 100644 --- a/aws-serverless-java-container-struts2/pom.xml +++ b/aws-serverless-java-container-struts2/pom.xml @@ -16,7 +16,7 @@ 2.5.20 - 2.9.8 + 2.9.9 diff --git a/aws-serverless-jersey-archetype/src/main/resources/archetype-resources/pom.xml b/aws-serverless-jersey-archetype/src/main/resources/archetype-resources/pom.xml index b94697c7f..549511720 100644 --- a/aws-serverless-jersey-archetype/src/main/resources/archetype-resources/pom.xml +++ b/aws-serverless-jersey-archetype/src/main/resources/archetype-resources/pom.xml @@ -15,7 +15,7 @@ 1.8 1.8 2.27 - 2.9.8 + 2.9.9 diff --git a/aws-serverless-spark-archetype/src/main/resources/archetype-resources/pom.xml b/aws-serverless-spark-archetype/src/main/resources/archetype-resources/pom.xml index eec41cd2a..ca9d4dd18 100644 --- a/aws-serverless-spark-archetype/src/main/resources/archetype-resources/pom.xml +++ b/aws-serverless-spark-archetype/src/main/resources/archetype-resources/pom.xml @@ -16,7 +16,7 @@ 1.8 1.8 - 2.9.8 + 2.9.9 2.8.0 diff --git a/aws-serverless-struts2-archetype/src/main/resources/archetype-resources/pom.xml b/aws-serverless-struts2-archetype/src/main/resources/archetype-resources/pom.xml index a1ca2962f..3e224f4a5 100644 --- a/aws-serverless-struts2-archetype/src/main/resources/archetype-resources/pom.xml +++ b/aws-serverless-struts2-archetype/src/main/resources/archetype-resources/pom.xml @@ -16,7 +16,7 @@ 1.8 1.8 2.5.20 - 2.9.8 + 2.9.9 4.12 2.11.1 diff --git a/samples/jersey/pet-store/pom.xml b/samples/jersey/pet-store/pom.xml index daf6b67c0..d85e13b08 100644 --- a/samples/jersey/pet-store/pom.xml +++ b/samples/jersey/pet-store/pom.xml @@ -77,7 +77,7 @@ com.fasterxml.jackson.core jackson-databind - 2.9.8 + 2.9.9 diff --git a/samples/spark/pet-store/pom.xml b/samples/spark/pet-store/pom.xml index 09502efd0..32181f3c9 100644 --- a/samples/spark/pet-store/pom.xml +++ b/samples/spark/pet-store/pom.xml @@ -26,7 +26,7 @@ 1.8 1.8 - 2.9.8 + 2.9.9 2.8.0 diff --git a/samples/struts/pet-store/pom.xml b/samples/struts/pet-store/pom.xml index b104bd716..5763b94a8 100644 --- a/samples/struts/pet-store/pom.xml +++ b/samples/struts/pet-store/pom.xml @@ -27,7 +27,7 @@ 1.8 1.8 2.5.20 - 2.9.8 + 2.9.9 4.12 2.11.1 From 2d1646380d9c0a09428c84f7f030952372f1754d Mon Sep 17 00:00:00 2001 From: sapessi Date: Thu, 20 Jun 2019 09:06:16 -0700 Subject: [PATCH 03/14] Added tests to validate #262 and jackson annotation to isBase64Encoded field in the request object --- .../proxy/model/AwsProxyRequest.java | 3 +- .../proxy/model/AwsProxyRequestTest.java | 92 ++++++++++++++++++- 2 files changed, 90 insertions(+), 5 deletions(-) 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 afea70f98..d361d6741 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 @@ -14,6 +14,7 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; import java.util.HashMap; import java.util.Map; @@ -167,7 +168,7 @@ public void setPath(String path) { this.path = path; } - + @JsonProperty("isBase64Encoded") public boolean isBase64Encoded() { return isBase64Encoded; } 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 index 6763bbda5..0b82c4bd3 100644 --- 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 @@ -1,12 +1,13 @@ package com.amazonaws.serverless.proxy.model; import com.amazonaws.serverless.proxy.internal.testutils.AwsProxyRequestBuilder; +import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.Test; import java.io.IOException; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; +import static junit.framework.TestCase.assertTrue; +import static org.junit.Assert.*; public class AwsProxyRequestTest { private static final String CUSTOM_HEADER_KEY_LOWER_CASE = "custom-header"; @@ -67,13 +68,96 @@ public class AwsProxyRequestTest { " \"apiId\": \"apiId\"\n" + " },\n" + " \"body\": null,\n" + - " \"isBase64Encoded\": false\n" + + " \"isBase64Encoded\": true\n" + "}"; @Test public void deserialize_multiValuedHeaders_caseInsensitive() throws IOException { - AwsProxyRequest req = new AwsProxyRequestBuilder().fromJsonString(REQUEST_JSON).build(); + AwsProxyRequest req = new AwsProxyRequestBuilder() + .fromJsonString(getRequestJson(true, CUSTOM_HEADER_KEY_LOWER_CASE, CUSTOM_HEADER_VALUE)).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)); + assertTrue(req.isBase64Encoded()); + } + + @Test + public void deserialize_base64Encoded_readsBoolCorrectly() throws IOException { + AwsProxyRequest req = new AwsProxyRequestBuilder() + .fromJsonString(getRequestJson(true, CUSTOM_HEADER_KEY_LOWER_CASE, CUSTOM_HEADER_VALUE)).build(); + assertTrue(req.isBase64Encoded()); + req = new AwsProxyRequestBuilder() + .fromJsonString(getRequestJson(false, CUSTOM_HEADER_KEY_LOWER_CASE, CUSTOM_HEADER_VALUE)).build(); + assertFalse(req.isBase64Encoded()); + } + + @Test + public void serialize_base64Encoded_fieldContainsIsPrefix() throws IOException { + AwsProxyRequest req = new AwsProxyRequestBuilder() + .fromJsonString(getRequestJson(true, CUSTOM_HEADER_KEY_LOWER_CASE, CUSTOM_HEADER_VALUE)).build(); + ObjectMapper mapper = new ObjectMapper(); + String serializedRequest = mapper.writeValueAsString(req); + System.out.println(serializedRequest); + assertTrue(serializedRequest.contains("\"isBase64Encoded\":true")); + } + + private String getRequestJson(boolean base64Encoded, String headerKey, String headerValue) { + return "{\n" + + " \"resource\": \"/api/{proxy+}\",\n" + + " \"path\": \"/api/endpoint\",\n" + + " \"httpMethod\": \"OPTIONS\",\n" + + " \"headers\": {\n" + + " \"Accept\": \"*/*\",\n" + + " \"User-Agent\": \"PostmanRuntime/7.1.1\",\n" + + " \"" + headerKey +"\":" + "\"" + headerValue + "\"\n" + + " },\n" + + " \"multiValueHeaders\": {\n" + + " \"Accept\": [\n" + + " \"*/*\"\n" + + " ],\n" + + " \"User-Agent\": [\n" + + " \"PostmanRuntime/7.1.1\"\n" + + " ],\n" + + " \"" + headerKey + "\": [\n" + + " \"" + headerValue + "\"\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\": " + (base64Encoded?"true":"false") + "\n" + + "}"; } } From 0e6468c5be98e38352a84c002db258fe46f05eb9 Mon Sep 17 00:00:00 2001 From: sapessi Date: Thu, 20 Jun 2019 09:08:01 -0700 Subject: [PATCH 04/14] Changed log message severity in setCharacterEncoding to debug to address issue #261 --- .../proxy/internal/servlet/AwsProxyHttpServletRequest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsProxyHttpServletRequest.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsProxyHttpServletRequest.java index 7783a95a6..96c925b76 100644 --- a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsProxyHttpServletRequest.java +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsProxyHttpServletRequest.java @@ -366,7 +366,7 @@ public void setCharacterEncoding(String s) throws UnsupportedEncodingException { String currentContentType = request.getMultiValueHeaders().getFirst(HttpHeaders.CONTENT_TYPE); if (currentContentType == null || "".equals(currentContentType)) { - log.error("Called set character encoding to " + SecurityUtils.crlf(s) + " on a request without a content type. Character encoding will not be set"); + log.debug("Called set character encoding to " + SecurityUtils.crlf(s) + " on a request without a content type. Character encoding will not be set"); return; } From 4032d14733c43a48e09634e3136c29fc2ccac642 Mon Sep 17 00:00:00 2001 From: sapessi Date: Thu, 20 Jun 2019 10:34:03 -0700 Subject: [PATCH 05/14] Change parse header function to handle base64 encoded values in headers (#263) --- .../internal/servlet/AwsHttpServletRequest.java | 12 +++++++++++- .../internal/servlet/AwsHttpServletRequestTest.java | 12 ++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpServletRequest.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpServletRequest.java index 86ce62500..7fa9d138f 100644 --- a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpServletRequest.java +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpServletRequest.java @@ -13,12 +13,18 @@ package com.amazonaws.serverless.proxy.internal.servlet; import com.amazonaws.serverless.proxy.RequestReader; +import com.amazonaws.serverless.proxy.internal.LambdaContainerHandler; import com.amazonaws.serverless.proxy.internal.SecurityUtils; import com.amazonaws.serverless.proxy.model.AwsProxyRequestContext; import com.amazonaws.serverless.proxy.model.ContainerConfig; import com.amazonaws.serverless.proxy.model.MultiValuedTreeMap; import com.amazonaws.services.lambda.runtime.Context; +import com.fasterxml.jackson.core.JsonProcessingException; +import org.apache.http.HeaderElement; +import org.apache.http.message.BasicHeaderValueParser; +import org.apache.http.message.ParserCursor; +import org.apache.http.util.CharArrayBuffer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -77,6 +83,7 @@ public abstract class AwsHttpServletRequest implements HttpServletRequest { private ServletContext servletContext; private AwsHttpSession session; private String queryString; + private BasicHeaderValueParser headerParser; protected DispatcherType dispatcherType; @@ -95,6 +102,7 @@ public abstract class AwsHttpServletRequest implements HttpServletRequest { AwsHttpServletRequest(Context lambdaContext) { this.lambdaContext = lambdaContext; attributes = new HashMap<>(); + headerParser = new BasicHeaderValueParser(); } @@ -352,6 +360,7 @@ protected List parseHeaderValue(String headerValue, String valueSep // Accept: text/html, application/xhtml+xml, application/xml;q=0.9, */*;q=0.8 // Accept-Language: fr-CH, fr;q=0.9, en;q=0.8, de;q=0.7, *;q=0.5 // Cookie: name=value; name2=value2; name3=value3 + // X-Custom-Header: YQ== List values = new ArrayList<>(); if (headerValue == null) { @@ -365,7 +374,8 @@ protected List parseHeaderValue(String headerValue, String valueSep newValue.setRawValue(v); for (String q : curValue.split(qualifierSeparator)) { - if (q.contains(HEADER_KEY_VALUE_SEPARATOR)) { + // contains key/value pairs and it's not a base64-encoded value. + if (q.contains(HEADER_KEY_VALUE_SEPARATOR) && !q.trim().endsWith("==")) { String[] kv = q.split(HEADER_KEY_VALUE_SEPARATOR); // TODO: Should we concatenate the rest of the values? if (newValue.getValue() == null) { diff --git a/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpServletRequestTest.java b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpServletRequestTest.java index 453a01af6..c0f20f193 100644 --- a/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpServletRequestTest.java +++ b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpServletRequestTest.java @@ -14,6 +14,7 @@ import static org.junit.Assert.*; +import java.util.Base64; import java.util.List; @@ -75,6 +76,17 @@ public void headers_parseHeaderValue_complexAccept() { assertEquals(4, values.size()); } + @Test + public void headers_parseHeaderValue_encodedContentWithEquals() { + AwsHttpServletRequest context = new AwsProxyHttpServletRequest(null,null,null); + + String value = Base64.getUrlEncoder().encodeToString("a".getBytes()); + + List result = context.parseHeaderValue(value); + + assertEquals("YQ==", result.get(0).getValue()); + } + @Test public void queryString_generateQueryString_validQuery() { AwsProxyHttpServletRequest request = new AwsProxyHttpServletRequest(queryString, mockContext, null, config); From 9098e3e6e44b69c06f5e368b2fc9fc64554af941 Mon Sep 17 00:00:00 2001 From: sapessi Date: Thu, 20 Jun 2019 10:43:29 -0700 Subject: [PATCH 06/14] Fixed bug with multipart form field name and file name being inverted and added unit test (#258) --- .../proxy/internal/servlet/AwsProxyHttpServletRequest.java | 4 ++-- .../servlet/AwsProxyHttpServletRequestFormTest.java | 6 +++++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsProxyHttpServletRequest.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsProxyHttpServletRequest.java index 96c925b76..dd77135fa 100644 --- a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsProxyHttpServletRequest.java +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsProxyHttpServletRequest.java @@ -709,8 +709,8 @@ private Map getMultipartFormParametersMap() { for (FileItem item : items) { String fileName = FilenameUtils.getName(item.getName()); AwsProxyRequestPart newPart = new AwsProxyRequestPart(item.get()); - newPart.setName(fileName); - newPart.setSubmittedFileName(item.getFieldName()); + newPart.setName(item.getFieldName()); + newPart.setSubmittedFileName(fileName); newPart.setContentType(item.getContentType()); newPart.setSize(item.getSize()); item.getHeaders().getHeaderNames().forEachRemaining(h -> { diff --git a/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/AwsProxyHttpServletRequestFormTest.java b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/AwsProxyHttpServletRequestFormTest.java index ff09b7dd6..b20982b7c 100644 --- a/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/AwsProxyHttpServletRequestFormTest.java +++ b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/AwsProxyHttpServletRequestFormTest.java @@ -6,6 +6,7 @@ import org.apache.commons.io.IOUtils; import org.apache.http.HttpEntity; +import org.apache.http.entity.ContentType; import org.apache.http.entity.mime.MultipartEntityBuilder; import org.junit.Test; @@ -31,6 +32,7 @@ public class AwsProxyHttpServletRequestFormTest { private static final String PART_KEY_2 = "test2"; private static final String PART_VALUE_2 = "value2"; private static final String FILE_KEY = "file_upload_1"; + private static final String FILE_NAME = "testImage.jpg"; private static final String ENCODED_VALUE = "test123a%3D1%262@3"; @@ -46,7 +48,7 @@ public class AwsProxyHttpServletRequestFormTest { private static final HttpEntity MULTIPART_BINARY_DATA = MultipartEntityBuilder.create() .addTextBody(PART_KEY_1, PART_VALUE_1) .addTextBody(PART_KEY_2, PART_VALUE_2) - .addBinaryBody(FILE_KEY, FILE_BYTES) + .addBinaryBody(FILE_KEY, FILE_BYTES, ContentType.IMAGE_JPEG, FILE_NAME) .build(); private static final String ENCODED_FORM_ENTITY = PART_KEY_1 + "=" + ENCODED_VALUE + "&" + PART_KEY_2 + "=" + PART_VALUE_2; @@ -99,6 +101,8 @@ public void multipart_getParts_binary() { assertEquals(3, request.getParts().size()); assertNotNull(request.getPart(FILE_KEY)); assertEquals(FILE_SIZE, request.getPart(FILE_KEY).getSize()); + assertEquals(FILE_KEY, request.getPart(FILE_KEY).getName()); + assertEquals(FILE_NAME, request.getPart(FILE_KEY).getSubmittedFileName()); assertEquals(PART_VALUE_1, IOUtils.toString(request.getPart(PART_KEY_1).getInputStream())); assertEquals(PART_VALUE_2, IOUtils.toString(request.getPart(PART_KEY_2).getInputStream())); } catch (IOException | ServletException e) { From c6f96b4f0b444a90c6b28ca390c7310ccd1c97af Mon Sep 17 00:00:00 2001 From: sapessi Date: Thu, 20 Jun 2019 11:08:59 -0700 Subject: [PATCH 07/14] Fix for empty header values as well as empty query string values (#247) --- .../servlet/AwsHttpServletRequest.java | 27 ++++++++++++------- .../servlet/AwsHttpServletRequestTest.java | 15 +++++++++++ 2 files changed, 32 insertions(+), 10 deletions(-) diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpServletRequest.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpServletRequest.java index 7fa9d138f..3aabc35ff 100644 --- a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpServletRequest.java +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpServletRequest.java @@ -320,10 +320,12 @@ protected String generateQueryString(MultiValuedTreeMap paramete queryStringBuilder.append(key); } queryStringBuilder.append("="); - if (encode) { - queryStringBuilder.append(URLEncoder.encode(val, encodeCharset)); - } else { - queryStringBuilder.append(val); + if (val != null) { + if (encode) { + queryStringBuilder.append(URLEncoder.encode(val, encodeCharset)); + } else { + queryStringBuilder.append(val); + } } } } @@ -342,7 +344,7 @@ protected String generateQueryString(MultiValuedTreeMap paramete * @param headerValue The value to be parsed * @return A list of SimpleMapEntry objects with all of the possible values for the header. */ - protected List parseHeaderValue(String headerValue) { + protected List parseHeaderValue(String headerValue) { return parseHeaderValue(headerValue, HEADER_VALUE_SEPARATOR, HEADER_QUALIFIER_SEPARATOR); } @@ -377,16 +379,21 @@ protected List parseHeaderValue(String headerValue, String valueSep // contains key/value pairs and it's not a base64-encoded value. if (q.contains(HEADER_KEY_VALUE_SEPARATOR) && !q.trim().endsWith("==")) { String[] kv = q.split(HEADER_KEY_VALUE_SEPARATOR); + String key = kv[0].trim(); + String val = null; + if (kv.length > 1) { + val = kv[1].trim(); + } // TODO: Should we concatenate the rest of the values? if (newValue.getValue() == null) { - newValue.setKey(kv[0].trim()); - newValue.setValue(kv[1].trim()); + newValue.setKey(key); + newValue.setValue(val); } else { // special case for quality q= - if ("q".equals(kv[0].trim())) { - curPreference = Float.parseFloat(kv[1].trim()); + if ("q".equals(key)) { + curPreference = Float.parseFloat(val); } else { - newValue.addAttribute(kv[0].trim(), kv[1].trim()); + newValue.addAttribute(key, val); } } } else { diff --git a/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpServletRequestTest.java b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpServletRequestTest.java index c0f20f193..15a98c0c2 100644 --- a/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpServletRequestTest.java +++ b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpServletRequestTest.java @@ -28,6 +28,8 @@ public class AwsHttpServletRequestTest { .header(HttpHeaders.ACCEPT, "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8").build(); private static final AwsProxyRequest queryString = new AwsProxyRequestBuilder("/test", "GET") .queryString("one", "two").queryString("three", "four").build(); + private static final AwsProxyRequest queryStringNullValue = new AwsProxyRequestBuilder("/test", "GET") + .queryString("one", "two").queryString("three", null).build(); private static final AwsProxyRequest encodedQueryString = new AwsProxyRequestBuilder("/test", "GET") .queryString("one", "two").queryString("json", "{\"name\":\"faisal\"}").build(); private static final AwsProxyRequest multipleParams = new AwsProxyRequestBuilder("/test", "GET") @@ -104,6 +106,19 @@ public void queryString_generateQueryString_validQuery() { assertTrue(parsedString.contains("&") && parsedString.indexOf("&") > 0 && parsedString.indexOf("&") < parsedString.length()); } + @Test + public void queryString_generateQueryString_nullParameterIsEmpty() { + AwsProxyHttpServletRequest request = new AwsProxyHttpServletRequest(queryStringNullValue, mockContext, null, config);String parsedString = null; + try { + parsedString = request.generateQueryString(request.getAwsProxyRequest().getMultiValueQueryStringParameters(), true, config.getUriEncoding()); + } catch (ServletException e) { + e.printStackTrace(); + fail("Could not generate query string"); + } + + assertTrue(parsedString.endsWith("three=")); + } + @Test public void queryStringWithEncodedParams_generateQueryString_validQuery() { AwsProxyHttpServletRequest request = new AwsProxyHttpServletRequest(encodedQueryString, mockContext, null, config); From 6cf2398a1578449b5599a178e819c9e69d17ccf8 Mon Sep 17 00:00:00 2001 From: sapessi Date: Thu, 20 Jun 2019 11:48:21 -0700 Subject: [PATCH 08/14] Additional unit tests on Jersey to validate that it honors the produces annotations (#250) --- .../proxy/jersey/EchoJerseyResource.java | 8 ++++++++ .../proxy/jersey/JerseyAwsProxyTest.java | 14 ++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/aws-serverless-java-container-jersey/src/test/java/com/amazonaws/serverless/proxy/jersey/EchoJerseyResource.java b/aws-serverless-java-container-jersey/src/test/java/com/amazonaws/serverless/proxy/jersey/EchoJerseyResource.java index 1e62291d8..da67dcf36 100644 --- a/aws-serverless-java-container-jersey/src/test/java/com/amazonaws/serverless/proxy/jersey/EchoJerseyResource.java +++ b/aws-serverless-java-container-jersey/src/test/java/com/amazonaws/serverless/proxy/jersey/EchoJerseyResource.java @@ -41,6 +41,7 @@ import java.io.IOException; import java.io.InputStream; import java.util.Enumeration; +import java.util.HashMap; import java.util.List; import java.util.Random; @@ -272,4 +273,11 @@ public Response fileSize(@FormDataParam("file") final File uploadedFile, return Response.status(500).build(); } } + + @Path("/plain") @GET + @Produces(MediaType.TEXT_PLAIN) + @Consumes(MediaType.APPLICATION_JSON) + public Response plain() { + return Response.status(200).entity("Hello!").build(); + } } diff --git a/aws-serverless-java-container-jersey/src/test/java/com/amazonaws/serverless/proxy/jersey/JerseyAwsProxyTest.java b/aws-serverless-java-container-jersey/src/test/java/com/amazonaws/serverless/proxy/jersey/JerseyAwsProxyTest.java index 491797957..c54de6c5a 100644 --- a/aws-serverless-java-container-jersey/src/test/java/com/amazonaws/serverless/proxy/jersey/JerseyAwsProxyTest.java +++ b/aws-serverless-java-container-jersey/src/test/java/com/amazonaws/serverless/proxy/jersey/JerseyAwsProxyTest.java @@ -363,6 +363,20 @@ public void refererHeader_headerParam_expectCorrectInjection() { validateSingleValueModel(resp, refererValue); } + @Test + public void textPlainContent_plain_responseHonorsContentType() { + AwsProxyRequest req = getRequestBuilder("/echo/plain", "GET") + .nullBody() + .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON) + .header(HttpHeaders.ACCEPT, MediaType.TEXT_PLAIN) + .build(); + + AwsProxyResponse resp = handler.proxy(req, lambdaContext); + assertEquals(200, resp.getStatusCode()); + assertTrue(resp.getMultiValueHeaders().containsKey(HttpHeaders.CONTENT_TYPE)); + assertEquals(MediaType.TEXT_PLAIN, resp.getMultiValueHeaders().get(HttpHeaders.CONTENT_TYPE).get(0)); + } + private void validateMapResponseModel(AwsProxyResponse output) { validateMapResponseModel(output, CUSTOM_HEADER_KEY, CUSTOM_HEADER_VALUE); } From c01c98fc338138d264bb2dd0ff823ce22d4bf8ad Mon Sep 17 00:00:00 2001 From: sapessi Date: Fri, 21 Jun 2019 11:03:03 -0700 Subject: [PATCH 09/14] Fixed a potential null pointer in security utils and simplified header processing to continue addressing the feedback in #263 --- .../proxy/internal/SecurityUtils.java | 3 + .../servlet/AwsHttpServletRequest.java | 59 +++++++++++-------- .../servlet/AwsHttpServletRequestTest.java | 41 ++++++++++++- 3 files changed, 76 insertions(+), 27 deletions(-) diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/SecurityUtils.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/SecurityUtils.java index 03dced873..1d39d849b 100644 --- a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/SecurityUtils.java +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/SecurityUtils.java @@ -70,6 +70,9 @@ public static boolean isValidHost(String host, String apiId, String region) { * @return A copy of the original string without CRLF characters */ public static String crlf(String s) { + if (s == null) { + return null; + } return s.replaceAll("[\r\n]", ""); } diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpServletRequest.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpServletRequest.java index 3aabc35ff..8712285ac 100644 --- a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpServletRequest.java +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpServletRequest.java @@ -13,18 +13,13 @@ package com.amazonaws.serverless.proxy.internal.servlet; import com.amazonaws.serverless.proxy.RequestReader; -import com.amazonaws.serverless.proxy.internal.LambdaContainerHandler; import com.amazonaws.serverless.proxy.internal.SecurityUtils; import com.amazonaws.serverless.proxy.model.AwsProxyRequestContext; import com.amazonaws.serverless.proxy.model.ContainerConfig; import com.amazonaws.serverless.proxy.model.MultiValuedTreeMap; import com.amazonaws.services.lambda.runtime.Context; -import com.fasterxml.jackson.core.JsonProcessingException; -import org.apache.http.HeaderElement; import org.apache.http.message.BasicHeaderValueParser; -import org.apache.http.message.ParserCursor; -import org.apache.http.util.CharArrayBuffer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -376,31 +371,44 @@ protected List parseHeaderValue(String headerValue, String valueSep newValue.setRawValue(v); for (String q : curValue.split(qualifierSeparator)) { - // contains key/value pairs and it's not a base64-encoded value. - if (q.contains(HEADER_KEY_VALUE_SEPARATOR) && !q.trim().endsWith("==")) { - String[] kv = q.split(HEADER_KEY_VALUE_SEPARATOR); - String key = kv[0].trim(); - String val = null; - if (kv.length > 1) { - val = kv[1].trim(); + + String[] kv = q.split(HEADER_KEY_VALUE_SEPARATOR, 2); + String key = null; + String val = null; + // no separator, set the value only + if (kv.length == 1) { + val = q.trim(); + } + // we have a separator + if (kv.length == 2) { + // if the length of the value is 0 we assume that we are looking at a + // base64 encoded value with padding so we just set the value. This is because + // we assume that empty values in a key/value pair will contain at least a white space + if (kv[1].length() == 0) { + val = q.trim(); } - // TODO: Should we concatenate the rest of the values? - if (newValue.getValue() == null) { - newValue.setKey(key); - newValue.setValue(val); - } else { - // special case for quality q= - if ("q".equals(key)) { - curPreference = Float.parseFloat(val); - } else { - newValue.addAttribute(key, val); - } + // this was a base64 string with an additional = for padding, set the value only + if ("=".equals(kv[1].trim())) { + val = q.trim(); + } else { // it's a proper key/value set both + key = kv[0].trim(); + val = ("".equals(kv[1].trim()) ? null : kv[1].trim()); } + } + + if (newValue.getValue() == null) { + newValue.setKey(key); + newValue.setValue(val); } else { - newValue.setValue(q.trim()); + // special case for quality q= + if ("q".equals(key)) { + curPreference = Float.parseFloat(val); + } else { + newValue.addAttribute(key, val); + } } - newValue.setPriority(curPreference); } + newValue.setPriority(curPreference); values.add(newValue); } @@ -428,7 +436,6 @@ protected String decodeRequestPath(String requestPath, ContainerConfig config) { } - /** * Class that represents a header value. */ diff --git a/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpServletRequestTest.java b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpServletRequestTest.java index 15a98c0c2..3cebc02fb 100644 --- a/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpServletRequestTest.java +++ b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpServletRequestTest.java @@ -10,6 +10,7 @@ import org.junit.Test; import javax.servlet.ServletException; +import javax.servlet.http.Cookie; import javax.ws.rs.core.HttpHeaders; import static org.junit.Assert.*; @@ -85,10 +86,48 @@ public void headers_parseHeaderValue_encodedContentWithEquals() { String value = Base64.getUrlEncoder().encodeToString("a".getBytes()); List result = context.parseHeaderValue(value); - + assertTrue(result.size() > 0); assertEquals("YQ==", result.get(0).getValue()); } + @Test + public void headers_parseHeaderValue_base64EncodedCookieValue() { + String value = Base64.getUrlEncoder().encodeToString("a".getBytes()); + String cookieValue = "jwt=" + value + "; secondValue=second"; + AwsProxyRequest req = new AwsProxyRequestBuilder("/test", "GET").header(HttpHeaders.COOKIE, cookieValue).build(); + AwsHttpServletRequest context = new AwsProxyHttpServletRequest(req,null,null); + + Cookie[] cookies = context.getCookies(); + + assertEquals(2, cookies.length); + assertEquals("jwt", cookies[0].getName()); + assertEquals(value, cookies[0].getValue()); + } + + @Test + public void headers_parseHeaderValue_cookieWithSeparatorInValue() { + String cookieValue = "jwt==test; secondValue=second"; + AwsProxyRequest req = new AwsProxyRequestBuilder("/test", "GET").header(HttpHeaders.COOKIE, cookieValue).build(); + AwsHttpServletRequest context = new AwsProxyHttpServletRequest(req,null,null); + + Cookie[] cookies = context.getCookies(); + + assertEquals(2, cookies.length); + assertEquals("jwt", cookies[0].getName()); + assertEquals("=test", cookies[0].getValue()); + } + + @Test + public void headers_parseHeaderValue_headerWithPaddingButNotBase64Encoded() { + AwsHttpServletRequest context = new AwsProxyHttpServletRequest(null,null,null); + + List result = context.parseHeaderValue("hello="); + assertTrue(result.size() > 0); + assertEquals("hello", result.get(0).getKey()); + System.out.println("\"" + result.get(0).getValue() + "\""); + assertNull(result.get(0).getValue()); + } + @Test public void queryString_generateQueryString_validQuery() { AwsProxyHttpServletRequest request = new AwsProxyHttpServletRequest(queryString, mockContext, null, config); From f25293917725374804b08e00fa44408d6e5e22f9 Mon Sep 17 00:00:00 2001 From: sapessi Date: Wed, 26 Jun 2019 13:40:40 -0700 Subject: [PATCH 10/14] Added unit test for static files (#254) and fixed typo in unit test name --- .../proxy/spring/SpringAwsProxyTest.java | 2 +- .../proxy/spring/SpringBootAppTest.java | 16 ++++++++++++++++ .../src/test/resources/static/static.html | 5 +++++ 3 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 aws-serverless-java-container-spring/src/test/resources/static/static.html diff --git a/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/SpringAwsProxyTest.java b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/SpringAwsProxyTest.java index e287aa953..3fe730da1 100644 --- a/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/SpringAwsProxyTest.java +++ b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/SpringAwsProxyTest.java @@ -382,7 +382,7 @@ public void contextPath_generateLink_returnsCorrectPath() { } @Test - public void multipart_getFileName_rerutrnsCorrectFileName() + public void multipart_getFileName_returnsCorrectFileName() throws IOException { AwsProxyRequest request = new AwsProxyRequestBuilder("/echo/attachment", "POST") .formFilePart("testFile", "myFile.txt", "hello".getBytes()) diff --git a/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/SpringBootAppTest.java b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/SpringBootAppTest.java index 5e3922f7a..f27d6e57a 100644 --- a/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/SpringBootAppTest.java +++ b/aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/SpringBootAppTest.java @@ -1,6 +1,7 @@ package com.amazonaws.serverless.proxy.spring; +import com.amazonaws.serverless.proxy.internal.LambdaContainerHandler; import com.amazonaws.serverless.proxy.internal.testutils.AwsProxyRequestBuilder; import com.amazonaws.serverless.proxy.internal.testutils.MockLambdaContext; import com.amazonaws.serverless.proxy.model.AwsProxyRequest; @@ -13,6 +14,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.Test; +import javax.ws.rs.core.HttpHeaders; import java.io.IOException; import static org.junit.Assert.*; @@ -74,6 +76,20 @@ public void queryString_commaSeparatedList_expectUnmarshalAsList() { validateSingleValueModel(resp, "3"); } + @Test + public void staticContent_getHtmlFile_returnsHtmlContent() { + LambdaContainerHandler.getContainerConfig().addValidFilePath("/Users/bulianis/workspace/aws-serverless-java-container/aws-serverless-java-container-spring"); + AwsProxyRequest request = new AwsProxyRequestBuilder("/static.html", "GET") + .header(HttpHeaders.ACCEPT, "text/html") + .header(HttpHeaders.CONTENT_TYPE, "text/plain") + .build(); + AwsProxyResponse output = handler.handleRequest(request, context); + System.out.println("Response: " + output.getBody()); + assertEquals(200, output.getStatusCode()); + assertTrue(output.getBody().contains("

Static

")); + } + + private void validateSingleValueModel(AwsProxyResponse output, String value) { try { SingleValueModel response = mapper.readValue(output.getBody(), SingleValueModel.class); diff --git a/aws-serverless-java-container-spring/src/test/resources/static/static.html b/aws-serverless-java-container-spring/src/test/resources/static/static.html new file mode 100644 index 000000000..5231067b7 --- /dev/null +++ b/aws-serverless-java-container-spring/src/test/resources/static/static.html @@ -0,0 +1,5 @@ + + +

Static

+ + \ No newline at end of file From 120129687af65ab4bf9f8d09d87e5b31645aa495 Mon Sep 17 00:00:00 2001 From: sapessi Date: Wed, 26 Jun 2019 13:55:00 -0700 Subject: [PATCH 11/14] Added null check before reading from the request input stream (#147) --- .../proxy/internal/servlet/AwsProxyHttpServletRequest.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsProxyHttpServletRequest.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsProxyHttpServletRequest.java index dd77135fa..a4d0ab816 100644 --- a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsProxyHttpServletRequest.java +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsProxyHttpServletRequest.java @@ -892,6 +892,9 @@ public void setReadListener(ReadListener readListener) { @Override public int read() throws IOException { + if (bodyStream == null || bodyStream instanceof NullInputStream) { + return -1; + } int readByte = bodyStream.read(); if (bodyStream.available() == 0 && listener != null) { listener.onAllDataRead(); From 6ff3b669e8ccf6ebef04f40c11cee02abde5e941 Mon Sep 17 00:00:00 2001 From: sapessi Date: Thu, 27 Jun 2019 09:41:39 -0700 Subject: [PATCH 12/14] changed the behavior of the getMimeType method to always try to load the mime type from the file name, without probing the file system. Updated unit tests to match (#254) --- .../internal/servlet/AwsServletContext.java | 17 +++++++++------ .../servlet/AwsServletContextTest.java | 21 ++++++++++++------- 2 files changed, 24 insertions(+), 14 deletions(-) diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsServletContext.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsServletContext.java index 5a6dc85d4..09cc00d3b 100644 --- a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsServletContext.java +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsServletContext.java @@ -20,6 +20,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import javax.activation.MimetypesFileTypeMap; import javax.servlet.Filter; import javax.servlet.FilterRegistration; import javax.servlet.RequestDispatcher; @@ -72,6 +73,7 @@ public class AwsServletContext private Map initParameters; private AwsLambdaServletContainerHandler containerHandler; private Logger log = LoggerFactory.getLogger(AwsServletContext.class); + private MimetypesFileTypeMap mimeTypes; // lazily loaded in the getMimeType method //------------------------------------------------------------- @@ -142,13 +144,16 @@ public int getEffectiveMinorVersion() { @Override @SuppressFBWarnings("PATH_TRAVERSAL_IN") // suppressing because we are using the getValidFilePath public String getMimeType(String s) { - try { - String validatedPath = SecurityUtils.getValidFilePath(s); - return Files.probeContentType(Paths.get(validatedPath)); - } catch (IOException e) { - log.warn("Could not find content type for file: " + SecurityUtils.encode(s), e); + if (!s.contains(".")) { return null; } + if (mimeTypes == null) { + mimeTypes = new MimetypesFileTypeMap(); + } + // TODO: The getContentType method of the MimetypesFileTypeMap returns application/octet-stream + // instead of null when the type cannot be found. We should replace with an implementation that + // loads the System mime types ($JAVA_HOME/lib/mime.types + return mimeTypes.getContentType(s); } @@ -232,7 +237,7 @@ public String getRealPath(String s) { try { absPath = new File(fileUrl.toURI()).getAbsolutePath(); } catch (URISyntaxException e) { - log.error("Error while looking for real path: {}", SecurityUtils.encode(s), SecurityUtils.encode(e.getMessage())); + log.error("Error while looking for real path {}: {}", SecurityUtils.encode(s), SecurityUtils.encode(e.getMessage())); } } return absPath; diff --git a/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/AwsServletContextTest.java b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/AwsServletContextTest.java index faf298558..0023be659 100644 --- a/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/AwsServletContextTest.java +++ b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/AwsServletContextTest.java @@ -4,6 +4,7 @@ import com.amazonaws.serverless.proxy.internal.servlet.filters.UrlPathValidator; import org.junit.BeforeClass; +import org.junit.Ignore; import org.junit.Test; import javax.servlet.Filter; @@ -43,7 +44,7 @@ public static void setUp() { LambdaContainerHandler.getContainerConfig().addValidFilePath("C:\\MyTestFolder"); } - @Test + @Test @Ignore public void getMimeType_disabledPath_expectException() { AwsServletContext ctx = new AwsServletContext(null); try { @@ -64,7 +65,7 @@ public void getMimeType_nonExistentFileInTaskPath_expectNull() { @Test public void getMimeType_mimeTypeOfCorrectFile_expectMime() { - String tmpFilePath = TMP_DIR + "/test_text.txt"; + String tmpFilePath = TMP_DIR + "test_text.txt"; try { System.out.println("Writing to tmp file " + tmpFilePath); PrintWriter tmpWriter = new PrintWriter(tmpFilePath, "UTF-8"); @@ -73,23 +74,27 @@ public void getMimeType_mimeTypeOfCorrectFile_expectMime() { AwsServletContext ctx = new AwsServletContext(null); String mimeType = ctx.getMimeType(tmpFilePath); - String deducedMimeType = Files.probeContentType(Paths.get(tmpFilePath)); - assertEquals(deducedMimeType, mimeType); + assertEquals("text/plain", mimeType); mimeType = ctx.getMimeType("file://" + tmpFilePath); - assertEquals(deducedMimeType, mimeType); + assertEquals("text/plain", mimeType); } catch (FileNotFoundException e) { fail("tmp file not found"); e.printStackTrace(); } catch (UnsupportedEncodingException e) { fail("Unsupported encoding"); e.printStackTrace(); - } catch (IOException e) { - fail("Failed to prove tmp file content type"); - e.printStackTrace(); } } + @Test + public void getMimeType_unknownExtension_expectAppOctetStream() { + AwsServletContext ctx = new AwsServletContext(null); + String mimeType = ctx.getMimeType("myfile.unkext"); + assertEquals("application/octet-stream", mimeType); + } + + @Test public void addFilter_nonExistentFilterClass_expectException() { AwsServletContext ctx = new AwsServletContext(null); From 915bee3743ef777a32ad9aeaba6099d673db84cb Mon Sep 17 00:00:00 2001 From: sapessi Date: Thu, 27 Jun 2019 09:43:30 -0700 Subject: [PATCH 13/14] whoops, null check. You'd think I would have learned by now (#254) --- .../serverless/proxy/internal/servlet/AwsServletContext.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsServletContext.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsServletContext.java index 09cc00d3b..cb72e9532 100644 --- a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsServletContext.java +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsServletContext.java @@ -144,7 +144,7 @@ public int getEffectiveMinorVersion() { @Override @SuppressFBWarnings("PATH_TRAVERSAL_IN") // suppressing because we are using the getValidFilePath public String getMimeType(String s) { - if (!s.contains(".")) { + if (s == null || !s.contains(".")) { return null; } if (mimeTypes == null) { From 445abc6bd3ca3783b8ef91b356ef8f5e33942ee3 Mon Sep 17 00:00:00 2001 From: sapessi Date: Thu, 27 Jun 2019 10:45:15 -0700 Subject: [PATCH 14/14] Changed unit test not to write to actual file since the getMimeType method does not require the file to exist anymore. --- .../servlet/AwsServletContextTest.java | 25 +++++-------------- 1 file changed, 6 insertions(+), 19 deletions(-) diff --git a/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/AwsServletContextTest.java b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/AwsServletContextTest.java index 0023be659..64c91b8db 100644 --- a/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/AwsServletContextTest.java +++ b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/AwsServletContextTest.java @@ -66,25 +66,12 @@ public void getMimeType_nonExistentFileInTaskPath_expectNull() { @Test public void getMimeType_mimeTypeOfCorrectFile_expectMime() { String tmpFilePath = TMP_DIR + "test_text.txt"; - try { - System.out.println("Writing to tmp file " + tmpFilePath); - PrintWriter tmpWriter = new PrintWriter(tmpFilePath, "UTF-8"); - tmpWriter.write("Test case for aws-serverless-java-container"); - tmpWriter.close(); - - AwsServletContext ctx = new AwsServletContext(null); - String mimeType = ctx.getMimeType(tmpFilePath); - assertEquals("text/plain", mimeType); - - mimeType = ctx.getMimeType("file://" + tmpFilePath); - assertEquals("text/plain", mimeType); - } catch (FileNotFoundException e) { - fail("tmp file not found"); - e.printStackTrace(); - } catch (UnsupportedEncodingException e) { - fail("Unsupported encoding"); - e.printStackTrace(); - } + AwsServletContext ctx = new AwsServletContext(null); + String mimeType = ctx.getMimeType(tmpFilePath); + assertEquals("text/plain", mimeType); + + mimeType = ctx.getMimeType("file://" + tmpFilePath); + assertEquals("text/plain", mimeType); } @Test