package com.limegroup.gnutella.http;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.ReadableByteChannel;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.limewire.nio.statemachine.ReadState;
import org.limewire.statistic.Statistic;
import org.limewire.util.BufferUtils;
public abstract class ReadHeadersIOState extends ReadState {
private static final Log LOG = LogFactory.getLog(ReadHeadersIOState.class);
/** Header support. */
protected final HeaderSupport support;
/** Statistic to add bandwidth data to. */
private final Statistic stat;
/** The maximum size of a header we'll read. */
private final int maxHeaderSize;
/** The maximum number of headers we'll read. */
private final int maxHeaders;
/** Whether or not we've finished reading the initial connect line. */
protected boolean doneConnect;
/** The current header we're in the process of reading. */
protected StringBuilder currentHeader = new StringBuilder(1024);
/** The connect line. */
protected String connectLine;
/** The amount of data read. */
private long amountRead;
/** Constructs a new ReadHandshakeState using the given support & stat. */
public ReadHeadersIOState(HeaderSupport support, Statistic stat,
int maxHeaders, int maxHeaderSize) {
this.support = support;
this.stat = stat;
this.maxHeaders = maxHeaders;
this.maxHeaderSize = maxHeaderSize;
}
/**
* Reads as much data as it can from the buffer, farming the processing of the
* connect line (same as response line) and headers out to the methods:
* <pre>
* processConnectLine(String line)
* processHeaders()
* </pre>
* This will return true if it needs to be called again for more processing,
* otherwise it will return false indicating it's time to move on to the next
* state.
*/
@Override
protected boolean processRead(ReadableByteChannel rc, ByteBuffer buffer) throws IOException {
boolean allDone = false;
while(!allDone) {
int read = 0;
while(buffer.hasRemaining() && (read = rc.read(buffer)) > 0) {
if(stat != null)
stat.addData(read);
amountRead += read;
}
if(buffer.position() == 0) {
if(read == -1)
throw new IOException("EOF");
break;
}
buffer.flip();
if(!doneConnect) {
if(BufferUtils.readLine(buffer, currentHeader)) {
connectLine = currentHeader.toString();
if(LOG.isDebugEnabled())
LOG.debug("Read connect line: " + connectLine);
currentHeader.delete(0, currentHeader.length());
processConnectLine();
doneConnect = true;
}
}
if(doneConnect) {
while(true) {
if(!BufferUtils.readLine(buffer, currentHeader))
break;
if(LOG.isDebugEnabled())
LOG.debug("Read header: " + currentHeader);
if(!support.processReadHeader(currentHeader.toString())) {
allDone = true;
break; // we finished reading this set of headers!
}
currentHeader.delete(0, currentHeader.length()); // reset for the next header.
// Make sure we don't try and read forever.
if(support.getHeadersReadSize() > maxHeaders)
throw new IOException("too many headers");
}
}
buffer.compact();
// Don't allow someone to send us a header so big that we blow up.
// Note that we don't check this after immediately after creating the
// header, because it's not really so important there. We know the
// data cannot be bigger than the buffer's size, and the buffer's size isn't
// too extraordinarily large, so this works out okay.
if(currentHeader.length() > maxHeaderSize)
throw new IOException("header too big");
}
if(allDone) {
processHeaders();
return false;
} else {
return true;
}
}
public long getAmountProcessed() {
return amountRead;
}
/**
* Reacts to the connect line, either throwing an IOException if it was invalid
* or doing nothing if it was valid.
*/
abstract protected void processConnectLine() throws IOException;
/**
* Reacts to the event of headers being finished processing. Throws an IOException
* if the connection wasn't allowed.
*
* @throws IOException
*/
abstract protected void processHeaders() throws IOException;
}