Issue #185 Implement RFC 7239

Also handle legacy Https headers for #834
This commit is contained in:
Greg Wilkins 2016-08-11 18:56:05 +10:00
parent c033a3734b
commit b45af1a3c9
6 changed files with 637 additions and 226 deletions

View File

@ -34,8 +34,8 @@ public class QuotedCSV implements Iterable<String>
{
private enum State { VALUE, PARAM_NAME, PARAM_VALUE};
private final List<String> _values = new ArrayList<>();
private final boolean _keepQuotes;
protected final List<String> _values = new ArrayList<>();
protected final boolean _keepQuotes;
/* ------------------------------------------------------------ */
public QuotedCSV(String... values)
@ -62,6 +62,10 @@ public class QuotedCSV implements Iterable<String>
boolean sloshed=false;
int nws_length=0;
int last_length=0;
int value_length=-1;
int param_name=-1;
int param_value=-1;
for (int i=0;i<=l;i++)
{
char c=i==l?0:value.charAt(i);
@ -105,12 +109,27 @@ public class QuotedCSV implements Iterable<String>
case '"':
quoted=true;
if (_keepQuotes)
{
if (state==State.PARAM_VALUE && param_value<0)
param_value=nws_length;
buffer.append(c);
}
else if (state==State.PARAM_VALUE && param_value<0)
param_value=nws_length;
nws_length=buffer.length();
continue;
case ';':
buffer.setLength(nws_length); // trim following OWS
if (state==State.VALUE)
{
parsedValue(buffer);
value_length=buffer.length();
}
else
parsedParam(buffer,value_length,param_name,param_value);
nws_length=buffer.length();
param_name=param_value=-1;
buffer.append(c);
last_length=++nws_length;
state=State.PARAM_NAME;
@ -121,14 +140,54 @@ public class QuotedCSV implements Iterable<String>
if (nws_length>0)
{
buffer.setLength(nws_length); // trim following OWS
switch(state)
{
case VALUE:
parsedValue(buffer);
value_length=buffer.length();
break;
case PARAM_NAME:
case PARAM_VALUE:
parsedParam(buffer,value_length,param_name,param_value);
break;
}
_values.add(buffer.toString());
}
buffer.setLength(0);
last_length=0;
nws_length=0;
value_length=param_name=param_value=-1;
state=State.VALUE;
continue;
case '=':
switch (state)
{
case VALUE:
// It wasn't really a value, it was a param name
value_length=param_name=0;
buffer.setLength(nws_length); // trim following OWS
buffer.append(c);
last_length=++nws_length;
state=State.PARAM_VALUE;
continue;
case PARAM_NAME:
buffer.setLength(nws_length); // trim following OWS
buffer.append(c);
last_length=++nws_length;
state=State.PARAM_VALUE;
continue;
case PARAM_VALUE:
if (param_value<0)
param_value=nws_length;
buffer.append(c);
nws_length=buffer.length();
continue;
}
continue;
default:
{
switch (state)
@ -142,22 +201,17 @@ public class QuotedCSV implements Iterable<String>
case PARAM_NAME:
{
if (c=='=')
{
buffer.setLength(nws_length); // trim following OWS
buffer.append(c);
last_length=++nws_length;
state=State.PARAM_VALUE;
continue;
}
if (param_name<0)
param_name=nws_length;
buffer.append(c);
nws_length=buffer.length();
continue;
}
case PARAM_VALUE:
{
if (param_value<0)
param_value=nws_length;
buffer.append(c);
nws_length=buffer.length();
continue;
@ -168,6 +222,26 @@ public class QuotedCSV implements Iterable<String>
}
}
/**
* Called when a value has been parsed
* @param buffer Containing the trimmed value, which may be mutated
*/
protected void parsedValue(StringBuffer buffer)
{
}
/**
* Called when a parameter has been parsed
* @param buffer Containing the trimmed value and all parameters, which may be mutated
* @param valueLength The length of the value
* @param paramName The index of the start of the parameter just parsed
* @param paramValue The index of the start of the parameter value just parsed, or -1
*/
protected void parsedParam(StringBuffer buffer, int valueLength, int paramName, int paramValue)
{
}
public List<String> getValues()
{
return _values;

View File

@ -32,13 +32,11 @@ import java.util.List;
* @see "https://tools.ietf.org/html/rfc7230#section-7"
* @see "https://tools.ietf.org/html/rfc7231#section-5.3.1"
*/
public class QuotedQualityCSV implements Iterable<String>
public class QuotedQualityCSV extends QuotedCSV implements Iterable<String>
{
private final static Double ZERO=new Double(0.0);
private final static Double ONE=new Double(1.0);
private enum State { VALUE, PARAM_NAME, PARAM_VALUE, Q_VALUE};
private final List<String> _values = new ArrayList<>();
private final List<Double> _quality = new ArrayList<>();
private boolean _sorted = false;
@ -53,154 +51,40 @@ public class QuotedQualityCSV implements Iterable<String>
/* ------------------------------------------------------------ */
public void addValue(String value)
{
StringBuffer buffer = new StringBuffer();
int l=value.length();
State state=State.VALUE;
boolean quoted=false;
boolean sloshed=false;
int nws_length=0;
int last_length=0;
Double q=ONE;
for (int i=0;i<=l;i++)
super.addValue(value);
while(_quality.size()<_values.size())
_quality.add(ONE);
}
/* ------------------------------------------------------------ */
@Override
protected void parsedValue(StringBuffer buffer)
{
super.parsedValue(buffer);
}
/* ------------------------------------------------------------ */
@Override
protected void parsedParam(StringBuffer buffer, int valueLength, int paramName, int paramValue)
{
if (buffer.charAt(paramName)=='q' && paramValue>paramName && buffer.charAt(paramName+1)=='=')
{
char c=i==l?0:value.charAt(i);
// Handle quoting https://tools.ietf.org/html/rfc7230#section-3.2.6
if (quoted && c!=0)
Double q;
try
{
if (sloshed)
sloshed=false;
else
{
switch(c)
{
case '\\':
sloshed=true;
break;
case '"':
quoted=false;
if (state==State.Q_VALUE)
continue;
break;
}
}
buffer.append(c);
nws_length=buffer.length();
continue;
q=(_keepQuotes && buffer.charAt(paramValue)=='"')
?new Double(buffer.substring(paramValue+1,buffer.length()-1))
:new Double(buffer.substring(paramValue));
}
// Handle common cases
switch(c)
catch(Exception e)
{
case ' ':
case '\t':
if (buffer.length()>last_length) // not leading OWS
buffer.append(c);
continue;
case '"':
quoted=true;
if (state==State.Q_VALUE)
continue;
buffer.append(c);
nws_length=buffer.length();
continue;
case ';':
if (state==State.Q_VALUE)
{
try
{
q=new Double(buffer.substring(last_length));
}
catch(Exception e)
{
q=ZERO;
}
nws_length=last_length;
}
buffer.setLength(nws_length); // trim following OWS
buffer.append(c);
last_length=++nws_length;
state=State.PARAM_NAME;
continue;
case ',':
case 0:
if (state==State.Q_VALUE)
{
try
{
q=new Double(buffer.substring(last_length));
}
catch(Exception e)
{
q=ZERO;
}
nws_length=last_length;
}
buffer.setLength(nws_length); // trim following OWS
if (q>0.0 && nws_length>0)
{
_values.add(buffer.toString());
_quality.add(q);
_sorted=false;
}
buffer.setLength(0);
last_length=0;
nws_length=0;
q=ONE;
state=State.VALUE;
continue;
default:
{
switch (state)
{
case VALUE:
{
buffer.append(c);
nws_length=buffer.length();
continue;
}
case PARAM_NAME:
{
if (c=='=')
{
buffer.setLength(nws_length); // trim following OWS
if (nws_length-last_length==1 && Character.toLowerCase(buffer.charAt(last_length))=='q')
{
buffer.setLength(last_length-1);
nws_length=buffer.length();
last_length=nws_length;
state=State.Q_VALUE;
continue;
}
buffer.append(c);
last_length=++nws_length;
state=State.PARAM_VALUE;
continue;
}
buffer.append(c);
nws_length=buffer.length();
continue;
}
case PARAM_VALUE:
case Q_VALUE:
{
buffer.append(c);
nws_length=buffer.length();
continue;
}
}
}
}
q=ZERO;
}
buffer.setLength(paramName-1);
while(_quality.size()<_values.size())
_quality.add(ONE);
_quality.add(q);
}
}
@ -246,7 +130,13 @@ public class QuotedQualityCSV implements Iterable<String>
last=q;
len=v.length();
}
int last_element=_quality.size();
while(last_element>0 && _quality.get(--last_element).equals(ZERO))
{
_quality.remove(last_element);
_values.remove(last_element);
}
}
}

