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