Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue #5304 HTTP2 HostHeader #5307

Merged
merged 6 commits into from
Sep 23, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -1126,18 +1126,12 @@ public static int normalizePort(String scheme, int port)
{
if (port > 0)
return port;
else if (isSchemeSecure(scheme))
return 443;
else
return 80;
return HttpScheme.getDefaultPort(scheme);
}

public boolean isDefaultPort(String scheme, int port)
{
if (isSchemeSecure(scheme))
return port == 443;
else
return port == 80;
return HttpScheme.getDefaultPort(scheme) == port;
}

public static boolean isSchemeSecure(String scheme)
Expand Down
18 changes: 12 additions & 6 deletions jetty-http/src/main/java/org/eclipse/jetty/http/HttpFields.java
Original file line number Diff line number Diff line change
Expand Up @@ -639,22 +639,28 @@ public Mutable add(HttpField field)

public Mutable add(HttpFields fields)
{
if (_fields == null)
_fields = new HttpField[fields.size() + 4];
else if (_size + fields.size() >= _fields.length)
_fields = Arrays.copyOf(_fields, _size + fields.size() + 4);

if (fields.size() == 0)
return this;

if (fields instanceof Immutable)
{
Immutable b = (Immutable)fields;
_fields = Arrays.copyOf(b._fields, b._fields.length + 4);
_size = b._fields.length;
System.arraycopy(b._fields, 0, _fields, _size, b._fields.length);
_size += b._fields.length;
}
else if (fields instanceof Mutable)
{
Mutable b = (Mutable)fields;
_fields = Arrays.copyOf(b._fields, b._fields.length);
_size = b._size;
System.arraycopy(b._fields, 0, _fields, _size, b._size);
_size += b._size;
}
else
{
_fields = new HttpField[fields.size() + 4];
_size = 0;
for (HttpField f : fields)
_fields[_size++] = f;
}
Expand Down
38 changes: 31 additions & 7 deletions jetty-http/src/main/java/org/eclipse/jetty/http/HttpScheme.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,14 @@
import org.eclipse.jetty.util.Trie;

/**
*
* HTTP and WebSocket Schemes
*/
public enum HttpScheme
{
HTTP("http"),
HTTPS("https"),
WS("ws"),
WSS("wss");
HTTP("http", 80),
HTTPS("https", 443),
WS("ws", 80),
WSS("wss", 443);

public static final Trie<HttpScheme> CACHE = new ArrayTrie<HttpScheme>();

Expand All @@ -46,11 +46,13 @@ public enum HttpScheme

private final String _string;
private final ByteBuffer _buffer;
private final int _defaultPort;

HttpScheme(String s)
HttpScheme(String s, int port)
{
_string = s;
_buffer = BufferUtil.toBuffer(s);
_defaultPort = port;
}

public ByteBuffer asByteBuffer()
Expand All @@ -60,17 +62,39 @@ public ByteBuffer asByteBuffer()

public boolean is(String s)
{
return s != null && _string.equalsIgnoreCase(s);
return _string.equalsIgnoreCase(s);
}

public String asString()
{
return _string;
}

public int getDefaultPort()
{
return _defaultPort;
}

public int normalizePort(int port)
{
return port == _defaultPort ? 0 : port;
}

@Override
public String toString()
{
return _string;
}

public static int getDefaultPort(String scheme)
{
HttpScheme httpScheme = scheme == null ? null : CACHE.get(scheme);
return httpScheme == null ? HTTP.getDefaultPort() : httpScheme.getDefaultPort();
}

public static int normalizePort(String scheme, int port)
{
HttpScheme httpScheme = scheme == null ? null : CACHE.get(scheme);
return httpScheme == null ? port : httpScheme.normalizePort(port);
}
}
9 changes: 5 additions & 4 deletions jetty-http/src/main/java/org/eclipse/jetty/http/HttpURI.java
Original file line number Diff line number Diff line change
Expand Up @@ -638,11 +638,12 @@ public boolean isAbsolute()

public Mutable normalize()
{
if (_port == 80 && HttpScheme.HTTP.is(_scheme))
_port = 0;
if (_port == 443 && HttpScheme.HTTPS.is(_scheme))
HttpScheme scheme = _scheme == null ? null : HttpScheme.CACHE.get(_scheme);
if (scheme != null && _port == scheme.getDefaultPort())
{
_port = 0;
_uri = null;
_uri = null;
}
return this;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -742,6 +742,25 @@ public void testAddNullName()
assertThat(fields.size(), is(0));
}

@Test
public void testAddHttpFields()
{
HttpFields.Mutable fields = new HttpFields.Mutable(new HttpFields.Mutable());
fields.add("One", "1");

fields = new HttpFields.Mutable(fields);

fields.add(HttpFields.build().add("two", "2").add("three", "3"));
fields.add(HttpFields.build().add("four", "4").add("five", "5").asImmutable());

assertThat(fields.size(), is(5));
assertThat(fields.get("one"), is("1"));
assertThat(fields.get("two"), is("2"));
assertThat(fields.get("three"), is("3"));
assertThat(fields.get("four"), is("4"));
assertThat(fields.get("five"), is("5"));
}

@Test
public void testPutNullName()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,31 +18,34 @@

package org.eclipse.jetty.server;

import java.util.Objects;
import javax.servlet.http.HttpServletRequest;

import org.eclipse.jetty.http.HostPortHttpField;
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.HttpVersion;

/**
* Customizes requests that lack the {@code Host} header (for example, HTTP 1.0 requests).
* <p>
* In case of HTTP 1.0 requests that lack the {@code Host} header, the application may issue
* a redirect, and the {@code Location} header is usually constructed from the {@code Host}
* header; if the {@code Host} header is missing, the server may query the connector for its
* IP address in order to construct the {@code Location} header, and thus leak to clients
* internal IP addresses.
* Adds a missing {@code Host} header (for example, HTTP 1.0 or 2.0 requests).
* <p>
* This {@link HttpConfiguration.Customizer} is configured with a {@code serverName} and
* optionally a {@code serverPort}.
* If the {@code Host} header is absent, the configured {@code serverName} will be set on
* the request so that {@link HttpServletRequest#getServerName()} will return that value,
* and likewise for {@code serverPort} and {@link HttpServletRequest#getServerPort()}.
* The host and port may be provided in the constructor or taken from the
* {@link Request#getServerName()} and {@link Request#getServerPort()} methods.
* </p>
*/
public class HostHeaderCustomizer implements HttpConfiguration.Customizer
{
private final String serverName;
private final int serverPort;

/**
* Construct customizer that uses {@link Request#getServerName()} and
* {@link Request#getServerPort()} to create a host header.
*/
public HostHeaderCustomizer()
joakime marked this conversation as resolved.
Show resolved Hide resolved
{
this(null, 0);
joakime marked this conversation as resolved.
Show resolved Hide resolved
}

/**
* @param serverName the {@code serverName} to set on the request (the {@code serverPort} will not be set)
*/
Expand All @@ -57,15 +60,26 @@ public HostHeaderCustomizer(String serverName)
*/
public HostHeaderCustomizer(String serverName, int serverPort)
{
this.serverName = Objects.requireNonNull(serverName);
this.serverName = serverName;
this.serverPort = serverPort;
}

@Override
public void customize(Connector connector, HttpConfiguration channelConfig, Request request)
{
if (request.getHeader("Host") == null)
// TODO set the field as well?
request.setHttpURI(HttpURI.build(request.getHttpURI()).host(serverName).port(serverPort));
if (request.getHttpVersion() != HttpVersion.HTTP_1_1 && !request.getHttpFields().contains(HttpHeader.HOST))
{
String host = serverName == null ? request.getServerName() : serverName;
int port = HttpScheme.normalizePort(request.getScheme(), serverPort == 0 ? request.getServerPort() : serverPort);

if (serverName != null || serverPort > 0)
joakime marked this conversation as resolved.
Show resolved Hide resolved
request.setHttpURI(HttpURI.build(request.getHttpURI()).authority(host, port));

HttpFields original = request.getHttpFields();
HttpFields.Mutable httpFields = HttpFields.build(original.size() + 1);
httpFields.add(new HostPortHttpField(host, port));
httpFields.add(request.getHttpFields());
joakime marked this conversation as resolved.
Show resolved Hide resolved
request.setHttpFields(httpFields);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1355,11 +1355,7 @@ public int getServerPort()

// If no port specified, return the default port for the scheme
if (port <= 0)
{
if (getScheme().equalsIgnoreCase(URIUtil.HTTPS))
return 443;
return 80;
}
return HttpScheme.getDefaultPort(getScheme());

// return a specific port
return port;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -330,7 +330,7 @@ public String encodeURL(String url)
path = (path == null ? "" : path);
int port = uri.getPort();
if (port < 0)
port = HttpScheme.HTTPS.asString().equalsIgnoreCase(uri.getScheme()) ? 443 : 80;
port = HttpScheme.getDefaultPort(uri.getScheme());

// Is it the same server?
if (!request.getServerName().equalsIgnoreCase(uri.getHost()))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
public class HostHeaderCustomizerTest
{
@Test
public void testHostHeaderCustomizer() throws Exception
public void testFixedHostPort() throws Exception
{
Server server = new Server();
HttpConfiguration httpConfig = new HttpConfiguration();
Expand All @@ -55,6 +55,7 @@ public void handle(String target, Request baseRequest, HttpServletRequest reques
baseRequest.setHandled(true);
assertEquals(serverName, request.getServerName());
assertEquals(serverPort, request.getServerPort());
assertEquals(serverName + ":" + serverPort, request.getHeader("Host"));
response.sendRedirect(redirectPath);
}
});
Expand Down Expand Up @@ -89,4 +90,59 @@ public void handle(String target, Request baseRequest, HttpServletRequest reques
server.stop();
}
}

@Test
public void testHostPort() throws Exception
{
Server server = new Server();
HttpConfiguration httpConfig = new HttpConfiguration();
final String serverName = "127.0.0.1";
final String redirectPath = "/redirect";
httpConfig.addCustomizer(new HostHeaderCustomizer());
final ServerConnector connector = new ServerConnector(server, new HttpConnectionFactory(httpConfig));
server.addConnector(connector);
server.setHandler(new AbstractHandler()
{
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
baseRequest.setHandled(true);
assertEquals(serverName, request.getServerName());
assertEquals(connector.getLocalPort(), request.getServerPort());
assertEquals(serverName + ":" + connector.getLocalPort(), request.getHeader("Host"));
response.sendRedirect(redirectPath);
}
});
server.start();

try
{
try (Socket socket = new Socket("localhost", connector.getLocalPort()))
{
try (OutputStream output = socket.getOutputStream())
{
String request =
"GET / HTTP/1.0\r\n" +
"\r\n";
output.write(request.getBytes(StandardCharsets.UTF_8));
output.flush();

HttpTester.Input input = HttpTester.from(socket.getInputStream());
HttpTester.Response response = HttpTester.parseResponse(input);

String location = response.get("location");
assertNotNull(location);
String schemePrefix = "http://";
assertTrue(location.startsWith(schemePrefix));
assertTrue(location.endsWith(redirectPath));
String hostPort = location.substring(schemePrefix.length(), location.length() - redirectPath.length());
assertEquals(serverName + ":" + connector.getLocalPort(), hostPort);
}
}
}
finally
{
server.stop();
}
}
}