Issue #346 HttpParser RFC2616 Compliance mode
Added RFC2616 compliance mode that allows wrapped headers
This commit is contained in:
parent
4a6602d007
commit
72edbe5cea
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue