16
16
17
17
package org .springframework .http .codec .multipart ;
18
18
19
- import java .io .UnsupportedEncodingException ;
20
19
import java .nio .charset .Charset ;
21
20
import java .nio .charset .StandardCharsets ;
22
21
import java .util .Arrays ;
23
22
import java .util .Collections ;
23
+ import java .util .HashMap ;
24
24
import java .util .List ;
25
25
import java .util .Map ;
26
26
import java .util .Optional ;
27
27
import java .util .concurrent .atomic .AtomicBoolean ;
28
28
import java .util .function .Supplier ;
29
29
import java .util .stream .Collectors ;
30
- import javax .mail .internet .MimeUtility ;
31
30
32
31
import org .reactivestreams .Publisher ;
33
32
import reactor .core .publisher .Flux ;
@@ -69,7 +68,7 @@ public class MultipartHttpMessageWriter implements HttpMessageWriter<MultiValueM
69
68
70
69
private final List <HttpMessageWriter <?>> partWriters ;
71
70
72
- private Charset filenameCharset = DEFAULT_CHARSET ;
71
+ private Charset charset = DEFAULT_CHARSET ;
73
72
74
73
private final DataBufferFactory bufferFactory = new DefaultDataBufferFactory ();
75
74
@@ -86,19 +85,20 @@ public MultipartHttpMessageWriter(List<HttpMessageWriter<?>> partWriters) {
86
85
}
87
86
88
87
/**
89
- * Set the character set to use for writing file names in the multipart request.
88
+ * Set the character set to use for part headers such as
89
+ * "Content-Disposition" (and its filename parameter).
90
90
* <p>By default this is set to "UTF-8".
91
91
*/
92
- public void setFilenameCharset (Charset charset ) {
92
+ public void setCharset (Charset charset ) {
93
93
Assert .notNull (charset , "'charset' must not be null" );
94
- this .filenameCharset = charset ;
94
+ this .charset = charset ;
95
95
}
96
96
97
97
/**
98
- * Return the configured filename charset.
98
+ * Return the configured charset for part headers .
99
99
*/
100
- public Charset getFilenameCharset () {
101
- return this .filenameCharset ;
100
+ public Charset getCharset () {
101
+ return this .charset ;
102
102
}
103
103
104
104
@@ -120,8 +120,10 @@ public Mono<Void> write(Publisher<? extends MultiValueMap<String, ?>> inputStrea
120
120
121
121
byte [] boundary = generateMultipartBoundary ();
122
122
123
- outputMessage .getHeaders ().setContentType (new MediaType ("multipart" , "form-data" ,
124
- Collections .singletonMap ("boundary" , new String (boundary , StandardCharsets .US_ASCII ))));
123
+ Map <String , String > params = new HashMap <>(2 );
124
+ params .put ("boundary" , new String (boundary , StandardCharsets .US_ASCII ));
125
+ params .put ("charset" , getCharset ().name ());
126
+ outputMessage .getHeaders ().setContentType (new MediaType (MediaType .MULTIPART_FORM_DATA , params ));
125
127
126
128
return Mono .from (inputStream ).flatMap (map -> {
127
129
@@ -149,7 +151,8 @@ private Flux<DataBuffer> encodePartValues(byte[] boundary, String name, List<?>
149
151
@ SuppressWarnings ("unchecked" )
150
152
private <T > Flux <DataBuffer > encodePart (byte [] boundary , String name , T value ) {
151
153
152
- MultipartHttpOutputMessage outputMessage = new MultipartHttpOutputMessage (this .bufferFactory );
154
+ MultipartHttpOutputMessage outputMessage =
155
+ new MultipartHttpOutputMessage (this .bufferFactory , getCharset ());
153
156
154
157
T body ;
155
158
if (value instanceof HttpEntity ) {
@@ -160,9 +163,10 @@ private <T> Flux<DataBuffer> encodePart(byte[] boundary, String name, T value) {
160
163
body = value ;
161
164
}
162
165
163
- ResolvableType bodyType = ResolvableType . forClass (body . getClass () );
164
- outputMessage .getHeaders ().setContentDispositionFormData (name , getFilename ( body ) );
166
+ String filename = (body instanceof Resource ? (( Resource ) body ). getFilename () : null );
167
+ outputMessage .getHeaders ().setContentDispositionFormData (name , filename );
165
168
169
+ ResolvableType bodyType = ResolvableType .forClass (body .getClass ());
166
170
MediaType contentType = outputMessage .getHeaders ().getContentType ();
167
171
168
172
Optional <HttpMessageWriter <?>> writer = this .partWriters .stream ()
@@ -189,26 +193,6 @@ private <T> Flux<DataBuffer> encodePart(byte[] boundary, String name, T value) {
189
193
);
190
194
}
191
195
192
- /**
193
- * Return the filename of the given multipart part. This value will be used
194
- * for the {@code Content-Disposition} header.
195
- * <p>The default implementation returns {@link Resource#getFilename()} if
196
- * the part is a {@code Resource}, and {@code null} in other cases.
197
- * @param part the part for which return a file name
198
- * @return the filename or {@code null}
199
- */
200
- protected String getFilename (Object part ) {
201
- if (part instanceof Resource ) {
202
- Resource resource = (Resource ) part ;
203
- String filename = resource .getFilename ();
204
- filename = MimeDelegate .encode (filename , this .filenameCharset .name ());
205
- return filename ;
206
- }
207
- else {
208
- return null ;
209
- }
210
- }
211
-
212
196
private DataBuffer generateBoundaryLine (byte [] boundary ) {
213
197
DataBuffer buffer = this .bufferFactory .allocateBuffer (boundary .length + 4 );
214
198
buffer .write ((byte )'-' );
@@ -243,15 +227,18 @@ private static class MultipartHttpOutputMessage implements ReactiveHttpOutputMes
243
227
244
228
private final DataBufferFactory bufferFactory ;
245
229
230
+ private final Charset charset ;
231
+
246
232
private final HttpHeaders headers = new HttpHeaders ();
247
233
248
234
private final AtomicBoolean commited = new AtomicBoolean ();
249
235
250
236
private Flux <DataBuffer > body ;
251
237
252
238
253
- public MultipartHttpOutputMessage (DataBufferFactory bufferFactory ) {
239
+ public MultipartHttpOutputMessage (DataBufferFactory bufferFactory , Charset charset ) {
254
240
this .bufferFactory = bufferFactory ;
241
+ this .charset = charset ;
255
242
}
256
243
257
244
@@ -287,9 +274,9 @@ public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
287
274
private DataBuffer generateHeaders () {
288
275
DataBuffer buffer = this .bufferFactory .allocateBuffer ();
289
276
for (Map .Entry <String , List <String >> entry : headers .entrySet ()) {
290
- byte [] headerName = entry .getKey ().getBytes (StandardCharsets . US_ASCII );
277
+ byte [] headerName = entry .getKey ().getBytes (this . charset );
291
278
for (String headerValueString : entry .getValue ()) {
292
- byte [] headerValue = headerValueString .getBytes (StandardCharsets . US_ASCII );
279
+ byte [] headerValue = headerValueString .getBytes (this . charset );
293
280
buffer .write (headerName );
294
281
buffer .write ((byte )':' );
295
282
buffer .write ((byte )' ' );
@@ -321,19 +308,4 @@ public Mono<Void> setComplete() {
321
308
322
309
}
323
310
324
- /**
325
- * Inner class to avoid a hard dependency on the JavaMail API.
326
- */
327
- private static class MimeDelegate {
328
-
329
- public static String encode (String value , String charset ) {
330
- try {
331
- return MimeUtility .encodeText (value , charset , null );
332
- }
333
- catch (UnsupportedEncodingException ex ) {
334
- throw new IllegalStateException (ex );
335
- }
336
- }
337
- }
338
-
339
311
}
0 commit comments