View Javadoc

1   package org.apache.turbine.util.upload;
2   
3   /*
4    * Copyright 2001-2005 The Apache Software Foundation.
5    *
6    * Licensed under the Apache License, Version 2.0 (the "License")
7    * you may not use this file except in compliance with the License.
8    * You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  
19  import java.io.IOException;
20  import java.io.InputStream;
21  import java.io.OutputStream;
22  
23  /***
24   * This class can be used to process data streams conforming to MIME
25   * 'multipart' format as defined in <a
26   * href="http://rf.cs/rfc1521.html">RFC&nbsp;1251</a>.  Arbitrary
27   * large amouns of data in the stream can be processed under constant
28   * memory usage.
29   *
30   * <p>The format of the stream is defined in the following way:<br>
31   *
32   * <code>
33   *   multipart-body := preamble 1*encapsulation close-delimiter epilogue<br>
34   *   encapsulation := delimiter body CRLF<br>
35   *   delimiter := "--" boundary CRLF<br>
36   *   close-delimiter := "--" boudary "--"<br>
37   *   preamble := &lt;ignore&gt;<br>
38   *   epilogue := &lt;ignore&gt;<br>
39   *   body := header-part CRLF body-part<br>
40   *   header-part := 1*header CRLF<br>
41   *   header := header-name ":" header-value<br>
42   *   header-name := &lt;printable ascii characters except ":"&gt;<br>
43   *   header-value := &lt;any ascii characters except CR & LF&gt;<br>
44   *   body-data := &lt;arbitrary data&gt;<br>
45   * </code>
46   *
47   * <p>Note that body-data can contain another mulipart entity.  There
48   * is limited support for single pass processing of such nested
49   * streams.  The nested stream is <strong>required</strong> to have a
50   * boundary token of the same length as the parent stream (see {@link
51   * #setBoundary(byte[])}).
52   *
53   * <p>Here is an exaple of usage of this class.<br>
54   *
55   * <pre>
56   *    try {
57   *        MultipartStream multipartStream = new MultipartStream(input,
58   *                                                              boundary);
59   *        boolean nextPart = malitPartStream.skipPreamble();
60   *        OutputStream output;
61   *        while(nextPart) {
62   *            header = chunks.readHeader();
63   *            // process headers
64   *            // create some output stream
65   *            multipartStream.readBodyPart(output);
66   *            nextPart = multipartStream.readBoundary();
67   *        }
68   *    } catch(MultipartStream.MalformedStreamException e) {
69   *          // the stream failed to follow required syntax
70   *    } catch(IOException) {
71   *          // a read or write error occurred
72   *    }
73   * </pre>
74   *
75   * @author <a href="mailto:Rafal.Krzewski@e-point.pl">Rafal Krzewski</a>
76   * @version $Id: MultipartStream.java 278822 2005-09-05 19:53:05Z henning $
77   * @deprecated use commons-fileupload instead
78   */
79  public class MultipartStream
80  {
81      /***
82       * The maximum lenght of <code>header-part</code> that will be
83       * processed (10 kilobytes = 10240 bytes.)
84       */
85      public static final int HEADER_PART_SIZE_MAX = 10240;
86  
87      /*** The stream were data is read from. */
88      protected InputStream input;
89  
90      /***
91       * The lenght of boundary token plus leading <code>CRLF--</code>.
92       */
93      protected int boundaryLength;
94  
95      /***
96       * The amount of data that must be kept in the buffer in order to
97       * detect delimiters reliably.
98       */
99      protected int keepRegion;
100 
101     /*** A byte sequence that partitions the stream. */
102     protected byte[] boundary;
103 
104     /*** The lenght of the buffer used for processing. */
105     protected int bufSize;
106 
107     /*** The default lenght of the buffer used for processing. */
108     protected static final int DEFAULT_BUFSIZE = 4096;
109 
110     /*** The buffer used for processing. */
111     protected byte[] buffer;
112 
113     /***
114      * The index of first valid character in the buffer.
115      *
116      * 0 <= head < bufSize
117      */
118     protected int head;
119 
120     /***
121      * The index of last valid characer in the buffer + 1.
122      *
123      * 0 <= tail <= bufSize
124      */
125     protected int tail;
126 
127     /***
128      * A byte sequence that marks the end of <code>header-part</code>
129      * (<code>CRLFCRLF</code>).
130      */
131     protected static final byte[] HEADER_SEPARATOR = {0x0D, 0x0A, 0x0D, 0x0A};
132 
133     /***
134      * A byte sequence that that follows a delimiter that will be
135      * followed by an encapsulation (<code>CRLF</code>).
136      */
137     protected static final byte[] FIELD_SEPARATOR = {0x0D, 0x0A};
138 
139     /***
140      * A byte sequence that that follows a delimiter of the last
141      * encapsulation in the stream (<code>--</code>).
142      */
143     protected static final byte[] STREAM_TERMINATOR = {0x2D, 0x2D};
144 
145     /***
146      * Constructs a MultipartStream with a custom size buffer.
147      *
148      * <p>Note that the buffer must be at least big enough to contain
149      * the boundary string, plus 4 characters for CR/LF and double
150      * dash, plus at least one byte of data.  Too small buffer size
151      * setting will degrade performance.
152      *
153      * @param input The <code>InputStream</code> to serve as a data
154      * source.
155      * @param boundary The token used for dividing the stream into
156      * <code>encapsulations</code>.
157      * @param bufSize The size of the buffer to be used in bytes.
158      * @exception MalformedStreamException.
159      * @exception IOException.
160      */
161     public MultipartStream(InputStream input,
162                            byte[] boundary,
163                            int bufSize)
164             throws MalformedStreamException,
165             IOException
166     {
167         this.input = input;
168         this.bufSize = bufSize;
169         this.buffer = new byte[bufSize];
170 
171         // We prepend CR/LF to the boundary to chop trailng CR/LF from
172         // body-data tokens.
173         this.boundary = new byte[boundary.length + 4];
174         this.boundaryLength = boundary.length + 4;
175         this.keepRegion = boundary.length + 3;
176         this.boundary[0] = 0x0D;
177         this.boundary[1] = 0x0A;
178         this.boundary[2] = 0x2D;
179         this.boundary[3] = 0x2D;
180         System.arraycopy(boundary, 0, this.boundary, 4, boundary.length);
181 
182         head = 0;
183         tail = 0;
184     }
185 
186     /***
187      * Constructs a MultipartStream with a defalut size buffer.
188      *
189      * @param input The <code>InputStream</code> to serve as a data
190      * source.
191      * @param boundary The token used for dividing the stream into
192      * <code>encapsulations</code>.
193      * @exception IOException.
194      */
195     public MultipartStream(InputStream input,
196                            byte[] boundary)
197             throws IOException
198     {
199         this(input, boundary, DEFAULT_BUFSIZE);
200     }
201 
202     /***
203      * Reads a byte from the <code>buffer</code>, and refills it as
204      * neccessary.
205      *
206      * @return Next byte from the input stream.
207      * @exception IOException, if there isn't any more data available.
208      */
209     public byte readByte()
210             throws IOException
211     {
212         // Buffer depleted ?
213         if (head == tail)
214         {
215             head = 0;
216             // Refill.
217             tail = input.read(buffer, head, bufSize);
218             if (tail == -1)
219             {
220                 // No more data available.
221                 throw new IOException("No more data is available");
222             }
223         }
224         return buffer[head++];
225     }
226 
227     /***
228      * Skips a <code>boundary</code> token, and checks wether more
229      * <code>encapsulations</code> are contained in the stream.
230      *
231      * @return <code>True</code> if there are more encapsulations in
232      * this stream.
233      * @exception MalformedStreamException if the stream ends
234      * unexpecetedly or fails to follow required syntax.
235      */
236     public boolean readBoundary()
237             throws MalformedStreamException
238     {
239         byte[] marker = new byte[2];
240         boolean nextChunk = false;
241 
242         head += boundaryLength;
243         try
244         {
245             marker[0] = readByte();
246             marker[1] = readByte();
247             if (arrayequals(marker, STREAM_TERMINATOR, 2))
248             {
249                 nextChunk = false;
250             }
251             else if (arrayequals(marker, FIELD_SEPARATOR, 2))
252             {
253                 nextChunk = true;
254             }
255             else
256             {
257                 throw new MalformedStreamException("Unexpected characters follow a boundary");
258             }
259         }
260         catch (IOException e)
261         {
262             throw new MalformedStreamException("Stream ended unexpectedly");
263         }
264         return nextChunk;
265     }
266 
267     /***
268      * Changes the boundary token used for partitioning the stream.
269      *
270      * <p>This method allows single pass processing of nested
271      * multipart streams.
272      *
273      * <p>The boundary token of the nested stream is
274      * <code>required</code> to be of the same length as the boundary
275      * token in parent stream.
276      *
277      * <p>Restoring parent stream boundary token after processing of a
278      * nested stream is left ot the application. <br>
279      *
280      * @param boundary A boundary to be used for parsing of the nested
281      * stream.
282      * @exception IllegalBoundaryException, if <code>boundary</code>
283      * has diffrent lenght than the one being currently in use.
284      */
285     public void setBoundary(byte[] boundary)
286             throws IllegalBoundaryException
287     {
288         if (boundary.length != boundaryLength - 4)
289         {
290             throw new IllegalBoundaryException("The length of a boundary token can not be changed");
291         }
292         System.arraycopy(boundary, 0, this.boundary, 4, boundary.length);
293     }
294 
295     /***
296      * <p>Reads <code>header-part</code> of the current
297      * <code>encapsulation</code>
298      *
299      * <p>Headers are returned verbatim to the input stream, including
300      * traling <code>CRLF</code> marker. Parsing is left to the
301      * application.
302      *
303      * <p><strong>TODO</strong> allow limiting maximum header size to
304      * protect against abuse.<br>
305      *
306      * @return <code>header-part</code> of the current encapsulation.
307      * @exception MalformedStreamException, if the stream ends
308      * unexpecetedly.
309      */
310     public String readHeaders()
311             throws MalformedStreamException
312     {
313         int i = 0;
314         byte b[] = new byte[1];
315         StringBuffer buf = new StringBuffer();
316         int sizeMax = HEADER_PART_SIZE_MAX;
317         int size = 0;
318         while (i < 4)
319         {
320             try
321             {
322                 b[0] = readByte();
323             }
324             catch (IOException e)
325             {
326                 throw new MalformedStreamException("Stream ended unexpectedly");
327             }
328             size++;
329             if (b[0] == HEADER_SEPARATOR[i])
330             {
331                 i++;
332             }
333             else
334             {
335                 i = 0;
336             }
337             if (size <= sizeMax)
338             {
339                 buf.append(new String(b));
340             }
341         }
342         return buf.toString();
343     }
344 
345     /***
346      * Reads <code>body-data</code> from the current
347      * <code>encapsulation</code> and writes its contents into the
348      * output <code>Stream</code>.
349      *
350      * <p>Arbitrary large amouts of data can be processed by this
351      * method using a constant size buffer. (see {@link
352      * #MultipartStream(InputStream,byte[],int) constructor}).
353      *
354      * @param output The <code>Stream</code> to write data into.
355      * @return the amount of data written.
356      * @exception MalformedStreamException
357      * @exception IOException
358      */
359     public int readBodyData(OutputStream output)
360             throws MalformedStreamException,
361             IOException
362     {
363         boolean done = false;
364         int pad;
365         int pos;
366         int bytesRead;
367         int total = 0;
368         while (!done)
369         {
370             // Is boundary token present somewere in the buffer?
371             pos = findSeparator();
372             if (pos != -1)
373             {
374                 // Write the rest of the data before the boundary.
375                 output.write(buffer, head, pos - head);
376                 total += pos - head;
377                 head = pos;
378                 done = true;
379             }
380             else
381             {
382                 // Determine how much data should be kept in the
383                 // buffer.
384                 if (tail - head > keepRegion)
385                 {
386                     pad = keepRegion;
387                 }
388                 else
389                 {
390                     pad = tail - head;
391                 }
392                 // Write out the data belonging to the body-data.
393                 output.write(buffer, head, tail - head - pad);
394 
395                 // Move the data to the beging of the buffer.
396                 total += tail - head - pad;
397                 System.arraycopy(buffer, tail - pad, buffer, 0, pad);
398 
399                 // Refill buffer with new data.
400                 head = 0;
401                 bytesRead = input.read(buffer, pad, bufSize - pad);
402 
403                 // [pprrrrrrr]
404                 if (bytesRead != -1)
405                 {
406                     tail = pad + bytesRead;
407                 }
408                 else
409                 {
410                     // The last pad amount is left in the buffer.
411                     // Boundary can't be in there so write out the
412                     // data you have and signal an error condition.
413                     output.write(buffer, 0, pad);
414                     output.flush();
415                     total += pad;
416                     throw new MalformedStreamException("Stream ended unexpectedly");
417                 }
418             }
419         }
420         output.flush();
421         return total;
422     }
423 
424     /***
425      * Reads <code>body-data</code> from the current
426      * <code>encapsulation</code> and discards it.
427      *
428      * <p>Use this method to skip encapsulations you don't need or
429      * don't understand.
430      *
431      * @return The amount of data discarded.
432      * @exception MalformedStreamException
433      * @exception IOException
434      */
435     public int discardBodyData()
436             throws MalformedStreamException,
437             IOException
438     {
439         boolean done = false;
440         int pad;
441         int pos;
442         int bytesRead;
443         int total = 0;
444         while (!done)
445         {
446             // Is boundary token present somewere in the buffer?
447             pos = findSeparator();
448             if (pos != -1)
449             {
450                 // Write the rest of the data before the boundary.
451                 total += pos - head;
452                 head = pos;
453                 done = true;
454             }
455             else
456             {
457                 // Determine how much data should be kept in the
458                 // buffer.
459                 if (tail - head > keepRegion)
460                 {
461                     pad = keepRegion;
462                 }
463                 else
464                 {
465                     pad = tail - head;
466                 }
467                 total += tail - head - pad;
468 
469                 // Move the data to the beging of the buffer.
470                 System.arraycopy(buffer, tail - pad, buffer, 0, pad);
471 
472                 // Refill buffer with new data.
473                 head = 0;
474                 bytesRead = input.read(buffer, pad, bufSize - pad);
475 
476                 // [pprrrrrrr]
477                 if (bytesRead != -1)
478                 {
479                     tail = pad + bytesRead;
480                 }
481                 else
482                 {
483                     // The last pad amount is left in the buffer.
484                     // Boundary can't be in there so signal an error
485                     // condition.
486                     total += pad;
487                     throw new MalformedStreamException("Stream ended unexpectedly");
488                 }
489             }
490         }
491         return total;
492     }
493 
494     /***
495      * Finds the beginning of the first <code>encapsulation</code>.
496      *
497      * @return <code>True</code> if an <code>encapsulation</code> was
498      * found in the stream.
499      * @exception IOException
500      */
501     public boolean skipPreamble()
502             throws IOException
503     {
504         // First delimiter may be not preceeded with a CRLF.
505         System.arraycopy(boundary, 2, boundary, 0, boundary.length - 2);
506         boundaryLength = boundary.length - 2;
507         try
508         {
509             // Discard all data up to the delimiter.
510             discardBodyData();
511 
512             // Read boundary - if succeded, the stream contains an
513             // encapsulation.
514             return readBoundary();
515         }
516         catch (MalformedStreamException e)
517         {
518             return false;
519         }
520         finally
521         {
522             // Restore delimiter.
523             System.arraycopy(boundary, 0, boundary, 2, boundary.length - 2);
524             boundaryLength = boundary.length;
525             boundary[0] = 0x0D;
526             boundary[1] = 0x0A;
527         }
528     }
529 
530     /***
531      * Compares <code>count</code> first bytes in the arrays
532      * <code>a</code> and <code>b</code>.
533      *
534      * @param a The first array to compare.
535      * @param b The second array to compare.
536      * @param count How many bytes should be compared.
537      * @return <code>true</code> if <code>count</code> first bytes in
538      * arrays <code>a</code> and <code>b</code> are equal.
539      */
540     public static boolean arrayequals(byte[] a,
541                                       byte[] b,
542                                       int count)
543     {
544         for (int i = 0; i < count; i++)
545         {
546             if (a[i] != b[i])
547             {
548                 return false;
549             }
550         }
551         return true;
552     }
553 
554     /***
555      * Searches a byte of specified value in the <code>buffer</code>
556      * starting at specified <code>position</code>.
557      *
558      * @param value the value to find.
559      * @param pos The starting position for searching.
560      * @return The position of byte found, counting from beginning of
561      * the <code>buffer</code>, or <code>-1</code> if not found.
562      */
563     protected int findByte(byte value,
564                            int pos)
565     {
566         for (int i = pos; i < tail; i++)
567             if (buffer[i] == value)
568                 return i;
569 
570         return -1;
571     }
572 
573     /***
574      * Searches the <code>boundary</code> in <code>buffer</code>
575      * region delimited by <code>head</code> and <code>tail</code>.
576      *
577      * @return The position of the boundary found, counting from
578      * beginning of the <code>buffer</code>, or <code>-1</code> if not
579      * found.
580      */
581     protected int findSeparator()
582     {
583         int first;
584         int match = 0;
585         int maxpos = tail - boundaryLength;
586         for (first = head;
587              (first <= maxpos) && (match != boundaryLength);
588              first++)
589         {
590             first = findByte(boundary[0], first);
591             if (first == -1 || (first > maxpos))
592                 return -1;
593             for (match = 1; match < boundaryLength; match++)
594             {
595                 if (buffer[first + match] != boundary[match])
596                     break;
597             }
598         }
599         if (match == boundaryLength)
600         {
601             return first - 1;
602         }
603         return -1;
604     }
605 
606     /***
607      * Thrown to indicate that the input stream fails to follow the
608      * required syntax.
609      */
610     public class MalformedStreamException
611             extends IOException
612     {
613         /*** Serial Version UID */
614         private static final long serialVersionUID = 3813694874163574138L;
615 
616         /***
617          * Constructs a <code>MalformedStreamException</code> with no
618          * detail message.
619          */
620         public MalformedStreamException()
621         {
622             super();
623         }
624 
625         /***
626          * Constructs an <code>MalformedStreamException</code> with
627          * the specified detail message.
628          *
629          * @param message The detail message.
630          */
631         public MalformedStreamException(String message)
632         {
633             super(message);
634         }
635     }
636 
637     /***
638      * Thrown upon attempt of setting an invalid boundary token.
639      */
640     public class IllegalBoundaryException
641             extends IOException
642     {
643         /*** Serial Version UID */
644         private static final long serialVersionUID = -2883885421190362860L;
645 
646         /***
647          * Constructs an <code>IllegalBoundaryException</code> with no
648          * detail message.
649          */
650         public IllegalBoundaryException()
651         {
652             super();
653         }
654 
655         /***
656          * Constructs an <code>IllegalBoundaryException</code> with
657          * the specified detail message.
658          *
659          * @param message The detail message.
660          */
661         public IllegalBoundaryException(String message)
662         {
663             super(message);
664         }
665     }
666 
667     /*-------------------------------------------------------------
668 
669     // These are the methods that were used to debug this stuff.
670 
671     // Dump data.
672     protected void dump()
673     {
674         System.out.println("01234567890");
675         byte[] temp = new byte[buffer.length];
676         for(int i=0; i<buffer.length; i++)
677         {
678             if(buffer[i] == 0x0D || buffer[i] == 0x0A)
679             {
680                 temp[i] = 0x21;
681             }
682             else
683             {
684                 temp[i] = buffer[i];
685             }
686         }
687         System.out.println(new String(temp));
688         int i;
689         for(i=0; i<head; i++)
690             System.out.print(" ");
691         System.out.println("h");
692         for(i=0; i<tail; i++)
693             System.out.print(" ");
694         System.out.println("t");
695         System.out.flush();
696     }
697 
698     // Main routine, for testing purposes only.
699     //
700     // @param args A String[] with the command line arguments.
701     // @exception Exception, a generic exception.
702     public static void main( String[] args )
703         throws Exception
704     {
705         File boundaryFile = new File("boundary.dat");
706         int boundarySize = (int)boundaryFile.length();
707         byte[] boundary = new byte[boundarySize];
708         FileInputStream input = new FileInputStream(boundaryFile);
709         input.read(boundary,0,boundarySize);
710 
711         input = new FileInputStream("multipart.dat");
712         MultipartStream chunks = new MultipartStream(input, boundary);
713 
714         int i = 0;
715         String header;
716         OutputStream output;
717         boolean nextChunk = chunks.skipPreamble();
718         while(nextChunk)
719         {
720             header = chunks.readHeaders();
721             System.out.println("!"+header+"!");
722             System.out.println("wrote part"+i+".dat");
723             output = new FileOutputStream("part"+(i++)+".dat");
724             chunks.readBodyData(output);
725             nextChunk = chunks.readBoundary();
726         }
727     }
728 
729     */
730 }