Update re-packaged commons-fileupload to 1.2.1
authormarkt <markt@13f79535-47bb-0310-9956-ffa450edef68>
Tue, 17 Nov 2009 20:30:39 +0000 (20:30 +0000)
committermarkt <markt@13f79535-47bb-0310-9956-ffa450edef68>
Tue, 17 Nov 2009 20:30:39 +0000 (20:30 +0000)
Done using svn copy from tags in commons so merging future updates should be easier
Changes made/required:
 - All classes in a single renamed package
 - Removed unused Util methods (so even more classes were not required)
 - Removed some deprecated classes
There is almost certainly more to be done in terms of pruning to the minimum required. Will so that once Servlet 3 file upload has been implemented.

git-svn-id: https://svn.apache.org/repos/asf/tomcat/trunk@881493 13f79535-47bb-0310-9956-ffa450edef68

34 files changed:
java/org/apache/catalina/manager/HTMLManagerServlet.java
java/org/apache/tomcat/util/http/fileupload/ByteArrayOutputStream.java [new file with mode: 0644]
java/org/apache/tomcat/util/http/fileupload/Closeable.java [new file with mode: 0644]
java/org/apache/tomcat/util/http/fileupload/DefaultFileItem.java [deleted file]
java/org/apache/tomcat/util/http/fileupload/DefaultFileItemFactory.java [deleted file]
java/org/apache/tomcat/util/http/fileupload/DeferredFileOutputStream.java
java/org/apache/tomcat/util/http/fileupload/DiskFileItem.java [new file with mode: 0644]
java/org/apache/tomcat/util/http/fileupload/DiskFileItemFactory.java [new file with mode: 0644]
java/org/apache/tomcat/util/http/fileupload/DiskFileUpload.java [deleted file]
java/org/apache/tomcat/util/http/fileupload/FileCleanerCleanup.java [new file with mode: 0644]
java/org/apache/tomcat/util/http/fileupload/FileCleaningTracker.java [new file with mode: 0644]
java/org/apache/tomcat/util/http/fileupload/FileDeleteStrategy.java [new file with mode: 0644]
java/org/apache/tomcat/util/http/fileupload/FileItem.java
java/org/apache/tomcat/util/http/fileupload/FileItemFactory.java
java/org/apache/tomcat/util/http/fileupload/FileItemHeaders.java [new file with mode: 0644]
java/org/apache/tomcat/util/http/fileupload/FileItemHeadersImpl.java [new file with mode: 0644]
java/org/apache/tomcat/util/http/fileupload/FileItemHeadersSupport.java [new file with mode: 0644]
java/org/apache/tomcat/util/http/fileupload/FileItemIterator.java [new file with mode: 0644]
java/org/apache/tomcat/util/http/fileupload/FileItemStream.java [new file with mode: 0644]
java/org/apache/tomcat/util/http/fileupload/FileUpload.java
java/org/apache/tomcat/util/http/fileupload/FileUploadBase.java
java/org/apache/tomcat/util/http/fileupload/FileUploadException.java
java/org/apache/tomcat/util/http/fileupload/FileUtils.java [new file with mode: 0644]
java/org/apache/tomcat/util/http/fileupload/IOUtils.java [new file with mode: 0644]
java/org/apache/tomcat/util/http/fileupload/LimitedInputStream.java [new file with mode: 0644]
java/org/apache/tomcat/util/http/fileupload/MultipartStream.java
java/org/apache/tomcat/util/http/fileupload/ParameterParser.java [new file with mode: 0644]
java/org/apache/tomcat/util/http/fileupload/ProgressListener.java [new file with mode: 0644]
java/org/apache/tomcat/util/http/fileupload/RequestContext.java [new file with mode: 0644]
java/org/apache/tomcat/util/http/fileupload/ServletFileUpload.java [new file with mode: 0644]
java/org/apache/tomcat/util/http/fileupload/ServletRequestContext.java [new file with mode: 0644]
java/org/apache/tomcat/util/http/fileupload/Streams.java [new file with mode: 0644]
java/org/apache/tomcat/util/http/fileupload/ThresholdingOutputStream.java
java/org/apache/tomcat/util/http/fileupload/package.html

index aa5576e..6054b62 100644 (file)
@@ -49,7 +49,8 @@ import org.apache.catalina.manager.util.SessionUtils;
 import org.apache.catalina.util.RequestUtil;
 import org.apache.catalina.util.ServerInfo;
 import org.apache.catalina.util.URLEncoder;
-import org.apache.tomcat.util.http.fileupload.DiskFileUpload;
+import org.apache.tomcat.util.http.fileupload.DiskFileItemFactory;
+import org.apache.tomcat.util.http.fileupload.ServletFileUpload;
 import org.apache.tomcat.util.http.fileupload.FileItem;
 
 /**
@@ -260,15 +261,18 @@ public final class HTMLManagerServlet extends ManagerServlet {
     protected String upload(HttpServletRequest request) throws IOException {
         String message = "";
 
-        // Create a new file upload handler
-        DiskFileUpload upload = new DiskFileUpload();
-
         // Get the tempdir
         File tempdir = (File) getServletContext().getAttribute
             (ServletContext.TEMPDIR);
+
+        // Create a new file upload handler
+        DiskFileItemFactory factory = new DiskFileItemFactory();
+        factory.setRepository(tempdir.getCanonicalFile());
+        ServletFileUpload upload = new ServletFileUpload();
+        upload.setFileItemFactory(factory);
+        
         // Set upload parameters
         upload.setSizeMax(-1);
-        upload.setRepositoryPath(tempdir.getCanonicalPath());
     
         // Parse the request
         String basename = null;
diff --git a/java/org/apache/tomcat/util/http/fileupload/ByteArrayOutputStream.java b/java/org/apache/tomcat/util/http/fileupload/ByteArrayOutputStream.java
new file mode 100644 (file)
index 0000000..b652d9d
--- /dev/null
@@ -0,0 +1,308 @@
+/*
+ * 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;
+import java.io.UnsupportedEncodingException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This class implements an output stream in which the data is 
+ * written into a byte array. The buffer automatically grows as data 
+ * is written to it.
+ * <p> 
+ * The data can be retrieved using <code>toByteArray()</code> and
+ * <code>toString()</code>.
+ * <p>
+ * Closing a <tt>ByteArrayOutputStream</tt> has no effect. The methods in
+ * this class can be called after the stream has been closed without
+ * generating an <tt>IOException</tt>.
+ * <p>
+ * 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 <a href="mailto:jeremias@apache.org">Jeremias Maerki</a>
+ * @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 <code>byte[]</code> 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 <tt>ByteArrayOutputStream</tt> has no effect. The methods in
+     * this class can be called after the stream has been closed without
+     * generating an <tt>IOException</tt>.
+     *
+     * @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 (file)
index 0000000..04d877b
--- /dev/null
@@ -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/DefaultFileItem.java b/java/org/apache/tomcat/util/http/fileupload/DefaultFileItem.java
deleted file mode 100644 (file)
index 65218dd..0000000
+++ /dev/null
@@ -1,609 +0,0 @@
-/*
- * 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.BufferedInputStream;
-import java.io.BufferedOutputStream;
-import java.io.ByteArrayInputStream;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.io.UnsupportedEncodingException;
-
-
-/**
- * <p> The default implementation of the
- * {@link org.apache.tomcat.util.http.fileupload.FileItem FileItem} interface.
- *
- * <p> 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
- * #parseRequest(javax.servlet.http.HttpServletRequest)}), you may
- * either request all contents of file at once using {@link #get()} or
- * request an {@link java.io.InputStream InputStream} with
- * {@link #getInputStream()} and process the file without attempting to load
- * it into memory, which may come handy with large files.
- *
- * @author <a href="mailto:Rafal.Krzewski@e-point.pl">Rafal Krzewski</a>
- * @author <a href="mailto:sean@informage.net">Sean Legassick</a>
- * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
- * @author <a href="mailto:jmcnally@apache.org">John McNally</a>
- * @author <a href="mailto:martinc@apache.org">Martin Cooper</a>
- * @author Sean C. Sullivan
- *
- * @version $Id$
- */
-public class DefaultFileItem
-    implements FileItem
-{
-
-    // ----------------------------------------------------------- Data members
-
-
-    /**
-     * Counter used in unique identifier generation.
-     */
-    private static int counter = 0;
-
-
-    /**
-     * The name of the form field as provided by the browser.
-     */
-    private String fieldName;
-
-
-    /**
-     * The content type passed by the browser, or <code>null</code> if
-     * not defined.
-     */
-    private String contentType;
-
-
-    /**
-     * Whether or not this item is a simple form field.
-     */
-    private boolean isFormField;
-
-
-    /**
-     * The original filename in the user's filesystem.
-     */
-    private String fileName;
-
-
-    /**
-     * The threshold above which uploads will be stored on disk.
-     */
-    private int sizeThreshold;
-
-
-    /**
-     * The directory in which uploaded files will be stored, if stored on disk.
-     */
-    private File repository;
-
-
-    /**
-     * Cached contents of the file.
-     */
-    private byte[] cachedContent;
-
-
-    /**
-     * Output stream for this item.
-     */
-    private DeferredFileOutputStream dfos;
-
-
-    // ----------------------------------------------------------- Constructors
-
-
-    /**
-     * Constructs a new <code>DefaultFileItem</code> instance.
-     *
-     * @param fieldName     The name of the form field.
-     * @param contentType   The content type passed by the browser or
-     *                      <code>null</code> if not specified.
-     * @param isFormField   Whether or not this item is a plain form field, as
-     *                      opposed to a file upload.
-     * @param fileName      The original filename in the user's filesystem, or
-     *                      <code>null</code> if not specified.
-     * @param sizeThreshold The threshold, in bytes, below which items will be
-     *                      retained in memory and above which they will be
-     *                      stored as a file.
-     * @param repository    The data repository, which is the directory in
-     *                      which files will be created, should the item size
-     *                      exceed the threshold.
-     */
-    DefaultFileItem(String fieldName, String contentType, boolean isFormField,
-                    String fileName, int sizeThreshold, File repository)
-    {
-        this.fieldName = fieldName;
-        this.contentType = contentType;
-        this.isFormField = isFormField;
-        this.fileName = fileName;
-        this.sizeThreshold = sizeThreshold;
-        this.repository = repository;
-    }
-
-
-    // ------------------------------- Methods from javax.activation.DataSource
-
-
-    /**
-     * Returns an {@link java.io.InputStream InputStream} that can be
-     * used to retrieve the contents of the file.
-     *
-     * @return An {@link java.io.InputStream InputStream} that can be
-     *         used to retrieve the contents of the file.
-     *
-     * @exception IOException if an error occurs.
-     */
-    public InputStream getInputStream()
-        throws IOException
-    {
-        if (!dfos.isInMemory())
-        {
-            return new FileInputStream(dfos.getFile());
-        }
-
-        if (cachedContent == null)
-        {
-            cachedContent = dfos.getData();
-        }
-        return new ByteArrayInputStream(cachedContent);
-    }
-
-
-    /**
-     * Returns the content type passed by the browser or <code>null</code> if
-     * not defined.
-     *
-     * @return The content type passed by the browser or <code>null</code> if
-     *         not defined.
-     */
-    public String getContentType()
-    {
-        return contentType;
-    }
-
-
-    /**
-     * Returns the original filename in the client's filesystem.
-     *
-     * @return The original filename in the client's filesystem.
-     */
-    public String getName()
-    {
-        return fileName;
-    }
-
-
-    // ------------------------------------------------------- FileItem methods
-
-
-    /**
-     * Provides a hint as to whether or not the file contents will be read
-     * from memory.
-     *
-     * @return <code>true</code> if the file contents will be read
-     *         from memory; <code>false</code> otherwise.
-     */
-    public boolean isInMemory()
-    {
-        return (dfos.isInMemory());
-    }
-
-
-    /**
-     * Returns the size of the file.
-     *
-     * @return The size of the file, in bytes.
-     */
-    public long getSize()
-    {
-        if (cachedContent != null)
-        {
-            return cachedContent.length;
-        }
-        else if (dfos.isInMemory())
-        {
-            return dfos.getData().length;
-        }
-        else
-        {
-            return dfos.getFile().length();
-        }
-    }
-
-
-    /**
-     * Returns the contents of the file as an array of bytes.  If the
-     * contents of the file were not yet cached in memory, they will be
-     * loaded from the disk storage and cached.
-     *
-     * @return The contents of the file as an array of bytes.
-     */
-    public byte[] get()
-    {
-        if (dfos.isInMemory())
-        {
-            if (cachedContent == null)
-            {
-                cachedContent = dfos.getData();
-            }
-            return cachedContent;
-        }
-
-        byte[] fileData = new byte[(int) getSize()];
-        FileInputStream fis = null;
-
-        try
-        {
-            fis = new FileInputStream(dfos.getFile());
-            fis.read(fileData);
-        }
-        catch (IOException e)
-        {
-            fileData = null;
-        }
-        finally
-        {
-            if (fis != null)
-            {
-                try
-                {
-                    fis.close();
-                }
-                catch (IOException e)
-                {
-                    // ignore
-                }
-            }
-        }
-
-        return fileData;
-    }
-
-
-    /**
-     * Returns the contents of the file as a String, using the specified
-     * encoding.  This method uses {@link #get()} to retrieve the
-     * contents of the file.
-     *
-     * @param encoding The character encoding to use.
-     *
-     * @return The contents of the file, as a string.
-     *
-     * @exception UnsupportedEncodingException if the requested character
-     *                                         encoding is not available.
-     */
-    public String getString(String encoding)
-        throws UnsupportedEncodingException
-    {
-        return new String(get(), encoding);
-    }
-
-
-    /**
-     * Returns the contents of the file as a String, using the default
-     * character encoding.  This method uses {@link #get()} to retrieve the
-     * contents of the file.
-     *
-     * @return The contents of the file, as a string.
-     */
-    public String getString()
-    {
-        return new String(get());
-    }
-
-
-    /**
-     * A convenience method to write an uploaded item to disk. The client code
-     * is not concerned with whether or not the item is stored in memory, or on
-     * disk in a temporary location. They just want to write the uploaded item
-     * to a file.
-     * <p>
-     * This implementation first attempts to rename the uploaded item to the
-     * specified destination file, if the item was originally written to disk.
-     * Otherwise, the data will be copied to the specified file.
-     * <p>
-     * This method is only guaranteed to work <em>once</em>, the first time it
-     * is invoked for a particular item. This is because, in the event that the
-     * method renames a temporary file, that file will no longer be available
-     * to copy or rename again at a later time.
-     *
-     * @param file The <code>File</code> into which the uploaded item should
-     *             be stored.
-     *
-     * @exception Exception if an error occurs.
-     */
-    public void write(File file) throws Exception
-    {
-        if (isInMemory())
-        {
-            FileOutputStream fout = null;
-            try
-            {
-                fout = new FileOutputStream(file);
-                fout.write(get());
-            }
-            finally
-            {
-                if (fout != null)
-                {
-                    fout.close();
-                }
-            }
-        }
-        else
-        {
-            File outputFile = getStoreLocation();
-            if (outputFile != null)
-            {
-                /*
-                 * The uploaded file is being stored on disk
-                 * in a temporary location so move it to the
-                 * desired file.
-                 */
-                if (!outputFile.renameTo(file))
-                {
-                    BufferedInputStream in = null;
-                    BufferedOutputStream out = null;
-                    try
-                    {
-                        in = new BufferedInputStream(
-                            new FileInputStream(outputFile));
-                        out = new BufferedOutputStream(
-                                new FileOutputStream(file));
-                        byte[] bytes = new byte[2048];
-                        int s = 0;
-                        while ((s = in.read(bytes)) != -1)
-                        {
-                            out.write(bytes, 0, s);
-                        }
-                    }
-                    finally
-                    {
-                        try
-                        {
-                            in.close();
-                        }
-                        catch (IOException e)
-                        {
-                            // ignore
-                        }
-                        try
-                        {
-                            out.close();
-                        }
-                        catch (IOException e)
-                        {
-                            // ignore
-                        }
-                    }
-                }
-            }
-            else
-            {
-                /*
-                 * For whatever reason we cannot write the
-                 * file to disk.
-                 */
-                throw new FileUploadException(
-                    "Cannot write uploaded file to disk!");
-            }
-        }
-    }
-
-
-    /**
-     * Deletes the underlying storage for a file item, including deleting any
-     * associated temporary disk file. Although this storage will be deleted
-     * automatically when the <code>FileItem</code> instance is garbage
-     * collected, this method can be used to ensure that this is done at an
-     * earlier time, thus preserving system resources.
-     */
-    public void delete()
-    {
-        cachedContent = null;
-        File outputFile = getStoreLocation();
-        if (outputFile != null && outputFile.exists())
-        {
-            outputFile.delete();
-        }
-    }
-
-
-    /**
-     * Returns the name of the field in the multipart form corresponding to
-     * this file item.
-     *
-     * @return The name of the form field.
-     *
-     * @see #setFieldName(java.lang.String)
-     *
-     */
-    public String getFieldName()
-    {
-        return fieldName;
-    }
-
-
-    /**
-     * Sets the field name used to reference this file item.
-     *
-     * @param fieldName The name of the form field.
-     *
-     * @see #getFieldName()
-     *
-     */
-    public void setFieldName(String fieldName)
-    {
-        this.fieldName = fieldName;
-    }
-
-
-    /**
-     * Determines whether or not a <code>FileItem</code> instance represents
-     * a simple form field.
-     *
-     * @return <code>true</code> if the instance represents a simple form
-     *         field; <code>false</code> if it represents an uploaded file.
-     *
-     * @see #setFormField(boolean)
-     *
-     */
-    public boolean isFormField()
-    {
-        return isFormField;
-    }
-
-
-    /**
-     * Specifies whether or not a <code>FileItem</code> instance represents
-     * a simple form field.
-     *
-     * @param state <code>true</code> if the instance represents a simple form
-     *              field; <code>false</code> if it represents an uploaded file.
-     *
-     * @see #isFormField()
-     *
-     */
-    public void setFormField(boolean state)
-    {
-        isFormField = state;
-    }
-
-
-    /**
-     * Returns an {@link java.io.OutputStream OutputStream} that can
-     * be used for storing the contents of the file.
-     *
-     * @return An {@link java.io.OutputStream OutputStream} that can be used
-     *         for storing the contensts of the file.
-     *
-     * @exception IOException if an error occurs.
-     */
-    public OutputStream getOutputStream()
-        throws IOException
-    {
-        if (dfos == null)
-        {
-            File outputFile = getTempFile();
-            dfos = new DeferredFileOutputStream(sizeThreshold, outputFile);
-        }
-        return dfos;
-    }
-
-
-    // --------------------------------------------------------- Public methods
-
-
-    /**
-     * Returns the {@link java.io.File} object for the <code>FileItem</code>'s
-     * data's temporary location on the disk. Note that for
-     * <code>FileItem</code>s that have their data stored in memory,
-     * this method will return <code>null</code>. When handling large
-     * files, you can use {@link java.io.File#renameTo(java.io.File)} to
-     * move the file to new location without copying the data, if the
-     * source and destination locations reside within the same logical
-     * volume.
-     *
-     * @return The data file, or <code>null</code> if the data is stored in
-     *         memory.
-     */
-    public File getStoreLocation()
-    {
-        return dfos.getFile();
-    }
-
-
-    // ------------------------------------------------------ Protected methods
-
-
-    /**
-     * Removes the file contents from the temporary storage.
-     */
-    @Override
-    protected void finalize()
-    {
-        File outputFile = dfos.getFile();
-
-        if (outputFile != null && outputFile.exists())
-        {
-            outputFile.delete();
-        }
-    }
-
-
-    /**
-     * Creates and returns a {@link java.io.File File} representing a uniquely
-     * named temporary file in the configured repository path.
-     *
-     * @return The {@link java.io.File File} to be used for temporary storage.
-     */
-    protected File getTempFile()
-    {
-        File tempDir = repository;
-        if (tempDir == null)
-        {
-            tempDir = new File(System.getProperty("java.io.tmpdir"));
-        }
-
-        String fileName = "upload_" + getUniqueId() + ".tmp";
-
-        File f = new File(tempDir, fileName);
-        f.deleteOnExit();
-        return f;
-    }
-
-
-    // -------------------------------------------------------- Private methods
-
-
-    /**
-     * Returns an identifier that is unique within the class loader used to
-     * load this class, but does not have random-like apearance.
-     *
-     * @return A String with the non-random looking instance identifier.
-     */
-    private static String getUniqueId()
-    {
-        int current;
-        synchronized (DefaultFileItem.class)
-        {
-            current = counter++;
-        }
-        String id = Integer.toString(current);
-
-        // If you manage to get more than 100 million of ids, you'll
-        // start getting ids longer than 8 characters.
-        if (current < 100000000)
-        {
-            id = ("00000000" + id).substring(id.length());
-        }
-        return id;
-    }
-
-}
diff --git a/java/org/apache/tomcat/util/http/fileupload/DefaultFileItemFactory.java b/java/org/apache/tomcat/util/http/fileupload/DefaultFileItemFactory.java
deleted file mode 100644 (file)
index 2d29b38..0000000
+++ /dev/null
@@ -1,190 +0,0 @@
-/*
- * 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;
-
-
-/**
- * <p>The default {@link org.apache.tomcat.util.http.fileupload.FileItemFactory}
- * implementation. This implementation creates
- * {@link org.apache.tomcat.util.http.fileupload.FileItem} instances which keep their
- * content either in memory, for smaller items, or in a temporary file on disk,
- * for larger items. The size threshold, above which content will be stored on
- * disk, is configurable, as is the directory in which temporary files will be
- * created.</p>
- *
- * <p>If not otherwise configured, the default configuration values are as
- * follows:
- * <ul>
- *   <li>Size threshold is 10KB.</li>
- *   <li>Repository is the system default temp directory, as returned by
- *       <code>System.getProperty("java.io.tmpdir")</code>.</li>
- * </ul>
- * </p>
- *
- * @author <a href="mailto:martinc@apache.org">Martin Cooper</a>
- *
- * @version $Id$
- */
-public class DefaultFileItemFactory implements FileItemFactory
-{
-
-    // ----------------------------------------------------- Manifest constants
-
-
-    /**
-     * The default threshold above which uploads will be stored on disk.
-     */
-    public static final int DEFAULT_SIZE_THRESHOLD = 10240;
-
-
-    // ----------------------------------------------------- Instance Variables
-
-
-    /**
-     * The directory in which uploaded files will be stored, if stored on disk.
-     */
-    private File repository;
-
-
-    /**
-     * The threshold above which uploads will be stored on disk.
-     */
-    private int sizeThreshold = DEFAULT_SIZE_THRESHOLD;
-
-
-    // ----------------------------------------------------------- Constructors
-
-
-    /**
-     * Constructs an unconfigured instance of this class. The resulting factory
-     * may be configured by calling the appropriate setter methods.
-     */
-    public DefaultFileItemFactory()
-    {
-    }
-
-
-    /**
-     * Constructs a preconfigured instance of this class.
-     *
-     * @param sizeThreshold The threshold, in bytes, below which items will be
-     *                      retained in memory and above which they will be
-     *                      stored as a file.
-     * @param repository    The data repository, which is the directory in
-     *                      which files will be created, should the item size
-     *                      exceed the threshold.
-     */
-    public DefaultFileItemFactory(int sizeThreshold, File repository)
-    {
-        this.sizeThreshold = sizeThreshold;
-        this.repository = repository;
-    }
-
-
-    // ------------------------------------------------------------- Properties
-
-
-    /**
-     * Returns the directory used to temporarily store files that are larger
-     * than the configured size threshold.
-     *
-     * @return The directory in which temporary files will be located.
-     *
-     * @see #setRepository(java.io.File)
-     *
-     */
-    public File getRepository()
-    {
-        return repository;
-    }
-
-
-    /**
-     * Sets the directory used to temporarily store files that are larger
-     * than the configured size threshold.
-     *
-     * @param repository The directory in which temporary files will be located.
-     *
-     * @see #getRepository()
-     *
-     */
-    public void setRepository(File repository)
-    {
-        this.repository = repository;
-    }
-
-
-    /**
-     * Returns the size threshold beyond which files are written directly to
-     * disk. The default value is 1024 bytes.
-     *
-     * @return The size threshold, in bytes.
-     *
-     * @see #setSizeThreshold(int)
-     */
-    public int getSizeThreshold()
-    {
-        return sizeThreshold;
-    }
-
-
-    /**
-     * Sets the size threshold beyond which files are written directly to disk.
-     *
-     * @param sizeThreshold The size threshold, in bytes.
-     *
-     * @see #getSizeThreshold()
-     *
-     */
-    public void setSizeThreshold(int sizeThreshold)
-    {
-        this.sizeThreshold = sizeThreshold;
-    }
-
-
-    // --------------------------------------------------------- Public Methods
-
-    /**
-     * Create a new {@link org.apache.tomcat.util.http.fileupload.DefaultFileItem}
-     * instance from the supplied parameters and the local factory
-     * configuration.
-     *
-     * @param fieldName   The name of the form field.
-     * @param contentType The content type of the form field.
-     * @param isFormField <code>true</code> if this is a plain form field;
-     *                    <code>false</code> otherwise.
-     * @param fileName    The name of the uploaded file, if any, as supplied
-     *                    by the browser or other client.
-     *
-     * @return The newly created file item.
-     */
-    public FileItem createItem(
-            String fieldName,
-            String contentType,
-            boolean isFormField,
-            String fileName
-            )
-    {
-        return new DefaultFileItem(fieldName, contentType,
-                isFormField, fileName, sizeThreshold, repository);
-    }
-
-}
index 8c1e4c0..bb21d4e 100644 (file)
  * 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;
 
