/** * Copyright 2005-2010 hdiv.org * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.hdiv.config.multipart; import java.io.UnsupportedEncodingException; import java.nio.charset.Charset; import java.util.Iterator; import java.util.List; import javax.servlet.ServletContext; import javax.servlet.http.HttpServletRequest; import org.apache.commons.fileupload.DiskFileUpload; import org.apache.commons.fileupload.FileItem; import org.apache.commons.fileupload.FileItemFactory; import org.apache.commons.fileupload.FileUpload; import org.apache.commons.fileupload.FileUploadBase; import org.apache.commons.fileupload.FileUploadException; import org.apache.commons.fileupload.disk.DiskFileItemFactory; import org.apache.commons.fileupload.servlet.ServletFileUpload; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.hdiv.filter.RequestWrapper; import org.springframework.http.MediaType; import org.springframework.util.StringUtils; import org.springframework.web.multipart.MaxUploadSizeExceededException; import org.springframework.web.multipart.MultipartException; import org.springframework.web.multipart.commons.CommonsMultipartFile; import org.springframework.web.multipart.commons.CommonsMultipartResolver; import org.springframework.web.util.WebUtils; /** * Class containing multipart request configuration. * * <p> * <b>NOTE:</b> This multipart resolver requires Commons FileUpload 1.1 or * higher. * * @author Gorka Vicente * @since HDIV 2.1.0 */ public class SpringMVCMultipartConfig implements IMultipartConfig { private static Log log = LogFactory.getLog(SpringMVCMultipartConfig.class); private CommonsMultipartResolver multipartResolver; /** * Parses the input stream and partitions the parsed items into a set of * form fields and a set of file items. * * @param request * The multipart request wrapper. * @param servletContext * Our ServletContext object * @throws FileUploadException * if an unrecoverable error occurs. * @throws DiskFileUpload.SizeLimitExceededException * if size limit exceeded */ public void handleMultipartRequest(RequestWrapper request, ServletContext servletContext) throws FileUploadException, FileUploadBase.SizeLimitExceededException, MaxUploadSizeExceededException { String encoding = determineEncoding(request); FileUpload fileUpload = prepareFileUpload(encoding); try { List fileItems = ((ServletFileUpload) fileUpload).parseRequest(request); parseFileItems(request, fileItems); } catch (FileUploadBase.SizeLimitExceededException ex) { throw new MaxUploadSizeExceededException(fileUpload.getSizeMax(), ex); } catch (FileUploadException ex) { throw ex; } } /** * Parse the given List of Commons FileItems into a Spring * MultipartParsingResult, containing Spring MultipartFile instances and a * Map of multipart parameter. * * @param request * the request to parse * @param fileItems * the Commons FileIterms to parse * @see CommonsMultipartFile#CommonsMultipartFile(org.apache.commons.fileupload.FileItem) */ protected void parseFileItems(RequestWrapper request, List fileItems) { // Extract multipart files and multipart parameters. for (Iterator it = fileItems.iterator(); it.hasNext();) { FileItem fileItem = (FileItem) it.next(); if (log.isDebugEnabled()) { log.debug("Found item " + fileItem.getFieldName()); } if (fileItem.isFormField()) { if (log.isDebugEnabled()) { log.debug("Item is a normal form field"); } this.addTextParameter(request, fileItem); } else { if (log.isDebugEnabled()) { log.debug("Item is a file upload"); } this.addFileParameter(request, fileItem); } } } /** * Adds a regular text parameter to the set of text parameters for this * request. Handles the case of multiple values for the same parameter by * using an array for the parameter value. * * @param request * The request in which the parameter was specified * @param fileItem * The file item for the parameter to add * @param encoding * the character encoding to use */ public void addTextParameter(RequestWrapper request, FileItem fileItem) { String encoding = determineEncoding(request); String name = fileItem.getFieldName(); String value = null; String partEncoding = determineEncoding(fileItem.getContentType(), encoding); if (partEncoding != null) { try { value = fileItem.getString(partEncoding); } catch (UnsupportedEncodingException ex) { if (log.isWarnEnabled()) { log.warn("Could not decode multipart item '" + fileItem.getFieldName() + "' with encoding '" + partEncoding + "': using platform default"); } value = fileItem.getString(); } } else { value = fileItem.getString(); } String[] oldArray = (String[]) request.getParameterValues(name); String[] newArray; if (oldArray != null) { newArray = new String[oldArray.length + 1]; System.arraycopy(oldArray, 0, newArray, 0, oldArray.length); newArray[oldArray.length] = value; } else { newArray = new String[] { value }; } request.addParameter(name, newArray); } /** * Adds a file parameter to the set of file parameters for this request and * also to the list of all parameters. * * @param request * The request in which the parameter was specified. * @param item * The file item for the parameter to add. */ public void addFileParameter(RequestWrapper request, FileItem item) { CommonsMultipartFile file = new CommonsMultipartFile(item); if (request.getFileElements().put(file.getName(), file) != null) { throw new MultipartException("Multiple files for field name [" + file.getName() + "] found - not supported by MultipartResolver"); } if (log.isDebugEnabled()) { log.debug("Found multipart file [" + file.getName() + "] of size " + file.getSize() + " bytes with original filename [" + file.getOriginalFilename() + "], stored " + file.getStorageDescription()); } } public String getRepositoryPath(ServletContext servletContext) { return null; } /** * @param multipartResolver * the multipart resolver to set */ public void setMultipartResolver(CommonsMultipartResolver multipartResolver) { this.multipartResolver = multipartResolver; } protected String getDefaultEncoding() { String encoding = this.multipartResolver.getFileUpload() .getHeaderEncoding(); if (encoding == null) { encoding = WebUtils.DEFAULT_CHARACTER_ENCODING; } return encoding; } private String determineEncoding(String contentTypeHeader, String defaultEncoding) { if (!StringUtils.hasText(contentTypeHeader)) { return defaultEncoding; } MediaType contentType = MediaType.parseMediaType(contentTypeHeader); Charset charset = contentType.getCharSet(); return charset != null ? charset.name() : defaultEncoding; } /** * Determine the encoding for the given request. Can be overridden in * subclasses. * <p> * The default implementation checks the request encoding, falling back to * the default encoding specified for this resolver. * * @param request * current HTTP request * @return the encoding for the request (never <code>null</code>) * @see javax.servlet.ServletRequest#getCharacterEncoding * @see #setDefaultEncoding */ protected String determineEncoding(HttpServletRequest request) { String encoding = request.getCharacterEncoding(); if (encoding == null) { encoding = getDefaultEncoding(); } return encoding; } /** * Determine an appropriate FileUpload instance for the given encoding. * <p> * Default implementation returns the shared FileUpload instance if the * encoding matches, else creates a new FileUpload instance with the same * configuration other than the desired encoding. * * @param encoding * the character encoding to use * @return an appropriate FileUpload instance. */ protected FileUpload prepareFileUpload(String encoding) { FileUpload fileUpload = this.multipartResolver.getFileUpload(); FileUpload actualFileUpload = fileUpload; // Use new temporary FileUpload instance if the request specifies // its own encoding that does not match the default encoding. if (encoding != null && !encoding.equals(fileUpload.getHeaderEncoding())) { actualFileUpload = newFileUpload(this.multipartResolver .getFileItemFactory()); actualFileUpload.setSizeMax(fileUpload.getSizeMax()); actualFileUpload.setHeaderEncoding(encoding); } return actualFileUpload; } /** * Factory method for a Commons DiskFileItemFactory instance. * <p> * Default implementation returns a standard DiskFileItemFactory. Can be * overridden to use a custom subclass, e.g. for testing purposes. * * @return the new DiskFileItemFactory instance */ protected DiskFileItemFactory newFileItemFactory() { return new DiskFileItemFactory(); } /** * Initialize the underlying * <code>org.apache.commons.fileupload.servlet.ServletFileUpload</code> * instance. Can be overridden to use a custom subclass, e.g. for testing * purposes. * * @param fileItemFactory * the Commons FileItemFactory to use * @return the new ServletFileUpload instance */ protected FileUpload newFileUpload(FileItemFactory fileItemFactory) { return new ServletFileUpload(fileItemFactory); } }