diff --git a/java/ModSecurityTestApp/src/java/org/apache/commons/fileupload/DefaultFileItem.java b/java/ModSecurityTestApp/src/java/org/apache/commons/fileupload/DefaultFileItem.java new file mode 100644 index 00000000..77c3b25a --- /dev/null +++ b/java/ModSecurityTestApp/src/java/org/apache/commons/fileupload/DefaultFileItem.java @@ -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 + * . + * + */ + + +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; + + +/** + *

The default implementation of the + * {@link org.apache.commons.fileupload.FileItem FileItem} interface. + * + *

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 Rafal Krzewski + * @author Sean Legassick + * @author Jason van Zyl + * @author John McNally + * @author Martin Cooper + * @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 null 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 DefaultFileItem instance. + * + * @param fieldName The name of the form field. + * @param contentType The content type passed by the browser or + * null 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 + * null 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 null if + * not defined. + * + * @return The content type passed by the browser or null 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 true if the file contents will be read + * from memory; false 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. + *

+ * 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. + *

+ * This method is only guaranteed to work once, 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 File 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 FileItem 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 FileItem instance represents + * a simple form field. + * + * @return true if the instance represents a simple form + * field; false if it represents an uploaded file. + * + * @see #setFormField(boolean) + * + */ + public boolean isFormField() + { + return isFormField; + } + + + /** + * Specifies whether or not a FileItem instance represents + * a simple form field. + * + * @param state true if the instance represents a simple form + * field; false 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 FileItem's + * data's temporary location on the disk. Note that for + * FileItems that have their data stored in memory, + * this method will return null. 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 null 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; + } + +} diff --git a/java/ModSecurityTestApp/src/java/org/apache/commons/fileupload/DefaultFileItemFactory.java b/java/ModSecurityTestApp/src/java/org/apache/commons/fileupload/DefaultFileItemFactory.java new file mode 100644 index 00000000..4c0ed03c --- /dev/null +++ b/java/ModSecurityTestApp/src/java/org/apache/commons/fileupload/DefaultFileItemFactory.java @@ -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 + * . + * + */ + + +package org.apache.commons.fileupload; + +import java.io.File; + + +/** + *

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.

+ * + *

If not otherwise configured, the default configuration values are as + * follows: + *

+ *

+ * + * @author Martin Cooper + * + * @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 true if this is a plain form field; + * false 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); + } + +} diff --git a/java/ModSecurityTestApp/src/java/org/apache/commons/fileupload/DeferredFileOutputStream.java b/java/ModSecurityTestApp/src/java/org/apache/commons/fileupload/DeferredFileOutputStream.java new file mode 100644 index 00000000..c89a3a50 --- /dev/null +++ b/java/ModSecurityTestApp/src/java/org/apache/commons/fileupload/DeferredFileOutputStream.java @@ -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 + * . + * + */ + + +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; + +/** + *

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.

+ * + * @author Martin Cooper + * + * @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 memoryOutputStream or + * diskOutputStream. + */ + 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 true if the data is available in memory; + * false 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 null. + * + * @return The data for this output stream, or null 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 File, assuming + * that the data was written to disk. If the data was retained in memory, + * this method returns null. + * + * @return The file for this output stream, or null if no such + * file exists. + */ + public File getFile() + { + return outputFile; + } +} diff --git a/java/ModSecurityTestApp/src/java/org/apache/commons/fileupload/DiskFileUpload.java b/java/ModSecurityTestApp/src/java/org/apache/commons/fileupload/DiskFileUpload.java new file mode 100644 index 00000000..e70c7733 --- /dev/null +++ b/java/ModSecurityTestApp/src/java/org/apache/commons/fileupload/DiskFileUpload.java @@ -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 + * . + * + */ + + +package org.apache.commons.fileupload; + + +import java.io.File; +import java.util.List; +import javax.servlet.http.HttpServletRequest; + + +/** + *

High level API for processing file uploads.

+ * + *

This class handles multiple files per single HTML widget, sent using + * multipart/mixed encoding type, as specified by + * RFC 1867. Use {@link + * #parseRequest(HttpServletRequest)} to acquire a list of {@link + * org.apache.commons.fileupload.FileItem}s associated with a given HTML + * widget.

+ * + *

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.

+ * + * @author Rafal Krzewski + * @author Daniel Rall + * @author Jason van Zyl + * @author John McNally + * @author Martin Cooper + * @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 FileItem 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 FileItem 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 DefaultFileItemFactory or a subclass + * thereof, or else a ClassCastException 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 RFC 1867 + * compliant multipart/form-data stream. If files are stored + * on disk, the path is given by getRepository(). + * + * @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 FileItem 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); + } + +} diff --git a/java/ModSecurityTestApp/src/java/org/apache/commons/fileupload/FileItem.java b/java/ModSecurityTestApp/src/java/org/apache/commons/fileupload/FileItem.java new file mode 100644 index 00000000..5a4133dc --- /dev/null +++ b/java/ModSecurityTestApp/src/java/org/apache/commons/fileupload/FileItem.java @@ -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 + * . + * + */ + + +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; + + +/** + *

This class represents a file or form item that was received within a + * multipart/form-data POST request. + * + *

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. + * + *

While this interface does not extend + * javax.activation.DataSource 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 + * javax.activation.DataSource with minimal additional work. + * + * @author Rafal Krzewski + * @author Sean Legassick + * @author Jason van Zyl + * @author Martin Cooper + * + * @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 null if + * not defined. + * + * @return The content type passed by the browser or null 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 true if the file contents will be read from memory; + * false 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. + *

+ * 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 File 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 FileItem 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 FileItem instance represents + * a simple form field. + * + * @return true if the instance represents a simple form + * field; false if it represents an uploaded file. + */ + boolean isFormField(); + + + /** + * Specifies whether or not a FileItem instance represents + * a simple form field. + * + * @param state true if the instance represents a simple form + * field; false 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; + +} diff --git a/java/ModSecurityTestApp/src/java/org/apache/commons/fileupload/FileItemFactory.java b/java/ModSecurityTestApp/src/java/org/apache/commons/fileupload/FileItemFactory.java new file mode 100644 index 00000000..5f7cac69 --- /dev/null +++ b/java/ModSecurityTestApp/src/java/org/apache/commons/fileupload/FileItemFactory.java @@ -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 + * . + * + */ + + +package org.apache.commons.fileupload; + + +/** + *

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.

+ * + * @author Martin Cooper + * + * @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 true if this is a plain form field; + * false 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 + ); +} diff --git a/java/ModSecurityTestApp/src/java/org/apache/commons/fileupload/FileUpload.java b/java/ModSecurityTestApp/src/java/org/apache/commons/fileupload/FileUpload.java new file mode 100644 index 00000000..be5e8af5 --- /dev/null +++ b/java/ModSecurityTestApp/src/java/org/apache/commons/fileupload/FileUpload.java @@ -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 + * . + * + */ + + +package org.apache.commons.fileupload; + + +/** + *

High level API for processing file uploads.

+ * + *

This class handles multiple files per single HTML widget, sent using + * multipart/mixed encoding type, as specified by + * RFC 1867. Use {@link + * #parseRequest(HttpServletRequest)} to acquire a list of {@link + * org.apache.commons.fileupload.FileItem}s associated with a given HTML + * widget.

+ * + *

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.

+ * + * @author Rafal Krzewski + * @author Daniel Rall + * @author Jason van Zyl + * @author John McNally + * @author Martin Cooper + * @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 FileItem instances. + * + * @see #FileUpload(FileItemFactory) + */ + public FileUpload() + { + super(); + } + + + /** + * Constructs an instance of this class which uses the supplied factory to + * create FileItem 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; + } + + +} diff --git a/java/ModSecurityTestApp/src/java/org/apache/commons/fileupload/FileUploadBase.java b/java/ModSecurityTestApp/src/java/org/apache/commons/fileupload/FileUploadBase.java new file mode 100644 index 00000000..bc55d509 --- /dev/null +++ b/java/ModSecurityTestApp/src/java/org/apache/commons/fileupload/FileUploadBase.java @@ -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 + * . + * + */ + + +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; + + +/** + *

High level API for processing file uploads.

+ * + *

This class handles multiple files per single HTML widget, sent using + * multipart/mixed encoding type, as specified by + * RFC 1867. Use {@link + * #parseRequest(HttpServletRequest)} to acquire a list of {@link + * org.apache.commons.fileupload.FileItem}s associated with a given HTML + * widget.

+ * + *

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.

+ * + * @author Rafal Krzewski + * @author Daniel Rall + * @author Jason van Zyl + * @author John McNally + * @author Martin Cooper + * @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 true if the request is multipart; + * false 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 null, 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 null, 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 RFC 1867 + * compliant multipart/form-data stream. If files are stored + * on disk, the path is given by getRepository(). + * + * @param req The servlet request to be parsed. + * + * @return A list of FileItem 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 Content-disposition + * header. + * + * @param headers A Map containing the HTTP request headers. + * + * @return The file name for the current encapsulation. + */ + 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 Content-disposition + * header. + * + * @param headers A Map containing the HTTP request headers. + * + * @return The field name for the current encapsulation. + */ + 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 Map 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 FileItem 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)); + } + + + /** + *

Parses the header-part and returns as key/value + * pairs. + * + *

If there are multiple headers of the same names, the name + * will map to a comma-separated list containing the values. + * + * @param headerPart The header-part of the current + * encapsulation. + * + * @return A Map 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 Map 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 InvalidContentTypeException with no + * detail message. + */ + public InvalidContentTypeException() + { + super(); + } + + /** + * Constructs an InvalidContentTypeException 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 UnknownSizeException with no + * detail message. + */ + public UnknownSizeException() + { + super(); + } + + /** + * Constructs an UnknownSizeException 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 SizeExceededException with no + * detail message. + */ + public SizeLimitExceededException() + { + super(); + } + + /** + * Constructs an SizeExceededException with + * the specified detail message. + * + * @param message The detail message. + */ + public SizeLimitExceededException(String message) + { + super(message); + } + } + +} diff --git a/java/ModSecurityTestApp/src/java/org/apache/commons/fileupload/FileUploadException.java b/java/ModSecurityTestApp/src/java/org/apache/commons/fileupload/FileUploadException.java new file mode 100644 index 00000000..04475f26 --- /dev/null +++ b/java/ModSecurityTestApp/src/java/org/apache/commons/fileupload/FileUploadException.java @@ -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 + * . + * + */ + + +package org.apache.commons.fileupload; + + +/** + * Exception for errors encountered while processing the request. + * + * @author John McNally + * @version $Id: FileUploadException.java,v 1.7 2003/04/27 17:30:06 martinc Exp $ + */ +public class FileUploadException + extends Exception +{ + + /** + * Constructs a new FileUploadException without message. + */ + public FileUploadException() + { + } + + /** + * Constructs a new FileUploadException with specified detail + * message. + * + * @param msg the error message. + */ + public FileUploadException(String msg) + { + super(msg); + } +} diff --git a/java/ModSecurityTestApp/src/java/org/apache/commons/fileupload/MultipartStream.java b/java/ModSecurityTestApp/src/java/org/apache/commons/fileupload/MultipartStream.java new file mode 100644 index 00000000..62aa16b5 --- /dev/null +++ b/java/ModSecurityTestApp/src/java/org/apache/commons/fileupload/MultipartStream.java @@ -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 + * . + * + */ + + +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; + + +/** + *

Low level API for processing file uploads. + * + *

This class can be used to process data streams conforming to MIME + * 'multipart' format as defined in + * RFC 1867. Arbitrarily + * large amounts of data in the stream can be processed under constant + * memory usage. + * + *

The format of the stream is defined in the following way:
+ * + * + * multipart-body := preamble 1*encapsulation close-delimiter epilogue
+ * encapsulation := delimiter body CRLF
+ * delimiter := "--" boundary CRLF
+ * close-delimiter := "--" boudary "--"
+ * preamble := <ignore>
+ * epilogue := <ignore>
+ * body := header-part CRLF body-part
+ * header-part := 1*header CRLF
+ * header := header-name ":" header-value
+ * header-name := <printable ascii characters except ":">
+ * header-value := <any ascii characters except CR & LF>
+ * body-data := <arbitrary data>
+ *
+ * + *

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 required to have a + * boundary token of the same length as the parent stream (see {@link + * #setBoundary(byte[])}). + * + *

Here is an exaple of usage of this class.
+ * + *

+ *    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
+ *    }
+ *
+ * 
+ * + * @author Rafal Krzewski + * @author Martin Cooper + * @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 header-part 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 header-part + * (CRLFCRLF). + */ + 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 (CRLF). + */ + protected static final byte[] FIELD_SEPARATOR = { 0x0D, 0x0A }; + + + /** + * A byte sequence that that follows a delimiter of the last + * encapsulation in the stream (--). + */ + 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 CRLF--. + */ + 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. + *
+ * 0 <= head < bufSize + */ + private int head; + + + /** + * The index of last valid characer in the buffer + 1. + *
+ * 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() + { + } + + + /** + *

Constructs a MultipartStream with a custom size buffer. + * + *

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 InputStream to serve as a data source. + * @param boundary The token used for dividing the stream into + * encapsulations. + * @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; + } + + + /** + *

Constructs a MultipartStream with a default size buffer. + * + * @param input The InputStream to serve as a data source. + * @param boundary The token used for dividing the stream into + * encapsulations. + * + * @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 null, 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 null, 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 buffer, 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 boundary token, and checks whether more + * encapsulations are contained in the stream. + * + * @return true if there are more encapsulations in + * this stream; false 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; + } + + + /** + *

Changes the boundary token used for partitioning the stream. + * + *

This method allows single pass processing of nested multipart + * streams. + * + *

The boundary token of the nested stream is required + * to be of the same length as the boundary token in parent stream. + * + *

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 boundary + * 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); + } + + + /** + *

Reads the header-part of the current + * encapsulation. + * + *

Headers are returned verbatim to the input stream, including the + * trailing CRLF marker. Parsing is left to the + * application. + * + *

TODO allow limiting maximum header size to + * protect against abuse. + * + * @return The header-part 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; + } + + + /** + *

Reads body-data from the current + * encapsulation and writes its contents into the + * output Stream. + * + *

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 Stream 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; + } + + + /** + *

Reads body-data from the current + * encapsulation and discards it. + * + *

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 encapsulation. + * + * @return true if an encapsulation 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 count first bytes in the arrays + * a and b. + * + * @param a The first array to compare. + * @param b The second array to compare. + * @param count How many bytes should be compared. + * + * @return true if count first bytes in arrays + * a and b 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 buffer, + * starting at the specified position. + * + * @param value The value to find. + * @param pos The starting position for searching. + * + * @return The position of byte found, counting from beginning of the + * buffer, or -1 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 boundary in the buffer + * region delimited by head and tail. + * + * @return The position of the boundary found, counting from the + * beginning of the buffer, or -1 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 MalformedStreamException with no + * detail message. + */ + public MalformedStreamException() + { + super(); + } + + /** + * Constructs an MalformedStreamException 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 IllegalBoundaryException with no + * detail message. + */ + public IllegalBoundaryException() + { + super(); + } + + /** + * Constructs an IllegalBoundaryException 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. + * + */ + + +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. + *

