001/* 002 * Copyright 2002-2018 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.support; 018 019import java.io.File; 020import java.io.FileOutputStream; 021import java.io.IOException; 022import java.io.InputStream; 023import java.io.Serializable; 024import java.nio.charset.Charset; 025import java.util.ArrayList; 026import java.util.Collection; 027import java.util.Collections; 028import java.util.Enumeration; 029import java.util.LinkedHashMap; 030import java.util.LinkedHashSet; 031import java.util.Map; 032import java.util.Set; 033import javax.servlet.http.HttpServletRequest; 034import javax.servlet.http.Part; 035 036import org.springframework.http.HttpHeaders; 037import org.springframework.util.FileCopyUtils; 038import org.springframework.util.LinkedMultiValueMap; 039import org.springframework.util.MultiValueMap; 040import org.springframework.web.multipart.MultipartException; 041import org.springframework.web.multipart.MultipartFile; 042 043/** 044 * Spring MultipartHttpServletRequest adapter, wrapping a Servlet 3.0 HttpServletRequest 045 * and its Part objects. Parameters get exposed through the native request's getParameter 046 * methods - without any custom processing on our side. 047 * 048 * @author Juergen Hoeller 049 * @author Rossen Stoyanchev 050 * @since 3.1 051 * @see StandardServletMultipartResolver 052 */ 053public class StandardMultipartHttpServletRequest extends AbstractMultipartHttpServletRequest { 054 055 private static final String CONTENT_DISPOSITION = "content-disposition"; 056 057 private static final String FILENAME_KEY = "filename="; 058 059 private static final String FILENAME_WITH_CHARSET_KEY = "filename*="; 060 061 private static final Charset US_ASCII = Charset.forName("us-ascii"); 062 063 064 private Set<String> multipartParameterNames; 065 066 067 /** 068 * Create a new StandardMultipartHttpServletRequest wrapper for the given request, 069 * immediately parsing the multipart content. 070 * @param request the servlet request to wrap 071 * @throws MultipartException if parsing failed 072 */ 073 public StandardMultipartHttpServletRequest(HttpServletRequest request) throws MultipartException { 074 this(request, false); 075 } 076 077 /** 078 * Create a new StandardMultipartHttpServletRequest wrapper for the given request. 079 * @param request the servlet request to wrap 080 * @param lazyParsing whether multipart parsing should be triggered lazily on 081 * first access of multipart files or parameters 082 * @throws MultipartException if an immediate parsing attempt failed 083 * @since 3.2.9 084 */ 085 public StandardMultipartHttpServletRequest(HttpServletRequest request, boolean lazyParsing) 086 throws MultipartException { 087 088 super(request); 089 if (!lazyParsing) { 090 parseRequest(request); 091 } 092 } 093 094 095 private void parseRequest(HttpServletRequest request) { 096 try { 097 Collection<Part> parts = request.getParts(); 098 this.multipartParameterNames = new LinkedHashSet<String>(parts.size()); 099 MultiValueMap<String, MultipartFile> files = new LinkedMultiValueMap<String, MultipartFile>(parts.size()); 100 for (Part part : parts) { 101 String disposition = part.getHeader(CONTENT_DISPOSITION); 102 String filename = extractFilename(disposition); 103 if (filename == null) { 104 filename = extractFilenameWithCharset(disposition); 105 } 106 if (filename != null) { 107 files.add(part.getName(), new StandardMultipartFile(part, filename)); 108 } 109 else { 110 this.multipartParameterNames.add(part.getName()); 111 } 112 } 113 setMultipartFiles(files); 114 } 115 catch (Throwable ex) { 116 throw new MultipartException("Could not parse multipart servlet request", ex); 117 } 118 } 119 120 private String extractFilename(String contentDisposition, String key) { 121 if (contentDisposition == null) { 122 return null; 123 } 124 int startIndex = contentDisposition.indexOf(key); 125 if (startIndex == -1) { 126 return null; 127 } 128 String filename = contentDisposition.substring(startIndex + key.length()); 129 if (filename.startsWith("\"")) { 130 int endIndex = filename.indexOf("\"", 1); 131 if (endIndex != -1) { 132 return filename.substring(1, endIndex); 133 } 134 } 135 else { 136 int endIndex = filename.indexOf(";"); 137 if (endIndex != -1) { 138 return filename.substring(0, endIndex); 139 } 140 } 141 return filename; 142 } 143 144 private String extractFilename(String contentDisposition) { 145 return extractFilename(contentDisposition, FILENAME_KEY); 146 } 147 148 private String extractFilenameWithCharset(String contentDisposition) { 149 String filename = extractFilename(contentDisposition, FILENAME_WITH_CHARSET_KEY); 150 if (filename == null) { 151 return null; 152 } 153 int index = filename.indexOf("'"); 154 if (index != -1) { 155 Charset charset = null; 156 try { 157 charset = Charset.forName(filename.substring(0, index)); 158 } 159 catch (IllegalArgumentException ex) { 160 // ignore 161 } 162 filename = filename.substring(index + 1); 163 // Skip language information.. 164 index = filename.indexOf("'"); 165 if (index != -1) { 166 filename = filename.substring(index + 1); 167 } 168 if (charset != null) { 169 filename = new String(filename.getBytes(US_ASCII), charset); 170 } 171 } 172 return filename; 173 } 174 175 176 @Override 177 protected void initializeMultipart() { 178 parseRequest(getRequest()); 179 } 180 181 @Override 182 public Enumeration<String> getParameterNames() { 183 if (this.multipartParameterNames == null) { 184 initializeMultipart(); 185 } 186 if (this.multipartParameterNames.isEmpty()) { 187 return super.getParameterNames(); 188 } 189 190 // Servlet 3.0 getParameterNames() not guaranteed to include multipart form items 191 // (e.g. on WebLogic 12) -> need to merge them here to be on the safe side 192 Set<String> paramNames = new LinkedHashSet<String>(); 193 Enumeration<String> paramEnum = super.getParameterNames(); 194 while (paramEnum.hasMoreElements()) { 195 paramNames.add(paramEnum.nextElement()); 196 } 197 paramNames.addAll(this.multipartParameterNames); 198 return Collections.enumeration(paramNames); 199 } 200 201 @Override 202 public Map<String, String[]> getParameterMap() { 203 if (this.multipartParameterNames == null) { 204 initializeMultipart(); 205 } 206 if (this.multipartParameterNames.isEmpty()) { 207 return super.getParameterMap(); 208 } 209 210 // Servlet 3.0 getParameterMap() not guaranteed to include multipart form items 211 // (e.g. on WebLogic 12) -> need to merge them here to be on the safe side 212 Map<String, String[]> paramMap = new LinkedHashMap<String, String[]>(); 213 paramMap.putAll(super.getParameterMap()); 214 for (String paramName : this.multipartParameterNames) { 215 if (!paramMap.containsKey(paramName)) { 216 paramMap.put(paramName, getParameterValues(paramName)); 217 } 218 } 219 return paramMap; 220 } 221 222 @Override 223 public String getMultipartContentType(String paramOrFileName) { 224 try { 225 Part part = getPart(paramOrFileName); 226 return (part != null ? part.getContentType() : null); 227 } 228 catch (Throwable ex) { 229 throw new MultipartException("Could not access multipart servlet request", ex); 230 } 231 } 232 233 @Override 234 public HttpHeaders getMultipartHeaders(String paramOrFileName) { 235 try { 236 Part part = getPart(paramOrFileName); 237 if (part != null) { 238 HttpHeaders headers = new HttpHeaders(); 239 for (String headerName : part.getHeaderNames()) { 240 headers.put(headerName, new ArrayList<String>(part.getHeaders(headerName))); 241 } 242 return headers; 243 } 244 else { 245 return null; 246 } 247 } 248 catch (Throwable ex) { 249 throw new MultipartException("Could not access multipart servlet request", ex); 250 } 251 } 252 253 254 /** 255 * Spring MultipartFile adapter, wrapping a Servlet 3.0 Part object. 256 */ 257 @SuppressWarnings("serial") 258 private static class StandardMultipartFile implements MultipartFile, Serializable { 259 260 private final Part part; 261 262 private final String filename; 263 264 public StandardMultipartFile(Part part, String filename) { 265 this.part = part; 266 this.filename = filename; 267 } 268 269 @Override 270 public String getName() { 271 return this.part.getName(); 272 } 273 274 @Override 275 public String getOriginalFilename() { 276 return this.filename; 277 } 278 279 @Override 280 public String getContentType() { 281 return this.part.getContentType(); 282 } 283 284 @Override 285 public boolean isEmpty() { 286 return (this.part.getSize() == 0); 287 } 288 289 @Override 290 public long getSize() { 291 return this.part.getSize(); 292 } 293 294 @Override 295 public byte[] getBytes() throws IOException { 296 return FileCopyUtils.copyToByteArray(this.part.getInputStream()); 297 } 298 299 @Override 300 public InputStream getInputStream() throws IOException { 301 return this.part.getInputStream(); 302 } 303 304 @Override 305 public void transferTo(File dest) throws IOException, IllegalStateException { 306 this.part.write(dest.getPath()); 307 if (dest.isAbsolute() && !dest.exists()) { 308 // Servlet 3.0 Part.write is not guaranteed to support absolute file paths: 309 // may translate the given path to a relative location within a temp dir 310 // (e.g. on Jetty whereas Tomcat and Undertow detect absolute paths). 311 // At least we offloaded the file from memory storage; it'll get deleted 312 // from the temp dir eventually in any case. And for our user's purposes, 313 // we can manually copy it to the requested location as a fallback. 314 FileCopyUtils.copy(this.part.getInputStream(), new FileOutputStream(dest)); 315 } } 317 } 318 319}