You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@commons.apache.org by jo...@apache.org on 2006/06/19 02:32:39 UTC

svn commit: r415221 - in /jakarta/commons/proper/fileupload/branches/streaming: ./ .settings/ src/java/org/apache/commons/fileupload/ src/test/org/apache/commons/fileupload/

Author: jochen
Date: Sun Jun 18 17:32:38 2006
New Revision: 415221

URL: http://svn.apache.org/viewvc?rev=415221&view=rev
Log:
First implementation of the streaming API.

Added:
    jakarta/commons/proper/fileupload/branches/streaming/.classpath
    jakarta/commons/proper/fileupload/branches/streaming/.project
    jakarta/commons/proper/fileupload/branches/streaming/.settings/
    jakarta/commons/proper/fileupload/branches/streaming/.settings/org.eclipse.jdt.core.prefs
    jakarta/commons/proper/fileupload/branches/streaming/.settings/org.eclipse.jdt.ui.prefs
    jakarta/commons/proper/fileupload/branches/streaming/src/java/org/apache/commons/fileupload/FileItemIterator.java
    jakarta/commons/proper/fileupload/branches/streaming/src/java/org/apache/commons/fileupload/FileItemStream.java
    jakarta/commons/proper/fileupload/branches/streaming/src/java/org/apache/commons/fileupload/StreamUtil.java
    jakarta/commons/proper/fileupload/branches/streaming/src/test/org/apache/commons/fileupload/SizesTest.java
Modified:
    jakarta/commons/proper/fileupload/branches/streaming/src/java/org/apache/commons/fileupload/FileUploadBase.java
    jakarta/commons/proper/fileupload/branches/streaming/src/java/org/apache/commons/fileupload/MultipartStream.java

Added: jakarta/commons/proper/fileupload/branches/streaming/.classpath
URL: http://svn.apache.org/viewvc/jakarta/commons/proper/fileupload/branches/streaming/.classpath?rev=415221&view=auto
==============================================================================
--- jakarta/commons/proper/fileupload/branches/streaming/.classpath (added)
+++ jakarta/commons/proper/fileupload/branches/streaming/.classpath Sun Jun 18 17:32:38 2006
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<classpath>
+  <classpathentry excluding="" kind="src" path="src/java">
+  </classpathentry>
+  <classpathentry output="target/test-classes" kind="src" path="src/test">
+  </classpathentry>
+  <classpathentry kind="var" path="MAVEN_REPO/junit/jars/junit-3.8.1.jar">
+  </classpathentry>
+  <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER">
+  </classpathentry>
+  <classpathentry kind="var" path="MAVEN_REPO/commons-io/jars/commons-io-1.1.jar">
+  </classpathentry>
+  <classpathentry kind="var" path="MAVEN_REPO/javax.servlet/jars/servlet-api-2.3.jar">
+  </classpathentry>
+  <classpathentry kind="var" path="MAVEN_REPO/javax.portlet/jars/portlet-api-1.0.jar">
+  </classpathentry>
+  <classpathentry kind="output" path="target/classes">
+  </classpathentry>
+</classpath>
\ No newline at end of file

Added: jakarta/commons/proper/fileupload/branches/streaming/.project
URL: http://svn.apache.org/viewvc/jakarta/commons/proper/fileupload/branches/streaming/.project?rev=415221&view=auto
==============================================================================
--- jakarta/commons/proper/fileupload/branches/streaming/.project (added)
+++ jakarta/commons/proper/fileupload/branches/streaming/.project Sun Jun 18 17:32:38 2006
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>commons-fileupload-trunk</name>
+	<comment>The FileUpload component provides a simple yet flexible means of adding support for multipart file upload functionality to servlets and web applications.</comment>
+	<projects>
+	</projects>
+	<buildSpec>
+		<buildCommand>
+			<name>org.eclipse.jdt.core.javabuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+	</buildSpec>
+	<natures>
+		<nature>org.eclipse.jdt.core.javanature</nature>
+	</natures>
+</projectDescription>

