From 71b775724f4d265966fd863b88c50de6e7b10296 Mon Sep 17 00:00:00 2001 From: Greg Wilkins Date: Sat, 16 Dec 2017 17:15:37 +0100 Subject: [PATCH] work in progress of fine grained compliance Signed-off-by: Greg Wilkins --- .../eclipse/jetty/http/HttpCompliance.java | 37 ++++++-- .../org/eclipse/jetty/http/HttpMethod.java | 11 ++- .../org/eclipse/jetty/http/HttpParser.java | 87 +++++++++++-------- .../java/org/eclipse/jetty/http/HttpRFC.java | 38 ++++++++ 4 files changed, 130 insertions(+), 43 deletions(-) create mode 100644 jetty-http/src/main/java/org/eclipse/jetty/http/HttpRFC.java diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpCompliance.java b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpCompliance.java index f885505f995..0a499fd3c32 100644 --- a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpCompliance.java +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpCompliance.java @@ -18,17 +18,44 @@ package org.eclipse.jetty.http; +import java.util.EnumSet; /** * HTTP compliance modes: *
*
RFC7230
(default) Compliance with RFC7230
*
RFC2616
Wrapped/Continued headers and HTTP/0.9 supported
- *
WEAK
Wrapped/Continued headers, HTTP/0.9 supported and make the parser more acceptable against miss - * formatted requests/responses
*
LEGACY
(aka STRICT) Adherence to Servlet Specification requirement for - * exact case of header names, bypassing the header caches, which are case insensitive, - * otherwise equivalent to WEAK
+ * exact case of header names, bypassing the header caches, which are case insensitive. *
*/ -public enum HttpCompliance { LEGACY, WEAK, RFC2616, RFC7230 } \ No newline at end of file +public enum HttpCompliance +{ + LEGACY(EnumSet.noneOf(HttpRFC.class)), + RFC2616(EnumSet.complementOf(EnumSet.of( + HttpRFC.RFC7230_3_2_4_WS_AFTER_FIELD_NAME, + HttpRFC.RFC7230_3_2_4_NO_FOLDING, + HttpRFC.RFC7230_A2_NO_HTTP_9))), + RFC7230(EnumSet.allOf(HttpRFC.class)), + ; + + final EnumSet _sections; + + HttpCompliance(EnumSet sections) + { + _sections = sections; + } + + public EnumSet sections() + { + return _sections; + } + + public EnumSet excluding(EnumSet exclusions) + { + EnumSet sections = EnumSet.copyOf(_sections); + sections.removeAll(exclusions); + return sections; + } + +} \ No newline at end of file diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpMethod.java b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpMethod.java index 8f7cfc961c4..6315eef735e 100644 --- a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpMethod.java +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpMethod.java @@ -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 CACHE= new ArrayTrie<>(); + public final static Trie INSENSITIVE_CACHE= new ArrayTrie<>(); + static + { + for (HttpMethod method : HttpMethod.values()) + INSENSITIVE_CACHE.put(method.toString(),method); + } + + /* ------------------------------------------------------------ */ + public final static Trie CACHE= new ArrayTernaryTrie<>(false); static { for (HttpMethod method : HttpMethod.values()) diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpParser.java b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpParser.java index bce94cede8c..7c96782d130 100644 --- a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpParser.java +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpParser.java @@ -36,7 +36,6 @@ 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.WEAK; 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; @@ -156,7 +155,7 @@ public class HttpParser private final ResponseHandler _responseHandler; private final ComplianceHandler _complianceHandler; private final int _maxHeaderBytes; - private final HttpCompliance _compliance; + private final EnumSet _compliances; private HttpField _field; private HttpHeader _header; private String _headerString; @@ -290,23 +289,24 @@ 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).sections()); } /* ------------------------------------------------------------------------------- */ public HttpParser(ResponseHandler handler,int maxHeaderBytes,HttpCompliance compliance) { - _handler=handler; - _requestHandler=null; - _responseHandler=handler; + this(null,handler,maxHeaderBytes,(compliance==null?compliance():compliance).sections()); + } + + /* ------------------------------------------------------------------------------- */ + private HttpParser(RequestHandler requestHandler,ResponseHandler responseHandler,int maxHeaderBytes,EnumSet compliances) + { + _handler=requestHandler!=null?requestHandler:responseHandler; + _requestHandler=requestHandler; + _responseHandler=responseHandler; _maxHeaderBytes=maxHeaderBytes; - _compliance=compliance==null?compliance():compliance; - _complianceHandler=(ComplianceHandler)(handler instanceof ComplianceHandler?handler:null); + _compliances=compliances; + _complianceHandler=(ComplianceHandler)(_handler instanceof ComplianceHandler?_handler:null); } /* ------------------------------------------------------------------------------- */ @@ -333,11 +333,21 @@ public class HttpParser return true; } + /* ------------------------------------------------------------------------------- */ + protected void handleViolation(HttpRFC section,String reason) + { + if (_complianceHandler!=null) + _complianceHandler.onComplianceViolation(section,reason); + } + /* ------------------------------------------------------------------------------- */ protected String caseInsensitiveHeader(String orig, String normative) { - return (_compliance!=LEGACY || orig.equals(normative) || complianceViolation(WEAK,"https://tools.ietf.org/html/rfc2616#section-4.2 case sensitive header: "+orig)) - ?normative:orig; + if (_compliances.contains(HttpRFC.RFC7230_3_2_CASE_INSENSITIVE_FIELD_NAME)) + return normative; + if (orig.equals(normative)) + handleViolation(HttpRFC.RFC7230_3_2_CASE_INSENSITIVE_FIELD_NAME,orig); + return orig; } /* ------------------------------------------------------------------------------- */ @@ -651,27 +661,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(HttpRFC.RFC7230_3_1_1_METHOD_CASE_SENSITIVE)) { - switch(_compliance) - { - case LEGACY: - // Legacy correctly allows case sensitive header; - break; - - case WEAK: - 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(HttpRFC.RFC7230_3_1_1_METHOD_CASE_SENSITIVE,_methodString); + _methodString = method.asString(); + } + } + setState(State.SPACE1); } else if (b < SPACE) @@ -957,7 +964,7 @@ 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,caseInsensitiveHeaderX(_headerString,_header.asString()),_valueString); add_to_connection_trie=_fieldCache!=null; } break; @@ -986,7 +993,7 @@ public class HttpParser if (add_to_connection_trie && !_fieldCache.isFull() && _header!=null && _valueString!=null) { if (_field==null) - _field=new HttpField(_header,caseInsensitiveHeader(_headerString,_header.asString()),_valueString); + _field=new HttpField(_header,_headerString,_valueString); _fieldCache.put(_field); } } @@ -1173,7 +1180,7 @@ public class HttpParser final String n; final String v; - if (_compliance==LEGACY) + if (!_compliances.contains(HttpRFC.RFC7230_3_2_CASE_INSENSITIVE_FIELD_NAME)) { // Have to get the fields exactly from the buffer to match case String fn=field.getName(); @@ -1829,7 +1836,13 @@ public class HttpParser /* ------------------------------------------------------------------------------- */ public interface ComplianceHandler extends HttpHandler { + @Deprecated public void onComplianceViolation(HttpCompliance compliance,HttpCompliance required,String reason); + + public default void onComplianceViolation(HttpRFC violation, String details) + { + + } } /* ------------------------------------------------------------------------------- */ diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpRFC.java b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpRFC.java new file mode 100644 index 00000000000..41921541a0c --- /dev/null +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpRFC.java @@ -0,0 +1,38 @@ +// +// ======================================================================== +// Copyright (c) 1995-2017 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 HttpRFC +{ + RFC7230_2_7_3_HOST_CASE_INSENSITIVE("https://tools.ietf.org/html/rfc7230#section-2.7.3","Host is case-insensitive"), + RFC7230_3_1_1_METHOD_CASE_SENSITIVE("https://tools.ietf.org/html/rfc7230#section-3.1.1","Method is case-sensitive"), + RFC7230_3_2_FIELD_COLON("https://tools.ietf.org/html/rfc7230#section-3.2","Fields must have a Colon"), + RFC7230_3_2_CASE_INSENSITIVE_FIELD_NAME("https://tools.ietf.org/html/rfc7230#section-3.2","Field name is case-insensitive"), + RFC7230_3_2_4_WS_AFTER_FIELD_NAME("https://tools.ietf.org/html/rfc7230#section-3.2.4","Whitespace not allowed after field name"), + RFC7230_3_2_4_NO_FOLDING("https://tools.ietf.org/html/rfc7230#section-3.2.4","No line Folding"), + RFC7230_A2_NO_HTTP_9("https://tools.ietf.org/html/rfc7230#appendix-A.2","No HTTP/0.9"), + ; + + HttpRFC(String url,String description) + { + + } +} \ No newline at end of file