Fixes #5079 - :authority header for IPv6 address not having square brackets.

On the client:
* Origin.Address.host is passed through HostPort.normalizeHost(),
so that if it is IPv6 is bracketed.
Now the ipv6 address passed to an `HttClient` request is bracketed.
* HttpRequest was de-bracketing the host, but now it does not anymore.

On the server:
* Request.getLocalAddr(), getLocalName(), getRemoteAddr(),
getRemoteHost(), getServerName(), when dealing with an IPv6 address,
return it bracketed.
The reason to return bracketed IPv6 also from *Addr() methods is that
if it is used with InetAddress/InetSocketAddress it still works, but
often it is interpreted as a URI host so brackets are necessary.
* DoSFilter was blindly bracketing - now it does not.

Added a number of test cases, and fixed those that expected
non-bracketed IPv6.

Signed-off-by: Simone Bordet <simone.bordet@gmail.com>
This commit is contained in:
Simone Bordet 2020-08-07 15:53:19 +02:00
parent 1f14dfa427
commit d53d9d8a1d
17 changed files with 303 additions and 74 deletions

View File

@ -27,6 +27,7 @@ import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.util.component.LifeCycle; import org.eclipse.jetty.util.component.LifeCycle;
import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.MatcherAssert.assertThat;
@ -54,8 +55,10 @@ public class OneWebAppTest extends AbstractEmbeddedTest
} }
@Test @Test
@Tag("external")
public void testGetAsyncRest() throws Exception public void testGetAsyncRest() throws Exception
{ {
// The async rest webapp forwards the call to ebay.com.
URI uri = server.getURI().resolve("/testAsync?items=mouse,beer,gnome"); URI uri = server.getURI().resolve("/testAsync?items=mouse,beer,gnome");
ContentResponse response = client.newRequest(uri) ContentResponse response = client.newRequest(uri)
.method(HttpMethod.GET) .method(HttpMethod.GET)

View File

@ -1155,10 +1155,14 @@ public class HttpClient extends ContainerLifeCycle
return encodingField; return encodingField;
} }
/**
* @param host the host to normalize
* @return the host itself
* @deprecated no replacement, do not use it
*/
@Deprecated
protected String normalizeHost(String host) protected String normalizeHost(String host)
{ {
if (host != null && host.startsWith("[") && host.endsWith("]"))
return host.substring(1, host.length() - 1);
return host; return host;
} }

View File

@ -95,7 +95,7 @@ public class HttpRequest implements Request
this.client = client; this.client = client;
this.conversation = conversation; this.conversation = conversation;
scheme = uri.getScheme(); scheme = uri.getScheme();
host = client.normalizeHost(uri.getHost()); host = uri.getHost();
port = HttpClient.normalizePort(scheme, uri.getPort()); port = HttpClient.normalizePort(scheme, uri.getPort());
path = uri.getRawPath(); path = uri.getRawPath();
query = uri.getRawQuery(); query = uri.getRawQuery();

View File

@ -20,6 +20,7 @@ package org.eclipse.jetty.client;
import java.util.Objects; import java.util.Objects;
import org.eclipse.jetty.util.HostPort;
import org.eclipse.jetty.util.URIUtil; import org.eclipse.jetty.util.URIUtil;
public class Origin public class Origin
@ -107,7 +108,7 @@ public class Origin
public Address(String host, int port) public Address(String host, int port)
{ {
this.host = Objects.requireNonNull(host); this.host = HostPort.normalizeHost(Objects.requireNonNull(host));
this.port = port; this.port = port;
} }

View File

