Skip to content

Commit 2233ec0

Browse files
committed
StandardMultipartFile.transferTo falls back to manual copy
Issue: SPR-15257 (cherry picked from commit b73153c)
1 parent e9ff3bb commit 2233ec0

File tree

3 files changed

+37
-17
lines changed

3 files changed

+37
-17
lines changed

spring-web/src/main/java/org/springframework/web/multipart/MultipartFile.java

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2016 the original author or authors.
2+
* Copyright 2002-2017 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -27,7 +27,7 @@
2727
*
2828
* <p>The file contents are either stored in memory or temporarily on disk.
2929
* In either case, the user is responsible for copying file contents to a
30-
* session-level or persistent store as and if desired. The temporary storages
30+
* session-level or persistent store as and if desired. The temporary storage
3131
* will be cleared at the end of request processing.
3232
*
3333
* @author Juergen Hoeller
@@ -50,6 +50,8 @@ public interface MultipartFile extends InputStreamSource {
5050
* but it typically will not with any other than Opera.
5151
* @return the original filename, or the empty String if no file has been chosen
5252
* in the multipart form, or {@code null} if not defined or not available
53+
* @see org.apache.commons.fileupload.FileItem#getName()
54+
* @see org.springframework.web.multipart.commons.CommonsMultipartFile#setPreserveFilename
5355
*/
5456
String getOriginalFilename();
5557

@@ -81,7 +83,7 @@ public interface MultipartFile extends InputStreamSource {
8183

8284
/**
8385
* Return an InputStream to read the contents of the file from.
84-
* The user is responsible for closing the stream.
86+
* <p>The user is responsible for closing the returned stream.
8587
* @return the contents of the file as stream, or an empty stream if empty
8688
* @throws IOException in case of access errors (if the temporary store fails)
8789
*/
@@ -91,18 +93,22 @@ public interface MultipartFile extends InputStreamSource {
9193
/**
9294
* Transfer the received file to the given destination file.
9395
* <p>This may either move the file in the filesystem, copy the file in the
94-
* filesystem, or save memory-held contents to the destination file.
95-
* If the destination file already exists, it will be deleted first.
96-
* <p>If the file has been moved in the filesystem, this operation cannot
97-
* be invoked again. Therefore, call this method just once to be able to
98-
* work with any storage mechanism.
99-
* <p><strong>Note:</strong> when using Servlet 3.0 multipart support you
100-
* need to configure the location relative to which files will be copied
101-
* as explained in {@link javax.servlet.http.Part#write}.
102-
* @param dest the destination file
96+
* filesystem, or save memory-held contents to the destination file. If the
97+
* destination file already exists, it will be deleted first.
98+
* <p>If the target file has been moved in the filesystem, this operation
99+
* cannot be invoked again afterwards. Therefore, call this method just once
100+
* in order to work with any storage mechanism.
101+
* <p><b>NOTE:</b> Depending on the underlying provider, temporary storage
102+
* may be container-dependent, including the base directory for relative
103+
* destinations specified here (e.g. with Servlet 3.0 multipart handling).
104+
* For absolute destinations, the target file may get renamed/moved from its
105+
* temporary location or newly copied, even if a temporary copy already exists.
106+
* @param dest the destination file (typically absolute)
103107
* @throws IOException in case of reading or writing errors
104108
* @throws IllegalStateException if the file has already been moved
105109
* in the filesystem and is not available anymore for another transfer
110+
* @see org.apache.commons.fileupload.FileItem#write(File)
111+
* @see javax.servlet.http.Part#write(String)
106112
*/
107113
void transferTo(File dest) throws IOException, IllegalStateException;
108114

spring-web/src/main/java/org/springframework/web/multipart/commons/CommonsMultipartFile.java

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2016 the original author or authors.
2+
* Copyright 2002-2017 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -165,22 +165,26 @@ public void transferTo(File dest) throws IOException, IllegalStateException {
165165
if (logger.isDebugEnabled()) {
166166
String action = "transferred";
167167
if (!this.fileItem.isInMemory()) {
168-
action = isAvailable() ? "copied" : "moved";
168+
action = (isAvailable() ? "copied" : "moved");
169169
}
170170
logger.debug("Multipart file '" + getName() + "' with original filename [" +
171171
getOriginalFilename() + "], stored " + getStorageDescription() + ": " +
172172
action + " to [" + dest.getAbsolutePath() + "]");
173173
}
174174
}
175175
catch (FileUploadException ex) {
176-
throw new IllegalStateException(ex.getMessage());
176+
throw new IllegalStateException(ex.getMessage(), ex);
177+
}
178+
catch (IllegalStateException ex) {
179+
// Pass through when coming from FileItem directly
180+
throw ex;
177181
}
178182
catch (IOException ex) {
183+
// From I/O operations within FileItem.write
179184
throw ex;
180185
}
181186
catch (Exception ex) {
182-
logger.error("Could not transfer to file", ex);
183-
throw new IOException("Could not transfer to file: " + ex.getMessage());
187+
throw new IOException("File transfer failed", ex);
184188
}
185189
}
186190

spring-web/src/main/java/org/springframework/web/multipart/support/StandardMultipartHttpServletRequest.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package org.springframework.web.multipart.support;
1818

1919
import java.io.File;
20+
import java.io.FileOutputStream;
2021
import java.io.IOException;
2122
import java.io.InputStream;
2223
import java.io.Serializable;
@@ -299,6 +300,15 @@ public InputStream getInputStream() throws IOException {
299300
@Override
300301
public void transferTo(File dest) throws IOException, IllegalStateException {
301302
this.part.write(dest.getPath());
303+
if (dest.isAbsolute() && !dest.exists()) {
304+
// Servlet 3.0 Part.write is not guaranteed to support absolute file paths:
305+
// may translate the given path to a relative location within a temp dir
306+
// (e.g. on Jetty whereas Tomcat and Undertow detect absolute paths).
307+
// At least we offloaded the file from memory storage; it'll get deleted
308+
// from the temp dir eventually in any case. And for our user's purposes,
309+
// we can manually copy it to the requested location as a fallback.
310+
FileCopyUtils.copy(this.part.getInputStream(), new FileOutputStream(dest));
311+
}
302312
}
303313
}
304314

0 commit comments

Comments
 (0)