Fix #11414 URI schema and port normalization (#11416)

* Issue #11414 - use HttpURI instead of URIUtil to have a single point of spec behavior

* Issue #11414 - enforce lowercase scheme in HttpConfiguration.secureScheme

* Issue #11414 - Scheme produced on `Location` header is lowercase

* Issue #11414 - Scheme to lowercase

* Issue #11414 - Scheme to lowercase

* Revert change to HttpClient

* Added schema port knowledge to URIUtil

* Fixed tests for normalized URIs

* updates from review

* updates from review

* Fix tests

* Restored methods as deprecated

* More testing

---------

Co-authored-by: Joakim Erdfelt <joakim.erdfelt@gmail.com>
This commit is contained in:
Greg Wilkins 2024-02-20 20:12:55 +01:00 committed by GitHub
parent d02406c164
commit f07d812698
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
36 changed files with 587 additions and 150 deletions

View File

@ -56,6 +56,7 @@ import org.eclipse.jetty.util.Jetty;
import org.eclipse.jetty.util.ProcessorUtils; import org.eclipse.jetty.util.ProcessorUtils;
import org.eclipse.jetty.util.Promise; import org.eclipse.jetty.util.Promise;
import org.eclipse.jetty.util.SocketAddressResolver; import org.eclipse.jetty.util.SocketAddressResolver;
import org.eclipse.jetty.util.URIUtil;
import org.eclipse.jetty.util.annotation.ManagedAttribute; import org.eclipse.jetty.util.annotation.ManagedAttribute;
import org.eclipse.jetty.util.annotation.ManagedObject; import org.eclipse.jetty.util.annotation.ManagedObject;
import org.eclipse.jetty.util.component.ContainerLifeCycle; import org.eclipse.jetty.util.component.ContainerLifeCycle;
@ -1099,11 +1100,17 @@ public class HttpClient extends ContainerLifeCycle
return proxyConfig; return proxyConfig;
} }
/**
* Return a normalized port suitable for use by Origin and Address
* @param scheme the scheme to use for the default port (if port is unspecified)
* @param port the port (0 or negative means the port is unspecified)
* @return the normalized port.
*/
public static int normalizePort(String scheme, int port) public static int normalizePort(String scheme, int port)
{ {
if (port > 0) if (port > 0)
return port; return port;
return HttpScheme.getDefaultPort(scheme); return URIUtil.getDefaultPortForScheme(scheme);
} }
public ClientConnectionFactory newSslClientConnectionFactory(SslContextFactory.Client sslContextFactory, ClientConnectionFactory connectionFactory) public ClientConnectionFactory newSslClientConnectionFactory(SslContextFactory.Client sslContextFactory, ClientConnectionFactory connectionFactory)

View File