@ -1623,29 +1623,32 @@ public class HttpClientTest extends AbstractHttpClientServerTest
@ParameterizedTest @ParameterizedTest
@ArgumentsSource(ScenarioProvider.class) @ArgumentsSource(ScenarioProvider.class)
public void testIPv6Host(Scenario scenario) throws Exception public void testIPv6HostWithHTTP10(Scenario scenario) throws Exception
{ {
Assumptions.assumeTrue(Net.isIpv6InterfaceAvailable()); Assumptions.assumeTrue(Net.isIpv6InterfaceAvailable());
start(scenario, new AbstractHandler() start(scenario, new EmptyServerHandler()
{ {
@Override @Override
public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException protected void service(String target, org.eclipse.jetty.server.Request jettyRequest, HttpServletRequest request, HttpServletResponse response) throws IOException
{ {
baseRequest.setHandled(true);
response.setContentType("text/plain"); response.setContentType("text/plain");
response.getOutputStream().print(request.getHeader("Host")); response.getOutputStream().print(request.getServerName());
} }
}); });
URI uri = URI.create(scenario.getScheme() + "://[::1]:" + connector.getLocalPort() + "/path"); URI uri = URI.create(scenario.getScheme() + "://[::1]:" + connector.getLocalPort() + "/path");
ContentResponse response = client.newRequest(uri) ContentResponse response = client.newRequest(uri)
.method(HttpMethod.PUT) .method(HttpMethod.PUT)
.version(HttpVersion.HTTP_1_0)
.onRequestBegin(r -> r.getHeaders().remove(HttpHeader.HOST))
.timeout(5, TimeUnit.SECONDS) .timeout(5, TimeUnit.SECONDS)
.send(); .send();
assertNotNull(response); assertNotNull(response);
assertEquals(200, response.getStatus()); assertEquals(200, response.getStatus());
assertThat(new String(response.getContent(), StandardCharsets.ISO_8859_1), Matchers.startsWith("[::1]:")); String content = response.getContentAsString();
assertThat(content, Matchers.startsWith("["));
assertThat(content, Matchers.endsWith(":1]"));
} }
@ParameterizedTest @ParameterizedTest

View File

@ -67,8 +67,10 @@ public class HttpClientURITest extends AbstractHttpClientServerTest
Assumptions.assumeTrue(Net.isIpv6InterfaceAvailable()); Assumptions.assumeTrue(Net.isIpv6InterfaceAvailable());
start(scenario, new EmptyServerHandler()); start(scenario, new EmptyServerHandler());
String host = "::1"; String hostAddress = "::1";
Request request = client.newRequest(host, connector.getLocalPort()) String host = "[" + hostAddress + "]";
// Explicitly use a non-bracketed IPv6 host.
Request request = client.newRequest(hostAddress, connector.getLocalPort())
.scheme(scenario.getScheme()) .scheme(scenario.getScheme())
.timeout(5, TimeUnit.SECONDS); .timeout(5, TimeUnit.SECONDS);

View File

@ -0,0 +1,41 @@
//
// ========================================================================
// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.proxy;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.handler.AbstractHandler;
public class EmptyServerHandler extends AbstractHandler
{
@Override
public final void handle(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
jettyRequest.setHandled(true);
service(target, jettyRequest, request, response);
}
protected void service(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
}
}

View File

