/**
* Copyright (c) 2009 - 2011 AppWork UG(haftungsbeschränkt) <e-mail@appwork.org>
*
* This file is part of org.appwork.utils.net
*
* This software is licensed under the Artistic License 2.0,
* see the LICENSE file or http://www.opensource.org/licenses/artistic-license-2.0.php
* for details
*/
package org.appwork.utils.net;
import java.io.IOException;
import java.io.InputStream;
/**
* @author daniel, ChunkedInputStream, see rfc2616#section-3.6
*
*/
public class ChunkedInputStream extends InputStream {
private final InputStream is;
private int nextChunkSize = 0;
private int nextChunkLeft = 0;
private long completeSize = 0;
public ChunkedInputStream(final InputStream is) {
this.is = is;
}
@Override
public int available() throws IOException {
if (this.nextChunkLeft > 0) { return this.nextChunkLeft; }
return this.is.available();
}
/**
* returns available bytes in current Chunk or reads next Chunk and parses
* it
*
* @return
* @throws IOException
*/
private int availableChunkData() throws IOException {
if (this.nextChunkLeft == -1) { return -1; }
if (this.nextChunkLeft > 0) { return this.nextChunkLeft; }
final StringBuilder sb = new StringBuilder();
boolean chunkExt = false;
final byte[] b = { (byte) 0x00 };
int read = 0;
if (this.nextChunkSize > 0) {
/* finish LF/CRLF from previous chunk */
read = this.is.read();
if (read == 13) {
read = this.is.read();
}
}
read = this.is.read();
while (read > -1 && read != 10 && read != 13) {
if (read == 59) {
/* ignore chunkExtensions */
// System.out.println("chunkedExtension found");
chunkExt = true;
}
if (chunkExt == false) {
b[0] = (byte) (read & 0xFF);
sb.append(new String(b, 0, 1));
}
read = this.is.read();
}
if (read == -1 && sb.length() == 0) { return -1; }
if (read == 13) {
/* finish CRLF here */
read = this.is.read();
}
this.nextChunkSize = 0;
if (sb.length() > 0) {
this.nextChunkSize = Integer.parseInt(sb.toString().trim(), 16);
}
if (this.nextChunkSize == 0) {
// System.out.println("lastChunk");
this.nextChunkLeft = -1;
this.readTrailers();
} else {
// System.out.println("nextchunkSize: " + this.nextChunkSize);
this.completeSize += this.nextChunkSize;
this.nextChunkLeft = this.nextChunkSize;
}
return this.nextChunkLeft;
}
@Override
public void close() throws IOException {
this.is.close();
}
/**
* Exhaust an input stream, reading until EOF has been encountered.
*
* @throws IOException
*/
private void exhaustInputStream() throws IOException {
while (this.is.read() > 0) {
}
}
/**
* @return the completeSize
*/
public long getCompleteSize() {
return this.completeSize;
}
@Override
public boolean markSupported() {
return false;
}
@Override
public int read() throws IOException {
final int left = this.availableChunkData();
if (left > 0) {
final int ret = this.is.read();
if (ret != -1) {
this.nextChunkLeft--;
return ret;
}
throw new IOException("premature EOF");
}
return -1;
}
@Override
public int read(final byte b[], final int off, final int len) throws IOException {
final int left = this.availableChunkData();
if (left > 0) {
final int ret = this.is.read(b, off, Math.min(left, len));
if (ret != -1) {
this.nextChunkLeft -= ret;
return ret;
}
throw new IOException("premature EOF");
}
return -1;
}
/**
* TODO: read the Trailers we read until EOF at the moment;
*
* @throws IOException
*/
private void readTrailers() throws IOException {
this.exhaustInputStream();
}
}