001: // Copyright (C) 1999-2001 by Jason Hunter <jhunter_AT_acm_DOT_org>.
002: // All rights reserved. Use of this class is limited.
003: // Please see the LICENSE for more information.
004:
005: package com.oreilly.servlet.multipart;
006:
007: import java.io.FilterInputStream;
008: import java.io.IOException;
009: import javax.servlet.ServletInputStream;
010:
011: /**
012: * A <code>PartInputStream</code> filters a <code>ServletInputStream</code>,
013: * providing access to a single MIME part contained with in which ends with
014: * the boundary specified. It uses buffering to provide maximum performance.
015: * <p>
016: * Note the <code>readLine</code> method of <code>ServletInputStream</code>
017: * has the annoying habit of adding a \r\n to the end of the last line. Since
018: * we want a byte-for-byte transfer, we have to cut those chars. This means
019: * that we must always maintain at least 2 characters in our buffer to allow
020: * us to trim when necessary.
021: *
022: * @author Geoff Soutter
023: * @author Jason Hunter
024: * @version 1.4, 2002/11/01, fix for "unexpected end of part" caused by
025: * boundary newlines split across buffers
026: * @version 1.3, 2001/05/21, fix to handle boundaries crossing 64K mark
027: * @version 1.2, 2001/02/07, added read(byte[]) implementation for safety
028: * @version 1.1, 2000/11/26, fixed available() to never return negative
029: * @version 1.0, 2000/10/27, initial revision
030: */
031: public class PartInputStream extends FilterInputStream {
032: /** boundary which "ends" the stream */
033: private String boundary;
034:
035: /** our buffer */
036: private byte[] buf = new byte[64 * 1024]; // 64k
037:
038: /** number of bytes we've read into the buffer */
039: private int count;
040:
041: /** current position in the buffer */
042: private int pos;
043:
044: /** flag that indicates if we have encountered the boundary */
045: private boolean eof;
046:
047: /**
048: * Creates a <code>PartInputStream</code> which stops at the specified
049: * boundary from a <code>ServletInputStream<code>.
050: *
051: * @param in a servlet input stream.
052: * @param boundary the MIME boundary to stop at.
053: */
054: PartInputStream(ServletInputStream in, String boundary)
055: throws IOException {
056: super (in);
057: this .boundary = boundary;
058: }
059:
060: /**
061: * Fill up our buffer from the underlying input stream, and check for the
062: * boundary that signifies end-of-file. Users of this method must ensure
063: * that they leave exactly 2 characters in the buffer before calling this
064: * method (except the first time), so that we may only use these characters
065: * if a boundary is not found in the first line read.
066: *
067: * @exception IOException if an I/O error occurs.
068: */
069: private void fill() throws IOException {
070: if (eof)
071: return;
072:
073: // as long as we are not just starting up
074: if (count > 0) {
075: // if the caller left the requisite amount spare in the buffer
076: if (count - pos == 2) {
077: // copy it back to the start of the buffer
078: System.arraycopy(buf, pos, buf, 0, count - pos);
079: count -= pos;
080: pos = 0;
081: } else {
082: // should never happen, but just in case
083: throw new IllegalStateException(
084: "fill() detected illegal buffer state");
085: }
086: }
087:
088: // Try and fill the entire buffer, starting at count, line by line
089: // but never read so close to the end that we might split a boundary
090: // Thanks to Tony Chu, tony.chu@brio.com, for the -2 suggestion.
091: int read = 0;
092: int boundaryLength = boundary.length();
093: int maxRead = buf.length - boundaryLength - 2; // -2 is for /r/n
094: while (count < maxRead) {
095: // read a line
096: read = ((ServletInputStream) in).readLine(buf, count,
097: buf.length - count);
098: // check for eof and boundary
099: if (read == -1) {
100: throw new IOException("unexpected end of part");
101: } else {
102: if (read >= boundaryLength) {
103: eof = true;
104: for (int i = 0; i < boundaryLength; i++) {
105: if (boundary.charAt(i) != buf[count + i]) {
106: // Not the boundary!
107: eof = false;
108: break;
109: }
110: }
111: if (eof) {
112: break;
113: }
114: }
115: }
116: // success
117: count += read;
118: }
119: }
120:
121: /**
122: * See the general contract of the <code>read</code>
123: * method of <code>InputStream</code>.
124: * <p>
125: * Returns <code>-1</code> (end of file) when the MIME
126: * boundary of this part is encountered.
127: *
128: * @return the next byte of data, or <code>-1</code> if the end of the
129: * stream is reached.
130: * @exception IOException if an I/O error occurs.
131: */
132: public int read() throws IOException {
133: if (count - pos <= 2) {
134: fill();
135: if (count - pos <= 2) {
136: return -1;
137: }
138: }
139: return buf[pos++] & 0xff;
140: }
141:
142: /**
143: * See the general contract of the <code>read</code>
144: * method of <code>InputStream</code>.
145: * <p>
146: * Returns <code>-1</code> (end of file) when the MIME
147: * boundary of this part is encountered.
148: *
149: * @param b the buffer into which the data is read.
150: * @return the total number of bytes read into the buffer, or
151: * <code>-1</code> if there is no more data because the end
152: * of the stream has been reached.
153: * @exception IOException if an I/O error occurs.
154: */
155: public int read(byte b[]) throws IOException {
156: return read(b, 0, b.length);
157: }
158:
159: /**
160: * See the general contract of the <code>read</code>
161: * method of <code>InputStream</code>.
162: * <p>
163: * Returns <code>-1</code> (end of file) when the MIME
164: * boundary of this part is encountered.
165: *
166: * @param b the buffer into which the data is read.
167: * @param off the start offset of the data.
168: * @param len the maximum number of bytes read.
169: * @return the total number of bytes read into the buffer, or
170: * <code>-1</code> if there is no more data because the end
171: * of the stream has been reached.
172: * @exception IOException if an I/O error occurs.
173: */
174: public int read(byte b[], int off, int len) throws IOException {
175: int total = 0;
176: if (len == 0) {
177: return 0;
178: }
179:
180: int avail = count - pos - 2;
181: if (avail <= 0) {
182: fill();
183: avail = count - pos - 2;
184: if (avail <= 0) {
185: return -1;
186: }
187: }
188: int copy = Math.min(len, avail);
189: System.arraycopy(buf, pos, b, off, copy);
190: pos += copy;
191: total += copy;
192:
193: while (total < len) {
194: fill();
195: avail = count - pos - 2;
196: if (avail <= 0) {
197: return total;
198: }
199: copy = Math.min(len - total, avail);
200: System.arraycopy(buf, pos, b, off + total, copy);
201: pos += copy;
202: total += copy;
203: }
204: return total;
205: }
206:
207: /**
208: * Returns the number of bytes that can be read from this input stream
209: * without blocking. This is a standard <code>InputStream</code> idiom
210: * to deal with buffering gracefully, and is not same as the length of the
211: * part arriving in this stream.
212: *
213: * @return the number of bytes that can be read from the input stream
214: * without blocking.
215: * @exception IOException if an I/O error occurs.
216: */
217: public int available() throws IOException {
218: int avail = (count - pos - 2) + in.available();
219: // Never return a negative value
220: return (avail < 0 ? 0 : avail);
221: }
222:
223: /**
224: * Closes this input stream and releases any system resources
225: * associated with the stream.
226: * <p>
227: * This method will read any unread data in the MIME part so that the next
228: * part starts an an expected place in the parent <code>InputStream</code>.
229: * Note that if the client code forgets to call this method on error,
230: * <code>MultipartParser</code> will call it automatically if you call
231: * <code>readNextPart()</code>.
232: *
233: * @exception IOException if an I/O error occurs.
234: */
235: public void close() throws IOException {
236: if (!eof) {
237: while (read(buf, 0, buf.length) != -1)
238: ; // do nothing
239: }
240: }
241: }
|