VIVO-617 Change the way we deal with Multipart requests.
This commit is contained in:
parent
e2ad45f2cb
commit
b7b28cc896
6 changed files with 373 additions and 457 deletions
|
@ -0,0 +1,288 @@
|
||||||
|
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
|
||||||
|
|
||||||
|
package edu.cornell.mannlib.vitro.webapp.controller;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.UnsupportedEncodingException;
|
||||||
|
import java.net.URLDecoder;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Enumeration;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletRequestWrapper;
|
||||||
|
|
||||||
|
import org.apache.commons.fileupload.FileItem;
|
||||||
|
import org.apache.commons.fileupload.FileUploadException;
|
||||||
|
import org.apache.commons.fileupload.FileUploadBase.FileSizeLimitExceededException;
|
||||||
|
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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wraps a multipart HTTP request, and pre-parses it for file uploads, without
|
||||||
|
* losing the request parameters.
|
||||||
|
*
|
||||||
|
* Parsing through the request with an Apache ServletFileUpload builds a list of
|
||||||
|
* FileItems that includes the parameters and the file parts. After that,
|
||||||
|
* however, the parameters are no longer accessible from the request. This
|
||||||
|
* wrapper will see that they don't get lost.
|
||||||
|
*
|
||||||
|
* The List of FileItems includes both "formField" items and "file" items. As
|
||||||
|
* with the usual parameters on a request, we can have more than one value with
|
||||||
|
* the same name. So this creates a map of <String, List<String>> to hold the
|
||||||
|
* parameters, and a map of <String, List<FileItem>> to hold the files.
|
||||||
|
*
|
||||||
|
* The parameters will be available to the wrapper through the normal methods.
|
||||||
|
* The files will be available as an attribute that holds the map. Also, a
|
||||||
|
* separate attribute will hold a Boolean to indicate that this was indeed a
|
||||||
|
* multipart request.
|
||||||
|
*
|
||||||
|
* Conveninence methods in VitroRequest will make these easy to handle, without
|
||||||
|
* actually touching the attributes.
|
||||||
|
*/
|
||||||
|
public class MultipartRequestWrapper extends HttpServletRequestWrapper {
|
||||||
|
private static final Log log = LogFactory
|
||||||
|
.getLog(MultipartRequestWrapper.class);
|
||||||
|
|
||||||
|
private static final String CLASS_NAME = MultipartRequestWrapper.class
|
||||||
|
.getSimpleName();
|
||||||
|
public static final String ATTRIBUTE_IS_MULTIPART = CLASS_NAME
|
||||||
|
+ "_isMultipart";
|
||||||
|
public static final String ATTRIBUTE_FILE_ITEM_MAP = CLASS_NAME
|
||||||
|
+ "_fileItemMap";
|
||||||
|
public static final String ATTRIBUTE_FILE_SIZE_EXCEPTION = CLASS_NAME
|
||||||
|
+ "_fileSizeException";
|
||||||
|
|
||||||
|
private static final String[] EMPTY_ARRAY = new String[0];
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------
|
||||||
|
// The factory
|
||||||
|
// ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If this is a multipart request, wrap it. Otherwise, just return the
|
||||||
|
* request as it is.
|
||||||
|
*/
|
||||||
|
public static HttpServletRequest parse(HttpServletRequest req,
|
||||||
|
ParsingStrategy strategy) throws IOException {
|
||||||
|
if (!ServletFileUpload.isMultipartContent(req)) {
|
||||||
|
return req;
|
||||||
|
}
|
||||||
|
|
||||||
|
ListsMap<String> parameters = new ListsMap<>();
|
||||||
|
ListsMap<FileItem> files = new ListsMap<>();
|
||||||
|
|
||||||
|
parseQueryString(req.getQueryString(), parameters);
|
||||||
|
parseFileParts(req, parameters, files, strategy);
|
||||||
|
|
||||||
|
return new MultipartRequestWrapper(req, parameters, files);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pull any parameters out of the URL.
|
||||||
|
*/
|
||||||
|
private static void parseQueryString(String queryString,
|
||||||
|
ListsMap<String> parameters) {
|
||||||
|
log.debug("Query string is : '" + queryString + "'");
|
||||||
|
if (queryString != null) {
|
||||||
|
String[] pieces = queryString.split("&");
|
||||||
|
|
||||||
|
for (String piece : pieces) {
|
||||||
|
int equalsHere = piece.indexOf('=');
|
||||||
|
if (piece.trim().isEmpty()) {
|
||||||
|
// Ignore an empty piece.
|
||||||
|
} else if (equalsHere <= 0) {
|
||||||
|
// A parameter without a value.
|
||||||
|
parameters.add(decode(piece), "");
|
||||||
|
} else {
|
||||||
|
// A parameter with a value.
|
||||||
|
String key = piece.substring(0, equalsHere);
|
||||||
|
String value = piece.substring(equalsHere + 1);
|
||||||
|
parameters.add(decode(key), decode(value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log.debug("Parameters from query string are: " + parameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove any special URL-style encoding.
|
||||||
|
*/
|
||||||
|
private static String decode(String encoded) {
|
||||||
|
try {
|
||||||
|
return URLDecoder.decode(encoded, "UTF-8");
|
||||||
|
} catch (UnsupportedEncodingException e) {
|
||||||
|
log.error(e, e);
|
||||||
|
return encoded;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void parseFileParts(HttpServletRequest req,
|
||||||
|
ListsMap<String> parameters, ListsMap<FileItem> files,
|
||||||
|
ParsingStrategy strategy) throws IOException {
|
||||||
|
|
||||||
|
ServletFileUpload upload = createUploadHandler(req,
|
||||||
|
strategy.maximumMultipartFileSize());
|
||||||
|
List<FileItem> items = parseRequestIntoFileItems(req, upload, strategy);
|
||||||
|
|
||||||
|
for (FileItem item : items) {
|
||||||
|
// Process a regular form field
|
||||||
|
String name = item.getFieldName();
|
||||||
|
if (item.isFormField()) {
|
||||||
|
String value;
|
||||||
|
try {
|
||||||
|
value = item.getString("UTF-8");
|
||||||
|
} catch (UnsupportedEncodingException e) {
|
||||||
|
value = item.getString();
|
||||||
|
}
|
||||||
|
parameters.add(name, value);
|
||||||
|
log.debug("Form field (parameter) " + name + "=" + value);
|
||||||
|
} else {
|
||||||
|
files.add(name, item);
|
||||||
|
log.debug("File " + name + ": " + item.getSize() + " bytes.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an upload handler that will throw an exception if the file is too
|
||||||
|
* large.
|
||||||
|
*/
|
||||||
|
private static ServletFileUpload createUploadHandler(
|
||||||
|
HttpServletRequest req, long maxFileSize) {
|
||||||
|
File tempDir = figureTemporaryDirectory(req);
|
||||||
|
|
||||||
|
DiskFileItemFactory factory = new DiskFileItemFactory();
|
||||||
|
factory.setSizeThreshold(DiskFileItemFactory.DEFAULT_SIZE_THRESHOLD);
|
||||||
|
factory.setRepository(tempDir);
|
||||||
|
|
||||||
|
ServletFileUpload upload = new ServletFileUpload(factory);
|
||||||
|
upload.setSizeMax(maxFileSize);
|
||||||
|
return upload;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find the temporary storage directory for this webapp.
|
||||||
|
*/
|
||||||
|
private static File figureTemporaryDirectory(HttpServletRequest request) {
|
||||||
|
return (File) request.getSession().getServletContext()
|
||||||
|
.getAttribute("javax.servlet.context.tempdir");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse the raw request into a list of parts.
|
||||||
|
*
|
||||||
|
* If there is a parsing error, let the strategy handle it. If the strategy
|
||||||
|
* throws it back, wrap it in an IOException and throw it on up.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private static List<FileItem> parseRequestIntoFileItems(
|
||||||
|
HttpServletRequest req, ServletFileUpload upload,
|
||||||
|
ParsingStrategy strategy) throws IOException {
|
||||||
|
try {
|
||||||
|
return upload.parseRequest(req);
|
||||||
|
} catch (FileSizeLimitExceededException e) {
|
||||||
|
if (strategy.stashFileSizeException()) {
|
||||||
|
req.setAttribute(ATTRIBUTE_FILE_SIZE_EXCEPTION, e);
|
||||||
|
return Collections.emptyList();
|
||||||
|
} else {
|
||||||
|
throw new IOException(e);
|
||||||
|
}
|
||||||
|
} catch (FileUploadException e) {
|
||||||
|
throw new IOException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------
|
||||||
|
// The instance
|
||||||
|
// ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
private final ListsMap<String> parameters;
|
||||||
|
|
||||||
|
public MultipartRequestWrapper(HttpServletRequest req,
|
||||||
|
ListsMap<String> parameters, ListsMap<FileItem> files) {
|
||||||
|
super(req);
|
||||||
|
|
||||||
|
this.parameters = parameters;
|
||||||
|
|
||||||
|
req.setAttribute(ATTRIBUTE_IS_MULTIPART, Boolean.TRUE);
|
||||||
|
req.setAttribute(ATTRIBUTE_FILE_ITEM_MAP, files);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Look in the map of parsed parameters.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String getParameter(String name) {
|
||||||
|
if (parameters.containsKey(name)) {
|
||||||
|
return parameters.get(name).get(0);
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Look in the map of parsed parameters. Make a protective copy.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Enumeration<?> getParameterNames() {
|
||||||
|
return Collections.enumeration(new HashSet<>(parameters.keySet()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Look in the map of parsed parameters.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String[] getParameterValues(String name) {
|
||||||
|
if (parameters.containsKey(name)) {
|
||||||
|
return parameters.get(name).toArray(EMPTY_ARRAY);
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Make a copy of the map of parsed parameters;
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Map<String, String[]> getParameterMap() {
|
||||||
|
Map<String, String[]> result = new HashMap<String, String[]>();
|
||||||
|
for (String key : parameters.keySet()) {
|
||||||
|
result.put(key, parameters.get(key).toArray(EMPTY_ARRAY));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------
|
||||||
|
// Helper classes
|
||||||
|
// ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
private static class ListsMap<T> extends HashMap<String, List<T>> {
|
||||||
|
void add(String key, T value) {
|
||||||
|
if (!containsKey(key)) {
|
||||||
|
put(key, new ArrayList<T>());
|
||||||
|
}
|
||||||
|
get(key).add(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface ParsingStrategy {
|
||||||
|
long maximumMultipartFileSize();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allows you to handle the exception in your code.
|
||||||
|
*
|
||||||
|
* Be aware that the multipart parameters have been lost, and that may
|
||||||
|
* include form fields.
|
||||||
|
*/
|
||||||
|
boolean stashFileSizeException();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -30,14 +30,13 @@ import edu.cornell.mannlib.vedit.beans.LoginStatusBean;
|
||||||
import edu.cornell.mannlib.vitro.webapp.auth.policy.PolicyHelper;
|
import edu.cornell.mannlib.vitro.webapp.auth.policy.PolicyHelper;
|
||||||
import edu.cornell.mannlib.vitro.webapp.auth.requestedAction.Actions;
|
import edu.cornell.mannlib.vitro.webapp.auth.requestedAction.Actions;
|
||||||
import edu.cornell.mannlib.vitro.webapp.auth.requestedAction.ifaces.RequestedAction;
|
import edu.cornell.mannlib.vitro.webapp.auth.requestedAction.ifaces.RequestedAction;
|
||||||
import edu.cornell.mannlib.vitro.webapp.beans.BaseResourceBean;
|
|
||||||
import edu.cornell.mannlib.vitro.webapp.beans.DisplayMessage;
|
import edu.cornell.mannlib.vitro.webapp.beans.DisplayMessage;
|
||||||
import edu.cornell.mannlib.vitro.webapp.beans.ResourceBean;
|
import edu.cornell.mannlib.vitro.webapp.beans.ResourceBean;
|
||||||
import edu.cornell.mannlib.vitro.webapp.controller.authenticate.LogoutRedirector;
|
import edu.cornell.mannlib.vitro.webapp.controller.authenticate.LogoutRedirector;
|
||||||
import edu.cornell.mannlib.vitro.webapp.controller.freemarker.UrlBuilder;
|
import edu.cornell.mannlib.vitro.webapp.controller.freemarker.UrlBuilder;
|
||||||
import edu.cornell.mannlib.vitro.webapp.i18n.I18n;
|
import edu.cornell.mannlib.vitro.webapp.i18n.I18n;
|
||||||
|
|
||||||
public class VitroHttpServlet extends HttpServlet {
|
public class VitroHttpServlet extends HttpServlet implements MultipartRequestWrapper.ParsingStrategy {
|
||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
protected static DateFormat publicDateFormat = new SimpleDateFormat(
|
protected static DateFormat publicDateFormat = new SimpleDateFormat(
|
||||||
|
@ -60,7 +59,8 @@ public class VitroHttpServlet extends HttpServlet {
|
||||||
if ((req instanceof HttpServletRequest)
|
if ((req instanceof HttpServletRequest)
|
||||||
&& (resp instanceof HttpServletResponse)) {
|
&& (resp instanceof HttpServletResponse)) {
|
||||||
HttpServletRequest hreq = (HttpServletRequest) req;
|
HttpServletRequest hreq = (HttpServletRequest) req;
|
||||||
HttpServletResponse hresp = (HttpServletResponse) resp;
|
|
||||||
|
hreq = MultipartRequestWrapper.parse(hreq, this);
|
||||||
|
|
||||||
if (log.isTraceEnabled()) {
|
if (log.isTraceEnabled()) {
|
||||||
dumpRequestHeaders(hreq);
|
dumpRequestHeaders(hreq);
|
||||||
|
@ -70,6 +70,25 @@ public class VitroHttpServlet extends HttpServlet {
|
||||||
super.service(req, resp);
|
super.service(req, resp);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Override this to change the maximum size of uploaded files in multipart
|
||||||
|
* requests.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public long maximumMultipartFileSize() {
|
||||||
|
return 50 * 1024 * 1024; // default is 50 megabytes
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Override this to change the way that exceptions are handled when parsing
|
||||||
|
* a multipart request. Be aware that multipart parameters have been lost,
|
||||||
|
* and that may include form fields.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean stashFileSizeException() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* doGet does nothing.
|
* doGet does nothing.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -3,12 +3,20 @@
|
||||||
package edu.cornell.mannlib.vitro.webapp.controller;
|
package edu.cornell.mannlib.vitro.webapp.controller;
|
||||||
|
|
||||||
|
|
||||||
|
import static edu.cornell.mannlib.vitro.webapp.controller.MultipartRequestWrapper.ATTRIBUTE_FILE_ITEM_MAP;
|
||||||
|
import static edu.cornell.mannlib.vitro.webapp.controller.MultipartRequestWrapper.ATTRIBUTE_FILE_SIZE_EXCEPTION;
|
||||||
|
import static edu.cornell.mannlib.vitro.webapp.controller.MultipartRequestWrapper.ATTRIBUTE_IS_MULTIPART;
|
||||||
|
|
||||||
import java.text.Collator;
|
import java.text.Collator;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletRequestWrapper;
|
import javax.servlet.http.HttpServletRequestWrapper;
|
||||||
|
|
||||||
|
import org.apache.commons.fileupload.FileItem;
|
||||||
|
import org.apache.commons.fileupload.FileUploadBase.FileSizeLimitExceededException;
|
||||||
import org.apache.commons.logging.Log;
|
import org.apache.commons.logging.Log;
|
||||||
import org.apache.commons.logging.LogFactory;
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
|
||||||
|
@ -231,4 +239,59 @@ public class VitroRequest extends HttpServletRequestWrapper {
|
||||||
public WebappDaoFactory getLanguageNeutralWebappDaoFactory() {
|
public WebappDaoFactory getLanguageNeutralWebappDaoFactory() {
|
||||||
return (WebappDaoFactory) getAttribute("languageNeutralWebappDaoFactory");
|
return (WebappDaoFactory) getAttribute("languageNeutralWebappDaoFactory");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------
|
||||||
|
// Deal with parsed multipart requests.
|
||||||
|
// ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
public boolean isMultipart() {
|
||||||
|
return getAttribute(ATTRIBUTE_IS_MULTIPART) != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public Map<String, List<FileItem>> getFiles() {
|
||||||
|
Map<String, List<FileItem>> map;
|
||||||
|
map = (Map<String, List<FileItem>>) getAttribute(ATTRIBUTE_FILE_ITEM_MAP);
|
||||||
|
if (map == null) {
|
||||||
|
return Collections.emptyMap();
|
||||||
|
} else {
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* There may be more than one file item with the given name. If the first
|
||||||
|
* one is empty (size is zero), keep looking for a non-empty one.
|
||||||
|
*/
|
||||||
|
public FileItem getFileItem(String name) {
|
||||||
|
Map<String, List<FileItem>> map = getFiles();
|
||||||
|
List<FileItem> items = map.get(name);
|
||||||
|
if (items == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
for (FileItem item : items) {
|
||||||
|
if (item.getSize() > 0L) {
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If the uploaded file exceeded the maximum size, and if the strategy said
|
||||||
|
* to stash the exception, it will be stored as a request attribute.
|
||||||
|
*/
|
||||||
|
public boolean hasFileSizeException() {
|
||||||
|
return getFileSizeException() != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public FileSizeLimitExceededException getFileSizeException() {
|
||||||
|
Object e = getAttribute(ATTRIBUTE_FILE_SIZE_EXCEPTION);
|
||||||
|
if (e instanceof FileSizeLimitExceededException) {
|
||||||
|
return (FileSizeLimitExceededException) e;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,112 +0,0 @@
|
||||||
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
|
|
||||||
|
|
||||||
package edu.cornell.mannlib.vitro.webapp.filestorage.uploadrequest;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
import javax.servlet.http.HttpServletRequestWrapper;
|
|
||||||
|
|
||||||
import org.apache.commons.fileupload.FileItem;
|
|
||||||
import org.apache.commons.fileupload.FileUploadException;
|
|
||||||
import org.apache.commons.fileupload.servlet.ServletFileUpload;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* <p>
|
|
||||||
* Wraps an HTTP request and parses it for file uploads, without losing the
|
|
||||||
* request parameters.
|
|
||||||
* </p>
|
|
||||||
* <p>
|
|
||||||
* The request will have an attribute named by {@link #FILE_ITEM_MAP}. Either
|
|
||||||
* this attribute or the call to {@link #getFiles()} will produce a map that may
|
|
||||||
* be empty but is never null. The keys to the map are the field names for the
|
|
||||||
* file fields. Since a form may have multiple fields with the same name, each
|
|
||||||
* field name maps to a list of items. If a user does not provide a file to be
|
|
||||||
* uploaded in a given field, the length of the file will be 0.
|
|
||||||
* </p>
|
|
||||||
* <p>
|
|
||||||
* If the uploaded file(s) would be larger than the <code>maxFileSize</code>,
|
|
||||||
* {@link #parseRequest(HttpServletRequest, int)} does not throw an Exception.
|
|
||||||
* Instead, it records the exception in a request attribute named by
|
|
||||||
* {@link #FILE_UPLOAD_EXCEPTION}. This attribute can be accessed directly, or
|
|
||||||
* indirectly via the methods {@link #hasFileUploadException()} and
|
|
||||||
* {@link #getFileUploadException()}. If there is an exception, the file item
|
|
||||||
* map (see above) will still be non-null, but it will be empty.
|
|
||||||
* </p>
|
|
||||||
* <p>
|
|
||||||
* Most methods are declared here simply delegate to the wrapped request.
|
|
||||||
* Methods that have to do with parameters, files, or parsing exceptions, are
|
|
||||||
* handled differently for simple requests and multipart request, and are
|
|
||||||
* implemented in the sub-classes.
|
|
||||||
* </p>
|
|
||||||
*/
|
|
||||||
public abstract class FileUploadServletRequest extends HttpServletRequestWrapper {
|
|
||||||
public static final String FILE_ITEM_MAP = "file_item_map";
|
|
||||||
public static final String FILE_UPLOAD_EXCEPTION = "file_upload_exception";
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------------
|
|
||||||
// The factory method
|
|
||||||
// ----------------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Wrap this {@link HttpServletRequest} in an appropriate wrapper class.
|
|
||||||
*/
|
|
||||||
public static FileUploadServletRequest parseRequest(
|
|
||||||
HttpServletRequest request, int maxFileSize) throws IOException {
|
|
||||||
boolean isMultipart = ServletFileUpload.isMultipartContent(request);
|
|
||||||
if (isMultipart) {
|
|
||||||
return new MultipartHttpServletRequest(request, maxFileSize);
|
|
||||||
} else {
|
|
||||||
return new SimpleHttpServletRequestWrapper(request);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------------
|
|
||||||
// The constructor and the delegate.
|
|
||||||
// ----------------------------------------------------------------------
|
|
||||||
|
|
||||||
private final HttpServletRequest delegate;
|
|
||||||
|
|
||||||
public FileUploadServletRequest(HttpServletRequest delegate) {
|
|
||||||
super(delegate);
|
|
||||||
this.delegate = delegate;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected HttpServletRequest getDelegate() {
|
|
||||||
return this.delegate;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------------
|
|
||||||
// New functionality to be implemented by the subclasses.
|
|
||||||
// ----------------------------------------------------------------------
|
|
||||||
|
|
||||||
/** Was this a multipart HTTP request? */
|
|
||||||
public abstract boolean isMultipart();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the map of file items, by name.
|
|
||||||
*/
|
|
||||||
public abstract Map<String, List<FileItem>> getFiles();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Find a non-empty file item with this name.
|
|
||||||
*
|
|
||||||
* @return the first such file item, or <code>null</code> if no matching,
|
|
||||||
* non-empty items were found.
|
|
||||||
*/
|
|
||||||
public abstract FileItem getFileItem(String string);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Was there an exception when uploading the file items?
|
|
||||||
*/
|
|
||||||
public abstract boolean hasFileUploadException();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the exception that occurred when uploading the file items. If no such
|
|
||||||
* exception, return <code>null</code>.
|
|
||||||
*/
|
|
||||||
public abstract FileUploadException getFileUploadException();
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,261 +0,0 @@
|
||||||
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
|
|
||||||
|
|
||||||
package edu.cornell.mannlib.vitro.webapp.filestorage.uploadrequest;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.UnsupportedEncodingException;
|
|
||||||
import java.net.URLDecoder;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Enumeration;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Map.Entry;
|
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
|
|
||||||
import org.apache.commons.fileupload.FileItem;
|
|
||||||
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;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A wrapper for a servlet request that holds multipart content. Parsing the
|
|
||||||
* request will consume the parameters, so we need to hold them here to answer
|
|
||||||
* any parameter-related requests. File-related information will also be held
|
|
||||||
* here, to answer file-related requests.
|
|
||||||
*/
|
|
||||||
class MultipartHttpServletRequest extends FileUploadServletRequest {
|
|
||||||
private static final Log log = LogFactory
|
|
||||||
.getLog(MultipartHttpServletRequest.class);
|
|
||||||
|
|
||||||
private static final String[] EMPTY_ARRAY = new String[0];
|
|
||||||
|
|
||||||
private final Map<String, List<String>> parameters;
|
|
||||||
private final Map<String, List<FileItem>> files;
|
|
||||||
private FileUploadException fileUploadException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parse the multipart request. Store the info about the request parameters
|
|
||||||
* and the uploaded files.
|
|
||||||
*/
|
|
||||||
public MultipartHttpServletRequest(HttpServletRequest request,
|
|
||||||
int maxFileSize) throws IOException {
|
|
||||||
super(request);
|
|
||||||
|
|
||||||
Map<String, List<String>> parameters = new HashMap<String, List<String>>();
|
|
||||||
Map<String, List<FileItem>> files = new HashMap<String, List<FileItem>>();
|
|
||||||
|
|
||||||
File tempDir = figureTemporaryDirectory(request);
|
|
||||||
ServletFileUpload upload = createUploadHandler(maxFileSize, tempDir);
|
|
||||||
|
|
||||||
parseQueryString(request.getQueryString(), parameters);
|
|
||||||
|
|
||||||
try {
|
|
||||||
List<FileItem> items = parseRequestIntoFileItems(request, upload);
|
|
||||||
|
|
||||||
for (FileItem item : items) {
|
|
||||||
// Process a regular form field
|
|
||||||
if (item.isFormField()) {
|
|
||||||
addToParameters(parameters, item.getFieldName(), item
|
|
||||||
.getString("UTF-8"));
|
|
||||||
log.debug("Form field (parameter) " + item.getFieldName()
|
|
||||||
+ "=" + item.getString());
|
|
||||||
} else {
|
|
||||||
addToFileItems(files, item);
|
|
||||||
log.debug("File " + item.getFieldName() + ": "
|
|
||||||
+ item.getName());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (FileUploadException e) {
|
|
||||||
fileUploadException = e;
|
|
||||||
request.setAttribute(
|
|
||||||
FileUploadServletRequest.FILE_UPLOAD_EXCEPTION, e);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.parameters = Collections.unmodifiableMap(parameters);
|
|
||||||
log.debug("Parameters are: " + this.parameters);
|
|
||||||
this.files = Collections.unmodifiableMap(files);
|
|
||||||
log.debug("Files are: " + this.files);
|
|
||||||
request.setAttribute(FILE_ITEM_MAP, this.files);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Pull any parameters out of the URL.
|
|
||||||
*/
|
|
||||||
private void parseQueryString(String queryString,
|
|
||||||
Map<String, List<String>> parameters) {
|
|
||||||
log.debug("Query string is : '" + queryString + "'");
|
|
||||||
if (queryString != null) {
|
|
||||||
String[] pieces = queryString.split("&");
|
|
||||||
|
|
||||||
for (String piece : pieces) {
|
|
||||||
int equalsHere = piece.indexOf('=');
|
|
||||||
if (piece.trim().isEmpty()) {
|
|
||||||
// Ignore an empty piece.
|
|
||||||
} else if (equalsHere <= 0) {
|
|
||||||
// A parameter without a value.
|
|
||||||
addToParameters(parameters, decode(piece), "");
|
|
||||||
} else {
|
|
||||||
// A parameter with a value.
|
|
||||||
String key = piece.substring(0, equalsHere);
|
|
||||||
String value = piece.substring(equalsHere + 1);
|
|
||||||
addToParameters(parameters, decode(key), decode(value));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
log.debug("Parameters from query string are: " + parameters);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove any special URL-style encoding.
|
|
||||||
*/
|
|
||||||
private String decode(String encoded) {
|
|
||||||
try {
|
|
||||||
return URLDecoder.decode(encoded, "UTF-8");
|
|
||||||
} catch (UnsupportedEncodingException e) {
|
|
||||||
log.error(e, e);
|
|
||||||
return encoded;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Find the temporary storage directory for this webapp.
|
|
||||||
*/
|
|
||||||
private File figureTemporaryDirectory(HttpServletRequest request) {
|
|
||||||
return (File) request.getSession().getServletContext().getAttribute(
|
|
||||||
"javax.servlet.context.tempdir");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create an upload handler that will throw an exception if the file is too
|
|
||||||
* large.
|
|
||||||
*/
|
|
||||||
private ServletFileUpload createUploadHandler(int maxFileSize, File tempDir) {
|
|
||||||
DiskFileItemFactory factory = new DiskFileItemFactory();
|
|
||||||
factory.setSizeThreshold(DiskFileItemFactory.DEFAULT_SIZE_THRESHOLD);
|
|
||||||
factory.setRepository(tempDir);
|
|
||||||
|
|
||||||
ServletFileUpload upload = new ServletFileUpload(factory);
|
|
||||||
upload.setSizeMax(maxFileSize);
|
|
||||||
|
|
||||||
return upload;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Either create a new List for the value, or add to an existing List. */
|
|
||||||
private void addToParameters(Map<String, List<String>> map, String name,
|
|
||||||
String value) {
|
|
||||||
if (!map.containsKey(name)) {
|
|
||||||
map.put(name, new ArrayList<String>());
|
|
||||||
}
|
|
||||||
map.get(name).add(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Either create a new List for the file, or add to an existing List. */
|
|
||||||
private void addToFileItems(Map<String, List<FileItem>> map, FileItem file) {
|
|
||||||
String name = file.getFieldName();
|
|
||||||
if (!map.containsKey(name)) {
|
|
||||||
map.put(name, new ArrayList<FileItem>());
|
|
||||||
}
|
|
||||||
map.get(name).add(file);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Minimize the code that uses the unchecked cast. */
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
private List<FileItem> parseRequestIntoFileItems(HttpServletRequest req,
|
|
||||||
ServletFileUpload upload) throws FileUploadException {
|
|
||||||
return upload.parseRequest(req);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------------
|
|
||||||
// This is a multipart request, so make the file info available. If there
|
|
||||||
// was an exception during parsing, make that available too.
|
|
||||||
// ----------------------------------------------------------------------
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isMultipart() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Map<String, List<FileItem>> getFiles() {
|
|
||||||
return files;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
* <p>
|
|
||||||
* There may be more than one file item with the given name. If the first
|
|
||||||
* one is empty (size is zero), keep looking for a non-empty one.
|
|
||||||
* </p>
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public FileItem getFileItem(String name) {
|
|
||||||
List<FileItem> items = files.get(name);
|
|
||||||
if (items == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (FileItem item : items) {
|
|
||||||
if (item.getSize() > 0L) {
|
|
||||||
return item;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public FileUploadException getFileUploadException() {
|
|
||||||
return fileUploadException;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean hasFileUploadException() {
|
|
||||||
return fileUploadException != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------------
|
|
||||||
// Parameter-related methods won't find anything on the delegate request,
|
|
||||||
// since parsing consumed the parameters. So we need to look to the parsed
|
|
||||||
// info for the answers.
|
|
||||||
// ----------------------------------------------------------------------
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getParameter(String name) {
|
|
||||||
if (parameters.containsKey(name)) {
|
|
||||||
return parameters.get(name).get(0);
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Enumeration<?> getParameterNames() {
|
|
||||||
return Collections.enumeration(parameters.keySet());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String[] getParameterValues(String name) {
|
|
||||||
if (parameters.containsKey(name)) {
|
|
||||||
return parameters.get(name).toArray(EMPTY_ARRAY);
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Map<String, String[]> getParameterMap() {
|
|
||||||
Map<String, String[]> result = new HashMap<String, String[]>();
|
|
||||||
for (Entry<String, List<String>> entry : parameters.entrySet()) {
|
|
||||||
result.put(entry.getKey(), entry.getValue().toArray(EMPTY_ARRAY));
|
|
||||||
}
|
|
||||||
log.debug("resulting parameter map: " + result);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,81 +0,0 @@
|
||||||
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
|
|
||||||
|
|
||||||
package edu.cornell.mannlib.vitro.webapp.filestorage.uploadrequest;
|
|
||||||
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Enumeration;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
|
|
||||||
import org.apache.commons.fileupload.FileItem;
|
|
||||||
import org.apache.commons.fileupload.FileUploadException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A wrapper for a servlet request that does not hold multipart content. Pass
|
|
||||||
* all parameter-related requests to the delegate, and give simple answers to
|
|
||||||
* all file-related requests.
|
|
||||||
*/
|
|
||||||
class SimpleHttpServletRequestWrapper extends FileUploadServletRequest {
|
|
||||||
|
|
||||||
SimpleHttpServletRequestWrapper(HttpServletRequest request) {
|
|
||||||
super(request);
|
|
||||||
request.setAttribute(FILE_ITEM_MAP, Collections.EMPTY_MAP);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------------
|
|
||||||
// Not a multipart request, so there are no files or upload exceptions.
|
|
||||||
// ----------------------------------------------------------------------
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isMultipart() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Map<String, List<FileItem>> getFiles() {
|
|
||||||
return Collections.emptyMap();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public FileItem getFileItem(String string) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public FileUploadException getFileUploadException() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean hasFileUploadException() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------------
|
|
||||||
// Since this is not a multipart request, the parameter methods can be
|
|
||||||
// delegated.
|
|
||||||
// ----------------------------------------------------------------------
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getParameter(String name) {
|
|
||||||
return getDelegate().getParameter(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Map<?, ?> getParameterMap() {
|
|
||||||
return getDelegate().getParameterMap();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Enumeration<?> getParameterNames() {
|
|
||||||
return getDelegate().getParameterNames();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String[] getParameterValues(String name) {
|
|
||||||
return getDelegate().getParameterValues(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
Loading…
Add table
Reference in a new issue