Merge pull request #2064 from eclipse/jetty-9.4.x-2022-FineGrainedComplianceModes

#2022 Fine Grained HTTP Compliance Modes
This commit is contained in:
Greg Wilkins 2018-01-11 16:40:21 +01:00 committed by GitHub
commit e1e5a1dbd6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 536 additions and 167 deletions

View File

@ -52,9 +52,11 @@ import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.client.api.Response;
import org.eclipse.jetty.client.http.HttpClientTransportOverHTTP;
import org.eclipse.jetty.client.util.FormContentProvider;
import org.eclipse.jetty.http.HttpCompliance;
import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http.HttpParser;
import org.eclipse.jetty.http.HttpScheme;
import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.io.ClientConnectionFactory;
@ -147,6 +149,7 @@ public class HttpClient extends ContainerLifeCycle
private boolean removeIdleDestinations = false;
private boolean connectBlocking = false;
private String name = getClass().getSimpleName() + "@" + Integer.toHexString(hashCode());
private HttpCompliance httpCompliance = HttpCompliance.RFC7230;
/**
* Creates a {@link HttpClient} instance that can perform requests to non-TLS destinations only
@ -972,6 +975,25 @@ public class HttpClient extends ContainerLifeCycle
{
}
/**
* Gets the http compliance mode for parsing http responses.
* The default http compliance level is {@link HttpCompliance#RFC7230} which is the latest HTTP/1.1 specification
*/
public HttpCompliance getHttpCompliance()
{
return httpCompliance;
}
/**
* Sets the http compliance mode for parsing http responses.
* This affect how weak the {@link HttpParser} parses http responses and which http protocol level is supported
* @param httpCompliance The compliance level which is used to actually parse http responses
*/
public void setHttpCompliance(HttpCompliance httpCompliance)
{
this.httpCompliance = httpCompliance;
}
/**
* @return whether request events must be strictly ordered
* @see #setStrictEventOrdering(boolean)

View File

@ -26,6 +26,7 @@ import org.eclipse.jetty.client.HttpExchange;
import org.eclipse.jetty.client.HttpReceiver;
import org.eclipse.jetty.client.HttpResponse;
import org.eclipse.jetty.client.HttpResponseException;
import org.eclipse.jetty.http.HttpCompliance;
import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http.HttpParser;
@ -38,13 +39,14 @@ import org.eclipse.jetty.util.CompletableCallback;
public class HttpReceiverOverHTTP extends HttpReceiver implements HttpParser.ResponseHandler
{
private final HttpParser parser = new HttpParser(this);
private final HttpParser parser;
private ByteBuffer buffer;
private boolean shutdown;
public HttpReceiverOverHTTP(HttpChannelOverHTTP channel)
{
super(channel);
parser = new HttpParser(this, -1, channel.getHttpDestination().getHttpClient().getHttpCompliance());
}
@Override

View File

@ -20,6 +20,8 @@ package org.eclipse.jetty.client.http;
import java.io.EOFException;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
@ -30,9 +32,9 @@ import org.eclipse.jetty.client.HttpExchange;
import org.eclipse.jetty.client.HttpRequest;
import org.eclipse.jetty.client.HttpResponseException;
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.util.FutureResponseListener;
import org.eclipse.jetty.http.HttpCompliance;
import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpVersion;
@ -44,21 +46,38 @@ import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@RunWith(Parameterized.class)
public class HttpReceiverOverHTTPTest
{
{
@Rule
public final TestTracker tracker = new TestTracker();
@Parameterized.Parameter(0)
public HttpCompliance compliance;
private HttpClient client;
private HttpDestinationOverHTTP destination;
private ByteArrayEndPoint endPoint;
private HttpConnectionOverHTTP connection;
@Parameterized.Parameters
public static Collection<Object[]> parameters() throws Exception
{
return Arrays.asList(
new Object[] { HttpCompliance.LEGACY },
new Object[] { HttpCompliance.RFC2616_LEGACY },
new Object[] { HttpCompliance.RFC7230_LEGACY }
);
}
@Before
public void init() throws Exception
{
client = new HttpClient();
client.setHttpCompliance(compliance);
client.start();
destination = new HttpDestinationOverHTTP(client, new Origin("http", "localhost", 8080));
destination.start();

View File

@ -18,15 +18,175 @@
package org.eclipse.jetty.http;
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:
* <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>
* HTTP compliance modes for Jetty HTTP parsing and handling.
* A Compliance mode consists of a set of {@link HttpComplianceSection}s which are applied
* when the mode is enabled.
* <p>
* Currently the set of modes is an enum and cannot be dynamically extended, but future major releases may convert this
* to a class. To modify modes there are four custom modes that can be modified by setting the property
* <code>org.eclipse.jetty.http.HttpCompliance.CUSTOMn</code> (where 'n' is '0', '1', '2' or '3'), to a comma separated
* list of sections. The list should start with one of the following strings:<dl>
* <dt>0</dt><dd>No {@link HttpComplianceSection}s</dd>
* <dt>*</dt><dd>All {@link HttpComplianceSection}s</dd>
* <dt>RFC2616</dt><dd>The set of {@link HttpComplianceSection}s application to https://tools.ietf.org/html/rfc2616,
* but not https://tools.ietf.org/html/rfc7230</dd>
* <dt>RFC7230</dt><dd>The set of {@link HttpComplianceSection}s application to https://tools.ietf.org/html/rfc7230</dd>
* </dl>
* The remainder of the list can contain then names of {@link HttpComplianceSection}s to include them in the mode, or prefixed
* with a '-' to exclude thm from the mode. Note that Jetty's modes may have some historic minor differences from the strict
* RFC compliance, for example the <code>RFC2616_LEGACY</code> HttpCompliance is defined as
* <code>RFC2616,-FIELD_COLON,-METHOD_CASE_SENSITIVE</code>.
* <p>
* Note also that the {@link EnumSet} return by {@link HttpCompliance#sections()} is mutable, so that modes may
* be altered in code and will affect all usages of the mode.
*/
public enum HttpCompliance { LEGACY, RFC2616, RFC7230 }
public enum HttpCompliance // TODO in Jetty-10 convert this enum to a class so that extra custom modes can be defined dynamically
{
/** A Legacy compliance mode to match jetty's behavior prior to RFC2616 and RFC7230. It only
* contains {@link HttpComplianceSection#METHOD_CASE_SENSITIVE}
*/
LEGACY(sectionsBySpec("0,METHOD_CASE_SENSITIVE")),
/** The legacy RFC2616 support, which incorrectly excludes
* {@link HttpComplianceSection#METHOD_CASE_SENSITIVE}, {@link HttpComplianceSection#FIELD_COLON}
*/
RFC2616_LEGACY(sectionsBySpec("RFC2616,-FIELD_COLON,-METHOD_CASE_SENSITIVE")),
/** The strict RFC2616 support mode */
RFC2616(sectionsBySpec("RFC2616")),
/** Jetty's current RFC7230 support, which incorrectly excludes {@link HttpComplianceSection#METHOD_CASE_SENSITIVE} */
RFC7230_LEGACY(sectionsBySpec("RFC7230,-METHOD_CASE_SENSITIVE")),
/** The RFC7230 support mode */
RFC7230(sectionsBySpec("RFC7230")),
/** Custom compliance mode that can be defined with System property <code>org.eclipse.jetty.http.HttpCompliance.CUSTOM0</code> */
@Deprecated
CUSTOM0(sectionsByProperty("CUSTOM0")),
/** Custom compliance mode that can be defined with System property <code>org.eclipse.jetty.http.HttpCompliance.CUSTOM1</code> */
@Deprecated
CUSTOM1(sectionsByProperty("CUSTOM1")),
/** Custom compliance mode that can be defined with System property <code>org.eclipse.jetty.http.HttpCompliance.CUSTOM2</code> */
@Deprecated
CUSTOM2(sectionsByProperty("CUSTOM2")),
/** Custom compliance mode that can be defined with System property <code>org.eclipse.jetty.http.HttpCompliance.CUSTOM3</code> */
@Deprecated
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 "0":
sections = EnumSet.noneOf(HttpComplianceSection.class);
i++;
break;
case "*":
i++;
sections = EnumSet.allOf(HttpComplianceSection.class);
break;
case "RFC2616":
sections = EnumSet.complementOf(EnumSet.of(
HttpComplianceSection.NO_FIELD_FOLDING,
HttpComplianceSection.NO_HTTP_9));
i++;
break;
case "RFC7230":
i++;
sections = EnumSet.allOf(HttpComplianceSection.class);
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;
}
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;
}
}
}
}
/**
* @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;
}
/**
* Get the set of {@link HttpComplianceSection}s supported by this compliance mode. This set
* is mutable, so it can be modified. Any modification will affect all usages of the mode
* within the same {@link ClassLoader}.
* @return The set of {@link HttpComplianceSection}s supported by this compliance mode.
*/
public EnumSet<HttpComplianceSection> sections()
{
return _sections;
}
}

