001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017: package org.apache.cocoon.servlet.multipart;
018:
019: import java.io.IOException;
020: import java.io.PushbackInputStream;
021:
022: /**
023: * Utility class for MultipartParser. Divides the inputstream into parts
024: * separated by a given boundary.
025: *
026: * A newline is espected after each boundary and is parsed away.
027: * @author <a href="mailto:j.tervoorde@home.nl">Jeroen ter Voorde</a>
028: * @version CVS $Id: TokenStream.java 433543 2006-08-22 06:22:54Z crossley $
029: */
030: class TokenStream extends PushbackInputStream {
031:
032: /**
033: * Initial state, no boundary has been set.
034: */
035: public static final int STATE_NOBOUNDARY = -1;
036:
037: /**
038: * Fully read a part, now at the beginning of a new part
039: */
040: public static final int STATE_NEXTPART = -2;
041:
042: /**
043: * Read last boundary, end of multipart block
044: */
045: public static final int STATE_ENDMULTIPART = -3;
046:
047: /**
048: * End of stream, this should not happen
049: */
050: public static final int STATE_ENDOFSTREAM = -4;
051:
052: /**
053: * Currently reading a part
054: */
055: public static final int STATE_READING = -5;
056:
057: /** Field in */
058: private PushbackInputStream in = null;
059:
060: /** Field boundary */
061: private byte[] boundary = null;
062:
063: /** Field state */
064: private int state = STATE_NOBOUNDARY;
065:
066: /**
067: * Creates a new pushback token stream from in.
068: *
069: * @param in The input stream
070: */
071: public TokenStream(PushbackInputStream in) {
072: this (in, 1);
073: }
074:
075: /**
076: * Creates a new pushback token stream from in.
077: *
078: * @param in The input stream
079: * @param size Size (in bytes) of the pushback buffer
080: */
081: public TokenStream(PushbackInputStream in, int size) {
082: super (in, size);
083: this .in = in;
084: }
085:
086: /**
087: * Sets the boundary to scan for
088: *
089: * @param boundary A byte array containg the boundary
090: *
091: * @throws MultipartException
092: */
093: public void setBoundary(byte[] boundary) throws MultipartException {
094: this .boundary = boundary;
095: if (state == STATE_NOBOUNDARY) {
096: state = STATE_READING;
097: }
098: }
099:
100: /**
101: * Start reading the next part in the stream. This method may only be called
102: * if state is STATE_NEXTPART. It will throw a MultipartException if not.
103: *
104: * @throws MultipartException
105: */
106: public void nextPart() throws MultipartException {
107: if (state != STATE_NEXTPART) {
108: throw new MultipartException("Illegal state");
109: }
110: state = STATE_READING;
111: }
112:
113: /**
114: * Return the stream state
115: */
116: public int getState() {
117: return state;
118: }
119:
120: /**
121: * Fill the ouput buffer until either it's full, the boundary has been reached or
122: * the end of the inputstream has been reached.
123: * When a boundary is reached it is entirely read away including trailing \r's and \n's.
124: * It will not be written to the output buffer.
125: * The stream state is updated after each call.
126: *
127: * @param out The output buffer
128: *
129: * @throws IOException
130: */
131: private int readToBoundary(byte[] out) throws IOException {
132: if (state != STATE_READING) {
133: return 0;
134: }
135: int boundaryIndex = 0;
136: int written = 0;
137: int b = in.read();
138:
139: while (true) {
140: while ((byte) b != boundary[0]) {
141: if (b == -1) {
142: state = STATE_ENDOFSTREAM;
143: return written;
144: }
145: out[written++] = (byte) b;
146:
147: if (written == out.length) {
148: return written;
149: }
150: b = in.read();
151: }
152: boundaryIndex = 0; // we know the first byte matched
153: // check for boundary
154: while ((boundaryIndex < boundary.length)
155: && ((byte) b == boundary[boundaryIndex])) {
156: b = in.read();
157: boundaryIndex++;
158: }
159:
160: if (boundaryIndex == boundary.length) { // matched boundary
161: if (b != -1) {
162: if (b == '\r') { // newline, another part follows
163: state = STATE_NEXTPART;
164: in.read();
165: } else if (b == '-') { // hyphen, end of multipart
166: state = STATE_ENDMULTIPART;
167: in.read(); // read next hyphen
168: in.read(); // read \r
169: in.read(); // read \n
170: } else { // something else, error
171: throw new IOException(
172: "Unexpected character after boundary");
173: }
174: } else { // nothing after boundary, this shouldn't happen either
175: state = STATE_ENDOFSTREAM;
176: }
177: return written;
178: } else { // did not match boundary
179: // bytes skipped, write first skipped byte, push back the rest
180: if (b != -1) { // b may be -1
181: in.unread(b); // the non-matching byte
182: }
183: in.unread(boundary, 1, boundaryIndex - 1); // unread skipped boundary data
184: out[written++] = boundary[0];
185: if (written == out.length) {
186: return written;
187: }
188: }
189: b = in.read();
190: }
191: }
192:
193: /**
194: * @see java.io.InputStream#read(byte[])
195: *
196: * @param out
197: *
198: * @throws IOException
199: */
200: public int read(byte[] out) throws IOException {
201: if (state != STATE_READING) {
202: return 0;
203: }
204: return readToBoundary(out);
205: }
206:
207: /**
208: * @see java.io.InputStream#read(byte[],int,int)
209: *
210: * @param out
211: * @param off
212: * @param len
213: *
214: * @throws IOException
215: */
216: public int read(byte[] out, int off, int len) throws IOException {
217: if ((off < 0) || (off >= out.length)) {
218: throw new IOException("Buffer offset outside buffer");
219: }
220: if (off + len >= out.length) {
221: throw new IOException("Buffer end outside buffer");
222: }
223: if (len < 0) {
224: throw new IOException("Length must be a positive integer");
225: }
226: byte[] buf = new byte[len];
227: int read = read(buf);
228: if (read > 0) {
229: System.arraycopy(buf, 0, out, off, read);
230: }
231: return read;
232: }
233:
234: /**
235: * @see java.io.InputStream#read()
236: *
237: * @throws IOException
238: */
239: public int read() throws IOException {
240: byte[] buf = new byte[1];
241: int read = read(buf);
242:
243: if (read == 0) {
244: return -1;
245: } else {
246: return buf[0];
247: }
248: }
249: }
|