From 69f1520108fa19153471ada4721aeec3514bc258 Mon Sep 17 00:00:00 2001 From: Burak KALAYCI Date: Tue, 25 Aug 2026 14:49:10 +0300 Subject: [PATCH] Accept RFC 1123 hostnames in EndpointUtil.validateEndpoint URI.getHost() returns null for some valid DNS names (JDK-8188305), such as a label that starts with a digit. Fall back to URL.getHost() so those endpoints are accepted, matching OtlpConfigUtil. Fixes #8745 Signed-off-by: Burak KALAYCI --- .../exporter/internal/EndpointUtil.java | 21 ++++++++++++++++++- .../exporter/internal/EndpointUtilTest.java | 11 +++++++++- 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/exporters/common/src/main/java/io/opentelemetry/exporter/internal/EndpointUtil.java b/exporters/common/src/main/java/io/opentelemetry/exporter/internal/EndpointUtil.java index 5f634ee9e15..a9c9ca00a37 100644 --- a/exporters/common/src/main/java/io/opentelemetry/exporter/internal/EndpointUtil.java +++ b/exporters/common/src/main/java/io/opentelemetry/exporter/internal/EndpointUtil.java @@ -5,8 +5,10 @@ package io.opentelemetry.exporter.internal; +import java.net.MalformedURLException; import java.net.URI; import java.net.URISyntaxException; +import java.net.URL; /** * Utilities for validating exporter endpoints. @@ -30,12 +32,29 @@ public static URI validateEndpoint(String endpoint) { throw new IllegalArgumentException( "Invalid endpoint, must start with http:// or https://: " + uri); } - if (uri.getHost() == null) { + if (!hasHost(uri, endpoint)) { throw new IllegalArgumentException( "Invalid endpoint, must start with http:// or https://: " + uri); } return uri; } + /** + * {@link URI#getHost()} follows RFC 2396 and returns {@code null} for some valid DNS names + * (JDK-8188305), for example a label that starts with a digit. {@link URL#getHost()} accepts + * those names, matching {@code OtlpConfigUtil.validateEndpoint}. + */ + private static boolean hasHost(URI uri, String endpoint) { + if (uri.getHost() != null) { + return true; + } + try { + String host = new URL(endpoint).getHost(); + return host != null && !host.isEmpty(); + } catch (MalformedURLException e) { + return false; + } + } + private EndpointUtil() {} } diff --git a/exporters/common/src/test/java/io/opentelemetry/exporter/internal/EndpointUtilTest.java b/exporters/common/src/test/java/io/opentelemetry/exporter/internal/EndpointUtilTest.java index 1c81213c416..8a28ff244d5 100644 --- a/exporters/common/src/test/java/io/opentelemetry/exporter/internal/EndpointUtilTest.java +++ b/exporters/common/src/test/java/io/opentelemetry/exporter/internal/EndpointUtilTest.java @@ -27,7 +27,15 @@ private static Stream validEndpoints() { Arguments.argumentSet("http", "http://localhost:4318"), Arguments.argumentSet("https", "https://localhost:4317"), Arguments.argumentSet("path", "http://localhost:4318/v1/traces"), - Arguments.argumentSet("userinfo", "http://foo:bar@localhost:4317/path")); + Arguments.argumentSet("userinfo", "http://foo:bar@localhost:4317/path"), + // URI.getHost() is null for these (JDK-8188305); they are valid DNS names. + Arguments.argumentSet( + "dns label starting with digit", "http://otlp.1234-k8s-namespace:4318"), + Arguments.argumentSet( + "dns label starting with digit, path", + "http://otlp-collector.14014-mosaik:4318/v1/metrics"), + Arguments.argumentSet( + "dns label starting with digit, userinfo", "http://foo:bar@otlp.1234-ns:4317/path")); } @ParameterizedTest @@ -42,6 +50,7 @@ private static Stream invalidEndpoints() { return Stream.of( Arguments.argumentSet("opaque, no host", "http:localhost:4317"), Arguments.argumentSet("single slash, no host", "https:/foo"), + Arguments.argumentSet("empty host with port", "http://:4318"), Arguments.argumentSet("no scheme", "localhost"), Arguments.argumentSet("wrong scheme", "gopher://localhost")); }