view src/org/eclipse/jetty/http/HttpParser.java @ 1052:4a2489f1d5fe

remove JBuffer.peek()
author Franklin Schmidt <fschmidt@gmail.com>
date Tue, 08 Nov 2016 00:27:16 -0700
parents 1ab2dd0a7db5
children 7e4b41247544
line wrap: on
line source

//
//  ========================================================================
//  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.io.IOException;

import org.eclipse.jetty.io.JBuffer;
import org.eclipse.jetty.io.BufferUtil;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.io.EofException;
import org.eclipse.jetty.util.StringUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public final class HttpParser
{
	private static final Logger LOG = LoggerFactory.getLogger(HttpParser.class);

	// States
	private static final int STATE_START = -14;
	private static final int STATE_FIELD0 = -13;
	private static final int STATE_SPACE1 = -12;
	private static final int STATE_STATUS = -11;
	private static final int STATE_URI = -10;
	private static final int STATE_SPACE2 = -9;
	private static final int STATE_FIELD2 = -6;
	private static final int STATE_HEADER = -5;
	private static final int STATE_HEADER_NAME = -4;
	private static final int STATE_HEADER_IN_NAME = -3;
	private static final int STATE_HEADER_VALUE = -2;
	private static final int STATE_HEADER_IN_VALUE = -1;
	private static final int STATE_END = 0;
	private static final int STATE_EOF_CONTENT = 1;
	private static final int STATE_CONTENT = 2;
	private static final int STATE_CHUNKED_CONTENT = 3;
	private static final int STATE_CHUNK_SIZE = 4;
	private static final int STATE_CHUNK_PARAMS = 5;
	private static final int STATE_CHUNK = 6;
	private static final int STATE_SEEKING_EOF = 7;

	private final EventHandler _handler;
	private final EndPoint _endp;
	public final JBuffer _header; // JBuffer for header data (and small _content)
	private final JBuffer _body; // JBuffer for large content
	private JBuffer _buffer; // The current buffer in use (either _header or _content)
	private int _mark = -1;
	private String _cached;
	private String _tok0 = ""; // Saved token: header name, request method or response version
	private String _tok1 = ""; // Saved token: header value, request URI or response code
	private String _multiLineValue;
	private int _responseStatus; // If >0 then we are parsing a response
	private boolean _persistent;

	private JBuffer _contentView = BufferUtil.EMPTY_BUFFER; // View of the content in the buffer for {@link Input}
	private int _state = STATE_START;
	private byte _eol;
	private int _length;
	private long _contentLength;
	private long _contentPosition;
	private int _chunkLength;
	private int _chunkPosition;
	private boolean _headResponse;

	public HttpParser(JBuffer headerBuffer,JBuffer bodyBuffer, EndPoint endp, EventHandler handler)
	{
		_header = headerBuffer;
		_body = bodyBuffer;
		_endp = endp;
		_handler = handler;
	}

	private void mark() {
		_mark = _buffer.getIndex() - 1;
	}

	private JBuffer sliceFromMark() {
		JBuffer buf = _buffer.duplicate();
		buf.position(_mark);
		buf.limit(_buffer.position()-1);
		_mark = -1;
		return buf;
	}

	private void clear() {
		_buffer.clear();
		_mark = -1;
	}

	private void compact() {
		if( _mark == -1 ) {
			_buffer.compact();
		} else if( _mark > 0 ) {
			int old = _buffer.getIndex();
			_buffer.setGetIndex(_mark);
			_buffer.compact();
			_buffer.setGetIndex( old - _mark );
			_mark = 0;
		}
	}

	public JBuffer getBuffer(int length) {
		JBuffer dup = _buffer.duplicate();
		int end = _buffer.position() + length;
		dup.limit(end);
		_buffer.position(end);
		return dup;
	}

	public byte peek() {
		return _buffer.get(_buffer.position());
	}


	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;
	}

	public boolean isChunking()
	{
		return _contentLength==HttpTokens.CHUNKED_CONTENT;
	}

	public boolean isIdle()
	{
		return _state==STATE_START;
	}

	public boolean isComplete()
	{
		return _state==STATE_END;
	}

	public boolean isPersistent()
	{
		return _persistent;
	}

	public void setPersistent(boolean persistent)
	{
		_persistent = persistent;
		if (!_persistent &&(_state==STATE_END || _state==STATE_START))
			_state = STATE_SEEKING_EOF;
	}

	/* ------------------------------------------------------------------------------- */
	/**
	 * Parse until END state.
	 * This method will parse any remaining content in the current buffer as long as there is
	 * no unconsumed content. It does not care about the {@link #getState current state} of the parser.
	 * @see #parse
	 * @see #parseNext
	 */
	public boolean parseAvailable() throws IOException
	{
		boolean progress = parseNext() > 0;

		// continue parsing
		while (!isComplete() && _buffer!=null && _buffer.remaining()>0 && !_contentView.hasRemaining())
		{
			progress |= parseNext()>0;
		}
		return progress;
	}


	/* ------------------------------------------------------------------------------- */
	/**
	 * Parse until next Event.
	 * @return an indication of progress <0 EOF, 0 no progress, >0 progress.
	 */
	private int parseNext() throws IOException
	{
		try
		{
			int progress = 0;

			if (_state == STATE_END) {
				return 0;
			}

			if (_buffer==null)
				_buffer = _header;


			if (_state == STATE_CONTENT && _contentPosition == _contentLength)
			{
				_state = STATE_END;
				_handler.messageComplete(_contentPosition);
				return 1;
			}

			int length = _buffer.remaining();

			// Fill buffer if we can
			if (length == 0)
			{
				int filled = -1;
				IOException ex = null;
				try
				{
					filled = fill();
					LOG.debug("filled {}/{}",filled,_buffer.remaining());
				}
				catch(IOException e)
				{
					LOG.debug(this.toString(),e);
					ex=e;
				}

				if (filled > 0 )
					progress++;
				else if (filled < 0 )
				{
					_persistent = false;

					// do we have content to deliver?
					if (_state>STATE_END)
					{
						if (_buffer.remaining()>0 && !_headResponse)
						{
							JBuffer chunk = getBuffer(_buffer.remaining());
							_contentPosition += chunk.remaining();
							_contentView = chunk;
							_handler.content(); // May recurse here
						}
					}

					// was this unexpected?
					switch(_state)
					{
						case STATE_END:
						case STATE_SEEKING_EOF:
							_state = STATE_END;
							break;

						case STATE_EOF_CONTENT:
							_state = STATE_END;
							_handler.messageComplete(_contentPosition);
							break;

						default:
							_state = STATE_END;
							if (!_headResponse)
								_handler.earlyEOF();
							_handler.messageComplete(_contentPosition);
					}

					if (ex!=null)
						throw ex;

					if (!isComplete() && !isIdle())
						throw new EofException();

					return -1;
				}
				length = _buffer.remaining();
			}


			// Handle header states
			byte ch;
			byte[] array = _buffer.array();
			int last = _state;
			while (_state<STATE_END && length-->0)
			{
				if (last!=_state)
				{
					progress++;
					last = _state;
				}

				ch = _buffer.get();

				if (_eol == HttpTokens.CARRIAGE_RETURN)
				{
					if (ch == HttpTokens.LINE_FEED)
					{
						_eol=HttpTokens.LINE_FEED;
						continue;
					}
					throw new HttpException(HttpStatus.BAD_REQUEST_400);
				}
				_eol=0;

				switch (_state)
				{
					case STATE_START:
						_contentLength = HttpTokens.UNKNOWN_CONTENT;
						_cached = null;
						if (ch > HttpTokens.SPACE || ch<0)
						{
							mark();
							_state = STATE_FIELD0;
						}
						break;

					case STATE_FIELD0:
						if (ch == HttpTokens.SPACE)
						{
							_tok0 = _buffer.toString(_mark, _buffer.getIndex() - 1 - _mark);
							_responseStatus = !HttpVersions.CACHE.contains(_tok0)?-1:0;
							_state=STATE_SPACE1;
							continue;
						}
						else if (ch < HttpTokens.SPACE && ch>=0)
						{
							throw new HttpException(HttpStatus.BAD_REQUEST_400);
						}
						break;

					case STATE_SPACE1:
						if (ch > HttpTokens.SPACE || ch<0)
						{
							mark();
							if (_responseStatus>=0)
							{
								_state = STATE_STATUS;
								_responseStatus=ch-'0';
							}
							else
								_state=STATE_URI;
						}
						else if (ch < HttpTokens.SPACE)
						{
							throw new HttpException(HttpStatus.BAD_REQUEST_400);
						}
						break;

					case STATE_STATUS:
						if (ch == HttpTokens.SPACE)
						{
//							_tok1.update(_mark, _buffer.getIndex() - 1);
							_tok1 = _buffer.toString(_mark, _buffer.getIndex() - 1 - _mark);
							_state = STATE_SPACE2;
							continue;
						}
						else if (ch>='0' && ch<='9')
						{
							_responseStatus=_responseStatus*10+(ch-'0');
							continue;
						}
						else if (ch < HttpTokens.SPACE && ch>=0)
						{
							_eol=ch;
							_state = STATE_HEADER;
							_tok0 = "";
							_tok1 = "";
							_multiLineValue=null;
							continue;
						}
						// not a digit, so must be a URI
						_state=STATE_URI;
						_responseStatus=-1;
						break;

					case STATE_URI:
						if (ch == HttpTokens.SPACE)
						{
//							_tok1.update(_mark, _buffer.getIndex() - 1);
							_tok1 = _buffer.toString(_mark, _buffer.getIndex() - 1 - _mark);
							_state=STATE_SPACE2;
							continue;
						}
						else if (ch < HttpTokens.SPACE && ch>=0)
						{
							// HTTP/0.9
							_handler.startRequest(_tok0, sliceFromMark().toString(), null);
							_persistent = false;
							_state = STATE_SEEKING_EOF;
							_handler.headerComplete();
							_handler.messageComplete(_contentPosition);
							return 1;
						}
						break;

					case STATE_SPACE2:
						if (ch > HttpTokens.SPACE || ch<0)
						{
							mark();
							_state=STATE_FIELD2;
						}
						else if (ch < HttpTokens.SPACE)
						{
							if (_responseStatus>0)
							{
								_eol=ch;
								_state=STATE_HEADER;
								_tok0 = "";
								_tok1 = "";
								_multiLineValue=null;
							}
							else
							{
								// HTTP/0.9
								_handler.startRequest(_tok0, _tok1, null);
								_persistent = false;
								_state = STATE_SEEKING_EOF;
								_handler.headerComplete();
								_handler.messageComplete(_contentPosition);
								return 1;
							}
						}
						break;

					case STATE_FIELD2:
						if (ch == HttpTokens.CARRIAGE_RETURN || ch == HttpTokens.LINE_FEED)
						{
							String version;
							if (_responseStatus > 0)
//								_handler.startResponse(version=HttpVersions.CACHE.lookup(_tok0), _responseStatus,sliceFromMark());
								version = _tok0;
							else
								_handler.startRequest(_tok0, _tok1, version=sliceFromMark().toString());
							_eol=ch;
							_persistent = HttpVersions.CACHE.getOrdinal(version) >= HttpVersions.HTTP_1_1_ORDINAL;
							_state=STATE_HEADER;
							_tok0 = "";
							_tok1 = "";
							_multiLineValue=null;
							continue;
						}
						break;

					case STATE_HEADER:
						switch(ch)
						{
							case HttpTokens.COLON:
							case HttpTokens.SPACE:
							case HttpTokens.TAB:
							{
								// header value without name - continuation?
								_length=-1;
								_state=STATE_HEADER_VALUE;
								break;
							}

							default:
							{
								// handler last header if any
								if (_cached!=null || _tok0.length() > 0 || _tok1.length() > 0 || _multiLineValue != null)
								{
									String header = _cached!=null ? _cached : _tok0;
									_cached = null;
									String value = _multiLineValue == null ? _tok1 : _multiLineValue;

									int ho = HttpHeaders.CACHE.getOrdinal(header);
									if (ho >= 0)
									{
										int vo;

										switch (ho)
										{
											case HttpHeaders.CONTENT_LENGTH_ORDINAL:
												if (_contentLength != HttpTokens.CHUNKED_CONTENT )
												{
													try
													{
														_contentLength = BufferUtil.toLong(value);
													}
													catch(NumberFormatException e)
													{
														LOG.trace("",e);
														throw new HttpException(HttpStatus.BAD_REQUEST_400);
													}
													if (_contentLength <= 0)
														_contentLength=HttpTokens.NO_CONTENT;
												}
												break;

											case HttpHeaders.TRANSFER_ENCODING_ORDINAL:
//												value=HttpHeaderValues.CACHE.lookup(value);
												vo = HttpHeaderValues.CACHE.getOrdinal(value);
												if (HttpHeaderValues.CHUNKED_ORDINAL == vo)
													_contentLength=HttpTokens.CHUNKED_CONTENT;
												else
												{
													if (value.endsWith(HttpHeaderValues.CHUNKED))
														_contentLength=HttpTokens.CHUNKED_CONTENT;

													else if (value.indexOf(HttpHeaderValues.CHUNKED) >= 0)
														throw new HttpException(400,null);
												}
												break;

											case HttpHeaders.CONNECTION_ORDINAL:
												switch(HttpHeaderValues.CACHE.getOrdinal(value))
												{
													case HttpHeaderValues.CLOSE_ORDINAL:
														_persistent = false;
														break;

													case HttpHeaderValues.KEEP_ALIVE_ORDINAL:
														_persistent = true;
														break;

													case -1: // No match, may be multi valued
													{
														for (String v : value.split(","))
														{
															switch(HttpHeaderValues.CACHE.getOrdinal(v.trim()))
															{
																case HttpHeaderValues.CLOSE_ORDINAL:
																	_persistent = false;
																	break;

																case HttpHeaderValues.KEEP_ALIVE_ORDINAL:
																	_persistent = true;
																	break;
															}
														}
														break;
													}
												}
										}
									}

									_handler.parsedHeader(header, value);
									_tok0 = "";
									_tok1 = "";
									_multiLineValue=null;
								}
								_mark = -1;

								// now handle ch
								if (ch == HttpTokens.CARRIAGE_RETURN || ch == HttpTokens.LINE_FEED)
								{
									// is it a response that cannot have a body?
									if (_responseStatus > 0  && // response  
									   (_responseStatus == 304  || // not-modified response
										_responseStatus == 204 || // no-content response
										_responseStatus < 200)) // 1xx response
										_contentLength=HttpTokens.NO_CONTENT; // ignore any other headers set
									// else if we don't know framing
									else if (_contentLength == HttpTokens.UNKNOWN_CONTENT)
									{
										if (_responseStatus == 0  // request
												|| _responseStatus == 304 // not-modified response
												|| _responseStatus == 204 // no-content response
												|| _responseStatus < 200) // 1xx response
											_contentLength=HttpTokens.NO_CONTENT;
										else
											_contentLength=HttpTokens.EOF_CONTENT;
									}

									_contentPosition=0;
									_eol=ch;
									if (_eol==HttpTokens.CARRIAGE_RETURN && _buffer.hasRemaining() && peek()==HttpTokens.LINE_FEED)
										_eol=_buffer.get();

									// We convert _contentLength to an int for this switch statement because
									// we don't care about the amount of data available just whether there is some.
									switch (_contentLength > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) _contentLength)
									{
										case HttpTokens.EOF_CONTENT:
											_state=STATE_EOF_CONTENT;
											_handler.headerComplete(); // May recurse here !
											break;

										case HttpTokens.CHUNKED_CONTENT:
											_state=STATE_CHUNKED_CONTENT;
											_handler.headerComplete(); // May recurse here !
											break;

										case HttpTokens.NO_CONTENT:
											_handler.headerComplete();
											_state = _persistent||(_responseStatus>=100&&_responseStatus<200)?STATE_END:STATE_SEEKING_EOF;
											_handler.messageComplete(_contentPosition);
											return 1;

										default:
											_state=STATE_CONTENT;
											_handler.headerComplete(); // May recurse here !
											break;
									}
									return 1;
								}
								else
								{
									// New header
									_length = 1;
									mark();
									_state = STATE_HEADER_NAME;

									// try cached name!
									if (array!=null)
									{
										String s = new String(array, _mark, length+1);
										_cached = HttpHeaders.CACHE.getBest(s);

										if (_cached!=null)
										{
											_length = _cached.length();
											_buffer.setGetIndex(_mark+_length);
											length = _buffer.remaining();
										}
									}
								}
							}
						}

						break;

					case STATE_HEADER_NAME:
						switch(ch)
						{
							case HttpTokens.CARRIAGE_RETURN:
							case HttpTokens.LINE_FEED:
								if (_length > 0) {
									_tok0 = _buffer.toString(_mark, _length);
								}
								_eol=ch;
								_state=STATE_HEADER;
								break;
							case HttpTokens.COLON:
								if (_length > 0 && _cached==null) {
									_tok0 = _buffer.toString(_mark, _length);
								}
								_length=-1;
								_state=STATE_HEADER_VALUE;
								break;
							case HttpTokens.SPACE:
							case HttpTokens.TAB:
								break;
							default:
							{
								_cached = null;
								if (_length == -1)
									mark();
								_length=_buffer.getIndex() - _mark;
								_state = STATE_HEADER_IN_NAME;
							}
						}

						break;

					case STATE_HEADER_IN_NAME:
						switch(ch)
						{
							case HttpTokens.CARRIAGE_RETURN:
							case HttpTokens.LINE_FEED:
								if (_length > 0) {
									_tok0 = _buffer.toString(_mark,_length);
								}
								_eol=ch;
								_state=STATE_HEADER;
								break;
							case HttpTokens.COLON:
								if (_length > 0 && _cached==null) {
									_tok0 = _buffer.toString(_mark,_length);
								}
								_length=-1;
								_state=STATE_HEADER_VALUE;
								break;
							case HttpTokens.SPACE:
							case HttpTokens.TAB:
								_state=STATE_HEADER_NAME;
								break;
							default:
							{
								_cached = null;
								_length++;
							}
						}
						break;

					case STATE_HEADER_VALUE:
						switch(ch)
						{
							case HttpTokens.CARRIAGE_RETURN:
							case HttpTokens.LINE_FEED:
								if (_length > 0)
								{
									if (_tok1.length() == 0)
//										_tok1.update(_mark, _mark + _length);
										_tok1 = _buffer.toString(_mark, _length);
									else
									{
										// Continuation line!
										if (_multiLineValue == null) _multiLineValue = _tok1;
//										_tok1.update(_mark, _mark + _length);
										_tok1 = _buffer.toString(_mark, _length);
										_multiLineValue += " " + _tok1;
									}
								}
								_eol=ch;
								_state=STATE_HEADER;
								break;
							case HttpTokens.SPACE:
							case HttpTokens.TAB:
								break;
							default:
							{
								if (_length == -1)
									mark();
								_length=_buffer.getIndex() - _mark;
								_state = STATE_HEADER_IN_VALUE;
							}
						}
						break;

					case STATE_HEADER_IN_VALUE:
						switch(ch)
						{
							case HttpTokens.CARRIAGE_RETURN:
							case HttpTokens.LINE_FEED:
								if (_length > 0)
								{
									if (_tok1.length() == 0)
//										_tok1.update(_mark, _mark + _length);
										_tok1 = _buffer.toString(_mark, _length);
									else
									{
										// Continuation line!
										if (_multiLineValue == null) _multiLineValue = _tok1;
//										_tok1.update(_mark, _mark + _length);
										_tok1 = _buffer.toString(_mark, _length);
										_multiLineValue += " " + _tok1;
									}
								}
								_eol=ch;
								_state=STATE_HEADER;
								break;
							case HttpTokens.SPACE:
							case HttpTokens.TAB:
								_state=STATE_HEADER_VALUE;
								break;
							default:
								_length++;
						}
						break;
				}
			} // end of HEADER states loop

			// ==========================

			// Handle HEAD response
			if (_responseStatus>0 && _headResponse)
			{
				_state = _persistent||(_responseStatus>=100&&_responseStatus<200)?STATE_END:STATE_SEEKING_EOF;
				_handler.messageComplete(_contentLength);
			}


			// ==========================

			// Handle _content
			length=_buffer.remaining();
			last=_state;
			while (_state > STATE_END && length > 0)
			{
				if (last!=_state)
				{
					progress++;
					last=_state;
				}

				if (_eol == HttpTokens.CARRIAGE_RETURN && peek() == HttpTokens.LINE_FEED)
				{
					_eol=_buffer.get();
					length=_buffer.remaining();
					continue;
				}
				_eol=0;
				switch (_state)
				{
					case STATE_EOF_CONTENT:
					{
						JBuffer chunk = getBuffer(_buffer.remaining());
						_contentPosition += chunk.remaining();
						_contentView = chunk;
						_handler.content(); // May recurse here
						// TODO adjust the _buffer to keep unconsumed content
						return 1;
					}

					case STATE_CONTENT:
					{
						long remaining = _contentLength - _contentPosition;
						if (remaining == 0)
						{
							_state = _persistent?STATE_END:STATE_SEEKING_EOF;
							_handler.messageComplete(_contentPosition);
							return 1;
						}

						if (length > remaining)
						{
							// We can cast reamining to an int as we know that it is smaller than
							// or equal to length which is already an int.
							length=(int)remaining;
						}

						JBuffer chunk = getBuffer(length);
						_contentPosition += chunk.remaining();
						_contentView = chunk;
						_handler.content(); // May recurse here

						if(_contentPosition == _contentLength)
						{
							_state = _persistent?STATE_END:STATE_SEEKING_EOF;
							_handler.messageComplete(_contentPosition);
						}
						// TODO adjust the _buffer to keep unconsumed content
						return 1;
					}

					case STATE_CHUNKED_CONTENT:
					{
						ch=peek();
						if (ch == HttpTokens.CARRIAGE_RETURN || ch == HttpTokens.LINE_FEED)
							_eol=_buffer.get();
						else if (ch <= HttpTokens.SPACE)
							_buffer.get();
						else
						{
							_chunkLength=0;
							_chunkPosition=0;
							_state = STATE_CHUNK_SIZE;
						}
						break;
					}

					case STATE_CHUNK_SIZE:
					{
						ch=_buffer.get();
						if (ch == HttpTokens.CARRIAGE_RETURN || ch == HttpTokens.LINE_FEED)
						{
							_eol=ch;

							if (_chunkLength == 0)
							{
								if (_eol==HttpTokens.CARRIAGE_RETURN && _buffer.hasRemaining() && peek()==HttpTokens.LINE_FEED)
									_eol=_buffer.get();
								_state = _persistent?STATE_END:STATE_SEEKING_EOF;
								_handler.messageComplete(_contentPosition);
								return 1;
							}
							else
								_state = STATE_CHUNK;
						}
						else if (ch <= HttpTokens.SPACE || ch == HttpTokens.SEMI_COLON)
							_state=STATE_CHUNK_PARAMS;
						else if (ch >= '0' && ch <= '9')
							_chunkLength=_chunkLength * 16 + (ch - '0');
						else if (ch >= 'a' && ch <= 'f')
							_chunkLength=_chunkLength * 16 + (10 + ch - 'a');
						else if (ch >= 'A' && ch <= 'F')
							_chunkLength=_chunkLength * 16 + (10 + ch - 'A');
						else
							throw new IOException("bad chunk char: " + ch);
						break;
					}

					case STATE_CHUNK_PARAMS:
					{
						ch=_buffer.get();
						if (ch == HttpTokens.CARRIAGE_RETURN || ch == HttpTokens.LINE_FEED)
						{
							_eol=ch;
							if (_chunkLength == 0)
							{
								if (_eol==HttpTokens.CARRIAGE_RETURN && _buffer.hasRemaining() && peek()==HttpTokens.LINE_FEED)
									_eol=_buffer.get();
								_state = _persistent?STATE_END:STATE_SEEKING_EOF;
								_handler.messageComplete(_contentPosition);
								return 1;
							}
							else
								_state=STATE_CHUNK;
						}
						break;
					}

					case STATE_CHUNK:
					{
						int remaining=_chunkLength - _chunkPosition;
						if (remaining == 0)
						{
							_state=STATE_CHUNKED_CONTENT;
							break;
						}
						else if (length > remaining)
							length=remaining;
						JBuffer chunk = getBuffer(length);
						_contentPosition += chunk.remaining();
						_chunkPosition += chunk.remaining();
						_contentView = chunk;
						_handler.content(); // May recurse here
						// TODO adjust the _buffer to keep unconsumed content
						return 1;
					}

					case STATE_SEEKING_EOF:
					{                        
						// Close if there is more data than CRLF
						if (_buffer.remaining()>2)
						{
							_state = STATE_END;
							_endp.close();
						}
						else  
						{
							// or if the data is not white space
							while (_buffer.remaining()>0)
								if (!Character.isWhitespace(_buffer.get()))
								{
									_state = STATE_END;
									_endp.close();
									clear();
								}
						}
						
						clear();
						break;
					}
				}

				length = _buffer.remaining();
			}

			return progress;
		}
		catch(HttpException e)
		{
			_persistent = false;
			_state = STATE_SEEKING_EOF;
			throw e;
		}
	}

	/* ------------------------------------------------------------------------------- */
	/** fill the buffers from the endpoint
	 *
	 */
	private int fill() throws IOException
	{
		// Do we have a buffer?
		if (_buffer==null)
			_buffer = _header;

		// Is there unconsumed content in body buffer
		if (_state>STATE_END && _buffer==_header && !_header.hasRemaining() && _body.hasRemaining())
		{
			_buffer = _body;
			return _buffer.remaining();
		}

		// Shall we switch to a body buffer?
		if (_buffer==_header && _state>STATE_END && _header.remaining()==0 && ((_contentLength-_contentPosition)>_header.capacity()))
		{
			_buffer = _body;
		}

		// Shall we compact the body?
		if (_buffer==_body || _state>STATE_END)
		{
			compact();
		}

		// Are we full?
		if (_buffer.space() == 0)
		{
			LOG.warn("HttpParser Full for {} ",_endp);
			clear();
			throw new HttpException(HttpStatus.REQUEST_ENTITY_TOO_LARGE_413, "Request Entity Too Large: "+(_buffer==_body?"body":"head"));
		}
/* why?
		try
		{
			int filled = _endp.fill(_buffer);
			return filled;
		}
		catch(IOException e)
		{
			LOG.debug("",e);
			throw (e instanceof EofException) ? e:new EofException(e);
		}
*/
		return _endp.fill(_buffer);
	}

	@Override
	public String toString()
	{
		return String.format("%s{s=%d,l=%d,c=%d}",
				getClass().getSimpleName(),
				_state,
				_length,
				_contentLength);
	}

	public JBuffer blockForContent(long maxIdleTime) throws IOException
	{
		if (_contentView.remaining()>0)
			return _contentView;

		if (_state <= STATE_END || _state==STATE_SEEKING_EOF)
			return null;

		try
		{
			parseNext();

			// parse until some progress is made (or IOException thrown for timeout)
			while(_contentView.remaining() == 0 && !(_state==STATE_END||_state==STATE_SEEKING_EOF) && _endp.isOpen())
			{
				if (!_endp.isBlocking())
				{
					if (parseNext()>0)
						continue;

					if (!_endp.blockReadable(maxIdleTime))
					{
						_endp.close();
						throw new EofException("timeout");
					}
				}

				parseNext();
			}
		}
		catch(IOException e)
		{
			// TODO is this needed?
			_endp.close();
			throw e;
		}

		return _contentView.remaining()>0 ? _contentView : null;
	}

	/* ------------------------------------------------------------ */
	/* (non-Javadoc)
	 * @see java.io.InputStream#available()
	 */
	public int available() throws IOException
	{
		if (_contentView.remaining()>0)
			return _contentView.remaining();

		if (_endp.isBlocking())
		{
			return 0;
		}

		parseNext();
		return _contentView.remaining();
	}


	public interface EventHandler
	{
		public abstract void content() throws IOException;

		public void headerComplete() throws IOException;

		public void messageComplete(long contentLength) throws IOException;

		/**
		 * This is the method called by parser when a HTTP Header name and value is found
		 */
		public void parsedHeader(String name, String value) throws IOException;

		/**
		 * This is the method called by parser when the HTTP request line is parsed
		 */
		public abstract void startRequest(String method, String url, String version)
				throws IOException;

		public void earlyEOF();
	}




}