001: /* ====================================================================
002: * The Jcorporate Apache Style Software License, Version 1.2 05-07-2002
003: *
004: * Copyright (c) 1995-2002 Jcorporate Ltd. All rights reserved.
005: *
006: * Redistribution and use in source and binary forms, with or without
007: * modification, are permitted provided that the following conditions
008: * are met:
009: *
010: * 1. Redistributions of source code must retain the above copyright
011: * notice, this list of conditions and the following disclaimer.
012: *
013: * 2. Redistributions in binary form must reproduce the above copyright
014: * notice, this list of conditions and the following disclaimer in
015: * the documentation and/or other materials provided with the
016: * distribution.
017: *
018: * 3. The end-user documentation included with the redistribution,
019: * if any, must include the following acknowledgment:
020: * "This product includes software developed by Jcorporate Ltd.
021: * (http://www.jcorporate.com/)."
022: * Alternately, this acknowledgment may appear in the software itself,
023: * if and wherever such third-party acknowledgments normally appear.
024: *
025: * 4. "Jcorporate" and product names such as "Expresso" must
026: * not be used to endorse or promote products derived from this
027: * software without prior written permission. For written permission,
028: * please contact info@jcorporate.com.
029: *
030: * 5. Products derived from this software may not be called "Expresso",
031: * or other Jcorporate product names; nor may "Expresso" or other
032: * Jcorporate product names appear in their name, without prior
033: * written permission of Jcorporate Ltd.
034: *
035: * 6. No product derived from this software may compete in the same
036: * market space, i.e. framework, without prior written permission
037: * of Jcorporate Ltd. For written permission, please contact
038: * partners@jcorporate.com.
039: *
040: * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
041: * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
042: * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
043: * DISCLAIMED. IN NO EVENT SHALL JCORPORATE LTD OR ITS CONTRIBUTORS
044: * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
045: * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
046: * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
047: * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
048: * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
049: * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
050: * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
051: * SUCH DAMAGE.
052: * ====================================================================
053: *
054: * This software consists of voluntary contributions made by many
055: * individuals on behalf of the Jcorporate Ltd. Contributions back
056: * to the project(s) are encouraged when you make modifications.
057: * Please send them to support@jcorporate.com. For more information
058: * on Jcorporate Ltd. and its products, please see
059: * <http://www.jcorporate.com/>.
060: *
061: * Portions of this software are based upon other open source
062: * products and are subject to their respective licenses.
063: */
064:
065: package com.jcorporate.expresso.core.misc.upload;
066:
067: /* ====================================================================
068: * The Apache Software License, Version 1.1
069: *
070: * Copyright (c) 2001 The Apache Software Foundation. All rights
071: * reserved.
072: *
073: * Redistribution and use in source and binary forms, with or without
074: * modification, are permitted provided that the following conditions
075: * are met:
076: *
077: * 1. Redistributions of source code must retain the above copyright
078: * notice, this list of conditions and the following disclaimer.
079: *
080: * 2. Redistributions in binary form must reproduce the above copyright
081: * notice, this list of conditions and the following disclaimer in
082: * the documentation and/or other materials provided with the
083: * distribution.
084: *
085: * 3. The end-user documentation included with the redistribution,
086: * if any, must include the following acknowledgment:
087: * "This product includes software developed by the
088: * Apache Software Foundation (http://www.apache.org/)."
089: * Alternately, this acknowledgment may appear in the software itself,
090: * if and wherever such third-party acknowledgments normally appear.
091: *
092: * 4. The names "Apache" and "Apache Software Foundation" and
093: * "Apache Turbine" must not be used to endorse or promote products
094: * derived from this software without prior written permission. For
095: * written permission, please contact apache@apache.org.
096: *
097: * 5. Products derived from this software may not be called "Apache",
098: * "Apache Turbine", nor may "Apache" appear in their name, without
099: * prior written permission of the Apache Software Foundation.
100: *
101: * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
102: * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
103: * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
104: * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
105: * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
106: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
107: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
108: * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
109: * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
110: * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
111: * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
112: * SUCH DAMAGE.
113: * ====================================================================
114: *
115: * This software consists of voluntary contributions made by many
116: * individuals on behalf of the Apache Software Foundation. For more
117: * information on the Apache Software Foundation, please see
118: * <http://www.apache.org/>.
119: */
120: // Java stuff.
121: import java.io.IOException;
122: import java.io.InputStream;
123: import java.io.OutputStream;
124:
125: /**
126: * This class can be used to process data streams conforming to MIME
127: * 'multipart' format as defined in <a
128: * href="http://www.ietf.org/rfc/rfc1521.txt">RFC 1521</a>. Arbitrary
129: * large amouns of data in the stream can be processed under constant
130: * memory usage.
131: * <p/>
132: * <p>The format of the stream is defined in the following way:<br>
133: * <p/>
134: * <code>
135: * multipart-body := preamble 1*encapsulation close-delimiter epilogue<br>
136: * encapsulation := delimiter body CRLF<br>
137: * delimiter := "--" boundary CRLF<br>
138: * close-delimiter := "--" boudary "--"<br>
139: * preamble := <ignore><br>
140: * epilogue := <ignore><br>
141: * body := header-part CRLF body-part<br>
142: * header-part := 1*header CRLF<br>
143: * header := header-name ":" header-value<br>
144: * header-name := <printable ascii characters except ":"><br>
145: * header-value := <any ascii characters except CR & LF><br>
146: * body-data := <arbitrary data><br>
147: * </code>
148: * <p/>
149: * <p>Note that body-data can contain another mulipart entity. There
150: * is limited support for single pass processing of such nested
151: * streams. The nested stream is <strong>required</strong> to have a
152: * boundary token of the same length as the parent stream (see {@link
153: * #setBoundary(byte[])}).
154: * <p/>
155: * <p>Here is an exaple of usage of this class.<br>
156: * <p/>
157: * <pre>
158: * try {
159: * MultipartStream multipartStream = new MultipartStream(input,
160: * boundary);
161: * boolean nextPart = malitPartStream.skipPreamble();
162: * OutputStream output;
163: * while(nextPart) {
164: * header = chunks.readHeader();
165: * // process headers
166: * // create some output stream
167: * multipartStream.readBodyPart(output);
168: * nextPart = multipartStream.readBoundary();
169: * }
170: * } catch(MultipartStream.MalformedStreamException e) {
171: * // the stream failed to follow required syntax
172: * } catch(IOException) {
173: * // a read or write error occurred
174: * }
175: * <p/>
176: * </pre>
177: *
178: * @author <a href="mailto:Rafal.Krzewski@e-point.pl">Rafal Krzewski</a>
179: * @version $Id: MultipartStream.java,v 1.9 2004/11/18 02:03:27 lhamel Exp $
180: */
181: public class MultipartStream {
182:
183: /**
184: * The maximum lenght of <code>header-part</code> that will be
185: * processed (10 kilobytes = 10240 bytes.
186: * )
187: */
188: public static final int HEADER_PART_SIZE_MAX = 10240;
189:
190: /**
191: * The stream were data is read from.
192: */
193: protected InputStream input;
194:
195: /**
196: * The lenght of boundary token plus leading <code>CRLF--</code>.
197: */
198: protected int boundaryLength;
199:
200: /**
201: * The amount of data that must be kept in the buffer in order to
202: * detect delimiters reliably.
203: */
204: protected int keepRegion;
205:
206: /**
207: * A byte sequence that partitions the stream.
208: */
209: protected byte[] boundary;
210:
211: /**
212: * The lenght of the buffer used for processing.
213: */
214: protected int bufSize;
215:
216: /**
217: * The default lenght of the buffer used for processing.
218: */
219: protected static final int DEFAULT_BUFSIZE = 4096;
220:
221: /**
222: * The buffer used for processing.
223: */
224: protected byte[] buffer;
225:
226: /**
227: * The index of first valid character in the buffer.
228: * <p/>
229: * 0 <= head < bufSize
230: */
231: protected int head;
232:
233: /**
234: * The index of last valid characer in the buffer + 1.
235: * <p/>
236: * 0 <= tail <= bufSize
237: */
238: protected int tail;
239:
240: /**
241: * A byte sequence that marks the end of <code>header-part</code>
242: * (<code>CRLFCRLF</code>).
243: */
244: protected static final byte[] HEADER_SEPARATOR = { 0x0D, 0x0A,
245: 0x0D, 0x0A };
246:
247: /**
248: * A byte sequence that that follows a delimiter that will be
249: * followed by an encapsulation (<code>CRLF</code>).
250: */
251: protected static final byte[] FIELD_SEPARATOR = { 0x0D, 0x0A };
252:
253: /**
254: * A byte sequence that that follows a delimiter of the last
255: * encapsulation in the stream (<code>--</code>).
256: */
257: protected static final byte[] STREAM_TERMINATOR = { 0x2D, 0x2D };
258:
259: /**
260: * Constructs a MultipartStream with a custom size buffer.
261: * <p/>
262: * <p>Note that the buffer must be at least big enough to contain
263: * the boundary string, plus 4 characters for CR/LF and double
264: * dash, plus at least one byte of data. Too small buffer size
265: * setting will degrade performance.
266: *
267: * @param input The <code>InputStream</code> to serve as a data
268: * source.
269: * @param boundary The token used for dividing the stream into
270: * <code>encapsulations</code>.
271: * @param bufSize The size of the buffer to be used in bytes.
272: */
273: public MultipartStream(InputStream input, byte[] boundary,
274: int bufSize) throws MalformedStreamException, IOException {
275: this .input = input;
276: this .bufSize = bufSize;
277: this .buffer = new byte[bufSize];
278:
279: // We prepend CR/LF to the boundary to chop trailng CR/LF from
280: // body-data tokens.
281: this .boundary = new byte[boundary.length + 4];
282: this .boundaryLength = boundary.length + 4;
283: this .keepRegion = boundary.length + 3;
284: this .boundary[0] = 0x0D;
285: this .boundary[1] = 0x0A;
286: this .boundary[2] = 0x2D;
287: this .boundary[3] = 0x2D;
288: System
289: .arraycopy(boundary, 0, this .boundary, 4,
290: boundary.length);
291: head = 0;
292: tail = 0;
293: }
294:
295: /**
296: * Constructs a MultipartStream with a defalut size buffer.
297: *
298: * @param input The <code>InputStream</code> to serve as a data
299: * source.
300: * @param boundary The token used for dividing the stream into
301: * <code>encapsulations</code>.
302: */
303: public MultipartStream(InputStream input, byte[] boundary)
304: throws IOException {
305: this (input, boundary, DEFAULT_BUFSIZE);
306: }
307:
308: /**
309: * Reads a byte from the <code>buffer</code>, and refills it as
310: * neccessary.
311: *
312: * @return Next byte from the input stream.
313: * @throws IOException if there isn't any more data available.
314: */
315: public byte readByte() throws IOException {
316:
317: // Buffer depleted ?
318: if (head == tail) {
319: head = 0;
320:
321: // Refill.
322: tail = input.read(buffer, head, bufSize);
323:
324: if (tail == -1) {
325:
326: // No more data available.
327: throw new IOException("No more data is available");
328: }
329: }
330:
331: return buffer[head++];
332: }
333:
334: /**
335: * Skips a <code>boundary</code> token, and checks wether more
336: * <code>encapsulations</code> are contained in the stream.
337: *
338: * @return <code>True</code> if there are more encapsulations in
339: * this stream.
340: * @throws MalformedStreamException if the stream ends
341: * unexpecetedly or fails to follow required syntax.
342: */
343: public boolean readBoundary() throws MalformedStreamException {
344: byte[] marker = new byte[2];
345: boolean nextChunk = false;
346: head += boundaryLength;
347:
348: try {
349: marker[0] = readByte();
350: marker[1] = readByte();
351:
352: if (arrayequals(marker, STREAM_TERMINATOR, 2)) {
353: nextChunk = false;
354: } else if (arrayequals(marker, FIELD_SEPARATOR, 2)) {
355: nextChunk = true;
356: } else {
357: throw new MalformedStreamException(
358: "Unexpected characters follow a boundary");
359: }
360: } catch (IOException e) {
361: throw new MalformedStreamException(
362: "Stream ended unexpectedly");
363: }
364:
365: return nextChunk;
366: }
367:
368: /**
369: * Changes the boundary token used for partitioning the stream.
370: * <p/>
371: * <p>This method allows single pass processing of nested
372: * multipart streams.
373: * <p/>
374: * <p>The boundary token of the nested stream is
375: * <code>required</code> to be of the same length as the boundary
376: * token in parent stream.
377: * <p/>
378: * <p>Restoring parent stream boundary token after processing of a
379: * nested stream is left ot the application. <br>
380: *
381: * @param boundary A boundary to be used for parsing of the nested
382: * stream.
383: * @throws IllegalBoundaryException if <code>boundary</code>
384: * has diffrent lenght than the one being currently in use.
385: */
386: public void setBoundary(byte[] boundary)
387: throws IllegalBoundaryException {
388: if (boundary.length != boundaryLength - 4) {
389: throw new IllegalBoundaryException(
390: "The length of a boundary token can not be changed");
391: }
392:
393: System
394: .arraycopy(boundary, 0, this .boundary, 4,
395: boundary.length);
396: }
397:
398: /**
399: * <p>Reads <code>header-part</code> of the current
400: * <code>encapsulation</code>
401: * <p/>
402: * <p>Headers are returned verbatim to the input stream, including
403: * traling <code>CRLF</code> marker. Parsing is left to the
404: * application.
405: * <p/>
406: * <p><strong>TODO</strong> allow limiting maximum header size to
407: * protect against abuse.<br>
408: *
409: * @return <code>header-part</code> of the current encapsulation.
410: * @throws MalformedStreamException if the stream ends
411: * unexpecetedly.
412: */
413: public String readHeaders() throws MalformedStreamException {
414: int i = 0;
415: byte[] b = new byte[1];
416: StringBuffer buf = new StringBuffer();
417: int sizeMax = HEADER_PART_SIZE_MAX;
418: int size = 0;
419:
420: while (i < 4) {
421: try {
422: b[0] = readByte();
423: } catch (IOException e) {
424: throw new MalformedStreamException(
425: "Stream ended unexpectedly");
426: }
427:
428: size++;
429:
430: if (b[0] == HEADER_SEPARATOR[i]) {
431: i++;
432: } else {
433: i = 0;
434: }
435: if (size <= sizeMax) {
436: buf.append(new String(b));
437: }
438: }
439:
440: return buf.toString();
441: }
442:
443: /**
444: * Reads <code>body-data</code> from the current
445: * <code>encapsulation</code> and writes its contents into the
446: * output <code>Stream</code>.
447: * <p/>
448: * <p>Arbitrary large amouts of data can be processed by this
449: * method using a constant size buffer. (see {@link
450: * #MultipartStream(InputStream,byte[],int) constructor}).
451: *
452: * @param output The <code>Stream</code> to write data into.
453: * @return the amount of data written.
454: */
455: public int readBodyData(OutputStream output)
456: throws MalformedStreamException, IOException {
457: if (output == null) {
458: throw new IllegalArgumentException(
459: "OutputStream may not be null");
460: }
461:
462: boolean done = false;
463: int pad;
464: int pos;
465: int bytesRead;
466: int total = 0;
467:
468: while (!done) {
469:
470: // Is boundary token present somewere in the buffer?
471: pos = findSeparator();
472:
473: if (pos != -1) {
474:
475: // Write the rest of the data before the boundary.
476: output.write(buffer, head, pos - head);
477: total += pos - head;
478: head = pos;
479: done = true;
480: } else {
481:
482: // Determine how much data should be kept in the
483: // buffer.
484: if (tail - head > keepRegion) {
485: pad = keepRegion;
486: } else {
487: pad = tail - head;
488: }
489:
490: // Write out the data belonging to the body-data.
491: output.write(buffer, head, tail - head - pad);
492:
493: // Move the data to the beging of the buffer.
494: total += tail - head - pad;
495: System.arraycopy(buffer, tail - pad, buffer, 0, pad);
496:
497: // Refill buffer with new data.
498: head = 0;
499: bytesRead = input.read(buffer, pad, bufSize - pad);
500:
501: // [pprrrrrrr]
502: if (bytesRead != -1) {
503: tail = pad + bytesRead;
504: } else {
505:
506: // The last pad amount is left in the buffer.
507: // Boundary can't be in there so write out the
508: // data you have and signal an error condition.
509: output.write(buffer, 0, pad);
510: total += pad;
511: throw new MalformedStreamException(
512: "Stream ended unexpectedly");
513: }
514: }
515: }
516:
517: return total;
518: }
519:
520: /**
521: * Reads <code>body-data</code> from the current
522: * <code>encapsulation</code> and discards it.
523: * <p/>
524: * <p>Use this method to skip encapsulations you don't need or
525: * don't understand.
526: *
527: * @return The amount of data discarded.
528: */
529: public int discardBodyData() throws MalformedStreamException,
530: IOException {
531: boolean done = false;
532: int pad;
533: int pos;
534: int bytesRead;
535: int total = 0;
536:
537: while (!done) {
538:
539: // Is boundary token present somewere in the buffer?
540: pos = findSeparator();
541:
542: if (pos != -1) {
543:
544: // Write the rest of the data before the boundary.
545: total += pos - head;
546: head = pos;
547: done = true;
548: } else {
549:
550: // Determine how much data should be kept in the
551: // buffer.
552: if (tail - head > keepRegion) {
553: pad = keepRegion;
554: } else {
555: pad = tail - head;
556: }
557:
558: total += tail - head - pad;
559:
560: // Move the data to the beging of the buffer.
561: System.arraycopy(buffer, tail - pad, buffer, 0, pad);
562:
563: // Refill buffer with new data.
564: head = 0;
565: bytesRead = input.read(buffer, pad, bufSize - pad);
566:
567: // [pprrrrrrr]
568: if (bytesRead != -1) {
569: tail = pad + bytesRead;
570: } else {
571:
572: // The last pad amount is left in the buffer.
573: // Boundary can't be in there so signal an error
574: // condition.
575: total += pad;
576: throw new MalformedStreamException(
577: "Stream ended unexpectedly");
578: }
579: }
580: }
581:
582: return total;
583: }
584:
585: /**
586: * Finds the beginning of the first <code>encapsulation</code>.
587: *
588: * @return <code>True</code> if an <code>encapsulation</code> was
589: * found in the stream.
590: */
591: public boolean skipPreamble() throws IOException {
592:
593: // First delimiter may be not preceeded with a CRLF.
594: System.arraycopy(boundary, 2, boundary, 0, boundary.length - 2);
595: boundaryLength = boundary.length - 2;
596:
597: try {
598:
599: // Discard all data up to the delimiter.
600: discardBodyData();
601:
602: // Read boundary - if succeded, the stream contains an
603: // encapsulation.
604: return readBoundary();
605: } catch (MalformedStreamException e) {
606: return false;
607: } finally {
608:
609: // Restore delimiter.
610: System.arraycopy(boundary, 0, boundary, 2,
611: boundary.length - 2);
612: boundaryLength = boundary.length;
613: boundary[0] = 0x0D;
614: boundary[1] = 0x0A;
615: }
616: }
617:
618: /**
619: * Compares <code>count</code> first bytes in the arrays
620: * <code>a</code> and <code>b</code>.
621: *
622: * @param a The first array to compare.
623: * @param b The second array to compare.
624: * @param count How many bytes should be compared.
625: * @return <code>true</code> if <code>count</code> first bytes in
626: * arrays <code>a</code> and <code>b</code> are equal.
627: */
628: public static boolean arrayequals(byte[] a, byte[] b, int count) {
629: for (int i = 0; i < count; i++) {
630: if (a[i] != b[i]) {
631: return false;
632: }
633: }
634:
635: return true;
636: }
637:
638: /**
639: * Searches a byte of specified value in the <code>buffer</code>
640: * starting at specified <code>position</code>.
641: *
642: * @param value the value to find.
643: * @param pos The starting position for searching.
644: * @return The position of byte found, counting from beginning of
645: * the <code>buffer</code>, or <code>-1</code> if not found.
646: */
647: protected int findByte(byte value, int pos) {
648: for (int i = pos; i < tail; i++) {
649: if (buffer[i] == value) {
650: return i;
651: }
652: }
653:
654: return -1;
655: }
656:
657: /**
658: * Searches the <code>boundary</code> in <code>buffer</code>
659: * region delimited by <code>head</code> and <code>tail</code>.
660: *
661: * @return The position of the boundary found, counting from
662: * beginning of the <code>buffer</code>, or <code>-1</code> if not
663: * found.
664: */
665: protected int findSeparator() {
666: int first;
667: int match = 0;
668: int maxpos = tail - boundaryLength;
669:
670: for (first = head; (first <= maxpos)
671: && (match != boundaryLength); first++) {
672: first = findByte(boundary[0], first);
673:
674: if (first == -1 || (first > maxpos)) {
675: return -1;
676: }
677: for (match = 1; match < boundaryLength; match++) {
678: if (buffer[first + match] != boundary[match]) {
679: break;
680: }
681: }
682: }
683: if (match == boundaryLength) {
684: return first - 1;
685: }
686:
687: return -1;
688: }
689:
690: /**
691: * Thrown to indicate that the input stream fails to follow the
692: * required syntax.
693: */
694: public class MalformedStreamException extends IOException {
695: /**
696: * Constructs a <code>MalformedStreamException</code> with no
697: * detail message.
698: */
699: public MalformedStreamException() {
700: super ();
701: }
702:
703: /**
704: * Constructs an <code>MalformedStreamException</code> with
705: * the specified detail message.
706: *
707: * @param message The detail message.
708: */
709: public MalformedStreamException(String message) {
710: super (message);
711: }
712: }
713:
714: /**
715: * Thrown upon attempt of setting an invalid boundary token.
716: */
717: public class IllegalBoundaryException extends IOException {
718: /**
719: * Constructs an <code>IllegalBoundaryException</code> with no
720: * detail message.
721: */
722: public IllegalBoundaryException() {
723: super ();
724: }
725:
726: /**
727: * Constructs an <code>IllegalBoundaryException</code> with
728: * the specified detail message.
729: *
730: * @param message The detail message.
731: */
732: public IllegalBoundaryException(String message) {
733: super (message);
734: }
735: }
736:
737: /*-------------------------------------------------------------
738:
739: // These are the methods that were used to debug this stuff.
740:
741: // Dump data.
742:
743: protected void dump()
744:
745: {
746:
747: System.out.println("01234567890");
748:
749: byte[] temp = new byte[buffer.length];
750:
751: for(int i=0; i<buffer.length; i++)
752:
753: {
754:
755: if(buffer[i] == 0x0D || buffer[i] == 0x0A)
756:
757: {
758:
759: temp[i] = 0x21;
760:
761: }
762:
763: else
764:
765: {
766:
767: temp[i] = buffer[i];
768:
769: }
770:
771: }
772:
773: System.out.println(new String(temp));
774:
775: int i;
776:
777: for(i=0; i<head; i++)
778:
779: System.out.print(" ");
780:
781: System.out.println("h");
782:
783: for(i=0; i<tail; i++)
784:
785: System.out.print(" ");
786:
787: System.out.println("t");
788:
789: System.out.flush();
790:
791: }
792:
793: // Main routine, for testing purposes only.
794:
795: //
796:
797: // @param args A String[] with the command line arguments.
798:
799: // @exception Exception, a generic exception.
800:
801: public static void main( String[] args )
802:
803: throws Exception
804:
805: {
806:
807: File boundaryFile = new File("boundary.dat");
808:
809: int boundarySize = (int)boundaryFile.length();
810:
811: byte[] boundary = new byte[boundarySize];
812:
813: FileInputStream input = new FileInputStream(boundaryFile);
814:
815: input.read(boundary,0,boundarySize);
816:
817: input = new FileInputStream("multipart.dat");
818:
819: MultipartStream chunks = new MultipartStream(input, boundary);
820:
821: int i = 0;
822:
823: String header;
824:
825: OutputStream output;
826:
827: boolean nextChunk = chunks.skipPreamble();
828:
829: while(nextChunk)
830:
831: {
832:
833: header = chunks.readHeaders();
834:
835: System.out.println("!"+header+"!");
836:
837: System.out.println("wrote part"+i+".dat");
838:
839: output = new FileOutputStream("part"+(i++)+".dat");
840:
841: chunks.readBodyData(output);
842:
843: nextChunk = chunks.readBoundary();
844:
845: }
846:
847: }
848:
849: */
850: }
|