Skip to content

Commit c0b3b29

Browse files
committed
Fixed issues with set content encoding header reported in #150
1 parent f18176f commit c0b3b29

File tree

2 files changed

+152
-27
lines changed

2 files changed

+152
-27
lines changed

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

Lines changed: 42 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ public class AwsProxyHttpServletRequest extends AwsHttpServletRequest {
8686
private SecurityContext securityContext;
8787
private Map<String, List<String>> urlEncodedFormParameters;
8888
private Map<String, Part> multipartFormParameters;
89+
private Map<String, String> caseInsensitiveHeaders;
8990
private EncodingQueryStringParameterMap queryStringParameters;
9091
private static Logger log = LoggerFactory.getLogger(AwsProxyHttpServletRequest.class);
9192
private ContainerConfig config;
@@ -108,6 +109,9 @@ public AwsProxyHttpServletRequest(AwsProxyRequest awsProxyRequest, Context lambd
108109

109110
this.queryStringParameters = new EncodingQueryStringParameterMap(config.isQueryStringCaseSensitive(), config.getUriEncoding());
110111
this.queryStringParameters.putAllMapEncoding(request.getQueryStringParameters());
112+
113+
this.caseInsensitiveHeaders = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
114+
this.caseInsensitiveHeaders.putAll(awsProxyRequest.getHeaders());
111115
}
112116

113117

@@ -171,10 +175,10 @@ public Enumeration<String> getHeaders(String s) {
171175

172176
@Override
173177
public Enumeration<String> getHeaderNames() {
174-
if (request.getHeaders() == null) {
178+
if (caseInsensitiveHeaders == null) {
175179
return Collections.emptyEnumeration();
176180
}
177-
return Collections.enumeration(request.getHeaders().keySet());
181+
return Collections.enumeration(caseInsensitiveHeaders.keySet());
178182
}
179183

180184

@@ -349,30 +353,29 @@ public String getCharacterEncoding() {
349353
@Override
350354
public void setCharacterEncoding(String s)
351355
throws UnsupportedEncodingException {
352-
String currentContentType = request.getHeaders().get(HttpHeaders.CONTENT_TYPE);
353-
if (currentContentType == null) {
354-
request.getHeaders().put(
355-
HttpHeaders.CONTENT_TYPE,
356-
HEADER_VALUE_SEPARATOR + " " + ENCODING_VALUE_KEY + HEADER_KEY_VALUE_SEPARATOR + s);
356+
String currentContentType = getHeaderCaseInsensitive(HttpHeaders.CONTENT_TYPE);
357+
if (currentContentType == null || "".equals(currentContentType)) {
358+
log.error("Called set character encoding to " + SecurityUtils.crlf(s) + " on a request without a content type. Character encoding will not be set");
357359
return;
358360
}
359361

360362
if (currentContentType.contains(HEADER_VALUE_SEPARATOR)) {
361363
String[] contentTypeValues = currentContentType.split(HEADER_VALUE_SEPARATOR);
362364
StringBuilder contentType = new StringBuilder(contentTypeValues[0]);
363365

364-
for (String contentTypeValue : contentTypeValues) {
366+
for (int i = 1; i < contentTypeValues.length; i++) {
367+
String contentTypeValue = contentTypeValues[i];
365368
String contentTypeString = HEADER_VALUE_SEPARATOR + " " + contentTypeValue;
366369
if (contentTypeValue.trim().startsWith(ENCODING_VALUE_KEY)) {
367370
contentTypeString = HEADER_VALUE_SEPARATOR + " " + ENCODING_VALUE_KEY + HEADER_KEY_VALUE_SEPARATOR + s;
368371
}
369372
contentType.append(contentTypeString);
370373
}
371374

372-
request.getHeaders().put(HttpHeaders.CONTENT_TYPE, contentType.toString());
375+
setHeaderCaseInsensitive(HttpHeaders.CONTENT_TYPE.toLowerCase(Locale.getDefault()), contentType.toString());
373376
} else {
374-
request.getHeaders().put(
375-
HttpHeaders.CONTENT_TYPE,
377+
setHeaderCaseInsensitive(
378+
HttpHeaders.CONTENT_TYPE.toLowerCase(Locale.getDefault()),
376379
currentContentType + HEADER_VALUE_SEPARATOR + " " + ENCODING_VALUE_KEY + HEADER_KEY_VALUE_SEPARATOR + s);
377380
}
378381
}
@@ -400,7 +403,12 @@ public long getContentLengthLong() {
400403

401404
@Override
402405
public String getContentType() {
403-
return getHeaderCaseInsensitive(HttpHeaders.CONTENT_TYPE);
406+
String contentTypeHeader = getHeaderCaseInsensitive(HttpHeaders.CONTENT_TYPE);
407+
if (contentTypeHeader == null || "".equals(contentTypeHeader.trim())) {
408+
return null;
409+
}
410+
411+
return contentTypeHeader;
404412
}
405413

406414

@@ -414,7 +422,17 @@ public ServletInputStream getInputStream()
414422
if (request.isBase64Encoded()) {
415423
bodyBytes = Base64.getMimeDecoder().decode(request.getBody());
416424
} else {
417-
bodyBytes = request.getBody().getBytes(StandardCharsets.UTF_8);
425+
String encoding = getCharacterEncoding();
426+
if (encoding == null) {
427+
encoding = StandardCharsets.ISO_8859_1.name();
428+
}
429+
try {
430+
bodyBytes = request.getBody().getBytes(encoding);
431+
} catch (Exception e) {
432+
log.error("Could not read request with character encoding: " + SecurityUtils.crlf(encoding), e);
433+
bodyBytes = request.getBody().getBytes(StandardCharsets.ISO_8859_1.name());
434+
}
435+
418436
}
419437
ByteArrayInputStream requestBodyStream = new ByteArrayInputStream(bodyBytes);
420438
return new AwsServletInputStream(requestBodyStream);
@@ -649,15 +667,21 @@ private String getHeaderCaseInsensitive(String key) {
649667
return request.getRequestContext().getIdentity().getUserAgent();
650668
}
651669

652-
if (request.getHeaders() == null) {
670+
if (caseInsensitiveHeaders == null) {
653671
return null;
654672
}
655-
for (String requestHeaderKey : request.getHeaders().keySet()) {
656-
if (key.toLowerCase(Locale.ENGLISH).equals(requestHeaderKey.toLowerCase(Locale.ENGLISH))) {
657-
return request.getHeaders().get(requestHeaderKey);
673+
return caseInsensitiveHeaders.get(key);
674+
}
675+
676+
private void setHeaderCaseInsensitive(String key, String value) {
677+
if (caseInsensitiveHeaders == null) {
678+
caseInsensitiveHeaders = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
679+
if (request.getHeaders() != null) {
680+
caseInsensitiveHeaders.putAll(request.getHeaders());
658681
}
659682
}
660-
return null;
683+
684+
caseInsensitiveHeaders.put(key, value);
661685
}
662686

663687
private String[] getFormBodyParameterCaseInsensitive(String key) {

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

Lines changed: 110 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,13 @@
99
import javax.ws.rs.core.HttpHeaders;
1010
import javax.ws.rs.core.MediaType;
1111

12+
import java.io.UnsupportedEncodingException;
13+
import java.nio.charset.StandardCharsets;
1214
import java.time.Instant;
1315
import java.time.ZonedDateTime;
1416
import java.util.Collections;
1517
import java.util.List;
18+
import java.util.Locale;
1619
import java.util.Map;
1720

1821
import static org.junit.Assert.*;
@@ -29,11 +32,6 @@ public class AwsProxyHttpServletRequestTest {
2932
private static final String REFERER = "https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/User-Agent/Firefox";
3033
private static ZonedDateTime REQUEST_DATE = ZonedDateTime.now();
3134

32-
private static final AwsProxyRequest REQUEST_WITH_HEADERS = new AwsProxyRequestBuilder("/hello", "GET")
33-
.header(CUSTOM_HEADER_KEY, CUSTOM_HEADER_VALUE)
34-
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON)
35-
.header(AwsProxyHttpServletRequest.CF_PROTOCOL_HEADER_NAME, REQUEST_SCHEME_HTTP)
36-
.build();
3735
private static final AwsProxyRequest REQUEST_FORM_URLENCODED = new AwsProxyRequestBuilder("/hello", "POST")
3836
.form(FORM_PARAM_NAME, FORM_PARAM_NAME_VALUE).build();
3937
private static final AwsProxyRequest REQUEST_INVALID_FORM_URLENCODED = new AwsProxyRequestBuilder("/hello", "GET")
@@ -54,10 +52,11 @@ public class AwsProxyHttpServletRequestTest {
5452
private static final AwsProxyRequest REQUEST_USER_AGENT_REFERER = new AwsProxyRequestBuilder("/hello", "POST")
5553
.userAgent(USER_AGENT)
5654
.referer(REFERER).build();
57-
5855
private static final AwsProxyRequest REQUEST_WITH_DATE = new AwsProxyRequestBuilder("/hello", "GET")
5956
.header(HttpHeaders.DATE, AwsHttpServletRequest.dateFormatter.format(REQUEST_DATE))
6057
.build();
58+
private static final AwsProxyRequest REQUEST_WITH_LOWERCASE_HEADER = new AwsProxyRequestBuilder("/hello", "POST")
59+
.header(HttpHeaders.CONTENT_TYPE.toLowerCase(Locale.getDefault()), MediaType.APPLICATION_JSON).build();
6160

6261
private static final AwsProxyRequest REQUEST_NULL_QUERY_STRING;
6362
static {
@@ -72,7 +71,7 @@ public class AwsProxyHttpServletRequestTest {
7271

7372
@Test
7473
public void headers_getHeader_validRequest() {
75-
HttpServletRequest request = new AwsProxyHttpServletRequest(REQUEST_WITH_HEADERS, null, null);
74+
HttpServletRequest request = new AwsProxyHttpServletRequest(getRequestWithHeaders(), null, null);
7675
assertNotNull(request.getHeader(CUSTOM_HEADER_KEY));
7776
assertEquals(CUSTOM_HEADER_VALUE, request.getHeader(CUSTOM_HEADER_KEY));
7877
assertEquals(MediaType.APPLICATION_JSON, request.getContentType());
@@ -147,15 +146,15 @@ public void scheme_getScheme_https() {
147146

148147
@Test
149148
public void scheme_getScheme_http() {
150-
HttpServletRequest request = new AwsProxyHttpServletRequest(REQUEST_WITH_HEADERS, null, null);
149+
HttpServletRequest request = new AwsProxyHttpServletRequest(getRequestWithHeaders(), null, null);
151150
assertNotNull(request);
152151
assertNotNull(request.getScheme());
153152
assertEquals(REQUEST_SCHEME_HTTP, request.getScheme());
154153
}
155154

156155
@Test
157156
public void cookie_getCookies_noCookies() {
158-
HttpServletRequest request = new AwsProxyHttpServletRequest(REQUEST_WITH_HEADERS, null, null);
157+
HttpServletRequest request = new AwsProxyHttpServletRequest(getRequestWithHeaders(), null, null);
159158
assertNotNull(request);
160159
assertNotNull(request.getCookies());
161160
assertEquals(0, request.getCookies().length);
@@ -243,4 +242,106 @@ public void queryParameter_getParameterMap_avoidDuplicationOnMultipleCalls() {
243242
assertNotNull(params.get(FORM_PARAM_TEST));
244243
assertEquals(1, params.get(FORM_PARAM_TEST).length);
245244
}
245+
246+
@Test
247+
public void charEncoding_getEncoding_expectNoEncodingWithoutContentType() {
248+
HttpServletRequest request = new AwsProxyHttpServletRequest(REQUEST_SINGLE_COOKIE, null, null);
249+
try {
250+
request.setCharacterEncoding(StandardCharsets.UTF_8.name());
251+
// we have not specified a content type so the encoding will not be set
252+
assertEquals(null, request.getCharacterEncoding());
253+
assertEquals(null, request.getContentType());
254+
} catch (UnsupportedEncodingException e) {
255+
fail("Unsupported encoding");
256+
e.printStackTrace();
257+
}
258+
}
259+
260+
@Test
261+
public void charEncoding_getEncoding_expectContentTypeOnly() {
262+
HttpServletRequest request = new AwsProxyHttpServletRequest(getRequestWithHeaders(), null, null);
263+
// we have not specified a content type so the encoding will not be set
264+
assertEquals(null, request.getCharacterEncoding());
265+
assertEquals(MediaType.APPLICATION_JSON, request.getContentType());
266+
try {
267+
request.setCharacterEncoding(StandardCharsets.UTF_8.name());
268+
String newHeaderValue = MediaType.APPLICATION_JSON + "; charset=" + StandardCharsets.UTF_8.name();
269+
assertEquals(newHeaderValue, request.getHeader(HttpHeaders.CONTENT_TYPE));
270+
assertEquals(newHeaderValue, request.getContentType());
271+
assertEquals(StandardCharsets.UTF_8.name(), request.getCharacterEncoding());
272+
} catch (UnsupportedEncodingException e) {
273+
fail("Unsupported encoding");
274+
e.printStackTrace();
275+
}
276+
}
277+
278+
@Test
279+
public void charEncoding_addCharEncodingTwice_expectSingleMediaTypeAndEncoding() {
280+
HttpServletRequest request = new AwsProxyHttpServletRequest(getRequestWithHeaders(), null, null);
281+
// we have not specified a content type so the encoding will not be set
282+
assertEquals(null, request.getCharacterEncoding());
283+
assertEquals(MediaType.APPLICATION_JSON, request.getContentType());
284+
285+
try {
286+
request.setCharacterEncoding(StandardCharsets.UTF_8.name());
287+
String newHeaderValue = MediaType.APPLICATION_JSON + "; charset=" + StandardCharsets.UTF_8.name();
288+
assertEquals(newHeaderValue, request.getHeader(HttpHeaders.CONTENT_TYPE));
289+
assertEquals(newHeaderValue, request.getContentType());
290+
assertEquals(StandardCharsets.UTF_8.name(), request.getCharacterEncoding());
291+
292+
293+
request.setCharacterEncoding(StandardCharsets.ISO_8859_1.name());
294+
newHeaderValue = MediaType.APPLICATION_JSON + "; charset=" + StandardCharsets.ISO_8859_1.name();
295+
assertEquals(newHeaderValue, request.getHeader(HttpHeaders.CONTENT_TYPE));
296+
assertEquals(newHeaderValue, request.getContentType());
297+
assertEquals(StandardCharsets.ISO_8859_1.name(), request.getCharacterEncoding());
298+
} catch (UnsupportedEncodingException e) {
299+
fail("Unsupported encoding");
300+
e.printStackTrace();
301+
}
302+
}
303+
304+
@Test
305+
public void contentType_lowerCaseHeaderKey_expectUpdatedMediaType() {
306+
HttpServletRequest request = new AwsProxyHttpServletRequest(REQUEST_WITH_LOWERCASE_HEADER, null, null);
307+
try {
308+
request.setCharacterEncoding(StandardCharsets.UTF_8.name());
309+
String newHeaderValue = MediaType.APPLICATION_JSON + "; charset=" + StandardCharsets.UTF_8.name();
310+
assertEquals(newHeaderValue, request.getHeader(HttpHeaders.CONTENT_TYPE));
311+
assertEquals(newHeaderValue, request.getContentType());
312+
assertEquals(StandardCharsets.UTF_8.name(), request.getCharacterEncoding());
313+
314+
} catch (UnsupportedEncodingException e) {
315+
fail("Unsupported encoding");
316+
e.printStackTrace();
317+
}
318+
}
319+
320+
@Test
321+
public void contentType_duplicateCase_expectSingleContentTypeHeader() {
322+
AwsProxyRequest proxyRequest = getRequestWithHeaders();
323+
HttpServletRequest request = new AwsProxyHttpServletRequest(proxyRequest, null, null);
324+
325+
try {
326+
request.setCharacterEncoding(StandardCharsets.ISO_8859_1.name());
327+
assertNotNull(request.getHeader(HttpHeaders.CONTENT_TYPE));
328+
assertNotNull(request.getHeader(HttpHeaders.CONTENT_TYPE.toLowerCase(Locale.getDefault())));
329+
330+
assertFalse(proxyRequest.getHeaders().containsKey(HttpHeaders.CONTENT_TYPE) && proxyRequest.getHeaders().containsKey(HttpHeaders.CONTENT_TYPE.toLowerCase(Locale.getDefault())));
331+
} catch (UnsupportedEncodingException e) {
332+
fail("Unsupported encoding");
333+
e.printStackTrace();
334+
}
335+
336+
337+
338+
}
339+
340+
private AwsProxyRequest getRequestWithHeaders() {
341+
return new AwsProxyRequestBuilder("/hello", "GET")
342+
.header(CUSTOM_HEADER_KEY, CUSTOM_HEADER_VALUE)
343+
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON)
344+
.header(AwsProxyHttpServletRequest.CF_PROTOCOL_HEADER_NAME, REQUEST_SCHEME_HTTP)
345+
.build();
346+
}
246347
}

0 commit comments

Comments
 (0)