package org.jboss.pitbull.internal.nio.http;
import org.jboss.pitbull.PitbullChannel;
import org.jboss.pitbull.ReadTimeoutException;
import org.jboss.pitbull.internal.logging.Logger;
import org.jboss.pitbull.internal.nio.socket.ByteBuffers;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.concurrent.TimeUnit;
import static java.lang.Math.*;
/**
* An input stream which reads from a http chunk stream source channel with a buffer. In addition, the
* {@link #available()} method can be used to determine whether the next read will or will not block.
*
* @apiviz.exclude
* @since 2.1
*/
public class ChunkedInputStream extends ContentInputStream
{
private final PitbullChannel channel;
private final ByteBuffer buffer;
private volatile boolean closed;
private boolean done;
private long remainingChunkBytes = 0;
private boolean initial = true;
protected static final Logger log = Logger.getLogger(ChunkedInputStream.class);
/**
* Construct a new instance.
*
* @param channel the channel to wrap
* @param bufferSize the size of the internal buffer
*/
public ChunkedInputStream(final PitbullChannel channel, ByteBuffer buffer)
{
if (channel == null)
{
throw new NullPointerException("channel is null");
}
if (buffer == null)
{
throw new NullPointerException("buffer is null");
}
this.buffer = buffer;
log.trace("Buffer Remaing On Creation: {0}", buffer.remaining());
this.channel = channel;
}
public void eat() throws IOException
{
while (!done) skip(1000);
}
protected long getChunkSize(String hex)
{
hex = hex.trim();
for (int i = 0; i < hex.length(); i++)
{
char c = hex.charAt(i);
if (c == ';' || Character.isWhitespace(c) || Character.isISOControl(c))
{
hex = hex.substring(0, i);
break;
}
}
return Long.parseLong(hex, 16);
}
protected long readSize() throws IOException
{
StringBuilder chunkSizeBuilder = new StringBuilder(10);
READ_SIZE:
for (; ; )
{
while (buffer.hasRemaining())
{
byte b = buffer.get();
if (b == HttpRequestDecoder.LF)
{
// strip out \r
if (chunkSizeBuilder.length() > 0 && chunkSizeBuilder.charAt(chunkSizeBuilder.length() - 1) == '\r')
chunkSizeBuilder.setLength(chunkSizeBuilder.length() - 1);
String hex = chunkSizeBuilder.toString();
if (initial)
{
initial = false;
}
else if (hex.length() == 0)// chunk ends with \r\n
{
chunkSizeBuilder = new StringBuilder(10);
continue;
}
long currentChunkSize = getChunkSize(hex);
if (currentChunkSize > 0)
{
log.trace("Chunk size: {0}", currentChunkSize);
return currentChunkSize;
}
break READ_SIZE;
}
else
{
chunkSizeBuilder.append((char) b);
}
}
buffer.clear();
if (readBuffer(buffer) == -1)
{
done = true;
return -1;
}
buffer.flip();
}
StringBuilder lineBuilder = new StringBuilder(100);
// we're at the trailer if we got this far.
for (; ; )
{
while (buffer.hasRemaining())
{
byte b = buffer.get();
if (b == HttpRequestDecoder.LF)
{
if (lineBuilder.length() > 0 && lineBuilder.charAt(lineBuilder.length() - 1) == '\r')
lineBuilder.setLength(lineBuilder.length() - 1);
String line = lineBuilder.toString();
if (line.length() == 0)
{
done = true;
return -1;
}
lineBuilder = new StringBuilder(100);
}
else
{
lineBuilder.append((char) b);
}
}
buffer.clear();
if (readBuffer(buffer) == -1)
{
done = true;
return -1;
}
buffer.flip();
}
}
/**
* Blocks until at least one byte is read or EOF. Does not clear or flip the buffer.
*
* @param buf
* @return
* @throws IOException
*/
protected int readBuffer(ByteBuffer buf) throws IOException
{
if (timeout == 0L)
{
for (; ; )
{
final int res = channel.readBlocking(buf);
if (res == -1)
{
return -1;
}
else if (res == 0)
{
continue;
}
return res;
}
}
else
{
long now = System.currentTimeMillis();
final long deadline = timeout - now;
for (; ; )
{
if (deadline <= now)
{
throw new ReadTimeoutException("Read timed out");
}
final int res = channel.readBlocking(buf, deadline - now, TimeUnit.MILLISECONDS);
if (res == -1)
{
return -1;
}
else if (res == 0)
{
continue;
}
return res;
}
}
}
/**
* Read a byte, blocking if necessary.
*
* @return the byte read, or -1 if the end of the stream has been reached
* @throws java.io.IOException if an I/O error occurs
*/
public int read() throws IOException
{
if (closed || done) return -1;
if (remainingChunkBytes <= 0)
{
long size = readSize();
if (size < 1)
{
return -1;
}
remainingChunkBytes = size;
}
if (buffer.hasRemaining())
{
remainingChunkBytes--;
return buffer.get() & 0xff;
}
buffer.clear();
if (readBuffer(buffer) == -1)
{
done = true;
return -1;
}
buffer.flip();
remainingChunkBytes--;
return buffer.get() & 0xFF;
}
/**
* Read bytes into an array.
*
* @param b the destination array
* @param off the offset into the array at which bytes should be filled
* @param len the number of bytes to fill
* @return the number of bytes read, or -1 if the end of the stream has been reached
* @throws java.io.IOException if an I/O error occurs
*/
public int read(final byte[] b, int off, int len) throws IOException
{
if (closed || done) return -1;
if (len < 1)
{
return 0;
}
if (remainingChunkBytes <= 0)
{
long size = readSize();
if (size < 1)
{
done = true;
return -1;
}
remainingChunkBytes = size;
}
if (len > remainingChunkBytes)
{
len = (int) remainingChunkBytes;
}
int total = 0;
final ByteBuffer buffer = this.buffer;
if (buffer.hasRemaining())
{
final int cnt = min(buffer.remaining(), len);
buffer.get(b, off, cnt);
total += cnt;
off += cnt;
len -= cnt;
remainingChunkBytes -= cnt;
}
if (closed) return -1;
if (len <= 0) return total;
ByteBuffer buf = ByteBuffer.wrap(b, off, len);
int read = readBuffer(buf);
if (read == -1)
{
done = true;
return -1;
}
total += read;
remainingChunkBytes -= read;
return total;
}
/**
* Skip bytes in the stream.
*
* @param n the number of bytes to skip
* @return the number of bytes skipped (0 if the end of stream has been reached)
* @throws java.io.IOException if an I/O error occurs
*/
public long skip(long n) throws IOException
{
long skipRemaining = n;
while (skipRemaining > 0)
{
long skipped = skipChunk(n);
if (skipped == 0L) return 0L;
skipRemaining -= skipped;
}
return n;
}
protected long skipChunk(long n) throws IOException
{
if (closed || done) return 0L;
if (n < 1L)
{
return 0L;
}
long total = 0;
if (remainingChunkBytes <= 0)
{
long size = readSize();
if (size < 1)
{
done = true;
return 0L;
}
remainingChunkBytes = size;
}
if (n > remainingChunkBytes)
{
n = remainingChunkBytes;
}
if (buffer.hasRemaining())
{
final int cnt = (int) min(buffer.remaining(), n);
ByteBuffers.skip(buffer, cnt);
remainingChunkBytes -= cnt;
n -= cnt;
total += cnt;
}
if (n < 1) return total;
buffer.clear();
int read = readBuffer(buffer);
if (read == -1)
{
done = true;
return total;
}
buffer.flip();
return total;
}
/**
* Return the number of bytes available to read
*
* @return the number of ready bytes, or 0 for none
* @throws java.io.IOException if an I/O error occurs
*/
public int available() throws IOException
{
if (closed || done) return 0;
if (remainingChunkBytes < 1) return 0;
return (int) min(buffer.remaining(), remainingChunkBytes);
}
/**
* Close the stream. Shuts down the channel's read side.
*
* @throws java.io.IOException if an I/O error occurs
*/
public void close() throws IOException
{
closed = true;
}
}