Merge pull request #3435 from eclipse/jetty-10.0.x-3012-Compliance
Issue #3012 refactored HttpCompliance
This commit is contained in:
commit
67838609c3
|
@ -221,6 +221,13 @@ public class HttpReceiverOverHTTP extends HttpReceiver implements HttpParser.Res
|
||||||
return 4096;
|
return 4096;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isHeaderCacheCaseSensitive()
|
||||||
|
{
|
||||||
|
// TODO get from configuration
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean startResponse(HttpVersion version, int status, String reason)
|
public boolean startResponse(HttpVersion version, int status, String reason)
|
||||||
{
|
{
|
||||||
|
|
|
@ -157,6 +157,13 @@ public class ResponseContentParser extends StreamContentParser
|
||||||
return 4096;
|
return 4096;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isHeaderCacheCaseSensitive()
|
||||||
|
{
|
||||||
|
// TODO get from configuration
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean startResponse(HttpVersion version, int status, String reason)
|
public boolean startResponse(HttpVersion version, int status, String reason)
|
||||||
{
|
{
|
||||||
|
|
|
@ -0,0 +1,48 @@
|
||||||
|
//
|
||||||
|
// ========================================================================
|
||||||
|
// 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;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
public interface ComplianceViolation
|
||||||
|
{
|
||||||
|
String getName();
|
||||||
|
String getURL();
|
||||||
|
String getDescription();
|
||||||
|
|
||||||
|
default boolean isAllowedBy(Mode mode)
|
||||||
|
{
|
||||||
|
return mode.allows(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Mode
|
||||||
|
{
|
||||||
|
String getName();
|
||||||
|
boolean allows(ComplianceViolation violation);
|
||||||
|
Set<? extends ComplianceViolation> getKnown();
|
||||||
|
Set<? extends ComplianceViolation> getAllowed();
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Listener
|
||||||
|
{
|
||||||
|
default void onComplianceViolation(Mode mode, ComplianceViolation violation, String details)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -18,8 +18,100 @@
|
||||||
|
|
||||||
package org.eclipse.jetty.http;
|
package org.eclipse.jetty.http;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.EnumSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import static java.util.Collections.unmodifiableSet;
|
||||||
|
import static java.util.EnumSet.allOf;
|
||||||
|
import static java.util.EnumSet.copyOf;
|
||||||
|
import static java.util.EnumSet.noneOf;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The compliance for Cookie handling.
|
* The compliance for Cookie handling.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public enum CookieCompliance { RFC6265, RFC2965 }
|
public class CookieCompliance implements ComplianceViolation.Mode
|
||||||
|
{
|
||||||
|
enum Violation implements ComplianceViolation
|
||||||
|
{
|
||||||
|
COMMA_NOT_VALID_OCTET("https://tools.ietf.org/html/rfc6265#section-4.1.1", "Comma not valid as cookie-octet or separator"),
|
||||||
|
RESERVED_NAMES_NOT_DOLLAR_PREFIXED("https://tools.ietf.org/html/rfc6265#section-4.1.1","Reserved names no longer use '$' prefix")
|
||||||
|
;
|
||||||
|
|
||||||
|
private final String url;
|
||||||
|
private final String description;
|
||||||
|
|
||||||
|
Violation(String url, String description)
|
||||||
|
{
|
||||||
|
this.url = url;
|
||||||
|
this.description = description;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName()
|
||||||
|
{
|
||||||
|
return name();
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public String getURL()
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDescription()
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final CookieCompliance RFC6265 = new CookieCompliance("RFC6265", noneOf(Violation.class));
|
||||||
|
public static final CookieCompliance RFC2965 = new CookieCompliance("RFC2965", allOf(Violation.class));
|
||||||
|
|
||||||
|
private final static List<CookieCompliance> KNOWN_MODES = Arrays.asList(RFC6265,RFC2965);
|
||||||
|
|
||||||
|
public static CookieCompliance valueOf(String name)
|
||||||
|
{
|
||||||
|
for (CookieCompliance compliance : KNOWN_MODES)
|
||||||
|
if (compliance.getName().equals(name))
|
||||||
|
return compliance;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private final String _name;
|
||||||
|
private final Set<Violation> _violations;
|
||||||
|
|
||||||
|
private CookieCompliance(String name, Set<Violation> violations)
|
||||||
|
{
|
||||||
|
Objects.nonNull(violations);
|
||||||
|
_name = name;
|
||||||
|
_violations = unmodifiableSet(copyOf(violations));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean allows(ComplianceViolation violation)
|
||||||
|
{
|
||||||
|
return _violations.contains(violation);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName()
|
||||||
|
{
|
||||||
|
return _name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<Violation> getKnown()
|
||||||
|
{
|
||||||
|
return EnumSet.allOf(Violation.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<Violation> getAllowed()
|
||||||
|
{
|
||||||
|
return _violations;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -24,17 +24,22 @@ import java.util.Locale;
|
||||||
import org.eclipse.jetty.util.log.Log;
|
import org.eclipse.jetty.util.log.Log;
|
||||||
import org.eclipse.jetty.util.log.Logger;
|
import org.eclipse.jetty.util.log.Logger;
|
||||||
|
|
||||||
|
import static org.eclipse.jetty.http.CookieCompliance.Violation.COMMA_NOT_VALID_OCTET;
|
||||||
|
import static org.eclipse.jetty.http.CookieCompliance.Violation.RESERVED_NAMES_NOT_DOLLAR_PREFIXED;
|
||||||
|
|
||||||
/** Cookie parser
|
/** Cookie parser
|
||||||
*/
|
*/
|
||||||
public abstract class CookieCutter
|
public abstract class CookieCutter
|
||||||
{
|
{
|
||||||
protected static final Logger LOG = Log.getLogger(CookieCutter.class);
|
protected static final Logger LOG = Log.getLogger(CookieCutter.class);
|
||||||
|
|
||||||
protected final CookieCompliance _compliance;
|
protected final CookieCompliance _complianceMode;
|
||||||
|
private final ComplianceViolation.Listener _complianceListener;
|
||||||
|
|
||||||
protected CookieCutter(CookieCompliance compliance)
|
protected CookieCutter(CookieCompliance compliance, ComplianceViolation.Listener complianceListener)
|
||||||
{
|
{
|
||||||
_compliance = compliance;
|
_complianceMode = compliance;
|
||||||
|
_complianceListener = complianceListener;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void parseFields(List<String> rawFields)
|
protected void parseFields(List<String> rawFields)
|
||||||
|
@ -121,7 +126,9 @@ public abstract class CookieCutter
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case ',':
|
case ',':
|
||||||
if (_compliance!=CookieCompliance.RFC2965)
|
if (COMMA_NOT_VALID_OCTET.isAllowedBy(_complianceMode))
|
||||||
|
reportComplianceViolation(COMMA_NOT_VALID_OCTET, "Cookie "+cookieName);
|
||||||
|
else
|
||||||
{
|
{
|
||||||
if (quoted)
|
if (quoted)
|
||||||
{
|
{
|
||||||
|
@ -157,8 +164,9 @@ public abstract class CookieCutter
|
||||||
{
|
{
|
||||||
if (name.startsWith("$"))
|
if (name.startsWith("$"))
|
||||||
{
|
{
|
||||||
if (_compliance==CookieCompliance.RFC2965)
|
if (RESERVED_NAMES_NOT_DOLLAR_PREFIXED.isAllowedBy(_complianceMode))
|
||||||
{
|
{
|
||||||
|
reportComplianceViolation(RESERVED_NAMES_NOT_DOLLAR_PREFIXED, "Cookie "+cookieName+" field "+name);
|
||||||
String lowercaseName = name.toLowerCase(Locale.ENGLISH);
|
String lowercaseName = name.toLowerCase(Locale.ENGLISH);
|
||||||
switch(lowercaseName)
|
switch(lowercaseName)
|
||||||
{
|
{
|
||||||
|
@ -277,7 +285,13 @@ public abstract class CookieCutter
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void reportComplianceViolation(CookieCompliance.Violation violation, String reason)
|
||||||
|
{
|
||||||
|
if (_complianceListener != null)
|
||||||
|
{
|
||||||
|
_complianceListener.onComplianceViolation(_complianceMode, violation, reason);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected abstract void addCookie(String cookieName, String cookieValue, String cookieDomain, String cookiePath, int cookieVersion, String cookieComment);
|
protected abstract void addCookie(String cookieName, String cookieValue, String cookieDomain, String cookiePath, int cookieVersion, String cookieComment);
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,110 +18,148 @@
|
||||||
|
|
||||||
package org.eclipse.jetty.http;
|
package org.eclipse.jetty.http;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.EnumSet;
|
import java.util.EnumSet;
|
||||||
import java.util.HashMap;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Objects;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
import org.eclipse.jetty.util.log.Log;
|
import org.eclipse.jetty.util.log.Log;
|
||||||
import org.eclipse.jetty.util.log.Logger;
|
import org.eclipse.jetty.util.log.Logger;
|
||||||
|
|
||||||
|
import static 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.noneOf;
|
||||||
|
import static java.util.EnumSet.of;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* HTTP compliance modes for Jetty HTTP parsing and handling.
|
* 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.
|
* 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 implements ComplianceViolation.Mode
|
||||||
{
|
{
|
||||||
/** 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
|
// These are compliance violations, which may optionally be allowed by the compliance mode, which mean that
|
||||||
* {@link HttpComplianceSection#METHOD_CASE_SENSITIVE},
|
// the relevant section of the RFC is not strictly adhered to.
|
||||||
* {@link HttpComplianceSection#FIELD_COLON},
|
public enum Violation implements ComplianceViolation
|
||||||
* {@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"));
|
|
||||||
|
|
||||||
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)
|
|
||||||
{
|
{
|
||||||
String s = System.getProperty(HttpCompliance.class.getName()+property);
|
CASE_SENSITIVE_FIELD_NAME("https://tools.ietf.org/html/rfc7230#section-3.2", "Field name is case-insensitive"),
|
||||||
return sectionsBySpec(s==null?"*":s);
|
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");
|
||||||
|
|
||||||
|
private final String url;
|
||||||
|
private final String description;
|
||||||
|
|
||||||
|
Violation(String url, String description)
|
||||||
|
{
|
||||||
|
this.url = url;
|
||||||
|
this.description = description;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName()
|
||||||
|
{
|
||||||
|
return name();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getURL()
|
||||||
|
{
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDescription()
|
||||||
|
{
|
||||||
|
return description;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static EnumSet<HttpComplianceSection> sectionsBySpec(String spec)
|
private static final Logger LOG = Log.getLogger(HttpParser.class);
|
||||||
{
|
public static final String VIOLATIONS_ATTR = "org.eclipse.jetty.http.compliance.violations";
|
||||||
EnumSet<HttpComplianceSection> sections;
|
|
||||||
String[] elements = spec.split("\\s*,\\s*");
|
|
||||||
int i=0;
|
|
||||||
|
|
||||||
switch(elements[i])
|
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);
|
||||||
|
|
||||||
|
|
||||||
|
private final static List<HttpCompliance> KNOWN_MODES = Arrays.asList(RFC7230,RFC2616,LEGACY,RFC2616_LEGACY,RFC7230_LEGACY);
|
||||||
|
private final static AtomicInteger __custom = new AtomicInteger();
|
||||||
|
|
||||||
|
public static HttpCompliance valueOf(String name)
|
||||||
|
{
|
||||||
|
for (HttpCompliance compliance : KNOWN_MODES)
|
||||||
|
if (compliance.getName().equals(name))
|
||||||
|
return compliance;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create compliance set from string.
|
||||||
|
* <p>
|
||||||
|
* Format:
|
||||||
|
* </p>
|
||||||
|
* <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>
|
||||||
|
* <dt>name</dt><dd>Any of the known modes defined in {@link HttpCompliance#KNOWN_MODES}</dd>
|
||||||
|
* </dl>
|
||||||
|
* <p>
|
||||||
|
* 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>
|
||||||
|
* @param spec A string in the format of a comma separated list starting with one of the following strings:
|
||||||
|
* @return the compliance from the string spec
|
||||||
|
*/
|
||||||
|
public static HttpCompliance from(String spec)
|
||||||
|
{
|
||||||
|
Set<Violation> sections;
|
||||||
|
String[] elements = spec.split("\\s*,\\s*");
|
||||||
|
switch(elements[0])
|
||||||
{
|
{
|
||||||
case "0":
|
case "0":
|
||||||
sections = EnumSet.noneOf(HttpComplianceSection.class);
|
sections = noneOf(Violation.class);
|
||||||
i++;
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "*":
|
case "*":
|
||||||
i++;
|
sections = allOf(Violation.class);
|
||||||
sections = EnumSet.allOf(HttpComplianceSection.class);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "RFC2616":
|
|
||||||
sections = EnumSet.complementOf(EnumSet.of(
|
|
||||||
HttpComplianceSection.NO_FIELD_FOLDING,
|
|
||||||
HttpComplianceSection.NO_HTTP_0_9));
|
|
||||||
i++;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "RFC7230":
|
|
||||||
i++;
|
|
||||||
sections = EnumSet.allOf(HttpComplianceSection.class);
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
sections = EnumSet.noneOf(HttpComplianceSection.class);
|
{
|
||||||
break;
|
HttpCompliance mode = HttpCompliance.valueOf(elements[0]);
|
||||||
|
if (mode==null)
|
||||||
|
sections = noneOf(Violation.class);
|
||||||
|
else
|
||||||
|
sections = copyOf(mode.getAllowed());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
while(i<elements.length)
|
for (int i=1; i<elements.length; i++)
|
||||||
{
|
{
|
||||||
String element = elements[i++];
|
String element = elements[i];
|
||||||
boolean exclude = element.startsWith("-");
|
boolean exclude = element.startsWith("-");
|
||||||
if (exclude)
|
if (exclude)
|
||||||
element = element.substring(1);
|
element = element.substring(1);
|
||||||
HttpComplianceSection section = HttpComplianceSection.valueOf(element);
|
Violation section = Violation.valueOf(element);
|
||||||
if (section==null)
|
if (section==null)
|
||||||
{
|
{
|
||||||
LOG.warn("Unknown section '"+element+"' in HttpCompliance spec: "+spec);
|
LOG.warn("Unknown section '"+element+"' in HttpCompliance spec: "+spec);
|
||||||
|
@ -131,53 +169,94 @@ public enum HttpCompliance // TODO in Jetty-10 convert this enum to a class so t
|
||||||
sections.remove(section);
|
sections.remove(section);
|
||||||
else
|
else
|
||||||
sections.add(section);
|
sections.add(section);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return sections;
|
return new HttpCompliance("CUSTOM" + __custom.getAndIncrement(), sections);
|
||||||
}
|
}
|
||||||
|
|
||||||
private final static Map<HttpComplianceSection,HttpCompliance> __required = new HashMap<>();
|
|
||||||
static
|
private final String _name;
|
||||||
|
private final Set<Violation> _violations;
|
||||||
|
|
||||||
|
private HttpCompliance(String name, Set<Violation> violations)
|
||||||
{
|
{
|
||||||
for (HttpComplianceSection section : HttpComplianceSection.values())
|
Objects.nonNull(violations);
|
||||||
{
|
_name = name;
|
||||||
for (HttpCompliance compliance : HttpCompliance.values())
|
_violations = unmodifiableSet(violations.isEmpty()?noneOf(Violation.class):copyOf(violations));
|
||||||
{
|
}
|
||||||
if (compliance.sections().contains(section))
|
|
||||||
{
|
@Override
|
||||||
__required.put(section,compliance);
|
public boolean allows(ComplianceViolation violation)
|
||||||
break;
|
{
|
||||||
}
|
return _violations.contains(violation);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@Override
|
||||||
|
public String getName()
|
||||||
|
{
|
||||||
|
return _name;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param section The section to query
|
* Get the set of {@link Violation}s allowed by this compliance mode.
|
||||||
* @return The minimum compliance required to enable the section.
|
* @return The immutable set of {@link Violation}s allowed by this compliance mode.
|
||||||
*/
|
*/
|
||||||
public static HttpCompliance requiredCompliance(HttpComplianceSection section)
|
@Override
|
||||||
|
public Set<Violation> getAllowed()
|
||||||
{
|
{
|
||||||
return __required.get(section);
|
return _violations;
|
||||||
}
|
}
|
||||||
|
|
||||||
private final EnumSet<HttpComplianceSection> _sections;
|
@Override
|
||||||
|
public Set<Violation> getKnown()
|
||||||
private HttpCompliance(EnumSet<HttpComplianceSection> sections)
|
|
||||||
{
|
{
|
||||||
_sections = sections;
|
return EnumSet.allOf(Violation.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the set of {@link HttpComplianceSection}s supported by this compliance mode. This set
|
* Create a new HttpCompliance mode that includes the passed {@link Violation}s.
|
||||||
* is mutable, so it can be modified. Any modification will affect all usages of the mode
|
* @param name The name of the new mode
|
||||||
* within the same {@link ClassLoader}.
|
* @param violations The violations to include
|
||||||
* @return The set of {@link HttpComplianceSection}s supported by this compliance mode.
|
* @return A new {@link HttpCompliance} mode.
|
||||||
*/
|
*/
|
||||||
public EnumSet<HttpComplianceSection> sections()
|
public HttpCompliance with(String name, Violation... violations)
|
||||||
{
|
{
|
||||||
return _sections;
|
Set<Violation> union = _violations.isEmpty()?EnumSet.noneOf(Violation.class):copyOf(_violations);
|
||||||
|
union.addAll(copyOf(violations));
|
||||||
|
return new HttpCompliance(name, union);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new HttpCompliance mode that excludes the passed {@link Violation}s.
|
||||||
|
* @param name The name of the new mode
|
||||||
|
* @param violations The violations to exclude
|
||||||
|
* @return A new {@link HttpCompliance} mode.
|
||||||
|
*/
|
||||||
|
public HttpCompliance without(String name, Violation... violations)
|
||||||
|
{
|
||||||
|
Set<Violation> remainder = _violations.isEmpty()?EnumSet.noneOf(Violation.class):copyOf(_violations);
|
||||||
|
remainder.removeAll(copyOf(violations));
|
||||||
|
return new HttpCompliance(name, remainder);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
return String.format("%s%s",_name,_violations);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static Set<Violation> copyOf(Violation[] violations)
|
||||||
|
{
|
||||||
|
if (violations==null || violations.length==0)
|
||||||
|
return EnumSet.noneOf(Violation.class);
|
||||||
|
return EnumSet.copyOf(asList(violations));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Set<Violation> copyOf(Set<Violation> violations)
|
||||||
|
{
|
||||||
|
if (violations==null || violations.isEmpty())
|
||||||
|
return EnumSet.noneOf(Violation.class);
|
||||||
|
return EnumSet.copyOf(violations);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -24,6 +24,7 @@ import java.util.EnumSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.http.HttpCompliance.Violation;
|
||||||
import org.eclipse.jetty.http.HttpTokens.EndOfContent;
|
import org.eclipse.jetty.http.HttpTokens.EndOfContent;
|
||||||
import org.eclipse.jetty.util.ArrayTernaryTrie;
|
import org.eclipse.jetty.util.ArrayTernaryTrie;
|
||||||
import org.eclipse.jetty.util.ArrayTrie;
|
import org.eclipse.jetty.util.ArrayTrie;
|
||||||
|
@ -33,8 +34,11 @@ import org.eclipse.jetty.util.Utf8StringBuilder;
|
||||||
import org.eclipse.jetty.util.log.Log;
|
import org.eclipse.jetty.util.log.Log;
|
||||||
import org.eclipse.jetty.util.log.Logger;
|
import org.eclipse.jetty.util.log.Logger;
|
||||||
|
|
||||||
import static org.eclipse.jetty.http.HttpComplianceSection.MULTIPLE_CONTENT_LENGTHS;
|
import static org.eclipse.jetty.http.HttpCompliance.Violation.CASE_SENSITIVE_FIELD_NAME;
|
||||||
import static org.eclipse.jetty.http.HttpComplianceSection.TRANSFER_ENCODING_WITH_CONTENT_LENGTH;
|
import static org.eclipse.jetty.http.HttpCompliance.Violation.MULTIPLE_CONTENT_LENGTHS;
|
||||||
|
import static org.eclipse.jetty.http.HttpCompliance.Violation.NO_COLON_AFTER_FIELD_NAME;
|
||||||
|
import static org.eclipse.jetty.http.HttpCompliance.Violation.TRANSFER_ENCODING_WITH_CONTENT_LENGTH;
|
||||||
|
import static org.eclipse.jetty.http.HttpCompliance.Violation.WHITESPACE_AFTER_FIELD_NAME;
|
||||||
|
|
||||||
|
|
||||||
/* ------------------------------------------------------------ */
|
/* ------------------------------------------------------------ */
|
||||||
|
@ -144,10 +148,9 @@ public class HttpParser
|
||||||
private final HttpHandler _handler;
|
private final HttpHandler _handler;
|
||||||
private final RequestHandler _requestHandler;
|
private final RequestHandler _requestHandler;
|
||||||
private final ResponseHandler _responseHandler;
|
private final ResponseHandler _responseHandler;
|
||||||
private final ComplianceHandler _complianceHandler;
|
private final ComplianceViolation.Listener _complianceListener;
|
||||||
private final int _maxHeaderBytes;
|
private final int _maxHeaderBytes;
|
||||||
private final HttpCompliance _compliance;
|
private final HttpCompliance _complianceMode;
|
||||||
private final EnumSet<HttpComplianceSection> _compliances;
|
|
||||||
private HttpField _field;
|
private HttpField _field;
|
||||||
private HttpHeader _header;
|
private HttpHeader _header;
|
||||||
private String _headerString;
|
private String _headerString;
|
||||||
|
@ -282,9 +285,8 @@ public class HttpParser
|
||||||
_requestHandler=requestHandler;
|
_requestHandler=requestHandler;
|
||||||
_responseHandler=responseHandler;
|
_responseHandler=responseHandler;
|
||||||
_maxHeaderBytes=maxHeaderBytes;
|
_maxHeaderBytes=maxHeaderBytes;
|
||||||
_compliance=compliance;
|
_complianceMode =compliance;
|
||||||
_compliances=compliance.sections();
|
_complianceListener =(ComplianceViolation.Listener)(_handler instanceof ComplianceViolation.Listener?_handler:null);
|
||||||
_complianceHandler=(ComplianceHandler)(_handler instanceof ComplianceHandler?_handler:null);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ------------------------------------------------------------------------------- */
|
/* ------------------------------------------------------------------------------- */
|
||||||
|
@ -293,48 +295,34 @@ public class HttpParser
|
||||||
return _handler;
|
return _handler;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ------------------------------------------------------------------------------- */
|
protected void checkViolation(Violation violation) throws BadMessageException
|
||||||
/** 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)
|
|
||||||
{
|
{
|
||||||
return complianceViolation(violation,null);
|
if (violation.isAllowedBy(_complianceMode))
|
||||||
|
reportComplianceViolation(violation, violation.getDescription());
|
||||||
|
else
|
||||||
|
throw new BadMessageException(HttpStatus.BAD_REQUEST_400,violation.getDescription());
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ------------------------------------------------------------------------------- */
|
/* ------------------------------------------------------------------------------- */
|
||||||
/** Check RFC compliance violation
|
protected void reportComplianceViolation(Violation violation)
|
||||||
* @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(HttpComplianceSection violation, String reason)
|
|
||||||
{
|
{
|
||||||
if (_compliances.contains(violation))
|
reportComplianceViolation(violation,violation.getDescription());
|
||||||
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 reportComplianceViolation(Violation violation, String reason)
|
||||||
{
|
{
|
||||||
if (_complianceHandler!=null)
|
if (_complianceListener !=null)
|
||||||
_complianceHandler.onComplianceViolation(_compliance,section,reason);
|
_complianceListener.onComplianceViolation(_complianceMode,violation,reason);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ------------------------------------------------------------------------------- */
|
/* ------------------------------------------------------------------------------- */
|
||||||
protected String caseInsensitiveHeader(String orig, String normative)
|
protected String caseInsensitiveHeader(String orig, String normative)
|
||||||
{
|
{
|
||||||
if (_compliances.contains(HttpComplianceSection.FIELD_NAME_CASE_INSENSITIVE))
|
if (CASE_SENSITIVE_FIELD_NAME.isAllowedBy(_complianceMode))
|
||||||
return normative;
|
return normative;
|
||||||
if (!orig.equals(normative))
|
if (!orig.equals(normative))
|
||||||
handleViolation(HttpComplianceSection.FIELD_NAME_CASE_INSENSITIVE,orig);
|
reportComplianceViolation(CASE_SENSITIVE_FIELD_NAME,orig);
|
||||||
return orig;
|
return orig;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -619,23 +607,22 @@ public class HttpParser
|
||||||
_length=_string.length();
|
_length=_string.length();
|
||||||
_methodString=takeString();
|
_methodString=takeString();
|
||||||
|
|
||||||
if (_compliances.contains(HttpComplianceSection.METHOD_CASE_SENSITIVE))
|
if (Violation.CASE_INSENSITIVE_METHOD.isAllowedBy(_complianceMode))
|
||||||
|
{
|
||||||
|
HttpMethod method=HttpMethod.INSENSITIVE_CACHE.get(_methodString);
|
||||||
|
if (method!=null)
|
||||||
|
{
|
||||||
|
if (!method.asString().equals(_methodString))
|
||||||
|
reportComplianceViolation(Violation.CASE_INSENSITIVE_METHOD,_methodString);
|
||||||
|
_methodString = method.asString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
HttpMethod method=HttpMethod.CACHE.get(_methodString);
|
HttpMethod method=HttpMethod.CACHE.get(_methodString);
|
||||||
if (method!=null)
|
if (method!=null)
|
||||||
_methodString = method.asString();
|
_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);
|
setState(State.SPACE1);
|
||||||
break;
|
break;
|
||||||
|
@ -762,8 +749,7 @@ public class HttpParser
|
||||||
|
|
||||||
case LF:
|
case LF:
|
||||||
// HTTP/0.9
|
// HTTP/0.9
|
||||||
if (complianceViolation(HttpComplianceSection.NO_HTTP_0_9,"No request version"))
|
checkViolation(Violation.HTTP_0_9);
|
||||||
throw new BadMessageException("HTTP/0.9 not supported");
|
|
||||||
handle=_requestHandler.startRequest(_methodString,_uri.toString(), HttpVersion.HTTP_0_9);
|
handle=_requestHandler.startRequest(_methodString,_uri.toString(), HttpVersion.HTTP_0_9);
|
||||||
setState(State.END);
|
setState(State.END);
|
||||||
BufferUtil.clear(buffer);
|
BufferUtil.clear(buffer);
|
||||||
|
@ -848,9 +834,7 @@ public class HttpParser
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// HTTP/0.9
|
// HTTP/0.9
|
||||||
if (complianceViolation(HttpComplianceSection.NO_HTTP_0_9,"No request version"))
|
checkViolation(Violation.HTTP_0_9);
|
||||||
throw new BadMessageException("HTTP/0.9 not supported");
|
|
||||||
|
|
||||||
handle=_requestHandler.startRequest(_methodString,_uri.toString(), HttpVersion.HTTP_0_9);
|
handle=_requestHandler.startRequest(_methodString,_uri.toString(), HttpVersion.HTTP_0_9);
|
||||||
setState(State.END);
|
setState(State.END);
|
||||||
BufferUtil.clear(buffer);
|
BufferUtil.clear(buffer);
|
||||||
|
@ -959,15 +943,14 @@ public class HttpParser
|
||||||
case CONTENT_LENGTH:
|
case CONTENT_LENGTH:
|
||||||
if (_hasContentLength)
|
if (_hasContentLength)
|
||||||
{
|
{
|
||||||
if(complianceViolation(MULTIPLE_CONTENT_LENGTHS))
|
checkViolation(MULTIPLE_CONTENT_LENGTHS);
|
||||||
throw new BadMessageException(HttpStatus.BAD_REQUEST_400,MULTIPLE_CONTENT_LENGTHS.description);
|
|
||||||
if (convertContentLength(_valueString)!=_contentLength)
|
if (convertContentLength(_valueString)!=_contentLength)
|
||||||
throw new BadMessageException(HttpStatus.BAD_REQUEST_400,MULTIPLE_CONTENT_LENGTHS.description);
|
throw new BadMessageException(HttpStatus.BAD_REQUEST_400,MULTIPLE_CONTENT_LENGTHS.getDescription());
|
||||||
}
|
}
|
||||||
_hasContentLength = true;
|
_hasContentLength = true;
|
||||||
|
|
||||||
if (_endOfContent == EndOfContent.CHUNKED_CONTENT && complianceViolation(TRANSFER_ENCODING_WITH_CONTENT_LENGTH))
|
if (_endOfContent == EndOfContent.CHUNKED_CONTENT)
|
||||||
throw new BadMessageException(HttpStatus.BAD_REQUEST_400,"Bad Content-Length");
|
checkViolation(TRANSFER_ENCODING_WITH_CONTENT_LENGTH);
|
||||||
|
|
||||||
if (_endOfContent != EndOfContent.CHUNKED_CONTENT)
|
if (_endOfContent != EndOfContent.CHUNKED_CONTENT)
|
||||||
{
|
{
|
||||||
|
@ -980,8 +963,8 @@ public class HttpParser
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case TRANSFER_ENCODING:
|
case TRANSFER_ENCODING:
|
||||||
if (_hasContentLength && complianceViolation(TRANSFER_ENCODING_WITH_CONTENT_LENGTH))
|
if (_hasContentLength)
|
||||||
throw new BadMessageException(HttpStatus.BAD_REQUEST_400,"Transfer-Encoding and Content-Length");
|
checkViolation(TRANSFER_ENCODING_WITH_CONTENT_LENGTH);
|
||||||
|
|
||||||
if (HttpHeaderValue.CHUNKED.is(_valueString))
|
if (HttpHeaderValue.CHUNKED.is(_valueString))
|
||||||
{
|
{
|
||||||
|
@ -1008,7 +991,7 @@ public class HttpParser
|
||||||
if (!(_field instanceof HostPortHttpField) && _valueString!=null && !_valueString.isEmpty())
|
if (!(_field instanceof HostPortHttpField) && _valueString!=null && !_valueString.isEmpty())
|
||||||
{
|
{
|
||||||
_field=new HostPortHttpField(_header,
|
_field=new HostPortHttpField(_header,
|
||||||
_compliances.contains(HttpComplianceSection.FIELD_NAME_CASE_INSENSITIVE)?_header.asString():_headerString,
|
CASE_SENSITIVE_FIELD_NAME.isAllowedBy(_complianceMode)?_headerString:_header.asString(),
|
||||||
_valueString);
|
_valueString);
|
||||||
add_to_connection_trie=_fieldCache!=null;
|
add_to_connection_trie=_fieldCache!=null;
|
||||||
}
|
}
|
||||||
|
@ -1106,8 +1089,7 @@ public class HttpParser
|
||||||
case SPACE:
|
case SPACE:
|
||||||
case HTAB:
|
case HTAB:
|
||||||
{
|
{
|
||||||
if (complianceViolation(HttpComplianceSection.NO_FIELD_FOLDING,_headerString))
|
checkViolation(Violation.MULTILINE_FIELD_VALUE);
|
||||||
throw new BadMessageException(HttpStatus.BAD_REQUEST_400,"Header Folding");
|
|
||||||
|
|
||||||
// header value without name - continuation?
|
// header value without name - continuation?
|
||||||
if (_valueString==null || _valueString.isEmpty())
|
if (_valueString==null || _valueString.isEmpty())
|
||||||
|
@ -1223,24 +1205,23 @@ public class HttpParser
|
||||||
String n = cached_field.getName();
|
String n = cached_field.getName();
|
||||||
String v = cached_field.getValue();
|
String v = cached_field.getValue();
|
||||||
|
|
||||||
if (!_compliances.contains(HttpComplianceSection.FIELD_NAME_CASE_INSENSITIVE))
|
if (CASE_SENSITIVE_FIELD_NAME.isAllowedBy(_complianceMode))
|
||||||
{
|
{
|
||||||
// Have to get the fields exactly from the buffer to match case
|
// Have to get the fields exactly from the buffer to match case
|
||||||
String en = BufferUtil.toString(buffer,buffer.position()-1,n.length(),StandardCharsets.US_ASCII);
|
String en = BufferUtil.toString(buffer,buffer.position()-1,n.length(),StandardCharsets.US_ASCII);
|
||||||
if (!n.equals(en))
|
if (!n.equals(en))
|
||||||
{
|
{
|
||||||
handleViolation(HttpComplianceSection.FIELD_NAME_CASE_INSENSITIVE,en);
|
reportComplianceViolation(CASE_SENSITIVE_FIELD_NAME,en);
|
||||||
n = en;
|
n = en;
|
||||||
cached_field = new HttpField(cached_field.getHeader(),n,v);
|
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);
|
String ev = BufferUtil.toString(buffer,buffer.position()+n.length()+1,v.length(),StandardCharsets.ISO_8859_1);
|
||||||
if (!v.equals(ev))
|
if (!v.equals(ev))
|
||||||
{
|
{
|
||||||
handleViolation(HttpComplianceSection.CASE_INSENSITIVE_FIELD_VALUE_CACHE,ev+"!="+v);
|
|
||||||
v = ev;
|
v = ev;
|
||||||
cached_field = new HttpField(cached_field.getHeader(),n,v);
|
cached_field = new HttpField(cached_field.getHeader(),n,v);
|
||||||
}
|
}
|
||||||
|
@ -1303,9 +1284,10 @@ public class HttpParser
|
||||||
case SPACE:
|
case SPACE:
|
||||||
case HTAB:
|
case HTAB:
|
||||||
//Ignore trailing whitespaces ?
|
//Ignore trailing whitespaces ?
|
||||||
if (!complianceViolation(HttpComplianceSection.NO_WS_AFTER_FIELD_NAME,null))
|
if (WHITESPACE_AFTER_FIELD_NAME.isAllowedBy(_complianceMode))
|
||||||
{
|
{
|
||||||
_headerString=takeString();
|
_headerString=takeString();
|
||||||
|
reportComplianceViolation(WHITESPACE_AFTER_FIELD_NAME,"Space after "+_headerString);
|
||||||
_header=HttpHeader.CACHE.get(_headerString);
|
_header=HttpHeader.CACHE.get(_headerString);
|
||||||
_length=-1;
|
_length=-1;
|
||||||
setState(FieldState.WS_AFTER_NAME);
|
setState(FieldState.WS_AFTER_NAME);
|
||||||
|
@ -1327,8 +1309,9 @@ public class HttpParser
|
||||||
_valueString="";
|
_valueString="";
|
||||||
_length=-1;
|
_length=-1;
|
||||||
|
|
||||||
if (!complianceViolation(HttpComplianceSection.FIELD_COLON,_headerString))
|
if (NO_COLON_AFTER_FIELD_NAME.isAllowedBy(_complianceMode))
|
||||||
{
|
{
|
||||||
|
reportComplianceViolation(NO_COLON_AFTER_FIELD_NAME,"Field "+_headerString);
|
||||||
setState(FieldState.FIELD);
|
setState(FieldState.FIELD);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -1359,8 +1342,9 @@ public class HttpParser
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case LF:
|
case LF:
|
||||||
if (!complianceViolation(HttpComplianceSection.FIELD_COLON,_headerString))
|
if (NO_COLON_AFTER_FIELD_NAME.isAllowedBy(_complianceMode))
|
||||||
{
|
{
|
||||||
|
reportComplianceViolation(NO_COLON_AFTER_FIELD_NAME,"Field "+_headerString);
|
||||||
setState(FieldState.FIELD);
|
setState(FieldState.FIELD);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -1877,44 +1861,46 @@ public class HttpParser
|
||||||
*/
|
*/
|
||||||
public interface HttpHandler
|
public interface HttpHandler
|
||||||
{
|
{
|
||||||
public boolean content(ByteBuffer item);
|
boolean content(ByteBuffer item);
|
||||||
|
|
||||||
public boolean headerComplete();
|
boolean headerComplete();
|
||||||
|
|
||||||
public boolean contentComplete();
|
boolean contentComplete();
|
||||||
|
|
||||||
public boolean messageComplete();
|
boolean messageComplete();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is the method called by parser when a HTTP Header name and value is found
|
* This is the method called by parser when a HTTP Header name and value is found
|
||||||
* @param field The field parsed
|
* @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
|
* This is the method called by parser when a HTTP Trailer name and value is found
|
||||||
* @param field The field parsed
|
* @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
|
/** Called to signal that an EOF was received unexpectedly
|
||||||
* during the parsing of a HTTP message
|
* during the parsing of a HTTP message
|
||||||
*/
|
*/
|
||||||
public void earlyEOF();
|
void earlyEOF();
|
||||||
|
|
||||||
/* ------------------------------------------------------------ */
|
/* ------------------------------------------------------------ */
|
||||||
/** Called to signal that a bad HTTP message has been received.
|
/** Called to signal that a bad HTTP message has been received.
|
||||||
* @param failure the failure with the bad message information
|
* @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
|
/** @return the size in bytes of the per parser header cache
|
||||||
*/
|
*/
|
||||||
public int getHeaderCacheSize();
|
int getHeaderCacheSize();
|
||||||
|
|
||||||
|
boolean isHeaderCacheCaseSensitive();
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ------------------------------------------------------------------------------- */
|
/* ------------------------------------------------------------------------------- */
|
||||||
|
@ -1929,7 +1915,7 @@ public class HttpParser
|
||||||
* @param version the http version in use
|
* @param version the http version in use
|
||||||
* @return true if handling parsing should return.
|
* @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,18 +1931,7 @@ public class HttpParser
|
||||||
* @param reason the response reason phrase
|
* @param reason the response reason phrase
|
||||||
* @return true if handling parsing should return
|
* @return true if handling parsing should return
|
||||||
*/
|
*/
|
||||||
public boolean startResponse(HttpVersion version, int status, String reason);
|
boolean startResponse(HttpVersion version, int status, String reason);
|
||||||
}
|
|
||||||
|
|
||||||
/* ------------------------------------------------------------------------------- */
|
|
||||||
/* ------------------------------------------------------------------------------- */
|
|
||||||
/* ------------------------------------------------------------------------------- */
|
|
||||||
public interface ComplianceHandler extends HttpHandler
|
|
||||||
{
|
|
||||||
public default void onComplianceViolation(HttpCompliance compliance, HttpComplianceSection violation, String details)
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ------------------------------------------------------------------------------- */
|
/* ------------------------------------------------------------------------------- */
|
||||||
|
|
|
@ -18,15 +18,15 @@
|
||||||
|
|
||||||
package org.eclipse.jetty.http;
|
package org.eclipse.jetty.http;
|
||||||
|
|
||||||
import org.junit.jupiter.params.ParameterizedTest;
|
|
||||||
import org.junit.jupiter.params.provider.Arguments;
|
|
||||||
import org.junit.jupiter.params.provider.MethodSource;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import org.junit.jupiter.params.ParameterizedTest;
|
||||||
|
import org.junit.jupiter.params.provider.Arguments;
|
||||||
|
import org.junit.jupiter.params.provider.MethodSource;
|
||||||
|
|
||||||
import static org.hamcrest.MatcherAssert.assertThat;
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
import static org.hamcrest.Matchers.is;
|
import static org.hamcrest.Matchers.is;
|
||||||
|
|
||||||
|
@ -179,7 +179,7 @@ public class CookieCutterLenientTest
|
||||||
|
|
||||||
protected TestCutter()
|
protected TestCutter()
|
||||||
{
|
{
|
||||||
super(CookieCompliance.RFC6265);
|
super(CookieCompliance.RFC6265, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -18,13 +18,12 @@
|
||||||
|
|
||||||
package org.eclipse.jetty.http;
|
package org.eclipse.jetty.http;
|
||||||
|
|
||||||
import org.junit.jupiter.api.Disabled;
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
import static org.hamcrest.MatcherAssert.assertThat;
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
import static org.hamcrest.Matchers.is;
|
import static org.hamcrest.Matchers.is;
|
||||||
|
|
||||||
|
@ -32,7 +31,7 @@ public class CookieCutterTest
|
||||||
{
|
{
|
||||||
private Cookie[] parseCookieHeaders(CookieCompliance compliance,String... headers)
|
private Cookie[] parseCookieHeaders(CookieCompliance compliance,String... headers)
|
||||||
{
|
{
|
||||||
TestCutter cutter = new TestCutter(compliance);
|
TestCutter cutter = new TestCutter(compliance, null);
|
||||||
for (String header : headers)
|
for (String header : headers)
|
||||||
{
|
{
|
||||||
cutter.parseFields(header);
|
cutter.parseFields(header);
|
||||||
|
@ -269,14 +268,9 @@ public class CookieCutterTest
|
||||||
{
|
{
|
||||||
List<Cookie> cookies = new ArrayList<>();
|
List<Cookie> cookies = new ArrayList<>();
|
||||||
|
|
||||||
protected TestCutter()
|
public TestCutter(CookieCompliance compliance, ComplianceViolation.Listener complianceListener)
|
||||||
{
|
{
|
||||||
this(CookieCompliance.RFC6265);
|
super(compliance, complianceListener);
|
||||||
}
|
|
||||||
|
|
||||||
public TestCutter(CookieCompliance compliance)
|
|
||||||
{
|
|
||||||
super(compliance);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -266,6 +266,12 @@ public class HttpGeneratorServerHTTPTest
|
||||||
{
|
{
|
||||||
return 4096;
|
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";
|
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";
|
||||||
|
|
|
@ -30,7 +30,10 @@ import org.hamcrest.Matchers;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
import static org.eclipse.jetty.http.HttpComplianceSection.NO_FIELD_FOLDING;
|
import static org.eclipse.jetty.http.HttpCompliance.Violation.CASE_INSENSITIVE_METHOD;
|
||||||
|
import static org.eclipse.jetty.http.HttpCompliance.Violation.CASE_SENSITIVE_FIELD_NAME;
|
||||||
|
import static org.eclipse.jetty.http.HttpCompliance.Violation.MULTILINE_FIELD_VALUE;
|
||||||
|
import static org.eclipse.jetty.http.HttpCompliance.Violation.TRANSFER_ENCODING_WITH_CONTENT_LENGTH;
|
||||||
import static org.hamcrest.MatcherAssert.assertThat;
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
import static org.hamcrest.Matchers.contains;
|
import static org.hamcrest.Matchers.contains;
|
||||||
import static org.hamcrest.Matchers.containsString;
|
import static org.hamcrest.Matchers.containsString;
|
||||||
|
@ -130,7 +133,7 @@ public class HttpParserTest
|
||||||
assertEquals("/999", _uriOrStatus);
|
assertEquals("/999", _uriOrStatus);
|
||||||
assertEquals("HTTP/0.9", _versionOrReason);
|
assertEquals("HTTP/0.9", _versionOrReason);
|
||||||
assertEquals(-1, _headers);
|
assertEquals(-1, _headers);
|
||||||
assertThat(_complianceViolation, contains(HttpComplianceSection.NO_HTTP_0_9));
|
assertThat(_complianceViolation, contains(HttpCompliance.Violation.HTTP_0_9));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -159,7 +162,7 @@ public class HttpParserTest
|
||||||
assertEquals("/222", _uriOrStatus);
|
assertEquals("/222", _uriOrStatus);
|
||||||
assertEquals("HTTP/0.9", _versionOrReason);
|
assertEquals("HTTP/0.9", _versionOrReason);
|
||||||
assertEquals(-1, _headers);
|
assertEquals(-1, _headers);
|
||||||
assertThat(_complianceViolation, contains(HttpComplianceSection.NO_HTTP_0_9));
|
assertThat(_complianceViolation, contains(HttpCompliance.Violation.HTTP_0_9));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -304,7 +307,7 @@ public class HttpParserTest
|
||||||
assertEquals("value extra", _val[1]);
|
assertEquals("value extra", _val[1]);
|
||||||
assertEquals("Name2", _hdr[2]);
|
assertEquals("Name2", _hdr[2]);
|
||||||
assertEquals("value2", _val[2]);
|
assertEquals("value2", _val[2]);
|
||||||
assertThat(_complianceViolation, contains(NO_FIELD_FOLDING,NO_FIELD_FOLDING));
|
assertThat(_complianceViolation, contains(MULTILINE_FIELD_VALUE,MULTILINE_FIELD_VALUE));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -322,7 +325,7 @@ public class HttpParserTest
|
||||||
parseAll(parser, buffer);
|
parseAll(parser, buffer);
|
||||||
|
|
||||||
assertThat(_bad, Matchers.notNullValue());
|
assertThat(_bad, Matchers.notNullValue());
|
||||||
assertThat(_bad, containsString("Header Folding"));
|
assertThat(_bad, containsString("Line Folding not supported"));
|
||||||
assertThat(_complianceViolation,Matchers.empty());
|
assertThat(_complianceViolation,Matchers.empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -807,7 +810,7 @@ public class HttpParserTest
|
||||||
parseAll(parser, buffer);
|
parseAll(parser, buffer);
|
||||||
assertNull(_bad);
|
assertNull(_bad);
|
||||||
assertEquals("GET", _methodOrVersion);
|
assertEquals("GET", _methodOrVersion);
|
||||||
assertThat(_complianceViolation, contains(HttpComplianceSection.METHOD_CASE_SENSITIVE));
|
assertThat(_complianceViolation, contains(CASE_INSENSITIVE_METHOD));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -857,7 +860,7 @@ public class HttpParserTest
|
||||||
"HOST: localhost\r\n" +
|
"HOST: localhost\r\n" +
|
||||||
"cOnNeCtIoN: ClOsE\r\n" +
|
"cOnNeCtIoN: ClOsE\r\n" +
|
||||||
"\r\n");
|
"\r\n");
|
||||||
HttpParser.RequestHandler handler = new Handler();
|
HttpParser.RequestHandler handler = new Handler(true);
|
||||||
HttpParser parser = new HttpParser(handler, -1, HttpCompliance.LEGACY);
|
HttpParser parser = new HttpParser(handler, -1, HttpCompliance.LEGACY);
|
||||||
parseAll(parser, buffer);
|
parseAll(parser, buffer);
|
||||||
assertNull(_bad);
|
assertNull(_bad);
|
||||||
|
@ -869,7 +872,7 @@ public class HttpParserTest
|
||||||
assertEquals("cOnNeCtIoN", _hdr[1]);
|
assertEquals("cOnNeCtIoN", _hdr[1]);
|
||||||
assertEquals("ClOsE", _val[1]);
|
assertEquals("ClOsE", _val[1]);
|
||||||
assertEquals(1, _headers);
|
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
|
@Test
|
||||||
|
@ -1862,7 +1865,7 @@ public class HttpParserTest
|
||||||
assertTrue(_headerCompleted);
|
assertTrue(_headerCompleted);
|
||||||
assertTrue(_messageCompleted);
|
assertTrue(_messageCompleted);
|
||||||
|
|
||||||
assertThat(_complianceViolation, contains(HttpComplianceSection.TRANSFER_ENCODING_WITH_CONTENT_LENGTH));
|
assertThat(_complianceViolation, contains(TRANSFER_ENCODING_WITH_CONTENT_LENGTH));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -1891,7 +1894,7 @@ public class HttpParserTest
|
||||||
assertTrue(_headerCompleted);
|
assertTrue(_headerCompleted);
|
||||||
assertTrue(_messageCompleted);
|
assertTrue(_messageCompleted);
|
||||||
|
|
||||||
assertThat(_complianceViolation, contains(HttpComplianceSection.TRANSFER_ENCODING_WITH_CONTENT_LENGTH));
|
assertThat(_complianceViolation, contains(TRANSFER_ENCODING_WITH_CONTENT_LENGTH));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -2185,10 +2188,22 @@ public class HttpParserTest
|
||||||
private boolean _early;
|
private boolean _early;
|
||||||
private boolean _headerCompleted;
|
private boolean _headerCompleted;
|
||||||
private boolean _messageCompleted;
|
private boolean _messageCompleted;
|
||||||
private final List<HttpComplianceSection> _complianceViolation = new ArrayList<>();
|
private final List<ComplianceViolation> _complianceViolation = new ArrayList<>();
|
||||||
|
|
||||||
private class Handler implements HttpParser.RequestHandler, HttpParser.ResponseHandler, HttpParser.ComplianceHandler
|
private class Handler implements HttpParser.RequestHandler, HttpParser.ResponseHandler, ComplianceViolation.Listener
|
||||||
{
|
{
|
||||||
|
private boolean _headerCacheCaseSensitive;
|
||||||
|
|
||||||
|
public Handler()
|
||||||
|
{
|
||||||
|
this(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Handler(boolean headerCacheCaseSensitive)
|
||||||
|
{
|
||||||
|
_headerCacheCaseSensitive = headerCacheCaseSensitive;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean content(ByteBuffer ref)
|
public boolean content(ByteBuffer ref)
|
||||||
{
|
{
|
||||||
|
@ -2295,7 +2310,13 @@ public class HttpParserTest
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onComplianceViolation(HttpCompliance compliance, HttpComplianceSection violation, String reason)
|
public boolean isHeaderCacheCaseSensitive()
|
||||||
|
{
|
||||||
|
return _headerCacheCaseSensitive;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onComplianceViolation(ComplianceViolation.Mode mode, ComplianceViolation violation, String reason)
|
||||||
{
|
{
|
||||||
_complianceViolation.add(violation);
|
_complianceViolation.add(violation);
|
||||||
}
|
}
|
||||||
|
|
|
@ -67,6 +67,7 @@
|
||||||
<Set name="delayDispatchUntilContent"><Property name="jetty.httpConfig.delayDispatchUntilContent" deprecated="jetty.delayDispatchUntilContent" default="true"/></Set>
|
<Set name="delayDispatchUntilContent"><Property name="jetty.httpConfig.delayDispatchUntilContent" deprecated="jetty.delayDispatchUntilContent" default="true"/></Set>
|
||||||
<Set name="maxErrorDispatches"><Property name="jetty.httpConfig.maxErrorDispatches" default="10"/></Set>
|
<Set name="maxErrorDispatches"><Property name="jetty.httpConfig.maxErrorDispatches" default="10"/></Set>
|
||||||
<Set name="persistentConnectionsEnabled"><Property name="jetty.httpConfig.persistentConnectionsEnabled" default="true"/></Set>
|
<Set name="persistentConnectionsEnabled"><Property name="jetty.httpConfig.persistentConnectionsEnabled" default="true"/></Set>
|
||||||
|
<Set name="httpCompliance"><Call class="org.eclipse.jetty.http.HttpCompliance" name="from"><Arg><Property name="jetty.httpConfig.compliance" deprecated="jetty.http.compliance" default="RFC7230"/></Arg></Call></Set>
|
||||||
<Set name="requestCookieCompliance"><Call class="org.eclipse.jetty.http.CookieCompliance" name="valueOf"><Arg><Property name="jetty.httpConfig.requestCookieCompliance" default="RFC6265"/></Arg></Call></Set>
|
<Set name="requestCookieCompliance"><Call class="org.eclipse.jetty.http.CookieCompliance" name="valueOf"><Arg><Property name="jetty.httpConfig.requestCookieCompliance" default="RFC6265"/></Arg></Call></Set>
|
||||||
<Set name="responseCookieCompliance"><Call class="org.eclipse.jetty.http.CookieCompliance" name="valueOf"><Arg><Property name="jetty.httpConfig.responseCookieCompliance" default="RFC6265"/></Arg></Call></Set>
|
<Set name="responseCookieCompliance"><Call class="org.eclipse.jetty.http.CookieCompliance" name="valueOf"><Arg><Property name="jetty.httpConfig.responseCookieCompliance" default="RFC6265"/></Arg></Call></Set>
|
||||||
</New>
|
</New>
|
||||||
|
|
|
@ -41,6 +41,3 @@ etc/jetty-http.xml
|
||||||
|
|
||||||
## Connect Timeout in milliseconds
|
## Connect Timeout in milliseconds
|
||||||
# jetty.http.connectTimeout=15000
|
# jetty.http.connectTimeout=15000
|
||||||
|
|
||||||
## HTTP Compliance: RFC7230, RFC7230_LEGACY, RFC2616, RFC2616_LEGACY, LEGACY or CUSTOMn
|
|
||||||
# jetty.http.compliance=RFC7230_LEGACY
|
|
||||||
|
|
|
@ -59,15 +59,15 @@ etc/jetty.xml
|
||||||
## Maximum number of error dispatches to prevent looping
|
## Maximum number of error dispatches to prevent looping
|
||||||
# jetty.httpConfig.maxErrorDispatches=10
|
# jetty.httpConfig.maxErrorDispatches=10
|
||||||
|
|
||||||
|
## HTTP Compliance: RFC7230, RFC7230_LEGACY, RFC2616, RFC2616_LEGACY, LEGACY
|
||||||
|
# jetty.httpConfig.compliance=RFC7230
|
||||||
|
|
||||||
## Cookie compliance mode for parsing request Cookie headers: RFC2965, RFC6265
|
## Cookie compliance mode for parsing request Cookie headers: RFC2965, RFC6265
|
||||||
# jetty.httpConfig.requestCookieCompliance=RFC6265
|
# jetty.httpConfig.requestCookieCompliance=RFC6265
|
||||||
|
|
||||||
## Cookie compliance mode for generating response Set-Cookie: RFC2965, RFC6265
|
## Cookie compliance mode for generating response Set-Cookie: RFC2965, RFC6265
|
||||||
# jetty.httpConfig.responseCookieCompliance=RFC6265
|
# jetty.httpConfig.responseCookieCompliance=RFC6265
|
||||||
|
|
||||||
## multipart/form-data compliance mode of: LEGACY(slow), RFC7578(fast)
|
|
||||||
# jetty.httpConfig.multiPartFormDataCompliance=LEGACY
|
|
||||||
|
|
||||||
### Server configuration
|
### Server configuration
|
||||||
## Whether ctrl+c on the console gracefully stops the Jetty server
|
## Whether ctrl+c on the console gracefully stops the Jetty server
|
||||||
# jetty.server.stopAtShutdown=true
|
# jetty.server.stopAtShutdown=true
|
||||||
|
|
|
@ -17,11 +17,12 @@
|
||||||
//
|
//
|
||||||
|
|
||||||
package org.eclipse.jetty.server;
|
package org.eclipse.jetty.server;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import javax.servlet.http.Cookie;
|
import javax.servlet.http.Cookie;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.http.ComplianceViolation;
|
||||||
import org.eclipse.jetty.http.CookieCompliance;
|
import org.eclipse.jetty.http.CookieCompliance;
|
||||||
import org.eclipse.jetty.http.CookieCutter;
|
import org.eclipse.jetty.http.CookieCutter;
|
||||||
import org.eclipse.jetty.util.log.Log;
|
import org.eclipse.jetty.util.log.Log;
|
||||||
|
@ -49,12 +50,12 @@ public class Cookies extends CookieCutter
|
||||||
|
|
||||||
public Cookies()
|
public Cookies()
|
||||||
{
|
{
|
||||||
this(CookieCompliance.RFC6265);
|
this(CookieCompliance.RFC6265, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Cookies(CookieCompliance compliance)
|
public Cookies(CookieCompliance compliance, ComplianceViolation.Listener complianceListener)
|
||||||
{
|
{
|
||||||
super(compliance);
|
super(compliance, complianceListener);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addCookieField(String rawField)
|
public void addCookieField(String rawField)
|
||||||
|
|
|
@ -765,9 +765,9 @@ public class HttpChannel implements Runnable, HttpOutput.Interceptor
|
||||||
public void onBadMessage(BadMessageException failure)
|
public void onBadMessage(BadMessageException failure)
|
||||||
{
|
{
|
||||||
int status = failure.getCode();
|
int status = failure.getCode();
|
||||||
String reason = failure.getReason();
|
String message = failure.getReason();
|
||||||
if (status < 400 || status > 599)
|
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);
|
notifyRequestFailure(_request, failure);
|
||||||
|
|
||||||
|
@ -793,9 +793,9 @@ public class HttpChannel implements Runnable, HttpOutput.Interceptor
|
||||||
|
|
||||||
ErrorHandler handler=getServer().getBean(ErrorHandler.class);
|
ErrorHandler handler=getServer().getBean(ErrorHandler.class);
|
||||||
if (handler!=null)
|
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)
|
catch (IOException e)
|
||||||
|
|
|
@ -25,9 +25,9 @@ import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.eclipse.jetty.http.BadMessageException;
|
import org.eclipse.jetty.http.BadMessageException;
|
||||||
|
import org.eclipse.jetty.http.ComplianceViolation;
|
||||||
import org.eclipse.jetty.http.HostPortHttpField;
|
import org.eclipse.jetty.http.HostPortHttpField;
|
||||||
import org.eclipse.jetty.http.HttpCompliance;
|
import org.eclipse.jetty.http.HttpCompliance;
|
||||||
import org.eclipse.jetty.http.HttpComplianceSection;
|
|
||||||
import org.eclipse.jetty.http.HttpField;
|
import org.eclipse.jetty.http.HttpField;
|
||||||
import org.eclipse.jetty.http.HttpFields;
|
import org.eclipse.jetty.http.HttpFields;
|
||||||
import org.eclipse.jetty.http.HttpGenerator;
|
import org.eclipse.jetty.http.HttpGenerator;
|
||||||
|
@ -47,7 +47,7 @@ import org.eclipse.jetty.util.log.Logger;
|
||||||
/**
|
/**
|
||||||
* A HttpChannel customized to be transported over the HTTP/1 protocol
|
* A HttpChannel customized to be transported over the HTTP/1 protocol
|
||||||
*/
|
*/
|
||||||
public class HttpChannelOverHttp extends HttpChannel implements HttpParser.RequestHandler, HttpParser.ComplianceHandler
|
public class HttpChannelOverHttp extends HttpChannel implements HttpParser.RequestHandler, ComplianceViolation.Listener
|
||||||
{
|
{
|
||||||
private static final Logger LOG = Log.getLogger(HttpChannelOverHttp.class);
|
private static final Logger LOG = Log.getLogger(HttpChannelOverHttp.class);
|
||||||
private final static HttpField PREAMBLE_UPGRADE_H2C = new HttpField(HttpHeader.UPGRADE, "h2c");
|
private final static HttpField PREAMBLE_UPGRADE_H2C = new HttpField(HttpHeader.UPGRADE, "h2c");
|
||||||
|
@ -516,7 +516,13 @@ public class HttpChannelOverHttp extends HttpChannel implements HttpParser.Reque
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onComplianceViolation(HttpCompliance compliance, HttpComplianceSection violation, String reason)
|
public boolean isHeaderCacheCaseSensitive()
|
||||||
|
{
|
||||||
|
return getHttpConfiguration().isHeaderCacheCaseSensitive();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onComplianceViolation(ComplianceViolation.Mode mode, ComplianceViolation violation, String details)
|
||||||
{
|
{
|
||||||
if (_httpConnection.isRecordHttpComplianceViolations())
|
if (_httpConnection.isRecordHttpComplianceViolations())
|
||||||
{
|
{
|
||||||
|
@ -525,7 +531,7 @@ public class HttpChannelOverHttp extends HttpChannel implements HttpParser.Reque
|
||||||
_complianceViolations = new ArrayList<>();
|
_complianceViolations = new ArrayList<>();
|
||||||
}
|
}
|
||||||
String record = String.format("%s (see %s) in mode %s for %s in %s",
|
String record = String.format("%s (see %s) in mode %s for %s in %s",
|
||||||
violation.getDescription(), violation.getURL(), compliance, reason, getHttpTransport());
|
violation.getDescription(), violation.getURL(), mode, details, getHttpTransport());
|
||||||
_complianceViolations.add(record);
|
_complianceViolations.add(record);
|
||||||
if (LOG.isDebugEnabled())
|
if (LOG.isDebugEnabled())
|
||||||
LOG.debug(record);
|
LOG.debug(record);
|
||||||
|
|
|
@ -740,13 +740,13 @@ public class HttpChannelState
|
||||||
final Request baseRequest = _channel.getRequest();
|
final Request baseRequest = _channel.getRequest();
|
||||||
|
|
||||||
int code=HttpStatus.INTERNAL_SERVER_ERROR_500;
|
int code=HttpStatus.INTERNAL_SERVER_ERROR_500;
|
||||||
String reason=null;
|
String message=null;
|
||||||
Throwable cause = _channel.unwrap(th,BadMessageException.class,UnavailableException.class);
|
Throwable cause = _channel.unwrap(th,BadMessageException.class,UnavailableException.class);
|
||||||
if (cause instanceof BadMessageException)
|
if (cause instanceof BadMessageException)
|
||||||
{
|
{
|
||||||
BadMessageException bme = (BadMessageException)cause;
|
BadMessageException bme = (BadMessageException)cause;
|
||||||
code = bme.getCode();
|
code = bme.getCode();
|
||||||
reason = bme.getReason();
|
message = bme.getReason();
|
||||||
}
|
}
|
||||||
else if (cause instanceof UnavailableException)
|
else if (cause instanceof UnavailableException)
|
||||||
{
|
{
|
||||||
|
@ -768,7 +768,7 @@ public class HttpChannelState
|
||||||
_event.getSuppliedRequest().setAttribute(ERROR_STATUS_CODE,code);
|
_event.getSuppliedRequest().setAttribute(ERROR_STATUS_CODE,code);
|
||||||
_event.getSuppliedRequest().setAttribute(ERROR_EXCEPTION,th);
|
_event.getSuppliedRequest().setAttribute(ERROR_EXCEPTION,th);
|
||||||
_event.getSuppliedRequest().setAttribute(ERROR_EXCEPTION_TYPE,th==null?null:th.getClass());
|
_event.getSuppliedRequest().setAttribute(ERROR_EXCEPTION_TYPE,th==null?null:th.getClass());
|
||||||
_event.getSuppliedRequest().setAttribute(ERROR_MESSAGE,reason);
|
_event.getSuppliedRequest().setAttribute(ERROR_MESSAGE,message);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -778,7 +778,7 @@ public class HttpChannelState
|
||||||
baseRequest.setAttribute(ERROR_STATUS_CODE,code);
|
baseRequest.setAttribute(ERROR_STATUS_CODE,code);
|
||||||
baseRequest.setAttribute(ERROR_EXCEPTION,th);
|
baseRequest.setAttribute(ERROR_EXCEPTION,th);
|
||||||
baseRequest.setAttribute(RequestDispatcher.ERROR_EXCEPTION_TYPE,th==null?null:th.getClass());
|
baseRequest.setAttribute(RequestDispatcher.ERROR_EXCEPTION_TYPE,th==null?null:th.getClass());
|
||||||
baseRequest.setAttribute(ERROR_MESSAGE,reason);
|
baseRequest.setAttribute(ERROR_MESSAGE,message);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Are we blocking?
|
// Are we blocking?
|
||||||
|
|
|
@ -61,6 +61,7 @@ public class HttpConfiguration implements Dumpable
|
||||||
private int _requestHeaderSize=8*1024;
|
private int _requestHeaderSize=8*1024;
|
||||||
private int _responseHeaderSize=8*1024;
|
private int _responseHeaderSize=8*1024;
|
||||||
private int _headerCacheSize=4*1024;
|
private int _headerCacheSize=4*1024;
|
||||||
|
private boolean _headerCacheCaseSensitive=false;
|
||||||
private int _securePort;
|
private int _securePort;
|
||||||
private long _idleTimeout=-1;
|
private long _idleTimeout=-1;
|
||||||
private String _secureScheme = HttpScheme.HTTPS.asString();
|
private String _secureScheme = HttpScheme.HTTPS.asString();
|
||||||
|
@ -197,6 +198,12 @@ public class HttpConfiguration implements Dumpable
|
||||||
return _headerCacheSize;
|
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")
|
@ManagedAttribute("The port to which Integral or Confidential security constraints are redirected")
|
||||||
public int getSecurePort()
|
public int getSecurePort()
|
||||||
{
|
{
|
||||||
|
@ -390,6 +397,11 @@ public class HttpConfiguration implements Dumpable
|
||||||
_headerCacheSize = headerCacheSize;
|
_headerCacheSize = headerCacheSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setHeaderCacheCaseSensitive(boolean headerCacheCaseSensitive)
|
||||||
|
{
|
||||||
|
this._headerCacheCaseSensitive = headerCacheCaseSensitive;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <p>Sets the TCP/IP port used for CONFIDENTIAL and INTEGRAL redirections.</p>
|
* <p>Sets the TCP/IP port used for CONFIDENTIAL and INTEGRAL redirections.</p>
|
||||||
*
|
*
|
||||||
|
@ -520,9 +532,9 @@ public class HttpConfiguration implements Dumpable
|
||||||
return _httpCompliance;
|
return _httpCompliance;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setHttpCompliance(HttpCompliance _httpCompliance)
|
public void setHttpCompliance(HttpCompliance httpCompliance)
|
||||||
{
|
{
|
||||||
this._httpCompliance = _httpCompliance;
|
_httpCompliance = httpCompliance;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -375,6 +375,12 @@ public class LocalConnector extends AbstractConnector
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isHeaderCacheCaseSensitive()
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void earlyEOF()
|
public void earlyEOF()
|
||||||
{
|
{
|
||||||
|
|
|
@ -66,6 +66,7 @@ import javax.servlet.http.Part;
|
||||||
import javax.servlet.http.PushBuilder;
|
import javax.servlet.http.PushBuilder;
|
||||||
|
|
||||||
import org.eclipse.jetty.http.BadMessageException;
|
import org.eclipse.jetty.http.BadMessageException;
|
||||||
|
import org.eclipse.jetty.http.ComplianceViolation;
|
||||||
import org.eclipse.jetty.http.HostPortHttpField;
|
import org.eclipse.jetty.http.HostPortHttpField;
|
||||||
import org.eclipse.jetty.http.HttpCookie;
|
import org.eclipse.jetty.http.HttpCookie;
|
||||||
import org.eclipse.jetty.http.HttpField;
|
import org.eclipse.jetty.http.HttpField;
|
||||||
|
@ -566,6 +567,22 @@ public class Request implements HttpServletRequest
|
||||||
return _channel.getState();
|
return _channel.getState();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------ */
|
||||||
|
public ComplianceViolation.Listener getComplianceViolationListener()
|
||||||
|
{
|
||||||
|
if (_channel instanceof ComplianceViolation.Listener)
|
||||||
|
{
|
||||||
|
return (ComplianceViolation.Listener) _channel;
|
||||||
|
}
|
||||||
|
|
||||||
|
ComplianceViolation.Listener listener = _channel.getConnector().getBean(ComplianceViolation.Listener.class);
|
||||||
|
if (listener == null)
|
||||||
|
{
|
||||||
|
listener = _channel.getServer().getBean(ComplianceViolation.Listener.class);
|
||||||
|
}
|
||||||
|
return listener;
|
||||||
|
}
|
||||||
|
|
||||||
/* ------------------------------------------------------------ */
|
/* ------------------------------------------------------------ */
|
||||||
/**
|
/**
|
||||||
* Get Request Attribute.
|
* Get Request Attribute.
|
||||||
|
@ -763,7 +780,7 @@ public class Request implements HttpServletRequest
|
||||||
if (field.getHeader()==HttpHeader.COOKIE)
|
if (field.getHeader()==HttpHeader.COOKIE)
|
||||||
{
|
{
|
||||||
if (_cookies==null)
|
if (_cookies==null)
|
||||||
_cookies = new Cookies(getHttpChannel().getHttpConfiguration().getRequestCookieCompliance());
|
_cookies = new Cookies(getHttpChannel().getHttpConfiguration().getRequestCookieCompliance(), getComplianceViolationListener());
|
||||||
_cookies.addCookieField(field.getValue());
|
_cookies.addCookieField(field.getValue());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2038,7 +2055,7 @@ public class Request implements HttpServletRequest
|
||||||
public void setCookies(Cookie[] cookies)
|
public void setCookies(Cookie[] cookies)
|
||||||
{
|
{
|
||||||
if (_cookies == null)
|
if (_cookies == null)
|
||||||
_cookies = new Cookies(getHttpChannel().getHttpConfiguration().getRequestCookieCompliance());
|
_cookies = new Cookies(getHttpChannel().getHttpConfiguration().getRequestCookieCompliance(), getComplianceViolationListener());
|
||||||
_cookies.setCookies(cookies);
|
_cookies.setCookies(cookies);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -151,12 +151,14 @@ public class HttpConnectionTest
|
||||||
connector.getConnectionFactory(HttpConnectionFactory.class).getHttpConfiguration().setHttpCompliance(HttpCompliance.RFC2616);
|
connector.getConnectionFactory(HttpConnectionFactory.class).getHttpConfiguration().setHttpCompliance(HttpCompliance.RFC2616);
|
||||||
String request = "GET / HTTP/0.9\r\n\r\n";
|
String request = "GET / HTTP/0.9\r\n\r\n";
|
||||||
String response = connector.getResponse(request);
|
String response = connector.getResponse(request);
|
||||||
assertThat(response, containsString("400 Bad Version"));
|
assertThat(response, containsString("400 Bad Request"));
|
||||||
|
assertThat(response, containsString("reason: Bad Version"));
|
||||||
|
|
||||||
connector.getConnectionFactory(HttpConnectionFactory.class).getHttpConfiguration().setHttpCompliance(HttpCompliance.RFC7230);
|
connector.getConnectionFactory(HttpConnectionFactory.class).getHttpConfiguration().setHttpCompliance(HttpCompliance.RFC7230);
|
||||||
request = "GET / HTTP/0.9\r\n\r\n";
|
request = "GET / HTTP/0.9\r\n\r\n";
|
||||||
response = connector.getResponse(request);
|
response = connector.getResponse(request);
|
||||||
assertThat(response, containsString("400 Bad Version"));
|
assertThat(response, containsString("400 Bad Request"));
|
||||||
|
assertThat(response, containsString("reason: Bad Version"));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -365,7 +367,8 @@ public class HttpConnectionTest
|
||||||
public void testBadPathDotDotPath() throws Exception
|
public void testBadPathDotDotPath() throws Exception
|
||||||
{
|
{
|
||||||
String response=connector.getResponse("GET /ooops/../../path HTTP/1.0\r\nHost: localhost:80\r\n\n");
|
String response=connector.getResponse("GET /ooops/../../path HTTP/1.0\r\nHost: localhost:80\r\n\n");
|
||||||
checkContains(response,0,"HTTP/1.1 400 Bad URI");
|
checkContains(response,0,"HTTP/1.1 400 Bad Request");
|
||||||
|
checkContains(response,0,"reason: Bad URI");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -380,28 +383,32 @@ public class HttpConnectionTest
|
||||||
public void testBadPathEncodedDotDotPath() throws Exception
|
public void testBadPathEncodedDotDotPath() throws Exception
|
||||||
{
|
{
|
||||||
String response=connector.getResponse("GET /ooops/%2e%2e/%2e%2e/path HTTP/1.0\r\nHost: localhost:80\r\n\n");
|
String response=connector.getResponse("GET /ooops/%2e%2e/%2e%2e/path HTTP/1.0\r\nHost: localhost:80\r\n\n");
|
||||||
checkContains(response,0,"HTTP/1.1 400 Bad URI");
|
checkContains(response,0,"HTTP/1.1 400 Bad Request");
|
||||||
|
checkContains(response,0,"reason: Bad URI");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testBadDotDotPath() throws Exception
|
public void testBadDotDotPath() throws Exception
|
||||||
{
|
{
|
||||||
String response=connector.getResponse("GET ../path HTTP/1.0\r\nHost: localhost:80\r\n\n");
|
String response=connector.getResponse("GET ../path HTTP/1.0\r\nHost: localhost:80\r\n\n");
|
||||||
checkContains(response,0,"HTTP/1.1 400 Bad URI");
|
checkContains(response,0,"HTTP/1.1 400 Bad Request");
|
||||||
|
checkContains(response,0,"reason: Bad URI");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testBadSlashDotDotPath() throws Exception
|
public void testBadSlashDotDotPath() throws Exception
|
||||||
{
|
{
|
||||||
String response=connector.getResponse("GET /../path HTTP/1.0\r\nHost: localhost:80\r\n\n");
|
String response=connector.getResponse("GET /../path HTTP/1.0\r\nHost: localhost:80\r\n\n");
|
||||||
checkContains(response,0,"HTTP/1.1 400 Bad URI");
|
checkContains(response,0,"HTTP/1.1 400 Bad Request");
|
||||||
|
checkContains(response,0,"reason: Bad URI");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testEncodedBadDotDotPath() throws Exception
|
public void testEncodedBadDotDotPath() throws Exception
|
||||||
{
|
{
|
||||||
String response=connector.getResponse("GET %2e%2e/path HTTP/1.0\r\nHost: localhost:80\r\n\n");
|
String response=connector.getResponse("GET %2e%2e/path HTTP/1.0\r\nHost: localhost:80\r\n\n");
|
||||||
checkContains(response,0,"HTTP/1.1 400 Bad URI");
|
checkContains(response,0,"HTTP/1.1 400 Bad Request");
|
||||||
|
checkContains(response,0,"reason: Bad URI");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -538,6 +545,7 @@ public class HttpConnectionTest
|
||||||
public void testChunkNoTrailer() throws Exception
|
public void testChunkNoTrailer() throws Exception
|
||||||
{
|
{
|
||||||
// Expect TimeoutException logged
|
// Expect TimeoutException logged
|
||||||
|
connector.setIdleTimeout(1000);
|
||||||
String response=connector.getResponse("GET /R1 HTTP/1.1\r\n"+
|
String response=connector.getResponse("GET /R1 HTTP/1.1\r\n"+
|
||||||
"Host: localhost\r\n"+
|
"Host: localhost\r\n"+
|
||||||
"Transfer-Encoding: chunked\r\n"+
|
"Transfer-Encoding: chunked\r\n"+
|
||||||
|
|
|
@ -18,9 +18,6 @@
|
||||||
|
|
||||||
package org.eclipse.jetty.servlet;
|
package org.eclipse.jetty.servlet;
|
||||||
|
|
||||||
import static org.hamcrest.MatcherAssert.assertThat;
|
|
||||||
import static org.hamcrest.Matchers.containsString;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.PrintWriter;
|
import java.io.PrintWriter;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
@ -48,6 +45,9 @@ import org.junit.jupiter.api.AfterAll;
|
||||||
import org.junit.jupiter.api.BeforeAll;
|
import org.junit.jupiter.api.BeforeAll;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
|
import static org.hamcrest.Matchers.containsString;
|
||||||
|
|
||||||
public class ComplianceViolations2616Test
|
public class ComplianceViolations2616Test
|
||||||
{
|
{
|
||||||
private static Server server;
|
private static Server server;
|
||||||
|
@ -183,7 +183,7 @@ public class ComplianceViolations2616Test
|
||||||
|
|
||||||
String response = connector.getResponse(req1.toString());
|
String response = connector.getResponse(req1.toString());
|
||||||
assertThat("Response status", response, containsString("HTTP/1.1 200"));
|
assertThat("Response status", response, containsString("HTTP/1.1 200"));
|
||||||
assertThat("Response headers", response, containsString("X-Http-Violation-0: No line Folding"));
|
assertThat("Response headers", response, containsString("X-Http-Violation-0: Line Folding not supported"));
|
||||||
assertThat("Response body", response, containsString("[Name] = [Some Value]"));
|
assertThat("Response body", response, containsString("[Name] = [Some Value]"));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -512,6 +512,12 @@ public class HttpTester
|
||||||
{
|
{
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isHeaderCacheCaseSensitive()
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class Request extends Message implements HttpParser.RequestHandler
|
public static class Request extends Message implements HttpParser.RequestHandler
|
||||||
|
|
Loading…
Reference in New Issue