Issue #3012 refactored HttpCompliance

Signed-off-by: Greg Wilkins <gregw@webtide.com>
This commit is contained in:
Greg Wilkins 2019-03-06 17:20:31 +11:00
parent c63a578c29
commit a652ce20ee
13 changed files with 282 additions and 247 deletions

View File

@ -221,6 +221,13 @@ public class HttpReceiverOverHTTP extends HttpReceiver implements HttpParser.Res
return 4096;
}
@Override
public boolean isHeaderCacheCaseSensitive()
{
// TODO get from configuration
return false;
}
@Override
public boolean startResponse(HttpVersion version, int status, String reason)
{

View File

@ -157,6 +157,13 @@ public class ResponseContentParser extends StreamContentParser
return 4096;
}
@Override
public boolean isHeaderCacheCaseSensitive()
{
// TODO get from configuration
return false;
}
@Override
public boolean startResponse(HttpVersion version, int status, String reason)
{

View File

@ -19,99 +19,127 @@
package org.eclipse.jetty.http;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import static java.util.Arrays.asList;
import static java.util.Collections.unmodifiableSet;
import static java.util.EnumSet.allOf;
import static java.util.EnumSet.complementOf;
import static java.util.EnumSet.copyOf;
import static java.util.EnumSet.noneOf;
import static java.util.EnumSet.of;
/**
* HTTP compliance modes for Jetty HTTP parsing and handling.
* A Compliance mode consists of a set of {@link HttpComplianceSection}s which are applied
* A Compliance mode consists of a set of {@link Violation}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 // TODO in Jetty-10 convert this enum to a class so that extra custom modes can be defined dynamically
public final class HttpCompliance
{
/** A Legacy compliance mode to match jetty's behavior prior to RFC2616 and RFC7230.
*/
LEGACY(sectionsBySpec("0,METHOD_CASE_SENSITIVE")),
/** The legacy RFC2616 support, which incorrectly excludes
* {@link HttpComplianceSection#METHOD_CASE_SENSITIVE},
* {@link HttpComplianceSection#FIELD_COLON},
* {@link HttpComplianceSection#TRANSFER_ENCODING_WITH_CONTENT_LENGTH},
* {@link HttpComplianceSection#MULTIPLE_CONTENT_LENGTHS},
*/
RFC2616_LEGACY(sectionsBySpec("RFC2616,-FIELD_COLON,-METHOD_CASE_SENSITIVE,-TRANSFER_ENCODING_WITH_CONTENT_LENGTH,-MULTIPLE_CONTENT_LENGTHS")),
/** 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"));
// These are compliance violations, which may optionally be allowed by the compliance mode, which mean that
// the relevant section of the RFC is not strictly adhered to.
public enum Violation
{
CASE_SENSITIVE_FIELD_NAME("https://tools.ietf.org/html/rfc7230#section-3.2", "Field name is case-insensitive"),
CASE_INSENSITIVE_METHOD("https://tools.ietf.org/html/rfc7230#section-3.1.1", "Method is case-sensitive"),
HTTP_0_9("https://tools.ietf.org/html/rfc7230#appendix-A.2", "HTTP/0.9 not supported"),
MULTILINE_FIELD_VALUE("https://tools.ietf.org/html/rfc7230#section-3.2.4", "Line Folding not supported"),
MULTIPLE_CONTENT_LENGTHS("https://tools.ietf.org/html/rfc7230#section-3.3.1", "Multiple Content-Lengths"),
TRANSFER_ENCODING_WITH_CONTENT_LENGTH("https://tools.ietf.org/html/rfc7230#section-3.3.1", "Transfer-Encoding and Content-Length"),
WHITESPACE_AFTER_FIELD_NAME("https://tools.ietf.org/html/rfc7230#section-3.2.4", "Whitespace not allowed after field name"),
NO_COLON_AFTER_FIELD_NAME("https://tools.ietf.org/html/rfc7230#section-3.2", "Fields must have a Colon");
public final String url;
public final String description;
Violation(String url, String description)
{
this.url = url;
this.description = description;
}
public boolean isAllowedBy(HttpCompliance compliance)
{
return compliance.isAllowed(this);
}
}
private final String _name;
private final Set<Violation> _violations;
public boolean isAllowed(Violation violation)
{
return _violations.contains(violation);
}
public final static HttpCompliance RFC7230 = new HttpCompliance("RFC7230", noneOf(Violation.class));
public final static HttpCompliance RFC2616 = new HttpCompliance("RFC2616", of(Violation.HTTP_0_9, Violation.MULTILINE_FIELD_VALUE));
public final static HttpCompliance LEGACY = new HttpCompliance("LEGACY", complementOf(of(Violation.CASE_INSENSITIVE_METHOD)));
public final static HttpCompliance RFC2616_LEGACY = RFC2616.with( "RFC2616_LEGACY",
Violation.CASE_INSENSITIVE_METHOD,
Violation.NO_COLON_AFTER_FIELD_NAME,
Violation.TRANSFER_ENCODING_WITH_CONTENT_LENGTH,
Violation.MULTIPLE_CONTENT_LENGTHS);
public final static HttpCompliance RFC7230_LEGACY = RFC7230.with("RFC7230_LEGACY", Violation.CASE_INSENSITIVE_METHOD);
public static final String VIOLATIONS_ATTR = "org.eclipse.jetty.http.compliance.violations";
private static final Logger LOG = Log.getLogger(HttpParser.class);
private static EnumSet<HttpComplianceSection> sectionsByProperty(String property)
private static EnumSet<Violation> violationByProperty(String property)
{
String s = System.getProperty(HttpCompliance.class.getName()+property);
return sectionsBySpec(s==null?"*":s);
return violationBySpec(s==null?"*":s);
}
static EnumSet<HttpComplianceSection> sectionsBySpec(String spec)
/**
* Create violation set from string
* <p>
* @param spec A string in the format of a comma separated list starting with one of the following strings:<dl>
* <dt>0</dt><dd>No {@link Violation}s</dd>
* <dt>*</dt><dd>All {@link Violation}s</dd>
* <dt>RFC2616</dt><dd>The set of {@link Violation}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 Violation}s application to https://tools.ietf.org/html/rfc7230</dd>
* </dl>
* The remainder of the list can contain then names of {@link Violation}s to include them in the mode, or prefixed
* with a '-' to exclude thm from the mode.
* <p>
*/
static EnumSet<Violation> violationBySpec(String spec)
{
EnumSet<HttpComplianceSection> sections;
EnumSet<Violation> sections;
String[] elements = spec.split("\\s*,\\s*");
int i=0;
switch(elements[i])
{
{
case "0":
sections = EnumSet.noneOf(HttpComplianceSection.class);
sections = noneOf(Violation.class);
i++;
break;
case "*":
sections = allOf(Violation.class);
i++;
sections = EnumSet.allOf(HttpComplianceSection.class);
break;
case "RFC2616":
sections = EnumSet.complementOf(EnumSet.of(
HttpComplianceSection.NO_FIELD_FOLDING,
HttpComplianceSection.NO_HTTP_0_9));
sections = copyOf(RFC2616.getAllowed());
i++;
break;
case "RFC7230":
sections = copyOf(RFC7230.getAllowed());
i++;
sections = EnumSet.allOf(HttpComplianceSection.class);
break;
default:
sections = EnumSet.noneOf(HttpComplianceSection.class);
sections = noneOf(Violation.class);
break;
}
@ -121,7 +149,7 @@ public enum HttpCompliance // TODO in Jetty-10 convert this enum to a class so t
boolean exclude = element.startsWith("-");
if (exclude)
element = element.substring(1);
HttpComplianceSection section = HttpComplianceSection.valueOf(element);
Violation section = Violation.valueOf(element);
if (section==null)
{
LOG.warn("Unknown section '"+element+"' in HttpCompliance spec: "+spec);
@ -133,51 +161,48 @@ public enum HttpCompliance // TODO in Jetty-10 convert this enum to a class so t
sections.add(section);
}
return sections;
}
private final static Map<HttpComplianceSection,HttpCompliance> __required = new HashMap<>();
static
private HttpCompliance(String name, Set<Violation> violations)
{
for (HttpComplianceSection section : HttpComplianceSection.values())
{
for (HttpCompliance compliance : HttpCompliance.values())
{
if (compliance.sections().contains(section))
{
__required.put(section,compliance);
break;
}
}
}
Objects.nonNull(violations);
_name = name;
_violations = unmodifiableSet(copyOf(violations));
}
public String getName()
{
return _name;
}
/**
* @param section The section to query
* @return The minimum compliance required to enable the section.
* Get the set of {@link Violation}s allowed by this compliance mode.
* @return The immutable set of {@link Violation}s allowed by this compliance mode.
*/
public static HttpCompliance requiredCompliance(HttpComplianceSection section)
public Set<Violation> getAllowed()
{
return __required.get(section);
return _violations;
}
private final EnumSet<HttpComplianceSection> _sections;
private HttpCompliance(EnumSet<HttpComplianceSection> sections)
public HttpCompliance with(String name, Violation... violations)
{
_sections = sections;
EnumSet<Violation> union = _violations.isEmpty()?EnumSet.noneOf(Violation.class):copyOf(_violations);
union.addAll(copyOf(asList(violations)));
return new HttpCompliance(name, union);
}
/**
* 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()
public HttpCompliance without(String name, Violation... violations)
{
return _sections;
EnumSet<Violation> remainder = _violations.isEmpty()?EnumSet.noneOf(Violation.class):copyOf(_violations);
remainder.removeAll(copyOf(asList(violations)));
return new HttpCompliance(name, remainder);
}
@Override
public String toString()
{
return String.format("%s%s",_name,_violations);
}
}

View File

@ -1,54 +0,0 @@
//
// ========================================================================
// Copyright (c) 1995-2019 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_0_9("https://tools.ietf.org/html/rfc7230#appendix-A.2","No HTTP/0.9"),
TRANSFER_ENCODING_WITH_CONTENT_LENGTH("https://tools.ietf.org/html/rfc7230#section-3.3.1","Transfer-Encoding and Content-Length"),
MULTIPLE_CONTENT_LENGTHS("https://tools.ietf.org/html/rfc7230#section-3.3.1","Multiple Content-Lengths");
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

@ -24,6 +24,7 @@ import java.util.EnumSet;
import java.util.List;
import java.util.Locale;
import org.eclipse.jetty.http.HttpCompliance.Violation;
import org.eclipse.jetty.http.HttpTokens.EndOfContent;
import org.eclipse.jetty.util.ArrayTernaryTrie;
import org.eclipse.jetty.util.ArrayTrie;
@ -33,8 +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.HttpComplianceSection.MULTIPLE_CONTENT_LENGTHS;
import static org.eclipse.jetty.http.HttpComplianceSection.TRANSFER_ENCODING_WITH_CONTENT_LENGTH;
import static org.eclipse.jetty.http.HttpCompliance.Violation.CASE_SENSITIVE_FIELD_NAME;
import static org.eclipse.jetty.http.HttpCompliance.Violation.MULTIPLE_CONTENT_LENGTHS;
import static org.eclipse.jetty.http.HttpCompliance.Violation.TRANSFER_ENCODING_WITH_CONTENT_LENGTH;
/* ------------------------------------------------------------ */
@ -147,7 +149,6 @@ 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;
@ -283,7 +284,6 @@ public class HttpParser
_responseHandler=responseHandler;
_maxHeaderBytes=maxHeaderBytes;
_compliance=compliance;
_compliances=compliance.sections();
_complianceHandler=(ComplianceHandler)(_handler instanceof ComplianceHandler?_handler:null);
}
@ -293,36 +293,35 @@ public class HttpParser
return _handler;
}
/* ------------------------------------------------------------------------------- */
/** Check RFC compliance violation
* @param violation The compliance section violation
* @return True if the current compliance level is set so as to Not allow this violation
*/
protected boolean complianceViolation(HttpComplianceSection violation)
protected void checkViolation(Violation violation) throws BadMessageException
{
return complianceViolation(violation,null);
if (violation.isAllowedBy(_compliance))
{
if (_complianceHandler!=null)
_complianceHandler.onComplianceViolation(_compliance, violation, violation.description);
return;
}
throw new BadMessageException(HttpStatus.BAD_REQUEST_400,violation.description);
}
/* ------------------------------------------------------------------------------- */
/** Check RFC compliance violation
* @param violation The compliance section violation
* @param reason The reason for the violation
* @param violation The compliance violation
* @return True if the current compliance level is set so as to Not allow this violation
*/
protected boolean complianceViolation(HttpComplianceSection violation, String reason)
protected boolean violationAllowed(Violation violation)
{
if (_compliances.contains(violation))
if (violation.isAllowedBy(_compliance))
{
if (_complianceHandler!=null)
_complianceHandler.onComplianceViolation(_compliance, violation, violation.description);
return true;
if (reason==null)
reason=violation.description;
if (_complianceHandler!=null)
_complianceHandler.onComplianceViolation(_compliance,violation,reason);
}
return false;
}
/* ------------------------------------------------------------------------------- */
protected void handleViolation(HttpComplianceSection section,String reason)
protected void handleViolation(Violation section, String reason)
{
if (_complianceHandler!=null)
_complianceHandler.onComplianceViolation(_compliance,section,reason);
@ -330,11 +329,11 @@ public class HttpParser
/* ------------------------------------------------------------------------------- */
protected String caseInsensitiveHeader(String orig, String normative)
{
if (_compliances.contains(HttpComplianceSection.FIELD_NAME_CASE_INSENSITIVE))
{
if (CASE_SENSITIVE_FIELD_NAME.isAllowedBy(_compliance))
return normative;
if (!orig.equals(normative))
handleViolation(HttpComplianceSection.FIELD_NAME_CASE_INSENSITIVE,orig);
handleViolation(CASE_SENSITIVE_FIELD_NAME,orig);
return orig;
}
@ -619,23 +618,22 @@ public class HttpParser
_length=_string.length();
_methodString=takeString();
if (_compliances.contains(HttpComplianceSection.METHOD_CASE_SENSITIVE))
if (Violation.CASE_INSENSITIVE_METHOD.isAllowedBy(_compliance))
{
HttpMethod method=HttpMethod.INSENSITIVE_CACHE.get(_methodString);
if (method!=null)
{
if (!method.asString().equals(_methodString))
handleViolation(Violation.CASE_INSENSITIVE_METHOD,_methodString);
_methodString = method.asString();
}
}
else
{
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);
break;
@ -762,8 +760,7 @@ public class HttpParser
case LF:
// HTTP/0.9
if (complianceViolation(HttpComplianceSection.NO_HTTP_0_9,"No request version"))
throw new BadMessageException("HTTP/0.9 not supported");
checkViolation(Violation.HTTP_0_9);
handle=_requestHandler.startRequest(_methodString,_uri.toString(), HttpVersion.HTTP_0_9);
setState(State.END);
BufferUtil.clear(buffer);
@ -848,9 +845,7 @@ public class HttpParser
else
{
// HTTP/0.9
if (complianceViolation(HttpComplianceSection.NO_HTTP_0_9,"No request version"))
throw new BadMessageException("HTTP/0.9 not supported");
checkViolation(Violation.HTTP_0_9);
handle=_requestHandler.startRequest(_methodString,_uri.toString(), HttpVersion.HTTP_0_9);
setState(State.END);
BufferUtil.clear(buffer);
@ -959,15 +954,14 @@ public class HttpParser
case CONTENT_LENGTH:
if (_hasContentLength)
{
if(complianceViolation(MULTIPLE_CONTENT_LENGTHS))
throw new BadMessageException(HttpStatus.BAD_REQUEST_400,MULTIPLE_CONTENT_LENGTHS.description);
checkViolation(MULTIPLE_CONTENT_LENGTHS);
if (convertContentLength(_valueString)!=_contentLength)
throw new BadMessageException(HttpStatus.BAD_REQUEST_400,MULTIPLE_CONTENT_LENGTHS.description);
}
_hasContentLength = true;
if (_endOfContent == EndOfContent.CHUNKED_CONTENT && complianceViolation(TRANSFER_ENCODING_WITH_CONTENT_LENGTH))
throw new BadMessageException(HttpStatus.BAD_REQUEST_400,"Bad Content-Length");
if (_endOfContent == EndOfContent.CHUNKED_CONTENT)
checkViolation(TRANSFER_ENCODING_WITH_CONTENT_LENGTH);
if (_endOfContent != EndOfContent.CHUNKED_CONTENT)
{
@ -980,8 +974,8 @@ public class HttpParser
break;
case TRANSFER_ENCODING:
if (_hasContentLength && complianceViolation(TRANSFER_ENCODING_WITH_CONTENT_LENGTH))
throw new BadMessageException(HttpStatus.BAD_REQUEST_400,"Transfer-Encoding and Content-Length");
if (_hasContentLength)
checkViolation(TRANSFER_ENCODING_WITH_CONTENT_LENGTH);
if (HttpHeaderValue.CHUNKED.is(_valueString))
{
@ -1008,7 +1002,7 @@ public class HttpParser
if (!(_field instanceof HostPortHttpField) && _valueString!=null && !_valueString.isEmpty())
{
_field=new HostPortHttpField(_header,
_compliances.contains(HttpComplianceSection.FIELD_NAME_CASE_INSENSITIVE)?_header.asString():_headerString,
CASE_SENSITIVE_FIELD_NAME.isAllowedBy(_compliance)?_headerString:_header.asString(),
_valueString);
add_to_connection_trie=_fieldCache!=null;
}
@ -1106,8 +1100,7 @@ public class HttpParser
case SPACE:
case HTAB:
{
if (complianceViolation(HttpComplianceSection.NO_FIELD_FOLDING,_headerString))
throw new BadMessageException(HttpStatus.BAD_REQUEST_400,"Header Folding");
checkViolation(Violation.MULTILINE_FIELD_VALUE);
// header value without name - continuation?
if (_valueString==null || _valueString.isEmpty())
@ -1223,24 +1216,23 @@ public class HttpParser
String n = cached_field.getName();
String v = cached_field.getValue();
if (!_compliances.contains(HttpComplianceSection.FIELD_NAME_CASE_INSENSITIVE))
if (CASE_SENSITIVE_FIELD_NAME.isAllowedBy(_compliance))
{
// Have to get the fields exactly from the buffer to match case
String en = BufferUtil.toString(buffer,buffer.position()-1,n.length(),StandardCharsets.US_ASCII);
if (!n.equals(en))
{
handleViolation(HttpComplianceSection.FIELD_NAME_CASE_INSENSITIVE,en);
handleViolation(CASE_SENSITIVE_FIELD_NAME,en);
n = en;
cached_field = new HttpField(cached_field.getHeader(),n,v);
}
}
if (v!=null && !_compliances.contains(HttpComplianceSection.CASE_INSENSITIVE_FIELD_VALUE_CACHE))
if (v!=null && _handler.isHeaderCacheCaseSensitive())
{
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);
}
@ -1303,7 +1295,7 @@ public class HttpParser
case SPACE:
case HTAB:
//Ignore trailing whitespaces ?
if (!complianceViolation(HttpComplianceSection.NO_WS_AFTER_FIELD_NAME,null))
if (violationAllowed(Violation.WHITESPACE_AFTER_FIELD_NAME))
{
_headerString=takeString();
_header=HttpHeader.CACHE.get(_headerString);
@ -1327,8 +1319,8 @@ public class HttpParser
_valueString="";
_length=-1;
if (!complianceViolation(HttpComplianceSection.FIELD_COLON,_headerString))
{
if (violationAllowed(Violation.NO_COLON_AFTER_FIELD_NAME))
{
setState(FieldState.FIELD);
break;
}
@ -1359,8 +1351,8 @@ public class HttpParser
break;
case LF:
if (!complianceViolation(HttpComplianceSection.FIELD_COLON,_headerString))
{
if (violationAllowed(Violation.NO_COLON_AFTER_FIELD_NAME))
{
setState(FieldState.FIELD);
break;
}
@ -1877,44 +1869,46 @@ public class HttpParser
*/
public interface HttpHandler
{
public boolean content(ByteBuffer item);
boolean content(ByteBuffer item);
public boolean headerComplete();
boolean headerComplete();
public boolean contentComplete();
public boolean messageComplete();
boolean contentComplete();
boolean messageComplete();
/**
* This is the method called by parser when a HTTP Header name and value is found
* @param field The field parsed
*/
public void parsedHeader(HttpField field);
void parsedHeader(HttpField field);
/**
* This is the method called by parser when a HTTP Trailer name and value is found
* @param field The field parsed
*/
public default void parsedTrailer(HttpField field) {}
default void parsedTrailer(HttpField field) {}
/* ------------------------------------------------------------ */
/** Called to signal that an EOF was received unexpectedly
* during the parsing of a HTTP message
*/
public void earlyEOF();
void earlyEOF();
/* ------------------------------------------------------------ */
/** Called to signal that a bad HTTP message has been received.
* @param failure the failure with the bad message information
*/
public default void badMessage(BadMessageException failure)
default void badMessage(BadMessageException failure)
{
}
/* ------------------------------------------------------------ */
/** @return the size in bytes of the per parser header cache
*/
public int getHeaderCacheSize();
int getHeaderCacheSize();
boolean isHeaderCacheCaseSensitive();
}
/* ------------------------------------------------------------------------------- */
@ -1929,7 +1923,7 @@ public class HttpParser
* @param version the http version in use
* @return true if handling parsing should return.
*/
public boolean startRequest(String method, String uri, HttpVersion version);
boolean startRequest(String method, String uri, HttpVersion version);
}
@ -1945,7 +1939,7 @@ public class HttpParser
* @param reason the response reason phrase
* @return true if handling parsing should return
*/
public boolean startResponse(HttpVersion version, int status, String reason);
boolean startResponse(HttpVersion version, int status, String reason);
}
/* ------------------------------------------------------------------------------- */
@ -1953,7 +1947,7 @@ public class HttpParser
/* ------------------------------------------------------------------------------- */
public interface ComplianceHandler extends HttpHandler
{
public default void onComplianceViolation(HttpCompliance compliance, HttpComplianceSection violation, String details)
default void onComplianceViolation(HttpCompliance compliance, HttpCompliance.Violation violation, String details)
{
}

View File

@ -266,6 +266,12 @@ public class HttpGeneratorServerHTTPTest
{
return 4096;
}
@Override
public boolean isHeaderCacheCaseSensitive()
{
return false;
}
}
public final static String CONTENT = "The quick brown fox jumped over the lazy dog.\nNow is the time for all good men to come to the aid of the party\nThe moon is blue to a fish in love.\n";

View File

@ -30,7 +30,10 @@ import org.hamcrest.Matchers;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.eclipse.jetty.http.HttpComplianceSection.NO_FIELD_FOLDING;
import static org.eclipse.jetty.http.HttpCompliance.Violation.TRANSFER_ENCODING_WITH_CONTENT_LENGTH;
import static org.eclipse.jetty.http.HttpCompliance.Violation.CASE_SENSITIVE_FIELD_NAME;
import static org.eclipse.jetty.http.HttpCompliance.Violation.CASE_INSENSITIVE_METHOD;
import static org.eclipse.jetty.http.HttpCompliance.Violation.MULTILINE_FIELD_VALUE;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.containsString;
@ -130,7 +133,7 @@ public class HttpParserTest
assertEquals("/999", _uriOrStatus);
assertEquals("HTTP/0.9", _versionOrReason);
assertEquals(-1, _headers);
assertThat(_complianceViolation, contains(HttpComplianceSection.NO_HTTP_0_9));
assertThat(_complianceViolation, contains(HttpCompliance.Violation.HTTP_0_9));
}
@Test
@ -159,7 +162,7 @@ public class HttpParserTest
assertEquals("/222", _uriOrStatus);
assertEquals("HTTP/0.9", _versionOrReason);
assertEquals(-1, _headers);
assertThat(_complianceViolation, contains(HttpComplianceSection.NO_HTTP_0_9));
assertThat(_complianceViolation, contains(HttpCompliance.Violation.HTTP_0_9));
}
@Test
@ -304,7 +307,7 @@ public class HttpParserTest
assertEquals("value extra", _val[1]);
assertEquals("Name2", _hdr[2]);
assertEquals("value2", _val[2]);
assertThat(_complianceViolation, contains(NO_FIELD_FOLDING,NO_FIELD_FOLDING));
assertThat(_complianceViolation, contains(MULTILINE_FIELD_VALUE,MULTILINE_FIELD_VALUE));
}
@Test
@ -322,7 +325,7 @@ public class HttpParserTest
parseAll(parser, buffer);
assertThat(_bad, Matchers.notNullValue());
assertThat(_bad, containsString("Header Folding"));
assertThat(_bad, containsString("Line Folding not supported"));
assertThat(_complianceViolation,Matchers.empty());
}
@ -807,7 +810,7 @@ public class HttpParserTest
parseAll(parser, buffer);
assertNull(_bad);
assertEquals("GET", _methodOrVersion);
assertThat(_complianceViolation, contains(HttpComplianceSection.METHOD_CASE_SENSITIVE));
assertThat(_complianceViolation, contains(CASE_INSENSITIVE_METHOD));
}
@Test
@ -857,7 +860,7 @@ public class HttpParserTest
"HOST: localhost\r\n" +
"cOnNeCtIoN: ClOsE\r\n" +
"\r\n");
HttpParser.RequestHandler handler = new Handler();
HttpParser.RequestHandler handler = new Handler(true);
HttpParser parser = new HttpParser(handler, -1, HttpCompliance.LEGACY);
parseAll(parser, buffer);
assertNull(_bad);
@ -869,7 +872,7 @@ public class HttpParserTest
assertEquals("cOnNeCtIoN", _hdr[1]);
assertEquals("ClOsE", _val[1]);
assertEquals(1, _headers);
assertThat(_complianceViolation, contains(HttpComplianceSection.FIELD_NAME_CASE_INSENSITIVE,HttpComplianceSection.FIELD_NAME_CASE_INSENSITIVE,HttpComplianceSection.CASE_INSENSITIVE_FIELD_VALUE_CACHE));
assertThat(_complianceViolation, contains(CASE_SENSITIVE_FIELD_NAME, CASE_SENSITIVE_FIELD_NAME));
}
@Test
@ -1862,7 +1865,7 @@ public class HttpParserTest
assertTrue(_headerCompleted);
assertTrue(_messageCompleted);
assertThat(_complianceViolation, contains(HttpComplianceSection.TRANSFER_ENCODING_WITH_CONTENT_LENGTH));
assertThat(_complianceViolation, contains(TRANSFER_ENCODING_WITH_CONTENT_LENGTH));
}
@Test
@ -1891,7 +1894,7 @@ public class HttpParserTest
assertTrue(_headerCompleted);
assertTrue(_messageCompleted);
assertThat(_complianceViolation, contains(HttpComplianceSection.TRANSFER_ENCODING_WITH_CONTENT_LENGTH));
assertThat(_complianceViolation, contains(TRANSFER_ENCODING_WITH_CONTENT_LENGTH));
}
@Test
@ -2185,10 +2188,22 @@ public class HttpParserTest
private boolean _early;
private boolean _headerCompleted;
private boolean _messageCompleted;
private final List<HttpComplianceSection> _complianceViolation = new ArrayList<>();
private final List<HttpCompliance.Violation> _complianceViolation = new ArrayList<>();
private class Handler implements HttpParser.RequestHandler, HttpParser.ResponseHandler, HttpParser.ComplianceHandler
{
private boolean _headerCacheCaseSensitive;
public Handler()
{
this(false);
}
public Handler(boolean headerCacheCaseSensitive)
{
_headerCacheCaseSensitive = headerCacheCaseSensitive;
}
@Override
public boolean content(ByteBuffer ref)
{
@ -2295,7 +2310,13 @@ public class HttpParserTest
}
@Override
public void onComplianceViolation(HttpCompliance compliance, HttpComplianceSection violation, String reason)
public boolean isHeaderCacheCaseSensitive()
{
return _headerCacheCaseSensitive;
}
@Override
public void onComplianceViolation(HttpCompliance compliance, HttpCompliance.Violation violation, String reason)
{
_complianceViolation.add(violation);
}

View File

@ -765,9 +765,9 @@ public class HttpChannel implements Runnable, HttpOutput.Interceptor
public void onBadMessage(BadMessageException failure)
{
int status = failure.getCode();
String reason = failure.getReason();
String message = failure.getReason();
if (status < 400 || status > 599)
failure = new BadMessageException(HttpStatus.BAD_REQUEST_400, reason, failure);
failure = new BadMessageException(HttpStatus.BAD_REQUEST_400, message, failure);
notifyRequestFailure(_request, failure);
@ -793,9 +793,9 @@ public class HttpChannel implements Runnable, HttpOutput.Interceptor
ErrorHandler handler=getServer().getBean(ErrorHandler.class);
if (handler!=null)
content=handler.badMessageError(status,reason,fields);
content=handler.badMessageError(status,message,fields);
sendResponse(new MetaData.Response(HttpVersion.HTTP_1_1,status,reason,fields,BufferUtil.length(content)),content ,true);
sendResponse(new MetaData.Response(HttpVersion.HTTP_1_1,status,null,fields,BufferUtil.length(content)),content ,true);
}
}
catch (IOException e)

