414449 Added HttpParser strict mode for case sensitivity

This commit is contained in:
Greg Wilkins 2013-08-12 23:01:05 +10:00
parent e3e9264a07
commit 43946c62e0
6 changed files with 207 additions and 191 deletions

View File

@ -68,6 +68,7 @@ public class HttpField
CACHE.put(new CachedHttpField(HttpHeader.CONTENT_LENGTH,"0"));
CACHE.put(new CachedHttpField(HttpHeader.CONTENT_ENCODING,"gzip"));
CACHE.put(new CachedHttpField(HttpHeader.CONTENT_ENCODING,"deflate"));
CACHE.put(new CachedHttpField(HttpHeader.TRANSFER_ENCODING,"chunked"));
CACHE.put(new CachedHttpField(HttpHeader.EXPIRES,"Fri, 01 Jan 1990 00:00:00 GMT"));
// Content types

View File

@ -63,11 +63,18 @@ import org.eclipse.jetty.util.log.Logger;
* 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);
static final int INITIAL_URI_LENGTH=256;
public final static boolean __STRICT=Boolean.getBoolean("org.eclipse.jetty.http.HttpParser.STRICT");
public final static int INITIAL_URI_LENGTH=256;
// States
public enum State
@ -82,7 +89,6 @@ public class HttpParser
REQUEST_VERSION,
REASON,
HEADER,
HEADER_NAME,
HEADER_IN_NAME,
HEADER_VALUE,
HEADER_IN_VALUE,
@ -101,6 +107,7 @@ public class HttpParser
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;
@ -132,31 +139,45 @@ public class HttpParser
/* ------------------------------------------------------------------------------- */
public HttpParser(RequestHandler<ByteBuffer> handler)
{
this(handler,-1);
this(handler,-1,__STRICT);
}
/* ------------------------------------------------------------------------------- */
public HttpParser(ResponseHandler<ByteBuffer> handler)
{
this(handler,-1);
this(handler,-1,__STRICT);
}
/* ------------------------------------------------------------------------------- */
public HttpParser(RequestHandler<ByteBuffer> handler,int maxHeaderBytes)
{
_handler=handler;
_requestHandler=handler;
_responseHandler=null;
_maxHeaderBytes=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;
}
/* ------------------------------------------------------------------------------- */
@ -376,14 +397,16 @@ public class HttpParser
return false;
}
private String takeString()
/* ------------------------------------------------------------------------------- */
private void setString(String s)
{
String s =_string.toString();
_string.setLength(0);
return s;
_string.append(s);
_length=s.length();
}
private String takeLengthString()
/* ------------------------------------------------------------------------------- */
private String takeString()
{
_string.setLength(_length);
String s =_string.toString();
@ -429,9 +452,10 @@ public class HttpParser
case METHOD:
if (ch == HttpTokens.SPACE)
{
_length=_string.length();
_methodString=takeString();
HttpMethod method=HttpMethod.CACHE.get(_methodString);
if (method!=null)
if (method!=null && !_strict)
_methodString=method.asString();
setState(State.SPACE1);
}
@ -444,6 +468,7 @@ public class HttpParser
case RESPONSE_VERSION:
if (ch == HttpTokens.SPACE)
{
_length=_string.length();
String version=takeString();
_version=HttpVersion.CACHE.get(version);
if (_version==null)
@ -622,7 +647,10 @@ public class HttpParser
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");
@ -651,7 +679,7 @@ public class HttpParser
case REASON:
if (ch == HttpTokens.LINE_FEED)
{
String reason=takeLengthString();
String reason=takeString();
setState(State.HEADER);
return_from_parse=_responseHandler.startResponse(_version, _responseStatus, reason)||return_from_parse;
@ -826,14 +854,18 @@ public class HttpParser
case HttpTokens.TAB:
{
// header value without name - continuation?
_string.setLength(0);
if (_valueString!=null)
if (_valueString==null)
{
_string.append(_valueString);
_string.append(' ');
_string.setLength(0);
_length=0;
}
else
{
setString(_valueString);
_string.append(' ');
_length++;
_valueString=null;
}
_length=_string.length();
_valueString=null;
setState(State.HEADER_VALUE);
break;
}
@ -929,14 +961,35 @@ public class HttpParser
if (field!=null)
{
String n=field.getName();
String v=field.getValue();
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(),StringUtil.__US_ASCII_CHARSET);
if (fv==null)
v=null;
else
{
v=BufferUtil.toString(buffer,buffer.position()+fn.length()+1,fv.length(),StringUtil.__ISO_8859_1_CHARSET);
field=new HttpField(field.getHeader(),n,v);
}
}
else
{
n=field.getName();
v=field.getValue();
}
_header=field.getHeader();
_headerString=n;
if (v==null)
{
// Header only
_header=field.getHeader();
_headerString=n;
setState(State.HEADER_VALUE);
_string.setLength(0);
_length=0;
@ -952,8 +1005,6 @@ public class HttpParser
if (b==HttpTokens.CARRIAGE_RETURN || b==HttpTokens.LINE_FEED)
{
_field=field;
_header=_field.getHeader();
_headerString=n;
_valueString=v;
setState(State.HEADER_IN_VALUE);
@ -966,194 +1017,111 @@ public class HttpParser
buffer.position(pos);
break;
}
else
{
setState(State.HEADER_IN_VALUE);
setString(v);
buffer.position(pos);
break;
}
}
}
}
// New header
setState(State.HEADER_NAME);
setState(State.HEADER_IN_NAME);
_string.setLength(0);
_string.append((char)ch);
_length=1;
}
}
}
break;
case HEADER_NAME:
if (ch<0)
throw new BadMessage();
switch(ch)
{
case HttpTokens.LINE_FEED:
if (_headerString==null)
{
_headerString=takeLengthString();
_header=HttpHeader.CACHE.get(_headerString);
}
setState(State.HEADER);
break;
case HttpTokens.COLON:
if (_headerString==null)
{
_headerString=takeLengthString();
_header=HttpHeader.CACHE.get(_headerString);
}
setState(State.HEADER_VALUE);
break;
case HttpTokens.SPACE:
case HttpTokens.TAB:
break;
default:
{
_string.append((char)ch);
_length=_string.length();
setState(State.HEADER_IN_NAME);
}
}
break;
case HEADER_IN_NAME:
if (ch<HttpTokens.SPACE)
if (ch==HttpTokens.COLON || ch==HttpTokens.LINE_FEED)
{
}
if (ch<0)
throw new BadMessage("Illegal character");
switch(ch)
{
case HttpTokens.LINE_FEED:
if (_headerString==null)
{
_headerString=takeString();
_length=-1;
_header=HttpHeader.CACHE.get(_headerString);
setState(State.HEADER);
break;
}
_length=-1;
case HttpTokens.COLON:
if (_headerString==null)
{
_headerString=takeString();
_header=HttpHeader.CACHE.get(_headerString);
}
_length=-1;
setState(State.HEADER_VALUE);
break;
case HttpTokens.SPACE:
case HttpTokens.TAB:
if (_header!=null)
{
_string.setLength(0);
_string.append(_header.asString());
_length=_string.length();
_header=null;
_headerString=null;
}
setState(State.HEADER_NAME);
_string.append((char)ch);
break;
default:
if (_header!=null)
{
_string.setLength(0);
_string.append(_header.asString());
_length=_string.length();
_header=null;
_headerString=null;
}
_string.append((char)ch);
_length++;
setState(ch==HttpTokens.LINE_FEED?State.HEADER:State.HEADER_VALUE);
break;
}
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:
switch(ch)
if (ch>HttpTokens.SPACE || ch<0)
{
case HttpTokens.LINE_FEED:
if (_length > 0)
{
if (_valueString!=null)
{
// multi line value!
_value=null;
_valueString+=" "+takeLengthString();
}
else if (HttpHeaderValue.hasKnownValues(_header))
{
_valueString=takeLengthString();
_value=HttpHeaderValue.CACHE.get(_valueString);
}
else
{
_value=null;
_valueString=takeLengthString();
}
}
setState(State.HEADER);
break;
case HttpTokens.SPACE:
case HttpTokens.TAB:
break;
default:
{
_string.append((char)(0xff&ch));
_length=_string.length();
setState(State.HEADER_IN_VALUE);
}
_string.append((char)(0xff&ch));
_length=_string.length();
setState(State.HEADER_IN_VALUE);
break;
}
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:
switch(ch)
if (ch>=HttpTokens.SPACE || ch<0)
{
case HttpTokens.LINE_FEED:
if (_length > 0)
{
if (HttpHeaderValue.hasKnownValues(_header))
{
_valueString=takeString();
_value=HttpHeaderValue.CACHE.get(_valueString);
}
else
{
_value=null;
_valueString=takeString();
}
_length=-1;
}
setState(State.HEADER);
break;
case HttpTokens.SPACE:
case HttpTokens.TAB:
if (_valueString!=null)
{
_string.setLength(0);
_string.append(_valueString);
_length=_valueString.length();
_valueString=null;
_field=null;
}
_string.append((char)ch);
setState(State.HEADER_VALUE);
break;
default:
if (_valueString!=null)
{
_string.setLength(0);
_string.append(_valueString);
_length=_valueString.length();
_valueString=null;
_field=null;
}
_string.append((char)(0xff&ch));
_length++;
if (_valueString!=null)
{
setString(_valueString);
_valueString=null;
_field=null;
}
_string.append((char)(0xff&ch));
if (ch>HttpTokens.SPACE || ch<0)
_length=_string.length();
break;
}
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());
@ -1377,7 +1345,7 @@ public class HttpParser
{
BufferUtil.clear(buffer);
LOG.warn("badMessage: "+e._code+(e._message!=null?" "+e._message:"")+" for "+_handler);
LOG.warn("BadMessage: "+e._code+(e._message!=null?" "+e._message:"")+" for "+_handler);
if (DEBUG)
LOG.debug(e);
setState(State.CLOSED);
@ -1388,7 +1356,7 @@ public class HttpParser
{
BufferUtil.clear(buffer);
LOG.warn("badMessage: "+e.toString()+" for "+_handler);
LOG.warn("Parsing Exception: "+e.toString()+" for "+_handler);
if (DEBUG)
LOG.debug(e);

View File

@ -379,6 +379,50 @@ public class HttpParserTest
HttpParser parser= new HttpParser(handler);
parseAll(parser,buffer);
assertThat(_bad,Matchers.notNullValue());
}
@Test
public void testNonStrict() throws Exception
{
ByteBuffer buffer= BufferUtil.toBuffer(
"get / http/1.0\015\012" +
"HOST: localhost\015\012" +
"cOnNeCtIoN: ClOsE\015\012"+
"\015\012");
HttpParser.RequestHandler<ByteBuffer> handler = new Handler();
HttpParser parser= new HttpParser(handler,-1,false);
parseAll(parser,buffer);
assertEquals("GET", _methodOrVersion);
assertEquals("/", _uriOrStatus);
assertEquals("HTTP/1.0", _versionOrReason);
assertEquals("Host", _hdr[0]);
assertEquals("localhost", _val[0]);
assertEquals("Connection", _hdr[1]);
assertEquals("close", _val[1]);
assertEquals(1, _h);
}
@Test
public void testStrict() throws Exception
{
ByteBuffer buffer= BufferUtil.toBuffer(
"gEt / http/1.0\015\012" +
"HOST: localhost\015\012" +
"cOnNeCtIoN: ClOsE\015\012"+
"\015\012");
HttpParser.RequestHandler<ByteBuffer> handler = new Handler();
HttpParser parser= new HttpParser(handler,-1,true);
parseAll(parser,buffer);
assertEquals("gEt", _methodOrVersion);
assertEquals("/", _uriOrStatus);
assertEquals("HTTP/1.0", _versionOrReason);
assertEquals("HOST", _hdr[0]);
assertEquals("localhost", _val[0]);
assertEquals("cOnNeCtIoN", _hdr[1]);
assertEquals("ClOsE", _val[1]);
assertEquals(1, _h);
}
@Test
@ -494,7 +538,6 @@ public class HttpParserTest
+ "\015\012"
+ "0123456789\015\012");
HttpParser.RequestHandler<ByteBuffer> handler = new Handler();
HttpParser parser= new HttpParser(handler);
parser.parseNext(buffer);

View File

@ -23,6 +23,7 @@ import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import javax.servlet.DispatcherType;
import javax.servlet.RequestDispatcher;

View File

@ -48,6 +48,7 @@ import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.io.EofException;
import org.eclipse.jetty.server.handler.AbstractHandler;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.log.Log;

View File

@ -53,12 +53,14 @@ public class StringUtil
public final static Charset __UTF8_CHARSET;
public final static Charset __ISO_8859_1_CHARSET;
public final static Charset __UTF16_CHARSET;
public final static Charset __US_ASCII_CHARSET;
static
{
__UTF8_CHARSET=Charset.forName(__UTF8);
__ISO_8859_1_CHARSET=Charset.forName(__ISO_8859_1);
__UTF16_CHARSET=Charset.forName(__UTF16);
__US_ASCII_CHARSET=Charset.forName("US-ASCII");
CHARSETS.put("UTF-8",__UTF8);
CHARSETS.put("UTF8",__UTF8);