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:
018: package org.apache.coyote.http11.filters;
019:
020: import java.io.IOException;
021:
022: import org.apache.tomcat.util.buf.ByteChunk;
023: import org.apache.tomcat.util.buf.HexUtils;
024:
025: import org.apache.coyote.InputBuffer;
026: import org.apache.coyote.Request;
027: import org.apache.coyote.http11.Constants;
028: import org.apache.coyote.http11.InputFilter;
029:
030: /**
031: * Chunked input filter. Parses chunked data according to
032: * <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.6.1">http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.6.1</a><br>
033: *
034: * @author Remy Maucherat
035: * @author Filip Hanik
036: */
037: public class ChunkedInputFilter implements InputFilter {
038:
039: // -------------------------------------------------------------- Constants
040:
041: protected static final String ENCODING_NAME = "chunked";
042: protected static final ByteChunk ENCODING = new ByteChunk();
043:
044: // ----------------------------------------------------- Static Initializer
045:
046: static {
047: ENCODING.setBytes(ENCODING_NAME.getBytes(), 0, ENCODING_NAME
048: .length());
049: }
050:
051: // ----------------------------------------------------- Instance Variables
052:
053: /**
054: * Next buffer in the pipeline.
055: */
056: protected InputBuffer buffer;
057:
058: /**
059: * Number of bytes remaining in the current chunk.
060: */
061: protected int remaining = 0;
062:
063: /**
064: * Position in the buffer.
065: */
066: protected int pos = 0;
067:
068: /**
069: * Last valid byte in the buffer.
070: */
071: protected int lastValid = 0;
072:
073: /**
074: * Read bytes buffer.
075: */
076: protected byte[] buf = null;
077:
078: /**
079: * Byte chunk used to read bytes.
080: */
081: protected ByteChunk readChunk = new ByteChunk();
082:
083: /**
084: * Flag set to true when the end chunk has been read.
085: */
086: protected boolean endChunk = false;
087:
088: /**
089: * Flag set to true if the next call to doRead() must parse a CRLF pair
090: * before doing anything else.
091: */
092: protected boolean needCRLFParse = false;
093:
094: // ------------------------------------------------------------- Properties
095:
096: // ---------------------------------------------------- InputBuffer Methods
097:
098: /**
099: * Read bytes.
100: *
101: * @return If the filter does request length control, this value is
102: * significant; it should be the number of bytes consumed from the buffer,
103: * up until the end of the current request body, or the buffer length,
104: * whichever is greater. If the filter does not do request body length
105: * control, the returned value should be -1.
106: */
107: public int doRead(ByteChunk chunk, Request req) throws IOException {
108:
109: if (endChunk)
110: return -1;
111:
112: if (needCRLFParse) {
113: needCRLFParse = false;
114: parseCRLF();
115: }
116:
117: if (remaining <= 0) {
118: if (!parseChunkHeader()) {
119: throw new IOException("Invalid chunk header");
120: }
121: if (endChunk) {
122: parseEndChunk();
123: return -1;
124: }
125: }
126:
127: int result = 0;
128:
129: if (pos >= lastValid) {
130: readBytes();
131: }
132:
133: if (remaining > (lastValid - pos)) {
134: result = lastValid - pos;
135: remaining = remaining - result;
136: chunk.setBytes(buf, pos, result);
137: pos = lastValid;
138: } else {
139: result = remaining;
140: chunk.setBytes(buf, pos, remaining);
141: pos = pos + remaining;
142: remaining = 0;
143: needCRLFParse = true;
144: }
145:
146: return result;
147:
148: }
149:
150: // ---------------------------------------------------- InputFilter Methods
151:
152: /**
153: * Read the content length from the request.
154: */
155: public void setRequest(Request request) {
156: }
157:
158: /**
159: * End the current request.
160: */
161: public long end() throws IOException {
162:
163: // Consume extra bytes : parse the stream until the end chunk is found
164: while (doRead(readChunk, null) >= 0) {
165: }
166:
167: // Return the number of extra bytes which were consumed
168: return (lastValid - pos);
169:
170: }
171:
172: /**
173: * Amount of bytes still available in a buffer.
174: */
175: public int available() {
176: return (lastValid - pos);
177: }
178:
179: /**
180: * Set the next buffer in the filter pipeline.
181: */
182: public void setBuffer(InputBuffer buffer) {
183: this .buffer = buffer;
184: }
185:
186: /**
187: * Make the filter ready to process the next request.
188: */
189: public void recycle() {
190: remaining = 0;
191: pos = 0;
192: lastValid = 0;
193: endChunk = false;
194: }
195:
196: /**
197: * Return the name of the associated encoding; Here, the value is
198: * "identity".
199: */
200: public ByteChunk getEncodingName() {
201: return ENCODING;
202: }
203:
204: // ------------------------------------------------------ Protected Methods
205:
206: /**
207: * Read bytes from the previous buffer.
208: */
209: protected int readBytes() throws IOException {
210:
211: int nRead = buffer.doRead(readChunk, null);
212: pos = readChunk.getStart();
213: lastValid = pos + nRead;
214: buf = readChunk.getBytes();
215:
216: return nRead;
217:
218: }
219:
220: /**
221: * Parse the header of a chunk.
222: * A chunk header can look like
223: * A10CRLF
224: * F23;chunk-extension to be ignoredCRLF
225: * The letters before CRLF but after the trailer mark, must be valid hex digits,
226: * we should not parse F23IAMGONNAMESSTHISUP34CRLF as a valid header
227: * according to spec
228: */
229: protected boolean parseChunkHeader() throws IOException {
230:
231: int result = 0;
232: boolean eol = false;
233: boolean readDigit = false;
234: boolean trailer = false;
235:
236: while (!eol) {
237:
238: if (pos >= lastValid) {
239: if (readBytes() <= 0)
240: return false;
241: }
242:
243: if (buf[pos] == Constants.CR) {
244: } else if (buf[pos] == Constants.LF) {
245: eol = true;
246: } else if (buf[pos] == Constants.SEMI_COLON) {
247: trailer = true;
248: } else if (!trailer) {
249: //don't read data after the trailer
250: if (HexUtils.DEC[buf[pos]] != -1) {
251: readDigit = true;
252: result *= 16;
253: result += HexUtils.DEC[buf[pos]];
254: } else {
255: //we shouldn't allow invalid, non hex characters
256: //in the chunked header
257: return false;
258: }
259: }
260:
261: pos++;
262:
263: }
264:
265: if (!readDigit)
266: return false;
267:
268: if (result == 0)
269: endChunk = true;
270:
271: remaining = result;
272: if (remaining < 0)
273: return false;
274:
275: return true;
276:
277: }
278:
279: /**
280: * Parse CRLF at end of chunk.
281: */
282: protected boolean parseCRLF() throws IOException {
283:
284: boolean eol = false;
285:
286: while (!eol) {
287:
288: if (pos >= lastValid) {
289: if (readBytes() <= 0)
290: throw new IOException("Invalid CRLF");
291: }
292:
293: if (buf[pos] == Constants.CR) {
294: } else if (buf[pos] == Constants.LF) {
295: eol = true;
296: } else {
297: throw new IOException("Invalid CRLF");
298: }
299:
300: pos++;
301:
302: }
303:
304: return true;
305:
306: }
307:
308: /**
309: * Parse end chunk data.
310: * FIXME: Handle trailers
311: */
312: protected boolean parseEndChunk() throws IOException {
313:
314: return parseCRLF(); // FIXME
315:
316: }
317:
318: }
|