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