KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > turbine > util > upload > MultipartStream


1 package org.apache.turbine.util.upload;
2
3 /*
4  * Copyright 2001-2004 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 JavaDoc;
20 import java.io.InputStream JavaDoc;
21 import java.io.OutputStream JavaDoc;
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,v 1.5.2.2 2004/05/20 03:28:01 seade Exp $
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 JavaDoc 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 JavaDoc input,
162                            byte[] boundary,
163                            int bufSize)
164             throws MalformedStreamException,
165             IOException JavaDoc
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 JavaDoc input,
196                            byte[] boundary)
197             throws IOException JavaDoc
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 JavaDoc
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 JavaDoc("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 JavaDoc 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 JavaDoc readHeaders()
311             throws MalformedStreamException
312     {
313         int i = 0;
314         byte b[] = new byte[1];
315         StringBuffer JavaDoc buf = new StringBuffer JavaDoc();
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 JavaDoc 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 JavaDoc(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 JavaDoc output)
360             throws MalformedStreamException,
361             IOException JavaDoc
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 JavaDoc
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 JavaDoc
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 JavaDoc
612     {
613         /**
614          * Constructs a <code>MalformedStreamException</code> with no
615          * detail message.
616          */

617         public MalformedStreamException()
618         {
619             super();
620         }
621
622         /**
623          * Constructs an <code>MalformedStreamException</code> with
624          * the specified detail message.
625          *
626          * @param message The detail message.
627          */

628         public MalformedStreamException(String JavaDoc message)
629         {
630             super(message);
631         }
632     }
633
634     /**
635      * Thrown upon attempt of setting an invalid boundary token.
636      */

637     public class IllegalBoundaryException
638             extends IOException JavaDoc
639     {
640         /**
641          * Constructs an <code>IllegalBoundaryException</code> with no
642          * detail message.
643          */

644         public IllegalBoundaryException()
645         {
646             super();
647         }
648
649         /**
650          * Constructs an <code>IllegalBoundaryException</code> with
651          * the specified detail message.
652          *
653          * @param message The detail message.
654          */

655         public IllegalBoundaryException(String JavaDoc message)
656         {
657             super(message);
658         }
659     }
660
661     /*-------------------------------------------------------------
662
663     // These are the methods that were used to debug this stuff.
664
665     // Dump data.
666     protected void dump()
667     {
668         System.out.println("01234567890");
669         byte[] temp = new byte[buffer.length];
670         for(int i=0; i<buffer.length; i++)
671         {
672             if(buffer[i] == 0x0D || buffer[i] == 0x0A)
673             {
674                 temp[i] = 0x21;
675             }
676             else
677             {
678                 temp[i] = buffer[i];
679             }
680         }
681         System.out.println(new String(temp));
682         int i;
683         for(i=0; i<head; i++)
684             System.out.print(" ");
685         System.out.println("h");
686         for(i=0; i<tail; i++)
687             System.out.print(" ");
688         System.out.println("t");
689         System.out.flush();
690     }
691
692     // Main routine, for testing purposes only.
693     //
694     // @param args A String[] with the command line arguments.
695     // @exception Exception, a generic exception.
696     public static void main( String[] args )
697         throws Exception
698     {
699         File boundaryFile = new File("boundary.dat");
700         int boundarySize = (int)boundaryFile.length();
701         byte[] boundary = new byte[boundarySize];
702         FileInputStream input = new FileInputStream(boundaryFile);
703         input.read(boundary,0,boundarySize);
704
705         input = new FileInputStream("multipart.dat");
706         MultipartStream chunks = new MultipartStream(input, boundary);
707
708         int i = 0;
709         String header;
710         OutputStream output;
711         boolean nextChunk = chunks.skipPreamble();
712         while(nextChunk)
713         {
714             header = chunks.readHeaders();
715             System.out.println("!"+header+"!");
716             System.out.println("wrote part"+i+".dat");
717             output = new FileOutputStream("part"+(i++)+".dat");
718             chunks.readBodyData(output);
719             nextChunk = chunks.readBoundary();
720         }
721     }
722
723     */

724 }
725
Popular Tags