Merge remote-tracking branch 'origin/jetty-9.4.x' into jetty-10.0.x

This commit is contained in:
Joakim Erdfelt 2020-09-23 18:33:45 -05:00
commit 39271d6b63
No known key found for this signature in database
GPG Key ID: 2D0E1FB8FE4B68B4
3 changed files with 471 additions and 340 deletions

View File

@ -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;
}
}
}
}
}

View File

@ -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);
}
}
}
}

View File

@ -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));