--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.http;
+
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+
+import org.eclipse.jetty.http.HttpTokens.EndOfContent;
+import org.eclipse.jetty.util.ArrayTernaryTrie;
+import org.eclipse.jetty.util.ArrayTrie;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.Trie;
+import org.eclipse.jetty.util.TypeUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+
+/* ------------------------------------------------------------ */
+/** A Parser for HTTP 0.9, 1.0 and 1.1
+ * <p>
+ * The is parser parses HTTP client and server messages from buffers
+ * passed in the {@link #parseNext(ByteBuffer)} method. The parsed
+ * elements of the HTTP message are passed as event calls to the
+ * {@link HttpHandler} instance the parser is constructed with.
+ * If the passed handler is a {@link RequestHandler} then server side
+ * parsing is performed and if it is a {@link ResponseHandler}, then
+ * client side parsing is done.
+ * </p>
+ * <p>
+ * The contract of the {@link HttpHandler} API is that if a call returns
+ * true then the call to {@link #parseNext(ByteBuffer)} will return as
+ * soon as possible also with a true response. Typically this indicates
+ * that the parsing has reached a stage where the caller should process
+ * the events accumulated by the handler. It is the preferred calling
+ * style that handling such as calling a servlet to process a request,
+ * should be done after a true return from {@link #parseNext(ByteBuffer)}
+ * rather than from within the scope of a call like
+ * {@link RequestHandler#messageComplete()}
+ * </p>
+ * <p>
+ * For performance, the parse is heavily dependent on the
+ * {@link Trie#getBest(ByteBuffer, int, int)} method to look ahead in a
+ * single pass for both the structure ( : and CRLF ) and semantic (which
+ * header and value) of a header. Specifically the static {@link HttpHeader#CACHE}
+ * is used to lookup common combinations of headers and values
+ * (eg. "Connection: close"), or just header names (eg. "Connection:" ).
+ * For headers who's value is not known statically (eg. Host, COOKIE) then a
+ * per parser dynamic Trie of {@link HttpFields} from previous parsed messages
+ * is used to help the parsing of subsequent messages.
+ * </p>
+ * <p>
+ * If the system property "org.eclipse.jetty.http.HttpParser.STRICT" is set to true,
+ * then the parser will strictly pass on the exact strings received for methods and header
+ * fields. Otherwise a fast case insensitive string lookup is used that may alter the
+ * case of the method and/or headers
+ * </p>
+ */
+public class HttpParser
+{
+ public static final Logger LOG = Log.getLogger(HttpParser.class);
+ public final static boolean __STRICT=Boolean.getBoolean("org.eclipse.jetty.http.HttpParser.STRICT");
+ public final static int INITIAL_URI_LENGTH=256;
+
+ /**
+ * Cache of common {@link HttpField}s including: <UL>
+ * <LI>Common static combinations such as:<UL>
+ * <li>Connection: close
+ * <li>Accept-Encoding: gzip
+ * <li>Content-Length: 0
+ * </ul>
+ * <li>Combinations of Content-Type header for common mime types by common charsets
+ * <li>Most common headers with null values so that a lookup will at least
+ * determine the header name even if the name:value combination is not cached
+ * </ul>
+ */
+ public final static Trie<HttpField> CACHE = new ArrayTrie<>(2048);
+
+ // States
+ public enum State
+ {
+ START,
+ METHOD,
+ RESPONSE_VERSION,
+ SPACE1,
+ STATUS,
+ URI,
+ SPACE2,
+ REQUEST_VERSION,
+ REASON,
+ PROXY,
+ HEADER,
+ HEADER_IN_NAME,
+ HEADER_VALUE,
+ HEADER_IN_VALUE,
+ CONTENT,
+ EOF_CONTENT,
+ CHUNKED_CONTENT,
+ CHUNK_SIZE,
+ CHUNK_PARAMS,
+ CHUNK,
+ END,
+ CLOSED
+ }
+
+ private final boolean DEBUG=LOG.isDebugEnabled(); // Cache debug to help branch prediction
+ private final HttpHandler<ByteBuffer> _handler;
+ private final RequestHandler<ByteBuffer> _requestHandler;
+ private final ResponseHandler<ByteBuffer> _responseHandler;
+ private final int _maxHeaderBytes;
+ private final boolean _strict;
+ private HttpField _field;
+ private HttpHeader _header;
+ private String _headerString;
+ private HttpHeaderValue _value;
+ private String _valueString;
+ private int _responseStatus;
+ private int _headerBytes;
+ private boolean _host;
+
+ /* ------------------------------------------------------------------------------- */
+ private volatile State _state=State.START;
+ private volatile boolean _eof;
+ private volatile boolean _closed;
+ private HttpMethod _method;
+ private String _methodString;
+ private HttpVersion _version;
+ private ByteBuffer _uri=ByteBuffer.allocate(INITIAL_URI_LENGTH); // Tune?
+ private EndOfContent _endOfContent;
+ private long _contentLength;
+ private long _contentPosition;
+ private int _chunkLength;
+ private int _chunkPosition;
+ private boolean _headResponse;
+ private boolean _cr;
+ private ByteBuffer _contentChunk;
+ private Trie<HttpField> _connectionFields;
+
+ private int _length;
+ private final StringBuilder _string=new StringBuilder();
+
+ static
+ {
+ CACHE.put(new HttpField(HttpHeader.CONNECTION,HttpHeaderValue.CLOSE));
+ CACHE.put(new HttpField(HttpHeader.CONNECTION,HttpHeaderValue.KEEP_ALIVE));
+ CACHE.put(new HttpField(HttpHeader.CONNECTION,HttpHeaderValue.UPGRADE));
+ CACHE.put(new HttpField(HttpHeader.ACCEPT_ENCODING,"gzip"));
+ CACHE.put(new HttpField(HttpHeader.ACCEPT_ENCODING,"gzip, deflate"));
+ CACHE.put(new HttpField(HttpHeader.ACCEPT_ENCODING,"gzip,deflate,sdch"));
+ CACHE.put(new HttpField(HttpHeader.ACCEPT_LANGUAGE,"en-US,en;q=0.5"));
+ CACHE.put(new HttpField(HttpHeader.ACCEPT_LANGUAGE,"en-GB,en-US;q=0.8,en;q=0.6"));
+ CACHE.put(new HttpField(HttpHeader.ACCEPT_CHARSET,"ISO-8859-1,utf-8;q=0.7,*;q=0.3"));
+ CACHE.put(new HttpField(HttpHeader.ACCEPT,"*/*"));
+ CACHE.put(new HttpField(HttpHeader.ACCEPT,"image/png,image/*;q=0.8,*/*;q=0.5"));
+ CACHE.put(new HttpField(HttpHeader.ACCEPT,"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"));
+ CACHE.put(new HttpField(HttpHeader.PRAGMA,"no-cache"));
+ CACHE.put(new HttpField(HttpHeader.CACHE_CONTROL,"private, no-cache, no-cache=Set-Cookie, proxy-revalidate"));
+ CACHE.put(new HttpField(HttpHeader.CACHE_CONTROL,"no-cache"));
+ CACHE.put(new HttpField(HttpHeader.CONTENT_LENGTH,"0"));
+ CACHE.put(new HttpField(HttpHeader.CONTENT_ENCODING,"gzip"));
+ CACHE.put(new HttpField(HttpHeader.CONTENT_ENCODING,"deflate"));
+ CACHE.put(new HttpField(HttpHeader.TRANSFER_ENCODING,"chunked"));
+ CACHE.put(new HttpField(HttpHeader.EXPIRES,"Fri, 01 Jan 1990 00:00:00 GMT"));
+
+ // Add common Content types as fields
+ for (String type : new String[]{"text/plain","text/html","text/xml","text/json","application/json","application/x-www-form-urlencoded"})
+ {
+ HttpField field=new HttpGenerator.CachedHttpField(HttpHeader.CONTENT_TYPE,type);
+ CACHE.put(field);
+
+ for (String charset : new String[]{"UTF-8","ISO-8859-1"})
+ {
+ CACHE.put(new HttpGenerator.CachedHttpField(HttpHeader.CONTENT_TYPE,type+";charset="+charset));
+ CACHE.put(new HttpGenerator.CachedHttpField(HttpHeader.CONTENT_TYPE,type+"; charset="+charset));
+ }
+ }
+
+ // Add headers with null values so HttpParser can avoid looking up name again for unknown values
+ for (HttpHeader h:HttpHeader.values())
+ if (!CACHE.put(new HttpField(h,(String)null)))
+ throw new IllegalStateException("CACHE FULL");
+ // Add some more common headers
+ CACHE.put(new HttpField(HttpHeader.REFERER,(String)null));
+ CACHE.put(new HttpField(HttpHeader.IF_MODIFIED_SINCE,(String)null));
+ CACHE.put(new HttpField(HttpHeader.IF_NONE_MATCH,(String)null));
+ CACHE.put(new HttpField(HttpHeader.AUTHORIZATION,(String)null));
+ CACHE.put(new HttpField(HttpHeader.COOKIE,(String)null));
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ public HttpParser(RequestHandler<ByteBuffer> handler)
+ {
+ this(handler,-1,__STRICT);
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ public HttpParser(ResponseHandler<ByteBuffer> handler)
+ {
+ this(handler,-1,__STRICT);
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ public HttpParser(RequestHandler<ByteBuffer> handler,int maxHeaderBytes)
+ {
+ this(handler,maxHeaderBytes,__STRICT);
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ public HttpParser(ResponseHandler<ByteBuffer> handler,int maxHeaderBytes)
+ {
+ this(handler,maxHeaderBytes,__STRICT);
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ public HttpParser(RequestHandler<ByteBuffer> handler,int maxHeaderBytes,boolean strict)
+ {
+ _handler=handler;
+ _requestHandler=handler;
+ _responseHandler=null;
+ _maxHeaderBytes=maxHeaderBytes;
+ _strict=strict;
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ public HttpParser(ResponseHandler<ByteBuffer> handler,int maxHeaderBytes,boolean strict)
+ {
+ _handler=handler;
+ _requestHandler=null;
+ _responseHandler=handler;
+ _maxHeaderBytes=maxHeaderBytes;
+ _strict=strict;
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ public long getContentLength()
+ {
+ return _contentLength;
+ }
+
+ /* ------------------------------------------------------------ */
+ public long getContentRead()
+ {
+ return _contentPosition;
+ }
+
+ /* ------------------------------------------------------------ */
+ /** Set if a HEAD response is expected
+ * @param head
+ */
+ public void setHeadResponse(boolean head)
+ {
+ _headResponse=head;
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ protected void setResponseStatus(int status)
+ {
+ _responseStatus=status;
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ public State getState()
+ {
+ return _state;
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ public boolean inContentState()
+ {
+ return _state.ordinal()>=State.CONTENT.ordinal() && _state.ordinal()<State.END.ordinal();
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ public boolean inHeaderState()
+ {
+ return _state.ordinal() < State.CONTENT.ordinal();
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ public boolean isChunking()
+ {
+ return _endOfContent==EndOfContent.CHUNKED_CONTENT;
+ }
+
+ /* ------------------------------------------------------------ */
+ public boolean isStart()
+ {
+ return isState(State.START);
+ }
+
+ /* ------------------------------------------------------------ */
+ public boolean isClosed()
+ {
+ return isState(State.CLOSED);
+ }
+
+ /* ------------------------------------------------------------ */
+ public boolean isIdle()
+ {
+ return isState(State.START)||isState(State.END)||isState(State.CLOSED);
+ }
+
+ /* ------------------------------------------------------------ */
+ public boolean isComplete()
+ {
+ return isState(State.END)||isState(State.CLOSED);
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ public boolean isState(State state)
+ {
+ return _state == state;
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ private static class BadMessage extends Error
+ {
+ private static final long serialVersionUID = 1L;
+ private final int _code;
+ private final String _message;
+
+ BadMessage()
+ {
+ this(400,null);
+ }
+
+ BadMessage(int code)
+ {
+ this(code,null);
+ }
+
+ BadMessage(String message)
+ {
+ this(400,message);
+ }
+
+ BadMessage(int code,String message)
+ {
+ _code=code;
+ _message=message;
+ }
+
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ private byte next(ByteBuffer buffer)
+ {
+ byte ch = buffer.get();
+
+ if (_cr)
+ {
+ if (ch!=HttpTokens.LINE_FEED)
+ throw new BadMessage("Bad EOL");
+ _cr=false;
+ return ch;
+ }
+
+ if (ch>=0 && ch<HttpTokens.SPACE)
+ {
+ if (ch==HttpTokens.CARRIAGE_RETURN)
+ {
+ if (buffer.hasRemaining())
+ {
+ if(_maxHeaderBytes>0 && _state.ordinal()<State.END.ordinal())
+ _headerBytes++;
+ ch=buffer.get();
+ if (ch!=HttpTokens.LINE_FEED)
+ throw new BadMessage("Bad EOL");
+ }
+ else
+ {
+ _cr=true;
+ // Can return 0 here to indicate the need for more characters,
+ // because a real 0 in the buffer would cause a BadMessage below
+ return 0;
+ }
+ }
+ // Only LF or TAB acceptable special characters
+ else if (!(ch==HttpTokens.LINE_FEED || ch==HttpTokens.TAB))
+ throw new BadMessage("Illegal character");
+ }
+
+ return ch;
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ /* Quick lookahead for the start state looking for a request method or a HTTP version,
+ * otherwise skip white space until something else to parse.
+ */
+ private boolean quickStart(ByteBuffer buffer)
+ {
+ if (_requestHandler!=null)
+ {
+ _method = HttpMethod.lookAheadGet(buffer);
+ if (_method!=null)
+ {
+ _methodString = _method.asString();
+ buffer.position(buffer.position()+_methodString.length()+1);
+
+ setState(State.SPACE1);
+ return false;
+ }
+ }
+ else if (_responseHandler!=null)
+ {
+ _version = HttpVersion.lookAheadGet(buffer);
+ if (_version!=null)
+ {
+ buffer.position(buffer.position()+_version.asString().length()+1);
+ setState(State.SPACE1);
+ return false;
+ }
+ }
+
+ // Quick start look
+ while (_state==State.START && buffer.hasRemaining())
+ {
+ int ch=next(buffer);
+
+ if (ch > HttpTokens.SPACE)
+ {
+ _string.setLength(0);
+ _string.append((char)ch);
+ setState(_requestHandler!=null?State.METHOD:State.RESPONSE_VERSION);
+ return false;
+ }
+ else if (ch==0)
+ break;
+ else if (ch<0)
+ throw new BadMessage();
+
+ // count this white space as a header byte to avoid DOS
+ if (_maxHeaderBytes>0 && ++_headerBytes>_maxHeaderBytes)
+ {
+ LOG.warn("padding is too large >"+_maxHeaderBytes);
+ throw new BadMessage(HttpStatus.BAD_REQUEST_400);
+ }
+ }
+ return false;
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ private void setString(String s)
+ {
+ _string.setLength(0);
+ _string.append(s);
+ _length=s.length();
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ private String takeString()
+ {
+ _string.setLength(_length);
+ String s =_string.toString();
+ _string.setLength(0);
+ _length=-1;
+ return s;
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ /* Parse a request or response line
+ */
+ private boolean parseLine(ByteBuffer buffer)
+ {
+ boolean handle=false;
+
+ // Process headers
+ while (_state.ordinal()<State.HEADER.ordinal() && buffer.hasRemaining() && !handle)
+ {
+ // process each character
+ byte ch=next(buffer);
+ if (ch==0)
+ break;
+
+ if (_maxHeaderBytes>0 && ++_headerBytes>_maxHeaderBytes)
+ {
+ if (_state==State.URI)
+ {
+ LOG.warn("URI is too large >"+_maxHeaderBytes);
+ throw new BadMessage(HttpStatus.REQUEST_URI_TOO_LONG_414);
+ }
+ else
+ {
+ if (_requestHandler!=null)
+ LOG.warn("request is too large >"+_maxHeaderBytes);
+ else
+ LOG.warn("response is too large >"+_maxHeaderBytes);
+ throw new BadMessage(HttpStatus.REQUEST_ENTITY_TOO_LARGE_413);
+ }
+ }
+
+ switch (_state)
+ {
+ case METHOD:
+ if (ch == HttpTokens.SPACE)
+ {
+ _length=_string.length();
+ _methodString=takeString();
+ HttpMethod method=HttpMethod.CACHE.get(_methodString);
+ if (method!=null && !_strict)
+ _methodString=method.asString();
+ setState(State.SPACE1);
+ }
+ else if (ch < HttpTokens.SPACE)
+ throw new BadMessage(ch<0?"Illegal character":"No URI");
+ else
+ _string.append((char)ch);
+ break;
+
+ case RESPONSE_VERSION:
+ if (ch == HttpTokens.SPACE)
+ {
+ _length=_string.length();
+ String version=takeString();
+ _version=HttpVersion.CACHE.get(version);
+ if (_version==null)
+ throw new BadMessage(HttpStatus.BAD_REQUEST_400,"Unknown Version");
+ setState(State.SPACE1);
+ }
+ else if (ch < HttpTokens.SPACE)
+ throw new BadMessage(ch<0?"Illegal character":"No Status");
+ else
+ _string.append((char)ch);
+ break;
+
+ case SPACE1:
+ if (ch > HttpTokens.SPACE || ch<0)
+ {
+ if (_responseHandler!=null)
+ {
+ setState(State.STATUS);
+ setResponseStatus(ch-'0');
+ }
+ else
+ {
+ _uri.clear();
+ setState(State.URI);
+ // quick scan for space or EoBuffer
+ if (buffer.hasArray())
+ {
+ byte[] array=buffer.array();
+ int p=buffer.arrayOffset()+buffer.position();
+ int l=buffer.arrayOffset()+buffer.limit();
+ int i=p;
+ while (i<l && array[i]>HttpTokens.SPACE)
+ i++;
+
+ int len=i-p;
+ _headerBytes+=len;
+
+ if (_maxHeaderBytes>0 && ++_headerBytes>_maxHeaderBytes)
+ {
+ LOG.warn("URI is too large >"+_maxHeaderBytes);
+ throw new BadMessage(HttpStatus.REQUEST_URI_TOO_LONG_414);
+ }
+ if (_uri.remaining()<=len)
+ {
+ ByteBuffer uri = ByteBuffer.allocate(_uri.capacity()+2*len);
+ _uri.flip();
+ uri.put(_uri);
+ _uri=uri;
+ }
+ _uri.put(array,p-1,len+1);
+ buffer.position(i-buffer.arrayOffset());
+ }
+ else
+ _uri.put(ch);
+ }
+ }
+ else if (ch < HttpTokens.SPACE)
+ {
+ throw new BadMessage(HttpStatus.BAD_REQUEST_400,_requestHandler!=null?"No URI":"No Status");
+ }
+ break;
+
+ case STATUS:
+ if (ch == HttpTokens.SPACE)
+ {
+ setState(State.SPACE2);
+ }
+ else if (ch>='0' && ch<='9')
+ {
+ _responseStatus=_responseStatus*10+(ch-'0');
+ }
+ else if (ch < HttpTokens.SPACE && ch>=0)
+ {
+ handle=_responseHandler.startResponse(_version, _responseStatus, null)||handle;
+ setState(State.HEADER);
+ }
+ else
+ {
+ throw new BadMessage();
+ }
+ break;
+
+ case URI:
+ if (ch == HttpTokens.SPACE)
+ {
+ setState(State.SPACE2);
+ }
+ else if (ch < HttpTokens.SPACE && ch>=0)
+ {
+ // HTTP/0.9
+ _uri.flip();
+ handle=_requestHandler.startRequest(_method,_methodString,_uri,null)||handle;
+ setState(State.END);
+ BufferUtil.clear(buffer);
+ handle=_handler.headerComplete()||handle;
+ handle=_handler.messageComplete()||handle;
+ }
+ else
+ {
+ if (!_uri.hasRemaining())
+ {
+ ByteBuffer uri = ByteBuffer.allocate(_uri.capacity()*2);
+ _uri.flip();
+ uri.put(_uri);
+ _uri=uri;
+ }
+ _uri.put(ch);
+ }
+ break;
+
+ case SPACE2:
+ if (ch > HttpTokens.SPACE)
+ {
+ _string.setLength(0);
+ _string.append((char)ch);
+ if (_responseHandler!=null)
+ {
+ _length=1;
+ setState(State.REASON);
+ }
+ else
+ {
+ setState(State.REQUEST_VERSION);
+
+ // try quick look ahead for HTTP Version
+ HttpVersion version;
+ if (buffer.position()>0 && buffer.hasArray())
+ version=HttpVersion.lookAheadGet(buffer.array(),buffer.arrayOffset()+buffer.position()-1,buffer.arrayOffset()+buffer.limit());
+ else
+ version=HttpVersion.CACHE.getBest(buffer,0,buffer.remaining());
+ if (version==null)
+ {
+ if (_method==HttpMethod.PROXY)
+ {
+ if (!(_requestHandler instanceof ProxyHandler))
+ throw new BadMessage();
+
+ _uri.flip();
+ String protocol=BufferUtil.toString(_uri);
+ // This is the proxy protocol, so we can assume entire first line is in buffer else 400
+ buffer.position(buffer.position()-1);
+ String sAddr = getProxyField(buffer);
+ String dAddr = getProxyField(buffer);
+ int sPort = BufferUtil.takeInt(buffer);
+ next(buffer);
+ int dPort = BufferUtil.takeInt(buffer);
+ next(buffer);
+ _state=State.START;
+ ((ProxyHandler)_requestHandler).proxied(protocol,sAddr,dAddr,sPort,dPort);
+ return false;
+ }
+ }
+ else
+ {
+ int pos = buffer.position()+version.asString().length()-1;
+ if (pos<buffer.limit())
+ {
+ byte n=buffer.get(pos);
+ if (n==HttpTokens.CARRIAGE_RETURN)
+ {
+ _cr=true;
+ _version=version;
+ _string.setLength(0);
+ buffer.position(pos+1);
+ }
+ else if (n==HttpTokens.LINE_FEED)
+ {
+ _version=version;
+ _string.setLength(0);
+ buffer.position(pos);
+ }
+ }
+ }
+ }
+ }
+ else if (ch == HttpTokens.LINE_FEED)
+ {
+ if (_responseHandler!=null)
+ {
+ handle=_responseHandler.startResponse(_version, _responseStatus, null)||handle;
+ setState(State.HEADER);
+ }
+ else
+ {
+ // HTTP/0.9
+ _uri.flip();
+ handle=_requestHandler.startRequest(_method,_methodString,_uri, null)||handle;
+ setState(State.END);
+ BufferUtil.clear(buffer);
+ handle=_handler.headerComplete()||handle;
+ handle=_handler.messageComplete()||handle;
+ }
+ }
+ else if (ch<0)
+ throw new BadMessage();
+ break;
+
+ case REQUEST_VERSION:
+ if (ch == HttpTokens.LINE_FEED)
+ {
+ if (_version==null)
+ {
+ _length=_string.length();
+ _version=HttpVersion.CACHE.get(takeString());
+ }
+ if (_version==null)
+ throw new BadMessage(HttpStatus.BAD_REQUEST_400,"Unknown Version");
+
+ // Should we try to cache header fields?
+ if (_connectionFields==null && _version.getVersion()>=HttpVersion.HTTP_1_1.getVersion())
+ {
+ int header_cache = _handler.getHeaderCacheSize();
+ _connectionFields=new ArrayTernaryTrie<>(header_cache);
+ }
+
+ setState(State.HEADER);
+ _uri.flip();
+ handle=_requestHandler.startRequest(_method,_methodString,_uri, _version)||handle;
+ continue;
+ }
+ else if (ch>=HttpTokens.SPACE)
+ _string.append((char)ch);
+ else
+ throw new BadMessage();
+
+ break;
+
+ case REASON:
+ if (ch == HttpTokens.LINE_FEED)
+ {
+ String reason=takeString();
+
+ setState(State.HEADER);
+ handle=_responseHandler.startResponse(_version, _responseStatus, reason)||handle;
+ continue;
+ }
+ else if (ch>=HttpTokens.SPACE)
+ {
+ _string.append((char)ch);
+ if (ch!=' '&&ch!='\t')
+ _length=_string.length();
+ }
+ else
+ throw new BadMessage();
+ break;
+
+ default:
+ throw new IllegalStateException(_state.toString());
+
+ }
+ }
+
+ return handle;
+ }
+
+ private boolean handleKnownHeaders(ByteBuffer buffer)
+ {
+ boolean add_to_connection_trie=false;
+ switch (_header)
+ {
+ case CONTENT_LENGTH:
+ if (_endOfContent != EndOfContent.CHUNKED_CONTENT)
+ {
+ try
+ {
+ _contentLength=Long.parseLong(_valueString);
+ }
+ catch(NumberFormatException e)
+ {
+ LOG.ignore(e);
+ throw new BadMessage(HttpStatus.BAD_REQUEST_400,"Bad Content-Length");
+ }
+ if (_contentLength <= 0)
+ _endOfContent=EndOfContent.NO_CONTENT;
+ else
+ _endOfContent=EndOfContent.CONTENT_LENGTH;
+ }
+ break;
+
+ case TRANSFER_ENCODING:
+ if (_value==HttpHeaderValue.CHUNKED)
+ _endOfContent=EndOfContent.CHUNKED_CONTENT;
+ else
+ {
+ if (_valueString.endsWith(HttpHeaderValue.CHUNKED.toString()))
+ _endOfContent=EndOfContent.CHUNKED_CONTENT;
+ else if (_valueString.contains(HttpHeaderValue.CHUNKED.toString()))
+ {
+ throw new BadMessage(HttpStatus.BAD_REQUEST_400,"Bad chunking");
+ }
+ }
+ break;
+
+ case HOST:
+ add_to_connection_trie=_connectionFields!=null && _field==null;
+ _host=true;
+ String host=_valueString;
+ int port=0;
+ if (host==null || host.length()==0)
+ {
+ throw new BadMessage(HttpStatus.BAD_REQUEST_400,"Bad Host header");
+ }
+
+ int len=host.length();
+ loop: for (int i = len; i-- > 0;)
+ {
+ char c2 = (char)(0xff & host.charAt(i));
+ switch (c2)
+ {
+ case ']':
+ break loop;
+
+ case ':':
+ try
+ {
+ len=i;
+ port = StringUtil.toInt(host.substring(i+1));
+ }
+ catch (NumberFormatException e)
+ {
+ if (DEBUG)
+ LOG.debug(e);
+ throw new BadMessage(HttpStatus.BAD_REQUEST_400,"Bad Host header");
+ }
+ break loop;
+ }
+ }
+ if (host.charAt(0)=='[')
+ {
+ if (host.charAt(len-1)!=']')
+ {
+ throw new BadMessage(HttpStatus.BAD_REQUEST_400,"Bad IPv6 Host header");
+ }
+ host = host.substring(1,len-1);
+ }
+ else if (len!=host.length())
+ host = host.substring(0,len);
+
+ if (_requestHandler!=null)
+ _requestHandler.parsedHostHeader(host,port);
+
+ break;
+
+ case CONNECTION:
+ // Don't cache if not persistent
+ if (_valueString!=null && _valueString.contains("close"))
+ {
+ _closed=true;
+ _connectionFields=null;
+ }
+ break;
+
+ case AUTHORIZATION:
+ case ACCEPT:
+ case ACCEPT_CHARSET:
+ case ACCEPT_ENCODING:
+ case ACCEPT_LANGUAGE:
+ case COOKIE:
+ case CACHE_CONTROL:
+ case USER_AGENT:
+ add_to_connection_trie=_connectionFields!=null && _field==null;
+ break;
+
+ default: break;
+ }
+
+ if (add_to_connection_trie && !_connectionFields.isFull() && _header!=null && _valueString!=null)
+ {
+ _field=new HttpField(_header,_valueString);
+ _connectionFields.put(_field);
+ }
+
+ return false;
+ }
+
+
+ /* ------------------------------------------------------------------------------- */
+ /*
+ * Parse the message headers and return true if the handler has signaled for a return
+ */
+ protected boolean parseHeaders(ByteBuffer buffer)
+ {
+ boolean handle=false;
+
+ // Process headers
+ while (_state.ordinal()<State.CONTENT.ordinal() && buffer.hasRemaining() && !handle)
+ {
+ // process each character
+ byte ch=next(buffer);
+ if (ch==0)
+ break;
+
+ if (_maxHeaderBytes>0 && ++_headerBytes>_maxHeaderBytes)
+ {
+ LOG.warn("Header is too large >"+_maxHeaderBytes);
+ throw new BadMessage(HttpStatus.REQUEST_ENTITY_TOO_LARGE_413);
+ }
+
+ switch (_state)
+ {
+ case HEADER:
+ switch(ch)
+ {
+ case HttpTokens.COLON:
+ case HttpTokens.SPACE:
+ case HttpTokens.TAB:
+ {
+ // header value without name - continuation?
+ if (_valueString==null)
+ {
+ _string.setLength(0);
+ _length=0;
+ }
+ else
+ {
+ setString(_valueString);
+ _string.append(' ');
+ _length++;
+ _valueString=null;
+ }
+ setState(State.HEADER_VALUE);
+ break;
+ }
+
+ default:
+ {
+ // handler last header if any. Delayed to here just in case there was a continuation line (above)
+ if (_headerString!=null || _valueString!=null)
+ {
+ // Handle known headers
+ if (_header!=null && handleKnownHeaders(buffer))
+ {
+ _headerString=_valueString=null;
+ _header=null;
+ _value=null;
+ _field=null;
+ return true;
+ }
+ handle=_handler.parsedHeader(_field!=null?_field:new HttpField(_header,_headerString,_valueString))||handle;
+ }
+ _headerString=_valueString=null;
+ _header=null;
+ _value=null;
+ _field=null;
+
+ // now handle the ch
+ if (ch == HttpTokens.LINE_FEED)
+ {
+ _contentPosition=0;
+
+ // End of headers!
+
+ // Was there a required host header?
+ if (!_host && _version!=HttpVersion.HTTP_1_0 && _requestHandler!=null)
+ {
+ throw new BadMessage(HttpStatus.BAD_REQUEST_400,"No Host");
+ }
+
+ // is it a response that cannot have a body?
+ if (_responseHandler !=null && // response
+ (_responseStatus == 304 || // not-modified response
+ _responseStatus == 204 || // no-content response
+ _responseStatus < 200)) // 1xx response
+ _endOfContent=EndOfContent.NO_CONTENT; // ignore any other headers set
+
+ // else if we don't know framing
+ else if (_endOfContent == EndOfContent.UNKNOWN_CONTENT)
+ {
+ if (_responseStatus == 0 // request
+ || _responseStatus == 304 // not-modified response
+ || _responseStatus == 204 // no-content response
+ || _responseStatus < 200) // 1xx response
+ _endOfContent=EndOfContent.NO_CONTENT;
+ else
+ _endOfContent=EndOfContent.EOF_CONTENT;
+ }
+
+ // How is the message ended?
+ switch (_endOfContent)
+ {
+ case EOF_CONTENT:
+ setState(State.EOF_CONTENT);
+ handle=_handler.headerComplete()||handle;
+ break;
+
+ case CHUNKED_CONTENT:
+ setState(State.CHUNKED_CONTENT);
+ handle=_handler.headerComplete()||handle;
+ break;
+
+ case NO_CONTENT:
+ handle=_handler.headerComplete()||handle;
+ setState(State.END);
+ handle=_handler.messageComplete()||handle;
+ break;
+
+ default:
+ setState(State.CONTENT);
+ handle=_handler.headerComplete()||handle;
+ break;
+ }
+ }
+ else if (ch<=HttpTokens.SPACE)
+ throw new BadMessage();
+ else
+ {
+ if (buffer.hasRemaining())
+ {
+ // Try a look ahead for the known header name and value.
+ HttpField field=_connectionFields==null?null:_connectionFields.getBest(buffer,-1,buffer.remaining());
+ if (field==null)
+ field=CACHE.getBest(buffer,-1,buffer.remaining());
+
+ if (field!=null)
+ {
+ final String n;
+ final String v;
+
+ if (_strict)
+ {
+ // Have to get the fields exactly from the buffer to match case
+ String fn=field.getName();
+ String fv=field.getValue();
+ n=BufferUtil.toString(buffer,buffer.position()-1,fn.length(),StandardCharsets.US_ASCII);
+ if (fv==null)
+ v=null;
+ else
+ {
+ v=BufferUtil.toString(buffer,buffer.position()+fn.length()+1,fv.length(),StandardCharsets.ISO_8859_1);
+ field=new HttpField(field.getHeader(),n,v);
+ }
+ }
+ else
+ {
+ n=field.getName();
+ v=field.getValue();
+ }
+
+ _header=field.getHeader();
+ _headerString=n;
+
+ if (v==null)
+ {
+ // Header only
+ setState(State.HEADER_VALUE);
+ _string.setLength(0);
+ _length=0;
+ buffer.position(buffer.position()+n.length()+1);
+ break;
+ }
+ else
+ {
+ // Header and value
+ int pos=buffer.position()+n.length()+v.length()+1;
+ byte b=buffer.get(pos);
+
+ if (b==HttpTokens.CARRIAGE_RETURN || b==HttpTokens.LINE_FEED)
+ {
+ _field=field;
+ _valueString=v;
+ setState(State.HEADER_IN_VALUE);
+
+ if (b==HttpTokens.CARRIAGE_RETURN)
+ {
+ _cr=true;
+ buffer.position(pos+1);
+ }
+ else
+ buffer.position(pos);
+ break;
+ }
+ else
+ {
+ setState(State.HEADER_IN_VALUE);
+ setString(v);
+ buffer.position(pos);
+ break;
+ }
+ }
+ }
+ }
+
+ // New header
+ setState(State.HEADER_IN_NAME);
+ _string.setLength(0);
+ _string.append((char)ch);
+ _length=1;
+ }
+ }
+ }
+ break;
+
+ case HEADER_IN_NAME:
+ if (ch==HttpTokens.COLON || ch==HttpTokens.LINE_FEED)
+ {
+ if (_headerString==null)
+ {
+ _headerString=takeString();
+ _header=HttpHeader.CACHE.get(_headerString);
+ }
+ _length=-1;
+
+ setState(ch==HttpTokens.LINE_FEED?State.HEADER:State.HEADER_VALUE);
+ break;
+ }
+
+ if (ch>=HttpTokens.SPACE || ch==HttpTokens.TAB)
+ {
+ if (_header!=null)
+ {
+ setString(_header.asString());
+ _header=null;
+ _headerString=null;
+ }
+
+ _string.append((char)ch);
+ if (ch>HttpTokens.SPACE)
+ _length=_string.length();
+ break;
+ }
+
+ throw new BadMessage("Illegal character");
+
+ case HEADER_VALUE:
+ if (ch>HttpTokens.SPACE || ch<0)
+ {
+ _string.append((char)(0xff&ch));
+ _length=_string.length();
+ setState(State.HEADER_IN_VALUE);
+ break;
+ }
+
+ if (ch==HttpTokens.SPACE || ch==HttpTokens.TAB)
+ break;
+
+ if (ch==HttpTokens.LINE_FEED)
+ {
+ if (_length > 0)
+ {
+ _value=null;
+ _valueString=(_valueString==null)?takeString():(_valueString+" "+takeString());
+ }
+ setState(State.HEADER);
+ break;
+ }
+
+ throw new BadMessage("Illegal character");
+
+ case HEADER_IN_VALUE:
+ if (ch>=HttpTokens.SPACE || ch<0 || ch==HttpTokens.TAB)
+ {
+ if (_valueString!=null)
+ {
+ setString(_valueString);
+ _valueString=null;
+ _field=null;
+ }
+ _string.append((char)(0xff&ch));
+ if (ch>HttpTokens.SPACE || ch<0)
+ _length=_string.length();
+ break;
+ }
+
+ if (ch==HttpTokens.LINE_FEED)
+ {
+ if (_length > 0)
+ {
+ _value=null;
+ _valueString=takeString();
+ _length=-1;
+ }
+ setState(State.HEADER);
+ break;
+ }
+ throw new BadMessage("Illegal character");
+
+ default:
+ throw new IllegalStateException(_state.toString());
+
+ }
+ }
+
+ return handle;
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ /**
+ * Parse until next Event.
+ * @return True if an {@link RequestHandler} method was called and it returned true;
+ */
+ public boolean parseNext(ByteBuffer buffer)
+ {
+ if (DEBUG)
+ LOG.debug("parseNext s={} {}",_state,BufferUtil.toDetailString(buffer));
+ try
+ {
+ // Start a request/response
+ if (_state==State.START)
+ {
+ _version=null;
+ _method=null;
+ _methodString=null;
+ _endOfContent=EndOfContent.UNKNOWN_CONTENT;
+ _header=null;
+ if (quickStart(buffer))
+ return true;
+ }
+
+ // Request/response line
+ if (_state.ordinal()>= State.START.ordinal() && _state.ordinal()<State.HEADER.ordinal())
+ {
+ if (parseLine(buffer))
+ return true;
+ }
+
+ // parse headers
+ if (_state.ordinal()>= State.HEADER.ordinal() && _state.ordinal()<State.CONTENT.ordinal())
+ {
+ if (parseHeaders(buffer))
+ return true;
+ }
+
+ // parse content
+ if (_state.ordinal()>= State.CONTENT.ordinal() && _state.ordinal()<State.END.ordinal())
+ {
+ // Handle HEAD response
+ if (_responseStatus>0 && _headResponse)
+ {
+ setState(State.END);
+ if (_handler.messageComplete())
+ return true;
+ }
+ else
+ {
+ if (parseContent(buffer))
+ return true;
+ }
+ }
+
+ // handle end states
+ if (_state==State.END)
+ {
+ // eat white space
+ while (buffer.remaining()>0 && buffer.get(buffer.position())<=HttpTokens.SPACE)
+ buffer.get();
+ }
+ else if (_state==State.CLOSED)
+ {
+ if (BufferUtil.hasContent(buffer))
+ {
+ // Just ignore data when closed
+ _headerBytes+=buffer.remaining();
+ BufferUtil.clear(buffer);
+ if (_headerBytes>_maxHeaderBytes)
+ {
+ // Don't want to waste time reading data of a closed request
+ throw new IllegalStateException("too much data after closed");
+ }
+ }
+ }
+
+ // Handle EOF
+ if (_eof && !buffer.hasRemaining())
+ {
+ switch(_state)
+ {
+ case CLOSED:
+ break;
+
+ case START:
+ setState(State.CLOSED);
+ _handler.earlyEOF();
+ break;
+
+ case END:
+ setState(State.CLOSED);
+ break;
+
+ case EOF_CONTENT:
+ setState(State.CLOSED);
+ return _handler.messageComplete();
+
+ case CONTENT:
+ case CHUNKED_CONTENT:
+ case CHUNK_SIZE:
+ case CHUNK_PARAMS:
+ case CHUNK:
+ setState(State.CLOSED);
+ _handler.earlyEOF();
+ break;
+
+ default:
+ if (DEBUG)
+ LOG.debug("{} EOF in {}",this,_state);
+ setState(State.CLOSED);
+ _handler.badMessage(400,null);
+ break;
+ }
+ }
+
+ return false;
+ }
+ catch(BadMessage e)
+ {
+ BufferUtil.clear(buffer);
+
+ LOG.warn("badMessage: "+e._code+(e._message!=null?" "+e._message:"")+" for "+_handler);
+ if (DEBUG)
+ LOG.debug(e);
+ setState(State.CLOSED);
+ _handler.badMessage(e._code, e._message);
+ return false;
+ }
+ catch(Exception e)
+ {
+ BufferUtil.clear(buffer);
+
+ LOG.warn("badMessage: "+e.toString()+" for "+_handler);
+ if (DEBUG)
+ LOG.debug(e);
+
+ if (_state.ordinal()<=State.END.ordinal())
+ {
+ setState(State.CLOSED);
+ _handler.badMessage(400,null);
+ }
+ else
+ {
+ _handler.earlyEOF();
+ setState(State.CLOSED);
+ }
+
+ return false;
+ }
+ }
+
+ protected boolean parseContent(ByteBuffer buffer)
+ {
+ int remaining=buffer.remaining();
+ if (remaining==0 && _state==State.CONTENT)
+ {
+ long content=_contentLength - _contentPosition;
+ if (content == 0)
+ {
+ setState(State.END);
+ if (_handler.messageComplete())
+ return true;
+ }
+ }
+
+ // Handle _content
+ byte ch;
+ while (_state.ordinal() < State.END.ordinal() && remaining>0)
+ {
+ switch (_state)
+ {
+ case EOF_CONTENT:
+ _contentChunk=buffer.asReadOnlyBuffer();
+ _contentPosition += remaining;
+ buffer.position(buffer.position()+remaining);
+ if (_handler.content(_contentChunk))
+ return true;
+ break;
+
+ case CONTENT:
+ {
+ long content=_contentLength - _contentPosition;
+ if (content == 0)
+ {
+ setState(State.END);
+ if (_handler.messageComplete())
+ return true;
+ }
+ else
+ {
+ _contentChunk=buffer.asReadOnlyBuffer();
+
+ // limit content by expected size
+ if (remaining > content)
+ {
+ // We can cast remaining to an int as we know that it is smaller than
+ // or equal to length which is already an int.
+ _contentChunk.limit(_contentChunk.position()+(int)content);
+ }
+
+ _contentPosition += _contentChunk.remaining();
+ buffer.position(buffer.position()+_contentChunk.remaining());
+
+ if (_handler.content(_contentChunk))
+ return true;
+
+ if(_contentPosition == _contentLength)
+ {
+ setState(State.END);
+ if (_handler.messageComplete())
+ return true;
+ }
+ }
+ break;
+ }
+
+ case CHUNKED_CONTENT:
+ {
+ ch=next(buffer);
+ if (ch>HttpTokens.SPACE)
+ {
+ _chunkLength=TypeUtil.convertHexDigit(ch);
+ _chunkPosition=0;
+ setState(State.CHUNK_SIZE);
+ }
+
+ break;
+ }
+
+ case CHUNK_SIZE:
+ {
+ ch=next(buffer);
+ if (ch==0)
+ break;
+ if (ch == HttpTokens.LINE_FEED)
+ {
+ if (_chunkLength == 0)
+ {
+ setState(State.END);
+ if (_handler.messageComplete())
+ return true;
+ }
+ else
+ setState(State.CHUNK);
+ }
+ else if (ch <= HttpTokens.SPACE || ch == HttpTokens.SEMI_COLON)
+ setState(State.CHUNK_PARAMS);
+ else
+ _chunkLength=_chunkLength * 16 + TypeUtil.convertHexDigit(ch);
+ break;
+ }
+
+ case CHUNK_PARAMS:
+ {
+ ch=next(buffer);
+ if (ch == HttpTokens.LINE_FEED)
+ {
+ if (_chunkLength == 0)
+ {
+ setState(State.END);
+ if (_handler.messageComplete())
+ return true;
+ }
+ else
+ setState(State.CHUNK);
+ }
+ break;
+ }
+
+ case CHUNK:
+ {
+ int chunk=_chunkLength - _chunkPosition;
+ if (chunk == 0)
+ {
+ setState(State.CHUNKED_CONTENT);
+ }
+ else
+ {
+ _contentChunk=buffer.asReadOnlyBuffer();
+
+ if (remaining > chunk)
+ _contentChunk.limit(_contentChunk.position()+chunk);
+ chunk=_contentChunk.remaining();
+
+ _contentPosition += chunk;
+ _chunkPosition += chunk;
+ buffer.position(buffer.position()+chunk);
+ if (_handler.content(_contentChunk))
+ return true;
+ }
+ break;
+ }
+
+ case CLOSED:
+ {
+ BufferUtil.clear(buffer);
+ return false;
+ }
+
+ default:
+ break;
+
+ }
+
+ remaining=buffer.remaining();
+ }
+ return false;
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ public boolean isAtEOF()
+
+ {
+ return _eof;
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ public void atEOF()
+
+ {
+ if (DEBUG)
+ LOG.debug("atEOF {}", this);
+ _eof=true;
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ public void close()
+ {
+ if (DEBUG)
+ LOG.debug("close {}", this);
+ setState(State.CLOSED);
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ public void reset()
+ {
+ if (DEBUG)
+ LOG.debug("reset {}", this);
+ // reset state
+ if (_state==State.CLOSED)
+ return;
+ if (_closed)
+ {
+ setState(State.CLOSED);
+ return;
+ }
+
+ setState(State.START);
+ _endOfContent=EndOfContent.UNKNOWN_CONTENT;
+ _contentLength=-1;
+ _contentPosition=0;
+ _responseStatus=0;
+ _contentChunk=null;
+ _headerBytes=0;
+ _host=false;
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ protected void setState(State state)
+ {
+ if (DEBUG)
+ LOG.debug("{} --> {}",_state,state);
+ _state=state;
+ }
+
+ /* ------------------------------------------------------------------------------- */
+ @Override
+ public String toString()
+ {
+ return String.format("%s{s=%s,%d of %d}",
+ getClass().getSimpleName(),
+ _state,
+ _contentPosition,
+ _contentLength);
+ }
+
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ /* ------------------------------------------------------------ */
+ /* Event Handler interface
+ * These methods return true if the caller should process the events
+ * so far received (eg return from parseNext and call HttpChannel.handle).
+ * If multiple callbacks are called in sequence (eg
+ * headerComplete then messageComplete) from the same point in the parsing
+ * then it is sufficient for the caller to process the events only once.
+ */
+ public interface HttpHandler<T>
+ {
+ public boolean content(T item);
+
+ public boolean headerComplete();
+
+ public boolean messageComplete();
+
+ /**
+ * This is the method called by parser when a HTTP Header name and value is found
+ * @param field The field parsed
+ * @return True if the parser should return to its caller
+ */
+ public boolean parsedHeader(HttpField field);
+
+ /* ------------------------------------------------------------ */
+ /** Called to signal that an EOF was received unexpectedly
+ * during the parsing of a HTTP message
+ */
+ public void earlyEOF();
+
+ /* ------------------------------------------------------------ */
+ /** Called to signal that a bad HTTP message has been received.
+ * @param status The bad status to send
+ * @param reason The textual reason for badness
+ */
+ public void badMessage(int status, String reason);
+
+ /* ------------------------------------------------------------ */
+ /** @return the size in bytes of the per parser header cache
+ */
+ public int getHeaderCacheSize();
+ }
+
+ public interface ProxyHandler
+ {
+ void proxied(String protocol, String sAddr, String dAddr, int sPort, int dPort);
+ }
+
+ public interface RequestHandler<T> extends HttpHandler<T>
+ {
+ /**
+ * This is the method called by parser when the HTTP request line is parsed
+ * @param method The method as enum if of a known type
+ * @param methodString The method as a string
+ * @param uri The raw bytes of the URI. These are copied into a ByteBuffer that will not be changed until this parser is reset and reused.
+ * @param version
+ * @return true if handling parsing should return.
+ */
+ public abstract boolean startRequest(HttpMethod method, String methodString, ByteBuffer uri, HttpVersion version);
+
+ /**
+ * This is the method called by the parser after it has parsed the host header (and checked it's format). This is
+ * called after the {@link HttpHandler#parsedHeader(HttpField)} methods and before
+ * HttpHandler#headerComplete();
+ */
+ public abstract boolean parsedHostHeader(String host,int port);
+ }
+
+ public interface ResponseHandler<T> extends HttpHandler<T>
+ {
+ /**
+ * This is the method called by parser when the HTTP request line is parsed
+ */
+ public abstract boolean startResponse(HttpVersion version, int status, String reason);
+ }
+
+ public Trie<HttpField> getFieldCache()
+ {
+ return _connectionFields;
+ }
+
+ private String getProxyField(ByteBuffer buffer)
+ {
+ _string.setLength(0);
+ _length=0;
+
+ while (buffer.hasRemaining())
+ {
+ // process each character
+ byte ch=next(buffer);
+ if (ch<=' ')
+ return _string.toString();
+ _string.append((char)ch);
+ }
+ throw new BadMessage();
+ }
+}