View File

@ -31,10 +31,11 @@ public class QuotedCSVTest
public void testOWS()
{
QuotedCSV values = new QuotedCSV();
values.addValue(" value 0.5 ; p = v ; q =0.5 , value 1.0 ");
values.addValue(" value 0.5 ; pqy = vwz ; q =0.5 , value 1.0 , other ; param ");
Assert.assertThat(values,Matchers.contains(
"value 0.5;p=v;q=0.5",
"value 1.0"));
"value 0.5;pqy=vwz;q=0.5",
"value 1.0",
"other;param"));
}
@Test
@ -88,6 +89,57 @@ public class QuotedCSVTest
"value;p=v"));
}
@Test
public void testParamsOnly()
{
QuotedCSV values = new QuotedCSV(false);
values.addValue("for=192.0.2.43, for=\"[2001:db8:cafe::17]\", for=unknown");
assertThat(values,Matchers.contains(
"for=192.0.2.43",
"for=[2001:db8:cafe::17]",
"for=unknown"));
}
@Test
public void testMutation()
{
QuotedCSV values = new QuotedCSV(false)
{
@Override
protected void parsedValue(StringBuffer buffer)
{
if (buffer.toString().contains("DELETE"))
{
String s = buffer.toString().replace("DELETE","");
buffer.setLength(0);
buffer.append(s);
}
if (buffer.toString().contains("APPEND"))
{
String s = buffer.toString().replace("APPEND","Append")+"!";
buffer.setLength(0);
buffer.append(s);
}
}
@Override
protected void parsedParam(StringBuffer buffer, int valueLength, int paramName, int paramValue)
{
String name = paramValue>0?buffer.substring(paramName,paramValue-1):buffer.substring(paramName);
if ("IGNORE".equals(name))
buffer.setLength(paramName-1);
}
};
values.addValue("normal;param=val, testAPPENDandDELETEvalue ; n=v; IGNORE = this; x=y ");
assertThat(values,Matchers.contains(
"normal;param=val",
"testAppendandvalue!;n=v;x=y"));
}
@Test
public void testUnQuote()
{

View File

@ -74,6 +74,7 @@ public class QuotedQualityCSVTest
values.addValue("*");
values.addValue("compress;q=0.5, gzip;q=1.0");
values.addValue("gzip;q=1.0, identity; q=0.5, *;q=0");
Assert.assertThat(values,Matchers.contains(
"compress",
"gzip",

View File

@ -23,22 +23,27 @@ import java.net.InetSocketAddress;
import javax.servlet.ServletRequest;
import org.eclipse.jetty.http.HostPortHttpField;
import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpScheme;
import org.eclipse.jetty.http.QuotedCSV;
import org.eclipse.jetty.server.HttpConfiguration.Customizer;
import org.eclipse.jetty.util.StringUtil;
/* ------------------------------------------------------------ */
/** Customize Requests for Proxy Forwarding.
* <p>
* This customizer looks at at HTTP request for headers that indicate
* it has been forwarded by one or more proxies. Specifically handled are:
* it has been forwarded by one or more proxies. Specifically handled are
* <ul>
* <li>Forwarded, as defined by <a href="https://tools.ietf.org/html/rfc7239">rfc7239</a>
* <li>X-Forwarded-Host</li>
* <li>X-Forwarded-Server</li>
* <li>X-Forwarded-For</li>
* <li>X-Forwarded-Proto</li>
* <li>X-Proxied-Https</li>
* </ul>
* <p>If these headers are present, then the {@link Request} object is updated
* so that the proxy is not seen as the other end point of the connection on which
@ -49,44 +54,86 @@ import org.eclipse.jetty.server.HttpConfiguration.Customizer;
*/
public class ForwardedRequestCustomizer implements Customizer
{
private HostPortHttpField _hostHeader;
private HostPortHttpField _forcedHost;
private String _forwardedHeader = HttpHeader.FORWARDED.toString();
private String _forwardedHostHeader = HttpHeader.X_FORWARDED_HOST.toString();
private String _forwardedServerHeader = HttpHeader.X_FORWARDED_SERVER.toString();
private String _forwardedForHeader = HttpHeader.X_FORWARDED_FOR.toString();
private String _forwardedProtoHeader = HttpHeader.X_FORWARDED_PROTO.toString();
private String _forwardedHttpsHeader = "X-Proxied-Https";
private String _forwardedCipherSuiteHeader;
private String _forwardedSslSessionIdHeader;
private boolean _proxyAsAuthority=false;
/* ------------------------------------------------------------ */
public String getHostHeader()
/**
* @return true if the proxy address obtained via
* X-Forwarded-Server or RFC7239 "by" is used as
* the request authority. Default false
*/
public boolean getProxyAsAuthority()
{
return _hostHeader.getValue();
return _proxyAsAuthority;
}
/* ------------------------------------------------------------ */
/**
* @param proxyAsAuthority if true, use the proxy address obtained via
* X-Forwarded-Server or RFC7239 "by" as the request authority.
*/
public void setProxyAsAuthority(boolean proxyAsAuthority)
{
_proxyAsAuthority = proxyAsAuthority;
}
/**
* Configure to only support the RFC7239 Forwarded header and to
* not support any X-Forwarded- headers.
*/
public void setForwardedOnly(boolean rfc7239only)
{
if (rfc7239only)
{
_forwardedHostHeader=null;
_forwardedServerHeader=null;
_forwardedForHeader=null;
_forwardedProtoHeader=null;
_forwardedHttpsHeader=null;
}
else
{
_forwardedHostHeader = HttpHeader.X_FORWARDED_HOST.toString();
_forwardedServerHeader = HttpHeader.X_FORWARDED_SERVER.toString();
_forwardedForHeader = HttpHeader.X_FORWARDED_FOR.toString();
_forwardedProtoHeader = HttpHeader.X_FORWARDED_PROTO.toString();
_forwardedHttpsHeader = "X-Proxied-Https";
}
}
public String getForcedHost()
{
return _forcedHost.getValue();
}
/**
* Set a forced valued for the host header to control what is returned by {@link ServletRequest#getServerName()} and {@link ServletRequest#getServerPort()}.
*
* @param hostHeader
* @param hostAndPort
* The value of the host header to force.
*/
public void setHostHeader(String hostHeader)
public void setForcedHost(String hostAndPort)
{
_hostHeader = new HostPortHttpField(hostHeader);
_forcedHost = new HostPortHttpField(hostAndPort);
}
/* ------------------------------------------------------------ */
/*
*
* @see #setForwarded(boolean)
*/
public String getForwardedHostHeader()
{
return _forwardedHostHeader;
}
/* ------------------------------------------------------------ */
/**
* @param forwardedHostHeader
* The header name for forwarded hosts (default x-forwarded-host)
@ -96,7 +143,6 @@ public class ForwardedRequestCustomizer implements Customizer
_forwardedHostHeader = forwardedHostHeader;
}
/* ------------------------------------------------------------ */
/**
* @return the header name for forwarded server.
*/
@ -105,7 +151,6 @@ public class ForwardedRequestCustomizer implements Customizer
return _forwardedServerHeader;
}
/* ------------------------------------------------------------ */
/**
* @param forwardedServerHeader
* The header name for forwarded server (default x-forwarded-server)
@ -115,7 +160,6 @@ public class ForwardedRequestCustomizer implements Customizer
_forwardedServerHeader = forwardedServerHeader;
}
/* ------------------------------------------------------------ */
/**
* @return the forwarded for header
*/
@ -124,7 +168,6 @@ public class ForwardedRequestCustomizer implements Customizer
return _forwardedForHeader;
}
/* ------------------------------------------------------------ */
/**
* @param forwardedRemoteAddressHeader
* The header name for forwarded for (default x-forwarded-for)
@ -134,7 +177,6 @@ public class ForwardedRequestCustomizer implements Customizer
_forwardedForHeader = forwardedRemoteAddressHeader;
}
/* ------------------------------------------------------------ */
/**
* Get the forwardedProtoHeader.
*
@ -145,7 +187,6 @@ public class ForwardedRequestCustomizer implements Customizer
return _forwardedProtoHeader;
}
/* ------------------------------------------------------------ */
/**
* Set the forwardedProtoHeader.
*
@ -157,7 +198,6 @@ public class ForwardedRequestCustomizer implements Customizer
_forwardedProtoHeader = forwardedProtoHeader;
}
/* ------------------------------------------------------------ */
/**
* @return The header name holding a forwarded cipher suite (default null)
*/
@ -166,7 +206,6 @@ public class ForwardedRequestCustomizer implements Customizer
return _forwardedCipherSuiteHeader;
}
/* ------------------------------------------------------------ */
/**
* @param forwardedCipherSuite
* The header name holding a forwarded cipher suite (default null)
@ -176,7 +215,6 @@ public class ForwardedRequestCustomizer implements Customizer
_forwardedCipherSuiteHeader = forwardedCipherSuite;
}
/* ------------------------------------------------------------ */
/**
* @return The header name holding a forwarded SSL Session ID (default null)
*/
@ -185,7 +223,6 @@ public class ForwardedRequestCustomizer implements Customizer
return _forwardedSslSessionIdHeader;
}
/* ------------------------------------------------------------ */
/**
* @param forwardedSslSessionId
* The header name holding a forwarded SSL Session ID (default null)
@ -195,75 +232,135 @@ public class ForwardedRequestCustomizer implements Customizer
_forwardedSslSessionIdHeader = forwardedSslSessionId;
}
/* ------------------------------------------------------------ */
/**
* @return The header name holding a forwarded Https (on|off true|false) value
*/
public String getForwardedHttpsHeader()
{
return _forwardedHttpsHeader;
}
/**
* @param forwardedHttpsHeader
*/
public void setForwardedHttpsHeader(String forwardedHttpsHeader)
{
_forwardedHttpsHeader = forwardedHttpsHeader;
}
@Override
public void customize(Connector connector, HttpConfiguration config, Request request)
{
HttpFields httpFields = request.getHttpFields();
// Do SSL first
if (getForwardedCipherSuiteHeader()!=null)
RFC7239 rfc7230 = null;
String forwardedHost = null;
String forwardedServer = null;
String forwardedFor = null;
String forwardedProto = null;
String forwardedHttps = null;
// Do a single pass through the header fields as it is a more efficient single iteration.
for (HttpField field : httpFields)
{
String cipher_suite=httpFields.get(getForwardedCipherSuiteHeader());
if (cipher_suite!=null)
request.setAttribute("javax.servlet.request.cipher_suite",cipher_suite);
}
if (getForwardedSslSessionIdHeader()!=null)
{
String ssl_session_id=httpFields.get(getForwardedSslSessionIdHeader());
if(ssl_session_id!=null)
String name = field.getName();
if (getForwardedCipherSuiteHeader()!=null && httpFields.get(getForwardedCipherSuiteHeader()).equalsIgnoreCase(name))
request.setAttribute("javax.servlet.request.cipher_suite",field.getValue());
if (getForwardedSslSessionIdHeader()!=null && httpFields.get(getForwardedSslSessionIdHeader()).equalsIgnoreCase(name))
request.setAttribute("javax.servlet.request.ssl_session_id", field.getValue());
if (forwardedHost==null && _forwardedHostHeader!=null && _forwardedHostHeader.equalsIgnoreCase(name))
forwardedHost = getLeftMost(field.getValue());
if (forwardedServer==null && _forwardedServerHeader!=null && _forwardedServerHeader.equalsIgnoreCase(name))
forwardedServer = getLeftMost(field.getValue());
if (forwardedFor==null && _forwardedForHeader!=null && _forwardedForHeader.equalsIgnoreCase(name))
forwardedFor = getLeftMost(field.getValue());
if (forwardedProto==null && _forwardedProtoHeader!=null && _forwardedProtoHeader.equalsIgnoreCase(name))
forwardedProto = getLeftMost(field.getValue());
if (forwardedHttps==null && _forwardedHttpsHeader!=null && _forwardedHttpsHeader.equalsIgnoreCase(name))
forwardedHttps = getLeftMost(field.getValue());
if (_forwardedHeader!=null && _forwardedHeader.equalsIgnoreCase(name))
{
request.setAttribute("javax.servlet.request.ssl_session_id", ssl_session_id);
request.setScheme(HttpScheme.HTTPS.asString());
if (rfc7230==null)
rfc7230= new RFC7239();
rfc7230.addValue(field.getValue());
}
}
// Retrieving headers from the request
String forwardedHost = getLeftMostFieldValue(httpFields,getForwardedHostHeader());
String forwardedServer = getLeftMostFieldValue(httpFields,getForwardedServerHeader());
String forwardedFor = getLeftMostFieldValue(httpFields,getForwardedForHeader());
String forwardedProto = getLeftMostFieldValue(httpFields,getForwardedProtoHeader());
if (_hostHeader != null)
// Handle host header if if not available any RFC7230.by or X-ForwardedServer header
if (_forcedHost != null)
{
// Update host header
httpFields.put(_hostHeader);
request.setAuthority(_hostHeader.getHost(),_hostHeader.getPort());
httpFields.put(_forcedHost);
request.setAuthority(_forcedHost.getHost(),_forcedHost.getPort());
}
else if (rfc7230!=null && rfc7230._host!=null)
{
HostPortHttpField auth = rfc7230._host;
httpFields.put(auth);
request.setAuthority(auth.getHost(),auth.getPort());
}
else if (forwardedHost != null)
{
// Update host header
HostPortHttpField auth = new HostPortHttpField(forwardedHost);
httpFields.put(auth);
request.setAuthority(auth.getHost(),auth.getPort());
}
else if (forwardedServer != null)
else if (_proxyAsAuthority)
{
// Use provided server name
request.setAuthority(forwardedServer,request.getServerPort());
if (rfc7230!=null && rfc7230._by!=null)
{
HostPortHttpField auth = rfc7230._by;
httpFields.put(auth);
request.setAuthority(auth.getHost(),auth.getPort());
}
else if (forwardedServer != null)
{
request.setAuthority(forwardedServer,request.getServerPort());
}
}
if (forwardedFor != null)
// handle remote end identifier
if (rfc7230!=null && rfc7230._for!=null)
{
request.setRemoteAddr(InetSocketAddress.createUnresolved(rfc7230._for.getHost(),rfc7230._for.getPort()));
}
else if (forwardedFor != null)
{
request.setRemoteAddr(InetSocketAddress.createUnresolved(forwardedFor,request.getRemotePort()));
}
if (forwardedProto != null)
// handle protocol identifier
if (rfc7230!=null && rfc7230._proto!=null)
{
request.setScheme(rfc7230._proto);
if (rfc7230._proto.equals(config.getSecureScheme()))
request.setSecure(true);
}
else if (forwardedProto != null)
{
request.setScheme(forwardedProto);
if (forwardedProto.equals(config.getSecureScheme()))
request.setSecure(true);
}
else if (forwardedHttps !=null && ("on".equalsIgnoreCase(forwardedHttps)||"true".equalsIgnoreCase(forwardedHttps)))
{
request.setScheme(HttpScheme.HTTPS.asString());
if (HttpScheme.HTTPS.asString().equals(config.getSecureScheme()))
request.setSecure(true);
}
}
/* ------------------------------------------------------------ */
protected String getLeftMostFieldValue(HttpFields fields, String header)
protected String getLeftMost(String headerValue)
{
if (header == null)
return null;
String headerValue = fields.get(header);
if (headerValue == null)
return null;
@ -276,14 +373,76 @@ public class ForwardedRequestCustomizer implements Customizer
}
// The left-most value is the farthest downstream client
return headerValue.substring(0,commaIndex);
return headerValue.substring(0,commaIndex).trim();
}
/* ------------------------------------------------------------ */
@Override
public String toString()
{
return String.format("%s@%x",this.getClass().getSimpleName(),hashCode());
}
@Deprecated
public String getHostHeader()
{
return _forcedHost.getValue();
}
/**
* Set a forced valued for the host header to control what is returned by {@link ServletRequest#getServerName()} and {@link ServletRequest#getServerPort()}.
*
* @param hostHeader
* The value of the host header to force.
*/
@Deprecated
public void setHostHeader(String hostHeader)
{
_forcedHost = new HostPortHttpField(hostHeader);
}
/* ------------------------------------------------------------ */
/* ------------------------------------------------------------ */
/* ------------------------------------------------------------ */
private final class RFC7239 extends QuotedCSV
{
HostPortHttpField _by;
HostPortHttpField _for;
HostPortHttpField _host;
String _proto;
private RFC7239()
{
super(false);
}
@Override
protected void parsedParam(StringBuffer buffer, int valueLength, int paramName, int paramValue)
{
if (valueLength==0 && paramValue>paramName)
{
String name=StringUtil.asciiToLowerCase(buffer.substring(paramName,paramValue-1));
String value=buffer.substring(paramValue);
switch(name)
{
case "by":
if (_by==null && !value.startsWith("_") && !"unknown".equals(value))
_by=new HostPortHttpField(value);
break;
case "for":
if (_for==null && !value.startsWith("_") && !"unknown".equals(value))
_for=new HostPortHttpField(value);
break;
case "host":
if (_host==null)
_host=new HostPortHttpField(value);
break;
case "proto":
if (_proto==null)
_proto=value;
break;
}
}
}
}
}