Added: jakarta/commons/proper/fileupload/branches/streaming/.settings/org.eclipse.jdt.core.prefs
URL: http://svn.apache.org/viewvc/jakarta/commons/proper/fileupload/branches/streaming/.settings/org.eclipse.jdt.core.prefs?rev=415221&view=auto
==============================================================================
--- jakarta/commons/proper/fileupload/branches/streaming/.settings/org.eclipse.jdt.core.prefs (added)
+++ jakarta/commons/proper/fileupload/branches/streaming/.settings/org.eclipse.jdt.core.prefs Sun Jun 18 17:32:38 2006
@@ -0,0 +1,24 @@
+#Sun Jun 18 22:37:19 CEST 2006
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=disabled
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.1
+org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
+org.eclipse.jdt.core.compiler.compliance=1.3
+org.eclipse.jdt.core.compiler.debug.lineNumber=generate
+org.eclipse.jdt.core.compiler.debug.localVariable=generate
+org.eclipse.jdt.core.compiler.debug.sourceFile=generate
+org.eclipse.jdt.core.compiler.doc.comment.support=enabled
+org.eclipse.jdt.core.compiler.problem.assertIdentifier=ignore
+org.eclipse.jdt.core.compiler.problem.enumIdentifier=ignore
+org.eclipse.jdt.core.compiler.problem.invalidJavadoc=error
+org.eclipse.jdt.core.compiler.problem.invalidJavadocTags=disabled
+org.eclipse.jdt.core.compiler.problem.invalidJavadocTagsDeprecatedRef=disabled
+org.eclipse.jdt.core.compiler.problem.invalidJavadocTagsNotVisibleRef=disabled
+org.eclipse.jdt.core.compiler.problem.invalidJavadocTagsVisibility=private
+org.eclipse.jdt.core.compiler.problem.missingJavadocComments=warning
+org.eclipse.jdt.core.compiler.problem.missingJavadocCommentsOverriding=disabled
+org.eclipse.jdt.core.compiler.problem.missingJavadocCommentsVisibility=public
+org.eclipse.jdt.core.compiler.problem.missingJavadocTags=ignore
+org.eclipse.jdt.core.compiler.problem.missingJavadocTagsOverriding=disabled
+org.eclipse.jdt.core.compiler.problem.missingJavadocTagsVisibility=public
+org.eclipse.jdt.core.compiler.source=1.3

Added: jakarta/commons/proper/fileupload/branches/streaming/.settings/org.eclipse.jdt.ui.prefs
URL: http://svn.apache.org/viewvc/jakarta/commons/proper/fileupload/branches/streaming/.settings/org.eclipse.jdt.ui.prefs?rev=415221&view=auto
==============================================================================
--- jakarta/commons/proper/fileupload/branches/streaming/.settings/org.eclipse.jdt.ui.prefs (added)
+++ jakarta/commons/proper/fileupload/branches/streaming/.settings/org.eclipse.jdt.ui.prefs Sun Jun 18 17:32:38 2006
@@ -0,0 +1,3 @@
+#Sun Jun 18 21:36:57 CEST 2006
+eclipse.preferences.version=1
+internal.default.compliance=default

Added: jakarta/commons/proper/fileupload/branches/streaming/src/java/org/apache/commons/fileupload/FileItemIterator.java
URL: http://svn.apache.org/viewvc/jakarta/commons/proper/fileupload/branches/streaming/src/java/org/apache/commons/fileupload/FileItemIterator.java?rev=415221&view=auto
==============================================================================
--- jakarta/commons/proper/fileupload/branches/streaming/src/java/org/apache/commons/fileupload/FileItemIterator.java (added)
+++ jakarta/commons/proper/fileupload/branches/streaming/src/java/org/apache/commons/fileupload/FileItemIterator.java Sun Jun 18 17:32:38 2006
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2001-2005 The Apache Software Foundation
+ *
+ * Licensed 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.commons.fileupload;
+
+
+/** 
+ * An iterator, as returned by
+ * {@link FileUploadBase#getItemRequest(RequestContext)}
+ */
+public interface FileItemIterator {
+    /** 
+     * Returns, whether another instance of {@link FileItemStream}
+     * is available.
+     */
+    boolean hasNext() throws FileUploadException;
+
+    /** 
+     * Returns the next available {@link FileItemStream}.
+     * @throws NoSuchElementException No more items are available. Use
+     * {@link #hasNext()} to prevent this exception.
+     */
+    FileItemStream next() throws FileUploadException;
+}

