Skip to content

Commit

Permalink
work in progress of fine grained compliance
Browse files Browse the repository at this point in the history
Signed-off-by: Greg Wilkins <gregw@webtide.com>
  • Loading branch information
gregw committed Dec 16, 2017
1 parent c6c5a3b commit 71b7757
Show file tree
Hide file tree
Showing 4 changed files with 129 additions and 42 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

}
11 changes: 10 additions & 1 deletion jetty-http/src/main/java/org/eclipse/jetty/http/HttpMethod.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

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;
Expand Down Expand Up @@ -132,7 +133,15 @@ public static HttpMethod lookAheadGet(ByteBuffer buffer)
}

/* ------------------------------------------------------------ */
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())
Expand Down
85 changes: 49 additions & 36 deletions jetty-http/src/main/java/org/eclipse/jetty/http/HttpParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@
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;
Expand Down Expand Up @@ -156,7 +155,7 @@ public enum State
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;
Expand Down Expand Up @@ -290,23 +289,24 @@ public HttpParser(RequestHandler handler,HttpCompliance compliance)
/* ------------------------------------------------------------------------------- */
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);
}

/* ------------------------------------------------------------------------------- */
Expand All @@ -333,11 +333,21 @@ protected boolean complianceViolation(HttpCompliance compliance,String reason)
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;
}

/* ------------------------------------------------------------------------------- */
Expand Down Expand Up @@ -651,27 +661,24 @@ private boolean parseLine(ByteBuffer buffer)
_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)
HttpMethod method=HttpMethod.CACHE.get(_methodString);
if (method!=null)
_methodString = method.asString();
}
else
{
HttpMethod method=HttpMethod.INSENSITIVE_CACHE.get(_methodString);

if (method!=null)
{
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;
}
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)
Expand Down Expand Up @@ -957,7 +964,7 @@ else if (values.stream().anyMatch(HttpHeaderValue.CHUNKED::is))
_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;
Expand Down Expand Up @@ -986,7 +993,7 @@ else if (values.stream().anyMatch(HttpHeaderValue.CHUNKED::is))
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);
}
}
Expand Down Expand Up @@ -1173,7 +1180,7 @@ else if (_endOfContent == EndOfContent.UNKNOWN_CONTENT)
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();
Expand Down Expand Up @@ -1829,7 +1836,13 @@ public interface ResponseHandler extends HttpHandler
/* ------------------------------------------------------------------------------- */
public interface ComplianceHandler extends HttpHandler
{
@Deprecated
public void onComplianceViolation(HttpCompliance compliance,HttpCompliance required,String reason);

public default void onComplianceViolation(HttpRFC violation, String details)
{

}
}

/* ------------------------------------------------------------------------------- */
Expand Down
38 changes: 38 additions & 0 deletions jetty-http/src/main/java/org/eclipse/jetty/http/HttpRFC.java
Original file line number Diff line number Diff line change
@@ -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)
{

}
}

0 comments on commit 71b7757

Please sign in to comment.