Martin Cooper wrote:
> Let's hope. ;-) Please add youself to the list of developers in the
> project.xml file.
Done.
> This looks interesting. However, given the non-trivial nature of the
> changes, and the "stylistic" effect on the way FileUpload works, I
> wonder if
> this wouldn't be better incorporated into the FileUpload 2 design, rather
> than being retrofitted into FileUpload 1.x.
I am open for both. In either way, we can start in a branch. That leaves
everything open, doesn't it? I have one concern, though: Please let's try to
have a at least a beta release within short time, say three months?
> Well, I guess I don't agree here. If FileItem extended SimFileItem, then I
> could pass a FileItem to methods that accept a SlimFileItem. At that point,
> that SlimFileItem may or may not throw an exception if getInputStream is
> called more than once. That's bad API behaviour.
Personally I have no problem with my suggestion: If a method would accept a
StreamingFileItem, then that method should not expect, that getInputStream()
cannot be used more than once, so that's okay.
However, let's say that FileItem doesn't extend StreamingFileItem. Fine with me.
> SlimFileItem is best implemented as an inner class of the MultipartStream.
>
>
> Hmm. Not if you expect to expose SlimFileItem as part of the public API,
> which you are suggesting with the getItemIterator method below.
That's something I do not understand. The StreamingFileItem is an interface,
which is exposed. What's the problem, if the implementation is an inner class?
> Multipartstream isn't (supposed to be) part of the public API, so exposing
> an inner class of it through the public API is a no-no.
That's good to know. I get you right, that this means we may change that
classes API, may we?
> No. A FileUploadException isn't always related to IO, so extending
> IOException would simply be wrong.
Ok.
Attached you find a proposed patch, that takes a first step: The
MultipartStream receives an inner class ItemInputStream. This inner class is
used by readBodyData. (Obviously, the same InputStream will later be
returned by the StreamingFileItem.)
Jochen
Index: /home/jwi/workspace/commons-fileupload/src/java/org/apache/commons/fileupload/MultipartStream.java
===================================================================
--- /home/jwi/workspace/commons-fileupload/src/java/org/apache/commons/fileupload/MultipartStream.java (revision 413075)
+++ /home/jwi/workspace/commons-fileupload/src/java/org/apache/commons/fileupload/MultipartStream.java (working copy)
@@ -496,64 +496,22 @@
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.
+ final ItemInputStream istream = new ItemInputStream();
+ final byte[] bytes = new byte[8192];
+ for (;;) {
+ int res = istream.read(bytes);
+ if (res == -1) {
if (output != null) {
- output.write(buffer, head, pos - head);
+ output.flush();
}
- 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.
+ return (int) istream.getBytesRead();
+ }
+ if (res > 0 && output != null) {
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");
+ output.write(bytes, 0, res);
}
}
}
- if (output != null) {
- output.flush();
- }
- return total;
}
@@ -748,6 +706,122 @@
}
}
+ /**
+ * An [EMAIL PROTECTED] InputStream} for reading an items contents.
+ */
+ public class ItemInputStream extends InputStream {
+ private long total;
+ private int pad, pos;
+
+ 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 (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 (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 {
+ for (;;) {
+ int av = available();
+ if (av == 0) {
+ av = makeAvailable();
+ if (av == 0) {
+ break;
+ }
+ }
+ skip(av);
+ }
+ }
+
+ public long skip(long bytes) throws IOException {
+ 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();
+ }
+ }
// ------------------------------------------------------ Debugging methods
Index: /home/jwi/workspace/commons-fileupload/src/test/org/apache/commons/fileupload/SizesTest.java
===================================================================
--- /home/jwi/workspace/commons-fileupload/src/test/org/apache/commons/fileupload/SizesTest.java (revision 0)
+++ /home/jwi/workspace/commons-fileupload/src/test/org/apache/commons/fileupload/SizesTest.java (revision 0)
@@ -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: [EMAIL PROTECTED]
For additional commands, e-mail: [EMAIL PROTECTED]