| /* |
| * Copyright 1999-2004 The Apache Software Foundation |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package org.apache.coyote.http11.filters; |
| |
| import java.io.IOException; |
| |
| import org.apache.tomcat.util.buf.ByteChunk; |
| import org.apache.tomcat.util.buf.HexUtils; |
| |
| import org.apache.coyote.InputBuffer; |
| import org.apache.coyote.Request; |
| import org.apache.coyote.http11.Constants; |
| import org.apache.coyote.http11.InputFilter; |
| |
| /** |
| * Chunked input filter. |
| * |
| * @author Remy Maucherat |
| */ |
| public class ChunkedInputFilter implements InputFilter { |
| |
| |
| // -------------------------------------------------------------- Constants |
| |
| |
| protected static final String ENCODING_NAME = "chunked"; |
| protected static final ByteChunk ENCODING = new ByteChunk(); |
| |
| |
| // ----------------------------------------------------- Static Initializer |
| |
| |
| static { |
| ENCODING.setBytes(ENCODING_NAME.getBytes(), 0, ENCODING_NAME.length()); |
| } |
| |
| |
| // ----------------------------------------------------- Instance Variables |
| |
| |
| /** |
| * Next buffer in the pipeline. |
| */ |
| protected InputBuffer buffer; |
| |
| |
| /** |
| * Number of bytes remaining in the current chunk. |
| */ |
| protected int remaining = 0; |
| |
| |
| /** |
| * Position in the buffer. |
| */ |
| protected int pos = 0; |
| |
| |
| /** |
| * Last valid byte in the buffer. |
| */ |
| protected int lastValid = 0; |
| |
| |
| /** |
| * Read bytes buffer. |
| */ |
| protected byte[] buf = null; |
| |
| |
| /** |
| * Byte chunk used to read bytes. |
| */ |
| protected ByteChunk readChunk = new ByteChunk(); |
| |
| |
| /** |
| * Flag set to true when the end chunk has been read. |
| */ |
| protected boolean endChunk = false; |
| |
| /** |
| * Flag set to true if the next call to doRead() must parse a CRLF pair |
| * before doing anything else. |
| */ |
| protected boolean needCRLFParse = false; |
| |
| // ------------------------------------------------------------- Properties |
| |
| |
| // ---------------------------------------------------- InputBuffer Methods |
| |
| |
| /** |
| * Read bytes. |
| * |
| * @return If the filter does request length control, this value is |
| * significant; it should be the number of bytes consumed from the buffer, |
| * up until the end of the current request body, or the buffer length, |
| * whichever is greater. If the filter does not do request body length |
| * control, the returned value should be -1. |
| */ |
| public int doRead(ByteChunk chunk, Request req) |
| throws IOException { |
| |
| if (endChunk) |
| return -1; |
| |
| if(needCRLFParse) { |
| needCRLFParse = false; |
| parseCRLF(); |
| } |
| |
| if (remaining <= 0) { |
| if (!parseChunkHeader()) { |
| throw new IOException("Invalid chunk"); |
| } |
| if (endChunk) { |
| parseEndChunk(); |
| return -1; |
| } |
| } |
| |
| int result = 0; |
| |
| if (pos >= lastValid) { |
| readBytes(); |
| } |
| |
| if (remaining > (lastValid - pos)) { |
| result = lastValid - pos; |
| remaining = remaining - result; |
| chunk.setBytes(buf, pos, result); |
| pos = lastValid; |
| } else { |
| result = remaining; |
| chunk.setBytes(buf, pos, remaining); |
| pos = pos + remaining; |
| remaining = 0; |
| needCRLFParse = true; |
| } |
| |
| return result; |
| |
| } |
| |
| |
| // ---------------------------------------------------- InputFilter Methods |
| |
| |
| /** |
| * Read the content length from the request. |
| */ |
| public void setRequest(Request request) { |
| } |
| |
| |
| /** |
| * End the current request. |
| */ |
| public long end() |
| throws IOException { |
| |
| // Consume extra bytes : parse the stream until the end chunk is found |
| while (doRead(readChunk, null) >= 0) { |
| } |
| |
| // Return the number of extra bytes which were consumed |
| return (lastValid - pos); |
| |
| } |
| |
| |
| /** |
| * Set the next buffer in the filter pipeline. |
| */ |
| public void setBuffer(InputBuffer buffer) { |
| this.buffer = buffer; |
| } |
| |
| |
| /** |
| * Make the filter ready to process the next request. |
| */ |
| public void recycle() { |
| remaining = 0; |
| pos = 0; |
| lastValid = 0; |
| endChunk = false; |
| } |
| |
| |
| /** |
| * Return the name of the associated encoding; Here, the value is |
| * "identity". |
| */ |
| public ByteChunk getEncodingName() { |
| return ENCODING; |
| } |
| |
| |
| // ------------------------------------------------------ Protected Methods |
| |
| |
| /** |
| * Read bytes from the previous buffer. |
| */ |
| protected int readBytes() |
| throws IOException { |
| |
| int nRead = buffer.doRead(readChunk, null); |
| pos = readChunk.getStart(); |
| lastValid = pos + nRead; |
| buf = readChunk.getBytes(); |
| |
| return nRead; |
| |
| } |
| |
| |
| /** |
| * Parse the header of a chunk. |
| */ |
| protected boolean parseChunkHeader() |
| throws IOException { |
| |
| int result = 0; |
| boolean eol = false; |
| boolean readDigit = false; |
| |
| while (!eol) { |
| |
| if (pos >= lastValid) { |
| if (readBytes() <= 0) |
| return false; |
| } |
| |
| if (buf[pos] == Constants.CR) { |
| } else if (buf[pos] == Constants.LF) { |
| eol = true; |
| } else { |
| if (HexUtils.DEC[buf[pos]] != -1) { |
| readDigit = true; |
| result *= 16; |
| result += HexUtils.DEC[buf[pos]]; |
| } |
| } |
| |
| pos++; |
| |
| } |
| |
| if (!readDigit) |
| return false; |
| |
| if (result == 0) |
| endChunk = true; |
| |
| remaining = result; |
| if (remaining < 0) |
| return false; |
| |
| return true; |
| |
| } |
| |
| |
| /** |
| * Parse CRLF at end of chunk. |
| */ |
| protected boolean parseCRLF() |
| throws IOException { |
| |
| boolean eol = false; |
| |
| while (!eol) { |
| |
| if (pos >= lastValid) { |
| if (readBytes() <= 0) |
| throw new IOException("Invalid CRLF"); |
| } |
| |
| if (buf[pos] == Constants.CR) { |
| } else if (buf[pos] == Constants.LF) { |
| eol = true; |
| } else { |
| throw new IOException("Invalid CRLF"); |
| } |
| |
| pos++; |
| |
| } |
| |
| return true; |
| |
| } |
| |
| |
| /** |
| * Parse end chunk data. |
| * FIXME: Handle trailers |
| */ |
| protected boolean parseEndChunk() |
| throws IOException { |
| |
| return parseCRLF(); // FIXME |
| |
| } |
| |
| |
| } |