View File

@ -0,0 +1,235 @@
//
// ========================================================================
// Copyright (c) 1995-2016 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.server;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import java.io.IOException;
import java.util.ArrayDeque;
import java.util.Deque;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.http.MimeTypes;
import org.eclipse.jetty.server.handler.AbstractHandler;
import org.eclipse.jetty.util.IO;
import org.hamcrest.Matchers;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
public class ForwardedRequestCustomizerTest
{
private Server _server;
private LocalConnector _connector;
private RequestHandler _handler;
final Deque<String> _results = new ArrayDeque<>();
@Before
public void init() throws Exception
{
_server = new Server();
HttpConnectionFactory http = new HttpConnectionFactory();
http.setInputBufferSize(1024);
http.getHttpConfiguration().setRequestHeaderSize(512);
http.getHttpConfiguration().setResponseHeaderSize(512);
http.getHttpConfiguration().setOutputBufferSize(2048);
http.getHttpConfiguration().addCustomizer(new ForwardedRequestCustomizer());
_connector = new LocalConnector(_server,http);
_server.addConnector(_connector);
_handler = new RequestHandler();
_server.setHandler(_handler);
_handler._checker = new RequestTester()
{
@Override
public boolean check(HttpServletRequest request,HttpServletResponse response)
{
_results.add(request.getScheme());
_results.add(request.getServerName());
_results.add(Integer.toString(request.getServerPort()));
_results.add(request.getRemoteAddr());
_results.add(Integer.toString(request.getRemotePort()));
return true;
}
};
_server.start();
}
@After
public void destroy() throws Exception
{
_server.stop();
_server.join();
}
@Test
public void testRFC7239_Examples_4() throws Exception
{
String response=_connector.getResponse(
"GET / HTTP/1.1\n"+
"Host: myhost\n"+
"Forwarded: for=\"_gazonk\"\n"+
"Forwarded: For=\"[2001:db8:cafe::17]:4711\"\n"+
"Forwarded: for=192.0.2.60;proto=http;by=203.0.113.43\n"+
"Forwarded: for=192.0.2.43, for=198.51.100.17\n"+
"\n");
assertThat(response, Matchers.containsString("200 OK"));
assertEquals("http",_results.poll());
assertEquals("myhost",_results.poll());
assertEquals("80",_results.poll());
assertEquals("[2001:db8:cafe::17]",_results.poll());
assertEquals("4711",_results.poll());
}
@Test
public void testRFC7239_Examples_7_1() throws Exception
{
_connector.getResponse(
"GET / HTTP/1.1\n"+
"Host: myhost\n"+
"Forwarded: for=192.0.2.43,for=\"[2001:db8:cafe::17]\",for=unknown\n"+
"\n");
_connector.getResponse(
"GET / HTTP/1.1\n"+
"Host: myhost\n"+
"Forwarded: for=192.0.2.43, for=\"[2001:db8:cafe::17]\", for=unknown\n"+
"\n");
_connector.getResponse(
"GET / HTTP/1.1\n"+
"Host: myhost\n"+
"Forwarded: for=192.0.2.43\n"+
"Forwarded: for=\"[2001:db8:cafe::17]\", for=unknown\n"+
"\n");
assertEquals("http",_results.poll());
assertEquals("myhost",_results.poll());
assertEquals("80",_results.poll());
assertEquals("192.0.2.43",_results.poll());
assertEquals("0",_results.poll());
assertEquals("http",_results.poll());
assertEquals("myhost",_results.poll());
assertEquals("80",_results.poll());
assertEquals("192.0.2.43",_results.poll());
assertEquals("0",_results.poll());
assertEquals("http",_results.poll());
assertEquals("myhost",_results.poll());
assertEquals("80",_results.poll());
assertEquals("192.0.2.43",_results.poll());
assertEquals("0",_results.poll());
}
@Test
public void testRFC7239_Examples_7_4() throws Exception
{
_connector.getResponse(
"GET / HTTP/1.1\n"+
"Host: myhost\n"+
"Forwarded: for=192.0.2.43, for=\"[2001:db8:cafe::17]\"\n"+
"\n");
assertEquals("http",_results.poll());
assertEquals("myhost",_results.poll());
assertEquals("80",_results.poll());
assertEquals("192.0.2.43",_results.poll());
assertEquals("0",_results.poll());
}
@Test
public void testRFC7239_Examples_7_5() throws Exception
{
_connector.getResponse(
"GET / HTTP/1.1\n"+
"Host: myhost\n"+
"Forwarded: for=192.0.2.43,for=198.51.100.17;by=203.0.113.60;proto=http;host=example.com\n"+
"\n");
assertEquals("http",_results.poll());
assertEquals("example.com",_results.poll());
assertEquals("80",_results.poll());
assertEquals("192.0.2.43",_results.poll());
assertEquals("0",_results.poll());
}
@Test
public void testProto() throws Exception
{
String response=_connector.getResponse(
"GET / HTTP/1.1\n"+
"Host: myhost\n"+
"X-Forwarded-Proto: foobar\n"+
"Forwarded: proto=https\n"+
"\n");
assertThat(response, Matchers.containsString("200 OK"));
assertEquals("https",_results.poll());
assertEquals("myhost",_results.poll());
assertEquals("443",_results.poll());
assertEquals("0.0.0.0",_results.poll());
assertEquals("0",_results.poll());
}
@Test
public void testLegacyProto() throws Exception
{
String response=_connector.getResponse(
"GET / HTTP/1.1\n"+
"Host: myhost\n"+
"X-Proxied-Https: on\n"+
"\n");
assertThat(response, Matchers.containsString("200 OK"));
assertEquals("https",_results.poll());
assertEquals("myhost",_results.poll());
assertEquals("443",_results.poll());
assertEquals("0.0.0.0",_results.poll());
assertEquals("0",_results.poll());
}
interface RequestTester
{
boolean check(HttpServletRequest request,HttpServletResponse response) throws IOException;
}
private class RequestHandler extends AbstractHandler
{
private RequestTester _checker;
@SuppressWarnings("unused")
private String _content;
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
((Request)request).setHandled(true);
if (request.getContentLength()>0
&& !MimeTypes.Type.FORM_ENCODED.asString().equals(request.getContentType())
&& !request.getContentType().startsWith("multipart/form-data"))
_content=IO.toString(request.getInputStream());
if (_checker!=null && _checker.check(request,response))
response.setStatus(200);
else
response.sendError(500);
}
}
}