Skip to content

Commit 5cf97f9

Browse files
committed
Changes to query string handling to address #162. parameter values come from request decoded. the getQueryString return an encoded string.
1 parent dfe6e4f commit 5cf97f9

File tree

9 files changed

+127
-123
lines changed

9 files changed

+127
-123
lines changed

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

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import javax.servlet.AsyncContext;
2525
import javax.servlet.DispatcherType;
2626
import javax.servlet.ServletContext;
27+
import javax.servlet.ServletException;
2728
import javax.servlet.http.Cookie;
2829
import javax.servlet.http.HttpServletRequest;
2930
import javax.servlet.http.HttpSession;
@@ -288,9 +289,12 @@ protected Cookie[] parseCookieHeaderValue(String headerValue) {
288289
* Given a map of key/values query string parameters from API Gateway, creates a query string as it would have
289290
* been in the original url.
290291
* @param parameters A Map<String, String> of query string parameters
292+
* @param encode Whether the key and values should be URL encoded
293+
* @param encodeCharset Charset to use for encoding the query string
291294
* @return The generated query string for the URI
292295
*/
293-
protected String generateQueryString(EncodingQueryStringParameterMap parameters) {
296+
protected String generateQueryString(Map<String, String> parameters, boolean encode, String encodeCharset)
297+
throws ServletException {
294298
if (parameters == null || parameters.size() == 0) {
295299
return null;
296300
}
@@ -300,12 +304,31 @@ protected String generateQueryString(EncodingQueryStringParameterMap parameters)
300304

301305
StringBuilder queryStringBuilder = new StringBuilder();
302306

303-
parameters.keySet().stream().forEach(k -> parameters.get(k).stream().forEach(v -> {
307+
/*parameters.keySet().stream().forEach(k -> parameters.stream().forEach(v -> {
304308
queryStringBuilder.append("&");
305309
queryStringBuilder.append(k);
306310
queryStringBuilder.append("=");
307311
queryStringBuilder.append(v);
308-
}));
312+
}));*/
313+
try {
314+
for (Map.Entry<String, String> e : parameters.entrySet()) {
315+
queryStringBuilder.append("&");
316+
if (encode) {
317+
queryStringBuilder.append(URLEncoder.encode(e.getKey(), encodeCharset));
318+
} else {
319+
queryStringBuilder.append(e.getKey());
320+
}
321+
queryStringBuilder.append("=");
322+
if (encode) {
323+
queryStringBuilder.append(URLEncoder.encode(e.getValue(), encodeCharset));
324+
} else {
325+
queryStringBuilder.append(e.getValue());
326+
}
327+
328+
}
329+
} catch (UnsupportedEncodingException e) {
330+
throw new ServletException("Invalid charset passed for query string encoding", e);
331+
}
309332

310333
queryString = queryStringBuilder.toString();
311334
queryString = queryString.substring(1); // remove the first & - faster to do it here than adding logic in the Lambda

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

Lines changed: 39 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,6 @@ public class AwsProxyHttpServletRequest extends AwsHttpServletRequest {
8787
private Map<String, List<String>> urlEncodedFormParameters;
8888
private Map<String, Part> multipartFormParameters;
8989
private Map<String, String> caseInsensitiveHeaders;
90-
private EncodingQueryStringParameterMap queryStringParameters;
9190
private static Logger log = LoggerFactory.getLogger(AwsProxyHttpServletRequest.class);
9291
private ContainerConfig config;
9392

@@ -107,9 +106,6 @@ public AwsProxyHttpServletRequest(AwsProxyRequest awsProxyRequest, Context lambd
107106
this.securityContext = awsSecurityContext;
108107
this.config = config;
109108

110-
this.queryStringParameters = new EncodingQueryStringParameterMap(config.isQueryStringCaseSensitive(), config.getUriEncoding());
111-
this.queryStringParameters.putAllMapEncoding(request.getQueryStringParameters());
112-
113109
this.caseInsensitiveHeaders = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
114110
this.caseInsensitiveHeaders.putAll(awsProxyRequest.getHeaders());
115111
}
@@ -230,7 +226,12 @@ public String getContextPath() {
230226

231227
@Override
232228
public String getQueryString() {
233-
return this.generateQueryString(queryStringParameters);
229+
try {
230+
return this.generateQueryString(request.getQueryStringParameters(), true, config.getUriEncoding());
231+
} catch (ServletException e) {
232+
log.error("Could not generate query string", e);
233+
return null;
234+
}
234235
}
235236

236237

@@ -441,11 +442,7 @@ public ServletInputStream getInputStream()
441442

442443
@Override
443444
public String getParameter(String s) {
444-
String paramKey = s;
445-
if (config.isQueryStringCaseSensitive()) {
446-
paramKey = paramKey.toLowerCase(Locale.getDefault());
447-
}
448-
String queryStringParameter = queryStringParameters.getFirst(paramKey);
445+
String queryStringParameter = getQueryParamValue(s, config.isQueryStringCaseSensitive());
449446
if (queryStringParameter != null) {
450447
return queryStringParameter;
451448
}
@@ -462,7 +459,9 @@ public String getParameter(String s) {
462459
@Override
463460
public Enumeration<String> getParameterNames() {
464461
List<String> paramNames = new ArrayList<>();
465-
paramNames.addAll(queryStringParameters.keySet());
462+
if (request.getQueryStringParameters() != null) {
463+
paramNames.addAll(request.getQueryStringParameters().keySet());
464+
}
466465
paramNames.addAll(getFormUrlEncodedParametersMap().keySet());
467466
return Collections.enumeration(paramNames);
468467
}
@@ -471,16 +470,11 @@ public Enumeration<String> getParameterNames() {
471470
@Override
472471
@SuppressFBWarnings("PZLA_PREFER_ZERO_LENGTH_ARRAYS") // suppressing this as according to the specs we should be returning null here if we can't find params
473472
public String[] getParameterValues(String s) {
474-
String paramKey = s;
475-
if (config.isQueryStringCaseSensitive()) {
476-
paramKey = paramKey.toLowerCase(Locale.getDefault());
477-
}
478473
List<String> values = new ArrayList<>();
479-
List<String> queryParamValues = queryStringParameters.get(paramKey);
480-
if (queryParamValues != null) {
481-
values.addAll(queryParamValues);
474+
String queryValue = getQueryParamValue(s, config.isQueryStringCaseSensitive());
475+
if (queryValue != null) {
476+
values.add(queryValue);
482477
}
483-
//values.addAll(queryStringParameters.get(paramKey));
484478

485479
String[] formBodyValues = getFormBodyParameterCaseInsensitive(s);
486480
if (formBodyValues != null) {
@@ -504,9 +498,17 @@ public Map<String, String[]> getParameterMap() {
504498
output.put(e.getKey(), e.getValue().toArray(new String[0]));
505499
});
506500

507-
queryStringParameters.keySet().stream().parallel().forEach(e -> {
508-
output.put(e, queryStringParameters.get(e).toArray(new String[0]));
509-
});
501+
if (request.getQueryStringParameters() != null) {
502+
request.getQueryStringParameters().keySet().stream().parallel().forEach(e -> {
503+
List<String> newValues = new ArrayList<>();
504+
if (output.containsKey(e)) {
505+
String[] values = output.get(e);
506+
newValues.addAll(Arrays.asList(values));
507+
}
508+
newValues.add(getQueryParamValue(e, config.isQueryStringCaseSensitive()));
509+
output.put(e, newValues.toArray(new String[0]));
510+
});
511+
}
510512

511513
return output;
512514
}
@@ -649,13 +651,6 @@ public AsyncContext startAsync(ServletRequest servletRequest, ServletResponse se
649651
return null;
650652
}
651653

652-
//-------------------------------------------------------------
653-
// Methods - Protected
654-
//-------------------------------------------------------------
655-
656-
protected EncodingQueryStringParameterMap getQueryParametersMap() {
657-
return queryStringParameters;
658-
}
659654

660655
//-------------------------------------------------------------
661656
// Methods - Private
@@ -816,6 +811,21 @@ public static String decodeValueIfEncoded(String value) {
816811
}
817812

818813

814+
private String getQueryParamValue(String key, boolean isCaseSensitive) {
815+
if (isCaseSensitive) {
816+
return request.getQueryStringParameters().get(key);
817+
}
818+
819+
for (String k : request.getQueryStringParameters().keySet()) {
820+
if (k.toLowerCase(Locale.getDefault()).equals(key.toLowerCase(Locale.getDefault()))) {
821+
return request.getQueryStringParameters().get(k);
822+
}
823+
}
824+
825+
return null;
826+
}
827+
828+
819829
public static class AwsServletInputStream extends ServletInputStream {
820830

821831
private InputStream bodyStream;

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

Lines changed: 0 additions & 89 deletions
This file was deleted.

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

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import com.fasterxml.jackson.databind.ObjectMapper;
1010
import org.junit.Test;
1111

12+
import javax.servlet.ServletException;
1213
import javax.ws.rs.core.HttpHeaders;
1314

1415
import static org.junit.Assert.*;
@@ -77,7 +78,13 @@ public void headers_parseHeaderValue_complexAccept() {
7778
public void queryString_generateQueryString_validQuery() {
7879
AwsProxyHttpServletRequest request = new AwsProxyHttpServletRequest(queryString, mockContext, null, config);
7980

80-
String parsedString = request.generateQueryString(request.getQueryParametersMap());
81+
String parsedString = null;
82+
try {
83+
parsedString = request.generateQueryString(request.getAwsProxyRequest().getQueryStringParameters(), true, config.getUriEncoding());
84+
} catch (ServletException e) {
85+
e.printStackTrace();
86+
fail("Could not generate query string");
87+
}
8188
System.out.println(parsedString);
8289
assertTrue(parsedString.contains("one=two"));
8390
assertTrue(parsedString.contains("three=four"));
@@ -88,7 +95,13 @@ public void queryString_generateQueryString_validQuery() {
8895
public void queryStringWithEncodedParams_generateQueryString_validQuery() {
8996
AwsProxyHttpServletRequest request = new AwsProxyHttpServletRequest(encodedQueryString, mockContext, null, config);
9097

91-
String parsedString = request.generateQueryString(request.getQueryParametersMap());
98+
String parsedString = null;
99+
try {
100+
parsedString = request.generateQueryString(request.getAwsProxyRequest().getQueryStringParameters(), true, config.getUriEncoding());
101+
} catch (ServletException e) {
102+
e.printStackTrace();
103+
fail("Could not generate query string");
104+
}
92105
assertTrue(parsedString.contains("one=two"));
93106
assertTrue(parsedString.contains("json=%7B%22name%22%3A%22faisal%22%7D"));
94107
assertTrue(parsedString.contains("&") && parsedString.indexOf("&") > 0 && parsedString.indexOf("&") < parsedString.length());

aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/SpringAwsProxyTest.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,19 @@ public void queryString_uriInfo_echo() {
116116
validateMapResponseModel(output);
117117
}
118118

119+
@Test
120+
public void queryString_listParameter_expectCorrectLength() {
121+
AwsProxyRequest request = new AwsProxyRequestBuilder("/echo/list-query-string", "GET")
122+
.json()
123+
.queryString("list", "v1,v2,v3")
124+
.build();
125+
126+
AwsProxyResponse output = handler.proxy(request, lambdaContext);
127+
assertEquals(200, output.getStatusCode());
128+
129+
validateSingleValueModel(output, "3");
130+
}
131+
119132
@Test
120133
public void dateHeader_notModified_expect304() {
121134
AwsProxyRequest request = new AwsProxyRequestBuilder("/echo/last-modified", "GET")

aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/SpringBootAppTest.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,16 @@ public void requestUri_dotInPathParam_expectRoutingToMethod() {
6666
validateSingleValueModel(resp, "testdomain.com");
6767
}
6868

69+
@Test
70+
public void queryString_commaSeparatedList_expectUnmarshalAsList() {
71+
AwsProxyRequest req = new AwsProxyRequestBuilder("/test/query-string", "GET")
72+
.queryString("list", "v1,v2,v3").build();
73+
AwsProxyResponse resp = handler.handleRequest(req, context);
74+
assertNotNull(resp);
75+
assertEquals(200, resp.getStatusCode());
76+
validateSingleValueModel(resp, "3");
77+
}
78+
6979
private void validateSingleValueModel(AwsProxyResponse output, String value) {
7080
try {
7181
SingleValueModel response = mapper.readValue(output.getBody(), SingleValueModel.class);

aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/echoapp/EchoResource.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import java.time.Instant;
1818
import java.time.temporal.ChronoUnit;
1919
import java.util.Enumeration;
20+
import java.util.List;
2021
import java.util.Map;
2122
import java.util.Optional;
2223
import java.util.Random;
@@ -67,6 +68,13 @@ public MapResponseModel echoQueryString(HttpServletRequest request, @RequestPara
6768
return queryStrings;
6869
}
6970

71+
@RequestMapping(path = "/list-query-string", method = RequestMethod.GET)
72+
public SingleValueModel echoListQueryString(@RequestParam(value="list") List<String> valueList) {
73+
SingleValueModel value = new SingleValueModel();
74+
value.setValue(valueList.size() + "");
75+
return value;
76+
}
77+
7078
@RequestMapping(path = "/authorizer-principal", method = RequestMethod.GET)
7179
public SingleValueModel echoAuthorizerPrincipal(HttpServletRequest context) {
7280
SingleValueModel valueModel = new SingleValueModel();

aws-serverless-java-container-spring/src/test/java/com/amazonaws/serverless/proxy/spring/springbootapp/TestApplication.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,15 @@
22

33

44
import org.springframework.boot.autoconfigure.SpringBootApplication;
5+
import org.springframework.boot.web.servlet.FilterRegistrationBean;
56
import org.springframework.boot.web.support.SpringBootServletInitializer;
7+
import org.springframework.context.annotation.Bean;
68
import org.springframework.context.annotation.ComponentScan;
9+
import org.springframework.web.filter.CharacterEncodingFilter;
710

811

912
@SpringBootApplication
1013
@ComponentScan(basePackages = "com.amazonaws.serverless.proxy.spring.springbootapp")
1114
public class TestApplication extends SpringBootServletInitializer {
15+
1216
}

0 commit comments

Comments
 (0)