Support for file upload & request/response java wrappers

This commit is contained in:
Mihai Pitu
2013-07-12 11:48:46 +01:00
committed by Felipe Zimmerle
parent b9080aad18
commit 8f3b3eb468
22 changed files with 5683 additions and 39 deletions

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}

View File

@@ -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
);
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}

View File

@@ -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 := &lt;ignore&gt;<br>
* epilogue := &lt;ignore&gt;<br>
* body := header-part CRLF body-part<br>
* header-part := 1*header CRLF<br>
* header := header-name ":" header-value<br>
* header-name := &lt;printable ascii characters except ":"&gt;<br>
* header-value := &lt;any ascii characters except CR & LF&gt;<br>
* body-data := &lt;arbitrary data&gt;<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();
}
}
*/
}

View File

@@ -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;
}

View File

@@ -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&nbsp;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>

View File

@@ -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;
}
}
}

View File

@@ -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);

View File

@@ -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

View File

@@ -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));
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View 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);
}
}

View File

@@ -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);

View File

@@ -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);