diff --git a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/OneHandler.java b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/OneHandler.java index b2613ba7f52..a4bd7d275cb 100644 --- a/examples/embedded/src/main/java/org/eclipse/jetty/embedded/OneHandler.java +++ b/examples/embedded/src/main/java/org/eclipse/jetty/embedded/OneHandler.java @@ -18,6 +18,8 @@ package org.eclipse.jetty.embedded; +import org.eclipse.jetty.http.HttpCompliance; +import org.eclipse.jetty.server.HttpConnectionFactory; import org.eclipse.jetty.server.Server; public class OneHandler @@ -25,6 +27,7 @@ public class OneHandler public static void main( String[] args ) throws Exception { Server server = new Server(8080); + server.getConnectors()[0].getConnectionFactory(HttpConnectionFactory.class).setHttpCompliance(HttpCompliance.LEGACY); server.setHandler(new HelloHandler()); server.start(); diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/ssl/SslBytesServerTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/ssl/SslBytesServerTest.java index 9c10f7bc1ac..36c501915cd 100644 --- a/jetty-client/src/test/java/org/eclipse/jetty/client/ssl/SslBytesServerTest.java +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/ssl/SslBytesServerTest.java @@ -110,7 +110,7 @@ public class SslBytesServerTest extends SslBytesTest @Override public Connection newConnection(Connector connector, EndPoint endPoint) { - return configure(new HttpConnection(getHttpConfiguration(), connector, endPoint,getHttpCompliance()) + return configure(new HttpConnection(getHttpConfiguration(), connector, endPoint,getHttpCompliance(),isRecordHttpComplianceViolations()) { @Override protected HttpParser newHttpParser(HttpCompliance compliance) diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpParser.java b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpParser.java index 12c7e2df9e3..842a16c17dc 100644 --- a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpParser.java +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpParser.java @@ -34,6 +34,9 @@ import org.eclipse.jetty.util.Utf8StringBuilder; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; +import static org.eclipse.jetty.http.HttpCompliance.LEGACY; +import static org.eclipse.jetty.http.HttpCompliance.RFC2616; +import static org.eclipse.jetty.http.HttpCompliance.RFC7230; import static org.eclipse.jetty.http.HttpTokens.CARRIAGE_RETURN; import static org.eclipse.jetty.http.HttpTokens.LINE_FEED; import static org.eclipse.jetty.http.HttpTokens.SPACE; @@ -141,6 +144,7 @@ public class HttpParser private final HttpHandler _handler; private final RequestHandler _requestHandler; private final ResponseHandler _responseHandler; + private final ComplianceHandler _complianceHandler; private final int _maxHeaderBytes; private final HttpCompliance _compliance; private HttpField _field; @@ -280,6 +284,7 @@ public class HttpParser _responseHandler=null; _maxHeaderBytes=maxHeaderBytes; _compliance=compliance==null?compliance():compliance; + _complianceHandler=(ComplianceHandler)(handler instanceof ComplianceHandler?handler:null); } /* ------------------------------------------------------------------------------- */ @@ -290,8 +295,33 @@ public class HttpParser _responseHandler=handler; _maxHeaderBytes=maxHeaderBytes; _compliance=compliance==null?compliance():compliance; + _complianceHandler=(ComplianceHandler)(handler instanceof ComplianceHandler?handler:null); } + /* ------------------------------------------------------------------------------- */ + /** Check RFC compliance violation + * @param compliance The compliance level violated + * @param reason The reason for the violation + * @return True if the current compliance level is set so as to Not allow this violation + */ + protected boolean complianceViolation(HttpCompliance compliance,String reason) + { + if (_complianceHandler==null) + return _compliance.ordinal()>=compliance.ordinal(); + if (_compliance.ordinal()=0) { // HTTP/0.9 - if (_compliance.ordinal()>=HttpCompliance.RFC7230.ordinal()) + if (complianceViolation(RFC7230,"HTTP/0.9")) throw new BadMessageException("HTTP/0.9 not supported"); - handle=_requestHandler.startRequest(_methodString,_uri.toString(), HttpVersion.HTTP_0_9); setState(State.END); BufferUtil.clear(buffer); @@ -757,7 +786,7 @@ public class HttpParser else { // HTTP/0.9 - if (_compliance.ordinal()>=HttpCompliance.RFC7230.ordinal()) + if (complianceViolation(RFC7230,"HTTP/0.9")) throw new BadMessageException("HTTP/0.9 not supported"); handle=_requestHandler.startRequest(_methodString,_uri.toString(), HttpVersion.HTTP_0_9); @@ -874,7 +903,7 @@ public class HttpParser _host=true; if (!(_field instanceof HostPortHttpField)) { - _field=new HostPortHttpField(_header,_compliance==HttpCompliance.LEGACY?_headerString:_header.asString(),_valueString); + _field=new HostPortHttpField(_header,legacyString(_headerString,_header.asString()),_valueString); add_to_connection_trie=_connectionFields!=null; } break; @@ -903,7 +932,7 @@ public class HttpParser if (add_to_connection_trie && !_connectionFields.isFull() && _header!=null && _valueString!=null) { if (_field==null) - _field=new HttpField(_header,_compliance==HttpCompliance.LEGACY?_headerString:_header.asString(),_valueString); + _field=new HttpField(_header,legacyString(_headerString,_header.asString()),_valueString); _connectionFields.put(_field); } } @@ -960,8 +989,8 @@ public class HttpParser case HttpTokens.SPACE: case HttpTokens.TAB: { - if (_compliance.ordinal()>=HttpCompliance.RFC7230.ordinal()) - throw new BadMessageException(HttpStatus.BAD_REQUEST_400,"Bad Continuation"); + if (complianceViolation(RFC7230,"header folding")) + throw new BadMessageException(HttpStatus.BAD_REQUEST_400,"Header Folding"); // header value without name - continuation? if (_valueString==null) @@ -1062,17 +1091,17 @@ public class HttpParser final String n; final String v; - if (_compliance==HttpCompliance.LEGACY) + if (_compliance==LEGACY) { // Have to get the fields exactly from the buffer to match case String fn=field.getName(); + n=legacyString(BufferUtil.toString(buffer,buffer.position()-1,fn.length(),StandardCharsets.US_ASCII),fn); String fv=field.getValue(); - n=BufferUtil.toString(buffer,buffer.position()-1,fn.length(),StandardCharsets.US_ASCII); if (fv==null) v=null; else { - v=BufferUtil.toString(buffer,buffer.position()+fn.length()+1,fv.length(),StandardCharsets.ISO_8859_1); + v=legacyString(BufferUtil.toString(buffer,buffer.position()+fn.length()+1,fv.length(),StandardCharsets.ISO_8859_1),fv); field=new HttpField(field.getHeader(),n,v); } } @@ -1164,6 +1193,22 @@ public class HttpParser _length=_string.length(); break; } + + if (ch==HttpTokens.LINE_FEED && !complianceViolation(RFC7230,"name only header")) + { + if (_headerString==null) + { + _headerString=takeString(); + _header=HttpHeader.CACHE.get(_headerString); + } + _value=null; + _string.setLength(0); + _valueString=""; + _length=-1; + + setState(State.HEADER); + break; + } throw new IllegalCharacterException(_state,ch,buffer); @@ -1729,6 +1774,14 @@ public class HttpParser public boolean startResponse(HttpVersion version, int status, String reason); } + /* ------------------------------------------------------------------------------- */ + /* ------------------------------------------------------------------------------- */ + /* ------------------------------------------------------------------------------- */ + public interface ComplianceHandler extends HttpHandler + { + public void onComplianceViolation(HttpCompliance compliance,HttpCompliance required,String reason); + } + /* ------------------------------------------------------------------------------- */ @SuppressWarnings("serial") private static class IllegalCharacterException extends BadMessageException diff --git a/jetty-http/src/test/java/org/eclipse/jetty/http/HttpParserTest.java b/jetty-http/src/test/java/org/eclipse/jetty/http/HttpParserTest.java index c03280f863f..0228896c619 100644 --- a/jetty-http/src/test/java/org/eclipse/jetty/http/HttpParserTest.java +++ b/jetty-http/src/test/java/org/eclipse/jetty/http/HttpParserTest.java @@ -30,6 +30,9 @@ import org.junit.Assert; import org.junit.Before; import org.junit.Test; +import static org.hamcrest.Matchers.containsString; +import static org.junit.Assert.assertThat; + public class HttpParserTest { /** @@ -119,6 +122,7 @@ public class HttpParserTest Assert.assertEquals("/999", _uriOrStatus); Assert.assertEquals("HTTP/0.9", _versionOrReason); Assert.assertEquals(-1, _headers); + assertThat(_complianceViolation,containsString("0.9")); } @Test @@ -130,6 +134,7 @@ public class HttpParserTest HttpParser parser = new HttpParser(handler); parseAll(parser, buffer); Assert.assertEquals("HTTP/0.9 not supported", _bad); + Assert.assertNull(_complianceViolation); } @Test @@ -146,6 +151,7 @@ public class HttpParserTest Assert.assertEquals("/222", _uriOrStatus); Assert.assertEquals("HTTP/0.9", _versionOrReason); Assert.assertEquals(-1, _headers); + assertThat(_complianceViolation,containsString("0.9")); } @Test @@ -158,6 +164,7 @@ public class HttpParserTest HttpParser parser = new HttpParser(handler); parseAll(parser, buffer); Assert.assertEquals("HTTP/0.9 not supported", _bad); + Assert.assertNull(_complianceViolation); } @Test @@ -241,7 +248,7 @@ public class HttpParserTest } @Test - public void test2616Continuations() throws Exception + public void testFoldedField2616() throws Exception { ByteBuffer buffer = BufferUtil.toBuffer( "GET / HTTP/1.0\r\n" + @@ -260,10 +267,11 @@ public class HttpParserTest Assert.assertEquals("Name", _hdr[1]); Assert.assertEquals("value extra", _val[1]); Assert.assertEquals(1, _headers); + assertThat(_complianceViolation,containsString("folding")); } @Test - public void test7230NoContinuations() throws Exception + public void testFoldedField7230() throws Exception { ByteBuffer buffer = BufferUtil.toBuffer( "GET / HTTP/1.0\r\n" + @@ -277,52 +285,44 @@ public class HttpParserTest parseAll(parser, buffer); Assert.assertThat(_bad, Matchers.notNullValue()); - Assert.assertThat(_bad, Matchers.containsString("Bad Continuation")); + Assert.assertThat(_bad, Matchers.containsString("Header Folding")); + Assert.assertNull(_complianceViolation); } - + @Test - public void test7230NoWhiteSpaceInName() throws Exception + public void testWhiteSpaceInName() throws Exception { ByteBuffer buffer = BufferUtil.toBuffer( - "GET / HTTP/1.0\r\n" + - "Host: localhost\r\n" + - " Name: value\r\n" + - "\r\n"); - - HttpParser.RequestHandler handler = new Handler(); - HttpParser parser = new HttpParser(handler, HttpCompliance.RFC7230); - parseAll(parser, buffer); - - Assert.assertThat(_bad, Matchers.notNullValue()); - Assert.assertThat(_bad, Matchers.containsString("Bad")); - - init(); - buffer = BufferUtil.toBuffer( "GET / HTTP/1.0\r\n" + "Host: localhost\r\n" + "N ame: value\r\n" + "\r\n"); - handler = new Handler(); - parser = new HttpParser(handler); + HttpParser.RequestHandler handler = new Handler(); + HttpParser parser = new HttpParser(handler, 4096, HttpCompliance.RFC7230); parseAll(parser, buffer); + Assert.assertThat(_bad, Matchers.notNullValue()); Assert.assertThat(_bad, Matchers.containsString("Illegal character")); - - init(); - buffer = BufferUtil.toBuffer( + } + + @Test + public void testWhiteSpaceAfterName() throws Exception + { + ByteBuffer buffer = BufferUtil.toBuffer( "GET / HTTP/1.0\r\n" + "Host: localhost\r\n" + "Name : value\r\n" + "\r\n"); - handler = new Handler(); - parser = new HttpParser(handler); + HttpParser.RequestHandler handler = new Handler(); + HttpParser parser = new HttpParser(handler, 4096, HttpCompliance.RFC7230); parseAll(parser, buffer); + Assert.assertThat(_bad, Matchers.notNullValue()); Assert.assertThat(_bad, Matchers.containsString("Illegal character")); } - + @Test public void testNoValue() throws Exception { @@ -330,12 +330,11 @@ public class HttpParserTest "GET / HTTP/1.0\r\n" + "Host: localhost\r\n" + "Name0: \r\n" + - "Name1: \r\n" + - "Connection: close\r\n" + + "Name1:\r\n" + "\r\n"); HttpParser.RequestHandler handler = new Handler(); - HttpParser parser = new HttpParser(handler, HttpCompliance.RFC2616); + HttpParser parser = new HttpParser(handler); parseAll(parser, buffer); Assert.assertTrue(_headerCompleted); @@ -349,11 +348,55 @@ public class HttpParserTest Assert.assertEquals("", _val[1]); Assert.assertEquals("Name1", _hdr[2]); Assert.assertEquals("", _val[2]); - Assert.assertEquals("Connection", _hdr[3]); - Assert.assertEquals("close", _val[3]); - Assert.assertEquals(3, _headers); + Assert.assertEquals(2, _headers); } + @Test + public void testNoColon2616() throws Exception + { + ByteBuffer buffer = BufferUtil.toBuffer( + "GET / HTTP/1.0\r\n" + + "Host: localhost\r\n" + + "Name\r\n" + + "Other: value\r\n" + + "\r\n"); + + HttpParser.RequestHandler handler = new Handler(); + HttpParser parser = new HttpParser(handler,HttpCompliance.RFC2616); + parseAll(parser, buffer); + + Assert.assertTrue(_headerCompleted); + Assert.assertTrue(_messageCompleted); + Assert.assertEquals("GET", _methodOrVersion); + Assert.assertEquals("/", _uriOrStatus); + Assert.assertEquals("HTTP/1.0", _versionOrReason); + Assert.assertEquals("Host", _hdr[0]); + Assert.assertEquals("localhost", _val[0]); + Assert.assertEquals("Name", _hdr[1]); + Assert.assertEquals("", _val[1]); + Assert.assertEquals("Other", _hdr[2]); + Assert.assertEquals("value", _val[2]); + Assert.assertEquals(2, _headers); + assertThat(_complianceViolation,containsString("name only")); + } + + @Test + public void testNoColon7230() throws Exception + { + ByteBuffer buffer = BufferUtil.toBuffer( + "GET / HTTP/1.0\r\n" + + "Host: localhost\r\n" + + "Name\r\n" + + "\r\n"); + + HttpParser.RequestHandler handler = new Handler(); + HttpParser parser = new HttpParser(handler,HttpCompliance.RFC7230); + parseAll(parser, buffer); + Assert.assertThat(_bad, Matchers.containsString("Illegal character")); + Assert.assertNull(_complianceViolation); + } + + @Test public void testHeaderParseDirect() throws Exception { @@ -607,7 +650,7 @@ public class HttpParserTest } @Test - public void testNonStrict() throws Exception + public void testCaseInsensitive() throws Exception { ByteBuffer buffer = BufferUtil.toBuffer( "get / http/1.0\r\n" + @@ -626,10 +669,11 @@ public class HttpParserTest Assert.assertEquals("Connection", _hdr[1]); Assert.assertEquals("close", _val[1]); Assert.assertEquals(1, _headers); + Assert.assertNull(_complianceViolation); } @Test - public void testStrict() throws Exception + public void testCaseSensitiveLegacy() throws Exception { ByteBuffer buffer = BufferUtil.toBuffer( "gEt / http/1.0\r\n" + @@ -648,6 +692,7 @@ public class HttpParserTest Assert.assertEquals("cOnNeCtIoN", _hdr[1]); Assert.assertEquals("ClOsE", _val[1]); Assert.assertEquals(1, _headers); + assertThat(_complianceViolation,containsString("case sensitive")); } @Test @@ -1732,44 +1777,6 @@ public class HttpParserTest Assert.assertEquals(null, _bad); } - @Test(expected = BadMessageException.class) - public void test7230HeaderValueWithNewLine() throws Exception - { - testHeaderValueWithNewLine(HttpCompliance.RFC7230); - } - - @Test - public void test2616HeaderValueWithNewLine() throws Exception - { - testHeaderValueWithNewLine(HttpCompliance.RFC2616); - } - - private void testHeaderValueWithNewLine(HttpCompliance compliance) throws Exception - { - ByteBuffer buffer= BufferUtil.toBuffer( - "GET / HTTP/1.1\r\n" + - "Host: localhost\r\n" + - "Header: va\r\n\tlue\r\n"+ - "\r\n"); - - HttpParser.RequestHandler handler = new Handler(); - HttpParser parser= new HttpParser(handler,compliance); - parseAll(parser,buffer); - - if (_bad != null) - throw new BadMessageException(_bad); - - Assert.assertTrue(_headerCompleted); - Assert.assertTrue(_messageCompleted); - Assert.assertEquals("GET", _methodOrVersion); - Assert.assertEquals("/", _uriOrStatus); - Assert.assertEquals("HTTP/1.1", _versionOrReason); - Assert.assertEquals("Host",_hdr[0]); - Assert.assertEquals("localhost",_val[0]); - Assert.assertEquals("Header",_hdr[1]); - Assert.assertEquals("va lue",_val[1]); - } - @Before public void init() { @@ -1783,6 +1790,7 @@ public class HttpParserTest _headers = 0; _headerCompleted = false; _messageCompleted = false; + _complianceViolation = null; } private String _host; @@ -1799,8 +1807,9 @@ public class HttpParserTest private boolean _early; private boolean _headerCompleted; private boolean _messageCompleted; + private String _complianceViolation; - private class Handler implements HttpParser.RequestHandler, HttpParser.ResponseHandler + private class Handler implements HttpParser.RequestHandler, HttpParser.ResponseHandler, HttpParser.ComplianceHandler { private HttpFields fields; @@ -1904,5 +1913,11 @@ public class HttpParserTest { return 512; } + + @Override + public void onComplianceViolation(HttpCompliance compliance, HttpCompliance required, String reason) + { + _complianceViolation=reason; + } } } diff --git a/jetty-http2/http2-server/src/test/java/org/eclipse/jetty/http2/server/HTTP2CServerTest.java b/jetty-http2/http2-server/src/test/java/org/eclipse/jetty/http2/server/HTTP2CServerTest.java index 6a587e37515..57e2de5c569 100644 --- a/jetty-http2/http2-server/src/test/java/org/eclipse/jetty/http2/server/HTTP2CServerTest.java +++ b/jetty-http2/http2-server/src/test/java/org/eclipse/jetty/http2/server/HTTP2CServerTest.java @@ -294,7 +294,7 @@ public class HTTP2CServerTest extends AbstractServerTest @Override public Connection newConnection(Connector connector, EndPoint endPoint) { - HttpConnection connection = new HttpConnection(getHttpConfiguration(), connector, endPoint,getHttpCompliance()) + HttpConnection connection = new HttpConnection(getHttpConfiguration(), connector, endPoint,getHttpCompliance(),isRecordHttpComplianceViolations()) { @Override public void onFillable() diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannelOverHttp.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannelOverHttp.java index 080cd67081e..11131494d68 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannelOverHttp.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannelOverHttp.java @@ -21,9 +21,12 @@ package org.eclipse.jetty.server; import java.io.IOException; import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.List; import org.eclipse.jetty.http.BadMessageException; import org.eclipse.jetty.http.HostPortHttpField; +import org.eclipse.jetty.http.HttpCompliance; import org.eclipse.jetty.http.HttpField; import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.http.HttpGenerator; @@ -43,10 +46,11 @@ import org.eclipse.jetty.util.log.Logger; /** * A HttpChannel customized to be transported over the HTTP/1 protocol */ -class HttpChannelOverHttp extends HttpChannel implements HttpParser.RequestHandler +public class HttpChannelOverHttp extends HttpChannel implements HttpParser.RequestHandler, HttpParser.ComplianceHandler { private static final Logger LOG = Log.getLogger(HttpChannelOverHttp.class); private final static HttpField PREAMBLE_UPGRADE_H2C = new HttpField(HttpHeader.UPGRADE,"h2c"); + private static final String ATTR_COMPLIANCE_VIOLATIONS = "org.eclipse.jetty.http.compliance.violations"; private final HttpFields _fields = new HttpFields(); private final MetaData.Request _metadata = new MetaData.Request(_fields); @@ -57,7 +61,7 @@ class HttpChannelOverHttp extends HttpChannel implements HttpParser.RequestHandl private boolean _unknownExpectation = false; private boolean _expect100Continue = false; private boolean _expect102Processing = false; - + private List _complianceViolations; public HttpChannelOverHttp(HttpConnection httpConnection, Connector connector, HttpConfiguration config, EndPoint endPoint, HttpTransport transport) { @@ -256,6 +260,9 @@ class HttpChannelOverHttp extends HttpChannel implements HttpParser.RequestHandl @Override public boolean headerComplete() { + if(_complianceViolations != null) + this.getRequest().setAttribute(ATTR_COMPLIANCE_VIOLATIONS, _complianceViolations); + boolean persistent; switch (_metadata.getVersion()) @@ -451,4 +458,20 @@ class HttpChannelOverHttp extends HttpChannel implements HttpParser.RequestHandl { return getHttpConfiguration().getHeaderCacheSize(); } + + @Override + public void onComplianceViolation(HttpCompliance compliance,HttpCompliance required, String reason) + { + if (_httpConnection.isRecordHttpComplianceViolations()) + { + if (_complianceViolations == null) + { + _complianceViolations = new ArrayList<>(); + } + String violation = String.format("%s<%s: %s for %s", compliance, required, reason, getHttpTransport()); + _complianceViolations.add(violation); + if (LOG.isDebugEnabled()) + LOG.debug(violation); + } + } } diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConnection.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConnection.java index 29ae4927be2..7e636e6d996 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConnection.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConnection.java @@ -71,6 +71,7 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http private final BlockingReadCallback _blockingReadCallback = new BlockingReadCallback(); private final AsyncReadCallback _asyncReadCallback = new AsyncReadCallback(); private final SendCallback _sendCallback = new SendCallback(); + private final boolean _recordHttpComplianceViolations; /** * Get the current connection that this thread is dispatched to. @@ -91,7 +92,7 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http return last; } - public HttpConnection(HttpConfiguration config, Connector connector, EndPoint endPoint, HttpCompliance compliance) + public HttpConnection(HttpConfiguration config, Connector connector, EndPoint endPoint, HttpCompliance compliance, boolean recordComplianceViolations) { super(endPoint, connector.getExecutor()); _config = config; @@ -101,6 +102,7 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http _channel = newHttpChannel(); _input = _channel.getRequest().getHttpInput(); _parser = newHttpParser(compliance); + _recordHttpComplianceViolations=recordComplianceViolations; if (LOG.isDebugEnabled()) LOG.debug("New HTTP Connection {}", this); } @@ -110,6 +112,11 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http return _config; } + public boolean isRecordHttpComplianceViolations() + { + return _recordHttpComplianceViolations; + } + protected HttpGenerator newHttpGenerator() { return new HttpGenerator(_config.getSendServerVersion(),_config.getSendXPoweredBy()); @@ -117,7 +124,9 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http protected HttpChannelOverHttp newHttpChannel() { - return new HttpChannelOverHttp(this, _connector, _config, getEndPoint(), this); + HttpChannelOverHttp httpChannel = new HttpChannelOverHttp(this, _connector, _config, getEndPoint(), this); + + return httpChannel; } protected HttpParser newHttpParser(HttpCompliance compliance) diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConnectionFactory.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConnectionFactory.java index 8024422adc1..1f3fd8482b0 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConnectionFactory.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConnectionFactory.java @@ -33,6 +33,7 @@ public class HttpConnectionFactory extends AbstractConnectionFactory implements { private final HttpConfiguration _config; private HttpCompliance _httpCompliance; + private boolean _recordHttpComplianceViolations = false; public HttpConnectionFactory() { @@ -65,6 +66,11 @@ public class HttpConnectionFactory extends AbstractConnectionFactory implements return _httpCompliance; } + public boolean isRecordHttpComplianceViolations() + { + return _recordHttpComplianceViolations; + } + /** * @param httpCompliance String value of {@link HttpCompliance} */ @@ -76,8 +82,13 @@ public class HttpConnectionFactory extends AbstractConnectionFactory implements @Override public Connection newConnection(Connector connector, EndPoint endPoint) { - return configure(new HttpConnection(_config, connector, endPoint, _httpCompliance), connector, endPoint); + HttpConnection conn = new HttpConnection(_config, connector, endPoint, _httpCompliance,isRecordHttpComplianceViolations()); + return configure(conn, connector, endPoint); } + public void setRecordHttpComplianceViolations(boolean recordHttpComplianceViolations) + { + this._recordHttpComplianceViolations = recordHttpComplianceViolations; + } } diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/ExtendedServerTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/ExtendedServerTest.java index 64de0e80d80..a4d520eab0a 100644 --- a/jetty-server/src/test/java/org/eclipse/jetty/server/ExtendedServerTest.java +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/ExtendedServerTest.java @@ -56,7 +56,7 @@ public class ExtendedServerTest extends HttpServerTestBase @Override public Connection newConnection(Connector connector, EndPoint endPoint) { - return configure(new ExtendedHttpConnection(getHttpConfiguration(), connector, endPoint,getHttpCompliance()), connector, endPoint); + return configure(new ExtendedHttpConnection(getHttpConfiguration(), connector, endPoint), connector, endPoint); } }) { @@ -94,9 +94,9 @@ public class ExtendedServerTest extends HttpServerTestBase private static class ExtendedHttpConnection extends HttpConnection { - public ExtendedHttpConnection(HttpConfiguration config, Connector connector, EndPoint endPoint, HttpCompliance compliance) + public ExtendedHttpConnection(HttpConfiguration config, Connector connector, EndPoint endPoint) { - super(config,connector,endPoint,compliance); + super(config,connector,endPoint,HttpCompliance.RFC7230,false); } @Override diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/SlowClientWithPipelinedRequestTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/SlowClientWithPipelinedRequestTest.java index 5a953e3b1d2..278c5f252d5 100644 --- a/jetty-server/src/test/java/org/eclipse/jetty/server/SlowClientWithPipelinedRequestTest.java +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/SlowClientWithPipelinedRequestTest.java @@ -54,7 +54,7 @@ public class SlowClientWithPipelinedRequestTest @Override public Connection newConnection(Connector connector, EndPoint endPoint) { - return configure(new HttpConnection(getHttpConfiguration(),connector,endPoint,getHttpCompliance()) + return configure(new HttpConnection(getHttpConfiguration(),connector,endPoint,getHttpCompliance(),isRecordHttpComplianceViolations()) { @Override public void onFillable() diff --git a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/ComplianceViolations2616Test.java b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/ComplianceViolations2616Test.java new file mode 100644 index 00000000000..a5f0b17906d --- /dev/null +++ b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/ComplianceViolations2616Test.java @@ -0,0 +1,191 @@ +// +// ======================================================================== +// 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.servlet; + +import java.io.IOException; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.Collections; +import java.util.EnumSet; +import java.util.List; + +import javax.servlet.DispatcherType; +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.http.HttpCompliance; +import org.eclipse.jetty.server.HttpConfiguration; +import org.eclipse.jetty.server.HttpConnectionFactory; +import org.eclipse.jetty.server.LocalConnector; +import org.eclipse.jetty.server.Server; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; + +import static org.hamcrest.Matchers.containsString; +import static org.junit.Assert.assertThat; + +public class ComplianceViolations2616Test +{ + private static Server server; + private static LocalConnector connector; + + public static class ReportViolationsFilter implements Filter + { + @Override + public void init(FilterConfig filterConfig) throws ServletException + { + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException + { + if (request instanceof HttpServletRequest) + { + List violations = (List) request.getAttribute("org.eclipse.jetty.http.compliance.violations"); + if (violations != null) + { + HttpServletResponse httpResponse = (HttpServletResponse) response; + int i = 0; + for (String violation : violations) + { + httpResponse.setHeader("X-Http-Violation-" + (i++), violation); + } + } + } + chain.doFilter(request, response); + } + + @Override + public void destroy() + { + } + } + + public static class DumpRequestHeadersServlet extends HttpServlet + { + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException + { + resp.setContentType("text/plain"); + PrintWriter out = resp.getWriter(); + List headerNames = new ArrayList<>(); + headerNames.addAll(Collections.list(req.getHeaderNames())); + Collections.sort(headerNames); + for (String name : headerNames) + { + out.printf("[%s] = [%s]%n", name, req.getHeader(name)); + } + } + } + + @BeforeClass + public static void startServer() throws Exception + { + server = new Server(); + + HttpConfiguration config = new HttpConfiguration(); + config.setSendServerVersion(false); + + HttpConnectionFactory httpConnectionFactory = new HttpConnectionFactory(config, HttpCompliance.RFC2616); + httpConnectionFactory.setRecordHttpComplianceViolations(true); + connector = new LocalConnector(server, null, null, null, -1, httpConnectionFactory); + + ServletContextHandler context = new ServletContextHandler(); + context.setContextPath("/"); + context.setWelcomeFiles(new String[]{"index.html", "index.jsp", "index.htm"}); + + context.addServlet(DumpRequestHeadersServlet.class, "/dump/*"); + context.addFilter(ReportViolationsFilter.class, "/*", EnumSet.of(DispatcherType.REQUEST)); + + server.setHandler(context); + server.addConnector(connector); + + server.start(); + } + + @AfterClass + public static void stopServer() throws Exception + { + server.stop(); + server.join(); + } + + @Test + public void testNoColonHeader_Middle() throws Exception + { + StringBuffer req1 = new StringBuffer(); + req1.append("GET /dump/ HTTP/1.1\r\n"); + req1.append("Name\r\n"); + req1.append("Host: local\r\n"); + req1.append("Accept: */*\r\n"); + req1.append("Connection: close\r\n"); + req1.append("\r\n"); + + String response = connector.getResponses(req1.toString()); + assertThat("Response status", response, containsString("HTTP/1.1 200 OK")); + assertThat("Response headers", response, containsString("X-Http-Violation-0: RFC2616