From b73153cd7c88acf89efcc48500af8f971d6cac83 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Fri, 24 Mar 2017 11:06:39 +0100 Subject: [PATCH] StandardMultipartFile.transferTo falls back to manual copy Issue: SPR-15257 --- .../web/multipart/MultipartFile.java | 30 +++++++++++-------- .../commons/CommonsMultipartFile.java | 14 +++++---- .../StandardMultipartHttpServletRequest.java | 10 +++++++ 3 files changed, 37 insertions(+), 17 deletions(-) diff --git a/spring-web/src/main/java/org/springframework/web/multipart/MultipartFile.java b/spring-web/src/main/java/org/springframework/web/multipart/MultipartFile.java index f5674104c1..5d27f350f9 100644 --- a/spring-web/src/main/java/org/springframework/web/multipart/MultipartFile.java +++ b/spring-web/src/main/java/org/springframework/web/multipart/MultipartFile.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,7 +27,7 @@ import org.springframework.core.io.InputStreamSource; * *

The file contents are either stored in memory or temporarily on disk. * In either case, the user is responsible for copying file contents to a - * session-level or persistent store as and if desired. The temporary storages + * session-level or persistent store as and if desired. The temporary storage * will be cleared at the end of request processing. * * @author Juergen Hoeller @@ -50,6 +50,8 @@ public interface MultipartFile extends InputStreamSource { * but it typically will not with any other than Opera. * @return the original filename, or the empty String if no file has been chosen * in the multipart form, or {@code null} if not defined or not available + * @see org.apache.commons.fileupload.FileItem#getName() + * @see org.springframework.web.multipart.commons.CommonsMultipartFile#setPreserveFilename */ String getOriginalFilename(); @@ -81,7 +83,7 @@ public interface MultipartFile extends InputStreamSource { /** * Return an InputStream to read the contents of the file from. - * The user is responsible for closing the stream. + *

The user is responsible for closing the returned stream. * @return the contents of the file as stream, or an empty stream if empty * @throws IOException in case of access errors (if the temporary store fails) */ @@ -91,18 +93,22 @@ public interface MultipartFile extends InputStreamSource { /** * Transfer the received file to the given destination file. *

This may either move the file in the filesystem, copy the file in the - * filesystem, or save memory-held contents to the destination file. - * If the destination file already exists, it will be deleted first. - *

If the file has been moved in the filesystem, this operation cannot - * be invoked again. Therefore, call this method just once to be able to - * work with any storage mechanism. - *

Note: when using Servlet 3.0 multipart support you - * need to configure the location relative to which files will be copied - * as explained in {@link javax.servlet.http.Part#write}. - * @param dest the destination file + * filesystem, or save memory-held contents to the destination file. If the + * destination file already exists, it will be deleted first. + *

If the target file has been moved in the filesystem, this operation + * cannot be invoked again afterwards. Therefore, call this method just once + * in order to work with any storage mechanism. + *

NOTE: Depending on the underlying provider, temporary storage + * may be container-dependent, including the base directory for relative + * destinations specified here (e.g. with Servlet 3.0 multipart handling). + * For absolute destinations, the target file may get renamed/moved from its + * temporary location or newly copied, even if a temporary copy already exists. + * @param dest the destination file (typically absolute) * @throws IOException in case of reading or writing errors * @throws IllegalStateException if the file has already been moved * in the filesystem and is not available anymore for another transfer + * @see org.apache.commons.fileupload.FileItem#write(File) + * @see javax.servlet.http.Part#write(String) */ void transferTo(File dest) throws IOException, IllegalStateException; diff --git a/spring-web/src/main/java/org/springframework/web/multipart/commons/CommonsMultipartFile.java b/spring-web/src/main/java/org/springframework/web/multipart/commons/CommonsMultipartFile.java index 0915b07d9d..3d0b3f538f 100644 --- a/spring-web/src/main/java/org/springframework/web/multipart/commons/CommonsMultipartFile.java +++ b/spring-web/src/main/java/org/springframework/web/multipart/commons/CommonsMultipartFile.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -165,7 +165,7 @@ public class CommonsMultipartFile implements MultipartFile, Serializable { if (logger.isDebugEnabled()) { String action = "transferred"; if (!this.fileItem.isInMemory()) { - action = isAvailable() ? "copied" : "moved"; + action = (isAvailable() ? "copied" : "moved"); } logger.debug("Multipart file '" + getName() + "' with original filename [" + getOriginalFilename() + "], stored " + getStorageDescription() + ": " + @@ -173,14 +173,18 @@ public class CommonsMultipartFile implements MultipartFile, Serializable { } } catch (FileUploadException ex) { - throw new IllegalStateException(ex.getMessage()); + throw new IllegalStateException(ex.getMessage(), ex); + } + catch (IllegalStateException ex) { + // Pass through when coming from FileItem directly + throw ex; } catch (IOException ex) { + // From I/O operations within FileItem.write throw ex; } catch (Exception ex) { - logger.error("Could not transfer to file", ex); - throw new IOException("Could not transfer to file: " + ex.getMessage()); + throw new IOException("File transfer failed", ex); } } diff --git a/spring-web/src/main/java/org/springframework/web/multipart/support/StandardMultipartHttpServletRequest.java b/spring-web/src/main/java/org/springframework/web/multipart/support/StandardMultipartHttpServletRequest.java index e696d11788..c631932cbc 100644 --- a/spring-web/src/main/java/org/springframework/web/multipart/support/StandardMultipartHttpServletRequest.java +++ b/spring-web/src/main/java/org/springframework/web/multipart/support/StandardMultipartHttpServletRequest.java @@ -17,6 +17,7 @@ package org.springframework.web.multipart.support; import java.io.File; +import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.Serializable; @@ -307,6 +308,15 @@ public class StandardMultipartHttpServletRequest extends AbstractMultipartHttpSe @Override public void transferTo(File dest) throws IOException, IllegalStateException { this.part.write(dest.getPath()); + if (dest.isAbsolute() && !dest.exists()) { + // Servlet 3.0 Part.write is not guaranteed to support absolute file paths: + // may translate the given path to a relative location within a temp dir + // (e.g. on Jetty whereas Tomcat and Undertow detect absolute paths). + // At least we offloaded the file from memory storage; it'll get deleted + // from the temp dir eventually in any case. And for our user's purposes, + // we can manually copy it to the requested location as a fallback. + FileCopyUtils.copy(this.part.getInputStream(), new FileOutputStream(dest)); + } } } -- GitLab