/* * The contents of this file are subject to the Mozilla Public License Version 1.1 * (the "License"); you may not use this file except in compliance with the License. * You may obtain a copy of the License at . * * Software distributed under the License is distributed on an "AS IS" basis, WITHOUT * WARRANTY OF ANY KIND, either express or implied. See the License for the specific * language governing rights and limitations under the License. * * The Original Code is the Venice Web Communities System. * * The Initial Developer of the Original Code is Eric J. Bowersox , * for Silverwrist Design Studios. Portions created by Eric J. Bowersox are * Copyright (C) 2001-2006 Eric J. Bowersox/Silverwrist Design Studios. All Rights Reserved. * * Contributor(s): */ package com.silverwrist.util; import java.io.*; import java.sql.Blob; import java.sql.SQLException; import java.util.*; import javax.activation.DataSource; import javax.mail.*; import javax.mail.internet.*; import javax.servlet.*; import org.apache.log4j.Logger; // This class was built in a process I call "Java Junkyard Wars," in which I put together // a bunch of APIs in ways that their designers would never have anticipated. It's // absolutely bodge-tastic! /** * A class which parses servlet request data of the MIME type multipart/form-data. This is * necessary because the standard Java Servlets API only handles the standard form data type of * application/x-www-form-urlencoded, yet the multipart/form-data form * data type is the only way to upload files using a standard Web browser (at least one that implements * RFC 1867, as most modern browsers do).

