diff --git a/spring-web/src/main/java/org/springframework/http/converter/FormHttpMessageConverter.java b/spring-web/src/main/java/org/springframework/http/converter/FormHttpMessageConverter.java index 8b839f5cd4d..93440fe9422 100644 --- a/spring-web/src/main/java/org/springframework/http/converter/FormHttpMessageConverter.java +++ b/spring-web/src/main/java/org/springframework/http/converter/FormHttpMessageConverter.java @@ -28,7 +28,6 @@ import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -import java.util.function.BiConsumer; import org.springframework.core.io.Resource; import org.springframework.http.ContentDisposition; @@ -51,13 +50,24 @@ import org.springframework.util.StringUtils; * Implementation of {@link HttpMessageConverter} to read and write 'normal' HTML * forms and also to write (but not read) multipart data (e.g. file uploads). * - *

In other words, this converter can read and write the - * {@code "application/x-www-form-urlencoded"} media type as - * {@code Map} or as - * {@link MultiValueMap MultiValueMap<String, String>}, and it can also - * write (but not read) the {@code "multipart/form-data"} and - * {@code "multipart/mixed"} media types as {@code Map} or as - * {@link MultiValueMap MultiValueMap<String, Object>}. + *

+ * The following table shows an overview of the supported media and class types. + * + * + * + * + * + * + * + * + * + * + * + * + *
Media typeReadWrite
{@code "application/x-www-form-urlencoded"}{@link MultiValueMap MultiValueMap<String, String>}{@link Map Map<String, String>}
+ * {@link MultiValueMap MultiValueMap<String, String>}
{@code "multipart/form-data"}
+ * {@code "multipart/mixed"}
Unsupported{@link Map Map<String, Object>}
+ * {@link MultiValueMap MultiValueMap<String, Object>}
* *

Multipart Data

* @@ -158,8 +168,8 @@ import org.springframework.util.StringUtils; * {@code org.apache.commons.httpclient.methods.multipart.MultipartRequestEntity}. * *

As of 6.2, the {@code FormHttpMessageConverter} is parameterized over - * {@code Map}, whereas before it was {@code MultiValueMap}, - * in order to support single-value maps. + * {@code Map} in order to support writing single-value maps. + * Before 6.2, this class was parameterized over {@code MultiValueMap}. * * @author Arjen Poutsma * @author Rossen Stoyanchev @@ -312,7 +322,7 @@ public class FormHttpMessageConverter implements HttpMessageConverter clazz, @Nullable MediaType mediaType) { - if (!Map.class.isAssignableFrom(clazz)) { + if (!MultiValueMap.class.isAssignableFrom(clazz)) { return false; } if (mediaType == null) { @@ -354,32 +364,21 @@ public class FormHttpMessageConverter implements HttpMessageConverter result = new LinkedMultiValueMap<>(pairs.length); - readToMap(pairs, charset, result::add); - return result; - } - else { - Map result = CollectionUtils.newLinkedHashMap(pairs.length); - readToMap(pairs, charset, result::putIfAbsent); - return result; - } - } - private static void readToMap(String[] pairs, Charset charset, BiConsumer addFunction) { + String[] pairs = StringUtils.tokenizeToStringArray(body, "&"); + MultiValueMap result = new LinkedMultiValueMap<>(pairs.length); for (String pair : pairs) { int idx = pair.indexOf('='); if (idx == -1) { - addFunction.accept(URLDecoder.decode(pair, charset), null); + result.add(URLDecoder.decode(pair, charset), null); } else { String name = URLDecoder.decode(pair.substring(0, idx), charset); String value = URLDecoder.decode(pair.substring(idx + 1), charset); - addFunction.accept(name, value); + result.add(name, value); } } + return result; } @Override diff --git a/spring-web/src/test/java/org/springframework/http/converter/FormHttpMessageConverterTests.java b/spring-web/src/test/java/org/springframework/http/converter/FormHttpMessageConverterTests.java index 7a30dae8ace..299ca939bf8 100644 --- a/spring-web/src/test/java/org/springframework/http/converter/FormHttpMessageConverterTests.java +++ b/spring-web/src/test/java/org/springframework/http/converter/FormHttpMessageConverterTests.java @@ -73,7 +73,6 @@ class FormHttpMessageConverterTests { @Test void canRead() { assertCanRead(MultiValueMap.class, null); - assertCanRead(Map.class, null); assertCanRead(APPLICATION_FORM_URLENCODED); assertCannotRead(String.class, null); @@ -120,7 +119,7 @@ class FormHttpMessageConverterTests { @Test @SuppressWarnings("unchecked") - void readFormMultiValue() throws Exception { + void readForm() throws Exception { String body = "name+1=value+1&name+2=value+2%2B1&name+2=value+2%2B2&name+3"; MockHttpInputMessage inputMessage = new MockHttpInputMessage(body.getBytes(StandardCharsets.ISO_8859_1)); inputMessage.getHeaders().setContentType( @@ -135,25 +134,7 @@ class FormHttpMessageConverterTests { } @Test - @SuppressWarnings({ "rawtypes", "unchecked" }) - void readFormSingleValue() throws Exception { - String body = "name+1=value+1&name+2=value+2%2B1&name+2=value+2%2B2&name+3"; - MockHttpInputMessage inputMessage = new MockHttpInputMessage(body.getBytes(StandardCharsets.ISO_8859_1)); - inputMessage.getHeaders().setContentType( - new MediaType("application", "x-www-form-urlencoded", StandardCharsets.ISO_8859_1)); - Object result = ((HttpMessageConverter) this.converter).read(Map.class, inputMessage); - - assertThat(result).isInstanceOf(Map.class); - assertThat(result).isNotInstanceOf(MultiValueMap.class); - Map map = (Map) result; - assertThat(map).as("Invalid result").hasSize(3); - assertThat(map.get("name 1")).as("Invalid result").isEqualTo("value 1"); - assertThat(map.get("name 2")).as("Invalid result").isEqualTo("value 2+1"); - assertThat(map.get("name 3")).as("Invalid result").isNull(); - } - - @Test - void writeFormMultiValue() throws IOException { + void writeForm() throws IOException { MultiValueMap body = new LinkedMultiValueMap<>(); body.set("name 1", "value 1"); body.add("name 2", "value 2+1");