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