diff --git a/spring-web/src/main/java/org/springframework/http/server/ServletServerHttpRequest.java b/spring-web/src/main/java/org/springframework/http/server/ServletServerHttpRequest.java index 47789cf114f..85850d138ff 100644 --- a/spring-web/src/main/java/org/springframework/http/server/ServletServerHttpRequest.java +++ b/spring-web/src/main/java/org/springframework/http/server/ServletServerHttpRequest.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -242,7 +242,7 @@ public class ServletServerHttpRequest implements ServerHttpRequest { * from the body, which can fail if any other code has used the ServletRequest * to access a parameter, thus causing the input stream to be "consumed". */ - private static InputStream getBodyFromServletRequestParameters(HttpServletRequest request) throws IOException { + private InputStream getBodyFromServletRequestParameters(HttpServletRequest request) throws IOException { ByteArrayOutputStream bos = new ByteArrayOutputStream(1024); Writer writer = new OutputStreamWriter(bos, FORM_CHARSET); @@ -268,7 +268,12 @@ public class ServletServerHttpRequest implements ServerHttpRequest { } writer.flush(); - return new ByteArrayInputStream(bos.toByteArray()); + byte[] bytes = bos.toByteArray(); + if (bytes.length > 0 && getHeaders().containsKey(HttpHeaders.CONTENT_LENGTH)) { + getHeaders().setContentLength(bytes.length); + } + + return new ByteArrayInputStream(bytes); } } diff --git a/spring-web/src/main/java/org/springframework/web/method/annotation/RequestParamMethodArgumentResolver.java b/spring-web/src/main/java/org/springframework/web/method/annotation/RequestParamMethodArgumentResolver.java index 2a0673239f9..921f7f656fa 100644 --- a/spring-web/src/main/java/org/springframework/web/method/annotation/RequestParamMethodArgumentResolver.java +++ b/spring-web/src/main/java/org/springframework/web/method/annotation/RequestParamMethodArgumentResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -179,6 +179,9 @@ public class RequestParamMethodArgumentResolver extends AbstractNamedValueMethod } if (arg == null) { String[] paramValues = request.getParameterValues(name); + if (paramValues == null) { + paramValues = request.getParameterValues(name + "[]"); + } if (paramValues != null) { arg = (paramValues.length == 1 ? paramValues[0] : paramValues); } diff --git a/spring-web/src/test/java/org/springframework/http/server/ServletServerHttpRequestTests.java b/spring-web/src/test/java/org/springframework/http/server/ServletServerHttpRequestTests.java index d5f67995983..2e1792f6da6 100644 --- a/spring-web/src/test/java/org/springframework/http/server/ServletServerHttpRequestTests.java +++ b/spring-web/src/test/java/org/springframework/http/server/ServletServerHttpRequestTests.java @@ -203,4 +203,17 @@ class ServletServerHttpRequestTests { assertThat(result).as("Invalid content returned").isEqualTo(content); } + @Test // gh-32471 + void getFormBodyWhenNotEncodedCharactersPresent() throws IOException { + mockRequest.setContentType("application/x-www-form-urlencoded; charset=UTF-8"); + mockRequest.setMethod("POST"); + mockRequest.addParameter("name", "Test"); + mockRequest.addParameter("lastName", "Test@er"); + mockRequest.addHeader("Content-Length", 26); + + byte[] result = FileCopyUtils.copyToByteArray(request.getBody()); + assertThat(result).isEqualTo("name=Test&lastName=Test%40er".getBytes(StandardCharsets.UTF_8)); + assertThat(request.getHeaders().getContentLength()).isEqualTo(result.length); + } + } diff --git a/spring-web/src/test/java/org/springframework/web/method/annotation/RequestParamMethodArgumentResolverTests.java b/spring-web/src/test/java/org/springframework/web/method/annotation/RequestParamMethodArgumentResolverTests.java index c596777fd5a..629b723c39e 100644 --- a/spring-web/src/test/java/org/springframework/web/method/annotation/RequestParamMethodArgumentResolverTests.java +++ b/spring-web/src/test/java/org/springframework/web/method/annotation/RequestParamMethodArgumentResolverTests.java @@ -70,11 +70,11 @@ class RequestParamMethodArgumentResolverTests { private RequestParamMethodArgumentResolver resolver; - private MockHttpServletRequest request = new MockHttpServletRequest(); + private final MockHttpServletRequest request = new MockHttpServletRequest(); private NativeWebRequest webRequest = new ServletWebRequest(request, new MockHttpServletResponse()); - private ResolvableMethod testMethod = ResolvableMethod.on(getClass()).named("handle").build(); + private final ResolvableMethod testMethod = ResolvableMethod.on(getClass()).named("handle").build(); @BeforeEach void setup() { @@ -186,6 +186,19 @@ class RequestParamMethodArgumentResolverTests { assertThat((String[]) result).as("Invalid result").isEqualTo(expected); } + @Test // gh-32577 + void resolveStringArrayWithEmptyArraySuffix() throws Exception { + String[] expected = new String[] {"foo", "bar"}; + request.addParameter("name[]", expected[0]); + request.addParameter("name[]", expected[1]); + + MethodParameter param = this.testMethod.annotPresent(RequestParam.class).arg(String[].class); + Object result = resolver.resolveArgument(param, null, webRequest, null); + boolean condition = result instanceof String[]; + assertThat(condition).isTrue(); + assertThat((String[]) result).isEqualTo(expected); + } + @Test void resolveMultipartFile() throws Exception { MockMultipartHttpServletRequest request = new MockMultipartHttpServletRequest(); diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestParamMethodArgumentResolver.java b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestParamMethodArgumentResolver.java index 55f4d62b37c..17c6a868d0c 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestParamMethodArgumentResolver.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestParamMethodArgumentResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -100,8 +100,11 @@ public class RequestParamMethodArgumentResolver extends AbstractNamedValueSyncAr @Override @Nullable protected Object resolveNamedValue(String name, MethodParameter parameter, ServerWebExchange exchange) { - List paramValues = exchange.getRequest().getQueryParams().get(name); Object result = null; + List paramValues = exchange.getRequest().getQueryParams().get(name); + if (paramValues == null) { + paramValues = exchange.getRequest().getQueryParams().get(name + "[]"); + } if (paramValues != null) { result = (paramValues.size() == 1 ? paramValues.get(0) : paramValues); } diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/RequestParamMethodArgumentResolverTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/RequestParamMethodArgumentResolverTests.java index ccb90f34d62..c42bb55f151 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/RequestParamMethodArgumentResolverTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/RequestParamMethodArgumentResolverTests.java @@ -53,7 +53,7 @@ class RequestParamMethodArgumentResolverTests { private BindingContext bindContext; - private ResolvableMethod testMethod = ResolvableMethod.on(getClass()).named("handle").build(); + private final ResolvableMethod testMethod = ResolvableMethod.on(getClass()).named("handle").build(); @BeforeEach @@ -130,6 +130,16 @@ class RequestParamMethodArgumentResolverTests { assertThat((String[]) result).isEqualTo(new String[] {"foo", "bar"}); } + @Test // gh-32577 + void resolveStringArrayWithEmptyArraySuffix() { + MethodParameter param = this.testMethod.annotPresent(RequestParam.class).arg(String[].class); + MockServerHttpRequest request = MockServerHttpRequest.get("/path?name[]=foo&name[]=bar").build(); + Object result = resolve(param, MockServerWebExchange.from(request)); + boolean condition = result instanceof String[]; + assertThat(condition).isTrue(); + assertThat((String[]) result).isEqualTo(new String[] {"foo", "bar"}); + } + @Test void resolveDefaultValue() { MethodParameter param = this.testMethod.annot(requestParam().notRequired("bar")).arg(String.class);