@ -21,10 +21,14 @@ package org.eclipse.jetty.proxy;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.stream.Stream; import java.util.stream.Stream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.client.HttpClient; import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.HttpProxy; import org.eclipse.jetty.client.HttpProxy;
import org.eclipse.jetty.client.api.ContentResponse; import org.eclipse.jetty.client.api.ContentResponse;
import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpMethod; import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.io.AbstractConnection; import org.eclipse.jetty.io.AbstractConnection;
@ -33,9 +37,14 @@ import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.server.AbstractConnectionFactory; import org.eclipse.jetty.server.AbstractConnectionFactory;
import org.eclipse.jetty.server.ConnectionFactory; import org.eclipse.jetty.server.ConnectionFactory;
import org.eclipse.jetty.server.Connector; import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.ForwardedRequestCustomizer;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.server.HttpConnectionFactory;
import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector; import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.servlet.ServletContextHandler; import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils; import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.Callback;
@ -44,10 +53,13 @@ import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.eclipse.jetty.util.thread.QueuedThreadPool; import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.hamcrest.Matchers; import org.hamcrest.Matchers;
import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource; import org.junit.jupiter.params.provider.MethodSource;
import org.junit.jupiter.params.provider.ValueSource;
import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.greaterThan;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertFalse;
@ -73,7 +85,7 @@ public class ForwardProxyServerTest
private Server proxy; private Server proxy;
private ServerConnector proxyConnector; private ServerConnector proxyConnector;
protected void startServer(SslContextFactory.Server serverTLS, ConnectionFactory connectionFactory) throws Exception protected void startServer(SslContextFactory.Server serverTLS, ConnectionFactory connectionFactory, Handler handler) throws Exception
{ {
serverSslContextFactory = serverTLS; serverSslContextFactory = serverTLS;
QueuedThreadPool serverThreads = new QueuedThreadPool(); QueuedThreadPool serverThreads = new QueuedThreadPool();
@ -81,10 +93,11 @@ public class ForwardProxyServerTest
server = new Server(serverThreads); server = new Server(serverThreads);
serverConnector = new ServerConnector(server, serverSslContextFactory, connectionFactory); serverConnector = new ServerConnector(server, serverSslContextFactory, connectionFactory);
server.addConnector(serverConnector); server.addConnector(serverConnector);
server.setHandler(handler);
server.start(); server.start();
} }
protected void startProxy() throws Exception protected void startProxy(ProxyServlet proxyServlet) throws Exception
{ {
QueuedThreadPool proxyThreads = new QueuedThreadPool(); QueuedThreadPool proxyThreads = new QueuedThreadPool();
proxyThreads.setName("proxy"); proxyThreads.setName("proxy");
@ -98,7 +111,7 @@ public class ForwardProxyServerTest
proxy.setHandler(connectHandler); proxy.setHandler(connectHandler);
ServletContextHandler proxyHandler = new ServletContextHandler(connectHandler, "/"); ServletContextHandler proxyHandler = new ServletContextHandler(connectHandler, "/");
proxyHandler.addServlet(ProxyServlet.class, "/*"); proxyHandler.addServlet(new ServletHolder(proxyServlet), "/*");
proxy.start(); proxy.start();
} }
@ -165,7 +178,7 @@ public class ForwardProxyServerTest
// the client, and convert it to a relative URI. // the client, and convert it to a relative URI.
// The ConnectHandler won't modify what the client // The ConnectHandler won't modify what the client
// sent, which must be a relative URI. // sent, which must be a relative URI.
assertThat(request.length(), Matchers.greaterThan(0)); assertThat(request.length(), greaterThan(0));
if (serverSslContextFactory == null) if (serverSslContextFactory == null)
assertFalse(request.contains("http://")); assertFalse(request.contains("http://"));
else else
@ -185,8 +198,8 @@ public class ForwardProxyServerTest
} }
}; };
} }
}); }, new EmptyServerHandler());
startProxy(); startProxy(new ProxyServlet());
SslContextFactory.Client clientTLS = new SslContextFactory.Client(true); SslContextFactory.Client clientTLS = new SslContextFactory.Client(true);
HttpClient httpClient = new HttpClient(clientTLS); HttpClient httpClient = new HttpClient(clientTLS);
@ -208,4 +221,79 @@ public class ForwardProxyServerTest
httpClient.stop(); httpClient.stop();
} }
} }
@ParameterizedTest
@ValueSource(strings = {"::2", "[::3]"})
public void testIPv6WithXForwardedForHeader(String ipv6) throws Exception
{
HttpConfiguration httpConfig = new HttpConfiguration();
httpConfig.addCustomizer(new ForwardedRequestCustomizer());
ConnectionFactory http = new HttpConnectionFactory(httpConfig);
startServer(null, http, new EmptyServerHandler()
{
@Override
protected void service(String target, org.eclipse.jetty.server.Request jettyRequest, HttpServletRequest request, HttpServletResponse response)
{
String remoteHost = jettyRequest.getRemoteHost();
assertThat(remoteHost, Matchers.matchesPattern("\\[.+\\]"));
String remoteAddr = jettyRequest.getRemoteAddr();
assertThat(remoteAddr, Matchers.matchesPattern("\\[.+\\]"));
}
});
startProxy(new ProxyServlet()
{
@Override
protected void addProxyHeaders(HttpServletRequest clientRequest, Request proxyRequest)
{
proxyRequest.header(HttpHeader.X_FORWARDED_FOR, ipv6);
}
});
HttpClient httpClient = new HttpClient();
httpClient.getProxyConfiguration().getProxies().add(newHttpProxy());
httpClient.start();
ContentResponse response = httpClient.newRequest("[::1]", serverConnector.getLocalPort())
.scheme("http")
.send();
assertEquals(HttpStatus.OK_200, response.getStatus());
}
@Test
public void testIPv6WithForwardedHeader() throws Exception
{
HttpConfiguration httpConfig = new HttpConfiguration();
httpConfig.addCustomizer(new ForwardedRequestCustomizer());
ConnectionFactory http = new HttpConnectionFactory(httpConfig);
startServer(null, http, new EmptyServerHandler()
{
@Override
protected void service(String target, org.eclipse.jetty.server.Request jettyRequest, HttpServletRequest request, HttpServletResponse response)
{
String remoteHost = jettyRequest.getRemoteHost();
assertThat(remoteHost, Matchers.matchesPattern("\\[.+\\]"));
String remoteAddr = jettyRequest.getRemoteAddr();
assertThat(remoteAddr, Matchers.matchesPattern("\\[.+\\]"));
}
});
startProxy(new ProxyServlet()
{
@Override
protected void addProxyHeaders(HttpServletRequest clientRequest, Request proxyRequest)
{
proxyRequest.header(HttpHeader.FORWARDED, "for=\"[::2]\"");
}
});
HttpClient httpClient = new HttpClient();
httpClient.getProxyConfiguration().getProxies().add(newHttpProxy());
httpClient.start();
ContentResponse response = httpClient.newRequest("[::1]", serverConnector.getLocalPort())
.scheme("http")
.send();
assertEquals(HttpStatus.OK_200, response.getStatus());
}
} }

