/* * JLibs: Common Utilities for Java * Copyright (C) 2009 Santhosh Kumar T <santhosh.tekuri@gmail.com> * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. */ package jlibs.nio.filters; import jlibs.nio.Input; import jlibs.nio.InputFilter; import jlibs.nio.Reactor; import jlibs.nio.util.Parser; import java.io.EOFException; import java.io.IOException; import java.nio.ByteBuffer; /** * @author Santhosh Kumar Tekuri */ public class ChunkedInput extends InputFilter{ private static final int STATE_CHUNK_BEGIN = 0; private static final int STATE_READ_EOL = 1; private static final int STATE_CHUNK_CONTENT = 2; private static final int STATE_CHUNK_END = 3; private static final int STATE_LAST_CHUNK_END = 4; private static final int STATE_TRAILER = 5; private static final int STATE_FINISHED = 6; private static final int HEX_DIGITS[] = new int['f'+1]; static{ for(int i='0'; i<='9'; ++i) HEX_DIGITS[i] = i-'0'; for(int i='a'; i<='f'; ++i) HEX_DIGITS[i] = i-'a'+10; for(int i='A'; i<='F'; ++i) HEX_DIGITS[i] = i-'A'+10; } private int state = STATE_CHUNK_BEGIN; private ByteBuffer buffer = Reactor.current().allocator.allocate(18); private int chunkLength = 0; private Parser trailers; public ChunkedInput(Input peer, Parser trailersParser){ super(peer); trailers = trailersParser; buffer.flip(); } @Override protected boolean readReady(){ return state==STATE_FINISHED || (state==STATE_CHUNK_CONTENT && buffer.hasRemaining()) || (state==STATE_CHUNK_END && buffer.remaining()>=2); } @Override public long available(){ return chunkLength<0 ? 0 : chunkLength; } @Override public int read(ByteBuffer dst) throws IOException{ int pos = dst.position(); while(pos==dst.position()){ switch(state){ case STATE_CHUNK_BEGIN: do{ if(!buffer.hasRemaining() && !fillBuffer()) return 0; do{ byte b = buffer.get(); if((b>='0' && b<='9') || (b>='a' && b<='f') || (b>='A' && b<='F')){ chunkLength <<= 4; chunkLength += HEX_DIGITS[b]; }else{ if(b=='\n') state = chunkLength==0 ? STATE_LAST_CHUNK_END : STATE_CHUNK_CONTENT; else state = STATE_READ_EOL; break; } }while(buffer.hasRemaining()); }while(state==STATE_CHUNK_BEGIN); break; case STATE_READ_EOL: do{ if(!buffer.hasRemaining() && !fillBuffer()) return 0; do{ if(buffer.get()=='\n'){ state = chunkLength==0 ? STATE_LAST_CHUNK_END : STATE_CHUNK_CONTENT; break; } }while(buffer.hasRemaining()); }while(state==STATE_READ_EOL); break; case STATE_CHUNK_CONTENT: if(buffer.hasRemaining()){ int min = Math.min(chunkLength, Math.min(buffer.remaining(), dst.remaining())); int _limit = buffer.limit(); buffer.limit(buffer.position()+min); dst.put(buffer); buffer.limit(_limit); chunkLength -= min; } if(chunkLength>0 && dst.hasRemaining()){ int _limit = dst.limit(); dst.limit(dst.position()+Math.min(chunkLength, dst.remaining())); int peerRead; try{ peerRead = peer.read(dst); }finally{ dst.limit(_limit); } if(peerRead==-1) throw new EOFException(chunkLength+" more bytes expected"); chunkLength -= peerRead; } if(chunkLength==0) state = STATE_CHUNK_END; return dst.position()-pos; case STATE_CHUNK_END: do{ if(!buffer.hasRemaining() && !fillBuffer()) return 0; do{ if(buffer.get()=='\n'){ state = STATE_CHUNK_BEGIN; break; } }while(buffer.hasRemaining()); }while(state==STATE_CHUNK_END); break; case STATE_LAST_CHUNK_END: while(buffer.remaining()<2){ if(!fillBuffer()) return 0; } if(buffer.get()=='\r'){ buffer.get(); // '\n' return finished(); }else{ buffer.position(buffer.position()-1); state = STATE_TRAILER; } case STATE_TRAILER: while(true){ if(trailers.parse(buffer, false)) return finished(); if(!fillBuffer()) return 0; } case STATE_FINISHED: return -1; } } return dst.position()-pos; } private boolean fillBuffer() throws IOException{ buffer.compact(); int read; try{ read = peer.read(buffer); }finally{ buffer.flip(); } if(read==-1) throw new EOFException("unexpected end of stream"); return read!=0; } private int finished(){ state = STATE_FINISHED; if(!buffer.hasRemaining()){ Reactor.current().allocator.free(buffer); buffer = null; } eof = true; return -1; } @Override protected ByteBuffer detached(){ if(!eof){ Reactor.current().allocator.free(buffer); buffer = null; } return buffer; } }