Merge remote-tracking branch 'origin/jetty-9.4.x' into jetty-10.0.x
This commit is contained in:
commit
39271d6b63
|
@ -58,8 +58,91 @@ import static java.lang.invoke.MethodType.methodType;
|
|||
* the request came</p>
|
||||
* <p>Headers can also be defined so that forwarded SSL Session IDs and Cipher
|
||||
* suites may be customised</p>
|
||||
* <p>
|
||||
* The Authority (host and port) is updated on the {@link Request} object based
|
||||
* on the host / port information in the following search order.
|
||||
* </p>
|
||||
* <table style="border: 1px solid black; border-collapse: separate; border-spacing: 0px;">
|
||||
* <caption style="font-weight: bold; font-size: 1.2em">Request Authority Search Order</caption>
|
||||
* <colgroup>
|
||||
* <col><col style="width: 15%"><col><col><col><col>
|
||||
* </colgroup>
|
||||
* <thead style="background-color: lightgray">
|
||||
* <tr>
|
||||
* <th>#</th>
|
||||
* <th>Value Origin</th>
|
||||
* <th>Host</th>
|
||||
* <th>Port</th>
|
||||
* <th>Protocol</th>
|
||||
* <th>Notes</th>
|
||||
* </tr>
|
||||
* </thead>
|
||||
* <tbody style="text-align: left; vertical-align: top;">
|
||||
* <tr>
|
||||
* <td>1</td>
|
||||
* <td><code>Forwarded</code> Header</td>
|
||||
* <td>"{@code host=<host>}" param (Required)</td>
|
||||
* <td>"{@code host=<host>:<port>} param (Implied)</td>
|
||||
* <td>"{@code proto=<value>}" param (Optional)</td>
|
||||
* <td>From left-most relevant parameter (see <a href="https://tools.ietf.org/html/rfc7239">rfc7239</a>)</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>2</td>
|
||||
* <td><code>X-Forwarded-Host</code> Header</td>
|
||||
* <td>Required</td>
|
||||
* <td>Implied</td>
|
||||
* <td>n/a</td>
|
||||
* <td>left-most value</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>3</td>
|
||||
* <td><code>X-Forwarded-Port</code> Header</td>
|
||||
* <td>n/a</td>
|
||||
* <td>Required</td>
|
||||
* <td>n/a</td>
|
||||
* <td>left-most value (only if {@link #getForwardedPortAsAuthority()} is true)</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>4</td>
|
||||
* <td><code>X-Forwarded-Server</code> Header</td>
|
||||
* <td>Required</td>
|
||||
* <td>Optional</td>
|
||||
* <td>n/a</td>
|
||||
* <td>left-most value</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>5</td>
|
||||
* <td><code>X-Forwarded-Proto</code> Header</td>
|
||||
* <td>n/a</td>
|
||||
* <td>Implied from value</td>
|
||||
* <td>Required</td>
|
||||
* <td>
|
||||
* <p>left-most value becomes protocol.</p>
|
||||
* <ul>
|
||||
* <li>Value of "<code>http</code>" means port=80.</li>
|
||||
* <li>Value of "{@link HttpConfiguration#getSecureScheme()}" means port={@link HttpConfiguration#getSecurePort()}.</li>
|
||||
* </ul>
|
||||
* </td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>6</td>
|
||||
* <td><code>X-Proxied-Https</code> Header</td>
|
||||
* <td>n/a</td>
|
||||
* <td>Implied from value</td>
|
||||
* <td>boolean</td>
|
||||
* <td>
|
||||
* <p>left-most value determines protocol and port.</p>
|
||||
* <ul>
|
||||
* <li>Value of "<code>on</code>" means port={@link HttpConfiguration#getSecurePort()}, and protocol={@link HttpConfiguration#getSecureScheme()}).</li>
|
||||
* <li>Value of "<code>off</code>" means port=80, and protocol=http.</li>
|
||||
* </ul>
|
||||
* </td>
|
||||
* </tr>
|
||||
* </tbody>
|
||||
* </table>
|
||||
*
|
||||
* @see <a href="http://en.wikipedia.org/wiki/X-Forwarded-For">Wikipedia: X-Forwarded-For</a>
|
||||
* @see <a href="https://tools.ietf.org/html/rfc7239">RFC 7239: Forwarded HTTP Extension</a>
|
||||
*/
|
||||
public class ForwardedRequestCustomizer implements Customizer
|
||||
{
|
||||
|
@ -152,7 +235,7 @@ public class ForwardedRequestCustomizer implements Customizer
|
|||
*/
|
||||
public void setForcedHost(String hostAndPort)
|
||||
{
|
||||
_forcedHost = new HostPortHttpField(new ForcedHostPort(hostAndPort));
|
||||
_forcedHost = new HostPortHttpField(hostAndPort);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -375,7 +458,6 @@ public class ForwardedRequestCustomizer implements Customizer
|
|||
public void customize(Connector connector, HttpConfiguration config, Request request)
|
||||
{
|
||||
HttpFields httpFields = request.getHttpFields();
|
||||
boolean wasSecure = request.isSecure();
|
||||
|
||||
// Do a single pass through the header fields as it is a more efficient single iteration.
|
||||
Forwarded forwarded = new Forwarded(request, config);
|
||||
|
@ -399,49 +481,97 @@ public class ForwardedRequestCustomizer implements Customizer
|
|||
|
||||
if (match)
|
||||
{
|
||||
String proto = "http";
|
||||
|
||||
HttpURI.Mutable builder = HttpURI.build(request.getHttpURI());
|
||||
boolean httpUriChanged = false;
|
||||
|
||||
// Is secure status configured from headers?
|
||||
if (forwarded.isSecure())
|
||||
{
|
||||
// set default to https
|
||||
proto = config.getSecureScheme();
|
||||
}
|
||||
|
||||
// Set Scheme from configured protocol
|
||||
if (forwarded._proto != null)
|
||||
{
|
||||
builder.scheme(forwarded._proto);
|
||||
if (forwarded._proto.equalsIgnoreCase(config.getSecureScheme()))
|
||||
request.setSecure(true);
|
||||
proto = forwarded._proto;
|
||||
builder.scheme(proto);
|
||||
httpUriChanged = true;
|
||||
}
|
||||
|
||||
if (forwarded._server != null && forwarded._host instanceof PortSetHostPort)
|
||||
// Set authority
|
||||
String host = null;
|
||||
int port = -1;
|
||||
|
||||
// Use authority from headers, if configured.
|
||||
if (forwarded._authority != null)
|
||||
{
|
||||
request.setHttpFields(HttpFields.build(httpFields,
|
||||
new HostPortHttpField(forwarded._server, forwarded._host.getPort())));
|
||||
builder.host(forwarded._server).port(forwarded._host.getPort());
|
||||
}
|
||||
else if (forwarded._host != null)
|
||||
{
|
||||
request.setHttpFields(HttpFields.build(httpFields, new HostPortHttpField(forwarded._host)));
|
||||
builder.host(forwarded._host.getHost()).port(forwarded._host.getPort());
|
||||
}
|
||||
else if (forwarded._server != null)
|
||||
{
|
||||
request.setHttpFields(HttpFields.build(httpFields, new HostPortHttpField(forwarded._server)));
|
||||
builder.host(forwarded._server).port(0);
|
||||
host = forwarded._authority._host;
|
||||
port = forwarded._authority._port;
|
||||
}
|
||||
|
||||
if (forwarded._for != null)
|
||||
// Fall back to request metadata if needed.
|
||||
if (host == null)
|
||||
{
|
||||
int port = forwarded._for.getPort() > 0 ? forwarded._for.getPort() : request.getRemotePort();
|
||||
request.setRemoteAddr(InetSocketAddress.createUnresolved(forwarded._for.getHost(), port));
|
||||
host = builder.getHost();
|
||||
}
|
||||
if (port == MutableHostPort.UNSET) // is unset by headers
|
||||
{
|
||||
port = builder.getPort();
|
||||
}
|
||||
if (port == MutableHostPort.IMPLIED) // is implied
|
||||
{
|
||||
// get Implied port (from protocol / scheme) and HttpConfiguration
|
||||
int defaultPort = 80;
|
||||
port = proto.equalsIgnoreCase(config.getSecureScheme()) ? getSecurePort(config) : defaultPort;
|
||||
}
|
||||
|
||||
if (request.isSecure() && !wasSecure)
|
||||
builder.scheme(HttpScheme.HTTPS);
|
||||
request.setHttpURI(builder);
|
||||
// Update authority if different from metadata
|
||||
if (!host.equalsIgnoreCase(builder.getHost()) ||
|
||||
port != builder.getPort())
|
||||
{
|
||||
request.setHttpFields(HttpFields.build(httpFields, new HostPortHttpField(host, port)));
|
||||
builder.authority(host, port);
|
||||
httpUriChanged = true;
|
||||
}
|
||||
|
||||
// Set secure status
|
||||
if (forwarded.isSecure() ||
|
||||
proto.equalsIgnoreCase(config.getSecureScheme()) ||
|
||||
port == getSecurePort(config))
|
||||
{
|
||||
request.setSecure(true);
|
||||
builder.scheme(proto);
|
||||
httpUriChanged = true;
|
||||
}
|
||||
|
||||
// Set Remote Address
|
||||
if (forwarded.hasFor())
|
||||
{
|
||||
int forPort = forwarded._for._port > 0 ? forwarded._for._port : request.getRemotePort();
|
||||
request.setRemoteAddr(InetSocketAddress.createUnresolved(forwarded._for._host, forPort));
|
||||
}
|
||||
|
||||
if (httpUriChanged)
|
||||
{
|
||||
request.setHttpURI(builder);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected static int getSecurePort(HttpConfiguration config)
|
||||
{
|
||||
return config.getSecurePort() > 0 ? config.getSecurePort() : 443;
|
||||
}
|
||||
|
||||
protected void onError(HttpField field, Throwable t)
|
||||
{
|
||||
throw new BadMessageException("Bad header value for " + field.getName(), t);
|
||||
}
|
||||
|
||||
protected String getLeftMost(String headerValue)
|
||||
protected static String getLeftMost(String headerValue)
|
||||
{
|
||||
if (headerValue == null)
|
||||
return null;
|
||||
|
@ -492,23 +622,23 @@ public class ForwardedRequestCustomizer implements Customizer
|
|||
size += 128; // experimented good baseline size
|
||||
_handles = new ArrayTrie<>(size);
|
||||
|
||||
if (updateForwardedHandle(lookup, getForwardedCipherSuiteHeader(), "handleCipherSuite"))
|
||||
continue;
|
||||
if (updateForwardedHandle(lookup, getForwardedSslSessionIdHeader(), "handleSslSessionId"))
|
||||
continue;
|
||||
if (updateForwardedHandle(lookup, getForwardedHeader(), "handleRFC7239"))
|
||||
continue;
|
||||
if (updateForwardedHandle(lookup, getForwardedForHeader(), "handleFor"))
|
||||
if (updateForwardedHandle(lookup, getForwardedHostHeader(), "handleForwardedHost"))
|
||||
continue;
|
||||
if (updateForwardedHandle(lookup, getForwardedPortHeader(), "handlePort"))
|
||||
if (updateForwardedHandle(lookup, getForwardedForHeader(), "handleForwardedFor"))
|
||||
continue;
|
||||
if (updateForwardedHandle(lookup, getForwardedHostHeader(), "handleHost"))
|
||||
if (updateForwardedHandle(lookup, getForwardedPortHeader(), "handleForwardedPort"))
|
||||
continue;
|
||||
if (updateForwardedHandle(lookup, getForwardedProtoHeader(), "handleProto"))
|
||||
continue;
|
||||
if (updateForwardedHandle(lookup, getForwardedHttpsHeader(), "handleHttps"))
|
||||
continue;
|
||||
if (updateForwardedHandle(lookup, getForwardedServerHeader(), "handleServer"))
|
||||
if (updateForwardedHandle(lookup, getForwardedServerHeader(), "handleForwardedServer"))
|
||||
continue;
|
||||
if (updateForwardedHandle(lookup, getForwardedCipherSuiteHeader(), "handleCipherSuite"))
|
||||
continue;
|
||||
if (updateForwardedHandle(lookup, getForwardedSslSessionIdHeader(), "handleSslSessionId"))
|
||||
continue;
|
||||
break;
|
||||
}
|
||||
|
@ -529,40 +659,98 @@ public class ForwardedRequestCustomizer implements Customizer
|
|||
return !_handles.put(headerName, lookup.findVirtual(Forwarded.class, forwardedMethodName, type));
|
||||
}
|
||||
|
||||
private static class ForcedHostPort extends HostPort
|
||||
private static class MutableHostPort
|
||||
{
|
||||
ForcedHostPort(String authority)
|
||||
public static final int UNSET = -1;
|
||||
public static final int IMPLIED = 0;
|
||||
|
||||
String _host;
|
||||
Source _hostSource = Source.UNSET;
|
||||
int _port = UNSET;
|
||||
Source _portSource = Source.UNSET;
|
||||
|
||||
public void setHostPort(String host, int port, Source source)
|
||||
{
|
||||
super(authority);
|
||||
setHost(host, source);
|
||||
setPort(port, source);
|
||||
}
|
||||
|
||||
public void setHost(String host, Source source)
|
||||
{
|
||||
if (source.priority() > _hostSource.priority())
|
||||
{
|
||||
_host = host;
|
||||
_hostSource = source;
|
||||
}
|
||||
}
|
||||
|
||||
public void setPort(int port, Source source)
|
||||
{
|
||||
if (source.priority() > _portSource.priority())
|
||||
{
|
||||
_port = port;
|
||||
_portSource = source;
|
||||
}
|
||||
}
|
||||
|
||||
public void setHostPort(HostPort hostPort, Source source)
|
||||
{
|
||||
if (source.priority() > _hostSource.priority())
|
||||
{
|
||||
_host = hostPort.getHost();
|
||||
_hostSource = source;
|
||||
}
|
||||
|
||||
int port = hostPort.getPort();
|
||||
// Is port supplied?
|
||||
if (port > 0 && source.priority() > _portSource.priority())
|
||||
{
|
||||
_port = hostPort.getPort();
|
||||
_portSource = source;
|
||||
}
|
||||
// Since we are Host:Port pair, the port could be unspecified
|
||||
// Meaning it's implied.
|
||||
// Make sure that we switch the tracked port from unset to implied
|
||||
else if (_port == UNSET)
|
||||
{
|
||||
// set port to implied (with no priority)
|
||||
_port = IMPLIED;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
final StringBuilder sb = new StringBuilder("MutableHostPort{");
|
||||
sb.append("host='").append(_host).append("'/").append(_hostSource);
|
||||
sb.append(", port=").append(_port);
|
||||
sb.append("/").append(_portSource);
|
||||
sb.append('}');
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
||||
private static class PossiblyPartialHostPort extends HostPort
|
||||
/**
|
||||
* Ordered Source Enum.
|
||||
* <p>
|
||||
* Lowest first, Last/Highest priority wins
|
||||
* </p>
|
||||
*/
|
||||
public enum Source
|
||||
{
|
||||
PossiblyPartialHostPort(String authority)
|
||||
{
|
||||
super(authority);
|
||||
}
|
||||
UNSET,
|
||||
XPROXIED_HTTPS,
|
||||
XFORWARDED_PROTO,
|
||||
XFORWARDED_SERVER,
|
||||
XFORWARDED_PORT,
|
||||
XFORWARDED_FOR,
|
||||
XFORWARDED_HOST,
|
||||
FORWARDED,
|
||||
FORCED;
|
||||
|
||||
protected PossiblyPartialHostPort(String host, int port)
|
||||
int priority()
|
||||
{
|
||||
super(host, port);
|
||||
}
|
||||
}
|
||||
|
||||
private static class PortSetHostPort extends PossiblyPartialHostPort
|
||||
{
|
||||
PortSetHostPort(String host, int port)
|
||||
{
|
||||
super(host, port);
|
||||
}
|
||||
}
|
||||
|
||||
private static class Rfc7239HostPort extends HostPort
|
||||
{
|
||||
Rfc7239HostPort(String authority)
|
||||
{
|
||||
super(authority);
|
||||
return ordinal();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -571,11 +759,11 @@ public class ForwardedRequestCustomizer implements Customizer
|
|||
HttpConfiguration _config;
|
||||
Request _request;
|
||||
|
||||
boolean _protoRfc7239;
|
||||
MutableHostPort _authority;
|
||||
MutableHostPort _for;
|
||||
String _proto;
|
||||
HostPort _for;
|
||||
HostPort _host;
|
||||
String _server;
|
||||
Source _protoSource = Source.UNSET;
|
||||
Boolean _secure;
|
||||
|
||||
public Forwarded(Request request, HttpConfiguration config)
|
||||
{
|
||||
|
@ -583,7 +771,40 @@ public class ForwardedRequestCustomizer implements Customizer
|
|||
_request = request;
|
||||
_config = config;
|
||||
if (_forcedHost != null)
|
||||
_host = _forcedHost.getHostPort();
|
||||
{
|
||||
getAuthority().setHostPort(
|
||||
_forcedHost.getHostPort().getHost(),
|
||||
_forcedHost.getHostPort().getPort(),
|
||||
Source.FORCED);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isSecure()
|
||||
{
|
||||
return (_secure != null && _secure);
|
||||
}
|
||||
|
||||
public boolean hasFor()
|
||||
{
|
||||
return _for != null && _for._host != null;
|
||||
}
|
||||
|
||||
private MutableHostPort getAuthority()
|
||||
{
|
||||
if (_authority == null)
|
||||
{
|
||||
_authority = new MutableHostPort();
|
||||
}
|
||||
return _authority;
|
||||
}
|
||||
|
||||
private MutableHostPort getFor()
|
||||
{
|
||||
if (_for == null)
|
||||
{
|
||||
_for = new MutableHostPort();
|
||||
}
|
||||
return _for;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
|
@ -591,7 +812,9 @@ public class ForwardedRequestCustomizer implements Customizer
|
|||
{
|
||||
_request.setAttribute("javax.servlet.request.cipher_suite", field.getValue());
|
||||
if (isSslIsSecure())
|
||||
_request.setSecure(true);
|
||||
{
|
||||
_secure = true;
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
|
@ -599,84 +822,55 @@ public class ForwardedRequestCustomizer implements Customizer
|
|||
{
|
||||
_request.setAttribute("javax.servlet.request.ssl_session_id", field.getValue());
|
||||
if (isSslIsSecure())
|
||||
_request.setSecure(true);
|
||||
{
|
||||
_secure = true;
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public void handleHost(HttpField field)
|
||||
public void handleForwardedHost(HttpField field)
|
||||
{
|
||||
updateAuthority(getLeftMost(field.getValue()), Source.XFORWARDED_HOST);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public void handleForwardedFor(HttpField field)
|
||||
{
|
||||
HostPort hostField = new HostPort(getLeftMost(field.getValue()));
|
||||
|
||||
if (getForwardedPortAsAuthority() && !StringUtil.isEmpty(getForwardedPortHeader()))
|
||||
{
|
||||
if (_host == null)
|
||||
_host = new PossiblyPartialHostPort(hostField.getHost(), hostField.getPort());
|
||||
else if (_host instanceof PortSetHostPort)
|
||||
_host = new HostPort(hostField.getHost(), hostField.getPort() > 0 ? hostField.getPort() : _host.getPort());
|
||||
}
|
||||
else if (_host == null)
|
||||
{
|
||||
_host = hostField;
|
||||
}
|
||||
getFor().setHostPort(hostField, Source.XFORWARDED_FOR);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public void handleServer(HttpField field)
|
||||
public void handleForwardedServer(HttpField field)
|
||||
{
|
||||
if (getProxyAsAuthority())
|
||||
return;
|
||||
_server = getLeftMost(field.getValue());
|
||||
updateAuthority(getLeftMost(field.getValue()), Source.XFORWARDED_SERVER);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public void handleForwardedPort(HttpField field)
|
||||
{
|
||||
int port = HostPort.parsePort(getLeftMost(field.getValue()));
|
||||
|
||||
updatePort(port, Source.XFORWARDED_PORT);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public void handleProto(HttpField field)
|
||||
{
|
||||
if (_proto == null)
|
||||
_proto = getLeftMost(field.getValue());
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public void handleFor(HttpField field)
|
||||
{
|
||||
String authority = getLeftMost(field.getValue());
|
||||
if (!getForwardedPortAsAuthority() && !StringUtil.isEmpty(getForwardedPortHeader()))
|
||||
{
|
||||
if (_for == null)
|
||||
_for = new PossiblyPartialHostPort(authority);
|
||||
else if (_for instanceof PortSetHostPort)
|
||||
_for = new HostPort(HostPort.normalizeHost(authority), _for.getPort());
|
||||
}
|
||||
else if (_for == null)
|
||||
{
|
||||
_for = new HostPort(authority);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public void handlePort(HttpField field)
|
||||
{
|
||||
int port = HostPort.parsePort(getLeftMost(field.getValue()));
|
||||
if (!getForwardedPortAsAuthority())
|
||||
{
|
||||
if (_for == null)
|
||||
_for = new PortSetHostPort(_request.getRemoteHost(), port);
|
||||
else if (_for instanceof PossiblyPartialHostPort && _for.getPort() <= 0)
|
||||
_for = new HostPort(HostPort.normalizeHost(_for.getHost()), port);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_host == null)
|
||||
_host = new PortSetHostPort(_request.getServerName(), port);
|
||||
else if (_host instanceof PossiblyPartialHostPort && _host.getPort() <= 0)
|
||||
_host = new HostPort(HostPort.normalizeHost(_host.getHost()), port);
|
||||
}
|
||||
updateProto(getLeftMost(field.getValue()), Source.XFORWARDED_PROTO);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public void handleHttps(HttpField field)
|
||||
{
|
||||
if (_proto == null && ("on".equalsIgnoreCase(field.getValue()) || "true".equalsIgnoreCase(field.getValue())))
|
||||
_proto = HttpScheme.HTTPS.asString();
|
||||
if ("on".equalsIgnoreCase(field.getValue()) || "true".equalsIgnoreCase(field.getValue()))
|
||||
{
|
||||
_secure = true;
|
||||
updateProto(HttpScheme.HTTPS.asString(), Source.XPROXIED_HTTPS);
|
||||
updatePort(getSecurePort(_config), Source.XPROXIED_HTTPS);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
|
@ -695,36 +889,68 @@ public class ForwardedRequestCustomizer implements Customizer
|
|||
switch (name)
|
||||
{
|
||||
case "by":
|
||||
{
|
||||
if (!getProxyAsAuthority())
|
||||
break;
|
||||
if (value.startsWith("_") || "unknown".equals(value))
|
||||
break;
|
||||
if (_host == null || !(_host instanceof Rfc7239HostPort))
|
||||
_host = new Rfc7239HostPort(value);
|
||||
HostPort hostField = new HostPort(value);
|
||||
getAuthority().setHostPort(hostField.getHost(), hostField.getPort(), Source.FORWARDED);
|
||||
break;
|
||||
}
|
||||
case "for":
|
||||
{
|
||||
if (value.startsWith("_") || "unknown".equals(value))
|
||||
break;
|
||||
if (_for == null || !(_for instanceof Rfc7239HostPort))
|
||||
_for = new Rfc7239HostPort(value);
|
||||
HostPort hostField = new HostPort(value);
|
||||
getFor().setHostPort(hostField.getHost(), hostField.getPort(), Source.FORWARDED);
|
||||
break;
|
||||
}
|
||||
case "host":
|
||||
{
|
||||
if (value.startsWith("_") || "unknown".equals(value))
|
||||
break;
|
||||
if (_host == null || !(_host instanceof Rfc7239HostPort))
|
||||
_host = new Rfc7239HostPort(value);
|
||||
HostPort hostField = new HostPort(value);
|
||||
getAuthority().setHostPort(hostField.getHost(), hostField.getPort(), Source.FORWARDED);
|
||||
break;
|
||||
}
|
||||
case "proto":
|
||||
if (_proto == null || !_protoRfc7239)
|
||||
{
|
||||
_protoRfc7239 = true;
|
||||
_proto = value;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
updateProto(value, Source.FORWARDED);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void updateAuthority(String value, Source source)
|
||||
{
|
||||
HostPort hostField = new HostPort(value);
|
||||
getAuthority().setHostPort(hostField, source);
|
||||
}
|
||||
|
||||
private void updatePort(int port, Source source)
|
||||
{
|
||||
if (getForwardedPortAsAuthority())
|
||||
{
|
||||
getAuthority().setPort(port, source);
|
||||
}
|
||||
else
|
||||
{
|
||||
getFor().setPort(port, source);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateProto(String proto, Source source)
|
||||
{
|
||||
if (source.priority() > _protoSource.priority())
|
||||
{
|
||||
_proto = proto;
|
||||
_protoSource = source;
|
||||
|
||||
if (_proto.equalsIgnoreCase(_config.getSecureScheme()))
|
||||
{
|
||||
_secure = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,200 +0,0 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under
|
||||
// the terms of the Eclipse Public License 2.0 which is available at
|
||||
// https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// This Source Code may also be made available under the following
|
||||
// Secondary Licenses when the conditions for such availability set
|
||||
// forth in the Eclipse Public License, v. 2.0 are satisfied:
|
||||
// the Apache License v2.0 which is available at
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
|
||||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.eclipse.jetty.server;
|
||||
|
||||
import java.io.IOException;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.eclipse.jetty.server.handler.AbstractHandler;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class CheckReverseProxyHeadersTest
|
||||
{
|
||||
@Test
|
||||
public void testCheckReverseProxyHeaders() throws Exception
|
||||
{
|
||||
// Classic ProxyPass from example.com:80 to localhost:8080
|
||||
testRequest("Host: localhost:8080\n" +
|
||||
"X-Forwarded-For: 10.20.30.40\n" +
|
||||
"X-Forwarded-Host: example.com", request ->
|
||||
{
|
||||
assertEquals("example.com", request.getServerName());
|
||||
assertEquals(80, request.getServerPort());
|
||||
assertEquals("10.20.30.40", request.getRemoteAddr());
|
||||
assertEquals("10.20.30.40", request.getRemoteHost());
|
||||
assertEquals("example.com", request.getHeader("Host"));
|
||||
assertEquals("http", request.getScheme());
|
||||
assertFalse(request.isSecure());
|
||||
});
|
||||
|
||||
// IPv6 ProxyPass from example.com:80 to localhost:8080
|
||||
testRequest("Host: localhost:8080\n" +
|
||||
"X-Forwarded-For: 10.20.30.40\n" +
|
||||
"X-Forwarded-Host: [::1]", request ->
|
||||
{
|
||||
assertEquals("[::1]", request.getServerName());
|
||||
assertEquals(80, request.getServerPort());
|
||||
assertEquals("10.20.30.40", request.getRemoteAddr());
|
||||
assertEquals("10.20.30.40", request.getRemoteHost());
|
||||
assertEquals("[::1]", request.getHeader("Host"));
|
||||
assertEquals("http", request.getScheme());
|
||||
assertFalse(request.isSecure());
|
||||
});
|
||||
|
||||
// IPv6 ProxyPass from example.com:80 to localhost:8080
|
||||
testRequest("Host: localhost:8080\n" +
|
||||
"X-Forwarded-For: 10.20.30.40\n" +
|
||||
"X-Forwarded-Host: [::1]:8888", request ->
|
||||
{
|
||||
assertEquals("[::1]", request.getServerName());
|
||||
assertEquals(8888, request.getServerPort());
|
||||
assertEquals("10.20.30.40", request.getRemoteAddr());
|
||||
assertEquals("10.20.30.40", request.getRemoteHost());
|
||||
assertEquals("[::1]:8888", request.getHeader("Host"));
|
||||
assertEquals("http", request.getScheme());
|
||||
assertFalse(request.isSecure());
|
||||
});
|
||||
|
||||
// ProxyPass from example.com:81 to localhost:8080
|
||||
testRequest("Host: localhost:8080\n" +
|
||||
"X-Forwarded-For: 10.20.30.40\n" +
|
||||
"X-Forwarded-Host: example.com:81\n" +
|
||||
"X-Forwarded-Server: example.com\n" +
|
||||
"X-Forwarded-Proto: https", request ->
|
||||
{
|
||||
assertEquals("example.com", request.getServerName());
|
||||
assertEquals(81, request.getServerPort());
|
||||
assertEquals("10.20.30.40", request.getRemoteAddr());
|
||||
assertEquals("10.20.30.40", request.getRemoteHost());
|
||||
assertEquals("example.com:81", request.getHeader("Host"));
|
||||
assertEquals("https", request.getScheme());
|
||||
assertTrue(request.isSecure());
|
||||
|
||||
});
|
||||
|
||||
// Multiple ProxyPass from example.com:80 to rp.example.com:82 to localhost:8080
|
||||
testRequest("Host: localhost:8080\n" +
|
||||
"X-Forwarded-For: 10.20.30.40, 10.0.0.1\n" +
|
||||
"X-Forwarded-Host: example.com, rp.example.com:82\n" +
|
||||
"X-Forwarded-Server: example.com, rp.example.com\n" +
|
||||
"X-Forwarded-Proto: https, http", request ->
|
||||
{
|
||||
assertEquals("example.com", request.getServerName());
|
||||
assertEquals(443, request.getServerPort());
|
||||
assertEquals("10.20.30.40", request.getRemoteAddr());
|
||||
assertEquals("10.20.30.40", request.getRemoteHost());
|
||||
assertEquals("example.com", request.getHeader("Host"));
|
||||
assertEquals("https", request.getScheme());
|
||||
assertTrue(request.isSecure());
|
||||
});
|
||||
}
|
||||
|
||||
private void testRequest(String headers, RequestValidator requestValidator) throws Exception
|
||||
{
|
||||
Server server = new Server();
|
||||
// Activate reverse proxy headers checking
|
||||
HttpConnectionFactory http = new HttpConnectionFactory();
|
||||
http.getHttpConfiguration().addCustomizer(new ForwardedRequestCustomizer());
|
||||
|
||||
LocalConnector connector = new LocalConnector(server, http);
|
||||
|
||||
server.setConnectors(new Connector[]{connector});
|
||||
ValidationHandler validationHandler = new ValidationHandler(requestValidator);
|
||||
server.setHandler(validationHandler);
|
||||
|
||||
try
|
||||
{
|
||||
server.start();
|
||||
connector.getResponse("GET / HTTP/1.1\r\n" + "Connection: close\r\n" + headers + "\r\n\r\n");
|
||||
Error error = validationHandler.getError();
|
||||
|
||||
if (error != null)
|
||||
{
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
server.stop();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface for validate a wrapped request.
|
||||
*/
|
||||
private static interface RequestValidator
|
||||
{
|
||||
/**
|
||||
* Validate the current request.
|
||||
*
|
||||
* @param request the request.
|
||||
*/
|
||||
void validate(HttpServletRequest request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler for validation.
|
||||
*/
|
||||
private static class ValidationHandler extends AbstractHandler
|
||||
{
|
||||
private final RequestValidator _requestValidator;
|
||||
private Error _error;
|
||||
|
||||
private ValidationHandler(RequestValidator requestValidator)
|
||||
{
|
||||
_requestValidator = requestValidator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the validation error.
|
||||
*
|
||||
* @return the validation error or <code>null</code> if there was no error.
|
||||
*/
|
||||
public Error getError()
|
||||
{
|
||||
return _error;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
|
||||
{
|
||||
try
|
||||
{
|
||||
_requestValidator.validate(request);
|
||||
}
|
||||
catch (Error e)
|
||||
{
|
||||
_error = e;
|
||||
}
|
||||
catch (Throwable e)
|
||||
{
|
||||
_error = new Error(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -76,6 +76,7 @@ public class ForwardedRequestCustomizerTest
|
|||
http.getHttpConfiguration().setRequestHeaderSize(512);
|
||||
http.getHttpConfiguration().setResponseHeaderSize(512);
|
||||
http.getHttpConfiguration().setOutputBufferSize(2048);
|
||||
http.getHttpConfiguration().setSecurePort(443);
|
||||
customizer = new ForwardedRequestCustomizer();
|
||||
http.getHttpConfiguration().addCustomizer(customizer);
|
||||
connector = new LocalConnector(server, http);
|
||||
|
@ -277,6 +278,83 @@ public class ForwardedRequestCustomizerTest
|
|||
.requestURL("https://myhost/")
|
||||
),
|
||||
|
||||
// =================================================================
|
||||
// ProxyPass usages
|
||||
Arguments.of(new Request("ProxyPass (example.com:80 to localhost:8080)")
|
||||
.headers(
|
||||
"GET / HTTP/1.1",
|
||||
"Host: localhost:8080",
|
||||
"X-Forwarded-For: 10.20.30.40",
|
||||
"X-Forwarded-Host: example.com"
|
||||
),
|
||||
new Expectations()
|
||||
.scheme("http").serverName("example.com").serverPort(80)
|
||||
.remoteAddr("10.20.30.40")
|
||||
.requestURL("http://example.com/")
|
||||
),
|
||||
Arguments.of(new Request("ProxyPass (example.com:81 to localhost:8080)")
|
||||
.headers(
|
||||
"GET / HTTP/1.1",
|
||||
"Host: localhost:8080",
|
||||
"X-Forwarded-For: 10.20.30.40",
|
||||
"X-Forwarded-Host: example.com:81",
|
||||
"X-Forwarded-Server: example.com",
|
||||
"X-Forwarded-Proto: https"
|
||||
),
|
||||
new Expectations()
|
||||
.scheme("https").serverName("example.com").serverPort(81)
|
||||
.remoteAddr("10.20.30.40")
|
||||
.requestURL("https://example.com:81/")
|
||||
),
|
||||
Arguments.of(new Request("ProxyPass (example.com:443 to localhost:8443)")
|
||||
.headers(
|
||||
"GET / HTTP/1.1",
|
||||
"Host: localhost:8443",
|
||||
"X-Forwarded-Host: example.com",
|
||||
"X-Forwarded-Proto: https"
|
||||
),
|
||||
new Expectations()
|
||||
.scheme("https").serverName("example.com").serverPort(443)
|
||||
.requestURL("https://example.com/")
|
||||
),
|
||||
Arguments.of(new Request("ProxyPass (IPv6 from [::1]:80 to localhost:8080)")
|
||||
.headers(
|
||||
"GET / HTTP/1.1",
|
||||
"Host: localhost:8080",
|
||||
"X-Forwarded-For: 10.20.30.40",
|
||||
"X-Forwarded-Host: [::1]"
|
||||
),
|
||||
new Expectations()
|
||||
.scheme("http").serverName("[::1]").serverPort(80)
|
||||
.remoteAddr("10.20.30.40")
|
||||
.requestURL("http://[::1]/")
|
||||
),
|
||||
Arguments.of(new Request("ProxyPass (IPv6 from [::1]:8888 to localhost:8080)")
|
||||
.headers(
|
||||
"GET / HTTP/1.1",
|
||||
"Host: localhost:8080",
|
||||
"X-Forwarded-For: 10.20.30.40",
|
||||
"X-Forwarded-Host: [::1]:8888"
|
||||
),
|
||||
new Expectations()
|
||||
.scheme("http").serverName("[::1]").serverPort(8888)
|
||||
.remoteAddr("10.20.30.40")
|
||||
.requestURL("http://[::1]:8888/")
|
||||
),
|
||||
Arguments.of(new Request("Multiple ProxyPass (example.com:80 to rp.example.com:82 to localhost:8080)")
|
||||
.headers(
|
||||
"GET / HTTP/1.1",
|
||||
"Host: localhost:8080",
|
||||
"X-Forwarded-For: 10.20.30.40, 10.0.0.1",
|
||||
"X-Forwarded-Host: example.com, rp.example.com:82",
|
||||
"X-Forwarded-Server: example.com, rp.example.com",
|
||||
"X-Forwarded-Proto: https, http"
|
||||
),
|
||||
new Expectations()
|
||||
.scheme("https").serverName("example.com").serverPort(443)
|
||||
.remoteAddr("10.20.30.40")
|
||||
.requestURL("https://example.com/")
|
||||
),
|
||||
// =================================================================
|
||||
// X-Forwarded-* usages
|
||||
Arguments.of(new Request("X-Forwarded-Proto (old syntax)")
|
||||
|
@ -574,7 +652,34 @@ public class ForwardedRequestCustomizerTest
|
|||
.requestURL("http://example.com/")
|
||||
.remoteAddr("192.0.2.43").remotePort(0)
|
||||
),
|
||||
|
||||
// =================================================================
|
||||
// Forced Behavior
|
||||
Arguments.of(new Request("Forced Host (no port)")
|
||||
.configureCustomizer((customizer) -> customizer.setForcedHost("always.example.com"))
|
||||
.headers(
|
||||
"GET / HTTP/1.1",
|
||||
"Host: myhost",
|
||||
"X-Forwarded-For: 11.9.8.7:1111",
|
||||
"X-Forwarded-Host: example.com:2222"
|
||||
),
|
||||
new Expectations()
|
||||
.scheme("http").serverName("always.example.com").serverPort(80)
|
||||
.requestURL("http://always.example.com/")
|
||||
.remoteAddr("11.9.8.7").remotePort(1111)
|
||||
),
|
||||
Arguments.of(new Request("Forced Host with port")
|
||||
.configureCustomizer((customizer) -> customizer.setForcedHost("always.example.com:9090"))
|
||||
.headers(
|
||||
"GET / HTTP/1.1",
|
||||
"Host: myhost",
|
||||
"X-Forwarded-For: 11.9.8.7:1111",
|
||||
"X-Forwarded-Host: example.com:2222"
|
||||
),
|
||||
new Expectations()
|
||||
.scheme("http").serverName("always.example.com").serverPort(9090)
|
||||
.requestURL("http://always.example.com:9090/")
|
||||
.remoteAddr("11.9.8.7").remotePort(1111)
|
||||
),
|
||||
// =================================================================
|
||||
// Legacy Headers
|
||||
Arguments.of(new Request("X-Proxied-Https")
|
||||
|
@ -650,7 +755,7 @@ public class ForwardedRequestCustomizerTest
|
|||
request.configure(customizer);
|
||||
|
||||
String rawRequest = request.getRawRequest((header) -> header);
|
||||
System.out.println(rawRequest);
|
||||
// System.out.println(rawRequest);
|
||||
|
||||
HttpTester.Response response = HttpTester.parseResponse(connector.getResponse(rawRequest));
|
||||
assertThat("status", response.getStatus(), is(200));
|
||||
|
@ -670,7 +775,7 @@ public class ForwardedRequestCustomizerTest
|
|||
.replaceFirst("X-Proxied-Https:", "Jetty-Proxied-Https:")
|
||||
.replaceFirst("Proxy-Ssl-Id:", "Jetty-Proxy-Ssl-Id:")
|
||||
.replaceFirst("Proxy-auth-cert:", "Jetty-Proxy-Auth-Cert:"));
|
||||
System.out.println(rawRequest);
|
||||
// System.out.println(rawRequest);
|
||||
|
||||
HttpTester.Response response = HttpTester.parseResponse(connectorConfigured.getResponse(rawRequest));
|
||||
assertThat("status", response.getStatus(), is(200));
|
||||
|
@ -691,7 +796,7 @@ public class ForwardedRequestCustomizerTest
|
|||
request.configure(customizer);
|
||||
|
||||
String rawRequest = request.getRawRequest((header) -> header);
|
||||
System.out.println(rawRequest);
|
||||
// System.out.println(rawRequest);
|
||||
|
||||
HttpTester.Response response = HttpTester.parseResponse(connector.getResponse(rawRequest));
|
||||
assertThat("status", response.getStatus(), is(400));
|
||||
|
|
Loading…
Reference in New Issue