Issue #185 Implement RFC 7239
Also handle legacy Https headers for #834
This commit is contained in:
parent
c033a3734b
commit
b45af1a3c9
|
@ -34,8 +34,8 @@ public class QuotedCSV implements Iterable<String>
|
||||||
{
|
{
|
||||||
private enum State { VALUE, PARAM_NAME, PARAM_VALUE};
|
private enum State { VALUE, PARAM_NAME, PARAM_VALUE};
|
||||||
|
|
||||||
private final List<String> _values = new ArrayList<>();
|
protected final List<String> _values = new ArrayList<>();
|
||||||
private final boolean _keepQuotes;
|
protected final boolean _keepQuotes;
|
||||||
|
|
||||||
/* ------------------------------------------------------------ */
|
/* ------------------------------------------------------------ */
|
||||||
public QuotedCSV(String... values)
|
public QuotedCSV(String... values)
|
||||||
|
@ -62,6 +62,10 @@ public class QuotedCSV implements Iterable<String>
|
||||||
boolean sloshed=false;
|
boolean sloshed=false;
|
||||||
int nws_length=0;
|
int nws_length=0;
|
||||||
int last_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++)
|
for (int i=0;i<=l;i++)
|
||||||
{
|
{
|
||||||
char c=i==l?0:value.charAt(i);
|
char c=i==l?0:value.charAt(i);
|
||||||
|
@ -105,12 +109,27 @@ public class QuotedCSV implements Iterable<String>
|
||||||
case '"':
|
case '"':
|
||||||
quoted=true;
|
quoted=true;
|
||||||
if (_keepQuotes)
|
if (_keepQuotes)
|
||||||
|
{
|
||||||
|
if (state==State.PARAM_VALUE && param_value<0)
|
||||||
|
param_value=nws_length;
|
||||||
buffer.append(c);
|
buffer.append(c);
|
||||||
|
}
|
||||||
|
else if (state==State.PARAM_VALUE && param_value<0)
|
||||||
|
param_value=nws_length;
|
||||||
nws_length=buffer.length();
|
nws_length=buffer.length();
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
case ';':
|
case ';':
|
||||||
buffer.setLength(nws_length); // trim following OWS
|
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);
|
buffer.append(c);
|
||||||
last_length=++nws_length;
|
last_length=++nws_length;
|
||||||
state=State.PARAM_NAME;
|
state=State.PARAM_NAME;
|
||||||
|
@ -121,14 +140,54 @@ public class QuotedCSV implements Iterable<String>
|
||||||
if (nws_length>0)
|
if (nws_length>0)
|
||||||
{
|
{
|
||||||
buffer.setLength(nws_length); // trim following OWS
|
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());
|
_values.add(buffer.toString());
|
||||||
}
|
}
|
||||||
buffer.setLength(0);
|
buffer.setLength(0);
|
||||||
last_length=0;
|
last_length=0;
|
||||||
nws_length=0;
|
nws_length=0;
|
||||||
|
value_length=param_name=param_value=-1;
|
||||||
state=State.VALUE;
|
state=State.VALUE;
|
||||||
continue;
|
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:
|
default:
|
||||||
{
|
{
|
||||||
switch (state)
|
switch (state)
|
||||||
|
@ -142,22 +201,17 @@ public class QuotedCSV implements Iterable<String>
|
||||||
|
|
||||||
case PARAM_NAME:
|
case PARAM_NAME:
|
||||||
{
|
{
|
||||||
if (c=='=')
|
if (param_name<0)
|
||||||
{
|
param_name=nws_length;
|
||||||
buffer.setLength(nws_length); // trim following OWS
|
|
||||||
buffer.append(c);
|
|
||||||
last_length=++nws_length;
|
|
||||||
state=State.PARAM_VALUE;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
buffer.append(c);
|
buffer.append(c);
|
||||||
nws_length=buffer.length();
|
nws_length=buffer.length();
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
case PARAM_VALUE:
|
case PARAM_VALUE:
|
||||||
{
|
{
|
||||||
|
if (param_value<0)
|
||||||
|
param_value=nws_length;
|
||||||
buffer.append(c);
|
buffer.append(c);
|
||||||
nws_length=buffer.length();
|
nws_length=buffer.length();
|
||||||
continue;
|
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()
|
public List<String> getValues()
|
||||||
{
|
{
|
||||||
return _values;
|
return _values;
|
||||||
|
|
|
@ -32,13 +32,11 @@ import java.util.List;
|
||||||
* @see "https://tools.ietf.org/html/rfc7230#section-7"
|
* @see "https://tools.ietf.org/html/rfc7230#section-7"
|
||||||
* @see "https://tools.ietf.org/html/rfc7231#section-5.3.1"
|
* @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 ZERO=new Double(0.0);
|
||||||
private final static Double ONE=new Double(1.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 final List<Double> _quality = new ArrayList<>();
|
||||||
private boolean _sorted = false;
|
private boolean _sorted = false;
|
||||||
|
|
||||||
|
@ -53,154 +51,40 @@ public class QuotedQualityCSV implements Iterable<String>
|
||||||
/* ------------------------------------------------------------ */
|
/* ------------------------------------------------------------ */
|
||||||
public void addValue(String value)
|
public void addValue(String value)
|
||||||
{
|
{
|
||||||
StringBuffer buffer = new StringBuffer();
|
super.addValue(value);
|
||||||
|
while(_quality.size()<_values.size())
|
||||||
int l=value.length();
|
_quality.add(ONE);
|
||||||
State state=State.VALUE;
|
}
|
||||||
boolean quoted=false;
|
|
||||||
boolean sloshed=false;
|
/* ------------------------------------------------------------ */
|
||||||
int nws_length=0;
|
@Override
|
||||||
int last_length=0;
|
protected void parsedValue(StringBuffer buffer)
|
||||||
Double q=ONE;
|
{
|
||||||
for (int i=0;i<=l;i++)
|
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);
|
Double q;
|
||||||
|
try
|
||||||
// Handle quoting https://tools.ietf.org/html/rfc7230#section-3.2.6
|
|
||||||
if (quoted && c!=0)
|
|
||||||
{
|
{
|
||||||
if (sloshed)
|
q=(_keepQuotes && buffer.charAt(paramValue)=='"')
|
||||||
sloshed=false;
|
?new Double(buffer.substring(paramValue+1,buffer.length()-1))
|
||||||
else
|
:new Double(buffer.substring(paramValue));
|
||||||
{
|
|
||||||
switch(c)
|
|
||||||
{
|
|
||||||
case '\\':
|
|
||||||
sloshed=true;
|
|
||||||
break;
|
|
||||||
case '"':
|
|
||||||
quoted=false;
|
|
||||||
if (state==State.Q_VALUE)
|
|
||||||
continue;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
buffer.append(c);
|
|
||||||
nws_length=buffer.length();
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
catch(Exception e)
|
||||||
// Handle common cases
|
|
||||||
switch(c)
|
|
||||||
{
|
{
|
||||||
case ' ':
|
q=ZERO;
|
||||||
case '\t':
|
}
|
||||||
if (buffer.length()>last_length) // not leading OWS
|
buffer.setLength(paramName-1);
|
||||||
buffer.append(c);
|
|
||||||
continue;
|
while(_quality.size()<_values.size())
|
||||||
|
_quality.add(ONE);
|
||||||
case '"':
|
_quality.add(q);
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -246,7 +130,13 @@ public class QuotedQualityCSV implements Iterable<String>
|
||||||
|
|
||||||
last=q;
|
last=q;
|
||||||
len=v.length();
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,10 +31,11 @@ public class QuotedCSVTest
|
||||||
public void testOWS()
|
public void testOWS()
|
||||||
{
|
{
|
||||||
QuotedCSV values = new QuotedCSV();
|
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(
|
Assert.assertThat(values,Matchers.contains(
|
||||||
"value 0.5;p=v;q=0.5",
|
"value 0.5;pqy=vwz;q=0.5",
|
||||||
"value 1.0"));
|
"value 1.0",
|
||||||
|
"other;param"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -88,6 +89,57 @@ public class QuotedCSVTest
|
||||||
"value;p=v"));
|
"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
|
@Test
|
||||||
public void testUnQuote()
|
public void testUnQuote()
|
||||||
{
|
{
|
||||||
|
|
|
@ -74,6 +74,7 @@ public class QuotedQualityCSVTest
|
||||||
values.addValue("*");
|
values.addValue("*");
|
||||||
values.addValue("compress;q=0.5, gzip;q=1.0");
|
values.addValue("compress;q=0.5, gzip;q=1.0");
|
||||||
values.addValue("gzip;q=1.0, identity; q=0.5, *;q=0");
|
values.addValue("gzip;q=1.0, identity; q=0.5, *;q=0");
|
||||||
|
|
||||||
Assert.assertThat(values,Matchers.contains(
|
Assert.assertThat(values,Matchers.contains(
|
||||||
"compress",
|
"compress",
|
||||||
"gzip",
|
"gzip",
|
||||||
|
|
|
@ -23,22 +23,27 @@ import java.net.InetSocketAddress;
|
||||||
import javax.servlet.ServletRequest;
|
import javax.servlet.ServletRequest;
|
||||||
|
|
||||||
import org.eclipse.jetty.http.HostPortHttpField;
|
import org.eclipse.jetty.http.HostPortHttpField;
|
||||||
|
import org.eclipse.jetty.http.HttpField;
|
||||||
import org.eclipse.jetty.http.HttpFields;
|
import org.eclipse.jetty.http.HttpFields;
|
||||||
import org.eclipse.jetty.http.HttpHeader;
|
import org.eclipse.jetty.http.HttpHeader;
|
||||||
import org.eclipse.jetty.http.HttpScheme;
|
import org.eclipse.jetty.http.HttpScheme;
|
||||||
|
import org.eclipse.jetty.http.QuotedCSV;
|
||||||
import org.eclipse.jetty.server.HttpConfiguration.Customizer;
|
import org.eclipse.jetty.server.HttpConfiguration.Customizer;
|
||||||
|
import org.eclipse.jetty.util.StringUtil;
|
||||||
|
|
||||||
|
|
||||||
/* ------------------------------------------------------------ */
|
/* ------------------------------------------------------------ */
|
||||||
/** Customize Requests for Proxy Forwarding.
|
/** Customize Requests for Proxy Forwarding.
|
||||||
* <p>
|
* <p>
|
||||||
* This customizer looks at at HTTP request for headers that indicate
|
* 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>
|
* <ul>
|
||||||
|
* <li>Forwarded, as defined by <a href="https://tools.ietf.org/html/rfc7239">rfc7239</a>
|
||||||
* <li>X-Forwarded-Host</li>
|
* <li>X-Forwarded-Host</li>
|
||||||
* <li>X-Forwarded-Server</li>
|
* <li>X-Forwarded-Server</li>
|
||||||
* <li>X-Forwarded-For</li>
|
* <li>X-Forwarded-For</li>
|
||||||
* <li>X-Forwarded-Proto</li>
|
* <li>X-Forwarded-Proto</li>
|
||||||
|
* <li>X-Proxied-Https</li>
|
||||||
* </ul>
|
* </ul>
|
||||||
* <p>If these headers are present, then the {@link Request} object is updated
|
* <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
|
* 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
|
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 _forwardedHostHeader = HttpHeader.X_FORWARDED_HOST.toString();
|
||||||
private String _forwardedServerHeader = HttpHeader.X_FORWARDED_SERVER.toString();
|
private String _forwardedServerHeader = HttpHeader.X_FORWARDED_SERVER.toString();
|
||||||
private String _forwardedForHeader = HttpHeader.X_FORWARDED_FOR.toString();
|
private String _forwardedForHeader = HttpHeader.X_FORWARDED_FOR.toString();
|
||||||
private String _forwardedProtoHeader = HttpHeader.X_FORWARDED_PROTO.toString();
|
private String _forwardedProtoHeader = HttpHeader.X_FORWARDED_PROTO.toString();
|
||||||
|
private String _forwardedHttpsHeader = "X-Proxied-Https";
|
||||||
private String _forwardedCipherSuiteHeader;
|
private String _forwardedCipherSuiteHeader;
|
||||||
private String _forwardedSslSessionIdHeader;
|
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()}.
|
* 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.
|
* 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()
|
public String getForwardedHostHeader()
|
||||||
{
|
{
|
||||||
return _forwardedHostHeader;
|
return _forwardedHostHeader;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ------------------------------------------------------------ */
|
|
||||||
/**
|
/**
|
||||||
* @param forwardedHostHeader
|
* @param forwardedHostHeader
|
||||||
* The header name for forwarded hosts (default x-forwarded-host)
|
* The header name for forwarded hosts (default x-forwarded-host)
|
||||||
|
@ -96,7 +143,6 @@ public class ForwardedRequestCustomizer implements Customizer
|
||||||
_forwardedHostHeader = forwardedHostHeader;
|
_forwardedHostHeader = forwardedHostHeader;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ------------------------------------------------------------ */
|
|
||||||
/**
|
/**
|
||||||
* @return the header name for forwarded server.
|
* @return the header name for forwarded server.
|
||||||
*/
|
*/
|
||||||
|
@ -105,7 +151,6 @@ public class ForwardedRequestCustomizer implements Customizer
|
||||||
return _forwardedServerHeader;
|
return _forwardedServerHeader;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ------------------------------------------------------------ */
|
|
||||||
/**
|
/**
|
||||||
* @param forwardedServerHeader
|
* @param forwardedServerHeader
|
||||||
* The header name for forwarded server (default x-forwarded-server)
|
* The header name for forwarded server (default x-forwarded-server)
|
||||||
|
@ -115,7 +160,6 @@ public class ForwardedRequestCustomizer implements Customizer
|
||||||
_forwardedServerHeader = forwardedServerHeader;
|
_forwardedServerHeader = forwardedServerHeader;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ------------------------------------------------------------ */
|
|
||||||
/**
|
/**
|
||||||
* @return the forwarded for header
|
* @return the forwarded for header
|
||||||
*/
|
*/
|
||||||
|
@ -124,7 +168,6 @@ public class ForwardedRequestCustomizer implements Customizer
|
||||||
return _forwardedForHeader;
|
return _forwardedForHeader;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ------------------------------------------------------------ */
|
|
||||||
/**
|
/**
|
||||||
* @param forwardedRemoteAddressHeader
|
* @param forwardedRemoteAddressHeader
|
||||||
* The header name for forwarded for (default x-forwarded-for)
|
* The header name for forwarded for (default x-forwarded-for)
|
||||||
|
@ -134,7 +177,6 @@ public class ForwardedRequestCustomizer implements Customizer
|
||||||
_forwardedForHeader = forwardedRemoteAddressHeader;
|
_forwardedForHeader = forwardedRemoteAddressHeader;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ------------------------------------------------------------ */
|
|
||||||
/**
|
/**
|
||||||
* Get the forwardedProtoHeader.
|
* Get the forwardedProtoHeader.
|
||||||
*
|
*
|
||||||
|
@ -145,7 +187,6 @@ public class ForwardedRequestCustomizer implements Customizer
|
||||||
return _forwardedProtoHeader;
|
return _forwardedProtoHeader;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ------------------------------------------------------------ */
|
|
||||||
/**
|
/**
|
||||||
* Set the forwardedProtoHeader.
|
* Set the forwardedProtoHeader.
|
||||||
*
|
*
|
||||||
|
@ -157,7 +198,6 @@ public class ForwardedRequestCustomizer implements Customizer
|
||||||
_forwardedProtoHeader = forwardedProtoHeader;
|
_forwardedProtoHeader = forwardedProtoHeader;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ------------------------------------------------------------ */
|
|
||||||
/**
|
/**
|
||||||
* @return The header name holding a forwarded cipher suite (default null)
|
* @return The header name holding a forwarded cipher suite (default null)
|
||||||
*/
|
*/
|
||||||
|
@ -166,7 +206,6 @@ public class ForwardedRequestCustomizer implements Customizer
|
||||||
return _forwardedCipherSuiteHeader;
|
return _forwardedCipherSuiteHeader;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ------------------------------------------------------------ */
|
|
||||||
/**
|
/**
|
||||||
* @param forwardedCipherSuite
|
* @param forwardedCipherSuite
|
||||||
* The header name holding a forwarded cipher suite (default null)
|
* The header name holding a forwarded cipher suite (default null)
|
||||||
|
@ -176,7 +215,6 @@ public class ForwardedRequestCustomizer implements Customizer
|
||||||
_forwardedCipherSuiteHeader = forwardedCipherSuite;
|
_forwardedCipherSuiteHeader = forwardedCipherSuite;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ------------------------------------------------------------ */
|
|
||||||
/**
|
/**
|
||||||
* @return The header name holding a forwarded SSL Session ID (default null)
|
* @return The header name holding a forwarded SSL Session ID (default null)
|
||||||
*/
|
*/
|
||||||
|
@ -185,7 +223,6 @@ public class ForwardedRequestCustomizer implements Customizer
|
||||||
return _forwardedSslSessionIdHeader;
|
return _forwardedSslSessionIdHeader;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ------------------------------------------------------------ */
|
|
||||||
/**
|
/**
|
||||||
* @param forwardedSslSessionId
|
* @param forwardedSslSessionId
|
||||||
* The header name holding a forwarded SSL Session ID (default null)
|
* The header name holding a forwarded SSL Session ID (default null)
|
||||||
|
@ -195,75 +232,135 @@ public class ForwardedRequestCustomizer implements Customizer
|
||||||
_forwardedSslSessionIdHeader = forwardedSslSessionId;
|
_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
|
@Override
|
||||||
public void customize(Connector connector, HttpConfiguration config, Request request)
|
public void customize(Connector connector, HttpConfiguration config, Request request)
|
||||||
{
|
{
|
||||||
HttpFields httpFields = request.getHttpFields();
|
HttpFields httpFields = request.getHttpFields();
|
||||||
|
|
||||||
// Do SSL first
|
RFC7239 rfc7230 = null;
|
||||||
if (getForwardedCipherSuiteHeader()!=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());
|
String name = field.getName();
|
||||||
if (cipher_suite!=null)
|
|
||||||
request.setAttribute("javax.servlet.request.cipher_suite",cipher_suite);
|
if (getForwardedCipherSuiteHeader()!=null && httpFields.get(getForwardedCipherSuiteHeader()).equalsIgnoreCase(name))
|
||||||
}
|
request.setAttribute("javax.servlet.request.cipher_suite",field.getValue());
|
||||||
if (getForwardedSslSessionIdHeader()!=null)
|
|
||||||
{
|
if (getForwardedSslSessionIdHeader()!=null && httpFields.get(getForwardedSslSessionIdHeader()).equalsIgnoreCase(name))
|
||||||
String ssl_session_id=httpFields.get(getForwardedSslSessionIdHeader());
|
request.setAttribute("javax.servlet.request.ssl_session_id", field.getValue());
|
||||||
if(ssl_session_id!=null)
|
|
||||||
|
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);
|
if (rfc7230==null)
|
||||||
request.setScheme(HttpScheme.HTTPS.asString());
|
rfc7230= new RFC7239();
|
||||||
|
rfc7230.addValue(field.getValue());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Retrieving headers from the request
|
// Handle host header if if not available any RFC7230.by or X-ForwardedServer header
|
||||||
String forwardedHost = getLeftMostFieldValue(httpFields,getForwardedHostHeader());
|
if (_forcedHost != null)
|
||||||
String forwardedServer = getLeftMostFieldValue(httpFields,getForwardedServerHeader());
|
|
||||||
String forwardedFor = getLeftMostFieldValue(httpFields,getForwardedForHeader());
|
|
||||||
String forwardedProto = getLeftMostFieldValue(httpFields,getForwardedProtoHeader());
|
|
||||||
|
|
||||||
if (_hostHeader != null)
|
|
||||||
{
|
{
|
||||||
// Update host header
|
// Update host header
|
||||||
httpFields.put(_hostHeader);
|
httpFields.put(_forcedHost);
|
||||||
request.setAuthority(_hostHeader.getHost(),_hostHeader.getPort());
|
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)
|
else if (forwardedHost != null)
|
||||||
{
|
{
|
||||||
// Update host header
|
|
||||||
HostPortHttpField auth = new HostPortHttpField(forwardedHost);
|
HostPortHttpField auth = new HostPortHttpField(forwardedHost);
|
||||||
httpFields.put(auth);
|
httpFields.put(auth);
|
||||||
request.setAuthority(auth.getHost(),auth.getPort());
|
request.setAuthority(auth.getHost(),auth.getPort());
|
||||||
}
|
}
|
||||||
else if (forwardedServer != null)
|
else if (_proxyAsAuthority)
|
||||||
{
|
{
|
||||||
// Use provided server name
|
if (rfc7230!=null && rfc7230._by!=null)
|
||||||
request.setAuthority(forwardedServer,request.getServerPort());
|
{
|
||||||
|
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()));
|
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);
|
request.setScheme(forwardedProto);
|
||||||
if (forwardedProto.equals(config.getSecureScheme()))
|
if (forwardedProto.equals(config.getSecureScheme()))
|
||||||
request.setSecure(true);
|
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)
|
if (headerValue == null)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
|
@ -276,14 +373,76 @@ public class ForwardedRequestCustomizer implements Customizer
|
||||||
}
|
}
|
||||||
|
|
||||||
// The left-most value is the farthest downstream client
|
// The left-most value is the farthest downstream client
|
||||||
return headerValue.substring(0,commaIndex);
|
return headerValue.substring(0,commaIndex).trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* ------------------------------------------------------------ */
|
|
||||||
@Override
|
@Override
|
||||||
public String toString()
|
public String toString()
|
||||||
{
|
{
|
||||||
return String.format("%s@%x",this.getClass().getSimpleName(),hashCode());
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue