From 62e13a2fe673fd52e813768df57fde0982283d14 Mon Sep 17 00:00:00 2001
From: markt
+ * The data can be retrieved using
+ * Closing a ByteArrayOutputStream has no effect. The methods in
+ * this class can be called after the stream has been closed without
+ * generating an IOException.
+ *
+ * This is an alternative implementation of the java.io.ByteArrayOutputStream
+ * class. The original implementation only allocates 32 bytes at the beginning.
+ * As this class is designed for heavy duty it starts at 1024 bytes. In contrast
+ * to the original it doesn't reallocate the whole memory block but allocates
+ * additional buffers. This way no buffers need to be garbage collected and
+ * the contents don't have to be copied to the new buffer. This class is
+ * designed to behave exactly like the original. The only exception is the
+ * deprecated toString(int) method that has been ignored.
+ *
+ * @author Jeremias Maerki
+ * @author Holger Hoffstatte
+ * @version $Id$
+ */
+public class ByteArrayOutputStream extends OutputStream {
+
+ /** A singleton empty byte array. */
+ private static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
+
+ /** The list of buffers, which grows and never reduces. */
+ private List buffers = new ArrayList();
+ /** The index of the current buffer. */
+ private int currentBufferIndex;
+ /** The total count of bytes in all the filled buffers. */
+ private int filledBufferSum;
+ /** The current buffer. */
+ private byte[] currentBuffer;
+ /** The total count of bytes written. */
+ private int count;
+
+ /**
+ * Creates a new byte array output stream. The buffer capacity is
+ * initially 1024 bytes, though its size increases if necessary.
+ */
+ public ByteArrayOutputStream() {
+ this(1024);
+ }
+
+ /**
+ * Creates a new byte array output stream, with a buffer capacity of
+ * the specified size, in bytes.
+ *
+ * @param size the initial size
+ * @throws IllegalArgumentException if size is negative
+ */
+ public ByteArrayOutputStream(int size) {
+ if (size < 0) {
+ throw new IllegalArgumentException(
+ "Negative initial size: " + size);
+ }
+ needNewBuffer(size);
+ }
+
+ /**
+ * Return the appropriate An output stream which will retain data in memory until a specified
+ * 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.
+ * This class originated in FileUpload processing. In this use case, you do
+ * not know in advance the size of the file being uploaded. If the file is small
+ * you want to store it in memory (for speed), but if the file is large you want
+ * to store it to file (to avoid memory issues).
*
* @author Martin Cooper
+ * @author gaxzerow
*
* @version $Id$
*/
@@ -42,7 +47,7 @@ public class DeferredFileOutputStream
/**
- * The output stream to which data will be written prior to the threshold
+ * The output stream to which data will be written prior to the theshold
* being reached.
*/
private ByteArrayOutputStream memoryOutputStream;
@@ -61,6 +66,26 @@ public class DeferredFileOutputStream
*/
private File outputFile;
+ /**
+ * The temporary file prefix.
+ */
+ private String prefix;
+
+ /**
+ * The temporary file suffix.
+ */
+ private String suffix;
+
+ /**
+ * The directory to use for temporary files.
+ */
+ private File directory;
+
+
+ /**
+ * True when close() has been called successfully.
+ */
+ private boolean closed = false;
// ----------------------------------------------------------- Constructors
@@ -77,18 +102,34 @@ public class DeferredFileOutputStream
super(threshold);
this.outputFile = outputFile;
- if (threshold < DefaultFileItemFactory.DEFAULT_SIZE_THRESHOLD) {
- // Small threshold, use it
- memoryOutputStream = new ByteArrayOutputStream(threshold);
- } else {
- // Large threshold. Use default and array will expand if required
- memoryOutputStream = new ByteArrayOutputStream(
- DefaultFileItemFactory.DEFAULT_SIZE_THRESHOLD);
- }
+ memoryOutputStream = new ByteArrayOutputStream();
currentOutputStream = memoryOutputStream;
}
+ /**
+ * Constructs an instance of this class which will trigger an event at the
+ * specified threshold, and save data to a temporary file beyond that point.
+ *
+ * @param threshold The number of bytes at which to trigger an event.
+ * @param prefix Prefix to use for the temporary file.
+ * @param suffix Suffix to use for the temporary file.
+ * @param directory Temporary file directory.
+ *
+ * @since Commons IO 1.4
+ */
+ public DeferredFileOutputStream(int threshold, String prefix, String suffix, File directory)
+ {
+ this(threshold, (File)null);
+ if (prefix == null) {
+ throw new IllegalArgumentException("Temporary file prefix is missing");
+ }
+ this.prefix = prefix;
+ this.suffix = suffix;
+ this.directory = directory;
+ }
+
+
// --------------------------------------- ThresholdingOutputStream methods
@@ -100,7 +141,6 @@ public class DeferredFileOutputStream
*
* @exception IOException if an error occurs.
*/
- @Override
protected OutputStream getStream() throws IOException
{
return currentOutputStream;
@@ -115,12 +155,13 @@ public class DeferredFileOutputStream
*
* @exception IOException if an error occurs.
*/
- @Override
protected void thresholdReached() throws IOException
{
- byte[] data = memoryOutputStream.toByteArray();
+ if (prefix != null) {
+ outputFile = File.createTempFile(prefix, suffix, directory);
+ }
FileOutputStream fos = new FileOutputStream(outputFile);
- fos.write(data);
+ memoryOutputStream.writeTo(fos);
currentOutputStream = fos;
memoryOutputStream = null;
}
@@ -161,9 +202,15 @@ public class DeferredFileOutputStream
/**
- * Returns the data for this output stream as a
+ * If the constructor specifying the file is used then it returns that
+ * same output file, even when threashold has not been reached.
+ *
+ * If constructor specifying a temporary file prefix/suffix is used
+ * then the temporary file created once the threashold is reached is returned
+ * If the threshold was not reached then The default implementation of the
- * {@link org.apache.tomcat.util.http.fileupload.FileItem FileItem} interface.
+ * {@link org.apache.commons.fileupload.FileItem FileItem} interface.
*
* After retrieving an instance of this class from a {@link
- * org.apache.tomcat.util.http.fileupload.DiskFileUpload DiskFileUpload} instance (see
- * {@link org.apache.tomcat.util.http.fileupload.DiskFileUpload
+ * 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.
*
+ * When using the The default {@link org.apache.tomcat.util.http.fileupload.FileItemFactory}
+ * The default {@link org.apache.commons.fileupload.FileItemFactory}
* implementation. This implementation creates
- * {@link org.apache.tomcat.util.http.fileupload.FileItem} instances which keep their
+ * {@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
@@ -39,12 +37,24 @@ import java.io.File;
*
* When using the The instance of {@link FileCleaningTracker}, which is responsible
+ * for deleting temporary files. May be null, if tracking files is not required. High level API for processing file uploads. This class handles multiple files per single HTML widget, sent using
- * Individual parts will be stored in temporary disk storage or in memory,
- * depending on their size, and will be available as {@link
- * org.apache.tomcat.util.http.fileupload.FileItem}s.
+ * This utility creates a background thread to handle file deletion.
+ * Each file to be deleted is registered with a handler object.
+ * When the handler object is garbage collected, the file is deleted.
+ *
+ * In an environment with multiple class loaders (a servlet container, for
+ * example), you should consider stopping the background thread if it is no
+ * longer needed. This is done by invoking the method
+ * {@link #exitWhenFinished}, typically in
+ * {@link javax.servlet.ServletContextListener#contextDestroyed} or similar.
+ *
+ * @author Noel Bergman
+ * @author Martin Cooper
+ * @version $Id: FileCleaner.java 490987 2006-12-29 12:11:48Z scolebourne $
+ */
+public class FileCleaningTracker {
+ /**
+ * Queue of
+ * In a simple environment, you don't need this method as the file cleaner
+ * thread will simply exit when the JVM exits. In a more complex environment,
+ * with multiple class loaders (such as an application server), you should be
+ * aware that the file cleaner thread will continue running even if the class
+ * loader it was started from terminates. This can consitute a memory leak.
+ *
+ * For example, suppose that you have developed a web application, which
+ * contains the commons-io jar file in your WEB-INF/lib directory. In other
+ * words, the FileCleaner class is loaded through the class loader of your
+ * web application. If the web application is terminated, but the servlet
+ * container is still running, then the file cleaner thread will still exist,
+ * posing a memory leak.
+ *
+ * This method allows the thread to be terminated. Simply call this method
+ * in the resource cleanup code, such as {@link javax.servlet.ServletContextListener#contextDestroyed}.
+ * One called, no new objects can be tracked by the file cleaner.
+ */
+ public synchronized void exitWhenFinished() {
+ // synchronized block protects reaper
+ exitWhenFinished = true;
+ if (reaper != null) {
+ synchronized (reaper) {
+ reaper.interrupt();
+ }
+ }
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * The reaper thread.
+ */
+ private final class Reaper extends Thread {
+ /** Construct a new Reaper */
+ Reaper() {
+ super("File Reaper");
+ setPriority(Thread.MAX_PRIORITY);
+ setDaemon(true);
+ }
+
+ /**
+ * Run the reaper thread that will delete files as their associated
+ * marker objects are reclaimed by the garbage collector.
+ */
+ public void run() {
+ // thread exits when exitWhenFinished is true and there are no more tracked objects
+ while (exitWhenFinished == false || trackers.size() > 0) {
+ Tracker tracker = null;
+ try {
+ // Wait for a tracker to remove.
+ tracker = (Tracker) q.remove();
+ } catch (Exception e) {
+ continue;
+ }
+ if (tracker != null) {
+ tracker.delete();
+ tracker.clear();
+ trackers.remove(tracker);
+ }
+ }
+ }
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Inner class which acts as the reference for a file pending deletion.
+ */
+ private static final class Tracker extends PhantomReference {
+
+ /**
+ * The full path to the file being tracked.
+ */
+ private final String path;
+ /**
+ * The strategy for deleting files.
+ */
+ private final FileDeleteStrategy deleteStrategy;
+
+ /**
+ * Constructs an instance of this class from the supplied parameters.
+ *
+ * @param path the full path to the file to be tracked, not null
+ * @param deleteStrategy the strategy to delete the file, null means normal
+ * @param marker the marker object used to track the file, not null
+ * @param queue the queue on to which the tracker will be pushed, not null
+ */
+ Tracker(String path, FileDeleteStrategy deleteStrategy, Object marker, ReferenceQueue queue) {
+ super(marker, queue);
+ this.path = path;
+ this.deleteStrategy = (deleteStrategy == null ? FileDeleteStrategy.NORMAL : deleteStrategy);
+ }
+
+ /**
+ * Deletes the file associated with this tracker instance.
+ *
+ * @return
+ * There is more than one way to delete a file.
+ * You may want to limit access to certain directories, to only delete
+ * directories if they are empty, or maybe to force deletion.
+ *
+ * This class captures the strategy to use and is designed for user subclassing.
+ *
+ * @author Stephen Colebourne
+ * @version $Id$
+ * @since Commons IO 1.3
+ */
+public class FileDeleteStrategy {
+
+ /**
+ * The singleton instance for normal file deletion, which does not permit
+ * the deletion of directories that are not empty.
+ */
+ public static final FileDeleteStrategy NORMAL = new FileDeleteStrategy("Normal");
+ /**
+ * The singleton instance for forced file deletion, which always deletes,
+ * even if the file represents a non-empty directory.
+ */
+ public static final FileDeleteStrategy FORCE = new ForceFileDeleteStrategy();
+
+ /** The name of the strategy. */
+ private final String name;
+
+ //-----------------------------------------------------------------------
+ /**
+ * Restricted constructor.
+ *
+ * @param name the name by which the strategy is known
+ */
+ protected FileDeleteStrategy(String name) {
+ this.name = name;
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Deletes the file object, which may be a file or a directory.
+ * All
+ * Subclass writers should override {@link #doDelete(File)}, not this method.
+ *
+ * @param fileToDelete the file to delete, null returns true
+ * @return true if the file was deleted, or there was no such file
+ */
+ public boolean deleteQuietly(File fileToDelete) {
+ if (fileToDelete == null || fileToDelete.exists() == false) {
+ return true;
+ }
+ try {
+ return doDelete(fileToDelete);
+ } catch (IOException ex) {
+ return false;
+ }
+ }
+
+ /**
+ * Deletes the file object, which may be a file or a directory.
+ * If the file does not exist, the method just returns.
+ *
+ * Subclass writers should override {@link #doDelete(File)}, not this method.
+ *
+ * @param fileToDelete the file to delete, not null
+ * @throws NullPointerException if the file is null
+ * @throws IOException if an error occurs during file deletion
+ */
+ public void delete(File fileToDelete) throws IOException {
+ if (fileToDelete.exists() && doDelete(fileToDelete) == false) {
+ throw new IOException("Deletion failed: " + fileToDelete);
+ }
+ }
+
+ /**
+ * Actually deletes the file object, which may be a file or a directory.
+ *
+ * This method is designed for subclasses to override.
+ * The implementation may return either false or an
+ * This implementation uses {@link File#delete()}.
+ *
+ * @param fileToDelete the file to delete, exists, not null
+ * @return true if the file was deleteds
+ * @throws NullPointerException if the file is null
+ * @throws IOException if an error occurs during file deletion
+ */
+ protected boolean doDelete(File fileToDelete) throws IOException {
+ return fileToDelete.delete();
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Gets a string describing the delete strategy.
+ *
+ * @return a string describing the delete strategy
+ */
+ public String toString() {
+ return "FileDeleteStrategy[" + name + "]";
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Force file deletion strategy.
+ */
+ static class ForceFileDeleteStrategy extends FileDeleteStrategy {
+ /** Default Constructor */
+ ForceFileDeleteStrategy() {
+ super("Force");
+ }
+
+ /**
+ * Deletes the file object.
+ *
+ * This implementation uses This class represents a file or form item that was received within a
* After retrieving an instance of this class from a {@link
- * org.apache.tomcat.util.http.fileupload.FileUpload FileUpload} instance (see
- * {@link org.apache.tomcat.util.http.fileupload.FileUpload
+ * 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
@@ -54,9 +50,7 @@ import java.io.UnsupportedEncodingException;
*
* @version $Id$
*/
-public interface FileItem
- extends Serializable
-{
+public interface FileItem extends Serializable {
// ------------------------------- Methods from javax.activation.DataSource
@@ -69,10 +63,9 @@ public interface FileItem
* @return An {@link java.io.InputStream InputStream} that can be
* used to retrieve the contents of the file.
*
- * @exception IOException if an error occurs.
+ * @throws IOException if an error occurs.
*/
- InputStream getInputStream()
- throws IOException;
+ InputStream getInputStream() throws IOException;
/**
@@ -134,11 +127,10 @@ public interface FileItem
*
* @return The contents of the item, as a string.
*
- * @exception UnsupportedEncodingException if the requested character
- * encoding is not available.
+ * @throws UnsupportedEncodingException if the requested character
+ * encoding is not available.
*/
- String getString(String encoding)
- throws UnsupportedEncodingException;
+ String getString(String encoding) throws UnsupportedEncodingException;
/**
@@ -165,7 +157,7 @@ public interface FileItem
* @param file The This class provides support for accessing the headers for a file or form
+ * item that was received within a
+ * Returns all the values of the specified item header as an
+ *
+ * If the item did not include any headers of the specified name, this
+ * method returns an empty
+ * Returns an
+ * If the item did not include any headers of the specified name, this
+ * method returns an empty This interface provides access to a file or form item that was
+ * received within a Instances of this class are created by accessing the
+ * iterator, returned by
+ * {@link FileUploadBase#getItemIterator(RequestContext)}. Note: There is an interaction between the iterator and
+ * its associated instances of {@link FileItemStream}: By invoking
+ * {@link java.util.Iterator#hasNext()} on the iterator, you discard all data,
+ * which hasn't been read so far from the previous data. This class handles multiple files per single HTML widget, sent using
* 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
@@ -43,8 +41,7 @@ package org.apache.tomcat.util.http.fileupload;
* @version $Id$
*/
public class FileUpload
- extends FileUploadBase
- {
+ extends FileUploadBase {
// ----------------------------------------------------------- Data members
@@ -59,13 +56,13 @@ public class FileUpload
/**
- * Constructs an instance of this class which uses the default factory to
- * create High level API for processing file uploads.toByteArray() and
+ * toString().
+ * byte[] buffer
+ * specified by index.
+ *
+ * @param index the index of the buffer required
+ * @return the buffer
+ */
+ private byte[] getBuffer(int index) {
+ return (byte[]) buffers.get(index);
+ }
+
+ /**
+ * Makes a new buffer available either by allocating
+ * a new one or re-cycling an existing one.
+ *
+ * @param newcount the size of the buffer if one is created
+ */
+ private void needNewBuffer(int newcount) {
+ if (currentBufferIndex < buffers.size() - 1) {
+ //Recycling old buffer
+ filledBufferSum += currentBuffer.length;
+
+ currentBufferIndex++;
+ currentBuffer = getBuffer(currentBufferIndex);
+ } else {
+ //Creating new buffer
+ int newBufferSize;
+ if (currentBuffer == null) {
+ newBufferSize = newcount;
+ filledBufferSum = 0;
+ } else {
+ newBufferSize = Math.max(
+ currentBuffer.length << 1,
+ newcount - filledBufferSum);
+ filledBufferSum += currentBuffer.length;
+ }
+
+ currentBufferIndex++;
+ currentBuffer = new byte[newBufferSize];
+ buffers.add(currentBuffer);
+ }
+ }
+
+ /**
+ * Write the bytes to byte array.
+ * @param b the bytes to write
+ * @param off The start offset
+ * @param len The number of bytes to write
+ */
+ public void write(byte[] b, int off, int len) {
+ if ((off < 0)
+ || (off > b.length)
+ || (len < 0)
+ || ((off + len) > b.length)
+ || ((off + len) < 0)) {
+ throw new IndexOutOfBoundsException();
+ } else if (len == 0) {
+ return;
+ }
+ synchronized (this) {
+ int newcount = count + len;
+ int remaining = len;
+ int inBufferPos = count - filledBufferSum;
+ while (remaining > 0) {
+ int part = Math.min(remaining, currentBuffer.length - inBufferPos);
+ System.arraycopy(b, off + len - remaining, currentBuffer, inBufferPos, part);
+ remaining -= part;
+ if (remaining > 0) {
+ needNewBuffer(newcount);
+ inBufferPos = 0;
+ }
+ }
+ count = newcount;
+ }
+ }
+
+ /**
+ * Write a byte to byte array.
+ * @param b the byte to write
+ */
+ public synchronized void write(int b) {
+ int inBufferPos = count - filledBufferSum;
+ if (inBufferPos == currentBuffer.length) {
+ needNewBuffer(count + 1);
+ inBufferPos = 0;
+ }
+ currentBuffer[inBufferPos] = (byte) b;
+ count++;
+ }
+
+ /**
+ * Writes the entire contents of the specified input stream to this
+ * byte stream. Bytes from the input stream are read directly into the
+ * internal buffers of this streams.
+ *
+ * @param in the input stream to read from
+ * @return total number of bytes read from the input stream
+ * (and written to this stream)
+ * @throws IOException if an I/O error occurs while reading the input stream
+ * @since Commons IO 1.4
+ */
+ public synchronized int write(InputStream in) throws IOException {
+ int readCount = 0;
+ int inBufferPos = count - filledBufferSum;
+ int n = in.read(currentBuffer, inBufferPos, currentBuffer.length - inBufferPos);
+ while (n != -1) {
+ readCount += n;
+ inBufferPos += n;
+ count += n;
+ if (inBufferPos == currentBuffer.length) {
+ needNewBuffer(currentBuffer.length);
+ inBufferPos = 0;
+ }
+ n = in.read(currentBuffer, inBufferPos, currentBuffer.length - inBufferPos);
+ }
+ return readCount;
+ }
+
+ /**
+ * Return the current size of the byte array.
+ * @return the current size of the byte array
+ */
+ public synchronized int size() {
+ return count;
+ }
+
+ /**
+ * Closing a ByteArrayOutputStream has no effect. The methods in
+ * this class can be called after the stream has been closed without
+ * generating an IOException.
+ *
+ * @throws IOException never (this method should not declare this exception
+ * but it has to now due to backwards compatability)
+ */
+ public void close() throws IOException {
+ //nop
+ }
+
+ /**
+ * @see java.io.ByteArrayOutputStream#reset()
+ */
+ public synchronized void reset() {
+ count = 0;
+ filledBufferSum = 0;
+ currentBufferIndex = 0;
+ currentBuffer = getBuffer(currentBufferIndex);
+ }
+
+ /**
+ * Writes the entire contents of this byte stream to the
+ * specified output stream.
+ *
+ * @param out the output stream to write to
+ * @throws IOException if an I/O error occurs, such as if the stream is closed
+ * @see java.io.ByteArrayOutputStream#writeTo(OutputStream)
+ */
+ public synchronized void writeTo(OutputStream out) throws IOException {
+ int remaining = count;
+ for (int i = 0; i < buffers.size(); i++) {
+ byte[] buf = getBuffer(i);
+ int c = Math.min(buf.length, remaining);
+ out.write(buf, 0, c);
+ remaining -= c;
+ if (remaining == 0) {
+ break;
+ }
+ }
+ }
+
+ /**
+ * Gets the curent contents of this byte stream as a byte array.
+ * The result is independent of this stream.
+ *
+ * @return the current contents of this output stream, as a byte array
+ * @see java.io.ByteArrayOutputStream#toByteArray()
+ */
+ public synchronized byte[] toByteArray() {
+ int remaining = count;
+ if (remaining == 0) {
+ return EMPTY_BYTE_ARRAY;
+ }
+ byte newbuf[] = new byte[remaining];
+ int pos = 0;
+ for (int i = 0; i < buffers.size(); i++) {
+ byte[] buf = getBuffer(i);
+ int c = Math.min(buf.length, remaining);
+ System.arraycopy(buf, 0, newbuf, pos, c);
+ pos += c;
+ remaining -= c;
+ if (remaining == 0) {
+ break;
+ }
+ }
+ return newbuf;
+ }
+
+ /**
+ * Gets the curent contents of this byte stream as a string.
+ * @return the contents of the byte array as a String
+ * @see java.io.ByteArrayOutputStream#toString()
+ */
+ public String toString() {
+ return new String(toByteArray());
+ }
+
+ /**
+ * Gets the curent contents of this byte stream as a string
+ * using the specified encoding.
+ *
+ * @param enc the name of the character encoding
+ * @return the string converted from the byte array
+ * @throws UnsupportedEncodingException if the encoding is not supported
+ * @see java.io.ByteArrayOutputStream#toString(String)
+ */
+ public String toString(String enc) throws UnsupportedEncodingException {
+ return new String(toByteArray(), enc);
+ }
+
+}
diff --git a/java/org/apache/tomcat/util/http/fileupload/Closeable.java b/java/org/apache/tomcat/util/http/fileupload/Closeable.java
new file mode 100644
index 000000000..04d877b58
--- /dev/null
+++ b/java/org/apache/tomcat/util/http/fileupload/Closeable.java
@@ -0,0 +1,38 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.tomcat.util.http.fileupload;
+
+import java.io.IOException;
+
+
+/**
+ * Interface of an object, which may be closed.
+ */
+public interface Closeable {
+ /**
+ * Closes the object.
+ * @throws IOException An I/O error occurred.
+ */
+ void close() throws IOException;
+
+ /**
+ * Returns, whether the object is already closed.
+ * @return True, if the object is closed, otherwise false.
+ * @throws IOException An I/O error occurred.
+ */
+ boolean isClosed() throws IOException;
+}
diff --git a/java/org/apache/tomcat/util/http/fileupload/DeferredFileOutputStream.java b/java/org/apache/tomcat/util/http/fileupload/DeferredFileOutputStream.java
index 8c1e4c0dd..bb21d4ecc 100644
--- a/java/org/apache/tomcat/util/http/fileupload/DeferredFileOutputStream.java
+++ b/java/org/apache/tomcat/util/http/fileupload/DeferredFileOutputStream.java
@@ -14,23 +14,28 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
-
package org.apache.tomcat.util.http.fileupload;
-import java.io.ByteArrayOutputStream;
import java.io.File;
+import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
+
/**
- * File, assuming
- * that the data was written to disk. If the data was retained in memory,
- * this method returns null.
+ * Returns either the output file specified in the constructor or
+ * the temporary file created or null.
+ * null is returned.
*
* @return The file for this output stream, or null if no such
* file exists.
@@ -172,4 +219,49 @@ public class DeferredFileOutputStream
{
return outputFile;
}
+
+
+ /**
+ * Closes underlying output stream, and mark this as closed
+ *
+ * @exception IOException if an error occurs.
+ */
+ public void close() throws IOException
+ {
+ super.close();
+ closed = true;
+ }
+
+
+ /**
+ * Writes the data from this output stream to the specified output stream,
+ * after it has been closed.
+ *
+ * @param out output stream to write to.
+ * @exception IOException if this stream is not yet closed or an error occurs.
+ */
+ public void writeTo(OutputStream out) throws IOException
+ {
+ // we may only need to check if this is closed if we are working with a file
+ // but we should force the habit of closing wether we are working with
+ // a file or memory.
+ if (!closed)
+ {
+ throw new IOException("Stream not closed");
+ }
+
+ if(isInMemory())
+ {
+ memoryOutputStream.writeTo(out);
+ }
+ else
+ {
+ FileInputStream fis = new FileInputStream(outputFile);
+ try {
+ IOUtils.copy(fis, out);
+ } finally {
+ IOUtils.closeQuietly(fis);
+ }
+ }
+ }
}
diff --git a/java/org/apache/tomcat/util/http/fileupload/DefaultFileItem.java b/java/org/apache/tomcat/util/http/fileupload/DiskFileItem.java
similarity index 58%
rename from java/org/apache/tomcat/util/http/fileupload/DefaultFileItem.java
rename to java/org/apache/tomcat/util/http/fileupload/DiskFileItem.java
index 65218dd32..719ae9368 100644
--- a/java/org/apache/tomcat/util/http/fileupload/DefaultFileItem.java
+++ b/java/org/apache/tomcat/util/http/fileupload/DiskFileItem.java
@@ -5,20 +5,17 @@
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
- *
+ *
* http://www.apache.org/licenses/LICENSE-2.0
- *
+ *
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
-
package org.apache.tomcat.util.http.fileupload;
-
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
@@ -27,23 +24,37 @@ import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
+import java.util.Map;
/**
* DiskFileItemFactory, then you should
+ * consider the following: Temporary files are automatically deleted as
+ * soon as they are no longer needed. (More precisely, when the
+ * corresponding instance of {@link java.io.File} is garbage collected.)
+ * This is done by the so-called reaper thread, which is started
+ * automatically when the class {@link org.apache.commons.io.FileCleaner}
+ * is loaded.
+ * It might make sense to terminate that thread, for example, if
+ * your web application ends. See the section on "Resource cleanup"
+ * in the users guide of commons-fileupload.DefaultFileItem instance.
+ * Constructs a new DiskFileItem instance.
*
* @param fieldName The name of the form field.
* @param contentType The content type passed by the browser or
@@ -135,9 +192,9 @@ public class DefaultFileItem
* 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)
- {
+ public DiskFileItem(String fieldName,
+ String contentType, boolean isFormField, String fileName,
+ int sizeThreshold, File repository) {
this.fieldName = fieldName;
this.contentType = contentType;
this.isFormField = isFormField;
@@ -157,18 +214,15 @@ public class DefaultFileItem
* @return An {@link java.io.InputStream InputStream} that can be
* used to retrieve the contents of the file.
*
- * @exception IOException if an error occurs.
+ * @throws IOException if an error occurs.
*/
public InputStream getInputStream()
- throws IOException
- {
- if (!dfos.isInMemory())
- {
+ throws IOException {
+ if (!isInMemory()) {
return new FileInputStream(dfos.getFile());
}
- if (cachedContent == null)
- {
+ if (cachedContent == null) {
cachedContent = dfos.getData();
}
return new ByteArrayInputStream(cachedContent);
@@ -176,25 +230,39 @@ public class DefaultFileItem
/**
- * Returns the content type passed by the browser or null if
+ * Returns the content type passed by the agent or null if
* not defined.
*
- * @return The content type passed by the browser or null if
+ * @return The content type passed by the agent or null if
* not defined.
*/
- public String getContentType()
- {
+ public String getContentType() {
return contentType;
}
/**
+ * Returns the content charset passed by the agent or null if
+ * not defined.
+ *
+ * @return The content charset passed by the agent or null if
+ * not defined.
+ */
+ public String getCharSet() {
+ ParameterParser parser = new ParameterParser();
+ parser.setLowerCaseNames(true);
+ // Parameter parser can handle null input
+ Map params = parser.parse(getContentType(), ';');
+ return (String) params.get("charset");
+ }
+
+
+ /**
* Returns the original filename in the client's filesystem.
*
* @return The original filename in the client's filesystem.
*/
- public String getName()
- {
+ public String getName() {
return fileName;
}
@@ -209,9 +277,11 @@ public class DefaultFileItem
* @return true if the file contents will be read
* from memory; false otherwise.
*/
- public boolean isInMemory()
- {
- return (dfos.isInMemory());
+ public boolean isInMemory() {
+ if (cachedContent != null) {
+ return true;
+ }
+ return dfos.isInMemory();
}
@@ -220,18 +290,14 @@ public class DefaultFileItem
*
* @return The size of the file, in bytes.
*/
- public long getSize()
- {
- if (cachedContent != null)
- {
+ public long getSize() {
+ if (size >= 0) {
+ return size;
+ } else if (cachedContent != null) {
return cachedContent.length;
- }
- else if (dfos.isInMemory())
- {
+ } else if (dfos.isInMemory()) {
return dfos.getData().length;
- }
- else
- {
+ } else {
return dfos.getFile().length();
}
}
@@ -244,12 +310,9 @@ public class DefaultFileItem
*
* @return The contents of the file as an array of bytes.
*/
- public byte[] get()
- {
- if (dfos.isInMemory())
- {
- if (cachedContent == null)
- {
+ public byte[] get() {
+ if (isInMemory()) {
+ if (cachedContent == null) {
cachedContent = dfos.getData();
}
return cachedContent;
@@ -258,25 +321,16 @@ public class DefaultFileItem
byte[] fileData = new byte[(int) getSize()];
FileInputStream fis = null;
- try
- {
+ try {
fis = new FileInputStream(dfos.getFile());
fis.read(fileData);
- }
- catch (IOException e)
- {
+ } catch (IOException e) {
fileData = null;
- }
- finally
- {
- if (fis != null)
- {
- try
- {
+ } finally {
+ if (fis != null) {
+ try {
fis.close();
- }
- catch (IOException e)
- {
+ } catch (IOException e) {
// ignore
}
}
@@ -291,17 +345,16 @@ public class DefaultFileItem
* encoding. This method uses {@link #get()} to retrieve the
* contents of the file.
*
- * @param encoding The character encoding to use.
+ * @param charset The charset to use.
*
* @return The contents of the file, as a string.
*
- * @exception UnsupportedEncodingException if the requested character
- * encoding is not available.
+ * @throws UnsupportedEncodingException if the requested character
+ * encoding is not available.
*/
- public String getString(String encoding)
- throws UnsupportedEncodingException
- {
- return new String(get(), encoding);
+ public String getString(final String charset)
+ throws UnsupportedEncodingException {
+ return new String(get(), charset);
}
@@ -311,10 +364,20 @@ public class DefaultFileItem
* contents of the file.
*
* @return The contents of the file, as a string.
+ *
+ * @todo Consider making this method throw UnsupportedEncodingException.
*/
- public String getString()
- {
- return new String(get());
+ public String getString() {
+ byte[] rawdata = get();
+ String charset = getCharSet();
+ if (charset == null) {
+ charset = DEFAULT_CHARSET;
+ }
+ try {
+ return new String(rawdata, charset);
+ } catch (UnsupportedEncodingException e) {
+ return new String(rawdata);
+ }
}
@@ -336,76 +399,56 @@ public class DefaultFileItem
* @param file The File into which the uploaded item should
* be stored.
*
- * @exception Exception if an error occurs.
+ * @throws Exception if an error occurs.
*/
- public void write(File file) throws Exception
- {
- if (isInMemory())
- {
+ public void write(File file) throws Exception {
+ if (isInMemory()) {
FileOutputStream fout = null;
- try
- {
+ try {
fout = new FileOutputStream(file);
fout.write(get());
- }
- finally
- {
- if (fout != null)
- {
+ } finally {
+ if (fout != null) {
fout.close();
}
}
- }
- else
- {
+ } else {
File outputFile = getStoreLocation();
- if (outputFile != null)
- {
+ if (outputFile != null) {
+ // Save the length of the file
+ size = outputFile.length();
/*
* The uploaded file is being stored on disk
* in a temporary location so move it to the
* desired file.
*/
- if (!outputFile.renameTo(file))
- {
+ if (!outputFile.renameTo(file)) {
BufferedInputStream in = null;
BufferedOutputStream out = null;
- try
- {
+ 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
+ IOUtils.copy(in, out);
+ } finally {
+ if (in != null) {
+ try {
+ in.close();
+ } catch (IOException e) {
+ // ignore
+ }
}
- try
- {
- out.close();
- }
- catch (IOException e)
- {
- // ignore
+ if (out != null) {
+ try {
+ out.close();
+ } catch (IOException e) {
+ // ignore
+ }
}
}
}
- }
- else
- {
+ } else {
/*
* For whatever reason we cannot write the
* file to disk.
@@ -424,12 +467,10 @@ public class DefaultFileItem
* collected, this method can be used to ensure that this is done at an
* earlier time, thus preserving system resources.
*/
- public void delete()
- {
+ public void delete() {
cachedContent = null;
File outputFile = getStoreLocation();
- if (outputFile != null && outputFile.exists())
- {
+ if (outputFile != null && outputFile.exists()) {
outputFile.delete();
}
}
@@ -444,8 +485,7 @@ public class DefaultFileItem
* @see #setFieldName(java.lang.String)
*
*/
- public String getFieldName()
- {
+ public String getFieldName() {
return fieldName;
}
@@ -458,8 +498,7 @@ public class DefaultFileItem
* @see #getFieldName()
*
*/
- public void setFieldName(String fieldName)
- {
+ public void setFieldName(String fieldName) {
this.fieldName = fieldName;
}
@@ -474,8 +513,7 @@ public class DefaultFileItem
* @see #setFormField(boolean)
*
*/
- public boolean isFormField()
- {
+ public boolean isFormField() {
return isFormField;
}
@@ -490,8 +528,7 @@ public class DefaultFileItem
* @see #isFormField()
*
*/
- public void setFormField(boolean state)
- {
+ public void setFormField(boolean state) {
isFormField = state;
}
@@ -503,13 +540,11 @@ public class DefaultFileItem
* @return An {@link java.io.OutputStream OutputStream} that can be used
* for storing the contensts of the file.
*
- * @exception IOException if an error occurs.
+ * @throws IOException if an error occurs.
*/
public OutputStream getOutputStream()
- throws IOException
- {
- if (dfos == null)
- {
+ throws IOException {
+ if (dfos == null) {
File outputFile = getTempFile();
dfos = new DeferredFileOutputStream(sizeThreshold, outputFile);
}
@@ -533,9 +568,8 @@ public class DefaultFileItem
* @return The data file, or null if the data is stored in
* memory.
*/
- public File getStoreLocation()
- {
- return dfos.getFile();
+ public File getStoreLocation() {
+ return dfos == null ? null : dfos.getFile();
}
@@ -545,13 +579,10 @@ public class DefaultFileItem
/**
* Removes the file contents from the temporary storage.
*/
- @Override
- protected void finalize()
- {
+ protected void finalize() {
File outputFile = dfos.getFile();
- if (outputFile != null && outputFile.exists())
- {
+ if (outputFile != null && outputFile.exists()) {
outputFile.delete();
}
}
@@ -559,23 +590,25 @@ public class DefaultFileItem
/**
* Creates and returns a {@link java.io.File File} representing a uniquely
- * named temporary file in the configured repository path.
+ * named temporary file in the configured repository path. The lifetime of
+ * the file is tied to the lifetime of the FileItem instance;
+ * the file will be deleted when the instance is garbage collected.
*
* @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"));
- }
+ protected File getTempFile() {
+ if (tempFile == null) {
+ File tempDir = repository;
+ if (tempDir == null) {
+ tempDir = new File(System.getProperty("java.io.tmpdir"));
+ }
- String fileName = "upload_" + getUniqueId() + ".tmp";
+ String tempFileName =
+ "upload_" + UID + "_" + getUniqueId() + ".tmp";
- File f = new File(tempDir, fileName);
- f.deleteOnExit();
- return f;
+ tempFile = new File(tempDir, tempFileName);
+ }
+ return tempFile;
}
@@ -588,22 +621,106 @@ public class DefaultFileItem
*
* @return A String with the non-random looking instance identifier.
*/
- private static String getUniqueId()
- {
+ private static String getUniqueId() {
+ final int limit = 100000000;
int current;
- synchronized (DefaultFileItem.class)
- {
+ synchronized (DiskFileItem.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)
- {
+ if (current < limit) {
id = ("00000000" + id).substring(id.length());
}
return id;
}
+
+
+
+ /**
+ * Returns a string representation of this object.
+ *
+ * @return a string representation of this object.
+ */
+ public String toString() {
+ return "name=" + this.getName()
+ + ", StoreLocation="
+ + String.valueOf(this.getStoreLocation())
+ + ", size="
+ + this.getSize()
+ + "bytes, "
+ + "isFormField=" + isFormField()
+ + ", FieldName="
+ + this.getFieldName();
+ }
+
+
+ // -------------------------------------------------- Serialization methods
+
+
+ /**
+ * Writes the state of this object during serialization.
+ *
+ * @param out The stream to which the state should be written.
+ *
+ * @throws IOException if an error occurs.
+ */
+ private void writeObject(ObjectOutputStream out) throws IOException {
+ // Read the data
+ if (dfos.isInMemory()) {
+ cachedContent = get();
+ } else {
+ cachedContent = null;
+ dfosFile = dfos.getFile();
+ }
+
+ // write out values
+ out.defaultWriteObject();
+ }
+
+ /**
+ * Reads the state of this object during deserialization.
+ *
+ * @param in The stream from which the state should be read.
+ *
+ * @throws IOException if an error occurs.
+ * @throws ClassNotFoundException if class cannot be found.
+ */
+ private void readObject(ObjectInputStream in)
+ throws IOException, ClassNotFoundException {
+ // read values
+ in.defaultReadObject();
+
+ OutputStream output = getOutputStream();
+ if (cachedContent != null) {
+ output.write(cachedContent);
+ } else {
+ FileInputStream input = new FileInputStream(dfosFile);
+ IOUtils.copy(input, output);
+ dfosFile.delete();
+ dfosFile = null;
+ }
+ output.close();
+
+ cachedContent = null;
+ }
+
+ /**
+ * Returns the file item headers.
+ * @return The file items headers.
+ */
+ public FileItemHeaders getHeaders() {
+ return headers;
+ }
+
+ /**
+ * Sets the file item headers.
+ * @param pHeaders The file items headers.
+ */
+ public void setHeaders(FileItemHeaders pHeaders) {
+ headers = pHeaders;
+ }
}
diff --git a/java/org/apache/tomcat/util/http/fileupload/DefaultFileItemFactory.java b/java/org/apache/tomcat/util/http/fileupload/DiskFileItemFactory.java
similarity index 64%
rename from java/org/apache/tomcat/util/http/fileupload/DefaultFileItemFactory.java
rename to java/org/apache/tomcat/util/http/fileupload/DiskFileItemFactory.java
index 2d29b3899..1dc8f2b45 100644
--- a/java/org/apache/tomcat/util/http/fileupload/DefaultFileItemFactory.java
+++ b/java/org/apache/tomcat/util/http/fileupload/DiskFileItemFactory.java
@@ -5,26 +5,24 @@
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
- *
+ *
* http://www.apache.org/licenses/LICENSE-2.0
- *
+ *
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
-
package org.apache.tomcat.util.http.fileupload;
import java.io.File;
/**
- * DiskFileItemFactory, then you should
+ * consider the following: Temporary files are automatically deleted as
+ * soon as they are no longer needed. (More precisely, when the
+ * corresponding instance of {@link java.io.File} is garbage collected.)
+ * Cleaning up those files is done by an instance of
+ * {@link FileCleaningTracker}, and an associated thread. In a complex
+ * environment, for example in a web application, you should consider
+ * terminating this thread, for example, when your web application
+ * ends. See the section on "Resource cleanup"
+ * in the users guide of commons-fileupload.multipart/mixed encoding type, as specified by
- * RFC 1867. Use {@link
- * #parseRequest(HttpServletRequest)} to acquire a list of {@link
- * org.apache.tomcat.util.http.fileupload.FileItem}s associated with a given HTML
- * widget.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.
- */
- @Override
- 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.
- */
- @Override
- 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 ListTracker instances being watched.
+ */
+ ReferenceQueue /* Tracker */ q = new ReferenceQueue();
+ /**
+ * Collection of Tracker instances in existence.
+ */
+ final Collection /* Tracker */ trackers = new Vector(); // synchronized
+ /**
+ * Whether to terminate the thread when the tracking is complete.
+ */
+ volatile boolean exitWhenFinished = false;
+ /**
+ * The thread that will clean up registered files.
+ */
+ Thread reaper;
+
+ //-----------------------------------------------------------------------
+ /**
+ * Track the specified file, using the provided marker, deleting the file
+ * when the marker instance is garbage collected.
+ * The {@link FileDeleteStrategy#NORMAL normal} deletion strategy will be used.
+ *
+ * @param file the file to be tracked, not null
+ * @param marker the marker object used to track the file, not null
+ * @throws NullPointerException if the file is null
+ */
+ public void track(File file, Object marker) {
+ track(file, marker, (FileDeleteStrategy) null);
+ }
+
+ /**
+ * Track the specified file, using the provided marker, deleting the file
+ * when the marker instance is garbage collected.
+ * The speified deletion strategy is used.
+ *
+ * @param file the file to be tracked, not null
+ * @param marker the marker object used to track the file, not null
+ * @param deleteStrategy the strategy to delete the file, null means normal
+ * @throws NullPointerException if the file is null
+ */
+ public void track(File file, Object marker, FileDeleteStrategy deleteStrategy) {
+ if (file == null) {
+ throw new NullPointerException("The file must not be null");
+ }
+ addTracker(file.getPath(), marker, deleteStrategy);
+ }
+
+ /**
+ * Track the specified file, using the provided marker, deleting the file
+ * when the marker instance is garbage collected.
+ * The {@link FileDeleteStrategy#NORMAL normal} deletion strategy will be used.
+ *
+ * @param path the full path to the file to be tracked, not null
+ * @param marker the marker object used to track the file, not null
+ * @throws NullPointerException if the path is null
+ */
+ public void track(String path, Object marker) {
+ track(path, marker, (FileDeleteStrategy) null);
+ }
+
+ /**
+ * Track the specified file, using the provided marker, deleting the file
+ * when the marker instance is garbage collected.
+ * The speified deletion strategy is used.
+ *
+ * @param path the full path to the file to be tracked, not null
+ * @param marker the marker object used to track the file, not null
+ * @param deleteStrategy the strategy to delete the file, null means normal
+ * @throws NullPointerException if the path is null
+ */
+ public void track(String path, Object marker, FileDeleteStrategy deleteStrategy) {
+ if (path == null) {
+ throw new NullPointerException("The path must not be null");
+ }
+ addTracker(path, marker, deleteStrategy);
+ }
+
+ /**
+ * Adds a tracker to the list of trackers.
+ *
+ * @param path the full path to the file to be tracked, not null
+ * @param marker the marker object used to track the file, not null
+ * @param deleteStrategy the strategy to delete the file, null means normal
+ */
+ private synchronized void addTracker(String path, Object marker, FileDeleteStrategy deleteStrategy) {
+ // synchronized block protects reaper
+ if (exitWhenFinished) {
+ throw new IllegalStateException("No new trackers can be added once exitWhenFinished() is called");
+ }
+ if (reaper == null) {
+ reaper = new Reaper();
+ reaper.start();
+ }
+ trackers.add(new Tracker(path, deleteStrategy, marker, q));
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Retrieve the number of files currently being tracked, and therefore
+ * awaiting deletion.
+ *
+ * @return the number of files being tracked
+ */
+ public int getTrackCount() {
+ return trackers.size();
+ }
+
+ /**
+ * Call this method to cause the file cleaner thread to terminate when
+ * there are no more objects being tracked for deletion.
+ * true if the file was deleted successfully;
+ * false otherwise.
+ */
+ public boolean delete() {
+ return deleteStrategy.deleteQuietly(new File(path));
+ }
+ }
+
+}
diff --git a/java/org/apache/tomcat/util/http/fileupload/FileDeleteStrategy.java b/java/org/apache/tomcat/util/http/fileupload/FileDeleteStrategy.java
new file mode 100644
index 000000000..471532204
--- /dev/null
+++ b/java/org/apache/tomcat/util/http/fileupload/FileDeleteStrategy.java
@@ -0,0 +1,156 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.tomcat.util.http.fileupload;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * Strategy for deleting files.
+ * IOExceptions are caught and false returned instead.
+ * If the file does not exist or is null, true is returned.
+ * IOException
+ * when deletion fails. The {@link #delete(File)} and {@link #deleteQuietly(File)}
+ * methods will handle either response appropriately.
+ * A check has been made to ensure that the file will exist.
+ * FileUtils.forceDelete()
+ * if the file exists.
+ *
+ * @param fileToDelete the file to delete, not null
+ * @return Always returns true
+ * @throws NullPointerException if the file is null
+ * @throws IOException if an error occurs during file deletion
+ */
+ protected boolean doDelete(File fileToDelete) throws IOException {
+ FileUtils.forceDelete(fileToDelete);
+ return true;
+ }
+ }
+
+}
diff --git a/java/org/apache/tomcat/util/http/fileupload/FileItem.java b/java/org/apache/tomcat/util/http/fileupload/FileItem.java
index 7237c089c..de9c9e4ee 100644
--- a/java/org/apache/tomcat/util/http/fileupload/FileItem.java
+++ b/java/org/apache/tomcat/util/http/fileupload/FileItem.java
@@ -5,20 +5,17 @@
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
- *
+ *
* http://www.apache.org/licenses/LICENSE-2.0
- *
+ *
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
-
package org.apache.tomcat.util.http.fileupload;
-
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
@@ -26,14 +23,13 @@ import java.io.OutputStream;
import java.io.Serializable;
import java.io.UnsupportedEncodingException;
-
/**
* multipart/form-data POST request.
*
* File into which the uploaded item should
* be stored.
*
- * @exception Exception if an error occurs.
+ * @throws Exception if an error occurs.
*/
void write(File file) throws Exception;
@@ -224,7 +216,7 @@ public interface FileItem
* @return An {@link java.io.OutputStream OutputStream} that can be used
* for storing the contensts of the file.
*
- * @exception IOException if an error occurs.
+ * @throws IOException if an error occurs.
*/
OutputStream getOutputStream() throws IOException;
diff --git a/java/org/apache/tomcat/util/http/fileupload/FileItemFactory.java b/java/org/apache/tomcat/util/http/fileupload/FileItemFactory.java
index f6988ae24..415bab796 100644
--- a/java/org/apache/tomcat/util/http/fileupload/FileItemFactory.java
+++ b/java/org/apache/tomcat/util/http/fileupload/FileItemFactory.java
@@ -5,17 +5,15 @@
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
- *
+ *
* http://www.apache.org/licenses/LICENSE-2.0
- *
+ *
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
-
package org.apache.tomcat.util.http.fileupload;
@@ -25,11 +23,10 @@ package org.apache.tomcat.util.http.fileupload;
* by the default file upload implementation.multipart/form-data POST
+ * request.String.
+ * If the part did not include a header of the specified name, this method
+ * return null. If there are multiple headers with the same
+ * name, this method returns the first header in the item. The header
+ * name is case insensitive.
+ *
+ * @param name a String specifying the header name
+ * @return a String containing the value of the requested
+ * header, or null if the item does not have a header
+ * of that name
+ */
+ String getHeader(String name);
+
+ /**
+ * Enumeration of String objects.
+ * Enumeration. The header name is
+ * case insensitive.
+ * String specifying the header name
+ * @return an Enumeration containing the values of the
+ * requested header. If the item does not have any headers of
+ * that name, return an empty Enumeration
+ */
+ Iterator getHeaders(String name);
+
+ /**
+ * Enumeration of all the header names.
+ * Enumeration. The header name is
+ * case insensitive.
+ * Enumeration containing the values of the
+ * requested header. If the item does not have any headers of
+ * that name return an empty Enumeration
+ */
+ Iterator getHeaderNames();
+}
diff --git a/java/org/apache/tomcat/util/http/fileupload/FileItemHeadersImpl.java b/java/org/apache/tomcat/util/http/fileupload/FileItemHeadersImpl.java
new file mode 100644
index 000000000..0c6ee19d3
--- /dev/null
+++ b/java/org/apache/tomcat/util/http/fileupload/FileItemHeadersImpl.java
@@ -0,0 +1,88 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.tomcat.util.http.fileupload;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+
+/**
+ * Default implementation of the {@link FileItemHeaders} interface.
+ *
+ * @author Michael C. Macaluso
+ * @since 1.3
+ */
+public class FileItemHeadersImpl implements FileItemHeaders, Serializable {
+ private static final long serialVersionUID = -4455695752627032559L;
+
+ /**
+ * Map of String keys to a List of
+ * String instances.
+ */
+ private final Map headerNameToValueListMap = new HashMap();
+
+ /**
+ * List to preserve order of headers as added. This would not be
+ * needed if a LinkedHashMap could be used, but don't
+ * want to depend on 1.4.
+ */
+ private final List headerNameList = new ArrayList();
+
+ public String getHeader(String name) {
+ String nameLower = name.toLowerCase();
+ List headerValueList = (List) headerNameToValueListMap.get(nameLower);
+ if (null == headerValueList) {
+ return null;
+ }
+ return (String) headerValueList.get(0);
+ }
+
+ public Iterator getHeaderNames() {
+ return headerNameList.iterator();
+ }
+
+ public Iterator getHeaders(String name) {
+ String nameLower = name.toLowerCase();
+ List headerValueList = (List) headerNameToValueListMap.get(nameLower);
+ if (null == headerValueList) {
+ return Collections.EMPTY_LIST.iterator();
+ }
+ return headerValueList.iterator();
+ }
+
+ /**
+ * Method to add header values to this instance.
+ *
+ * @param name name of this header
+ * @param value value of this header
+ */
+ public synchronized void addHeader(String name, String value) {
+ String nameLower = name.toLowerCase();
+ List headerValueList = (List) headerNameToValueListMap.get(nameLower);
+ if (null == headerValueList) {
+ headerValueList = new ArrayList();
+ headerNameToValueListMap.put(nameLower, headerValueList);
+ headerNameList.add(nameLower);
+ }
+ headerValueList.add(value);
+ }
+}
diff --git a/java/org/apache/tomcat/util/http/fileupload/FileItemHeadersSupport.java b/java/org/apache/tomcat/util/http/fileupload/FileItemHeadersSupport.java
new file mode 100644
index 000000000..b478d12da
--- /dev/null
+++ b/java/org/apache/tomcat/util/http/fileupload/FileItemHeadersSupport.java
@@ -0,0 +1,47 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.tomcat.util.http.fileupload;
+
+/**
+ * Interface that will indicate that {@link FileItem} or {@link FileItemStream}
+ * implementations will accept the headers read for the item.
+ *
+ * @author Michael C. Macaluso
+ * @since 1.3
+ *
+ * @see FileItem
+ * @see FileItemStream
+ */
+public interface FileItemHeadersSupport {
+ /**
+ * Returns the collection of headers defined locally within this item.
+ *
+ * @return the {@link FileItemHeaders} present for this item.
+ */
+ FileItemHeaders getHeaders();
+
+ /**
+ * Sets the headers read from within an item. Implementations of
+ * {@link FileItem} or {@link FileItemStream} should implement this
+ * interface to be able to get the raw headers found within the item
+ * header block.
+ *
+ * @param headers the instance that holds onto the headers
+ * for this instance.
+ */
+ void setHeaders(FileItemHeaders headers);
+}
diff --git a/java/org/apache/tomcat/util/http/fileupload/FileItemIterator.java b/java/org/apache/tomcat/util/http/fileupload/FileItemIterator.java
new file mode 100644
index 000000000..7279c7500
--- /dev/null
+++ b/java/org/apache/tomcat/util/http/fileupload/FileItemIterator.java
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.tomcat.util.http.fileupload;
+import java.io.IOException;
+
+
+/**
+ * An iterator, as returned by
+ * {@link FileUploadBase#getItemIterator(RequestContext)}.
+ */
+public interface FileItemIterator {
+ /**
+ * Returns, whether another instance of {@link FileItemStream}
+ * is available.
+ * @throws FileUploadException Parsing or processing the
+ * file item failed.
+ * @throws IOException Reading the file item failed.
+ * @return True, if one or more additional file items
+ * are available, otherwise false.
+ */
+ boolean hasNext() throws FileUploadException, IOException;
+
+ /**
+ * Returns the next available {@link FileItemStream}.
+ * @throws java.util.NoSuchElementException No more items are available. Use
+ * {@link #hasNext()} to prevent this exception.
+ * @throws FileUploadException Parsing or processing the
+ * file item failed.
+ * @throws IOException Reading the file item failed.
+ * @return FileItemStream instance, which provides
+ * access to the next file item.
+ */
+ FileItemStream next() throws FileUploadException, IOException;
+}
diff --git a/java/org/apache/tomcat/util/http/fileupload/FileItemStream.java b/java/org/apache/tomcat/util/http/fileupload/FileItemStream.java
new file mode 100644
index 000000000..92f94874d
--- /dev/null
+++ b/java/org/apache/tomcat/util/http/fileupload/FileItemStream.java
@@ -0,0 +1,97 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.tomcat.util.http.fileupload;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+
+/**
+ * multipart/form-data POST request.
+ * The items contents are retrieved by calling {@link #openStream()}.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();
+
+ /**
+ * Returns the name of the field in the multipart form corresponding to
+ * this file item.
+ *
+ * @return The name of the form field.
+ */
+ String getFieldName();
+
+ /**
+ * 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();
+}
diff --git a/java/org/apache/tomcat/util/http/fileupload/FileUpload.java b/java/org/apache/tomcat/util/http/fileupload/FileUpload.java
index a417cb31d..867627307 100644
--- a/java/org/apache/tomcat/util/http/fileupload/FileUpload.java
+++ b/java/org/apache/tomcat/util/http/fileupload/FileUpload.java
@@ -5,17 +5,15 @@
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
- *
+ *
* http://www.apache.org/licenses/LICENSE-2.0
- *
+ *
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
-
package org.apache.tomcat.util.http.fileupload;
@@ -25,9 +23,9 @@ package org.apache.tomcat.util.http.fileupload;
* multipart/mixed encoding type, as specified by
* RFC 1867. Use {@link
- * #parseRequest(HttpServletRequest)} to acquire a list of {@link
- * org.apache.tomcat.util.http.fileupload.FileItem}s associated with a given HTML
- * widget.FileItem instances.
+ * Constructs an uninitialised instance of this class. A factory must be
+ * configured, using setFileItemFactory(), before attempting
+ * to parse requests.
*
* @see #FileUpload(FileItemFactory)
*/
- public FileUpload()
- {
+ public FileUpload() {
super();
}
@@ -75,9 +72,9 @@ public class FileUpload
* create FileItem instances.
*
* @see #FileUpload()
+ * @param fileItemFactory The factory to use for creating file items.
*/
- public FileUpload(FileItemFactory fileItemFactory)
- {
+ public FileUpload(FileItemFactory fileItemFactory) {
super();
this.fileItemFactory = fileItemFactory;
}
@@ -91,9 +88,7 @@ public class FileUpload
*
* @return The factory class for new file items.
*/
- @Override
- public FileItemFactory getFileItemFactory()
- {
+ public FileItemFactory getFileItemFactory() {
return fileItemFactory;
}
@@ -103,9 +98,7 @@ public class FileUpload
*
* @param factory The factory class for new file items.
*/
- @Override
- public void setFileItemFactory(FileItemFactory factory)
- {
+ public void setFileItemFactory(FileItemFactory factory) {
this.fileItemFactory = factory;
}
diff --git a/java/org/apache/tomcat/util/http/fileupload/FileUploadBase.java b/java/org/apache/tomcat/util/http/fileupload/FileUploadBase.java
index efced3202..341d18277 100644
--- a/java/org/apache/tomcat/util/http/fileupload/FileUploadBase.java
+++ b/java/org/apache/tomcat/util/http/fileupload/FileUploadBase.java
@@ -5,29 +5,31 @@
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
- *
+ *
* http://www.apache.org/licenses/LICENSE-2.0
- *
+ *
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
-
package org.apache.tomcat.util.http.fileupload;
-
import java.io.IOException;
import java.io.InputStream;
-import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.HashMap;
+import java.util.Iterator;
import java.util.List;
import java.util.Map;
+import java.util.NoSuchElementException;
+
import javax.servlet.http.HttpServletRequest;
+import org.apache.tomcat.util.http.fileupload.MultipartStream.ItemInputStream;
+
/**
* multipart/mixed encoding type, as specified by
* RFC 1867. Use {@link
* #parseRequest(HttpServletRequest)} to acquire a list of {@link
- * org.apache.tomcat.util.http.fileupload.FileItem}s associated with a given HTML
+ * 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 @@ -52,36 +54,53 @@ import javax.servlet.http.HttpServletRequest; * * @version $Id$ */ -public abstract class FileUploadBase -{ +public abstract class FileUploadBase { // ---------------------------------------------------------- Class methods /** - * Utility method that determines whether the request contains multipart - * content. + *
Utility method that determines whether the request contains multipart + * content.
* - * @param req The servlet request to be evaluated. Must be non-null. + *NOTE:This method will be moved to the
+ * ServletFileUpload class after the FileUpload 1.1 release.
+ * Unfortunately, since this method is static, it is not possible to
+ * provide its replacement until this method is removed.
true if the request is multipart;
* false otherwise.
*/
- public static final boolean isMultipartContent(HttpServletRequest req)
- {
- String contentType = req.getHeader(CONTENT_TYPE);
- if (contentType == null)
- {
+ public static final boolean isMultipartContent(RequestContext ctx) {
+ String contentType = ctx.getContentType();
+ if (contentType == null) {
return false;
}
- if (contentType.startsWith(MULTIPART))
- {
+ if (contentType.toLowerCase().startsWith(MULTIPART)) {
return true;
}
return false;
}
+ /**
+ * 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.
+ *
+ * @deprecated Use the method on ServletFileUpload instead.
+ */
+ public static boolean isMultipartContent(HttpServletRequest req) {
+ return ServletFileUpload.isMultipartContent(req);
+ }
+
+
// ----------------------------------------------------- Manifest constants
@@ -96,6 +115,11 @@ public abstract class FileUploadBase
*/
public static final String CONTENT_DISPOSITION = "Content-disposition";
+ /**
+ * HTTP content length header name.
+ */
+ public static final String CONTENT_LENGTH = "Content-length";
+
/**
* Content-disposition value for form data.
@@ -130,6 +154,9 @@ public abstract class FileUploadBase
/**
* The maximum length of a single header line that will be parsed
* (1024 bytes).
+ * @deprecated This constant is no longer used. As of commons-fileupload
+ * 1.2, the only applicable limit is the total size of a parts headers,
+ * {@link MultipartStream#HEADER_PART_SIZE_MAX}.
*/
public static final int MAX_HEADER_SIZE = 1024;
@@ -138,17 +165,26 @@ public abstract class FileUploadBase
/**
- * The maximum size permitted for an uploaded file. A value of -1 indicates
- * no maximum.
+ * The maximum size permitted for the complete request, as opposed to
+ * {@link #fileSizeMax}. A value of -1 indicates no maximum.
*/
private long sizeMax = -1;
+ /**
+ * The maximum size permitted for a single uploaded file, as opposed
+ * to {@link #sizeMax}. A value of -1 indicates no maximum.
+ */
+ private long fileSizeMax = -1;
/**
* The content encoding to use when reading part headers.
*/
private String headerEncoding;
+ /**
+ * The progress listener.
+ */
+ private ProgressListener listener;
// ----------------------------------------------------- Property accessors
@@ -170,55 +206,78 @@ public abstract class FileUploadBase
/**
- * Returns the maximum allowed upload size.
+ * Returns the maximum allowed size of a complete request, as opposed
+ * to {@link #getFileSizeMax()}.
*
- * @return The maximum allowed size, in bytes.
+ * @return The maximum allowed size, in bytes. The default value of
+ * -1 indicates, that there is no limit.
*
* @see #setSizeMax(long)
*
*/
- public long getSizeMax()
- {
+ public long getSizeMax() {
return sizeMax;
}
/**
- * Sets the maximum allowed upload size. If negative, there is no maximum.
+ * Sets the maximum allowed size of a complete request, as opposed
+ * to {@link #setFileSizeMax(long)}.
*
- * @param sizeMax The maximum allowed size, in bytes, or -1 for no maximum.
+ * @param sizeMax The maximum allowed size, in bytes. The default value of
+ * -1 indicates, that there is no limit.
*
* @see #getSizeMax()
*
*/
- public void setSizeMax(long sizeMax)
- {
+ public void setSizeMax(long sizeMax) {
this.sizeMax = sizeMax;
}
+ /**
+ * Returns the maximum allowed size of a single uploaded file,
+ * as opposed to {@link #getSizeMax()}.
+ *
+ * @see #setFileSizeMax(long)
+ * @return Maximum size of a single uploaded file.
+ */
+ public long getFileSizeMax() {
+ return fileSizeMax;
+ }
+
+ /**
+ * Sets the maximum allowed size of a single uploaded file,
+ * as opposed to {@link #getSizeMax()}.
+ *
+ * @see #getFileSizeMax()
+ * @param fileSizeMax Maximum size of a single uploaded file.
+ */
+ public void setFileSizeMax(long fileSizeMax) {
+ this.fileSizeMax = fileSizeMax;
+ }
/**
* Retrieves the character encoding used when reading the headers of an
- * individual part. When not specified, or null, the platform
- * default encoding is used.
+ * individual part. When not specified, or null, the request
+ * encoding is used. If that is also not specified, or null,
+ * the platform default encoding is used.
*
* @return The encoding used to read part headers.
*/
- public String getHeaderEncoding()
- {
+ 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.
+ * individual part. When not specified, or null, the request
+ * encoding is used. If that is also not specified, or null,
+ * the platform default encoding is used.
*
* @param encoding The encoding used to read part headers.
*/
- public void setHeaderEncoding(String encoding)
- {
+ public void setHeaderEncoding(String encoding) {
headerEncoding = encoding;
}
@@ -228,166 +287,93 @@ public abstract class FileUploadBase
/**
* Processes an RFC 1867
- * compliant multipart/form-data stream. If files are stored
- * on disk, the path is given by getRepository().
+ * compliant multipart/form-data stream.
*
* @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.
+ * @throws FileUploadException if there are problems reading/parsing
+ * the request or storing files.
+ *
+ * @deprecated Use the method in ServletFileUpload instead.
*/
- public Listmultipart/form-data stream.
+ *
+ * @param ctx The context for the request to be parsed.
+ *
+ * @return An iterator to instances of FileItemStream
+ * parsed from the request, in the order that they were
+ * transmitted.
+ *
+ * @throws FileUploadException if there are problems reading/parsing
+ * the request or storing files.
+ * @throws IOException An I/O error occurred. This may be a network
+ * error while communicating with the client or a problem while
+ * storing the uploaded content.
+ */
+ public FileItemIterator getItemIterator(RequestContext ctx)
+ throws FileUploadException, IOException {
+ return new FileItemIteratorImpl(ctx);
+ }
- try
- {
- int boundaryIndex = contentType.indexOf("boundary=");
- if (boundaryIndex < 0)
- {
- throw new FileUploadException(
- "the request was rejected because "
- + "no multipart boundary was found");
+ /**
+ * Processes an RFC 1867
+ * compliant multipart/form-data stream.
+ *
+ * @param ctx The context for the request to be parsed.
+ *
+ * @return A list of FileItem instances parsed from the
+ * request, in the order that they were transmitted.
+ *
+ * @throws FileUploadException if there are problems reading/parsing
+ * the request or storing files.
+ */
+ public List /* FileItem */ parseRequest(RequestContext ctx)
+ throws FileUploadException {
+ try {
+ FileItemIterator iter = getItemIterator(ctx);
+ List items = new ArrayList();
+ FileItemFactory fac = getFileItemFactory();
+ if (fac == null) {
+ throw new NullPointerException(
+ "No FileItemFactory has been set.");
}
- 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)
- {
- MapContent-type header.
+ *
+ * @param contentType The value of the content type header from which to
+ * extract the boundary value.
+ *
+ * @return The boundary, as a byte array.
+ */
+ protected byte[] getBoundary(String contentType) {
+ ParameterParser parser = new ParameterParser();
+ parser.setLowerCaseNames(true);
+ // Parameter parser can handle null input
+ Map params = parser.parse(contentType, new char[] {';', ','});
+ String boundaryStr = (String) params.get("boundary");
+
+ if (boundaryStr == null) {
+ return null;
+ }
+ byte[] boundary;
+ try {
+ boundary = boundaryStr.getBytes("ISO-8859-1");
+ } catch (UnsupportedEncodingException e) {
+ boundary = boundaryStr.getBytes();
+ }
+ return boundary;
+ }
+
+
+ /**
* 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.
+ * @deprecated Use {@link #getFileName(FileItemHeaders)}.
*/
- protected String getFileName(MapContent-disposition
+ * header.
+ *
+ * @param headers The HTTP headers object.
+ *
+ * @return The file name for the current encapsulation.
+ */
+ protected String getFileName(FileItemHeaders headers) {
+ return getFileName(headers.getHeader(CONTENT_DISPOSITION));
+ }
+
+ /**
+ * Returns the given content-disposition headers file name.
+ * @param pContentDisposition The content-disposition headers value.
+ * @return The file name
+ */
+ private String getFileName(String pContentDisposition) {
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();
+ if (pContentDisposition != null) {
+ String cdl = pContentDisposition.toLowerCase();
+ if (cdl.startsWith(FORM_DATA) || cdl.startsWith(ATTACHMENT)) {
+ ParameterParser parser = new ParameterParser();
+ parser.setLowerCaseNames(true);
+ // Parameter parser can handle null input
+ Map params = parser.parse(pContentDisposition, ';');
+ if (params.containsKey("filename")) {
+ fileName = (String) params.get("filename");
+ if (fileName != null) {
+ fileName = fileName.trim();
+ } else {
+ // Even if there is no value, the parameter is present,
+ // so we return an empty file name rather than no file
+ // name.
+ fileName = "";
+ }
+ }
}
}
return fileName;
@@ -427,22 +472,45 @@ public abstract class FileUploadBase
*
* @return The field name for the current encapsulation.
*/
- protected String getFieldName(MapContent-disposition
+ * header.
+ *
+ * @param headers A Map containing the HTTP request headers.
+ *
+ * @return The field name for the current encapsulation.
+ * @deprecated Use {@link #getFieldName(FileItemHeaders)}.
+ */
+ protected String getFieldName(Map /* String, String */ headers) {
+ return getFieldName(getHeader(headers, CONTENT_DISPOSITION));
+ }
+
/**
* Creates a new {@link FileItem} instance.
@@ -454,19 +522,19 @@ public abstract class FileUploadBase
*
* @return A newly created FileItem instance.
*
- * @exception FileUploadException if an error occurs.
+ * @throws FileUploadException if an error occurs.
+ * @deprecated This method is no longer used in favour of
+ * internally created instances of {@link FileItem}.
*/
- protected FileItem createItem(Map Parses the Parses the If there are multiple headers of the same names, the name
+ * will map to a comma-separated list containing the values.
+ *
+ * @param headerPart The
+ * Facilities are provided in the following areas:
+ *
+ * Origin of code: Excalibur, Alexandria, Commons-Utils
+ *
+ * @author Kevin A. Burton
+ * @author Scott Sanders
+ * @author Daniel Rall
+ * @author Christoph.Reck
+ * @author Peter Donald
+ * @author Jeff Turner
+ * @author Matthew Hawthorne
+ * @author Jeremias Maerki
+ * @author Stephen Colebourne
+ * @author Ian Springer
+ * @author Chris Eldredge
+ * @author Jim Harrington
+ * @author Niall Pemberton
+ * @author Sandy McArthur
+ * @version $Id$
+ */
+public class FileUtils {
+
+ /**
+ * Instances should NOT be constructed in standard programming.
+ */
+ public FileUtils() {
+ super();
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Deletes a directory recursively.
+ *
+ * @param directory directory to delete
+ * @throws IOException in case deletion is unsuccessful
+ */
+ public static void deleteDirectory(File directory) throws IOException {
+ if (!directory.exists()) {
+ return;
+ }
+
+ cleanDirectory(directory);
+ if (!directory.delete()) {
+ String message =
+ "Unable to delete directory " + directory + ".";
+ throw new IOException(message);
+ }
+ }
+
+
+
+ /**
+ * Cleans a directory without deleting it.
+ *
+ * @param directory directory to clean
+ * @throws IOException in case cleaning is unsuccessful
+ */
+ public static void cleanDirectory(File directory) throws IOException {
+ if (!directory.exists()) {
+ String message = directory + " does not exist";
+ throw new IllegalArgumentException(message);
+ }
+
+ if (!directory.isDirectory()) {
+ String message = directory + " is not a directory";
+ throw new IllegalArgumentException(message);
+ }
+
+ File[] files = directory.listFiles();
+ if (files == null) { // null if security restricted
+ throw new IOException("Failed to list contents of " + directory);
+ }
+
+ IOException exception = null;
+ for (int i = 0; i < files.length; i++) {
+ File file = files[i];
+ try {
+ forceDelete(file);
+ } catch (IOException ioe) {
+ exception = ioe;
+ }
+ }
+
+ if (null != exception) {
+ throw exception;
+ }
+ }
+
+
+ //-----------------------------------------------------------------------
+ /**
+ * Deletes a file. If file is a directory, delete it and all sub-directories.
+ *
+ * The difference between File.delete() and this method are:
+ *
+ * This class provides static utility methods for input/output operations.
+ *
+ * The byte-to-char methods and char-to-byte methods involve a conversion step.
+ * Two methods are provided in each case, one that uses the platform default
+ * encoding and the other which allows you to specify an encoding. You are
+ * encouraged to always specify an encoding because relying on the platform
+ * default can lead to unexpected results, for example when moving from
+ * development to production.
+ *
+ * All the methods in this class that read a stream are buffered internally.
+ * This means that there is no cause to use a
+ * Wherever possible, the methods in this class do not flush or close
+ * the stream. This is to avoid making non-portable assumptions about the
+ * streams' origin and further use. Thus the caller is still responsible for
+ * closing streams after use.
+ *
+ * Origin of code: Excalibur.
+ *
+ * @author Peter Donald
+ * @author Jeff Turner
+ * @author Matthew Hawthorne
+ * @author Stephen Colebourne
+ * @author Gareth Davis
+ * @author Ian Springer
+ * @author Niall Pemberton
+ * @author Sandy McArthur
+ * @version $Id$
+ */
+public class IOUtils {
+ // NOTE: This class is focussed on InputStream, OutputStream, Reader and
+ // Writer. Each method should take at least one of these as a parameter,
+ // or return one of them.
+
+ /**
+ * The default buffer size to use.
+ */
+ private static final int DEFAULT_BUFFER_SIZE = 1024 * 4;
+
+ /**
+ * Instances should NOT be constructed in standard programming.
+ */
+ public IOUtils() {
+ super();
+ }
+
+
+ /**
+ * Unconditionally close an
+ * Equivalent to {@link InputStream#close()}, except any exceptions will be ignored.
+ * This is typically used in finally blocks.
+ *
+ * @param input the InputStream to close, may be null or already closed
+ */
+ public static void closeQuietly(InputStream input) {
+ try {
+ if (input != null) {
+ input.close();
+ }
+ } catch (IOException ioe) {
+ // ignore
+ }
+ }
+
+ // copy from InputStream
+ //-----------------------------------------------------------------------
+ /**
+ * Copy bytes from an
+ * This method buffers the input internally, so there is no need to use a
+ *
+ * Large streams (over 2GB) will return a bytes copied value of
+ *
+ * This method buffers the input internally, so there is no need to use a
+ *
+ * This method
+ * simply performs
+ * This method simply performs Here is an exaple of usage of this class. Here is an example of usage of this class. Constructs a 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 Constructs a Constructs a Arbitrary large amounts of data can be processed by this
* method using a constant size buffer. (see {@link
- * #MultipartStream(InputStream,byte[],int) constructor}).
+ * #MultipartStream(InputStream,byte[],int,
+ * MultipartStream.ProgressNotifier) constructor}).
*
- * @param output The Reads
+ * Abstracts access to the request information needed for file uploads. This
+ * interfsace should be implemented for each type of request that may be
+ * handled by FileUpload, such as servlets and portlets. High level API for processing file uploads. This class handles multiple files per single HTML widget, sent using
+ * 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. Provides access to the request information needed for a request made to
+ * an HTTP servlet.
- 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
- Normal usage example:
- NOTE: This code has been copied from commons-fileupload 1.2.1 and
+ commons-io 1.4 and package renamed to avoid clashes with any web apps that
+ may wish to use these libraries.
+
+ A component for handling HTML file uploads as specified by
+ RFC 1867.
+ This component provides support for uploads within both servlets (JSR 53)
+ and portlets (JSR 168).
+
+ While this package provides the generic functionality for file uploads,
+ these classes are not typically used directly. Instead, normal usage
+ involves one of the provided extensions of
+ {@link org.apache.commons.fileupload.FileUpload FileUpload} such as
+ {@link org.apache.commons.fileupload.servlet.ServletFileUpload ServletFileUpload}
+ or
+ {@link org.apache.commons.fileupload.portlet.PortletFileUpload PortletFileUpload},
+ together with a factory for
+ {@link org.apache.commons.fileupload.FileItem FileItem} instances,
+ such as
+ {@link org.apache.commons.fileupload.disk.DiskFileItemFactory DiskFileItemFactory}.
+
+ The following is a brief example of typical usage in a servlet, storing
+ the uploaded files on disk.
+
- In the example above the first file is loaded into memory as a
-
+ In the example above, the first file is loaded into memory as a
+
+ Please see the FileUpload
+ User Guide
+ for further details and examples of how to use this package.
+ header-part and returns as key/value
* pairs.
@@ -479,64 +547,114 @@ public abstract class FileUploadBase
*
* @return A Map containing the parsed HTTP request headers.
*/
- protected Mapheader-part and returns as key/value
+ * pairs.
+ *
+ * header-part of the current
+ * encapsulation.
+ *
+ * @return A Map containing the parsed HTTP request headers.
+ * @deprecated Use {@link #getParsedHeaders(String)}
+ */
+ protected Map /* String, String */ parseHeaders(String headerPart) {
+ FileItemHeaders headers = getParsedHeaders(headerPart);
+ Map result = new HashMap();
+ for (Iterator iter = headers.getHeaderNames(); iter.hasNext();) {
+ String headerName = (String) iter.next();
+ Iterator iter2 = headers.getHeaders(headerName);
+ String headerValue = (String) iter2.next();
+ while (iter2.hasNext()) {
+ headerValue += "," + iter2.next();
+ }
+ result.put(headerName, headerValue);
+ }
+ return result;
+ }
+
+ /**
+ * Skips bytes until the end of the current line.
+ * @param headerPart The headers, which are being parsed.
+ * @param end Index of the last byte, which has yet been
+ * processed.
+ * @return Index of the \r\n sequence, which indicates
+ * end of line.
+ */
+ private int parseEndOfLine(String headerPart, int end) {
+ int index = end;
+ for (;;) {
+ int offset = headerPart.indexOf('\r', index);
+ if (offset == -1 || offset + 1 >= headerPart.length()) {
+ throw new IllegalStateException(
+ "Expected headers to be terminated by an empty line.");
+ }
+ if (headerPart.charAt(offset + 1) == '\n') {
+ return offset;
+ }
+ index = offset + 1;
+ }
+ }
+
+ /**
+ * Reads the next header line.
+ * @param headers String with all headers.
+ * @param header Map where to store the current header.
+ */
+ private void parseHeaderLine(FileItemHeadersImpl headers, String header) {
+ final int colonOffset = header.indexOf(':');
+ if (colonOffset == -1) {
+ // This header line is malformed, skip it.
+ return;
+ }
+ String headerName = header.substring(0, colonOffset).trim();
+ String headerValue =
+ header.substring(header.indexOf(':') + 1).trim();
+ headers.addHeader(headerName, headerValue);
+ }
/**
* Returns the header with the specified name from the supplied map. The
@@ -547,27 +665,442 @@ public abstract class FileUploadBase
*
* @return The value of specified header, or a comma-separated list if
* there were multiple headers of that name.
+ * @deprecated Use {@link FileItemHeaders#getHeader(String)}.
*/
- protected final String getHeader(MapFileUploadIOException with the
+ * given cause.
+ * @param pCause The exceptions cause, if any, or null.
+ */
+ public FileUploadIOException(FileUploadException pCause) {
+ // We're not doing super(pCause) cause of 1.3 compatibility.
+ cause = pCause;
+ }
+
+ /**
+ * Returns the exceptions cause.
+ * @return The exceptions cause, if any, or null.
+ */
+ public Throwable getCause() {
+ return cause;
+ }
+ }
/**
* Thrown to indicate that the request is not a multipart request.
*/
public static class InvalidContentTypeException
- extends FileUploadException
- {
+ extends FileUploadException {
+ /** The exceptions UID, for serializing an instance.
+ */
+ private static final long serialVersionUID = -9073026332015646668L;
+
/**
* Constructs a InvalidContentTypeException with no
* detail message.
*/
- public InvalidContentTypeException()
- {
- super();
+ public InvalidContentTypeException() {
+ // Nothing to do.
}
/**
@@ -576,25 +1109,106 @@ public abstract class FileUploadBase
*
* @param message The detail message.
*/
- public InvalidContentTypeException(String message)
- {
+ public InvalidContentTypeException(String message) {
super(message);
}
}
+ /**
+ * Thrown to indicate an IOException.
+ */
+ public static class IOFileUploadException extends FileUploadException {
+ /** The exceptions UID, for serializing an instance.
+ */
+ private static final long serialVersionUID = 1749796615868477269L;
+ /** The exceptions cause; we overwrite the parent
+ * classes field, which is available since Java
+ * 1.4 only.
+ */
+ private final IOException cause;
+
+ /**
+ * Creates a new instance with the given cause.
+ * @param pMsg The detail message.
+ * @param pException The exceptions cause.
+ */
+ public IOFileUploadException(String pMsg, IOException pException) {
+ super(pMsg);
+ cause = pException;
+ }
+
+ /**
+ * Returns the exceptions cause.
+ * @return The exceptions cause, if any, or null.
+ */
+ public Throwable getCause() {
+ return cause;
+ }
+ }
+
+ /** This exception is thrown, if a requests permitted size
+ * is exceeded.
+ */
+ protected abstract static class SizeException extends FileUploadException {
+ /**
+ * The actual size of the request.
+ */
+ private final long actual;
+
+ /**
+ * The maximum permitted size of the request.
+ */
+ private final long permitted;
+
+ /**
+ * Creates a new instance.
+ * @param message The detail message.
+ * @param actual The actual number of bytes in the request.
+ * @param permitted The requests size limit, in bytes.
+ */
+ protected SizeException(String message, long actual, long permitted) {
+ super(message);
+ this.actual = actual;
+ this.permitted = permitted;
+ }
+
+ /**
+ * Retrieves the actual size of the request.
+ *
+ * @return The actual size of the request.
+ */
+ public long getActualSize() {
+ return actual;
+ }
+
+ /**
+ * Retrieves the permitted size of the request.
+ *
+ * @return The permitted size of the request.
+ */
+ public long getPermittedSize() {
+ return permitted;
+ }
+ }
/**
- * Thrown to indicate that the request size is not specified.
+ * Thrown to indicate that the request size is not specified. In other
+ * words, it is thrown, if the content-length header is missing or
+ * contains the value -1.
+ * @deprecated As of commons-fileupload 1.2, the presence of a
+ * content-length header is no longer required.
*/
public static class UnknownSizeException
- extends FileUploadException
- {
+ extends FileUploadException {
+ /** The exceptions UID, for serializing an instance.
+ */
+ private static final long serialVersionUID = 7062279004812015273L;
+
/**
* Constructs a UnknownSizeException with no
* detail message.
*/
- public UnknownSizeException()
- {
+ public UnknownSizeException() {
super();
}
@@ -604,38 +1218,87 @@ public abstract class FileUploadBase
*
* @param message The detail message.
*/
- public UnknownSizeException(String message)
- {
+ public UnknownSizeException(String message) {
super(message);
}
}
-
/**
* Thrown to indicate that the request size exceeds the configured maximum.
*/
public static class SizeLimitExceededException
- extends FileUploadException
- {
+ extends SizeException {
+ /** The exceptions UID, for serializing an instance.
+ */
+ private static final long serialVersionUID = -2474893167098052828L;
+
/**
- * Constructs a SizeExceededException with no
- * detail message.
+ * @deprecated Replaced by
+ * {@link #SizeLimitExceededException(String, long, long)}
*/
- public SizeLimitExceededException()
- {
- super();
+ public SizeLimitExceededException() {
+ this(null, 0, 0);
}
/**
- * Constructs an SizeExceededException with
- * the specified detail message.
+ * @deprecated Replaced by
+ * {@link #SizeLimitExceededException(String, long, long)}
+ * @param message The exceptions detail message.
+ */
+ public SizeLimitExceededException(String message) {
+ this(message, 0, 0);
+ }
+
+ /**
+ * Constructs a SizeExceededException with
+ * the specified detail message, and actual and permitted sizes.
*
- * @param message The detail message.
+ * @param message The detail message.
+ * @param actual The actual request size.
+ * @param permitted The maximum permitted request size.
*/
- public SizeLimitExceededException(String message)
- {
- super(message);
+ public SizeLimitExceededException(String message, long actual,
+ long permitted) {
+ super(message, actual, permitted);
+ }
+ }
+
+ /**
+ * Thrown to indicate that A files size exceeds the configured maximum.
+ */
+ public static class FileSizeLimitExceededException
+ extends SizeException {
+ /** The exceptions UID, for serializing an instance.
+ */
+ private static final long serialVersionUID = 8150776562029630058L;
+
+ /**
+ * Constructs a SizeExceededException with
+ * the specified detail message, and actual and permitted sizes.
+ *
+ * @param message The detail message.
+ * @param actual The actual request size.
+ * @param permitted The maximum permitted request size.
+ */
+ public FileSizeLimitExceededException(String message, long actual,
+ long permitted) {
+ super(message, actual, permitted);
}
}
+ /**
+ * Returns the progress listener.
+ * @return The progress listener, if any, or null.
+ */
+ public ProgressListener getProgressListener() {
+ return listener;
+ }
+
+ /**
+ * Sets the progress listener.
+ * @param pListener The progress listener, if any. Defaults to null.
+ */
+ public void setProgressListener(ProgressListener pListener) {
+ listener = pListener;
+ }
}
diff --git a/java/org/apache/tomcat/util/http/fileupload/FileUploadException.java b/java/org/apache/tomcat/util/http/fileupload/FileUploadException.java
index fd5def139..092ee340d 100644
--- a/java/org/apache/tomcat/util/http/fileupload/FileUploadException.java
+++ b/java/org/apache/tomcat/util/http/fileupload/FileUploadException.java
@@ -5,19 +5,20 @@
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
- *
+ *
* http://www.apache.org/licenses/LICENSE-2.0
- *
+ *
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
-
package org.apache.tomcat.util.http.fileupload;
+import java.io.PrintStream;
+import java.io.PrintWriter;
+
/**
* Exception for errors encountered while processing the request.
@@ -25,15 +26,23 @@ package org.apache.tomcat.util.http.fileupload;
* @author John McNally
* @version $Id$
*/
-public class FileUploadException
- extends Exception
-{
+public class FileUploadException extends Exception {
+ /**
+ * Serial version UID, being used, if the exception
+ * is serialized.
+ */
+ private static final long serialVersionUID = 8881893724388807504L;
+ /**
+ * The exceptions cause. We overwrite the cause of
+ * the super class, which isn't available in Java 1.3.
+ */
+ private final Throwable cause;
/**
* Constructs a new FileUploadException without message.
*/
- public FileUploadException()
- {
+ public FileUploadException() {
+ this(null, null);
}
/**
@@ -42,8 +51,49 @@ public class FileUploadException
*
* @param msg the error message.
*/
- public FileUploadException(String msg)
- {
+ public FileUploadException(final String msg) {
+ this(msg, null);
+ }
+
+ /**
+ * Creates a new FileUploadException with the given
+ * detail message and cause.
+ * @param msg The exceptions detail message.
+ * @param cause The exceptions cause.
+ */
+ public FileUploadException(String msg, Throwable cause) {
super(msg);
+ this.cause = cause;
+ }
+
+ /**
+ * Prints this throwable and its backtrace to the specified print stream.
+ *
+ * @param stream PrintStream to use for output
+ */
+ public void printStackTrace(PrintStream stream) {
+ super.printStackTrace(stream);
+ if (cause != null) {
+ stream.println("Caused by:");
+ cause.printStackTrace(stream);
+ }
+ }
+
+ /**
+ * Prints this throwable and its backtrace to the specified
+ * print writer.
+ *
+ * @param writer PrintWriter to use for output
+ */
+ public void printStackTrace(PrintWriter writer) {
+ super.printStackTrace(writer);
+ if (cause != null) {
+ writer.println("Caused by:");
+ cause.printStackTrace(writer);
+ }
+ }
+
+ public Throwable getCause() {
+ return cause;
}
}
diff --git a/java/org/apache/tomcat/util/http/fileupload/FileUtils.java b/java/org/apache/tomcat/util/http/fileupload/FileUtils.java
new file mode 100644
index 000000000..e8af88597
--- /dev/null
+++ b/java/org/apache/tomcat/util/http/fileupload/FileUtils.java
@@ -0,0 +1,229 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.tomcat.util.http.fileupload;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+
+
+/**
+ * General file manipulation utilities.
+ *
+ *
+ *
+ *
+ *
+ * @param file file or directory to delete, must not be null
+ * @throws NullPointerException if the directory is null
+ * @throws FileNotFoundException if the file was not found
+ * @throws IOException in case deletion is unsuccessful
+ */
+ public static void forceDelete(File file) throws IOException {
+ if (file.isDirectory()) {
+ deleteDirectory(file);
+ } else {
+ boolean filePresent = file.exists();
+ if (!file.delete()) {
+ if (!filePresent){
+ throw new FileNotFoundException("File does not exist: " + file);
+ }
+ String message =
+ "Unable to delete file: " + file;
+ throw new IOException(message);
+ }
+ }
+ }
+
+ /**
+ * Schedules a file to be deleted when JVM exits.
+ * If file is directory delete it and all sub-directories.
+ *
+ * @param file file or directory to delete, must not be null
+ * @throws NullPointerException if the file is null
+ * @throws IOException in case deletion is unsuccessful
+ */
+ public static void forceDeleteOnExit(File file) throws IOException {
+ if (file.isDirectory()) {
+ deleteDirectoryOnExit(file);
+ } else {
+ file.deleteOnExit();
+ }
+ }
+
+ /**
+ * Schedules a directory recursively for deletion on JVM exit.
+ *
+ * @param directory directory to delete, must not be null
+ * @throws NullPointerException if the directory is null
+ * @throws IOException in case deletion is unsuccessful
+ */
+ private static void deleteDirectoryOnExit(File directory) throws IOException {
+ if (!directory.exists()) {
+ return;
+ }
+
+ cleanDirectoryOnExit(directory);
+ directory.deleteOnExit();
+ }
+
+ /**
+ * Cleans a directory without deleting it.
+ *
+ * @param directory directory to clean, must not be null
+ * @throws NullPointerException if the directory is null
+ * @throws IOException in case cleaning is unsuccessful
+ */
+ private static void cleanDirectoryOnExit(File directory) throws IOException {
+ if (!directory.exists()) {
+ String message = directory + " does not exist";
+ throw new IllegalArgumentException(message);
+ }
+
+ if (!directory.isDirectory()) {
+ String message = directory + " is not a directory";
+ throw new IllegalArgumentException(message);
+ }
+
+ File[] files = directory.listFiles();
+ if (files == null) { // null if security restricted
+ throw new IOException("Failed to list contents of " + directory);
+ }
+
+ IOException exception = null;
+ for (int i = 0; i < files.length; i++) {
+ File file = files[i];
+ try {
+ forceDeleteOnExit(file);
+ } catch (IOException ioe) {
+ exception = ioe;
+ }
+ }
+
+ if (null != exception) {
+ throw exception;
+ }
+ }
+}
diff --git a/java/org/apache/tomcat/util/http/fileupload/IOUtils.java b/java/org/apache/tomcat/util/http/fileupload/IOUtils.java
new file mode 100644
index 000000000..103dfd47b
--- /dev/null
+++ b/java/org/apache/tomcat/util/http/fileupload/IOUtils.java
@@ -0,0 +1,162 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.tomcat.util.http.fileupload;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+
+/**
+ * General IO stream manipulation utilities.
+ *
+ *
+ * BufferedInputStream
+ * or BufferedReader. The default buffer size of 4K has been shown
+ * to be efficient in tests.
+ * InputStream.
+ * InputStream to an
+ * OutputStream.
+ * BufferedInputStream.
+ * -1 after the copy has completed since the correct
+ * number of bytes cannot be returned as an int. For large streams
+ * use the copyLarge(InputStream, OutputStream) method.
+ *
+ * @param input the InputStream to read from
+ * @param output the OutputStream to write to
+ * @return the number of bytes copied
+ * @throws NullPointerException if the input or output is null
+ * @throws IOException if an I/O error occurs
+ * @throws ArithmeticException if the byte count is too large
+ * @since Commons IO 1.1
+ */
+ public static int copy(InputStream input, OutputStream output) throws IOException {
+ long count = copyLarge(input, output);
+ if (count > Integer.MAX_VALUE) {
+ return -1;
+ }
+ return (int) count;
+ }
+
+ /**
+ * Copy bytes from a large (over 2GB) InputStream to an
+ * OutputStream.
+ * BufferedInputStream.
+ *
+ * @param input the InputStream to read from
+ * @param output the OutputStream to write to
+ * @return the number of bytes copied
+ * @throws NullPointerException if the input or output is null
+ * @throws IOException if an I/O error occurs
+ * @since Commons IO 1.3
+ */
+ public static long copyLarge(InputStream input, OutputStream output)
+ throws IOException {
+ byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];
+ long count = 0;
+ int n = 0;
+ while (-1 != (n = input.read(buffer))) {
+ output.write(buffer, 0, n);
+ count += n;
+ }
+ return count;
+ }
+
+
+
+
+
+
+}
diff --git a/java/org/apache/tomcat/util/http/fileupload/LimitedInputStream.java b/java/org/apache/tomcat/util/http/fileupload/LimitedInputStream.java
new file mode 100644
index 000000000..6d0a536ae
--- /dev/null
+++ b/java/org/apache/tomcat/util/http/fileupload/LimitedInputStream.java
@@ -0,0 +1,155 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.tomcat.util.http.fileupload;
+
+import java.io.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+
+/**
+ * An input stream, which limits its data size. This stream is
+ * used, if the content length is unknown.
+ */
+public abstract class LimitedInputStream
+ extends FilterInputStream implements Closeable {
+ /**
+ * The maximum size of an item, in bytes.
+ */
+ private long sizeMax;
+ /**
+ * The current number of bytes.
+ */
+ private long count;
+ /**
+ * Whether this stream is already closed.
+ */
+ private boolean closed;
+
+ /**
+ * Creates a new instance.
+ * @param pIn The input stream, which shall be limited.
+ * @param pSizeMax The limit; no more than this number of bytes
+ * shall be returned by the source stream.
+ */
+ public LimitedInputStream(InputStream pIn, long pSizeMax) {
+ super(pIn);
+ sizeMax = pSizeMax;
+ }
+
+ /**
+ * Called to indicate, that the input streams limit has
+ * been exceeded.
+ * @param pSizeMax The input streams limit, in bytes.
+ * @param pCount The actual number of bytes.
+ * @throws IOException The called method is expected
+ * to raise an IOException.
+ */
+ protected abstract void raiseError(long pSizeMax, long pCount)
+ throws IOException;
+
+ /** Called to check, whether the input streams
+ * limit is reached.
+ * @throws IOException The given limit is exceeded.
+ */
+ private void checkLimit() throws IOException {
+ if (count > sizeMax) {
+ raiseError(sizeMax, count);
+ }
+ }
+
+ /**
+ * Reads the next byte of data from this input stream. The value
+ * byte is returned as an int in the range
+ * 0 to 255. If no byte is available
+ * because the end of the stream has been reached, the value
+ * -1 is returned. This method blocks until input data
+ * is available, the end of the stream is detected, or an exception
+ * is thrown.
+ * in.read() and returns the result.
+ *
+ * @return the next byte of data, or -1 if the end of the
+ * stream is reached.
+ * @exception IOException if an I/O error occurs.
+ * @see java.io.FilterInputStream#in
+ */
+ public int read() throws IOException {
+ int res = super.read();
+ if (res != -1) {
+ count++;
+ checkLimit();
+ }
+ return res;
+ }
+
+ /**
+ * Reads up to len bytes of data from this input stream
+ * into an array of bytes. If len is not zero, the method
+ * blocks until some input is available; otherwise, no
+ * bytes are read and 0 is returned.
+ * in.read(b, off, len)
+ * and returns the result.
+ *
+ * @param b the buffer into which the data is read.
+ * @param off The start offset in the destination array
+ * b.
+ * @param len the maximum number of bytes read.
+ * @return the total number of bytes read into the buffer, or
+ * -1 if there is no more data because the end of
+ * the stream has been reached.
+ * @exception NullPointerException If b is null.
+ * @exception IndexOutOfBoundsException If off is negative,
+ * len is negative, or len is greater than
+ * b.length - off
+ * @exception IOException if an I/O error occurs.
+ * @see java.io.FilterInputStream#in
+ */
+ public int read(byte[] b, int off, int len) throws IOException {
+ int res = super.read(b, off, len);
+ if (res > 0) {
+ count += res;
+ checkLimit();
+ }
+ return res;
+ }
+
+ /**
+ * Returns, whether this stream is already closed.
+ * @return True, if the stream is closed, otherwise false.
+ * @throws IOException An I/O error occurred.
+ */
+ public boolean isClosed() throws IOException {
+ return closed;
+ }
+
+ /**
+ * Closes this input stream and releases any system resources
+ * associated with the stream.
+ * This
+ * method simply performs in.close().
+ *
+ * @exception IOException if an I/O error occurs.
+ * @see java.io.FilterInputStream#in
+ */
+ public void close() throws IOException {
+ closed = true;
+ super.close();
+ }
+}
diff --git a/java/org/apache/tomcat/util/http/fileupload/MultipartStream.java b/java/org/apache/tomcat/util/http/fileupload/MultipartStream.java
index 446eef83d..5f03f4d5c 100644
--- a/java/org/apache/tomcat/util/http/fileupload/MultipartStream.java
+++ b/java/org/apache/tomcat/util/http/fileupload/MultipartStream.java
@@ -5,20 +5,17 @@
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
- *
+ *
* http://www.apache.org/licenses/LICENSE-2.0
- *
+ *
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
-
package org.apache.tomcat.util.http.fileupload;
-
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
@@ -58,13 +55,13 @@ import java.io.UnsupportedEncodingException;
* boundary token of the same length as the parent stream (see {@link
* #setBoundary(byte[])}).
*
- *
+ *
*
*
* try {
* MultipartStream multipartStream = new MultipartStream(input,
* boundary);
- * boolean nextPart = malitPartStream.skipPreamble();
+ * boolean nextPart = multipartStream.skipPreamble();
* OutputStream output;
* while(nextPart) {
* header = chunks.readHeader();
@@ -87,13 +84,79 @@ import java.io.UnsupportedEncodingException;
*
* @version $Id$
*/
-public class MultipartStream
-{
+public class MultipartStream {
+ /**
+ * Internal class, which is used to invoke the
+ * {@link ProgressListener}.
+ */
+ static class ProgressNotifier {
+ /** The listener to invoke.
+ */
+ private final ProgressListener listener;
+ /** Number of expected bytes, if known, or -1.
+ */
+ private final long contentLength;
+ /** Number of bytes, which have been read so far.
+ */
+ private long bytesRead;
+ /** Number of items, which have been read so far.
+ */
+ private int items;
+ /** Creates a new instance with the given listener
+ * and content length.
+ * @param pListener The listener to invoke.
+ * @param pContentLength The expected content length.
+ */
+ ProgressNotifier(ProgressListener pListener, long pContentLength) {
+ listener = pListener;
+ contentLength = pContentLength;
+ }
+ /** Called to indicate that bytes have been read.
+ * @param pBytes Number of bytes, which have been read.
+ */
+ void noteBytesRead(int pBytes) {
+ /* Indicates, that the given number of bytes have been read from
+ * the input stream.
+ */
+ bytesRead += pBytes;
+ notifyListener();
+ }
+ /** Called to indicate, that a new file item has been detected.
+ */
+ void noteItem() {
+ ++items;
+ }
+ /** Called for notifying the listener.
+ */
+ private void notifyListener() {
+ if (listener != null) {
+ listener.update(bytesRead, contentLength, items);
+ }
+ }
+ }
// ----------------------------------------------------- Manifest constants
/**
+ * The Carriage Return ASCII character value.
+ */
+ public static final byte CR = 0x0D;
+
+
+ /**
+ * The Line Feed ASCII character value.
+ */
+ public static final byte LF = 0x0A;
+
+
+ /**
+ * The dash (-) ASCII character value.
+ */
+ public static final byte DASH = 0x2D;
+
+
+ /**
* The maximum length of header-part that will be
* processed (10 kilobytes = 10240 bytes.).
*/
@@ -110,21 +173,31 @@ public class MultipartStream
* A byte sequence that marks the end of header-part
* (CRLFCRLF).
*/
- protected static final byte[] HEADER_SEPARATOR = {0x0D, 0x0A, 0x0D, 0x0A};
+ protected static final byte[] HEADER_SEPARATOR = {
+ CR, LF, CR, LF };
/**
* A byte sequence that that follows a delimiter that will be
* followed by an encapsulation (CRLF).
*/
- protected static final byte[] FIELD_SEPARATOR = { 0x0D, 0x0A };
+ protected static final byte[] FIELD_SEPARATOR = {
+ CR, LF};
/**
* A byte sequence that that follows a delimiter of the last
* encapsulation in the stream (--).
*/
- protected static final byte[] STREAM_TERMINATOR = { 0x2D, 0x2D };
+ protected static final byte[] STREAM_TERMINATOR = {
+ DASH, DASH};
+
+
+ /**
+ * A byte sequence that precedes a boundary (CRLF--).
+ */
+ protected static final byte[] BOUNDARY_PREFIX = {
+ CR, LF, DASH, DASH};
// ----------------------------------------------------------- Data members
@@ -133,7 +206,7 @@ public class MultipartStream
/**
* The input stream from which data is read.
*/
- private InputStream input;
+ private final InputStream input;
/**
@@ -158,13 +231,13 @@ public class MultipartStream
/**
* The length of the buffer used for processing the request.
*/
- private int bufSize;
+ private final int bufSize;
/**
* The buffer used for processing the request.
*/
- private byte[] buffer;
+ private final byte[] buffer;
/**
@@ -189,21 +262,47 @@ public class MultipartStream
private String headerEncoding;
+ /**
+ * The progress notifier, if any, or null.
+ */
+ private final ProgressNotifier notifier;
+
// ----------------------------------------------------------- Constructors
+ /**
+ * Creates a new instance.
+ * @deprecated Use {@link #MultipartStream(InputStream, byte[],
+ * org.apache.commons.fileupload.MultipartStream.ProgressNotifier)},
+ * or {@link #MultipartStream(InputStream, byte[], int,
+ * org.apache.commons.fileupload.MultipartStream.ProgressNotifier)}
+ */
+ public MultipartStream() {
+ this(null, null, null);
+ }
/**
- * Default constructor.
+ * MultipartStream with a custom size buffer
+ * and no progress notifier.
*
- * @see #MultipartStream(InputStream, byte[], int)
- * @see #MultipartStream(InputStream, byte[])
+ * 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(InputStream, byte[],
+ * MultipartStream.ProgressNotifier)
+ * @deprecated Use {@link #MultipartStream(InputStream, byte[], int,
+ * org.apache.commons.fileupload.MultipartStream.ProgressNotifier)}.
*/
- public MultipartStream()
- {
+ public MultipartStream(InputStream input, byte[] boundary, int bufSize) {
+ this(input, boundary, bufSize, null);
}
-
/**
* MultipartStream with a custom size buffer.
*
@@ -216,30 +315,30 @@ public class MultipartStream
* @param boundary The token used for dividing the stream into
* encapsulations.
* @param bufSize The size of the buffer to be used, in bytes.
+ * @param pNotifier The notifier, which is used for calling the
+ * progress listener, if any.
*
- *
- * @see #MultipartStream()
- * @see #MultipartStream(InputStream, byte[])
- *
+ * @see #MultipartStream(InputStream, byte[],
+ * MultipartStream.ProgressNotifier)
*/
- public MultipartStream(InputStream input,
- byte[] boundary,
- int bufSize)
- {
+ MultipartStream(InputStream input,
+ byte[] boundary,
+ int bufSize,
+ ProgressNotifier pNotifier) {
this.input = input;
this.bufSize = bufSize;
this.buffer = new byte[bufSize];
+ this.notifier = pNotifier;
// 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);
+ this.boundary = new byte[boundary.length + BOUNDARY_PREFIX.length];
+ this.boundaryLength = boundary.length + BOUNDARY_PREFIX.length;
+ this.keepRegion = this.boundary.length;
+ System.arraycopy(BOUNDARY_PREFIX, 0, this.boundary, 0,
+ BOUNDARY_PREFIX.length);
+ System.arraycopy(boundary, 0, this.boundary, BOUNDARY_PREFIX.length,
+ boundary.length);
head = 0;
tail = 0;
@@ -252,21 +351,35 @@ public class MultipartStream
* @param input The InputStream to serve as a data source.
* @param boundary The token used for dividing the stream into
* encapsulations.
+ * @param pNotifier An object for calling the progress listener, if any.
+ *
*
- * @exception IOException when an error occurs.
+ * @see #MultipartStream(InputStream, byte[], int,
+ * MultipartStream.ProgressNotifier)
+ */
+ MultipartStream(InputStream input,
+ byte[] boundary,
+ ProgressNotifier pNotifier) {
+ this(input, boundary, DEFAULT_BUFSIZE, pNotifier);
+ }
+
+ /**
+ * MultipartStream with a default size buffer.
*
- * @see #MultipartStream()
- * @see #MultipartStream(InputStream, byte[], int)
+ * @param input The InputStream to serve as a data source.
+ * @param boundary The token used for dividing the stream into
+ * encapsulations.
*
+ * @deprecated Use {@link #MultipartStream(InputStream, byte[],
+ * MultipartStream.ProgressNotifier)}.
+ * @see #MultipartStream(InputStream, byte[], int,
+ * MultipartStream.ProgressNotifier)
*/
public MultipartStream(InputStream input,
- byte[] boundary)
- throws IOException
- {
- this(input, boundary, DEFAULT_BUFSIZE);
+ byte[] boundary) {
+ this(input, boundary, DEFAULT_BUFSIZE, null);
}
-
// --------------------------------------------------------- Public methods
@@ -278,8 +391,7 @@ public class MultipartStream
*
* @return The encoding used to read part headers.
*/
- public String getHeaderEncoding()
- {
+ public String getHeaderEncoding() {
return headerEncoding;
}
@@ -291,8 +403,7 @@ public class MultipartStream
*
* @param encoding The encoding used to read part headers.
*/
- public void setHeaderEncoding(String encoding)
- {
+ public void setHeaderEncoding(String encoding) {
headerEncoding = encoding;
}
@@ -303,22 +414,21 @@ public class MultipartStream
*
* @return The next byte from the input stream.
*
- * @exception IOException if there is no more data available.
+ * @throws IOException if there is no more data available.
*/
- public byte readByte()
- throws IOException
- {
+ public byte readByte() throws IOException {
// Buffer depleted ?
- if (head == tail)
- {
+ if (head == tail) {
head = 0;
// Refill.
tail = input.read(buffer, head, bufSize);
- if (tail == -1)
- {
+ if (tail == -1) {
// No more data available.
throw new IOException("No more data is available");
}
+ if (notifier != null) {
+ notifier.noteBytesRead(tail);
+ }
}
return buffer[head++];
}
@@ -331,36 +441,37 @@ public class MultipartStream
* @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.
+ * @throws MalformedStreamException if the stream ends unexpecetedly or
+ * fails to follow required syntax.
*/
public boolean readBoundary()
- throws MalformedStreamException
- {
+ throws MalformedStreamException {
byte[] marker = new byte[2];
boolean nextChunk = false;
head += boundaryLength;
- try
- {
+ try {
marker[0] = readByte();
+ if (marker[0] == LF) {
+ // Work around IE5 Mac bug with input type=image.
+ // Because the boundary delimiter, not including the trailing
+ // CRLF, must not appear within any file (RFC 2046, section
+ // 5.1.1), we know the missing CR is due to a buggy browser
+ // rather than a file containing something similar to a
+ // boundary.
+ return true;
+ }
+
marker[1] = readByte();
- if (arrayequals(marker, STREAM_TERMINATOR, 2))
- {
+ if (arrayequals(marker, STREAM_TERMINATOR, 2)) {
nextChunk = false;
- }
- else if (arrayequals(marker, FIELD_SEPARATOR, 2))
- {
+ } else if (arrayequals(marker, FIELD_SEPARATOR, 2)) {
nextChunk = true;
- }
- else
- {
+ } else {
throw new MalformedStreamException(
- "Unexpected characters follow a boundary");
+ "Unexpected characters follow a boundary");
}
- }
- catch (IOException e)
- {
+ } catch (IOException e) {
throw new MalformedStreamException("Stream ended unexpectedly");
}
return nextChunk;
@@ -382,19 +493,18 @@ public class MultipartStream
* @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.
+ * @throws 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)
- {
+ throws IllegalBoundaryException {
+ if (boundary.length != boundaryLength - BOUNDARY_PREFIX.length) {
throw new IllegalBoundaryException(
- "The length of a boundary token can not be changed");
+ "The length of a boundary token can not be changed");
}
- System.arraycopy(boundary, 0, this.boundary, 4, boundary.length);
+ System.arraycopy(boundary, 0, this.boundary, BOUNDARY_PREFIX.length,
+ boundary.length);
}
@@ -411,58 +521,44 @@ public class MultipartStream
*
* @return The header-part of the current encapsulation.
*
- * @exception MalformedStreamException if the stream ends unexpecetedly.
+ * @throws MalformedStreamException if the stream ends unexpecetedly.
*/
public String readHeaders()
- throws MalformedStreamException
- {
+ throws MalformedStreamException {
int i = 0;
- byte b[] = new byte[1];
+ byte b;
// 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)
- {
+ while (i < HEADER_SEPARATOR.length) {
+ try {
+ b = readByte();
+ } catch (IOException e) {
throw new MalformedStreamException("Stream ended unexpectedly");
}
- size++;
- if (b[0] == HEADER_SEPARATOR[i])
- {
- i++;
+ if (++size > HEADER_PART_SIZE_MAX) {
+ throw new MalformedStreamException(
+ "Header section has more than " + HEADER_PART_SIZE_MAX
+ + " bytes (maybe it is not properly terminated)");
}
- else
- {
+ if (b == HEADER_SEPARATOR[i]) {
+ i++;
+ } else {
i = 0;
}
- if (size <= sizeMax)
- {
- baos.write(b[0]);
- }
+ baos.write(b);
}
String headers = null;
- if (headerEncoding != null)
- {
- try
- {
+ if (headerEncoding != null) {
+ try {
headers = baos.toString(headerEncoding);
- }
- catch (UnsupportedEncodingException e)
- {
+ } catch (UnsupportedEncodingException e) {
// Fall back to platform default if specified encoding is not
// supported.
headers = baos.toString();
}
- }
- else
- {
+ } else {
headers = baos.toString();
}
@@ -477,81 +573,31 @@ public class MultipartStream
*
* Stream to write data into.
+ * @param output The Stream to write data into. May
+ * be null, in which case this method is equivalent
+ * to {@link #discardBodyData()}.
*
* @return the amount of data written.
*
- * @exception MalformedStreamException if the stream ends unexpectedly.
- * @exception IOException if an i/o error occurs.
+ * @throws MalformedStreamException if the stream ends unexpectedly.
+ * @throws 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;
+ throws MalformedStreamException, IOException {
+ final InputStream istream = newInputStream();
+ return (int) Streams.copy(istream, output, false);
}
+ /**
+ * Creates a new {@link ItemInputStream}.
+ * @return A new instance of {@link ItemInputStream}.
+ */
+ ItemInputStream newInputStream() {
+ return new ItemInputStream();
+ }
/**
* body-data from the current
@@ -562,67 +608,13 @@ public class MultipartStream
*
* @return The amount of data discarded.
*
- * @exception MalformedStreamException if the stream ends unexpectedly.
- * @exception IOException if an i/o error occurs.
+ * @throws MalformedStreamException if the stream ends unexpectedly.
+ * @throws 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;
+ throws MalformedStreamException,
+ IOException {
+ return readBodyData(null);
}
@@ -632,34 +624,28 @@ public class MultipartStream
* @return true if an encapsulation was found in
* the stream.
*
- * @exception IOException if an i/o error occurs.
+ * @throws IOException if an i/o error occurs.
*/
public boolean skipPreamble()
- throws IOException
- {
+ 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
- {
+ try {
// Discard all data up to the delimiter.
discardBodyData();
// Read boundary - if succeded, the stream contains an
// encapsulation.
return readBoundary();
- }
- catch (MalformedStreamException e)
- {
+ } catch (MalformedStreamException e) {
return false;
- }
- finally
- {
+ } finally {
// Restore delimiter.
System.arraycopy(boundary, 0, boundary, 2, boundary.length - 2);
boundaryLength = boundary.length;
- boundary[0] = 0x0D;
- boundary[1] = 0x0A;
+ boundary[0] = CR;
+ boundary[1] = LF;
}
}
@@ -676,13 +662,10 @@ public class MultipartStream
* 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])
- {
+ byte[] b,
+ int count) {
+ for (int i = 0; i < count; i++) {
+ if (a[i] != b[i]) {
return false;
}
}
@@ -701,12 +684,9 @@ public class MultipartStream
* buffer, or -1 if not found.
*/
protected int findByte(byte value,
- int pos)
- {
- for (int i = pos; i < tail; i++)
- {
- if (buffer[i] == value)
- {
+ int pos) {
+ for (int i = pos; i < tail; i++) {
+ if (buffer[i] == value) {
return i;
}
}
@@ -723,64 +703,40 @@ public class MultipartStream
* beginning of the buffer, or -1 if
* not found.
*/
- protected int findSeparator()
- {
+ protected int findSeparator() {
int first;
int match = 0;
int maxpos = tail - boundaryLength;
for (first = head;
- (first <= maxpos) && (match != boundaryLength);
- first++)
- {
+ (first <= maxpos) && (match != boundaryLength);
+ first++) {
first = findByte(boundary[0], first);
- if (first == -1 || (first > maxpos))
- {
+ if (first == -1 || (first > maxpos)) {
return -1;
}
- for (match = 1; match < boundaryLength; match++)
- {
- if (buffer[first + match] != boundary[match])
- {
+ for (match = 1; match < boundaryLength; match++) {
+ if (buffer[first + match] != boundary[match]) {
break;
}
}
}
- if (match == boundaryLength)
- {
+ if (match == boundaryLength) {
return first - 1;
}
return -1;
}
/**
- * Returns a string representation of this object.
- *
- * @return The string representation of this object.
- */
- @Override
- public String toString()
- {
- StringBuilder sbTemp = new StringBuilder();
- 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
- {
+ public static class MalformedStreamException
+ extends IOException {
/**
* Constructs a MalformedStreamException with no
* detail message.
*/
- public MalformedStreamException()
- {
+ public MalformedStreamException() {
super();
}
@@ -790,8 +746,7 @@ public class MultipartStream
*
* @param message The detail message.
*/
- public MalformedStreamException(String message)
- {
+ public MalformedStreamException(String message) {
super(message);
}
}
@@ -800,15 +755,13 @@ public class MultipartStream
/**
* Thrown upon attempt of setting an invalid boundary token.
*/
- public class IllegalBoundaryException
- extends IOException
- {
+ public static class IllegalBoundaryException
+ extends IOException {
/**
* Constructs an IllegalBoundaryException with no
* detail message.
*/
- public IllegalBoundaryException()
- {
+ public IllegalBoundaryException() {
super();
}
@@ -818,12 +771,237 @@ public class MultipartStream
*
* @param message The detail message.
*/
- public IllegalBoundaryException(String message)
- {
+ public IllegalBoundaryException(String message) {
super(message);
}
}
+ /**
+ * An {@link InputStream} for reading an items contents.
+ */
+ public class ItemInputStream extends InputStream implements Closeable {
+ /** The number of bytes, which have been read so far.
+ */
+ private long total;
+ /** The number of bytes, which must be hold, because
+ * they might be a part of the boundary.
+ */
+ private int pad;
+ /** The current offset in the buffer.
+ */
+ private int pos;
+ /** Whether the stream is already closed.
+ */
+ private boolean closed;
+
+ /**
+ * Creates a new instance.
+ */
+ ItemInputStream() {
+ findSeparator();
+ }
+
+ /**
+ * Called for finding the separator.
+ */
+ private void findSeparator() {
+ pos = MultipartStream.this.findSeparator();
+ if (pos == -1) {
+ if (tail - head > keepRegion) {
+ pad = keepRegion;
+ } else {
+ pad = tail - head;
+ }
+ }
+ }
+
+ /**
+ * Returns the number of bytes, which have been read
+ * by the stream.
+ * @return Number of bytes, which have been read so far.
+ */
+ public long getBytesRead() {
+ return total;
+ }
+
+ /**
+ * Returns the number of bytes, which are currently
+ * available, without blocking.
+ * @throws IOException An I/O error occurs.
+ * @return Number of bytes in the buffer.
+ */
+ public int available() throws IOException {
+ if (pos == -1) {
+ return tail - head - pad;
+ }
+ return pos - head;
+ }
+
+ /** Offset when converting negative bytes to integers.
+ */
+ private static final int BYTE_POSITIVE_OFFSET = 256;
+
+ /**
+ * Returns the next byte in the stream.
+ * @return The next byte in the stream, as a non-negative
+ * integer, or -1 for EOF.
+ * @throws IOException An I/O error occurred.
+ */
+ public int read() throws IOException {
+ if (closed) {
+ throw new FileItemStream.ItemSkippedException();
+ }
+ if (available() == 0) {
+ if (makeAvailable() == 0) {
+ return -1;
+ }
+ }
+ ++total;
+ int b = buffer[head++];
+ if (b >= 0) {
+ return b;
+ }
+ return b + BYTE_POSITIVE_OFFSET;
+ }
+
+ /**
+ * Reads bytes into the given buffer.
+ * @param b The destination buffer, where to write to.
+ * @param off Offset of the first byte in the buffer.
+ * @param len Maximum number of bytes to read.
+ * @return Number of bytes, which have been actually read,
+ * or -1 for EOF.
+ * @throws IOException An I/O error occurred.
+ */
+ public int read(byte[] b, int off, int len) throws IOException {
+ if (closed) {
+ throw new FileItemStream.ItemSkippedException();
+ }
+ if (len == 0) {
+ return 0;
+ }
+ int res = available();
+ if (res == 0) {
+ res = makeAvailable();
+ if (res == 0) {
+ return -1;
+ }
+ }
+ res = Math.min(res, len);
+ System.arraycopy(buffer, head, b, off, res);
+ head += res;
+ total += res;
+ return res;
+ }
+
+ /**
+ * Closes the input stream.
+ * @throws IOException An I/O error occurred.
+ */
+ public void close() throws IOException {
+ close(false);
+ }
+
+ /**
+ * Closes the input stream.
+ * @param pCloseUnderlying Whether to close the underlying stream
+ * (hard close)
+ * @throws IOException An I/O error occurred.
+ */
+ public void close(boolean pCloseUnderlying) throws IOException {
+ if (closed) {
+ return;
+ }
+ if (pCloseUnderlying) {
+ closed = true;
+ input.close();
+ } else {
+ for (;;) {
+ int av = available();
+ if (av == 0) {
+ av = makeAvailable();
+ if (av == 0) {
+ break;
+ }
+ }
+ skip(av);
+ }
+ }
+ closed = true;
+ }
+
+ /**
+ * Skips the given number of bytes.
+ * @param bytes Number of bytes to skip.
+ * @return The number of bytes, which have actually been
+ * skipped.
+ * @throws IOException An I/O error occurred.
+ */
+ public long skip(long bytes) throws IOException {
+ if (closed) {
+ throw new FileItemStream.ItemSkippedException();
+ }
+ int av = available();
+ if (av == 0) {
+ av = makeAvailable();
+ if (av == 0) {
+ return 0;
+ }
+ }
+ long res = Math.min(av, bytes);
+ head += res;
+ return res;
+ }
+
+ /**
+ * Attempts to read more data.
+ * @return Number of available bytes
+ * @throws IOException An I/O error occurred.
+ */
+ private int makeAvailable() throws IOException {
+ if (pos != -1) {
+ return 0;
+ }
+
+ // Move the data to the beginning of the buffer.
+ total += tail - head - pad;
+ System.arraycopy(buffer, tail - pad, buffer, 0, pad);
+
+ // Refill buffer with new data.
+ head = 0;
+ tail = pad;
+
+ for (;;) {
+ int bytesRead = input.read(buffer, tail, bufSize - tail);
+ if (bytesRead == -1) {
+ // The last pad amount is left in the buffer.
+ // Boundary can't be in there so signal an error
+ // condition.
+ final String msg = "Stream ended unexpectedly";
+ throw new MalformedStreamException(msg);
+ }
+ if (notifier != null) {
+ notifier.noteBytesRead(bytesRead);
+ }
+ tail += bytesRead;
+
+ findSeparator();
+ int av = available();
+
+ if (av > 0 || pos != -1) {
+ return av;
+ }
+ }
+ }
+
+ /**
+ * Returns, whether the stream is closed.
+ * @return True, if the stream is closed, otherwise false.
+ */
+ public boolean isClosed() {
+ return closed;
+ }
+ }
// ------------------------------------------------------ Debugging methods
@@ -861,7 +1039,7 @@ public class MultipartStream
// Main routine, for testing purposes only.
//
// @param args A String[] with the command line arguments.
- // @exception Exception, a generic exception.
+ // @throws Exception, a generic exception.
public static void main( String[] args )
throws Exception
{
@@ -889,5 +1067,5 @@ public class MultipartStream
}
}
- */
+ */
}
diff --git a/java/org/apache/tomcat/util/http/fileupload/ParameterParser.java b/java/org/apache/tomcat/util/http/fileupload/ParameterParser.java
new file mode 100644
index 000000000..dce150777
--- /dev/null
+++ b/java/org/apache/tomcat/util/http/fileupload/ParameterParser.java
@@ -0,0 +1,329 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.tomcat.util.http.fileupload;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * A simple parser intended to parse sequences of name/value pairs.
+ * Parameter values are exptected to be enclosed in quotes if they
+ * contain unsafe characters, such as '=' characters or separators.
+ * Parameter values are optional and can be omitted.
+ *
+ * param1 = value; param2 = "anything goes; really"; param3
+ * 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.true if the request is multipart;
+ * false otherwise.
+ */
+ public static final boolean isMultipartContent(
+ HttpServletRequest request) {
+ if (!"post".equals(request.getMethod().toLowerCase())) {
+ return false;
+ }
+ String contentType = request.getContentType();
+ if (contentType == null) {
+ return false;
+ }
+ if (contentType.toLowerCase().startsWith(MULTIPART)) {
+ return true;
+ }
+ return false;
+ }
+
+
+ // ----------------------------------------------------------- Constructors
+
+
+ /**
+ * Constructs an uninitialised instance of this class. A factory must be
+ * configured, using setFileItemFactory(), before attempting
+ * to parse requests.
+ *
+ * @see FileUpload#FileUpload(FileItemFactory)
+ */
+ public ServletFileUpload() {
+ super();
+ }
+
+
+ /**
+ * Constructs an instance of this class which uses the supplied factory to
+ * create FileItem instances.
+ *
+ * @see FileUpload#FileUpload()
+ * @param fileItemFactory The factory to use for creating file items.
+ */
+ public ServletFileUpload(FileItemFactory fileItemFactory) {
+ super(fileItemFactory);
+ }
+
+
+ // --------------------------------------------------------- Public methods
+
+
+ /**
+ * Processes an RFC 1867
+ * compliant multipart/form-data stream.
+ *
+ * @param request The servlet request to be parsed.
+ *
+ * @return A list of FileItem instances parsed from the
+ * request, in the order that they were transmitted.
+ *
+ * @throws FileUploadException if there are problems reading/parsing
+ * the request or storing files.
+ */
+ public List /* FileItem */ parseRequest(HttpServletRequest request)
+ throws FileUploadException {
+ return parseRequest(new ServletRequestContext(request));
+ }
+
+
+ /**
+ * Processes an RFC 1867
+ * compliant multipart/form-data stream.
+ *
+ * @param request The servlet request to be parsed.
+ *
+ * @return An iterator to instances of FileItemStream
+ * parsed from the request, in the order that they were
+ * transmitted.
+ *
+ * @throws FileUploadException if there are problems reading/parsing
+ * the request or storing files.
+ * @throws IOException An I/O error occurred. This may be a network
+ * error while communicating with the client or a problem while
+ * storing the uploaded content.
+ */
+ public FileItemIterator getItemIterator(HttpServletRequest request)
+ throws FileUploadException, IOException {
+ return super.getItemIterator(new ServletRequestContext(request));
+ }
+}
diff --git a/java/org/apache/tomcat/util/http/fileupload/ServletRequestContext.java b/java/org/apache/tomcat/util/http/fileupload/ServletRequestContext.java
new file mode 100644
index 000000000..7fdaedcc2
--- /dev/null
+++ b/java/org/apache/tomcat/util/http/fileupload/ServletRequestContext.java
@@ -0,0 +1,107 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.tomcat.util.http.fileupload;
+
+import java.io.InputStream;
+import java.io.IOException;
+import javax.servlet.http.HttpServletRequest;
+
+
+/**
+ *
+ * copy(pInputStream, pOutputStream, new byte[8192]);
+ *
+ * @param pInputStream The input stream, which is being read.
+ * It is guaranteed, that {@link InputStream#close()} is called
+ * on the stream.
+ * @param pOutputStream The output stream, to which data should
+ * be written. May be null, in which case the input streams
+ * contents are simply discarded.
+ * @param pClose True guarantees, that {@link OutputStream#close()}
+ * is called on the stream. False indicates, that only
+ * {@link OutputStream#flush()} should be called finally.
+ *
+ * @return Number of bytes, which have been copied.
+ * @throws IOException An I/O error occurred.
+ */
+ public static long copy(InputStream pInputStream,
+ OutputStream pOutputStream, boolean pClose)
+ throws IOException {
+ return copy(pInputStream, pOutputStream, pClose,
+ new byte[DEFAULT_BUFFER_SIZE]);
+ }
+
+ /**
+ * Copies the contents of the given {@link InputStream}
+ * to the given {@link OutputStream}.
+ * @param pIn The input stream, which is being read.
+ * It is guaranteed, that {@link InputStream#close()} is called
+ * on the stream.
+ * @param pOut The output stream, to which data should
+ * be written. May be null, in which case the input streams
+ * contents are simply discarded.
+ * @param pClose True guarantees, that {@link OutputStream#close()}
+ * is called on the stream. False indicates, that only
+ * {@link OutputStream#flush()} should be called finally.
+ * @param pBuffer Temporary buffer, which is to be used for
+ * copying data.
+ * @return Number of bytes, which have been copied.
+ * @throws IOException An I/O error occurred.
+ */
+ public static long copy(InputStream pIn,
+ OutputStream pOut, boolean pClose,
+ byte[] pBuffer)
+ throws IOException {
+ OutputStream out = pOut;
+ InputStream in = pIn;
+ try {
+ long total = 0;
+ for (;;) {
+ int res = in.read(pBuffer);
+ if (res == -1) {
+ break;
+ }
+ if (res > 0) {
+ total += res;
+ if (out != null) {
+ out.write(pBuffer, 0, res);
+ }
+ }
+ }
+ if (out != null) {
+ if (pClose) {
+ out.close();
+ } else {
+ out.flush();
+ }
+ out = null;
+ }
+ in.close();
+ in = null;
+ return total;
+ } finally {
+ if (in != null) {
+ try {
+ in.close();
+ } catch (Throwable t) {
+ /* Ignore me */
+ }
+ }
+ if (pClose && out != null) {
+ try {
+ out.close();
+ } catch (Throwable t) {
+ /* Ignore me */
+ }
+ }
+ }
+ }
+
+ /**
+ * This convenience method allows to read a
+ * {@link org.apache.commons.fileupload.FileItemStream}'s
+ * content into a string. The platform's default character encoding
+ * is used for converting bytes into characters.
+ * @param pStream The input stream to read.
+ * @see #asString(InputStream, String)
+ * @return The streams contents, as a string.
+ * @throws IOException An I/O error occurred.
+ */
+ public static String asString(InputStream pStream) throws IOException {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ copy(pStream, baos, true);
+ return baos.toString();
+ }
+
+ /**
+ * This convenience method allows to read a
+ * {@link org.apache.commons.fileupload.FileItemStream}'s
+ * content into a string, using the given character encoding.
+ * @param pStream The input stream to read.
+ * @param pEncoding The character encoding, typically "UTF-8".
+ * @see #asString(InputStream)
+ * @return The streams contents, as a string.
+ * @throws IOException An I/O error occurred.
+ */
+ public static String asString(InputStream pStream, String pEncoding)
+ throws IOException {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ copy(pStream, baos, true);
+ return baos.toString(pEncoding);
+ }
+}
diff --git a/java/org/apache/tomcat/util/http/fileupload/ThresholdingOutputStream.java b/java/org/apache/tomcat/util/http/fileupload/ThresholdingOutputStream.java
index 54271fa91..142cb4012 100644
--- a/java/org/apache/tomcat/util/http/fileupload/ThresholdingOutputStream.java
+++ b/java/org/apache/tomcat/util/http/fileupload/ThresholdingOutputStream.java
@@ -14,8 +14,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
-
package org.apache.tomcat.util.http.fileupload;
import java.io.IOException;
@@ -90,7 +88,6 @@ public abstract class ThresholdingOutputStream
*
* @exception IOException if an error occurs.
*/
- @Override
public void write(int b) throws IOException
{
checkThreshold(1);
@@ -107,7 +104,6 @@ public abstract class ThresholdingOutputStream
*
* @exception IOException if an error occurs.
*/
- @Override
public void write(byte b[]) throws IOException
{
checkThreshold(b.length);
@@ -126,7 +122,6 @@ public abstract class ThresholdingOutputStream
*
* @exception IOException if an error occurs.
*/
- @Override
public void write(byte b[], int off, int len) throws IOException
{
checkThreshold(len);
@@ -141,7 +136,6 @@ public abstract class ThresholdingOutputStream
*
* @exception IOException if an error occurs.
*/
- @Override
public void flush() throws IOException
{
getStream().flush();
@@ -154,7 +148,6 @@ public abstract class ThresholdingOutputStream
*
* @exception IOException if an error occurs.
*/
- @Override
public void close() throws IOException
{
try
@@ -224,11 +217,20 @@ public abstract class ThresholdingOutputStream
{
if (!thresholdExceeded && (written + count > threshold))
{
- thresholdReached();
thresholdExceeded = true;
+ thresholdReached();
}
}
+ /**
+ * Resets the byteCount to zero. You can call this from
+ * {@link #thresholdReached()} if you want the event to be triggered again.
+ */
+ protected void resetByteCount()
+ {
+ this.thresholdExceeded = false;
+ this.written = 0;
+ }
// ------------------------------------------------------- Abstract methods
diff --git a/java/org/apache/tomcat/util/http/fileupload/package.html b/java/org/apache/tomcat/util/http/fileupload/package.html
index 0aa24add9..88c2540d2 100644
--- a/java/org/apache/tomcat/util/http/fileupload/package.html
+++ b/java/org/apache/tomcat/util/http/fileupload/package.html
@@ -1,57 +1,65 @@
-
- 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.
-
-
- public void doPost(HttpServletRequest req, HttpServletResponse res)
- {
- DiskFileUpload fu = new DiskFileUpload();
- // maximum size before a FileUploadException will be thrown
- fu.setSizeMax(1000000);
+ public void doPost(HttpServletRequest req, HttpServletResponse res) {
+ DiskFileItemFactory factory = new DiskFileItemFactory();
// maximum size that will be stored in memory
- fu.setSizeThreshold(4096);
+ factory.setSizeThreshold(4096);
// the location for saving data that is larger than getSizeThreshold()
- fu.setRepositoryPath("/tmp");
+ factory.setRepository(new File("/tmp"));
+
+ ServletFileUpload upload = new ServletFileUpload(factory);
+ // maximum size before a FileUploadException will be thrown
+ upload.setSizeMax(1000000);
- List fileItems = fu.parseRequest(req);
+ List fileItems = upload.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
@@ -63,20 +71,24 @@
// save comment and filename to database
...
// write the file
- fi.write("/www/uploads/" + fileName);
+ fi.write(new File("/www/uploads/", fileName));
}
- 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.
- 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.
+