1 package org.apache.turbine.util.upload;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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 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 := <ignore><br>
38 * epilogue := <ignore><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 := <printable ascii characters except ":"><br>
43 * header-value := <any ascii characters except CR & LF><br>
44 * body-data := <arbitrary data><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
172
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
213 if (head == tail)
214 {
215 head = 0;
216
217 tail = input.read(buffer, head, bufSize);
218 if (tail == -1)
219 {
220
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
371 pos = findSeparator();
372 if (pos != -1)
373 {
374
375 output.write(buffer, head, pos - head);
376 total += pos - head;
377 head = pos;
378 done = true;
379 }
380 else
381 {
382
383
384 if (tail - head > keepRegion)
385 {
386 pad = keepRegion;
387 }
388 else
389 {
390 pad = tail - head;
391 }
392
393 output.write(buffer, head, tail - head - pad);
394
395
396 total += tail - head - pad;
397 System.arraycopy(buffer, tail - pad, buffer, 0, pad);
398
399
400 head = 0;
401 bytesRead = input.read(buffer, pad, bufSize - pad);
402
403
404 if (bytesRead != -1)
405 {
406 tail = pad + bytesRead;
407 }
408 else
409 {
410
411
412
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
447 pos = findSeparator();
448 if (pos != -1)
449 {
450
451 total += pos - head;
452 head = pos;
453 done = true;
454 }
455 else
456 {
457
458
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
470 System.arraycopy(buffer, tail - pad, buffer, 0, pad);
471
472
473 head = 0;
474 bytesRead = input.read(buffer, pad, bufSize - pad);
475
476
477 if (bytesRead != -1)
478 {
479 tail = pad + bytesRead;
480 }
481 else
482 {
483
484
485
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
505 System.arraycopy(boundary, 2, boundary, 0, boundary.length - 2);
506 boundaryLength = boundary.length - 2;
507 try
508 {
509
510 discardBodyData();
511
512
513
514 return readBoundary();
515 }
516 catch (MalformedStreamException e)
517 {
518 return false;
519 }
520 finally
521 {
522
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
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730 }