package com.koushikdutta.async.http.filter; import com.koushikdutta.async.ByteBufferList; import com.koushikdutta.async.DataEmitter; import com.koushikdutta.async.FilteredDataEmitter; import com.koushikdutta.async.Util; public class ChunkedInputFilter extends FilteredDataEmitter { private int mChunkLength = 0; private int mChunkLengthRemaining = 0; private State mState = State.CHUNK_LEN; private static enum State { CHUNK_LEN, CHUNK_LEN_CR, CHUNK_LEN_CRLF, CHUNK, CHUNK_CR, CHUNK_CRLF, COMPLETE } private boolean checkByte(char b, char value) { if (b != value) { report(new Exception(value + " was expeceted, got " + (char)b)); return false; } return true; } private boolean checkLF(char b) { return checkByte(b, '\n'); } private boolean checkCR(char b) { return checkByte(b, '\r'); } @Override public void onDataAvailable(DataEmitter emitter, ByteBufferList bb) { try { while (bb.remaining() > 0) { switch (mState) { case CHUNK_LEN: char c = bb.getByteChar(); if (c == '\r') { mState = State.CHUNK_LEN_CR; } else { mChunkLength *= 16; if (c >= 'a' && c <= 'f') mChunkLength += (c - 'a' + 10); else if (c >= '0' && c <= '9') mChunkLength += c - '0'; else if (c >= 'A' && c <= 'F') mChunkLength += (c - 'A' + 10); else { report(new Exception("invalid chunk length: " + c)); return; } } mChunkLengthRemaining = mChunkLength; break; case CHUNK_LEN_CR: if (!checkLF(bb.getByteChar())) return; mState = State.CHUNK; break; case CHUNK: int remaining = bb.remaining(); int reading = Math.min(mChunkLengthRemaining, remaining); mChunkLengthRemaining -= reading; if (mChunkLengthRemaining == 0) { mState = State.CHUNK_CR; } if (reading == 0) break; ByteBufferList chunk = bb.get(reading); int newRemaining = bb.remaining(); assert remaining == chunk.remaining() + bb.remaining(); assert reading == chunk.remaining(); Util.emitAllData(this, chunk); assert newRemaining == bb.remaining(); break; case CHUNK_CR: if (!checkCR(bb.getByteChar())) return; mState = State.CHUNK_CRLF; break; case CHUNK_CRLF: if (!checkLF(bb.getByteChar())) return; if (mChunkLength > 0) { mState = State.CHUNK_LEN; } else { mState = State.COMPLETE; report(null); } mChunkLength = 0; break; case COMPLETE: assert false; // Exception fail = new Exception("Continued receiving data after chunk complete"); // report(fail); return; } } } catch (Exception ex) { report(ex); } } }