Issue #2022 Fine Gramed Compliance Modes

Added HttpComplianceSection and an EnumSet within HttpCompliance

Signed-off-by: Greg Wilkins <gregw@webtide.com>
This commit is contained in:
Greg Wilkins 2017-12-24 14:41:18 +01:00
parent c5fc35fa05
commit d8dead35ae
7 changed files with 227 additions and 143 deletions

View File

@ -32,10 +32,10 @@ import org.eclipse.jetty.client.HttpExchange;
import org.eclipse.jetty.client.HttpRequest; import org.eclipse.jetty.client.HttpRequest;
import org.eclipse.jetty.client.HttpResponseException; import org.eclipse.jetty.client.HttpResponseException;
import org.eclipse.jetty.client.Origin; import org.eclipse.jetty.client.Origin;
import org.eclipse.jetty.client.api.Connection;
import org.eclipse.jetty.client.api.Response; import org.eclipse.jetty.client.api.Response;
import org.eclipse.jetty.client.util.FutureResponseListener; import org.eclipse.jetty.client.util.FutureResponseListener;
import org.eclipse.jetty.http.HttpCompliance; import org.eclipse.jetty.http.HttpCompliance;
import org.eclipse.jetty.http.HttpComplianceSection;
import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.http.HttpVersion;
@ -55,6 +55,11 @@ import org.junit.runners.Parameterized;
@RunWith(Parameterized.class) @RunWith(Parameterized.class)
public class HttpReceiverOverHTTPTest public class HttpReceiverOverHTTPTest
{ {
static
{
HttpCompliance.CUSTOM0.sections().remove(HttpComplianceSection.RFC7230_3_2_4_NO_WS_AFTER_FIELD_NAME);
}
@Rule @Rule
public final TestTracker tracker = new TestTracker(); public final TestTracker tracker = new TestTracker();
@ -71,7 +76,7 @@ public class HttpReceiverOverHTTPTest
{ {
return Arrays.asList( return Arrays.asList(
new Object[] { HttpCompliance.LEGACY }, new Object[] { HttpCompliance.LEGACY },
new Object[] { HttpCompliance.WEAK }, new Object[] { HttpCompliance.CUSTOM0 },
new Object[] { HttpCompliance.RFC2616 }, new Object[] { HttpCompliance.RFC2616 },
new Object[] { HttpCompliance.RFC7230 } new Object[] { HttpCompliance.RFC7230 }
); );
@ -245,7 +250,7 @@ public class HttpReceiverOverHTTPTest
try { try {
Response response = listener.get(5, TimeUnit.SECONDS); Response response = listener.get(5, TimeUnit.SECONDS);
Assert.assertThat(compliance, Matchers.lessThanOrEqualTo(HttpCompliance.WEAK)); Assert.assertThat(compliance, Matchers.lessThanOrEqualTo(HttpCompliance.CUSTOM0));
Assert.assertNotNull(response); Assert.assertNotNull(response);
Assert.assertEquals(200, response.getStatus()); Assert.assertEquals(200, response.getStatus());
Assert.assertEquals("OK", response.getReason()); Assert.assertEquals("OK", response.getReason());
@ -255,7 +260,7 @@ public class HttpReceiverOverHTTPTest
Assert.assertEquals(1, headers.size()); Assert.assertEquals(1, headers.size());
Assert.assertEquals("0", headers.get(HttpHeader.CONTENT_LENGTH)); Assert.assertEquals("0", headers.get(HttpHeader.CONTENT_LENGTH));
} catch (Exception e) { } catch (Exception e) {
Assert.assertThat(compliance, Matchers.greaterThan(HttpCompliance.WEAK)); Assert.assertThat(compliance, Matchers.greaterThan(HttpCompliance.CUSTOM0));
Throwable cause = e.getCause(); Throwable cause = e.getCause();
Assert.assertThat(cause, CoreMatchers.instanceOf(HttpResponseException.class)); Assert.assertThat(cause, CoreMatchers.instanceOf(HttpResponseException.class));
Assert.assertThat(cause.getMessage(), Matchers.containsString("HTTP protocol violation")); Assert.assertThat(cause.getMessage(), Matchers.containsString("HTTP protocol violation"));

View File

@ -19,6 +19,11 @@
package org.eclipse.jetty.http; package org.eclipse.jetty.http;
import java.util.EnumSet; import java.util.EnumSet;
import java.util.HashMap;
import java.util.Map;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
/** /**
* HTTP compliance modes: * HTTP compliance modes:
@ -31,29 +36,122 @@ import java.util.EnumSet;
*/ */
public enum HttpCompliance public enum HttpCompliance
{ {
LEGACY(EnumSet.noneOf(HttpRFC.class)), // TODO in Jetty-10 convert this enum to a class so that extra custom modes can be defined dynamically
RFC2616(EnumSet.complementOf(EnumSet.of( LEGACY(sectionsBySpec("LEGACY")),
HttpRFC.RFC7230_3_2_4_WS_AFTER_FIELD_NAME, RFC2616(sectionsBySpec("RFC2616")),
HttpRFC.RFC7230_3_2_4_NO_FOLDING, RFC7230(sectionsBySpec("RFC7230,-RFC7230_3_1_1_METHOD_CASE_SENSITIVE")), // TODO fix in Jetty-10
HttpRFC.RFC7230_A2_NO_HTTP_9))), CUSTOM0(sectionsByProperty("CUSTOM0")),
RFC7230(EnumSet.allOf(HttpRFC.class)), CUSTOM1(sectionsByProperty("CUSTOM1")),
; CUSTOM2(sectionsByProperty("CUSTOM2")),
CUSTOM3(sectionsByProperty("CUSTOM3"));
private static final Logger LOG = Log.getLogger(HttpParser.class);
private static EnumSet<HttpComplianceSection> sectionsByProperty(String property)
{
String s = System.getProperty(HttpCompliance.class.getName()+property);
return sectionsBySpec(s==null?"*":s);
}
static EnumSet<HttpComplianceSection> sectionsBySpec(String spec)
{
EnumSet<HttpComplianceSection> sections;
String[] elements = spec.split("\\s*,\\s*");
int i=0;
switch(elements[i])
{
case "RFC2616":
sections = EnumSet.complementOf(EnumSet.of(
HttpComplianceSection.RFC7230_3_2_4_NO_WS_AFTER_FIELD_NAME,
HttpComplianceSection.RFC7230_3_2_4_NO_FOLDING,
HttpComplianceSection.RFC7230_A2_NO_HTTP_9));
i++;
break;
case "RFC7230":
case "*":
i++;
sections = EnumSet.allOf(HttpComplianceSection.class);
break;
case "LEGACY":
sections = EnumSet.of(HttpComplianceSection.RFC7230_3_1_1_METHOD_CASE_SENSITIVE);
i++;
break;
case "0":
sections = EnumSet.noneOf(HttpComplianceSection.class);
i++;
break;
default:
sections = EnumSet.noneOf(HttpComplianceSection.class);
break;
}
while(i<elements.length)
{
String element = elements[i++];
boolean exclude = element.startsWith("-");
if (exclude)
element = element.substring(1);
HttpComplianceSection section = HttpComplianceSection.valueOf(element);
if (section==null)
{
LOG.warn("Unknown section '"+element+"' in HttpCompliance spec: "+spec);
continue;
}
if (exclude)
sections.remove(section);
else
sections.add(section);
}
return sections;
}
final EnumSet<HttpRFC> _sections; private final static Map<HttpComplianceSection,HttpCompliance> __required = new HashMap<>();
static
{
for (HttpComplianceSection section : HttpComplianceSection.values())
{
for (HttpCompliance compliance : HttpCompliance.values())
{
if (compliance.sections().contains(section))
{
__required.put(section,compliance);
break;
}
}
}
}
HttpCompliance(EnumSet<HttpRFC> sections) /**
* @param section The section to query
* @return The minimum compliance required to enable the section.
*/
public static HttpCompliance requiredCompliance(HttpComplianceSection section)
{
return __required.get(section);
}
private final EnumSet<HttpComplianceSection> _sections;
private HttpCompliance(EnumSet<HttpComplianceSection> sections)
{ {
_sections = sections; _sections = sections;
} }
public EnumSet<HttpRFC> sections() public EnumSet<HttpComplianceSection> sections()
{ {
return _sections; return _sections;
} }
public EnumSet<HttpRFC> excluding(EnumSet<HttpRFC> exclusions) public EnumSet<HttpComplianceSection> excluding(EnumSet<HttpComplianceSection> exclusions)
{ {
EnumSet<HttpRFC> sections = EnumSet.copyOf(_sections); EnumSet<HttpComplianceSection> sections = EnumSet.copyOf(_sections);
sections.removeAll(exclusions); sections.removeAll(exclusions);
return sections; return sections;
} }

View File

@ -20,18 +20,18 @@ package org.eclipse.jetty.http;
/** /**
*/ */
public enum HttpRFC public enum HttpComplianceSection
{ {
CASE_SENSITIVE_FIELD_VALUES("","case-sensitive field values"), USE_CASE_INSENSITIVE_FIELD_VALUE_CACHE("","Use case insensitive field value cache"),
RFC7230_3_1_1_METHOD_CASE_SENSITIVE("https://tools.ietf.org/html/rfc7230#section-3.1.1","Method is case-sensitive"), RFC7230_3_1_1_METHOD_CASE_SENSITIVE("https://tools.ietf.org/html/rfc7230#section-3.1.1","Method is case-sensitive"),
RFC7230_3_2_FIELD_COLON("https://tools.ietf.org/html/rfc7230#section-3.2","Fields must have a Colon"), RFC7230_3_2_FIELD_COLON("https://tools.ietf.org/html/rfc7230#section-3.2","Fields must have a Colon"),
RFC7230_3_2_CASE_INSENSITIVE_FIELD_NAME("https://tools.ietf.org/html/rfc7230#section-3.2","Field name is case-insensitive"), RFC7230_3_2_CASE_INSENSITIVE_FIELD_NAME("https://tools.ietf.org/html/rfc7230#section-3.2","Field name is case-insensitive"),
RFC7230_3_2_4_WS_AFTER_FIELD_NAME("https://tools.ietf.org/html/rfc7230#section-3.2.4","Whitespace not allowed after field name"), RFC7230_3_2_4_NO_WS_AFTER_FIELD_NAME("https://tools.ietf.org/html/rfc7230#section-3.2.4","Whitespace not allowed after field name"),
RFC7230_3_2_4_NO_FOLDING("https://tools.ietf.org/html/rfc7230#section-3.2.4","No line Folding"), RFC7230_3_2_4_NO_FOLDING("https://tools.ietf.org/html/rfc7230#section-3.2.4","No line Folding"),
RFC7230_A2_NO_HTTP_9("https://tools.ietf.org/html/rfc7230#appendix-A.2","No HTTP/0.9"), RFC7230_A2_NO_HTTP_9("https://tools.ietf.org/html/rfc7230#appendix-A.2","No HTTP/0.9"),
; ;
HttpRFC(String url,String description) HttpComplianceSection(String url,String description)
{ {
} }

View File

@ -35,7 +35,6 @@ import org.eclipse.jetty.util.Utf8StringBuilder;
import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger; 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.RFC2616;
import static org.eclipse.jetty.http.HttpCompliance.RFC7230; import static org.eclipse.jetty.http.HttpCompliance.RFC7230;
import static org.eclipse.jetty.http.HttpTokens.CARRIAGE_RETURN; import static org.eclipse.jetty.http.HttpTokens.CARRIAGE_RETURN;
@ -117,7 +116,7 @@ public class HttpParser
IN_NAME, IN_NAME,
VALUE, VALUE,
IN_VALUE, IN_VALUE,
OWS_AFTER_NAME, WS_AFTER_NAME,
} }
// States // States
@ -155,7 +154,8 @@ public class HttpParser
private final ResponseHandler _responseHandler; private final ResponseHandler _responseHandler;
private final ComplianceHandler _complianceHandler; private final ComplianceHandler _complianceHandler;
private final int _maxHeaderBytes; private final int _maxHeaderBytes;
private final EnumSet<HttpRFC> _compliances; private final HttpCompliance _compliance;
private final EnumSet<HttpComplianceSection> _compliances;
private HttpField _field; private HttpField _field;
private HttpHeader _header; private HttpHeader _header;
private String _headerString; private String _headerString;
@ -294,23 +294,24 @@ public class HttpParser
/* ------------------------------------------------------------------------------- */ /* ------------------------------------------------------------------------------- */
public HttpParser(RequestHandler handler,int maxHeaderBytes,HttpCompliance compliance) public HttpParser(RequestHandler handler,int maxHeaderBytes,HttpCompliance compliance)
{ {
this(handler,null,maxHeaderBytes,(compliance==null?compliance():compliance).sections()); this(handler,null,maxHeaderBytes,compliance==null?compliance():compliance);
} }
/* ------------------------------------------------------------------------------- */ /* ------------------------------------------------------------------------------- */
public HttpParser(ResponseHandler handler,int maxHeaderBytes,HttpCompliance compliance) public HttpParser(ResponseHandler handler,int maxHeaderBytes,HttpCompliance compliance)
{ {
this(null,handler,maxHeaderBytes,(compliance==null?compliance():compliance).sections()); this(null,handler,maxHeaderBytes,compliance==null?compliance():compliance);
} }
/* ------------------------------------------------------------------------------- */ /* ------------------------------------------------------------------------------- */
private HttpParser(RequestHandler requestHandler,ResponseHandler responseHandler,int maxHeaderBytes,EnumSet<HttpRFC> compliances) private HttpParser(RequestHandler requestHandler,ResponseHandler responseHandler,int maxHeaderBytes,HttpCompliance compliance)
{ {
_handler=requestHandler!=null?requestHandler:responseHandler; _handler=requestHandler!=null?requestHandler:responseHandler;
_requestHandler=requestHandler; _requestHandler=requestHandler;
_responseHandler=responseHandler; _responseHandler=responseHandler;
_maxHeaderBytes=maxHeaderBytes; _maxHeaderBytes=maxHeaderBytes;
_compliances=compliances; _compliance=compliance;
_compliances=compliance.sections();
_complianceHandler=(ComplianceHandler)(_handler instanceof ComplianceHandler?_handler:null); _complianceHandler=(ComplianceHandler)(_handler instanceof ComplianceHandler?_handler:null);
} }
@ -322,36 +323,35 @@ public class HttpParser
/* ------------------------------------------------------------------------------- */ /* ------------------------------------------------------------------------------- */
/** Check RFC compliance violation /** Check RFC compliance violation
* @param compliance The compliance level violated * @param violation The compliance section violation
* @param reason The reason for the violation * @param reason The reason for the violation
* @return True if the current compliance level is set so as to Not allow this violation * @return True if the current compliance level is set so as to Not allow this violation
*/ */
protected boolean complianceViolation(HttpCompliance compliance,String reason) protected boolean complianceViolation(HttpComplianceSection violation, String reason)
{ {
if (_complianceHandler==null) if (_compliances.contains(violation))
return _compliance.ordinal()>=compliance.ordinal(); return true;
if (_compliance.ordinal()<compliance.ordinal())
{ if (_complianceHandler!=null)
_complianceHandler.onComplianceViolation(_compliance,compliance,reason); _complianceHandler.onComplianceViolation(_compliance,violation,reason);
return false;
} return false;
return true;
} }
/* ------------------------------------------------------------------------------- */ /* ------------------------------------------------------------------------------- */
protected void handleViolation(HttpRFC section,String reason) protected void handleViolation(HttpComplianceSection section,String reason)
{ {
if (_complianceHandler!=null) if (_complianceHandler!=null)
_complianceHandler.onComplianceViolation(section,reason); _complianceHandler.onComplianceViolation(_compliance,section,reason);
} }
/* ------------------------------------------------------------------------------- */ /* ------------------------------------------------------------------------------- */
protected String caseInsensitiveHeader(String orig, String normative) protected String caseInsensitiveHeader(String orig, String normative)
{ {
if (_compliances.contains(HttpRFC.RFC7230_3_2_CASE_INSENSITIVE_FIELD_NAME)) if (_compliances.contains(HttpComplianceSection.RFC7230_3_2_CASE_INSENSITIVE_FIELD_NAME))
return normative; return normative;
if (orig.equals(normative)) if (!orig.equals(normative))
handleViolation(HttpRFC.RFC7230_3_2_CASE_INSENSITIVE_FIELD_NAME,orig); handleViolation(HttpComplianceSection.RFC7230_3_2_CASE_INSENSITIVE_FIELD_NAME,orig);
return orig; return orig;
} }
@ -666,7 +666,7 @@ public class HttpParser
_length=_string.length(); _length=_string.length();
_methodString=takeString(); _methodString=takeString();
if (!_compliances.contains(HttpRFC.RFC7230_3_1_1_METHOD_CASE_SENSITIVE)) if (_compliances.contains(HttpComplianceSection.RFC7230_3_1_1_METHOD_CASE_SENSITIVE))
{ {
HttpMethod method=HttpMethod.CACHE.get(_methodString); HttpMethod method=HttpMethod.CACHE.get(_methodString);
if (method!=null) if (method!=null)
@ -679,7 +679,7 @@ public class HttpParser
if (method!=null) if (method!=null)
{ {
if (!method.asString().equals(_methodString)) if (!method.asString().equals(_methodString))
handleViolation(HttpRFC.RFC7230_3_1_1_METHOD_CASE_SENSITIVE,_methodString); handleViolation(HttpComplianceSection.RFC7230_3_1_1_METHOD_CASE_SENSITIVE,_methodString);
_methodString = method.asString(); _methodString = method.asString();
} }
} }
@ -784,7 +784,7 @@ public class HttpParser
else if (b < HttpTokens.SPACE && b>=0) else if (b < HttpTokens.SPACE && b>=0)
{ {
// HTTP/0.9 // HTTP/0.9
if (complianceViolation(RFC7230,"https://tools.ietf.org/html/rfc7230#appendix-A.2 HTTP/0.9")) if (complianceViolation(HttpComplianceSection.RFC7230_A2_NO_HTTP_9,"No request version"))
throw new BadMessageException("HTTP/0.9 not supported"); throw new BadMessageException("HTTP/0.9 not supported");
handle=_requestHandler.startRequest(_methodString,_uri.toString(), HttpVersion.HTTP_0_9); handle=_requestHandler.startRequest(_methodString,_uri.toString(), HttpVersion.HTTP_0_9);
setState(State.END); setState(State.END);
@ -851,7 +851,7 @@ public class HttpParser
else else
{ {
// HTTP/0.9 // HTTP/0.9
if (complianceViolation(RFC7230,"https://tools.ietf.org/html/rfc7230#appendix-A.2 HTTP/0.9")) if (complianceViolation(HttpComplianceSection.RFC7230_A2_NO_HTTP_9,"No request version"))
throw new BadMessageException("HTTP/0.9 not supported"); throw new BadMessageException("HTTP/0.9 not supported");
handle=_requestHandler.startRequest(_methodString,_uri.toString(), HttpVersion.HTTP_0_9); handle=_requestHandler.startRequest(_methodString,_uri.toString(), HttpVersion.HTTP_0_9);
@ -969,7 +969,9 @@ public class HttpParser
_host=true; _host=true;
if (!(_field instanceof HostPortHttpField) && _valueString!=null && !_valueString.isEmpty()) if (!(_field instanceof HostPortHttpField) && _valueString!=null && !_valueString.isEmpty())
{ {
_field=new HostPortHttpField(_header,caseInsensitiveHeader(_headerString,_header.asString()),_valueString); _field=new HostPortHttpField(_header,
_compliances.contains(HttpComplianceSection.RFC7230_3_2_CASE_INSENSITIVE_FIELD_NAME)?_header.asString():_headerString,
_valueString);
add_to_connection_trie=_fieldCache!=null; add_to_connection_trie=_fieldCache!=null;
} }
break; break;
@ -1066,7 +1068,7 @@ public class HttpParser
case HttpTokens.SPACE: case HttpTokens.SPACE:
case HttpTokens.TAB: case HttpTokens.TAB:
{ {
if (complianceViolation(RFC7230,"https://tools.ietf.org/html/rfc7230#section-3.2.4 folding")) if (complianceViolation(HttpComplianceSection.RFC7230_3_2_4_NO_FOLDING,"Folded "+_headerString))
throw new BadMessageException(HttpStatus.BAD_REQUEST_400,"Header Folding"); throw new BadMessageException(HttpStatus.BAD_REQUEST_400,"Header Folding");
// header value without name - continuation? // header value without name - continuation?
@ -1176,37 +1178,39 @@ public class HttpParser
if (buffer.hasRemaining()) if (buffer.hasRemaining())
{ {
// Try a look ahead for the known header name and value. // Try a look ahead for the known header name and value.
HttpField field=_fieldCache==null?null:_fieldCache.getBest(buffer,-1,buffer.remaining()); HttpField cached_field=_fieldCache==null?null:_fieldCache.getBest(buffer,-1,buffer.remaining());
if (field==null) if (cached_field==null)
field=CACHE.getBest(buffer,-1,buffer.remaining()); cached_field=CACHE.getBest(buffer,-1,buffer.remaining());
if (field!=null) if (cached_field!=null)
{ {
final String n; String n = cached_field.getName();
final String v; String v = cached_field.getValue();
if (!_compliances.contains(HttpRFC.RFC7230_3_2_CASE_INSENSITIVE_FIELD_NAME)) if (!_compliances.contains(HttpComplianceSection.RFC7230_3_2_CASE_INSENSITIVE_FIELD_NAME))
{ {
// Have to get the fields exactly from the buffer to match case // Have to get the fields exactly from the buffer to match case
String fn=field.getName(); String en = BufferUtil.toString(buffer,buffer.position()-1,n.length(),StandardCharsets.US_ASCII);
n=caseInsensitiveHeader(BufferUtil.toString(buffer,buffer.position()-1,fn.length(),StandardCharsets.US_ASCII),fn); if (!n.equals(en))
String fv=field.getValue();
if (fv==null)
v=null;
else
{ {
XXX; handleViolation(HttpComplianceSection.RFC7230_3_2_CASE_INSENSITIVE_FIELD_NAME,en);
v=BufferUtil.toString(buffer,buffer.position()+fn.length()+1,fv.length(),StandardCharsets.ISO_8859_1); n = en;
field=new HttpField(field.getHeader(),n,v); cached_field = new HttpField(cached_field.getHeader(),n,v);
} }
} }
else
if (v!=null && !_compliances.contains(HttpComplianceSection.USE_CASE_INSENSITIVE_FIELD_VALUE_CACHE))
{ {
n=field.getName(); String ev = BufferUtil.toString(buffer,buffer.position()+n.length()+1,v.length(),StandardCharsets.ISO_8859_1);
v=field.getValue(); if (!v.equals(ev))
{
handleViolation(HttpComplianceSection.USE_CASE_INSENSITIVE_FIELD_VALUE_CACHE,ev+"!="+v);
v = ev;
cached_field = new HttpField(cached_field.getHeader(),n,v);
}
} }
_header=field.getHeader(); _header=cached_field.getHeader();
_headerString=n; _headerString=n;
if (v==null) if (v==null)
@ -1226,7 +1230,7 @@ public class HttpParser
if (peek==HttpTokens.CARRIAGE_RETURN || peek==HttpTokens.LINE_FEED) if (peek==HttpTokens.CARRIAGE_RETURN || peek==HttpTokens.LINE_FEED)
{ {
_field=field; _field=cached_field;
_valueString=v; _valueString=v;
setState(FieldState.IN_VALUE); setState(FieldState.IN_VALUE);
@ -1255,7 +1259,6 @@ public class HttpParser
_string.setLength(0); _string.setLength(0);
_string.append((char)b); _string.append((char)b);
_length=1; _length=1;
} }
} }
break; break;
@ -1276,7 +1279,7 @@ public class HttpParser
} }
// Fallthrough // Fallthrough
case OWS_AFTER_NAME: case WS_AFTER_NAME:
if (b==HttpTokens.COLON) if (b==HttpTokens.COLON)
{ {
if (_headerString==null) if (_headerString==null)
@ -1290,7 +1293,7 @@ public class HttpParser
break; break;
} }
if (b==HttpTokens.LINE_FEED && !complianceViolation(RFC2616,"https://tools.ietf.org/html/rfc2616#section-4.2 No colon")) if (b==HttpTokens.LINE_FEED && !complianceViolation(HttpComplianceSection.RFC7230_3_2_FIELD_COLON,null))
{ {
if (_headerString==null) if (_headerString==null)
{ {
@ -1306,9 +1309,9 @@ public class HttpParser
} }
//Ignore trailing whitespaces //Ignore trailing whitespaces
if (b==HttpTokens.SPACE && !complianceViolation(RFC2616,"https://tools.ietf.org/html/rfc2616#section-4.2 Invalid token in header name")) if (b==HttpTokens.SPACE && !complianceViolation(HttpComplianceSection.RFC7230_3_2_4_NO_WS_AFTER_FIELD_NAME,null))
{ {
setState(FieldState.OWS_AFTER_NAME); setState(FieldState.WS_AFTER_NAME);
break; break;
} }
@ -1843,11 +1846,11 @@ public class HttpParser
public interface ComplianceHandler extends HttpHandler public interface ComplianceHandler extends HttpHandler
{ {
@Deprecated @Deprecated
public void onComplianceViolation(HttpCompliance compliance,HttpCompliance required,String reason); public default void onComplianceViolation(HttpCompliance compliance, HttpCompliance required, String reason) {}
public default void onComplianceViolation(HttpRFC violation, String details) public default void onComplianceViolation(HttpCompliance compliance, HttpComplianceSection violation, String details)
{ {
onComplianceViolation(compliance,HttpCompliance.requiredCompliance(violation), details);
} }
} }

View File

@ -18,6 +18,8 @@
package org.eclipse.jetty.http; package org.eclipse.jetty.http;
import static org.hamcrest.Matchers.contains;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.ArrayList; import java.util.ArrayList;
@ -34,6 +36,11 @@ import org.junit.Test;
public class HttpParserTest public class HttpParserTest
{ {
static
{
HttpCompliance.CUSTOM0.sections().remove(HttpComplianceSection.RFC7230_3_2_4_NO_WS_AFTER_FIELD_NAME);
}
/** /**
* Parse until {@link State#END} state. * Parse until {@link State#END} state.
* If the parser is already in the END state, then it is {@link HttpParser#reset()} and re-parsed. * If the parser is already in the END state, then it is {@link HttpParser#reset()} and re-parsed.
@ -121,7 +128,7 @@ public class HttpParserTest
Assert.assertEquals("/999", _uriOrStatus); Assert.assertEquals("/999", _uriOrStatus);
Assert.assertEquals("HTTP/0.9", _versionOrReason); Assert.assertEquals("HTTP/0.9", _versionOrReason);
Assert.assertEquals(-1, _headers); Assert.assertEquals(-1, _headers);
Assert.assertThat(_complianceViolation, Matchers.containsString("0.9")); Assert.assertThat(_complianceViolation, contains(HttpComplianceSection.RFC7230_A2_NO_HTTP_9));
} }
@Test @Test
@ -133,7 +140,7 @@ public class HttpParserTest
HttpParser parser = new HttpParser(handler); HttpParser parser = new HttpParser(handler);
parseAll(parser, buffer); parseAll(parser, buffer);
Assert.assertEquals("HTTP/0.9 not supported", _bad); Assert.assertEquals("HTTP/0.9 not supported", _bad);
Assert.assertNull(_complianceViolation); Assert.assertThat(_complianceViolation,Matchers.empty());
} }
@Test @Test
@ -150,7 +157,7 @@ public class HttpParserTest
Assert.assertEquals("/222", _uriOrStatus); Assert.assertEquals("/222", _uriOrStatus);
Assert.assertEquals("HTTP/0.9", _versionOrReason); Assert.assertEquals("HTTP/0.9", _versionOrReason);
Assert.assertEquals(-1, _headers); Assert.assertEquals(-1, _headers);
Assert.assertThat(_complianceViolation, Matchers.containsString("0.9")); Assert.assertThat(_complianceViolation, contains(HttpComplianceSection.RFC7230_A2_NO_HTTP_9));
} }
@Test @Test
@ -163,7 +170,7 @@ public class HttpParserTest
HttpParser parser = new HttpParser(handler); HttpParser parser = new HttpParser(handler);
parseAll(parser, buffer); parseAll(parser, buffer);
Assert.assertEquals("HTTP/0.9 not supported", _bad); Assert.assertEquals("HTTP/0.9 not supported", _bad);
Assert.assertNull(_complianceViolation); Assert.assertThat(_complianceViolation,Matchers.empty());
} }
@Test @Test
@ -266,7 +273,7 @@ public class HttpParserTest
Assert.assertEquals("Name", _hdr[1]); Assert.assertEquals("Name", _hdr[1]);
Assert.assertEquals("value extra", _val[1]); Assert.assertEquals("value extra", _val[1]);
Assert.assertEquals(1, _headers); Assert.assertEquals(1, _headers);
Assert.assertThat(_complianceViolation, Matchers.containsString("folding")); Assert.assertThat(_complianceViolation, contains(HttpComplianceSection.RFC7230_3_2_4_NO_FOLDING));
} }
@Test @Test
@ -285,7 +292,7 @@ public class HttpParserTest
Assert.assertThat(_bad, Matchers.notNullValue()); Assert.assertThat(_bad, Matchers.notNullValue());
Assert.assertThat(_bad, Matchers.containsString("Header Folding")); Assert.assertThat(_bad, Matchers.containsString("Header Folding"));
Assert.assertNull(_complianceViolation); Assert.assertThat(_complianceViolation,Matchers.empty());
} }
@Test @Test
@ -351,74 +358,52 @@ public class HttpParserTest
} }
@Test @Test
public void testNoColonWeak() throws Exception public void testSpaceinNameCustom0() throws Exception
{ {
ByteBuffer buffer = BufferUtil.toBuffer( ByteBuffer buffer = BufferUtil.toBuffer(
"GET / HTTP/1.0\r\n" + "GET / HTTP/1.0\r\n" +
"Host: localhost\r\n" + "Host: localhost\r\n" +
"Name\r\n" + "Name with space: value\r\n" +
"Other: value\r\n" + "Other: value\r\n" +
"\r\n"); "\r\n");
HttpParser.RequestHandler handler = new Handler(); HttpParser.RequestHandler handler = new Handler();
HttpParser parser = new HttpParser(handler,HttpCompliance.WEAK); HttpParser parser = new HttpParser(handler,HttpCompliance.CUSTOM0);
parseAll(parser, buffer); parseAll(parser, buffer);
Assert.assertTrue(_headerCompleted); Assert.assertThat(_bad, Matchers.containsString("Illegal character"));
Assert.assertTrue(_messageCompleted); Assert.assertThat(_complianceViolation,contains(HttpComplianceSection.RFC7230_3_2_4_NO_WS_AFTER_FIELD_NAME));
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);
Assert.assertThat(_complianceViolation, Matchers.containsString("No colon"));
} }
@Test @Test
public void testNoColonWithWhitespaceWeak() throws Exception public void testNoColonCustom0() throws Exception
{ {
ByteBuffer buffer = BufferUtil.toBuffer( ByteBuffer buffer = BufferUtil.toBuffer(
"GET / HTTP/1.0\r\n" + "GET / HTTP/1.0\r\n" +
"Host: localhost\r\n" + "Host: localhost\r\n" +
"Name \r\n" + "Name \r\n" +
"Other: value\r\n" + "Other: value\r\n" +
"\r\n"); "\r\n");
HttpParser.RequestHandler handler = new Handler(); HttpParser.RequestHandler handler = new Handler();
HttpParser parser = new HttpParser(handler,HttpCompliance.WEAK); HttpParser parser = new HttpParser(handler,HttpCompliance.CUSTOM0);
parseAll(parser, buffer); parseAll(parser, buffer);
Assert.assertTrue(_headerCompleted); Assert.assertThat(_bad, Matchers.containsString("Illegal character"));
Assert.assertTrue(_messageCompleted); Assert.assertThat(_complianceViolation,contains(HttpComplianceSection.RFC7230_3_2_4_NO_WS_AFTER_FIELD_NAME));
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);
Assert.assertThat(_complianceViolation, Matchers.containsString("No colon"));
} }
@Test @Test
public void testTrailingSpacesInHeaderNameInWeakMode() throws Exception public void testTrailingSpacesInHeaderNameInCustom0Mode() throws Exception
{ {
ByteBuffer buffer = BufferUtil.toBuffer( ByteBuffer buffer = BufferUtil.toBuffer(
"HTTP/1.1 204 No Content\r\n" + "HTTP/1.1 204 No Content\r\n" +
"Access-Control-Allow-Headers : Origin\r\n" + "Access-Control-Allow-Headers : Origin\r\n" +
"Other: value\r\n" + "Other: value\r\n" +
"\r\n"); "\r\n");
HttpParser.ResponseHandler handler = new Handler(); HttpParser.ResponseHandler handler = new Handler();
HttpParser parser = new HttpParser(handler, -1, HttpCompliance.WEAK); HttpParser parser = new HttpParser(handler, -1, HttpCompliance.CUSTOM0);
parseAll(parser, buffer); parseAll(parser, buffer);
Assert.assertTrue(_headerCompleted); Assert.assertTrue(_headerCompleted);
@ -437,11 +422,11 @@ public class HttpParserTest
Assert.assertEquals("Other", _hdr[1]); Assert.assertEquals("Other", _hdr[1]);
Assert.assertEquals("value", _val[1]); Assert.assertEquals("value", _val[1]);
Assert.assertThat(_complianceViolation, Matchers.containsString("Invalid token in header name")); Assert.assertThat(_complianceViolation, contains(HttpComplianceSection.RFC7230_3_2_4_NO_WS_AFTER_FIELD_NAME));
} }
@Test @Test
public void testTrailingSpacesInHeaderNameNoWeak() throws Exception public void testTrailingSpacesInHeaderNameNoCustom0() throws Exception
{ {
ByteBuffer buffer = BufferUtil.toBuffer( ByteBuffer buffer = BufferUtil.toBuffer(
"HTTP/1.1 204 No Content\r\n" + "HTTP/1.1 204 No Content\r\n" +
@ -472,7 +457,7 @@ public class HttpParserTest
HttpParser parser = new HttpParser(handler,HttpCompliance.RFC7230); HttpParser parser = new HttpParser(handler,HttpCompliance.RFC7230);
parseAll(parser, buffer); parseAll(parser, buffer);
Assert.assertThat(_bad, Matchers.containsString("Illegal character")); Assert.assertThat(_bad, Matchers.containsString("Illegal character"));
Assert.assertNull(_complianceViolation); Assert.assertThat(_complianceViolation,Matchers.empty());
} }
@ -767,7 +752,7 @@ public class HttpParserTest
parseAll(parser, buffer); parseAll(parser, buffer);
Assert.assertNull(_bad); Assert.assertNull(_bad);
Assert.assertEquals("GET", _methodOrVersion); Assert.assertEquals("GET", _methodOrVersion);
Assert.assertThat(_complianceViolation, Matchers.containsString("case insensitive method gEt")); Assert.assertThat(_complianceViolation, contains(HttpComplianceSection.RFC7230_3_1_1_METHOD_CASE_SENSITIVE));
} }
@Test @Test
@ -783,7 +768,7 @@ public class HttpParserTest
parseAll(parser, buffer); parseAll(parser, buffer);
Assert.assertNull(_bad); Assert.assertNull(_bad);
Assert.assertEquals("gEt", _methodOrVersion); Assert.assertEquals("gEt", _methodOrVersion);
Assert.assertNull(_complianceViolation); Assert.assertThat(_complianceViolation,Matchers.empty());
} }
@Test @Test
@ -806,7 +791,7 @@ public class HttpParserTest
Assert.assertEquals("Connection", _hdr[1]); Assert.assertEquals("Connection", _hdr[1]);
Assert.assertEquals("close", _val[1]); Assert.assertEquals("close", _val[1]);
Assert.assertEquals(1, _headers); Assert.assertEquals(1, _headers);
Assert.assertNull(_complianceViolation); Assert.assertThat(_complianceViolation,Matchers.empty());
} }
@Test @Test
@ -829,7 +814,7 @@ public class HttpParserTest
Assert.assertEquals("cOnNeCtIoN", _hdr[1]); Assert.assertEquals("cOnNeCtIoN", _hdr[1]);
Assert.assertEquals("ClOsE", _val[1]); Assert.assertEquals("ClOsE", _val[1]);
Assert.assertEquals(1, _headers); Assert.assertEquals(1, _headers);
Assert.assertThat(_complianceViolation, Matchers.containsString("case sensitive")); Assert.assertThat(_complianceViolation, contains(HttpComplianceSection.RFC7230_3_2_CASE_INSENSITIVE_FIELD_NAME,HttpComplianceSection.RFC7230_3_2_CASE_INSENSITIVE_FIELD_NAME,HttpComplianceSection.USE_CASE_INSENSITIVE_FIELD_VALUE_CACHE));
} }
@Test @Test
@ -2122,7 +2107,7 @@ public class HttpParserTest
_headers = 0; _headers = 0;
_headerCompleted = false; _headerCompleted = false;
_messageCompleted = false; _messageCompleted = false;
_complianceViolation = null; _complianceViolation.clear();
} }
private String _host; private String _host;
@ -2140,8 +2125,8 @@ public class HttpParserTest
private boolean _early; private boolean _early;
private boolean _headerCompleted; private boolean _headerCompleted;
private boolean _messageCompleted; private boolean _messageCompleted;
private String _complianceViolation; private final List<HttpComplianceSection> _complianceViolation = new ArrayList<>();
private class Handler implements HttpParser.RequestHandler, HttpParser.ResponseHandler, HttpParser.ComplianceHandler private class Handler implements HttpParser.RequestHandler, HttpParser.ResponseHandler, HttpParser.ComplianceHandler
{ {
@Override @Override
@ -2249,9 +2234,9 @@ public class HttpParserTest
} }
@Override @Override
public void onComplianceViolation(HttpCompliance compliance, HttpCompliance required, String reason) public void onComplianceViolation(HttpCompliance compliance, HttpComplianceSection violation, String reason)
{ {
_complianceViolation=reason; _complianceViolation.add(violation);
} }
} }
} }

View File

@ -1,3 +1,3 @@
org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog
#org.eclipse.jetty.LEVEL=DEBUG #org.eclipse.jetty.LEVEL=DEBUG
#org.eclipse.jetty.server.LEVEL=DEBUG org.eclipse.jetty.server.LEVEL=DEBUG

View File

@ -336,13 +336,6 @@ public class ServerConnector extends AbstractNetworkConnector
return serverChannel; return serverChannel;
} }
@Override
public Future<Void> shutdown()
{
// shutdown all the connections
return super.shutdown();
}
@Override @Override
public void close() public void close()