//
// ========================================================================
-// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// Copyright (c) 1995-2016 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
package org.eclipse.jetty.http;
+import static org.eclipse.jetty.http.HttpTokens.*;
+
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
/* ------------------------------------------------------------ */
/** A Parser for HTTP 0.9, 1.0 and 1.1
* <p>
- * The is parser parses HTTP client and server messages from buffers
+ * This 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.
CHUNK_SIZE,
CHUNK_PARAMS,
CHUNK,
+ CHUNK_END,
END,
CLOSED
}
}
/* ------------------------------------------------------------------------------- */
- private static class BadMessage extends Error
+ @SuppressWarnings("serial")
+ private static class BadMessageException extends RuntimeException
{
- private static final long serialVersionUID = 1L;
private final int _code;
- private final String _message;
- BadMessage()
+ private BadMessageException()
{
this(400,null);
}
- BadMessage(int code)
+ private BadMessageException(int code)
{
this(code,null);
}
- BadMessage(String message)
+ private BadMessageException(String message)
{
this(400,message);
}
- BadMessage(int code,String message)
+ private BadMessageException(int code,String message)
{
+ super(message);
_code=code;
- _message=message;
}
-
}
/* ------------------------------------------------------------------------------- */
if (_cr)
{
- if (ch!=HttpTokens.LINE_FEED)
- throw new BadMessage("Bad EOL");
+ if (ch!=LINE_FEED)
+ throw new BadMessageException("Bad EOL");
_cr=false;
return ch;
}
- if (ch>=0 && ch<HttpTokens.SPACE)
+ if (ch>=0 && ch<SPACE)
{
- if (ch==HttpTokens.CARRIAGE_RETURN)
+ if (ch==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");
+ if (ch!=LINE_FEED)
+ throw new BadMessageException("Bad EOL");
}
else
{
}
}
// Only LF or TAB acceptable special characters
- else if (!(ch==HttpTokens.LINE_FEED || ch==HttpTokens.TAB))
- throw new BadMessage("Illegal character");
+ else if (!(ch==LINE_FEED || ch==TAB))
+ throw new IllegalCharacterException(_state,ch,buffer);
}
return ch;
{
int ch=next(buffer);
- if (ch > HttpTokens.SPACE)
+ if (ch > SPACE)
{
_string.setLength(0);
_string.append((char)ch);
else if (ch==0)
break;
else if (ch<0)
- throw new BadMessage();
+ throw new BadMessageException();
// 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);
+ throw new BadMessageException(HttpStatus.BAD_REQUEST_400);
}
}
return false;
if (_state==State.URI)
{
LOG.warn("URI is too large >"+_maxHeaderBytes);
- throw new BadMessage(HttpStatus.REQUEST_URI_TOO_LONG_414);
+ throw new BadMessageException(HttpStatus.REQUEST_URI_TOO_LONG_414);
}
else
{
LOG.warn("request is too large >"+_maxHeaderBytes);
else
LOG.warn("response is too large >"+_maxHeaderBytes);
- throw new BadMessage(HttpStatus.REQUEST_ENTITY_TOO_LARGE_413);
+ throw new BadMessageException(HttpStatus.REQUEST_ENTITY_TOO_LARGE_413);
}
}
switch (_state)
{
case METHOD:
- if (ch == HttpTokens.SPACE)
+ if (ch == SPACE)
{
_length=_string.length();
_methodString=takeString();
_methodString=method.asString();
setState(State.SPACE1);
}
- else if (ch < HttpTokens.SPACE)
- throw new BadMessage(ch<0?"Illegal character":"No URI");
+ else if (ch < SPACE)
+ {
+ if (ch==LINE_FEED)
+ throw new BadMessageException("No URI");
+ else
+ throw new IllegalCharacterException(_state,ch,buffer);
+ }
else
_string.append((char)ch);
break;
String version=takeString();
_version=HttpVersion.CACHE.get(version);
if (_version==null)
- throw new BadMessage(HttpStatus.BAD_REQUEST_400,"Unknown Version");
+ throw new BadMessageException(HttpStatus.BAD_REQUEST_400,"Unknown Version");
setState(State.SPACE1);
}
else if (ch < HttpTokens.SPACE)
- throw new BadMessage(ch<0?"Illegal character":"No Status");
+ throw new IllegalCharacterException(_state,ch,buffer);
else
_string.append((char)ch);
break;
if (_maxHeaderBytes>0 && ++_headerBytes>_maxHeaderBytes)
{
LOG.warn("URI is too large >"+_maxHeaderBytes);
- throw new BadMessage(HttpStatus.REQUEST_URI_TOO_LONG_414);
+ throw new BadMessageException(HttpStatus.REQUEST_URI_TOO_LONG_414);
}
if (_uri.remaining()<=len)
{
}
else if (ch < HttpTokens.SPACE)
{
- throw new BadMessage(HttpStatus.BAD_REQUEST_400,_requestHandler!=null?"No URI":"No Status");
+ throw new BadMessageException(HttpStatus.BAD_REQUEST_400,_requestHandler!=null?"No URI":"No Status");
}
break;
}
else
{
- throw new BadMessage();
+ throw new BadMessageException();
}
break;
BufferUtil.clear(buffer);
handle=_handler.headerComplete()||handle;
handle=_handler.messageComplete()||handle;
+ return handle;
}
else
{
if (_method==HttpMethod.PROXY)
{
if (!(_requestHandler instanceof ProxyHandler))
- throw new BadMessage();
+ throw new BadMessageException();
_uri.flip();
String protocol=BufferUtil.toString(_uri);
BufferUtil.clear(buffer);
handle=_handler.headerComplete()||handle;
handle=_handler.messageComplete()||handle;
+ return handle;
}
}
else if (ch<0)
- throw new BadMessage();
+ throw new BadMessageException();
break;
case REQUEST_VERSION:
_version=HttpVersion.CACHE.get(takeString());
}
if (_version==null)
- throw new BadMessage(HttpStatus.BAD_REQUEST_400,"Unknown Version");
+ throw new BadMessageException(HttpStatus.BAD_REQUEST_400,"Unknown Version");
// Should we try to cache header fields?
if (_connectionFields==null && _version.getVersion()>=HttpVersion.HTTP_1_1.getVersion())
else if (ch>=HttpTokens.SPACE)
_string.append((char)ch);
else
- throw new BadMessage();
+ throw new BadMessageException();
break;
_length=_string.length();
}
else
- throw new BadMessage();
+ throw new BadMessageException();
break;
default:
catch(NumberFormatException e)
{
LOG.ignore(e);
- throw new BadMessage(HttpStatus.BAD_REQUEST_400,"Bad Content-Length");
+ throw new BadMessageException(HttpStatus.BAD_REQUEST_400,"Bad Content-Length");
}
if (_contentLength <= 0)
_endOfContent=EndOfContent.NO_CONTENT;
_endOfContent=EndOfContent.CHUNKED_CONTENT;
else if (_valueString.contains(HttpHeaderValue.CHUNKED.toString()))
{
- throw new BadMessage(HttpStatus.BAD_REQUEST_400,"Bad chunking");
+ throw new BadMessageException(HttpStatus.BAD_REQUEST_400,"Bad chunking");
}
}
break;
int port=0;
if (host==null || host.length()==0)
{
- throw new BadMessage(HttpStatus.BAD_REQUEST_400,"Bad Host header");
+ throw new BadMessageException(HttpStatus.BAD_REQUEST_400,"Bad Host header");
}
int len=host.length();
{
if (DEBUG)
LOG.debug(e);
- throw new BadMessage(HttpStatus.BAD_REQUEST_400,"Bad Host header");
+ throw new BadMessageException(HttpStatus.BAD_REQUEST_400,"Bad Host header");
}
break loop;
}
{
if (host.charAt(len-1)!=']')
{
- throw new BadMessage(HttpStatus.BAD_REQUEST_400,"Bad IPv6 Host header");
+ throw new BadMessageException(HttpStatus.BAD_REQUEST_400,"Bad IPv6 Host header");
}
- host = host.substring(1,len-1);
+ host = host.substring(0,len);
}
else if (len!=host.length())
host = host.substring(0,len);
if (_maxHeaderBytes>0 && ++_headerBytes>_maxHeaderBytes)
{
LOG.warn("Header is too large >"+_maxHeaderBytes);
- throw new BadMessage(HttpStatus.REQUEST_ENTITY_TOO_LARGE_413);
+ throw new BadMessageException(HttpStatus.REQUEST_ENTITY_TOO_LARGE_413);
}
switch (_state)
// End of headers!
// Was there a required host header?
- if (!_host && _version!=HttpVersion.HTTP_1_0 && _requestHandler!=null)
+ if (!_host && _version==HttpVersion.HTTP_1_1 && _requestHandler!=null)
{
- throw new BadMessage(HttpStatus.BAD_REQUEST_400,"No Host");
+ throw new BadMessageException(HttpStatus.BAD_REQUEST_400,"No Host");
}
// is it a response that cannot have a body?
case EOF_CONTENT:
setState(State.EOF_CONTENT);
handle=_handler.headerComplete()||handle;
- break;
+ return handle;
case CHUNKED_CONTENT:
setState(State.CHUNKED_CONTENT);
handle=_handler.headerComplete()||handle;
- break;
+ return handle;
case NO_CONTENT:
handle=_handler.headerComplete()||handle;
setState(State.END);
handle=_handler.messageComplete()||handle;
- break;
+ return handle;
default:
setState(State.CONTENT);
handle=_handler.headerComplete()||handle;
- break;
+ return handle;
}
}
else if (ch<=HttpTokens.SPACE)
- throw new BadMessage();
+ throw new BadMessageException();
else
{
if (buffer.hasRemaining())
_length=_string.length();
break;
}
-
- throw new BadMessage("Illegal character");
+
+ throw new IllegalCharacterException(_state,ch,buffer);
case HEADER_VALUE:
if (ch>HttpTokens.SPACE || ch<0)
setState(State.HEADER);
break;
}
-
- throw new BadMessage("Illegal character");
+
+ throw new IllegalCharacterException(_state,ch,buffer);
case HEADER_IN_VALUE:
if (ch>=HttpTokens.SPACE || ch<0 || ch==HttpTokens.TAB)
setState(State.HEADER);
break;
}
- throw new BadMessage("Illegal character");
+
+ throw new IllegalCharacterException(_state,ch,buffer);
default:
throw new IllegalStateException(_state.toString());
if (_responseStatus>0 && _headResponse)
{
setState(State.END);
- if (_handler.messageComplete())
- return true;
+ return _handler.messageComplete();
}
else
{
// Just ignore data when closed
_headerBytes+=buffer.remaining();
BufferUtil.clear(buffer);
- if (_headerBytes>_maxHeaderBytes)
+ if (_maxHeaderBytes>0 && _headerBytes>_maxHeaderBytes)
{
// Don't want to waste time reading data of a closed request
throw new IllegalStateException("too much data after closed");
return false;
}
- catch(BadMessage e)
+ catch(BadMessageException e)
{
BufferUtil.clear(buffer);
- LOG.warn("badMessage: "+e._code+(e._message!=null?" "+e._message:"")+" for "+_handler);
+ LOG.warn("badMessage: "+e._code+(e.getMessage()!=null?" "+e.getMessage():"")+" for "+_handler);
if (DEBUG)
LOG.debug(e);
setState(State.CLOSED);
- _handler.badMessage(e._code, e._message);
+ _handler.badMessage(e._code, e.getMessage());
return false;
}
catch(Exception e)
if (content == 0)
{
setState(State.END);
- if (_handler.messageComplete())
- return true;
+ return _handler.messageComplete();
}
}
if (content == 0)
{
setState(State.END);
- if (_handler.messageComplete())
- return true;
+ return _handler.messageComplete();
}
else
{
if(_contentPosition == _contentLength)
{
setState(State.END);
- if (_handler.messageComplete())
- return true;
+ return _handler.messageComplete();
}
}
break;
if (ch == HttpTokens.LINE_FEED)
{
if (_chunkLength == 0)
- {
- setState(State.END);
- if (_handler.messageComplete())
- return true;
- }
+ setState(State.CHUNK_END);
else
setState(State.CHUNK);
}
if (ch == HttpTokens.LINE_FEED)
{
if (_chunkLength == 0)
- {
- setState(State.END);
- if (_handler.messageComplete())
- return true;
- }
+ setState(State.CHUNK_END);
else
setState(State.CHUNK);
}
break;
}
+ case CHUNK_END:
+ {
+ // TODO handle chunk trailer
+ ch=next(buffer);
+ if (ch==0)
+ break;
+ if (ch == HttpTokens.LINE_FEED)
+ {
+ setState(State.END);
+ return _handler.messageComplete();
+ }
+ throw new IllegalCharacterException(_state,ch,buffer);
+ }
+
case CLOSED:
{
BufferUtil.clear(buffer);
_state=state;
}
+ /* ------------------------------------------------------------------------------- */
+ 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 BadMessageException();
+ }
+
/* ------------------------------------------------------------------------------- */
@Override
public String toString()
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>
{
/**
public abstract boolean parsedHostHeader(String host,int port);
}
+ /* ------------------------------------------------------------------------------- */
+ /* ------------------------------------------------------------------------------- */
+ /* ------------------------------------------------------------------------------- */
public interface ResponseHandler<T> extends HttpHandler<T>
{
/**
public abstract boolean startResponse(HttpVersion version, int status, String reason);
}
- public Trie<HttpField> getFieldCache()
- {
- return _connectionFields;
- }
-
- private String getProxyField(ByteBuffer buffer)
+ /* ------------------------------------------------------------------------------- */
+ @SuppressWarnings("serial")
+ private static class IllegalCharacterException extends BadMessageException
{
- _string.setLength(0);
- _length=0;
-
- while (buffer.hasRemaining())
+ private IllegalCharacterException(State state,byte ch,ByteBuffer buffer)
{
- // process each character
- byte ch=next(buffer);
- if (ch<=' ')
- return _string.toString();
- _string.append((char)ch);
+ super(400,String.format("Illegal character 0x%X",ch));
+ // Bug #460642 - don't reveal buffers to end user
+ LOG.warn(String.format("Illegal character 0x%X in state=%s for buffer %s",ch,state,BufferUtil.toDetailString(buffer)));
}
- throw new BadMessage();
}
}