From 9f4499cb3675d6b8a82af05042e5d01137b48edf Mon Sep 17 00:00:00 2001 From: Arjen Poutsma Date: Mon, 20 Dec 2010 16:56:14 +0000 Subject: [PATCH] SPR-7789 - FormHttpMessageConverter does not honor the charset in the content type when writing a form and uses a wrong default charset --- .../converter/FormHttpMessageConverter.java | 87 ++++++++----------- 1 file changed, 38 insertions(+), 49 deletions(-) diff --git a/org.springframework.web/src/main/java/org/springframework/http/converter/FormHttpMessageConverter.java b/org.springframework.web/src/main/java/org/springframework/http/converter/FormHttpMessageConverter.java index 144f98e738..00971f2715 100644 --- a/org.springframework.web/src/main/java/org/springframework/http/converter/FormHttpMessageConverter.java +++ b/org.springframework.web/src/main/java/org/springframework/http/converter/FormHttpMessageConverter.java @@ -45,35 +45,26 @@ import org.springframework.util.MultiValueMap; import org.springframework.util.StringUtils; /** - * Implementation of {@link HttpMessageConverter} that can handle form data, including multipart form data - * (i.e. file uploads). + * Implementation of {@link HttpMessageConverter} that can handle form data, including multipart form data (i.e. file + * uploads). * *

This converter can write the {@code application/x-www-form-urlencoded} and {@code multipart/form-data} media * types, and read the {@code application/x-www-form-urlencoded}) media type (but not {@code multipart/form-data}). * - *

In other words, this converter can read and write 'normal' HTML forms (as - * {@link MultiValueMap MultiValueMap<String, String>}), and it can write multipart form (as - * {@link MultiValueMap MultiValueMap<String, Object>}. When writing multipart, this converter uses other - * {@link HttpMessageConverter HttpMessageConverters} to write the respective MIME parts. By default, basic converters - * are registered (supporting {@code Strings} and {@code Resources}, for instance); these can be overridden by setting - * the {@link #setPartConverters(java.util.List) partConverters} property. + *

In other words, this converter can read and write 'normal' HTML forms (as {@link MultiValueMap + * MultiValueMap<String, String>}), and it can write multipart form (as {@link MultiValueMap + * MultiValueMap<String, Object>}. When writing multipart, this converter uses other {@link HttpMessageConverter + * HttpMessageConverters} to write the respective MIME parts. By default, basic converters are registered (supporting + * {@code Strings} and {@code Resources}, for instance); these can be overridden by setting the {@link + * #setPartConverters(java.util.List) partConverters} property. * - *

For example, the following snippet shows how to submit an HTML form: - *

- * RestTemplate template = new RestTemplate(); // FormHttpMessageConverter is configured by default
- * MultiValueMap<String, String> form = new LinkedMultiValueMap<String, String>();
- * form.add("field 1", "value 1");
- * form.add("field 2", "value 2");
- * form.add("field 2", "value 3");
- * template.postForLocation("http://example.com/myForm", form);
- * 
- *

The following snippet shows how to do a file upload: - *

- * MultiValueMap<String, Object> parts = new LinkedMultiValueMap<String, Object>();
- * parts.add("field 1", "value 1");
- * parts.add("file", new ClassPathResource("myFile.jpg"));
- * template.postForLocation("http://example.com/myFileUpload", parts);
- * 
+ *

For example, the following snippet shows how to submit an HTML form:

 RestTemplate template =
+ * new RestTemplate(); // FormHttpMessageConverter is configured by default MultiValueMap<String, String> form =
+ * new LinkedMultiValueMap<String, String>(); form.add("field 1", "value 1"); form.add("field 2", "value 2");
+ * form.add("field 2", "value 3"); template.postForLocation("http://example.com/myForm", form); 

The following + * snippet shows how to do a file upload:

 MultiValueMap<String, Object> parts = new
+ * LinkedMultiValueMap<String, Object>(); parts.add("field 1", "value 1"); parts.add("file", new
+ * ClassPathResource("myFile.jpg")); template.postForLocation("http://example.com/myFileUpload", parts); 
* *

Some methods in this class were inspired by {@link org.apache.commons.httpclient.methods.multipart.MultipartRequestEntity}. * @@ -87,7 +78,7 @@ public class FormHttpMessageConverter implements HttpMessageConverter> partConverters = new ArrayList>(); - public FormHttpMessageConverter() { this.partConverters.add(new ByteArrayHttpMessageConverter()); StringHttpMessageConverter stringHttpMessageConverter = new StringHttpMessageConverter(); @@ -104,31 +94,23 @@ public class FormHttpMessageConverter implements HttpMessageConverter partConverter) { Assert.notNull(partConverter, "'partConverter' must not be NULL"); this.partConverters.add(partConverter); } - /** - * Set the message body converters to use. These converters are used to convert objects to MIME parts. - */ + /** Set the message body converters to use. These converters are used to convert objects to MIME parts. */ public final void setPartConverters(List> partConverters) { Assert.notEmpty(partConverters, "'partConverters' must not be empty"); this.partConverters = partConverters; } - /** - * Sets the character set used for writing form data. - */ + /** Sets the character set used for writing form data. */ public void setCharset(Charset charset) { this.charset = charset; } - public boolean canRead(Class clazz, MediaType mediaType) { if (!MultiValueMap.class.isAssignableFrom(clazz)) { return false; @@ -187,7 +169,7 @@ public class FormHttpMessageConverter implements HttpMessageConverter map, MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException { if (!isMultipart(map, contentType)) { - writeForm((MultiValueMap) map, outputMessage); + writeForm((MultiValueMap) map, contentType, outputMessage); } else { writeMultipart((MultiValueMap) map, outputMessage); @@ -208,8 +190,16 @@ public class FormHttpMessageConverter implements HttpMessageConverter form, HttpOutputMessage outputMessage) throws IOException { - outputMessage.getHeaders().setContentType(MediaType.APPLICATION_FORM_URLENCODED); + private void writeForm(MultiValueMap form, MediaType contentType, HttpOutputMessage outputMessage) + throws IOException { + Charset charset; + if (contentType != null) { + outputMessage.getHeaders().setContentType(contentType); + charset = contentType.getCharSet() != null ? contentType.getCharSet() : this.charset; + } else { + outputMessage.getHeaders().setContentType(MediaType.APPLICATION_FORM_URLENCODED); + charset = this.charset; + } StringBuilder builder = new StringBuilder(); for (Iterator nameIterator = form.keySet().iterator(); nameIterator.hasNext();) { String name = nameIterator.next(); @@ -231,7 +221,8 @@ public class FormHttpMessageConverter implements HttpMessageConverter parts, HttpOutputMessage outputMessage) throws IOException { + private void writeMultipart(MultiValueMap parts, HttpOutputMessage outputMessage) + throws IOException { byte[] boundary = generateMultipartBoundary(); Map parameters = Collections.singletonMap("boundary", new String(boundary, "US-ASCII")); @@ -308,8 +299,8 @@ public class FormHttpMessageConverter implements HttpMessageConverterDefault implementation returns a random boundary. Can be overridden in subclasses. + * Generate a multipart boundary.

Default implementation returns a random boundary. Can be overridden in + * subclasses. */ protected byte[] generateMultipartBoundary() { byte[] boundary = new byte[rnd.nextInt(11) + 30]; @@ -321,8 +312,9 @@ public class FormHttpMessageConverter implements HttpMessageConverterDefault implementation returns {@link Resource#getFilename()} if the part is a {@code Resource}, and - * {@code null} in other cases. Can be overridden in subclasses. + *

Default implementation returns {@link Resource#getFilename()} if the part is a {@code Resource}, and {@code null} + * in other cases. Can be overridden in subclasses. + * * @param part the part to determine the file name for * @return the filename, or {@code null} if not known */ @@ -336,10 +328,7 @@ public class FormHttpMessageConverter implements HttpMessageConverter