001: //httpChunkedInputStream.java
002: //-----------------------
003: //(C) by Michael Peter Christen; mc@anomic.de
004: //first published on http://www.anomic.de
005: //Frankfurt, Germany, 2004
006: //
007: // This file is contributed by Martin Thelian
008: // last major change: $LastChangedDate: 2007-12-07 02:15:12 +0000 (Fr, 07 Dez 2007) $ by $LastChangedBy: orbiter $
009: // Revision: $LastChangedRevision: 4253 $
010: //
011: //This program is free software; you can redistribute it and/or modify
012: //it under the terms of the GNU General Public License as published by
013: //the Free Software Foundation; either version 2 of the License, or
014: //(at your option) any later version.
015: //
016: //This program is distributed in the hope that it will be useful,
017: //but WITHOUT ANY WARRANTY; without even the implied warranty of
018: //MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
019: //GNU General Public License for more details.
020: //
021: //You should have received a copy of the GNU General Public License
022: //along with this program; if not, write to the Free Software
023: //Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
024: //
025: //Using this software in any meaning (reading, learning, copying, compiling,
026: //running) means that you agree that the Author(s) is (are) not responsible
027: //for cost, loss of data or any harm that may be caused directly or indirectly
028: //by usage of this softare or this documentation. The usage of this software
029: //is on your own risk. The installation and usage (starting/running) of this
030: //software may allow other people or application to access your computer and
031: //any attached devices and is highly dependent on the configuration of the
032: //software which must be done by the user of the software; the author(s) is
033: //(are) also not responsible for proper configuration and usage of the
034: //software, even if provoked by documentation provided together with
035: //the software.
036: //
037: //Any changes to this file according to the GPL as documented in the file
038: //gpl.txt aside this file in the shipment you received can be done to the
039: //lines that follows this copyright notice here, but changes must not be
040: //done inside the copyright notive above. A re-distribution must contain
041: //the intact and unchanged copyright notice.
042: //Contributions and changes to the program code must be marked as such.
043:
044: package de.anomic.http;
045:
046: import java.io.BufferedReader;
047: import java.io.ByteArrayInputStream;
048: import java.io.IOException;
049: import java.io.InputStream;
050: import java.io.InputStreamReader;
051:
052: import de.anomic.server.serverByteBuffer;
053:
054: /**
055: * Some parts of this class code was copied from <a href="http://www.devdaily.com/java/jwarehouse/commons-httpclient-2.0/src/java/org/apache/commons/httpclient/ChunkedInputStream.shtml">Apache httpclient Project.</a>
056: * @author theli
057: */
058: public final class httpChunkedInputStream extends InputStream {
059:
060: private static final int READ_CHUNK_STATE_NORMAL = 0;
061: private static final int READ_CHUNK_STATE_CR_READ = 1;
062: private static final int READ_CHUNK_STATE_IN_EXT_CHUNK = 2;
063: private static final int READ_CHUNK_STATE_FINISHED = -1;
064:
065: private static final char CR = '\r';
066: private static final char LF = '\n';
067:
068: private final InputStream inputStream;
069: private int currPos;
070: private int currChunkSize;
071: private httpHeader httpTrailer;
072:
073: private boolean beginningOfStream = true;
074: private boolean isEOF = false;
075: private boolean isClosed = false;
076:
077: public httpChunkedInputStream(InputStream in) {
078:
079: if (in == null)
080: throw new IllegalArgumentException(
081: "InputStream must not be null");
082:
083: this .inputStream = in;
084: this .currPos = 0;
085: }
086:
087: public int read() throws IOException {
088:
089: if (this .isClosed)
090: throw new IOException("Inputstream already closed.");
091: if (this .isEOF)
092: return -1;
093:
094: if (this .currPos >= this .currChunkSize) {
095: readNextChunk();
096: if (this .isEOF)
097: return -1;
098: }
099: this .currPos++;
100: return this .inputStream.read();
101: }
102:
103: public int read(byte[] b, int off, int len) throws IOException {
104: if (this .isClosed)
105: throw new IOException("Inputstream already closed.");
106: if (this .isEOF)
107: return -1;
108:
109: if (this .currPos >= this .currChunkSize) {
110: readNextChunk();
111: if (this .isEOF)
112: return -1;
113: }
114: len = Math.min(len, this .currChunkSize - this .currPos);
115: int count = this .inputStream.read(b, off, len);
116: this .currPos += count;
117: return count;
118: }
119:
120: public int read(byte[] b) throws IOException {
121: return read(b, 0, b.length);
122: }
123:
124: /**
125: * Read the CRLF terminator.
126: * @throws IOException If an IO error occurs.
127: */
128: private void readCRLF() throws IOException {
129: int cr = this .inputStream.read();
130: int lf = this .inputStream.read();
131: if ((cr != CR) || (lf != LF)) {
132: throw new IOException(
133: "Malformed chunk. CRLF expected but '" + cr + lf
134: + "' found");
135: }
136: }
137:
138: private void readNextChunk() throws IOException {
139: if (!this .beginningOfStream)
140: readCRLF();
141:
142: this .currChunkSize = readChunkFromStream(this .inputStream);
143: this .beginningOfStream = false;
144: this .currPos = 0;
145: if (this .currChunkSize == 0) {
146: this .isEOF = true;
147: readTrailer();
148: }
149: }
150:
151: private void readTrailer() throws IOException {
152: BufferedReader reader = null;
153: serverByteBuffer bout = null;
154: try {
155: bout = new serverByteBuffer();
156: do {
157: int ch;
158: while ((ch = this .inputStream.read()) >= 0) {
159: bout.write(ch);
160: if (ch == LF) {
161: break;
162: }
163: }
164: if (bout.length() <= 2)
165: break;
166: } while (true);
167:
168: ByteArrayInputStream bin = new ByteArrayInputStream(bout
169: .getBytes());
170: reader = new BufferedReader(new InputStreamReader(bin));
171: this .httpTrailer = httpHeader.readHttpHeader(reader);
172: } finally {
173: if (reader != null)
174: try {
175: reader.close();
176: } catch (Exception e) {
177: }
178: if (bout != null)
179: try {
180: bout.close();
181: } catch (Exception e) {
182: }
183: }
184: }
185:
186: public httpHeader getTrailer() {
187: return this .httpTrailer;
188: }
189:
190: private static int readChunkFromStream(final InputStream in)
191: throws IOException {
192:
193: serverByteBuffer baos = new serverByteBuffer();
194: int state = READ_CHUNK_STATE_NORMAL;
195: while (state != READ_CHUNK_STATE_FINISHED) {
196: int b = in.read();
197: if (b == -1)
198: throw new IOException("Malformed chunk. Unexpected end");
199:
200: switch (state) {
201: case READ_CHUNK_STATE_NORMAL: // 0
202: switch (b) {
203: case CR:
204: state = READ_CHUNK_STATE_CR_READ;
205: break;
206: case '\"':
207: case ';':
208: case ' ':
209: state = READ_CHUNK_STATE_IN_EXT_CHUNK;
210: break;
211: default:
212: baos.write(b);
213: }
214: break;
215:
216: case READ_CHUNK_STATE_CR_READ: // 1
217: if (b == LF) {
218: state = READ_CHUNK_STATE_FINISHED;
219: } else {
220: // this was not CRLF
221: throw new IOException(
222: "Malformed chunk. Unexpected enf of chunk. MIssing CR character.");
223: }
224: break;
225:
226: case READ_CHUNK_STATE_IN_EXT_CHUNK: // 2
227: switch (b) {
228: case CR:
229: state = READ_CHUNK_STATE_CR_READ;
230: break;
231: default:
232: break;
233: }
234: break;
235: default:
236: throw new RuntimeException(
237: "Malformed chunk. Illegal state.");
238: }
239: }
240:
241: int result;
242: try {
243: result = Integer.parseInt(baos.toString().trim(), 16);
244: } catch (NumberFormatException e) {
245: throw new IOException("Malformed chunk. Bad chunk size: "
246: + baos.toString());
247: }
248: return result;
249: }
250:
251: public void close() throws IOException {
252: if (!this .isClosed) {
253: try {
254: if (!this .isEOF) {
255: exhaustInputStream(this );
256: }
257: } finally {
258: this .isEOF = true;
259: this .isClosed = true;
260: }
261: }
262: }
263:
264: static void exhaustInputStream(InputStream inStream)
265: throws IOException {
266: byte buffer[] = new byte[1024];
267: while (inStream.read(buffer) >= 0) {
268: }
269: }
270: }
|