work in progress of fine grained compliance

Signed-off-by: Greg Wilkins <gregw@webtide.com>
This commit is contained in:
Greg Wilkins 2017-12-16 17:15:37 +01:00
parent c6c5a3b890
commit 71b775724f
4 changed files with 130 additions and 43 deletions

View File

@ -18,17 +18,44 @@
package org.eclipse.jetty.http;
import java.util.EnumSet;
/**
* HTTP compliance modes:
* <dl>
* <dt>RFC7230</dt><dd>(default) Compliance with RFC7230</dd>
* <dt>RFC2616</dt><dd>Wrapped/Continued headers and HTTP/0.9 supported</dd>
* <dt>WEAK</dt><dd>Wrapped/Continued headers, HTTP/0.9 supported and make the parser more acceptable against miss
* formatted requests/responses</dd>
* <dt>LEGACY</dt><dd>(aka STRICT) Adherence to Servlet Specification requirement for
* exact case of header names, bypassing the header caches, which are case insensitive,
* otherwise equivalent to WEAK</dd>
* exact case of header names, bypassing the header caches, which are case insensitive.</dd>
* </dl>
*/
public enum HttpCompliance { LEGACY, WEAK, RFC2616, RFC7230 }
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<HttpRFC> _sections;
HttpCompliance(EnumSet<HttpRFC> sections)
{
_sections = sections;
}
public EnumSet<HttpRFC> sections()
{
return _sections;
}
public EnumSet<HttpRFC> excluding(EnumSet<HttpRFC> exclusions)
{
EnumSet<HttpRFC> sections = EnumSet.copyOf(_sections);
sections.removeAll(exclusions);
return sections;
}
}

View File

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

View File

@ -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<HttpRFC> _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<HttpRFC> 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)
{
}
}
/* ------------------------------------------------------------------------------- */

View File

@ -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)
{
}
}