Added: jakarta/commons/proper/fileupload/branches/streaming/src/java/org/apache/commons/fileupload/FileItemStream.java
URL: http://svn.apache.org/viewvc/jakarta/commons/proper/fileupload/branches/streaming/src/java/org/apache/commons/fileupload/FileItemStream.java?rev=415221&view=auto
==============================================================================
--- jakarta/commons/proper/fileupload/branches/streaming/src/java/org/apache/commons/fileupload/FileItemStream.java (added)
+++ jakarta/commons/proper/fileupload/branches/streaming/src/java/org/apache/commons/fileupload/FileItemStream.java Sun Jun 18 17:32:38 2006
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2001-2005 The Apache Software Foundation
+ *
+ * Licensed 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.commons.fileupload;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Iterator;
+
+
+/**
+ * <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#getRequestIterator(RequestContext)}.</p>
+ * <em>Note</em>: There is an interaction between the iterator and
+ * its associated instances of {@link FileItemStream}: By invoking
+ * {@link Iterator#hasNext()} on the iterator, you discard all data,
+ * which hasn't been read so far from the previous data.</p> 
+ */
+public interface FileItemStream {
+    /**
+     * 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 Iterator#hasNext()} has been invoked on the
+     * iterator, which created the {@link FileItemStream}.
+     */
+    public static class ItemSkippedException extends IOException {
+        private static final long serialVersionUID = -7280778431581963740L;
+    }
+
+    /** Creates an {@link InputStream}, which allows to read the
+     * items contents.
+     * @throws IllegalStateException The method was already invoked on
+     * this item. It is not possible to recreate the data stream.
+     * @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();
+}

Modified: jakarta/commons/proper/fileupload/branches/streaming/src/java/org/apache/commons/fileupload/FileUploadBase.java
URL: http://svn.apache.org/viewvc/jakarta/commons/proper/fileupload/branches/streaming/src/java/org/apache/commons/fileupload/FileUploadBase.java?rev=415221&r1=415220&r2=415221&view=diff
==============================================================================
--- jakarta/commons/proper/fileupload/branches/streaming/src/java/org/apache/commons/fileupload/FileUploadBase.java (original)
+++ jakarta/commons/proper/fileupload/branches/streaming/src/java/org/apache/commons/fileupload/FileUploadBase.java Sun Jun 18 17:32:38 2006
@@ -15,14 +15,18 @@
  */
 package org.apache.commons.fileupload;
 
+import java.io.FilterInputStream;
 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.commons.fileupload.servlet.ServletRequestContext;
@@ -274,6 +278,24 @@
      *
      * @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.
+     */
+    public FileItemIterator getItemRequest(RequestContext ctx)
+            throws FileUploadException {
+        return new FileItemIteratorImpl(ctx);
+    }
+
+    /**
+     * 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.
      *
@@ -282,110 +304,27 @@
      */
     public List /* FileItem */ parseRequest(RequestContext ctx)
             throws FileUploadException {
-        if (ctx == null) {
-            throw new NullPointerException("ctx parameter");
-        }
-
-        ArrayList items = new ArrayList();
-        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);
-        }
-        int requestSize = ctx.getContentLength();
-
-        if (requestSize == -1) {
-            throw new UnknownSizeException(
-                "the request was rejected because its size is unknown");
-        }
-
-        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();
-        }
-
-        try {
-            byte[] boundary = getBoundary(contentType);
-            if (boundary == null) {
+        FileItemIterator iter = getItemRequest(ctx);
+        List items = new ArrayList();
+        FileItemFactory fac = getFileItemFactory();
+        final byte[] buffer = new byte[8192];
+        while (iter.hasNext()) {
+            FileItemStream item = iter.next();
+            FileItem fileItem = fac.createItem(item.getFieldName(),
+                    item.getContentType(), item.isFormField(),
+                    item.getName());
+            try {
+                StreamUtil.copy(item.openStream(), fileItem.getOutputStream(),
+                            true, buffer);
+            } catch (FileUploadIOException e) {
+                throw (FileUploadException) e.getCause();
+            } catch (IOException e) {
                 throw new FileUploadException(
-                        "the request was rejected because "
-                        + "no multipart boundary was found");
+                    "Processing of " + MULTIPART_FORM_DATA
+                        + " request failed. " + e.getMessage());
             }
-
-            InputStream input = ctx.getInputStream();
-
-            MultipartStream multi = new MultipartStream(input, boundary);
-            multi.setHeaderEncoding(charEncoding);
-
-            boolean nextPart = multi.skipPreamble();
-            while (nextPart) {
-                Map headers = parseHeaders(multi.readHeaders());
-                String fieldName = getFieldName(headers);
-                if (fieldName != null) {
-                    String subContentType = getHeader(headers, CONTENT_TYPE);
-                    if (subContentType != null && subContentType
-                        .toLowerCase().startsWith(MULTIPART_MIXED)) {
-                        // Multiple files.
-                        byte[] subBoundary = getBoundary(subContentType);
-                        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 {
-                        FileItem item = createItem(headers,
-                                getFileName(headers) == null);
-                        OutputStream os = item.getOutputStream();
-                        try {
-                            multi.readBodyData(os);
-                        } finally {
-                            os.close();
-                        }
-                        items.add(item);
-                    }
-                } else {
-                    // Skip this part.
-                    multi.discardBodyData();
-                }
-                nextPart = multi.readBoundary();
-            }
-        } catch (IOException e) {
-            throw new FileUploadException(
-                "Processing of " + MULTIPART_FORM_DATA
-                    + " request failed. " + e.getMessage());
+            items.add(fileItem);
         }
-
         return items;
     }
 
@@ -483,28 +422,6 @@
 
 
     /**
-     * Creates a new {@link FileItem} instance.
-     *
-     * @param headers       A <code>Map</code> containing the HTTP request
-     *                      headers.
-     * @param isFormField   Whether or not this item is a form field, as
-     *                      opposed to a file.
-     *
-     * @return A newly created <code>FileItem</code> instance.
-     *
-     * @throws FileUploadException if an error occurs.
-     */
-    protected FileItem createItem(Map /* String, String */ headers,
-                                  boolean isFormField)
-        throws FileUploadException {
-        return getFileItemFactory().createItem(getFieldName(headers),
-                getHeader(headers, CONTENT_TYPE),
-                isFormField,
-                getFileName(headers));
-    }
-
-
-    /**
      * <p> Parses the <code>header-part</code> and returns as key/value
      * pairs.
      *
@@ -578,52 +495,299 @@
         return (String) headers.get(name.toLowerCase());
     }
 
+    /**
+     * The iterator, which is returned by
+     * {@link FileUploadBase#getRequestIterator(RequestContext)}.
+     */
+    private class FileItemIteratorImpl implements FileItemIterator {
+        private class FileItemStreamImpl implements FileItemStream {
+            private final String contentType, fieldName, name;
+            private final boolean formField;
+            private final MultipartStream.ItemInputStream stream;
+            private boolean opened;
+
+            FileItemStreamImpl(String pName, String pFieldName,
+                    String pContentType, boolean pFormField) {
+                name = pName;
+                fieldName = pFieldName;
+                contentType = pContentType;
+                formField = pFormField;
+                stream = multi.newInputStream();
+            }
+
+            public String getContentType() {
+                return contentType;
+            }
+
+            public String getFieldName() {
+                return fieldName;
+            }
+
+            public String getName() {
+                return name;
+            }
+
+            public boolean isFormField() {
+                return formField;
+            }
+
+            public InputStream openStream() throws IOException {
+                if (opened) {
+                    throw new IllegalStateException("The stream was already opened.");
+                }
+                if (stream.isClosed()) {
+                    throw new FileItemStream.ItemSkippedException();
+                }
+                return stream;
+            }
+
+            void close() throws IOException {
+                stream.close();
+            }
+        }
+
+        private final MultipartStream multi;
+        private final byte[] boundary;
+        private FileItemStreamImpl currentItem;
+        private String currentFieldName;
+        private boolean skipPreamble;
+        private boolean itemValid;
+        private boolean eof;
+
+        FileItemIteratorImpl(RequestContext ctx) throws FileUploadException {
+            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);
+            }
+
+            try {
+                InputStream input = ctx.getInputStream();
+
+                if (sizeMax >= 0) {
+                    int requestSize = ctx.getContentLength();
+                    if (requestSize == -1) {
+                        input = new LimitedInputStream(input, sizeMax);
+                    } 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");
+                }
+
+                multi = new MultipartStream(input, boundary);
+                multi.setHeaderEncoding(charEncoding);
+
+                skipPreamble = true;
+                findNextItem();
+            } catch (FileUploadIOException e) {
+                throw (FileUploadException) e.getCause();
+            } catch (IOException e) {
+                throw new FileUploadException(
+                    "Processing of " + MULTIPART_FORM_DATA
+                        + " request failed. " + e.getMessage());
+            }
+        }
+
+        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;
+                }
+                Map headers = parseHeaders(multi.readHeaders());
+                if (currentFieldName == null) {
+                    // We're parsing the outer multipart
+                    String fieldName = getFieldName(headers);
+                    if (fieldName != null) {
+                        String subContentType = getHeader(headers, 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;
+                        } else {
+                            String fileName = getFileName(headers);
+                            currentItem = new FileItemStreamImpl(fileName,
+                                    fieldName, getHeader(headers, CONTENT_TYPE),
+                                    fileName == null);
+                            itemValid = true;
+                            return true;
+                        }
+                    }
+                } else {
+                    String fileName = getFileName(headers);
+                    if (fileName != null) {
+                        currentItem = new FileItemStreamImpl(fileName,
+                                currentFieldName, getHeader(headers, CONTENT_TYPE),
+                                false);
+                        itemValid = true;
+                        return true;
+                    }
+                }
+                multi.discardBodyData();
+            }
+        }
+
+        public boolean hasNext() throws FileUploadException {
+            if (eof) {
+                return false;
+            }
+            if (itemValid) {
+                return true;
+            }
+            try {
+                return findNextItem();
+            } catch (FileUploadIOException e) {
+                throw (FileUploadException) e.getCause();
+            } catch (IOException e) {
+                throw new FileUploadException(
+                    "Processing of " + MULTIPART_FORM_DATA
+                        + " request failed. " + e.getMessage());
+            }
+        }
+
+        public FileItemStream next() throws FileUploadException {
+            if (eof  ||  (!itemValid && !hasNext())) {
+                throw new NoSuchElementException();
+            }
+            itemValid = false;
+            return currentItem;
+        }
+    }
+
+    /**
+     * An input stream, which limits its data size. This stream is
+     * used, if the content length is unknown.
+     */
+    private static class LimitedInputStream extends FilterInputStream {
+        private long sizeMax;
+        private long count;
+
+        private void checkLimit() throws IOException {
+            if (count > sizeMax) {
+                FileUploadException ex = new SizeLimitExceededException(
+                        "the request was rejected because its size (" + count
+                        + ") exceeds the configured maximum (" + sizeMax + ")",
+                        count, sizeMax);
+                throw new FileUploadIOException(ex);
+            }
+        }
+
+        public int read() throws IOException {
+            int res = super.read();
+            if (res != -1) {
+                count++;
+                checkLimit();
+            }
+            return res;
+        }
+
+        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;
+        }
+
+        LimitedInputStream(InputStream pIn, long pSizeMax) {
+            super(pIn);
+            sizeMax = pSizeMax;
+        }
+    }
 
     /**
-     * Thrown to indicate that the request is not a multipart request.
+     * This exception is thrown for hiding an inner 
+     * {@link FileUploadException} in an {@link IOException}.
      */
