001: /*
002: * $HeadURL: https://svn.apache.org/repos/asf/httpcomponents/httpcore/tags/4.0-beta1/module-nio/src/main/java/org/apache/http/impl/nio/codecs/ChunkDecoder.java $
003: * $Revision: 613298 $
004: * $Date: 2008-01-18 23:09:22 +0100 (Fri, 18 Jan 2008) $
005: *
006: * ====================================================================
007: * Licensed to the Apache Software Foundation (ASF) under one
008: * or more contributor license agreements. See the NOTICE file
009: * distributed with this work for additional information
010: * regarding copyright ownership. The ASF licenses this file
011: * to you under the Apache License, Version 2.0 (the
012: * "License"); you may not use this file except in compliance
013: * with the License. You may obtain a copy of the License at
014: *
015: * http://www.apache.org/licenses/LICENSE-2.0
016: *
017: * Unless required by applicable law or agreed to in writing,
018: * software distributed under the License is distributed on an
019: * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
020: * KIND, either express or implied. See the License for the
021: * specific language governing permissions and limitations
022: * under the License.
023: * ====================================================================
024: *
025: * This software consists of voluntary contributions made by many
026: * individuals on behalf of the Apache Software Foundation. For more
027: * information on the Apache Software Foundation, please see
028: * <http://www.apache.org/>.
029: *
030: */
031:
032: package org.apache.http.impl.nio.codecs;
033:
034: import java.io.IOException;
035: import java.nio.ByteBuffer;
036: import java.nio.channels.ReadableByteChannel;
037: import java.util.ArrayList;
038: import java.util.List;
039:
040: import org.apache.http.Header;
041: import org.apache.http.MalformedChunkCodingException;
042: import org.apache.http.message.BufferedHeader;
043: import org.apache.http.nio.reactor.SessionInputBuffer;
044: import org.apache.http.impl.io.HttpTransportMetricsImpl;
045: import org.apache.http.ParseException;
046: import org.apache.http.protocol.HTTP;
047: import org.apache.http.util.CharArrayBuffer;
048:
049: public class ChunkDecoder extends AbstractContentDecoder {
050:
051: private static final int READ_CONTENT = 0;
052: private static final int READ_FOOTERS = 1;
053: private static final int COMPLETED = 2;
054:
055: private int state;
056: private boolean endOfChunk;
057: private boolean endOfStream;
058:
059: private CharArrayBuffer lineBuf;
060: private int chunkSize;
061: private int pos;
062:
063: private final List<CharArrayBuffer> trailerBufs;
064:
065: private Header[] footers;
066:
067: public ChunkDecoder(final ReadableByteChannel channel,
068: final SessionInputBuffer buffer,
069: final HttpTransportMetricsImpl metrics) {
070: super (channel, buffer, metrics);
071: this .state = READ_CONTENT;
072: this .chunkSize = -1;
073: this .pos = 0;
074: this .endOfChunk = false;
075: this .endOfStream = false;
076: this .trailerBufs = new ArrayList<CharArrayBuffer>();
077: }
078:
079: private void readChunkHead() throws IOException {
080: if (this .endOfChunk) {
081: if (this .buffer.length() < 2) {
082: return;
083: }
084: int cr = this .buffer.read();
085: int lf = this .buffer.read();
086: if (cr != HTTP.CR || lf != HTTP.LF) {
087: throw new MalformedChunkCodingException(
088: "CRLF expected at end of chunk");
089: }
090: this .endOfChunk = false;
091: }
092: if (this .lineBuf == null) {
093: this .lineBuf = new CharArrayBuffer(32);
094: } else {
095: this .lineBuf.clear();
096: }
097: if (this .buffer.readLine(this .lineBuf, this .endOfStream)) {
098: int separator = this .lineBuf.indexOf(';');
099: if (separator < 0) {
100: separator = this .lineBuf.length();
101: }
102: try {
103: String s = this .lineBuf.substringTrimmed(0, separator);
104: this .chunkSize = Integer.parseInt(s, 16);
105: } catch (NumberFormatException e) {
106: throw new MalformedChunkCodingException(
107: "Bad chunk header");
108: }
109: this .pos = 0;
110: }
111: }
112:
113: private void parseHeader() {
114: CharArrayBuffer current = this .lineBuf;
115: int count = this .trailerBufs.size();
116: if ((this .lineBuf.charAt(0) == ' ' || this .lineBuf.charAt(0) == '\t')
117: && count > 0) {
118: // Handle folded header line
119: CharArrayBuffer previous = this .trailerBufs.get(count - 1);
120: int i = 0;
121: while (i < current.length()) {
122: char ch = current.charAt(i);
123: if (ch != ' ' && ch != '\t') {
124: break;
125: }
126: i++;
127: }
128: previous.append(' ');
129: previous.append(current, i, current.length() - i);
130: } else {
131: this .trailerBufs.add(current);
132: this .lineBuf = null;
133: }
134: }
135:
136: private void processFooters() throws IOException {
137: int count = this .trailerBufs.size();
138: if (count > 0) {
139: this .footers = new Header[this .trailerBufs.size()];
140: for (int i = 0; i < this .trailerBufs.size(); i++) {
141: CharArrayBuffer buffer = this .trailerBufs.get(i);
142: try {
143: this .footers[i] = new BufferedHeader(buffer);
144: } catch (ParseException ex) {
145: throw new IOException(ex.getMessage());
146: }
147: }
148: }
149: this .trailerBufs.clear();
150: }
151:
152: public int read(final ByteBuffer dst) throws IOException {
153: if (dst == null) {
154: throw new IllegalArgumentException(
155: "Byte buffer may not be null");
156: }
157: if (this .state == COMPLETED) {
158: return -1;
159: }
160:
161: int totalRead = 0;
162: while (this .state != COMPLETED) {
163:
164: if (!this .buffer.hasData() || this .chunkSize == -1) {
165: int bytesRead = this .buffer.fill(this .channel);
166: if (bytesRead > 0) {
167: this .metrics.incrementBytesTransferred(bytesRead);
168: }
169: if (bytesRead == -1) {
170: this .endOfStream = true;
171: }
172: }
173:
174: switch (this .state) {
175: case READ_CONTENT:
176:
177: if (this .chunkSize == -1) {
178: readChunkHead();
179: if (this .chunkSize == -1) {
180: // Unable to read a chunk head
181: if (this .endOfStream) {
182: this .state = COMPLETED;
183: this .completed = true;
184: }
185: return totalRead;
186: }
187: if (this .chunkSize == 0) {
188: // Last chunk. Read footers
189: this .chunkSize = -1;
190: this .state = READ_FOOTERS;
191: break;
192: }
193: }
194:
195: int maxLen = this .chunkSize - this .pos;
196: int len = this .buffer.read(dst, maxLen);
197: this .pos += len;
198: totalRead += len;
199:
200: if (this .pos == this .chunkSize) {
201: // At the end of the chunk
202: this .chunkSize = -1;
203: this .pos = 0;
204: this .endOfChunk = true;
205: break;
206: }
207: return totalRead;
208: case READ_FOOTERS:
209: if (this .lineBuf == null) {
210: this .lineBuf = new CharArrayBuffer(32);
211: } else {
212: this .lineBuf.clear();
213: }
214: if (!this .buffer.readLine(this .lineBuf,
215: this .endOfStream)) {
216: // Unable to read a footer
217: if (this .endOfStream) {
218: this .state = COMPLETED;
219: this .completed = true;
220: }
221: return totalRead;
222: }
223: if (this .lineBuf.length() > 0) {
224: parseHeader();
225: } else {
226: this .state = COMPLETED;
227: this .completed = true;
228: processFooters();
229: }
230: break;
231: }
232:
233: }
234: return totalRead;
235: }
236:
237: public Header[] getFooters() {
238: if (this .footers != null) {
239: return (Header[]) this .footers.clone();
240: } else {
241: return new Header[] {};
242: }
243: }
244:
245: @Override
246: public String toString() {
247: StringBuffer buffer = new StringBuffer();
248: buffer.append("[chunk-coded; completed: ");
249: buffer.append(this .completed);
250: buffer.append("]");
251: return buffer.toString();
252: }
253:
254: }
|