//
// ========================================================================
// 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.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.net.InetSocketAddress;
import jakarta.servlet.ServletRequest;
import org.eclipse.jetty.http.BadMessageException;
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.HttpURI;
import org.eclipse.jetty.http.QuotedCSVParser;
import org.eclipse.jetty.server.HttpConfiguration.Customizer;
import org.eclipse.jetty.util.HostPort;
import org.eclipse.jetty.util.Index;
import org.eclipse.jetty.util.StringUtil;
import static java.lang.invoke.MethodType.methodType;
Customize Requests for Proxy Forwarding.
This customizer looks at at HTTP request for headers that indicate
it has been forwarded by one or more proxies. Specifically handled are
Forwarded
, as defined by rfc7239
X-Forwarded-Host
X-Forwarded-Server
X-Forwarded-For
X-Forwarded-Proto
X-Proxied-Https
If these headers are present, then the Request
object is updated so that the proxy is not seen as the other end point of the connection on which the request came
Headers can also be defined so that forwarded SSL Session IDs and Cipher
suites may be customised
The Authority (host and port) is updated on the Request
object based on the host / port information in the following search order.
Request Authority Search Order
#
Value Origin
Host
Port
Protocol
Notes
1
Forwarded
Header
"host=<host>
" param (Required)
"host=<host>:<port>
param (Implied)
"proto=<value>
" param (Optional)
From left-most relevant parameter (see rfc7239)
2
X-Forwarded-Host
Header
Required
Implied
n/a
left-most value
3
X-Forwarded-Port
Header
n/a
Required
n/a
left-most value (only if getForwardedPortAsAuthority()
is true)
4
X-Forwarded-Server
Header
Required
Optional
n/a
left-most value
5
X-Forwarded-Proto
Header
n/a
Implied from value
Required
left-most value becomes protocol.
- Value of "
http
" means port=80.
- Value of "
HttpConfiguration.getSecureScheme()
" means port=HttpConfiguration.getSecurePort()
.
6
X-Proxied-Https
Header
n/a
Implied from value
boolean
left-most value determines protocol and port.
- Value of "
on
" means port=HttpConfiguration.getSecurePort()
, and protocol=HttpConfiguration.getSecureScheme()
).
- Value of "
off
" means port=80, and protocol=http.
See Also:
/**
* 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
* <ul>
* <li>{@code Forwarded}, as defined by <a href="https://tools.ietf.org/html/rfc7239">rfc7239</a>
* <li>{@code X-Forwarded-Host}</li>
* <li>{@code X-Forwarded-Server}</li>
* <li>{@code X-Forwarded-For}</li>
* <li>{@code X-Forwarded-Proto}</li>
* <li>{@code 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
* 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
{
private HostPortHttpField _forcedHost;
private boolean _proxyAsAuthority = false;
private boolean _forwardedPortAsAuthority = true;
private String _forwardedHeader = HttpHeader.FORWARDED.toString();
private String _forwardedHostHeader = HttpHeader.X_FORWARDED_HOST.toString();
private String _forwardedServerHeader = HttpHeader.X_FORWARDED_SERVER.toString();
private String _forwardedProtoHeader = HttpHeader.X_FORWARDED_PROTO.toString();
private String _forwardedForHeader = HttpHeader.X_FORWARDED_FOR.toString();
private String _forwardedPortHeader = HttpHeader.X_FORWARDED_PORT.toString();
private String _forwardedHttpsHeader = "X-Proxied-Https";
private String _forwardedCipherSuiteHeader = "Proxy-auth-cert";
private String _forwardedSslSessionIdHeader = "Proxy-ssl-id";
private boolean _sslIsSecure = true;
private final Index.Mutable<MethodHandle> _handles = new Index.Builder<MethodHandle>()
.caseSensitive(false)
.mutable()
.build();
public ForwardedRequestCustomizer()
{
updateHandles();
}
Returns: true if the proxy address obtained via X-Forwarded-Server
or RFC7239 "by" is used as the request authority. Default false
/**
* @return true if the proxy address obtained via
* {@code X-Forwarded-Server} or RFC7239 "by" is used as
* the request authority. Default false
*/
public boolean getProxyAsAuthority()
{
return _proxyAsAuthority;
}
Params: - proxyAsAuthority – if true, use the proxy address obtained via
X-Forwarded-Server
or RFC7239 "by" as the request authority.
/**
* @param proxyAsAuthority if true, use the proxy address obtained via
* {@code X-Forwarded-Server} or RFC7239 "by" as the request authority.
*/
public void setProxyAsAuthority(boolean proxyAsAuthority)
{
_proxyAsAuthority = proxyAsAuthority;
}
Params: - rfc7239only – Configure to only support the RFC7239 Forwarded header and to not support any
X-Forwarded-
headers. This convenience method clears all the non RFC headers if passed true and sets them to the default values (if not already set) if passed false.
/**
* @param rfc7239only Configure to only support the RFC7239 Forwarded header and to
* not support any {@code X-Forwarded-} headers. This convenience method
* clears all the non RFC headers if passed true and sets them to
* the default values (if not already set) if passed false.
*/
public void setForwardedOnly(boolean rfc7239only)
{
if (rfc7239only)
{
if (_forwardedHeader == null)
_forwardedHeader = HttpHeader.FORWARDED.toString();
_forwardedHostHeader = null;
_forwardedServerHeader = null;
_forwardedForHeader = null;
_forwardedPortHeader = null;
_forwardedProtoHeader = null;
_forwardedHttpsHeader = null;
}
else
{
if (_forwardedHostHeader == null)
_forwardedHostHeader = HttpHeader.X_FORWARDED_HOST.toString();
if (_forwardedServerHeader == null)
_forwardedServerHeader = HttpHeader.X_FORWARDED_SERVER.toString();
if (_forwardedForHeader == null)
_forwardedForHeader = HttpHeader.X_FORWARDED_FOR.toString();
if (_forwardedPortHeader == null)
_forwardedPortHeader = HttpHeader.X_FORWARDED_PORT.toString();
if (_forwardedProtoHeader == null)
_forwardedProtoHeader = HttpHeader.X_FORWARDED_PROTO.toString();
if (_forwardedHttpsHeader == null)
_forwardedHttpsHeader = "X-Proxied-Https";
}
updateHandles();
}
public String getForcedHost()
{
return _forcedHost.getValue();
}
Set a forced valued for the host header to control what is returned by ServletRequest.getServerName()
and ServletRequest.getServerPort()
. Params: - hostAndPort – The value of the host header to force.
/**
* Set a forced valued for the host header to control what is returned by {@link ServletRequest#getServerName()} and {@link ServletRequest#getServerPort()}.
*
* @param hostAndPort The value of the host header to force.
*/
public void setForcedHost(String hostAndPort)
{
_forcedHost = new HostPortHttpField(hostAndPort);
}
Returns: The header name for RFC forwarded (default Forwarded)
/**
* @return The header name for RFC forwarded (default Forwarded)
*/
public String getForwardedHeader()
{
return _forwardedHeader;
}
Params: - forwardedHeader – The header name for RFC forwarded (default Forwarded)
/**
* @param forwardedHeader The header name for RFC forwarded (default Forwarded)
*/
public void setForwardedHeader(String forwardedHeader)
{
if (_forwardedHeader == null || !_forwardedHeader.equals(forwardedHeader))
{
_forwardedHeader = forwardedHeader;
updateHandles();
}
}
public String getForwardedHostHeader()
{
return _forwardedHostHeader;
}
Params: - forwardedHostHeader – The header name for forwarded hosts (default
X-Forwarded-Host
)
/**
* @param forwardedHostHeader The header name for forwarded hosts (default {@code X-Forwarded-Host})
*/
public void setForwardedHostHeader(String forwardedHostHeader)
{
if (_forwardedHostHeader == null || !_forwardedHostHeader.equalsIgnoreCase(forwardedHostHeader))
{
_forwardedHostHeader = forwardedHostHeader;
updateHandles();
}
}
Returns: the header name for forwarded server.
/**
* @return the header name for forwarded server.
*/
public String getForwardedServerHeader()
{
return _forwardedServerHeader;
}
Params: - forwardedServerHeader – The header name for forwarded server (default
X-Forwarded-Server
)
/**
* @param forwardedServerHeader The header name for forwarded server (default {@code X-Forwarded-Server})
*/
public void setForwardedServerHeader(String forwardedServerHeader)
{
if (_forwardedServerHeader == null || !_forwardedServerHeader.equalsIgnoreCase(forwardedServerHeader))
{
_forwardedServerHeader = forwardedServerHeader;
updateHandles();
}
}
Returns: the forwarded for header
/**
* @return the forwarded for header
*/
public String getForwardedForHeader()
{
return _forwardedForHeader;
}
Params: - forwardedRemoteAddressHeader – The header name for forwarded for (default
X-Forwarded-For
)
/**
* @param forwardedRemoteAddressHeader The header name for forwarded for (default {@code X-Forwarded-For})
*/
public void setForwardedForHeader(String forwardedRemoteAddressHeader)
{
if (_forwardedForHeader == null || !_forwardedForHeader.equalsIgnoreCase(forwardedRemoteAddressHeader))
{
_forwardedForHeader = forwardedRemoteAddressHeader;
updateHandles();
}
}
public String getForwardedPortHeader()
{
return _forwardedPortHeader;
}
Params: - forwardedPortHeader – The header name for forwarded hosts (default
X-Forwarded-Port
)
/**
* @param forwardedPortHeader The header name for forwarded hosts (default {@code X-Forwarded-Port})
*/
public void setForwardedPortHeader(String forwardedPortHeader)
{
if (_forwardedPortHeader == null || !_forwardedPortHeader.equalsIgnoreCase(forwardedPortHeader))
{
_forwardedPortHeader = forwardedPortHeader;
updateHandles();
}
}
Returns: if true, the X-Forwarded-Port header applies to the authority,
else it applies to the remote client address
/**
* @return if true, the X-Forwarded-Port header applies to the authority,
* else it applies to the remote client address
*/
public boolean getForwardedPortAsAuthority()
{
return _forwardedPortAsAuthority;
}
Set if the X-Forwarded-Port header will be used for Authority
Params: - forwardedPortAsAuthority – if true, the X-Forwarded-Port header applies to the authority,
else it applies to the remote client address
/**
* Set if the X-Forwarded-Port header will be used for Authority
*
* @param forwardedPortAsAuthority if true, the X-Forwarded-Port header applies to the authority,
* else it applies to the remote client address
*/
public void setForwardedPortAsAuthority(boolean forwardedPortAsAuthority)
{
_forwardedPortAsAuthority = forwardedPortAsAuthority;
}
Get the forwardedProtoHeader.
Returns: the forwardedProtoHeader (default X-Forwarded-Proto
)
/**
* Get the forwardedProtoHeader.
*
* @return the forwardedProtoHeader (default {@code X-Forwarded-Proto})
*/
public String getForwardedProtoHeader()
{
return _forwardedProtoHeader;
}
Set the forwardedProtoHeader.
Params: - forwardedProtoHeader – the forwardedProtoHeader to set (default
X-Forwarded-Proto
)
/**
* Set the forwardedProtoHeader.
*
* @param forwardedProtoHeader the forwardedProtoHeader to set (default {@code X-Forwarded-Proto})
*/
public void setForwardedProtoHeader(String forwardedProtoHeader)
{
if (_forwardedProtoHeader == null || !_forwardedProtoHeader.equalsIgnoreCase(forwardedProtoHeader))
{
_forwardedProtoHeader = forwardedProtoHeader;
updateHandles();
}
}
Returns: The header name holding a forwarded cipher suite (default Proxy-auth-cert
)
/**
* @return The header name holding a forwarded cipher suite (default {@code Proxy-auth-cert})
*/
public String getForwardedCipherSuiteHeader()
{
return _forwardedCipherSuiteHeader;
}
Params: - forwardedCipherSuiteHeader – The header name holding a forwarded cipher suite (default
Proxy-auth-cert
)
/**
* @param forwardedCipherSuiteHeader The header name holding a forwarded cipher suite (default {@code Proxy-auth-cert})
*/
public void setForwardedCipherSuiteHeader(String forwardedCipherSuiteHeader)
{
if (_forwardedCipherSuiteHeader == null || !_forwardedCipherSuiteHeader.equalsIgnoreCase(forwardedCipherSuiteHeader))
{
_forwardedCipherSuiteHeader = forwardedCipherSuiteHeader;
updateHandles();
}
}
Returns: The header name holding a forwarded SSL Session ID (default Proxy-ssl-id
)
/**
* @return The header name holding a forwarded SSL Session ID (default {@code Proxy-ssl-id})
*/
public String getForwardedSslSessionIdHeader()
{
return _forwardedSslSessionIdHeader;
}
Params: - forwardedSslSessionIdHeader – The header name holding a forwarded SSL Session ID (default
Proxy-ssl-id
)
/**
* @param forwardedSslSessionIdHeader The header name holding a forwarded SSL Session ID (default {@code Proxy-ssl-id})
*/
public void setForwardedSslSessionIdHeader(String forwardedSslSessionIdHeader)
{
if (_forwardedSslSessionIdHeader == null || !_forwardedSslSessionIdHeader.equalsIgnoreCase(forwardedSslSessionIdHeader))
{
_forwardedSslSessionIdHeader = forwardedSslSessionIdHeader;
updateHandles();
}
}
Returns: The header name holding a forwarded Https status indicator (on|off true|false) (default X-Proxied-Https
)
/**
* @return The header name holding a forwarded Https status indicator (on|off true|false) (default {@code X-Proxied-Https})
*/
public String getForwardedHttpsHeader()
{
return _forwardedHttpsHeader;
}
Params: - forwardedHttpsHeader – the header name holding a forwarded Https status indicator(default
X-Proxied-Https
)
/**
* @param forwardedHttpsHeader the header name holding a forwarded Https status indicator(default {@code X-Proxied-Https})
*/
public void setForwardedHttpsHeader(String forwardedHttpsHeader)
{
if (_forwardedHttpsHeader == null || !_forwardedHttpsHeader.equalsIgnoreCase(forwardedHttpsHeader))
{
_forwardedHttpsHeader = forwardedHttpsHeader;
updateHandles();
}
}
Returns: true if the presence of an SSL session or certificate header is sufficient
to indicate a secure request (default is true)
/**
* @return true if the presence of an SSL session or certificate header is sufficient
* to indicate a secure request (default is true)
*/
public boolean isSslIsSecure()
{
return _sslIsSecure;
}
Params: - sslIsSecure – true if the presence of an SSL session or certificate header is sufficient
to indicate a secure request (default is true)
/**
* @param sslIsSecure true if the presence of an SSL session or certificate header is sufficient
* to indicate a secure request (default is true)
*/
public void setSslIsSecure(boolean sslIsSecure)
{
_sslIsSecure = sslIsSecure;
}
@Override
public void customize(Connector connector, HttpConfiguration config, Request request)
{
HttpFields httpFields = request.getHttpFields();
// Do a single pass through the header fields as it is a more efficient single iteration.
Forwarded forwarded = new Forwarded(request, config);
boolean match = false;
for (HttpField field : httpFields)
{
try
{
MethodHandle handle = _handles.get(field.getName());
if (handle != null)
{
match = true;
handle.invoke(forwarded, field);
}
}
catch (Throwable t)
{
onError(field, t);
}
}
if (match)
{
HttpURI.Mutable builder = HttpURI.build(request.getHttpURI());
boolean httpUriChanged = false;
// Is secure status configured from headers?
if (forwarded.isSecure())
{
request.setSecure(true);
}
// Set Scheme from configured protocol
if (forwarded._proto != null)
{
builder.scheme(forwarded._proto);
httpUriChanged = true;
}
// Set scheme if header implies secure scheme is to be used (see #isSslIsSecure())
else if (forwarded._secureScheme)
{
builder.scheme(config.getSecureScheme());
httpUriChanged = true;
}
// Use authority from headers, if configured.
if (forwarded._authority != null)
{
String host = forwarded._authority._host;
int port = forwarded._authority._port;
// Fall back to request metadata if needed.
if (host == null)
{
host = builder.getHost();
}
if (port == MutableHostPort.UNSET) // is unset by headers
{
port = builder.getPort();
}
// Don't change port if port == IMPLIED.
// 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;
}
}
if (httpUriChanged)
{
request.setHttpURI(builder);
}
// 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));
}
}
}
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 static String getLeftMost(String headerValue)
{
if (headerValue == null)
return null;
int commaIndex = headerValue.indexOf(',');
if (commaIndex == -1)
{
// Single value
return headerValue;
}
// The left-most value is the farthest downstream client
return headerValue.substring(0, commaIndex).trim();
}
@Override
public String toString()
{
return String.format("%s@%x", this.getClass().getSimpleName(), hashCode());
}
public String getHostHeader()
{
return _forcedHost.getValue();
}
Set a forced valued for the host header to control what is returned by ServletRequest.getServerName()
and ServletRequest.getServerPort()
. Params: - hostHeader – The value of the host header to force.
/**
* 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.
*/
public void setHostHeader(String hostHeader)
{
_forcedHost = new HostPortHttpField(hostHeader);
}
private void updateHandles()
{
MethodHandles.Lookup lookup = MethodHandles.lookup();
try
{
updateForwardedHandle(lookup, getForwardedHeader(), "handleRFC7239");
updateForwardedHandle(lookup, getForwardedHostHeader(), "handleForwardedHost");
updateForwardedHandle(lookup, getForwardedForHeader(), "handleForwardedFor");
updateForwardedHandle(lookup, getForwardedPortHeader(), "handleForwardedPort");
updateForwardedHandle(lookup, getForwardedProtoHeader(), "handleProto");
updateForwardedHandle(lookup, getForwardedHttpsHeader(), "handleHttps");
updateForwardedHandle(lookup, getForwardedServerHeader(), "handleForwardedServer");
updateForwardedHandle(lookup, getForwardedCipherSuiteHeader(), "handleCipherSuite");
updateForwardedHandle(lookup, getForwardedSslSessionIdHeader(), "handleSslSessionId");
}
catch (NoSuchMethodException | IllegalAccessException e)
{
throw new IllegalStateException(e);
}
}
private void updateForwardedHandle(MethodHandles.Lookup lookup, String headerName, String forwardedMethodName) throws NoSuchMethodException, IllegalAccessException
{
final MethodType type = methodType(void.class, HttpField.class);
if (StringUtil.isBlank(headerName))
return;
_handles.put(headerName, lookup.findVirtual(Forwarded.class, forwardedMethodName, type));
}
private static class MutableHostPort
{
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)
{
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();
}
}
Ordered Source Enum.
Lowest first, Last/Highest priority wins
/**
* Ordered Source Enum.
* <p>
* Lowest first, Last/Highest priority wins
* </p>
*/
public enum Source
{
UNSET,
XPROXIED_HTTPS,
XFORWARDED_PROTO,
XFORWARDED_SERVER,
XFORWARDED_PORT,
XFORWARDED_FOR,
XFORWARDED_HOST,
FORWARDED,
FORCED;
int priority()
{
return ordinal();
}
}
private class Forwarded extends QuotedCSVParser
{
HttpConfiguration _config;
Request _request;
MutableHostPort _authority;
MutableHostPort _for;
String _proto;
Source _protoSource = Source.UNSET;
Boolean _secure;
boolean _secureScheme = false;
public Forwarded(Request request, HttpConfiguration config)
{
super(false);
_request = request;
_config = config;
if (_forcedHost != null)
{
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;
}
Called if header is Proxy-auth-cert
/**
* Called if header is <code>Proxy-auth-cert</code>
*/
public void handleCipherSuite(HttpField field)
{
_request.setAttribute("jakarta.servlet.request.cipher_suite", field.getValue());
// Is ForwardingRequestCustomizer configured to trigger isSecure and scheme change on this header?
if (isSslIsSecure())
{
_secure = true;
// track desire for secure scheme, actual protocol will be resolved later.
_secureScheme = true;
}
}
Called if header is Proxy-Ssl-Id
/**
* Called if header is <code>Proxy-Ssl-Id</code>
*/
public void handleSslSessionId(HttpField field)
{
_request.setAttribute("jakarta.servlet.request.ssl_session_id", field.getValue());
// Is ForwardingRequestCustomizer configured to trigger isSecure and scheme change on this header?
if (isSslIsSecure())
{
_secure = true;
// track desire for secure scheme, actual protocol will be resolved later.
_secureScheme = true;
}
}
Called if header is X-Forwarded-Host
/**
* Called if header is <code>X-Forwarded-Host</code>
*/
public void handleForwardedHost(HttpField field)
{
updateAuthority(getLeftMost(field.getValue()), Source.XFORWARDED_HOST);
}
Called if header is X-Forwarded-For
/**
* Called if header is <code>X-Forwarded-For</code>
*/
public void handleForwardedFor(HttpField field)
{
HostPort hostField = new HostPort(getLeftMost(field.getValue()));
getFor().setHostPort(hostField, Source.XFORWARDED_FOR);
}
Called if header is X-Forwarded-Server
/**
* Called if header is <code>X-Forwarded-Server</code>
*/
public void handleForwardedServer(HttpField field)
{
if (getProxyAsAuthority())
return;
updateAuthority(getLeftMost(field.getValue()), Source.XFORWARDED_SERVER);
}
Called if header is X-Forwarded-Port
/**
* Called if header is <code>X-Forwarded-Port</code>
*/
public void handleForwardedPort(HttpField field)
{
int port = HostPort.parsePort(getLeftMost(field.getValue()));
updatePort(port, Source.XFORWARDED_PORT);
}
Called if header is X-Forwarded-Proto
/**
* Called if header is <code>X-Forwarded-Proto</code>
*/
public void handleProto(HttpField field)
{
updateProto(getLeftMost(field.getValue()), Source.XFORWARDED_PROTO);
}
Called if header is X-Proxied-Https
/**
* Called if header is <code>X-Proxied-Https</code>
*/
public void handleHttps(HttpField field)
{
if ("on".equalsIgnoreCase(field.getValue()) || "true".equalsIgnoreCase(field.getValue()))
{
_secure = true;
updateProto(HttpScheme.HTTPS.asString(), Source.XPROXIED_HTTPS);
updatePort(getSecurePort(_config), Source.XPROXIED_HTTPS);
}
else if ("off".equalsIgnoreCase(field.getValue()) || "false".equalsIgnoreCase(field.getValue()))
{
_secure = false;
updateProto(HttpScheme.HTTP.asString(), Source.XPROXIED_HTTPS);
updatePort(MutableHostPort.IMPLIED, Source.XPROXIED_HTTPS);
}
else
{
throw new BadMessageException("Invalid value for " + field.getName());
}
}
Called if header is Forwarded
/**
* Called if header is <code>Forwarded</code>
*/
public void handleRFC7239(HttpField field)
{
addValue(field.getValue());
}
@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 (!getProxyAsAuthority())
break;
if (value.startsWith("_") || "unknown".equals(value))
break;
HostPort hostField = new HostPort(value);
getAuthority().setHostPort(hostField.getHost(), hostField.getPort(), Source.FORWARDED);
break;
}
case "for":
{
if (value.startsWith("_") || "unknown".equals(value))
break;
HostPort hostField = new HostPort(value);
getFor().setHostPort(hostField.getHost(), hostField.getPort(), Source.FORWARDED);
break;
}
case "host":
{
if (value.startsWith("_") || "unknown".equals(value))
break;
HostPort hostField = new HostPort(value);
getAuthority().setHostPort(hostField.getHost(), hostField.getPort(), Source.FORWARDED);
break;
}
case "proto":
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;
}
}
}
}
}