-    public static class InvalidContentTypeException
-        extends FileUploadException {
+    public static class FileUploadIOException extends IOException {
+        private static final long serialVersionUID = -7047616958165584154L;
+        private final FileUploadException cause;
+
         /**
-         * Constructs a <code>InvalidContentTypeException</code> with no
-         * detail message.
+         * Creates a <code>FileUploadIOException</code> with the
+         * given cause.
          */
-        public InvalidContentTypeException() {
-            super();
+        public FileUploadIOException(FileUploadException pCause) {
+            // We're not doing super(pCause) cause of 1.3 compatibility.
+            cause = pCause;
         }
 
-        /**
-         * Constructs an <code>InvalidContentTypeException</code> with
-         * the specified detail message.
-         *
-         * @param message The detail message.
-         */
-        public InvalidContentTypeException(String message) {
-            super(message);
+        public Throwable getCause() {
+            return cause;
         }
     }
 
-
     /**
-     * Thrown to indicate that the request size is not specified.
+     * Thrown to indicate that the request is not a multipart request.
      */
-    public static class UnknownSizeException
+    public static class InvalidContentTypeException
         extends FileUploadException {
         /**
-         * Constructs a <code>UnknownSizeException</code> with no
+         * Constructs a <code>InvalidContentTypeException</code> with no
          * detail message.
          */
-        public UnknownSizeException() {
+        public InvalidContentTypeException() {
             super();
         }
 
         /**
-         * Constructs an <code>UnknownSizeException</code> with
+         * Constructs an <code>InvalidContentTypeException</code> with
          * the specified detail message.
          *
          * @param message The detail message.
          */
-        public UnknownSizeException(String message) {
+        public InvalidContentTypeException(String message) {
             super(message);
         }
     }

Modified: jakarta/commons/proper/fileupload/branches/streaming/src/java/org/apache/commons/fileupload/MultipartStream.java
URL: http://svn.apache.org/viewvc/jakarta/commons/proper/fileupload/branches/streaming/src/java/org/apache/commons/fileupload/MultipartStream.java?rev=415221&r1=415220&r2=415221&view=diff
==============================================================================
--- jakarta/commons/proper/fileupload/branches/streaming/src/java/org/apache/commons/fileupload/MultipartStream.java (original)
+++ jakarta/commons/proper/fileupload/branches/streaming/src/java/org/apache/commons/fileupload/MultipartStream.java Sun Jun 18 17:32:38 2006
@@ -496,66 +496,13 @@
     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.
-                if (output != null) {
-                    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.
-                if (output != null) {
-                    output.write(buffer, head, tail - head - pad);
-                }
-
-                // 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;
-                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.
-                    if (output != null) {
-                        output.write(buffer, 0, pad);
-                        output.flush();
-                    }
-                    total += pad;
-                    throw new MalformedStreamException(
-                            "Stream ended unexpectedly");
-                }
-            }
-        }
-        if (output != null) {
-            output.flush();
-        }
-        return total;
+        final InputStream istream = newInputStream();
+        return (int) StreamUtil.copy(istream, output, false);
     }
 
+    ItemInputStream newInputStream() {
+        return new ItemInputStream();
+    }
 
     /**
      * <p> Reads <code>body-data</code> from the current
@@ -748,6 +695,142 @@
         }
     }
 
+    /**
+     * An {@link InputStream} for reading an items contents.
+     */
+    public class ItemInputStream extends InputStream {
+        private long total;
+        private int pad, pos;
+        private boolean closed;
+
+        ItemInputStream() {
+            findSeparator();
+        }
+
+        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.
+         */
+        public long getBytesRead() {
+            return total;
+        }
+
+        public int available() throws IOException {
+            if (pos == -1) {
+                return tail - head - pad;
+            } else {
+                return pos - head;
+            }
+        }
+
+        public int read() throws IOException {
+            if (closed) {
+                throw new FileItemStream.ItemSkippedException();
+            }
+            if (available() == 0) {
+                if (makeAvailable() == 0) {
+                    return -1;
+                }
+            }
+            ++total;
+            int b = buffer[head++];
+            return b >= 0 ? b : b + 256;
+        }
+
+        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;
+        }
+
+        public void close() throws IOException {
+            if (closed) {
+                return;
+            }
+            for (;;) {
+                int av = available();
+                if (av == 0) {
+                    av = makeAvailable();
+                    if (av == 0) {
+                        break;
+                    }
+                }
+                skip(av);
+            }
+            closed = true;
+        }
+
+        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;
+        }
+
+        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;
+            int bytesRead = input.read(buffer, pad, bufSize - pad);
+            if (bytesRead == -1) {
+                // The last pad amount is left in the buffer.
+                // Boundary can't be in there so signal an error
+                // condition.
+                throw new MalformedStreamException(
+                        "Stream ended unexpectedly");
+            }
+            tail = pad + bytesRead;
+            findSeparator();
+            return available();
+        }
+
+        /** Returns, whether the stream is closed.
+         */
+        public boolean isClosed() {
+            return closed;
+        }
+    }
 
     // ------------------------------------------------------ Debugging methods
 

