001/* 002 * Copyright 2002-2017 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.web.multipart.commons; 018 019import java.io.File; 020import java.io.IOException; 021import java.io.InputStream; 022import java.io.Serializable; 023 024import org.apache.commons.fileupload.FileItem; 025import org.apache.commons.fileupload.FileUploadException; 026import org.apache.commons.fileupload.disk.DiskFileItem; 027import org.apache.commons.logging.Log; 028import org.apache.commons.logging.LogFactory; 029 030import org.springframework.util.StreamUtils; 031import org.springframework.web.multipart.MultipartFile; 032 033/** 034 * {@link MultipartFile} implementation for Apache Commons FileUpload. 035 * 036 * @author Trevor D. Cook 037 * @author Juergen Hoeller 038 * @since 29.09.2003 039 * @see CommonsMultipartResolver 040 */ 041@SuppressWarnings("serial") 042public class CommonsMultipartFile implements MultipartFile, Serializable { 043 044 protected static final Log logger = LogFactory.getLog(CommonsMultipartFile.class); 045 046 private final FileItem fileItem; 047 048 private final long size; 049 050 private boolean preserveFilename = false; 051 052 053 /** 054 * Create an instance wrapping the given FileItem. 055 * @param fileItem the FileItem to wrap 056 */ 057 public CommonsMultipartFile(FileItem fileItem) { 058 this.fileItem = fileItem; 059 this.size = this.fileItem.getSize(); 060 } 061 062 063 /** 064 * Return the underlying {@code org.apache.commons.fileupload.FileItem} 065 * instance. There is hardly any need to access this. 066 */ 067 public final FileItem getFileItem() { 068 return this.fileItem; 069 } 070 071 /** 072 * Set whether to preserve the filename as sent by the client, not stripping off 073 * path information in {@link CommonsMultipartFile#getOriginalFilename()}. 074 * <p>Default is "false", stripping off path information that may prefix the 075 * actual filename e.g. from Opera. Switch this to "true" for preserving the 076 * client-specified filename as-is, including potential path separators. 077 * @since 4.3.5 078 * @see #getOriginalFilename() 079 * @see CommonsMultipartResolver#setPreserveFilename(boolean) 080 */ 081 public void setPreserveFilename(boolean preserveFilename) { 082 this.preserveFilename = preserveFilename; 083 } 084 085 086 @Override 087 public String getName() { 088 return this.fileItem.getFieldName(); 089 } 090 091 @Override 092 public String getOriginalFilename() { 093 String filename = this.fileItem.getName(); 094 if (filename == null) { 095 // Should never happen. 096 return ""; 097 } 098 if (this.preserveFilename) { 099 // Do not try to strip off a path... 100 return filename; 101 } 102 103 // Check for Unix-style path 104 int unixSep = filename.lastIndexOf('/'); 105 // Check for Windows-style path 106 int winSep = filename.lastIndexOf('\\'); 107 // Cut off at latest possible point 108 int pos = (winSep > unixSep ? winSep : unixSep); 109 if (pos != -1) { 110 // Any sort of path separator found... 111 return filename.substring(pos + 1); 112 } 113 else { 114 // A plain name 115 return filename; 116 } 117 } 118 119 @Override 120 public String getContentType() { 121 return this.fileItem.getContentType(); 122 } 123 124 @Override 125 public boolean isEmpty() { 126 return (this.size == 0); 127 } 128 129 @Override 130 public long getSize() { 131 return this.size; 132 } 133 134 @Override 135 public byte[] getBytes() { 136 if (!isAvailable()) { 137 throw new IllegalStateException("File has been moved - cannot be read again"); 138 } 139 byte[] bytes = this.fileItem.get(); 140 return (bytes != null ? bytes : new byte[0]); 141 } 142 143 @Override 144 public InputStream getInputStream() throws IOException { 145 if (!isAvailable()) { 146 throw new IllegalStateException("File has been moved - cannot be read again"); 147 } 148 InputStream inputStream = this.fileItem.getInputStream(); 149 return (inputStream != null ? inputStream : StreamUtils.emptyInput()); 150 } 151 152 @Override 153 public void transferTo(File dest) throws IOException, IllegalStateException { 154 if (!isAvailable()) { 155 throw new IllegalStateException("File has already been moved - cannot be transferred again"); 156 } 157 158 if (dest.exists() && !dest.delete()) { 159 throw new IOException( 160 "Destination file [" + dest.getAbsolutePath() + "] already exists and could not be deleted"); 161 } 162 163 try { 164 this.fileItem.write(dest); 165 if (logger.isDebugEnabled()) { 166 String action = "transferred"; 167 if (!this.fileItem.isInMemory()) { 168 action = (isAvailable() ? "copied" : "moved"); 169 } 170 logger.debug("Multipart file '" + getName() + "' with original filename [" + 171 getOriginalFilename() + "], stored " + getStorageDescription() + ": " + 172 action + " to [" + dest.getAbsolutePath() + "]"); 173 } 174 } 175 catch (FileUploadException ex) { 176 throw new IllegalStateException(ex.getMessage(), ex); 177 } 178 catch (IllegalStateException ex) { 179 // Pass through when coming from FileItem directly 180 throw ex; 181 } 182 catch (IOException ex) { 183 // From I/O operations within FileItem.write 184 throw ex; 185 } 186 catch (Exception ex) { 187 throw new IOException("File transfer failed", ex); 188 } 189 } 190 191 /** 192 * Determine whether the multipart content is still available. 193 * If a temporary file has been moved, the content is no longer available. 194 */ 195 protected boolean isAvailable() { 196 // If in memory, it's available. 197 if (this.fileItem.isInMemory()) { 198 return true; 199 } 200 // Check actual existence of temporary file. 201 if (this.fileItem instanceof DiskFileItem) { 202 return ((DiskFileItem) this.fileItem).getStoreLocation().exists(); 203 } 204 // Check whether current file size is different than original one. 205 return (this.fileItem.getSize() == this.size); 206 } 207 208 /** 209 * Return a description for the storage location of the multipart content. 210 * Tries to be as specific as possible: mentions the file location in case 211 * of a temporary file. 212 */ 213 public String getStorageDescription() { 214 if (this.fileItem.isInMemory()) { 215 return "in memory"; 216 } 217 else if (this.fileItem instanceof DiskFileItem) { 218 return "at [" + ((DiskFileItem) this.fileItem).getStoreLocation().getAbsolutePath() + "]"; 219 } 220 else { 221 return "on disk"; 222 } 223 } 224 225}