001: /*
002: License $Id: ChunkedInputStream.java,v 1.6 2003/09/13 04:59:55 hendriks73 Exp $
003:
004: Copyright (c) 2001-2005 tagtraum industries.
005:
006: LGPL
007: ====
008:
009: jo! is free software; you can redistribute it and/or
010: modify it under the terms of the GNU Lesser General Public
011: License as published by the Free Software Foundation; either
012: version 2.1 of the License, or (at your option) any later version.
013:
014: jo! is distributed in the hope that it will be useful,
015: but WITHOUT ANY WARRANTY; without even the implied warranty of
016: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
017: Lesser General Public License for more details.
018:
019: You should have received a copy of the GNU Lesser General Public
020: License along with this library; if not, write to the Free Software
021: Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
022:
023: For LGPL see <http://www.fsf.org/copyleft/lesser.txt>
024:
025:
026: Sun license
027: ===========
028:
029: This release contains software by Sun Microsystems. Therefore
030: the following conditions have to be met, too. They apply to the
031: files
032:
033: - lib/mail.jar
034: - lib/activation.jar
035: - lib/jsse.jar
036: - lib/jcert.jar
037: - lib/jaxp.jar
038: - lib/crimson.jar
039: - lib/servlet.jar
040: - lib/jnet.jar
041: - lib/jaas.jar
042: - lib/jaasmod.jar
043:
044: contained in this release.
045:
046: a. Licensee may not modify the Java Platform
047: Interface (JPI, identified as classes contained within the javax
048: package or any subpackages of the javax package), by creating additional
049: classes within the JPI or otherwise causing the addition to or modification
050: of the classes in the JPI. In the event that Licensee creates any
051: Java-related API and distribute such API to others for applet or
052: application development, you must promptly publish broadly, an accurate
053: specification for such API for free use by all developers of Java-based
054: software.
055:
056: b. Software is confidential copyrighted information of Sun and
057: title to all copies is retained by Sun and/or its licensors. Licensee
058: shall not modify, decompile, disassemble, decrypt, extract, or otherwise
059: reverse engineer Software. Software may not be leased, assigned, or
060: sublicensed, in whole or in part. Software is not designed or intended
061: for use in on-line control of aircraft, air traffic, aircraft navigation
062: or aircraft communications; or in the design, construction, operation or
063: maintenance of any nuclear facility. Licensee warrants that it will not
064: use or redistribute the Software for such purposes.
065:
066: c. Software is provided "AS IS," without a warranty
067: of any kind. ALL EXPRESS OR IMPLIED REPRESENTATIONS AND WARRANTIES,
068: INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A
069: PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED.
070:
071: d. This License is effective until terminated. Licensee may
072: terminate this License at any time by destroying all copies of Software.
073: This License will terminate immediately without notice from Sun if Licensee
074: fails to comply with any provision of this License. Upon such termination,
075: Licensee must destroy all copies of Software.
076:
077: e. Software, including technical data, is subject to U.S.
078: export control laws, including the U.S. Export Administration Act and its
079: associated regulations, and may be subject to export or import regulations
080: in other countries. Licensee agrees to comply strictly with all such
081: regulations and acknowledges that it has the responsibility to obtain
082: licenses to export, re-export, or import Software. Software may not be
083: downloaded, or otherwise exported or re-exported (i) into, or to a national
084: or resident of, Cuba, Iraq, Iran, North Korea, Libya, Sudan, Syria or any
085: country to which the U.S. has embargoed goods; or (ii) to anyone on the
086: U.S. Treasury Department's list of Specially Designated Nations or the U.S.
087: Commerce Department's Table of Denial Orders.
088:
089:
090: Feedback
091: ========
092:
093: We encourage your feedback and suggestions and want to use your feedback to
094: improve the Software. Send all such feedback to:
095: <feedback@tagtraum.com>
096:
097: For more information on tagtraum industries and jo!
098: please see <http://www.tagtraum.com/>.
099:
100:
101: */
102: package com.tagtraum.framework.http;
103:
104: import com.tagtraum.framework.util.UnSyncStringBuffer;
105:
106: import java.io.FilterInputStream;
107: import java.io.IOException;
108: import java.io.InputStream;
109:
110: /**
111: * InputStream that supports chunked encoding as defined in HTTP/1.1.
112: *
113: * @author <a href="mailto:hs@tagtraum.com">Hendrik Schreiber</a>
114: * @version 1.1beta1 $Id: ChunkedInputStream.java,v 1.6 2003/09/13 04:59:55 hendriks73 Exp $
115: */
116: public class ChunkedInputStream extends FilterInputStream {
117:
118: // SKIP_BUFFER_SIZE is used to determine the size of skipBuffer
119: private static final int SKIP_BUFFER_SIZE = 2048;
120: // skipBuffer is initialized in skip(long), if needed.
121: private static byte[] skipBuffer;
122:
123: /**
124: * The input stream to be filtered.
125: */
126: private InputStream in;
127:
128: /**
129: * Chunk length.
130: */
131: private long chunkSize = 0;
132:
133: /**
134: * Bytes already read.
135: */
136: private long pos = 0;
137:
138: /**
139: * Trailer.
140: */
141: private HttpHeader myTrailer = null;
142:
143: /**
144: * Creates a <code>FilterInputStream</code>
145: * by assigning the argument <code>in</code>
146: * to the field <code>this.in</code> so as
147: * to remember it for later use.
148: *
149: * @param in the underlying input stream, or <code>null</code> if
150: * this instance is to be created without an underlying stream.
151: */
152: public ChunkedInputStream(InputStream in) throws IOException {
153: super (in);
154: this .in = in;
155: readChunksize();
156: }
157:
158: /**
159: * Reads the size of a chunk.
160: *
161: *
162: * @throws IOException
163: *
164: * @see
165: */
166: private void readChunksize() throws IOException {
167: UnSyncStringBuffer sb = new UnSyncStringBuffer();
168: int c;
169: int extension = -1;
170:
171: while ((c = in.read()) != '\r') {
172: if (c == -1) {
173: // set chunksize to -1 at end of stream
174: chunkSize = -1;
175:
176: return;
177: }
178: if (c == ';')
179: extension = sb.length();
180: sb.append((char) c);
181: if (sb.length() > 255 || extension > 5)
182: throw new IOException("Chunksize too big: "
183: + sb.toString());
184: }
185:
186: if (in.read() != '\n') {
187: throw new IOException(
188: "Format error. Couldn't read chunksize.");
189: }
190:
191: String line = null;
192:
193: // cut off extension
194: if (extension > 0) {
195: line = sb.substring(0, extension);
196: } else {
197: line = sb.toString();
198: }
199: try {
200: // do we really need to trim()?
201: // chunk length is coded to base 16
202: chunkSize = Integer.parseInt(line.trim(), 16);
203: } catch (NumberFormatException nfe) {
204: throw new IOException("Could not parse chunk size: " + line);
205: }
206:
207: if (chunkSize == 0) {
208: // chunksize == 0 => we read the last chunk and can now read the trailer
209: readTrailer();
210: }
211: }
212:
213: /**
214: * Reads the next byte of data from this input stream. The value
215: * byte is returned as an <code>int</code> in the range
216: * <code>0</code> to <code>255</code>. If no byte is available
217: * because the end of the stream has been reached, the value
218: * <code>-1</code> is returned. This method blocks until input data
219: * is available, the end of the stream is detected, or an exception
220: * is thrown.
221: * <p>
222: * This method
223: * simply performs <code>in.read()</code> and returns the result.
224: *
225: * @return the next byte of data, or <code>-1</code> if the end of the
226: * stream is reached.
227: * @exception IOException if an I/O error occurs.
228: * @see java.io.FilterInputStream#in
229: */
230: public int read() throws IOException {
231: if (chunkSize == -1) {
232: return -1;
233: }
234:
235: if (pos < chunkSize) {
236: pos++;
237:
238: return in.read();
239: } else {
240: if (in.read() != '\r') {
241: throw new IOException("Format Error. Expected \\r.");
242: }
243:
244: if (in.read() != '\n') {
245: throw new IOException("Format Error. Expected \\n.");
246: }
247:
248: readChunksize();
249:
250: pos = 0;
251:
252: if (chunkSize <= 0) {
253: return -1;
254: }
255:
256: pos = 1;
257:
258: return in.read();
259: }
260: }
261:
262: /**
263: * Reads up to <code>len</code> bytes of data from this input stream
264: * into an array of bytes. This method blocks until some input is
265: * available.
266: * <p>
267: * This method simply performs <code>in.read(b, off, len)</code>
268: * and returns the result.
269: *
270: * @param b the buffer into which the data is read.
271: * @param off the start offset of the data.
272: * @param len the maximum number of bytes read.
273: * @return the total number of bytes read into the buffer, or
274: * <code>-1</code> if there is no more data because the end of
275: * the stream has been reached.
276: * @exception IOException if an I/O error occurs.
277: * @see java.io.FilterInputStream#in
278: */
279: public int read(byte b[], int off, int len) throws IOException {
280: if (chunkSize == -1) {
281: return -1;
282: }
283:
284: boolean eos = false;
285: int bytesRead = 0;
286: int justRead = 0;
287:
288: while (bytesRead < len && eos == false && chunkSize > 0) {
289: int stillToRead = len - bytesRead;
290: long available = chunkSize - pos;
291:
292: if (available >= stillToRead) {
293: justRead = in.read(b, off + bytesRead, stillToRead);
294: } else {
295: justRead = in.read(b, off + bytesRead, (int) available);
296: }
297:
298: pos += justRead;
299: eos = justRead != stillToRead;
300:
301: if (justRead > 0) {
302: bytesRead += justRead;
303: }
304:
305: if (justRead == -1 && available > 0) {
306: throw new IOException("Unexpected end of stream.");
307: }
308:
309: if (justRead == available) {
310: if (in.read() != '\r') {
311: throw new IOException("Format Error. Expected \\r.");
312: }
313:
314: if (in.read() != '\n') {
315: throw new IOException("Format Error. Expected \\n.");
316: }
317:
318: readChunksize();
319:
320: pos = 0;
321:
322: if (chunkSize <= 0 && bytesRead == 0) {
323: bytesRead = -1;
324: }
325: }
326: }
327:
328: return bytesRead;
329: }
330:
331: /**
332: * Reads the trailer..
333: *
334: * @throws IOException
335: *
336: * @see
337: */
338: private synchronized void readTrailer() throws IOException {
339: myTrailer = new HttpHeader();
340:
341: myTrailer.init();
342:
343: try {
344: myTrailer.read(in);
345: } catch (HttpException he) {
346: throw new IOException("Error reading trailer: "
347: + he.toString());
348: }
349:
350: chunkSize = -1;
351: }
352:
353: /**
354: * Returns the trailer.
355: *
356: * @return trailer
357: *
358: * @see
359: */
360: public synchronized HttpHeader getTrailer() {
361: return myTrailer;
362: }
363:
364: /**
365: * Skips over and discards <code>n</code> bytes of data from the
366: * input stream. The <code>skip</code> method may, for a variety of
367: * reasons, end up skipping over some smaller number of bytes,
368: * possibly <code>0</code>. The actual number of bytes skipped is
369: * returned.
370: * <p>
371: * This implementation simply performs a series of {@link #read(byte[], int, int)}.
372: *
373: * @param n the number of bytes to be skipped.
374: * @return the actual number of bytes skipped.
375: * @exception IOException if an I/O error occurs.
376: */
377: public long skip(long n) throws IOException {
378: long remaining = n;
379: int nr;
380: if (skipBuffer == null)
381: skipBuffer = new byte[SKIP_BUFFER_SIZE];
382: byte[] localSkipBuffer = skipBuffer;
383: if (n <= 0) {
384: return 0;
385: }
386: while (remaining > 0) {
387: nr = read(localSkipBuffer, 0, (int) Math.min(
388: SKIP_BUFFER_SIZE, remaining));
389: if (nr < 0) {
390: break;
391: }
392: remaining -= nr;
393: }
394: return n - remaining;
395: }
396:
397: /**
398: * Returns the number of bytes that can be read from this input
399: * stream without blocking.
400: * <p>
401: * This method
402: * simply performs <code>in.available(n)</code> and
403: * returns the result.
404: *
405: * @return the number of bytes that can be read from the input stream
406: * without blocking.
407: * @exception IOException if an I/O error occurs.
408: * @see java.io.FilterInputStream#in
409: */
410: public int available() throws IOException {
411: return (int) (chunkSize - pos);
412: }
413:
414: /**
415: * Marks the current position in this input stream. A subsequent
416: * call to the <code>reset</code> method repositions this stream at
417: * the last marked position so that subsequent reads re-read the same bytes.
418: * <p>
419: * The <code>readlimit</code> argument tells this input stream to
420: * allow that many bytes to be read before the mark position gets
421: * invalidated.
422: * <p>
423: * This method simply performs <code>in.mark(readlimit)</code>.
424: *
425: * @param readlimit the maximum limit of bytes that can be read before
426: * the mark position becomes invalid.
427: * @see java.io.FilterInputStream#in
428: * @see java.io.FilterInputStream#reset()
429: */
430: public void mark(int readlimit) {
431: }
432:
433: /**
434: * Repositions this stream to the position at the time the
435: * <code>mark</code> method was last called on this input stream.
436: * <p>
437: * This method
438: * simply performs <code>in.reset()</code>.
439: * <p>
440: * Stream marks are intended to be used in
441: * situations where you need to read ahead a little to see what's in
442: * the stream. Often this is most easily done by invoking some
443: * general parser. If the stream is of the type handled by the
444: * parse, it just chugs along happily. If the stream is not of
445: * that type, the parser should toss an exception when it fails.
446: * If this happens within readlimit bytes, it allows the outer
447: * code to reset the stream and try another parser.
448: *
449: * @exception IOException if the stream has not been marked or if the
450: * mark has been invalidated.
451: * @see java.io.FilterInputStream#in
452: * @see java.io.FilterInputStream#mark(int)
453: */
454: public void reset() throws IOException {
455: throw new IOException("Mark not supported.");
456: }
457:
458: /**
459: * Tests if this input stream supports the <code>mark</code>
460: * and <code>reset</code> methods.
461: * This method
462: * simply performs <code>in.markSupported()</code>.
463: *
464: * @return <code>true</code> if this stream type supports the
465: * <code>mark</code> and <code>reset</code> method;
466: * <code>false</code> otherwise.
467: * @see java.io.FilterInputStream#in
468: * @see java.io.InputStream#mark(int)
469: * @see java.io.InputStream#reset()
470: */
471: public boolean markSupported() {
472: return false;
473: }
474:
475: }
|