Skip to content

Commit 0c0ea6f

Browse files
authored
H2 with IPv6 URI fix helidon-io#9590 (helidon-io#9961)
* H2 with IPv6 URI fix helidon-io#9590 Signed-off-by: Daniel Kec <daniel.kec@oracle.com>
1 parent 8240354 commit 0c0ea6f

10 files changed

Lines changed: 469 additions & 28 deletions

File tree

common/socket/src/main/java/io/helidon/common/socket/PeerInfo.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2022 Oracle and/or its affiliates.
2+
* Copyright (c) 2022, 2025 Oracle and/or its affiliates.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.

http/http/src/main/java/io/helidon/http/RequestedUriDiscoveryContext.java

Lines changed: 53 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2023, 2024 Oracle and/or its affiliates.
2+
* Copyright (c) 2023, 2025 Oracle and/or its affiliates.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -15,6 +15,8 @@
1515
*/
1616
package io.helidon.http;
1717

18+
import java.net.InetSocketAddress;
19+
import java.net.SocketAddress;
1820
import java.util.ArrayList;
1921
import java.util.List;
2022

@@ -64,15 +66,40 @@ static RequestedUriDiscoveryContext create(Config config) {
6466
* {@link RequestedUriDiscoveryContext} and the specified request-related information.
6567
*
6668
* @param remoteAddress remote address from the request
67-
* @param localAddress local address from the request
68-
* @param requestPath path from the request
69-
* @param headers request headers
70-
* @param query query information from the request
71-
* @param isSecure whether the request is secure
69+
* @param localAddress local address from the request
70+
* @param requestPath path from the request
71+
* @param headers request headers
72+
* @param query query information from the request
73+
* @param isSecure whether the request is secure
7274
* @return {@code UriInfo} which reconstructs, as well as possible, the requested URI from the originating client
75+
* @deprecated Use
76+
* {@link RequestedUriDiscoveryContext#uriInfo(java.net.InetSocketAddress, java.net.InetSocketAddress, String,
77+
* ServerRequestHeaders, io.helidon.common.uri.UriQuery, boolean)}
7378
*/
74-
UriInfo uriInfo(String remoteAddress,
75-
String localAddress,
79+
@Deprecated(forRemoval = true, since = "4.2.1")
80+
default UriInfo uriInfo(String remoteAddress,
81+
String localAddress,
82+
String requestPath,
83+
ServerRequestHeaders headers,
84+
UriQuery query,
85+
boolean isSecure) {
86+
return UriInfoCompatibilityHelper.uriInfo(this, remoteAddress, localAddress, requestPath, headers, query, isSecure);
87+
}
88+
89+
/**
90+
* Creates a {@link io.helidon.common.uri.UriInfo} object for a request based on the discovery settings in the
91+
* {@link RequestedUriDiscoveryContext} and the specified request-related information.
92+
*
93+
* @param remoteAddress remote address from the request
94+
* @param localAddress local address from the request
95+
* @param requestPath path from the request
96+
* @param headers request headers
97+
* @param query query information from the request
98+
* @param isSecure whether the request is secure
99+
* @return {@code UriInfo} which reconstructs, as well as possible, the requested URI from the originating client
100+
*/
101+
UriInfo uriInfo(SocketAddress remoteAddress,
102+
SocketAddress localAddress,
76103
String requestPath,
77104
ServerRequestHeaders headers,
78105
UriQuery query,
@@ -275,8 +302,8 @@ private RequestedUriDiscoveryContextImpl(RequestedUriDiscoveryContext.Builder bu
275302
}
276303

277304
@Override
278-
public UriInfo uriInfo(String remoteAddress,
279-
String localAddress,
305+
public UriInfo uriInfo(SocketAddress remoteAddress,
306+
SocketAddress localAddress,
280307
String requestPath,
281308
ServerRequestHeaders headers,
282309
UriQuery query,
@@ -289,8 +316,12 @@ public UriInfo uriInfo(String remoteAddress,
289316

290317
// Note: enabled() returns true if discovery is explicitly enabled or if either
291318
// requestedUriDiscoveryTypes or trustedProxies is set.
292-
if (enabled) {
293-
if (trustedProxies.test(hostPart(remoteAddress))) {
319+
if (enabled && remoteAddress instanceof InetSocketAddress remoteInetAddress) {
320+
// Host with ip address fallback or ip address only
321+
if (trustedProxies.test(remoteInetAddress.getHostString())
322+
|| (remoteInetAddress.getAddress() != null
323+
&& trustedProxies.test(remoteInetAddress.getAddress().getHostAddress()))) {
324+
294325
// Once we discover trusted information using one of the discovery discoveryTypes, we do not mix in
295326
// information from other discoveryTypes.
296327

@@ -338,14 +369,22 @@ public UriInfo uriInfo(String remoteAddress,
338369
authority = headers.first(HeaderNames.HOST).orElse(null);
339370
}
340371

372+
if (host == null
373+
&& authority == null
374+
&& localAddress instanceof InetSocketAddress localInetAddress) {
375+
uriInfo.host(localInetAddress.getHostString()); // fallback
376+
}
377+
341378
uriInfo.path(path == null ? requestPath : path);
342-
uriInfo.host(localAddress); // this is the fallback
379+
343380
if (authority != null) {
344381
uriInfo.authority(authority); // this is the second possibility
345382
}
383+
346384
if (host != null) {
347385
uriInfo.host(host); // and this one has priority
348386
}
387+
349388
if (port != -1) {
350389
uriInfo.port(port);
351390
}
@@ -368,11 +407,6 @@ public UriInfo uriInfo(String remoteAddress,
368407
return uriInfo.build();
369408
}
370409

371-
private static String hostPart(String address) {
372-
int colon = address.indexOf(':');
373-
return colon == -1 ? address : address.substring(0, colon);
374-
}
375-
376410
private ForwardedDiscovery discoverUsingForwarded(ServerRequestHeaders headers) {
377411
String scheme = null;
378412
String authority = null;
@@ -416,7 +450,7 @@ private XForwardedDiscovery discoverUsingXForwarded(ServerRequestHeaders headers
416450

417451
List<String> xForwardedFors = headers.values(HeaderNames.X_FORWARDED_FOR);
418452
boolean areProxiesTrusted = true;
419-
if (xForwardedFors.size() > 0) {
453+
if (!xForwardedFors.isEmpty()) {
420454
// Intentionally skip the first X-Forwarded-For value. That is the originating client, and as such it
421455
// is not a proxy and we do not need to check its trustworthiness.
422456
for (int i = 1; i < xForwardedFors.size(); i++) {
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
/*
2+
* Copyright (c) 2025 Oracle and/or its affiliates.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.helidon.http;
18+
19+
import java.net.InetSocketAddress;
20+
import java.util.regex.Matcher;
21+
import java.util.regex.Pattern;
22+
23+
import io.helidon.common.uri.UriInfo;
24+
import io.helidon.common.uri.UriQuery;
25+
26+
/**
27+
* @deprecated Remove when
28+
* {@link RequestedUriDiscoveryContext#uriInfo(String, String, String, ServerRequestHeaders,
29+
* io.helidon.common.uri.UriQuery, boolean)} is removed.
30+
*/
31+
@Deprecated(forRemoval = true, since = "4.2.1")
32+
final class UriInfoCompatibilityHelper {
33+
private static final Pattern ADDRESS_PATTERN = Pattern.compile("([^/]*)/(\\[[^]]+]|[^:]+|<unresolved>):?([0-9]*)");
34+
35+
private UriInfoCompatibilityHelper() {
36+
//noop
37+
}
38+
39+
static UriInfo uriInfo(
40+
RequestedUriDiscoveryContext ctx,
41+
String remoteAddress,
42+
String localAddress,
43+
String requestPath,
44+
ServerRequestHeaders headers,
45+
UriQuery query,
46+
boolean isSecure) {
47+
48+
Matcher remoteMatcher = ADDRESS_PATTERN.matcher(remoteAddress);
49+
Matcher localMatcher = ADDRESS_PATTERN.matcher(localAddress);
50+
if (remoteMatcher.matches() && localMatcher.matches()) {
51+
String remotePort = remoteMatcher.group(3);
52+
String localPort = localMatcher.group(3);
53+
54+
return ctx.uriInfo(InetSocketAddress.createUnresolved(remoteMatcher.group(1),
55+
remotePort.isEmpty() ? 0 : Integer.parseInt(remotePort)),
56+
InetSocketAddress.createUnresolved(localMatcher.group(1),
57+
localPort.isEmpty() ? 0 : Integer.parseInt(remotePort)),
58+
requestPath, headers, query, isSecure);
59+
}
60+
61+
throw new IllegalArgumentException("Invalid remote: " + remoteAddress + " or local address: " + localAddress);
62+
}
63+
}
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
/*
2+
* Copyright (c) 2025 Oracle and/or its affiliates.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.helidon.http;
18+
19+
import java.net.Inet6Address;
20+
import java.net.InetAddress;
21+
import java.net.InetSocketAddress;
22+
import java.net.URI;
23+
import java.net.UnknownHostException;
24+
import java.util.stream.Stream;
25+
26+
import io.helidon.common.configurable.AllowList;
27+
import io.helidon.common.uri.UriQuery;
28+
29+
import org.junit.jupiter.api.Assumptions;
30+
import org.junit.jupiter.params.ParameterizedTest;
31+
import org.junit.jupiter.params.provider.Arguments;
32+
import org.junit.jupiter.params.provider.MethodSource;
33+
34+
import static org.junit.jupiter.api.Assertions.assertEquals;
35+
import static org.junit.jupiter.params.provider.Arguments.arguments;
36+
37+
public class RequestedUriDiscoveryContextTest {
38+
39+
private static Stream<Arguments> params() throws UnknownHostException {
40+
return Stream.of(
41+
arguments(new InetSocketAddress(InetAddress.getLoopbackAddress(), 8088),
42+
AllowList.builder()
43+
.addAllowed("localhost")
44+
.build(),
45+
"UriInfo{scheme=https,host=helidon.io,port=443,path=/path,query=,fragment=}"),
46+
47+
arguments(new InetSocketAddress(InetAddress.getLoopbackAddress(), 8088),
48+
AllowList.builder()
49+
.addAllowed("127.0.0.1")
50+
.build(),
51+
"UriInfo{scheme=https,host=helidon.io,port=443,path=/path,query=,fragment=}"),
52+
53+
arguments(new InetSocketAddress(Inet6Address.getByAddress("localhost",
54+
Inet6Address.getByName("::1")
55+
.getAddress()), 8088),
56+
AllowList.builder()
57+
.addAllowed("localhost")
58+
.build(),
59+
"UriInfo{scheme=https,host=helidon.io,port=443,path=/path,query=,fragment=}"),
60+
61+
arguments(new InetSocketAddress(Inet6Address.getByAddress("localhost",
62+
Inet6Address.getByName("::1")
63+
.getAddress()), 8088),
64+
AllowList.builder()
65+
.addAllowed("0:0:0:0:0:0:0:1")
66+
.build(),
67+
"UriInfo{scheme=https,host=helidon.io,port=443,path=/path,query=,fragment=}")
68+
);
69+
}
70+
71+
@ParameterizedTest
72+
@MethodSource("params")
73+
void inetAddress(InetSocketAddress remotePeerAddress, AllowList allowList, String expected) throws UnknownHostException {
74+
var uriInfo = RequestedUriDiscoveryContext.builder()
75+
.enabled(true)
76+
.trustedProxies(allowList)
77+
.build()
78+
.uriInfo(remotePeerAddress,
79+
remotePeerAddress,
80+
"/path",
81+
ServerRequestHeaders.create(WritableHeaders.create()
82+
.add(HeaderNames.FORWARDED,
83+
"by=a.b.c;for=d.e.f;host=helidon.io;proto=https")),
84+
UriQuery.create(URI.create("http://localhost:8088/path")),
85+
true);
86+
87+
assertEquals(expected, uriInfo.toString());
88+
}
89+
90+
@ParameterizedTest
91+
@MethodSource("params")
92+
@Deprecated(forRemoval = true, since = "4.2.1")
93+
void stringAddress(InetSocketAddress remotePeerAddress, AllowList allowList, String expected) throws UnknownHostException {
94+
if (!allowList.prototype().allowed().getFirst().equals("localhost")) {
95+
Assumptions.abort("IP address filtering is not supported by deprecated api.");
96+
}
97+
98+
var uriInfo = RequestedUriDiscoveryContext.builder()
99+
.enabled(true)
100+
.trustedProxies(allowList)
101+
.build()
102+
.uriInfo(remotePeerAddress.toString(),
103+
remotePeerAddress.toString(),
104+
"/path",
105+
ServerRequestHeaders.create(WritableHeaders.create()
106+
.add(HeaderNames.FORWARDED,
107+
"by=a.b.c;for=d.e.f;host=helidon.io;proto=https")),
108+
UriQuery.create(URI.create("http://localhost:8088/path")),
109+
true);
110+
111+
assertEquals(expected, uriInfo.toString());
112+
}
113+
}

webserver/http2/src/main/java/io/helidon/webserver/http2/Http2ServerRequest.java

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2022, 2024 Oracle and/or its affiliates.
2+
* Copyright (c) 2022, 2025 Oracle and/or its affiliates.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -52,7 +52,6 @@ class Http2ServerRequest implements RoutingRequest {
5252
RequestedUriDiscoveryContext.builder()
5353
.build();
5454

55-
5655
private static final Runnable NO_OP_RUNNABLE = () -> {
5756
};
5857
private final Http2Headers http2Headers;
@@ -236,8 +235,8 @@ public Optional<ProxyProtocolData> proxyProtocolData() {
236235
private UriInfo createUriInfo() {
237236
return ctx.listenerContext().config().requestedUriDiscoveryContext()
238237
.orElse(DEFAULT_REQUESTED_URI_DISCOVERY_CONTEXT)
239-
.uriInfo(remotePeer().address().toString(),
240-
localPeer().address().toString(),
238+
.uriInfo(remotePeer().address(),
239+
localPeer().address(),
241240
path.absolute().path(),
242241
headers,
243242
query(),

0 commit comments

Comments
 (0)