Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion aws-serverless-java-container-core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
</parent>

<properties>
<jackson.version>2.9.8</jackson.version>
<jackson.version>2.9.9</jackson.version>
<jaxrs.version>2.1</jaxrs.version>
<servlet.version>3.1.0</servlet.version>
</properties>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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]", "");
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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;

Expand All @@ -95,6 +97,7 @@ public abstract class AwsHttpServletRequest implements HttpServletRequest {
AwsHttpServletRequest(Context lambdaContext) {
this.lambdaContext = lambdaContext;
attributes = new HashMap<>();
headerParser = new BasicHeaderValueParser();
}


Expand Down Expand Up @@ -312,10 +315,12 @@ protected String generateQueryString(MultiValuedTreeMap<String, String> 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);
}
}
}
}
Expand All @@ -334,7 +339,7 @@ protected String generateQueryString(MultiValuedTreeMap<String, String> 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<HeaderValue> parseHeaderValue(String headerValue) {
protected List<HeaderValue> parseHeaderValue(String headerValue) {
return parseHeaderValue(headerValue, HEADER_VALUE_SEPARATOR, HEADER_QUALIFIER_SEPARATOR);
}

Expand All @@ -352,6 +357,7 @@ protected List<HeaderValue> 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<HeaderValue> values = new ArrayList<>();
if (headerValue == null) {
Expand All @@ -365,25 +371,44 @@ protected List<HeaderValue> 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);
}

Expand Down Expand Up @@ -411,7 +436,6 @@ protected String decodeRequestPath(String requestPath, ContainerConfig config) {

}


/**
* Class that represents a header value.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down Expand Up @@ -709,8 +709,8 @@ private Map<String, Part> 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 -> {
Expand Down Expand Up @@ -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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -72,6 +73,7 @@ public class AwsServletContext
private Map<String, String> initParameters;
private AwsLambdaServletContainerHandler containerHandler;
private Logger log = LoggerFactory.getLogger(AwsServletContext.class);
private MimetypesFileTypeMap mimeTypes; // lazily loaded in the getMimeType method


//-------------------------------------------------------------
Expand Down Expand Up @@ -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);
}


Expand Down Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -167,7 +168,7 @@ public void setPath(String path) {
this.path = path;
}


@JsonProperty("isBase64Encoded")
public boolean isBase64Encoded() {
return isBase64Encoded;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;


Expand All @@ -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")
Expand Down Expand Up @@ -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<AwsHttpServletRequest.HeaderValue> 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<AwsHttpServletRequest.HeaderValue> 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);
Expand All @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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";

Expand All @@ -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;

Expand Down Expand Up @@ -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) {
Expand Down
Loading