Issue #346 HttpParser RFC2616 Compliance mode

Added RFC2616 compliance mode that allows wrapped headers
This commit is contained in:
Greg Wilkins 2016-02-19 11:21:39 +01:00
parent 4a6602d007
commit 72edbe5cea
2 changed files with 105 additions and 47 deletions

View File

@ -74,10 +74,14 @@ import static org.eclipse.jetty.http.HttpTokens.TAB;
* 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
* The parser can work in varying compliance modes (set either in constructor or with
* the "org.eclipse.jetty.http.HttpParser.COMPLIANCE" System property):
* <dl>
* <dt>RFC7230</dt><dd>(default) Compliance with RFC7230</dd>
* <dt>RFC2616</dt><dd>Wrapped headers supported</dd>
* <dt>STRICT</dt><dd>(misnomer) Adherence to Servlet Specification requirement for
* exact case of header names, bypassing the header caches, which are case insensitive,
* otherwise equivalent to RFC2616</dd>
* </p>
* <p>
* @see <a href="http://tools.ietf.org/html/rfc7230">RFC 7230</a>
@ -85,7 +89,9 @@ import static org.eclipse.jetty.http.HttpTokens.TAB;
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");
@Deprecated
public final static String __STRICT="org.eclipse.jetty.http.HttpParser.STRICT";
public final static String __COMPLIANCE="org.eclipse.jetty.http.HttpParser.COMPLIANCE";
public final static int INITIAL_URI_LENGTH=256;
/**
@ -102,6 +108,9 @@ public class HttpParser
*/
public final static Trie<HttpField> CACHE = new ArrayTrie<>(2048);
// Compliance
public enum Compliance { STRICT, RFC2616, RFC7230 };
// States
public enum State
{
@ -139,7 +148,7 @@ public class HttpParser
private final RequestHandler _requestHandler;
private final ResponseHandler _responseHandler;
private final int _maxHeaderBytes;
private final boolean _strict;
private final Compliance _compliance;
private HttpField _field;
private HttpHeader _header;
private String _headerString;
@ -218,49 +227,73 @@ public class HttpParser
CACHE.put(new HttpField(HttpHeader.AUTHORIZATION,(String)null));
CACHE.put(new HttpField(HttpHeader.COOKIE,(String)null));
}
private static Compliance compliance()
{
String compliance = System.getProperty(__COMPLIANCE);
if (compliance!=null)
return Compliance.valueOf(compliance);
Boolean strict = Boolean.getBoolean(__STRICT);
return strict?Compliance.STRICT:Compliance.RFC7230;
}
/* ------------------------------------------------------------------------------- */
public HttpParser(RequestHandler handler)
{
this(handler,-1,__STRICT);
this(handler,-1,compliance());
}
/* ------------------------------------------------------------------------------- */
public HttpParser(ResponseHandler handler)
{
this(handler,-1,__STRICT);
this(handler,-1,compliance());
}
/* ------------------------------------------------------------------------------- */
public HttpParser(RequestHandler handler,int maxHeaderBytes)
{
this(handler,maxHeaderBytes,__STRICT);
this(handler,maxHeaderBytes,compliance());
}
/* ------------------------------------------------------------------------------- */
public HttpParser(ResponseHandler handler,int maxHeaderBytes)
{
this(handler,maxHeaderBytes,__STRICT);
this(handler,maxHeaderBytes,Compliance.RFC7230);
}
/* ------------------------------------------------------------------------------- */
@Deprecated
public HttpParser(RequestHandler handler,int maxHeaderBytes,boolean strict)
{
this(handler,maxHeaderBytes,strict?Compliance.STRICT:compliance());
}
/* ------------------------------------------------------------------------------- */
@Deprecated
public HttpParser(ResponseHandler handler,int maxHeaderBytes,boolean strict)
{
this(handler,maxHeaderBytes,strict?Compliance.STRICT:compliance());
}
/* ------------------------------------------------------------------------------- */
public HttpParser(RequestHandler handler,int maxHeaderBytes,Compliance compliance)
{
_handler=handler;
_requestHandler=handler;
_responseHandler=null;
_maxHeaderBytes=maxHeaderBytes;
_strict=strict;
_compliance=compliance==null?compliance():compliance;
}
/* ------------------------------------------------------------------------------- */
public HttpParser(ResponseHandler handler,int maxHeaderBytes,boolean strict)
public HttpParser(ResponseHandler handler,int maxHeaderBytes,Compliance compliance)
{
_handler=handler;
_requestHandler=null;
_responseHandler=handler;
_maxHeaderBytes=maxHeaderBytes;
_strict=strict;
_compliance=compliance==null?compliance():compliance;
}
/* ------------------------------------------------------------------------------- */
@ -556,7 +589,7 @@ public class HttpParser
_length=_string.length();
_methodString=takeString();
HttpMethod method=HttpMethod.CACHE.get(_methodString);
if (method!=null && !_strict)
if (method!=null && _compliance!=Compliance.STRICT)
_methodString=method.asString();
setState(State.SPACE1);
}
@ -832,7 +865,7 @@ public class HttpParser
_host=true;
if (!(_field instanceof HostPortHttpField))
{
_field=new HostPortHttpField(_header,_strict?_headerString:_header.asString(),_valueString);
_field=new HostPortHttpField(_header,_compliance==Compliance.STRICT?_headerString:_header.asString(),_valueString);
add_to_connection_trie=_connectionFields!=null;
}
break;
@ -861,7 +894,7 @@ public class HttpParser
if (add_to_connection_trie && !_connectionFields.isFull() && _header!=null && _valueString!=null)
{
if (_field==null)
_field=new HttpField(_header,_strict?_headerString:_header.asString(),_valueString);
_field=new HttpField(_header,_compliance==Compliance.STRICT?_headerString:_header.asString(),_valueString);
_connectionFields.put(_field);
}
}
@ -905,10 +938,32 @@ public class HttpParser
case HttpTokens.COLON:
case HttpTokens.SPACE:
case HttpTokens.TAB:
throw new BadMessageException(HttpStatus.BAD_REQUEST_400,"Bad Continuation");
{
if (_compliance.ordinal()>=Compliance.RFC7230.ordinal())
throw new BadMessageException(HttpStatus.BAD_REQUEST_400,"Bad Continuation");
// 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;
}
case HttpTokens.LINE_FEED:
{
// process previous header
parsedHeader();
_contentPosition=0;
// End of headers!
@ -967,9 +1022,13 @@ public class HttpParser
default:
{
// now handle the ch
if (ch<=HttpTokens.SPACE)
if (ch<HttpTokens.SPACE)
throw new BadMessageException();
// process previous header
parsedHeader();
// handle new header
if (buffer.hasRemaining())
{
// Try a look ahead for the known header name and value.
@ -982,7 +1041,7 @@ public class HttpParser
final String n;
final String v;
if (_strict)
if (_compliance==Compliance.STRICT)
{
// Have to get the fields exactly from the buffer to match case
String fn=field.getName();
@ -1106,7 +1165,6 @@ public class HttpParser
_valueString=null;
_length=-1;
parsedHeader();
setState(State.HEADER);
break;
}
@ -1135,7 +1193,6 @@ public class HttpParser
_valueString=takeString();
_length=-1;
}
parsedHeader();
setState(State.HEADER);
break;
}

View File

@ -218,6 +218,28 @@ public class HttpParserTest
assertEquals("close", _val[1]);
assertEquals(1, _headers);
}
@Test
public void test2616Continuations() throws Exception
{
ByteBuffer buffer= BufferUtil.toBuffer(
"GET / HTTP/1.0\015\012" +
"Host: localhost\015\012" +
"Name: value\015\012" +
" extra\015\012" +
"\015\012");
HttpParser.RequestHandler handler = new Handler();
HttpParser parser= new HttpParser(handler,4096,HttpParser.Compliance.RFC2616);
parseAll(parser,buffer);
Assert.assertThat(_bad,Matchers.nullValue());
assertEquals("Host", _hdr[0]);
assertEquals("localhost", _val[0]);
assertEquals("Name", _hdr[1]);
assertEquals("value extra", _val[1]);
assertEquals(1, _headers);
}
@Test
public void test7230NoContinuations() throws Exception
@ -230,13 +252,12 @@ public class HttpParserTest
"\015\012");
HttpParser.RequestHandler handler = new Handler();
HttpParser parser= new HttpParser(handler);
HttpParser parser= new HttpParser(handler,4096,HttpParser.Compliance.RFC7230);
parseAll(parser,buffer);
Assert.assertThat(_bad,Matchers.notNullValue());
Assert.assertThat(_bad,Matchers.containsString("Bad Continuation"));
}
@Test
public void test7230NoWhiteSpaceInName() throws Exception
@ -581,9 +602,9 @@ public class HttpParserTest
"cOnNeCtIoN: ClOsE\015\012"+
"\015\012");
HttpParser.RequestHandler handler = new Handler();
HttpParser parser= new HttpParser(handler,-1,false);
HttpParser parser= new HttpParser(handler,-1,HttpParser.Compliance.RFC7230);
parseAll(parser,buffer);
assertNull(_bad);
assertEquals("GET", _methodOrVersion);
assertEquals("/", _uriOrStatus);
assertEquals("HTTP/1.0", _versionOrReason);
@ -603,9 +624,9 @@ public class HttpParserTest
"cOnNeCtIoN: ClOsE\015\012"+
"\015\012");
HttpParser.RequestHandler handler = new Handler();
HttpParser parser= new HttpParser(handler,-1,true);
HttpParser parser= new HttpParser(handler,-1,HttpParser.Compliance.STRICT);
parseAll(parser,buffer);
assertNull(_bad);
assertEquals("gEt", _methodOrVersion);
assertEquals("/", _uriOrStatus);
assertEquals("HTTP/1.0", _versionOrReason);
@ -1562,26 +1583,6 @@ public class HttpParserTest
assertTrue(field==_fields.get(0));
}
@Test
public void testFolded() throws Exception
{
ByteBuffer buffer= BufferUtil.toBuffer(
"GET / HTTP/1.0\015\012" +
"Host: localhost\015\012" +
"Connection: close\015\012" +
"Content-Type: application/soap+xml; charset=utf-8; \015\012"+
"\taction=\"xxx\" \015\012" +
"\015\012");
HttpParser.RequestHandler handler = new Handler();
HttpParser parser= new HttpParser(handler);
parseAll(parser,buffer);
assertFalse(_headerCompleted);
assertEquals(_bad, "Bad Continuation");
}
@Test
public void testParseRequest() throws Exception