mirror of
https://github.com/jetty/jetty.project.git
synced 2025-03-03 12:29:31 +00:00
Merge pull request #2064 from eclipse/jetty-9.4.x-2022-FineGrainedComplianceModes
#2022 Fine Grained HTTP Compliance Modes
This commit is contained in:
commit
e1e5a1dbd6
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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())
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------------- */
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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>
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
|
@ -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());
|
||||
|
@ -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]"));
|
||||
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user