* When using this code, do not use the standard ServletRequest.getParameter API! * Instead, if the datatype returned by ServletRequest.getContentType is "multipart/form-data", * pass the request to the constructor of this class, and then use the methods of this class to extract both * file and non-file parameters. * * @author Eric J. Bowersox <erbo@users.sf.net> * @version X * @see ServletMultipartException * @see javax.servlet.ServletRequest */ public final class ServletMultipartHandler { /*-------------------------------------------------------------------------------- * Internal wrapper around the ServletRequest that implements DataSource *-------------------------------------------------------------------------------- */ static class ServletDataSource implements DataSource { private ServletRequest request; private InputStream istm = null; public ServletDataSource(ServletRequest request) { this.request = request; } // end constructor public InputStream getInputStream() throws IOException { if (istm==null) istm = request.getInputStream(); return istm; } // end getInputStream public OutputStream getOutputStream() throws IOException { throw new IOException("tried to get OutputStream on servlet input?!?!?"); } // end getOutputStream public String getContentType() { return request.getContentType(); } // end getContentType public String getName() { return "servlet"; } // end getName } // end class ServletDataSource /*-------------------------------------------------------------------------------- * Internal class representing a data value *-------------------------------------------------------------------------------- */ static class MultipartDataValue implements Blob { private byte[] actual_data; // the actual data we contain public MultipartDataValue(MimeBodyPart part) throws MessagingException, IOException { if (logger.isDebugEnabled()) logger.debug("creating new MultipartDataValue"); // load actual data actual_data = IOUtil.load(part.getInputStream()); if (logger.isDebugEnabled()) logger.debug("finished copying, " + actual_data.length + " bytes transferred"); } // end constructor public void free() { actual_data = null; } public long length() { return actual_data.length; } // end length public byte[] getBytes(long pos, int length) { byte[] rc = new byte[length]; System.arraycopy(actual_data,(int)pos,rc,0,length); return rc; } // end getBytes public InputStream getBinaryStream() { return new ByteArrayInputStream(actual_data); } // end getBinaryStream public InputStream getBinaryStream(long pos, long length) { return new ByteArrayInputStream(actual_data,(int)pos,(int)length); } public long position(byte[] pattern, long start) throws SQLException { logger.warn("position() function is not implemented for MultipartDataValue"); throw new SQLException("function not implemented"); } // end position public long position(Blob pattern, long start) throws SQLException { return position(pattern.getBytes(0,(int)(pattern.length())),start); } // end position public int setBytes(long pos, byte[] bytes) throws SQLException { return setBytes(pos,bytes,0,bytes.length); } // end setBytes public int setBytes(long pos, byte[] bytes, int offset, int len) throws SQLException { logger.warn("setBytes() function is not implemented for MultipartDataValue"); throw new SQLException("function not implemented"); } // end setBytes public OutputStream setBinaryStream(long pos) throws SQLException { logger.warn("setBinaryStream() function is not implemented for MultipartDataValue"); throw new SQLException("function not implemented"); } // end setBinaryStream public void truncate(long len) throws SQLException { logger.warn("truncate() function is not implemented for MultipartDataValue"); throw new SQLException("function not implemented"); } // end truncate } // end class MultipartDataValue /*-------------------------------------------------------------------------------- * Internal class representing a request parameter *-------------------------------------------------------------------------------- */ final class MultipartParameter { private MimeBodyPart part; // the actual body part data private String name; // the parameter name private String filename; // the filename private MultipartDataValue cached_value = null; public MultipartParameter(MimeBodyPart part) throws MessagingException { this.part = part; // save part reference // Parse the Content-Disposition header. String[] cdstr = part.getHeader("Content-Disposition"); if (logger.isDebugEnabled()) logger.debug("Content-Disposition is " + cdstr[0]); ContentDisposition cdisp = new ContentDisposition(cdstr[0]); name = cdisp.getParameter("name"); filename = cdisp.getParameter("filename"); if (filename!=null) { // EJB 4/4/2001 - Windows boxes pass the filename complete with the backslashed pathname on the // front, and, for some reason, ContentDisposition strips out the backslashes while leaving the // pathname components in place (including the drive letter!). So we have to go to manual here // to extract the filename ourselves. int pos = cdstr[0].indexOf("filename"); pos += 8; while ((cdstr[0].charAt(pos)==' ') || (cdstr[0].charAt(pos)=='\t')) pos++; if (cdstr[0].charAt(pos)!='=') throw new RuntimeException("must have = sign after filename"); pos++; while ((cdstr[0].charAt(pos)==' ') || (cdstr[0].charAt(pos)=='\t')) pos++; if ((cdstr[0].charAt(pos)=='\'') || (cdstr[0].charAt(pos)=='"')) { // filename enclosed in quotes... char match = cdstr[0].charAt(pos++); filename = cdstr[0].substring(pos); pos = filename.lastIndexOf(match); if (pos<0) throw new RuntimeException("must have closing quote"); filename = filename.substring(0,pos); } // end if else { // no quotes, just take the rest of the line filename = cdstr[0].substring(pos); pos = filename.lastIndexOf(';'); if (pos>=0) filename = filename.substring(0,pos); } // end else if (logger.isDebugEnabled()) logger.debug("Raw filename: " + filename); // Strip off everything but the base filename, if the browser happened to pass that. int sep = filename.lastIndexOf('\\'); if (sep>=0) filename = filename.substring(sep+1); sep = filename.lastIndexOf('/'); if (sep>=0) filename = filename.substring(sep+1); } // end if if (logger.isDebugEnabled()) { // tell us what kind of parameter we have if (filename!=null) logger.debug("new file parameter \"" + name + "\" defined (filename = " + filename + ")"); else logger.debug("new text parameter \"" + name + "\" defined"); } // end if } // end constructor public final String getName() { return name; } // end getName public final boolean isFile() { return (filename!=null); } // end isFile public final String getValue() { if (filename!=null) return filename; // "value" for file parts is the filename try { // Retrieve the part's actual content and convert it to a String. (Since non-file // fields are of type text/plain, the Object "val" should actually be a String, in // which case the toString() call is actually a no-op. But this is safe.) Object val = part.getContent(); return val.toString(); } // end try catch (Exception e) { // turn any exception returns here into null returns logger.warn("parameter getValue() method threw a " + e.getClass().getName(),e); return null; } // end catch } // end getValue public final String getContentType() { try { // pass through to the interior part return part.getContentType(); } // end try catch (Exception e) { // just dump a null on error logger.warn("parameter getContentType() method threw a " + e.getClass().getName(),e); return null; } // end catch } // end getContentType public final int getSize() { try { // pass through to the interior part return part.getSize(); } // end try catch (Exception e) { // just dump a -1 on error logger.warn("parameter getSize() method threw a " + e.getClass().getName(),e); return -1; } // end catch } // end getSize public final MultipartDataValue getContent() throws ServletMultipartException { if (filename==null) return null; // not a file parameter if (cached_value==null) { // we don't have the value cached yet if (logger.isDebugEnabled()) logger.debug("getting MultipartDataValue for parameter \"" + name + "\""); try { // extract the value cached_value = new MultipartDataValue(part); } // end try catch (MessagingException me) { // translate exception here logger.error("MIME parse error getting data content: " + me.getMessage(),me); throw new ServletMultipartException("Error getting data content: " + me.getMessage(),me); } // end catch catch (IOException ioe) { // translate exception here logger.error("IO error getting data: " + ioe.getMessage(),ioe); throw new ServletMultipartException("Error getting data content: " + ioe.getMessage(),ioe); } // end catch } // end if return cached_value; } // end getContent } // end class MultipartParameter /*-------------------------------------------------------------------------------- * Static data members *-------------------------------------------------------------------------------- */ /** Instance of {@link org.apache.log4j.Logger Logger} for this class. */ private static final Logger logger = Logger.getLogger(ServletMultipartHandler.class); /*-------------------------------------------------------------------------------- * Attributes *-------------------------------------------------------------------------------- */ private MimeMultipart multipart; // holds all the multipart data private HashMap param_byname; // parameters by name private ArrayList param_order; // parameters in order /*-------------------------------------------------------------------------------- * Constructor *-------------------------------------------------------------------------------- */ /** * Constructs a new ServletMultipartHandler. * * @param request The ServletRequest which contains the request data of type * multipart/form-data. * @exception com.silverwrist.util.ServletMultipartException The request could not be correctly parsed. */ public ServletMultipartHandler(ServletRequest request) throws ServletMultipartException { try { // build the MimeMultipart based on the ServletDataSource logger.debug("about to create MimeMultipart"); multipart = new MimeMultipart(new ServletDataSource(request)); logger.debug("about to get request count"); int count = multipart.getCount(); if (logger.isDebugEnabled()) logger.debug("retrieved " + count + " parameter(s) from request"); // allocate the multipart parameters and slot them into our arrays param_byname = new HashMap(); param_order = new ArrayList(); for (int i=0; itrue if the given ServletRequest can be handled by * the ServletMultipartHandler, false if not. * * @param request The ServletRequest to be checked. * @return true if the given ServletRequest can be handled by * the ServletMultipartHandler, false if not. */ public final static boolean canHandle(ServletRequest request) { String ctype = request.getContentType(); return (ctype.startsWith("multipart/form-data")); } // end canHandle /*-------------------------------------------------------------------------------- * External operations *-------------------------------------------------------------------------------- */ /** * Returns an Enumeration of all parameter names in this request. * * @return See above. * @see #getParamNames() * @see java.util.Enumeration */ public final Enumeration getNames() { ArrayList tmp_v = new ArrayList(); Iterator it = param_order.iterator(); while (it.hasNext()) { // add each name to the temporary vector MultipartParameter parm = (MultipartParameter)(it.next()); tmp_v.add(parm.getName()); } // end while return Collections.enumeration(tmp_v); // and enumerate it } // end getNames /** * Returns an Iterator over all parameter names in this request. This Iterator * must be considered read-only. * * @return See above. * @see #getNames() * @see java.util.Iterator */ public final Iterator getParamNames() { ArrayList tmp_v = new ArrayList(); Iterator it = param_order.iterator(); while (it.hasNext()) { // add each name to the temporary vector MultipartParameter parm = (MultipartParameter)(it.next()); tmp_v.add(parm.getName()); } // end while return Collections.unmodifiableList(tmp_v).iterator(); } // end getParamNames public final boolean hasParameter(String name) { return param_byname.containsKey(name); } // end hasParameter /** * Returns whether or not the parameter with the specified name is a file parameter. * * @param name Name of the parameter to test. * @return true if the specified parameter is a file parameter, false of not. * If no parameter by this name exists in the request, the method returns false. */ public final boolean isFileParam(String name) { MultipartParameter parm = (MultipartParameter)(param_byname.get(name)); if (parm==null) { // no such parameter! logger.warn("parameter \"" + name + "\" not found"); return false; } // end if else return parm.isFile(); } // end isFileParam /** * Returns the value of the parameter with the specified name. In the case of file parameters, returns * the name of the file that was uploaded.

* If the parameter may have multiple values, you should use {@link getParamValues(java.lang.String)} to * return all possible values for this parameter. * * @param name Name of the parameter to return. * @return Value of the parameter, which is a filename if this parameter is a file. If no parameter by * this name exists in the request, the method returns null. */ public final String getValue(String name) { MultipartParameter parm = (MultipartParameter)(param_byname.get(name)); if (parm==null) { // no such parameter! logger.warn("parameter \"" + name + "\" not found"); return null; } // end if else return parm.getValue(); } // end getValue /** * Returns an array of String objects containing all of the values the given request * parameter has, or null if the parameter does not exist. In the case of file parameters, * the value is ne name of the file that was uploaded. If the parameter has a single value, the returned * array has a length of 1. * * @param name Name of the parameter to return. * @return Array containing all of the values associated with this parameter name. If no values * are associated with the parameter name, returns null. */ public final String[] getParamValues(String name) { if (!(param_byname.containsKey(name))) return null; // no parameters with this name // Collect all the parameter values with this name. ArrayList rc = new ArrayList(param_order.size()); Iterator it = param_order.iterator(); while (it.hasNext()) { // find names that match, copy values MultipartParameter parm = (MultipartParameter)(it.next()); if (parm.getName().equals(name)) rc.add(parm.getValue()); } // end while // return the results array return (String[])(rc.toArray(new String[0])); } // end getParamValues /** * Returns the MIME type of the file parameter with the specified name. Ordinary text parameters return * the MIME type text/plain. * * @param name Name of the parameter to return. * @return Content type of the parameter. If no parameter by this name exists in the request, the method * returns null. */ public final String getContentType(String name) { MultipartParameter parm = (MultipartParameter)(param_byname.get(name)); if (parm==null) { // no such parameter! logger.warn("parameter \"" + name + "\" not found"); return null; } // end if else return parm.getContentType(); } // end getContentType /** * Returns the size in bytes of the file parameter with the specified name. For ordinary text parameters, * the return value is meaningless. * * @param name Name of the parameter to return. * @return Size in bytes of the parameter. If no parameter by this name exists in the request, the method * returns -1. */ public final int getContentSize(String name) { MultipartParameter parm = (MultipartParameter)(param_byname.get(name)); if (parm==null) { // no such parameter! logger.warn("parameter \"" + name + "\" not found"); return -1; } // end if else return parm.getSize(); } // end getContentSize /** * Returns the contents of the specified file parameter expressed as a Blob. * * @param name Name of the parameter to return. * @return Blob containing the file parameter data. If this parameter does not * exist in the request or is not a file parameter, the method returns null. * @exception com.silverwrist.util.ServletMultipartException Unable to prepare the file parameter data. * @see #getFileContentStream() * @see java.sql.Blob */ public final Blob getFileContentBlob(String name) throws ServletMultipartException { MultipartParameter parm = (MultipartParameter)(param_byname.get(name)); if (parm==null) { // no such parameter! logger.warn("parameter \"" + name + "\" not found"); return null; } // end if else return parm.getContent(); } // end getFileContentBlob /** * Returns the contents of the specified file parameter expressed as an InputStream. * * @param name Name of the parameter to return. * @return InputStream containing the file parameter data. If this parameter does not * exist in the request or is not a file parameter, the method returns null. * @exception com.silverwrist.util.ServletMultipartException Unable to prepare the file parameter data. * @see #getFileContentBlob() * @see java.io.InputStream */ public final InputStream getFileContentStream(String name) throws ServletMultipartException { MultipartParameter parm = (MultipartParameter)(param_byname.get(name)); if (parm==null) { // no such parameter! logger.warn("parameter \"" + name + "\" not found"); return null; } // end if MultipartDataValue val = parm.getContent(); return ((val==null) ? null : val.getBinaryStream()); } // end getFileContentStream } // end class ServletMultipartHandler