414449 Added HttpParser strict mode for case sensitivity
This commit is contained in:
parent
e3e9264a07
commit
43946c62e0
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in New Issue