@ -30,6 +30,7 @@ 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.util.NanoTime; import org.eclipse.jetty.util.NanoTime;
import org.eclipse.jetty.util.URIUtil;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -278,7 +279,7 @@ public class HttpRedirector
Matcher matcher = URI_PATTERN.matcher(location); Matcher matcher = URI_PATTERN.matcher(location);
if (matcher.matches()) if (matcher.matches())
{ {
String scheme = matcher.group(2); String scheme = URIUtil.normalizeScheme(matcher.group(2));
String authority = matcher.group(3); String authority = matcher.group(3);
String path = matcher.group(4); String path = matcher.group(4);
String query = matcher.group(5); String query = matcher.group(5);

View File

@ -20,6 +20,7 @@ import java.util.Map;
import java.util.Objects; import java.util.Objects;
import org.eclipse.jetty.client.transport.HttpClientTransportDynamic; import org.eclipse.jetty.client.transport.HttpClientTransportDynamic;
import org.eclipse.jetty.http.HttpURI;
import org.eclipse.jetty.io.ClientConnectionFactory; import org.eclipse.jetty.io.ClientConnectionFactory;
import org.eclipse.jetty.io.EndPoint; import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.util.HostPort; import org.eclipse.jetty.util.HostPort;
@ -74,7 +75,7 @@ public class Origin
public Origin(String scheme, Address address, Object tag, Protocol protocol) public Origin(String scheme, Address address, Object tag, Protocol protocol)
{ {
this.scheme = Objects.requireNonNull(scheme); this.scheme = URIUtil.normalizeScheme(Objects.requireNonNull(scheme));
this.address = address; this.address = address;
this.tag = tag; this.tag = tag;
this.protocol = protocol; this.protocol = protocol;
@ -122,9 +123,7 @@ public class Origin
public String asString() public String asString()
{ {
StringBuilder result = new StringBuilder(); return HttpURI.from(scheme, address.host, address.port, null).asString();
URIUtil.appendSchemeHostPort(result, scheme, address.host, address.port);
return result.toString();
} }
@Override @Override

View File

@ -39,6 +39,7 @@ import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.HostPort; import org.eclipse.jetty.util.HostPort;
import org.eclipse.jetty.util.NanoTime; import org.eclipse.jetty.util.NanoTime;
import org.eclipse.jetty.util.Promise; import org.eclipse.jetty.util.Promise;
import org.eclipse.jetty.util.URIUtil;
import org.eclipse.jetty.util.annotation.ManagedAttribute; import org.eclipse.jetty.util.annotation.ManagedAttribute;
import org.eclipse.jetty.util.annotation.ManagedObject; import org.eclipse.jetty.util.annotation.ManagedObject;
import org.eclipse.jetty.util.component.ContainerLifeCycle; import org.eclipse.jetty.util.component.ContainerLifeCycle;
@ -80,7 +81,8 @@ public class HttpDestination extends ContainerLifeCycle implements Destination,
String host = HostPort.normalizeHost(getHost()); String host = HostPort.normalizeHost(getHost());
int port = getPort(); int port = getPort();
if (port != HttpScheme.getDefaultPort(getScheme())) String scheme = getScheme();
if (port != URIUtil.getDefaultPortForScheme(scheme))
host += ":" + port; host += ":" + port;
hostField = new HttpField(HttpHeader.HOST, host); hostField = new HttpField(HttpHeader.HOST, host);

View File

@ -55,6 +55,7 @@ import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpMethod; import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http.HttpURI;
import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.util.Fields; import org.eclipse.jetty.util.Fields;
import org.eclipse.jetty.util.NanoTime; import org.eclipse.jetty.util.NanoTime;
@ -122,9 +123,7 @@ public class HttpRequest implements Request
{ {
if (newURI == null) if (newURI == null)
{ {
StringBuilder builder = new StringBuilder(64); newURI = HttpURI.from(getScheme(), getHost(), getPort(), null).toURI();
URIUtil.appendSchemeHostPort(builder, getScheme(), getHost(), getPort());
newURI = URI.create(builder.toString());
} }
HttpRequest newRequest = copyInstance(newURI); HttpRequest newRequest = copyInstance(newURI);
@ -184,7 +183,7 @@ public class HttpRequest implements Request
@Override @Override
public Request scheme(String scheme) public Request scheme(String scheme)
{ {
this.scheme = scheme; this.scheme = URIUtil.normalizeScheme(scheme);
this.uri = null; this.uri = null;
return this; return this;
} }

View File

@ -32,13 +32,13 @@ import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpHeader; 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.http.HttpURI;
import org.eclipse.jetty.http.UriCompliance; import org.eclipse.jetty.http.UriCompliance;
import org.eclipse.jetty.server.HttpConfiguration; import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.server.Response; import org.eclipse.jetty.server.Response;
import org.eclipse.jetty.toolchain.test.Net; import org.eclipse.jetty.toolchain.test.Net;
import org.eclipse.jetty.util.Fields; import org.eclipse.jetty.util.Fields;
import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.URIUtil;
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;
@ -68,9 +68,8 @@ public class HttpClientURITest extends AbstractHttpClientServerTest
.timeout(5, TimeUnit.SECONDS); .timeout(5, TimeUnit.SECONDS);
assertEquals(host, request.getHost()); assertEquals(host, request.getHost());
StringBuilder uri = new StringBuilder(); HttpURI httpURI = HttpURI.from(scenario.getScheme(), host, connector.getLocalPort(), null);
URIUtil.appendSchemeHostPort(uri, scenario.getScheme(), host, connector.getLocalPort()); assertEquals(httpURI.asString(), request.getURI().toString());
assertEquals(uri.toString(), request.getURI().toString());
assertEquals(HttpStatus.OK_200, request.send().getStatus()); assertEquals(HttpStatus.OK_200, request.send().getStatus());
} }

View File

@ -35,6 +35,7 @@ import java.util.Properties;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import org.eclipse.jetty.http.HttpScheme; import org.eclipse.jetty.http.HttpScheme;
import org.eclipse.jetty.http.HttpURI;
import org.eclipse.jetty.server.Connector; import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.NetworkConnector; import org.eclipse.jetty.server.NetworkConnector;
@ -249,9 +250,7 @@ public class XmlConfiguredJetty
public URI getServerURI() throws UnknownHostException public URI getServerURI() throws UnknownHostException
{ {
StringBuilder uri = new StringBuilder(); return HttpURI.from(getScheme(), InetAddress.getLocalHost().getHostAddress(), getServerPort(), null).toURI();
URIUtil.appendSchemeHostPort(uri, getScheme(), InetAddress.getLocalHost().getHostAddress(), getServerPort());
return URI.create(uri.toString());
} }
public Path getJettyBasePath() public Path getJettyBasePath()
@ -332,7 +331,7 @@ public class XmlConfiguredJetty
public void setScheme(String scheme) public void setScheme(String scheme)
{ {
this._scheme = scheme; this._scheme = URIUtil.normalizeScheme(scheme);
} }
public void start() throws Exception public void start() throws Exception

View File

@ -38,6 +38,7 @@ import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Response; import org.eclipse.jetty.server.Response;
import org.eclipse.jetty.server.handler.TryPathsHandler; import org.eclipse.jetty.server.handler.TryPathsHandler;
import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.URIUtil;
import org.eclipse.jetty.util.thread.QueuedThreadPool; import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -317,7 +318,7 @@ public class FastCGIProxyHandler extends ProxyHandler.Reverse
// If the Host header is missing, add it. // If the Host header is missing, add it.
if (!proxyToServerRequest.getHeaders().contains(HttpHeader.HOST)) if (!proxyToServerRequest.getHeaders().contains(HttpHeader.HOST))
{ {
if (serverPort != HttpScheme.getDefaultPort(scheme)) if (serverPort != URIUtil.getDefaultPortForScheme(scheme))
serverName += ":" + serverPort; serverName += ":" + serverPort;
String host = serverName; String host = serverName;
proxyToServerRequest.headers(headers -> headers proxyToServerRequest.headers(headers -> headers

View File

@ -17,16 +17,17 @@ import java.nio.ByteBuffer;
import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Index; import org.eclipse.jetty.util.Index;
import org.eclipse.jetty.util.URIUtil;
/** /**
* HTTP and WebSocket Schemes * HTTP and WebSocket Schemes
*/ */
public enum HttpScheme public enum HttpScheme
{ {
HTTP("http", 80), HTTP("http"),
HTTPS("https", 443), HTTPS("https"),
WS("ws", 80), WS("ws"),
WSS("wss", 443); WSS("wss");
public static final Index<HttpScheme> CACHE = new Index.Builder<HttpScheme>() public static final Index<HttpScheme> CACHE = new Index.Builder<HttpScheme>()
.caseSensitive(false) .caseSensitive(false)
@ -37,11 +38,11 @@ public enum HttpScheme
private final ByteBuffer _buffer; private final ByteBuffer _buffer;
private final int _defaultPort; private final int _defaultPort;
HttpScheme(String s, int port) HttpScheme(String s)
{ {
_string = s; _string = s;
_buffer = BufferUtil.toBuffer(s); _buffer = BufferUtil.toBuffer(s);
_defaultPort = port; _defaultPort = URIUtil.getDefaultPortForScheme(s);
} }
public ByteBuffer asByteBuffer() public ByteBuffer asByteBuffer()
@ -75,16 +76,29 @@ public enum HttpScheme
return _string; return _string;
} }
/**
* Get the default port for a URI scheme
* @param scheme The scheme
* @return Default port for URI scheme
* @deprecated Use {@link URIUtil#getDefaultPortForScheme(String)}
*/
@Deprecated
public static int getDefaultPort(String scheme) public static int getDefaultPort(String scheme)
{ {
HttpScheme httpScheme = scheme == null ? null : CACHE.get(scheme); return URIUtil.getDefaultPortForScheme(scheme);
return httpScheme == null ? HTTP.getDefaultPort() : httpScheme.getDefaultPort();
} }
/**
* Normalize a port for a URI scheme
* @param scheme the scheme
* @param port the port to normalize
* @return The normalized port
* @deprecated Use {@link URIUtil#normalizePortForScheme(String, int)}
*/
@Deprecated
public static int normalizePort(String scheme, int port) public static int normalizePort(String scheme, int port)
{ {
HttpScheme httpScheme = scheme == null ? null : CACHE.get(scheme); return URIUtil.normalizePortForScheme(scheme, port);
return httpScheme == null ? port : httpScheme.normalizePort(port);
} }
public static boolean isSecure(String scheme) public static boolean isSecure(String scheme)

View File

@ -151,6 +151,11 @@ public interface HttpURI
return new Mutable(scheme, host, port, pathQuery).asImmutable(); return new Mutable(scheme, host, port, pathQuery).asImmutable();
} }
static Immutable from(String scheme, String host, int port, String path, String query, String fragment)
{
return new Immutable(scheme, host, port, path, query, fragment);
}
Immutable asImmutable(); Immutable asImmutable();
String asString(); String asString();
@ -302,18 +307,19 @@ public interface HttpURI
_violations = Collections.unmodifiableSet(EnumSet.copyOf(builder._violations)); _violations = Collections.unmodifiableSet(EnumSet.copyOf(builder._violations));
} }
private Immutable(String uri) private Immutable(String scheme, String host, int port, String path, String query, String fragment)
{ {
_scheme = null; _uri = null;
_scheme = URIUtil.normalizeScheme(scheme);
_user = null; _user = null;
_host = null; _host = host;
_port = -1; _port = port;
_path = uri; _path = path;
_canonicalPath = _path == null ? null : URIUtil.canonicalPath(_path);
_param = null; _param = null;
_query = null; _query = query;
_fragment = null; _fragment = fragment;
_uri = uri;
_canonicalPath = null;
} }
@Override @Override
@ -340,19 +346,26 @@ public interface HttpURI
out.append(_host); out.append(_host);
} }
if (_port > 0) int normalizedPort = URIUtil.normalizePortForScheme(_scheme, _port);
out.append(':').append(_port); if (normalizedPort > 0)
out.append(':').append(normalizedPort);
// we output even if the input is an empty string (to match java URI / URL behaviors)
boolean hasQuery = _query != null;
boolean hasFragment = _fragment != null;
if (_path != null) if (_path != null)
out.append(_path); out.append(_path);
else if (hasQuery || hasFragment)
out.append('/');
if (_query != null) if (hasQuery)
out.append('?').append(_query); out.append('?').append(_query);
if (_fragment != null) if (hasFragment)
out.append('#').append(_fragment); out.append('#').append(_fragment);
if (out.length() > 0) if (!out.isEmpty())
_uri = out.toString(); _uri = out.toString();
else else
_uri = ""; _uri = "";
@ -504,7 +517,7 @@ public interface HttpURI
{ {
try try
{ {
return new URI(_scheme, null, _host, _port, _path, _query == null ? null : UrlEncoded.decodeString(_query), _fragment); return new URI(_scheme, null, _host, URIUtil.normalizePortForScheme(_scheme, _port), _path, _query == null ? null : UrlEncoded.decodeString(_query), _fragment);
} }
catch (URISyntaxException x) catch (URISyntaxException x)
{ {
@ -616,7 +629,7 @@ public interface HttpURI
{ {
_uri = null; _uri = null;
_scheme = uri.getScheme(); _scheme = URIUtil.normalizeScheme(uri.getScheme());
_host = uri.getHost(); _host = uri.getHost();
if (_host == null && uri.getRawSchemeSpecificPart().startsWith("//")) if (_host == null && uri.getRawSchemeSpecificPart().startsWith("//"))
_host = ""; _host = "";
@ -631,13 +644,9 @@ public interface HttpURI
private Mutable(String scheme, String host, int port, String pathQuery) private Mutable(String scheme, String host, int port, String pathQuery)
{ {
// TODO review if this should be here
if (port == HttpScheme.getDefaultPort(scheme))
port = 0;
_uri = null; _uri = null;
_scheme = scheme; _scheme = URIUtil.normalizeScheme(scheme);
_host = host; _host = host;
_port = port; _port = port;
@ -960,7 +969,7 @@ public interface HttpURI
public Mutable scheme(String scheme) public Mutable scheme(String scheme)
{ {
_scheme = scheme; _scheme = URIUtil.normalizeScheme(scheme);
_uri = null; _uri = null;
return this; return this;
} }
@ -1122,7 +1131,7 @@ public interface HttpURI
{ {
case ':': case ':':
// must have been a scheme // must have been a scheme
_scheme = uri.substring(mark, i); _scheme = URIUtil.normalizeScheme(uri.substring(mark, i));
// Start again with scheme set // Start again with scheme set
state = State.START; state = State.START;
break; break;

View File

@ -940,4 +940,141 @@ public class HttpURITest
{ {
assertThat(UriCompliance.from(UriCompliance.DEFAULT.getName()), sameInstance(UriCompliance.DEFAULT)); assertThat(UriCompliance.from(UriCompliance.DEFAULT.getName()), sameInstance(UriCompliance.DEFAULT));
} }
public static Stream<Arguments> concatNormalizedURIShortCases()
{
return Stream.of(
// Default behaviors of stripping a port number based on scheme
Arguments.of("http", "example.org", 80, "http://example.org"),
Arguments.of("https", "example.org", 443, "https://example.org"),
Arguments.of("ws", "example.org", 80, "ws://example.org"),
Arguments.of("wss", "example.org", 443, "wss://example.org"),
// Mismatches between scheme and port
Arguments.of("http", "example.org", 443, "http://example.org:443"),
Arguments.of("https", "example.org", 80, "https://example.org:80"),
Arguments.of("ws", "example.org", 443, "ws://example.org:443"),
Arguments.of("wss", "example.org", 80, "wss://example.org:80"),
// Odd ports
Arguments.of("http", "example.org", 12345, "http://example.org:12345"),
Arguments.of("https", "example.org", 54321, "https://example.org:54321"),
Arguments.of("ws", "example.org", 6666, "ws://example.org:6666"),
Arguments.of("wss", "example.org", 7777, "wss://example.org:7777"),
// Non-lowercase Schemes
Arguments.of("HTTP", "example.org", 8181, "http://example.org:8181"),
Arguments.of("hTTps", "example.org", 443, "https://example.org"),
Arguments.of("WS", "example.org", 8282, "ws://example.org:8282"),
Arguments.of("wsS", "example.org", 8383, "wss://example.org:8383"),
// Undefined Ports
Arguments.of("http", "example.org", 0, "http://example.org"),
Arguments.of("https", "example.org", -1, "https://example.org"),
Arguments.of("ws", "example.org", -80, "ws://example.org"),
Arguments.of("wss", "example.org", -2, "wss://example.org"),
// Unrecognized (non-http) schemes
Arguments.of("foo", "example.org", 0, "foo://example.org"),
Arguments.of("ssh", "example.org", 22, "ssh://example.org"),
Arguments.of("ftp", "example.org", 21, "ftp://example.org"),
Arguments.of("ssh", "example.org", 2222, "ssh://example.org:2222"),
Arguments.of("ftp", "example.org", 2121, "ftp://example.org:2121"),
Arguments.of("file", "etc", -1, "file://etc")
);
}
@ParameterizedTest
@MethodSource("concatNormalizedURIShortCases")
public void testFromShortAsStringNormalized(String scheme, String server, int port, String expectedStr)
{
HttpURI httpURI = HttpURI.from(scheme, server, port, null);
assertThat(httpURI.asString(), is(expectedStr));
}
public static Stream<Arguments> concatNormalizedURICases()
{
return Stream.of(
// Default behaviors of stripping a port number based on scheme
Arguments.of("http", "example.org", 80, "/", null, null, "http://example.org/"),
Arguments.of("https", "example.org", 443, "/", null, null, "https://example.org/"),
Arguments.of("ws", "example.org", 80, "/", null, null, "ws://example.org/"),
Arguments.of("wss", "example.org", 443, "/", null, null, "wss://example.org/"),
// Mismatches between scheme and port
Arguments.of("http", "example.org", 443, "/", null, null, "http://example.org:443/"),
Arguments.of("https", "example.org", 80, "/", null, null, "https://example.org:80/"),
Arguments.of("ws", "example.org", 443, "/", null, null, "ws://example.org:443/"),
Arguments.of("wss", "example.org", 80, "/", null, null, "wss://example.org:80/"),
// Odd ports
Arguments.of("http", "example.org", 12345, "/", null, null, "http://example.org:12345/"),
Arguments.of("https", "example.org", 54321, "/", null, null, "https://example.org:54321/"),
Arguments.of("ws", "example.org", 6666, "/", null, null, "ws://example.org:6666/"),
Arguments.of("wss", "example.org", 7777, "/", null, null, "wss://example.org:7777/"),
// Non-lowercase Schemes
Arguments.of("HTTP", "example.org", 8181, "/", null, null, "http://example.org:8181/"),
Arguments.of("hTTps", "example.org", 443, "/", null, null, "https://example.org/"),
Arguments.of("WS", "example.org", 8282, "/", null, null, "ws://example.org:8282/"),
Arguments.of("wsS", "example.org", 8383, "/", null, null, "wss://example.org:8383/"),
// Undefined Ports
Arguments.of("http", "example.org", 0, "/", null, null, "http://example.org/"),
Arguments.of("https", "example.org", -1, "/", null, null, "https://example.org/"),
Arguments.of("ws", "example.org", -80, "/", null, null, "ws://example.org/"),
Arguments.of("wss", "example.org", -2, "/", null, null, "wss://example.org/"),
// Unrecognized (non-http) schemes
Arguments.of("foo", "example.org", 0, "/", null, null, "foo://example.org/"),
Arguments.of("ssh", "example.org", 22, "/", null, null, "ssh://example.org/"),
Arguments.of("ftp", "example.org", 21, "/", null, null, "ftp://example.org/"),
Arguments.of("ssh", "example.org", 2222, "/", null, null, "ssh://example.org:2222/"),
Arguments.of("ftp", "example.org", 2121, "/", null, null, "ftp://example.org:2121/"),
// Path choices
Arguments.of("http", "example.org", 0, "/a/b/c/d", null, null, "http://example.org/a/b/c/d"),
Arguments.of("http", "example.org", 0, "/a%20b/c%20d", null, null, "http://example.org/a%20b/c%20d"),
// Query specified
Arguments.of("http", "example.org", 0, "/", "a=b", null, "http://example.org/?a=b"),
Arguments.of("http", "example.org", 0, "/documentation/latest/", "a=b", null, "http://example.org/documentation/latest/?a=b"),
Arguments.of("http", "example.org", 0, null, "a=b", null, "http://example.org/?a=b"),
Arguments.of("http", "example.org", 0, null, "", null, "http://example.org/?"),
// Fragment specified
Arguments.of("http", "example.org", 0, "/", null, "", "http://example.org/#"),
Arguments.of("http", "example.org", 0, "/", null, "toc", "http://example.org/#toc"),
Arguments.of("http", "example.org", 0, null, null, "toc", "http://example.org/#toc"),
// Empty query & fragment - behavior matches java URI and URL
Arguments.of("http", "example.org", 0, null, "", "", "http://example.org/?#")
);
}
@ParameterizedTest
@MethodSource("concatNormalizedURICases")
public void testFromAsStringNormalized(String scheme, String server, int port, String path, String query, String fragment, String expectedStr)
{
HttpURI httpURI = HttpURI.from(scheme, server, port, path, query, fragment);
assertThat(httpURI.asString(), is(expectedStr));
}
/**
* Tests of parameters that result in undesired behaviors.
* {@link HttpURI#from(String, String, int, String)}
*/
public static Stream<Arguments> fromBad()
{
return Stream.of(
// bad schemes
Arguments.of(null, "example.org", 0, "//example.org"),
Arguments.of("", "example.org", 0, "://example.org"),
Arguments.of("\t", "example.org", 0, "\t://example.org"),
Arguments.of(" ", "example.org", 0, " ://example.org"),
// bad ports
Arguments.of("http", "example.org", 1_000_000, "http://example.org:1000000"),
// bad ports
Arguments.of("ws", "example.org", -222333, "ws://example.org"), // negative port same as -1, i.e. not set.
// bad servers
Arguments.of("http", null, 0, "http:"),
Arguments.of("http", "", 0, "http://"),
Arguments.of("http", "\t", 0, "http://\t"),
Arguments.of("http", " ", 0, "http:// ")
);
}
@ParameterizedTest
@MethodSource("fromBad")
public void testFromBad(String scheme, String server, int port, String expectedStr)
{
HttpURI httpURI = HttpURI.from(scheme, server, port, null);
assertThat(httpURI.asString(), is(expectedStr));
}
} }

View File

@ -672,8 +672,7 @@ public class OpenIdAuthenticator extends LoginAuthenticator
private String getRedirectUri(Request request) private String getRedirectUri(Request request)
{ {
final StringBuffer redirectUri = new StringBuffer(128); final StringBuilder redirectUri = URIUtil.newURIBuilder(request.getHttpURI().getScheme(),
URIUtil.appendSchemeHostPort(redirectUri, request.getHttpURI().getScheme(),
Request.getServerName(request), Request.getServerPort(request)); Request.getServerName(request), Request.getServerPort(request));
redirectUri.append(URIUtil.addPaths(request.getContext().getContextPath(), _redirectPath)); redirectUri.append(URIUtil.addPaths(request.getContext().getContextPath(), _redirectPath));
return redirectUri.toString(); return redirectUri.toString();

View File

@ -14,6 +14,7 @@
package org.eclipse.jetty.rewrite.handler; package org.eclipse.jetty.rewrite.handler;
import org.eclipse.jetty.http.HttpURI; import org.eclipse.jetty.http.HttpURI;
import org.eclipse.jetty.util.URIUtil;
/** /**
* <p>Sets the request URI scheme, by default {@code https}.</p> * <p>Sets the request URI scheme, by default {@code https}.</p>
@ -29,7 +30,7 @@ public class ForwardedSchemeHeaderRule extends HeaderRule
public void setScheme(String scheme) public void setScheme(String scheme)
{ {
_scheme = scheme; _scheme = URIUtil.normalizeScheme(scheme);
} }
@Override @Override

View File

@ -134,7 +134,7 @@ public class SecurityHandlerTest
response = _connector.getResponse("GET /ctx/confidential/info HTTP/1.0\r\n\r\n"); response = _connector.getResponse("GET /ctx/confidential/info HTTP/1.0\r\n\r\n");
assertThat(response, containsString("HTTP/1.1 302 Found")); assertThat(response, containsString("HTTP/1.1 302 Found"));
assertThat(response, containsString("Location: BWTP://")); assertThat(response, containsString("Location: bwtp://"));
assertThat(response, containsString(":9999")); assertThat(response, containsString(":9999"));
assertThat(response, not(containsString("OK"))); assertThat(response, not(containsString("OK")));
@ -161,7 +161,7 @@ public class SecurityHandlerTest
response = _connector.getResponse("GET /ctx/confidential/info HTTP/1.0\r\n\r\n"); response = _connector.getResponse("GET /ctx/confidential/info HTTP/1.0\r\n\r\n");
assertThat(response, containsString("HTTP/1.1 302 Found")); assertThat(response, containsString("HTTP/1.1 302 Found"));
assertThat(response, containsString("Location: BWTP://")); assertThat(response, containsString("Location: bwtp://"));
assertThat(response, containsString(":9999")); assertThat(response, containsString(":9999"));
assertThat(response, not(containsString("OK"))); assertThat(response, not(containsString("OK")));

View File

@ -16,9 +16,9 @@ package org.eclipse.jetty.server;
import org.eclipse.jetty.http.HostPortHttpField; import org.eclipse.jetty.http.HostPortHttpField;
import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpScheme;
import org.eclipse.jetty.http.HttpURI; import org.eclipse.jetty.http.HttpURI;
import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.util.URIUtil;
/** /**
* Adds a missing {@code Host} header (for example, HTTP 1.0 or 2.0 requests). * Adds a missing {@code Host} header (for example, HTTP 1.0 or 2.0 requests).
@ -58,7 +58,7 @@ public class HostHeaderCustomizer implements HttpConfiguration.Customizer
return request; return request;
String host = serverName == null ? Request.getServerName(request) : serverName; String host = serverName == null ? Request.getServerName(request) : serverName;
int port = HttpScheme.normalizePort(request.getHttpURI().getScheme(), serverPort == 0 ? Request.getServerPort(request) : serverPort); int port = URIUtil.normalizePortForScheme(request.getHttpURI().getScheme(), serverPort == 0 ? Request.getServerPort(request) : serverPort);
HttpURI uri = (serverName != null || serverPort > 0) HttpURI uri = (serverName != null || serverPort > 0)
? HttpURI.build(request.getHttpURI()).authority(host, port).asImmutable() ? HttpURI.build(request.getHttpURI()).authority(host, port).asImmutable()

View File

@ -32,6 +32,7 @@ import org.eclipse.jetty.http.UriCompliance;
import org.eclipse.jetty.util.HostPort; import org.eclipse.jetty.util.HostPort;
import org.eclipse.jetty.util.Index; import org.eclipse.jetty.util.Index;
import org.eclipse.jetty.util.Jetty; import org.eclipse.jetty.util.Jetty;
import org.eclipse.jetty.util.URIUtil;
import org.eclipse.jetty.util.annotation.ManagedAttribute; import org.eclipse.jetty.util.annotation.ManagedAttribute;
import org.eclipse.jetty.util.annotation.ManagedObject; import org.eclipse.jetty.util.annotation.ManagedObject;
import org.eclipse.jetty.util.component.Dumpable; import org.eclipse.jetty.util.component.Dumpable;
@ -476,7 +477,7 @@ public class HttpConfiguration implements Dumpable
*/ */
public void setSecureScheme(String secureScheme) public void setSecureScheme(String secureScheme)
{ {
_secureScheme = secureScheme; _secureScheme = URIUtil.normalizeScheme(secureScheme);
} }
/** /**

View File

@ -356,10 +356,7 @@ public interface Response extends Content.Sink
if (!request.getConnectionMetaData().getHttpConfiguration().isRelativeRedirectAllowed()) if (!request.getConnectionMetaData().getHttpConfiguration().isRelativeRedirectAllowed())
{ {
// make the location an absolute URI // make the location an absolute URI
StringBuilder url = new StringBuilder(128); location = URIUtil.newURI(uri.getScheme(), Request.getServerName(request), Request.getServerPort(request), location, null);
URIUtil.appendSchemeHostPort(url, uri.getScheme(), Request.getServerName(request), Request.getServerPort(request));
url.append(location);
location = url.toString();
} }
} }
return location; return location;

View File

@ -70,6 +70,7 @@ import org.eclipse.jetty.util.HostPort;
import org.eclipse.jetty.util.IteratingCallback; import org.eclipse.jetty.util.IteratingCallback;
import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.TypeUtil; import org.eclipse.jetty.util.TypeUtil;
import org.eclipse.jetty.util.URIUtil;
import org.eclipse.jetty.util.thread.Invocable; import org.eclipse.jetty.util.thread.Invocable;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -1218,7 +1219,7 @@ public class HttpConnection extends AbstractMetaDataConnection implements Runnab
{ {
HostPort hostPort = _hostField == null ? getServerAuthority() : _hostField.getHostPort(); HostPort hostPort = _hostField == null ? getServerAuthority() : _hostField.getHostPort();
int port = hostPort.getPort(); int port = hostPort.getPort();
if (port == HttpScheme.getDefaultPort(_uri.getScheme())) if (port == URIUtil.getDefaultPortForScheme(_uri.getScheme()))
port = -1; port = -1;
_uri.authority(hostPort.getHost(), port); _uri.authority(hostPort.getHost(), port);
} }

View File

@ -666,7 +666,10 @@ public abstract class AbstractSessionManager extends ContainerLifeCycle implemen
path = (path == null ? "" : path); path = (path == null ? "" : path);
int port = httpURI.getPort(); int port = httpURI.getPort();
if (port < 0) if (port < 0)
port = HttpScheme.getDefaultPort(httpURI.getScheme()); {
String scheme = httpURI.getScheme();
port = URIUtil.getDefaultPortForScheme(scheme);
}
// Is it the same server? // Is it the same server?
if (!Request.getServerName(request).equalsIgnoreCase(httpURI.getHost())) if (!Request.getServerName(request).equalsIgnoreCase(httpURI.getHost()))

View File

@ -1359,6 +1359,19 @@ public final class URIUtil
return true; return true;
} }
/**
* Create a new URI from the arguments, handling IPv6 host encoding and default ports
*
* @param scheme the URI scheme
* @param server the URI server
* @param port the URI port
* @return A String URI
*/
public static String newURI(String scheme, String server, int port)
{
return newURI(scheme, server, port, null, null);
}
/** /**
* Create a new URI from the arguments, handling IPv6 host encoding and default ports * Create a new URI from the arguments, handling IPv6 host encoding and default ports
* *
@ -1370,11 +1383,36 @@ public final class URIUtil
* @return A String URI * @return A String URI
*/ */
public static String newURI(String scheme, String server, int port, String path, String query) public static String newURI(String scheme, String server, int port, String path, String query)
{
return newURI(scheme, server, port, path, query, null);
}
/**
* Create a new URI from the arguments, handling IPv6 host encoding and default ports
*
* @param scheme the URI scheme
* @param server the URI server
* @param port the URI port
* @param path the URI path
* @param query the URI query
* @param fragment the URI fragment
* @return A String URI
*/
public static String newURI(String scheme, String server, int port, String path, String query, String fragment)
{ {
StringBuilder builder = newURIBuilder(scheme, server, port); StringBuilder builder = newURIBuilder(scheme, server, port);
builder.append(path); // check only for null, as empty query/fragment have meaning.
if (query != null && query.length() > 0) // this also matches the behavior of java URL & URI
boolean hasQuery = query != null;
boolean hasFragment = fragment != null;
if (StringUtil.isNotBlank(path))
builder.append(path);
else if (hasQuery || hasFragment)
builder.append('/');
if (hasQuery)
builder.append('?').append(query); builder.append('?').append(query);
if (hasFragment)
builder.append('#').append(fragment);
return builder.toString(); return builder.toString();
} }
@ -1388,7 +1426,7 @@ public final class URIUtil
*/ */
public static StringBuilder newURIBuilder(String scheme, String server, int port) public static StringBuilder newURIBuilder(String scheme, String server, int port)
{ {
StringBuilder builder = new StringBuilder(); StringBuilder builder = new StringBuilder(128);
appendSchemeHostPort(builder, scheme, server, port); appendSchemeHostPort(builder, scheme, server, port);
return builder; return builder;
} }
@ -1403,28 +1441,11 @@ public final class URIUtil
*/ */
public static void appendSchemeHostPort(StringBuilder url, String scheme, String server, int port) public static void appendSchemeHostPort(StringBuilder url, String scheme, String server, int port)
{ {
scheme = normalizeScheme(scheme);
url.append(scheme).append("://").append(HostPort.normalizeHost(server)); url.append(scheme).append("://").append(HostPort.normalizeHost(server));
port = normalizePortForScheme(scheme, port);
if (port > 0) if (port > 0)
{ url.append(':').append(port);
switch (scheme)
{
case "ws":
case "http":
if (port != 80)
url.append(':').append(port);
break;
case "wss":
case "https":
if (port != 443)
url.append(':').append(port);
break;
default:
url.append(':').append(port);
}
}
} }
/** /**
@ -1434,31 +1455,16 @@ public final class URIUtil
* @param scheme the URI scheme * @param scheme the URI scheme
* @param server the URI server * @param server the URI server
* @param port the URI port * @param port the URI port
* @deprecated Use {@link #appendSchemeHostPort(StringBuilder, String, String, int)}
*/ */
@Deprecated
public static void appendSchemeHostPort(StringBuffer url, String scheme, String server, int port) public static void appendSchemeHostPort(StringBuffer url, String scheme, String server, int port)
{ {
scheme = normalizeScheme(scheme);
url.append(scheme).append("://").append(HostPort.normalizeHost(server)); url.append(scheme).append("://").append(HostPort.normalizeHost(server));
port = normalizePortForScheme(scheme, port);
if (port > 0) if (port > 0)
{ url.append(':').append(port);
switch (scheme)
{
case "ws":
case "http":
if (port != 80)
url.append(':').append(port);
break;
case "wss":
case "https":
if (port != 443)
url.append(':').append(port);
break;
default:
url.append(':').append(port);
}
}
} }
/** /**
@ -1925,4 +1931,52 @@ public final class URIUtil
.map(URIUtil::unwrapContainer) .map(URIUtil::unwrapContainer)
.map(URIUtil::correctFileURI); .map(URIUtil::correctFileURI);
} }
private static final Index<Integer> DEFAULT_PORT_FOR_SCHEME = new Index.Builder<Integer>()
.caseSensitive(false)
.with("ftp", 21)
.with("ssh", 22)
.with("telnet", 23)
.with("smtp", 25)
.with("http", 80)
.with("ws", 80)
.with("https", 443)
.with("wss", 443)
.build();
/**
* Get the default port for some well known schemes
* @param scheme The scheme
* @return The default port or -1 if not known
*/
public static int getDefaultPortForScheme(String scheme)
{
if (scheme == null)
return -1;
Integer port = DEFAULT_PORT_FOR_SCHEME.get(scheme);
return port == null ? -1 : port;
}
/**
* Normalize the scheme
* @param scheme The scheme to normalize
* @return The normalized version of the scheme
*/
public static String normalizeScheme(String scheme)
{
return scheme == null ? null : StringUtil.asciiToLowerCase(scheme);
}
/**
* Normalize a port for a given scheme
* @param scheme The scheme
* @param port The port to normalize
* @return The port number or 0 if provided port was less than 0 or was equal to the default port for the scheme
*/
public static int normalizePortForScheme(String scheme, int port)
{
if (port <= 0)
return 0;
return port == getDefaultPortForScheme(scheme) ? 0 : port;
}
} }

View File

@ -34,6 +34,7 @@ import org.junit.jupiter.api.condition.OS;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.CsvSource;
import org.junit.jupiter.params.provider.MethodSource; import org.junit.jupiter.params.provider.MethodSource;
import org.junit.jupiter.params.provider.ValueSource; import org.junit.jupiter.params.provider.ValueSource;
@ -47,6 +48,7 @@ import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull; 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.Assumptions.assumeTrue;
/** /**
* URIUtil Tests. * URIUtil Tests.
@ -1186,7 +1188,7 @@ public class URIUtilTest
assertThat(uris, contains(expected)); assertThat(uris, contains(expected));
} }
public static Stream<Arguments> appendSchemeHostPortCases() public static Stream<Arguments> schemeHostPortShortCases()
{ {
return Stream.of( return Stream.of(
// Default behaviors of stripping a port number based on scheme // Default behaviors of stripping a port number based on scheme
@ -1203,12 +1205,29 @@ public class URIUtilTest
Arguments.of("http", "example.org", 12345, "http://example.org:12345"), Arguments.of("http", "example.org", 12345, "http://example.org:12345"),
Arguments.of("https", "example.org", 54321, "https://example.org:54321"), Arguments.of("https", "example.org", 54321, "https://example.org:54321"),
Arguments.of("ws", "example.org", 6666, "ws://example.org:6666"), Arguments.of("ws", "example.org", 6666, "ws://example.org:6666"),
Arguments.of("wss", "example.org", 7777, "wss://example.org:7777") Arguments.of("wss", "example.org", 7777, "wss://example.org:7777"),
// Non-lowercase Schemes
Arguments.of("HTTP", "example.org", 8181, "http://example.org:8181"),
Arguments.of("hTTps", "example.org", 443, "https://example.org"),
Arguments.of("WS", "example.org", 8282, "ws://example.org:8282"),
Arguments.of("wsS", "example.org", 8383, "wss://example.org:8383"),
// Undefined Ports
Arguments.of("http", "example.org", 0, "http://example.org"),
Arguments.of("https", "example.org", -1, "https://example.org"),
Arguments.of("ws", "example.org", -80, "ws://example.org"),
Arguments.of("wss", "example.org", -2, "wss://example.org"),
// Unrecognized (non-http) schemes
Arguments.of("foo", "example.org", 0, "foo://example.org"),
Arguments.of("ssh", "example.org", 22, "ssh://example.org"),
Arguments.of("ftp", "example.org", 21, "ftp://example.org"),
Arguments.of("ssh", "example.org", 2222, "ssh://example.org:2222"),
Arguments.of("ftp", "example.org", 2121, "ftp://example.org:2121"),
Arguments.of("file", "etc", -1, "file://etc")
); );
} }
@ParameterizedTest @ParameterizedTest
@MethodSource("appendSchemeHostPortCases") @MethodSource("schemeHostPortShortCases")
public void testAppendSchemeHostPortBuilder(String scheme, String server, int port, String expectedStr) public void testAppendSchemeHostPortBuilder(String scheme, String server, int port, String expectedStr)
{ {
StringBuilder actual = new StringBuilder(); StringBuilder actual = new StringBuilder();
@ -1217,11 +1236,211 @@ public class URIUtilTest
} }
@ParameterizedTest @ParameterizedTest
@MethodSource("appendSchemeHostPortCases") @MethodSource("schemeHostPortShortCases")
public void testAppendSchemeHostPortBuffer(String scheme, String server, int port, String expectedStr) public void testAppendSchemeHostPortBuffer(String scheme, String server, int port, String expectedStr)
{ {
StringBuffer actual = new StringBuffer(); StringBuffer actual = new StringBuffer();
URIUtil.appendSchemeHostPort(actual, scheme, server, port); URIUtil.appendSchemeHostPort(actual, scheme, server, port);
assertEquals(expectedStr, actual.toString()); assertEquals(expectedStr, actual.toString());
} }
public static List<Arguments> getNewURICases()
{
List<Arguments> cases = new ArrayList<>();
cases.addAll(List.of(
// Default behaviors of stripping a port number based on scheme
// Query specified
Arguments.of("http", "example.org", 0, "/", "a=b", null, "http://example.org/?a=b"),
Arguments.of("http", "example.org", 0, "/documentation/latest/", "a=b", null, "http://example.org/documentation/latest/?a=b"),
Arguments.of("http", "example.org", 0, null, "a=b", null, "http://example.org/?a=b"),
Arguments.of("http", "example.org", 0, null, "", null, "http://example.org/?")
));
return cases;
}
public static List<Arguments> schemeHostPortCases()
{
return List.of(
// Default behaviors of stripping a port number based on scheme
Arguments.of("http", "example.org", 80, "http://example.org"),
Arguments.of("https", "example.org", 443, "https://example.org"),
Arguments.of("ws", "example.org", 80, "ws://example.org"),
Arguments.of("wss", "example.org", 443, "wss://example.org"),
// Mismatches between scheme and port
Arguments.of("http", "example.org", 443, "http://example.org:443"),
Arguments.of("https", "example.org", 80, "https://example.org:80"),
Arguments.of("ws", "example.org", 443, "ws://example.org:443"),
Arguments.of("wss", "example.org", 80, "wss://example.org:80"),
// Odd ports
Arguments.of("http", "example.org", 12345, "http://example.org:12345"),
Arguments.of("https", "example.org", 54321, "https://example.org:54321"),
Arguments.of("ws", "example.org", 6666, "ws://example.org:6666"),
Arguments.of("wss", "example.org", 7777, "wss://example.org:7777"),
// Non-lowercase Schemes
Arguments.of("HTTP", "example.org", 8181, "http://example.org:8181"),
Arguments.of("hTTps", "example.org", 443, "https://example.org"),
Arguments.of("WS", "example.org", 8282, "ws://example.org:8282"),
Arguments.of("wsS", "example.org", 8383, "wss://example.org:8383"),
// Undefined Ports
Arguments.of("http", "example.org", 0, "http://example.org"),
Arguments.of("https", "example.org", -1, "https://example.org"),
Arguments.of("ws", "example.org", -80, "ws://example.org"),
Arguments.of("wss", "example.org", -2, "wss://example.org"),
// Unrecognized (non-http) schemes
Arguments.of("foo", "example.org", 0, "foo://example.org"),
Arguments.of("ssh", "example.org", 22, "ssh://example.org"),
Arguments.of("ftp", "example.org", 21, "ftp://example.org"),
Arguments.of("ssh", "example.org", 2222, "ssh://example.org:2222"),
Arguments.of("ftp", "example.org", 2121, "ftp://example.org:2121")
);
}
public static List<Arguments> schemeHostPortPathCases()
{
List<Arguments> cases = new ArrayList<>();
cases.addAll(List.of(
// Default behaviors of stripping a port number based on scheme
Arguments.of("http", "example.org", 80, "/", null, null, "http://example.org/"),
Arguments.of("https", "example.org", 443, "/", null, null, "https://example.org/"),
Arguments.of("ws", "example.org", 80, "/", null, null, "ws://example.org/"),
Arguments.of("wss", "example.org", 443, "/", null, null, "wss://example.org/"),
// Mismatches between scheme and port
Arguments.of("http", "example.org", 443, "/", null, null, "http://example.org:443/"),
Arguments.of("https", "example.org", 80, "/", null, null, "https://example.org:80/"),
Arguments.of("ws", "example.org", 443, "/", null, null, "ws://example.org:443/"),
Arguments.of("wss", "example.org", 80, "/", null, null, "wss://example.org:80/"),
// Odd ports
Arguments.of("http", "example.org", 12345, "/", null, null, "http://example.org:12345/"),
Arguments.of("https", "example.org", 54321, "/", null, null, "https://example.org:54321/"),
Arguments.of("ws", "example.org", 6666, "/", null, null, "ws://example.org:6666/"),
Arguments.of("wss", "example.org", 7777, "/", null, null, "wss://example.org:7777/"),
// Non-lowercase Schemes
Arguments.of("HTTP", "example.org", 8181, "/", null, null, "http://example.org:8181/"),
Arguments.of("hTTps", "example.org", 443, "/", null, null, "https://example.org/"),
Arguments.of("WS", "example.org", 8282, "/", null, null, "ws://example.org:8282/"),
Arguments.of("wsS", "example.org", 8383, "/", null, null, "wss://example.org:8383/"),
// Undefined Ports
Arguments.of("http", "example.org", 0, "/", null, null, "http://example.org/"),
Arguments.of("https", "example.org", -1, "/", null, null, "https://example.org/"),
Arguments.of("ws", "example.org", -80, "/", null, null, "ws://example.org/"),
Arguments.of("wss", "example.org", -2, "/", null, null, "wss://example.org/"),
// Unrecognized (non-http) schemes
Arguments.of("foo", "example.org", 0, "/", null, null, "foo://example.org/"),
Arguments.of("ssh", "example.org", 22, "/", null, null, "ssh://example.org/"),
Arguments.of("ftp", "example.org", 21, "/", null, null, "ftp://example.org/"),
Arguments.of("ssh", "example.org", 2222, "/", null, null, "ssh://example.org:2222/"),
Arguments.of("ftp", "example.org", 2121, "/", null, null, "ftp://example.org:2121/"),
// Path choices
Arguments.of("http", "example.org", 0, "/a/b/c/d", null, null, "http://example.org/a/b/c/d"),
Arguments.of("http", "example.org", 0, "/a%20b/c%20d", null, null, "http://example.org/a%20b/c%20d"),
// Query specified
Arguments.of("http", "example.org", 0, "/", "a=b", null, "http://example.org/?a=b"),
Arguments.of("http", "example.org", 0, "/documentation/latest/", "a=b", null, "http://example.org/documentation/latest/?a=b"),
Arguments.of("http", "example.org", 0, null, "a=b", null, "http://example.org/?a=b"),
Arguments.of("http", "example.org", 0, null, "", null, "http://example.org/?")
));
return cases;
}
public static List<Arguments> schemeHostPortFragmentCases()
{
List<Arguments> cases = new ArrayList<>();
cases.addAll(schemeHostPortPathCases());
cases.addAll(List.of(
// Fragment specified
Arguments.of("http", "example.org", 0, "/", null, "", "http://example.org/#"),
Arguments.of("http", "example.org", 0, "/", null, "toc", "http://example.org/#toc"),
Arguments.of("http", "example.org", 0, null, null, "toc", "http://example.org/#toc"),
// Empty query & fragment - behavior matches java URI and URL
Arguments.of("http", "example.org", 0, null, "", "", "http://example.org/?#")
));
return cases;
}
@ParameterizedTest
@MethodSource("schemeHostPortCases")
public void testNewURIShort(String scheme, String server, int port, String expectedStr)
{
String actual = URIUtil.newURI(scheme, server, port);
assertEquals(expectedStr, actual.toString());
}
@ParameterizedTest
@MethodSource("schemeHostPortPathCases")
public void testNewURI(String scheme, String server, int port, String path, String query, String fragment, String expectedStr)
{
assumeTrue(StringUtil.isBlank(fragment), "Skip tests with fragments, as this newURI doesn't have them");
String actual = URIUtil.newURI(scheme, server, port, path, query);
assertEquals(expectedStr, actual.toString());
}
@ParameterizedTest
@MethodSource("schemeHostPortFragmentCases")
public void testNewURIFragment(String scheme, String server, int port, String path, String query, String fragment, String expectedStr)
{
String actual = URIUtil.newURI(scheme, server, port, path, query, fragment);
assertEquals(expectedStr, actual.toString());
}
@ParameterizedTest
@CsvSource(value = {
"http,80",
"https,443",
"ws,80",
"wss,443",
"ssh,22",
"file,-1",
"bundle,-1",
"HTTP,80",
"HttPs,443",
"http+ssl,-1"
})
public void testGetDefaultPortForScheme(String scheme, int expectedPort)
{
int actual = URIUtil.getDefaultPortForScheme(scheme);
assertEquals(expectedPort, actual);
}
@ParameterizedTest
@CsvSource(value = {
"http,80,0",
"https,443,0",
"https,8443,8443",
"ws,80,0",
"ws,9999,9999",
"wss,443,0",
"wss,-1,0",
"wss,0,0",
"ssh,22,0",
"file,-1,0",
"bundle,-1,0",
"HTTP,80,0",
"HttPs,443,0",
"http+ssl,-1,0"
})
public void testNormalizePortForScheme(String scheme, int port, int expectedPort)
{
int actual = URIUtil.normalizePortForScheme(scheme, port);
assertEquals(expectedPort, actual);
}
@ParameterizedTest
@CsvSource(value = {
"http,http",
"https,https",
"HTTP,http",
"WSS,wss",
"WS,ws",
"XRTP,xrtp",
"Https,https"
})
public void testNormalizeScheme(String input, String expected)
{
String actual = URIUtil.normalizeScheme(input);
assertThat(actual, is(expected));
}
} }

View File

@ -26,6 +26,7 @@ import org.eclipse.jetty.logging.StacklessLogging;
import org.eclipse.jetty.util.BlockingArrayQueue; import org.eclipse.jetty.util.BlockingArrayQueue;
import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.URIUtil;
import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource; import org.junit.jupiter.params.provider.ValueSource;
@ -77,7 +78,7 @@ public class WebSocketCloseTest extends WebSocketTester
public void setup(State state, String scheme) throws Exception public void setup(State state, String scheme) throws Exception
{ {
boolean tls; boolean tls;
switch (scheme) switch (URIUtil.normalizeScheme(scheme))
{ {
case "ws": case "ws":
tls = false; tls = false;

View File

@ -102,17 +102,17 @@ public final class WSURI
public static URI toWebsocket(final URI inputUri) throws URISyntaxException public static URI toWebsocket(final URI inputUri) throws URISyntaxException
{ {
Objects.requireNonNull(inputUri, "Input URI must not be null"); Objects.requireNonNull(inputUri, "Input URI must not be null");
String httpScheme = inputUri.getScheme(); String scheme = inputUri.getScheme();
if (httpScheme == null) if (scheme == null)
throw new URISyntaxException(inputUri.toString(), "Undefined HTTP scheme"); throw new URISyntaxException(inputUri.toString(), "Undefined HTTP scheme");
if ("ws".equalsIgnoreCase(httpScheme) || "wss".equalsIgnoreCase(httpScheme)) if ("ws".equalsIgnoreCase(scheme) || "wss".equalsIgnoreCase(scheme))
return inputUri; return inputUri;
String afterScheme = inputUri.toString().substring(httpScheme.length()); String afterScheme = inputUri.toString().substring(scheme.length());
if ("http".equalsIgnoreCase(httpScheme)) if ("http".equalsIgnoreCase(scheme))
return new URI("ws" + afterScheme); return new URI("ws" + afterScheme);
if ("https".equalsIgnoreCase(httpScheme)) if ("https".equalsIgnoreCase(scheme))
return new URI("wss" + afterScheme); return new URI("wss" + afterScheme);
throw new URISyntaxException(inputUri.toString(), "Unrecognized HTTP scheme"); throw new URISyntaxException(inputUri.toString(), "Unrecognized HTTP scheme");

View File

@ -40,6 +40,7 @@ import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpScheme; import org.eclipse.jetty.http.HttpScheme;
import org.eclipse.jetty.io.ClientConnector; import org.eclipse.jetty.io.ClientConnector;
import org.eclipse.jetty.util.ProcessorUtils; import org.eclipse.jetty.util.ProcessorUtils;
import org.eclipse.jetty.util.URIUtil;
/** /**
* Specific implementation of {@link org.eclipse.jetty.ee10.proxy.AsyncProxyServlet.Transparent} for FastCGI. * Specific implementation of {@link org.eclipse.jetty.ee10.proxy.AsyncProxyServlet.Transparent} for FastCGI.
@ -195,7 +196,7 @@ public class FastCGIProxyServlet extends AsyncProxyServlet.Transparent
{ {
String server = request.getServerName(); String server = request.getServerName();
int port = request.getServerPort(); int port = request.getServerPort();
if (port != HttpScheme.getDefaultPort(request.getScheme())) if (port != URIUtil.getDefaultPortForScheme(request.getScheme()))
server += ":" + port; server += ":" + port;
String host = server; String host = server;
proxyRequest.headers(headers -> headers proxyRequest.headers(headers -> headers

View File

@ -65,7 +65,6 @@ import org.eclipse.jetty.http.HttpException;
import org.eclipse.jetty.http.HttpField; import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpScheme;
import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.http.HttpURI; import org.eclipse.jetty.http.HttpURI;
import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.http.HttpVersion;
@ -1017,7 +1016,7 @@ public class ServletApiRequest implements HttpServletRequest
// If no port specified, return the default port for the scheme // If no port specified, return the default port for the scheme
if (port <= 0) if (port <= 0)
return HttpScheme.getDefaultPort(getScheme()); return URIUtil.getDefaultPortForScheme(getScheme());
// return a specific port // return a specific port
return port; return port;

View File

@ -35,9 +35,7 @@ import jakarta.servlet.ServletContext;
import jakarta.servlet.ServletException; import jakarta.servlet.ServletException;
import jakarta.servlet.ServletOutputStream; import jakarta.servlet.ServletOutputStream;
import jakarta.servlet.ServletRequest; import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletRequestWrapper;
import jakarta.servlet.ServletResponse; import jakarta.servlet.ServletResponse;
import jakarta.servlet.ServletResponseWrapper;
import jakarta.servlet.WriteListener; import jakarta.servlet.WriteListener;
import jakarta.servlet.http.HttpServlet; import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletMapping; import jakarta.servlet.http.HttpServletMapping;
@ -1519,7 +1517,7 @@ public class DispatcherTest
assertEquals("/context/AssertForwardServlet", request.getRequestURI()); assertEquals("/context/AssertForwardServlet", request.getRequestURI());
assertEquals("/context", request.getContextPath()); assertEquals("/context", request.getContextPath());
assertEquals("/AssertForwardServlet", request.getServletPath()); assertEquals("/AssertForwardServlet", request.getServletPath());
assertEquals("http://local:80/context/AssertForwardServlet", request.getRequestURL().toString()); assertEquals("http://local/context/AssertForwardServlet", request.getRequestURL().toString());
response.setContentType("text/html"); response.setContentType("text/html");
response.setStatus(HttpServletResponse.SC_OK); response.setStatus(HttpServletResponse.SC_OK);

View File

@ -679,7 +679,7 @@ public class ErrorPageTest
assertThat(responseBody, Matchers.containsString("ERROR_SERVLET: " + failServlet.getClass().getName())); assertThat(responseBody, Matchers.containsString("ERROR_SERVLET: " + failServlet.getClass().getName()));
assertThat(responseBody, Matchers.containsString("ERROR_REQUEST_URI: /fail/599")); assertThat(responseBody, Matchers.containsString("ERROR_REQUEST_URI: /fail/599"));
assertThat(responseBody, Matchers.containsString("getQueryString()=[code=param]")); assertThat(responseBody, Matchers.containsString("getQueryString()=[code=param]"));
assertThat(responseBody, Matchers.containsString("getRequestURL()=[http://test:80/error/599?code=param]")); assertThat(responseBody, Matchers.containsString("getRequestURL()=[http://test/error/599?code=param]"));
assertThat(responseBody, Matchers.containsString("getParameterMap().size=2")); assertThat(responseBody, Matchers.containsString("getParameterMap().size=2"));
assertThat(responseBody, Matchers.containsString("getParameterMap()[code]=[param]")); assertThat(responseBody, Matchers.containsString("getParameterMap()[code]=[param]"));
assertThat(responseBody, Matchers.containsString("getParameterMap()[value]=[zed]")); assertThat(responseBody, Matchers.containsString("getParameterMap()[value]=[zed]"));

View File

@ -29,6 +29,7 @@ import org.eclipse.jetty.http.HttpScheme;
import org.eclipse.jetty.server.NetworkConnector; import org.eclipse.jetty.server.NetworkConnector;
import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils; import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
import org.eclipse.jetty.util.URIUtil;
import org.eclipse.jetty.util.resource.Resource; import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.util.resource.ResourceFactory; import org.eclipse.jetty.util.resource.ResourceFactory;
import org.eclipse.jetty.xml.XmlConfiguration; import org.eclipse.jetty.xml.XmlConfiguration;
@ -168,7 +169,7 @@ public class XmlBasedJettyServer
public void setScheme(String scheme) public void setScheme(String scheme)
{ {
this._scheme = scheme; this._scheme = URIUtil.normalizeScheme(scheme);
} }
public void start() throws Exception public void start() throws Exception
@ -196,11 +197,7 @@ public class XmlBasedJettyServer
public URI getServerURI() public URI getServerURI()
{ {
StringBuffer uri = new StringBuffer(); return URI.create(URIUtil.newURI(_scheme, InetAddress.getLoopbackAddress().getHostAddress(), _serverPort));
uri.append(this._scheme).append("://");
uri.append(InetAddress.getLoopbackAddress().getHostAddress());
uri.append(":").append(this._serverPort);
return URI.create(uri.toString());
} }
public Server getServer() public Server getServer()

View File

@ -40,6 +40,7 @@ import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpScheme; import org.eclipse.jetty.http.HttpScheme;
import org.eclipse.jetty.io.ClientConnector; import org.eclipse.jetty.io.ClientConnector;
import org.eclipse.jetty.util.ProcessorUtils; import org.eclipse.jetty.util.ProcessorUtils;
import org.eclipse.jetty.util.URIUtil;
/** /**
* Specific implementation of {@link org.eclipse.jetty.ee9.proxy.AsyncProxyServlet.Transparent} for FastCGI. * Specific implementation of {@link org.eclipse.jetty.ee9.proxy.AsyncProxyServlet.Transparent} for FastCGI.
@ -195,7 +196,8 @@ public class FastCGIProxyServlet extends AsyncProxyServlet.Transparent
{ {
String server = request.getServerName(); String server = request.getServerName();
int port = request.getServerPort(); int port = request.getServerPort();
if (port != HttpScheme.getDefaultPort(request.getScheme())) String scheme = request.getScheme();
if (port != URIUtil.getDefaultPortForScheme(scheme))
server += ":" + port; server += ":" + port;
String host = server; String host = server;
proxyRequest.headers(headers -> headers proxyRequest.headers(headers -> headers

View File

@ -1177,9 +1177,7 @@ public class Request implements HttpServletRequest
*/ */
public StringBuilder getRootURL() public StringBuilder getRootURL()
{ {
StringBuilder url = new StringBuilder(128); return new StringBuilder(HttpURI.from(getScheme(), getServerName(), getServerPort(), null).asString());
URIUtil.appendSchemeHostPort(url, getScheme(), getServerName(), getServerPort());
return url;
} }
@Override @Override

View File

@ -347,7 +347,7 @@ public class Response implements HttpServletResponse
path = (path == null ? "" : path); path = (path == null ? "" : path);
int port = uri.getPort(); int port = uri.getPort();
if (port < 0) if (port < 0)
port = HttpScheme.getDefaultPort(uri.getScheme()); port = URIUtil.getDefaultPortForScheme(uri.getScheme());
// Is it the same server? // Is it the same server?
if (!request.getServerName().equalsIgnoreCase(uri.getHost())) if (!request.getServerName().equalsIgnoreCase(uri.getHost()))

View File

@ -81,9 +81,9 @@ public class SecuredRedirectHandler extends HandlerWrapper
if (securePort > 0) if (securePort > 0)
{ {
String secureScheme = httpConfig.getSecureScheme(); String secureScheme = httpConfig.getSecureScheme();
String url = URIUtil.newURI(secureScheme, baseRequest.getServerName(), securePort, baseRequest.getRequestURI(), baseRequest.getQueryString()); String uri = URIUtil.newURI(secureScheme, baseRequest.getServerName(), securePort, baseRequest.getRequestURI(), baseRequest.getQueryString());
response.setContentLength(0); response.setContentLength(0);
baseRequest.getResponse().sendRedirect(_redirectCode, url, true); baseRequest.getResponse().sendRedirect(_redirectCode, uri, true);
} }
else else
{ {

View File

@ -292,8 +292,7 @@ public class OpenIdAuthenticator extends LoginAuthenticator
String redirectUri = null; String redirectUri = null;
if (_logoutRedirectPath != null) if (_logoutRedirectPath != null)
{ {
StringBuilder sb = new StringBuilder(128); StringBuilder sb = URIUtil.newURIBuilder(request.getScheme(), request.getServerName(), request.getServerPort());
URIUtil.appendSchemeHostPort(sb, request.getScheme(), request.getServerName(), request.getServerPort());
sb.append(baseRequest.getContextPath()); sb.append(baseRequest.getContextPath());
sb.append(_logoutRedirectPath); sb.append(_logoutRedirectPath);
redirectUri = sb.toString(); redirectUri = sb.toString();
@ -614,8 +613,7 @@ public class OpenIdAuthenticator extends LoginAuthenticator
private String getRedirectUri(HttpServletRequest request) private String getRedirectUri(HttpServletRequest request)
{ {
final StringBuffer redirectUri = new StringBuffer(128); final StringBuilder redirectUri = URIUtil.newURIBuilder(request.getScheme(),
URIUtil.appendSchemeHostPort(redirectUri, request.getScheme(),
request.getServerName(), request.getServerPort()); request.getServerName(), request.getServerPort());
redirectUri.append(request.getContextPath()); redirectUri.append(request.getContextPath());
redirectUri.append(_redirectPath); redirectUri.append(_redirectPath);

View File

@ -89,6 +89,7 @@ import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
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.StringUtil; import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.URIUtil;
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.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.AfterEach;
@ -841,7 +842,7 @@ public class ProxyServletTest
int serverPort = serverConnector.getLocalPort(); int serverPort = serverConnector.getLocalPort();
if (HttpScheme.HTTPS.is(scheme)) if (HttpScheme.HTTPS.is(scheme))
serverPort = tlsServerConnector.getLocalPort(); serverPort = tlsServerConnector.getLocalPort();
String proxyTo = scheme + "://localhost:" + serverPort; String proxyTo = URIUtil.normalizeScheme(scheme) + "://localhost:" + serverPort;
Map<String, String> params = new HashMap<>(); Map<String, String> params = new HashMap<>();
params.put("proxyTo", proxyTo); params.put("proxyTo", proxyTo);
params.put("prefix", prefix); params.put("prefix", prefix);

View File

@ -138,7 +138,7 @@ public class DataConstraintsTest
response = _connector.getResponse("GET /ctx/integral/info HTTP/1.0\r\n\r\n"); response = _connector.getResponse("GET /ctx/integral/info HTTP/1.0\r\n\r\n");
assertThat(response, Matchers.containsString("HTTP/1.1 302 Found")); assertThat(response, Matchers.containsString("HTTP/1.1 302 Found"));
assertThat(response, Matchers.containsString("Location: BWTP://")); assertThat(response, Matchers.containsString("Location: bwtp://"));
assertThat(response, Matchers.containsString(":9999")); assertThat(response, Matchers.containsString(":9999"));
response = _connectorS.getResponse("GET /ctx/integral/info HTTP/1.0\r\n\r\n"); response = _connectorS.getResponse("GET /ctx/integral/info HTTP/1.0\r\n\r\n");
@ -166,7 +166,7 @@ public class DataConstraintsTest
response = _connector.getResponse("GET /ctx/confid/info HTTP/1.0\r\n\r\n"); response = _connector.getResponse("GET /ctx/confid/info HTTP/1.0\r\n\r\n");
assertThat(response, Matchers.containsString("HTTP/1.1 302 Found")); assertThat(response, Matchers.containsString("HTTP/1.1 302 Found"));
assertThat(response, Matchers.containsString("Location: BWTP://")); assertThat(response, Matchers.containsString("Location: bwtp://"));
assertThat(response, Matchers.containsString(":9999")); assertThat(response, Matchers.containsString(":9999"));
response = _connectorS.getResponse("GET /ctx/confid/info HTTP/1.0\r\n\r\n"); response = _connectorS.getResponse("GET /ctx/confid/info HTTP/1.0\r\n\r\n");

View File

@ -13,7 +13,6 @@
package org.eclipse.jetty.ee9.test.support; package org.eclipse.jetty.ee9.test.support;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream; import java.io.OutputStream;
import java.net.InetAddress; import java.net.InetAddress;
@ -30,6 +29,7 @@ import org.eclipse.jetty.http.HttpScheme;
import org.eclipse.jetty.server.NetworkConnector; import org.eclipse.jetty.server.NetworkConnector;
import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils; import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
import org.eclipse.jetty.util.URIUtil;
import org.eclipse.jetty.util.resource.Resource; import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.util.resource.ResourceFactory; import org.eclipse.jetty.util.resource.ResourceFactory;
import org.eclipse.jetty.xml.XmlConfiguration; import org.eclipse.jetty.xml.XmlConfiguration;
@ -169,7 +169,7 @@ public class XmlBasedJettyServer
public void setScheme(String scheme) public void setScheme(String scheme)
{ {
this._scheme = scheme; this._scheme = URIUtil.normalizeScheme(scheme);
} }
public void start() throws Exception public void start() throws Exception