0001: /*
0002: * Licensed to the Apache Software Foundation (ASF) under one or more
0003: * contributor license agreements. See the NOTICE file distributed with
0004: * this work for additional information regarding copyright ownership.
0005: * The ASF licenses this file to You under the Apache License, Version 2.0
0006: * (the "License"); you may not use this file except in compliance with
0007: * the License. You may obtain a copy of the License at
0008: *
0009: * http://www.apache.org/licenses/LICENSE-2.0
0010: *
0011: * Unless required by applicable law or agreed to in writing, software
0012: * distributed under the License is distributed on an "AS IS" BASIS,
0013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0014: * See the License for the specific language governing permissions and
0015: * limitations under the License.
0016: */
0017: package org.apache.commons.fileupload;
0018:
0019: import java.io.ByteArrayOutputStream;
0020: import java.io.IOException;
0021: import java.io.InputStream;
0022: import java.io.OutputStream;
0023: import java.io.UnsupportedEncodingException;
0024:
0025: import org.apache.commons.fileupload.util.Closeable;
0026: import org.apache.commons.fileupload.util.Streams;
0027:
0028: /**
0029: * <p> Low level API for processing file uploads.
0030: *
0031: * <p> This class can be used to process data streams conforming to MIME
0032: * 'multipart' format as defined in
0033: * <a href="http://www.ietf.org/rfc/rfc1867.txt">RFC 1867</a>. Arbitrarily
0034: * large amounts of data in the stream can be processed under constant
0035: * memory usage.
0036: *
0037: * <p> The format of the stream is defined in the following way:<br>
0038: *
0039: * <code>
0040: * multipart-body := preamble 1*encapsulation close-delimiter epilogue<br>
0041: * encapsulation := delimiter body CRLF<br>
0042: * delimiter := "--" boundary CRLF<br>
0043: * close-delimiter := "--" boudary "--"<br>
0044: * preamble := <ignore><br>
0045: * epilogue := <ignore><br>
0046: * body := header-part CRLF body-part<br>
0047: * header-part := 1*header CRLF<br>
0048: * header := header-name ":" header-value<br>
0049: * header-name := <printable ascii characters except ":"><br>
0050: * header-value := <any ascii characters except CR & LF><br>
0051: * body-data := <arbitrary data><br>
0052: * </code>
0053: *
0054: * <p>Note that body-data can contain another mulipart entity. There
0055: * is limited support for single pass processing of such nested
0056: * streams. The nested stream is <strong>required</strong> to have a
0057: * boundary token of the same length as the parent stream (see {@link
0058: * #setBoundary(byte[])}).
0059: *
0060: * <p>Here is an example of usage of this class.<br>
0061: *
0062: * <pre>
0063: * try {
0064: * MultipartStream multipartStream = new MultipartStream(input,
0065: * boundary);
0066: * boolean nextPart = multipartStream.skipPreamble();
0067: * OutputStream output;
0068: * while(nextPart) {
0069: * header = chunks.readHeader();
0070: * // process headers
0071: * // create some output stream
0072: * multipartStream.readBodyPart(output);
0073: * nextPart = multipartStream.readBoundary();
0074: * }
0075: * } catch(MultipartStream.MalformedStreamException e) {
0076: * // the stream failed to follow required syntax
0077: * } catch(IOException) {
0078: * // a read or write error occurred
0079: * }
0080: *
0081: * </pre>
0082: *
0083: * @author <a href="mailto:Rafal.Krzewski@e-point.pl">Rafal Krzewski</a>
0084: * @author <a href="mailto:martinc@apache.org">Martin Cooper</a>
0085: * @author Sean C. Sullivan
0086: *
0087: * @version $Id: MultipartStream.java 502350 2007-02-01 20:42:48Z jochen $
0088: */
0089: public class MultipartStream {
0090: /**
0091: * Internal class, which is used to invoke the
0092: * {@link ProgressListener}.
0093: */
0094: static class ProgressNotifier {
0095: /** The listener to invoke.
0096: */
0097: private final ProgressListener listener;
0098: /** Number of expected bytes, if known, or -1.
0099: */
0100: private final long contentLength;
0101: /** Number of bytes, which have been read so far.
0102: */
0103: private long bytesRead;
0104: /** Number of items, which have been read so far.
0105: */
0106: private int items;
0107:
0108: /** Creates a new instance with the given listener
0109: * and content length.
0110: * @param pListener The listener to invoke.
0111: * @param pContentLength The expected content length.
0112: */
0113: ProgressNotifier(ProgressListener pListener, long pContentLength) {
0114: listener = pListener;
0115: contentLength = pContentLength;
0116: }
0117:
0118: /** Called to indicate that bytes have been read.
0119: * @param pBytes Number of bytes, which have been read.
0120: */
0121: void noteBytesRead(int pBytes) {
0122: /* Indicates, that the given number of bytes have been read from
0123: * the input stream.
0124: */
0125: bytesRead += pBytes;
0126: notifyListener();
0127: }
0128:
0129: /** Called to indicate, that a new file item has been detected.
0130: */
0131: void noteItem() {
0132: ++items;
0133: }
0134:
0135: /** Called for notifying the listener.
0136: */
0137: private void notifyListener() {
0138: if (listener != null) {
0139: listener.update(bytesRead, contentLength, items);
0140: }
0141: }
0142: }
0143:
0144: // ----------------------------------------------------- Manifest constants
0145:
0146: /**
0147: * The Carriage Return ASCII character value.
0148: */
0149: public static final byte CR = 0x0D;
0150:
0151: /**
0152: * The Line Feed ASCII character value.
0153: */
0154: public static final byte LF = 0x0A;
0155:
0156: /**
0157: * The dash (-) ASCII character value.
0158: */
0159: public static final byte DASH = 0x2D;
0160:
0161: /**
0162: * The maximum length of <code>header-part</code> that will be
0163: * processed (10 kilobytes = 10240 bytes.).
0164: */
0165: public static final int HEADER_PART_SIZE_MAX = 10240;
0166:
0167: /**
0168: * The default length of the buffer used for processing a request.
0169: */
0170: protected static final int DEFAULT_BUFSIZE = 4096;
0171:
0172: /**
0173: * A byte sequence that marks the end of <code>header-part</code>
0174: * (<code>CRLFCRLF</code>).
0175: */
0176: protected static final byte[] HEADER_SEPARATOR = { CR, LF, CR, LF };
0177:
0178: /**
0179: * A byte sequence that that follows a delimiter that will be
0180: * followed by an encapsulation (<code>CRLF</code>).
0181: */
0182: protected static final byte[] FIELD_SEPARATOR = { CR, LF };
0183:
0184: /**
0185: * A byte sequence that that follows a delimiter of the last
0186: * encapsulation in the stream (<code>--</code>).
0187: */
0188: protected static final byte[] STREAM_TERMINATOR = { DASH, DASH };
0189:
0190: /**
0191: * A byte sequence that precedes a boundary (<code>CRLF--</code>).
0192: */
0193: protected static final byte[] BOUNDARY_PREFIX = { CR, LF, DASH,
0194: DASH };
0195:
0196: /**
0197: * The number of bytes, over and above the boundary size, to use for the
0198: * keep region.
0199: */
0200: private static final int KEEP_REGION_PAD = 3;
0201:
0202: // ----------------------------------------------------------- Data members
0203:
0204: /**
0205: * The input stream from which data is read.
0206: */
0207: private final InputStream input;
0208:
0209: /**
0210: * The length of the boundary token plus the leading <code>CRLF--</code>.
0211: */
0212: private int boundaryLength;
0213:
0214: /**
0215: * The amount of data, in bytes, that must be kept in the buffer in order
0216: * to detect delimiters reliably.
0217: */
0218: private int keepRegion;
0219:
0220: /**
0221: * The byte sequence that partitions the stream.
0222: */
0223: private byte[] boundary;
0224:
0225: /**
0226: * The length of the buffer used for processing the request.
0227: */
0228: private final int bufSize;
0229:
0230: /**
0231: * The buffer used for processing the request.
0232: */
0233: private final byte[] buffer;
0234:
0235: /**
0236: * The index of first valid character in the buffer.
0237: * <br>
0238: * 0 <= head < bufSize
0239: */
0240: private int head;
0241:
0242: /**
0243: * The index of last valid characer in the buffer + 1.
0244: * <br>
0245: * 0 <= tail <= bufSize
0246: */
0247: private int tail;
0248:
0249: /**
0250: * The content encoding to use when reading headers.
0251: */
0252: private String headerEncoding;
0253:
0254: /**
0255: * The progress notifier, if any, or null.
0256: */
0257: private final ProgressNotifier notifier;
0258:
0259: // ----------------------------------------------------------- Constructors
0260:
0261: /**
0262: * Creates a new instance.
0263: * @deprecated Use {@link #MultipartStream(InputStream, byte[],
0264: * org.apache.commons.fileupload.MultipartStream.ProgressNotifier)},
0265: * or {@link #MultipartStream(InputStream, byte[], int,
0266: * org.apache.commons.fileupload.MultipartStream.ProgressNotifier)}
0267: */
0268: public MultipartStream() {
0269: this (null, null, null);
0270: }
0271:
0272: /**
0273: * <p> Constructs a <code>MultipartStream</code> with a custom size buffer
0274: * and no progress notifier.
0275: *
0276: * <p> Note that the buffer must be at least big enough to contain the
0277: * boundary string, plus 4 characters for CR/LF and double dash, plus at
0278: * least one byte of data. Too small a buffer size setting will degrade
0279: * performance.
0280: *
0281: * @param input The <code>InputStream</code> to serve as a data source.
0282: * @param boundary The token used for dividing the stream into
0283: * <code>encapsulations</code>.
0284: * @param bufSize The size of the buffer to be used, in bytes.
0285: *
0286: * @see #MultipartStream(InputStream, byte[], ProgressNotifier)
0287: * @deprecated Use {@link #MultipartStream(InputStream, byte[], int,
0288: * org.apache.commons.fileupload.MultipartStream.ProgressNotifier)}.
0289: */
0290: public MultipartStream(InputStream input, byte[] boundary,
0291: int bufSize) {
0292: this (input, boundary, bufSize, null);
0293: }
0294:
0295: /**
0296: * <p> Constructs a <code>MultipartStream</code> with a custom size buffer.
0297: *
0298: * <p> Note that the buffer must be at least big enough to contain the
0299: * boundary string, plus 4 characters for CR/LF and double dash, plus at
0300: * least one byte of data. Too small a buffer size setting will degrade
0301: * performance.
0302: *
0303: * @param input The <code>InputStream</code> to serve as a data source.
0304: * @param boundary The token used for dividing the stream into
0305: * <code>encapsulations</code>.
0306: * @param bufSize The size of the buffer to be used, in bytes.
0307: * @param pNotifier The notifier, which is used for calling the
0308: * progress listener, if any.
0309: *
0310: * @see #MultipartStream(InputStream, byte[], ProgressNotifier)
0311: */
0312: MultipartStream(InputStream input, byte[] boundary, int bufSize,
0313: ProgressNotifier pNotifier) {
0314: this .input = input;
0315: this .bufSize = bufSize;
0316: this .buffer = new byte[bufSize];
0317: this .notifier = pNotifier;
0318:
0319: // We prepend CR/LF to the boundary to chop trailng CR/LF from
0320: // body-data tokens.
0321: this .boundary = new byte[boundary.length
0322: + BOUNDARY_PREFIX.length];
0323: this .boundaryLength = boundary.length + BOUNDARY_PREFIX.length;
0324: this .keepRegion = boundary.length + KEEP_REGION_PAD;
0325: System.arraycopy(BOUNDARY_PREFIX, 0, this .boundary, 0,
0326: BOUNDARY_PREFIX.length);
0327: System.arraycopy(boundary, 0, this .boundary,
0328: BOUNDARY_PREFIX.length, boundary.length);
0329:
0330: head = 0;
0331: tail = 0;
0332: }
0333:
0334: /**
0335: * <p> Constructs a <code>MultipartStream</code> with a default size buffer.
0336: *
0337: * @param input The <code>InputStream</code> to serve as a data source.
0338: * @param boundary The token used for dividing the stream into
0339: * <code>encapsulations</code>.
0340: * @param pNotifier An object for calling the progress listener, if any.
0341: *
0342: *
0343: * @see #MultipartStream(InputStream, byte[], int, ProgressNotifier)
0344: */
0345: MultipartStream(InputStream input, byte[] boundary,
0346: ProgressNotifier pNotifier) {
0347: this (input, boundary, DEFAULT_BUFSIZE, pNotifier);
0348: }
0349:
0350: /**
0351: * <p> Constructs a <code>MultipartStream</code> with a default size buffer.
0352: *
0353: * @param input The <code>InputStream</code> to serve as a data source.
0354: * @param boundary The token used for dividing the stream into
0355: * <code>encapsulations</code>.
0356: *
0357: * @deprecated Use {@link #MultipartStream(InputStream, byte[],
0358: * ProgressNotifier)}.
0359: * @see #MultipartStream(InputStream, byte[], int, ProgressNotifier)
0360: */
0361: public MultipartStream(InputStream input, byte[] boundary) {
0362: this (input, boundary, DEFAULT_BUFSIZE, null);
0363: }
0364:
0365: // --------------------------------------------------------- Public methods
0366:
0367: /**
0368: * Retrieves the character encoding used when reading the headers of an
0369: * individual part. When not specified, or <code>null</code>, the platform
0370: * default encoding is used.
0371:
0372: *
0373: * @return The encoding used to read part headers.
0374: */
0375: public String getHeaderEncoding() {
0376: return headerEncoding;
0377: }
0378:
0379: /**
0380: * Specifies the character encoding to be used when reading the headers of
0381: * individual parts. When not specified, or <code>null</code>, the platform
0382: * default encoding is used.
0383: *
0384: * @param encoding The encoding used to read part headers.
0385: */
0386: public void setHeaderEncoding(String encoding) {
0387: headerEncoding = encoding;
0388: }
0389:
0390: /**
0391: * Reads a byte from the <code>buffer</code>, and refills it as
0392: * necessary.
0393: *
0394: * @return The next byte from the input stream.
0395: *
0396: * @throws IOException if there is no more data available.
0397: */
0398: public byte readByte() throws IOException {
0399: // Buffer depleted ?
0400: if (head == tail) {
0401: head = 0;
0402: // Refill.
0403: tail = input.read(buffer, head, bufSize);
0404: if (tail == -1) {
0405: // No more data available.
0406: throw new IOException("No more data is available");
0407: }
0408: notifier.noteBytesRead(tail);
0409: }
0410: return buffer[head++];
0411: }
0412:
0413: /**
0414: * Skips a <code>boundary</code> token, and checks whether more
0415: * <code>encapsulations</code> are contained in the stream.
0416: *
0417: * @return <code>true</code> if there are more encapsulations in
0418: * this stream; <code>false</code> otherwise.
0419: *
0420: * @throws MalformedStreamException if the stream ends unexpecetedly or
0421: * fails to follow required syntax.
0422: */
0423: public boolean readBoundary() throws MalformedStreamException {
0424: byte[] marker = new byte[2];
0425: boolean nextChunk = false;
0426:
0427: head += boundaryLength;
0428: try {
0429: marker[0] = readByte();
0430: if (marker[0] == LF) {
0431: // Work around IE5 Mac bug with input type=image.
0432: // Because the boundary delimiter, not including the trailing
0433: // CRLF, must not appear within any file (RFC 2046, section
0434: // 5.1.1), we know the missing CR is due to a buggy browser
0435: // rather than a file containing something similar to a
0436: // boundary.
0437: return true;
0438: }
0439:
0440: marker[1] = readByte();
0441: if (arrayequals(marker, STREAM_TERMINATOR, 2)) {
0442: nextChunk = false;
0443: } else if (arrayequals(marker, FIELD_SEPARATOR, 2)) {
0444: nextChunk = true;
0445: } else {
0446: throw new MalformedStreamException(
0447: "Unexpected characters follow a boundary");
0448: }
0449: } catch (IOException e) {
0450: throw new MalformedStreamException(
0451: "Stream ended unexpectedly");
0452: }
0453: return nextChunk;
0454: }
0455:
0456: /**
0457: * <p>Changes the boundary token used for partitioning the stream.
0458: *
0459: * <p>This method allows single pass processing of nested multipart
0460: * streams.
0461: *
0462: * <p>The boundary token of the nested stream is <code>required</code>
0463: * to be of the same length as the boundary token in parent stream.
0464: *
0465: * <p>Restoring the parent stream boundary token after processing of a
0466: * nested stream is left to the application.
0467: *
0468: * @param boundary The boundary to be used for parsing of the nested
0469: * stream.
0470: *
0471: * @throws IllegalBoundaryException if the <code>boundary</code>
0472: * has a different length than the one
0473: * being currently parsed.
0474: */
0475: public void setBoundary(byte[] boundary)
0476: throws IllegalBoundaryException {
0477: if (boundary.length != boundaryLength - BOUNDARY_PREFIX.length) {
0478: throw new IllegalBoundaryException(
0479: "The length of a boundary token can not be changed");
0480: }
0481: System.arraycopy(boundary, 0, this .boundary,
0482: BOUNDARY_PREFIX.length, boundary.length);
0483: }
0484:
0485: /**
0486: * <p>Reads the <code>header-part</code> of the current
0487: * <code>encapsulation</code>.
0488: *
0489: * <p>Headers are returned verbatim to the input stream, including the
0490: * trailing <code>CRLF</code> marker. Parsing is left to the
0491: * application.
0492: *
0493: * <p><strong>TODO</strong> allow limiting maximum header size to
0494: * protect against abuse.
0495: *
0496: * @return The <code>header-part</code> of the current encapsulation.
0497: *
0498: * @throws MalformedStreamException if the stream ends unexpecetedly.
0499: */
0500: public String readHeaders() throws MalformedStreamException {
0501: int i = 0;
0502: byte[] b = new byte[1];
0503: // to support multi-byte characters
0504: ByteArrayOutputStream baos = new ByteArrayOutputStream();
0505: int sizeMax = HEADER_PART_SIZE_MAX;
0506: int size = 0;
0507: while (i < HEADER_SEPARATOR.length) {
0508: try {
0509: b[0] = readByte();
0510: } catch (IOException e) {
0511: throw new MalformedStreamException(
0512: "Stream ended unexpectedly");
0513: }
0514: size++;
0515: if (b[0] == HEADER_SEPARATOR[i]) {
0516: i++;
0517: } else {
0518: i = 0;
0519: }
0520: if (size <= sizeMax) {
0521: baos.write(b[0]);
0522: }
0523: }
0524:
0525: String headers = null;
0526: if (headerEncoding != null) {
0527: try {
0528: headers = baos.toString(headerEncoding);
0529: } catch (UnsupportedEncodingException e) {
0530: // Fall back to platform default if specified encoding is not
0531: // supported.
0532: headers = baos.toString();
0533: }
0534: } else {
0535: headers = baos.toString();
0536: }
0537:
0538: return headers;
0539: }
0540:
0541: /**
0542: * <p>Reads <code>body-data</code> from the current
0543: * <code>encapsulation</code> and writes its contents into the
0544: * output <code>Stream</code>.
0545: *
0546: * <p>Arbitrary large amounts of data can be processed by this
0547: * method using a constant size buffer. (see {@link
0548: * #MultipartStream(InputStream,byte[],int, ProgressNotifier) constructor}).
0549: *
0550: * @param output The <code>Stream</code> to write data into. May
0551: * be null, in which case this method is equivalent
0552: * to {@link #discardBodyData()}.
0553: *
0554: * @return the amount of data written.
0555: *
0556: * @throws MalformedStreamException if the stream ends unexpectedly.
0557: * @throws IOException if an i/o error occurs.
0558: */
0559: public int readBodyData(OutputStream output)
0560: throws MalformedStreamException, IOException {
0561: final InputStream istream = newInputStream();
0562: return (int) Streams.copy(istream, output, false);
0563: }
0564:
0565: /**
0566: * Creates a new {@link ItemInputStream}.
0567: * @return A new instance of {@link ItemInputStream}.
0568: */
0569: ItemInputStream newInputStream() {
0570: return new ItemInputStream();
0571: }
0572:
0573: /**
0574: * <p> Reads <code>body-data</code> from the current
0575: * <code>encapsulation</code> and discards it.
0576: *
0577: * <p>Use this method to skip encapsulations you don't need or don't
0578: * understand.
0579: *
0580: * @return The amount of data discarded.
0581: *
0582: * @throws MalformedStreamException if the stream ends unexpectedly.
0583: * @throws IOException if an i/o error occurs.
0584: */
0585: public int discardBodyData() throws MalformedStreamException,
0586: IOException {
0587: return readBodyData(null);
0588: }
0589:
0590: /**
0591: * Finds the beginning of the first <code>encapsulation</code>.
0592: *
0593: * @return <code>true</code> if an <code>encapsulation</code> was found in
0594: * the stream.
0595: *
0596: * @throws IOException if an i/o error occurs.
0597: */
0598: public boolean skipPreamble() throws IOException {
0599: // First delimiter may be not preceeded with a CRLF.
0600: System.arraycopy(boundary, 2, boundary, 0, boundary.length - 2);
0601: boundaryLength = boundary.length - 2;
0602: try {
0603: // Discard all data up to the delimiter.
0604: discardBodyData();
0605:
0606: // Read boundary - if succeded, the stream contains an
0607: // encapsulation.
0608: return readBoundary();
0609: } catch (MalformedStreamException e) {
0610: return false;
0611: } finally {
0612: // Restore delimiter.
0613: System.arraycopy(boundary, 0, boundary, 2,
0614: boundary.length - 2);
0615: boundaryLength = boundary.length;
0616: boundary[0] = CR;
0617: boundary[1] = LF;
0618: }
0619: }
0620:
0621: /**
0622: * Compares <code>count</code> first bytes in the arrays
0623: * <code>a</code> and <code>b</code>.
0624: *
0625: * @param a The first array to compare.
0626: * @param b The second array to compare.
0627: * @param count How many bytes should be compared.
0628: *
0629: * @return <code>true</code> if <code>count</code> first bytes in arrays
0630: * <code>a</code> and <code>b</code> are equal.
0631: */
0632: public static boolean arrayequals(byte[] a, byte[] b, int count) {
0633: for (int i = 0; i < count; i++) {
0634: if (a[i] != b[i]) {
0635: return false;
0636: }
0637: }
0638: return true;
0639: }
0640:
0641: /**
0642: * Searches for a byte of specified value in the <code>buffer</code>,
0643: * starting at the specified <code>position</code>.
0644: *
0645: * @param value The value to find.
0646: * @param pos The starting position for searching.
0647: *
0648: * @return The position of byte found, counting from beginning of the
0649: * <code>buffer</code>, or <code>-1</code> if not found.
0650: */
0651: protected int findByte(byte value, int pos) {
0652: for (int i = pos; i < tail; i++) {
0653: if (buffer[i] == value) {
0654: return i;
0655: }
0656: }
0657:
0658: return -1;
0659: }
0660:
0661: /**
0662: * Searches for the <code>boundary</code> in the <code>buffer</code>
0663: * region delimited by <code>head</code> and <code>tail</code>.
0664: *
0665: * @return The position of the boundary found, counting from the
0666: * beginning of the <code>buffer</code>, or <code>-1</code> if
0667: * not found.
0668: */
0669: protected int findSeparator() {
0670: int first;
0671: int match = 0;
0672: int maxpos = tail - boundaryLength;
0673: for (first = head; (first <= maxpos)
0674: && (match != boundaryLength); first++) {
0675: first = findByte(boundary[0], first);
0676: if (first == -1 || (first > maxpos)) {
0677: return -1;
0678: }
0679: for (match = 1; match < boundaryLength; match++) {
0680: if (buffer[first + match] != boundary[match]) {
0681: break;
0682: }
0683: }
0684: }
0685: if (match == boundaryLength) {
0686: return first - 1;
0687: }
0688: return -1;
0689: }
0690:
0691: /**
0692: * Thrown to indicate that the input stream fails to follow the
0693: * required syntax.
0694: */
0695: public static class MalformedStreamException extends IOException {
0696: /**
0697: * Constructs a <code>MalformedStreamException</code> with no
0698: * detail message.
0699: */
0700: public MalformedStreamException() {
0701: super ();
0702: }
0703:
0704: /**
0705: * Constructs an <code>MalformedStreamException</code> with
0706: * the specified detail message.
0707: *
0708: * @param message The detail message.
0709: */
0710: public MalformedStreamException(String message) {
0711: super (message);
0712: }
0713: }
0714:
0715: /**
0716: * Thrown upon attempt of setting an invalid boundary token.
0717: */
0718: public static class IllegalBoundaryException extends IOException {
0719: /**
0720: * Constructs an <code>IllegalBoundaryException</code> with no
0721: * detail message.
0722: */
0723: public IllegalBoundaryException() {
0724: super ();
0725: }
0726:
0727: /**
0728: * Constructs an <code>IllegalBoundaryException</code> with
0729: * the specified detail message.
0730: *
0731: * @param message The detail message.
0732: */
0733: public IllegalBoundaryException(String message) {
0734: super (message);
0735: }
0736: }
0737:
0738: /**
0739: * An {@link InputStream} for reading an items contents.
0740: */
0741: public class ItemInputStream extends InputStream implements
0742: Closeable {
0743: /** The number of bytes, which have been read so far.
0744: */
0745: private long total;
0746: /** The number of bytes, which must be hold, because
0747: * they might be a part of the boundary.
0748: */
0749: private int pad;
0750: /** The current offset in the buffer.
0751: */
0752: private int pos;
0753: /** Whether the stream is already closed.
0754: */
0755: private boolean closed;
0756:
0757: /**
0758: * Creates a new instance.
0759: */
0760: ItemInputStream() {
0761: findSeparator();
0762: }
0763:
0764: /**
0765: * Called for finding the separator.
0766: */
0767: private void findSeparator() {
0768: pos = MultipartStream.this .findSeparator();
0769: if (pos == -1) {
0770: if (tail - head > keepRegion) {
0771: pad = keepRegion;
0772: } else {
0773: pad = tail - head;
0774: }
0775: }
0776: }
0777:
0778: /**
0779: * Returns the number of bytes, which have been read
0780: * by the stream.
0781: * @return Number of bytes, which have been read so far.
0782: */
0783: public long getBytesRead() {
0784: return total;
0785: }
0786:
0787: /**
0788: * Returns the number of bytes, which are currently
0789: * available, without blocking.
0790: * @throws IOException An I/O error occurs.
0791: * @return Number of bytes in the buffer.
0792: */
0793: public int available() throws IOException {
0794: if (pos == -1) {
0795: return tail - head - pad;
0796: }
0797: return pos - head;
0798: }
0799:
0800: /** Offset when converting negative bytes to integers.
0801: */
0802: private static final int BYTE_POSITIVE_OFFSET = 256;
0803:
0804: /**
0805: * Returns the next byte in the stream.
0806: * @return The next byte in the stream, as a non-negative
0807: * integer, or -1 for EOF.
0808: * @throws IOException An I/O error occurred.
0809: */
0810: public int read() throws IOException {
0811: if (closed) {
0812: throw new FileItemStream.ItemSkippedException();
0813: }
0814: if (available() == 0) {
0815: if (makeAvailable() == 0) {
0816: return -1;
0817: }
0818: }
0819: ++total;
0820: int b = buffer[head++];
0821: if (b >= 0) {
0822: return b;
0823: }
0824: return b + BYTE_POSITIVE_OFFSET;
0825: }
0826:
0827: /**
0828: * Reads bytes into the given buffer.
0829: * @param b The destination buffer, where to write to.
0830: * @param off Offset of the first byte in the buffer.
0831: * @param len Maximum number of bytes to read.
0832: * @return Number of bytes, which have been actually read,
0833: * or -1 for EOF.
0834: * @throws IOException An I/O error occurred.
0835: */
0836: public int read(byte[] b, int off, int len) throws IOException {
0837: if (closed) {
0838: throw new FileItemStream.ItemSkippedException();
0839: }
0840: if (len == 0) {
0841: return 0;
0842: }
0843: int res = available();
0844: if (res == 0) {
0845: res = makeAvailable();
0846: if (res == 0) {
0847: return -1;
0848: }
0849: }
0850: res = Math.min(res, len);
0851: System.arraycopy(buffer, head, b, off, res);
0852: head += res;
0853: total += res;
0854: return res;
0855: }
0856:
0857: /**
0858: * Closes the input stream.
0859: * @throws IOException An I/O error occurred.
0860: */
0861: public void close() throws IOException {
0862: if (closed) {
0863: return;
0864: }
0865: for (;;) {
0866: int av = available();
0867: if (av == 0) {
0868: av = makeAvailable();
0869: if (av == 0) {
0870: break;
0871: }
0872: }
0873: skip(av);
0874: }
0875: closed = true;
0876: }
0877:
0878: /**
0879: * Skips the given number of bytes.
0880: * @param bytes Number of bytes to skip.
0881: * @return The number of bytes, which have actually been
0882: * skipped.
0883: * @throws IOException An I/O error occurred.
0884: */
0885: public long skip(long bytes) throws IOException {
0886: if (closed) {
0887: throw new FileItemStream.ItemSkippedException();
0888: }
0889: int av = available();
0890: if (av == 0) {
0891: av = makeAvailable();
0892: if (av == 0) {
0893: return 0;
0894: }
0895: }
0896: long res = Math.min(av, bytes);
0897: head += res;
0898: return res;
0899: }
0900:
0901: /**
0902: * Attempts to read more data.
0903: * @return Number of available bytes
0904: * @throws IOException An I/O error occurred.
0905: */
0906: private int makeAvailable() throws IOException {
0907: if (pos != -1) {
0908: return 0;
0909: }
0910:
0911: // Move the data to the beginning of the buffer.
0912: total += tail - head - pad;
0913: System.arraycopy(buffer, tail - pad, buffer, 0, pad);
0914:
0915: // Refill buffer with new data.
0916: head = 0;
0917: int bytesRead = input.read(buffer, pad, bufSize - pad);
0918: if (bytesRead == -1) {
0919: // The last pad amount is left in the buffer.
0920: // Boundary can't be in there so signal an error
0921: // condition.
0922: throw new MalformedStreamException(
0923: "Stream ended unexpectedly");
0924: }
0925: notifier.noteBytesRead(bytesRead);
0926: tail = pad + bytesRead;
0927: findSeparator();
0928: return available();
0929: }
0930:
0931: /**
0932: * Returns, whether the stream is closed.
0933: * @return True, if the stream is closed, otherwise false.
0934: */
0935: public boolean isClosed() {
0936: return closed;
0937: }
0938: }
0939:
0940: // ------------------------------------------------------ Debugging methods
0941:
0942: // These are the methods that were used to debug this stuff.
0943: /*
0944:
0945: // Dump data.
0946: protected void dump()
0947: {
0948: System.out.println("01234567890");
0949: byte[] temp = new byte[buffer.length];
0950: for(int i=0; i<buffer.length; i++)
0951: {
0952: if (buffer[i] == 0x0D || buffer[i] == 0x0A)
0953: {
0954: temp[i] = 0x21;
0955: }
0956: else
0957: {
0958: temp[i] = buffer[i];
0959: }
0960: }
0961: System.out.println(new String(temp));
0962: int i;
0963: for (i=0; i<head; i++)
0964: System.out.print(" ");
0965: System.out.println("h");
0966: for (i=0; i<tail; i++)
0967: System.out.print(" ");
0968: System.out.println("t");
0969: System.out.flush();
0970: }
0971:
0972: // Main routine, for testing purposes only.
0973: //
0974: // @param args A String[] with the command line arguments.
0975: // @throws Exception, a generic exception.
0976: public static void main( String[] args )
0977: throws Exception
0978: {
0979: File boundaryFile = new File("boundary.dat");
0980: int boundarySize = (int)boundaryFile.length();
0981: byte[] boundary = new byte[boundarySize];
0982: FileInputStream input = new FileInputStream(boundaryFile);
0983: input.read(boundary,0,boundarySize);
0984:
0985: input = new FileInputStream("multipart.dat");
0986: MultipartStream chunks = new MultipartStream(input, boundary);
0987:
0988: int i = 0;
0989: String header;
0990: OutputStream output;
0991: boolean nextChunk = chunks.skipPreamble();
0992: while (nextChunk)
0993: {
0994: header = chunks.readHeaders();
0995: System.out.println("!"+header+"!");
0996: System.out.println("wrote part"+i+".dat");
0997: output = new FileOutputStream("part"+(i++)+".dat");
0998: chunks.readBodyData(output);
0999: nextChunk = chunks.readBoundary();
1000: }
1001: }
1002:
1003: */
1004: }
|