View File

@ -0,0 +1,53 @@
//
// ========================================================================
// Copyright (c) 1995-2018 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;
/**
*/
public enum HttpComplianceSection
{
CASE_INSENSITIVE_FIELD_VALUE_CACHE("","Use case insensitive field value cache"),
METHOD_CASE_SENSITIVE("https://tools.ietf.org/html/rfc7230#section-3.1.1","Method is case-sensitive"),
FIELD_COLON("https://tools.ietf.org/html/rfc7230#section-3.2","Fields must have a Colon"),
FIELD_NAME_CASE_INSENSITIVE("https://tools.ietf.org/html/rfc7230#section-3.2","Field name is case-insensitive"),
NO_WS_AFTER_FIELD_NAME("https://tools.ietf.org/html/rfc7230#section-3.2.4","Whitespace not allowed after field name"),
NO_FIELD_FOLDING("https://tools.ietf.org/html/rfc7230#section-3.2.4","No line Folding"),
NO_HTTP_9("https://tools.ietf.org/html/rfc7230#appendix-A.2","No HTTP/0.9"),
;
final String url;
final String description;
HttpComplianceSection(String url,String description)
{
this.url = url;
this.description = description;
}
public String getURL()
{
return url;
}
public String getDescription()
{
return description;
}
}

View File

