001: /*
002: * Enhydra Java Application Server Project
003: *
004: * The contents of this file are subject to the Enhydra Public License
005: * Version 1.1 (the "License"); you may not use this file except in
006: * compliance with the License. You may obtain a copy of the License on
007: * the Enhydra web site ( http://www.enhydra.org/ ).
008: *
009: * Software distributed under the License is distributed on an "AS IS"
010: * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
011: * the License for the specific terms governing rights and limitations
012: * under the License.
013: *
014: * The Initial Developer of the Enhydra Application Server is Lutris
015: * Technologies, Inc. The Enhydra Application Server and portions created
016: * by Lutris Technologies, Inc. are Copyright Lutris Technologies, Inc.
017: * All Rights Reserved.
018: *
019: * Contributor(s):
020: *
021: * $Id: MultipartMimeInputStream.java,v 1.2 2006-06-15 13:47:00 sinisa Exp $
022: */
023:
024: package com.lutris.mime;
025:
026: import java.io.IOException;
027: import java.io.InputStream;
028: import java.io.UnsupportedEncodingException;
029: import java.util.Hashtable;
030:
031: import com.lutris.util.BMByteSearch;
032: import com.lutris.util.BMByteSearchStream;
033:
034: /**
035: * Presents the current section of a multipart MIME stream as a distinct
036: * InputStream object for which the end of the section is the end of the stream.
037: * MIME headers for the current section are extracted, parsed, and made
038: * available via the <code>getMimeHeaders()<code> method. The
039: * stream itself begins at the first byte following the Mime header
040: * section. Closing an instance of <code>MultipartMimeInputStream</code>
041: * causes it to skip data on the underlying input stream until the next
042: * section is found, or end-of-input is reached.
043: */
044: public class MultipartMimeInputStream extends InputStream {
045: /**
046: * Boyer-Moore streaming pattern searcher that represents the input source.
047: * It searches its InputStream, up to a specified pattern, returning the
048: * read data until the pattern is reached.
049: */
050: private BMByteSearchStream inputSource;
051:
052: /**
053: * The stream is closed if <code>true</code> or not closed if
054: * <code>false</code>.
055: */
056: private boolean closed;
057:
058: /**
059: * Precomputed separator pattern search object.
060: */
061: private BMByteSearch searchPattern;
062:
063: /**
064: * Precomputed BM search pattern for "\n". This should only ever be computed
065: * once.
066: */
067: private static BMByteSearch newlinePattern = null;
068:
069: /**
070: * Byte to read into for single-byte read() operation.
071: */
072: private byte[] readByte = new byte[1];
073:
074: /**
075: * EOF indicated to prevent skipping end marker twice.
076: */
077: private boolean atEOF = false;
078:
079: /**
080: * Array of raw, unparsed Mime headers.
081: */
082: private String[] rawHeaders = null;
083:
084: /**
085: * Array of unparseable headers.
086: */
087: private String[] garbageHeaders = null;
088:
089: /**
090: * Table of all parsed headers.
091: */
092: private Hashtable headers = null;
093:
094: /**
095: * Boolean that indicates that this input stream was the last part of the
096: * total multipart input. This field is only to be accessed by the
097: * MultipartMimeInput object.
098: */
099: boolean lastPart = false;
100:
101: /**
102: * Creates a MultipartFormStream object from the given pattern search stream
103: * object, separator, and end of line pattern. This constructor may only be
104: * called internally from within <code>MultipartMimeInput</code> or its
105: * derived subclasses. Other packages never create instances of this class
106: * directly.
107: *
108: * @param source
109: * Input stream that can perform a pattern serarch on the raw
110: * input stream.
111: * @param sep
112: * Separator pattern.
113: * @exception IOException
114: * If an I/O error occurs.
115: * @exception MimeEOFException
116: * If at EOF and the current section does not exist.
117: */
118: protected MultipartMimeInputStream(BMByteSearchStream source,
119: BMByteSearch sep) throws IOException, MimeEOFException {
120: this (source, sep, null);
121: }
122:
123: /**
124: * Creates a MultipartFormStream object from the given pattern search stream
125: * object, separator, and end of line pattern. This constructor may only be
126: * called internally from within <code>MultipartMimeInput</code> or its
127: * derived subclasses. Other packages never create instances of this class
128: * directly.
129: *
130: * @param source
131: * Input stream that can perform a pattern serarch on the raw
132: * input stream.
133: * @param sep
134: * Separator pattern.
135: * @param encoding
136: * String encoding to apply.
137: * @exception IOException
138: * If an I/O error occurs.
139: * @exception MimeEOFException
140: * If at EOF and the current section does not exist.
141: */
142: protected MultipartMimeInputStream(BMByteSearchStream source,
143: BMByteSearch sep, String encoding) throws IOException,
144: MimeEOFException {
145: int rawPos = 0, garbagePos = 0;
146: int n;
147: byte[] lineBuf = new byte[2048];
148: inputSource = source;
149: searchPattern = sep;
150:
151: // Create hash table for headers.
152: headers = new Hashtable();
153:
154: // Only create the newline pattern once.
155: if (newlinePattern == null) {
156: newlinePattern = new BMByteSearch("\n");
157: }
158: inputSource.setPattern(newlinePattern);
159:
160: //
161: // Read and process the MIME header up to and including
162: // the terminating blank line.
163: //
164: outer: while ((n = readAll(lineBuf)) > 0) {
165: if (lineBuf[n - 1] == '\r')
166: --n; // Handle CR+LF case.
167: if (n <= 0)
168: break outer; // ..and recheck length.
169: String line = null;
170:
171: /**
172: * Joćo Paulo Ribeiro patch: new, encoding dependant, MIME header
173: * line initialization.
174: */
175: if (encoding != null && !"".equals(encoding))
176: try {
177: line = new String(lineBuf, 0, n, encoding);
178: } catch (UnsupportedEncodingException exc) {
179: line = new String(bytesToChars(lineBuf, 0, n));
180: }
181: else
182: line = new String(bytesToChars(lineBuf, 0, n));
183:
184: String[] newRaw = new String[rawPos + 1];
185: for (int i = 0; i < rawPos; i++) {
186: newRaw[i] = rawHeaders[i];
187: rawHeaders[i] = null;
188: }
189: newRaw[rawPos++] = line;
190: rawHeaders = newRaw;
191: newRaw = null;
192: try {
193:
194: /*
195: * Michael Strapp patch: Parameters are now being picked up from
196: * the headers
197: */
198: //MimeHeader header = new MimeHeader(line);
199: MimeHeader header = new ContentHeader(line);
200:
201: if ((header.getValue() == null)
202: || (header.getHeaderType() == null)) {
203: // Not a parseable Mime header. Save under garbage
204: // collection key in case caller wants to try to parse it.
205: String[] newGarbage = new String[garbagePos + 1];
206: for (int i = 0; i < garbagePos; i++) {
207: newGarbage[i] = garbageHeaders[i];
208: garbageHeaders[i] = null;
209: }
210: newGarbage[garbagePos++] = line;
211: garbageHeaders = newGarbage;
212: newGarbage = null;
213: } else {
214: String name = header.getHeaderType().toLowerCase();
215: MimeHeader[] hdrs = (MimeHeader[]) headers
216: .get(name);
217: int num = (hdrs == null) ? 0 : hdrs.length;
218: MimeHeader[] newHdrs = new MimeHeader[num + 1];
219: for (int i = 0; i < num; i++) {
220: newHdrs[i] = hdrs[i];
221: hdrs[i] = null;
222: }
223: newHdrs[num] = header;
224: hdrs = null;
225: headers.put(name, newHdrs);
226: }
227: } catch (Exception e) {
228: throw new IOException("Error while reading "
229: + "MultipartMimeInputStream: " + e.toString());
230: }
231: } /* outer */
232: // Hack: the last part detection code would wrongly detect
233: // this part as the last one if it begins with "--" if we didn't
234: // reset this flag after headers parsing.
235: lastPart = false;
236: // Now, begin reading the stream.
237: inputSource.setPattern(searchPattern);
238: }
239:
240: /**
241: * Reads the next byte of data from this input stream. The value byte is
242: * returned as an <code>int</code> in the range 0 to 255. If no byte is
243: * available because the end of the stream has been reached, the value -1 is
244: * returned. This method blocks until input data is available, the end of
245: * the stream is detected, or an exception is thrown
246: *
247: * @return The next byte of data, or -1 if the end of stream is reached.
248: * @exception IOException
249: * If an I/O error occurs.
250: */
251: public int read() throws IOException {
252: int len = read(readByte, 0, 1);
253: if (len != 1)
254: return -1;
255: return ((int) readByte[0] + 0x100) & 0xff;
256: }
257:
258: /**
259: * Reads up to <code>buffer.length</code> bytes of data from this input
260: * stream into an array of bytes. This method blocks until some input is
261: * available
262: *
263: * @param buffer
264: * The buffer into which data are read.
265: * @return The number of bytes actually read, or -1 if there are no more
266: * bytes because the end of stream has been reached.
267: * @exception IOException
268: * If an I/O error occurs.
269: */
270: public int read(byte[] buffer) throws IOException {
271: return (read(buffer, 0, buffer.length));
272: }
273:
274: /**
275: * Reads <code>length</code> bytes of data from this input stream into an
276: * array of bytes. This method blocks until some input is available.
277: *
278: * @param buffer
279: * The buffer into which data are read.
280: * @param offset
281: * The start offset of the data.
282: * @param length
283: * The maximum number of bytes read.
284: * @return The total number of bytes read into the buffer, or -1 if there
285: * are no more bytes because the end of stream has been reached.
286: * @exception IOException
287: * If an I/O error occurs.
288: */
289: public int read(byte[] buffer, int offset, int length)
290: throws IOException {
291: checkOpen();
292: int ret = inputSource.readTo(buffer, offset, length);
293: switch (ret) {
294: case BMByteSearchStream.AT_PATTERN:
295: inputSource.skipPattern();
296: if (inputSource.peekAheadString(2).equals("--"))
297: lastPart = true;
298: inputSource.setPattern(newlinePattern);
299: inputSource.skipPattern();
300: atEOF = true;
301: return (-1);
302: case BMByteSearchStream.EOF:
303: return (-1);
304: default:
305: if (ret < 0)
306: return -1;
307: return (ret);
308: }
309: }
310:
311: /**
312: * Skips over and discards <code>n</code> bytes of data from this input
313: * stream. The <code>skip</code> method may, for a variety of reasons, end
314: * up skipping over some smaller number of bytes, possibly <code>0</code>.
315: * The actual number of bytes skipped is returned.
316: *
317: * @param num
318: * The number of bytes to be skipped.
319: * @return The actual number of bytes skipped.
320: *
321: * @exception IOException
322: * If an I/O error occurs.
323: */
324: public long skip(long num) throws IOException {
325: checkOpen();
326: byte[] buf = new byte[1024];
327: long count = 0, n;
328: while (count < num) {
329: if (num - count > 1024)
330: n = this .read(buf, 0, 1024);
331: else
332: n = this .read(buf, 0, (int) (num - count));
333: if (n < 0)
334: break;
335: count += n;
336: }
337: return count;
338: }
339:
340: /**
341: * Returns the number of bytes that can be read from this stream without
342: * blocking.
343: *
344: * @return The number of bytes that can be read from this stream without
345: * blocking.
346: *
347: * @exception IOException
348: * If an I/O error occurs.
349: */
350: public int available() throws IOException {
351: checkOpen();
352: return (inputSource.availableTo());
353: //return(inputSource.available());
354: }
355:
356: /**
357: * Skips all remaining bytes on this stream and closes it. Further
358: * operations on this stream, other than <code>close()</code> will cause
359: * an <code>IOException</code> to be thrown.
360: *
361: * @exception IOException
362: * If an I/O error occurs.
363: */
364: public void close() throws IOException {
365: if (!atEOF) {
366: inputSource.setPattern(searchPattern);
367: inputSource.skipPattern();
368: if (inputSource.peekAheadString(2).equals("--"))
369: lastPart = true;
370: inputSource.setPattern(newlinePattern);
371: inputSource.skipPattern();
372: }
373: atEOF = true;
374: closed = true;
375: }
376:
377: /**
378: * Returns a single <code>MimeHeader</code> object associated with a given
379: * Mime header name. If the selected header name is associated with more
380: * than one value, then the last instance received from the input stream is
381: * returned.
382: *
383: * @param headerName
384: * The name of the Mime header to return.
385: * @return The value of the header, or <code>null</code> if not found.
386: */
387: public MimeHeader getHeader(String headerName) {
388: MimeHeader[] hdrs = (MimeHeader[]) headers.get(headerName
389: .toLowerCase());
390: if (hdrs == null)
391: return null;
392: if (hdrs.length < 1)
393: return null;
394: return (hdrs[hdrs.length - 1]);
395: }
396:
397: /**
398: * Returns a array of type <code>MimeHeader</code> containing all values
399: * associated with a given Mime header name.
400: *
401: * @param headerName
402: * The name of the Mime header to return.
403: * @return All values associated with the header, or <code>null</code> if
404: * not found.
405: */
406: public MimeHeader[] getHeaders(String headerName) {
407: MimeHeader[] hdrs = (MimeHeader[]) headers.get(headerName
408: .toLowerCase());
409: if ((hdrs == null) || (hdrs.length < 1))
410: return null;
411: return hdrs;
412: }
413:
414: /**
415: * Returns an array of <code>String</code> containing each Mime header as
416: * it was read from the header of this section. All whitespace and
417: * punctuation is left intact.
418: *
419: * @return Array of raw Mime headers.
420: */
421: public String[] getRawHeaders() {
422: return rawHeaders;
423: }
424:
425: /**
426: * Returns an array of all Mime header lines that could not be parsed in the
427: * normal "name: value" fashion.
428: *
429: * @return Array of raw Mime headers.
430: */
431: public String[] getGarbageHeaders() {
432: return garbageHeaders;
433: }
434:
435: // ------ Private Utility Routines Below -------
436:
437: /**
438: * Reads into a byte array until the array is full, EOF is reached, or an
439: * I/O error occurs. This private method is intended only for reading
440: * individual lines of MIME headers. If the buffer is not long enough to
441: * hold an entire line from the input then the remainder bytes are skipped.
442: *
443: * @param buffer
444: * The buffer to read into.
445: * @return The number of bytes read.
446: * @exception IOException
447: * If an I/O error occurs.
448: * @exception MimeEOFException
449: * If EOF reached in the MIME header.
450: */
451: private int readAll(byte[] buffer) throws IOException,
452: MimeEOFException {
453: String la;
454: int pos = 0;
455: int len = buffer.length;
456: int n = 0;
457:
458: while (pos < len) {
459: switch (n = inputSource.readTo(buffer, pos, len - pos)) {
460: case BMByteSearchStream.EOF:
461: throw new MimeEOFException("EOF");
462: case BMByteSearchStream.AT_PATTERN:
463: inputSource.skipPattern();
464: if (inputSource.peekAheadString(2).equals("--"))
465: lastPart = true;
466: return pos;
467: default:
468: pos += n;
469: break;
470: }
471: }
472: //
473: // If the input line overflows the buffer, then the overflow
474: // part is discarded. Care should be taken to use a buffer
475: // large enough to hold any reasonable Mime header line.
476: //
477: inputSource.skipPattern();
478: return pos;
479: }
480:
481: /**
482: * Converts a byte array into a raw ASCII character array with characters in
483: * the range \u0000 to \u00ff by twos-complement signed-to-unsigned
484: * conversion.
485: *
486: * @param buffer
487: * The byte array to convert.
488: * @param off
489: * Offset in buffer where conversion begins.
490: * @param len
491: * Number of bytes to convert.
492: * @return New character array containing the converted bytes
493: */
494: private static final char[] bytesToChars(byte[] buffer, int off,
495: int len) {
496: char[] cbuf = new char[len];
497: int src, dst;
498: for (dst = 0, src = off; dst < len; dst++, src++) {
499: cbuf[dst] = (char) (((int) (buffer[src]) + 0x100) & 0xff);
500: }
501: return cbuf;
502: }
503:
504: /**
505: * Used by routines that fail after closure to verify that the current
506: * stream is open. If closed, this method throws an <code>IOException</code>
507: * to indicate an operation attempted on a closed stream.
508: *
509: * @exception IOException
510: * Thrown if the stream is closed.
511: */
512: private void checkOpen() throws IOException {
513: if (!closed)
514: return;
515: throw new IOException("Operation on a closed stream.");
516: }
517:
518: }
|