package org.neo4j.smack.pipeline.http; import java.util.Set; import org.jboss.netty.buffer.ChannelBuffer; import org.jboss.netty.handler.codec.frame.TooLongFrameException; import org.neo4j.smack.gcfree.MutableString; public class HttpHeaderDecoder { // Content-length : 0 // Some-random-header : a value // can continue on the next line // which is insane // Some-other-header: 12 private final HttpHeaderName [] headersToCareAbout; private final int maxHeaderSize; private final MutableString rawHeaderName = new MutableString(32); private final MutableString value = new MutableString(128); private HttpHeaderName headerName; private int headerSize; public HttpHeaderDecoder(Set<HttpHeaderName> headersToCareAbout, int maxHeaderSize) { if (maxHeaderSize <= 0) { throw new IllegalArgumentException( "maxHeaderSize must be a positive integer: " + maxHeaderSize); } this.headersToCareAbout = headersToCareAbout.toArray(new HttpHeaderName [headersToCareAbout.size()]); this.maxHeaderSize = maxHeaderSize; } public void decode(ChannelBuffer buf, HttpHeaderContainer output) throws TooLongFrameException { int currentIndex, lineStopIndex; headerName = null; headerSize = 0; // Read one line currentIndex = buf.readerIndex(); lineStopIndex = readLine(buf); // Parse loop if(lineStopIndex-currentIndex != 0) { do { char firstChar = (char)buf.getByte(currentIndex); if (headerName != null && (firstChar == ' ' || firstChar == '\t')) { value.append(' '); while(currentIndex++ < lineStopIndex) { value.append((char)buf.getByte(currentIndex)); } } else { if (headerName != null) { output.addHeader(headerName, value); } // Read header name currentIndex = readHeader(buf, currentIndex, lineStopIndex); // Skip space after header currentIndex = skipStuffBetweenNameAndValue(buf, currentIndex, lineStopIndex); // Read header value currentIndex = readValue(buf, currentIndex, lineStopIndex); } currentIndex = buf.readerIndex(); lineStopIndex = readLine(buf); } while (lineStopIndex-currentIndex != 0); // Add the last header. if (headerName != null) { output.addHeader(headerName, value); } } } // // // Parse line // for(;currentIndex<=lineStopIndex;currentIndex++) { // // nextByte = (char)buf.getByte(currentIndex); // // switchStatement: // switch(state){ // case SKIP_INITIAL_WHITESPACE : // do { // nextByte = (char)buf.readByte(); // } while(Character.isWhitespace(nextByte)); // // case READ_HEADER_NAME : // rawHeaderName.reset(); // while(nextByte != ':' && !Character.isWhitespace(nextByte)) { // rawHeaderName.append(nextByte); // nextByte = (char)buf.readByte(); // } // // for(int i=0;i<headersToCareAbout.length;i++) // { // if(headersToCareAbout[i].equalsString(rawHeaderName)) // { // headerName = headersToCareAbout[i]; // state = State.SKIP_SPACE_AFTER_HEADER_NAME; // break switchStatement; // } // } // // // This is a header we don't care about // state = State.SKIP_UNTIL_NEXT_HEADER_STARTS; // break switchStatement; // // case SKIP_SPACE_AFTER_HEADER_NAME: // while(nextByte != ':') { // nextByte = (char)buf.readByte(); // } // do { // nextByte = (char)buf.readByte(); // } while(Character.isWhitespace(nextByte)); // // case READ_VALUE: // value.reset(); // valueLoop: // for(;;) { // switch (nextByte) { // case HttpTokens.CR: // nextByte = (char) buf.readByte(); // headerSize ++; // if (nextByte == HttpTokens.LF) { // break valueLoop; // } // break; // case HttpTokens.LF: // break valueLoop; // } // // value.append(nextByte); // nextByte = (char)buf.readByte(); // } // // output.addHeader(headerName, value); // break switchStatement; // // case SKIP_UNTIL_NEXT_HEADER_STARTS: // break mainLoop; // } // } // } private int readValue(ChannelBuffer buf, int currentIndex, int lineStopIndex) { value.reset(); char nextByte; while(currentIndex < lineStopIndex) { nextByte = (char)buf.getByte(currentIndex++); value.append(nextByte); } return currentIndex; } // Hops past the ' : ' part of 'Content-length : 0' private int skipStuffBetweenNameAndValue(ChannelBuffer buf, int currentIndex, int lineStopIndex) { char nextByte = (char)buf.getByte(currentIndex); // Read up until ':' while(nextByte != ':' && currentIndex < lineStopIndex) { nextByte = (char)buf.getByte(++currentIndex); } // Read until end of whitespace if(currentIndex < lineStopIndex) { do { nextByte = (char)buf.getByte(++currentIndex); } while(Character.isWhitespace(nextByte) && currentIndex < lineStopIndex); } return currentIndex; } private int readHeader(ChannelBuffer buf, int currentIndex, int lineStopIndex) { rawHeaderName.reset(); headerName = null; char nextByte = (char)buf.getByte(currentIndex); while(nextByte != ':' && !Character.isWhitespace(nextByte) && currentIndex < lineStopIndex) { rawHeaderName.append(nextByte); nextByte = (char)buf.getByte(++currentIndex); } for(int i=0;i<headersToCareAbout.length;i++) { if(headersToCareAbout[i].equalsString(rawHeaderName)) { headerName = headersToCareAbout[i]; break; } } return currentIndex; } private int readLine(ChannelBuffer buf) throws TooLongFrameException { int skip = 1; char nextByte; lineLoop: for (;;) { nextByte = (char) buf.readByte(); headerSize ++; switch (nextByte) { case HttpTokens.CR: nextByte = (char) buf.readByte(); headerSize ++; if (nextByte == HttpTokens.LF) { skip = 2; break lineLoop; } break; case HttpTokens.LF: skip = 1; break lineLoop; } // Abort decoding if the header part is too large. if (headerSize >= maxHeaderSize) { // TODO: Respond with Bad Request and discard the traffic // or close the connection. // No need to notify the upstream handlers - just log. // If decoding a response, just throw an exception. throw new TooLongFrameException( "HTTP header is larger than " + maxHeaderSize + " bytes."); } } return buf.readerIndex() - skip; // -2 to not include CR LF } }