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