+
 /**
- * <p>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.</p>
+ * disk at all.
+ * <p>
+ * 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 <a href="mailto:martinc@apache.org">Martin Cooper</a>
+ * @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 <code>File</code>, assuming
-     * that the data was written to disk. If the data was retained in memory,
-     * this method returns <code>null</code>.
+     * Returns either the output file specified in the constructor or
+     * the temporary file created or null.
+     * <p>
+     * If the constructor specifying the file is used then it returns that
+     * same output file, even when threashold has not been reached.
+     * <p>
+     * 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 <code>null</code> is returned.
      *
      * @return The file for this output stream, or <code>null</code> 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/DiskFileItem.java b/java/org/apache/tomcat/util/http/fileupload/DiskFileItem.java
new file mode 100644 (file)
index 0000000..719ae93
--- /dev/null
@@ -0,0 +1,726 @@
+/*
+ * 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.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+import java.util.Map;
+
+
+/**
+ * <p> The default implementation of the
+ * {@link org.apache.commons.fileupload.FileItem FileItem} interface.
+ *
+ * <p> After retrieving an instance of this class from a {@link
+ * org.apache.commons.fileupload.DiskFileUpload DiskFileUpload} instance (see
+ * {@link org.apache.commons.fileupload.DiskFileUpload
+ * #parseRequest(javax.servlet.http.HttpServletRequest)}), you may
+ * either request all contents of file at once using {@link #get()} or
+ * request an {@link java.io.InputStream InputStream} with
+ * {@link #getInputStream()} and process the file without attempting to load
+ * it into memory, which may come handy with large files.
+ *
+ * <p>When using the <code>DiskFileItemFactory</code>, 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.</p>
+ *
+ * @author <a href="mailto:Rafal.Krzewski@e-point.pl">Rafal Krzewski</a>
+ * @author <a href="mailto:sean@informage.net">Sean Legassick</a>
+ * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
+ * @author <a href="mailto:jmcnally@apache.org">John McNally</a>
+ * @author <a href="mailto:martinc@apache.org">Martin Cooper</a>
+ * @author Sean C. Sullivan
+ *
+ * @since FileUpload 1.1
+ *
+ * @version $Id$
+ */
+public class DiskFileItem
+    implements FileItem, FileItemHeadersSupport {
+
+    // ----------------------------------------------------- Manifest constants
+
+    /**
+     * The UID to use when serializing this instance.
+     */
+    private static final long serialVersionUID = 2237570099615271025L;
+
+
+    /**
+     * Default content charset to be used when no explicit charset
+     * parameter is provided by the sender. Media subtypes of the
+     * "text" type are defined to have a default charset value of
+     * "ISO-8859-1" when received via HTTP.
+     */
+    public static final String DEFAULT_CHARSET = "ISO-8859-1";
+
+
+    // ----------------------------------------------------------- Data members
+
+
+    /**
+     * UID used in unique file name generation.
+     */
+    private static final String UID =
+            new java.rmi.server.UID().toString()
+                .replace(':', '_').replace('-', '_');
+
+    /**
+     * Counter used in unique identifier generation.
+     */
+    private static int counter = 0;
+
+
+    /**
+     * The name of the form field as provided by the browser.
+     */
+    private String fieldName;
+
+
+    /**
+     * The content type passed by the browser, or <code>null</code> if
+     * not defined.
+     */
+    private String contentType;
+
+
+    /**
+     * Whether or not this item is a simple form field.
+     */
+    private boolean isFormField;
+
+
+    /**
+     * The original filename in the user's filesystem.
+     */
+    private String fileName;
+
+
+    /**
+     * The size of the item, in bytes. This is used to cache the size when a
+     * file item is moved from its original location.
+     */
+    private long size = -1;
+
+
+    /**
+     * The threshold above which uploads will be stored on disk.
+     */
+    private int sizeThreshold;
+
+
+    /**
+     * The directory in which uploaded files will be stored, if stored on disk.
+     */
+    private File repository;
+
+
+    /**
+     * Cached contents of the file.
+     */
+    private byte[] cachedContent;
+
+
+    /**
+     * Output stream for this item.
+     */
+    private transient DeferredFileOutputStream dfos;
+
+    /**
+     * The temporary file to use.
+     */
+    private transient File tempFile;
+
+    /**
+     * File to allow for serialization of the content of this item.
+     */
+    private File dfosFile;
+
+    /**
+     * The file items headers.
+     */
+    private FileItemHeaders headers;
+
+    // ----------------------------------------------------------- Constructors
+
+
+    /**
+     * Constructs a new <code>DiskFileItem</code> instance.
+     *
+     * @param fieldName     The name of the form field.
+     * @param contentType   The content type passed by the browser or
+     *                      <code>null</code> if not specified.
+     * @param isFormField   Whether or not this item is a plain form field, as
+     *                      opposed to a file upload.
+     * @param fileName      The original filename in the user's filesystem, or
+     *                      <code>null</code> if not specified.
+     * @param sizeThreshold The threshold, in bytes, below which items will be
+     *                      retained in memory and above which they will be
+     *                      stored as a file.
+     * @param repository    The data repository, which is the directory in
+     *                      which files will be created, should the item size
+     *                      exceed the threshold.
+     */
+    public DiskFileItem(String fieldName,
+            String contentType, boolean isFormField, String fileName,
+            int sizeThreshold, File repository) {
+        this.fieldName = fieldName;
+        this.contentType = contentType;
+        this.isFormField = isFormField;
+        this.fileName = fileName;
+        this.sizeThreshold = sizeThreshold;
+        this.repository = repository;
+    }
+
+
+    // ------------------------------- Methods from javax.activation.DataSource
+
+
+    /**
+     * Returns an {@link java.io.InputStream InputStream} that can be
+     * used to retrieve the contents of the file.
+     *
+     * @return An {@link java.io.InputStream InputStream} that can be
+     *         used to retrieve the contents of the file.
+     *
+     * @throws IOException if an error occurs.
+     */
+    public InputStream getInputStream()
+        throws IOException {
+        if (!isInMemory()) {
+            return new FileInputStream(dfos.getFile());
+        }
+
+        if (cachedContent == null) {
+            cachedContent = dfos.getData();
+        }
+        return new ByteArrayInputStream(cachedContent);
+    }
+
+
+    /**
+     * Returns the content type passed by the agent or <code>null</code> if
+     * not defined.
+     *
+     * @return The content type passed by the agent or <code>null</code> if
+     *         not defined.
+     */
+    public String getContentType() {
+        return contentType;
+    }
+
+
+    /**
+     * Returns the content charset passed by the agent or <code>null</code> if
+     * not defined.
+     *
+     * @return The content charset passed by the agent or <code>null</code> 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() {
+        return fileName;
+    }
+
+
+    // ------------------------------------------------------- FileItem methods
+
+
+    /**
+     * Provides a hint as to whether or not the file contents will be read
+     * from memory.
+     *
+     * @return <code>true</code> if the file contents will be read
+     *         from memory; <code>false</code> otherwise.
+     */
+    public boolean isInMemory() {
+        if (cachedContent != null) {
+            return true;
+        }
+        return dfos.isInMemory();
+    }
+
+
+    /**
+     * Returns the size of the file.
+     *
+     * @return The size of the file, in bytes.
+     */
+    public long getSize() {
+        if (size >= 0) {
+            return size;
+        } else if (cachedContent != null) {
+            return cachedContent.length;
+        } else if (dfos.isInMemory()) {
+            return dfos.getData().length;
+        } else {
+            return dfos.getFile().length();
+        }
+    }
+
+
+    /**
+     * Returns the contents of the file as an array of bytes.  If the
+     * contents of the file were not yet cached in memory, they will be
+     * loaded from the disk storage and cached.
+     *
+     * @return The contents of the file as an array of bytes.
+     */
+    public byte[] get() {
+        if (isInMemory()) {
+            if (cachedContent == null) {
+                cachedContent = dfos.getData();
+            }
+            return cachedContent;
+        }
+
+        byte[] fileData = new byte[(int) getSize()];
+        FileInputStream fis = null;
+
+        try {
+            fis = new FileInputStream(dfos.getFile());
+            fis.read(fileData);
+        } catch (IOException e) {
+            fileData = null;
+        } finally {
+            if (fis != null) {
+                try {
+                    fis.close();
+                } catch (IOException e) {
+                    // ignore
+                }
+            }
+        }
+
+        return fileData;
+    }
+
+
+    /**
+     * Returns the contents of the file as a String, using the specified
+     * encoding.  This method uses {@link #get()} to retrieve the
+     * contents of the file.
+     *
+     * @param charset The charset to use.
+     *
+     * @return The contents of the file, as a string.
+     *
+     * @throws UnsupportedEncodingException if the requested character
+     *                                      encoding is not available.
+     */
+    public String getString(final String charset)
+        throws UnsupportedEncodingException {
+        return new String(get(), charset);
+    }
+
+
+    /**
+     * Returns the contents of the file as a String, using the default
+     * character encoding.  This method uses {@link #get()} to retrieve the
+     * contents of the file.
+     *
+     * @return The contents of the file, as a string.
+     *
+     * @todo Consider making this method throw UnsupportedEncodingException.
+     */
+    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);
+        }
+    }
+
+
+    /**
+     * A convenience method to write an uploaded item to disk. The client code
+     * is not concerned with whether or not the item is stored in memory, or on
+     * disk in a temporary location. They just want to write the uploaded item
+     * to a file.
+     * <p>
+     * This implementation first attempts to rename the uploaded item to the
+     * specified destination file, if the item was originally written to disk.
+     * Otherwise, the data will be copied to the specified file.
+     * <p>
+     * This method is only guaranteed to work <em>once</em>, the first time it
+     * is invoked for a particular item. This is because, in the event that the
+     * method renames a temporary file, that file will no longer be available
+     * to copy or rename again at a later time.
+     *
+     * @param file The <code>File</code> into which the uploaded item should
+     *             be stored.
+     *
+     * @throws Exception if an error occurs.
+     */
+    public void write(File file) throws Exception {
+        if (isInMemory()) {
+            FileOutputStream fout = null;
+            try {
+                fout = new FileOutputStream(file);
+                fout.write(get());
+            } finally {
+                if (fout != null) {
+                    fout.close();
+                }
+            }
+        } else {
+            File outputFile = getStoreLocation();
+            if (outputFile != null) {
+                // 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)) {
+                    BufferedInputStream in = null;
+                    BufferedOutputStream out = null;
+                    try {
+                        in = new BufferedInputStream(
+                            new FileInputStream(outputFile));
+                        out = new BufferedOutputStream(
+                                new FileOutputStream(file));
+                        IOUtils.copy(in, out);
+                    } finally {
+                        if (in != null) {
+                            try {
+                                in.close();
+                            } catch (IOException e) {
+                                // ignore
+                            }
+                        }
+                        if (out != null) {
+                            try {
+                                out.close();
+                            } catch (IOException e) {
+                                // ignore
+                            }
+                        }
+                    }
+                }
+            } else {
+                /*
+                 * For whatever reason we cannot write the
+                 * file to disk.
+                 */
+                throw new FileUploadException(
+                    "Cannot write uploaded file to disk!");
+            }
+        }
+    }
+
+
+    /**
+     * Deletes the underlying storage for a file item, including deleting any
+     * associated temporary disk file. Although this storage will be deleted
+     * automatically when the <code>FileItem</code> instance is garbage
+     * collected, this method can be used to ensure that this is done at an
+     * earlier time, thus preserving system resources.
+     */
+    public void delete() {
+        cachedContent = null;
+        File outputFile = getStoreLocation();
+        if (outputFile != null && outputFile.exists()) {
+            outputFile.delete();
+        }
+    }
+
+
+    /**
+     * Returns the name of the field in the multipart form corresponding to
+     * this file item.
+     *
+     * @return The name of the form field.
+     *
+     * @see #setFieldName(java.lang.String)
+     *
+     */
+    public String getFieldName() {
+        return fieldName;
+    }
+
+
+    /**
+     * Sets the field name used to reference this file item.
+     *
+     * @param fieldName The name of the form field.
+     *
+     * @see #getFieldName()
+     *
+     */
+    public void setFieldName(String fieldName) {
+        this.fieldName = fieldName;
+    }
+
+
+    /**
+     * Determines whether or not a <code>FileItem</code> instance represents
+     * a simple form field.
+     *
+     * @return <code>true</code> if the instance represents a simple form
+     *         field; <code>false</code> if it represents an uploaded file.
+     *
+     * @see #setFormField(boolean)
+     *
+     */
+    public boolean isFormField() {
+        return isFormField;
+    }
+
+
+    /**
+     * Specifies whether or not a <code>FileItem</code> instance represents
+     * a simple form field.
+     *
+     * @param state <code>true</code> if the instance represents a simple form
+     *              field; <code>false</code> if it represents an uploaded file.
+     *
+     * @see #isFormField()
+     *
+     */
+    public void setFormField(boolean state) {
+        isFormField = state;
+    }
+
+
+    /**
+     * Returns an {@link java.io.OutputStream OutputStream} that can
+     * be used for storing the contents of the file.
+     *
+     * @return An {@link java.io.OutputStream OutputStream} that can be used
+     *         for storing the contensts of the file.
+     *
+     * @throws IOException if an error occurs.
+     */
+    public OutputStream getOutputStream()
+        throws IOException {
+        if (dfos == null) {
+            File outputFile = getTempFile();
+            dfos = new DeferredFileOutputStream(sizeThreshold, outputFile);
+        }
+        return dfos;
+    }
+
+
+    // --------------------------------------------------------- Public methods
+
+
+    /**
+     * Returns the {@link java.io.File} object for the <code>FileItem</code>'s
+     * data's temporary location on the disk. Note that for
+     * <code>FileItem</code>s that have their data stored in memory,
+     * this method will return <code>null</code>. When handling large
+     * files, you can use {@link java.io.File#renameTo(java.io.File)} to
+     * move the file to new location without copying the data, if the
+     * source and destination locations reside within the same logical
+     * volume.
+     *
+     * @return The data file, or <code>null</code> if the data is stored in
+     *         memory.
+     */
+    public File getStoreLocation() {
+        return dfos == null ? null : dfos.getFile();
+    }
+
+
+    // ------------------------------------------------------ Protected methods
+
+
+    /**
+     * Removes the file contents from the temporary storage.
+     */
+    protected void finalize() {
+        File outputFile = dfos.getFile();
+
+        if (outputFile != null && outputFile.exists()) {
+            outputFile.delete();
+        }
+    }
+
+
+    /**
+     * Creates and returns a {@link java.io.File File} representing a uniquely
+     * named temporary file in the configured repository path. The lifetime of
+     * the file is tied to the lifetime of the <code>FileItem</code> 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() {
+        if (tempFile == null) {
+            File tempDir = repository;
+            if (tempDir == null) {
+                tempDir = new File(System.getProperty("java.io.tmpdir"));
+            }
+
+            String tempFileName =
+                "upload_" + UID + "_" + getUniqueId() + ".tmp";
+
+            tempFile = new File(tempDir, tempFileName);
+        }
+        return tempFile;
+    }
+
+
+    // -------------------------------------------------------- Private methods
+
+
+    /**
+     * Returns an identifier that is unique within the class loader used to
+     * load this class, but does not have random-like apearance.
+     *
+     * @return A String with the non-random looking instance identifier.
+     */
+    private static String getUniqueId() {
+        final int limit = 100000000;
+        int current;
+        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 < 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/DiskFileItemFactory.java b/java/org/apache/tomcat/util/http/fileupload/DiskFileItemFactory.java
new file mode 100644 (file)
index 0000000..1dc8f2b
--- /dev/null
@@ -0,0 +1,223 @@
+/*
+ * 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;
+
+
+/**
+ * <p>The default {@link org.apache.commons.fileupload.FileItemFactory}
+ * implementation. This implementation creates
+ * {@link org.apache.commons.fileupload.FileItem} instances which keep their
+ * content either in memory, for smaller items, or in a temporary file on disk,
+ * for larger items. The size threshold, above which content will be stored on
+ * disk, is configurable, as is the directory in which temporary files will be
+ * created.</p>
+ *
+ * <p>If not otherwise configured, the default configuration values are as
+ * follows:
+ * <ul>
+ *   <li>Size threshold is 10KB.</li>
+ *   <li>Repository is the system default temp directory, as returned by
+ *       <code>System.getProperty("java.io.tmpdir")</code>.</li>
+ * </ul>
+ * </p>
+ *
+ * <p>When using the <code>DiskFileItemFactory</code>, 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.</p>
+ *
+ * @author <a href="mailto:martinc@apache.org">Martin Cooper</a>
+ *
+ * @since FileUpload 1.1
+ *
+ * @version $Id$
+ */
+public class DiskFileItemFactory implements FileItemFactory {
+
+    // ----------------------------------------------------- Manifest constants
+
+
+    /**
+     * The default threshold above which uploads will be stored on disk.
+     */
+    public static final int DEFAULT_SIZE_THRESHOLD = 10240;
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * The directory in which uploaded files will be stored, if stored on disk.
+     */
+    private File repository;
+
+
+    /**
+     * The threshold above which uploads will be stored on disk.
+     */
+    private int sizeThreshold = DEFAULT_SIZE_THRESHOLD;
+
+
+    /**
+     * <p>The instance of {@link FileCleaningTracker}, which is responsible
+     * for deleting temporary files.</p>
+     * <p>May be null, if tracking files is not required.</p>
+     */
+    private FileCleaningTracker fileCleaningTracker;
+
+    // ----------------------------------------------------------- Constructors
+
+
+    /**
+     * Constructs an unconfigured instance of this class. The resulting factory
+     * may be configured by calling the appropriate setter methods.
+     */
+    public DiskFileItemFactory() {
+        this(DEFAULT_SIZE_THRESHOLD, null);
+    }
+
+
+    /**
+     * Constructs a preconfigured instance of this class.
+     *
+     * @param sizeThreshold The threshold, in bytes, below which items will be
+     *                      retained in memory and above which they will be
+     *                      stored as a file.
+     * @param repository    The data repository, which is the directory in
+     *                      which files will be created, should the item size
+     *                      exceed the threshold.
+     */
+    public DiskFileItemFactory(int sizeThreshold, File repository) {
+        this.sizeThreshold = sizeThreshold;
+        this.repository = repository;
+    }
+
+    // ------------------------------------------------------------- Properties
+
+
+    /**
+     * Returns the directory used to temporarily store files that are larger
+     * than the configured size threshold.
+     *
+     * @return The directory in which temporary files will be located.
+     *
+     * @see #setRepository(java.io.File)
+     *
+     */
+    public File getRepository() {
+        return repository;
+    }
+
+
+    /**
+     * Sets the directory used to temporarily store files that are larger
+     * than the configured size threshold.
+     *
+     * @param repository The directory in which temporary files will be located.
+     *
+     * @see #getRepository()
+     *
+     */
+    public void setRepository(File repository) {
+        this.repository = repository;
+    }
+
+
+    /**
+     * Returns the size threshold beyond which files are written directly to
+     * disk. The default value is 10240 bytes.
+     *
+     * @return The size threshold, in bytes.
+     *
+     * @see #setSizeThreshold(int)
+     */
+    public int getSizeThreshold() {
+        return sizeThreshold;
+    }
+
+
+    /**
+     * Sets the size threshold beyond which files are written directly to disk.
+     *
+     * @param sizeThreshold The size threshold, in bytes.
+     *
+     * @see #getSizeThreshold()
+     *
+     */
+    public void setSizeThreshold(int sizeThreshold) {
+        this.sizeThreshold = sizeThreshold;
+    }
+
+
+    // --------------------------------------------------------- Public Methods
+
+    /**
+     * Create a new {@link org.apache.commons.fileupload.disk.DiskFileItem}
+     * instance from the supplied parameters and the local factory
+     * configuration.
+     *
+     * @param fieldName   The name of the form field.
+     * @param contentType The content type of the form field.
+     * @param isFormField <code>true</code> if this is a plain form field;
+     *                    <code>false</code> otherwise.
+     * @param fileName    The name of the uploaded file, if any, as supplied
+     *                    by the browser or other client.
+     *
+     * @return The newly created file item.
+     */
+    public FileItem createItem(String fieldName, String contentType,
+            boolean isFormField, String fileName) {
+        DiskFileItem result = new DiskFileItem(fieldName, contentType,
+                isFormField, fileName, sizeThreshold, repository);
+        FileCleaningTracker tracker = getFileCleaningTracker();
+        if (tracker != null) {
+            tracker.track(result.getTempFile(), this);
+        }
+        return result;
+    }
+
+
+    /**
+     * Returns the tracker, which is responsible for deleting temporary
+     * files.
+     * @return An instance of {@link FileCleaningTracker}, defaults to
+     *   {@link org.apache.commons.io.FileCleaner#getInstance()}. Null,
+     *   if temporary files aren't tracked.
+     */
+    public FileCleaningTracker getFileCleaningTracker() {
+        return fileCleaningTracker;
+    }
+
+    /**
+     * Returns the tracker, which is responsible for deleting temporary
+     * files.
+     * @param pTracker An instance of {@link FileCleaningTracker},
+     *   which will from now on track the created files. May be null
+     *   to disable tracking.
+     */
+    public void setFileCleaningTracker(FileCleaningTracker pTracker) {
+        fileCleaningTracker = pTracker;
+    }
+}
diff --git a/java/org/apache/tomcat/util/http/fileupload/DiskFileUpload.java b/java/org/apache/tomcat/util/http/fileupload/DiskFileUpload.java
deleted file mode 100644 (file)
index a514e0f..0000000
+++ /dev/null
@@ -1,206 +0,0 @@
-/*
- * 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.util.List;
-import javax.servlet.http.HttpServletRequest;
-
-
-/**
- * <p>High level API for processing file uploads.</p>
- *
- * <p>This class handles multiple files per single HTML widget, sent using
- * <code>multipart/mixed</code> encoding type, as specified by
- * <a href="http://www.ietf.org/rfc/rfc1867.txt">RFC 1867</a>.  Use {@link
- * #parseRequest(HttpServletRequest)} to acquire a list of {@link
- * org.apache.tomcat.util.http.fileupload.FileItem}s associated with a given HTML
- * widget.</p>
- *
- * <p>Individual parts will be stored in temporary disk storage or in memory,
- * depending on their size, and will be available as {@link
- * org.apache.tomcat.util.http.fileupload.FileItem}s.</p>
- *
- * @author <a href="mailto:Rafal.Krzewski@e-point.pl">Rafal Krzewski</a>
- * @author <a href="mailto:dlr@collab.net">Daniel Rall</a>
- * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
- * @author <a href="mailto:jmcnally@collab.net">John McNally</a>
- * @author <a href="mailto:martinc@apache.org">Martin Cooper</a>
- * @author Sean C. Sullivan
- *
- * @version $Id$
- */
-public class DiskFileUpload
-    extends FileUploadBase
- {
-
-    // ----------------------------------------------------------- Data members
-
-
-    /**
-     * The factory to use to create new form items.
-     */
-    private DefaultFileItemFactory fileItemFactory;
-
-
-    // ----------------------------------------------------------- Constructors
-
-
-    /**
-     * Constructs an instance of this class which uses the default factory to
-     * create <code>FileItem</code> instances.
-     *
-     * @see #DiskFileUpload(DefaultFileItemFactory fileItemFactory)
-     */
-    public DiskFileUpload()
-    {
-        super();
-        this.fileItemFactory = new DefaultFileItemFactory();
-    }
-
-
-    /**
-     * Constructs an instance of this class which uses the supplied factory to
-     * create <code>FileItem</code> instances.
-     *
-     * @see #DiskFileUpload()
-     */
-    public DiskFileUpload(DefaultFileItemFactory fileItemFactory)
-    {
-        super();
-        this.fileItemFactory = fileItemFactory;
-    }
-
-
-    // ----------------------------------------------------- Property accessors
-
-
-    /**
-     * Returns the factory class used when creating file items.
-     *
-     * @return The factory class for new file items.
-     */
-    @Override
-    public FileItemFactory getFileItemFactory()
-    {
-        return fileItemFactory;
-    }
-
-
-    /**
-     * Sets the factory class to use when creating file items. The factory must
-     * be an instance of <code>DefaultFileItemFactory</code> or a subclass
-     * thereof, or else a <code>ClassCastException</code> will be thrown.
-     *
-     * @param factory The factory class for new file items.
-     */
-    @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 <a href="http://www.ietf.org/rfc/rfc1867.txt">RFC 1867</a>
-     * compliant <code>multipart/form-data</code> stream. If files are stored
-     * on disk, the path is given by <code>getRepository()</code>.
-     *
-     * @param req           The servlet request to be parsed. Must be non-null.
-     * @param sizeThreshold The max size in bytes to be stored in memory.
-     * @param sizeMax       The maximum allowed upload size, in bytes.
-     * @param path          The location where the files should be stored.
-     *
-     * @return A list of <code>FileItem</code> instances parsed from the
-     *         request, in the order that they were transmitted.
-     *
-     * @exception FileUploadException if there are problems reading/parsing
-     *                                the request or storing files.
-     */
-    public List<FileItem> parseRequest(HttpServletRequest req,
-                                       int sizeThreshold,
-                                       long sizeMax, String path)
-        throws FileUploadException
-    {
-        setSizeThreshold(sizeThreshold);
-        setSizeMax(sizeMax);
-        setRepositoryPath(path);
-        return parseRequest(req);
-    }
-
-}
diff --git a/java/org/apache/tomcat/util/http/fileupload/FileCleanerCleanup.java b/java/org/apache/tomcat/util/http/fileupload/FileCleanerCleanup.java
new file mode 100644 (file)
index 0000000..988a370
--- /dev/null
@@ -0,0 +1,81 @@
+/*
+ * 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 javax.servlet.ServletContext;
+import javax.servlet.ServletContextListener;
+import javax.servlet.ServletContextEvent;
+
+
+/**
+ * A servlet context listener, which ensures that the
+ * {@link org.apache.commons.io.FileCleaner FileCleaner's}
+ * reaper thread is terminated,
+ * when the web application is destroyed.
+ */
+public class FileCleanerCleanup implements ServletContextListener {
+    /**
+     * Attribute name, which is used for storing an instance of
+     * {@link FileCleaningTracker} in the web application.
+     */
+    public static final String FILE_CLEANING_TRACKER_ATTRIBUTE
+        = FileCleanerCleanup.class.getName() + ".FileCleaningTracker";
+
+    /**
+     * Returns the instance of {@link FileCleaningTracker}, which is
+     * associated with the given {@link ServletContext}.
+     * @param pServletContext The servlet context to query
+     * @return The contexts tracker
+     */
+    public static FileCleaningTracker
+            getFileCleaningTracker(ServletContext pServletContext) {
+        return (FileCleaningTracker)
+            pServletContext.getAttribute(FILE_CLEANING_TRACKER_ATTRIBUTE);
+    }
+
+    /**
+     * Sets the instance of {@link FileCleaningTracker}, which is
+     * associated with the given {@link ServletContext}.
+     * @param pServletContext The servlet context to modify
+     * @param pTracker The tracker to set
+     */
+    public static void setFileCleaningTracker(ServletContext pServletContext,
+            FileCleaningTracker pTracker) {
+        pServletContext.setAttribute(FILE_CLEANING_TRACKER_ATTRIBUTE, pTracker);
+    }
+
+    /**
+     * Called when the web application is initialized. Does
+     * nothing.
+     * @param sce The servlet context, used for calling
+     *   {@link #setFileCleaningTracker(ServletContext, FileCleaningTracker)}.
+     */
+    public void contextInitialized(ServletContextEvent sce) {
+        setFileCleaningTracker(sce.getServletContext(),
+                new FileCleaningTracker());
+    }
+
+    /**
+     * Called when the web application is being destroyed.
+     * Calls {@link FileCleaningTracker#exitWhenFinished()}.
+     * @param sce The servlet context, used for calling
+     *     {@link #getFileCleaningTracker(ServletContext)}.
+     */
+    public void contextDestroyed(ServletContextEvent sce) {
+        getFileCleaningTracker(sce.getServletContext()).exitWhenFinished();
+    }
+}
diff --git a/java/org/apache/tomcat/util/http/fileupload/FileCleaningTracker.java b/java/org/apache/tomcat/util/http/fileupload/FileCleaningTracker.java
new file mode 100644 (file)
index 0000000..cd64a32
--- /dev/null
@@ -0,0 +1,258 @@
+/*\r
+ * Licensed to the Apache Software Foundation (ASF) under one or more\r
+ * contributor license agreements.  See the NOTICE file distributed with\r
+ * this work for additional information regarding copyright ownership.\r
+ * The ASF licenses this file to You under the Apache License, Version 2.0\r
+ * (the "License"); you may not use this file except in compliance with\r
+ * the License.  You may obtain a copy of the License at\r
+ * \r
+ *      http://www.apache.org/licenses/LICENSE-2.0\r
+ * \r
+ * Unless required by applicable law or agreed to in writing, software\r
+ * distributed under the License is distributed on an "AS IS" BASIS,\r
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ * See the License for the specific language governing permissions and\r
+ * limitations under the License.\r
+ */\r
+package org.apache.tomcat.util.http.fileupload;\r
+\r
+import java.io.File;\r
+import java.lang.ref.PhantomReference;\r
+import java.lang.ref.ReferenceQueue;\r
+import java.util.Collection;\r
+import java.util.Vector;\r
+\r
+/**\r
+ * Keeps track of files awaiting deletion, and deletes them when an associated\r
+ * marker object is reclaimed by the garbage collector.\r
+ * <p>\r
+ * This utility creates a background thread to handle file deletion.\r
+ * Each file to be deleted is registered with a handler object.\r
+ * When the handler object is garbage collected, the file is deleted.\r
+ * <p>\r
+ * In an environment with multiple class loaders (a servlet container, for\r
+ * example), you should consider stopping the background thread if it is no\r
+ * longer needed. This is done by invoking the method\r
+ * {@link #exitWhenFinished}, typically in\r
+ * {@link javax.servlet.ServletContextListener#contextDestroyed} or similar.\r
+ *\r
+ * @author Noel Bergman\r
+ * @author Martin Cooper\r
+ * @version $Id: FileCleaner.java 490987 2006-12-29 12:11:48Z scolebourne $\r
+ */\r
+public class FileCleaningTracker {\r
+    /**\r
+     * Queue of <code>Tracker</code> instances being watched.\r
+     */\r
+    ReferenceQueue /* Tracker */ q = new ReferenceQueue();\r
+    /**\r
+     * Collection of <code>Tracker</code> instances in existence.\r
+     */\r
+    final Collection /* Tracker */ trackers = new Vector();  // synchronized\r
+    /**\r
+     * Whether to terminate the thread when the tracking is complete.\r
+     */\r
+    volatile boolean exitWhenFinished = false;\r
+    /**\r
+     * The thread that will clean up registered files.\r
+     */\r
+    Thread reaper;\r
+\r
+    //-----------------------------------------------------------------------\r
+    /**\r
+     * Track the specified file, using the provided marker, deleting the file\r
+     * when the marker instance is garbage collected.\r
+     * The {@link FileDeleteStrategy#NORMAL normal} deletion strategy will be used.\r
+     *\r
+     * @param file  the file to be tracked, not null\r
+     * @param marker  the marker object used to track the file, not null\r
+     * @throws NullPointerException if the file is null\r
+     */\r
+    public void track(File file, Object marker) {\r
+        track(file, marker, (FileDeleteStrategy) null);\r
+    }\r
+\r
+    /**\r
+     * Track the specified file, using the provided marker, deleting the file\r
+     * when the marker instance is garbage collected.\r
+     * The speified deletion strategy is used.\r
+     *\r
+     * @param file  the file to be tracked, not null\r
+     * @param marker  the marker object used to track the file, not null\r
+     * @param deleteStrategy  the strategy to delete the file, null means normal\r
+     * @throws NullPointerException if the file is null\r
+     */\r
+    public void track(File file, Object marker, FileDeleteStrategy deleteStrategy) {\r
+        if (file == null) {\r
+            throw new NullPointerException("The file must not be null");\r
+        }\r
+        addTracker(file.getPath(), marker, deleteStrategy);\r
+    }\r
+\r
+    /**\r
+     * Track the specified file, using the provided marker, deleting the file\r
+     * when the marker instance is garbage collected.\r
+     * The {@link FileDeleteStrategy#NORMAL normal} deletion strategy will be used.\r
+     *\r
+     * @param path  the full path to the file to be tracked, not null\r
+     * @param marker  the marker object used to track the file, not null\r
+     * @throws NullPointerException if the path is null\r
+     */\r
+    public void track(String path, Object marker) {\r
+        track(path, marker, (FileDeleteStrategy) null);\r
+    }\r
+\r
+    /**\r
+     * Track the specified file, using the provided marker, deleting the file\r
+     * when the marker instance is garbage collected.\r
+     * The speified deletion strategy is used.\r
+     *\r
+     * @param path  the full path to the file to be tracked, not null\r
+     * @param marker  the marker object used to track the file, not null\r
+     * @param deleteStrategy  the strategy to delete the file, null means normal\r
+     * @throws NullPointerException if the path is null\r
+     */\r
+    public void track(String path, Object marker, FileDeleteStrategy deleteStrategy) {\r
+        if (path == null) {\r
+            throw new NullPointerException("The path must not be null");\r
+        }\r
+        addTracker(path, marker, deleteStrategy);\r
+    }\r
+\r
+    /**\r
+     * Adds a tracker to the list of trackers.\r
+     * \r
+     * @param path  the full path to the file to be tracked, not null\r
+     * @param marker  the marker object used to track the file, not null\r
+     * @param deleteStrategy  the strategy to delete the file, null means normal\r
+     */\r
+    private synchronized void addTracker(String path, Object marker, FileDeleteStrategy deleteStrategy) {\r
+        // synchronized block protects reaper\r
+        if (exitWhenFinished) {\r
+            throw new IllegalStateException("No new trackers can be added once exitWhenFinished() is called");\r
+        }\r
+        if (reaper == null) {\r
+            reaper = new Reaper();\r
+            reaper.start();\r
+        }\r
+        trackers.add(new Tracker(path, deleteStrategy, marker, q));\r
+    }\r
+\r
+    //-----------------------------------------------------------------------\r
+    /**\r
+     * Retrieve the number of files currently being tracked, and therefore\r
+     * awaiting deletion.\r
+     *\r
+     * @return the number of files being tracked\r
+     */\r
+    public int getTrackCount() {\r
+        return trackers.size();\r
+    }\r
+\r
+    /**\r
+     * Call this method to cause the file cleaner thread to terminate when\r
+     * there are no more objects being tracked for deletion.\r
+     * <p>\r
+     * In a simple environment, you don't need this method as the file cleaner\r
+     * thread will simply exit when the JVM exits. In a more complex environment,\r
+     * with multiple class loaders (such as an application server), you should be\r
+     * aware that the file cleaner thread will continue running even if the class\r
+     * loader it was started from terminates. This can consitute a memory leak.\r
+     * <p>\r
+     * For example, suppose that you have developed a web application, which\r
+     * contains the commons-io jar file in your WEB-INF/lib directory. In other\r
+     * words, the FileCleaner class is loaded through the class loader of your\r
+     * web application. If the web application is terminated, but the servlet\r
+     * container is still running, then the file cleaner thread will still exist,\r
+     * posing a memory leak.\r
+     * <p>\r
+     * This method allows the thread to be terminated. Simply call this method\r
+     * in the resource cleanup code, such as {@link javax.servlet.ServletContextListener#contextDestroyed}.\r
+     * One called, no new objects can be tracked by the file cleaner.\r
+     */\r
+    public synchronized void exitWhenFinished() {\r
+        // synchronized block protects reaper\r
+        exitWhenFinished = true;\r
+        if (reaper != null) {\r
+            synchronized (reaper) {\r
+                reaper.interrupt();\r
+            }\r
+        }\r
+    }\r
+\r
+    //-----------------------------------------------------------------------\r
+    /**\r
+     * The reaper thread.\r
+     */\r
+    private final class Reaper extends Thread {\r
+        /** Construct a new Reaper */\r
+        Reaper() {\r
+            super("File Reaper");\r
+            setPriority(Thread.MAX_PRIORITY);\r
+            setDaemon(true);\r
+        }\r
+\r
+        /**\r
+         * Run the reaper thread that will delete files as their associated\r
+         * marker objects are reclaimed by the garbage collector.\r
+         */\r
+        public void run() {\r
+            // thread exits when exitWhenFinished is true and there are no more tracked objects\r
+            while (exitWhenFinished == false || trackers.size() > 0) {\r
+                Tracker tracker = null;\r
+                try {\r
+                    // Wait for a tracker to remove.\r
+                    tracker = (Tracker) q.remove();\r
+                } catch (Exception e) {\r
+                    continue;\r
+                }\r
+                if (tracker != null) {\r
+                    tracker.delete();\r
+                    tracker.clear();\r
+                    trackers.remove(tracker);\r
+                }\r
+            }\r
+        }\r
+    }\r
+\r
+    //-----------------------------------------------------------------------\r
+    /**\r
+     * Inner class which acts as the reference for a file pending deletion.\r
+     */\r
+    private static final class Tracker extends PhantomReference {\r
+\r
+        /**\r
+         * The full path to the file being tracked.\r
+         */\r
+        private final String path;\r
+        /**\r
+         * The strategy for deleting files.\r
+         */\r
+        private final FileDeleteStrategy deleteStrategy;\r
+\r
+        /**\r
+         * Constructs an instance of this class from the supplied parameters.\r
+         *\r
+         * @param path  the full path to the file to be tracked, not null\r
+         * @param deleteStrategy  the strategy to delete the file, null means normal\r
+         * @param marker  the marker object used to track the file, not null\r
+         * @param queue  the queue on to which the tracker will be pushed, not null\r
+         */\r
+        Tracker(String path, FileDeleteStrategy deleteStrategy, Object marker, ReferenceQueue queue) {\r
+            super(marker, queue);\r
+            this.path = path;\r
+            this.deleteStrategy = (deleteStrategy == null ? FileDeleteStrategy.NORMAL : deleteStrategy);\r
+        }\r
+\r
+        /**\r
+         * Deletes the file associated with this tracker instance.\r
+         *\r
+         * @return <code>true</code> if the file was deleted successfully;\r
+         *         <code>false</code> otherwise.\r
+         */\r
+        public boolean delete() {\r
+            return deleteStrategy.deleteQuietly(new File(path));\r
+        }\r
+    }\r
+\r
+}\r
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 (file)
index 0000000..4715322
--- /dev/null
@@ -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.
+ * <p>
+ * 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.
+ * <p>
+ * 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 <code>IOException</code>s are caught and false returned instead.
+     * If the file does not exist or is null, true is returned.
+     * <p>
+     * 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.
+     * <p>
+     * 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.
+     * <p>
+     * This method is designed for subclasses to override.
+     * The implementation may return either false or an <code>IOException</code>
+     * 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.
+     * <p>
+     * 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.
+         * <p>
+         * This implementation uses <code>FileUtils.forceDelete() <code>
+         * if the file exists.
+         *
+         * @param fileToDelete  the file to delete, not null
+         * @return Always returns <code>true</code>
+         * @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;
+        }
+    }
+
+}
index 7237c08..de9c9e4 100644 (file)
@@ -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;
 