Added: jakarta/commons/proper/fileupload/branches/streaming/src/java/org/apache/commons/fileupload/StreamUtil.java
URL: http://svn.apache.org/viewvc/jakarta/commons/proper/fileupload/branches/streaming/src/java/org/apache/commons/fileupload/StreamUtil.java?rev=415221&view=auto
==============================================================================
--- jakarta/commons/proper/fileupload/branches/streaming/src/java/org/apache/commons/fileupload/StreamUtil.java (added)
+++ jakarta/commons/proper/fileupload/branches/streaming/src/java/org/apache/commons/fileupload/StreamUtil.java Sun Jun 18 17:32:38 2006
@@ -0,0 +1,100 @@
+package org.apache.commons.fileupload;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+
+/** Utility class for working with streams.
+ */
+public class StreamUtil {
+    /**
+     * 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.
+     */
+    public static long copy(InputStream pInputStream,
+                OutputStream pOutputStream, boolean pClose)
+            throws IOException {
+        return copy(pInputStream, pOutputStream, pClose, new byte[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.
+     * @param pBuffer Temporary buffer, which is to be used for
+     * copying data.
+     * @return Number of bytes, which have been copied.
+     */
+    public static long copy(InputStream pInputStream,
+                OutputStream pOutputStream, boolean pClose,
+                byte[] pBuffer)
+            throws IOException {
+        try {
+            long total = 0;
+            for (;;) {
+                int res = pInputStream.read(pBuffer);
+                if (res == -1) {
+                    break;
+                }
+                if (res > 0) {
+                    total += res;
+                    if (pOutputStream != null) {
+                        pOutputStream.write(pBuffer, 0, res);
+                    }
+                }
+            }
+            if (pOutputStream != null) {
+                if (pClose) {
+                    pOutputStream.close();
+                } else {
+                    pOutputStream.flush();
+                }
+                pOutputStream = null;
+            }
+            pInputStream.close();
+            pInputStream = null;
+            return total;
+        } finally {
+            if (pInputStream != null) {
+                try {
+                    pInputStream.close();
+                } catch (Throwable t) {
+                    /* Ignore me */
+                }
+            }
+            if (pClose  &&  pOutputStream != null) {
+                try {
+                    pOutputStream.close();
+                } catch (Throwable t) {
+                    /* Ignore me */
+                }
+            }
+        }
+    }
+}

Added: jakarta/commons/proper/fileupload/branches/streaming/src/test/org/apache/commons/fileupload/SizesTest.java
URL: http://svn.apache.org/viewvc/jakarta/commons/proper/fileupload/branches/streaming/src/test/org/apache/commons/fileupload/SizesTest.java?rev=415221&view=auto
==============================================================================
--- jakarta/commons/proper/fileupload/branches/streaming/src/test/org/apache/commons/fileupload/SizesTest.java (added)
+++ jakarta/commons/proper/fileupload/branches/streaming/src/test/org/apache/commons/fileupload/SizesTest.java Sun Jun 18 17:32:38 2006
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2001-2004 The Apache Software Foundation
+ *
+ * Licensed 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.commons.fileupload;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.Iterator;
+import java.util.List;
+import javax.servlet.http.HttpServletRequest;
+import junit.framework.TestCase;
+
+
+/**
+ * Unit test for items with varying sizes.
+ */
+public class SizesTest extends TestCase
+{
+    public void testFileUpload()
+            throws IOException, FileUploadException
+    {
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        int add = 16;
+        int num = 0;
+        for (int i = 0;  i < 16384;  i += add) {
+            if (++add == 32) {
+                add = 16;
+            }
+            String header = "-----1234\r\n"
+                + "Content-Disposition: form-data; name=\"field" + (num++) + "\"\r\n"
+                + "\r\n";
+            baos.write(header.getBytes("US-ASCII"));
+            for (int j = 0;  j < i;  j++) {
+                baos.write((byte) j);
+            }
+            baos.write("\r\n".getBytes("US-ASCII"));
+        }
+        baos.write("-----1234--\r\n".getBytes("US-ASCII"));
+
+        List fileItems = parseUpload(baos.toByteArray());
+        Iterator fileIter = fileItems.iterator();
+        add = 16;
+        num = 0;
+        for (int i = 0;  i < 16384;  i += add) {
+            if (++add == 32) {
+                add = 16;
+            }
+            FileItem item = (FileItem) fileIter.next();
+            assertEquals("field" + (num++), item.getFieldName());
+            byte[] bytes = item.get();
+            assertEquals(i, bytes.length);
+            for (int j = 0;  j < i;  j++) {
+                assertEquals((byte) j, bytes[j]);
+            }
+        }
+        assertTrue(!fileIter.hasNext());
+    }
+
+    private List parseUpload(byte[] bytes) throws FileUploadException {
+        String contentType = "multipart/form-data; boundary=---1234";
+
+        FileUploadBase upload = new DiskFileUpload();
+        HttpServletRequest request = new MockHttpServletRequest(bytes, contentType);
+
+        List fileItems = upload.parseRequest(request);
+        return fileItems;
+    }
+
+}



---------------------------------------------------------------------
To unsubscribe, e-mail: commons-dev-unsubscribe@jakarta.apache.org
For additional commands, e-mail: commons-dev-help@jakarta.apache.org