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-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 86ce62500..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 @@ -19,6 +19,7 @@ import com.amazonaws.serverless.proxy.model.MultiValuedTreeMap; import com.amazonaws.services.lambda.runtime.Context; +import org.apache.http.message.BasicHeaderValueParser; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -77,6 +78,7 @@ public abstract class AwsHttpServletRequest implements HttpServletRequest { private ServletContext servletContext; private AwsHttpSession session; private String queryString; + private BasicHeaderValueParser headerParser; protected DispatcherType dispatcherType; @@ -95,6 +97,7 @@ public abstract class AwsHttpServletRequest implements HttpServletRequest { AwsHttpServletRequest(Context lambdaContext) { this.lambdaContext = lambdaContext; attributes = new HashMap<>(); + headerParser = new BasicHeaderValueParser(); } @@ -312,10 +315,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); + } } } } @@ -334,7 +339,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); } @@ -352,6 +357,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,25 +371,44 @@ protected List parseHeaderValue(String headerValue, String valueSep newValue.setRawValue(v); for (String q : curValue.split(qualifierSeparator)) { - if (q.contains(HEADER_KEY_VALUE_SEPARATOR)) { - String[] kv = q.split(HEADER_KEY_VALUE_SEPARATOR); - // TODO: Should we concatenate the rest of the values? - if (newValue.getValue() == null) { - newValue.setKey(kv[0].trim()); - newValue.setValue(kv[1].trim()); - } else { - // special case for quality q= - if ("q".equals(kv[0].trim())) { - curPreference = Float.parseFloat(kv[1].trim()); - } else { - newValue.addAttribute(kv[0].trim(), 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(); + } + // 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); } @@ -411,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/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..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 @@ -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; } @@ -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 -> { @@ -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(); 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..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 @@ -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 == null || !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/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/internal/servlet/AwsHttpServletRequestTest.java b/aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpServletRequestTest.java index 453a01af6..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,10 +10,12 @@ import org.junit.Test; import javax.servlet.ServletException; +import javax.servlet.http.Cookie; import javax.ws.rs.core.HttpHeaders; import static org.junit.Assert.*; +import java.util.Base64; import java.util.List; @@ -27,6 +29,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") @@ -75,6 +79,55 @@ 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); + 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); @@ -92,6 +145,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); 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) { 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..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 @@ -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,32 +65,23 @@ 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); - String deducedMimeType = Files.probeContentType(Paths.get(tmpFilePath)); - assertEquals(deducedMimeType, mimeType); - - mimeType = ctx.getMimeType("file://" + tmpFilePath); - assertEquals(deducedMimeType, 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(); - } + String tmpFilePath = TMP_DIR + "test_text.txt"; + AwsServletContext ctx = new AwsServletContext(null); + String mimeType = ctx.getMimeType(tmpFilePath); + assertEquals("text/plain", mimeType); + + mimeType = ctx.getMimeType("file://" + tmpFilePath); + assertEquals("text/plain", mimeType); } + @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); 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" + + "}"; } } 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-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/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); } 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); + + } +} 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-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 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