提交 75117f42 编写于 作者: R Rossen Stoyanchev

Use the configured charset for part headers

This comment extends the use of the charset property in
FormHttpMessageConverter to also include multipart headers with a
default of UTF-8.

We now also set the charset parameter of the "Content-Type" header to
indicate to the server side how to decode correctly.

Issue: SPR-15205
上级 bda27239
...@@ -25,6 +25,7 @@ import java.nio.charset.Charset; ...@@ -25,6 +25,7 @@ import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
...@@ -148,10 +149,17 @@ public class FormHttpMessageConverter implements HttpMessageConverter<MultiValue ...@@ -148,10 +149,17 @@ public class FormHttpMessageConverter implements HttpMessageConverter<MultiValue
/** /**
* Set the default character set to use for reading and writing form data when * Set the default character set to use for reading and writing form data when
* the request or response Content-Type header does not explicitly specify it. * the request or response Content-Type header does not explicitly specify it.
* <p>By default this is set to "UTF-8". As of 4.3, it will also be used as *
* the default charset for the conversion of text bodies in a multipart request. * <p>As of 4.3, this is also used as the default charset for the conversion
* In contrast to this, {@link #setMultipartCharset} only affects the encoding of * of text bodies in a multipart request.
* <i>file names</i> in a multipart request according to the encoded-word syntax. *
* <p>As of 5.0 this is also used for part headers including
* "Content-Disposition" (and its filename parameter) unless (the mutually
* exclusive) {@link #setMultipartCharset} is also set, in which case part
* headers are encoded as ASCII and <i>filename</i> is encoded with the
* "encoded-word" syntax from RFC 2047.
*
* <p>By default this is set to "UTF-8".
*/ */
public void setCharset(Charset charset) { public void setCharset(Charset charset) {
if (charset != this.charset) { if (charset != this.charset) {
...@@ -177,9 +185,13 @@ public class FormHttpMessageConverter implements HttpMessageConverter<MultiValue ...@@ -177,9 +185,13 @@ public class FormHttpMessageConverter implements HttpMessageConverter<MultiValue
/** /**
* Set the character set to use when writing multipart data to encode file * Set the character set to use when writing multipart data to encode file
* names. Encoding is based on the encoded-word syntax defined in RFC 2047 * names. Encoding is based on the "encoded-word" syntax defined in RFC 2047
* and relies on {@code MimeUtility} from "javax.mail". * and relies on {@code MimeUtility} from "javax.mail".
* <p>If not set file names will be encoded as US-ASCII. *
* <p>As of 5.0 by default part headers, including Content-Disposition (and
* its filename parameter) will be encoded based on the setting of
* {@link #setCharset(Charset)} or {@code UTF-8} by default.
*
* @since 4.1.1 * @since 4.1.1
* @see <a href="http://en.wikipedia.org/wiki/MIME#Encoded-Word">Encoded-Word</a> * @see <a href="http://en.wikipedia.org/wiki/MIME#Encoded-Word">Encoded-Word</a>
*/ */
...@@ -322,7 +334,11 @@ public class FormHttpMessageConverter implements HttpMessageConverter<MultiValue ...@@ -322,7 +334,11 @@ public class FormHttpMessageConverter implements HttpMessageConverter<MultiValue
private void writeMultipart(final MultiValueMap<String, Object> parts, HttpOutputMessage outputMessage) throws IOException { private void writeMultipart(final MultiValueMap<String, Object> parts, HttpOutputMessage outputMessage) throws IOException {
final byte[] boundary = generateMultipartBoundary(); final byte[] boundary = generateMultipartBoundary();
Map<String, String> parameters = Collections.singletonMap("boundary", new String(boundary, "US-ASCII")); Map<String, String> parameters = new HashMap<>(2);
parameters.put("boundary", new String(boundary, "US-ASCII"));
if (!isFilenameCharsetSet()) {
parameters.put("charset", this.charset.name());
}
MediaType contentType = new MediaType(MediaType.MULTIPART_FORM_DATA, parameters); MediaType contentType = new MediaType(MediaType.MULTIPART_FORM_DATA, parameters);
HttpHeaders headers = outputMessage.getHeaders(); HttpHeaders headers = outputMessage.getHeaders();
...@@ -344,6 +360,15 @@ public class FormHttpMessageConverter implements HttpMessageConverter<MultiValue ...@@ -344,6 +360,15 @@ public class FormHttpMessageConverter implements HttpMessageConverter<MultiValue
} }
} }
/**
* When {@link #setMultipartCharset(Charset)} is configured (i.e. RFC 2047,
* "encoded-word" syntax) we need to use ASCII for part headers or otherwise
* we encode directly using the configured {@link #setCharset(Charset)}.
*/
private boolean isFilenameCharsetSet() {
return this.multipartCharset != null;
}
private void writeParts(OutputStream os, MultiValueMap<String, Object> parts, byte[] boundary) throws IOException { private void writeParts(OutputStream os, MultiValueMap<String, Object> parts, byte[] boundary) throws IOException {
for (Map.Entry<String, List<Object>> entry : parts.entrySet()) { for (Map.Entry<String, List<Object>> entry : parts.entrySet()) {
String name = entry.getKey(); String name = entry.getKey();
...@@ -365,7 +390,8 @@ public class FormHttpMessageConverter implements HttpMessageConverter<MultiValue ...@@ -365,7 +390,8 @@ public class FormHttpMessageConverter implements HttpMessageConverter<MultiValue
MediaType partContentType = partHeaders.getContentType(); MediaType partContentType = partHeaders.getContentType();
for (HttpMessageConverter<?> messageConverter : this.partConverters) { for (HttpMessageConverter<?> messageConverter : this.partConverters) {
if (messageConverter.canWrite(partType, partContentType)) { if (messageConverter.canWrite(partType, partContentType)) {
HttpOutputMessage multipartMessage = new MultipartHttpOutputMessage(os); Charset charset = isFilenameCharsetSet() ? StandardCharsets.US_ASCII : this.charset;
HttpOutputMessage multipartMessage = new MultipartHttpOutputMessage(os, charset);
multipartMessage.getHeaders().setContentDispositionFormData(name, getFilename(partBody)); multipartMessage.getHeaders().setContentDispositionFormData(name, getFilename(partBody));
if (!partHeaders.isEmpty()) { if (!partHeaders.isEmpty()) {
multipartMessage.getHeaders().putAll(partHeaders); multipartMessage.getHeaders().putAll(partHeaders);
...@@ -378,7 +404,6 @@ public class FormHttpMessageConverter implements HttpMessageConverter<MultiValue ...@@ -378,7 +404,6 @@ public class FormHttpMessageConverter implements HttpMessageConverter<MultiValue
"found for request type [" + partType.getName() + "]"); "found for request type [" + partType.getName() + "]");
} }
/** /**
* Generate a multipart boundary. * Generate a multipart boundary.
* <p>This implementation delegates to * <p>This implementation delegates to
...@@ -451,12 +476,15 @@ public class FormHttpMessageConverter implements HttpMessageConverter<MultiValue ...@@ -451,12 +476,15 @@ public class FormHttpMessageConverter implements HttpMessageConverter<MultiValue
private final OutputStream outputStream; private final OutputStream outputStream;
private final Charset charset;
private final HttpHeaders headers = new HttpHeaders(); private final HttpHeaders headers = new HttpHeaders();
private boolean headersWritten = false; private boolean headersWritten = false;
public MultipartHttpOutputMessage(OutputStream outputStream) { public MultipartHttpOutputMessage(OutputStream outputStream, Charset charset) {
this.outputStream = outputStream; this.outputStream = outputStream;
this.charset = charset;
} }
@Override @Override
...@@ -473,9 +501,9 @@ public class FormHttpMessageConverter implements HttpMessageConverter<MultiValue ...@@ -473,9 +501,9 @@ public class FormHttpMessageConverter implements HttpMessageConverter<MultiValue
private void writeHeaders() throws IOException { private void writeHeaders() throws IOException {
if (!this.headersWritten) { if (!this.headersWritten) {
for (Map.Entry<String, List<String>> entry : this.headers.entrySet()) { for (Map.Entry<String, List<String>> entry : this.headers.entrySet()) {
byte[] headerName = getAsciiBytes(entry.getKey()); byte[] headerName = getBytes(entry.getKey());
for (String headerValueString : entry.getValue()) { for (String headerValueString : entry.getValue()) {
byte[] headerValue = getAsciiBytes(headerValueString); byte[] headerValue = getBytes(headerValueString);
this.outputStream.write(headerName); this.outputStream.write(headerName);
this.outputStream.write(':'); this.outputStream.write(':');
this.outputStream.write(' '); this.outputStream.write(' ');
...@@ -488,8 +516,8 @@ public class FormHttpMessageConverter implements HttpMessageConverter<MultiValue ...@@ -488,8 +516,8 @@ public class FormHttpMessageConverter implements HttpMessageConverter<MultiValue
} }
} }
private byte[] getAsciiBytes(String name) { private byte[] getBytes(String name) {
return name.getBytes(StandardCharsets.US_ASCII); return name.getBytes(this.charset);
} }
} }
......
...@@ -43,11 +43,17 @@ import org.springframework.http.converter.support.AllEncompassingFormHttpMessage ...@@ -43,11 +43,17 @@ import org.springframework.http.converter.support.AllEncompassingFormHttpMessage
import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap; import org.springframework.util.MultiValueMap;
import static org.hamcrest.CoreMatchers.*; import static org.hamcrest.CoreMatchers.allOf;
import static org.hamcrest.CoreMatchers.endsWith; import static org.hamcrest.CoreMatchers.endsWith;
import static org.hamcrest.CoreMatchers.startsWith; import static org.hamcrest.CoreMatchers.startsWith;
import static org.junit.Assert.*; import static org.junit.Assert.assertEquals;
import static org.mockito.BDDMockito.*; import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.mockito.BDDMockito.never;
import static org.mockito.BDDMockito.verify;
/** /**
* @author Arjen Poutsma * @author Arjen Poutsma
...@@ -138,7 +144,6 @@ public class FormHttpMessageConverterTests { ...@@ -138,7 +144,6 @@ public class FormHttpMessageConverterTests {
parts.add("xml", entity); parts.add("xml", entity);
MockHttpOutputMessage outputMessage = new MockHttpOutputMessage(); MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
this.converter.setMultipartCharset(StandardCharsets.UTF_8);
this.converter.write(parts, new MediaType("multipart", "form-data", StandardCharsets.UTF_8), outputMessage); this.converter.write(parts, new MediaType("multipart", "form-data", StandardCharsets.UTF_8), outputMessage);
final MediaType contentType = outputMessage.getHeaders().getContentType(); final MediaType contentType = outputMessage.getHeaders().getContentType();
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册