Skip to content

Commit

Permalink
Issue #346 HttpParser RFC2616 Compliance mode
Browse files Browse the repository at this point in the history
Reimplmented HTTP/0.9 support in RFC2616 compliance mode
  • Loading branch information
gregw committed Feb 23, 2016
1 parent 76689dd commit 3c671aa
Show file tree
Hide file tree
Showing 14 changed files with 191 additions and 40 deletions.
Expand Up @@ -53,6 +53,7 @@
import javax.servlet.http.HttpServletResponse;

import org.eclipse.jetty.client.ssl.SslBytesTest.TLSRecord.Type;
import org.eclipse.jetty.http.HttpCompliance;
import org.eclipse.jetty.http.HttpParser;
import org.eclipse.jetty.io.Connection;
import org.eclipse.jetty.io.EndPoint;
Expand Down Expand Up @@ -114,7 +115,7 @@ public Connection newConnection(Connector connector, EndPoint endPoint)
return configure(new HttpConnection(getHttpConfiguration(), connector, endPoint,getHttpCompliance())
{
@Override
protected HttpParser newHttpParser(HttpParser.Compliance compliance)
protected HttpParser newHttpParser(HttpCompliance compliance)
{
return new HttpParser(newRequestHandler(), getHttpConfiguration().getRequestHeaderSize(),compliance)
{
Expand Down
@@ -0,0 +1,31 @@
//
// ========================================================================
// 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
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//

package org.eclipse.jetty.http;


/**
* HTTP compliance modes:
* <dl>
* <dt>RFC7230</dt><dd>(default) Compliance with RFC7230</dd>
* <dt>RFC2616</dt><dd>Wrapped/Continued headers and HTTP/0.9 supported</dd>
* <dt>LEGACY</dt><dd>(aka STRICT) Adherence to Servlet Specification requirement for
* exact case of header names, bypassing the header caches, which are case insensitive,
* otherwise equivalent to RFC2616</dd>
*/
public enum HttpCompliance { LEGACY, RFC2616, RFC7230 }
Expand Up @@ -359,7 +359,12 @@ public Result generateResponse(MetaData.Response info, boolean head, ByteBuffer
break;

default:
throw new IllegalArgumentException(version+" not supported");
_persistent = false;
_endOfContent=EndOfContent.EOF_CONTENT;
if (BufferUtil.hasContent(content))
_contentPrepared+=content.remaining();
_state = last?State.COMPLETING:State.COMMITTED;
return Result.FLUSH;
}

// Do we need a response header
Expand Down
59 changes: 39 additions & 20 deletions jetty-http/src/main/java/org/eclipse/jetty/http/HttpParser.java
Expand Up @@ -77,8 +77,8 @@
* The parser can work in varying compliance modes:
* <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
* <dt>RFC2616</dt><dd>Wrapped headers and HTTP/0.9 supported</dd>
* <dt>LEGACY</dt><dd>(aka STRICT) 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>
Expand Down Expand Up @@ -106,9 +106,6 @@ public class HttpParser
*/
public final static Trie<HttpField> CACHE = new ArrayTrie<>(2048);

// Compliance
public enum Compliance { STRICT, RFC2616, RFC7230 };

// States
public enum State
{
Expand Down Expand Up @@ -146,7 +143,7 @@ public enum State
private final RequestHandler _requestHandler;
private final ResponseHandler _responseHandler;
private final int _maxHeaderBytes;
private final Compliance _compliance;
private final HttpCompliance _compliance;
private HttpField _field;
private HttpHeader _header;
private String _headerString;
Expand Down Expand Up @@ -226,10 +223,10 @@ public enum State
CACHE.put(new HttpField(HttpHeader.COOKIE,(String)null));
}

private static Compliance compliance()
private static HttpCompliance compliance()
{
Boolean strict = Boolean.getBoolean(__STRICT);
return strict?Compliance.STRICT:Compliance.RFC7230;
return strict?HttpCompliance.LEGACY:HttpCompliance.RFC7230;
}

/* ------------------------------------------------------------------------------- */
Expand Down Expand Up @@ -260,18 +257,24 @@ public HttpParser(ResponseHandler handler,int maxHeaderBytes)
@Deprecated
public HttpParser(RequestHandler handler,int maxHeaderBytes,boolean strict)
{
this(handler,maxHeaderBytes,strict?Compliance.STRICT:compliance());
this(handler,maxHeaderBytes,strict?HttpCompliance.LEGACY:compliance());
}

/* ------------------------------------------------------------------------------- */
@Deprecated
public HttpParser(ResponseHandler handler,int maxHeaderBytes,boolean strict)
{
this(handler,maxHeaderBytes,strict?Compliance.STRICT:compliance());
this(handler,maxHeaderBytes,strict?HttpCompliance.LEGACY:compliance());
}

/* ------------------------------------------------------------------------------- */
public HttpParser(RequestHandler handler,HttpCompliance compliance)
{
this(handler,-1,compliance);
}

/* ------------------------------------------------------------------------------- */
public HttpParser(RequestHandler handler,int maxHeaderBytes,Compliance compliance)
public HttpParser(RequestHandler handler,int maxHeaderBytes,HttpCompliance compliance)
{
_handler=handler;
_requestHandler=handler;
Expand All @@ -281,7 +284,7 @@ public HttpParser(RequestHandler handler,int maxHeaderBytes,Compliance complianc
}

/* ------------------------------------------------------------------------------- */
public HttpParser(ResponseHandler handler,int maxHeaderBytes,Compliance compliance)
public HttpParser(ResponseHandler handler,int maxHeaderBytes,HttpCompliance compliance)
{
_handler=handler;
_requestHandler=null;
Expand Down Expand Up @@ -583,7 +586,7 @@ private boolean parseLine(ByteBuffer buffer)
_length=_string.length();
_methodString=takeString();
HttpMethod method=HttpMethod.CACHE.get(_methodString);
if (method!=null && _compliance!=Compliance.STRICT)
if (method!=null && _compliance!=HttpCompliance.LEGACY)
_methodString=method.asString();
setState(State.SPACE1);
}
Expand Down Expand Up @@ -685,7 +688,15 @@ else if (ch < HttpTokens.SPACE && ch>=0)
else if (ch < HttpTokens.SPACE && ch>=0)
{
// HTTP/0.9
throw new BadMessageException("HTTP/0.9 not supported");
if (_compliance.ordinal()>=HttpCompliance.RFC7230.ordinal())
throw new BadMessageException("HTTP/0.9 not supported");

handle=_requestHandler.startRequest(_methodString,_uri.toString(), HttpVersion.HTTP_0_9);
setState(State.END);
BufferUtil.clear(buffer);
handle=_handler.headerComplete()||handle;
handle=_handler.messageComplete()||handle;
return handle;
}
else
{
Expand Down Expand Up @@ -747,7 +758,15 @@ else if (ch == HttpTokens.LINE_FEED)
else
{
// HTTP/0.9
throw new BadMessageException("HTTP/0.9 not supported");
if (_compliance.ordinal()>=HttpCompliance.RFC7230.ordinal())
throw new BadMessageException("HTTP/0.9 not supported");

handle=_requestHandler.startRequest(_methodString,_uri.toString(), HttpVersion.HTTP_0_9);
setState(State.END);
BufferUtil.clear(buffer);
handle=_handler.headerComplete()||handle;
handle=_handler.messageComplete()||handle;
return handle;
}
}
else if (ch<0)
Expand Down Expand Up @@ -859,7 +878,7 @@ else if (_valueString.contains(HttpHeaderValue.CHUNKED.toString()))
_host=true;
if (!(_field instanceof HostPortHttpField))
{
_field=new HostPortHttpField(_header,_compliance==Compliance.STRICT?_headerString:_header.asString(),_valueString);
_field=new HostPortHttpField(_header,_compliance==HttpCompliance.LEGACY?_headerString:_header.asString(),_valueString);
add_to_connection_trie=_connectionFields!=null;
}
break;
Expand Down Expand Up @@ -888,7 +907,7 @@ else if (_valueString.contains(HttpHeaderValue.CHUNKED.toString()))
if (add_to_connection_trie && !_connectionFields.isFull() && _header!=null && _valueString!=null)
{
if (_field==null)
_field=new HttpField(_header,_compliance==Compliance.STRICT?_headerString:_header.asString(),_valueString);
_field=new HttpField(_header,_compliance==HttpCompliance.LEGACY?_headerString:_header.asString(),_valueString);
_connectionFields.put(_field);
}
}
Expand Down Expand Up @@ -933,7 +952,7 @@ protected boolean parseHeaders(ByteBuffer buffer)
case HttpTokens.SPACE:
case HttpTokens.TAB:
{
if (_compliance.ordinal()>=Compliance.RFC7230.ordinal())
if (_compliance.ordinal()>=HttpCompliance.RFC7230.ordinal())
throw new BadMessageException(HttpStatus.BAD_REQUEST_400,"Bad Continuation");

// header value without name - continuation?
Expand Down Expand Up @@ -1035,7 +1054,7 @@ else if (_endOfContent == EndOfContent.UNKNOWN_CONTENT)
final String n;
final String v;

if (_compliance==Compliance.STRICT)
if (_compliance==HttpCompliance.LEGACY)
{
// Have to get the fields exactly from the buffer to match case
String fn=field.getName();
Expand Down
Expand Up @@ -32,6 +32,41 @@

public class HttpGeneratorServerTest
{
@Test
public void test_0_9() throws Exception
{
ByteBuffer header = BufferUtil.allocate(8096);
ByteBuffer content = BufferUtil.toBuffer("0123456789");

HttpGenerator gen = new HttpGenerator();

HttpGenerator.Result result = gen.generateResponse(null, null, null, content, true);
assertEquals(HttpGenerator.Result.NEED_INFO, result);
assertEquals(HttpGenerator.State.START, gen.getState());

MetaData.Response info = new MetaData.Response(HttpVersion.HTTP_0_9, 200, null, new HttpFields(), 10);
info.getFields().add("Content-Type", "test/data");
info.getFields().add("Last-Modified", DateGenerator.__01Jan1970);

result = gen.generateResponse(info, null, null, content, true);
assertEquals(HttpGenerator.Result.FLUSH, result);
assertEquals(HttpGenerator.State.COMPLETING, gen.getState());
String response = BufferUtil.toString(header);
BufferUtil.clear(header);
response += BufferUtil.toString(content);
BufferUtil.clear(content);

result = gen.generateResponse(null, null, null, content, false);
assertEquals(HttpGenerator.Result.SHUTDOWN_OUT, result);
assertEquals(HttpGenerator.State.END, gen.getState());

assertEquals(10, gen.getContentPrepared());

assertThat(response, not(containsString("200 OK")));
assertThat(response, not(containsString("Last-Modified: Thu, 01 Jan 1970 00:00:00 GMT")));
assertThat(response, not(containsString("Content-Length: 10")));
assertThat(response, containsString("0123456789"));
}

@Test
public void testSimple() throws Exception
Expand Down
Expand Up @@ -114,19 +114,49 @@ public void testLineParse0() throws Exception
assertEquals(-1, _headers);
}

@Test
public void testLineParse1_RFC2616() throws Exception
{
ByteBuffer buffer= BufferUtil.toBuffer("GET /999\015\012");

HttpParser.RequestHandler handler = new Handler();
HttpParser parser= new HttpParser(handler,HttpCompliance.RFC2616);
parseAll(parser,buffer);

assertNull(_bad);
assertEquals("GET", _methodOrVersion);
assertEquals("/999", _uriOrStatus);
assertEquals("HTTP/0.9", _versionOrReason);
assertEquals(-1, _headers);
}

@Test
public void testLineParse1() throws Exception
{
ByteBuffer buffer= BufferUtil.toBuffer("GET /999\015\012");

_versionOrReason= null;
HttpParser.RequestHandler handler = new Handler();
HttpParser parser= new HttpParser(handler);
parseAll(parser,buffer);

assertEquals("HTTP/0.9 not supported", _bad);
}

@Test
public void testLineParse2_RFC2616() throws Exception
{
ByteBuffer buffer= BufferUtil.toBuffer("POST /222 \015\012");

HttpParser.RequestHandler handler = new Handler();
HttpParser parser= new HttpParser(handler,HttpCompliance.RFC2616);
parseAll(parser,buffer);

assertNull(_bad);
assertEquals("POST", _methodOrVersion);
assertEquals("/222", _uriOrStatus);
assertEquals("HTTP/0.9", _versionOrReason);
assertEquals(-1, _headers);
}

@Test
public void testLineParse2() throws Exception
{
Expand All @@ -136,7 +166,7 @@ public void testLineParse2() throws Exception
HttpParser.RequestHandler handler = new Handler();
HttpParser parser= new HttpParser(handler);
parseAll(parser,buffer);
assertEquals("HTTP/0.9 not supported", _bad);
assertEquals("HTTP/0.9 not supported", _bad);
}

@Test
Expand Down Expand Up @@ -230,7 +260,7 @@ public void test2616Continuations() throws Exception
"\015\012");

HttpParser.RequestHandler handler = new Handler();
HttpParser parser= new HttpParser(handler,4096,HttpParser.Compliance.RFC2616);
HttpParser parser= new HttpParser(handler,4096,HttpCompliance.RFC2616);
parseAll(parser,buffer);

Assert.assertThat(_bad,Matchers.nullValue());
Expand All @@ -252,7 +282,7 @@ public void test7230NoContinuations() throws Exception
"\015\012");

HttpParser.RequestHandler handler = new Handler();
HttpParser parser= new HttpParser(handler,4096,HttpParser.Compliance.RFC7230);
HttpParser parser= new HttpParser(handler,4096,HttpCompliance.RFC7230);
parseAll(parser,buffer);

Assert.assertThat(_bad,Matchers.notNullValue());
Expand Down Expand Up @@ -602,7 +632,7 @@ public void testNonStrict() throws Exception
"cOnNeCtIoN: ClOsE\015\012"+
"\015\012");
HttpParser.RequestHandler handler = new Handler();
HttpParser parser= new HttpParser(handler,-1,HttpParser.Compliance.RFC7230);
HttpParser parser= new HttpParser(handler,-1,HttpCompliance.RFC7230);
parseAll(parser,buffer);
assertNull(_bad);
assertEquals("GET", _methodOrVersion);
Expand All @@ -624,7 +654,7 @@ public void testStrict() throws Exception
"cOnNeCtIoN: ClOsE\015\012"+
"\015\012");
HttpParser.RequestHandler handler = new Handler();
HttpParser parser= new HttpParser(handler,-1,HttpParser.Compliance.STRICT);
HttpParser parser= new HttpParser(handler,-1,HttpCompliance.LEGACY);
parseAll(parser,buffer);
assertNull(_bad);
assertEquals("gEt", _methodOrVersion);
Expand Down
1 change: 1 addition & 0 deletions jetty-server/src/main/config/etc/jetty-http.xml
Expand Up @@ -29,6 +29,7 @@
<Item>
<New class="org.eclipse.jetty.server.HttpConnectionFactory">
<Arg name="config"><Ref refid="httpConfig" /></Arg>
<Arg name="compliance"><Call class="org.eclipse.jetty.http.HttpCompliance" name="valueOf"><Arg><Property name="jetty.http.compliance" default="RFC7230"/></Arg></Call></Arg>
</New>
</Item>
</Array>
Expand Down
1 change: 1 addition & 0 deletions jetty-server/src/main/config/etc/jetty-https.xml
Expand Up @@ -21,6 +21,7 @@
<Arg>
<New class="org.eclipse.jetty.server.HttpConnectionFactory">
<Arg name="config"><Ref refid="sslHttpConfig" /></Arg>
<Arg name="compliance"><Call class="org.eclipse.jetty.http.HttpCompliance" name="valueOf"><Arg><Property name="jetty.http.compliance" default="RFC7230"/></Arg></Call></Arg>
</New>
</Arg>
</Call>
Expand Down
3 changes: 3 additions & 0 deletions jetty-server/src/main/config/modules/http.mod
Expand Up @@ -34,3 +34,6 @@ etc/jetty-http.xml

## Thread priority delta to give to acceptor threads
# jetty.http.acceptorPriorityDelta=0

## HTTP Compliance: RFC7230, RFC2616, LEGACY
# jetty.http.compliance=RFC7230

0 comments on commit 3c671aa

Please sign in to comment.