Skip to content

Commit 47e87e7

Browse files
authored
Merge pull request #265 from awslabs/core
Preparing for 1.3.2 release
2 parents e4f4d4d + 445abc6 commit 47e87e7

File tree

26 files changed

+365
-72
lines changed

26 files changed

+365
-72
lines changed

aws-serverless-java-container-core/pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
</parent>
1717

1818
<properties>
19-
<jackson.version>2.9.8</jackson.version>
19+
<jackson.version>2.9.9</jackson.version>
2020
<jaxrs.version>2.1</jaxrs.version>
2121
<servlet.version>3.1.0</servlet.version>
2222
</properties>

aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/SecurityUtils.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,9 @@ public static boolean isValidHost(String host, String apiId, String region) {
7070
* @return A copy of the original string without CRLF characters
7171
*/
7272
public static String crlf(String s) {
73+
if (s == null) {
74+
return null;
75+
}
7376
return s.replaceAll("[\r\n]", "");
7477
}
7578

aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpServletRequest.java

Lines changed: 45 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import com.amazonaws.serverless.proxy.model.MultiValuedTreeMap;
2020
import com.amazonaws.services.lambda.runtime.Context;
2121

22+
import org.apache.http.message.BasicHeaderValueParser;
2223
import org.slf4j.Logger;
2324
import org.slf4j.LoggerFactory;
2425

@@ -77,6 +78,7 @@ public abstract class AwsHttpServletRequest implements HttpServletRequest {
7778
private ServletContext servletContext;
7879
private AwsHttpSession session;
7980
private String queryString;
81+
private BasicHeaderValueParser headerParser;
8082

8183
protected DispatcherType dispatcherType;
8284

@@ -95,6 +97,7 @@ public abstract class AwsHttpServletRequest implements HttpServletRequest {
9597
AwsHttpServletRequest(Context lambdaContext) {
9698
this.lambdaContext = lambdaContext;
9799
attributes = new HashMap<>();
100+
headerParser = new BasicHeaderValueParser();
98101
}
99102

100103

@@ -312,10 +315,12 @@ protected String generateQueryString(MultiValuedTreeMap<String, String> paramete
312315
queryStringBuilder.append(key);
313316
}
314317
queryStringBuilder.append("=");
315-
if (encode) {
316-
queryStringBuilder.append(URLEncoder.encode(val, encodeCharset));
317-
} else {
318-
queryStringBuilder.append(val);
318+
if (val != null) {
319+
if (encode) {
320+
queryStringBuilder.append(URLEncoder.encode(val, encodeCharset));
321+
} else {
322+
queryStringBuilder.append(val);
323+
}
319324
}
320325
}
321326
}
@@ -334,7 +339,7 @@ protected String generateQueryString(MultiValuedTreeMap<String, String> paramete
334339
* @param headerValue The value to be parsed
335340
* @return A list of SimpleMapEntry objects with all of the possible values for the header.
336341
*/
337-
protected List<HeaderValue> parseHeaderValue(String headerValue) {
342+
protected List<HeaderValue> parseHeaderValue(String headerValue) {
338343
return parseHeaderValue(headerValue, HEADER_VALUE_SEPARATOR, HEADER_QUALIFIER_SEPARATOR);
339344
}
340345

@@ -352,6 +357,7 @@ protected List<HeaderValue> parseHeaderValue(String headerValue, String valueSep
352357
// Accept: text/html, application/xhtml+xml, application/xml;q=0.9, */*;q=0.8
353358
// Accept-Language: fr-CH, fr;q=0.9, en;q=0.8, de;q=0.7, *;q=0.5
354359
// Cookie: name=value; name2=value2; name3=value3
360+
// X-Custom-Header: YQ==
355361

356362
List<HeaderValue> values = new ArrayList<>();
357363
if (headerValue == null) {
@@ -365,25 +371,44 @@ protected List<HeaderValue> parseHeaderValue(String headerValue, String valueSep
365371
newValue.setRawValue(v);
366372

367373
for (String q : curValue.split(qualifierSeparator)) {
368-
if (q.contains(HEADER_KEY_VALUE_SEPARATOR)) {
369-
String[] kv = q.split(HEADER_KEY_VALUE_SEPARATOR);
370-
// TODO: Should we concatenate the rest of the values?
371-
if (newValue.getValue() == null) {
372-
newValue.setKey(kv[0].trim());
373-
newValue.setValue(kv[1].trim());
374-
} else {
375-
// special case for quality q=
376-
if ("q".equals(kv[0].trim())) {
377-
curPreference = Float.parseFloat(kv[1].trim());
378-
} else {
379-
newValue.addAttribute(kv[0].trim(), kv[1].trim());
380-
}
374+
375+
String[] kv = q.split(HEADER_KEY_VALUE_SEPARATOR, 2);
376+
String key = null;
377+
String val = null;
378+
// no separator, set the value only
379+
if (kv.length == 1) {
380+
val = q.trim();
381+
}
382+
// we have a separator
383+
if (kv.length == 2) {
384+
// if the length of the value is 0 we assume that we are looking at a
385+
// base64 encoded value with padding so we just set the value. This is because
386+
// we assume that empty values in a key/value pair will contain at least a white space
387+
if (kv[1].length() == 0) {
388+
val = q.trim();
389+
}
390+
// this was a base64 string with an additional = for padding, set the value only
391+
if ("=".equals(kv[1].trim())) {
392+
val = q.trim();
393+
} else { // it's a proper key/value set both
394+
key = kv[0].trim();
395+
val = ("".equals(kv[1].trim()) ? null : kv[1].trim());
381396
}
397+
}
398+
399+
if (newValue.getValue() == null) {
400+
newValue.setKey(key);
401+
newValue.setValue(val);
382402
} else {
383-
newValue.setValue(q.trim());
403+
// special case for quality q=
404+
if ("q".equals(key)) {
405+
curPreference = Float.parseFloat(val);
406+
} else {
407+
newValue.addAttribute(key, val);
408+
}
384409
}
385-
newValue.setPriority(curPreference);
386410
}
411+
newValue.setPriority(curPreference);
387412
values.add(newValue);
388413
}
389414

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

412437
}
413438

414-
415439
/**
416440
* Class that represents a header value.
417441
*/

aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsProxyHttpServletRequest.java

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -366,7 +366,7 @@ public void setCharacterEncoding(String s)
366366
throws UnsupportedEncodingException {
367367
String currentContentType = request.getMultiValueHeaders().getFirst(HttpHeaders.CONTENT_TYPE);
368368
if (currentContentType == null || "".equals(currentContentType)) {
369-
log.error("Called set character encoding to " + SecurityUtils.crlf(s) + " on a request without a content type. Character encoding will not be set");
369+
log.debug("Called set character encoding to " + SecurityUtils.crlf(s) + " on a request without a content type. Character encoding will not be set");
370370
return;
371371
}
372372

@@ -709,8 +709,8 @@ private Map<String, Part> getMultipartFormParametersMap() {
709709
for (FileItem item : items) {
710710
String fileName = FilenameUtils.getName(item.getName());
711711
AwsProxyRequestPart newPart = new AwsProxyRequestPart(item.get());
712-
newPart.setName(fileName);
713-
newPart.setSubmittedFileName(item.getFieldName());
712+
newPart.setName(item.getFieldName());
713+
newPart.setSubmittedFileName(fileName);
714714
newPart.setContentType(item.getContentType());
715715
newPart.setSize(item.getSize());
716716
item.getHeaders().getHeaderNames().forEachRemaining(h -> {
@@ -892,6 +892,9 @@ public void setReadListener(ReadListener readListener) {
892892
@Override
893893
public int read()
894894
throws IOException {
895+
if (bodyStream == null || bodyStream instanceof NullInputStream) {
896+
return -1;
897+
}
895898
int readByte = bodyStream.read();
896899
if (bodyStream.available() == 0 && listener != null) {
897900
listener.onAllDataRead();

aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsServletContext.java

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import org.slf4j.Logger;
2121
import org.slf4j.LoggerFactory;
2222

23+
import javax.activation.MimetypesFileTypeMap;
2324
import javax.servlet.Filter;
2425
import javax.servlet.FilterRegistration;
2526
import javax.servlet.RequestDispatcher;
@@ -72,6 +73,7 @@ public class AwsServletContext
7273
private Map<String, String> initParameters;
7374
private AwsLambdaServletContainerHandler containerHandler;
7475
private Logger log = LoggerFactory.getLogger(AwsServletContext.class);
76+
private MimetypesFileTypeMap mimeTypes; // lazily loaded in the getMimeType method
7577

7678

7779
//-------------------------------------------------------------
@@ -142,13 +144,16 @@ public int getEffectiveMinorVersion() {
142144
@Override
143145
@SuppressFBWarnings("PATH_TRAVERSAL_IN") // suppressing because we are using the getValidFilePath
144146
public String getMimeType(String s) {
145-
try {
146-
String validatedPath = SecurityUtils.getValidFilePath(s);
147-
return Files.probeContentType(Paths.get(validatedPath));
148-
} catch (IOException e) {
149-
log.warn("Could not find content type for file: " + SecurityUtils.encode(s), e);
147+
if (s == null || !s.contains(".")) {
150148
return null;
151149
}
150+
if (mimeTypes == null) {
151+
mimeTypes = new MimetypesFileTypeMap();
152+
}
153+
// TODO: The getContentType method of the MimetypesFileTypeMap returns application/octet-stream
154+
// instead of null when the type cannot be found. We should replace with an implementation that
155+
// loads the System mime types ($JAVA_HOME/lib/mime.types
156+
return mimeTypes.getContentType(s);
152157
}
153158

154159

@@ -232,7 +237,7 @@ public String getRealPath(String s) {
232237
try {
233238
absPath = new File(fileUrl.toURI()).getAbsolutePath();
234239
} catch (URISyntaxException e) {
235-
log.error("Error while looking for real path: {}", SecurityUtils.encode(s), SecurityUtils.encode(e.getMessage()));
240+
log.error("Error while looking for real path {}: {}", SecurityUtils.encode(s), SecurityUtils.encode(e.getMessage()));
236241
}
237242
}
238243
return absPath;

aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/model/AwsProxyRequest.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
import com.fasterxml.jackson.annotation.JsonIgnore;
1616
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
17+
import com.fasterxml.jackson.annotation.JsonProperty;
1718

1819
import java.util.HashMap;
1920
import java.util.Map;
@@ -167,7 +168,7 @@ public void setPath(String path) {
167168
this.path = path;
168169
}
169170

170-
171+
@JsonProperty("isBase64Encoded")
171172
public boolean isBase64Encoded() {
172173
return isBase64Encoded;
173174
}

aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpServletRequestTest.java

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,12 @@
1010
import org.junit.Test;
1111

1212
import javax.servlet.ServletException;
13+
import javax.servlet.http.Cookie;
1314
import javax.ws.rs.core.HttpHeaders;
1415

1516
import static org.junit.Assert.*;
1617

18+
import java.util.Base64;
1719
import java.util.List;
1820

1921

@@ -27,6 +29,8 @@ public class AwsHttpServletRequestTest {
2729
.header(HttpHeaders.ACCEPT, "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8").build();
2830
private static final AwsProxyRequest queryString = new AwsProxyRequestBuilder("/test", "GET")
2931
.queryString("one", "two").queryString("three", "four").build();
32+
private static final AwsProxyRequest queryStringNullValue = new AwsProxyRequestBuilder("/test", "GET")
33+
.queryString("one", "two").queryString("three", null).build();
3034
private static final AwsProxyRequest encodedQueryString = new AwsProxyRequestBuilder("/test", "GET")
3135
.queryString("one", "two").queryString("json", "{\"name\":\"faisal\"}").build();
3236
private static final AwsProxyRequest multipleParams = new AwsProxyRequestBuilder("/test", "GET")
@@ -75,6 +79,55 @@ public void headers_parseHeaderValue_complexAccept() {
7579
assertEquals(4, values.size());
7680
}
7781

82+
@Test
83+
public void headers_parseHeaderValue_encodedContentWithEquals() {
84+
AwsHttpServletRequest context = new AwsProxyHttpServletRequest(null,null,null);
85+
86+
String value = Base64.getUrlEncoder().encodeToString("a".getBytes());
87+
88+
List<AwsHttpServletRequest.HeaderValue> result = context.parseHeaderValue(value);
89+
assertTrue(result.size() > 0);
90+
assertEquals("YQ==", result.get(0).getValue());
91+
}
92+
93+
@Test
94+
public void headers_parseHeaderValue_base64EncodedCookieValue() {
95+
String value = Base64.getUrlEncoder().encodeToString("a".getBytes());
96+
String cookieValue = "jwt=" + value + "; secondValue=second";
97+
AwsProxyRequest req = new AwsProxyRequestBuilder("/test", "GET").header(HttpHeaders.COOKIE, cookieValue).build();
98+
AwsHttpServletRequest context = new AwsProxyHttpServletRequest(req,null,null);
99+
100+
Cookie[] cookies = context.getCookies();
101+
102+
assertEquals(2, cookies.length);
103+
assertEquals("jwt", cookies[0].getName());
104+
assertEquals(value, cookies[0].getValue());
105+
}
106+
107+
@Test
108+
public void headers_parseHeaderValue_cookieWithSeparatorInValue() {
109+
String cookieValue = "jwt==test; secondValue=second";
110+
AwsProxyRequest req = new AwsProxyRequestBuilder("/test", "GET").header(HttpHeaders.COOKIE, cookieValue).build();
111+
AwsHttpServletRequest context = new AwsProxyHttpServletRequest(req,null,null);
112+
113+
Cookie[] cookies = context.getCookies();
114+
115+
assertEquals(2, cookies.length);
116+
assertEquals("jwt", cookies[0].getName());
117+
assertEquals("=test", cookies[0].getValue());
118+
}
119+
120+
@Test
121+
public void headers_parseHeaderValue_headerWithPaddingButNotBase64Encoded() {
122+
AwsHttpServletRequest context = new AwsProxyHttpServletRequest(null,null,null);
123+
124+
List<AwsHttpServletRequest.HeaderValue> result = context.parseHeaderValue("hello=");
125+
assertTrue(result.size() > 0);
126+
assertEquals("hello", result.get(0).getKey());
127+
System.out.println("\"" + result.get(0).getValue() + "\"");
128+
assertNull(result.get(0).getValue());
129+
}
130+
78131
@Test
79132
public void queryString_generateQueryString_validQuery() {
80133
AwsProxyHttpServletRequest request = new AwsProxyHttpServletRequest(queryString, mockContext, null, config);
@@ -92,6 +145,19 @@ public void queryString_generateQueryString_validQuery() {
92145
assertTrue(parsedString.contains("&") && parsedString.indexOf("&") > 0 && parsedString.indexOf("&") < parsedString.length());
93146
}
94147

148+
@Test
149+
public void queryString_generateQueryString_nullParameterIsEmpty() {
150+
AwsProxyHttpServletRequest request = new AwsProxyHttpServletRequest(queryStringNullValue, mockContext, null, config);String parsedString = null;
151+
try {
152+
parsedString = request.generateQueryString(request.getAwsProxyRequest().getMultiValueQueryStringParameters(), true, config.getUriEncoding());
153+
} catch (ServletException e) {
154+
e.printStackTrace();
155+
fail("Could not generate query string");
156+
}
157+
158+
assertTrue(parsedString.endsWith("three="));
159+
}
160+
95161
@Test
96162
public void queryStringWithEncodedParams_generateQueryString_validQuery() {
97163
AwsProxyHttpServletRequest request = new AwsProxyHttpServletRequest(encodedQueryString, mockContext, null, config);

aws-serverless-java-container-core/src/test/java/com/amazonaws/serverless/proxy/internal/servlet/AwsProxyHttpServletRequestFormTest.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
import org.apache.commons.io.IOUtils;
88
import org.apache.http.HttpEntity;
9+
import org.apache.http.entity.ContentType;
910
import org.apache.http.entity.mime.MultipartEntityBuilder;
1011
import org.junit.Test;
1112

@@ -31,6 +32,7 @@ public class AwsProxyHttpServletRequestFormTest {
3132
private static final String PART_KEY_2 = "test2";
3233
private static final String PART_VALUE_2 = "value2";
3334
private static final String FILE_KEY = "file_upload_1";
35+
private static final String FILE_NAME = "testImage.jpg";
3436

3537
private static final String ENCODED_VALUE = "test123a%3D1%262@3";
3638

@@ -46,7 +48,7 @@ public class AwsProxyHttpServletRequestFormTest {
4648
private static final HttpEntity MULTIPART_BINARY_DATA = MultipartEntityBuilder.create()
4749
.addTextBody(PART_KEY_1, PART_VALUE_1)
4850
.addTextBody(PART_KEY_2, PART_VALUE_2)
49-
.addBinaryBody(FILE_KEY, FILE_BYTES)
51+
.addBinaryBody(FILE_KEY, FILE_BYTES, ContentType.IMAGE_JPEG, FILE_NAME)
5052
.build();
5153
private static final String ENCODED_FORM_ENTITY = PART_KEY_1 + "=" + ENCODED_VALUE + "&" + PART_KEY_2 + "=" + PART_VALUE_2;
5254

@@ -99,6 +101,8 @@ public void multipart_getParts_binary() {
99101
assertEquals(3, request.getParts().size());
100102
assertNotNull(request.getPart(FILE_KEY));
101103
assertEquals(FILE_SIZE, request.getPart(FILE_KEY).getSize());
104+
assertEquals(FILE_KEY, request.getPart(FILE_KEY).getName());
105+
assertEquals(FILE_NAME, request.getPart(FILE_KEY).getSubmittedFileName());
102106
assertEquals(PART_VALUE_1, IOUtils.toString(request.getPart(PART_KEY_1).getInputStream()));
103107
assertEquals(PART_VALUE_2, IOUtils.toString(request.getPart(PART_KEY_2).getInputStream()));
104108
} catch (IOException | ServletException e) {

0 commit comments

Comments
 (0)