Skip to content

Commit 1be29bd

Browse files
committed
8361060: Keep track of the origin server against which a jdk.internal.net.http.HttpConnection was constructed
Reviewed-by: dfuchs
1 parent 2f683fd commit 1be29bd

File tree

11 files changed

+416
-89
lines changed

11 files changed

+416
-89
lines changed

src/java.net.http/share/classes/jdk/internal/net/http/AbstractAsyncSSLConnection.java

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@
4141
import jdk.internal.net.http.common.SSLTube;
4242
import jdk.internal.net.http.common.Log;
4343
import jdk.internal.net.http.common.Utils;
44-
import static jdk.internal.net.http.common.Utils.ServerName;
44+
import sun.net.util.IPAddressUtil;
4545

4646
/**
4747
* Asynchronous version of SSLConnection.
@@ -63,6 +63,10 @@
6363
*/
6464
abstract class AbstractAsyncSSLConnection extends HttpConnection
6565
{
66+
67+
private record ServerName(String name, boolean isLiteral) {
68+
}
69+
6670
protected final SSLEngine engine;
6771
protected final SSLParameters sslParameters;
6872
private final List<SNIServerName> sniServerNames;
@@ -71,17 +75,19 @@ abstract class AbstractAsyncSSLConnection extends HttpConnection
7175
private static final boolean disableHostnameVerification
7276
= Utils.isHostnameVerificationDisabled();
7377

74-
AbstractAsyncSSLConnection(InetSocketAddress addr,
78+
AbstractAsyncSSLConnection(Origin originServer,
79+
InetSocketAddress addr,
7580
HttpClientImpl client,
76-
ServerName serverName, int port,
7781
String[] alpn,
7882
String label) {
79-
super(addr, client, label);
83+
super(originServer, addr, client, label);
84+
assert originServer != null : "origin server is null";
85+
final ServerName serverName = getServerName(originServer);
8086
this.sniServerNames = formSNIServerNames(serverName, client);
8187
SSLContext context = client.theSSLContext();
8288
sslParameters = createSSLParameters(client, this.sniServerNames, alpn);
8389
Log.logParams(sslParameters);
84-
engine = createEngine(context, serverName.name(), port, sslParameters);
90+
engine = createEngine(context, serverName.name(), originServer.port(), sslParameters);
8591
}
8692

8793
abstract SSLTube getConnectionFlow();
@@ -187,6 +193,23 @@ private static SSLEngine createEngine(SSLContext context, String peerHost, int p
187193
return engine;
188194
}
189195

196+
/**
197+
* Analyse the given {@linkplain Origin origin server} and determine
198+
* if the origin server's host is a literal or not, returning the server's
199+
* address in String form.
200+
*/
201+
private static ServerName getServerName(final Origin originServer) {
202+
final String host = originServer.host();
203+
byte[] literal = IPAddressUtil.textToNumericFormatV4(host);
204+
if (literal == null) {
205+
// not IPv4 literal. Check IPv6
206+
literal = IPAddressUtil.textToNumericFormatV6(host);
207+
return new ServerName(host, literal != null);
208+
} else {
209+
return new ServerName(host, true);
210+
}
211+
}
212+
190213
@Override
191214
final boolean isSecure() {
192215
return true;

src/java.net.http/share/classes/jdk/internal/net/http/AsyncSSLConnection.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,12 +42,13 @@ class AsyncSSLConnection extends AbstractAsyncSSLConnection {
4242
final PlainHttpPublisher writePublisher;
4343
private volatile SSLTube flow;
4444

45-
AsyncSSLConnection(InetSocketAddress addr,
45+
AsyncSSLConnection(Origin originServer,
46+
InetSocketAddress addr,
4647
HttpClientImpl client,
4748
String[] alpn,
4849
String label) {
49-
super(addr, client, Utils.getServerName(addr), addr.getPort(), alpn, label);
50-
plainConnection = new PlainHttpConnection(addr, client, label);
50+
super(originServer, addr, client, alpn, label);
51+
plainConnection = new PlainHttpConnection(originServer, addr, client, label);
5152
writePublisher = new PlainHttpPublisher();
5253
}
5354

src/java.net.http/share/classes/jdk/internal/net/http/AsyncSSLTunnelConnection.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,15 +43,17 @@ class AsyncSSLTunnelConnection extends AbstractAsyncSSLConnection {
4343
final PlainHttpPublisher writePublisher;
4444
volatile SSLTube flow;
4545

46-
AsyncSSLTunnelConnection(InetSocketAddress addr,
46+
AsyncSSLTunnelConnection(Origin originServer,
47+
InetSocketAddress addr,
4748
HttpClientImpl client,
4849
String[] alpn,
4950
InetSocketAddress proxy,
5051
ProxyHeaders proxyHeaders,
5152
String label)
5253
{
53-
super(addr, client, Utils.getServerName(addr), addr.getPort(), alpn, label);
54-
this.plainConnection = new PlainTunnelingConnection(addr, proxy, client, proxyHeaders, label);
54+
super(originServer, addr, client, alpn, label);
55+
this.plainConnection = new PlainTunnelingConnection(originServer, addr, proxy, client,
56+
proxyHeaders, label);
5557
this.writePublisher = new PlainHttpPublisher();
5658
}
5759

src/java.net.http/share/classes/jdk/internal/net/http/HttpConnection.java

Lines changed: 33 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,11 @@ abstract class HttpConnection implements Closeable {
100100
*/
101101
private final String label;
102102

103-
HttpConnection(InetSocketAddress address, HttpClientImpl client, String label) {
103+
private final Origin originServer;
104+
105+
HttpConnection(Origin originServer, InetSocketAddress address, HttpClientImpl client,
106+
String label) {
107+
this.originServer = originServer;
104108
this.address = address;
105109
this.client = client;
106110
trailingOperations = new TrailingOperations();
@@ -244,6 +248,14 @@ final boolean checkOpen() {
244248
return false;
245249
}
246250

251+
/**
252+
* {@return the {@link Origin} server against which this connection communicates.
253+
* Returns {@code null} if the connection is a plain connection to a proxy}
254+
*/
255+
final Origin getOriginServer() {
256+
return this.originServer;
257+
}
258+
247259
interface HttpPublisher extends FlowTube.TubePublisher {
248260
void enqueue(List<ByteBuffer> buffers) throws IOException;
249261
void enqueueUnordered(List<ByteBuffer> buffers) throws IOException;
@@ -334,13 +346,20 @@ private static HttpConnection getSSLConnection(InetSocketAddress addr,
334346
String[] alpn,
335347
HttpRequestImpl request,
336348
HttpClientImpl client) {
337-
String label = nextLabel();
349+
final String label = nextLabel();
350+
final Origin originServer;
351+
try {
352+
originServer = Origin.from(request.uri());
353+
} catch (IllegalArgumentException iae) {
354+
// should never happen
355+
throw new AssertionError("failed to determine origin server from request URI", iae);
356+
}
338357
if (proxy != null)
339-
return new AsyncSSLTunnelConnection(addr, client, alpn, proxy,
358+
return new AsyncSSLTunnelConnection(originServer, addr, client, alpn, proxy,
340359
proxyTunnelHeaders(request),
341360
label);
342361
else
343-
return new AsyncSSLConnection(addr, client, alpn, label);
362+
return new AsyncSSLConnection(originServer, addr, client, alpn, label);
344363
}
345364

346365
/**
@@ -414,14 +433,21 @@ private static HttpConnection getPlainConnection(InetSocketAddress addr,
414433
InetSocketAddress proxy,
415434
HttpRequestImpl request,
416435
HttpClientImpl client) {
417-
String label = nextLabel();
436+
final String label = nextLabel();
437+
final Origin originServer;
438+
try {
439+
originServer = Origin.from(request.uri());
440+
} catch (IllegalArgumentException iae) {
441+
// should never happen
442+
throw new AssertionError("failed to determine origin server from request URI", iae);
443+
}
418444
if (request.isWebSocket() && proxy != null)
419-
return new PlainTunnelingConnection(addr, proxy, client,
445+
return new PlainTunnelingConnection(originServer, addr, proxy, client,
420446
proxyTunnelHeaders(request),
421447
label);
422448

423449
if (proxy == null)
424-
return new PlainHttpConnection(addr, client, label);
450+
return new PlainHttpConnection(originServer, addr, client, label);
425451
else
426452
return new PlainProxyConnection(proxy, client, label);
427453
}
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
/*
2+
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* This code is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU General Public License version 2 only, as
7+
* published by the Free Software Foundation. Oracle designates this
8+
* particular file as subject to the "Classpath" exception as provided
9+
* by Oracle in the LICENSE file that accompanied this code.
10+
*
11+
* This code is distributed in the hope that it will be useful, but WITHOUT
12+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14+
* version 2 for more details (a copy is included in the LICENSE file that
15+
* accompanied this code).
16+
*
17+
* You should have received a copy of the GNU General Public License version
18+
* 2 along with this work; if not, write to the Free Software Foundation,
19+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20+
*
21+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22+
* or visit www.oracle.com if you need additional information or have any
23+
* questions.
24+
*/
25+
26+
package jdk.internal.net.http;
27+
28+
import java.net.URI;
29+
import java.util.Locale;
30+
import java.util.Objects;
31+
32+
import sun.net.util.IPAddressUtil;
33+
34+
/**
35+
* Represents an origin server to which a HTTP request is targeted.
36+
*
37+
* @param scheme The scheme of the origin (for example: https). Unlike the application layer
38+
* protocol (which can be a finer grained protocol like h2, h3 etc...),
39+
* this is actually a scheme. Only {@code http} and {@code https} literals are
40+
* supported. Cannot be null.
41+
* @param host The host of the origin, cannot be null. If the host is an IPv6 address,
42+
* then it must not be enclosed in square brackets ({@code '['} and {@code ']'}).
43+
* If the host is a DNS hostname, then it must be passed as a lower case String.
44+
* @param port The port of the origin. Must be greater than 0.
45+
*/
46+
public record Origin(String scheme, String host, int port) {
47+
public Origin {
48+
Objects.requireNonNull(scheme);
49+
Objects.requireNonNull(host);
50+
if (!isValidScheme(scheme)) {
51+
throw new IllegalArgumentException("Unsupported scheme: " + scheme);
52+
}
53+
if (host.startsWith("[") && host.endsWith("]")) {
54+
throw new IllegalArgumentException("Invalid host: " + host);
55+
}
56+
// expect DNS hostname to be passed as lower case
57+
if (isDNSHostName(host) && !host.toLowerCase(Locale.ROOT).equals(host)) {
58+
throw new IllegalArgumentException("non-lowercase hostname: " + host);
59+
}
60+
if (port <= 0) {
61+
throw new IllegalArgumentException("Invalid port: " + port);
62+
}
63+
}
64+
65+
@Override
66+
public String toString() {
67+
return scheme + "://" + toAuthority(host, port);
68+
}
69+
70+
/**
71+
* {@return Creates and returns an Origin from an URI}
72+
*
73+
* @param uri The URI of the origin
74+
* @throws IllegalArgumentException if a Origin cannot be constructed from
75+
* the given {@code uri}
76+
*/
77+
public static Origin from(final URI uri) throws IllegalArgumentException {
78+
Objects.requireNonNull(uri);
79+
final String scheme = uri.getScheme();
80+
if (scheme == null) {
81+
throw new IllegalArgumentException("missing scheme in URI");
82+
}
83+
final String lcaseScheme = scheme.toLowerCase(Locale.ROOT);
84+
if (!isValidScheme(lcaseScheme)) {
85+
throw new IllegalArgumentException("Unsupported scheme: " + scheme);
86+
}
87+
final String host = uri.getHost();
88+
if (host == null) {
89+
throw new IllegalArgumentException("missing host in URI");
90+
}
91+
String effectiveHost;
92+
if (host.startsWith("[") && host.endsWith("]")) {
93+
// strip the square brackets from IPv6 host
94+
effectiveHost = host.substring(1, host.length() - 1);
95+
} else {
96+
effectiveHost = host;
97+
}
98+
assert !effectiveHost.isEmpty() : "unexpected URI host: " + host;
99+
// If the host is a DNS hostname, then convert the host to lower case.
100+
// The DNS hostname is expected to be ASCII characters and is case-insensitive.
101+
//
102+
// Its usage in areas like SNI too match this expectation - RFC-6066, section 3:
103+
// "HostName" contains the fully qualified DNS hostname of the server,
104+
// as understood by the client. The hostname is represented as a byte
105+
// string using ASCII encoding without a trailing dot. ... DNS hostnames
106+
// are case-insensitive.
107+
if (isDNSHostName(effectiveHost)) {
108+
effectiveHost = effectiveHost.toLowerCase(Locale.ROOT);
109+
}
110+
int port = uri.getPort();
111+
if (port == -1) {
112+
port = switch (lcaseScheme) {
113+
case "http" -> 80;
114+
case "https" -> 443;
115+
// we have already verified that this is a valid scheme, so this
116+
// should never happen
117+
default -> throw new AssertionError("Unsupported scheme: " + scheme);
118+
};
119+
}
120+
return new Origin(lcaseScheme, effectiveHost, port);
121+
}
122+
123+
static String toAuthority(final String host, final int port) {
124+
assert port > 0 : "invalid port: " + port;
125+
// borrowed from code in java.net.URI
126+
final boolean needBrackets = host.indexOf(':') >= 0
127+
&& !host.startsWith("[")
128+
&& !host.endsWith("]");
129+
if (needBrackets) {
130+
return "[" + host + "]:" + port;
131+
}
132+
return host + ":" + port;
133+
}
134+
135+
private static boolean isValidScheme(final String scheme) {
136+
// only "http" and "https" literals allowed
137+
return "http".equals(scheme) || "https".equals(scheme);
138+
}
139+
140+
private static boolean isDNSHostName(final String host) {
141+
final boolean isLiteral = IPAddressUtil.isIPv4LiteralAddress(host)
142+
|| IPAddressUtil.isIPv6LiteralAddress(host);
143+
144+
return !isLiteral;
145+
}
146+
}

src/java.net.http/share/classes/jdk/internal/net/http/PlainHttpConnection.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -310,8 +310,9 @@ final FlowTube getConnectionFlow() {
310310
return tube;
311311
}
312312

313-
PlainHttpConnection(InetSocketAddress addr, HttpClientImpl client, String label) {
314-
super(addr, client, label);
313+
PlainHttpConnection(Origin originServer, InetSocketAddress addr, HttpClientImpl client,
314+
String label) {
315+
super(originServer, addr, client, label);
315316
try {
316317
this.chan = SocketChannel.open();
317318
chan.configureBlocking(false);

src/java.net.http/share/classes/jdk/internal/net/http/PlainProxyConnection.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,9 @@
3030
class PlainProxyConnection extends PlainHttpConnection {
3131

3232
PlainProxyConnection(InetSocketAddress proxy, HttpClientImpl client, String label) {
33-
super(proxy, client, label);
33+
// we don't track the origin server for a plain proxy connection, since it
34+
// can be used to serve requests against several different origin servers.
35+
super(null, proxy, client, label);
3436
}
3537

3638
@Override

src/java.net.http/share/classes/jdk/internal/net/http/PlainTunnelingConnection.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,15 +51,16 @@ final class PlainTunnelingConnection extends HttpConnection {
5151
final InetSocketAddress proxyAddr;
5252
private volatile boolean connected;
5353

54-
protected PlainTunnelingConnection(InetSocketAddress addr,
54+
protected PlainTunnelingConnection(Origin originServer,
55+
InetSocketAddress addr,
5556
InetSocketAddress proxy,
5657
HttpClientImpl client,
5758
ProxyHeaders proxyHeaders,
5859
String label) {
59-
super(addr, client, label);
60+
super(originServer, addr, client, label);
6061
this.proxyAddr = proxy;
6162
this.proxyHeaders = proxyHeaders;
62-
delegate = new PlainHttpConnection(proxy, client, label);
63+
delegate = new PlainHttpConnection(originServer, proxy, client, label);
6364
}
6465

6566
@Override

0 commit comments

Comments
 (0)