@ -16,18 +16,17 @@
package org.springframework.http.codec.multipart ;
package org.springframework.http.codec.multipart ;
import java.io.UnsupportedEncodingException ;
import java.nio.charset.Charset ;
import java.nio.charset.Charset ;
import java.nio.charset.StandardCharsets ;
import java.nio.charset.StandardCharsets ;
import java.util.Arrays ;
import java.util.Arrays ;
import java.util.Collections ;
import java.util.Collections ;
import java.util.HashMap ;
import java.util.List ;
import java.util.List ;
import java.util.Map ;
import java.util.Map ;
import java.util.Optional ;
import java.util.Optional ;
import java.util.concurrent.atomic.AtomicBoolean ;
import java.util.concurrent.atomic.AtomicBoolean ;
import java.util.function.Supplier ;
import java.util.function.Supplier ;
import java.util.stream.Collectors ;
import java.util.stream.Collectors ;
import javax.mail.internet.MimeUtility ;
import org.reactivestreams.Publisher ;
import org.reactivestreams.Publisher ;
import reactor.core.publisher.Flux ;
import reactor.core.publisher.Flux ;
@ -69,7 +68,7 @@ public class MultipartHttpMessageWriter implements HttpMessageWriter<MultiValueM
private final List < HttpMessageWriter < ? > > partWriters ;
private final List < HttpMessageWriter < ? > > partWriters ;
private Charset filenameC harset = DEFAULT_CHARSET ;
private Charset c harset = DEFAULT_CHARSET ;
private final DataBufferFactory bufferFactory = new DefaultDataBufferFactory ( ) ;
private final DataBufferFactory bufferFactory = new DefaultDataBufferFactory ( ) ;
@ -86,19 +85,20 @@ public class MultipartHttpMessageWriter implements HttpMessageWriter<MultiValueM
}
}
/ * *
/ * *
* Set the character set to use for writing file names in the multipart request .
* Set the character set to use for part headers such as
* "Content-Disposition" ( and its filename parameter ) .
* < p > By default this is set to "UTF-8" .
* < p > By default this is set to "UTF-8" .
* /
* /
public void setFilename Charset ( Charset charset ) {
public void setCharset ( Charset charset ) {
Assert . notNull ( charset , "'charset' must not be null" ) ;
Assert . notNull ( charset , "'charset' must not be null" ) ;
this . filenameC harset = charset ;
this . c harset = charset ;
}
}
/ * *
/ * *
* Return the configured filename charset .
* Return the configured charset for part headers .
* /
* /
public Charset getFilename Charset ( ) {
public Charset getCharset ( ) {
return this . filenameC harset;
return this . c harset;
}
}
@ -120,8 +120,10 @@ public class MultipartHttpMessageWriter implements HttpMessageWriter<MultiValueM
byte [ ] boundary = generateMultipartBoundary ( ) ;
byte [ ] boundary = generateMultipartBoundary ( ) ;
outputMessage . getHeaders ( ) . setContentType ( new MediaType ( "multipart" , "form-data" ,
Map < String , String > params = new HashMap < > ( 2 ) ;
Collections . singletonMap ( "boundary" , new String ( boundary , StandardCharsets . US_ASCII ) ) ) ) ;
params . put ( "boundary" , new String ( boundary , StandardCharsets . US_ASCII ) ) ;
params . put ( "charset" , getCharset ( ) . name ( ) ) ;
outputMessage . getHeaders ( ) . setContentType ( new MediaType ( MediaType . MULTIPART_FORM_DATA , params ) ) ;
return Mono . from ( inputStream ) . flatMap ( map - > {
return Mono . from ( inputStream ) . flatMap ( map - > {
@ -149,7 +151,8 @@ public class MultipartHttpMessageWriter implements HttpMessageWriter<MultiValueM
@SuppressWarnings ( "unchecked" )
@SuppressWarnings ( "unchecked" )
private < T > Flux < DataBuffer > encodePart ( byte [ ] boundary , String name , T value ) {
private < T > Flux < DataBuffer > encodePart ( byte [ ] boundary , String name , T value ) {
MultipartHttpOutputMessage outputMessage = new MultipartHttpOutputMessage ( this . bufferFactory ) ;
MultipartHttpOutputMessage outputMessage =
new MultipartHttpOutputMessage ( this . bufferFactory , getCharset ( ) ) ;
T body ;
T body ;
if ( value instanceof HttpEntity ) {
if ( value instanceof HttpEntity ) {
@ -160,9 +163,10 @@ public class MultipartHttpMessageWriter implements HttpMessageWriter<MultiValueM
body = value ;
body = value ;
}
}
ResolvableType bodyType = ResolvableType . forClass ( body . getClass ( ) ) ;
String filename = ( body instanceof Resource ? ( ( Resource ) body ) . getFilename ( ) : null ) ;
outputMessage . getHeaders ( ) . setContentDispositionFormData ( name , getFilename ( body ) ) ;
outputMessage . getHeaders ( ) . setContentDispositionFormData ( name , filename ) ;
ResolvableType bodyType = ResolvableType . forClass ( body . getClass ( ) ) ;
MediaType contentType = outputMessage . getHeaders ( ) . getContentType ( ) ;
MediaType contentType = outputMessage . getHeaders ( ) . getContentType ( ) ;
Optional < HttpMessageWriter < ? > > writer = this . partWriters . stream ( )
Optional < HttpMessageWriter < ? > > writer = this . partWriters . stream ( )
@ -189,26 +193,6 @@ public class MultipartHttpMessageWriter implements HttpMessageWriter<MultiValueM
) ;
) ;
}
}
/ * *
* Return the filename of the given multipart part . This value will be used
* for the { @code Content - Disposition } header .
* < p > The default implementation returns { @link Resource # getFilename ( ) } if
* the part is a { @code Resource } , and { @code null } in other cases .
* @param part the part for which return a file name
* @return the filename or { @code null }
* /
protected String getFilename ( Object part ) {
if ( part instanceof Resource ) {
Resource resource = ( Resource ) part ;
String filename = resource . getFilename ( ) ;
filename = MimeDelegate . encode ( filename , this . filenameCharset . name ( ) ) ;
return filename ;
}
else {
return null ;
}
}
private DataBuffer generateBoundaryLine ( byte [ ] boundary ) {
private DataBuffer generateBoundaryLine ( byte [ ] boundary ) {
DataBuffer buffer = this . bufferFactory . allocateBuffer ( boundary . length + 4 ) ;
DataBuffer buffer = this . bufferFactory . allocateBuffer ( boundary . length + 4 ) ;
buffer . write ( ( byte ) '-' ) ;
buffer . write ( ( byte ) '-' ) ;
@ -243,6 +227,8 @@ public class MultipartHttpMessageWriter implements HttpMessageWriter<MultiValueM
private final DataBufferFactory bufferFactory ;
private final DataBufferFactory bufferFactory ;
private final Charset charset ;
private final HttpHeaders headers = new HttpHeaders ( ) ;
private final HttpHeaders headers = new HttpHeaders ( ) ;
private final AtomicBoolean commited = new AtomicBoolean ( ) ;
private final AtomicBoolean commited = new AtomicBoolean ( ) ;
@ -250,8 +236,9 @@ public class MultipartHttpMessageWriter implements HttpMessageWriter<MultiValueM
private Flux < DataBuffer > body ;
private Flux < DataBuffer > body ;
public MultipartHttpOutputMessage ( DataBufferFactory bufferFactory ) {
public MultipartHttpOutputMessage ( DataBufferFactory bufferFactory , Charset charset ) {
this . bufferFactory = bufferFactory ;
this . bufferFactory = bufferFactory ;
this . charset = charset ;
}
}
@ -287,9 +274,9 @@ public class MultipartHttpMessageWriter implements HttpMessageWriter<MultiValueM
private DataBuffer generateHeaders ( ) {
private DataBuffer generateHeaders ( ) {
DataBuffer buffer = this . bufferFactory . allocateBuffer ( ) ;
DataBuffer buffer = this . bufferFactory . allocateBuffer ( ) ;
for ( Map . Entry < String , List < String > > entry : headers . entrySet ( ) ) {
for ( Map . Entry < String , List < String > > entry : headers . entrySet ( ) ) {
byte [ ] headerName = entry . getKey ( ) . getBytes ( StandardCharsets . US_ASCII ) ;
byte [ ] headerName = entry . getKey ( ) . getBytes ( this . charset ) ;
for ( String headerValueString : entry . getValue ( ) ) {
for ( String headerValueString : entry . getValue ( ) ) {
byte [ ] headerValue = headerValueString . getBytes ( StandardCharsets . US_ASCII ) ;
byte [ ] headerValue = headerValueString . getBytes ( this . charset ) ;
buffer . write ( headerName ) ;
buffer . write ( headerName ) ;
buffer . write ( ( byte ) ':' ) ;
buffer . write ( ( byte ) ':' ) ;
buffer . write ( ( byte ) ' ' ) ;
buffer . write ( ( byte ) ' ' ) ;
@ -321,19 +308,4 @@ public class MultipartHttpMessageWriter implements HttpMessageWriter<MultiValueM
}
}
/ * *
* Inner class to avoid a hard dependency on the JavaMail API .
* /
private static class MimeDelegate {
public static String encode ( String value , String charset ) {
try {
return MimeUtility . encodeText ( value , charset , null ) ;
}
catch ( UnsupportedEncodingException ex ) {
throw new IllegalStateException ( ex ) ;
}
}
}
}
}