mirror of
https://github.com/owasp-modsecurity/ModSecurity.git
synced 2025-11-16 01:22:18 +03:00
Support for file upload & request/response java wrappers
This commit is contained in:
committed by
Felipe Zimmerle
parent
b9080aad18
commit
8f3b3eb468
@@ -0,0 +1,652 @@
|
||||
/*
|
||||
* $Header: /home/cvs/jakarta-commons/fileupload/src/java/org/apache/commons/fileupload/DefaultFileItem.java,v 1.21 2003/06/24 05:45:15 martinc Exp $
|
||||
* $Revision: 1.21 $
|
||||
* $Date: 2003/06/24 05:45:15 $
|
||||
*
|
||||
* ====================================================================
|
||||
*
|
||||
* The Apache Software License, Version 1.1
|
||||
*
|
||||
* Copyright (c) 2001-2003 The Apache Software Foundation. All rights
|
||||
* reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in
|
||||
* the documentation and/or other materials provided with the
|
||||
* distribution.
|
||||
*
|
||||
* 3. The end-user documentation included with the redistribution, if
|
||||
* any, must include the following acknowlegement:
|
||||
* "This product includes software developed by the
|
||||
* Apache Software Foundation (http://www.apache.org/)."
|
||||
* Alternately, this acknowlegement may appear in the software itself,
|
||||
* if and wherever such third-party acknowlegements normally appear.
|
||||
*
|
||||
* 4. The names "The Jakarta Project", "Commons", and "Apache Software
|
||||
* Foundation" must not be used to endorse or promote products derived
|
||||
* from this software without prior written permission. For written
|
||||
* permission, please contact apache@apache.org.
|
||||
*
|
||||
* 5. Products derived from this software may not be called "Apache"
|
||||
* nor may "Apache" appear in their names without prior written
|
||||
* permission of the Apache Group.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
|
||||
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
|
||||
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
|
||||
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
|
||||
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
* SUCH DAMAGE.
|
||||
* ====================================================================
|
||||
*
|
||||
* This software consists of voluntary contributions made by many
|
||||
* individuals on behalf of the Apache Software Foundation. For more
|
||||
* information on the Apache Software Foundation, please see
|
||||
* <http://www.apache.org/>.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
package org.apache.commons.fileupload;
|
||||
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
|
||||
|
||||
/**
|
||||
* <p> The default implementation of the
|
||||
* {@link org.apache.commons.fileupload.FileItem FileItem} interface.
|
||||
*
|
||||
* <p> After retrieving an instance of this class from a {@link
|
||||
* org.apache.commons.fileupload.DiskFileUpload DiskFileUpload} instance (see
|
||||
* {@link org.apache.commons.fileupload.DiskFileUpload
|
||||
* #parseRequest(javax.servlet.http.HttpServletRequest)}), you may
|
||||
* either request all contents of file at once using {@link #get()} or
|
||||
* request an {@link java.io.InputStream InputStream} with
|
||||
* {@link #getInputStream()} and process the file without attempting to load
|
||||
* it into memory, which may come handy with large files.
|
||||
*
|
||||
* @author <a href="mailto:Rafal.Krzewski@e-point.pl">Rafal Krzewski</a>
|
||||
* @author <a href="mailto:sean@informage.net">Sean Legassick</a>
|
||||
* @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
|
||||
* @author <a href="mailto:jmcnally@apache.org">John McNally</a>
|
||||
* @author <a href="mailto:martinc@apache.org">Martin Cooper</a>
|
||||
* @author Sean C. Sullivan
|
||||
*
|
||||
* @version $Id: DefaultFileItem.java,v 1.21 2003/06/24 05:45:15 martinc Exp $
|
||||
*/
|
||||
public class DefaultFileItem
|
||||
implements FileItem
|
||||
{
|
||||
|
||||
// ----------------------------------------------------------- Data members
|
||||
|
||||
|
||||
/**
|
||||
* Counter used in unique identifier generation.
|
||||
*/
|
||||
private static int counter = 0;
|
||||
|
||||
|
||||
/**
|
||||
* The name of the form field as provided by the browser.
|
||||
*/
|
||||
private String fieldName;
|
||||
|
||||
|
||||
/**
|
||||
* The content type passed by the browser, or <code>null</code> if
|
||||
* not defined.
|
||||
*/
|
||||
private String contentType;
|
||||
|
||||
|
||||
/**
|
||||
* Whether or not this item is a simple form field.
|
||||
*/
|
||||
private boolean isFormField;
|
||||
|
||||
|
||||
/**
|
||||
* The original filename in the user's filesystem.
|
||||
*/
|
||||
private String fileName;
|
||||
|
||||
|
||||
/**
|
||||
* The threshold above which uploads will be stored on disk.
|
||||
*/
|
||||
private int sizeThreshold;
|
||||
|
||||
|
||||
/**
|
||||
* The directory in which uploaded files will be stored, if stored on disk.
|
||||
*/
|
||||
private File repository;
|
||||
|
||||
|
||||
/**
|
||||
* Cached contents of the file.
|
||||
*/
|
||||
private byte[] cachedContent;
|
||||
|
||||
|
||||
/**
|
||||
* Output stream for this item.
|
||||
*/
|
||||
private DeferredFileOutputStream dfos;
|
||||
|
||||
|
||||
// ----------------------------------------------------------- Constructors
|
||||
|
||||
|
||||
/**
|
||||
* Constructs a new <code>DefaultFileItem</code> instance.
|
||||
*
|
||||
* @param fieldName The name of the form field.
|
||||
* @param contentType The content type passed by the browser or
|
||||
* <code>null</code> if not specified.
|
||||
* @param isFormField Whether or not this item is a plain form field, as
|
||||
* opposed to a file upload.
|
||||
* @param fileName The original filename in the user's filesystem, or
|
||||
* <code>null</code> if not specified.
|
||||
* @param sizeThreshold The threshold, in bytes, below which items will be
|
||||
* retained in memory and above which they will be
|
||||
* stored as a file.
|
||||
* @param repository The data repository, which is the directory in
|
||||
* which files will be created, should the item size
|
||||
* exceed the threshold.
|
||||
*/
|
||||
DefaultFileItem(String fieldName, String contentType, boolean isFormField,
|
||||
String fileName, int sizeThreshold, File repository)
|
||||
{
|
||||
this.fieldName = fieldName;
|
||||
this.contentType = contentType;
|
||||
this.isFormField = isFormField;
|
||||
this.fileName = fileName;
|
||||
this.sizeThreshold = sizeThreshold;
|
||||
this.repository = repository;
|
||||
}
|
||||
|
||||
|
||||
// ------------------------------- Methods from javax.activation.DataSource
|
||||
|
||||
|
||||
/**
|
||||
* Returns an {@link java.io.InputStream InputStream} that can be
|
||||
* used to retrieve the contents of the file.
|
||||
*
|
||||
* @return An {@link java.io.InputStream InputStream} that can be
|
||||
* used to retrieve the contents of the file.
|
||||
*
|
||||
* @exception IOException if an error occurs.
|
||||
*/
|
||||
public InputStream getInputStream()
|
||||
throws IOException
|
||||
{
|
||||
if (!dfos.isInMemory())
|
||||
{
|
||||
return new FileInputStream(dfos.getFile());
|
||||
}
|
||||
|
||||
if (cachedContent == null)
|
||||
{
|
||||
cachedContent = dfos.getData();
|
||||
}
|
||||
return new ByteArrayInputStream(cachedContent);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the content type passed by the browser or <code>null</code> if
|
||||
* not defined.
|
||||
*
|
||||
* @return The content type passed by the browser or <code>null</code> if
|
||||
* not defined.
|
||||
*/
|
||||
public String getContentType()
|
||||
{
|
||||
return contentType;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the original filename in the client's filesystem.
|
||||
*
|
||||
* @return The original filename in the client's filesystem.
|
||||
*/
|
||||
public String getName()
|
||||
{
|
||||
return fileName;
|
||||
}
|
||||
|
||||
|
||||
// ------------------------------------------------------- FileItem methods
|
||||
|
||||
|
||||
/**
|
||||
* Provides a hint as to whether or not the file contents will be read
|
||||
* from memory.
|
||||
*
|
||||
* @return <code>true</code> if the file contents will be read
|
||||
* from memory; <code>false</code> otherwise.
|
||||
*/
|
||||
public boolean isInMemory()
|
||||
{
|
||||
return (dfos.isInMemory());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the size of the file.
|
||||
*
|
||||
* @return The size of the file, in bytes.
|
||||
*/
|
||||
public long getSize()
|
||||
{
|
||||
if (cachedContent != null)
|
||||
{
|
||||
return cachedContent.length;
|
||||
}
|
||||
else if (dfos.isInMemory())
|
||||
{
|
||||
return dfos.getData().length;
|
||||
}
|
||||
else
|
||||
{
|
||||
return dfos.getFile().length();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the contents of the file as an array of bytes. If the
|
||||
* contents of the file were not yet cached in memory, they will be
|
||||
* loaded from the disk storage and cached.
|
||||
*
|
||||
* @return The contents of the file as an array of bytes.
|
||||
*/
|
||||
public byte[] get()
|
||||
{
|
||||
if (dfos.isInMemory())
|
||||
{
|
||||
if (cachedContent == null)
|
||||
{
|
||||
cachedContent = dfos.getData();
|
||||
}
|
||||
return cachedContent;
|
||||
}
|
||||
|
||||
byte[] fileData = new byte[(int) getSize()];
|
||||
FileInputStream fis = null;
|
||||
|
||||
try
|
||||
{
|
||||
fis = new FileInputStream(dfos.getFile());
|
||||
fis.read(fileData);
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
fileData = null;
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (fis != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
fis.close();
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return fileData;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the contents of the file as a String, using the specified
|
||||
* encoding. This method uses {@link #get()} to retrieve the
|
||||
* contents of the file.
|
||||
*
|
||||
* @param encoding The character encoding to use.
|
||||
*
|
||||
* @return The contents of the file, as a string.
|
||||
*
|
||||
* @exception UnsupportedEncodingException if the requested character
|
||||
* encoding is not available.
|
||||
*/
|
||||
public String getString(String encoding)
|
||||
throws UnsupportedEncodingException
|
||||
{
|
||||
return new String(get(), encoding);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the contents of the file as a String, using the default
|
||||
* character encoding. This method uses {@link #get()} to retrieve the
|
||||
* contents of the file.
|
||||
*
|
||||
* @return The contents of the file, as a string.
|
||||
*/
|
||||
public String getString()
|
||||
{
|
||||
return new String(get());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* A convenience method to write an uploaded item to disk. The client code
|
||||
* is not concerned with whether or not the item is stored in memory, or on
|
||||
* disk in a temporary location. They just want to write the uploaded item
|
||||
* to a file.
|
||||
* <p>
|
||||
* This implementation first attempts to rename the uploaded item to the
|
||||
* specified destination file, if the item was originally written to disk.
|
||||
* Otherwise, the data will be copied to the specified file.
|
||||
* <p>
|
||||
* This method is only guaranteed to work <em>once</em>, the first time it
|
||||
* is invoked for a particular item. This is because, in the event that the
|
||||
* method renames a temporary file, that file will no longer be available
|
||||
* to copy or rename again at a later time.
|
||||
*
|
||||
* @param file The <code>File</code> into which the uploaded item should
|
||||
* be stored.
|
||||
*
|
||||
* @exception Exception if an error occurs.
|
||||
*/
|
||||
public void write(File file) throws Exception
|
||||
{
|
||||
if (isInMemory())
|
||||
{
|
||||
FileOutputStream fout = null;
|
||||
try
|
||||
{
|
||||
fout = new FileOutputStream(file);
|
||||
fout.write(get());
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (fout != null)
|
||||
{
|
||||
fout.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
File outputFile = getStoreLocation();
|
||||
if (outputFile != null)
|
||||
{
|
||||
/*
|
||||
* The uploaded file is being stored on disk
|
||||
* in a temporary location so move it to the
|
||||
* desired file.
|
||||
*/
|
||||
if (!outputFile.renameTo(file))
|
||||
{
|
||||
BufferedInputStream in = null;
|
||||
BufferedOutputStream out = null;
|
||||
try
|
||||
{
|
||||
in = new BufferedInputStream(
|
||||
new FileInputStream(outputFile));
|
||||
out = new BufferedOutputStream(
|
||||
new FileOutputStream(file));
|
||||
byte[] bytes = new byte[2048];
|
||||
int s = 0;
|
||||
while ((s = in.read(bytes)) != -1)
|
||||
{
|
||||
out.write(bytes, 0, s);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
try
|
||||
{
|
||||
in.close();
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
// ignore
|
||||
}
|
||||
try
|
||||
{
|
||||
out.close();
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
/*
|
||||
* For whatever reason we cannot write the
|
||||
* file to disk.
|
||||
*/
|
||||
throw new FileUploadException(
|
||||
"Cannot write uploaded file to disk!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Deletes the underlying storage for a file item, including deleting any
|
||||
* associated temporary disk file. Although this storage will be deleted
|
||||
* automatically when the <code>FileItem</code> instance is garbage
|
||||
* collected, this method can be used to ensure that this is done at an
|
||||
* earlier time, thus preserving system resources.
|
||||
*/
|
||||
public void delete()
|
||||
{
|
||||
cachedContent = null;
|
||||
File outputFile = getStoreLocation();
|
||||
if (outputFile != null && outputFile.exists())
|
||||
{
|
||||
outputFile.delete();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the name of the field in the multipart form corresponding to
|
||||
* this file item.
|
||||
*
|
||||
* @return The name of the form field.
|
||||
*
|
||||
* @see #setFieldName(java.lang.String)
|
||||
*
|
||||
*/
|
||||
public String getFieldName()
|
||||
{
|
||||
return fieldName;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the field name used to reference this file item.
|
||||
*
|
||||
* @param fieldName The name of the form field.
|
||||
*
|
||||
* @see #getFieldName()
|
||||
*
|
||||
*/
|
||||
public void setFieldName(String fieldName)
|
||||
{
|
||||
this.fieldName = fieldName;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Determines whether or not a <code>FileItem</code> instance represents
|
||||
* a simple form field.
|
||||
*
|
||||
* @return <code>true</code> if the instance represents a simple form
|
||||
* field; <code>false</code> if it represents an uploaded file.
|
||||
*
|
||||
* @see #setFormField(boolean)
|
||||
*
|
||||
*/
|
||||
public boolean isFormField()
|
||||
{
|
||||
return isFormField;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Specifies whether or not a <code>FileItem</code> instance represents
|
||||
* a simple form field.
|
||||
*
|
||||
* @param state <code>true</code> if the instance represents a simple form
|
||||
* field; <code>false</code> if it represents an uploaded file.
|
||||
*
|
||||
* @see #isFormField()
|
||||
*
|
||||
*/
|
||||
public void setFormField(boolean state)
|
||||
{
|
||||
isFormField = state;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns an {@link java.io.OutputStream OutputStream} that can
|
||||
* be used for storing the contents of the file.
|
||||
*
|
||||
* @return An {@link java.io.OutputStream OutputStream} that can be used
|
||||
* for storing the contensts of the file.
|
||||
*
|
||||
* @exception IOException if an error occurs.
|
||||
*/
|
||||
public OutputStream getOutputStream()
|
||||
throws IOException
|
||||
{
|
||||
if (dfos == null)
|
||||
{
|
||||
File outputFile = getTempFile();
|
||||
dfos = new DeferredFileOutputStream(sizeThreshold, outputFile);
|
||||
}
|
||||
return dfos;
|
||||
}
|
||||
|
||||
|
||||
// --------------------------------------------------------- Public methods
|
||||
|
||||
|
||||
/**
|
||||
* Returns the {@link java.io.File} object for the <code>FileItem</code>'s
|
||||
* data's temporary location on the disk. Note that for
|
||||
* <code>FileItem</code>s that have their data stored in memory,
|
||||
* this method will return <code>null</code>. When handling large
|
||||
* files, you can use {@link java.io.File#renameTo(java.io.File)} to
|
||||
* move the file to new location without copying the data, if the
|
||||
* source and destination locations reside within the same logical
|
||||
* volume.
|
||||
*
|
||||
* @return The data file, or <code>null</code> if the data is stored in
|
||||
* memory.
|
||||
*/
|
||||
public File getStoreLocation()
|
||||
{
|
||||
return dfos.getFile();
|
||||
}
|
||||
|
||||
|
||||
// ------------------------------------------------------ Protected methods
|
||||
|
||||
|
||||
/**
|
||||
* Removes the file contents from the temporary storage.
|
||||
*/
|
||||
protected void finalize()
|
||||
{
|
||||
File outputFile = dfos.getFile();
|
||||
|
||||
if (outputFile != null && outputFile.exists())
|
||||
{
|
||||
outputFile.delete();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates and returns a {@link java.io.File File} representing a uniquely
|
||||
* named temporary file in the configured repository path.
|
||||
*
|
||||
* @return The {@link java.io.File File} to be used for temporary storage.
|
||||
*/
|
||||
protected File getTempFile()
|
||||
{
|
||||
File tempDir = repository;
|
||||
if (tempDir == null)
|
||||
{
|
||||
tempDir = new File(System.getProperty("java.io.tmpdir"));
|
||||
}
|
||||
|
||||
String fileName = "upload_" + getUniqueId() + ".tmp";
|
||||
|
||||
File f = new File(tempDir, fileName);
|
||||
f.deleteOnExit();
|
||||
return f;
|
||||
}
|
||||
|
||||
|
||||
// -------------------------------------------------------- Private methods
|
||||
|
||||
|
||||
/**
|
||||
* Returns an identifier that is unique within the class loader used to
|
||||
* load this class, but does not have random-like apearance.
|
||||
*
|
||||
* @return A String with the non-random looking instance identifier.
|
||||
*/
|
||||
private static String getUniqueId()
|
||||
{
|
||||
int current;
|
||||
synchronized (DefaultFileItem.class)
|
||||
{
|
||||
current = counter++;
|
||||
}
|
||||
String id = Integer.toString(current);
|
||||
|
||||
// If you manage to get more than 100 million of ids, you'll
|
||||
// start getting ids longer than 8 characters.
|
||||
if (current < 100000000)
|
||||
{
|
||||
id = ("00000000" + id).substring(id.length());
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,234 @@
|
||||
/*
|
||||
* $Header: /home/cvs/jakarta-commons/fileupload/src/java/org/apache/commons/fileupload/DefaultFileItemFactory.java,v 1.2 2003/05/31 22:31:08 martinc Exp $
|
||||
* $Revision: 1.2 $
|
||||
* $Date: 2003/05/31 22:31:08 $
|
||||
*
|
||||
* ====================================================================
|
||||
*
|
||||
* The Apache Software License, Version 1.1
|
||||
*
|
||||
* Copyright (c) 2001-2003 The Apache Software Foundation. All rights
|
||||
* reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in
|
||||
* the documentation and/or other materials provided with the
|
||||
* distribution.
|
||||
*
|
||||
* 3. The end-user documentation included with the redistribution, if
|
||||
* any, must include the following acknowlegement:
|
||||
* "This product includes software developed by the
|
||||
* Apache Software Foundation (http://www.apache.org/)."
|
||||
* Alternately, this acknowlegement may appear in the software itself,
|
||||
* if and wherever such third-party acknowlegements normally appear.
|
||||
*
|
||||
* 4. The names "The Jakarta Project", "Commons", and "Apache Software
|
||||
* Foundation" must not be used to endorse or promote products derived
|
||||
* from this software without prior written permission. For written
|
||||
* permission, please contact apache@apache.org.
|
||||
*
|
||||
* 5. Products derived from this software may not be called "Apache"
|
||||
* nor may "Apache" appear in their names without prior written
|
||||
* permission of the Apache Group.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
|
||||
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
|
||||
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
|
||||
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
|
||||
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
* SUCH DAMAGE.
|
||||
* ====================================================================
|
||||
*
|
||||
* This software consists of voluntary contributions made by many
|
||||
* individuals on behalf of the Apache Software Foundation. For more
|
||||
* information on the Apache Software Foundation, please see
|
||||
* <http://www.apache.org/>.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
package org.apache.commons.fileupload;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
|
||||
/**
|
||||
* <p>The default {@link org.apache.commons.fileupload.FileItemFactory}
|
||||
* implementation. This implementation creates
|
||||
* {@link org.apache.commons.fileupload.FileItem} instances which keep their
|
||||
* content either in memory, for smaller items, or in a temporary file on disk,
|
||||
* for larger items. The size threshold, above which content will be stored on
|
||||
* disk, is configurable, as is the directory in which temporary files will be
|
||||
* created.</p>
|
||||
*
|
||||
* <p>If not otherwise configured, the default configuration values are as
|
||||
* follows:
|
||||
* <ul>
|
||||
* <li>Size threshold is 10KB.</li>
|
||||
* <li>Repository is the system default temp directory, as returned by
|
||||
* <code>System.getProperty("java.io.tmpdir")</code>.</li>
|
||||
* </ul>
|
||||
* </p>
|
||||
*
|
||||
* @author <a href="mailto:martinc@apache.org">Martin Cooper</a>
|
||||
*
|
||||
* @version $Id: DefaultFileItemFactory.java,v 1.2 2003/05/31 22:31:08 martinc Exp $
|
||||
*/
|
||||
public class DefaultFileItemFactory implements FileItemFactory
|
||||
{
|
||||
|
||||
// ----------------------------------------------------- Manifest constants
|
||||
|
||||
|
||||
/**
|
||||
* The default threshold above which uploads will be stored on disk.
|
||||
*/
|
||||
public static final int DEFAULT_SIZE_THRESHOLD = 10240;
|
||||
|
||||
|
||||
// ----------------------------------------------------- Instance Variables
|
||||
|
||||
|
||||
/**
|
||||
* The directory in which uploaded files will be stored, if stored on disk.
|
||||
*/
|
||||
private File repository;
|
||||
|
||||
|
||||
/**
|
||||
* The threshold above which uploads will be stored on disk.
|
||||
*/
|
||||
private int sizeThreshold = DEFAULT_SIZE_THRESHOLD;
|
||||
|
||||
|
||||
// ----------------------------------------------------------- Constructors
|
||||
|
||||
|
||||
/**
|
||||
* Constructs an unconfigured instance of this class. The resulting factory
|
||||
* may be configured by calling the appropriate setter methods.
|
||||
*/
|
||||
public DefaultFileItemFactory()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Constructs a preconfigured instance of this class.
|
||||
*
|
||||
* @param sizeThreshold The threshold, in bytes, below which items will be
|
||||
* retained in memory and above which they will be
|
||||
* stored as a file.
|
||||
* @param repository The data repository, which is the directory in
|
||||
* which files will be created, should the item size
|
||||
* exceed the threshold.
|
||||
*/
|
||||
public DefaultFileItemFactory(int sizeThreshold, File repository)
|
||||
{
|
||||
this.sizeThreshold = sizeThreshold;
|
||||
this.repository = repository;
|
||||
}
|
||||
|
||||
|
||||
// ------------------------------------------------------------- Properties
|
||||
|
||||
|
||||
/**
|
||||
* Returns the directory used to temporarily store files that are larger
|
||||
* than the configured size threshold.
|
||||
*
|
||||
* @return The directory in which temporary files will be located.
|
||||
*
|
||||
* @see #setRepository(java.io.File)
|
||||
*
|
||||
*/
|
||||
public File getRepository()
|
||||
{
|
||||
return repository;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the directory used to temporarily store files that are larger
|
||||
* than the configured size threshold.
|
||||
*
|
||||
* @param repository The directory in which temporary files will be located.
|
||||
*
|
||||
* @see #getRepository()
|
||||
*
|
||||
*/
|
||||
public void setRepository(File repository)
|
||||
{
|
||||
this.repository = repository;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the size threshold beyond which files are written directly to
|
||||
* disk. The default value is 1024 bytes.
|
||||
*
|
||||
* @return The size threshold, in bytes.
|
||||
*
|
||||
* @see #setSizeThreshold(int)
|
||||
*/
|
||||
public int getSizeThreshold()
|
||||
{
|
||||
return sizeThreshold;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the size threshold beyond which files are written directly to disk.
|
||||
*
|
||||
* @param sizeThreshold The size threshold, in bytes.
|
||||
*
|
||||
* @see #getSizeThreshold()
|
||||
*
|
||||
*/
|
||||
public void setSizeThreshold(int sizeThreshold)
|
||||
{
|
||||
this.sizeThreshold = sizeThreshold;
|
||||
}
|
||||
|
||||
|
||||
// --------------------------------------------------------- Public Methods
|
||||
|
||||
/**
|
||||
* Create a new {@link org.apache.commons.fileupload.DefaultFileItem}
|
||||
* instance from the supplied parameters and the local factory
|
||||
* configuration.
|
||||
*
|
||||
* @param fieldName The name of the form field.
|
||||
* @param contentType The content type of the form field.
|
||||
* @param isFormField <code>true</code> if this is a plain form field;
|
||||
* <code>false</code> otherwise.
|
||||
* @param fileName The name of the uploaded file, if any, as supplied
|
||||
* by the browser or other client.
|
||||
*
|
||||
* @return The newly created file item.
|
||||
*/
|
||||
public FileItem createItem(
|
||||
String fieldName,
|
||||
String contentType,
|
||||
boolean isFormField,
|
||||
String fileName
|
||||
)
|
||||
{
|
||||
if (isFormField == true) return new DefaultFileItem(fieldName, contentType, isFormField, fileName, sizeThreshold, repository);
|
||||
else return new DefaultFileItem(fieldName, contentType, isFormField, fileName, 0, repository);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,218 @@
|
||||
/*
|
||||
* $Header: /home/cvs/jakarta-commons/fileupload/src/java/org/apache/commons/fileupload/DeferredFileOutputStream.java,v 1.2 2003/05/31 22:31:08 martinc Exp $
|
||||
* $Revision: 1.2 $
|
||||
* $Date: 2003/05/31 22:31:08 $
|
||||
*
|
||||
* ====================================================================
|
||||
*
|
||||
* The Apache Software License, Version 1.1
|
||||
*
|
||||
* Copyright (c) 2001-2003 The Apache Software Foundation. All rights
|
||||
* reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in
|
||||
* the documentation and/or other materials provided with the
|
||||
* distribution.
|
||||
*
|
||||
* 3. The end-user documentation included with the redistribution, if
|
||||
* any, must include the following acknowlegement:
|
||||
* "This product includes software developed by the
|
||||
* Apache Software Foundation (http://www.apache.org/)."
|
||||
* Alternately, this acknowlegement may appear in the software itself,
|
||||
* if and wherever such third-party acknowlegements normally appear.
|
||||
*
|
||||
* 4. The names "The Jakarta Project", "Commons", and "Apache Software
|
||||
* Foundation" must not be used to endorse or promote products derived
|
||||
* from this software without prior written permission. For written
|
||||
* permission, please contact apache@apache.org.
|
||||
*
|
||||
* 5. Products derived from this software may not be called "Apache"
|
||||
* nor may "Apache" appear in their names without prior written
|
||||
* permission of the Apache Group.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
|
||||
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
|
||||
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
|
||||
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
|
||||
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
* SUCH DAMAGE.
|
||||
* ====================================================================
|
||||
*
|
||||
* This software consists of voluntary contributions made by many
|
||||
* individuals on behalf of the Apache Software Foundation. For more
|
||||
* information on the Apache Software Foundation, please see
|
||||
* <http://www.apache.org/>.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
package org.apache.commons.fileupload;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
/**
|
||||
* <p>An output stream which will retain data in memory until a specified
|
||||
* threshold is reached, and only then commit it to disk. If the stream is
|
||||
* closed before the threshold is reached, the data will not be written to
|
||||
* disk at all.</p>
|
||||
*
|
||||
* @author <a href="mailto:martinc@apache.org">Martin Cooper</a>
|
||||
*
|
||||
* @version $Id: DeferredFileOutputStream.java,v 1.2 2003/05/31 22:31:08 martinc Exp $
|
||||
*/
|
||||
public class DeferredFileOutputStream
|
||||
extends ThresholdingOutputStream
|
||||
{
|
||||
|
||||
// ----------------------------------------------------------- Data members
|
||||
|
||||
|
||||
/**
|
||||
* The output stream to which data will be written prior to the theshold
|
||||
* being reached.
|
||||
*/
|
||||
private ByteArrayOutputStream memoryOutputStream;
|
||||
|
||||
|
||||
/**
|
||||
* The output stream to which data will be written after the theshold is
|
||||
* reached.
|
||||
*/
|
||||
private FileOutputStream diskOutputStream;
|
||||
|
||||
|
||||
/**
|
||||
* The output stream to which data will be written at any given time. This
|
||||
* will always be one of <code>memoryOutputStream</code> or
|
||||
* <code>diskOutputStream</code>.
|
||||
*/
|
||||
private OutputStream currentOutputStream;
|
||||
|
||||
|
||||
/**
|
||||
* The file to which output will be directed if the threshold is exceeded.
|
||||
*/
|
||||
private File outputFile;
|
||||
|
||||
|
||||
// ----------------------------------------------------------- Constructors
|
||||
|
||||
|
||||
/**
|
||||
* Constructs an instance of this class which will trigger an event at the
|
||||
* specified threshold, and save data to a file beyond that point.
|
||||
*
|
||||
* @param threshold The number of bytes at which to trigger an event.
|
||||
* @param outputFile The file to which data is saved beyond the threshold.
|
||||
*/
|
||||
public DeferredFileOutputStream(int threshold, File outputFile)
|
||||
{
|
||||
super(threshold);
|
||||
this.outputFile = outputFile;
|
||||
|
||||
memoryOutputStream = new ByteArrayOutputStream(threshold);
|
||||
currentOutputStream = memoryOutputStream;
|
||||
}
|
||||
|
||||
|
||||
// --------------------------------------- ThresholdingOutputStream methods
|
||||
|
||||
|
||||
/**
|
||||
* Returns the current output stream. This may be memory based or disk
|
||||
* based, depending on the current state with respect to the threshold.
|
||||
*
|
||||
* @return The underlying output stream.
|
||||
*
|
||||
* @exception IOException if an error occurs.
|
||||
*/
|
||||
protected OutputStream getStream() throws IOException
|
||||
{
|
||||
return currentOutputStream;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Switches the underlying output stream from a memory based stream to one
|
||||
* that is backed by disk. This is the point at which we realise that too
|
||||
* much data is being written to keep in memory, so we elect to switch to
|
||||
* disk-based storage.
|
||||
*
|
||||
* @exception IOException if an error occurs.
|
||||
*/
|
||||
protected void thresholdReached() throws IOException
|
||||
{
|
||||
byte[] data = memoryOutputStream.toByteArray();
|
||||
FileOutputStream fos = new FileOutputStream(outputFile);
|
||||
fos.write(data);
|
||||
diskOutputStream = fos;
|
||||
currentOutputStream = fos;
|
||||
memoryOutputStream = null;
|
||||
}
|
||||
|
||||
|
||||
// --------------------------------------------------------- Public methods
|
||||
|
||||
|
||||
/**
|
||||
* Determines whether or not the data for this output stream has been
|
||||
* retained in memory.
|
||||
*
|
||||
* @return <code>true</code> if the data is available in memory;
|
||||
* <code>false</code> otherwise.
|
||||
*/
|
||||
public boolean isInMemory()
|
||||
{
|
||||
return (!isThresholdExceeded());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the data for this output stream as an array of bytes, assuming
|
||||
* that the data has been retained in memory. If the data was written to
|
||||
* disk, this method returns <code>null</code>.
|
||||
*
|
||||
* @return The data for this output stream, or <code>null</code> if no such
|
||||
* data is available.
|
||||
*/
|
||||
public byte[] getData()
|
||||
{
|
||||
if (memoryOutputStream != null)
|
||||
{
|
||||
return memoryOutputStream.toByteArray();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the data for this output stream as a <code>File</code>, assuming
|
||||
* that the data was written to disk. If the data was retained in memory,
|
||||
* this method returns <code>null</code>.
|
||||
*
|
||||
* @return The file for this output stream, or <code>null</code> if no such
|
||||
* file exists.
|
||||
*/
|
||||
public File getFile()
|
||||
{
|
||||
return outputFile;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,248 @@
|
||||
/*
|
||||
* $Header: /home/cvs/jakarta-commons/fileupload/src/java/org/apache/commons/fileupload/DiskFileUpload.java,v 1.3 2003/06/01 00:18:13 martinc Exp $
|
||||
* $Revision: 1.3 $
|
||||
* $Date: 2003/06/01 00:18:13 $
|
||||
*
|
||||
* ====================================================================
|
||||
*
|
||||
* The Apache Software License, Version 1.1
|
||||
*
|
||||
* Copyright (c) 2001-2003 The Apache Software Foundation. All rights
|
||||
* reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in
|
||||
* the documentation and/or other materials provided with the
|
||||
* distribution.
|
||||
*
|
||||
* 3. The end-user documentation included with the redistribution, if
|
||||
* any, must include the following acknowlegement:
|
||||
* "This product includes software developed by the
|
||||
* Apache Software Foundation (http://www.apache.org/)."
|
||||
* Alternately, this acknowlegement may appear in the software itself,
|
||||
* if and wherever such third-party acknowlegements normally appear.
|
||||
*
|
||||
* 4. The names "The Jakarta Project", "Commons", and "Apache Software
|
||||
* Foundation" must not be used to endorse or promote products derived
|
||||
* from this software without prior written permission. For written
|
||||
* permission, please contact apache@apache.org.
|
||||
*
|
||||
* 5. Products derived from this software may not be called "Apache"
|
||||
* nor may "Apache" appear in their names without prior written
|
||||
* permission of the Apache Group.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
|
||||
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
|
||||
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
|
||||
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
|
||||
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
* SUCH DAMAGE.
|
||||
* ====================================================================
|
||||
*
|
||||
* This software consists of voluntary contributions made by many
|
||||
* individuals on behalf of the Apache Software Foundation. For more
|
||||
* information on the Apache Software Foundation, please see
|
||||
* <http://www.apache.org/>.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
package org.apache.commons.fileupload;
|
||||
|
||||
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
|
||||
/**
|
||||
* <p>High level API for processing file uploads.</p>
|
||||
*
|
||||
* <p>This class handles multiple files per single HTML widget, sent using
|
||||
* <code>multipart/mixed</code> encoding type, as specified by
|
||||
* <a href="http://www.ietf.org/rfc/rfc1867.txt">RFC 1867</a>. Use {@link
|
||||
* #parseRequest(HttpServletRequest)} to acquire a list of {@link
|
||||
* org.apache.commons.fileupload.FileItem}s associated with a given HTML
|
||||
* widget.</p>
|
||||
*
|
||||
* <p>Individual parts will be stored in temporary disk storage or in memory,
|
||||
* depending on their size, and will be available as {@link
|
||||
* org.apache.commons.fileupload.FileItem}s.</p>
|
||||
*
|
||||
* @author <a href="mailto:Rafal.Krzewski@e-point.pl">Rafal Krzewski</a>
|
||||
* @author <a href="mailto:dlr@collab.net">Daniel Rall</a>
|
||||
* @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
|
||||
* @author <a href="mailto:jmcnally@collab.net">John McNally</a>
|
||||
* @author <a href="mailto:martinc@apache.org">Martin Cooper</a>
|
||||
* @author Sean C. Sullivan
|
||||
*
|
||||
* @version $Id: DiskFileUpload.java,v 1.3 2003/06/01 00:18:13 martinc Exp $
|
||||
*/
|
||||
public class DiskFileUpload
|
||||
extends FileUploadBase
|
||||
{
|
||||
|
||||
// ----------------------------------------------------------- Data members
|
||||
|
||||
|
||||
/**
|
||||
* The factory to use to create new form items.
|
||||
*/
|
||||
private DefaultFileItemFactory fileItemFactory;
|
||||
|
||||
|
||||
// ----------------------------------------------------------- Constructors
|
||||
|
||||
|
||||
/**
|
||||
* Constructs an instance of this class which uses the default factory to
|
||||
* create <code>FileItem</code> instances.
|
||||
*
|
||||
* @see #DiskFileUpload(DefaultFileItemFactory fileItemFactory)
|
||||
*/
|
||||
public DiskFileUpload()
|
||||
{
|
||||
super();
|
||||
this.fileItemFactory = new DefaultFileItemFactory();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Constructs an instance of this class which uses the supplied factory to
|
||||
* create <code>FileItem</code> instances.
|
||||
*
|
||||
* @see #DiskFileUpload()
|
||||
*/
|
||||
public DiskFileUpload(DefaultFileItemFactory fileItemFactory)
|
||||
{
|
||||
super();
|
||||
this.fileItemFactory = fileItemFactory;
|
||||
}
|
||||
|
||||
|
||||
// ----------------------------------------------------- Property accessors
|
||||
|
||||
|
||||
/**
|
||||
* Returns the factory class used when creating file items.
|
||||
*
|
||||
* @return The factory class for new file items.
|
||||
*/
|
||||
public FileItemFactory getFileItemFactory()
|
||||
{
|
||||
return fileItemFactory;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the factory class to use when creating file items. The factory must
|
||||
* be an instance of <code>DefaultFileItemFactory</code> or a subclass
|
||||
* thereof, or else a <code>ClassCastException</code> will be thrown.
|
||||
*
|
||||
* @param factory The factory class for new file items.
|
||||
*/
|
||||
public void setFileItemFactory(FileItemFactory factory)
|
||||
{
|
||||
this.fileItemFactory = (DefaultFileItemFactory) factory;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the size threshold beyond which files are written directly to
|
||||
* disk.
|
||||
*
|
||||
* @return The size threshold, in bytes.
|
||||
*
|
||||
* @see #setSizeThreshold(int)
|
||||
*/
|
||||
public int getSizeThreshold()
|
||||
{
|
||||
return fileItemFactory.getSizeThreshold();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the size threshold beyond which files are written directly to disk.
|
||||
*
|
||||
* @param sizeThreshold The size threshold, in bytes.
|
||||
*
|
||||
* @see #getSizeThreshold()
|
||||
*/
|
||||
public void setSizeThreshold(int sizeThreshold)
|
||||
{
|
||||
fileItemFactory.setSizeThreshold(sizeThreshold);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the location used to temporarily store files that are larger
|
||||
* than the configured size threshold.
|
||||
*
|
||||
* @return The path to the temporary file location.
|
||||
*
|
||||
* @see #setRepositoryPath(String)
|
||||
*/
|
||||
public String getRepositoryPath()
|
||||
{
|
||||
return fileItemFactory.getRepository().getPath();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the location used to temporarily store files that are larger
|
||||
* than the configured size threshold.
|
||||
*
|
||||
* @param repositoryPath The path to the temporary file location.
|
||||
*
|
||||
* @see #getRepositoryPath()
|
||||
*/
|
||||
public void setRepositoryPath(String repositoryPath)
|
||||
{
|
||||
fileItemFactory.setRepository(new File(repositoryPath));
|
||||
}
|
||||
|
||||
|
||||
// --------------------------------------------------------- Public methods
|
||||
|
||||
|
||||
/**
|
||||
* Processes an <a href="http://www.ietf.org/rfc/rfc1867.txt">RFC 1867</a>
|
||||
* compliant <code>multipart/form-data</code> stream. If files are stored
|
||||
* on disk, the path is given by <code>getRepository()</code>.
|
||||
*
|
||||
* @param req The servlet request to be parsed. Must be non-null.
|
||||
* @param sizeThreshold The max size in bytes to be stored in memory.
|
||||
* @param sizeMax The maximum allowed upload size, in bytes.
|
||||
* @param path The location where the files should be stored.
|
||||
*
|
||||
* @return A list of <code>FileItem</code> instances parsed from the
|
||||
* request, in the order that they were transmitted.
|
||||
*
|
||||
* @exception FileUploadException if there are problems reading/parsing
|
||||
* the request or storing files.
|
||||
*/
|
||||
public List /* FileItem */ parseRequest(HttpServletRequest req,
|
||||
int sizeThreshold,
|
||||
long sizeMax, String path)
|
||||
throws FileUploadException
|
||||
{
|
||||
setSizeThreshold(sizeThreshold);
|
||||
setSizeMax(sizeMax);
|
||||
setRepositoryPath(path);
|
||||
return parseRequest(req);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,275 @@
|
||||
/*
|
||||
* $Header: /home/cvs/jakarta-commons/fileupload/src/java/org/apache/commons/fileupload/FileItem.java,v 1.15 2003/06/01 17:33:24 martinc Exp $
|
||||
* $Revision: 1.15 $
|
||||
* $Date: 2003/06/01 17:33:24 $
|
||||
*
|
||||
* ====================================================================
|
||||
*
|
||||
* The Apache Software License, Version 1.1
|
||||
*
|
||||
* Copyright (c) 2001-2003 The Apache Software Foundation. All rights
|
||||
* reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in
|
||||
* the documentation and/or other materials provided with the
|
||||
* distribution.
|
||||
*
|
||||
* 3. The end-user documentation included with the redistribution, if
|
||||
* any, must include the following acknowlegement:
|
||||
* "This product includes software developed by the
|
||||
* Apache Software Foundation (http://www.apache.org/)."
|
||||
* Alternately, this acknowlegement may appear in the software itself,
|
||||
* if and wherever such third-party acknowlegements normally appear.
|
||||
*
|
||||
* 4. The names "The Jakarta Project", "Commons", and "Apache Software
|
||||
* Foundation" must not be used to endorse or promote products derived
|
||||
* from this software without prior written permission. For written
|
||||
* permission, please contact apache@apache.org.
|
||||
*
|
||||
* 5. Products derived from this software may not be called "Apache"
|
||||
* nor may "Apache" appear in their names without prior written
|
||||
* permission of the Apache Group.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
|
||||
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
|
||||
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
|
||||
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
|
||||
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
* SUCH DAMAGE.
|
||||
* ====================================================================
|
||||
*
|
||||
* This software consists of voluntary contributions made by many
|
||||
* individuals on behalf of the Apache Software Foundation. For more
|
||||
* information on the Apache Software Foundation, please see
|
||||
* <http://www.apache.org/>.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
package org.apache.commons.fileupload;
|
||||
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.Serializable;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
|
||||
|
||||
/**
|
||||
* <p> This class represents a file or form item that was received within a
|
||||
* <code>multipart/form-data</code> POST request.
|
||||
*
|
||||
* <p> After retrieving an instance of this class from a {@link
|
||||
* org.apache.commons.fileupload.FileUpload FileUpload} instance (see
|
||||
* {@link org.apache.commons.fileupload.FileUpload
|
||||
* #parseRequest(javax.servlet.http.HttpServletRequest)}), you may
|
||||
* either request all contents of the file at once using {@link #get()} or
|
||||
* request an {@link java.io.InputStream InputStream} with
|
||||
* {@link #getInputStream()} and process the file without attempting to load
|
||||
* it into memory, which may come handy with large files.
|
||||
*
|
||||
* <p> While this interface does not extend
|
||||
* <code>javax.activation.DataSource</code> per se (to avoid a seldom used
|
||||
* dependency), several of the defined methods are specifically defined with
|
||||
* the same signatures as methods in that interface. This allows an
|
||||
* implementation of this interface to also implement
|
||||
* <code>javax.activation.DataSource</code> with minimal additional work.
|
||||
*
|
||||
* @author <a href="mailto:Rafal.Krzewski@e-point.pl">Rafal Krzewski</a>
|
||||
* @author <a href="mailto:sean@informage.net">Sean Legassick</a>
|
||||
* @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
|
||||
* @author <a href="mailto:martinc@apache.org">Martin Cooper</a>
|
||||
*
|
||||
* @version $Id: FileItem.java,v 1.15 2003/06/01 17:33:24 martinc Exp $
|
||||
*/
|
||||
public interface FileItem
|
||||
extends Serializable
|
||||
{
|
||||
|
||||
|
||||
// ------------------------------- Methods from javax.activation.DataSource
|
||||
|
||||
|
||||
/**
|
||||
* Returns an {@link java.io.InputStream InputStream} that can be
|
||||
* used to retrieve the contents of the file.
|
||||
*
|
||||
* @return An {@link java.io.InputStream InputStream} that can be
|
||||
* used to retrieve the contents of the file.
|
||||
*
|
||||
* @exception IOException if an error occurs.
|
||||
*/
|
||||
InputStream getInputStream()
|
||||
throws IOException;
|
||||
|
||||
|
||||
/**
|
||||
* Returns the content type passed by the browser or <code>null</code> if
|
||||
* not defined.
|
||||
*
|
||||
* @return The content type passed by the browser or <code>null</code> if
|
||||
* not defined.
|
||||
*/
|
||||
String getContentType();
|
||||
|
||||
|
||||
/**
|
||||
* Returns the original filename in the client's filesystem, as provided by
|
||||
* the browser (or other client software). In most cases, this will be the
|
||||
* base file name, without path information. However, some clients, such as
|
||||
* the Opera browser, do include path information.
|
||||
*
|
||||
* @return The original filename in the client's filesystem.
|
||||
*/
|
||||
String getName();
|
||||
|
||||
|
||||
// ------------------------------------------------------- FileItem methods
|
||||
|
||||
|
||||
/**
|
||||
* Provides a hint as to whether or not the file contents will be read
|
||||
* from memory.
|
||||
*
|
||||
* @return <code>true</code> if the file contents will be read from memory;
|
||||
* <code>false</code> otherwise.
|
||||
*/
|
||||
boolean isInMemory();
|
||||
|
||||
|
||||
/**
|
||||
* Returns the size of the file item.
|
||||
*
|
||||
* @return The size of the file item, in bytes.
|
||||
*/
|
||||
long getSize();
|
||||
|
||||
|
||||
/**
|
||||
* Returns the contents of the file item as an array of bytes.
|
||||
*
|
||||
* @return The contents of the file item as an array of bytes.
|
||||
*/
|
||||
byte[] get();
|
||||
|
||||
|
||||
/**
|
||||
* Returns the contents of the file item as a String, using the specified
|
||||
* encoding. This method uses {@link #get()} to retrieve the
|
||||
* contents of the item.
|
||||
*
|
||||
* @param encoding The character encoding to use.
|
||||
*
|
||||
* @return The contents of the item, as a string.
|
||||
*
|
||||
* @exception UnsupportedEncodingException if the requested character
|
||||
* encoding is not available.
|
||||
*/
|
||||
String getString(String encoding)
|
||||
throws UnsupportedEncodingException;
|
||||
|
||||
|
||||
/**
|
||||
* Returns the contents of the file item as a String, using the default
|
||||
* character encoding. This method uses {@link #get()} to retrieve the
|
||||
* contents of the item.
|
||||
*
|
||||
* @return The contents of the item, as a string.
|
||||
*/
|
||||
String getString();
|
||||
|
||||
|
||||
/**
|
||||
* A convenience method to write an uploaded item to disk. The client code
|
||||
* is not concerned with whether or not the item is stored in memory, or on
|
||||
* disk in a temporary location. They just want to write the uploaded item
|
||||
* to a file.
|
||||
* <p>
|
||||
* This method is not guaranteed to succeed if called more than once for
|
||||
* the same item. This allows a particular implementation to use, for
|
||||
* example, file renaming, where possible, rather than copying all of the
|
||||
* underlying data, thus gaining a significant performance benefit.
|
||||
*
|
||||
* @param file The <code>File</code> into which the uploaded item should
|
||||
* be stored.
|
||||
*
|
||||
* @exception Exception if an error occurs.
|
||||
*/
|
||||
void write(File file) throws Exception;
|
||||
|
||||
|
||||
/**
|
||||
* Deletes the underlying storage for a file item, including deleting any
|
||||
* associated temporary disk file. Although this storage will be deleted
|
||||
* automatically when the <code>FileItem</code> instance is garbage
|
||||
* collected, this method can be used to ensure that this is done at an
|
||||
* earlier time, thus preserving system resources.
|
||||
*/
|
||||
void delete();
|
||||
|
||||
|
||||
/**
|
||||
* Returns the name of the field in the multipart form corresponding to
|
||||
* this file item.
|
||||
*
|
||||
* @return The name of the form field.
|
||||
*/
|
||||
String getFieldName();
|
||||
|
||||
|
||||
/**
|
||||
* Sets the field name used to reference this file item.
|
||||
*
|
||||
* @param name The name of the form field.
|
||||
*/
|
||||
void setFieldName(String name);
|
||||
|
||||
|
||||
/**
|
||||
* Determines whether or not a <code>FileItem</code> instance represents
|
||||
* a simple form field.
|
||||
*
|
||||
* @return <code>true</code> if the instance represents a simple form
|
||||
* field; <code>false</code> if it represents an uploaded file.
|
||||
*/
|
||||
boolean isFormField();
|
||||
|
||||
|
||||
/**
|
||||
* Specifies whether or not a <code>FileItem</code> instance represents
|
||||
* a simple form field.
|
||||
*
|
||||
* @param state <code>true</code> if the instance represents a simple form
|
||||
* field; <code>false</code> if it represents an uploaded file.
|
||||
*/
|
||||
void setFormField(boolean state);
|
||||
|
||||
|
||||
/**
|
||||
* Returns an {@link java.io.OutputStream OutputStream} that can
|
||||
* be used for storing the contents of the file.
|
||||
*
|
||||
* @return An {@link java.io.OutputStream OutputStream} that can be used
|
||||
* for storing the contensts of the file.
|
||||
*
|
||||
* @exception IOException if an error occurs.
|
||||
*/
|
||||
OutputStream getOutputStream() throws IOException;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
/*
|
||||
* $Header: /home/cvs/jakarta-commons/fileupload/src/java/org/apache/commons/fileupload/FileItemFactory.java,v 1.1 2003/04/27 17:30:06 martinc Exp $
|
||||
* $Revision: 1.1 $
|
||||
* $Date: 2003/04/27 17:30:06 $
|
||||
*
|
||||
* ====================================================================
|
||||
*
|
||||
* The Apache Software License, Version 1.1
|
||||
*
|
||||
* Copyright (c) 2001-2003 The Apache Software Foundation. All rights
|
||||
* reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in
|
||||
* the documentation and/or other materials provided with the
|
||||
* distribution.
|
||||
*
|
||||
* 3. The end-user documentation included with the redistribution, if
|
||||
* any, must include the following acknowlegement:
|
||||
* "This product includes software developed by the
|
||||
* Apache Software Foundation (http://www.apache.org/)."
|
||||
* Alternately, this acknowlegement may appear in the software itself,
|
||||
* if and wherever such third-party acknowlegements normally appear.
|
||||
*
|
||||
* 4. The names "The Jakarta Project", "Commons", and "Apache Software
|
||||
* Foundation" must not be used to endorse or promote products derived
|
||||
* from this software without prior written permission. For written
|
||||
* permission, please contact apache@apache.org.
|
||||
*
|
||||
* 5. Products derived from this software may not be called "Apache"
|
||||
* nor may "Apache" appear in their names without prior written
|
||||
* permission of the Apache Group.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
|
||||
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
|
||||
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
|
||||
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
|
||||
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
* SUCH DAMAGE.
|
||||
* ====================================================================
|
||||
*
|
||||
* This software consists of voluntary contributions made by many
|
||||
* individuals on behalf of the Apache Software Foundation. For more
|
||||
* information on the Apache Software Foundation, please see
|
||||
* <http://www.apache.org/>.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
package org.apache.commons.fileupload;
|
||||
|
||||
|
||||
/**
|
||||
* <p>A factory interface for creating {@link FileItem} instances. Factories
|
||||
* can provide their own custom configuration, over and above that provided
|
||||
* by the default file upload implementation.</p>
|
||||
*
|
||||
* @author <a href="mailto:martinc@apache.org">Martin Cooper</a>
|
||||
*
|
||||
* @version $Id: FileItemFactory.java,v 1.1 2003/04/27 17:30:06 martinc Exp $
|
||||
*/
|
||||
public interface FileItemFactory
|
||||
{
|
||||
|
||||
/**
|
||||
* Create a new {@link FileItem} instance from the supplied parameters and
|
||||
* any local factory configuration.
|
||||
*
|
||||
* @param fieldName The name of the form field.
|
||||
* @param contentType The content type of the form field.
|
||||
* @param isFormField <code>true</code> if this is a plain form field;
|
||||
* <code>false</code> otherwise.
|
||||
* @param fileName The name of the uploaded file, if any, as supplied
|
||||
* by the browser or other client.
|
||||
*
|
||||
* @return The newly created file item.
|
||||
*/
|
||||
FileItem createItem(
|
||||
String fieldName,
|
||||
String contentType,
|
||||
boolean isFormField,
|
||||
String fileName
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
/*
|
||||
* $Header: /home/cvs/jakarta-commons/fileupload/src/java/org/apache/commons/fileupload/FileUpload.java,v 1.23 2003/06/24 05:45:43 martinc Exp $
|
||||
* $Revision: 1.23 $
|
||||
* $Date: 2003/06/24 05:45:43 $
|
||||
*
|
||||
* ====================================================================
|
||||
*
|
||||
* The Apache Software License, Version 1.1
|
||||
*
|
||||
* Copyright (c) 2001-2003 The Apache Software Foundation. All rights
|
||||
* reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in
|
||||
* the documentation and/or other materials provided with the
|
||||
* distribution.
|
||||
*
|
||||
* 3. The end-user documentation included with the redistribution, if
|
||||
* any, must include the following acknowlegement:
|
||||
* "This product includes software developed by the
|
||||
* Apache Software Foundation (http://www.apache.org/)."
|
||||
* Alternately, this acknowlegement may appear in the software itself,
|
||||
* if and wherever such third-party acknowlegements normally appear.
|
||||
*
|
||||
* 4. The names "The Jakarta Project", "Commons", and "Apache Software
|
||||
* Foundation" must not be used to endorse or promote products derived
|
||||
* from this software without prior written permission. For written
|
||||
* permission, please contact apache@apache.org.
|
||||
*
|
||||
* 5. Products derived from this software may not be called "Apache"
|
||||
* nor may "Apache" appear in their names without prior written
|
||||
* permission of the Apache Group.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
|
||||
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
|
||||
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
|
||||
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
|
||||
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
* SUCH DAMAGE.
|
||||
* ====================================================================
|
||||
*
|
||||
* This software consists of voluntary contributions made by many
|
||||
* individuals on behalf of the Apache Software Foundation. For more
|
||||
* information on the Apache Software Foundation, please see
|
||||
* <http://www.apache.org/>.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
package org.apache.commons.fileupload;
|
||||
|
||||
|
||||
/**
|
||||
* <p>High level API for processing file uploads.</p>
|
||||
*
|
||||
* <p>This class handles multiple files per single HTML widget, sent using
|
||||
* <code>multipart/mixed</code> encoding type, as specified by
|
||||
* <a href="http://www.ietf.org/rfc/rfc1867.txt">RFC 1867</a>. Use {@link
|
||||
* #parseRequest(HttpServletRequest)} to acquire a list of {@link
|
||||
* org.apache.commons.fileupload.FileItem}s associated with a given HTML
|
||||
* widget.</p>
|
||||
*
|
||||
* <p>How the data for individual parts is stored is determined by the factory
|
||||
* used to create them; a given part may be in memory, on disk, or somewhere
|
||||
* else.</p>
|
||||
*
|
||||
* @author <a href="mailto:Rafal.Krzewski@e-point.pl">Rafal Krzewski</a>
|
||||
* @author <a href="mailto:dlr@collab.net">Daniel Rall</a>
|
||||
* @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
|
||||
* @author <a href="mailto:jmcnally@collab.net">John McNally</a>
|
||||
* @author <a href="mailto:martinc@apache.org">Martin Cooper</a>
|
||||
* @author Sean C. Sullivan
|
||||
*
|
||||
* @version $Id: FileUpload.java,v 1.23 2003/06/24 05:45:43 martinc Exp $
|
||||
*/
|
||||
public class FileUpload
|
||||
extends FileUploadBase
|
||||
{
|
||||
|
||||
// ----------------------------------------------------------- Data members
|
||||
|
||||
|
||||
/**
|
||||
* The factory to use to create new form items.
|
||||
*/
|
||||
private FileItemFactory fileItemFactory;
|
||||
|
||||
|
||||
// ----------------------------------------------------------- Constructors
|
||||
|
||||
|
||||
/**
|
||||
* Constructs an instance of this class which uses the default factory to
|
||||
* create <code>FileItem</code> instances.
|
||||
*
|
||||
* @see #FileUpload(FileItemFactory)
|
||||
*/
|
||||
public FileUpload()
|
||||
{
|
||||
super();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Constructs an instance of this class which uses the supplied factory to
|
||||
* create <code>FileItem</code> instances.
|
||||
*
|
||||
* @see #FileUpload()
|
||||
*/
|
||||
public FileUpload(FileItemFactory fileItemFactory)
|
||||
{
|
||||
super();
|
||||
this.fileItemFactory = fileItemFactory;
|
||||
}
|
||||
|
||||
|
||||
// ----------------------------------------------------- Property accessors
|
||||
|
||||
|
||||
/**
|
||||
* Returns the factory class used when creating file items.
|
||||
*
|
||||
* @return The factory class for new file items.
|
||||
*/
|
||||
public FileItemFactory getFileItemFactory()
|
||||
{
|
||||
return fileItemFactory;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the factory class to use when creating file items.
|
||||
*
|
||||
* @param factory The factory class for new file items.
|
||||
*/
|
||||
public void setFileItemFactory(FileItemFactory factory)
|
||||
{
|
||||
this.fileItemFactory = factory;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,685 @@
|
||||
/*
|
||||
* $Header: /home/cvs/jakarta-commons/fileupload/src/java/org/apache/commons/fileupload/FileUploadBase.java,v 1.3 2003/06/01 00:18:13 martinc Exp $
|
||||
* $Revision: 1.3 $
|
||||
* $Date: 2003/06/01 00:18:13 $
|
||||
*
|
||||
* ====================================================================
|
||||
*
|
||||
* The Apache Software License, Version 1.1
|
||||
*
|
||||
* Copyright (c) 2001-2003 The Apache Software Foundation. All rights
|
||||
* reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in
|
||||
* the documentation and/or other materials provided with the
|
||||
* distribution.
|
||||
*
|
||||
* 3. The end-user documentation included with the redistribution, if
|
||||
* any, must include the following acknowlegement:
|
||||
* "This product includes software developed by the
|
||||
* Apache Software Foundation (http://www.apache.org/)."
|
||||
* Alternately, this acknowlegement may appear in the software itself,
|
||||
* if and wherever such third-party acknowlegements normally appear.
|
||||
*
|
||||
* 4. The names "The Jakarta Project", "Commons", and "Apache Software
|
||||
* Foundation" must not be used to endorse or promote products derived
|
||||
* from this software without prior written permission. For written
|
||||
* permission, please contact apache@apache.org.
|
||||
*
|
||||
* 5. Products derived from this software may not be called "Apache"
|
||||
* nor may "Apache" appear in their names without prior written
|
||||
* permission of the Apache Group.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
|
||||
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
|
||||
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
|
||||
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
|
||||
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
* SUCH DAMAGE.
|
||||
* ====================================================================
|
||||
*
|
||||
* This software consists of voluntary contributions made by many
|
||||
* individuals on behalf of the Apache Software Foundation. For more
|
||||
* information on the Apache Software Foundation, please see
|
||||
* <http://www.apache.org/>.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
package org.apache.commons.fileupload;
|
||||
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
|
||||
/**
|
||||
* <p>High level API for processing file uploads.</p>
|
||||
*
|
||||
* <p>This class handles multiple files per single HTML widget, sent using
|
||||
* <code>multipart/mixed</code> encoding type, as specified by
|
||||
* <a href="http://www.ietf.org/rfc/rfc1867.txt">RFC 1867</a>. Use {@link
|
||||
* #parseRequest(HttpServletRequest)} to acquire a list of {@link
|
||||
* org.apache.commons.fileupload.FileItem}s associated with a given HTML
|
||||
* widget.</p>
|
||||
*
|
||||
* <p>How the data for individual parts is stored is determined by the factory
|
||||
* used to create them; a given part may be in memory, on disk, or somewhere
|
||||
* else.</p>
|
||||
*
|
||||
* @author <a href="mailto:Rafal.Krzewski@e-point.pl">Rafal Krzewski</a>
|
||||
* @author <a href="mailto:dlr@collab.net">Daniel Rall</a>
|
||||
* @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
|
||||
* @author <a href="mailto:jmcnally@collab.net">John McNally</a>
|
||||
* @author <a href="mailto:martinc@apache.org">Martin Cooper</a>
|
||||
* @author Sean C. Sullivan
|
||||
*
|
||||
* @version $Id: FileUploadBase.java,v 1.3 2003/06/01 00:18:13 martinc Exp $
|
||||
*/
|
||||
public abstract class FileUploadBase
|
||||
{
|
||||
|
||||
// ---------------------------------------------------------- Class methods
|
||||
|
||||
|
||||
/**
|
||||
* Utility method that determines whether the request contains multipart
|
||||
* content.
|
||||
*
|
||||
* @param req The servlet request to be evaluated. Must be non-null.
|
||||
*
|
||||
* @return <code>true</code> if the request is multipart;
|
||||
* <code>false</code> otherwise.
|
||||
*/
|
||||
public static final boolean isMultipartContent(HttpServletRequest req)
|
||||
{
|
||||
String contentType = req.getHeader(CONTENT_TYPE);
|
||||
if (contentType == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (contentType.startsWith(MULTIPART))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// ----------------------------------------------------- Manifest constants
|
||||
|
||||
|
||||
/**
|
||||
* HTTP content type header name.
|
||||
*/
|
||||
public static final String CONTENT_TYPE = "Content-type";
|
||||
|
||||
|
||||
/**
|
||||
* HTTP content disposition header name.
|
||||
*/
|
||||
public static final String CONTENT_DISPOSITION = "Content-disposition";
|
||||
|
||||
|
||||
/**
|
||||
* Content-disposition value for form data.
|
||||
*/
|
||||
public static final String FORM_DATA = "form-data";
|
||||
|
||||
|
||||
/**
|
||||
* Content-disposition value for file attachment.
|
||||
*/
|
||||
public static final String ATTACHMENT = "attachment";
|
||||
|
||||
|
||||
/**
|
||||
* Part of HTTP content type header.
|
||||
*/
|
||||
public static final String MULTIPART = "multipart/";
|
||||
|
||||
|
||||
/**
|
||||
* HTTP content type header for multipart forms.
|
||||
*/
|
||||
public static final String MULTIPART_FORM_DATA = "multipart/form-data";
|
||||
|
||||
|
||||
/**
|
||||
* HTTP content type header for multiple uploads.
|
||||
*/
|
||||
public static final String MULTIPART_MIXED = "multipart/mixed";
|
||||
|
||||
|
||||
/**
|
||||
* The maximum length of a single header line that will be parsed
|
||||
* (1024 bytes).
|
||||
*/
|
||||
public static final int MAX_HEADER_SIZE = 1024;
|
||||
|
||||
|
||||
// ----------------------------------------------------------- Data members
|
||||
|
||||
|
||||
/**
|
||||
* The maximum size permitted for an uploaded file. A value of -1 indicates
|
||||
* no maximum.
|
||||
*/
|
||||
private long sizeMax = -1;
|
||||
|
||||
|
||||
/**
|
||||
* The content encoding to use when reading part headers.
|
||||
*/
|
||||
private String headerEncoding;
|
||||
|
||||
|
||||
// ----------------------------------------------------- Property accessors
|
||||
|
||||
|
||||
/**
|
||||
* Returns the factory class used when creating file items.
|
||||
*
|
||||
* @return The factory class for new file items.
|
||||
*/
|
||||
public abstract FileItemFactory getFileItemFactory();
|
||||
|
||||
|
||||
/**
|
||||
* Sets the factory class to use when creating file items.
|
||||
*
|
||||
* @param factory The factory class for new file items.
|
||||
*/
|
||||
public abstract void setFileItemFactory(FileItemFactory factory);
|
||||
|
||||
|
||||
/**
|
||||
* Returns the maximum allowed upload size.
|
||||
*
|
||||
* @return The maximum allowed size, in bytes.
|
||||
*
|
||||
* @see #setSizeMax(long)
|
||||
*
|
||||
*/
|
||||
public long getSizeMax()
|
||||
{
|
||||
return sizeMax;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the maximum allowed upload size. If negative, there is no maximum.
|
||||
*
|
||||
* @param sizeMax The maximum allowed size, in bytes, or -1 for no maximum.
|
||||
*
|
||||
* @see #getSizeMax()
|
||||
*
|
||||
*/
|
||||
public void setSizeMax(long sizeMax)
|
||||
{
|
||||
this.sizeMax = sizeMax;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieves the character encoding used when reading the headers of an
|
||||
* individual part. When not specified, or <code>null</code>, the platform
|
||||
* default encoding is used.
|
||||
*
|
||||
* @return The encoding used to read part headers.
|
||||
*/
|
||||
public String getHeaderEncoding()
|
||||
{
|
||||
return headerEncoding;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Specifies the character encoding to be used when reading the headers of
|
||||
* individual parts. When not specified, or <code>null</code>, the platform
|
||||
* default encoding is used.
|
||||
*
|
||||
* @param encoding The encoding used to read part headers.
|
||||
*/
|
||||
public void setHeaderEncoding(String encoding)
|
||||
{
|
||||
headerEncoding = encoding;
|
||||
}
|
||||
|
||||
|
||||
// --------------------------------------------------------- Public methods
|
||||
|
||||
|
||||
/**
|
||||
* Processes an <a href="http://www.ietf.org/rfc/rfc1867.txt">RFC 1867</a>
|
||||
* compliant <code>multipart/form-data</code> stream. If files are stored
|
||||
* on disk, the path is given by <code>getRepository()</code>.
|
||||
*
|
||||
* @param req The servlet request to be parsed.
|
||||
*
|
||||
* @return A list of <code>FileItem</code> instances parsed from the
|
||||
* request, in the order that they were transmitted.
|
||||
*
|
||||
* @exception FileUploadException if there are problems reading/parsing
|
||||
* the request or storing files.
|
||||
*/
|
||||
public List /* FileItem */ parseRequest(HttpServletRequest req)
|
||||
throws FileUploadException
|
||||
{
|
||||
if (null == req)
|
||||
{
|
||||
throw new NullPointerException("req parameter");
|
||||
}
|
||||
|
||||
ArrayList items = new ArrayList();
|
||||
String contentType = req.getHeader(CONTENT_TYPE);
|
||||
|
||||
if ((null == contentType) || (!contentType.startsWith(MULTIPART)))
|
||||
{
|
||||
throw new InvalidContentTypeException(
|
||||
"the request doesn't contain a "
|
||||
+ MULTIPART_FORM_DATA
|
||||
+ " or "
|
||||
+ MULTIPART_MIXED
|
||||
+ " stream, content type header is "
|
||||
+ contentType);
|
||||
}
|
||||
int requestSize = req.getContentLength();
|
||||
|
||||
if (requestSize == -1)
|
||||
{
|
||||
throw new UnknownSizeException(
|
||||
"the request was rejected because it's size is unknown");
|
||||
}
|
||||
|
||||
if (sizeMax >= 0 && requestSize > sizeMax)
|
||||
{
|
||||
throw new SizeLimitExceededException(
|
||||
"the request was rejected because "
|
||||
+ "it's size exceeds allowed range");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
int boundaryIndex = contentType.indexOf("boundary=");
|
||||
if (boundaryIndex < 0)
|
||||
{
|
||||
throw new FileUploadException(
|
||||
"the request was rejected because "
|
||||
+ "no multipart boundary was found");
|
||||
}
|
||||
byte[] boundary = contentType.substring(
|
||||
boundaryIndex + 9).getBytes();
|
||||
|
||||
InputStream input = req.getInputStream();
|
||||
|
||||
MultipartStream multi = new MultipartStream(input, boundary);
|
||||
multi.setHeaderEncoding(headerEncoding);
|
||||
|
||||
boolean nextPart = multi.skipPreamble();
|
||||
while (nextPart)
|
||||
{
|
||||
Map headers = parseHeaders(multi.readHeaders());
|
||||
String fieldName = getFieldName(headers);
|
||||
if (fieldName != null)
|
||||
{
|
||||
String subContentType = getHeader(headers, CONTENT_TYPE);
|
||||
if (subContentType != null && subContentType
|
||||
.startsWith(MULTIPART_MIXED))
|
||||
{
|
||||
// Multiple files.
|
||||
byte[] subBoundary =
|
||||
subContentType.substring(
|
||||
subContentType
|
||||
.indexOf("boundary=") + 9).getBytes();
|
||||
multi.setBoundary(subBoundary);
|
||||
boolean nextSubPart = multi.skipPreamble();
|
||||
while (nextSubPart)
|
||||
{
|
||||
headers = parseHeaders(multi.readHeaders());
|
||||
if (getFileName(headers) != null)
|
||||
{
|
||||
FileItem item =
|
||||
createItem(headers, false);
|
||||
OutputStream os = item.getOutputStream();
|
||||
try
|
||||
{
|
||||
multi.readBodyData(os);
|
||||
}
|
||||
finally
|
||||
{
|
||||
os.close();
|
||||
}
|
||||
items.add(item);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Ignore anything but files inside
|
||||
// multipart/mixed.
|
||||
multi.discardBodyData();
|
||||
}
|
||||
nextSubPart = multi.readBoundary();
|
||||
}
|
||||
multi.setBoundary(boundary);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (getFileName(headers) != null)
|
||||
{
|
||||
// A single file.
|
||||
FileItem item = createItem(headers, false);
|
||||
OutputStream os = item.getOutputStream();
|
||||
try
|
||||
{
|
||||
multi.readBodyData(os);
|
||||
}
|
||||
finally
|
||||
{
|
||||
os.close();
|
||||
}
|
||||
items.add(item);
|
||||
}
|
||||
else
|
||||
{
|
||||
// A form field.
|
||||
FileItem item = createItem(headers, true);
|
||||
OutputStream os = item.getOutputStream();
|
||||
try
|
||||
{
|
||||
multi.readBodyData(os);
|
||||
}
|
||||
finally
|
||||
{
|
||||
os.close();
|
||||
}
|
||||
items.add(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Skip this part.
|
||||
multi.discardBodyData();
|
||||
}
|
||||
nextPart = multi.readBoundary();
|
||||
}
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
throw new FileUploadException(
|
||||
"Processing of " + MULTIPART_FORM_DATA
|
||||
+ " request failed. " + e.getMessage());
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
|
||||
// ------------------------------------------------------ Protected methods
|
||||
|
||||
|
||||
/**
|
||||
* Retrieves the file name from the <code>Content-disposition</code>
|
||||
* header.
|
||||
*
|
||||
* @param headers A <code>Map</code> containing the HTTP request headers.
|
||||
*
|
||||
* @return The file name for the current <code>encapsulation</code>.
|
||||
*/
|
||||
protected String getFileName(Map /* String, String */ headers)
|
||||
{
|
||||
String fileName = null;
|
||||
String cd = getHeader(headers, CONTENT_DISPOSITION);
|
||||
if (cd.startsWith(FORM_DATA) || cd.startsWith(ATTACHMENT))
|
||||
{
|
||||
int start = cd.indexOf("filename=\"");
|
||||
int end = cd.indexOf('"', start + 10);
|
||||
if (start != -1 && end != -1)
|
||||
{
|
||||
fileName = cd.substring(start + 10, end).trim();
|
||||
}
|
||||
}
|
||||
return fileName;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieves the field name from the <code>Content-disposition</code>
|
||||
* header.
|
||||
*
|
||||
* @param headers A <code>Map</code> containing the HTTP request headers.
|
||||
*
|
||||
* @return The field name for the current <code>encapsulation</code>.
|
||||
*/
|
||||
protected String getFieldName(Map /* String, String */ headers)
|
||||
{
|
||||
String fieldName = null;
|
||||
String cd = getHeader(headers, CONTENT_DISPOSITION);
|
||||
if (cd != null && cd.startsWith(FORM_DATA))
|
||||
{
|
||||
int start = cd.indexOf("name=\"");
|
||||
int end = cd.indexOf('"', start + 6);
|
||||
if (start != -1 && end != -1)
|
||||
{
|
||||
fieldName = cd.substring(start + 6, end);
|
||||
}
|
||||
}
|
||||
return fieldName;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new {@link FileItem} instance.
|
||||
*
|
||||
* @param headers A <code>Map</code> containing the HTTP request
|
||||
* headers.
|
||||
* @param isFormField Whether or not this item is a form field, as
|
||||
* opposed to a file.
|
||||
*
|
||||
* @return A newly created <code>FileItem</code> instance.
|
||||
*
|
||||
* @exception FileUploadException if an error occurs.
|
||||
*/
|
||||
protected FileItem createItem(Map /* String, String */ headers,
|
||||
boolean isFormField)
|
||||
throws FileUploadException
|
||||
{
|
||||
return getFileItemFactory().createItem(getFieldName(headers),
|
||||
getHeader(headers, CONTENT_TYPE),
|
||||
isFormField,
|
||||
getFileName(headers));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* <p> Parses the <code>header-part</code> and returns as key/value
|
||||
* pairs.
|
||||
*
|
||||
* <p> If there are multiple headers of the same names, the name
|
||||
* will map to a comma-separated list containing the values.
|
||||
*
|
||||
* @param headerPart The <code>header-part</code> of the current
|
||||
* <code>encapsulation</code>.
|
||||
*
|
||||
* @return A <code>Map</code> containing the parsed HTTP request headers.
|
||||
*/
|
||||
protected Map /* String, String */ parseHeaders(String headerPart)
|
||||
{
|
||||
Map headers = new HashMap();
|
||||
char buffer[] = new char[MAX_HEADER_SIZE];
|
||||
boolean done = false;
|
||||
int j = 0;
|
||||
int i;
|
||||
String header, headerName, headerValue;
|
||||
try
|
||||
{
|
||||
while (!done)
|
||||
{
|
||||
i = 0;
|
||||
// Copy a single line of characters into the buffer,
|
||||
// omitting trailing CRLF.
|
||||
while (i < 2 || buffer[i - 2] != '\r' || buffer[i - 1] != '\n')
|
||||
{
|
||||
buffer[i++] = headerPart.charAt(j++);
|
||||
}
|
||||
header = new String(buffer, 0, i - 2);
|
||||
if (header.equals(""))
|
||||
{
|
||||
done = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (header.indexOf(':') == -1)
|
||||
{
|
||||
// This header line is malformed, skip it.
|
||||
continue;
|
||||
}
|
||||
headerName = header.substring(0, header.indexOf(':'))
|
||||
.trim().toLowerCase();
|
||||
headerValue =
|
||||
header.substring(header.indexOf(':') + 1).trim();
|
||||
if (getHeader(headers, headerName) != null)
|
||||
{
|
||||
// More that one heder of that name exists,
|
||||
// append to the list.
|
||||
headers.put(headerName,
|
||||
getHeader(headers, headerName) + ','
|
||||
+ headerValue);
|
||||
}
|
||||
else
|
||||
{
|
||||
headers.put(headerName, headerValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (IndexOutOfBoundsException e)
|
||||
{
|
||||
// Headers were malformed. continue with all that was
|
||||
// parsed.
|
||||
}
|
||||
return headers;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the header with the specified name from the supplied map. The
|
||||
* header lookup is case-insensitive.
|
||||
*
|
||||
* @param headers A <code>Map</code> containing the HTTP request headers.
|
||||
* @param name The name of the header to return.
|
||||
*
|
||||
* @return The value of specified header, or a comma-separated list if
|
||||
* there were multiple headers of that name.
|
||||
*/
|
||||
protected final String getHeader(Map /* String, String */ headers,
|
||||
String name)
|
||||
{
|
||||
return (String) headers.get(name.toLowerCase());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Thrown to indicate that the request is not a multipart request.
|
||||
*/
|
||||
public static class InvalidContentTypeException
|
||||
extends FileUploadException
|
||||
{
|
||||
/**
|
||||
* Constructs a <code>InvalidContentTypeException</code> with no
|
||||
* detail message.
|
||||
*/
|
||||
public InvalidContentTypeException()
|
||||
{
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an <code>InvalidContentTypeException</code> with
|
||||
* the specified detail message.
|
||||
*
|
||||
* @param message The detail message.
|
||||
*/
|
||||
public InvalidContentTypeException(String message)
|
||||
{
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Thrown to indicate that the request size is not specified.
|
||||
*/
|
||||
public static class UnknownSizeException
|
||||
extends FileUploadException
|
||||
{
|
||||
/**
|
||||
* Constructs a <code>UnknownSizeException</code> with no
|
||||
* detail message.
|
||||
*/
|
||||
public UnknownSizeException()
|
||||
{
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an <code>UnknownSizeException</code> with
|
||||
* the specified detail message.
|
||||
*
|
||||
* @param message The detail message.
|
||||
*/
|
||||
public UnknownSizeException(String message)
|
||||
{
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Thrown to indicate that the request size exceeds the configured maximum.
|
||||
*/
|
||||
public static class SizeLimitExceededException
|
||||
extends FileUploadException
|
||||
{
|
||||
/**
|
||||
* Constructs a <code>SizeExceededException</code> with no
|
||||
* detail message.
|
||||
*/
|
||||
public SizeLimitExceededException()
|
||||
{
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an <code>SizeExceededException</code> with
|
||||
* the specified detail message.
|
||||
*
|
||||
* @param message The detail message.
|
||||
*/
|
||||
public SizeLimitExceededException(String message)
|
||||
{
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* $Header: /home/cvs/jakarta-commons/fileupload/src/java/org/apache/commons/fileupload/FileUploadException.java,v 1.7 2003/04/27 17:30:06 martinc Exp $
|
||||
* $Revision: 1.7 $
|
||||
* $Date: 2003/04/27 17:30:06 $
|
||||
*
|
||||
* ====================================================================
|
||||
*
|
||||
* The Apache Software License, Version 1.1
|
||||
*
|
||||
* Copyright (c) 2001-2003 The Apache Software Foundation. All rights
|
||||
* reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in
|
||||
* the documentation and/or other materials provided with the
|
||||
* distribution.
|
||||
*
|
||||
* 3. The end-user documentation included with the redistribution, if
|
||||
* any, must include the following acknowlegement:
|
||||
* "This product includes software developed by the
|
||||
* Apache Software Foundation (http://www.apache.org/)."
|
||||
* Alternately, this acknowlegement may appear in the software itself,
|
||||
* if and wherever such third-party acknowlegements normally appear.
|
||||
*
|
||||
* 4. The names "The Jakarta Project", "Commons", and "Apache Software
|
||||
* Foundation" must not be used to endorse or promote products derived
|
||||
* from this software without prior written permission. For written
|
||||
* permission, please contact apache@apache.org.
|
||||
*
|
||||
* 5. Products derived from this software may not be called "Apache"
|
||||
* nor may "Apache" appear in their names without prior written
|
||||
* permission of the Apache Group.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
|
||||
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
|
||||
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
|
||||
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
|
||||
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
* SUCH DAMAGE.
|
||||
* ====================================================================
|
||||
*
|
||||
* This software consists of voluntary contributions made by many
|
||||
* individuals on behalf of the Apache Software Foundation. For more
|
||||
* information on the Apache Software Foundation, please see
|
||||
* <http://www.apache.org/>.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
package org.apache.commons.fileupload;
|
||||
|
||||
|
||||
/**
|
||||
* Exception for errors encountered while processing the request.
|
||||
*
|
||||
* @author <a href="mailto:jmcnally@collab.net">John McNally</a>
|
||||
* @version $Id: FileUploadException.java,v 1.7 2003/04/27 17:30:06 martinc Exp $
|
||||
*/
|
||||
public class FileUploadException
|
||||
extends Exception
|
||||
{
|
||||
|
||||
/**
|
||||
* Constructs a new <code>FileUploadException</code> without message.
|
||||
*/
|
||||
public FileUploadException()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new <code>FileUploadException</code> with specified detail
|
||||
* message.
|
||||
*
|
||||
* @param msg the error message.
|
||||
*/
|
||||
public FileUploadException(String msg)
|
||||
{
|
||||
super(msg);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,936 @@
|
||||
/*
|
||||
* $Header: /home/cvs/jakarta-commons/fileupload/src/java/org/apache/commons/fileupload/MultipartStream.java,v 1.12 2003/06/01 00:18:13 martinc Exp $
|
||||
* $Revision: 1.12 $
|
||||
* $Date: 2003/06/01 00:18:13 $
|
||||
*
|
||||
* ====================================================================
|
||||
*
|
||||
* The Apache Software License, Version 1.1
|
||||
*
|
||||
* Copyright (c) 2001-2003 The Apache Software Foundation. All rights
|
||||
* reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in
|
||||
* the documentation and/or other materials provided with the
|
||||
* distribution.
|
||||
*
|
||||
* 3. The end-user documentation included with the redistribution, if
|
||||
* any, must include the following acknowlegement:
|
||||
* "This product includes software developed by the
|
||||
* Apache Software Foundation (http://www.apache.org/)."
|
||||
* Alternately, this acknowlegement may appear in the software itself,
|
||||
* if and wherever such third-party acknowlegements normally appear.
|
||||
*
|
||||
* 4. The names "The Jakarta Project", "Commons", and "Apache Software
|
||||
* Foundation" must not be used to endorse or promote products derived
|
||||
* from this software without prior written permission. For written
|
||||
* permission, please contact apache@apache.org.
|
||||
*
|
||||
* 5. Products derived from this software may not be called "Apache"
|
||||
* nor may "Apache" appear in their names without prior written
|
||||
* permission of the Apache Group.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
|
||||
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
|
||||
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
|
||||
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
|
||||
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
* SUCH DAMAGE.
|
||||
* ====================================================================
|
||||
*
|
||||
* This software consists of voluntary contributions made by many
|
||||
* individuals on behalf of the Apache Software Foundation. For more
|
||||
* information on the Apache Software Foundation, please see
|
||||
* <http://www.apache.org/>.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
package org.apache.commons.fileupload;
|
||||
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
|
||||
|
||||
/**
|
||||
* <p> Low level API for processing file uploads.
|
||||
*
|
||||
* <p> This class can be used to process data streams conforming to MIME
|
||||
* 'multipart' format as defined in
|
||||
* <a href="http://www.ietf.org/rfc/rfc1867.txt">RFC 1867</a>. Arbitrarily
|
||||
* large amounts of data in the stream can be processed under constant
|
||||
* memory usage.
|
||||
*
|
||||
* <p> The format of the stream is defined in the following way:<br>
|
||||
*
|
||||
* <code>
|
||||
* multipart-body := preamble 1*encapsulation close-delimiter epilogue<br>
|
||||
* encapsulation := delimiter body CRLF<br>
|
||||
* delimiter := "--" boundary CRLF<br>
|
||||
* close-delimiter := "--" boudary "--"<br>
|
||||
* preamble := <ignore><br>
|
||||
* epilogue := <ignore><br>
|
||||
* body := header-part CRLF body-part<br>
|
||||
* header-part := 1*header CRLF<br>
|
||||
* header := header-name ":" header-value<br>
|
||||
* header-name := <printable ascii characters except ":"><br>
|
||||
* header-value := <any ascii characters except CR & LF><br>
|
||||
* body-data := <arbitrary data><br>
|
||||
* </code>
|
||||
*
|
||||
* <p>Note that body-data can contain another mulipart entity. There
|
||||
* is limited support for single pass processing of such nested
|
||||
* streams. The nested stream is <strong>required</strong> to have a
|
||||
* boundary token of the same length as the parent stream (see {@link
|
||||
* #setBoundary(byte[])}).
|
||||
*
|
||||
* <p>Here is an exaple of usage of this class.<br>
|
||||
*
|
||||
* <pre>
|
||||
* try {
|
||||
* MultipartStream multipartStream = new MultipartStream(input,
|
||||
* boundary);
|
||||
* boolean nextPart = malitPartStream.skipPreamble();
|
||||
* OutputStream output;
|
||||
* while(nextPart) {
|
||||
* header = chunks.readHeader();
|
||||
* // process headers
|
||||
* // create some output stream
|
||||
* multipartStream.readBodyPart(output);
|
||||
* nextPart = multipartStream.readBoundary();
|
||||
* }
|
||||
* } catch(MultipartStream.MalformedStreamException e) {
|
||||
* // the stream failed to follow required syntax
|
||||
* } catch(IOException) {
|
||||
* // a read or write error occurred
|
||||
* }
|
||||
*
|
||||
* </pre>
|
||||
*
|
||||
* @author <a href="mailto:Rafal.Krzewski@e-point.pl">Rafal Krzewski</a>
|
||||
* @author <a href="mailto:martinc@apache.org">Martin Cooper</a>
|
||||
* @author Sean C. Sullivan
|
||||
*
|
||||
* @version $Id: MultipartStream.java,v 1.12 2003/06/01 00:18:13 martinc Exp $
|
||||
*/
|
||||
public class MultipartStream
|
||||
{
|
||||
|
||||
// ----------------------------------------------------- Manifest constants
|
||||
|
||||
|
||||
/**
|
||||
* The maximum length of <code>header-part</code> that will be
|
||||
* processed (10 kilobytes = 10240 bytes.).
|
||||
*/
|
||||
public static final int HEADER_PART_SIZE_MAX = 10240;
|
||||
|
||||
|
||||
/**
|
||||
* The default length of the buffer used for processing a request.
|
||||
*/
|
||||
protected static final int DEFAULT_BUFSIZE = 4096;
|
||||
|
||||
|
||||
/**
|
||||
* A byte sequence that marks the end of <code>header-part</code>
|
||||
* (<code>CRLFCRLF</code>).
|
||||
*/
|
||||
protected static final byte[] HEADER_SEPARATOR = {0x0D, 0x0A, 0x0D, 0x0A};
|
||||
|
||||
|
||||
/**
|
||||
* A byte sequence that that follows a delimiter that will be
|
||||
* followed by an encapsulation (<code>CRLF</code>).
|
||||
*/
|
||||
protected static final byte[] FIELD_SEPARATOR = { 0x0D, 0x0A };
|
||||
|
||||
|
||||
/**
|
||||
* A byte sequence that that follows a delimiter of the last
|
||||
* encapsulation in the stream (<code>--</code>).
|
||||
*/
|
||||
protected static final byte[] STREAM_TERMINATOR = { 0x2D, 0x2D };
|
||||
|
||||
|
||||
// ----------------------------------------------------------- Data members
|
||||
|
||||
|
||||
/**
|
||||
* The input stream from which data is read.
|
||||
*/
|
||||
private InputStream input;
|
||||
|
||||
|
||||
/**
|
||||
* The length of the boundary token plus the leading <code>CRLF--</code>.
|
||||
*/
|
||||
private int boundaryLength;
|
||||
|
||||
|
||||
/**
|
||||
* The amount of data, in bytes, that must be kept in the buffer in order
|
||||
* to detect delimiters reliably.
|
||||
*/
|
||||
private int keepRegion;
|
||||
|
||||
|
||||
/**
|
||||
* The byte sequence that partitions the stream.
|
||||
*/
|
||||
private byte[] boundary;
|
||||
|
||||
|
||||
/**
|
||||
* The length of the buffer used for processing the request.
|
||||
*/
|
||||
private int bufSize;
|
||||
|
||||
|
||||
/**
|
||||
* The buffer used for processing the request.
|
||||
*/
|
||||
private byte[] buffer;
|
||||
|
||||
|
||||
/**
|
||||
* The index of first valid character in the buffer.
|
||||
* <br>
|
||||
* 0 <= head < bufSize
|
||||
*/
|
||||
private int head;
|
||||
|
||||
|
||||
/**
|
||||
* The index of last valid characer in the buffer + 1.
|
||||
* <br>
|
||||
* 0 <= tail <= bufSize
|
||||
*/
|
||||
private int tail;
|
||||
|
||||
|
||||
/**
|
||||
* The content encoding to use when reading headers.
|
||||
*/
|
||||
private String headerEncoding;
|
||||
|
||||
|
||||
// ----------------------------------------------------------- Constructors
|
||||
|
||||
|
||||
/**
|
||||
* Default constructor.
|
||||
*
|
||||
* @see #MultipartStream(InputStream, byte[], int)
|
||||
* @see #MultipartStream(InputStream, byte[])
|
||||
*
|
||||
*/
|
||||
public MultipartStream()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* <p> Constructs a <code>MultipartStream</code> with a custom size buffer.
|
||||
*
|
||||
* <p> Note that the buffer must be at least big enough to contain the
|
||||
* boundary string, plus 4 characters for CR/LF and double dash, plus at
|
||||
* least one byte of data. Too small a buffer size setting will degrade
|
||||
* performance.
|
||||
*
|
||||
* @param input The <code>InputStream</code> to serve as a data source.
|
||||
* @param boundary The token used for dividing the stream into
|
||||
* <code>encapsulations</code>.
|
||||
* @param bufSize The size of the buffer to be used, in bytes.
|
||||
*
|
||||
*
|
||||
* @see #MultipartStream()
|
||||
* @see #MultipartStream(InputStream, byte[])
|
||||
*
|
||||
*/
|
||||
public MultipartStream(InputStream input,
|
||||
byte[] boundary,
|
||||
int bufSize)
|
||||
{
|
||||
this.input = input;
|
||||
this.bufSize = bufSize;
|
||||
this.buffer = new byte[bufSize];
|
||||
|
||||
// We prepend CR/LF to the boundary to chop trailng CR/LF from
|
||||
// body-data tokens.
|
||||
this.boundary = new byte[boundary.length + 4];
|
||||
this.boundaryLength = boundary.length + 4;
|
||||
this.keepRegion = boundary.length + 3;
|
||||
this.boundary[0] = 0x0D;
|
||||
this.boundary[1] = 0x0A;
|
||||
this.boundary[2] = 0x2D;
|
||||
this.boundary[3] = 0x2D;
|
||||
System.arraycopy(boundary, 0, this.boundary, 4, boundary.length);
|
||||
|
||||
head = 0;
|
||||
tail = 0;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* <p> Constructs a <code>MultipartStream</code> with a default size buffer.
|
||||
*
|
||||
* @param input The <code>InputStream</code> to serve as a data source.
|
||||
* @param boundary The token used for dividing the stream into
|
||||
* <code>encapsulations</code>.
|
||||
*
|
||||
* @exception IOException when an error occurs.
|
||||
*
|
||||
* @see #MultipartStream()
|
||||
* @see #MultipartStream(InputStream, byte[], int)
|
||||
*
|
||||
*/
|
||||
public MultipartStream(InputStream input,
|
||||
byte[] boundary)
|
||||
throws IOException
|
||||
{
|
||||
this(input, boundary, DEFAULT_BUFSIZE);
|
||||
}
|
||||
|
||||
|
||||
// --------------------------------------------------------- Public methods
|
||||
|
||||
|
||||
/**
|
||||
* Retrieves the character encoding used when reading the headers of an
|
||||
* individual part. When not specified, or <code>null</code>, the platform
|
||||
* default encoding is used.
|
||||
|
||||
*
|
||||
* @return The encoding used to read part headers.
|
||||
*/
|
||||
public String getHeaderEncoding()
|
||||
{
|
||||
return headerEncoding;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Specifies the character encoding to be used when reading the headers of
|
||||
* individual parts. When not specified, or <code>null</code>, the platform
|
||||
* default encoding is used.
|
||||
*
|
||||
* @param encoding The encoding used to read part headers.
|
||||
*/
|
||||
public void setHeaderEncoding(String encoding)
|
||||
{
|
||||
headerEncoding = encoding;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Reads a byte from the <code>buffer</code>, and refills it as
|
||||
* necessary.
|
||||
*
|
||||
* @return The next byte from the input stream.
|
||||
*
|
||||
* @exception IOException if there is no more data available.
|
||||
*/
|
||||
public byte readByte()
|
||||
throws IOException
|
||||
{
|
||||
// Buffer depleted ?
|
||||
if (head == tail)
|
||||
{
|
||||
head = 0;
|
||||
// Refill.
|
||||
tail = input.read(buffer, head, bufSize);
|
||||
if (tail == -1)
|
||||
{
|
||||
// No more data available.
|
||||
throw new IOException("No more data is available");
|
||||
}
|
||||
}
|
||||
return buffer[head++];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Skips a <code>boundary</code> token, and checks whether more
|
||||
* <code>encapsulations</code> are contained in the stream.
|
||||
*
|
||||
* @return <code>true</code> if there are more encapsulations in
|
||||
* this stream; <code>false</code> otherwise.
|
||||
*
|
||||
* @exception MalformedStreamException if the stream ends unexpecetedly or
|
||||
* fails to follow required syntax.
|
||||
*/
|
||||
public boolean readBoundary()
|
||||
throws MalformedStreamException
|
||||
{
|
||||
byte[] marker = new byte[2];
|
||||
boolean nextChunk = false;
|
||||
|
||||
head += boundaryLength;
|
||||
try
|
||||
{
|
||||
marker[0] = readByte();
|
||||
marker[1] = readByte();
|
||||
if (arrayequals(marker, STREAM_TERMINATOR, 2))
|
||||
{
|
||||
nextChunk = false;
|
||||
}
|
||||
else if (arrayequals(marker, FIELD_SEPARATOR, 2))
|
||||
{
|
||||
nextChunk = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new MalformedStreamException(
|
||||
"Unexpected characters follow a boundary");
|
||||
}
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
throw new MalformedStreamException("Stream ended unexpectedly");
|
||||
}
|
||||
return nextChunk;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* <p>Changes the boundary token used for partitioning the stream.
|
||||
*
|
||||
* <p>This method allows single pass processing of nested multipart
|
||||
* streams.
|
||||
*
|
||||
* <p>The boundary token of the nested stream is <code>required</code>
|
||||
* to be of the same length as the boundary token in parent stream.
|
||||
*
|
||||
* <p>Restoring the parent stream boundary token after processing of a
|
||||
* nested stream is left to the application.
|
||||
*
|
||||
* @param boundary The boundary to be used for parsing of the nested
|
||||
* stream.
|
||||
*
|
||||
* @exception IllegalBoundaryException if the <code>boundary</code>
|
||||
* has a different length than the one
|
||||
* being currently parsed.
|
||||
*/
|
||||
public void setBoundary(byte[] boundary)
|
||||
throws IllegalBoundaryException
|
||||
{
|
||||
if (boundary.length != boundaryLength - 4)
|
||||
{
|
||||
throw new IllegalBoundaryException(
|
||||
"The length of a boundary token can not be changed");
|
||||
}
|
||||
System.arraycopy(boundary, 0, this.boundary, 4, boundary.length);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* <p>Reads the <code>header-part</code> of the current
|
||||
* <code>encapsulation</code>.
|
||||
*
|
||||
* <p>Headers are returned verbatim to the input stream, including the
|
||||
* trailing <code>CRLF</code> marker. Parsing is left to the
|
||||
* application.
|
||||
*
|
||||
* <p><strong>TODO</strong> allow limiting maximum header size to
|
||||
* protect against abuse.
|
||||
*
|
||||
* @return The <code>header-part</code> of the current encapsulation.
|
||||
*
|
||||
* @exception MalformedStreamException if the stream ends unexpecetedly.
|
||||
*/
|
||||
public String readHeaders()
|
||||
throws MalformedStreamException
|
||||
{
|
||||
int i = 0;
|
||||
byte b[] = new byte[1];
|
||||
// to support multi-byte characters
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
int sizeMax = HEADER_PART_SIZE_MAX;
|
||||
int size = 0;
|
||||
while (i < 4)
|
||||
{
|
||||
try
|
||||
{
|
||||
b[0] = readByte();
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
throw new MalformedStreamException("Stream ended unexpectedly");
|
||||
}
|
||||
size++;
|
||||
if (b[0] == HEADER_SEPARATOR[i])
|
||||
{
|
||||
i++;
|
||||
}
|
||||
else
|
||||
{
|
||||
i = 0;
|
||||
}
|
||||
if (size <= sizeMax)
|
||||
{
|
||||
baos.write(b[0]);
|
||||
}
|
||||
}
|
||||
|
||||
String headers = null;
|
||||
if (headerEncoding != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
headers = baos.toString(headerEncoding);
|
||||
}
|
||||
catch (UnsupportedEncodingException e)
|
||||
{
|
||||
// Fall back to platform default if specified encoding is not
|
||||
// supported.
|
||||
headers = baos.toString();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
headers = baos.toString();
|
||||
}
|
||||
|
||||
return headers;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* <p>Reads <code>body-data</code> from the current
|
||||
* <code>encapsulation</code> and writes its contents into the
|
||||
* output <code>Stream</code>.
|
||||
*
|
||||
* <p>Arbitrary large amounts of data can be processed by this
|
||||
* method using a constant size buffer. (see {@link
|
||||
* #MultipartStream(InputStream,byte[],int) constructor}).
|
||||
*
|
||||
* @param output The <code>Stream</code> to write data into.
|
||||
*
|
||||
* @return the amount of data written.
|
||||
*
|
||||
* @exception MalformedStreamException if the stream ends unexpectedly.
|
||||
* @exception IOException if an i/o error occurs.
|
||||
*/
|
||||
public int readBodyData(OutputStream output)
|
||||
throws MalformedStreamException,
|
||||
IOException
|
||||
{
|
||||
boolean done = false;
|
||||
int pad;
|
||||
int pos;
|
||||
int bytesRead;
|
||||
int total = 0;
|
||||
while (!done)
|
||||
{
|
||||
// Is boundary token present somewere in the buffer?
|
||||
pos = findSeparator();
|
||||
if (pos != -1)
|
||||
{
|
||||
// Write the rest of the data before the boundary.
|
||||
output.write(buffer, head, pos - head);
|
||||
total += pos - head;
|
||||
head = pos;
|
||||
done = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Determine how much data should be kept in the
|
||||
// buffer.
|
||||
if (tail - head > keepRegion)
|
||||
{
|
||||
pad = keepRegion;
|
||||
}
|
||||
else
|
||||
{
|
||||
pad = tail - head;
|
||||
}
|
||||
// Write out the data belonging to the body-data.
|
||||
output.write(buffer, head, tail - head - pad);
|
||||
|
||||
// Move the data to the beging of the buffer.
|
||||
total += tail - head - pad;
|
||||
System.arraycopy(buffer, tail - pad, buffer, 0, pad);
|
||||
|
||||
// Refill buffer with new data.
|
||||
head = 0;
|
||||
bytesRead = input.read(buffer, pad, bufSize - pad);
|
||||
|
||||
// [pprrrrrrr]
|
||||
if (bytesRead != -1)
|
||||
{
|
||||
tail = pad + bytesRead;
|
||||
}
|
||||
else
|
||||
{
|
||||
// The last pad amount is left in the buffer.
|
||||
// Boundary can't be in there so write out the
|
||||
// data you have and signal an error condition.
|
||||
output.write(buffer, 0, pad);
|
||||
output.flush();
|
||||
total += pad;
|
||||
throw new MalformedStreamException(
|
||||
"Stream ended unexpectedly");
|
||||
}
|
||||
}
|
||||
}
|
||||
output.flush();
|
||||
return total;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* <p> Reads <code>body-data</code> from the current
|
||||
* <code>encapsulation</code> and discards it.
|
||||
*
|
||||
* <p>Use this method to skip encapsulations you don't need or don't
|
||||
* understand.
|
||||
*
|
||||
* @return The amount of data discarded.
|
||||
*
|
||||
* @exception MalformedStreamException if the stream ends unexpectedly.
|
||||
* @exception IOException if an i/o error occurs.
|
||||
*/
|
||||
public int discardBodyData()
|
||||
throws MalformedStreamException,
|
||||
IOException
|
||||
{
|
||||
boolean done = false;
|
||||
int pad;
|
||||
int pos;
|
||||
int bytesRead;
|
||||
int total = 0;
|
||||
while (!done)
|
||||
{
|
||||
// Is boundary token present somewere in the buffer?
|
||||
pos = findSeparator();
|
||||
if (pos != -1)
|
||||
{
|
||||
// Write the rest of the data before the boundary.
|
||||
total += pos - head;
|
||||
head = pos;
|
||||
done = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Determine how much data should be kept in the
|
||||
// buffer.
|
||||
if (tail - head > keepRegion)
|
||||
{
|
||||
pad = keepRegion;
|
||||
}
|
||||
else
|
||||
{
|
||||
pad = tail - head;
|
||||
}
|
||||
total += tail - head - pad;
|
||||
|
||||
// Move the data to the beging of the buffer.
|
||||
System.arraycopy(buffer, tail - pad, buffer, 0, pad);
|
||||
|
||||
// Refill buffer with new data.
|
||||
head = 0;
|
||||
bytesRead = input.read(buffer, pad, bufSize - pad);
|
||||
|
||||
// [pprrrrrrr]
|
||||
if (bytesRead != -1)
|
||||
{
|
||||
tail = pad + bytesRead;
|
||||
}
|
||||
else
|
||||
{
|
||||
// The last pad amount is left in the buffer.
|
||||
// Boundary can't be in there so signal an error
|
||||
// condition.
|
||||
total += pad;
|
||||
throw new MalformedStreamException(
|
||||
"Stream ended unexpectedly");
|
||||
}
|
||||
}
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Finds the beginning of the first <code>encapsulation</code>.
|
||||
*
|
||||
* @return <code>true</code> if an <code>encapsulation</code> was found in
|
||||
* the stream.
|
||||
*
|
||||
* @exception IOException if an i/o error occurs.
|
||||
*/
|
||||
public boolean skipPreamble()
|
||||
throws IOException
|
||||
{
|
||||
// First delimiter may be not preceeded with a CRLF.
|
||||
System.arraycopy(boundary, 2, boundary, 0, boundary.length - 2);
|
||||
boundaryLength = boundary.length - 2;
|
||||
try
|
||||
{
|
||||
// Discard all data up to the delimiter.
|
||||
discardBodyData();
|
||||
|
||||
// Read boundary - if succeded, the stream contains an
|
||||
// encapsulation.
|
||||
return readBoundary();
|
||||
}
|
||||
catch (MalformedStreamException e)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Restore delimiter.
|
||||
System.arraycopy(boundary, 0, boundary, 2, boundary.length - 2);
|
||||
boundaryLength = boundary.length;
|
||||
boundary[0] = 0x0D;
|
||||
boundary[1] = 0x0A;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Compares <code>count</code> first bytes in the arrays
|
||||
* <code>a</code> and <code>b</code>.
|
||||
*
|
||||
* @param a The first array to compare.
|
||||
* @param b The second array to compare.
|
||||
* @param count How many bytes should be compared.
|
||||
*
|
||||
* @return <code>true</code> if <code>count</code> first bytes in arrays
|
||||
* <code>a</code> and <code>b</code> are equal.
|
||||
*/
|
||||
public static boolean arrayequals(byte[] a,
|
||||
byte[] b,
|
||||
int count)
|
||||
{
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
if (a[i] != b[i])
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Searches for a byte of specified value in the <code>buffer</code>,
|
||||
* starting at the specified <code>position</code>.
|
||||
*
|
||||
* @param value The value to find.
|
||||
* @param pos The starting position for searching.
|
||||
*
|
||||
* @return The position of byte found, counting from beginning of the
|
||||
* <code>buffer</code>, or <code>-1</code> if not found.
|
||||
*/
|
||||
protected int findByte(byte value,
|
||||
int pos)
|
||||
{
|
||||
for (int i = pos; i < tail; i++)
|
||||
{
|
||||
if (buffer[i] == value)
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Searches for the <code>boundary</code> in the <code>buffer</code>
|
||||
* region delimited by <code>head</code> and <code>tail</code>.
|
||||
*
|
||||
* @return The position of the boundary found, counting from the
|
||||
* beginning of the <code>buffer</code>, or <code>-1</code> if
|
||||
* not found.
|
||||
*/
|
||||
protected int findSeparator()
|
||||
{
|
||||
int first;
|
||||
int match = 0;
|
||||
int maxpos = tail - boundaryLength;
|
||||
for (first = head;
|
||||
(first <= maxpos) && (match != boundaryLength);
|
||||
first++)
|
||||
{
|
||||
first = findByte(boundary[0], first);
|
||||
if (first == -1 || (first > maxpos))
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
for (match = 1; match < boundaryLength; match++)
|
||||
{
|
||||
if (buffer[first + match] != boundary[match])
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (match == boundaryLength)
|
||||
{
|
||||
return first - 1;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a string representation of this object.
|
||||
*
|
||||
* @return The string representation of this object.
|
||||
*/
|
||||
public String toString()
|
||||
{
|
||||
StringBuffer sbTemp = new StringBuffer();
|
||||
sbTemp.append("boundary='");
|
||||
sbTemp.append(String.valueOf(boundary));
|
||||
sbTemp.append("'\nbufSize=");
|
||||
sbTemp.append(bufSize);
|
||||
return sbTemp.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Thrown to indicate that the input stream fails to follow the
|
||||
* required syntax.
|
||||
*/
|
||||
public class MalformedStreamException
|
||||
extends IOException
|
||||
{
|
||||
/**
|
||||
* Constructs a <code>MalformedStreamException</code> with no
|
||||
* detail message.
|
||||
*/
|
||||
public MalformedStreamException()
|
||||
{
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an <code>MalformedStreamException</code> with
|
||||
* the specified detail message.
|
||||
*
|
||||
* @param message The detail message.
|
||||
*/
|
||||
public MalformedStreamException(String message)
|
||||
{
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Thrown upon attempt of setting an invalid boundary token.
|
||||
*/
|
||||
public class IllegalBoundaryException
|
||||
extends IOException
|
||||
{
|
||||
/**
|
||||
* Constructs an <code>IllegalBoundaryException</code> with no
|
||||
* detail message.
|
||||
*/
|
||||
public IllegalBoundaryException()
|
||||
{
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an <code>IllegalBoundaryException</code> with
|
||||
* the specified detail message.
|
||||
*
|
||||
* @param message The detail message.
|
||||
*/
|
||||
public IllegalBoundaryException(String message)
|
||||
{
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ------------------------------------------------------ Debugging methods
|
||||
|
||||
|
||||
// These are the methods that were used to debug this stuff.
|
||||
/*
|
||||
|
||||
// Dump data.
|
||||
protected void dump()
|
||||
{
|
||||
System.out.println("01234567890");
|
||||
byte[] temp = new byte[buffer.length];
|
||||
for(int i=0; i<buffer.length; i++)
|
||||
{
|
||||
if (buffer[i] == 0x0D || buffer[i] == 0x0A)
|
||||
{
|
||||
temp[i] = 0x21;
|
||||
}
|
||||
else
|
||||
{
|
||||
temp[i] = buffer[i];
|
||||
}
|
||||
}
|
||||
System.out.println(new String(temp));
|
||||
int i;
|
||||
for (i=0; i<head; i++)
|
||||
System.out.print(" ");
|
||||
System.out.println("h");
|
||||
for (i=0; i<tail; i++)
|
||||
System.out.print(" ");
|
||||
System.out.println("t");
|
||||
System.out.flush();
|
||||
}
|
||||
|
||||
// Main routine, for testing purposes only.
|
||||
//
|
||||
// @param args A String[] with the command line arguments.
|
||||
// @exception Exception, a generic exception.
|
||||
public static void main( String[] args )
|
||||
throws Exception
|
||||
{
|
||||
File boundaryFile = new File("boundary.dat");
|
||||
int boundarySize = (int)boundaryFile.length();
|
||||
byte[] boundary = new byte[boundarySize];
|
||||
FileInputStream input = new FileInputStream(boundaryFile);
|
||||
input.read(boundary,0,boundarySize);
|
||||
|
||||
input = new FileInputStream("multipart.dat");
|
||||
MultipartStream chunks = new MultipartStream(input, boundary);
|
||||
|
||||
int i = 0;
|
||||
String header;
|
||||
OutputStream output;
|
||||
boolean nextChunk = chunks.skipPreamble();
|
||||
while (nextChunk)
|
||||
{
|
||||
header = chunks.readHeaders();
|
||||
System.out.println("!"+header+"!");
|
||||
System.out.println("wrote part"+i+".dat");
|
||||
output = new FileOutputStream("part"+(i++)+".dat");
|
||||
chunks.readBodyData(output);
|
||||
nextChunk = chunks.readBoundary();
|
||||
}
|
||||
}
|
||||
|
||||
*/
|
||||
}
|
||||
@@ -0,0 +1,294 @@
|
||||
/*
|
||||
* $Header: /home/cvs/jakarta-commons/fileupload/src/java/org/apache/commons/fileupload/ThresholdingOutputStream.java,v 1.3 2003/05/31 22:31:08 martinc Exp $
|
||||
* $Revision: 1.3 $
|
||||
* $Date: 2003/05/31 22:31:08 $
|
||||
*
|
||||
* ====================================================================
|
||||
*
|
||||
* The Apache Software License, Version 1.1
|
||||
*
|
||||
* Copyright (c) 2001-2003 The Apache Software Foundation. All rights
|
||||
* reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in
|
||||
* the documentation and/or other materials provided with the
|
||||
* distribution.
|
||||
*
|
||||
* 3. The end-user documentation included with the redistribution, if
|
||||
* any, must include the following acknowlegement:
|
||||
* "This product includes software developed by the
|
||||
* Apache Software Foundation (http://www.apache.org/)."
|
||||
* Alternately, this acknowlegement may appear in the software itself,
|
||||
* if and wherever such third-party acknowlegements normally appear.
|
||||
*
|
||||
* 4. The names "The Jakarta Project", "Commons", and "Apache Software
|
||||
* Foundation" must not be used to endorse or promote products derived
|
||||
* from this software without prior written permission. For written
|
||||
* permission, please contact apache@apache.org.
|
||||
*
|
||||
* 5. Products derived from this software may not be called "Apache"
|
||||
* nor may "Apache" appear in their names without prior written
|
||||
* permission of the Apache Group.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
|
||||
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
|
||||
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
|
||||
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
|
||||
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
* SUCH DAMAGE.
|
||||
* ====================================================================
|
||||
*
|
||||
* This software consists of voluntary contributions made by many
|
||||
* individuals on behalf of the Apache Software Foundation. For more
|
||||
* information on the Apache Software Foundation, please see
|
||||
* <http://www.apache.org/>.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
package org.apache.commons.fileupload;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
|
||||
/**
|
||||
* An output stream which triggers an event when a specified number of bytes of
|
||||
* data have been written to it. The event can be used, for example, to throw
|
||||
* an exception if a maximum has been reached, or to switch the underlying
|
||||
* stream type when the threshold is exceeded.
|
||||
* <p>
|
||||
* This class overrides all <code>OutputStream</code> methods. However, these
|
||||
* overrides ultimately call the corresponding methods in the underlying output
|
||||
* stream implementation.
|
||||
* <p>
|
||||
* NOTE: This implementation may trigger the event <em>before</em> the threshold
|
||||
* is actually reached, since it triggers when a pending write operation would
|
||||
* cause the threshold to be exceeded.
|
||||
*
|
||||
* @author <a href="mailto:martinc@apache.org">Martin Cooper</a>
|
||||
*
|
||||
* @version $Id: ThresholdingOutputStream.java,v 1.3 2003/05/31 22:31:08 martinc Exp $
|
||||
*/
|
||||
public abstract class ThresholdingOutputStream
|
||||
extends OutputStream
|
||||
{
|
||||
|
||||
// ----------------------------------------------------------- Data members
|
||||
|
||||
|
||||
/**
|
||||
* The threshold at which the event will be triggered.
|
||||
*/
|
||||
private int threshold;
|
||||
|
||||
|
||||
/**
|
||||
* The number of bytes written to the output stream.
|
||||
*/
|
||||
private long written;
|
||||
|
||||
|
||||
/**
|
||||
* Whether or not the configured threshold has been exceeded.
|
||||
*/
|
||||
private boolean thresholdExceeded;
|
||||
|
||||
|
||||
// ----------------------------------------------------------- Constructors
|
||||
|
||||
|
||||
/**
|
||||
* Constructs an instance of this class which will trigger an event at the
|
||||
* specified threshold.
|
||||
*
|
||||
* @param threshold The number of bytes at which to trigger an event.
|
||||
*/
|
||||
public ThresholdingOutputStream(int threshold)
|
||||
{
|
||||
this.threshold = threshold;
|
||||
}
|
||||
|
||||
|
||||
// --------------------------------------------------- OutputStream methods
|
||||
|
||||
|
||||
/**
|
||||
* Writes the specified byte to this output stream.
|
||||
*
|
||||
* @param b The byte to be written.
|
||||
*
|
||||
* @exception IOException if an error occurs.
|
||||
*/
|
||||
public void write(int b) throws IOException
|
||||
{
|
||||
checkThreshold(1);
|
||||
getStream().write(b);
|
||||
written++;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Writes <code>b.length</code> bytes from the specified byte array to this
|
||||
* output stream.
|
||||
*
|
||||
* @param b The array of bytes to be written.
|
||||
*
|
||||
* @exception IOException if an error occurs.
|
||||
*/
|
||||
public void write(byte b[]) throws IOException
|
||||
{
|
||||
checkThreshold(b.length);
|
||||
getStream().write(b);
|
||||
written += b.length;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Writes <code>len</code> bytes from the specified byte array starting at
|
||||
* offset <code>off</code> to this output stream.
|
||||
*
|
||||
* @param b The byte array from which the data will be written.
|
||||
* @param off The start offset in the byte array.
|
||||
* @param len The number of bytes to write.
|
||||
*
|
||||
* @exception IOException if an error occurs.
|
||||
*/
|
||||
public void write(byte b[], int off, int len) throws IOException
|
||||
{
|
||||
checkThreshold(len);
|
||||
getStream().write(b, off, len);
|
||||
written += len;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Flushes this output stream and forces any buffered output bytes to be
|
||||
* written out.
|
||||
*
|
||||
* @exception IOException if an error occurs.
|
||||
*/
|
||||
public void flush() throws IOException
|
||||
{
|
||||
getStream().flush();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Closes this output stream and releases any system resources associated
|
||||
* with this stream.
|
||||
*
|
||||
* @exception IOException if an error occurs.
|
||||
*/
|
||||
public void close() throws IOException
|
||||
{
|
||||
try
|
||||
{
|
||||
flush();
|
||||
}
|
||||
catch (IOException ignored)
|
||||
{
|
||||
// ignore
|
||||
}
|
||||
getStream().close();
|
||||
}
|
||||
|
||||
|
||||
// --------------------------------------------------------- Public methods
|
||||
|
||||
|
||||
/**
|
||||
* Returns the threshold, in bytes, at which an event will be triggered.
|
||||
*
|
||||
* @return The threshold point, in bytes.
|
||||
*/
|
||||
public int getThreshold()
|
||||
{
|
||||
return threshold;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the number of bytes that have been written to this output stream.
|
||||
*
|
||||
* @return The number of bytes written.
|
||||
*/
|
||||
public long getByteCount()
|
||||
{
|
||||
return written;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Determines whether or not the configured threshold has been exceeded for
|
||||
* this output stream.
|
||||
*
|
||||
* @return <code>true</code> if the threshold has been reached;
|
||||
* <code>false</code> otherwise.
|
||||
*/
|
||||
public boolean isThresholdExceeded()
|
||||
{
|
||||
return (written > threshold);
|
||||
}
|
||||
|
||||
|
||||
// ------------------------------------------------------ Protected methods
|
||||
|
||||
|
||||
/**
|
||||
* Checks to see if writing the specified number of bytes would cause the
|
||||
* configured threshold to be exceeded. If so, triggers an event to allow
|
||||
* a concrete implementation to take action on this.
|
||||
*
|
||||
* @param count The number of bytes about to be written to the underlying
|
||||
* output stream.
|
||||
*
|
||||
* @exception IOException if an error occurs.
|
||||
*/
|
||||
protected void checkThreshold(int count) throws IOException
|
||||
{
|
||||
if (!thresholdExceeded && (written + count > threshold))
|
||||
{
|
||||
thresholdReached();
|
||||
thresholdExceeded = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ------------------------------------------------------- Abstract methods
|
||||
|
||||
|
||||
/**
|
||||
* Returns the underlying output stream, to which the corresponding
|
||||
* <code>OutputStream</code> methods in this class will ultimately delegate.
|
||||
*
|
||||
* @return The underlying output stream.
|
||||
*
|
||||
* @exception IOException if an error occurs.
|
||||
*/
|
||||
protected abstract OutputStream getStream() throws IOException;
|
||||
|
||||
|
||||
/**
|
||||
* Indicates that the configured threshold has been reached, and that a
|
||||
* subclass should take whatever action necessary on this event. This may
|
||||
* include changing the underlying output stream.
|
||||
*
|
||||
* @exception IOException if an error occurs.
|
||||
*/
|
||||
protected abstract void thresholdReached() throws IOException;
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
<!-- $Id: package.html,v 1.3 2003/04/27 17:30:06 martinc Exp $ -->
|
||||
<html>
|
||||
<head>
|
||||
<title>Overview of the org.apache.commons.fileupload component</title>
|
||||
</head>
|
||||
<body>
|
||||
<p>
|
||||
Component for handling html file uploads as given by rfc 1867
|
||||
<a href="http://www.ietf.org/rfc/rfc1867.txt">RFC 1867</a>.
|
||||
</p>
|
||||
<p>
|
||||
Normal usage of the package involves
|
||||
{@link org.apache.commons.fileupload.DiskFileUpload DiskFileUpload}
|
||||
parsing the HttpServletRequest and returning a list of
|
||||
{@link org.apache.commons.fileupload.FileItem FileItem}'s.
|
||||
These <code>FileItem</code>'s provide easy access to the data
|
||||
given in the upload. There is also a low level api for
|
||||
manipulating the upload data encapsulated in the
|
||||
{@link org.apache.commons.fileupload.MultipartStream MultipartStream}
|
||||
class.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Normal usage example:
|
||||
</p>
|
||||
<pre>
|
||||
|
||||
public void doPost(HttpServletRequest req, HttpServletResponse res)
|
||||
{
|
||||
DiskFileUpload fu = new DiskFileUpload();
|
||||
// maximum size before a FileUploadException will be thrown
|
||||
fu.setSizeMax(1000000);
|
||||
// maximum size that will be stored in memory
|
||||
fu.setSizeThreshold(4096);
|
||||
// the location for saving data that is larger than getSizeThreshold()
|
||||
fu.setRepositoryPath("/tmp");
|
||||
|
||||
List fileItems = fu.parseRequest(req);
|
||||
// assume we know there are two files. The first file is a small
|
||||
// text file, the second is unknown and is written to a file on
|
||||
// the server
|
||||
Iterator i = fileItems.iterator();
|
||||
String comment = ((FileItem)i.next()).getString();
|
||||
FileItem fi = (FileItem)i.next();
|
||||
// filename on the client
|
||||
String fileName = fi.getName();
|
||||
// save comment and filename to database
|
||||
...
|
||||
// write the file
|
||||
fi.write("/www/uploads/" + fileName);
|
||||
}
|
||||
</pre>
|
||||
<p>
|
||||
In the example above the first file is loaded into memory as a
|
||||
<code>String</code>. Before calling the getString method, the data
|
||||
may have been in memory or on disk depending on its size. The second
|
||||
file we assume it will be large and therefore never explicitly load
|
||||
it into memory, though if it is less than 4096 bytes it will be
|
||||
in memory before it is written to its final location. When writing to
|
||||
the final location, if the data is larger than the
|
||||
threshold, an attempt is made to rename the temporary file to
|
||||
the given location. If it cannot be renamed, it is streamed to the
|
||||
new location.
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,114 @@
|
||||
package org.modsecurity;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.Collections;
|
||||
import java.util.Enumeration;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import javax.servlet.ServletInputStream;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletRequestWrapper;
|
||||
|
||||
public class MSHttpServletRequestWrapper extends HttpServletRequestWrapper {
|
||||
|
||||
private HttpServletRequest request;
|
||||
private MSServletInputStream servletStream;
|
||||
private Map<String, String[]> parameterMap = null;
|
||||
|
||||
public MSHttpServletRequestWrapper(HttpServletRequest request) {
|
||||
super(request);
|
||||
this.request = request;
|
||||
}
|
||||
|
||||
private void resetInputStream() throws IOException {
|
||||
if (this.servletStream == null) {
|
||||
this.servletStream = new MSServletInputStream();
|
||||
this.servletStream.stream = request.getInputStream();
|
||||
this.servletStream.outStream = new ByteArrayOutputStream();
|
||||
// Read the parameters first, because they can get reachless after the inputStream is read.
|
||||
getParameterMap();
|
||||
} else {
|
||||
while (servletStream.read() != -1) { //finish the stream
|
||||
}
|
||||
this.servletStream.stream.close();
|
||||
servletStream.close();
|
||||
this.servletStream.outStream.flush();
|
||||
this.servletStream.stream = new ByteArrayInputStream(this.servletStream.outStream.toByteArray());
|
||||
this.servletStream.outStream.close();
|
||||
this.servletStream.outStream = new ByteArrayOutputStream();
|
||||
}
|
||||
//System.out.println("getinputstream");
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServletInputStream getInputStream() throws IOException {
|
||||
resetInputStream();
|
||||
|
||||
return servletStream;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BufferedReader getReader() throws IOException {
|
||||
resetInputStream();
|
||||
|
||||
return new BufferedReader(new InputStreamReader(servletStream));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getParameter(String name) {
|
||||
String[] a = parameterMap.get(name);
|
||||
if (a == null || a.length == 0) {
|
||||
return null;
|
||||
}
|
||||
return a[0];
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map getParameterMap() {
|
||||
if (parameterMap == null) {
|
||||
parameterMap = new HashMap<String, String[]>();
|
||||
parameterMap.putAll(super.getParameterMap());
|
||||
}
|
||||
return parameterMap;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Enumeration getParameterNames() {
|
||||
return Collections.enumeration(parameterMap.values());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getParameterValues(String name) {
|
||||
return parameterMap.get(name);
|
||||
}
|
||||
|
||||
private class MSServletInputStream extends ServletInputStream {
|
||||
|
||||
private InputStream stream;
|
||||
private ByteArrayOutputStream outStream;
|
||||
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
int out = stream.read();
|
||||
if (out != -1) {
|
||||
outStream.write(out); //writing in an out stream as the in stream is read, rather than storing it all in a buffer, to prevent DOS attack
|
||||
}
|
||||
System.out.println("read=" + out);
|
||||
return out;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(byte[] b, int off, int len) throws IOException {
|
||||
int out = stream.read(b, off, len);
|
||||
if (out != -1) {
|
||||
outStream.write(b, off, len);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -19,26 +19,21 @@ public final class ModSecurity {
|
||||
public static final int DONE = -2;
|
||||
public static final int DECLINED = -1;
|
||||
public static final int OK = 0;
|
||||
|
||||
//From build/classes: >"c:\Program Files\Java\jdk1.7.0_05\bin\javah.exe" -classpath c:\work\apache-tomcat-7.0.39\lib\servlet-api.jar;. org.modsecurity.ModSecurity
|
||||
|
||||
private FilterConfig filterConfig;
|
||||
private String confFilename;
|
||||
private long confTime;
|
||||
private final static String pathToLib = "c:\\work\\mod_security\\java\\Debug\\";
|
||||
|
||||
static {
|
||||
try {
|
||||
//TODO: bad practice, native libraries should be loaded in server's classloader
|
||||
System.load("c:\\work\\mod_security\\java\\libs\\zlib1.dll");
|
||||
System.load("c:\\work\\mod_security\\java\\libs\\libxml2.dll");
|
||||
System.load("c:\\work\\mod_security\\java\\libs\\pcre.dll");
|
||||
System.load("c:\\work\\mod_security\\java\\libs\\libapr-1.dll");
|
||||
System.load("c:\\work\\mod_security\\java\\libs\\libapriconv-1.dll");
|
||||
System.load("c:\\work\\mod_security\\java\\libs\\libaprutil-1.dll");
|
||||
System.load("c:\\work\\mod_security\\java\\Debug\\ModSecurityJNI.dll");
|
||||
} catch (UnsatisfiedLinkError err) {
|
||||
err.printStackTrace();
|
||||
}
|
||||
//TODO: bad practice, native libraries should be loaded in server's classloader
|
||||
System.load("c:\\work\\mod_security\\java\\libs\\zlib1.dll");
|
||||
System.load("c:\\work\\mod_security\\java\\libs\\libxml2.dll");
|
||||
System.load("c:\\work\\mod_security\\java\\libs\\pcre.dll");
|
||||
System.load("c:\\work\\mod_security\\java\\libs\\libapr-1.dll");
|
||||
System.load("c:\\work\\mod_security\\java\\libs\\libapriconv-1.dll");
|
||||
System.load("c:\\work\\mod_security\\java\\libs\\libaprutil-1.dll");
|
||||
System.load("c:\\work\\mod_security\\java\\Debug\\ModSecurityJNI.dll");
|
||||
//java.lang.reflect.Field loadedLibraries = ClassLoader.class.getDeclaredField("loadedLibraryNames");
|
||||
//loadedLibraries.setAccessible(true);
|
||||
//final Vector<String> libraries = (Vector<String>) loadedLibraries.get(ClassLoader.getSystemClassLoader());
|
||||
@@ -57,10 +52,10 @@ public final class ModSecurity {
|
||||
|
||||
public native int destroy();
|
||||
|
||||
public native int onRequest(String config, ServletRequest request, HttpServletRequest httprequest, String requestID, boolean reloadConfig);
|
||||
public native int onRequest(String config, MsHttpTransaction httpTran, boolean reloadConfig);
|
||||
|
||||
public native int onResponse(ServletResponse response, HttpServletResponse htttpResponse, String requestID);
|
||||
|
||||
|
||||
public static String[][] getHttpRequestHeaders(HttpServletRequest req) {
|
||||
ArrayList<String> aList = Collections.list(req.getHeaderNames());
|
||||
String[][] result = new String[aList.size()][2];
|
||||
@@ -72,9 +67,9 @@ public final class ModSecurity {
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
public static String[][] getHttpResponseHeaders(HttpServletResponse resp) {
|
||||
|
||||
|
||||
Collection<String> headerNames = resp.getHeaderNames();
|
||||
String[][] result = new String[headerNames.size()][2];
|
||||
|
||||
@@ -91,7 +86,7 @@ public final class ModSecurity {
|
||||
public static boolean isIPv6(String addr) {
|
||||
try {
|
||||
InetAddress inetAddress = InetAddress.getByName(addr);
|
||||
|
||||
|
||||
if (inetAddress instanceof Inet6Address) {
|
||||
return true;
|
||||
} else {
|
||||
@@ -101,7 +96,7 @@ public final class ModSecurity {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void log(int level, String msg) {
|
||||
//if (level == 1) {
|
||||
filterConfig.getServletContext().log(msg);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.modsecurity;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.UUID;
|
||||
import javax.servlet.Filter;
|
||||
@@ -35,18 +36,23 @@ public class ModSecurityFilter implements Filter {
|
||||
public void doFilter(ServletRequest request, ServletResponse response, FilterChain fc) throws IOException, ServletException {
|
||||
HttpServletRequest httpReq = (HttpServletRequest) request;
|
||||
HttpServletResponse httpResp = (HttpServletResponse) response;
|
||||
MsHttpTransaction httpTran = new MsHttpTransaction(httpReq, httpResp);
|
||||
|
||||
String requestID = UUID.randomUUID().toString();
|
||||
|
||||
int status = modsecurity.onRequest(modsecurity.getConfFilename(), request, httpReq, requestID, modsecurity.checkModifiedConfig());
|
||||
|
||||
if (status != ModSecurity.DECLINED) {
|
||||
return;
|
||||
try {
|
||||
int status = modsecurity.onRequest(modsecurity.getConfFilename(), httpTran, modsecurity.checkModifiedConfig());
|
||||
|
||||
if (status != ModSecurity.DECLINED) {
|
||||
return;
|
||||
}
|
||||
|
||||
//BufferedInputStream buf = new BufferedInputStream(httpReqWrapper.getInputStream());
|
||||
fc.doFilter(httpTran.getMsHttpRequest(), httpTran.getMsHttpResponse());
|
||||
//status = modsecurity.onResponse(response, httpResp, requestID);
|
||||
|
||||
} finally {
|
||||
httpTran.destroy();
|
||||
}
|
||||
|
||||
fc.doFilter(request, response);
|
||||
|
||||
status = modsecurity.onResponse(response, httpResp, requestID);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -0,0 +1,500 @@
|
||||
package org.modsecurity;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URLDecoder;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Enumeration;
|
||||
import java.util.HashMap;
|
||||
import java.util.Hashtable;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.ServletInputStream;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletRequestWrapper;
|
||||
import org.apache.commons.fileupload.DefaultFileItem;
|
||||
import org.apache.commons.fileupload.DiskFileUpload;
|
||||
import org.apache.commons.fileupload.FileItem;
|
||||
import org.apache.commons.fileupload.FileUploadException;
|
||||
|
||||
|
||||
public class MsHttpServletRequest extends HttpServletRequestWrapper {
|
||||
|
||||
public final static int BODY_NOTYETREAD = 0;
|
||||
public final static int BODY_INTERCEPT = 1;
|
||||
public final static int BODY_DISK = 2;
|
||||
public final static int BODY_MEMORY = 3;
|
||||
public final static int BODY_CONSUMED = 4;
|
||||
private HttpServletRequest req;
|
||||
private ArrayList parameters = new ArrayList();
|
||||
private byte bodyBytes[];
|
||||
private int bodyStatus = BODY_NOTYETREAD;
|
||||
private String body;
|
||||
private File bodyFile;
|
||||
private String encoding;
|
||||
private List fileItems;
|
||||
private String tmpPath;
|
||||
//private int maxContentLength = 1024 * 1024 * 1024; // 1 GB
|
||||
private boolean preserveRequestBody = true;
|
||||
|
||||
private class Parameter {
|
||||
|
||||
String name;
|
||||
String value;
|
||||
|
||||
Parameter(String name, String value) {
|
||||
this.name = name;
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
|
||||
public MsHttpServletRequest(HttpServletRequest req) {
|
||||
super(req);
|
||||
this.req = req;
|
||||
|
||||
encoding = req.getCharacterEncoding();
|
||||
if (encoding == null) {
|
||||
encoding = "UTF-8";
|
||||
}
|
||||
addUrlEncoded(req.getQueryString());
|
||||
setTmpPath(System.getProperty("java.io.tmpdir"));
|
||||
}
|
||||
|
||||
public void destroy() {
|
||||
if (bodyFile != null) {
|
||||
bodyFile.delete();
|
||||
}
|
||||
}
|
||||
|
||||
public String getTmpPath() {
|
||||
return tmpPath;
|
||||
}
|
||||
|
||||
public final void setTmpPath(String tmpPath) {
|
||||
this.tmpPath = tmpPath;
|
||||
}
|
||||
|
||||
public void setPreserveRequestBody(boolean preserveRequestBody) {
|
||||
this.preserveRequestBody = preserveRequestBody;
|
||||
}
|
||||
|
||||
public boolean getPreserveRequestBody() {
|
||||
return preserveRequestBody;
|
||||
}
|
||||
|
||||
public int getBodyStatus() {
|
||||
return bodyStatus;
|
||||
}
|
||||
|
||||
public File getBodyFile() {
|
||||
return bodyFile;
|
||||
}
|
||||
|
||||
public String getBody() {
|
||||
return body;
|
||||
}
|
||||
|
||||
public byte[] getBodyBytes() {
|
||||
return bodyBytes;
|
||||
}
|
||||
|
||||
public void readBody(int maxContentLength) throws IOException, ServletException {
|
||||
String contentType = req.getContentType();
|
||||
if ((contentType != null) && (contentType.startsWith("multipart/form-data"))) {
|
||||
readBodyMultipart(maxContentLength);
|
||||
} else {
|
||||
int contentLength = req.getContentLength();
|
||||
if (contentLength != -1) {
|
||||
// known body length, we can allocate a byte
|
||||
// array of required length
|
||||
|
||||
// restriction
|
||||
if (contentLength > maxContentLength) {
|
||||
throw new ServletException("Request body too large: " + contentLength + " bytes (" + maxContentLength + " allowed)");
|
||||
}
|
||||
|
||||
int count = 0;
|
||||
bodyBytes = new byte[contentLength];
|
||||
ServletInputStream sis = req.getInputStream();
|
||||
while (count < contentLength) {
|
||||
int bytesRead = sis.read(bodyBytes, count, contentLength - count);
|
||||
if (bytesRead == -1) {
|
||||
return;
|
||||
}
|
||||
count += bytesRead;
|
||||
}
|
||||
|
||||
body = new String(bodyBytes, encoding);
|
||||
if ((contentType != null) && ((contentType.compareTo("application/x-www-form-urlencoded") == 0) || (contentType.compareTo("application/x-form-urlencoded") == 0))) {
|
||||
addUrlEncoded(body);
|
||||
}
|
||||
} else {
|
||||
// unknown body length, so we send bytes to a
|
||||
// ByteArrayOutputStream instance
|
||||
byte b[] = new byte[1024];
|
||||
ServletInputStream sis = req.getInputStream();
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream(32768);
|
||||
|
||||
int count = 0;
|
||||
for (;;) {
|
||||
int bytesRead = sis.read(b);
|
||||
if (bytesRead == -1) {
|
||||
break;
|
||||
}
|
||||
|
||||
// size restriction
|
||||
if (count > maxContentLength) {
|
||||
throw new ServletException("Request body too large: " + count + " bytes (" + maxContentLength + " allowed)");
|
||||
}
|
||||
|
||||
baos.write(b, 0, bytesRead);
|
||||
count += bytesRead;
|
||||
}
|
||||
bodyBytes = baos.toByteArray();
|
||||
}
|
||||
|
||||
bodyStatus = BODY_MEMORY;
|
||||
}
|
||||
}
|
||||
|
||||
private void readBodyMultipart(int maxContentLength) throws IOException, ServletException {
|
||||
DiskFileUpload fu = new DiskFileUpload();
|
||||
fu.setSizeMax(maxContentLength); // max body size
|
||||
fu.setSizeThreshold(655350); // max in-memory storage for fields (not files)
|
||||
fu.setRepositoryPath(tmpPath); // tmp path on disk
|
||||
|
||||
try {
|
||||
// setting bodyStatus to BODY_INTERCEPT
|
||||
// will cause the ServletRequest wrapper to
|
||||
// intercept the body and make a copy of it
|
||||
// on disk
|
||||
if (preserveRequestBody) {
|
||||
bodyStatus = BODY_INTERCEPT;
|
||||
}
|
||||
fileItems = fu.parseRequest(this);
|
||||
} catch (FileUploadException e) {
|
||||
// e.printStackTrace(System.out);
|
||||
throw new ServletException(e);
|
||||
}
|
||||
|
||||
// loop through the available items and
|
||||
// add parameters to the request parameters list
|
||||
Iterator iterator = fileItems.iterator();
|
||||
while (iterator.hasNext()) {
|
||||
FileItem item = (FileItem) iterator.next();
|
||||
if (item.isFormField()) {
|
||||
addParameter(item.getFieldName(), item.getString(encoding));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the given URL-encoded string and adds the parameters to the
|
||||
* request parameter list.
|
||||
*/
|
||||
private void addUrlEncoded(String text) {
|
||||
if (text == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
int flag = 0;
|
||||
int startPos = -1;
|
||||
|
||||
String name = null, value;
|
||||
|
||||
try {
|
||||
|
||||
for (int i = 0; i < text.length(); i++) {
|
||||
char c = text.charAt(i);
|
||||
if (startPos == -1) {
|
||||
startPos = i;
|
||||
}
|
||||
|
||||
if (flag == 0) {
|
||||
if (c == '=') {
|
||||
name = URLDecoder.decode(text.substring(startPos, i), encoding);
|
||||
flag = 1;
|
||||
startPos = -1;
|
||||
}
|
||||
} else {
|
||||
if (c == '&') {
|
||||
value = URLDecoder.decode(text.substring(startPos, i), encoding);
|
||||
addParameter(name, value);
|
||||
flag = 0;
|
||||
startPos = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (flag == 1) {
|
||||
value = "";
|
||||
if (startPos != -1) {
|
||||
value = URLDecoder.decode(text.substring(startPos), encoding);
|
||||
}
|
||||
addParameter(name, value);
|
||||
} else {
|
||||
if (startPos != -1) {
|
||||
name = URLDecoder.decode(text.substring(startPos), encoding);
|
||||
addParameter(name, "");
|
||||
}
|
||||
}
|
||||
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
// TODO it should not happen to us, but
|
||||
// log somewhere just in case it does
|
||||
e.printStackTrace(System.err);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the given parameter to the request parameters list.
|
||||
*/
|
||||
private void addParameter(String name, String value) {
|
||||
parameters.add(new Parameter(name, value));
|
||||
}
|
||||
|
||||
|
||||
/* -- Methods for access to uploaded files ------------------------- */
|
||||
/**
|
||||
* Returns a File instance representing the uploaded file.
|
||||
*/
|
||||
public File getFile(String name) {
|
||||
if (fileItems == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Iterator iterator = fileItems.iterator();
|
||||
while (iterator.hasNext()) {
|
||||
DefaultFileItem item = (DefaultFileItem) iterator.next();
|
||||
if (item.getFieldName().compareTo(name) == 0) {
|
||||
return item.getStoreLocation();
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the (field) names of all uploaded files as an Enumeration.
|
||||
*/
|
||||
public Enumeration getFileNames() {
|
||||
Hashtable names = new Hashtable();
|
||||
if (fileItems == null) {
|
||||
return names.keys();
|
||||
}
|
||||
|
||||
Iterator iterator = fileItems.iterator();
|
||||
while (iterator.hasNext()) {
|
||||
FileItem item = (FileItem) iterator.next();
|
||||
if (!item.isFormField()) {
|
||||
names.put(item.getFieldName(), item);
|
||||
}
|
||||
}
|
||||
|
||||
return names.keys();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the original filename of the uploaded file.
|
||||
*/
|
||||
public String getFileSystemName(String name) {
|
||||
Iterator iterator = fileItems.iterator();
|
||||
while (iterator.hasNext()) {
|
||||
DefaultFileItem item = (DefaultFileItem) iterator.next();
|
||||
if (item.getFieldName().compareTo(name) == 0) {
|
||||
return item.getName();
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/* -- ServletRequest wrapper --------------------------------------- */
|
||||
/**
|
||||
* This class is an ServletInputStream wrapper, which passes the input from
|
||||
* the stream to the caller and writes it to a temporary file at the same
|
||||
* time.
|
||||
*/
|
||||
private class InterceptServletInputStream extends ServletInputStream {
|
||||
|
||||
private ServletInputStream sis;
|
||||
private int count;
|
||||
private OutputStream os;
|
||||
|
||||
private InterceptServletInputStream(ServletInputStream sis) throws IOException {
|
||||
this.sis = sis;
|
||||
bodyFile = File.createTempFile("modsec", ".tmp", new File(tmpPath));
|
||||
os = new BufferedOutputStream(new FileOutputStream(bodyFile));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
int i = sis.read();
|
||||
|
||||
if (i != -1) {
|
||||
count++;
|
||||
os.write(i);
|
||||
} else {
|
||||
os.close();
|
||||
bodyStatus = BODY_DISK;
|
||||
}
|
||||
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This ServletInputStream implementation reads the body from the memory.
|
||||
*/
|
||||
private class BodyMemoryServletInputStream extends ServletInputStream {
|
||||
|
||||
private ByteArrayInputStream bais;
|
||||
|
||||
private BodyMemoryServletInputStream() {
|
||||
bais = new ByteArrayInputStream(bodyBytes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read() {
|
||||
return bais.read();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This ServletInputStream implementation reads the body from the temporary
|
||||
* file on disk.
|
||||
*/
|
||||
private class BodyDiskServletInputStream extends ServletInputStream {
|
||||
|
||||
private InputStream is;
|
||||
|
||||
private BodyDiskServletInputStream() throws FileNotFoundException {
|
||||
is = new BufferedInputStream(new FileInputStream(bodyFile));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
return is.read();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Replacement for the ServletRequest.getInputStream() method.
|
||||
*/
|
||||
@Override
|
||||
public ServletInputStream getInputStream() throws java.io.IOException {
|
||||
ServletInputStream sis;
|
||||
|
||||
switch (bodyStatus) {
|
||||
|
||||
case BODY_INTERCEPT:
|
||||
sis = new InterceptServletInputStream(req.getInputStream());
|
||||
break;
|
||||
|
||||
case BODY_MEMORY:
|
||||
sis = new BodyMemoryServletInputStream();
|
||||
break;
|
||||
|
||||
case BODY_DISK:
|
||||
sis = new BodyDiskServletInputStream();
|
||||
break;
|
||||
|
||||
default:
|
||||
sis = req.getInputStream();
|
||||
break;
|
||||
}
|
||||
|
||||
return sis;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replacement for the ServletRequest.getParameter() method.
|
||||
*/
|
||||
@Override
|
||||
public String getParameter(String name) {
|
||||
for (int i = 0, n = parameters.size(); i < n; i++) {
|
||||
Parameter p = (Parameter) parameters.get(i);
|
||||
if (p.name.compareTo(name) == 0) {
|
||||
return p.value;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replacement for the ServletRequest.getParameterMap() method.
|
||||
*/
|
||||
@Override
|
||||
public Map getParameterMap() {
|
||||
HashMap map = new HashMap();
|
||||
for (int i = 0, n = parameters.size(); i < n; i++) {
|
||||
Parameter p = (Parameter) parameters.get(i);
|
||||
map.put(p.name, p.value);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replacement for the ServletRequest.getParameterNames() method.
|
||||
*/
|
||||
@Override
|
||||
public Enumeration getParameterNames() {
|
||||
Hashtable parameterNames = new Hashtable();
|
||||
for (int i = 0, n = parameters.size(); i < n; i++) {
|
||||
Parameter p = (Parameter) parameters.get(i);
|
||||
parameterNames.put(p.name, p.value);
|
||||
}
|
||||
return parameterNames.keys();
|
||||
}
|
||||
|
||||
/**
|
||||
* Replacement for the ServletRequest.getParameterValues(String) method.
|
||||
*/
|
||||
@Override
|
||||
public String[] getParameterValues(String name) {
|
||||
// how many parameters are there with the
|
||||
// given name?
|
||||
int count = 0;
|
||||
for (int i = 0, n = parameters.size(); i < n; i++) {
|
||||
Parameter p = (Parameter) parameters.get(i);
|
||||
if (p.name.compareTo(name) == 0) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
// put them into a String array
|
||||
String values[] = new String[count];
|
||||
count = 0;
|
||||
for (int i = 0, n = parameters.size(); i < n; i++) {
|
||||
Parameter p = (Parameter) parameters.get(i);
|
||||
if (p.name.compareTo(name) == 0) {
|
||||
values[count++] = p.value;
|
||||
}
|
||||
}
|
||||
|
||||
return values;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replacement for the ServletRequest.getReader() method.
|
||||
*/
|
||||
@Override
|
||||
public BufferedReader getReader() throws java.io.IOException {
|
||||
return new BufferedReader(new InputStreamReader(getInputStream(), encoding));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,679 @@
|
||||
package org.modsecurity;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.text.DateFormat;
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Locale;
|
||||
import java.util.TimeZone;
|
||||
import javax.servlet.ServletOutputStream;
|
||||
import javax.servlet.http.Cookie;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.servlet.http.HttpServletResponseWrapper;
|
||||
|
||||
public class MsHttpServletResponse extends HttpServletResponseWrapper {
|
||||
|
||||
private static final int INTERCEPT_OFF = 0;
|
||||
private static final int INTERCEPT_ON = 1;
|
||||
private static final int INTERCEPT_OBSERVE_ONLY = 2;
|
||||
public static final String DEFAULT_CHARACTER_ENCODING = "ISO-8859-1";
|
||||
private int interceptMode = INTERCEPT_ON;
|
||||
private ArrayList headers = new ArrayList();
|
||||
private ArrayList cookies = new ArrayList();
|
||||
private int status = -1;
|
||||
private boolean committed = false;
|
||||
private boolean suspended = false;
|
||||
private boolean destroyed = false;
|
||||
private String statusMessage;
|
||||
private String contentType;
|
||||
private String characterEncoding;
|
||||
private int contentLength = -1;
|
||||
private Locale locale;
|
||||
private MsOutputStream msOutputStream;
|
||||
private MsWriter msWriter;
|
||||
protected SimpleDateFormat formats[] = {
|
||||
new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US),
|
||||
new SimpleDateFormat("EEEEEE, dd-MMM-yy HH:mm:ss zzz", Locale.US),
|
||||
new SimpleDateFormat("EEE MMMM d HH:mm:ss yyyy", Locale.US)
|
||||
};
|
||||
|
||||
private class Header {
|
||||
|
||||
String name;
|
||||
String value;
|
||||
|
||||
Header(String name, String value) {
|
||||
this.name = name;
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
|
||||
public MsHttpServletResponse(HttpServletResponse response) {
|
||||
super(response);
|
||||
|
||||
characterEncoding = DEFAULT_CHARACTER_ENCODING;
|
||||
TimeZone GMT_ZONE = TimeZone.getTimeZone("GMT");
|
||||
formats[0].setTimeZone(GMT_ZONE);
|
||||
formats[1].setTimeZone(GMT_ZONE);
|
||||
formats[2].setTimeZone(GMT_ZONE);
|
||||
locale = Locale.getDefault();
|
||||
}
|
||||
|
||||
public void destroy() throws IOException {
|
||||
if (destroyed == true) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (interceptMode == INTERCEPT_ON) {
|
||||
if (status != -1) {
|
||||
super.setStatus(status);
|
||||
}
|
||||
if (contentType != null) {
|
||||
super.setContentType(contentType);
|
||||
}
|
||||
if (characterEncoding != null) {
|
||||
super.setCharacterEncoding(characterEncoding);
|
||||
}
|
||||
if (contentLength != -1) {
|
||||
super.setContentLength(contentLength);
|
||||
}
|
||||
if (locale != null) {
|
||||
super.setLocale(locale);
|
||||
}
|
||||
|
||||
// send cookies
|
||||
for (int i = 0, n = cookies.size(); i < n; i++) {
|
||||
super.addCookie((Cookie) cookies.get(i));
|
||||
}
|
||||
|
||||
// send headers
|
||||
for (int i = 0, n = headers.size(); i < n; i++) {
|
||||
Header h = (Header) headers.get(i);
|
||||
// TODO don't send our cookie headers because
|
||||
// they are not well implemented yet. Cookies
|
||||
// are sent directly
|
||||
if (h.name.compareTo("Set-Cookie") != 0) {
|
||||
super.addHeader(h.name, h.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (msWriter != null) {
|
||||
msWriter.commit();
|
||||
}
|
||||
if (msOutputStream != null) {
|
||||
msOutputStream.commit();
|
||||
}
|
||||
|
||||
destroyed = true;
|
||||
}
|
||||
|
||||
public String getBody() {
|
||||
if (msWriter != null) {
|
||||
return msWriter.toString();
|
||||
}
|
||||
if (msOutputStream != null) {
|
||||
return msOutputStream.toString(getCharacterEncoding());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void setSuspended(boolean suspended) {
|
||||
this.suspended = true;
|
||||
if (msWriter != null) {
|
||||
msWriter.setSuspended(suspended);
|
||||
}
|
||||
if (msOutputStream != null) {
|
||||
msOutputStream.setSuspended(suspended);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getContentType() {
|
||||
if (interceptMode != INTERCEPT_OFF) {
|
||||
return contentType;
|
||||
}
|
||||
return super.getContentType();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServletOutputStream getOutputStream() throws IllegalStateException, IOException {
|
||||
if (interceptMode != INTERCEPT_OFF) {
|
||||
if (msWriter != null) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
if (msOutputStream == null) {
|
||||
msOutputStream = new MsOutputStream(super.getOutputStream());
|
||||
}
|
||||
if (interceptMode == INTERCEPT_ON) {
|
||||
msOutputStream.setBuffering(true);
|
||||
}
|
||||
return msOutputStream;
|
||||
} else {
|
||||
return super.getOutputStream();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public PrintWriter getWriter() throws IllegalStateException, IOException {
|
||||
if (interceptMode != INTERCEPT_OFF) {
|
||||
if (msOutputStream != null) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
if (msWriter == null) {
|
||||
msWriter = new MsWriter(super.getWriter());
|
||||
}
|
||||
if (interceptMode == INTERCEPT_ON) {
|
||||
msWriter.setBuffering(true);
|
||||
}
|
||||
return msWriter;
|
||||
} else {
|
||||
return super.getWriter();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCharacterEncoding(String charset) {
|
||||
if (interceptMode != INTERCEPT_ON) {
|
||||
super.setCharacterEncoding(charset);
|
||||
}
|
||||
if (interceptMode != INTERCEPT_OFF) {
|
||||
characterEncoding = charset;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setContentLength(int contentLength) {
|
||||
if (interceptMode != INTERCEPT_ON) {
|
||||
super.setContentLength(contentLength);
|
||||
}
|
||||
if (interceptMode != INTERCEPT_OFF) {
|
||||
this.contentLength = contentLength;
|
||||
headers.add(new Header("Content-Length", Integer.toString(contentLength)));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setContentType(String contentType) {
|
||||
if (interceptMode != INTERCEPT_ON) {
|
||||
super.setContentType(contentType);
|
||||
}
|
||||
if (interceptMode != INTERCEPT_OFF) {
|
||||
this.contentType = contentType;
|
||||
headers.add(new Header("Content-Type", contentType));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBufferSize(int size) throws IllegalStateException {
|
||||
super.setBufferSize(size);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBufferSize() {
|
||||
return super.getBufferSize();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flushBuffer() throws IOException {
|
||||
if (interceptMode != INTERCEPT_ON) {
|
||||
super.flushBuffer();
|
||||
}
|
||||
if (interceptMode != INTERCEPT_OFF) {
|
||||
committed = true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resetBuffer() {
|
||||
if (interceptMode != INTERCEPT_ON) {
|
||||
super.resetBuffer();
|
||||
}
|
||||
if (interceptMode != INTERCEPT_OFF) {
|
||||
if (committed) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
|
||||
if (msWriter != null) {
|
||||
msWriter.reset();
|
||||
}
|
||||
if (msOutputStream != null) {
|
||||
msOutputStream.reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCommitted() {
|
||||
if (interceptMode != INTERCEPT_OFF) {
|
||||
return committed;
|
||||
}
|
||||
return super.isCommitted();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset() throws IllegalStateException {
|
||||
if (interceptMode != INTERCEPT_ON) {
|
||||
super.reset();
|
||||
}
|
||||
if (interceptMode != INTERCEPT_OFF) {
|
||||
if (committed) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
|
||||
status = 200;
|
||||
characterEncoding = DEFAULT_CHARACTER_ENCODING;
|
||||
contentType = null;
|
||||
contentLength = -1;
|
||||
headers.clear();
|
||||
status = 200;
|
||||
statusMessage = null;
|
||||
|
||||
if (msWriter != null) {
|
||||
msWriter.reset();
|
||||
}
|
||||
if (msOutputStream != null) {
|
||||
msOutputStream.reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLocale(Locale locale) {
|
||||
if (interceptMode != INTERCEPT_ON) {
|
||||
super.setLocale(locale);
|
||||
}
|
||||
if (interceptMode != INTERCEPT_OFF) {
|
||||
this.locale = locale;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Locale getLocale() {
|
||||
if (interceptMode != INTERCEPT_OFF) {
|
||||
return locale;
|
||||
}
|
||||
return super.getLocale();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addCookie(Cookie cookie) {
|
||||
if (interceptMode != INTERCEPT_ON) {
|
||||
super.addCookie(cookie);
|
||||
}
|
||||
if (interceptMode != INTERCEPT_OFF) {
|
||||
cookies.add(cookie);
|
||||
// TODO improve; these headers will not be
|
||||
// sent to the client
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(cookie.getName());
|
||||
sb.append("=");
|
||||
if (cookie.getDomain() != null) {
|
||||
sb.append("; domain=").append(cookie.getDomain());
|
||||
}
|
||||
if (cookie.getPath() != null) {
|
||||
sb.append("; path=").append(cookie.getPath());
|
||||
}
|
||||
if (cookie.getSecure()) {
|
||||
sb.append("; secure");
|
||||
}
|
||||
headers.add(new Header("Set-Cookie", sb.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addDateHeader(String name, long value) {
|
||||
SimpleDateFormat format = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US);
|
||||
format.setTimeZone(TimeZone.getTimeZone("GMT"));
|
||||
this.addHeader(name, FastHttpDateFormat.formatDate(value, format));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addHeader(String name, String value) {
|
||||
if (interceptMode != INTERCEPT_ON) {
|
||||
super.addHeader(name, value);
|
||||
}
|
||||
if (interceptMode != INTERCEPT_OFF) {
|
||||
headers.add(new Header(name, value));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addIntHeader(String name, int value) {
|
||||
this.addHeader(name, Integer.toString(value));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsHeader(String name) {
|
||||
if (interceptMode == INTERCEPT_OFF) {
|
||||
return super.containsHeader(name);
|
||||
} else {
|
||||
for (int i = 0, n = headers.size(); i < n; i++) {
|
||||
Header h = (Header) headers.get(i);
|
||||
if (h.name.compareTo(name) == 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDateHeader(String name, long value) {
|
||||
SimpleDateFormat format = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US);
|
||||
format.setTimeZone(TimeZone.getTimeZone("GMT"));
|
||||
this.setHeader(name, FastHttpDateFormat.formatDate(value, format));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setHeader(String name, String value) {
|
||||
if (interceptMode != INTERCEPT_ON) {
|
||||
super.setHeader(name, value);
|
||||
}
|
||||
if (interceptMode != INTERCEPT_OFF) {
|
||||
for (int i = 0, n = headers.size(); i < n; i++) {
|
||||
Header h = (Header) headers.get(i);
|
||||
if (h.name.compareTo(name) == 0) {
|
||||
headers.remove(i);
|
||||
i--;
|
||||
}
|
||||
}
|
||||
headers.add(new Header(name, value));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setIntHeader(String name, int value) {
|
||||
this.setHeader(name, Integer.toString(value));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setStatus(int status) {
|
||||
if (interceptMode != INTERCEPT_ON) {
|
||||
super.setStatus(status);
|
||||
}
|
||||
if (interceptMode != INTERCEPT_OFF) {
|
||||
this.status = status;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setStatus(int status, String message) {
|
||||
if (interceptMode != INTERCEPT_ON) {
|
||||
super.setStatus(status);
|
||||
}
|
||||
if (interceptMode != INTERCEPT_OFF) {
|
||||
this.status = status;
|
||||
this.statusMessage = message;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendError(int status) throws IOException {
|
||||
if (interceptMode != INTERCEPT_ON) {
|
||||
super.sendError(status);
|
||||
}
|
||||
if (interceptMode != INTERCEPT_OFF) {
|
||||
this.status = status;
|
||||
this.setSuspended(true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendError(int status, String message) throws IOException {
|
||||
if (interceptMode != INTERCEPT_ON) {
|
||||
super.sendError(status);
|
||||
}
|
||||
if (interceptMode != INTERCEPT_OFF) {
|
||||
this.status = status;
|
||||
this.statusMessage = message;
|
||||
this.setSuspended(true);
|
||||
}
|
||||
}
|
||||
|
||||
/* -- Inspection methods ---------------------------------------------- */
|
||||
// TODO throw exception when interceptMode set to OFF
|
||||
public Cookie[] getCookies() {
|
||||
return (Cookie[]) cookies.toArray(new Cookie[cookies.size()]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public int getContentLength() {
|
||||
return contentLength;
|
||||
}
|
||||
|
||||
public long getDateHeader(String name) throws IllegalArgumentException {
|
||||
String value = this.getHeader(name);
|
||||
if (value == null) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
long result = FastHttpDateFormat.parseDate(value, formats);
|
||||
if (result == -1) {
|
||||
throw new IllegalArgumentException(value);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHeader(String name) {
|
||||
for (int i = 0, n = headers.size(); i < n; i++) {
|
||||
Header h = (Header) headers.get(i);
|
||||
if (h.name.compareTo(name) == 0) {
|
||||
return h.value;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<String> getHeaderNames() {
|
||||
Collection<String> headerNames = new LinkedList<String>();
|
||||
for (int i = 0, n = headers.size(); i < n; i++) {
|
||||
Header h = (Header) headers.get(i);
|
||||
headerNames.add(h.value);
|
||||
}
|
||||
return headerNames;
|
||||
}
|
||||
|
||||
public int getIntHeader(String name) throws NumberFormatException {
|
||||
String value = this.getHeader(name);
|
||||
if (value == null) {
|
||||
return -1;
|
||||
}
|
||||
return Integer.parseInt(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<String> getHeaders(String name) {
|
||||
Collection<String> headerValues = new LinkedList<String>();
|
||||
for (int i = 0, n = headers.size(); i < n; i++) {
|
||||
Header h = (Header) headers.get(i);
|
||||
if (h.name.compareTo(name) == 0) {
|
||||
headerValues.add(h.value);
|
||||
}
|
||||
}
|
||||
return headerValues;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility class to generate HTTP dates.
|
||||
*
|
||||
* @author Remy Maucherat
|
||||
*/
|
||||
final class FastHttpDateFormat {
|
||||
|
||||
// -------------------------------------------------------------- Variables
|
||||
/**
|
||||
* HTTP date format.
|
||||
*/
|
||||
protected static final SimpleDateFormat format =
|
||||
new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US);
|
||||
/**
|
||||
* The set of SimpleDateFormat formats to use in getDateHeader().
|
||||
*/
|
||||
protected static final SimpleDateFormat formats[] = {
|
||||
new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US),
|
||||
new SimpleDateFormat("EEEEEE, dd-MMM-yy HH:mm:ss zzz", Locale.US),
|
||||
new SimpleDateFormat("EEE MMMM d HH:mm:ss yyyy", Locale.US)
|
||||
};
|
||||
protected final static TimeZone gmtZone = TimeZone.getTimeZone("GMT");
|
||||
|
||||
/**
|
||||
* GMT timezone - all HTTP dates are on GMT
|
||||
*/
|
||||
static {
|
||||
|
||||
format.setTimeZone(gmtZone);
|
||||
|
||||
formats[0].setTimeZone(gmtZone);
|
||||
formats[1].setTimeZone(gmtZone);
|
||||
formats[2].setTimeZone(gmtZone);
|
||||
|
||||
}
|
||||
/**
|
||||
* Instant on which the currentDate object was generated.
|
||||
*/
|
||||
protected static long currentDateGenerated = 0L;
|
||||
/**
|
||||
* Current formatted date.
|
||||
*/
|
||||
protected static String currentDate = null;
|
||||
/**
|
||||
* Formatter cache.
|
||||
*/
|
||||
protected static final HashMap formatCache = new HashMap();
|
||||
/**
|
||||
* Parser cache.
|
||||
*/
|
||||
protected static final HashMap parseCache = new HashMap();
|
||||
|
||||
// --------------------------------------------------------- Public Methods
|
||||
/**
|
||||
* Get the current date in HTTP format.
|
||||
*/
|
||||
public static String getCurrentDate() {
|
||||
|
||||
long now = System.currentTimeMillis();
|
||||
if ((now - currentDateGenerated) > 1000) {
|
||||
synchronized (format) {
|
||||
if ((now - currentDateGenerated) > 1000) {
|
||||
currentDateGenerated = now;
|
||||
currentDate = format.format(new Date(now));
|
||||
}
|
||||
}
|
||||
}
|
||||
return currentDate;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the HTTP format of the specified date.
|
||||
*/
|
||||
public static String formatDate(long value, DateFormat threadLocalformat) {
|
||||
|
||||
String cachedDate = null;
|
||||
Long longValue = new Long(value);
|
||||
try {
|
||||
cachedDate = (String) formatCache.get(longValue);
|
||||
} catch (Exception e) {
|
||||
}
|
||||
if (cachedDate != null) {
|
||||
return cachedDate;
|
||||
}
|
||||
|
||||
String newDate;
|
||||
Date dateValue = new Date(value);
|
||||
if (threadLocalformat != null) {
|
||||
newDate = threadLocalformat.format(dateValue);
|
||||
synchronized (formatCache) {
|
||||
updateCache(formatCache, longValue, newDate);
|
||||
}
|
||||
} else {
|
||||
synchronized (formatCache) {
|
||||
newDate = format.format(dateValue);
|
||||
updateCache(formatCache, longValue, newDate);
|
||||
}
|
||||
}
|
||||
return newDate;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to parse the given date as a HTTP date.
|
||||
*/
|
||||
public static long parseDate(String value,
|
||||
DateFormat[] threadLocalformats) {
|
||||
|
||||
Long cachedDate = null;
|
||||
try {
|
||||
cachedDate = (Long) parseCache.get(value);
|
||||
} catch (Exception e) {
|
||||
}
|
||||
if (cachedDate != null) {
|
||||
return cachedDate.longValue();
|
||||
}
|
||||
|
||||
Long date;
|
||||
if (threadLocalformats != null) {
|
||||
date = internalParseDate(value, threadLocalformats);
|
||||
synchronized (parseCache) {
|
||||
updateCache(parseCache, value, date);
|
||||
}
|
||||
} else {
|
||||
synchronized (parseCache) {
|
||||
date = internalParseDate(value, formats);
|
||||
updateCache(parseCache, value, date);
|
||||
}
|
||||
}
|
||||
if (date == null) {
|
||||
return (-1L);
|
||||
} else {
|
||||
return date.longValue();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse date with given formatters.
|
||||
*/
|
||||
private static Long internalParseDate(String value, DateFormat[] formats) {
|
||||
Date date = null;
|
||||
for (int i = 0; (date == null) && (i < formats.length); i++) {
|
||||
try {
|
||||
date = formats[i].parse(value);
|
||||
} catch (ParseException e) {
|
||||
}
|
||||
}
|
||||
if (date == null) {
|
||||
return null;
|
||||
}
|
||||
return new Long(date.getTime());
|
||||
}
|
||||
|
||||
/**
|
||||
* Update cache.
|
||||
*/
|
||||
private static void updateCache(HashMap cache, Object key,
|
||||
Object value) {
|
||||
if (value == null) {
|
||||
return;
|
||||
}
|
||||
if (cache.size() > 1000) {
|
||||
cache.clear();
|
||||
}
|
||||
cache.put(key, value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package org.modsecurity;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.UUID;
|
||||
import javax.servlet.ServletRequest;
|
||||
import javax.servlet.ServletResponse;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
public class MsHttpTransaction {
|
||||
|
||||
private HttpServletRequest req;
|
||||
private HttpServletResponse res;
|
||||
|
||||
private MsHttpServletRequest msReq;
|
||||
private MsHttpServletResponse msRes;
|
||||
|
||||
private String tranID;
|
||||
|
||||
public MsHttpTransaction(ServletRequest req, ServletResponse res) {
|
||||
tranID = UUID.randomUUID().toString();
|
||||
this.req = (HttpServletRequest)req;
|
||||
this.res = (HttpServletResponse)res;
|
||||
this.msReq = new MsHttpServletRequest(this.req);
|
||||
this.msRes = new MsHttpServletResponse(this.res);
|
||||
}
|
||||
|
||||
public void destroy() throws IOException {
|
||||
msRes.destroy();
|
||||
msReq.destroy();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the req
|
||||
*/
|
||||
public HttpServletRequest getHttpRequest() {
|
||||
return req;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the res
|
||||
*/
|
||||
public HttpServletResponse getHttpResponse() {
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the msReq
|
||||
*/
|
||||
public MsHttpServletRequest getMsHttpRequest() {
|
||||
return msReq;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the msRes
|
||||
*/
|
||||
public MsHttpServletResponse getMsHttpResponse() {
|
||||
return msRes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the tranID
|
||||
*/
|
||||
public String getTransactionID() {
|
||||
return tranID;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
package org.modsecurity;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import javax.servlet.ServletOutputStream;
|
||||
|
||||
public class MsOutputStream extends ServletOutputStream {
|
||||
|
||||
private boolean buffering = false;
|
||||
|
||||
private ServletOutputStream sos;
|
||||
|
||||
private ByteArrayOutputStream buffer;
|
||||
|
||||
public MsOutputStream(ServletOutputStream sos) {
|
||||
super();
|
||||
this.sos = sos;
|
||||
buffer = new ByteArrayOutputStream();
|
||||
}
|
||||
|
||||
public void setBuffering(boolean buffering) {
|
||||
this.buffering = buffering;
|
||||
}
|
||||
|
||||
public String toString(String encoding) {
|
||||
String s = null;
|
||||
try {
|
||||
s = buffer.toString(encoding);
|
||||
} catch(UnsupportedEncodingException e) {
|
||||
// TODO error
|
||||
e.printStackTrace(System.err);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
public byte[] toByteArray() {
|
||||
return buffer.toByteArray();
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
buffer.reset();
|
||||
}
|
||||
|
||||
public void commit() throws IOException {
|
||||
if (!buffering) return;
|
||||
buffer.writeTo(sos);
|
||||
sos.close();
|
||||
}
|
||||
|
||||
public void setSuspended(boolean suspended) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(int i) throws IOException {
|
||||
if (!buffering) sos.write(i);
|
||||
print(Integer.toString(i));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(byte[] b) throws IOException {
|
||||
if (!buffering) sos.write(b, 0, b.length);
|
||||
buffer.write(b, 0, b.length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(byte[] b, int off, int len) throws IOException {
|
||||
if (!buffering) sos.write(b, off, len);
|
||||
buffer.write(b, off, len);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flush() throws IOException {
|
||||
if (!buffering) sos.flush();
|
||||
// we can't flush our buffer
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
if (!buffering) sos.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void print(String s) throws IOException {
|
||||
if (!buffering) sos.print(s);
|
||||
byte[] bytes = s.getBytes();
|
||||
buffer.write(bytes, 0, bytes.length);
|
||||
}
|
||||
}
|
||||
215
java/ModSecurityTestApp/src/java/org/modsecurity/MsWriter.java
Normal file
215
java/ModSecurityTestApp/src/java/org/modsecurity/MsWriter.java
Normal file
@@ -0,0 +1,215 @@
|
||||
package org.modsecurity;
|
||||
|
||||
import java.io.CharArrayWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
|
||||
public class MsWriter extends PrintWriter {
|
||||
|
||||
private static final char[] NEW_LINE = { '\r', '\n' };
|
||||
|
||||
private boolean buffering = false;
|
||||
|
||||
// TODO this buffer should be replaced with a
|
||||
// custom-made one to reduce memory consumption
|
||||
private CharArrayWriter buffer;
|
||||
|
||||
private PrintWriter writer;
|
||||
|
||||
public MsWriter(PrintWriter writer) {
|
||||
super(writer);
|
||||
this.writer = writer;
|
||||
buffer = new CharArrayWriter();
|
||||
}
|
||||
|
||||
public void setBuffering(boolean buffering) {
|
||||
this.buffering = buffering;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
public char[] toCharArray() {
|
||||
return buffer.toCharArray();
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
buffer.reset();
|
||||
}
|
||||
|
||||
public void commit() throws IOException {
|
||||
if (!buffering) return;
|
||||
buffer.writeTo(writer);
|
||||
writer.close();
|
||||
}
|
||||
|
||||
public void setSuspended(boolean suspended) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flush() {
|
||||
if (!buffering) super.flush();
|
||||
// we can't flush our buffer
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
if (!buffering) super.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void print(boolean b) {
|
||||
if (!buffering) super.print(b);
|
||||
write(Boolean.toString(b));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void print(char c) {
|
||||
if (!buffering) super.print(c);
|
||||
buffer.append(c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void print(int i) {
|
||||
if (!buffering) super.print(i);
|
||||
write(Integer.toString(i));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void print(long l) {
|
||||
if (!buffering) super.print(l);
|
||||
write(Long.toString(l));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void print(float f) {
|
||||
if (!buffering) super.print(f);
|
||||
write(Float.toString(f));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void print(double d) {
|
||||
if (!buffering) super.print(d);
|
||||
write(Double.toString(d));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void print(char s[]) {
|
||||
if (!buffering) super.print(s);
|
||||
buffer.write(s, 0, s.length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void print(String s) {
|
||||
if (!buffering) super.print(s);
|
||||
write(s);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void print(Object obj) {
|
||||
if (!buffering) super.print(obj);
|
||||
write(obj.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void println() {
|
||||
if (!buffering) super.println();
|
||||
write(NEW_LINE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void println(boolean b) {
|
||||
if (!buffering) super.println(b);
|
||||
write(Boolean.toString(b));
|
||||
write(NEW_LINE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void println(char c) {
|
||||
if (!buffering) super.println(c);
|
||||
buffer.write(c);
|
||||
write(NEW_LINE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void println(int i) {
|
||||
if (!buffering) super.println(i);
|
||||
write(Integer.toString(i));
|
||||
write(NEW_LINE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void println(long l) {
|
||||
if (!buffering) super.println(l);
|
||||
write(Long.toString(l));
|
||||
write(NEW_LINE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void println(float f) {
|
||||
if (!buffering) super.println(f);
|
||||
write(Float.toString(f));
|
||||
write(NEW_LINE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void println(double d) {
|
||||
if (!buffering) super.println(d);
|
||||
write(Double.toString(d));
|
||||
write(NEW_LINE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void println(char c[]) {
|
||||
if (!buffering) super.println(c);
|
||||
write(c, 0, c.length);
|
||||
write(NEW_LINE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void println(String s) {
|
||||
if (!buffering) super.println(s);
|
||||
write(s);
|
||||
write(NEW_LINE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void println(Object o) {
|
||||
if (!buffering) super.println(o);
|
||||
write(o.toString());
|
||||
write(NEW_LINE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(int c) {
|
||||
if (!buffering) super.write(c);
|
||||
buffer.write(c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(char buf[], int off, int len) {
|
||||
if (!buffering) super.write(buf, off, len);
|
||||
buffer.write(buf, off, len);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(char buf[]) {
|
||||
if (!buffering) super.write(buf);
|
||||
buffer.write(buf, 0, buf.length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(String s) {
|
||||
if (!buffering) super.write(s);
|
||||
buffer.write(s, 0, s.length());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(String s, int off, int len) {
|
||||
if (!buffering) super.write(s, off, len);
|
||||
buffer.write(s, off, len);
|
||||
}
|
||||
}
|
||||
@@ -24,6 +24,13 @@
|
||||
#define MODSECURITY__ISPV6_MET "isIPv6"
|
||||
#define MODSECURITY__ISPV6_SIG "(Ljava/lang/String;)Z"
|
||||
|
||||
#define HTTPTRANSACTION_HTTPREQUEST_MET "getHttpRequest"
|
||||
#define HTTPTRANSACTION_HTTPREQUEST_SIG "()Ljavax/servlet/http/HttpServletRequest;"
|
||||
#define HTTPTRANSACTION_MSHTTPREQUEST_MET "getMsHttpRequest"
|
||||
#define HTTPTRANSACTION_MSHTTPREQUEST_SIG "()Lorg/modsecurity/MsHttpServletRequest;"
|
||||
#define HTTPTRANSACTION_TRANSACTIONID_MET "getTransactionID"
|
||||
|
||||
|
||||
#define SERVLETREQUEST_SERVERNAME_MET "getServerName"
|
||||
#define SERVLETREQUEST_CHARENCODING_MET "getCharacterEncoding"
|
||||
#define SERVLETREQUEST_CONTENTTYPE_MET "getContentType"
|
||||
@@ -42,6 +49,8 @@
|
||||
#define HTTPSERVLETREQUEST_REQUESTURL_MET "getRequestURL"
|
||||
#define HTTPSERVLETREQUEST_REQUESTURL_SIG "()Ljava/lang/StringBuffer;"
|
||||
|
||||
#define MSHTTPSERVLETREQUEST_READBODY_MET "readBody"
|
||||
#define MSHTTPSERVLETREQUEST_READBODY_SIG "(I)V"
|
||||
|
||||
#define SERVLETRESPONSE_CONTENTTYPE_MET "getContentType"
|
||||
#define SERVLETRESPONSE_CHARENCODING_MET "getCharacterEncoding"
|
||||
@@ -324,7 +333,7 @@ inline void setHeaders(JNIEnv *env, jclass modSecurityClass, jobject httpServlet
|
||||
}
|
||||
}
|
||||
|
||||
JNIEXPORT jint JNICALL Java_org_modsecurity_ModSecurity_onRequest(JNIEnv *env, jobject obj, jstring configPath, jobject servletRequest, jobject httpServletRequest, jstring requestID, jboolean reloadConfig)
|
||||
JNIEXPORT jint JNICALL Java_org_modsecurity_ModSecurity_onRequest(JNIEnv *env, jobject obj, jstring configPath, jobject httpTransaction, jboolean reloadConfig)
|
||||
{
|
||||
//critical section ?
|
||||
conn_rec *c;
|
||||
@@ -350,18 +359,33 @@ JNIEXPORT jint JNICALL Java_org_modsecurity_ModSecurity_onRequest(JNIEnv *env, j
|
||||
c = modsecNewConnection();
|
||||
modsecProcessConnection(c);
|
||||
r = modsecNewRequest(c, config);
|
||||
|
||||
|
||||
const char *reqID = fromJString(env, requestID, r->pool); //unique ID of this request
|
||||
apr_table_setn(requests, reqID, (const char*) r); //store this request for response processing
|
||||
|
||||
|
||||
jclass httpTransactionClass = env->GetObjectClass(httpTransaction);
|
||||
jmethodID getHttpRequest = env->GetMethodID(httpTransactionClass, HTTPTRANSACTION_MSHTTPREQUEST_MET, HTTPTRANSACTION_MSHTTPREQUEST_SIG);
|
||||
|
||||
jobject httpServletRequest = env->CallObjectMethod(httpTransaction, getHttpRequest);
|
||||
jobject servletRequest = httpServletRequest; //test it
|
||||
|
||||
jclass httpServletRequestClass = env->GetObjectClass(httpServletRequest); //HttpServletRequest interface
|
||||
jclass servletRequestClass = env->GetObjectClass(servletRequest); //ServletRequest interface
|
||||
jclass modSecurityClass = env->GetObjectClass(obj); //ModSecurity class
|
||||
|
||||
jmethodID readBody = env->GetMethodID(httpServletRequestClass, MSHTTPSERVLETREQUEST_READBODY_MET, MSHTTPSERVLETREQUEST_READBODY_SIG);
|
||||
env->CallIntMethod(httpServletRequest, readBody, config->reqbody_limit);
|
||||
if (env->ExceptionCheck() == JNI_TRUE) //read body raised an Exception, return to JVM
|
||||
{
|
||||
modsecFinishRequest(r);
|
||||
return DONE;
|
||||
}
|
||||
|
||||
jmethodID getTransactionID = env->GetMethodID(httpTransactionClass, HTTPTRANSACTION_TRANSACTIONID_MET, STRINGRETURN_SIG);
|
||||
const char *reqID = fromJStringMethod(env, getTransactionID, httpTransaction, r->pool); //fromJString(env, requestID, r->pool); //unique ID of this request
|
||||
apr_table_setn(requests, reqID, (const char*) r); //store this request for response processing
|
||||
|
||||
jmethodID getInputStream = (env)->GetMethodID(servletRequestClass, SERVLETREQUEST_INPUTSTREAM_MET, SERVLETREQUEST_INPUTSTREAM_SIG);
|
||||
jobject inputStream = (env)->CallObjectMethod(servletRequest, getInputStream); //Request body input stream used in the read body callback
|
||||
|
||||
jmethodID getInputStream = (env)->GetMethodID(httpServletRequestClass, SERVLETREQUEST_INPUTSTREAM_MET, SERVLETREQUEST_INPUTSTREAM_SIG);
|
||||
jobject inputStream = (env)->CallObjectMethod(httpServletRequest, getInputStream); //Request body input stream used in the read body callback
|
||||
|
||||
storeJavaServletContext(r, inputStream);
|
||||
|
||||
|
||||
@@ -32,15 +32,15 @@ JNIEXPORT jint JNICALL Java_org_modsecurity_ModSecurity_destroy
|
||||
/*
|
||||
* Class: org_modsecurity_ModSecurity
|
||||
* Method: onRequest
|
||||
* Signature: (Ljava/lang/String;Ljavax/servlet/ServletRequest;Ljavax/servlet/http/HttpServletRequest;Ljava/lang/String;Z)I
|
||||
* Signature: (Ljava/lang/String;Lorg/modsecurity/MsHttpTransaction;Z)I
|
||||
*/
|
||||
JNIEXPORT jint JNICALL Java_org_modsecurity_ModSecurity_onRequest
|
||||
(JNIEnv *, jobject, jstring, jobject, jobject, jstring, jboolean);
|
||||
(JNIEnv *, jobject, jstring, jobject, jboolean);
|
||||
|
||||
/*
|
||||
* Class: org_modsecurity_ModSecurity
|
||||
* Method: onResponse
|
||||
* Signature: (Ljavax/servlet/ServletResponse;Ljavax/servlet/http/HttpServletResponse;Ljava/lang/String)I
|
||||
* Signature: (Ljavax/servlet/ServletResponse;Ljavax/servlet/http/HttpServletResponse;Ljava/lang/String;)I
|
||||
*/
|
||||
JNIEXPORT jint JNICALL Java_org_modsecurity_ModSecurity_onResponse
|
||||
(JNIEnv *, jobject, jobject, jobject, jstring);
|
||||
|
||||
Reference in New Issue
Block a user