View File

@ -463,6 +463,34 @@ public class ForwardProxyTLSServerTest
httpClient.stop(); httpClient.stop();
} }
@ParameterizedTest
@MethodSource("proxyTLS")
public void testIPv6(SslContextFactory.Server proxyTLS) throws Exception
{
startTLSServer(new ServerHandler());
startProxy(proxyTLS);
HttpClient httpClient = new HttpClient(newClientSslContextFactory());
HttpProxy httpProxy = new HttpProxy(new Origin.Address("[::1]", proxyConnector.getLocalPort()), proxyTLS != null);
httpClient.getProxyConfiguration().getProxies().add(httpProxy);
httpClient.start();
try
{
ContentResponse response = httpClient.newRequest("[::1]", serverConnector.getLocalPort())
.scheme(HttpScheme.HTTPS.asString())
.path("/echo?body=")
.timeout(5, TimeUnit.SECONDS)
.send();
assertEquals(HttpStatus.OK_200, response.getStatus());
}
finally
{
httpClient.stop();
}
}
@ParameterizedTest @ParameterizedTest
@MethodSource("proxyTLS") @MethodSource("proxyTLS")
public void testProxyAuthentication(SslContextFactory.Server proxyTLS) throws Exception public void testProxyAuthentication(SslContextFactory.Server proxyTLS) throws Exception

View File

