diff --git a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpServletRequest.java b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpServletRequest.java index c169a1f0..bde53961 100644 --- a/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpServletRequest.java +++ b/aws-serverless-java-container-core/src/main/java/com/amazonaws/serverless/proxy/internal/servlet/AwsHttpServletRequest.java @@ -546,7 +546,7 @@ protected String[] getQueryParamValues(MultiValuedTreeMap qs, St return value.toArray(new String[0]); } - protected List getQueryParamValuesAsList(MultiValuedTreeMap qs, String key, boolean isCaseSensitive) { + public static List getQueryParamValuesAsList(MultiValuedTreeMap qs, String key, boolean isCaseSensitive) { if (qs != null) { if (isCaseSensitive) { return qs.get(key); @@ -788,7 +788,7 @@ static String cleanUri(String uri) { return finalUri; } - static String decodeValueIfEncoded(String value) { + public static String decodeValueIfEncoded(String value) { if (value == null) { return null; } diff --git a/aws-serverless-java-container-springboot3/src/main/java/com/amazonaws/serverless/proxy/spring/AwsSpringHttpProcessingUtils.java b/aws-serverless-java-container-springboot3/src/main/java/com/amazonaws/serverless/proxy/spring/AwsSpringHttpProcessingUtils.java index 09649b31..0f6270b3 100644 --- a/aws-serverless-java-container-springboot3/src/main/java/com/amazonaws/serverless/proxy/spring/AwsSpringHttpProcessingUtils.java +++ b/aws-serverless-java-container-springboot3/src/main/java/com/amazonaws/serverless/proxy/spring/AwsSpringHttpProcessingUtils.java @@ -4,12 +4,14 @@ import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.util.Base64; +import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; - import com.amazonaws.serverless.proxy.internal.HttpUtils; +import com.amazonaws.serverless.proxy.internal.servlet.AwsHttpServletRequest; +import com.amazonaws.serverless.proxy.model.RequestSource; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.cloud.function.serverless.web.ServerlessHttpServletRequest; @@ -36,6 +38,9 @@ import jakarta.servlet.ServletContext; import jakarta.servlet.http.HttpServletRequest; +import static com.amazonaws.serverless.proxy.internal.servlet.AwsHttpServletRequest.decodeValueIfEncoded; +import static com.amazonaws.serverless.proxy.internal.servlet.AwsHttpServletRequest.getQueryParamValuesAsList; + class AwsSpringHttpProcessingUtils { private static Log logger = LogFactory.getLog(AwsSpringHttpProcessingUtils.class); @@ -116,13 +121,8 @@ private static HttpServletRequest generateRequest1(String request, Context lambd ServerlessHttpServletRequest httpRequest = new ServerlessHttpServletRequest(servletContext, v1Request.getHttpMethod(), v1Request.getPath()); - populateQueryStringParameters(v1Request.getQueryStringParameters(), httpRequest); - if (v1Request.getMultiValueQueryStringParameters() != null) { - MultiValueMapAdapter queryStringParameters = new MultiValueMapAdapter(v1Request.getMultiValueQueryStringParameters()); - queryStringParameters.forEach((k, v) -> { - httpRequest.setParameter(k, v.toArray(new String[0])); - }); - } + populateQueryStringParametersV1(v1Request, httpRequest); + populateMultiValueQueryStringParametersV1(v1Request, httpRequest); if (v1Request.getMultiValueHeaders() != null) { MultiValueMapAdapter headers = new MultiValueMapAdapter(v1Request.getMultiValueHeaders()); @@ -156,7 +156,7 @@ private static HttpServletRequest generateRequest2(String request, Context lambd ServerlessHttpServletRequest httpRequest = new ServerlessHttpServletRequest(servletContext, v2Request.getRequestContext().getHttp().getMethod(), v2Request.getRequestContext().getHttp().getPath()); - populateQueryStringParameters(v2Request.getQueryStringParameters(), httpRequest); + populateQueryStringParametersV2(v2Request.getQueryStringParameters(), httpRequest); v2Request.getHeaders().forEach(httpRequest::setHeader); @@ -176,7 +176,7 @@ private static HttpServletRequest generateRequest2(String request, Context lambd return httpRequest; } - private static void populateQueryStringParameters(Map requestParameters, ServerlessHttpServletRequest httpRequest) { + private static void populateQueryStringParametersV2(Map requestParameters, ServerlessHttpServletRequest httpRequest) { if (!CollectionUtils.isEmpty(requestParameters)) { for (Entry entry : requestParameters.entrySet()) { // fix according to parseRawQueryString @@ -184,6 +184,35 @@ private static void populateQueryStringParameters(Map requestPar } } } + + private static void populateQueryStringParametersV1(AwsProxyRequest v1Request, ServerlessHttpServletRequest httpRequest) { + Map requestParameters = v1Request.getQueryStringParameters(); + if (!CollectionUtils.isEmpty(requestParameters)) { + // decode all keys and values in map + for (Entry entry : requestParameters.entrySet()) { + String k = v1Request.getRequestSource() == RequestSource.ALB ? decodeValueIfEncoded(entry.getKey()) : entry.getKey(); + String v = v1Request.getRequestSource() == RequestSource.ALB ? decodeValueIfEncoded(entry.getValue()) : entry.getValue(); + httpRequest.setParameter(k, v); + } + } + } + + private static void populateMultiValueQueryStringParametersV1(AwsProxyRequest v1Request, ServerlessHttpServletRequest httpRequest) { + if (v1Request.getMultiValueQueryStringParameters() != null) { + MultiValueMapAdapter queryStringParameters = new MultiValueMapAdapter<>(v1Request.getMultiValueQueryStringParameters()); + queryStringParameters.forEach((k, v) -> { + String key = v1Request.getRequestSource() == RequestSource.ALB + ? decodeValueIfEncoded(k) + : k; + List value = v1Request.getRequestSource() == RequestSource.ALB + ? getQueryParamValuesAsList(v1Request.getMultiValueQueryStringParameters(), k, false).stream() + .map(AwsHttpServletRequest::decodeValueIfEncoded) + .toList() + : v; + httpRequest.setParameter(key, value.toArray(new String[0])); + }); + } + } private static T readValue(String json, Class clazz, ObjectMapper mapper) { try { diff --git a/aws-serverless-java-container-springboot3/src/test/java/com/amazonaws/serverless/proxy/spring/AwsSpringHttpProcessingUtilsTests.java b/aws-serverless-java-container-springboot3/src/test/java/com/amazonaws/serverless/proxy/spring/AwsSpringHttpProcessingUtilsTests.java index fa0e92e9..94232cbf 100644 --- a/aws-serverless-java-container-springboot3/src/test/java/com/amazonaws/serverless/proxy/spring/AwsSpringHttpProcessingUtilsTests.java +++ b/aws-serverless-java-container-springboot3/src/test/java/com/amazonaws/serverless/proxy/spring/AwsSpringHttpProcessingUtilsTests.java @@ -6,6 +6,7 @@ import java.util.Collection; import com.amazonaws.serverless.proxy.RequestReader; +import com.amazonaws.serverless.proxy.model.AlbContext; import com.amazonaws.serverless.proxy.model.HttpApiV2ProxyRequest; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; @@ -194,7 +195,7 @@ public class AwsSpringHttpProcessingUtilsTests { " },\n" + " \"httpMethod\": \"POST\",\n" + " \"path\": \"/async\",\n" + - " \"multiValueQueryStringParameters\": { \"parameter1\": [\"value1\", \"value2\"]},\n" + + " \"multiValueQueryStringParameters\": { \"parameter1\": [\"value1\", \"value2\"], \"parameter2\": [\"1970-01-01T00%3A00%3A00.004Z\"]},\n" + " \"multiValueHeaders\": {\n" + " \"accept\": [\"text/html,application/xhtml+xml\"],\n" + " \"accept-language\": [\"en-US,en;q=0.8\"],\n" + @@ -238,6 +239,10 @@ private static void assertRequest(HttpServletRequest request) { assertEquals("value1", request.getParameter("parameter1")); assertArrayEquals(new String[]{"value1", "value2"}, request.getParameterValues("parameter1")); } + if (request.getAttribute(RequestReader.ALB_CONTEXT_PROPERTY) instanceof AlbContext) { + // query params should be decoded + assertEquals("1970-01-01T00:00:00.004Z", request.getParameter("parameter2")); + } } @MethodSource("data")