@ -20,6 +20,7 @@ package org.eclipse.jetty.http;
import java.nio.ByteBuffer;
import org.eclipse.jetty.util.ArrayTernaryTrie;
import org.eclipse.jetty.util.ArrayTrie;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.Trie;
@ -132,7 +133,15 @@ public enum HttpMethod
}
/* ------------------------------------------------------------ */
public final static Trie<HttpMethod> CACHE= new ArrayTrie<>();
public final static Trie<HttpMethod> INSENSITIVE_CACHE= new ArrayTrie<>();
static
{
for (HttpMethod method : HttpMethod.values())
INSENSITIVE_CACHE.put(method.toString(),method);
}
/* ------------------------------------------------------------ */
public final static Trie<HttpMethod> CACHE= new ArrayTernaryTrie<>(false);
static
{
for (HttpMethod method : HttpMethod.values())

View File

@ -35,9 +35,6 @@ 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;
@ -82,6 +79,7 @@ import static org.eclipse.jetty.http.HttpTokens.TAB;
* <dl>
* <dt>RFC7230</dt><dd>(default) Compliance with RFC7230</dd>
* <dt>RFC2616</dt><dd>Wrapped headers and HTTP/0.9 supported</dd>
* <dt>WEAK</dt><dd>Wrapped headers, HTTP/0.9 supported and a weaker parsing behaviour</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>
@ -116,6 +114,7 @@ public class HttpParser
IN_NAME,
VALUE,
IN_VALUE,
WS_AFTER_NAME,
}
// States
@ -154,6 +153,7 @@ public class HttpParser
private final ComplianceHandler _complianceHandler;
private final int _maxHeaderBytes;
private final HttpCompliance _compliance;
private final EnumSet<HttpComplianceSection> _compliances;
private HttpField _field;
private HttpHeader _header;
private String _headerString;
@ -191,16 +191,21 @@ public class HttpParser
CACHE.put(new HttpField(HttpHeader.CONNECTION,HttpHeaderValue.UPGRADE));
CACHE.put(new HttpField(HttpHeader.ACCEPT_ENCODING,"gzip"));
CACHE.put(new HttpField(HttpHeader.ACCEPT_ENCODING,"gzip, deflate"));
CACHE.put(new HttpField(HttpHeader.ACCEPT_ENCODING,"gzip, deflate, br"));
CACHE.put(new HttpField(HttpHeader.ACCEPT_ENCODING,"gzip,deflate,sdch"));
CACHE.put(new HttpField(HttpHeader.ACCEPT_LANGUAGE,"en-US,en;q=0.5"));
CACHE.put(new HttpField(HttpHeader.ACCEPT_LANGUAGE,"en-GB,en-US;q=0.8,en;q=0.6"));
CACHE.put(new HttpField(HttpHeader.ACCEPT_LANGUAGE,"en-AU,en;q=0.9,it-IT;q=0.8,it;q=0.7,en-GB;q=0.6,en-US;q=0.5"));
CACHE.put(new HttpField(HttpHeader.ACCEPT_CHARSET,"ISO-8859-1,utf-8;q=0.7,*;q=0.3"));
CACHE.put(new HttpField(HttpHeader.ACCEPT,"*/*"));
CACHE.put(new HttpField(HttpHeader.ACCEPT,"image/png,image/*;q=0.8,*/*;q=0.5"));
CACHE.put(new HttpField(HttpHeader.ACCEPT,"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"));
CACHE.put(new HttpField(HttpHeader.ACCEPT,"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8"));
CACHE.put(new HttpField(HttpHeader.ACCEPT_RANGES,HttpHeaderValue.BYTES));
CACHE.put(new HttpField(HttpHeader.PRAGMA,"no-cache"));
CACHE.put(new HttpField(HttpHeader.CACHE_CONTROL,"private, no-cache, no-cache=Set-Cookie, proxy-revalidate"));
CACHE.put(new HttpField(HttpHeader.CACHE_CONTROL,"no-cache"));
CACHE.put(new HttpField(HttpHeader.CACHE_CONTROL,"max-age=0"));
CACHE.put(new HttpField(HttpHeader.CONTENT_LENGTH,"0"));
CACHE.put(new HttpField(HttpHeader.CONTENT_ENCODING,"gzip"));
CACHE.put(new HttpField(HttpHeader.CONTENT_ENCODING,"deflate"));
@ -237,7 +242,12 @@ public class HttpParser
private static HttpCompliance compliance()
{
Boolean strict = Boolean.getBoolean(__STRICT);
return strict?HttpCompliance.LEGACY:HttpCompliance.RFC7230;
if (strict)
{
LOG.warn("Deprecated property used: "+__STRICT);
return HttpCompliance.LEGACY;
}
return HttpCompliance.RFC7230;
}
/* ------------------------------------------------------------------------------- */
@ -287,23 +297,25 @@ public class HttpParser
/* ------------------------------------------------------------------------------- */
public HttpParser(RequestHandler handler,int maxHeaderBytes,HttpCompliance compliance)
{
_handler=handler;
_requestHandler=handler;
_responseHandler=null;
_maxHeaderBytes=maxHeaderBytes;
_compliance=compliance==null?compliance():compliance;
_complianceHandler=(ComplianceHandler)(handler instanceof ComplianceHandler?handler:null);
this(handler,null,maxHeaderBytes,compliance==null?compliance():compliance);
}
/* ------------------------------------------------------------------------------- */
public HttpParser(ResponseHandler handler,int maxHeaderBytes,HttpCompliance compliance)
{
_handler=handler;
_requestHandler=null;
_responseHandler=handler;
this(null,handler,maxHeaderBytes,compliance==null?compliance():compliance);
}
/* ------------------------------------------------------------------------------- */
private HttpParser(RequestHandler requestHandler,ResponseHandler responseHandler,int maxHeaderBytes,HttpCompliance compliance)
{
_handler=requestHandler!=null?requestHandler:responseHandler;
_requestHandler=requestHandler;
_responseHandler=responseHandler;
_maxHeaderBytes=maxHeaderBytes;
_compliance=compliance==null?compliance():compliance;
_complianceHandler=(ComplianceHandler)(handler instanceof ComplianceHandler?handler:null);
_compliance=compliance;
_compliances=compliance.sections();
_complianceHandler=(ComplianceHandler)(_handler instanceof ComplianceHandler?_handler:null);
}
/* ------------------------------------------------------------------------------- */
@ -314,27 +326,36 @@ public class HttpParser
/* ------------------------------------------------------------------------------- */
/** Check RFC compliance violation
* @param compliance The compliance level violated
* @param violation The compliance section violation
* @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)
protected boolean complianceViolation(HttpComplianceSection violation, String reason)
{
if (_complianceHandler==null)
return _compliance.ordinal()>=compliance.ordinal();
if (_compliance.ordinal()<compliance.ordinal())
{
_complianceHandler.onComplianceViolation(_compliance,compliance,reason);
return false;
}
return true;
if (_compliances.contains(violation))
return true;
if (_complianceHandler!=null)
_complianceHandler.onComplianceViolation(_compliance,violation,reason);
return false;
}
/* ------------------------------------------------------------------------------- */
protected void handleViolation(HttpComplianceSection section,String reason)
{
if (_complianceHandler!=null)
_complianceHandler.onComplianceViolation(_compliance,section,reason);
}
/* ------------------------------------------------------------------------------- */
protected String caseInsensitiveHeader(String orig, String normative)
{
return (_compliance!=LEGACY || orig.equals(normative) || complianceViolation(RFC2616,"https://tools.ietf.org/html/rfc2616#section-4.2 case sensitive header: "+orig))
?normative:orig;
if (_compliances.contains(HttpComplianceSection.FIELD_NAME_CASE_INSENSITIVE))
return normative;
if (!orig.equals(normative))
handleViolation(HttpComplianceSection.FIELD_NAME_CASE_INSENSITIVE,orig);
return orig;
}
/* ------------------------------------------------------------------------------- */
@ -648,26 +669,24 @@ public class HttpParser
_length=_string.length();
_methodString=takeString();
// TODO #1966 This cache lookup is case insensitive when it should be case sensitive by RFC2616, RFC7230
HttpMethod method=HttpMethod.CACHE.get(_methodString);
if (method!=null)
if (_compliances.contains(HttpComplianceSection.METHOD_CASE_SENSITIVE))
{
switch(_compliance)
{
case LEGACY:
// Legacy correctly allows case sensitive header;
break;
case RFC2616:
case RFC7230:
if (!method.asString().equals(_methodString) && _complianceHandler!=null)
_complianceHandler.onComplianceViolation(_compliance,HttpCompliance.LEGACY,
"https://tools.ietf.org/html/rfc7230#section-3.1.1 case insensitive method "+_methodString);
// TODO Good to used cached version for faster equals checking, but breaks case sensitivity because cache is insensitive
_methodString = method.asString();
break;
}
HttpMethod method=HttpMethod.CACHE.get(_methodString);
if (method!=null)
_methodString = method.asString();
}
else
{
HttpMethod method=HttpMethod.INSENSITIVE_CACHE.get(_methodString);
if (method!=null)
{
if (!method.asString().equals(_methodString))
handleViolation(HttpComplianceSection.METHOD_CASE_SENSITIVE,_methodString);
_methodString = method.asString();
}
}
setState(State.SPACE1);
}
else if (b < SPACE)
@ -768,7 +787,7 @@ public class HttpParser
else if (b < HttpTokens.SPACE && b>=0)
{
// HTTP/0.9
if (complianceViolation(RFC7230,"https://tools.ietf.org/html/rfc7230#appendix-A.2 HTTP/0.9"))
if (complianceViolation(HttpComplianceSection.NO_HTTP_9,"No request version"))
throw new BadMessageException("HTTP/0.9 not supported");
handle=_requestHandler.startRequest(_methodString,_uri.toString(), HttpVersion.HTTP_0_9);
setState(State.END);
@ -835,7 +854,7 @@ public class HttpParser
else
{
// HTTP/0.9
if (complianceViolation(RFC7230,"https://tools.ietf.org/html/rfc7230#appendix-A.2 HTTP/0.9"))
if (complianceViolation(HttpComplianceSection.NO_HTTP_9,"No request version"))
throw new BadMessageException("HTTP/0.9 not supported");
handle=_requestHandler.startRequest(_methodString,_uri.toString(), HttpVersion.HTTP_0_9);
@ -953,7 +972,9 @@ public class HttpParser
_host=true;
if (!(_field instanceof HostPortHttpField) && _valueString!=null && !_valueString.isEmpty())
{
_field=new HostPortHttpField(_header,caseInsensitiveHeader(_headerString,_header.asString()),_valueString);
_field=new HostPortHttpField(_header,
_compliances.contains(HttpComplianceSection.FIELD_NAME_CASE_INSENSITIVE)?_header.asString():_headerString,
_valueString);
add_to_connection_trie=_fieldCache!=null;
}
break;
@ -1050,7 +1071,7 @@ public class HttpParser
case HttpTokens.SPACE:
case HttpTokens.TAB:
{
if (complianceViolation(RFC7230,"https://tools.ietf.org/html/rfc7230#section-3.2.4 folding"))
if (complianceViolation(HttpComplianceSection.NO_FIELD_FOLDING,_headerString))
throw new BadMessageException(HttpStatus.BAD_REQUEST_400,"Header Folding");
// header value without name - continuation?
@ -1160,36 +1181,39 @@ public class HttpParser
if (buffer.hasRemaining())
{
// Try a look ahead for the known header name and value.
HttpField field=_fieldCache==null?null:_fieldCache.getBest(buffer,-1,buffer.remaining());
if (field==null)
field=CACHE.getBest(buffer,-1,buffer.remaining());
HttpField cached_field=_fieldCache==null?null:_fieldCache.getBest(buffer,-1,buffer.remaining());
if (cached_field==null)
cached_field=CACHE.getBest(buffer,-1,buffer.remaining());
if (field!=null)
if (cached_field!=null)
{
final String n;
final String v;
String n = cached_field.getName();
String v = cached_field.getValue();
if (_compliance==LEGACY)
if (!_compliances.contains(HttpComplianceSection.FIELD_NAME_CASE_INSENSITIVE))
{
// Have to get the fields exactly from the buffer to match case
String fn=field.getName();
n=caseInsensitiveHeader(BufferUtil.toString(buffer,buffer.position()-1,fn.length(),StandardCharsets.US_ASCII),fn);
String fv=field.getValue();
if (fv==null)
v=null;
else
String en = BufferUtil.toString(buffer,buffer.position()-1,n.length(),StandardCharsets.US_ASCII);
if (!n.equals(en))
{
v=caseInsensitiveHeader(BufferUtil.toString(buffer,buffer.position()+fn.length()+1,fv.length(),StandardCharsets.ISO_8859_1),fv);
field=new HttpField(field.getHeader(),n,v);
handleViolation(HttpComplianceSection.FIELD_NAME_CASE_INSENSITIVE,en);
n = en;
cached_field = new HttpField(cached_field.getHeader(),n,v);
}
}
else
if (v!=null && !_compliances.contains(HttpComplianceSection.CASE_INSENSITIVE_FIELD_VALUE_CACHE))
{
n=field.getName();
v=field.getValue();
String ev = BufferUtil.toString(buffer,buffer.position()+n.length()+1,v.length(),StandardCharsets.ISO_8859_1);
if (!v.equals(ev))
{
handleViolation(HttpComplianceSection.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;
if (v==null)
@ -1209,7 +1233,7 @@ public class HttpParser
if (peek==HttpTokens.CARRIAGE_RETURN || peek==HttpTokens.LINE_FEED)
{
_field=field;
_field=cached_field;
_valueString=v;
setState(FieldState.IN_VALUE);
@ -1238,12 +1262,28 @@ public class HttpParser
_string.setLength(0);
_string.append((char)b);
_length=1;
}
}
break;
case IN_NAME:
if (b>HttpTokens.SPACE && b!=HttpTokens.COLON)
{
if (_header!=null)
{
setString(_header.asString());
_header=null;
_headerString=null;
}
_string.append((char)b);
_length=_string.length();
break;
}
// Fallthrough
case WS_AFTER_NAME:
if (b==HttpTokens.COLON)
{
if (_headerString==null)
@ -1256,23 +1296,8 @@ public class HttpParser
setState(FieldState.VALUE);
break;
}
if (b>HttpTokens.SPACE)
{
if (_header!=null)
{
setString(_header.asString());
_header=null;
_headerString=null;
}
_string.append((char)b);
if (b>HttpTokens.SPACE)
_length=_string.length();
break;
}
if (b==HttpTokens.LINE_FEED && !complianceViolation(RFC7230,"https://tools.ietf.org/html/rfc7230#section-3.2 No colon"))
if (b==HttpTokens.LINE_FEED)
{
if (_headerString==null)
{
@ -1283,7 +1308,17 @@ public class HttpParser
_valueString="";
_length=-1;
setState(FieldState.FIELD);
if (!complianceViolation(HttpComplianceSection.FIELD_COLON,_headerString))
{
setState(FieldState.FIELD);
break;
}
}
//Ignore trailing whitespaces
if (b==HttpTokens.SPACE && !complianceViolation(HttpComplianceSection.NO_WS_AFTER_FIELD_NAME,null))
{
setState(FieldState.WS_AFTER_NAME);
break;
}
@ -1817,7 +1852,13 @@ public class HttpParser
/* ------------------------------------------------------------------------------- */
public interface ComplianceHandler extends HttpHandler
{
public void onComplianceViolation(HttpCompliance compliance,HttpCompliance required,String reason);
@Deprecated
public default void onComplianceViolation(HttpCompliance compliance, HttpCompliance required, String reason) {}
public default void onComplianceViolation(HttpCompliance compliance, HttpComplianceSection violation, String details)
{
onComplianceViolation(compliance,HttpCompliance.requiredCompliance(violation), details);
}
}
/* ------------------------------------------------------------------------------- */

View File

@ -18,9 +18,12 @@
package org.eclipse.jetty.http;
import static org.hamcrest.Matchers.contains;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.eclipse.jetty.http.HttpParser.State;
@ -33,6 +36,11 @@ import org.junit.Test;
public class HttpParserTest
{
static
{
HttpCompliance.CUSTOM0.sections().remove(HttpComplianceSection.NO_WS_AFTER_FIELD_NAME);
}
/**
* Parse until {@link State#END} state.
* If the parser is already in the END state, then it is {@link HttpParser#reset()} and re-parsed.
@ -112,7 +120,7 @@ public class HttpParserTest
ByteBuffer buffer = BufferUtil.toBuffer("GET /999\r\n");
HttpParser.RequestHandler handler = new Handler();
HttpParser parser = new HttpParser(handler, HttpCompliance.RFC2616);
HttpParser parser = new HttpParser(handler, HttpCompliance.RFC2616_LEGACY);
parseAll(parser, buffer);
Assert.assertNull(_bad);
@ -120,7 +128,7 @@ public class HttpParserTest
Assert.assertEquals("/999", _uriOrStatus);
Assert.assertEquals("HTTP/0.9", _versionOrReason);
Assert.assertEquals(-1, _headers);
Assert.assertThat(_complianceViolation, Matchers.containsString("0.9"));
Assert.assertThat(_complianceViolation, contains(HttpComplianceSection.NO_HTTP_9));
}
@Test
@ -132,7 +140,7 @@ public class HttpParserTest
HttpParser parser = new HttpParser(handler);
parseAll(parser, buffer);
Assert.assertEquals("HTTP/0.9 not supported", _bad);
Assert.assertNull(_complianceViolation);
Assert.assertThat(_complianceViolation,Matchers.empty());
}
@Test
@ -141,7 +149,7 @@ public class HttpParserTest
ByteBuffer buffer = BufferUtil.toBuffer("POST /222 \r\n");
HttpParser.RequestHandler handler = new Handler();
HttpParser parser = new HttpParser(handler, HttpCompliance.RFC2616);
HttpParser parser = new HttpParser(handler, HttpCompliance.RFC2616_LEGACY);
parseAll(parser, buffer);
Assert.assertNull(_bad);
@ -149,7 +157,7 @@ public class HttpParserTest
Assert.assertEquals("/222", _uriOrStatus);
Assert.assertEquals("HTTP/0.9", _versionOrReason);
Assert.assertEquals(-1, _headers);
Assert.assertThat(_complianceViolation, Matchers.containsString("0.9"));
Assert.assertThat(_complianceViolation, contains(HttpComplianceSection.NO_HTTP_9));
}
@Test
@ -162,7 +170,7 @@ public class HttpParserTest
HttpParser parser = new HttpParser(handler);
parseAll(parser, buffer);
Assert.assertEquals("HTTP/0.9 not supported", _bad);
Assert.assertNull(_complianceViolation);
Assert.assertThat(_complianceViolation,Matchers.empty());
}
@Test
@ -256,7 +264,7 @@ public class HttpParserTest
"\r\n");
HttpParser.RequestHandler handler = new Handler();
HttpParser parser = new HttpParser(handler, HttpCompliance.RFC2616);
HttpParser parser = new HttpParser(handler, HttpCompliance.RFC2616_LEGACY);
parseAll(parser, buffer);
Assert.assertThat(_bad, Matchers.nullValue());
@ -265,7 +273,7 @@ public class HttpParserTest
Assert.assertEquals("Name", _hdr[1]);
Assert.assertEquals("value extra", _val[1]);
Assert.assertEquals(1, _headers);
Assert.assertThat(_complianceViolation, Matchers.containsString("folding"));
Assert.assertThat(_complianceViolation, contains(HttpComplianceSection.NO_FIELD_FOLDING));
}
@Test
@ -279,12 +287,12 @@ public class HttpParserTest
"\r\n");
HttpParser.RequestHandler handler = new Handler();
HttpParser parser = new HttpParser(handler, 4096, HttpCompliance.RFC7230);
HttpParser parser = new HttpParser(handler, 4096, HttpCompliance.RFC7230_LEGACY);
parseAll(parser, buffer);
Assert.assertThat(_bad, Matchers.notNullValue());
Assert.assertThat(_bad, Matchers.containsString("Header Folding"));
Assert.assertNull(_complianceViolation);
Assert.assertThat(_complianceViolation,Matchers.empty());
}
@Test
@ -297,7 +305,7 @@ public class HttpParserTest
"\r\n");
HttpParser.RequestHandler handler = new Handler();
HttpParser parser = new HttpParser(handler, 4096, HttpCompliance.RFC7230);
HttpParser parser = new HttpParser(handler, 4096, HttpCompliance.RFC7230_LEGACY);
parseAll(parser, buffer);
Assert.assertThat(_bad, Matchers.notNullValue());
@ -314,7 +322,7 @@ public class HttpParserTest
"\r\n");
HttpParser.RequestHandler handler = new Handler();
HttpParser parser = new HttpParser(handler, 4096, HttpCompliance.RFC7230);
HttpParser parser = new HttpParser(handler, 4096, HttpCompliance.RFC7230_LEGACY);
parseAll(parser, buffer);
Assert.assertThat(_bad, Matchers.notNullValue());
@ -350,34 +358,92 @@ public class HttpParserTest
}
@Test
public void testNoColonLegacy() throws Exception
public void testSpaceinNameCustom0() throws Exception
{
ByteBuffer buffer = BufferUtil.toBuffer(
"GET / HTTP/1.0\r\n" +
"Host: localhost\r\n" +
"Name\r\n" +
"Name with space: value\r\n" +
"Other: value\r\n" +
"\r\n");
HttpParser.RequestHandler handler = new Handler();
HttpParser parser = new HttpParser(handler,HttpCompliance.LEGACY);
HttpParser parser = new HttpParser(handler,HttpCompliance.CUSTOM0);
parseAll(parser, buffer);
Assert.assertThat(_bad, Matchers.containsString("Illegal character"));
Assert.assertThat(_complianceViolation,contains(HttpComplianceSection.NO_WS_AFTER_FIELD_NAME));
}
@Test
public void testNoColonCustom0() 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.CUSTOM0);
parseAll(parser, buffer);
Assert.assertThat(_bad, Matchers.containsString("Illegal character"));
Assert.assertThat(_complianceViolation,contains(HttpComplianceSection.NO_WS_AFTER_FIELD_NAME));
}
@Test
public void testTrailingSpacesInHeaderNameInCustom0Mode() throws Exception
{
ByteBuffer buffer = BufferUtil.toBuffer(
"HTTP/1.1 204 No Content\r\n" +
"Access-Control-Allow-Headers : Origin\r\n" +
"Other: value\r\n" +
"\r\n");
HttpParser.ResponseHandler handler = new Handler();
HttpParser parser = new HttpParser(handler, -1, HttpCompliance.CUSTOM0);
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);
Assert.assertThat(_complianceViolation, Matchers.containsString("No colon"));
Assert.assertEquals("HTTP/1.1", _methodOrVersion);
Assert.assertEquals("204", _uriOrStatus);
Assert.assertEquals("No Content", _versionOrReason);
Assert.assertEquals(null, _content);
Assert.assertEquals(1, _headers);
System.out.println(Arrays.asList(_hdr));
System.out.println(Arrays.asList(_val));
Assert.assertEquals("Access-Control-Allow-Headers", _hdr[0]);
Assert.assertEquals("Origin", _val[0]);
Assert.assertEquals("Other", _hdr[1]);
Assert.assertEquals("value", _val[1]);
Assert.assertThat(_complianceViolation, contains(HttpComplianceSection.NO_WS_AFTER_FIELD_NAME));
}
@Test
public void testTrailingSpacesInHeaderNameNoCustom0() throws Exception
{
ByteBuffer buffer = BufferUtil.toBuffer(
"HTTP/1.1 204 No Content\r\n" +
"Access-Control-Allow-Headers : Origin\r\n" +
"Other: value\r\n" +
"\r\n");
HttpParser.ResponseHandler handler = new Handler();
HttpParser parser = new HttpParser(handler);
parseAll(parser, buffer);
Assert.assertEquals("HTTP/1.1", _methodOrVersion);
Assert.assertEquals("204", _uriOrStatus);
Assert.assertEquals("No Content", _versionOrReason);
Assert.assertThat(_bad, Matchers.containsString("Illegal character 0x20"));
}
@Test
public void testNoColon7230() throws Exception
{
@ -388,10 +454,10 @@ public class HttpParserTest
"\r\n");
HttpParser.RequestHandler handler = new Handler();
HttpParser parser = new HttpParser(handler,HttpCompliance.RFC7230);
HttpParser parser = new HttpParser(handler,HttpCompliance.RFC7230_LEGACY);
parseAll(parser, buffer);
Assert.assertThat(_bad, Matchers.containsString("Illegal character"));
Assert.assertNull(_complianceViolation);
Assert.assertThat(_complianceViolation,Matchers.empty());
}
@ -682,11 +748,11 @@ public class HttpParserTest
"Connection: close\r\n" +
"\r\n");
HttpParser.RequestHandler handler = new Handler();
HttpParser parser = new HttpParser(handler, -1, HttpCompliance.RFC7230);
HttpParser parser = new HttpParser(handler, -1, HttpCompliance.RFC7230_LEGACY);
parseAll(parser, buffer);
Assert.assertNull(_bad);
Assert.assertEquals("GET", _methodOrVersion);
Assert.assertThat(_complianceViolation, Matchers.containsString("case insensitive method gEt"));
Assert.assertThat(_complianceViolation, contains(HttpComplianceSection.METHOD_CASE_SENSITIVE));
}
@Test
@ -702,7 +768,7 @@ public class HttpParserTest
parseAll(parser, buffer);
Assert.assertNull(_bad);
Assert.assertEquals("gEt", _methodOrVersion);
Assert.assertNull(_complianceViolation);
Assert.assertThat(_complianceViolation,Matchers.empty());
}
@Test
@ -714,7 +780,7 @@ public class HttpParserTest
"cOnNeCtIoN: ClOsE\r\n" +
"\r\n");
HttpParser.RequestHandler handler = new Handler();
HttpParser parser = new HttpParser(handler, -1, HttpCompliance.RFC7230);
HttpParser parser = new HttpParser(handler, -1, HttpCompliance.RFC7230_LEGACY);
parseAll(parser, buffer);
Assert.assertNull(_bad);
Assert.assertEquals("GET", _methodOrVersion);
@ -725,7 +791,7 @@ public class HttpParserTest
Assert.assertEquals("Connection", _hdr[1]);
Assert.assertEquals("close", _val[1]);
Assert.assertEquals(1, _headers);
Assert.assertNull(_complianceViolation);
Assert.assertThat(_complianceViolation,Matchers.empty());
}
@Test
@ -748,7 +814,7 @@ public class HttpParserTest
Assert.assertEquals("cOnNeCtIoN", _hdr[1]);
Assert.assertEquals("ClOsE", _val[1]);
Assert.assertEquals(1, _headers);
Assert.assertThat(_complianceViolation, Matchers.containsString("case sensitive"));
Assert.assertThat(_complianceViolation, contains(HttpComplianceSection.FIELD_NAME_CASE_INSENSITIVE,HttpComplianceSection.FIELD_NAME_CASE_INSENSITIVE,HttpComplianceSection.CASE_INSENSITIVE_FIELD_VALUE_CACHE));
}
@Test
@ -1366,10 +1432,10 @@ public class HttpParserTest
@Test
public void testResponseReasonIso8859_1() throws Exception
{
{
ByteBuffer buffer = BufferUtil.toBuffer(
"HTTP/1.1 302 déplacé temporairement\r\n"
+ "Content-Length: 0\r\n"
+ "Content-Length: 0\r\n"
+ "\r\n",StandardCharsets.ISO_8859_1);
HttpParser.ResponseHandler handler = new Handler();
@ -2041,7 +2107,7 @@ public class HttpParserTest
_headers = 0;
_headerCompleted = false;
_messageCompleted = false;
_complianceViolation = null;
_complianceViolation.clear();
}
private String _host;
@ -2059,8 +2125,8 @@ public class HttpParserTest
private boolean _early;
private boolean _headerCompleted;
private boolean _messageCompleted;
private String _complianceViolation;
private final List<HttpComplianceSection> _complianceViolation = new ArrayList<>();
private class Handler implements HttpParser.RequestHandler, HttpParser.ResponseHandler, HttpParser.ComplianceHandler
{
@Override
@ -2147,8 +2213,9 @@ public class HttpParserTest
_methodOrVersion = version.asString();
_uriOrStatus = Integer.toString(status);
_versionOrReason = reason;
_hdr = new String[9];
_val = new String[9];
_headers = -1;
_hdr = new String[10];
_val = new String[10];
_messageCompleted = false;
_headerCompleted = false;
return false;
@ -2167,9 +2234,9 @@ public class HttpParserTest
}
@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

@ -29,7 +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>
<Arg name="compliance"><Call class="org.eclipse.jetty.http.HttpCompliance" name="valueOf"><Arg><Property name="jetty.http.compliance" default="RFC7230_LEGACY"/></Arg></Call></Arg>
</New>
</Item>
</Array>

View File

@ -46,5 +46,5 @@ etc/jetty-http.xml
## Connect Timeout in milliseconds
# jetty.http.connectTimeout=15000
## HTTP Compliance: RFC7230, RFC2616, LEGACY
# jetty.http.compliance=RFC7230
## HTTP Compliance: RFC7230, RFC7230_LEGACY, RFC2616, RFC2616_LEGACY, LEGACY or CUSTOMn
# jetty.http.compliance=RFC7230_LEGACY

View File

@ -27,6 +27,7 @@ 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.HttpComplianceSection;
import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpGenerator;
@ -516,7 +517,7 @@ public class HttpChannelOverHttp extends HttpChannel implements HttpParser.Reque
}
@Override
public void onComplianceViolation(HttpCompliance compliance, HttpCompliance required, String reason)
public void onComplianceViolation(HttpCompliance compliance, HttpComplianceSection violation, String reason)
{
if (_httpConnection.isRecordHttpComplianceViolations())
{
@ -524,10 +525,11 @@ public class HttpChannelOverHttp extends HttpChannel implements HttpParser.Reque
{
_complianceViolations = new ArrayList<>();
}
String violation = String.format("%s<%s: %s for %s", compliance, required, reason, getHttpTransport());
_complianceViolations.add(violation);
String record = String.format("%s (see %s) in mode %s for %s in %s",
violation.getDescription(), violation.getURL(), compliance, reason, getHttpTransport());
_complianceViolations.add(record);
if (LOG.isDebugEnabled())
LOG.debug(violation);
LOG.debug(record);
}
}
}

View File

@ -106,7 +106,7 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http
_channel = newHttpChannel();
_input = _channel.getRequest().getHttpInput();
_parser = newHttpParser(compliance);
_recordHttpComplianceViolations=recordComplianceViolations;
_recordHttpComplianceViolations = recordComplianceViolations;
if (LOG.isDebugEnabled())
LOG.debug("New HTTP Connection {}", this);
}

View File

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

View File

@ -104,7 +104,7 @@ public class ExtendedServerTest extends HttpServerTestBase
{
public ExtendedHttpConnection(HttpConfiguration config, Connector connector, EndPoint endPoint)
{
super(config,connector,endPoint,HttpCompliance.RFC7230,false);
super(config,connector,endPoint,HttpCompliance.RFC7230_LEGACY,false);
}
@Override

View File

@ -245,7 +245,7 @@ public class HttpConnectionTest
@Test
public void test_0_9() throws Exception
{
connector.getConnectionFactory(HttpConnectionFactory.class).setHttpCompliance(HttpCompliance.RFC2616);
connector.getConnectionFactory(HttpConnectionFactory.class).setHttpCompliance(HttpCompliance.RFC2616_LEGACY);
LocalEndPoint endp = connector.executeRequest("GET /R1\n");
endp.waitUntilClosed();
String response=BufferUtil.toString(endp.takeOutput());

View File

@ -110,7 +110,7 @@ public class ComplianceViolations2616Test
HttpConfiguration config = new HttpConfiguration();
config.setSendServerVersion(false);
HttpConnectionFactory httpConnectionFactory = new HttpConnectionFactory(config, HttpCompliance.RFC2616);
HttpConnectionFactory httpConnectionFactory = new HttpConnectionFactory(config, HttpCompliance.RFC2616_LEGACY);
httpConnectionFactory.setRecordHttpComplianceViolations(true);
connector = new LocalConnector(server, null, null, null, -1, httpConnectionFactory);
@ -147,7 +147,7 @@ public class ComplianceViolations2616Test
String response = connector.getResponse(req1.toString());
assertThat("Response status", response, containsString("HTTP/1.1 200 OK"));
assertThat("Response headers", response, containsString("X-Http-Violation-0: RFC2616<RFC7230: https://tools.ietf.org/html/rfc7230#section-3.2 No colon"));
assertThat("Response headers", response, containsString("X-Http-Violation-0: Fields must have a Colon"));
assertThat("Response body", response, containsString("[Name] = []"));
}
@ -164,7 +164,7 @@ public class ComplianceViolations2616Test
String response = connector.getResponse(req1.toString());
assertThat("Response status", response, containsString("HTTP/1.1 200"));
assertThat("Response headers", response, containsString("X-Http-Violation-0: RFC2616<RFC7230: https://tools.ietf.org/html/rfc7230#section-3.2 No colon"));
assertThat("Response headers", response, containsString("X-Http-Violation-0: Fields must have a Colon"));
assertThat("Response body", response, containsString("[Name] = []"));
}
@ -182,7 +182,8 @@ public class ComplianceViolations2616Test
String response = connector.getResponse(req1.toString());
assertThat("Response status", response, containsString("HTTP/1.1 200"));
assertThat("Response headers", response, containsString("X-Http-Violation-0: RFC2616<RFC7230: https://tools.ietf.org/html/rfc7230#section-3.2.4 folding"));
assertThat("Response headers", response, containsString("X-Http-Violation-0: No line Folding"));
assertThat("Response body", response, containsString("[Name] = [Some Value]"));
}
}