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: MultipartMimeInput.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:
029: import com.lutris.util.BMByteSearch;
030: import com.lutris.util.BMByteSearchStream;
031:
032: /**
033: * Presents an incoming Multipart MIME message as a series of distinct
034: * MultipartMimeInputStream streams.
035: */
036: public class MultipartMimeInput {
037: /**
038: * The current section of Multipart input. This is maintained so that it can
039: * be invalidated if a request is made to go to the next section.
040: */
041: protected MultipartMimeInputStream currentStream;
042:
043: /**
044: * An input stream that does exact pattern searching using an efficient
045: * string matching algorithm.
046: */
047: protected BMByteSearchStream inputSource;
048:
049: /**
050: * The string that represents a newline for this instance of input. This is
051: * necessary because some web browsers (typically on Unix hosts) send LF
052: * instead of the Mime-required CR+LF.
053: */
054: protected String newlineString = "\r\n";
055:
056: /**
057: * The boundary string which separates different sections of the multipart
058: * Mime input.
059: */
060: protected String inputSeparator;
061:
062: /**
063: * Pre-compiled Boyer-Moore pattern search object for the Mime boundary
064: * string.
065: */
066: protected BMByteSearch inputPattern;
067:
068: /**
069: * True if the end of the input source has been reached and no more
070: * multipart sections are available to be read.
071: */
072: protected boolean atEOF = false;
073:
074: /**
075: * Constructs a new MultipartMimeInput object from an input source of type
076: * <code>InputStream</code> and a <code>ContentHeader</code> object. The
077: * <code>ContentHeader</code> object will usually be a
078: * <code>Content-Type</code> header and its value <i>must </i> begin with
079: * <code>"multipart/"</code> to indicate multipart Mime input. Other Mime
080: * types will cause a <code>MimeException</code> to be thrown. In
081: * addition, a parameter called <code>"boundary"</code> <i>must </i> exist
082: * in the header, since multipart Mime input is split using a boundary
083: * string passed in the <code>Content-Type</code> header.
084: *
085: * @param source
086: * Input stream from which Mime input will be read.
087: * @param contentHeader
088: * <code>ContentHeader</code> object containing a
089: * <code>boundary</code> field and Mime type to be used in
090: * scanning the input.
091: * @exception MimeException
092: * Thrown if an illegal header or Mime type is encountered
093: * while processing the input.
094: */
095: public MultipartMimeInput(InputStream source,
096: ContentHeader contentHeader) throws MimeException {
097: atEOF = false;
098: String value = contentHeader.getValue();
099: if (value == null) {
100: throw new MimeException("Missing content header value.",
101: MimeException.INVALID_HEADER);
102: }
103: if (!value.toLowerCase().trim().startsWith("multipart/")) {
104: throw new MimeException("Illegal mime type.",
105: MimeException.INVALID_MIME_TYPE);
106: }
107: String boundary = contentHeader.getParameter("boundary");
108: if (boundary == null) {
109: throw new MimeException("Missing boundary parameter.",
110: MimeException.INVALID_HEADER);
111: }
112:
113: //
114: // Skip the first boundary.
115: //
116: int skipped = 0;
117: try {
118: inputSource = new BMByteSearchStream(source, boundary, 2000);
119: skipped = inputSource.skipPattern();
120: } catch (IOException e) {
121: throw new MimeException(
122: "Error while reading to first boundary: "
123: + e.toString(), MimeException.GENERIC);
124: }
125: if (skipped < 1) {
126: throw new MimeException(
127: "Boundary pattern missing in input " + "stream.",
128: MimeException.GENERIC);
129: }
130:
131: //
132: // Skip newline after first boundary. Also determine whether
133: // "\n\r" (correct), or "\n" (incorrect) is being used. We're
134: // nice and handle both. The catch is that the user must be
135: // consistently right or consistently buggy, not both.
136: //
137: try {
138: switch (inputSource.read()) {
139: case '\r':
140: if (inputSource.read() != '\n') {
141: // This should be rare, but we'll try to accept it.
142: newlineString = "\r";
143: } else {
144: // Newline sequence is RFC2045-compliant (CR+LF).
145: newlineString = "\r\n";
146: }
147: break;
148: case '\n':
149: // Input was sent by an off-standard unix browser.
150: // Uses LF only. We'll be nice and accept the input
151: // as long as "LF only" is done consistently. Using
152: // CR+LF later will cause spurious CR's to be appended
153: // to input sections.
154: newlineString = "\n"; // Incorrect but accept anyway.
155: break;
156: default:
157: // If the boundary pattern is not followed immediately
158: // by a newline in any know form, then we just give up
159: // and return nothing.
160: atEOF = true; // Corrupt stream - no valid parts.
161: throw new MimeException(
162: "Missing newline after boundary.",
163: MimeException.INVALID_HEADER);
164: }
165: } catch (IOException e) {
166: throw new MimeException("Missing newline after boundary:"
167: + e.toString(), MimeException.INVALID_HEADER);
168: }
169: inputSeparator = newlineString + "--" + boundary;
170: inputPattern = new BMByteSearch(inputSeparator);
171: inputSource.setPattern(inputPattern);
172: }
173:
174: /**
175: * Returns the next section of the Multipart MIME stream as a
176: * MultipartMimeInputStream object. Returns <code>null</code> if the end
177: * of the input source has been reached.
178: * <P>
179: * <B>Note: </B>
180: * <P>
181: * Since Multipart MIME data flows in a single stream, calling this method
182: * causes the previously returned MultipartMimeInputStream to be
183: * automatically closed and its input to be skipped.
184: *
185: * @return A MultipartMimeInputStream object for the next section of the
186: * multipart message.
187: * @exception MimeException
188: * If an error occurs while skipping to the next section of
189: * input.
190: */
191: public MultipartMimeInputStream nextPart() throws MimeException {
192: return nextPart(null);
193: }
194:
195: /**
196: * Returns the next section of the Multipart MIME stream as a
197: * MultipartMimeInputStream object. Returns <code>null</code> if the end
198: * of the input source has been reached.
199: * <P>
200: * <B>Note: </B>
201: * <P>
202: * Since Multipart MIME data flows in a single stream, calling this method
203: * causes the previously returned MultipartMimeInputStream to be
204: * automatically closed and its input to be skipped.
205: *
206: * @param encoding
207: * String encoding to apply during MultipartMimeInputStream initialization.
208: * @return A MultipartMimeInputStream object for the next section of the
209: * multipart message.
210: * @exception MimeException
211: * If an error occurs while skipping to the next section of
212: * input.
213: */
214: public MultipartMimeInputStream nextPart(String encoding)
215: throws MimeException {
216: try {
217: if (currentStream != null) {
218: currentStream.close();
219: if (currentStream.lastPart)
220: atEOF = true;
221: }
222: if (atEOF)
223: return null;
224:
225: if (encoding != null && !"".equals(encoding))
226: currentStream = new MultipartMimeInputStream(
227: inputSource, inputPattern, encoding);
228: else
229: currentStream = new MultipartMimeInputStream(
230: inputSource, inputPattern);
231:
232: } catch (MimeEOFException e) {
233: return null;
234: } catch (IOException ioe) {
235: throw new MimeException("IO Error between parts: "
236: + ioe.toString(), MimeException.GENERIC);
237: }
238: return currentStream;
239: }
240:
241: /**
242: * Closes the input source and the current
243: * <code>MultipartMimeInputStream</code> object. No more parts
244: * will be returned by <code>nextPart</code>.
245: *
246: * @exception MimeException If an error occurs while closing the
247: * input stream.
248: */
249: public void close() throws MimeException {
250: try {
251: currentStream.close();
252: currentStream = null;
253: inputSource.close();
254: inputSource = null;
255: atEOF = true;
256: } catch (IOException ioe) {
257: throw new MimeException("MultipartMimeInput: "
258: + "IO Error during close: " + ioe.toString(),
259: MimeException.GENERIC);
260: }
261: }
262: }
|