-
 /**
  * <p> This class represents a file or form item that was received within a
  * <code>multipart/form-data</code> POST request.
  *
  * <p> After retrieving an instance of this class from a {@link
- * org.apache.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 <code>File</code> 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;
 
index f6988ae..415bab7 100644 (file)
@@ -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.</p>
  *
  * @author <a href="mailto:martinc@apache.org">Martin Cooper</a>
- * 
+ *
  * @version $Id$
  */
-public interface FileItemFactory
-{
+public interface FileItemFactory {
 
     /**
      * Create a new {@link FileItem} instance from the supplied parameters and
diff --git a/java/org/apache/tomcat/util/http/fileupload/FileItemHeaders.java b/java/org/apache/tomcat/util/http/fileupload/FileItemHeaders.java
new file mode 100644 (file)
index 0000000..7d46743
--- /dev/null
@@ -0,0 +1,77 @@
+/*\r
+ * Licensed to the Apache Software Foundation (ASF) under one or more\r
+ * contributor license agreements.  See the NOTICE file distributed with\r
+ * this work for additional information regarding copyright ownership.\r
+ * The ASF licenses this file to You under the Apache License, Version 2.0\r
+ * (the "License"); you may not use this file except in compliance with\r
+ * the License.  You may obtain a copy of the License at\r
+ *\r
+ *      http://www.apache.org/licenses/LICENSE-2.0\r
+ *\r
+ * Unless required by applicable law or agreed to in writing, software\r
+ * distributed under the License is distributed on an "AS IS" BASIS,\r
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ * See the License for the specific language governing permissions and\r
+ * limitations under the License.\r
+ */\r
+package org.apache.tomcat.util.http.fileupload;\r
+\r
+import java.util.Iterator;\r
+\r
+/**\r
+ * <p> This class provides support for accessing the headers for a file or form\r
+ * item that was received within a <code>multipart/form-data</code> POST\r
+ * request.</p>\r
+ *\r
+ * @author Michael C. Macaluso\r
+ * @since 1.3\r
+ */\r
+public interface FileItemHeaders {\r
+    /**\r
+     * Returns the value of the specified part header as a <code>String</code>.\r
+     * If the part did not include a header of the specified name, this method\r
+     * return <code>null</code>.  If there are multiple headers with the same\r
+     * name, this method returns the first header in the item.  The header\r
+     * name is case insensitive.\r
+     *\r
+     * @param name a <code>String</code> specifying the header name\r
+     * @return a <code>String</code> containing the value of the requested\r
+     *         header, or <code>null</code> if the item does not have a header\r
+     *         of that name\r
+     */\r
+    String getHeader(String name);\r
+\r
+    /**\r
+     * <p>\r
+     * Returns all the values of the specified item header as an\r
+     * <code>Enumeration</code> of <code>String</code> objects.\r
+     * </p>\r
+     * <p>\r
+     * If the item did not include any headers of the specified name, this\r
+     * method returns an empty <code>Enumeration</code>. The header name is\r
+     * case insensitive.\r
+     * </p>\r
+     *\r
+     * @param name a <code>String</code> specifying the header name\r
+     * @return an <code>Enumeration</code> containing the values of the\r
+     *         requested header. If the item does not have any headers of\r
+     *         that name, return an empty <code>Enumeration</code>\r
+     */\r
+    Iterator getHeaders(String name);\r
+\r
+    /**\r
+     * <p>\r
+     * Returns an <code>Enumeration</code> of all the header names.\r
+     * </p>\r
+     * <p>\r
+     * If the item did not include any headers of the specified name, this\r
+     * method returns an empty <code>Enumeration</code>. The header name is\r
+     * case insensitive.\r
+     * </p>\r
+     *\r
+     * @return an <code>Enumeration</code> containing the values of the\r
+     *         requested header. If the item does not have any headers of\r
+     *         that name return an empty <code>Enumeration</code>\r
+     */\r
+    Iterator getHeaderNames();\r
+}\r
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 (file)
index 0000000..0c6ee19
--- /dev/null
@@ -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 <code>String</code> keys to a <code>List</code> of
+     * <code>String</code> instances.
+     */
+    private final Map headerNameToValueListMap = new HashMap();
+
+    /**
+     * List to preserve order of headers as added.  This would not be
+     * needed if a <code>LinkedHashMap</code> 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 (file)
index 0000000..b478d12
--- /dev/null
@@ -0,0 +1,47 @@
+/*\r
+ * Licensed to the Apache Software Foundation (ASF) under one or more\r
+ * contributor license agreements.  See the NOTICE file distributed with\r
+ * this work for additional information regarding copyright ownership.\r
+ * The ASF licenses this file to You under the Apache License, Version 2.0\r
+ * (the "License"); you may not use this file except in compliance with\r
+ * the License.  You may obtain a copy of the License at\r
+ *\r
+ *      http://www.apache.org/licenses/LICENSE-2.0\r
+ *\r
+ * Unless required by applicable law or agreed to in writing, software\r
+ * distributed under the License is distributed on an "AS IS" BASIS,\r
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ * See the License for the specific language governing permissions and\r
+ * limitations under the License.\r
+ */\r
+package org.apache.tomcat.util.http.fileupload;\r
+\r
+/**\r
+ * Interface that will indicate that {@link FileItem} or {@link FileItemStream}\r
+ * implementations will accept the headers read for the item.\r
+ *\r
+ * @author Michael C. Macaluso\r
+ * @since 1.3\r
+ *\r
+ * @see FileItem\r
+ * @see FileItemStream\r
+ */\r
+public interface FileItemHeadersSupport {\r
+    /**\r
+     * Returns the collection of headers defined locally within this item.\r
+     *\r
+     * @return the {@link FileItemHeaders} present for this item.\r
+     */\r
+    FileItemHeaders getHeaders();\r
+\r
+    /**\r
+     * Sets the headers read from within an item.  Implementations of\r
+     * {@link FileItem} or {@link FileItemStream} should implement this\r
+     * interface to be able to get the raw headers found within the item\r
+     * header block.\r
+     *\r
+     * @param headers the instance that holds onto the headers\r
+     *         for this instance.\r
+     */\r
+    void setHeaders(FileItemHeaders headers);\r
+}\r
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 (file)
index 0000000..7279c75
--- /dev/null
@@ -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 (file)
index 0000000..92f9487
--- /dev/null
@@ -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;
+
+
+/**
+ * <p> This interface provides access to a file or form item that was
+ * received within a <code>multipart/form-data</code> POST request.
+ * The items contents are retrieved by calling {@link #openStream()}.</p>
+ * <p>Instances of this class are created by accessing the
+ * iterator, returned by
+ * {@link FileUploadBase#getItemIterator(RequestContext)}.</p>
+ * <p><em>Note</em>: 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.</p>
+ */
+public interface FileItemStream extends FileItemHeadersSupport {
+    /**
+     * This exception is thrown, if an attempt is made to read
+     * data from the {@link InputStream}, which has been returned
+     * by {@link FileItemStream#openStream()}, after
+     * {@link java.util.Iterator#hasNext()} has been invoked on the
+     * iterator, which created the {@link FileItemStream}.
+     */
+    public static class ItemSkippedException extends IOException {
+        /**
+         * The exceptions serial version UID, which is being used
+         * when serializing an exception instance.
+         */
+        private static final long serialVersionUID = -7280778431581963740L;
+    }
+
+    /** Creates an {@link InputStream}, which allows to read the
+     * items contents.
+     * @return The input stream, from which the items data may
+     *   be read.
+     * @throws IllegalStateException The method was already invoked on
+     * this item. It is not possible to recreate the data stream.
+     * @throws IOException An I/O error occurred.
+     * @see ItemSkippedException
+     */
+    InputStream openStream() throws IOException;
+
+    /**
+     * Returns the content type passed by the browser or <code>null</code> if
+     * not defined.
+     *
+     * @return The content type passed by the browser or <code>null</code> if
+     *         not defined.
+     */
+    String getContentType();
+
+    /**
+     * Returns the original filename in the client's filesystem, as provided by
+     * the browser (or other client software). In most cases, this will be the
+     * base file name, without path information. However, some clients, such as
+     * the Opera browser, do include path information.
+     *
+     * @return The original filename in the client's filesystem.
+     */
+    String getName();
+
+    /**
+     * 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 <code>FileItem</code> instance represents
+     * a simple form field.
+     *
+     * @return <code>true</code> if the instance represents a simple form
+     *         field; <code>false</code> if it represents an uploaded file.
+     */
+    boolean isFormField();
+}
index a417cb3..8676273 100644 (file)
@@ -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;
  * <p>This class handles multiple files per single HTML widget, sent using
  * <code>multipart/mixed</code> encoding type, as specified by
  * <a href="http://www.ietf.org/rfc/rfc1867.txt">RFC 1867</a>.  Use {@link
- * #parseRequest(HttpServletRequest)} to acquire a list of {@link
- * org.apache.tomcat.util.http.fileupload.FileItem}s associated with a given HTML
- * widget.</p>
+ * #parseRequest(javax.servlet.http.HttpServletRequest)} to acquire a list
+ * of {@link org.apache.commons.fileupload.FileItem FileItems} associated
+ * with a given HTML widget.</p>
  *
  * <p>How the data for individual parts is stored is determined by the factory
  * used to create them; a given part may be in memory, on disk, or somewhere
@@ -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 <code>FileItem</code> instances.
+     * Constructs an uninitialised instance of this class. A factory must be
+     * configured, using <code>setFileItemFactory()</code>, before attempting
+     * to parse requests.
      *
      * @see #FileUpload(FileItemFactory)
      */
-    public FileUpload()
-    {
+    public FileUpload() {
         super();
     }
 
@@ -75,9 +72,9 @@ public class FileUpload
      * create <code>FileItem</code> 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;
     }
 
index efced32..341d182 100644 (file)
@@ -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;
+
 
 /**
  * <p>High level API for processing file uploads.</p>
@@ -36,7 +38,7 @@ import javax.servlet.http.HttpServletRequest;
  * <code>multipart/mixed</code> encoding type, as specified by
  * <a href="http://www.ietf.org/rfc/rfc1867.txt">RFC 1867</a>.  Use {@link
  * #parseRequest(HttpServletRequest)} to acquire a list of {@link
- * org.apache.tomcat.util.http.fileupload.FileItem}s associated with a given HTML
+ * org.apache.commons.fileupload.FileItem}s associated with a given HTML
  * widget.</p>
  *
  * <p>How the data for individual parts is stored is determined by the factory
@@ -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.
+     * <p>Utility method that determines whether the request contains multipart
+     * content.</p>
      *
-     * @param req The servlet request to be evaluated. Must be non-null.
+     * <p><strong>NOTE:</strong>This method will be moved to the
+     * <code>ServletFileUpload</code> 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.</p>
+     *
+     * @param ctx The request context to be evaluated. Must be non-null.
      *
      * @return <code>true</code> if the request is multipart;
      *         <code>false</code> otherwise.
      */
-    public static final boolean isMultipartContent(HttpServletRequest req)
-    {
-        String contentType = req.getHeader(CONTENT_TYPE);
-        if (contentType == null)
-        {
+    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 <code>true</code> if the request is multipart;
+     *         <code>false</code> otherwise.
+     *
+     * @deprecated Use the method on <code>ServletFileUpload</code> 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 <code>null</code>, the platform
-     * default encoding is used.
+     * individual part. When not specified, or <code>null</code>, the request
+     * encoding is used. If that is also not specified, or <code>null</code>,
+     * 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 <code>null</code>, the platform
-     * default encoding is used.
+     * individual part. When not specified, or <code>null</code>, the request
+     * encoding is used. If that is also not specified, or <code>null</code>,
+     * the platform default encoding is used.
      *
      * @param encoding The encoding used to read part headers.
      */
-    public void setHeaderEncoding(String encoding)
-    {
+    public void setHeaderEncoding(String encoding) {
         headerEncoding = encoding;
     }
 
@@ -228,166 +287,93 @@ public abstract class FileUploadBase
 
     /**
      * Processes an <a href="http://www.ietf.org/rfc/rfc1867.txt">RFC 1867</a>
-     * compliant <code>multipart/form-data</code> stream. If files are stored
-     * on disk, the path is given by <code>getRepository()</code>.
+     * compliant <code>multipart/form-data</code> stream.
      *
      * @param req The servlet request to be parsed.
      *
      * @return A list of <code>FileItem</code> instances parsed from the
      *         request, in the order that they were transmitted.
      *
-     * @exception FileUploadException if there are problems reading/parsing
-     *                                the request or storing files.
+     * @throws FileUploadException if there are problems reading/parsing
+     *                             the request or storing files.
+     *
+     * @deprecated Use the method in <code>ServletFileUpload</code> instead.
      */
-    public List<FileItem> parseRequest(HttpServletRequest req)
-        throws FileUploadException
-    {
-        if (null == req)
-        {
-            throw new NullPointerException("req parameter");
-        }
-
-        ArrayList<FileItem> items = new ArrayList<FileItem>();
-        String contentType = req.getHeader(CONTENT_TYPE);
-
-        if ((null == contentType) || (!contentType.startsWith(MULTIPART)))
-        {
-            throw new InvalidContentTypeException(
-                "the request doesn't contain a "
-                + MULTIPART_FORM_DATA
-                + " or "
-                + MULTIPART_MIXED
-                + " stream, content type header is "
-                + contentType);
-        }
-        int requestSize = req.getContentLength();
-
-        if (requestSize == -1)
-        {
-            throw new UnknownSizeException(
-                "the request was rejected because it's size is unknown");
-        }
+    public List /* FileItem */ parseRequest(HttpServletRequest req)
+    throws FileUploadException {
+        return parseRequest(new ServletRequestContext(req));
+    }
 
-        if (sizeMax >= 0 && requestSize > sizeMax)
-        {
-            throw new SizeLimitExceededException(
-                "the request was rejected because "
-                + "it's size exceeds allowed range");
-        }
+    /**
+     * Processes an <a href="http://www.ietf.org/rfc/rfc1867.txt">RFC 1867</a>
+     * compliant <code>multipart/form-data</code> stream.
+     *
+     * @param ctx The context for the request to be parsed.
+     *
+     * @return An iterator to instances of <code>FileItemStream</code>
+     *         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 <a href="http://www.ietf.org/rfc/rfc1867.txt">RFC 1867</a>
+     * compliant <code>multipart/form-data</code> stream.
+     *
+     * @param ctx The context for the request to be parsed.
+     *
+     * @return A list of <code>FileItem</code> 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)
-            {
-                Map<String,String> headers = parseHeaders(multi.readHeaders());
-                String fieldName = getFieldName(headers);
-                if (fieldName != null)
-                {
-                    String subContentType = getHeader(headers, CONTENT_TYPE);
-                    if (subContentType != null && subContentType
-                                                .startsWith(MULTIPART_MIXED))
-                    {
-                        // Multiple files.
-                        byte[] subBoundary =
-                            subContentType.substring(
-                                subContentType
-                                .indexOf("boundary=") + 9).getBytes();
-                        multi.setBoundary(subBoundary);
-                        boolean nextSubPart = multi.skipPreamble();
-                        while (nextSubPart)
-                        {
-                            headers = parseHeaders(multi.readHeaders());
-                            if (getFileName(headers) != null)
-                            {
-                                FileItem item =
-                                        createItem(headers, false);
-                                OutputStream os = item.getOutputStream();
-                                try
-                                {
-                                    multi.readBodyData(os);
-                                }
-                                finally
-                                {
-                                    os.close();
-                                }
-                                items.add(item);
-                            }
-                            else
-                            {
-                                // Ignore anything but files inside
-                                // multipart/mixed.
-                                multi.discardBodyData();
-                            }
-                            nextSubPart = multi.readBoundary();
-                        }
-                        multi.setBoundary(boundary);
-                    }
-                    else
-                    {
-                        if (getFileName(headers) != null)
-                        {
-                            // A single file.
-                            FileItem item = createItem(headers, false);
-                            OutputStream os = item.getOutputStream();
-                            try
-                            {
-                                multi.readBodyData(os);
-                            }
-                            finally
-                            {
-                                os.close();
-                            }
-                            items.add(item);
-                        }
-                        else
-                        {
-                            // A form field.
-                            FileItem item = createItem(headers, true);
-                            OutputStream os = item.getOutputStream();
-                            try
-                            {
-                                multi.readBodyData(os);
-                            }
-                            finally
-                            {
-                                os.close();
-                            }
-                            items.add(item);
-                        }
-                    }
+            while (iter.hasNext()) {
+                FileItemStream item = iter.next();
+                FileItem fileItem = fac.createItem(item.getFieldName(),
+                        item.getContentType(), item.isFormField(),
+                        item.getName());
+                try {
+                    Streams.copy(item.openStream(), fileItem.getOutputStream(),
+                            true);
+                } catch (FileUploadIOException e) {
+                    throw (FileUploadException) e.getCause();
+                } catch (IOException e) {
+                    throw new IOFileUploadException(
+                            "Processing of " + MULTIPART_FORM_DATA
+                            + " request failed. " + e.getMessage(), e);
                 }
-                else
-                {
-                    // Skip this part.
-                    multi.discardBodyData();
+                if (fileItem instanceof FileItemHeadersSupport) {
+                    final FileItemHeaders fih = item.getHeaders();
+                    ((FileItemHeadersSupport) fileItem).setHeaders(fih);
                 }
-                nextPart = multi.readBoundary();
+                items.add(fileItem);
             }
+            return items;
+        } catch (FileUploadIOException e) {
+            throw (FileUploadException) e.getCause();
+        } catch (IOException e) {
+            throw new FileUploadException(e.getMessage(), e);
         }
-        catch (IOException e)
-        {
-            throw new FileUploadException(
-                "Processing of " + MULTIPART_FORM_DATA
-                    + " request failed. " + e.getMessage());
-        }
-
-        return items;
     }
 
 
@@ -395,24 +381,83 @@ public abstract class FileUploadBase
 
 
     /**
+     * Retrieves the boundary from the <code>Content-type</code> 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 <code>Content-disposition</code>
      * header.
      *
      * @param headers A <code>Map</code> containing the HTTP request headers.
      *
      * @return The file name for the current <code>encapsulation</code>.
+     * @deprecated Use {@link #getFileName(FileItemHeaders)}.
      */
-    protected String getFileName(Map<String,String> headers)
-    {
+    protected String getFileName(Map /* String, String */ headers) {
+        return getFileName(getHeader(headers, CONTENT_DISPOSITION));
+    }
+
+    /**
+     * Retrieves the file name from the <code>Content-disposition</code>
+     * header.
+     *
+     * @param headers The HTTP headers object.
+     *
+     * @return The file name for the current <code>encapsulation</code>.
+     */
+    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 <code>encapsulation</code>.
      */
-    protected String getFieldName(Map<String,String> headers)
-    {
+    protected String getFieldName(FileItemHeaders headers) {
+        return getFieldName(headers.getHeader(CONTENT_DISPOSITION));
+    }
+
+    /**
+     * Returns the field name, which is given by the content-disposition
+     * header.
+     * @param pContentDisposition The content-dispositions header value.
+     * @return The field jake
+     */
+    private String getFieldName(String pContentDisposition) {
         String fieldName = null;
-        String cd = getHeader(headers, CONTENT_DISPOSITION);
-        if (cd != null && cd.startsWith(FORM_DATA))
-        {
-            int start = cd.indexOf("name=\"");
-            int end = cd.indexOf('"', start + 6);
-            if (start != -1 && end != -1)
-            {
-                fieldName = cd.substring(start + 6, end);
+        if (pContentDisposition != null
+                && pContentDisposition.toLowerCase().startsWith(FORM_DATA)) {
+            ParameterParser parser = new ParameterParser();
+            parser.setLowerCaseNames(true);
+            // Parameter parser can handle null input
+            Map params = parser.parse(pContentDisposition, ';');
+            fieldName = (String) params.get("name");
+            if (fieldName != null) {
+                fieldName = fieldName.trim();
             }
         }
         return fieldName;
     }
 
+    /**
+     * Retrieves the field name from the <code>Content-disposition</code>
+     * header.
+     *
+     * @param headers A <code>Map</code> containing the HTTP request headers.
+     *
+     * @return The field name for the current <code>encapsulation</code>.
+     * @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 <code>FileItem</code> 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<String,String> headers,
+    protected FileItem createItem(Map /* String, String */ headers,
                                   boolean isFormField)
-        throws FileUploadException
-    {
+        throws FileUploadException {
         return getFileItemFactory().createItem(getFieldName(headers),
                 getHeader(headers, CONTENT_TYPE),
                 isFormField,
                 getFileName(headers));
     }
 
-
     /**
      * <p> Parses the <code>header-part</code> and returns as key/value
      * pairs.
@@ -479,64 +547,114 @@ public abstract class FileUploadBase
      *
      * @return A <code>Map</code> containing the parsed HTTP request headers.
      */
-    protected Map<String,String> parseHeaders(String headerPart)
-    {
-        Map<String,String> headers = new HashMap<String,String>();
-        char buffer[] = new char[MAX_HEADER_SIZE];
-        boolean done = false;
-        int j = 0;
-        int i;
-        String header, headerName, headerValue;
-        try
-        {
-            while (!done)
-            {
-                i = 0;
-                // Copy a single line of characters into the buffer,
-                // omitting trailing CRLF.
-                while (i < 2 || buffer[i - 2] != '\r' || buffer[i - 1] != '\n')
-                {
-                    buffer[i++] = headerPart.charAt(j++);
-                }
-                header = new String(buffer, 0, i - 2);
-                if (header.equals(""))
-                {
-                    done = true;
-                }
-                else
-                {
-                    if (header.indexOf(':') == -1)
-                    {
-                        // This header line is malformed, skip it.
-                        continue;
-                    }
-                    headerName = header.substring(0, header.indexOf(':'))
-                        .trim().toLowerCase();
-                    headerValue =
-                        header.substring(header.indexOf(':') + 1).trim();
-                    if (getHeader(headers, headerName) != null)
-                    {
-                        // More that one heder of that name exists,
-                        // append to the list.
-                        headers.put(headerName,
-                                    getHeader(headers, headerName) + ','
-                                        + headerValue);
-                    }
-                    else
-                    {
-                        headers.put(headerName, headerValue);
+    protected FileItemHeaders getParsedHeaders(String headerPart) {
+        final int len = headerPart.length();
+        FileItemHeadersImpl headers = newFileItemHeaders();
+        int start = 0;
+        for (;;) {
+            int end = parseEndOfLine(headerPart, start);
+            if (start == end) {
+                break;
+            }
+            String header = headerPart.substring(start, end);
+            start = end + 2;
+            while (start < len) {
+                int nonWs = start;
+                while (nonWs < len) {
+                    char c = headerPart.charAt(nonWs);
+                    if (c != ' '  &&  c != '\t') {
+                        break;
                     }
+                    ++nonWs;
+                }
+                if (nonWs == start) {
+                    break;
                 }
+                // Continuation line found
+                end = parseEndOfLine(headerPart, nonWs);
+                header += " " + headerPart.substring(nonWs, end);
+                start = end + 2;
             }
-        }
-        catch (IndexOutOfBoundsException e)
-        {
-            // Headers were malformed. continue with all that was
-            // parsed.
+            parseHeaderLine(headers, header);
         }
         return headers;
     }
 
+    /**
+     * Creates a new instance of {@link FileItemHeaders}.
+     * @return The new instance.
+     */
+    protected FileItemHeadersImpl newFileItemHeaders() {
+        return new FileItemHeadersImpl();
+    }
+
+    /**
+     * <p> Parses the <code>header-part</code> and returns as key/value
+     * pairs.
+     *
+     * <p> If there are multiple headers of the same names, the name
+     * will map to a comma-separated list containing the values.
+     *
+     * @param headerPart The <code>header-part</code> of the current
+     *                   <code>encapsulation</code>.
+     *
+     * @return A <code>Map</code> containing the parsed HTTP request headers.
+     * @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(Map<String,String> headers,
-                                     String name)
-    {
-        return headers.get(name.toLowerCase());
+    protected final String getHeader(Map /* String, String */ headers,
+            String name) {
+        return (String) headers.get(name.toLowerCase());
     }
 
+    /**
+     * The iterator, which is returned by
+     * {@link FileUploadBase#getItemIterator(RequestContext)}.
+     */
+    private class FileItemIteratorImpl implements FileItemIterator {
+        /**
+         * Default implementation of {@link FileItemStream}.
+         */
+        private class FileItemStreamImpl implements FileItemStream {
+            /** The file items content type.
+             */
+            private final String contentType;
+            /** The file items field name.
+             */
+            private final String fieldName;
+            /** The file items file name.
+             */
+            private final String name;
+            /** Whether the file item is a form field.
+             */
+            private final boolean formField;
+            /** The file items input stream.
+             */
+            private final InputStream stream;
+            /** Whether the file item was already opened.
+             */
+            private boolean opened;
+            /** The headers, if any.
+             */
+            private FileItemHeaders headers;
+
+            /**
+             * Creates a new instance.
+             * @param pName The items file name, or null.
+             * @param pFieldName The items field name.
+             * @param pContentType The items content type, or null.
+             * @param pFormField Whether the item is a form field.
+             * @param pContentLength The items content length, if known, or -1
+             * @throws IOException Creating the file item failed.
+             */
+            FileItemStreamImpl(String pName, String pFieldName,
+                    String pContentType, boolean pFormField,
+                    long pContentLength) throws IOException {
+                name = pName;
+                fieldName = pFieldName;
+                contentType = pContentType;
+                formField = pFormField;
+                final ItemInputStream itemStream = multi.newInputStream();
+                InputStream istream = itemStream;
+                if (fileSizeMax != -1) {
+                    if (pContentLength != -1
+                            &&  pContentLength > fileSizeMax) {
+                        FileUploadException e =
+                            new FileSizeLimitExceededException(
+                                "The field " + fieldName
+                                + " exceeds its maximum permitted "
+                                + " size of " + fileSizeMax
+                                + " characters.",
+                                pContentLength, fileSizeMax);
+                        throw new FileUploadIOException(e);
+                    }
+                    istream = new LimitedInputStream(istream, fileSizeMax) {
+                        protected void raiseError(long pSizeMax, long pCount)
+                                throws IOException {
+                            itemStream.close(true);
+                            FileUploadException e =
+                                new FileSizeLimitExceededException(
+                                    "The field " + fieldName
+                                    + " exceeds its maximum permitted "
+                                    + " size of " + pSizeMax
+                                    + " characters.",
+                                    pCount, pSizeMax);
+                            throw new FileUploadIOException(e);
+                        }
+                    };
+                }
+                stream = istream;
+            }
+
+            /**
+             * Returns the items content type, or null.
+             * @return Content type, if known, or null.
+             */
+            public String getContentType() {
+                return contentType;
+            }
+
+            /**
+             * Returns the items field name.
+             * @return Field name.
+             */
+            public String getFieldName() {
+                return fieldName;
+            }
+
+            /**
+             * Returns the items file name.
+             * @return File name, if known, or null.
+             */
+            public String getName() {
+                return name;
+            }
+
+            /**
+             * Returns, whether this is a form field.
+             * @return True, if the item is a form field,
+             *   otherwise false.
+             */
+            public boolean isFormField() {
+                return formField;
+            }
+
+            /**
+             * Returns an input stream, which may be used to
+             * read the items contents.
+             * @return Opened input stream.
+             * @throws IOException An I/O error occurred.
+             */
+            public InputStream openStream() throws IOException {
+                if (opened) {
+                    throw new IllegalStateException(
+                            "The stream was already opened.");
+                }
+                if (((Closeable) stream).isClosed()) {
+                    throw new FileItemStream.ItemSkippedException();
+                }
+                return stream;
+            }
+
+            /**
+             * Closes the file item.
+             * @throws IOException An I/O error occurred.
+             */
+            void close() throws IOException {
+                stream.close();
+            }
+
+            /**
+             * Returns the file item headers.
+             * @return The items header object
+             */
+            public FileItemHeaders getHeaders() {
+                return headers;
+            }
+
+            /**
+             * Sets the file item headers.
+             * @param pHeaders The items header object
+             */
+            public void setHeaders(FileItemHeaders pHeaders) {
+                headers = pHeaders;
+            }
+        }
+
+        /**
+         * The multi part stream to process.
+         */
+        private final MultipartStream multi;
+        /**
+         * The notifier, which used for triggering the
+         * {@link ProgressListener}.
+         */
+        private final MultipartStream.ProgressNotifier notifier;
+        /**
+         * The boundary, which separates the various parts.
+         */
+        private final byte[] boundary;
+        /**
+         * The item, which we currently process.
+         */
+        private FileItemStreamImpl currentItem;
+        /**
+         * The current items field name.
+         */
+        private String currentFieldName;
+        /**
+         * Whether we are currently skipping the preamble.
+         */
+        private boolean skipPreamble;
+        /**
+         * Whether the current item may still be read.
+         */
+        private boolean itemValid;
+        /**
+         * Whether we have seen the end of the file.
+         */
+        private boolean eof;
+
+        /**
+         * Creates a new instance.
+         * @param ctx The request context.
+         * @throws FileUploadException An error occurred while
+         *   parsing the request.
+         * @throws IOException An I/O error occurred.
+         */
+        FileItemIteratorImpl(RequestContext ctx)
+                throws FileUploadException, IOException {
+            if (ctx == null) {
+                throw new NullPointerException("ctx parameter");
+            }
+
+            String contentType = ctx.getContentType();
+            if ((null == contentType)
+                    || (!contentType.toLowerCase().startsWith(MULTIPART))) {
+                throw new InvalidContentTypeException(
+                        "the request doesn't contain a "
+                        + MULTIPART_FORM_DATA
+                        + " or "
+                        + MULTIPART_MIXED
+                        + " stream, content type header is "
+                        + contentType);
+            }
+
+            InputStream input = ctx.getInputStream();
+
+            if (sizeMax >= 0) {
+                int requestSize = ctx.getContentLength();
+                if (requestSize == -1) {
+                    input = new LimitedInputStream(input, sizeMax) {
+                        protected void raiseError(long pSizeMax, long pCount)
+                                throws IOException {
+                            FileUploadException ex =
+                                new SizeLimitExceededException(
+                                    "the request was rejected because"
+                                    + " its size (" + pCount
+                                    + ") exceeds the configured maximum"
+                                    + " (" + pSizeMax + ")",
+                                    pCount, pSizeMax);
+                            throw new FileUploadIOException(ex);
+                        }
+                    };
+                } else {
+                    if (sizeMax >= 0 && requestSize > sizeMax) {
+                        throw new SizeLimitExceededException(
+                                "the request was rejected because its size ("
+                                + requestSize
+                                + ") exceeds the configured maximum ("
+                                + sizeMax + ")",
+                                requestSize, sizeMax);
+                    }
+                }
+            }
+
+            String charEncoding = headerEncoding;
+            if (charEncoding == null) {
+                charEncoding = ctx.getCharacterEncoding();
+            }
+
+            boundary = getBoundary(contentType);
+            if (boundary == null) {
+                throw new FileUploadException(
+                        "the request was rejected because "
+                        + "no multipart boundary was found");
+            }
+
+            notifier = new MultipartStream.ProgressNotifier(listener,
+                    ctx.getContentLength());
+            multi = new MultipartStream(input, boundary, notifier);
+            multi.setHeaderEncoding(charEncoding);
+
+            skipPreamble = true;
+            findNextItem();
+        }
+
+        /**
+         * Called for finding the nex item, if any.
+         * @return True, if an next item was found, otherwise false.
+         * @throws IOException An I/O error occurred.
+         */
+        private boolean findNextItem() throws IOException {
+            if (eof) {
+                return false;
+            }
+            if (currentItem != null) {
+                currentItem.close();
+                currentItem = null;
+            }
+            for (;;) {
+                boolean nextPart;
+                if (skipPreamble) {
+                    nextPart = multi.skipPreamble();
+                } else {
+                    nextPart = multi.readBoundary();
+                }
+                if (!nextPart) {
+                    if (currentFieldName == null) {
+                        // Outer multipart terminated -> No more data
+                        eof = true;
+                        return false;
+                    }
+                    // Inner multipart terminated -> Return to parsing the outer
+                    multi.setBoundary(boundary);
+                    currentFieldName = null;
+                    continue;
+                }
+                FileItemHeaders headers = getParsedHeaders(multi.readHeaders());
+                if (currentFieldName == null) {
+                    // We're parsing the outer multipart
+                    String fieldName = getFieldName(headers);
+                    if (fieldName != null) {
+                        String subContentType = headers.getHeader(CONTENT_TYPE);
+                        if (subContentType != null
+                                &&  subContentType.toLowerCase()
+                                        .startsWith(MULTIPART_MIXED)) {
+                            currentFieldName = fieldName;
+                            // Multiple files associated with this field name
+                            byte[] subBoundary = getBoundary(subContentType);
+                            multi.setBoundary(subBoundary);
+                            skipPreamble = true;
+                            continue;
+                        }
+                        String fileName = getFileName(headers);
+                        currentItem = new FileItemStreamImpl(fileName,
+                                fieldName, headers.getHeader(CONTENT_TYPE),
+                                fileName == null, getContentLength(headers));
+                        notifier.noteItem();
+                        itemValid = true;
+                        return true;
+                    }
+                } else {
+                    String fileName = getFileName(headers);
+                    if (fileName != null) {
+                        currentItem = new FileItemStreamImpl(fileName,
+                                currentFieldName,
+                                headers.getHeader(CONTENT_TYPE),
+                                false, getContentLength(headers));
+                        notifier.noteItem();
+                        itemValid = true;
+                        return true;
+                    }
+                }
+                multi.discardBodyData();
+            }
+        }
+
+        private long getContentLength(FileItemHeaders pHeaders) {
+            try {
+                return Long.parseLong(pHeaders.getHeader(CONTENT_LENGTH));
+            } catch (Exception e) {
+                return -1;
+            }
+        }
+
+        /**
+         * 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.
+         */
+        public boolean hasNext() throws FileUploadException, IOException {
+            if (eof) {
+                return false;
+            }
+            if (itemValid) {
+                return true;
+            }
+            return findNextItem();
+        }
+
+        /**
+         * 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.
+         */
+        public FileItemStream next() throws FileUploadException, IOException {
+            if (eof  ||  (!itemValid && !hasNext())) {
+                throw new NoSuchElementException();
+            }
+            itemValid = false;
+            return currentItem;
+        }
+    }
+
+    /**
+     * This exception is thrown for hiding an inner
+     * {@link FileUploadException} in an {@link IOException}.
+     */
+    public static class FileUploadIOException extends IOException {
+        /** The exceptions UID, for serializing an instance.
+         */
+        private static final long serialVersionUID = -7047616958165584154L;
+        /** The exceptions cause; we overwrite the parent
+         * classes field, which is available since Java
+         * 1.4 only.
+         */
+        private final FileUploadException cause;
+
+        /**
+         * Creates a <code>FileUploadIOException</code> 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 <code>InvalidContentTypeException</code> 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 <code>UnknownSizeException</code> 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 <code>SizeExceededException</code> with no
-         * detail message.
+         * @deprecated Replaced by
+         * {@link #SizeLimitExceededException(String, long, long)}
          */
-        public SizeLimitExceededException()
-        {
-            super();
+        public SizeLimitExceededException() {
+            this(null, 0, 0);
         }
 
         /**
-         * Constructs an <code>SizeExceededException</code> 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 <code>SizeExceededException</code> 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 <code>SizeExceededException</code> 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;
+    }
 }
index fd5def1..092ee34 100644 (file)
@@ -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 <a href="mailto:jmcnally@collab.net">John McNally</a>
  * @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 <code>FileUploadException</code> 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 <code>FileUploadException</code> 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 <code>PrintStream</code> 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 <code>PrintWriter</code> 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 (file)
index 0000000..e8af885
--- /dev/null
@@ -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.
+ * <p>
+ * Facilities are provided in the following areas:
+ * <ul>
+ * <li>writing to a file
+ * <li>reading from a file
+ * <li>make a directory including parent directories
+ * <li>copying files and directories
+ * <li>deleting files and directories
+ * <li>converting to and from a URL
+ * <li>listing files and directories by filter and extension
+ * <li>comparing file content
+ * <li>file last changed date
+ * <li>calculating a checksum
+ * </ul>
+ * <p>
+ * Origin of code: Excalibur, Alexandria, Commons-Utils
+ *
+ * @author <a href="mailto:burton@relativity.yi.org">Kevin A. Burton</A>
+ * @author <a href="mailto:sanders@apache.org">Scott Sanders</a>
+ * @author <a href="mailto:dlr@finemaltcoding.com">Daniel Rall</a>
+ * @author <a href="mailto:Christoph.Reck@dlr.de">Christoph.Reck</a>
+ * @author <a href="mailto:peter@apache.org">Peter Donald</a>
+ * @author <a href="mailto:jefft@apache.org">Jeff Turner</a>
+ * @author Matthew Hawthorne
+ * @author <a href="mailto:jeremias@apache.org">Jeremias Maerki</a>
+ * @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.
+     * <p>
+     * The difference between File.delete() and this method are:
+     * <ul>
+     * <li>A directory to be deleted does not have to be empty.</li>
+     * <li>You get exceptions when a file or directory cannot be deleted.
+     *      (java.io.File methods returns a boolean)</li>
+     * </ul>
+     *
+     * @param file  file or directory to delete, must not be <code>null</code>
+     * @throws NullPointerException if the directory is <code>null</code>
+     * @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 <code>null</code>
+     * @throws NullPointerException if the file is <code>null</code>
+     * @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 <code>null</code>
+     * @throws NullPointerException if the directory is <code>null</code>
+     * @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 <code>null</code>
+     * @throws NullPointerException if the directory is <code>null</code>
+     * @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 (file)
index 0000000..103dfd4
--- /dev/null
@@ -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.
+ * <p>
+ * This class provides static utility methods for input/output operations.
+ * <ul>
+ * <li>closeQuietly - these methods close a stream ignoring nulls and exceptions
+ * <li>toXxx/read - these methods read data from a stream
+ * <li>write - these methods write data to a stream
+ * <li>copy - these methods copy all the data from one stream to another
+ * <li>contentEquals - these methods compare the content of two streams
+ * </ul>
+ * <p>
+ * 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.
+ * <p>
+ * All the methods in this class that read a stream are buffered internally.
+ * This means that there is no cause to use a <code>BufferedInputStream</code>
+ * or <code>BufferedReader</code>. The default buffer size of 4K has been shown
+ * to be efficient in tests.
+ * <p>
+ * Wherever possible, the methods in this class do <em>not</em> 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.
+ * <p>
+ * 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 <code>InputStream</code>.
+     * <p>
+     * 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 <code>InputStream</code> to an
+     * <code>OutputStream</code>.
+     * <p>
+     * This method buffers the input internally, so there is no need to use a
+     * <code>BufferedInputStream</code>.
+     * <p>
+     * Large streams (over 2GB) will return a bytes copied value of
+     * <code>-1</code> after the copy has completed since the correct
+     * number of bytes cannot be returned as an int. For large streams
+     * use the <code>copyLarge(InputStream, OutputStream)</code> method.
+     * 
+     * @param input  the <code>InputStream</code> to read from
+     * @param output  the <code>OutputStream</code> 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) <code>InputStream</code> to an
+     * <code>OutputStream</code>.
+     * <p>
+     * This method buffers the input internally, so there is no need to use a
+     * <code>BufferedInputStream</code>.
+     * 
+     * @param input  the <code>InputStream</code> to read from
+     * @param output  the <code>OutputStream</code> 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 (file)
index 0000000..6d0a536
--- /dev/null
@@ -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 <code>int</code> in the range
+     * <code>0</code> to <code>255</code>. If no byte is available
+     * because the end of the stream has been reached, the value
+     * <code>-1</code> is returned. This method blocks until input data
+     * is available, the end of the stream is detected, or an exception
+     * is thrown.
+     * <p>
+     * This method
+     * simply performs <code>in.read()</code> and returns the result.
+     *
+     * @return     the next byte of data, or <code>-1</code> 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 <code>len</code> bytes of data from this input stream
+     * into an array of bytes. If <code>len</code> is not zero, the method
+     * blocks until some input is available; otherwise, no
+     * bytes are read and <code>0</code> is returned.
+     * <p>
+     * This method simply performs <code>in.read(b, off, len)</code>
+     * and returns the result.
+     *
+     * @param      b     the buffer into which the data is read.
+     * @param      off   The start offset in the destination array
+     *                   <code>b</code>.
+     * @param      len   the maximum number of bytes read.
+     * @return     the total number of bytes read into the buffer, or
+     *             <code>-1</code> if there is no more data because the end of
+     *             the stream has been reached.
+     * @exception  NullPointerException If <code>b</code> is <code>null</code>.
+     * @exception  IndexOutOfBoundsException If <code>off</code> is negative,
+     * <code>len</code> is negative, or <code>len</code> is greater than
+     * <code>b.length - off</code>
+     * @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 <code>in.close()</code>.
+     *
+     * @exception  IOException  if an I/O error occurs.
+     * @see        java.io.FilterInputStream#in
+     */
+    public void close() throws IOException {
+        closed = true;
+        super.close();
+    }
+}
index 446eef8..5f03f4d 100644 (file)
@@ -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[])}).
  *
- * <p>Here is an exaple of usage of this class.<br>
+ * <p>Here is an example of usage of this class.<br>
  *
  * <pre>
  *    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 <code>header-part</code> that will be
      * processed (10 kilobytes = 10240 bytes.).
      */
@@ -110,21 +173,31 @@ public class MultipartStream
      * A byte sequence that marks the end of <code>header-part</code>
      * (<code>CRLFCRLF</code>).
      */
-    protected static final byte[] HEADER_SEPARATOR = {0x0D, 0x0A, 0x0D, 0x0A};
+    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 (<code>CRLF</code>).
      */
-    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 (<code>--</code>).
      */
-    protected static final byte[] STREAM_TERMINATOR = { 0x2D, 0x2D };
+    protected static final byte[] STREAM_TERMINATOR = {
+        DASH, DASH};
+
+
+    /**
+     * A byte sequence that precedes a boundary (<code>CRLF--</code>).
+     */
+    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.
+     * <p> Constructs a <code>MultipartStream</code> with a custom size buffer
+     * and no progress notifier.
      *
-     * @see #MultipartStream(InputStream, byte[], int)
-     * @see #MultipartStream(InputStream, byte[])
+     * <p> Note that the buffer must be at least big enough to contain the
+     * boundary string, plus 4 characters for CR/LF and double dash, plus at
+     * least one byte of data.  Too small a buffer size setting will degrade
+     * performance.
      *
+     * @param input    The <code>InputStream</code> to serve as a data source.
+     * @param boundary The token used for dividing the stream into
+     *                 <code>encapsulations</code>.
+     * @param bufSize  The size of the buffer to be used, in bytes.
+     *
+     * @see #MultipartStream(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);
     }
 
-
     /**
      * <p> Constructs a <code>MultipartStream</code> with a custom size buffer.
      *
@@ -216,30 +315,30 @@ public class MultipartStream
      * @param boundary The token used for dividing the stream into
      *                 <code>encapsulations</code>.
      * @param bufSize  The size of the buffer to be used, in bytes.
+     * @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 <code>InputStream</code> to serve as a data source.
      * @param boundary The token used for dividing the stream into
      *                 <code>encapsulations</code>.
+     * @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);
+    }
+
+    /**
+     * <p> Constructs a <code>MultipartStream</code> with a default size buffer.
      *
-     * @see #MultipartStream()
-     * @see #MultipartStream(InputStream, byte[], int)
+     * @param input    The <code>InputStream</code> to serve as a data source.
+     * @param boundary The token used for dividing the stream into
+     *                 <code>encapsulations</code>.
      *
+     * @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 <code>true</code> if there are more encapsulations in
      *         this stream; <code>false</code> otherwise.
      *
-     * @exception MalformedStreamException if the stream ends unexpecetedly or
-     *                                     fails to follow required syntax.
+     * @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 <code>boundary</code>
-     *                                     has a different length than the one
-     *                                     being currently parsed.
+     * @throws IllegalBoundaryException if the <code>boundary</code>
+     *                                  has a different length than the one
+     *                                  being currently parsed.
      */
     public void setBoundary(byte[] boundary)
-        throws IllegalBoundaryException
-    {
-        if (boundary.length != boundaryLength - 4)
-        {
+            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 <code>header-part</code> 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
      *
      * <p>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 <code>Stream</code> to write data into.
+     * @param output The <code>Stream</code> 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();
+    }
 
     /**
      * <p> Reads <code>body-data</code> 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 <code>true</code> if an <code>encapsulation</code> 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
      *         <code>a</code> and <code>b</code> are equal.
      */
     public static boolean arrayequals(byte[] a,
-                                      byte[] b,
-                                      int count)
-    {
-        for (int i = 0; i < count; i++)
-        {
-            if (a[i] != b[i])
-            {
+            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
      *         <code>buffer</code>, or <code>-1</code> if not found.
      */
     protected int findByte(byte value,
-                           int pos)
-    {
-        for (int i = pos; i < tail; i++)
-        {
-            if (buffer[i] == value)
-            {
+            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 <code>buffer</code>, or <code>-1</code> 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 <code>MalformedStreamException</code> 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 <code>IllegalBoundaryException</code> 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 (file)
index 0000000..dce1507
--- /dev/null
@@ -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.
+ *
+ * <p>
+ *  <code>param1 = value; param2 = "anything goes; really"; param3</code>
+ * </p>
+ *
+ * @author <a href="mailto:oleg@ural.ru">Oleg Kalnichevski</a>
+ */
+
+public class ParameterParser {
+    /**
+     * String to be parsed.
+     */
+    private char[] chars = null;
+
+    /**
+     * Current position in the string.
+     */
+    private int pos = 0;
+
+    /**
+     * Maximum position in the string.
+     */
+    private int len = 0;
+
+    /**
+     * Start of a token.
+     */
+    private int i1 = 0;
+
+    /**
+     * End of a token.
+     */
+    private int i2 = 0;
+
+    /**
+     * Whether names stored in the map should be converted to lower case.
+     */
+    private boolean lowerCaseNames = false;
+
+    /**
+     * Default ParameterParser constructor.
+     */
+    public ParameterParser() {
+        super();
+    }
+
+    /**
+     * Are there any characters left to parse?
+     *
+     * @return <tt>true</tt> if there are unparsed characters,
+     *         <tt>false</tt> otherwise.
+     */
+    private boolean hasChar() {
+        return this.pos < this.len;
+    }
+
+    /**
+     * A helper method to process the parsed token. This method removes
+     * leading and trailing blanks as well as enclosing quotation marks,
+     * when necessary.
+     *
+     * @param quoted <tt>true</tt> if quotation marks are expected,
+     *               <tt>false</tt> otherwise.
+     * @return the token
+     */
+    private String getToken(boolean quoted) {
+        // Trim leading white spaces
+        while ((i1 < i2) && (Character.isWhitespace(chars[i1]))) {
+            i1++;
+        }
+        // Trim trailing white spaces
+        while ((i2 > i1) && (Character.isWhitespace(chars[i2 - 1]))) {
+            i2--;
+        }
+        // Strip away quotation marks if necessary
+        if (quoted) {
+            if (((i2 - i1) >= 2)
+                && (chars[i1] == '"')
+                && (chars[i2 - 1] == '"')) {
+                i1++;
+                i2--;
+            }
+        }
+        String result = null;
+        if (i2 > i1) {
+            result = new String(chars, i1, i2 - i1);
+        }
+        return result;
+    }
+
+    /**
+     * Tests if the given character is present in the array of characters.
+     *
+     * @param ch the character to test for presense in the array of characters
+     * @param charray the array of characters to test against
+     *
+     * @return <tt>true</tt> if the character is present in the array of
+     *   characters, <tt>false</tt> otherwise.
+     */
+    private boolean isOneOf(char ch, final char[] charray) {
+        boolean result = false;
+        for (int i = 0; i < charray.length; i++) {
+            if (ch == charray[i]) {
+                result = true;
+                break;
+            }
+        }
+        return result;
+    }
+
+    /**
+     * Parses out a token until any of the given terminators
+     * is encountered.
+     *
+     * @param terminators the array of terminating characters. Any of these
+     * characters when encountered signify the end of the token
+     *
+     * @return the token
+     */
+    private String parseToken(final char[] terminators) {
+        char ch;
+        i1 = pos;
+        i2 = pos;
+        while (hasChar()) {
+            ch = chars[pos];
+            if (isOneOf(ch, terminators)) {
+                break;
+            }
+            i2++;
+            pos++;
+        }
+        return getToken(false);
+    }
+
+    /**
+     * Parses out a token until any of the given terminators
+     * is encountered outside the quotation marks.
+     *
+     * @param terminators the array of terminating characters. Any of these
+     * characters when encountered outside the quotation marks signify the end
+     * of the token
+     *
+     * @return the token
+     */
+    private String parseQuotedToken(final char[] terminators) {
+        char ch;
+        i1 = pos;
+        i2 = pos;
+        boolean quoted = false;
+        boolean charEscaped = false;
+        while (hasChar()) {
+            ch = chars[pos];
+            if (!quoted && isOneOf(ch, terminators)) {
+                break;
+            }
+            if (!charEscaped && ch == '"') {
+                quoted = !quoted;
+            }
+            charEscaped = (!charEscaped && ch == '\\');
+            i2++;
+            pos++;
+
+        }
+        return getToken(true);
+    }
+
+    /**
+     * Returns <tt>true</tt> if parameter names are to be converted to lower
+     * case when name/value pairs are parsed.
+     *
+     * @return <tt>true</tt> if parameter names are to be
+     * converted to lower case when name/value pairs are parsed.
+     * Otherwise returns <tt>false</tt>
+     */
+    public boolean isLowerCaseNames() {
+        return this.lowerCaseNames;
+    }
+
+    /**
+     * Sets the flag if parameter names are to be converted to lower case when
+     * name/value pairs are parsed.
+     *
+     * @param b <tt>true</tt> if parameter names are to be
+     * converted to lower case when name/value pairs are parsed.
+     * <tt>false</tt> otherwise.
+     */
+    public void setLowerCaseNames(boolean b) {
+        this.lowerCaseNames = b;
+    }
+
+    /**
+     * Extracts a map of name/value pairs from the given string. Names are
+     * expected to be unique. Multiple separators may be specified and
+     * the earliest found in the input string is used.
+     *
+     * @param str the string that contains a sequence of name/value pairs
+     * @param separators the name/value pairs separators
+     *
+     * @return a map of name/value pairs
+     */
+    public Map parse(final String str, char[] separators) {
+        if (separators == null || separators.length == 0) {
+            return new HashMap();
+        }
+        char separator = separators[0];
+        if (str != null) {
+            int idx = str.length();
+            for (int i = 0;  i < separators.length;  i++) {
+                int tmp = str.indexOf(separators[i]);
+                if (tmp != -1) {
+                    if (tmp < idx) {
+                        idx = tmp;
+                        separator = separators[i];
+                    }
+                }
+            }
+        }
+        return parse(str, separator);
+    }
+
+    /**
+     * Extracts a map of name/value pairs from the given string. Names are
+     * expected to be unique.
+     *
+     * @param str the string that contains a sequence of name/value pairs
+     * @param separator the name/value pairs separator
+     *
+     * @return a map of name/value pairs
+     */
+    public Map parse(final String str, char separator) {
+        if (str == null) {
+            return new HashMap();
+        }
+        return parse(str.toCharArray(), separator);
+    }
+
+    /**
+     * Extracts a map of name/value pairs from the given array of
+     * characters. Names are expected to be unique.
+     *
+     * @param chars the array of characters that contains a sequence of
+     * name/value pairs
+     * @param separator the name/value pairs separator
+     *
+     * @return a map of name/value pairs
+     */
+    public Map parse(final char[] chars, char separator) {
+        if (chars == null) {
+            return new HashMap();
+        }
+        return parse(chars, 0, chars.length, separator);
+    }
+
+    /**
+     * Extracts a map of name/value pairs from the given array of
+     * characters. Names are expected to be unique.
+     *
+     * @param chars the array of characters that contains a sequence of
+     * name/value pairs
+     * @param offset - the initial offset.
+     * @param length - the length.
+     * @param separator the name/value pairs separator
+     *
+     * @return a map of name/value pairs
+     */
+    public Map parse(
+        final char[] chars,
+        int offset,
+        int length,
+        char separator) {
+
+        if (chars == null) {
+            return new HashMap();
+        }
+        HashMap params = new HashMap();
+        this.chars = chars;
+        this.pos = offset;
+        this.len = length;
+
+        String paramName = null;
+        String paramValue = null;
+        while (hasChar()) {
+            paramName = parseToken(new char[] {
+                    '=', separator });
+            paramValue = null;
+            if (hasChar() && (chars[pos] == '=')) {
+                pos++; // skip '='
+                paramValue = parseQuotedToken(new char[] {
+                        separator });
+            }
+            if (hasChar() && (chars[pos] == separator)) {
+                pos++; // skip separator
+            }
+            if ((paramName != null) && (paramName.length() > 0)) {
+                if (this.lowerCaseNames) {
+                    paramName = paramName.toLowerCase();
+                }
+                params.put(paramName, paramValue);
+            }
+        }
+        return params;
+    }
+}
diff --git a/java/org/apache/tomcat/util/http/fileupload/ProgressListener.java b/java/org/apache/tomcat/util/http/fileupload/ProgressListener.java
new file mode 100644 (file)
index 0000000..61744ca
--- /dev/null
@@ -0,0 +1,34 @@
+/*
+ * 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;
+
+
+/**
+ * The {@link ProgressListener} may be used to display a progress bar
+ * or do stuff like that.
+ */
+public interface ProgressListener {
+    /** Updates the listeners status information.
+     * @param pBytesRead The total number of bytes, which have been read
+     *   so far.
+     * @param pContentLength The total number of bytes, which are being
+     *   read. May be -1, if this number is unknown.
+     * @param pItems The number of the field, which is currently being
+     *   read. (0 = no item so far, 1 = first item is being read, ...)
+     */
+    void update(long pBytesRead, long pContentLength, int pItems);
+}
diff --git a/java/org/apache/tomcat/util/http/fileupload/RequestContext.java b/java/org/apache/tomcat/util/http/fileupload/RequestContext.java
new file mode 100644 (file)
index 0000000..370d86a
--- /dev/null
@@ -0,0 +1,64 @@
+/*
+ * 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;
+
+/**
+ * <p>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.</p>
+ *
+ * @author <a href="mailto:martinc@apache.org">Martin Cooper</a>
+ *
+ * @since FileUpload 1.1
+ *
+ * @version $Id$
+ */
+public interface RequestContext {
+
+    /**
+     * Retrieve the character encoding for the request.
+     *
+     * @return The character encoding for the request.
+     */
+    String getCharacterEncoding();
+
+    /**
+     * Retrieve the content type of the request.
+     *
+     * @return The content type of the request.
+     */
+    String getContentType();
+
+    /**
+     * Retrieve the content length of the request.
+     *
+     * @return The content length of the request.
+     */
+    int getContentLength();
+
+    /**
+     * Retrieve the input stream for the request.
+     *
+     * @return The input stream for the request.
+     *
+     * @throws IOException if a problem occurs.
+     */
+    InputStream getInputStream() throws IOException;
+}
diff --git a/java/org/apache/tomcat/util/http/fileupload/ServletFileUpload.java b/java/org/apache/tomcat/util/http/fileupload/ServletFileUpload.java
new file mode 100644 (file)
index 0000000..589a229
--- /dev/null
@@ -0,0 +1,146 @@
+/*
+ * 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.util.List;
+
+import javax.servlet.http.HttpServletRequest;
+
+
+/**
+ * <p>High level API for processing file uploads.</p>
+ *
+ * <p>This class handles multiple files per single HTML widget, sent using
+ * <code>multipart/mixed</code> encoding type, as specified by
+ * <a href="http://www.ietf.org/rfc/rfc1867.txt">RFC 1867</a>.  Use {@link
+ * #parseRequest(HttpServletRequest)} to acquire a list of {@link
+ * org.apache.commons.fileupload.FileItem}s associated with a given HTML
+ * widget.</p>
+ *
+ * <p>How the data for individual parts is stored is determined by the factory
+ * used to create them; a given part may be in memory, on disk, or somewhere
+ * else.</p>
+ *
+ * @author <a href="mailto:Rafal.Krzewski@e-point.pl">Rafal Krzewski</a>
+ * @author <a href="mailto:dlr@collab.net">Daniel Rall</a>
+ * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
+ * @author <a href="mailto:jmcnally@collab.net">John McNally</a>
+ * @author <a href="mailto:martinc@apache.org">Martin Cooper</a>
+ * @author Sean C. Sullivan
+ *
+ * @version $Id$
+ */
+public class ServletFileUpload extends FileUpload {
+
+    // ---------------------------------------------------------- Class methods
+
+
+    /**
+     * Utility method that determines whether the request contains multipart
+     * content.
+     *
+     * @param request The servlet request to be evaluated. Must be non-null.
+     *
+     * @return <code>true</code> if the request is multipart;
+     *         <code>false</code> otherwise.
+     */
+    public static final boolean isMultipartContent(
+            HttpServletRequest 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 <code>setFileItemFactory()</code>, 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 <code>FileItem</code> instances.
+     *
+     * @see FileUpload#FileUpload()
+     * @param fileItemFactory The factory to use for creating file items.
+     */
+    public ServletFileUpload(FileItemFactory fileItemFactory) {
+        super(fileItemFactory);
+    }
+
+
+    // --------------------------------------------------------- Public methods
+
+
+    /**
+     * Processes an <a href="http://www.ietf.org/rfc/rfc1867.txt">RFC 1867</a>
+     * compliant <code>multipart/form-data</code> stream.
+     *
+     * @param request The servlet request to be parsed.
+     *
+     * @return A list of <code>FileItem</code> instances parsed from the
+     *         request, in the order that they were transmitted.
+     *
+     * @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 <a href="http://www.ietf.org/rfc/rfc1867.txt">RFC 1867</a>
+     * compliant <code>multipart/form-data</code> stream.
+     *
+     * @param request The servlet request to be parsed.
+     *
+     * @return An iterator to instances of <code>FileItemStream</code>
+     *         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 (file)
index 0000000..7fdaedc
--- /dev/null
@@ -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;
+
+
+/**
+ * <p>Provides access to the request information needed for a request made to
+ * an HTTP servlet.</p>
+ *
+ * @author <a href="mailto:martinc@apache.org">Martin Cooper</a>
+ *
+ * @since FileUpload 1.1
+ *
+ * @version $Id$
+ */
+public class ServletRequestContext implements RequestContext {
+
+    // ----------------------------------------------------- Instance Variables
+
+    /**
+     * The request for which the context is being provided.
+     */
+    private HttpServletRequest request;
+
+
+    // ----------------------------------------------------------- Constructors
+
+    /**
+     * Construct a context for this request.
+     *
+     * @param request The request to which this context applies.
+     */
+    public ServletRequestContext(HttpServletRequest request) {
+        this.request = request;
+    }
+
+
+    // --------------------------------------------------------- Public Methods
+
+    /**
+     * Retrieve the character encoding for the request.
+     *
+     * @return The character encoding for the request.
+     */
+    public String getCharacterEncoding() {
+        return request.getCharacterEncoding();
+    }
+
+    /**
+     * Retrieve the content type of the request.
+     *
+     * @return The content type of the request.
+     */
+    public String getContentType() {
+        return request.getContentType();
+    }
+
+    /**
+     * Retrieve the content length of the request.
+     *
+     * @return The content length of the request.
+     */
+    public int getContentLength() {
+        return request.getContentLength();
+    }
+
+    /**
+     * Retrieve the input stream for the request.
+     *
+     * @return The input stream for the request.
+     *
+     * @throws IOException if a problem occurs.
+     */
+    public InputStream getInputStream() throws IOException {
+        return request.getInputStream();
+    }
+
+    /**
+     * Returns a string representation of this object.
+     *
+     * @return a string representation of this object.
+     */
+    public String toString() {
+        return "ContentLength="
+            + this.getContentLength()
+            + ", ContentType="
+            + this.getContentType();
+    }
+}
diff --git a/java/org/apache/tomcat/util/http/fileupload/Streams.java b/java/org/apache/tomcat/util/http/fileupload/Streams.java
new file mode 100644 (file)
index 0000000..d89f937
--- /dev/null
@@ -0,0 +1,166 @@
+/*
+ * 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.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+
+/** Utility class for working with streams.
+ */
+public final class Streams {
+    /**
+     * Private constructor, to prevent instantiation.
+     * This class has only static methods.
+     */
+    private Streams() {
+        // Does nothing
+    }
+
+    /**
+     * Default buffer size for use in
+     * {@link #copy(InputStream, OutputStream, boolean)}.
+     */
+    private static final int DEFAULT_BUFFER_SIZE = 8192;
+
+    /**
+     * Copies the contents of the given {@link InputStream}
+     * to the given {@link OutputStream}. Shortcut for
+     * <pre>
+     *   copy(pInputStream, pOutputStream, new byte[8192]);
+     * </pre>
+     * @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);
+    }
+}
index 54271fa..142cb40 100644 (file)
@@ -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
 
index 0aa24ad..88c2540 100644 (file)
@@ -1,57 +1,65 @@
 <!--
 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
+ 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.
+ 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.
 -->
 <!-- $Id$ -->
 <html>
-   <head>
-      <title>Overview of the org.apache.commons.fileupload component</title>
-   </head>
-   <body>
-      <p>
-         Component for handling html file uploads as given by rfc 1867
-         <a href="http://www.ietf.org/rfc/rfc1867.txt">RFC&nbsp;1867</a>.
-      </p>
-      <p>
-         Normal usage of the package involves
-         {@link org.apache.commons.fileupload.DiskFileUpload DiskFileUpload}
-         parsing the HttpServletRequest and returning a list of
-         {@link org.apache.commons.fileupload.FileItem FileItem}'s.
-         These <code>FileItem</code>'s provide easy access to the data
-         given in the upload.  There is also a low level api for
-         manipulating the upload data encapsulated in the
-         {@link org.apache.commons.fileupload.MultipartStream MultipartStream}
-         class.
-      </p>
-
-      <p>
-         Normal usage example:
-      </p>
+  <head>
+    <title>Overview of the org.apache.commons.fileupload component</title>
+  </head>
+  <body>
+    <p><b>NOTE:</b> 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.
+    </p>
+    <p>
+      A component for handling HTML file uploads as specified by
+      <a href="http://www.ietf.org/rfc/rfc1867.txt" target="_top">RFC&nbsp;1867</a>.
+      This component provides support for uploads within both servlets (JSR 53)
+      and portlets (JSR 168).
+    </p>
+    <p>
+      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}.
+    </p>
+    <p>
+      The following is a brief example of typical usage in a servlet, storing
+      the uploaded files on disk.
+    </p>
 <pre>
-
-    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
         // save comment and filename to database
         ...
         // write the file
-        fi.write("/www/uploads/" + fileName);
+        fi.write(new File("/www/uploads/", fileName));
     }
 </pre>
-      <p>
-         In the example above the first file is loaded into memory as a
-         <code>String</code>. Before calling the getString method, the data
-         may have been in memory or on disk depending on its size.  The second
-         file we assume it will be large and therefore never explicitly load
-         it into memory, though if it is less than 4096 bytes it will be
-         in memory before it is written to its final location.  When writing to
-         the final location, if the data is larger than the
-         threshold, an attempt is made to rename the temporary file to
-         the given location.  If it cannot be renamed, it is streamed to the
-         new location.
-      </p>
-   </body>
+    <p>
+      In the example above, the first file is loaded into memory as a
+      <code>String</code>. Before calling the <code>getString</code> method,
+      the data may have been in memory or on disk depending on its size. The
+      second file we assume it will be large and therefore never explicitly
+      load it into memory, though if it is less than 4096 bytes it will be
+      in memory before it is written to its final location. When writing to
+      the final location, if the data is larger than the threshold, an attempt
+      is made to rename the temporary file to the given location.  If it cannot
+      be renamed, it is streamed to the new location.
+    </p>
+    <p>
+      Please see the FileUpload
+      <a href="http://commons.apache.org/fileupload/using.html" target="_top">User Guide</a>
+      for further details and examples of how to use this package.
+    </p>
+  </body>
 </html>