@ -27,6 +27,7 @@ import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.nio.charset.UnsupportedCharsetException; import java.nio.charset.UnsupportedCharsetException;
@ -83,6 +84,7 @@ import org.eclipse.jetty.server.session.Session;
import org.eclipse.jetty.server.session.SessionHandler; import org.eclipse.jetty.server.session.SessionHandler;
import org.eclipse.jetty.util.Attributes; import org.eclipse.jetty.util.Attributes;
import org.eclipse.jetty.util.AttributesMap; import org.eclipse.jetty.util.AttributesMap;
import org.eclipse.jetty.util.HostPort;
import org.eclipse.jetty.util.IO; import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.MultiMap; import org.eclipse.jetty.util.MultiMap;
import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.util.StringUtil;
@ -984,11 +986,12 @@ public class Request implements HttpServletRequest
String name = InetAddress.getLocalHost().getHostAddress(); String name = InetAddress.getLocalHost().getHostAddress();
if (StringUtil.ALL_INTERFACES.equals(name)) if (StringUtil.ALL_INTERFACES.equals(name))
return null; return null;
return name; return HostPort.normalizeHost(name);
} }
catch (java.net.UnknownHostException e) catch (UnknownHostException e)
{ {
LOG.ignore(e); LOG.ignore(e);
return null;
} }
} }
@ -996,9 +999,10 @@ public class Request implements HttpServletRequest
if (local == null) if (local == null)
return ""; return "";
InetAddress address = local.getAddress(); InetAddress address = local.getAddress();
if (address == null) String result = address == null
return local.getHostString(); ? local.getHostString()
return address.getHostAddress(); : address.getHostAddress();
return HostPort.normalizeHost(result);
} }
/* /*
@ -1011,7 +1015,7 @@ public class Request implements HttpServletRequest
{ {
InetSocketAddress local = _channel.getLocalAddress(); InetSocketAddress local = _channel.getLocalAddress();
if (local != null) if (local != null)
return local.getHostString(); return HostPort.normalizeHost(local.getHostString());
} }
try try
@ -1019,9 +1023,9 @@ public class Request implements HttpServletRequest
String name = InetAddress.getLocalHost().getHostName(); String name = InetAddress.getLocalHost().getHostName();
if (StringUtil.ALL_INTERFACES.equals(name)) if (StringUtil.ALL_INTERFACES.equals(name))
return null; return null;
return name; return HostPort.normalizeHost(name);
} }
catch (java.net.UnknownHostException e) catch (UnknownHostException e)
{ {
LOG.ignore(e); LOG.ignore(e);
} }
@ -1236,15 +1240,17 @@ public class Request implements HttpServletRequest
InetSocketAddress remote = _remote; InetSocketAddress remote = _remote;
if (remote == null) if (remote == null)
remote = _channel.getRemoteAddress(); remote = _channel.getRemoteAddress();
if (remote == null) if (remote == null)
return ""; return "";
InetAddress address = remote.getAddress(); InetAddress address = remote.getAddress();
if (address == null) String result = address == null
return remote.getHostString(); ? remote.getHostString()
: address.getHostAddress();
return address.getHostAddress(); // Add IPv6 brackets if necessary, to be consistent
// with cases where _remote has been built from other
// sources such as forward headers or PROXY protocol.
return HostPort.normalizeHost(result);
} }
/* /*
@ -1256,7 +1262,10 @@ public class Request implements HttpServletRequest
InetSocketAddress remote = _remote; InetSocketAddress remote = _remote;
if (remote == null) if (remote == null)
remote = _channel.getRemoteAddress(); remote = _channel.getRemoteAddress();
return remote == null ? "" : remote.getHostString(); if (remote == null)
return "";
// We want the URI host, so add IPv6 brackets if necessary.
return HostPort.normalizeHost(remote.getHostString());
} }
/* /*
@ -1411,14 +1420,14 @@ public class Request implements HttpServletRequest
// Return host from connection // Return host from connection
String name = getLocalName(); String name = getLocalName();
if (name != null) if (name != null)
return name; return HostPort.normalizeHost(name);
// Return the local host // Return the local host
try try
{ {
return InetAddress.getLocalHost().getHostAddress(); return HostPort.normalizeHost(InetAddress.getLocalHost().getHostAddress());
} }
catch (java.net.UnknownHostException e) catch (UnknownHostException e)
{ {
LOG.ignore(e); LOG.ignore(e);
} }

View File

@ -703,7 +703,7 @@ public class ForwardedRequestCustomizerTest
public void accept(Actual actual) public void accept(Actual actual)
{ {
assertThat("scheme", actual.scheme.get(), is(expectedScheme)); assertThat("scheme", actual.scheme.get(), is(expectedScheme));
if (actual.scheme.equals("https")) if (actual.scheme.get().equals("https"))
{ {
assertTrue(actual.wasSecure.get(), "wasSecure"); assertTrue(actual.wasSecure.get(), "wasSecure");
} }

View File

@ -109,8 +109,8 @@ public class ProxyConnectionTest
assertThat(response, Matchers.containsString("HTTP/1.1 200")); assertThat(response, Matchers.containsString("HTTP/1.1 200"));
assertThat(response, Matchers.containsString("pathInfo=/path")); assertThat(response, Matchers.containsString("pathInfo=/path"));
assertThat(response, Matchers.containsString("remote=eeee:eeee:eeee:eeee:eeee:eeee:eeee:eeee:65535")); assertThat(response, Matchers.containsString("remote=[eeee:eeee:eeee:eeee:eeee:eeee:eeee:eeee]:65535"));
assertThat(response, Matchers.containsString("local=ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff:65535")); assertThat(response, Matchers.containsString("local=[ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff]:65535"));
} }
@ParameterizedTest @ParameterizedTest
@ -147,8 +147,8 @@ public class ProxyConnectionTest
assertThat(response, Matchers.containsString("HTTP/1.1 200")); assertThat(response, Matchers.containsString("HTTP/1.1 200"));
assertThat(response, Matchers.containsString("pathInfo=/path")); assertThat(response, Matchers.containsString("pathInfo=/path"));
assertThat(response, Matchers.containsString("local=eeee:eeee:eeee:eeee:eeee:eeee:eeee:eeee:8080")); assertThat(response, Matchers.containsString("local=[eeee:eeee:eeee:eeee:eeee:eeee:eeee:eeee]:8080"));
assertThat(response, Matchers.containsString("remote=ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff:12345")); assertThat(response, Matchers.containsString("remote=[ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff]:12345"));
} }
@ParameterizedTest @ParameterizedTest

View File

@ -813,7 +813,7 @@ public class ResponseTest
}; };
int[] ports = new int[]{8080, 80}; int[] ports = new int[]{8080, 80};
String[] hosts = new String[]{null, "myhost", "192.168.0.1", "0::1"}; String[] hosts = new String[]{null, "myhost", "192.168.0.1", "[0::1]"};
for (int port : ports) for (int port : ports)
{ {
for (String host : hosts) for (String host : hosts)
@ -850,7 +850,7 @@ public class ResponseTest
String location = response.getHeader("Location"); String location = response.getHeader("Location");
String expected = tests[i][1] String expected = tests[i][1]
.replace("@HOST@", host == null ? request.getLocalAddr() : (host.contains(":") ? ("[" + host + "]") : host)) .replace("@HOST@", host == null ? request.getLocalAddr() : host)
.replace("@PORT@", host == null ? ":8888" : (port == 80 ? "" : (":" + port))); .replace("@PORT@", host == null ? ":8888" : (port == 80 ? "" : (":" + port)));
assertEquals(expected, location, "test-" + i + " " + host + ":" + port); assertEquals(expected, location, "test-" + i + " " + host + ":" + port);
} }
@ -887,7 +887,7 @@ public class ResponseTest
}; };
int[] ports = new int[]{8080, 80}; int[] ports = new int[]{8080, 80};
String[] hosts = new String[]{null, "myhost", "192.168.0.1", "0::1"}; String[] hosts = new String[]{null, "myhost", "192.168.0.1", "[0::1]"};
for (int port : ports) for (int port : ports)
{ {
for (String host : hosts) for (String host : hosts)
@ -925,7 +925,7 @@ public class ResponseTest
String location = response.getHeader("Location"); String location = response.getHeader("Location");
String expected = tests[i][1] String expected = tests[i][1]
.replace("@HOST@", host == null ? request.getLocalAddr() : (host.contains(":") ? ("[" + host + "]") : host)) .replace("@HOST@", host == null ? request.getLocalAddr() : host)
.replace("@PORT@", host == null ? ":8888" : (port == 80 ? "" : (":" + port))); .replace("@PORT@", host == null ? ":8888" : (port == 80 ? "" : (":" + port)));
assertEquals(expected, location, "test-" + i + " " + host + ":" + port); assertEquals(expected, location, "test-" + i + " " + host + ":" + port);
} }

View File

@ -1,5 +1,4 @@
org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog
org.eclipse.jetty.LEVEL=INFO
#org.eclipse.jetty.LEVEL=DEBUG #org.eclipse.jetty.LEVEL=DEBUG
#org.eclipse.jetty.server.LEVEL=DEBUG #org.eclipse.jetty.server.LEVEL=DEBUG
#org.eclipse.jetty.servlet.LEVEL=DEBUG #org.eclipse.jetty.servlet.LEVEL=DEBUG

View File

@ -1369,12 +1369,10 @@ public class DoSFilter implements Filter
} }
} }
private String createRemotePortId(final ServletRequest request) private String createRemotePortId(ServletRequest request)
{ {
final String addr = request.getRemoteAddr(); String addr = request.getRemoteAddr();
final int port = request.getRemotePort(); int port = request.getRemotePort();
if (addr.contains(":"))
return "[" + addr + "]:" + port;
return addr + ":" + port; return addr + ":" + port;
} }
} }

View File

@ -19,22 +19,18 @@
package org.eclipse.jetty.util; package org.eclipse.jetty.util;
/** /**
* Parse an authority string into Host and Port * <p>Parse an authority string (in the form {@code host:port}) into
* <p>Parse a string in the form "host:port", handling IPv4 an IPv6 hosts</p> * {@code host} and {@code port}, handling IPv4 and IPv6 host formats
* * as defined in https://www.ietf.org/rfc/rfc2732.txt</p>
* <p>The System property "org.eclipse.jetty.util.HostPort.STRIP_IPV6" can be set to a boolean
* value to control of the square brackets are stripped off IPv6 addresses (default false).</p>
*/ */
public class HostPort public class HostPort
{ {
private static final boolean STRIP_IPV6 = Boolean.parseBoolean(System.getProperty("org.eclipse.jetty.util.HostPort.STRIP_IPV6", "false"));
private final String _host; private final String _host;
private final int _port; private final int _port;
public HostPort(String host, int port) public HostPort(String host, int port)
{ {
_host = host; _host = normalizeHost(host);
_port = port; _port = port;
} }
@ -55,7 +51,7 @@ public class HostPort
int close = authority.lastIndexOf(']'); int close = authority.lastIndexOf(']');
if (close < 0) if (close < 0)
throw new IllegalArgumentException("Bad IPv6 host"); throw new IllegalArgumentException("Bad IPv6 host");
_host = STRIP_IPV6 ? authority.substring(1, close) : authority.substring(0, close + 1); _host = authority.substring(0, close + 1);
if (authority.length() > close + 1) if (authority.length() > close + 1)
{ {
@ -64,14 +60,17 @@ public class HostPort
_port = parsePort(authority.substring(close + 2)); _port = parsePort(authority.substring(close + 2));
} }
else else
{
_port = 0; _port = 0;
} }
}
else else
{ {
// ipv4address or hostname // ipv6address or ipv4address or hostname
int c = authority.lastIndexOf(':'); int c = authority.lastIndexOf(':');
if (c >= 0) if (c >= 0)
{ {
// ipv6address
if (c != authority.indexOf(':')) if (c != authority.indexOf(':'))
{ {
_host = "[" + authority + "]"; _host = "[" + authority + "]";
@ -94,14 +93,9 @@ public class HostPort
{ {
throw iae; throw iae;
} }
catch (final Exception ex) catch (Exception ex)
{ {
throw new IllegalArgumentException("Bad HostPort") throw new IllegalArgumentException("Bad HostPort", ex);
{
{
initCause(ex);
}
};
} }
} }
@ -126,7 +120,7 @@ public class HostPort
} }
/** /**
* Get the port. * Get the port or the given default port.
* *
* @param defaultPort, the default port to return if a port is not specified * @param defaultPort, the default port to return if a port is not specified
* @return the port * @return the port
@ -140,15 +134,17 @@ public class HostPort
public String toString() public String toString()
{ {
if (_port > 0) if (_port > 0)
return normalizeHost(_host) + ":" + _port; return _host + ":" + _port;
return _host; return _host;
} }
/** /**
* Normalize IPv6 address as per https://www.ietf.org/rfc/rfc2732.txt * Normalizes IPv6 address as per https://tools.ietf.org/html/rfc2732
* and https://tools.ietf.org/html/rfc6874,
* surrounding with square brackets if they are absent.
* *
* @param host A host name * @param host a host name, IPv4 address, IPv6 address or IPv6 literal
* @return Host name surrounded by '[' and ']' as needed. * @return a host name or an IPv4 address or an IPv6 literal (not an IPv6 address)
*/ */
public static String normalizeHost(String host) public static String normalizeHost(String host)
{ {
@ -165,7 +161,7 @@ public class HostPort
* *
* @param rawPort the port string. * @param rawPort the port string.
* @return the integer value for the port. * @return the integer value for the port.
* @throws IllegalArgumentException * @throws IllegalArgumentException if the port is invalid
*/ */
public static int parsePort(String rawPort) throws IllegalArgumentException public static int parsePort(String rawPort) throws IllegalArgumentException
{ {

View File

@ -21,6 +21,7 @@ package org.eclipse.jetty.http.client;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.InterruptedIOException; import java.io.InterruptedIOException;
import java.nio.charset.StandardCharsets;
import java.util.List; import java.util.List;
import java.util.Random; import java.util.Random;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
@ -47,10 +48,12 @@ import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.http2.FlowControlStrategy; import org.eclipse.jetty.http2.FlowControlStrategy;
import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.handler.AbstractHandler; import org.eclipse.jetty.server.handler.AbstractHandler;
import org.eclipse.jetty.toolchain.test.Net;
import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.IO; import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.ssl.SslContextFactory; import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.eclipse.jetty.util.thread.QueuedThreadPool; import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.hamcrest.Matchers;
import org.junit.jupiter.api.Assumptions; import org.junit.jupiter.api.Assumptions;
import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ArgumentsSource; import org.junit.jupiter.params.provider.ArgumentsSource;
@ -60,6 +63,7 @@ import static org.hamcrest.Matchers.containsString;
import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail; import static org.junit.jupiter.api.Assertions.fail;
@ -676,6 +680,59 @@ public class HttpClientTest extends AbstractTest<TransportScenario>
assertEquals(users, destinations.size()); assertEquals(users, destinations.size());
} }
@ParameterizedTest
@ArgumentsSource(TransportProvider.class)
public void testIPv6Host(Transport transport) throws Exception
{
Assumptions.assumeTrue(Net.isIpv6InterfaceAvailable());
init(transport);
scenario.start(new EmptyServerHandler()
{
@Override
protected void service(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) throws IOException
{
response.setContentType("text/plain");
response.getOutputStream().print(request.getHeader("Host"));
}
});
// Test with a full URI.
String hostAddress = "::1";
String host = "[" + hostAddress + "]";
int port = Integer.parseInt(scenario.getNetworkConnectorLocalPort().get());
String uri = scenario.getScheme() + "://" + host + ":" + port + "/path";
ContentResponse response = scenario.client.newRequest(uri)
.method(HttpMethod.PUT)
.timeout(5, TimeUnit.SECONDS)
.send();
assertNotNull(response);
assertEquals(200, response.getStatus());
assertThat(new String(response.getContent(), StandardCharsets.ISO_8859_1), Matchers.startsWith("[::1]:"));
// Test with host address.
response = scenario.client.newRequest(hostAddress, port)
.scheme(scenario.getScheme())
.method(HttpMethod.PUT)
.timeout(5, TimeUnit.SECONDS)
.send();
assertNotNull(response);
assertEquals(200, response.getStatus());
assertThat(new String(response.getContent(), StandardCharsets.ISO_8859_1), Matchers.startsWith("[::1]:"));
// Test with host.
response = scenario.client.newRequest(host, port)
.scheme(scenario.getScheme())
.method(HttpMethod.PUT)
.timeout(5, TimeUnit.SECONDS)
.send();
assertNotNull(response);
assertEquals(200, response.getStatus());
assertThat(new String(response.getContent(), StandardCharsets.ISO_8859_1), Matchers.startsWith("[::1]:"));
assertEquals(1, scenario.client.getDestinations().size());
}
private void sleep(long time) throws IOException private void sleep(long time) throws IOException
{ {
try try