ulrich
2024-02-26 85c480d3d8f2c7214b6d8311ed232f7b385f012e
commit | author | age
85c480 1 /*
U 2  * Licensed to the Apache Software Foundation (ASF) under one or more
3  * contributor license agreements.  See the NOTICE file distributed with
4  * this work for additional information regarding copyright ownership.
5  * The ASF licenses this file to You under the Apache License, Version 2.0
6  * (the "License"); you may not use this file except in compliance with
7  * the License.  You may obtain a copy of the License at
8  *
9  *      http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */
17 package de.uhilger.neon.up;
18
19 import static java.lang.String.format;
20
21 import java.io.ByteArrayOutputStream;
22 import java.io.IOException;
23 import java.io.InputStream;
24 import java.io.OutputStream;
25 import java.io.UnsupportedEncodingException;
26
27
28 /**
29  * Der Lowlevel MultipartParser aus dem Projekt Apache Commons Fileupload 
30  * ohne Abhaengigkeiten zum Rest von Commons Fileupload und Commons IO.
31  * 
32  * <p> Low level API for processing file uploads.
33  *
34  * <p> This class can be used to process data streams conforming to MIME
35  * 'multipart' format as defined in
36  * <a href="http://www.ietf.org/rfc/rfc1867.txt">RFC 1867</a>. Arbitrarily
37  * large amounts of data in the stream can be processed under constant
38  * memory usage.
39  *
40  * <p> The format of the stream is defined in the following way:<br>
41  *
42  * <code>
43  *   multipart-body := preamble 1*encapsulation close-delimiter epilogue<br>
44  *   encapsulation := delimiter body CRLF<br>
45  *   delimiter := "--" boundary CRLF<br>
46  *   close-delimiter := "--" boundary "--"<br>
47  *   preamble := &lt;ignore&gt;<br>
48  *   epilogue := &lt;ignore&gt;<br>
49  *   body := header-part CRLF body-part<br>
50  *   header-part := 1*header CRLF<br>
51  *   header := header-name ":" header-value<br>
52  *   header-name := &lt;printable ascii characters except ":"&gt;<br>
53  *   header-value := &lt;any ascii characters except CR &amp; LF&gt;<br>
54  *   body-data := &lt;arbitrary data&gt;<br>
55  * </code>
56  *
57  * <p>Note that body-data can contain another mulipart entity.  There
58  * is limited support for single pass processing of such nested
59  * streams.  The nested stream is <strong>required</strong> to have a
60  * boundary token of the same length as the parent stream (see {@link
61  * #setBoundary(byte[])}).
62  *
63  * <p>Here is an example of usage of this class.<br>
64  *
65  * <pre>
66  *   try {
67  *     MultipartStream multipartStream = new MultipartStream(input, boundary);
68  *     boolean nextPart = multipartStream.skipPreamble();
69  *     OutputStream output;
70  *     while(nextPart) {
71  *       String header = multipartStream.readHeaders();
72  *       // process headers
73  *       // create some output stream
74  *       multipartStream.readBodyData(output);
75  *       nextPart = multipartStream.readBoundary();
76  *     }
77  *   } catch(MultipartStream.MalformedStreamException e) {
78  *     // the stream failed to follow required syntax
79  *   } catch(IOException e) {
80  *     // a read or write error occurred
81  *   }
82  * </pre>
83  */
84 public class MultipartStream {
85
86     /**
87      * Internal class, which is used to invoke the
88      * {@link ProgressListener}.
89      */
90     public static class ProgressNotifier {
91
92         /**
93          * The listener to invoke.
94          */
95         private final ProgressListener listener;
96
97         /**
98          * Number of expected bytes, if known, or -1.
99          */
100         private final long contentLength;
101
102         /**
103          * Number of bytes, which have been read so far.
104          */
105         private long bytesRead;
106
107         /**
108          * Number of items, which have been read so far.
109          */
110         private int items;
111
112         /**
113          * Creates a new instance with the given listener
114          * and content length.
115          *
116          * @param pListener The listener to invoke.
117          * @param pContentLength The expected content length.
118          */
119         ProgressNotifier(ProgressListener pListener, long pContentLength) {
120             listener = pListener;
121             contentLength = pContentLength;
122         }
123
124         /**
125          * Called to indicate that bytes have been read.
126          *
127          * @param pBytes Number of bytes, which have been read.
128          */
129         void noteBytesRead(int pBytes) {
130             /* Indicates, that the given number of bytes have been read from
131              * the input stream.
132              */
133             bytesRead += pBytes;
134             notifyListener();
135         }
136
137         /**
138          * Called to indicate, that a new file item has been detected.
139          */
140         void noteItem() {
141             ++items;
142             notifyListener();
143         }
144
145         /**
146          * Called for notifying the listener.
147          */
148         private void notifyListener() {
149             if (listener != null) {
150                 listener.update(bytesRead, contentLength, items);
151             }
152         }
153
154     }
155
156     // ----------------------------------------------------- Manifest constants
157
158     /**
159      * The Carriage Return ASCII character value.
160      */
161     public static final byte CR = 0x0D;
162
163     /**
164      * The Line Feed ASCII character value.
165      */
166     public static final byte LF = 0x0A;
167
168     /**
169      * The dash (-) ASCII character value.
170      */
171     public static final byte DASH = 0x2D;
172
173     /**
174      * The maximum length of <code>header-part</code> that will be
175      * processed (10 kilobytes = 10240 bytes.).
176      */
177     public static final int HEADER_PART_SIZE_MAX = 10240;
178
179     /**
180      * The default length of the buffer used for processing a request.
181      */
182     protected static final int DEFAULT_BUFSIZE = 4096;
183
184     /**
185      * A byte sequence that marks the end of <code>header-part</code>
186      * (<code>CRLFCRLF</code>).
187      */
188     protected static final byte[] HEADER_SEPARATOR = {CR, LF, CR, LF};
189
190     /**
191      * A byte sequence that that follows a delimiter that will be
192      * followed by an encapsulation (<code>CRLF</code>).
193      */
194     protected static final byte[] FIELD_SEPARATOR = {CR, LF};
195
196     /**
197      * A byte sequence that that follows a delimiter of the last
198      * encapsulation in the stream (<code>--</code>).
199      */
200     protected static final byte[] STREAM_TERMINATOR = {DASH, DASH};
201
202     /**
203      * A byte sequence that precedes a boundary (<code>CRLF--</code>).
204      */
205     protected static final byte[] BOUNDARY_PREFIX = {CR, LF, DASH, DASH};
206
207     // ----------------------------------------------------------- Data members
208
209     /**
210      * The input stream from which data is read.
211      */
212     private final InputStream input;
213
214     /**
215      * The length of the boundary token plus the leading <code>CRLF--</code>.
216      */
217     private int boundaryLength;
218
219     /**
220      * The amount of data, in bytes, that must be kept in the buffer in order
221      * to detect delimiters reliably.
222      */
223     private final int keepRegion;
224
225     /**
226      * The byte sequence that partitions the stream.
227      */
228     private final byte[] boundary;
229
230     /**
231      * The table for Knuth-Morris-Pratt search algorithm.
232      */
233     private final int[] boundaryTable;
234
235     /**
236      * The length of the buffer used for processing the request.
237      */
238     private final int bufSize;
239
240     /**
241      * The buffer used for processing the request.
242      */
243     private final byte[] buffer;
244
245     /**
246      * The index of first valid character in the buffer.
247      * <br>
248      * 0 <= head < bufSize
249      */
250     private int head;
251
252     /**
253      * The index of last valid character in the buffer + 1.
254      * <br>
255      * 0 <= tail <= bufSize
256      */
257     private int tail;
258
259     /**
260      * The content encoding to use when reading headers.
261      */
262     private String headerEncoding;
263
264     /**
265      * The progress notifier, if any, or null.
266      */
267     private final ProgressNotifier notifier;
268
269     // ----------------------------------------------------------- Constructors
270
271     /**
272      * Creates a new instance.
273      *
274      * @deprecated 1.2.1 Use {@link #MultipartStream(InputStream, byte[], int,
275      * ProgressNotifier)}
276      */
277     @Deprecated
278     public MultipartStream() {
279         this(null, null, null);
280     }
281
282     /**
283      * <p> Constructs a <code>MultipartStream</code> with a custom size buffer
284      * and no progress notifier.
285      *
286      * <p> Note that the buffer must be at least big enough to contain the
287      * boundary string, plus 4 characters for CR/LF and double dash, plus at
288      * least one byte of data.  Too small a buffer size setting will degrade
289      * performance.
290      *
291      * @param input    The <code>InputStream</code> to serve as a data source.
292      * @param boundary The token used for dividing the stream into
293      *                 <code>encapsulations</code>.
294      * @param bufSize  The size of the buffer to be used, in bytes.
295      *
296      * @deprecated 1.2.1 Use {@link #MultipartStream(InputStream, byte[], int,
297      * ProgressNotifier)}.
298      */
299     @Deprecated
300     public MultipartStream(InputStream input, byte[] boundary, int bufSize) {
301         this(input, boundary, bufSize, null);
302     }
303
304     /**
305      * <p> Constructs a <code>MultipartStream</code> with a custom size buffer.
306      *
307      * <p> Note that the buffer must be at least big enough to contain the
308      * boundary string, plus 4 characters for CR/LF and double dash, plus at
309      * least one byte of data.  Too small a buffer size setting will degrade
310      * performance.
311      *
312      * @param input    The <code>InputStream</code> to serve as a data source.
313      * @param boundary The token used for dividing the stream into
314      *                 <code>encapsulations</code>.
315      * @param bufSize  The size of the buffer to be used, in bytes.
316      * @param pNotifier The notifier, which is used for calling the
317      *                  progress listener, if any.
318      *
319      * @throws IllegalArgumentException If the buffer size is too small
320      *
321      * @since 1.3.1
322      */
323     public MultipartStream(InputStream input,
324             byte[] boundary,
325             int bufSize,
326             ProgressNotifier pNotifier) {
327
328         if (boundary == null) {
329             throw new IllegalArgumentException("boundary may not be null");
330         }
331         // We prepend CR/LF to the boundary to chop trailing CR/LF from
332         // body-data tokens.
333         this.boundaryLength = boundary.length + BOUNDARY_PREFIX.length;
334         if (bufSize < this.boundaryLength + 1) {
335             throw new IllegalArgumentException(
336                     "The buffer size specified for the MultipartStream is too small");
337         }
338
339         this.input = input;
340         this.bufSize = Math.max(bufSize, boundaryLength * 2);
341         this.buffer = new byte[this.bufSize];
342         this.notifier = pNotifier;
343
344         this.boundary = new byte[this.boundaryLength];
345         this.boundaryTable = new int[this.boundaryLength + 1];
346         this.keepRegion = this.boundary.length;
347
348         System.arraycopy(BOUNDARY_PREFIX, 0, this.boundary, 0,
349                 BOUNDARY_PREFIX.length);
350         System.arraycopy(boundary, 0, this.boundary, BOUNDARY_PREFIX.length,
351                 boundary.length);
352         computeBoundaryTable();
353
354         head = 0;
355         tail = 0;
356     }
357
358     /**
359      * <p> Constructs a <code>MultipartStream</code> with a default size buffer.
360      *
361      * @param input    The <code>InputStream</code> to serve as a data source.
362      * @param boundary The token used for dividing the stream into
363      *                 <code>encapsulations</code>.
364      * @param pNotifier An object for calling the progress listener, if any.
365      *
366      *
367      * @see #MultipartStream(InputStream, byte[], int, ProgressNotifier)
368      */
369     MultipartStream(InputStream input,
370             byte[] boundary,
371             ProgressNotifier pNotifier) {
372         this(input, boundary, DEFAULT_BUFSIZE, pNotifier);
373     }
374
375     /**
376      * <p> Constructs a <code>MultipartStream</code> with a default size buffer.
377      *
378      * @param input    The <code>InputStream</code> to serve as a data source.
379      * @param boundary The token used for dividing the stream into
380      *                 <code>encapsulations</code>.
381      *
382      * @deprecated 1.2.1 Use {@link #MultipartStream(InputStream, byte[], int,
383      *  ProgressNotifier)}.
384      */
385     @Deprecated
386     public MultipartStream(InputStream input,
387             byte[] boundary) {
388         this(input, boundary, DEFAULT_BUFSIZE, null);
389     }
390
391     // --------------------------------------------------------- Public methods
392
393     /**
394      * Retrieves the character encoding used when reading the headers of an
395      * individual part. When not specified, or <code>null</code>, the platform
396      * default encoding is used.
397      *
398      * @return The encoding used to read part headers.
399      */
400     public String getHeaderEncoding() {
401         return headerEncoding;
402     }
403
404     /**
405      * Specifies the character encoding to be used when reading the headers of
406      * individual parts. When not specified, or <code>null</code>, the platform
407      * default encoding is used.
408      *
409      * @param encoding The encoding used to read part headers.
410      */
411     public void setHeaderEncoding(String encoding) {
412         headerEncoding = encoding;
413     }
414
415     /**
416      * Reads a byte from the <code>buffer</code>, and refills it as
417      * necessary.
418      *
419      * @return The next byte from the input stream.
420      *
421      * @throws IOException if there is no more data available.
422      */
423     public byte readByte() throws IOException {
424         // Buffer depleted ?
425         if (head == tail) {
426             head = 0;
427             // Refill.
428             tail = input.read(buffer, head, bufSize);
429             if (tail == -1) {
430                 // No more data available.
431                 throw new IOException("No more data is available");
432             }
433             if (notifier != null) {
434                 notifier.noteBytesRead(tail);
435             }
436         }
437         return buffer[head++];
438     }
439
440     /**
441      * Skips a <code>boundary</code> token, and checks whether more
442      * <code>encapsulations</code> are contained in the stream.
443      *
444      * @return <code>true</code> if there are more encapsulations in
445      *         this stream; <code>false</code> otherwise.
446      *
447      * @throws FileUploadIOException if the bytes read from the stream exceeded the size limits
448      * @throws MalformedStreamException if the stream ends unexpectedly or
449      *                                  fails to follow required syntax.
450      */
451     public boolean readBoundary()
452             throws FileUploadIOException, MalformedStreamException {
453         byte[] marker = new byte[2];
454         boolean nextChunk = false;
455
456         head += boundaryLength;
457         try {
458             marker[0] = readByte();
459             if (marker[0] == LF) {
460                 // Work around IE5 Mac bug with input type=image.
461                 // Because the boundary delimiter, not including the trailing
462                 // CRLF, must not appear within any file (RFC 2046, section
463                 // 5.1.1), we know the missing CR is due to a buggy browser
464                 // rather than a file containing something similar to a
465                 // boundary.
466                 return true;
467             }
468
469             marker[1] = readByte();
470             if (arrayequals(marker, STREAM_TERMINATOR, 2)) {
471                 nextChunk = false;
472             } else if (arrayequals(marker, FIELD_SEPARATOR, 2)) {
473                 nextChunk = true;
474             } else {
475                 throw new MalformedStreamException(
476                 "Unexpected characters follow a boundary");
477             }
478         } catch (FileUploadIOException e) {
479             // wraps a SizeException, re-throw as it will be unwrapped later
480             throw e;
481         } catch (IOException e) {
482             throw new MalformedStreamException("Stream ended unexpectedly");
483         }
484         return nextChunk;
485     }
486
487     /**
488      * <p>Changes the boundary token used for partitioning the stream.
489      *
490      * <p>This method allows single pass processing of nested multipart
491      * streams.
492      *
493      * <p>The boundary token of the nested stream is <code>required</code>
494      * to be of the same length as the boundary token in parent stream.
495      *
496      * <p>Restoring the parent stream boundary token after processing of a
497      * nested stream is left to the application.
498      *
499      * @param boundary The boundary to be used for parsing of the nested
500      *                 stream.
501      *
502      * @throws IllegalBoundaryException if the <code>boundary</code>
503      *                                  has a different length than the one
504      *                                  being currently parsed.
505      */
506     public void setBoundary(byte[] boundary)
507             throws IllegalBoundaryException {
508         if (boundary.length != boundaryLength - BOUNDARY_PREFIX.length) {
509             throw new IllegalBoundaryException(
510             "The length of a boundary token cannot be changed");
511         }
512         System.arraycopy(boundary, 0, this.boundary, BOUNDARY_PREFIX.length,
513                 boundary.length);
514         computeBoundaryTable();
515     }
516
517     /**
518      * Compute the table used for Knuth-Morris-Pratt search algorithm.
519      */
520     private void computeBoundaryTable() {
521         int position = 2;
522         int candidate = 0;
523
524         boundaryTable[0] = -1;
525         boundaryTable[1] = 0;
526
527         while (position <= boundaryLength) {
528             if (boundary[position - 1] == boundary[candidate]) {
529                 boundaryTable[position] = candidate + 1;
530                 candidate++;
531                 position++;
532             } else if (candidate > 0) {
533                 candidate = boundaryTable[candidate];
534             } else {
535                 boundaryTable[position] = 0;
536                 position++;
537             }
538         }
539     }
540
541     /**
542      * <p>Reads the <code>header-part</code> of the current
543      * <code>encapsulation</code>.
544      *
545      * <p>Headers are returned verbatim to the input stream, including the
546      * trailing <code>CRLF</code> marker. Parsing is left to the
547      * application.
548      *
549      * <p><strong>TODO</strong> allow limiting maximum header size to
550      * protect against abuse.
551      *
552      * @return The <code>header-part</code> of the current encapsulation.
553      *
554      * @throws FileUploadIOException if the bytes read from the stream exceeded the size limits.
555      * @throws MalformedStreamException if the stream ends unexpectedly.
556      */
557     public String readHeaders() throws FileUploadIOException, MalformedStreamException {
558         int i = 0;
559         byte b;
560         // to support multi-byte characters
561         ByteArrayOutputStream baos = new ByteArrayOutputStream();
562         int size = 0;
563         while (i < HEADER_SEPARATOR.length) {
564             try {
565                 b = readByte();
566             } catch (FileUploadIOException e) {
567                 // wraps a SizeException, re-throw as it will be unwrapped later
568                 throw e;
569             } catch (IOException e) {
570                 throw new MalformedStreamException("Stream ended unexpectedly");
571             }
572             if (++size > HEADER_PART_SIZE_MAX) {
573                 throw new MalformedStreamException(
574                         format("Header section has more than %s bytes (maybe it is not properly terminated)",
575                                Integer.valueOf(HEADER_PART_SIZE_MAX)));
576             }
577             if (b == HEADER_SEPARATOR[i]) {
578                 i++;
579             } else {
580                 i = 0;
581             }
582             baos.write(b);
583         }
584
585         String headers = null;
586         if (headerEncoding != null) {
587             try {
588                 headers = baos.toString(headerEncoding);
589             } catch (UnsupportedEncodingException e) {
590                 // Fall back to platform default if specified encoding is not
591                 // supported.
592                 headers = baos.toString();
593             }
594         } else {
595             headers = baos.toString();
596         }
597
598         return headers;
599     }
600
601     /**
602      * <p>Reads <code>body-data</code> from the current
603      * <code>encapsulation</code> and writes its contents into the
604      * output <code>Stream</code>.
605      *
606      * <p>Arbitrary large amounts of data can be processed by this
607      * method using a constant size buffer. (see {@link
608      * #MultipartStream(InputStream,byte[],int,
609      *   MultipartStream.ProgressNotifier) constructor}).
610      *
611      * @param output The <code>Stream</code> to write data into. May
612      *               be null, in which case this method is equivalent
613      *               to {@link #discardBodyData()}.
614      *
615      * @return the amount of data written.
616      *
617      * @throws MalformedStreamException if the stream ends unexpectedly.
618      * @throws IOException              if an i/o error occurs.
619      */
620     public int readBodyData(OutputStream output)
621             throws MalformedStreamException, IOException {
622         return (int) Streams.copy(newInputStream(), output, false); // N.B. Streams.copy closes the input stream
623     }
624
625     /**
626      * Creates a new {@link ItemInputStream}.
627      * @return A new instance of {@link ItemInputStream}.
628      */
629     ItemInputStream newInputStream() {
630         return new ItemInputStream();
631     }
632
633     /**
634      * <p> Reads <code>body-data</code> from the current
635      * <code>encapsulation</code> and discards it.
636      *
637      * <p>Use this method to skip encapsulations you don't need or don't
638      * understand.
639      *
640      * @return The amount of data discarded.
641      *
642      * @throws MalformedStreamException if the stream ends unexpectedly.
643      * @throws IOException              if an i/o error occurs.
644      */
645     public int discardBodyData() throws MalformedStreamException, IOException {
646         return readBodyData(null);
647     }
648
649     /**
650      * Finds the beginning of the first <code>encapsulation</code>.
651      *
652      * @return <code>true</code> if an <code>encapsulation</code> was found in
653      *         the stream.
654      *
655      * @throws IOException if an i/o error occurs.
656      */
657     public boolean skipPreamble() throws IOException {
658         // First delimiter may be not preceeded with a CRLF.
659         System.arraycopy(boundary, 2, boundary, 0, boundary.length - 2);
660         boundaryLength = boundary.length - 2;
661         computeBoundaryTable();
662         try {
663             // Discard all data up to the delimiter.
664             discardBodyData();
665
666             // Read boundary - if succeeded, the stream contains an
667             // encapsulation.
668             return readBoundary();
669         } catch (MalformedStreamException e) {
670             return false;
671         } finally {
672             // Restore delimiter.
673             System.arraycopy(boundary, 0, boundary, 2, boundary.length - 2);
674             boundaryLength = boundary.length;
675             boundary[0] = CR;
676             boundary[1] = LF;
677             computeBoundaryTable();
678         }
679     }
680
681     /**
682      * Compares <code>count</code> first bytes in the arrays
683      * <code>a</code> and <code>b</code>.
684      *
685      * @param a     The first array to compare.
686      * @param b     The second array to compare.
687      * @param count How many bytes should be compared.
688      *
689      * @return <code>true</code> if <code>count</code> first bytes in arrays
690      *         <code>a</code> and <code>b</code> are equal.
691      */
692     public static boolean arrayequals(byte[] a,
693             byte[] b,
694             int count) {
695         for (int i = 0; i < count; i++) {
696             if (a[i] != b[i]) {
697                 return false;
698             }
699         }
700         return true;
701     }
702
703     /**
704      * Searches for a byte of specified value in the <code>buffer</code>,
705      * starting at the specified <code>position</code>.
706      *
707      * @param value The value to find.
708      * @param pos   The starting position for searching.
709      *
710      * @return The position of byte found, counting from beginning of the
711      *         <code>buffer</code>, or <code>-1</code> if not found.
712      */
713     protected int findByte(byte value,
714             int pos) {
715         for (int i = pos; i < tail; i++) {
716             if (buffer[i] == value) {
717                 return i;
718             }
719         }
720
721         return -1;
722     }
723
724     /**
725      * Searches for the <code>boundary</code> in the <code>buffer</code>
726      * region delimited by <code>head</code> and <code>tail</code>.
727      *
728      * @return The position of the boundary found, counting from the
729      *         beginning of the <code>buffer</code>, or <code>-1</code> if
730      *         not found.
731      */
732     protected int findSeparator() {
733
734         int bufferPos = this.head;
735         int tablePos = 0;
736
737         while (bufferPos < this.tail) {
738             while (tablePos >= 0 && buffer[bufferPos] != boundary[tablePos]) {
739                 tablePos = boundaryTable[tablePos];
740             }
741             bufferPos++;
742             tablePos++;
743             if (tablePos == boundaryLength) {
744                 return bufferPos - boundaryLength;
745             }
746         }
747         return -1;
748     }
749
750     /**
751      * Thrown to indicate that the input stream fails to follow the
752      * required syntax.
753      */
754     public static class MalformedStreamException extends IOException {
755
756         /**
757          * The UID to use when serializing this instance.
758          */
759         private static final long serialVersionUID = 6466926458059796677L;
760
761         /**
762          * Constructs a <code>MalformedStreamException</code> with no
763          * detail message.
764          */
765         public MalformedStreamException() {
766             super();
767         }
768
769         /**
770          * Constructs an <code>MalformedStreamException</code> with
771          * the specified detail message.
772          *
773          * @param message The detail message.
774          */
775         public MalformedStreamException(String message) {
776             super(message);
777         }
778
779     }
780
781     /**
782      * Thrown upon attempt of setting an invalid boundary token.
783      */
784     public static class IllegalBoundaryException extends IOException {
785
786         /**
787          * The UID to use when serializing this instance.
788          */
789         private static final long serialVersionUID = -161533165102632918L;
790
791         /**
792          * Constructs an <code>IllegalBoundaryException</code> with no
793          * detail message.
794          */
795         public IllegalBoundaryException() {
796             super();
797         }
798
799         /**
800          * Constructs an <code>IllegalBoundaryException</code> with
801          * the specified detail message.
802          *
803          * @param message The detail message.
804          */
805         public IllegalBoundaryException(String message) {
806             super(message);
807         }
808
809     }
810
811     /**
812      * An {@link InputStream} for reading an items contents.
813      */
814     public class ItemInputStream extends InputStream implements Closeable {
815
816         /**
817          * The number of bytes, which have been read so far.
818          */
819         private long total;
820
821         /**
822          * The number of bytes, which must be hold, because
823          * they might be a part of the boundary.
824          */
825         private int pad;
826
827         /**
828          * The current offset in the buffer.
829          */
830         private int pos;
831
832         /**
833          * Whether the stream is already closed.
834          */
835         private boolean closed;
836
837         /**
838          * Creates a new instance.
839          */
840         ItemInputStream() {
841             findSeparator();
842         }
843
844         /**
845          * Called for finding the separator.
846          */
847         private void findSeparator() {
848             pos = MultipartStream.this.findSeparator();
849             if (pos == -1) {
850                 if (tail - head > keepRegion) {
851                     pad = keepRegion;
852                 } else {
853                     pad = tail - head;
854                 }
855             }
856         }
857
858         /**
859          * Returns the number of bytes, which have been read
860          * by the stream.
861          *
862          * @return Number of bytes, which have been read so far.
863          */
864         public long getBytesRead() {
865             return total;
866         }
867
868         /**
869          * Returns the number of bytes, which are currently
870          * available, without blocking.
871          *
872          * @throws IOException An I/O error occurs.
873          * @return Number of bytes in the buffer.
874          */
875         @Override
876         public int available() throws IOException {
877             if (pos == -1) {
878                 return tail - head - pad;
879             }
880             return pos - head;
881         }
882
883         /**
884          * Offset when converting negative bytes to integers.
885          */
886         private static final int BYTE_POSITIVE_OFFSET = 256;
887
888         /**
889          * Returns the next byte in the stream.
890          *
891          * @return The next byte in the stream, as a non-negative
892          *   integer, or -1 for EOF.
893          * @throws IOException An I/O error occurred.
894          */
895         @Override
896         public int read() throws IOException {
897             if (closed) {
898                 throw new ItemSkippedException();
899             }
900             if (available() == 0 && makeAvailable() == 0) {
901                 return -1;
902             }
903             ++total;
904             int b = buffer[head++];
905             if (b >= 0) {
906                 return b;
907             }
908             return b + BYTE_POSITIVE_OFFSET;
909         }
910
911         /**
912          * Reads bytes into the given buffer.
913          *
914          * @param b The destination buffer, where to write to.
915          * @param off Offset of the first byte in the buffer.
916          * @param len Maximum number of bytes to read.
917          * @return Number of bytes, which have been actually read,
918          *   or -1 for EOF.
919          * @throws IOException An I/O error occurred.
920          */
921         @Override
922         public int read(byte[] b, int off, int len) throws IOException {
923             if (closed) {
924                 throw new ItemSkippedException();
925             }
926             if (len == 0) {
927                 return 0;
928             }
929             int res = available();
930             if (res == 0) {
931                 res = makeAvailable();
932                 if (res == 0) {
933                     return -1;
934                 }
935             }
936             res = Math.min(res, len);
937             System.arraycopy(buffer, head, b, off, res);
938             head += res;
939             total += res;
940             return res;
941         }
942
943         /**
944          * Closes the input stream.
945          *
946          * @throws IOException An I/O error occurred.
947          */
948         @Override
949         public void close() throws IOException {
950             close(false);
951         }
952
953         /**
954          * Closes the input stream.
955          *
956          * @param pCloseUnderlying Whether to close the underlying stream
957          *   (hard close)
958          * @throws IOException An I/O error occurred.
959          */
960         public void close(boolean pCloseUnderlying) throws IOException {
961             if (closed) {
962                 return;
963             }
964             if (pCloseUnderlying) {
965                 closed = true;
966                 input.close();
967             } else {
968                 for (;;) {
969                     int av = available();
970                     if (av == 0) {
971                         av = makeAvailable();
972                         if (av == 0) {
973                             break;
974                         }
975                     }
976                     skip(av);
977                 }
978             }
979             closed = true;
980         }
981
982         /**
983          * Skips the given number of bytes.
984          *
985          * @param bytes Number of bytes to skip.
986          * @return The number of bytes, which have actually been
987          *   skipped.
988          * @throws IOException An I/O error occurred.
989          */
990         @Override
991         public long skip(long bytes) throws IOException {
992             if (closed) {
993                 throw new ItemSkippedException();
994             }
995             int av = available();
996             if (av == 0) {
997                 av = makeAvailable();
998                 if (av == 0) {
999                     return 0;
1000                 }
1001             }
1002             long res = Math.min(av, bytes);
1003             head += res;
1004             return res;
1005         }
1006
1007         /**
1008          * Attempts to read more data.
1009          *
1010          * @return Number of available bytes
1011          * @throws IOException An I/O error occurred.
1012          */
1013         private int makeAvailable() throws IOException {
1014             if (pos != -1) {
1015                 return 0;
1016             }
1017
1018             // Move the data to the beginning of the buffer.
1019             total += tail - head - pad;
1020             System.arraycopy(buffer, tail - pad, buffer, 0, pad);
1021
1022             // Refill buffer with new data.
1023             head = 0;
1024             tail = pad;
1025
1026             for (;;) {
1027                 int bytesRead = input.read(buffer, tail, bufSize - tail);
1028                 if (bytesRead == -1) {
1029                     // The last pad amount is left in the buffer.
1030                     // Boundary can't be in there so signal an error
1031                     // condition.
1032                     final String msg = "Stream ended unexpectedly";
1033                     throw new MalformedStreamException(msg);
1034                 }
1035                 if (notifier != null) {
1036                     notifier.noteBytesRead(bytesRead);
1037                 }
1038                 tail += bytesRead;
1039
1040                 findSeparator();
1041                 int av = available();
1042
1043                 if (av > 0 || pos != -1) {
1044                     return av;
1045                 }
1046             }
1047         }
1048
1049         /**
1050          * Returns, whether the stream is closed.
1051          *
1052          * @return True, if the stream is closed, otherwise false.
1053          */
1054         @Override
1055         public boolean isClosed() {
1056             return closed;
1057         }
1058
1059     }
1060
1061 }