diff --git a/xds/src/main/java/io/grpc/xds/XdsNameResolver.java b/xds/src/main/java/io/grpc/xds/XdsNameResolver.java index 29043b177d9..079f862aeca 100644 --- a/xds/src/main/java/io/grpc/xds/XdsNameResolver.java +++ b/xds/src/main/java/io/grpc/xds/XdsNameResolver.java @@ -105,6 +105,9 @@ final class XdsNameResolver extends NameResolver { @Nullable private final String targetAuthority; private final String serviceAuthority; + // Encoded version of the service authority as per + // https://datatracker.ietf.org/doc/html/rfc3986#section-3.2. + private final String encodedServiceAuthority; private final String overrideAuthority; private final ServiceConfigParser serviceConfigParser; private final SynchronizationContext syncContext; @@ -149,8 +152,9 @@ final class XdsNameResolver extends NameResolver { this.targetAuthority = targetAuthority; // The name might have multiple slashes so encode it before verifying. - String authority = GrpcUtil.AuthorityEscaper.encodeAuthority(checkNotNull(name, "name")); - serviceAuthority = GrpcUtil.checkAuthority(authority); + serviceAuthority = checkNotNull(name, "name"); + this.encodedServiceAuthority = + GrpcUtil.checkAuthority(GrpcUtil.AuthorityEscaper.encodeAuthority(serviceAuthority)); this.overrideAuthority = overrideAuthority; this.serviceConfigParser = checkNotNull(serviceConfigParser, "serviceConfigParser"); @@ -169,7 +173,7 @@ final class XdsNameResolver extends NameResolver { @Override public String getServiceAuthority() { - return serviceAuthority; + return encodedServiceAuthority; } @Override diff --git a/xds/src/test/java/io/grpc/xds/XdsNameResolverTest.java b/xds/src/test/java/io/grpc/xds/XdsNameResolverTest.java index 4b858c5716e..9865b60172a 100644 --- a/xds/src/test/java/io/grpc/xds/XdsNameResolverTest.java +++ b/xds/src/test/java/io/grpc/xds/XdsNameResolverTest.java @@ -265,6 +265,31 @@ public void resolving_noTargetAuthority_templateWithXdstp() { verify(mockListener, never()).onError(any(Status.class)); } + @Test + public void resolving_noTargetAuthority_xdstpWithMultipleSlashes() { + bootstrapInfo = BootstrapInfo.builder() + .servers(ImmutableList.of(ServerInfo.create( + "td.googleapis.com", InsecureChannelCredentials.create()))) + .node(Node.newBuilder().build()) + .clientDefaultListenerResourceNameTemplate( + "xdstp://xds.authority.com/envoy.config.listener.v3.Listener/%s?id=1") + .build(); + String serviceAuthority = "path/to/service"; + expectedLdsResourceName = + "xdstp://xds.authority.com/envoy.config.listener.v3.Listener/" + + "path/to/service?id=1"; + resolver = new XdsNameResolver( + null, serviceAuthority, null, serviceConfigParser, syncContext, scheduler, + xdsClientPoolFactory, mockRandom, FilterRegistry.getDefaultRegistry(), null); + + + // The Service Authority must be URL encoded, but unlike the LDS resource name. + assertThat(resolver.getServiceAuthority()).isEqualTo("path%2Fto%2Fservice"); + + resolver.start(mockListener); + verify(mockListener, never()).onError(any(Status.class)); + } + @Test public void resolving_targetAuthorityInAuthoritiesMap() { String targetAuthority = "xds.authority.com";