Added:
tomcat/tc6.0.x/trunk/java/org/apache/tomcat/util/http/fileupload/MultipartStream.java
URL:
http://svn.apache.org/viewvc/tomcat/tc6.0.x/trunk/java/org/apache/tomcat/util/http/fileupload/MultipartStream.java?rev=412780&view=auto
==============================================================================
---
tomcat/tc6.0.x/trunk/java/org/apache/tomcat/util/http/fileupload/MultipartStream.java
(added)
+++
tomcat/tc6.0.x/trunk/java/org/apache/tomcat/util/http/fileupload/MultipartStream.java
Thu Jun 8 08:35:56 2006
@@ -0,0 +1,891 @@
+/*
+ * Copyright 2001-2006 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.tomcat.util.http.fileupload;
+
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+
+
+/**
+ * <p> Low level API for processing file uploads.
+ *
+ * <p> This class can be used to process data streams conforming to MIME
+ * 'multipart' format as defined in
+ * <a href="http://www.ietf.org/rfc/rfc1867.txt">RFC 1867</a>. Arbitrarily
+ * large amounts of data in the stream can be processed under constant
+ * memory usage.
+ *
+ * <p> The format of the stream is defined in the following way:<br>
+ *
+ * <code>
+ * multipart-body := preamble 1*encapsulation close-delimiter epilogue<br>
+ * encapsulation := delimiter body CRLF<br>
+ * delimiter := "--" boundary CRLF<br>
+ * close-delimiter := "--" boudary "--"<br>
+ * preamble := <ignore><br>
+ * epilogue := <ignore><br>
+ * body := header-part CRLF body-part<br>
+ * header-part := 1*header CRLF<br>
+ * header := header-name ":" header-value<br>
+ * header-name := <printable ascii characters except ":"><br>
+ * header-value := <any ascii characters except CR & LF><br>
+ * body-data := <arbitrary data><br>
+ * </code>
+ *
+ * <p>Note that body-data can contain another mulipart entity. There
+ * is limited support for single pass processing of such nested
+ * streams. The nested stream is <strong>required</strong> to have a
+ * boundary token of the same length as the parent stream (see [EMAIL
PROTECTED]
+ * #setBoundary(byte[])}).
+ *
+ * <p>Here is an exaple of usage of this class.<br>
+ *
+ * <pre>
+ * try {
+ * MultipartStream multipartStream = new MultipartStream(input,
+ * boundary);
+ * boolean nextPart = malitPartStream.skipPreamble();
+ * OutputStream output;
+ * while(nextPart) {
+ * header = chunks.readHeader();
+ * // process headers
+ * // create some output stream
+ * multipartStream.readBodyPart(output);
+ * nextPart = multipartStream.readBoundary();
+ * }
+ * } catch(MultipartStream.MalformedStreamException e) {
+ * // the stream failed to follow required syntax
+ * } catch(IOException) {
+ * // a read or write error occurred
+ * }
+ *
+ * </pre>
+ *
+ * @author <a href="mailto:[EMAIL PROTECTED]">Rafal Krzewski</a>
+ * @author <a href="mailto:[EMAIL PROTECTED]">Martin Cooper</a>
+ * @author Sean C. Sullivan
+ *
+ * @version $Id: MultipartStream.java,v 1.12 2003/06/01 00:18:13 martinc Exp $
+ */
+public class MultipartStream
+{
+
+ // ----------------------------------------------------- Manifest constants
+
+
+ /**
+ * The maximum length of <code>header-part</code> that will be
+ * processed (10 kilobytes = 10240 bytes.).
+ */
+ public static final int HEADER_PART_SIZE_MAX = 10240;
+
+
+ /**
+ * The default length of the buffer used for processing a request.
+ */
+ protected static final int DEFAULT_BUFSIZE = 4096;
+
+
+ /**
+ * A byte sequence that marks the end of <code>header-part</code>
+ * (<code>CRLFCRLF</code>).
+ */
+ protected static final byte[] HEADER_SEPARATOR = {0x0D, 0x0A, 0x0D, 0x0A};
+
+
+ /**
+ * A byte sequence that that follows a delimiter that will be
+ * followed by an encapsulation (<code>CRLF</code>).
+ */
+ protected static final byte[] FIELD_SEPARATOR = { 0x0D, 0x0A };
+
+
+ /**
+ * A byte sequence that that follows a delimiter of the last
+ * encapsulation in the stream (<code>--</code>).
+ */
+ protected static final byte[] STREAM_TERMINATOR = { 0x2D, 0x2D };
+
+
+ // ----------------------------------------------------------- Data members
+
+
+ /**
+ * The input stream from which data is read.
+ */
+ private InputStream input;
+
+
+ /**
+ * The length of the boundary token plus the leading <code>CRLF--</code>.
+ */
+ private int boundaryLength;
+
+
+ /**
+ * The amount of data, in bytes, that must be kept in the buffer in order
+ * to detect delimiters reliably.
+ */
+ private int keepRegion;
+
+
+ /**
+ * The byte sequence that partitions the stream.
+ */
+ private byte[] boundary;
+
+
+ /**
+ * The length of the buffer used for processing the request.
+ */
+ private int bufSize;
+
+
+ /**
+ * The buffer used for processing the request.
+ */
+ private byte[] buffer;
+
+
+ /**
+ * The index of first valid character in the buffer.
+ * <br>
+ * 0 <= head < bufSize
+ */
+ private int head;
+
+
+ /**
+ * The index of last valid characer in the buffer + 1.
+ * <br>
+ * 0 <= tail <= bufSize
+ */
+ private int tail;
+
+
+ /**
+ * The content encoding to use when reading headers.
+ */
+ private String headerEncoding;
+
+
+ // ----------------------------------------------------------- Constructors
+
+
+ /**
+ * Default constructor.
+ *
+ * @see #MultipartStream(InputStream, byte[], int)
+ * @see #MultipartStream(InputStream, byte[])
+ *
+ */
+ public MultipartStream()
+ {
+ }
+
+
+ /**
+ * <p> Constructs a <code>MultipartStream</code> with a custom size buffer.
+ *
+ * <p> Note that the buffer must be at least big enough to contain the
+ * boundary string, plus 4 characters for CR/LF and double dash, plus at
+ * least one byte of data. Too small a buffer size setting will degrade
+ * performance.
+ *
+ * @param input The <code>InputStream</code> to serve as a data source.
+ * @param boundary The token used for dividing the stream into
+ * <code>encapsulations</code>.
+ * @param bufSize The size of the buffer to be used, in bytes.
+ *
+ *
+ * @see #MultipartStream()
+ * @see #MultipartStream(InputStream, byte[])
+ *
+ */
+ public MultipartStream(InputStream input,
+ byte[] boundary,
+ int bufSize)
+ {
+ this.input = input;
+ this.bufSize = bufSize;
+ this.buffer = new byte[bufSize];
+
+ // We prepend CR/LF to the boundary to chop trailng CR/LF from
+ // body-data tokens.
+ this.boundary = new byte[boundary.length + 4];
+ this.boundaryLength = boundary.length + 4;
+ this.keepRegion = boundary.length + 3;
+ this.boundary[0] = 0x0D;
+ this.boundary[1] = 0x0A;
+ this.boundary[2] = 0x2D;
+ this.boundary[3] = 0x2D;
+ System.arraycopy(boundary, 0, this.boundary, 4, boundary.length);
+
+ head = 0;
+ tail = 0;
+ }
+
+
+ /**
+ * <p> Constructs a <code>MultipartStream</code> with a default size
buffer.
+ *
+ * @param input The <code>InputStream</code> to serve as a data source.
+ * @param boundary The token used for dividing the stream into
+ * <code>encapsulations</code>.
+ *
+ * @exception IOException when an error occurs.
+ *
+ * @see #MultipartStream()
+ * @see #MultipartStream(InputStream, byte[], int)
+ *
+ */
+ public MultipartStream(InputStream input,
+ byte[] boundary)
+ throws IOException
+ {
+ this(input, boundary, DEFAULT_BUFSIZE);
+ }
+
+
+ // --------------------------------------------------------- Public methods
+
+
+ /**
+ * Retrieves the character encoding used when reading the headers of an
+ * individual part. When not specified, or <code>null</code>, the platform
+ * default encoding is used.
+
+ *
+ * @return The encoding used to read part headers.
+ */
+ public String getHeaderEncoding()
+ {
+ return headerEncoding;
+ }
+
+
+ /**
+ * Specifies the character encoding to be used when reading the headers of
+ * individual parts. When not specified, or <code>null</code>, the platform
+ * default encoding is used.
+ *
+ * @param encoding The encoding used to read part headers.
+ */
+ public void setHeaderEncoding(String encoding)
+ {
+ headerEncoding = encoding;
+ }
+
+
+ /**
+ * Reads a byte from the <code>buffer</code>, and refills it as
+ * necessary.
+ *
+ * @return The next byte from the input stream.
+ *
+ * @exception IOException if there is no more data available.
+ */
+ public byte readByte()
+ throws IOException
+ {
+ // Buffer depleted ?
+ if (head == tail)
+ {
+ head = 0;
+ // Refill.
+ tail = input.read(buffer, head, bufSize);
+ if (tail == -1)
+ {
+ // No more data available.
+ throw new IOException("No more data is available");
+ }
+ }
+ return buffer[head++];
+ }
+
+
+ /**
+ * Skips a <code>boundary</code> token, and checks whether more
+ * <code>encapsulations</code> are contained in the stream.
+ *
+ * @return <code>true</code> if there are more encapsulations in
+ * this stream; <code>false</code> otherwise.
+ *
+ * @exception MalformedStreamException if the stream ends unexpecetedly or
+ * fails to follow required syntax.
+ */
+ public boolean readBoundary()
+ throws MalformedStreamException
+ {
+ byte[] marker = new byte[2];
+ boolean nextChunk = false;
+
+ head += boundaryLength;
+ try
+ {
+ marker[0] = readByte();
+ marker[1] = readByte();
+ if (arrayequals(marker, STREAM_TERMINATOR, 2))
+ {
+ nextChunk = false;
+ }
+ else if (arrayequals(marker, FIELD_SEPARATOR, 2))
+ {
+ nextChunk = true;
+ }
+ else
+ {
+ throw new MalformedStreamException(
+ "Unexpected characters follow a boundary");
+ }
+ }
+ catch (IOException e)
+ {
+ throw new MalformedStreamException("Stream ended unexpectedly");
+ }
+ return nextChunk;
+ }
+
+
+ /**
+ * <p>Changes the boundary token used for partitioning the stream.
+ *
+ * <p>This method allows single pass processing of nested multipart
+ * streams.
+ *
+ * <p>The boundary token of the nested stream is <code>required</code>
+ * to be of the same length as the boundary token in parent stream.
+ *
+ * <p>Restoring the parent stream boundary token after processing of a
+ * nested stream is left to the application.
+ *
+ * @param boundary The boundary to be used for parsing of the nested
+ * stream.
+ *
+ * @exception IllegalBoundaryException if the <code>boundary</code>
+ * has a different length than the one
+ * being currently parsed.
+ */
+ public void setBoundary(byte[] boundary)
+ throws IllegalBoundaryException
+ {
+ if (boundary.length != boundaryLength - 4)
+ {
+ throw new IllegalBoundaryException(
+ "The length of a boundary token can not be changed");
+ }
+ System.arraycopy(boundary, 0, this.boundary, 4, boundary.length);
+ }
+
+
+ /**
+ * <p>Reads the <code>header-part</code> of the current
+ * <code>encapsulation</code>.
+ *
+ * <p>Headers are returned verbatim to the input stream, including the
+ * trailing <code>CRLF</code> marker. Parsing is left to the
+ * application.
+ *
+ * <p><strong>TODO</strong> allow limiting maximum header size to
+ * protect against abuse.
+ *
+ * @return The <code>header-part</code> of the current encapsulation.
+ *
+ * @exception MalformedStreamException if the stream ends unexpecetedly.
+ */
+ public String readHeaders()
+ throws MalformedStreamException
+ {
+ int i = 0;
+ byte b[] = new byte[1];
+ // to support multi-byte characters
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ int sizeMax = HEADER_PART_SIZE_MAX;
+ int size = 0;
+ while (i < 4)
+ {
+ try
+ {
+ b[0] = readByte();
+ }
+ catch (IOException e)
+ {
+ throw new MalformedStreamException("Stream ended
unexpectedly");
+ }
+ size++;
+ if (b[0] == HEADER_SEPARATOR[i])
+ {
+ i++;
+ }
+ else
+ {
+ i = 0;
+ }
+ if (size <= sizeMax)
+ {
+ baos.write(b[0]);
+ }
+ }
+
+ String headers = null;
+ if (headerEncoding != null)
+ {
+ try
+ {
+ headers = baos.toString(headerEncoding);
+ }
+ catch (UnsupportedEncodingException e)
+ {
+ // Fall back to platform default if specified encoding is not
+ // supported.
+ headers = baos.toString();
+ }
+ }
+ else
+ {
+ headers = baos.toString();
+ }
+
+ return headers;
+ }
+
+
+ /**
+ * <p>Reads <code>body-data</code> from the current
+ * <code>encapsulation</code> and writes its contents into the
+ * output <code>Stream</code>.
+ *
+ * <p>Arbitrary large amounts of data can be processed by this
+ * method using a constant size buffer. (see [EMAIL PROTECTED]
+ * #MultipartStream(InputStream,byte[],int) constructor}).
+ *
+ * @param output The <code>Stream</code> to write data into.
+ *
+ * @return the amount of data written.
+ *
+ * @exception MalformedStreamException if the stream ends unexpectedly.
+ * @exception IOException if an i/o error occurs.
+ */
+ public int readBodyData(OutputStream output)
+ throws MalformedStreamException,
+ IOException
+ {
+ boolean done = false;
+ int pad;
+ int pos;
+ int bytesRead;
+ int total = 0;
+ while (!done)
+ {
+ // Is boundary token present somewere in the buffer?
+ pos = findSeparator();
+ if (pos != -1)
+ {
+ // Write the rest of the data before the boundary.
+ output.write(buffer, head, pos - head);
+ total += pos - head;
+ head = pos;
+ done = true;
+ }
+ else
+ {
+ // Determine how much data should be kept in the
+ // buffer.
+ if (tail - head > keepRegion)
+ {
+ pad = keepRegion;
+ }
+ else
+ {
+ pad = tail - head;
+ }
+ // Write out the data belonging to the body-data.
+ output.write(buffer, head, tail - head - pad);
+
+ // Move the data to the beging of the buffer.
+ total += tail - head - pad;
+ System.arraycopy(buffer, tail - pad, buffer, 0, pad);
+
+ // Refill buffer with new data.
+ head = 0;
+ bytesRead = input.read(buffer, pad, bufSize - pad);
+
+ // [pprrrrrrr]
+ if (bytesRead != -1)
+ {
+ tail = pad + bytesRead;
+ }
+ else
+ {
+ // The last pad amount is left in the buffer.
+ // Boundary can't be in there so write out the
+ // data you have and signal an error condition.
+ output.write(buffer, 0, pad);
+ output.flush();
+ total += pad;
+ throw new MalformedStreamException(
+ "Stream ended unexpectedly");
+ }
+ }
+ }
+ output.flush();
+ return total;
+ }
+
+
+ /**
+ * <p> Reads <code>body-data</code> from the current
+ * <code>encapsulation</code> and discards it.
+ *
+ * <p>Use this method to skip encapsulations you don't need or don't
+ * understand.
+ *
+ * @return The amount of data discarded.
+ *
+ * @exception MalformedStreamException if the stream ends unexpectedly.
+ * @exception IOException if an i/o error occurs.
+ */
+ public int discardBodyData()
+ throws MalformedStreamException,
+ IOException
+ {
+ boolean done = false;
+ int pad;
+ int pos;
+ int bytesRead;
+ int total = 0;
+ while (!done)
+ {
+ // Is boundary token present somewere in the buffer?
+ pos = findSeparator();
+ if (pos != -1)
+ {
+ // Write the rest of the data before the boundary.
+ total += pos - head;
+ head = pos;
+ done = true;
+ }
+ else
+ {
+ // Determine how much data should be kept in the
+ // buffer.
+ if (tail - head > keepRegion)
+ {
+ pad = keepRegion;
+ }
+ else
+ {
+ pad = tail - head;
+ }
+ total += tail - head - pad;
+
+ // Move the data to the beging of the buffer.
+ System.arraycopy(buffer, tail - pad, buffer, 0, pad);
+
+ // Refill buffer with new data.
+ head = 0;
+ bytesRead = input.read(buffer, pad, bufSize - pad);
+
+ // [pprrrrrrr]
+ if (bytesRead != -1)
+ {
+ tail = pad + bytesRead;
+ }
+ else
+ {
+ // The last pad amount is left in the buffer.
+ // Boundary can't be in there so signal an error
+ // condition.
+ total += pad;
+ throw new MalformedStreamException(
+ "Stream ended unexpectedly");
+ }
+ }
+ }
+ return total;
+ }
+
+
+ /**
+ * Finds the beginning of the first <code>encapsulation</code>.
+ *
+ * @return <code>true</code> if an <code>encapsulation</code> was found in
+ * the stream.
+ *
+ * @exception IOException if an i/o error occurs.
+ */
+ public boolean skipPreamble()
+ throws IOException
+ {
+ // First delimiter may be not preceeded with a CRLF.
+ System.arraycopy(boundary, 2, boundary, 0, boundary.length - 2);
+ boundaryLength = boundary.length - 2;
+ try
+ {
+ // Discard all data up to the delimiter.
+ discardBodyData();
+
+ // Read boundary - if succeded, the stream contains an
+ // encapsulation.
+ return readBoundary();
+ }
+ catch (MalformedStreamException e)
+ {
+ return false;
+ }
+ finally
+ {
+ // Restore delimiter.
+ System.arraycopy(boundary, 0, boundary, 2, boundary.length - 2);
+ boundaryLength = boundary.length;
+ boundary[0] = 0x0D;
+ boundary[1] = 0x0A;
+ }
+ }
+
+
+ /**
+ * Compares <code>count</code> first bytes in the arrays
+ * <code>a</code> and <code>b</code>.
+ *
+ * @param a The first array to compare.
+ * @param b The second array to compare.
+ * @param count How many bytes should be compared.
+ *
+ * @return <code>true</code> if <code>count</code> first bytes in arrays
+ * <code>a</code> and <code>b</code> are equal.
+ */
+ public static boolean arrayequals(byte[] a,
+ byte[] b,
+ int count)
+ {
+ for (int i = 0; i < count; i++)
+ {
+ if (a[i] != b[i])
+ {
+ return false;
+ }
+ }
+ return true;
+ }
+
+
+ /**
+ * Searches for a byte of specified value in the <code>buffer</code>,
+ * starting at the specified <code>position</code>.
+ *
+ * @param value The value to find.
+ * @param pos The starting position for searching.
+ *
+ * @return The position of byte found, counting from beginning of the
+ * <code>buffer</code>, or <code>-1</code> if not found.
+ */
+ protected int findByte(byte value,
+ int pos)
+ {
+ for (int i = pos; i < tail; i++)
+ {
+ if (buffer[i] == value)
+ {
+ return i;
+ }
+ }
+
+ return -1;
+ }
+
+
+ /**
+ * Searches for the <code>boundary</code> in the <code>buffer</code>
+ * region delimited by <code>head</code> and <code>tail</code>.
+ *
+ * @return The position of the boundary found, counting from the
+ * beginning of the <code>buffer</code>, or <code>-1</code> if
+ * not found.
+ */
+ protected int findSeparator()
+ {
+ int first;
+ int match = 0;
+ int maxpos = tail - boundaryLength;
+ for (first = head;
+ (first <= maxpos) && (match != boundaryLength);
+ first++)
+ {
+ first = findByte(boundary[0], first);
+ if (first == -1 || (first > maxpos))
+ {
+ return -1;
+ }
+ for (match = 1; match < boundaryLength; match++)
+ {
+ if (buffer[first + match] != boundary[match])
+ {
+ break;
+ }
+ }
+ }
+ if (match == boundaryLength)
+ {
+ return first - 1;
+ }
+ return -1;
+ }
+
+ /**
+ * Returns a string representation of this object.
+ *
+ * @return The string representation of this object.
+ */
+ public String toString()
+ {
+ StringBuffer sbTemp = new StringBuffer();
+ sbTemp.append("boundary='");
+ sbTemp.append(String.valueOf(boundary));
+ sbTemp.append("'\nbufSize=");
+ sbTemp.append(bufSize);
+ return sbTemp.toString();
+ }
+
+ /**
+ * Thrown to indicate that the input stream fails to follow the
+ * required syntax.
+ */
+ public class MalformedStreamException
+ extends IOException
+ {
+ /**
+ * Constructs a <code>MalformedStreamException</code> with no
+ * detail message.
+ */
+ public MalformedStreamException()
+ {
+ super();
+ }
+
+ /**
+ * Constructs an <code>MalformedStreamException</code> with
+ * the specified detail message.
+ *
+ * @param message The detail message.
+ */
+ public MalformedStreamException(String message)
+ {
+ super(message);
+ }
+ }
+
+
+ /**
+ * Thrown upon attempt of setting an invalid boundary token.
+ */
+ public class IllegalBoundaryException
+ extends IOException
+ {
+ /**
+ * Constructs an <code>IllegalBoundaryException</code> with no
+ * detail message.
+ */
+ public IllegalBoundaryException()
+ {
+ super();
+ }
+
+ /**
+ * Constructs an <code>IllegalBoundaryException</code> with
+ * the specified detail message.
+ *
+ * @param message The detail message.
+ */
+ public IllegalBoundaryException(String message)
+ {
+ super(message);
+ }
+ }
+
+
+ // ------------------------------------------------------ Debugging methods
+
+
+ // These are the methods that were used to debug this stuff.
+ /*
+
+ // Dump data.
+ protected void dump()
+ {
+ System.out.println("01234567890");
+ byte[] temp = new byte[buffer.length];
+ for(int i=0; i<buffer.length; i++)
+ {
+ if (buffer[i] == 0x0D || buffer[i] == 0x0A)
+ {
+ temp[i] = 0x21;
+ }
+ else
+ {
+ temp[i] = buffer[i];
+ }
+ }
+ System.out.println(new String(temp));
+ int i;
+ for (i=0; i<head; i++)
+ System.out.print(" ");
+ System.out.println("h");
+ for (i=0; i<tail; i++)
+ System.out.print(" ");
+ System.out.println("t");
+ System.out.flush();
+ }
+
+ // Main routine, for testing purposes only.
+ //
+ // @param args A String[] with the command line arguments.
+ // @exception Exception, a generic exception.
+ public static void main( String[] args )
+ throws Exception
+ {
+ File boundaryFile = new File("boundary.dat");
+ int boundarySize = (int)boundaryFile.length();
+ byte[] boundary = new byte[boundarySize];
+ FileInputStream input = new FileInputStream(boundaryFile);
+ input.read(boundary,0,boundarySize);
+
+ input = new FileInputStream("multipart.dat");
+ MultipartStream chunks = new MultipartStream(input, boundary);
+
+ int i = 0;
+ String header;
+ OutputStream output;
+ boolean nextChunk = chunks.skipPreamble();
+ while (nextChunk)
+ {
+ header = chunks.readHeaders();
+ System.out.println("!"+header+"!");
+ System.out.println("wrote part"+i+".dat");
+ output = new FileOutputStream("part"+(i++)+".dat");
+ chunks.readBodyData(output);
+ nextChunk = chunks.readBoundary();
+ }
+ }
+
+ */
+}
Propchange:
tomcat/tc6.0.x/trunk/java/org/apache/tomcat/util/http/fileupload/MultipartStream.java
------------------------------------------------------------------------------
svn:eol-style = native
Added:
tomcat/tc6.0.x/trunk/java/org/apache/tomcat/util/http/fileupload/ThresholdingOutputStream.java
URL:
http://svn.apache.org/viewvc/tomcat/tc6.0.x/trunk/java/org/apache/tomcat/util/http/fileupload/ThresholdingOutputStream.java?rev=412780&view=auto
==============================================================================
---
tomcat/tc6.0.x/trunk/java/org/apache/tomcat/util/http/fileupload/ThresholdingOutputStream.java
(added)
+++
tomcat/tc6.0.x/trunk/java/org/apache/tomcat/util/http/fileupload/ThresholdingOutputStream.java
Thu Jun 8 08:35:56 2006
@@ -0,0 +1,249 @@
+/*
+ * Copyright 2001-2006 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.tomcat.util.http.fileupload;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+
+/**
+ * An output stream which triggers an event when a specified number of bytes of
+ * data have been written to it. The event can be used, for example, to throw
+ * an exception if a maximum has been reached, or to switch the underlying
+ * stream type when the threshold is exceeded.
+ * <p>
+ * This class overrides all <code>OutputStream</code> methods. However, these
+ * overrides ultimately call the corresponding methods in the underlying output
+ * stream implementation.
+ * <p>
+ * NOTE: This implementation may trigger the event <em>before</em> the
threshold
+ * is actually reached, since it triggers when a pending write operation would
+ * cause the threshold to be exceeded.
+ *
+ * @author <a href="mailto:[EMAIL PROTECTED]">Martin Cooper</a>
+ *
+ * @version $Id: ThresholdingOutputStream.java,v 1.3 2003/05/31 22:31:08
martinc Exp $
+ */
+public abstract class ThresholdingOutputStream
+ extends OutputStream
+{
+
+ // ----------------------------------------------------------- Data members
+
+
+ /**
+ * The threshold at which the event will be triggered.
+ */
+ private int threshold;
+
+
+ /**
+ * The number of bytes written to the output stream.
+ */
+ private long written;
+
+
+ /**
+ * Whether or not the configured threshold has been exceeded.
+ */
+ private boolean thresholdExceeded;
+
+
+ // ----------------------------------------------------------- Constructors
+
+
+ /**
+ * Constructs an instance of this class which will trigger an event at the
+ * specified threshold.
+ *
+ * @param threshold The number of bytes at which to trigger an event.
+ */
+ public ThresholdingOutputStream(int threshold)
+ {
+ this.threshold = threshold;
+ }
+
+
+ // --------------------------------------------------- OutputStream methods
+
+
+ /**
+ * Writes the specified byte to this output stream.
+ *
+ * @param b The byte to be written.
+ *
+ * @exception IOException if an error occurs.
+ */
+ public void write(int b) throws IOException
+ {
+ checkThreshold(1);
+ getStream().write(b);
+ written++;
+ }
+
+
+ /**
+ * Writes <code>b.length</code> bytes from the specified byte array to this
+ * output stream.
+ *
+ * @param b The array of bytes to be written.
+ *
+ * @exception IOException if an error occurs.
+ */
+ public void write(byte b[]) throws IOException
+ {
+ checkThreshold(b.length);
+ getStream().write(b);
+ written += b.length;
+ }
+
+
+ /**
+ * Writes <code>len</code> bytes from the specified byte array starting at
+ * offset <code>off</code> to this output stream.
+ *
+ * @param b The byte array from which the data will be written.
+ * @param off The start offset in the byte array.
+ * @param len The number of bytes to write.
+ *
+ * @exception IOException if an error occurs.
+ */
+ public void write(byte b[], int off, int len) throws IOException
+ {
+ checkThreshold(len);
+ getStream().write(b, off, len);
+ written += len;
+ }
+
+
+ /**
+ * Flushes this output stream and forces any buffered output bytes to be
+ * written out.
+ *
+ * @exception IOException if an error occurs.
+ */
+ public void flush() throws IOException
+ {
+ getStream().flush();
+ }
+
+
+ /**
+ * Closes this output stream and releases any system resources associated
+ * with this stream.
+ *
+ * @exception IOException if an error occurs.
+ */
+ public void close() throws IOException
+ {
+ try
+ {
+ flush();
+ }
+ catch (IOException ignored)
+ {
+ // ignore
+ }
+ getStream().close();
+ }
+
+
+ // --------------------------------------------------------- Public methods
+
+
+ /**
+ * Returns the threshold, in bytes, at which an event will be triggered.
+ *
+ * @return The threshold point, in bytes.
+ */
+ public int getThreshold()
+ {
+ return threshold;
+ }
+
+
+ /**
+ * Returns the number of bytes that have been written to this output
stream.
+ *
+ * @return The number of bytes written.
+ */
+ public long getByteCount()
+ {
+ return written;
+ }
+
+
+ /**
+ * Determines whether or not the configured threshold has been exceeded for
+ * this output stream.
+ *
+ * @return <code>true</code> if the threshold has been reached;
+ * <code>false</code> otherwise.
+ */
+ public boolean isThresholdExceeded()
+ {
+ return (written > threshold);
+ }
+
+
+ // ------------------------------------------------------ Protected methods
+
+
+ /**
+ * Checks to see if writing the specified number of bytes would cause the
+ * configured threshold to be exceeded. If so, triggers an event to allow
+ * a concrete implementation to take action on this.
+ *
+ * @param count The number of bytes about to be written to the underlying
+ * output stream.
+ *
+ * @exception IOException if an error occurs.
+ */
+ protected void checkThreshold(int count) throws IOException
+ {
+ if (!thresholdExceeded && (written + count > threshold))
+ {
+ thresholdReached();
+ thresholdExceeded = true;
+ }
+ }
+
+
+ // ------------------------------------------------------- Abstract methods
+
+
+ /**
+ * Returns the underlying output stream, to which the corresponding
+ * <code>OutputStream</code> methods in this class will ultimately
delegate.
+ *
+ * @return The underlying output stream.
+ *
+ * @exception IOException if an error occurs.
+ */
+ protected abstract OutputStream getStream() throws IOException;
+
+
+ /**
+ * Indicates that the configured threshold has been reached, and that a
+ * subclass should take whatever action necessary on this event. This may
+ * include changing the underlying output stream.
+ *
+ * @exception IOException if an error occurs.
+ */
+ protected abstract void thresholdReached() throws IOException;
+}
Propchange:
tomcat/tc6.0.x/trunk/java/org/apache/tomcat/util/http/fileupload/ThresholdingOutputStream.java
------------------------------------------------------------------------------
svn:eol-style = native
Added:
tomcat/tc6.0.x/trunk/java/org/apache/tomcat/util/http/fileupload/package.html
URL:
http://svn.apache.org/viewvc/tomcat/tc6.0.x/trunk/java/org/apache/tomcat/util/http/fileupload/package.html?rev=412780&view=auto
==============================================================================
---
tomcat/tc6.0.x/trunk/java/org/apache/tomcat/util/http/fileupload/package.html
(added)
+++
tomcat/tc6.0.x/trunk/java/org/apache/tomcat/util/http/fileupload/package.html
Thu Jun 8 08:35:56 2006
@@ -0,0 +1,66 @@
+<!-- $Id: package.html,v 1.3 2003/04/27 17:30:06 martinc Exp $ -->
+<html>
+ <head>
+ <title>Overview of the org.apache.commons.fileupload component</title>
+ </head>
+ <body>
+ <p>
+ Component for handling html file uploads as given by rfc 1867
+ <a href="http://www.ietf.org/rfc/rfc1867.txt">RFC 1867</a>.
+ </p>
+ <p>
+ Normal usage of the package involves
+ [EMAIL PROTECTED] org.apache.commons.fileupload.DiskFileUpload
DiskFileUpload}
+ parsing the HttpServletRequest and returning a list of
+ [EMAIL PROTECTED] org.apache.commons.fileupload.FileItem FileItem}'s.
+ These <code>FileItem</code>'s provide easy access to the data
+ given in the upload. There is also a low level api for
+ manipulating the upload data encapsulated in the
+ [EMAIL PROTECTED] org.apache.commons.fileupload.MultipartStream
MultipartStream}
+ class.
+ </p>
+
+ <p>
+ Normal usage example:
+ </p>
+<pre>
+
+ public void doPost(HttpServletRequest req, HttpServletResponse res)
+ {
+ DiskFileUpload fu = new DiskFileUpload();
+ // maximum size before a FileUploadException will be thrown
+ fu.setSizeMax(1000000);
+ // maximum size that will be stored in memory
+ fu.setSizeThreshold(4096);
+ // the location for saving data that is larger than getSizeThreshold()
+ fu.setRepositoryPath("/tmp");
+
+ List fileItems = fu.parseRequest(req);
+ // assume we know there are two files. The first file is a small
+ // text file, the second is unknown and is written to a file on
+ // the server
+ Iterator i = fileItems.iterator();
+ String comment = ((FileItem)i.next()).getString();
+ FileItem fi = (FileItem)i.next();
+ // filename on the client
+ String fileName = fi.getName();
+ // save comment and filename to database
+ ...
+ // write the file
+ fi.write("/www/uploads/" + fileName);
+ }
+</pre>
+ <p>
+ In the example above the first file is loaded into memory as a
+ <code>String</code>. Before calling the getString method, the data
+ may have been in memory or on disk depending on its size. The second
+ file we assume it will be large and therefore never explicitly load
+ it into memory, though if it is less than 4096 bytes it will be
+ in memory before it is written to its final location. When writing to
+ the final location, if the data is larger than the
+ threshold, an attempt is made to rename the temporary file to
+ the given location. If it cannot be renamed, it is streamed to the
+ new location.
+ </p>
+ </body>
+</html>
Propchange:
tomcat/tc6.0.x/trunk/java/org/apache/tomcat/util/http/fileupload/package.html
------------------------------------------------------------------------------
svn:eol-style = native
---------------------------------------------------------------------
To unsubscribe, e-mail: [EMAIL PROTECTED]
For additional commands, e-mail: [EMAIL PROTECTED]