Skip to content

Commit c15f931

Browse files
authored
Merge pull request #616 from valerena/change-parameters
Modify algorithm for parameter transformation logic
2 parents 32eb54a + 1a0906f commit c15f931

File tree

2 files changed

+179
-18
lines changed

2 files changed

+179
-18
lines changed

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

Lines changed: 42 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@
4444
import java.nio.charset.StandardCharsets;
4545
import java.time.format.DateTimeFormatter;
4646
import java.util.*;
47+
import java.util.stream.Collectors;
48+
import java.util.stream.Stream;
4749

4850

4951
/**
@@ -546,39 +548,61 @@ protected Map<String, Part> getMultipartFormParametersMap() {
546548
}
547549

548550
protected String[] getQueryParamValues(MultiValuedTreeMap<String, String> qs, String key, boolean isCaseSensitive) {
551+
List<String> value = getQueryParamValuesAsList(qs, key, isCaseSensitive);
552+
if (value == null){
553+
return null;
554+
}
555+
return value.toArray(new String[0]);
556+
}
557+
558+
protected List<String> getQueryParamValuesAsList(MultiValuedTreeMap<String, String> qs, String key, boolean isCaseSensitive) {
549559
if (qs != null) {
550560
if (isCaseSensitive) {
551-
return qs.get(key).toArray(new String[0]);
561+
return qs.get(key);
552562
}
553563

554564
for (String k : qs.keySet()) {
555565
if (k.toLowerCase(Locale.getDefault()).equals(key.toLowerCase(Locale.getDefault()))) {
556-
return qs.get(k).toArray(new String[0]);
566+
return qs.get(k);
557567
}
558568
}
559569
}
560570

561-
return new String[0];
571+
return Collections.emptyList();
562572
}
563573

564574
protected Map<String, String[]> generateParameterMap(MultiValuedTreeMap<String, String> qs, ContainerConfig config) {
565-
Map<String, String[]> output = new HashMap<>();
575+
Map<String, String[]> output;
566576

567-
Map<String, List<String>> params = getFormUrlEncodedParametersMap();
568-
params.entrySet().stream().parallel().forEach(e -> {
569-
output.put(e.getKey(), e.getValue().toArray(new String[0]));
570-
});
577+
Map<String, List<String>> formEncodedParams = getFormUrlEncodedParametersMap();
578+
579+
if (qs == null) {
580+
// Just transform the List<String> values to String[]
581+
output = formEncodedParams.entrySet().stream()
582+
.collect(Collectors.toMap(Map.Entry::getKey, (e) -> e.getValue().toArray(new String[0])));
583+
} else {
584+
Map<String, List<String>> queryStringParams;
585+
if (config.isQueryStringCaseSensitive()) {
586+
queryStringParams = qs;
587+
} else {
588+
// If it's case insensitive, we check the entire map on every parameter
589+
queryStringParams = qs.entrySet().stream().parallel().collect(
590+
Collectors.toMap(
591+
Map.Entry::getKey,
592+
e -> getQueryParamValuesAsList(qs, e.getKey(), false)
593+
));
594+
}
595+
596+
// Merge formEncodedParams and queryStringParams Maps
597+
output = Stream.of(formEncodedParams, queryStringParams).flatMap(m -> m.entrySet().stream())
598+
.collect(
599+
Collectors.toMap(
600+
Map.Entry::getKey,
601+
e -> e.getValue().toArray(new String[0]),
602+
// If a parameter is in both Maps, we merge the list of values (and ultimately transform to String[])
603+
(formParam, queryParam) -> Stream.of(formParam, queryParam).flatMap(Stream::of).toArray(String[]::new)
604+
));
571605

572-
if (qs != null) {
573-
qs.keySet().stream().parallel().forEach(e -> {
574-
List<String> newValues = new ArrayList<>();
575-
if (output.containsKey(e)) {
576-
String[] values = output.get(e);
577-
newValues.addAll(Arrays.asList(values));
578-
}
579-
newValues.addAll(Arrays.asList(getQueryParamValues(qs, e, config.isQueryStringCaseSensitive())));
580-
output.put(e, newValues.toArray(new String[0]));
581-
});
582606
}
583607

584608
return output;

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

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.amazonaws.serverless.proxy.internal.servlet;
22

33
import com.amazonaws.serverless.proxy.model.AwsProxyRequest;
4+
import com.amazonaws.serverless.proxy.model.MultiValuedTreeMap;
45
import com.amazonaws.serverless.proxy.internal.testutils.AwsProxyRequestBuilder;
56
import com.amazonaws.serverless.proxy.internal.testutils.MockLambdaContext;
67
import com.amazonaws.serverless.proxy.model.ContainerConfig;
@@ -15,6 +16,8 @@
1516

1617
import java.util.Base64;
1718
import java.util.List;
19+
import java.util.Map;
20+
import java.util.Arrays;
1821

1922

2023
public class AwsHttpServletRequestTest {
@@ -33,6 +36,14 @@ public class AwsHttpServletRequestTest {
3336
.queryString("one", "two").queryString("json", "{\"name\":\"faisal\"}").build();
3437
private static final AwsProxyRequest multipleParams = new AwsProxyRequestBuilder("/test", "GET")
3538
.queryString("one", "two").queryString("one", "three").queryString("json", "{\"name\":\"faisal\"}").build();
39+
private static final AwsProxyRequest formEncodedAndQueryString = new AwsProxyRequestBuilder("/test", "POST")
40+
.queryString("one", "two").queryString("one", "three")
41+
.queryString("five", "six")
42+
.form("one", "four")
43+
.form("seven", "eight").build();
44+
private static final AwsProxyRequest differentCasing = new AwsProxyRequestBuilder("/test", "POST")
45+
.queryString("one", "two").queryString("one", "three")
46+
.queryString("ONE", "four").build();
3647

3748
private static final MockLambdaContext mockContext = new MockLambdaContext();
3849

@@ -182,4 +193,130 @@ void queryStringWithMultipleValues_generateQueryString_validQuery() {
182193
assertTrue(parsedString.contains("json=%7B%22name%22%3A%22faisal%22%7D"));
183194
assertTrue(parsedString.contains("&") && parsedString.indexOf("&") > 0 && parsedString.indexOf("&") < parsedString.length());
184195
}
196+
197+
@Test
198+
void parameterMap_generateParameterMap_validQuery() {
199+
AwsProxyHttpServletRequest request = new AwsProxyHttpServletRequest(queryString, mockContext, null, config);
200+
201+
Map<String, String[]> paramMap = null;
202+
try {
203+
paramMap = request.generateParameterMap(request.getAwsProxyRequest().getMultiValueQueryStringParameters(), config);
204+
} catch (Exception e) {
205+
e.printStackTrace();
206+
fail("Could not generate parameter map");
207+
}
208+
assertArrayEquals(new String[]{"two"}, paramMap.get("one"));
209+
assertArrayEquals(new String[]{"four"}, paramMap.get("three"));
210+
assertTrue(paramMap.size() == 2);
211+
}
212+
213+
@Test
214+
void parameterMap_generateParameterMap_nullParameter() {
215+
AwsProxyHttpServletRequest request = new AwsProxyHttpServletRequest(queryStringNullValue, mockContext, null, config);
216+
Map<String, String[]> paramMap = null;
217+
try {
218+
paramMap = request.generateParameterMap(request.getAwsProxyRequest().getMultiValueQueryStringParameters(), config);
219+
} catch (Exception e) {
220+
e.printStackTrace();
221+
fail("Could not generate parameter map");
222+
}
223+
224+
assertArrayEquals(new String[]{"two"}, paramMap.get("one"));
225+
assertArrayEquals(new String[]{null}, paramMap.get("three"));
226+
assertTrue(paramMap.size() == 2);
227+
}
228+
229+
@Test
230+
void parameterMapWithEncodedParams_generateParameterMap_validQuery() {
231+
AwsProxyHttpServletRequest request = new AwsProxyHttpServletRequest(encodedQueryString, mockContext, null, config);
232+
233+
Map<String, String[]> paramMap = null;
234+
try {
235+
paramMap = request.generateParameterMap(request.getAwsProxyRequest().getMultiValueQueryStringParameters(), config);
236+
} catch (Exception e) {
237+
e.printStackTrace();
238+
fail("Could not generate parameter map");
239+
}
240+
241+
assertArrayEquals(new String[]{"two"}, paramMap.get("one"));
242+
assertArrayEquals(new String[]{"{\"name\":\"faisal\"}"}, paramMap.get("json"));
243+
assertTrue(paramMap.size() == 2);
244+
}
245+
246+
@Test
247+
void parameterMapWithMultipleValues_generateParameterMap_validQuery() {
248+
AwsProxyHttpServletRequest request = new AwsProxyHttpServletRequest(multipleParams, mockContext, null, config);
249+
250+
Map<String, String[]> paramMap = null;
251+
try {
252+
paramMap = request.generateParameterMap(request.getAwsProxyRequest().getMultiValueQueryStringParameters(), config);
253+
} catch (Exception e) {
254+
e.printStackTrace();
255+
fail("Could not generate parameter map");
256+
}
257+
assertArrayEquals(new String[]{"two", "three"}, paramMap.get("one"));
258+
assertArrayEquals(new String[]{"{\"name\":\"faisal\"}"}, paramMap.get("json"));
259+
assertTrue(paramMap.size() == 2);
260+
}
261+
262+
@Test
263+
void parameterMap_generateParameterMap_formEncodedAndQueryString() {
264+
AwsProxyHttpServletRequest request = new AwsProxyHttpServletRequest(formEncodedAndQueryString, mockContext, null, config);
265+
266+
Map<String, String[]> paramMap = null;
267+
try {
268+
paramMap = request.generateParameterMap(request.getAwsProxyRequest().getMultiValueQueryStringParameters(), config);
269+
} catch (Exception e) {
270+
e.printStackTrace();
271+
fail("Could not generate parameter map");
272+
}
273+
// Combines form encoded parameters (one=four) with query string (one=two,three)
274+
// The order between them is not officially guaranteed (it could be four,two,three or two,three,four)
275+
// Current implementation gives form encoded parameters first
276+
assertArrayEquals(new String[]{"four", "two", "three"}, paramMap.get("one"));
277+
assertArrayEquals(new String[]{"six"}, paramMap.get("five"));
278+
assertArrayEquals(new String[]{"eight"}, paramMap.get("seven"));
279+
assertTrue(paramMap.size() == 3);
280+
}
281+
282+
@Test
283+
void parameterMap_generateParameterMap_differentCasing() {
284+
AwsProxyHttpServletRequest request = new AwsProxyHttpServletRequest(differentCasing, mockContext, null, config);
285+
286+
Map<String, String[]> paramMap = null;
287+
try {
288+
paramMap = request.generateParameterMap(request.getAwsProxyRequest().getMultiValueQueryStringParameters(), config);
289+
} catch (Exception e) {
290+
e.printStackTrace();
291+
fail("Could not generate parameter map");
292+
}
293+
// If a parameter is duplicated but with a different casing, it's replaced with only one of them
294+
assertArrayEquals(paramMap.get("one"), paramMap.get("ONE"));
295+
assertTrue(paramMap.size() == 2);
296+
}
297+
298+
@Test
299+
void queryParamValues_getQueryParamValues() {
300+
AwsProxyHttpServletRequest request = new AwsProxyHttpServletRequest(new AwsProxyRequest(), mockContext, null);
301+
MultiValuedTreeMap<String, String> map = new MultiValuedTreeMap<>();
302+
map.add("test", "test");
303+
map.add("test", "test2");
304+
String[] result1 = request.getQueryParamValues(map, "test", true);
305+
assertArrayEquals(new String[]{"test", "test2"}, result1);
306+
String[] result2 = request.getQueryParamValues(map, "TEST", true);
307+
assertNull(result2);
308+
}
309+
310+
@Test
311+
void queryParamValues_getQueryParamValues_caseInsensitive() {
312+
AwsProxyHttpServletRequest request = new AwsProxyHttpServletRequest(new AwsProxyRequest(), mockContext, null);
313+
MultiValuedTreeMap<String, String> map = new MultiValuedTreeMap<>();
314+
map.add("test", "test");
315+
map.add("test", "test2");
316+
String[] result1 = request.getQueryParamValues(map, "test", false);
317+
assertArrayEquals(new String[]{"test", "test2"}, result1);
318+
String[] result2 = request.getQueryParamValues(map, "TEST", false);
319+
assertArrayEquals(new String[]{"test", "test2"}, result2);
320+
}
321+
185322
}

0 commit comments

Comments
 (0)