+ * This class overrides all OutputStream methods. However, these + * overrides ultimately call the corresponding methods in the underlying output + * stream implementation. + *

+ * NOTE: This implementation may trigger the event before the threshold + * is actually reached, since it triggers when a pending write operation would + * cause the threshold to be exceeded. + * + * @author Martin Cooper + * + * @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 b.length 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 len bytes from the specified byte array starting at + * offset off 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 true if the threshold has been reached; + * false 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 + * OutputStream 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; +} diff --git a/java/ModSecurityTestApp/src/java/org/apache/commons/fileupload/package.html b/java/ModSecurityTestApp/src/java/org/apache/commons/fileupload/package.html new file mode 100644 index 00000000..853871f1 --- /dev/null +++ b/java/ModSecurityTestApp/src/java/org/apache/commons/fileupload/package.html @@ -0,0 +1,66 @@ + + + + Overview of the org.apache.commons.fileupload component + + +

+ Component for handling html file uploads as given by rfc 1867 + RFC 1867. +

+

+ 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 FileItem'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. +

+ +

+ Normal usage example: +

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

+ In the example above the first file is loaded into memory as a + String. 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. +

+ + diff --git a/java/ModSecurityTestApp/src/java/org/modsecurity/MSHttpServletRequestWrapper.java b/java/ModSecurityTestApp/src/java/org/modsecurity/MSHttpServletRequestWrapper.java new file mode 100644 index 00000000..6a3c8bfd --- /dev/null +++ b/java/ModSecurityTestApp/src/java/org/modsecurity/MSHttpServletRequestWrapper.java @@ -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 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(); + 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; + } + } +} diff --git a/java/ModSecurityTestApp/src/java/org/modsecurity/ModSecurity.java b/java/ModSecurityTestApp/src/java/org/modsecurity/ModSecurity.java index 4bc78a48..3564e062 100644 --- a/java/ModSecurityTestApp/src/java/org/modsecurity/ModSecurity.java +++ b/java/ModSecurityTestApp/src/java/org/modsecurity/ModSecurity.java @@ -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 libraries = (Vector) 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 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 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); diff --git a/java/ModSecurityTestApp/src/java/org/modsecurity/ModSecurityFilter.java b/java/ModSecurityTestApp/src/java/org/modsecurity/ModSecurityFilter.java index d48b24ab..d5b41901 100644 --- a/java/ModSecurityTestApp/src/java/org/modsecurity/ModSecurityFilter.java +++ b/java/ModSecurityTestApp/src/java/org/modsecurity/ModSecurityFilter.java @@ -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 diff --git a/java/ModSecurityTestApp/src/java/org/modsecurity/MsHttpServletRequest.java b/java/ModSecurityTestApp/src/java/org/modsecurity/MsHttpServletRequest.java new file mode 100644 index 00000000..aee1094b --- /dev/null +++ b/java/ModSecurityTestApp/src/java/org/modsecurity/MsHttpServletRequest.java @@ -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)); + } +} \ No newline at end of file diff --git a/java/ModSecurityTestApp/src/java/org/modsecurity/MsHttpServletResponse.java b/java/ModSecurityTestApp/src/java/org/modsecurity/MsHttpServletResponse.java new file mode 100644 index 00000000..f876551b --- /dev/null +++ b/java/ModSecurityTestApp/src/java/org/modsecurity/MsHttpServletResponse.java @@ -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 getHeaderNames() { + Collection headerNames = new LinkedList(); + 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 getHeaders(String name) { + Collection headerValues = new LinkedList(); + 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); + } +} diff --git a/java/ModSecurityTestApp/src/java/org/modsecurity/MsHttpTransaction.java b/java/ModSecurityTestApp/src/java/org/modsecurity/MsHttpTransaction.java new file mode 100644 index 00000000..5d50cf5f --- /dev/null +++ b/java/ModSecurityTestApp/src/java/org/modsecurity/MsHttpTransaction.java @@ -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; + } + +} diff --git a/java/ModSecurityTestApp/src/java/org/modsecurity/MsOutputStream.java b/java/ModSecurityTestApp/src/java/org/modsecurity/MsOutputStream.java new file mode 100644 index 00000000..b2c3273e --- /dev/null +++ b/java/ModSecurityTestApp/src/java/org/modsecurity/MsOutputStream.java @@ -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); + } +} \ No newline at end of file diff --git a/java/ModSecurityTestApp/src/java/org/modsecurity/MsWriter.java b/java/ModSecurityTestApp/src/java/org/modsecurity/MsWriter.java new file mode 100644 index 00000000..5b7a0c31 --- /dev/null +++ b/java/ModSecurityTestApp/src/java/org/modsecurity/MsWriter.java @@ -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); + } +} \ No newline at end of file diff --git a/java/org_modsecurity_ModSecurity.cpp b/java/org_modsecurity_ModSecurity.cpp index 187b09b5..a45b05f6 100644 --- a/java/org_modsecurity_ModSecurity.cpp +++ b/java/org_modsecurity_ModSecurity.cpp @@ -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); diff --git a/java/org_modsecurity_ModSecurity.h b/java/org_modsecurity_ModSecurity.h index f562d87b..a8791e72 100644 --- a/java/org_modsecurity_ModSecurity.h +++ b/java/org_modsecurity_ModSecurity.h @@ -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);