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
* 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 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; iServletRequest
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.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