View File

@ -27,7 +27,6 @@ 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 +515,13 @@ public class HttpChannelOverHttp extends HttpChannel implements HttpParser.Reque
}
@Override
public void onComplianceViolation(HttpCompliance compliance, HttpComplianceSection violation, String reason)
public boolean isHeaderCacheCaseSensitive()
{
return getHttpConfiguration().isHeaderCacheCaseSensitive();
}
@Override
public void onComplianceViolation(HttpCompliance compliance, HttpCompliance.Violation violation, String details)
{
if (_httpConnection.isRecordHttpComplianceViolations())
{
@ -524,8 +529,8 @@ public class HttpChannelOverHttp extends HttpChannel implements HttpParser.Reque
{
_complianceViolations = new ArrayList<>();
}
String record = String.format("%s (see %s) in mode %s for %s in %s",
violation.getDescription(), violation.getURL(), compliance, reason, getHttpTransport());
String record = String.format("%s (see %s) in mode %s for %s in %s",
violation.description, violation.url, compliance, details, getHttpTransport());
_complianceViolations.add(record);
if (LOG.isDebugEnabled())
LOG.debug(record);

View File

@ -740,13 +740,13 @@ public class HttpChannelState
final Request baseRequest = _channel.getRequest();
int code=HttpStatus.INTERNAL_SERVER_ERROR_500;
String reason=null;
String message=null;
Throwable cause = _channel.unwrap(th,BadMessageException.class,UnavailableException.class);
if (cause instanceof BadMessageException)
{
BadMessageException bme = (BadMessageException)cause;
code = bme.getCode();
reason = bme.getReason();
message = bme.getReason();
}
else if (cause instanceof UnavailableException)
{
@ -768,7 +768,7 @@ public class HttpChannelState
_event.getSuppliedRequest().setAttribute(ERROR_STATUS_CODE,code);
_event.getSuppliedRequest().setAttribute(ERROR_EXCEPTION,th);
_event.getSuppliedRequest().setAttribute(ERROR_EXCEPTION_TYPE,th==null?null:th.getClass());
_event.getSuppliedRequest().setAttribute(ERROR_MESSAGE,reason);
_event.getSuppliedRequest().setAttribute(ERROR_MESSAGE,message);
}
else
{
@ -778,7 +778,7 @@ public class HttpChannelState
baseRequest.setAttribute(ERROR_STATUS_CODE,code);
baseRequest.setAttribute(ERROR_EXCEPTION,th);
baseRequest.setAttribute(RequestDispatcher.ERROR_EXCEPTION_TYPE,th==null?null:th.getClass());
baseRequest.setAttribute(ERROR_MESSAGE,reason);
baseRequest.setAttribute(ERROR_MESSAGE,message);
}
// Are we blocking?

View File

@ -61,6 +61,7 @@ public class HttpConfiguration implements Dumpable
private int _requestHeaderSize=8*1024;
private int _responseHeaderSize=8*1024;
private int _headerCacheSize=4*1024;
private boolean _headerCacheCaseSensitive=false;
private int _securePort;
private long _idleTimeout=-1;
private String _secureScheme = HttpScheme.HTTPS.asString();
@ -197,6 +198,12 @@ public class HttpConfiguration implements Dumpable
return _headerCacheSize;
}
@ManagedAttribute("True if the header field cache is case sensitive")
public boolean isHeaderCacheCaseSensitive()
{
return _headerCacheCaseSensitive;
}
@ManagedAttribute("The port to which Integral or Confidential security constraints are redirected")
public int getSecurePort()
{
@ -390,6 +397,11 @@ public class HttpConfiguration implements Dumpable
_headerCacheSize = headerCacheSize;
}
public void setHeaderCacheCaseSensitive(boolean headerCacheCaseSensitive)
{
this._headerCacheCaseSensitive = headerCacheCaseSensitive;
}
/**
* <p>Sets the TCP/IP port used for CONFIDENTIAL and INTEGRAL redirections.</p>
*

View File

@ -374,7 +374,13 @@ public class LocalConnector extends AbstractConnector
{
return 0;
}
@Override
public boolean isHeaderCacheCaseSensitive()
{
return false;
}
@Override
public void earlyEOF()
{

View File

@ -512,6 +512,12 @@ public class HttpTester
{
return 0;
}
@Override
public boolean isHeaderCacheCaseSensitive()
{
return false;
}
}
public static class Request extends Message implements HttpParser.RequestHandler