Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Diagnostics.CodeAnalysis;
using System.Net;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.Extensions.ServiceDiscovery.Internal;
Expand Down Expand Up @@ -34,4 +35,52 @@ public static ServiceEndpoint Create(EndPoint endPoint, IFeatureCollection? feat

return new ServiceEndpointImpl(endPoint, features);
}

/// <summary>
/// Tries to convert a specified string representation to its <see cref="ServiceEndpoint"/> equivalent,
/// and returns a value that indicates whether the conversion succeeded.
/// </summary>
/// <param name="value">A string that consists of an IP address or hostname, optionally followed by a colon and port number, or a URI.</param>
/// <param name="serviceEndpoint">When this method returns, contains the equivalent <see cref="ServiceEndpoint"/> if the conversion succeeded; otherwise,
/// <see langword="null"/>. This parameter is passed uninitialized; any value originally supplied will be overwritten.</param>
/// <returns><see langword="true"/> if the string was successfully parsed into a <see cref="ServiceEndpoint"/>; otherwise, <see langword="false"/>.</returns>
public static bool TryParse([NotNullWhen(true)] string? value,
[NotNullWhen(true)] out ServiceEndpoint? serviceEndpoint)
{
EndPoint? endPoint = TryParseEndPoint(value);

if (endPoint != null)
{
serviceEndpoint = Create(endPoint);
return true;
}
else
{
serviceEndpoint = null;
return false;
}
}

private static EndPoint? TryParseEndPoint(string? value)
{
if (!string.IsNullOrWhiteSpace(value))
{
#pragma warning disable CS8602
if (value.IndexOf("://", StringComparison.Ordinal) < 0 && Uri.TryCreate($"fakescheme://{value}", default, out var uri))
#pragma warning restore CS8602
{
var port = uri.Port > 0 ? uri.Port : 0;
return IPAddress.TryParse(uri.Host, out var ip)
? new IPEndPoint(ip, port)
: new DnsEndPoint(uri.Host, port);
}

if (Uri.TryCreate(value, default, out uri))
{
return new UriEndPoint(uri);
}
}

return null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,22 @@ namespace Microsoft.Extensions.ServiceDiscovery;
/// <summary>
/// An endpoint represented by a <see cref="System.Uri"/>.
/// </summary>
/// <param name="uri">The <see cref="System.Uri"/>.</param>
internal sealed class UriEndPoint(Uri uri) : EndPoint
public class UriEndPoint : EndPoint
{
/// <summary>
/// Creates a new <see cref="UriEndPoint"/>.
/// </summary>
/// <param name="uri">The <see cref="System.Uri"/>.</param>
public UriEndPoint(Uri uri)
{
ArgumentNullException.ThrowIfNull(uri);
Uri = uri;
}

/// <summary>
/// Gets the <see cref="System.Uri"/> associated with this endpoint.
/// </summary>
public Uri Uri => uri;
public Uri Uri { get; }

/// <inheritdoc/>
public override bool Equals(object? obj)
Expand All @@ -26,5 +35,5 @@ public override bool Equals(object? obj)
public override int GetHashCode() => Uri.GetHashCode();

/// <inheritdoc/>
public override string? ToString() => uri.ToString();
public override string? ToString() => Uri.ToString();
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Diagnostics.CodeAnalysis;
using System.Net;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
Expand Down Expand Up @@ -41,7 +39,8 @@ public ConfigurationServiceEndpointProvider(
_serviceName = query.ServiceName;
_endpointName = query.EndpointName;
_includeAllSchemes = serviceDiscoveryOptions.Value.AllowAllSchemes && query.IncludedSchemes.Count == 0;
_schemes = ServiceDiscoveryOptions.ApplyAllowedSchemes(query.IncludedSchemes, serviceDiscoveryOptions.Value.AllowedSchemes, serviceDiscoveryOptions.Value.AllowAllSchemes);
var allowedSchemes = serviceDiscoveryOptions.Value.ApplyAllowedSchemes(query.IncludedSchemes);
_schemes = allowedSchemes as string[] ?? allowedSchemes.ToArray();
_configuration = configuration;
_logger = logger;
_options = options;
Expand Down Expand Up @@ -190,52 +189,18 @@ public ValueTask PopulateAsync(IServiceEndpointBuilder endpoints, CancellationTo

private void AddEndpoint(List<ServiceEndpoint> endpoints, IConfigurationSection section, string endpointName)
{
var value = section.Value;
if (string.IsNullOrWhiteSpace(value) || !TryParseEndPoint(value, out var endPoint))
if (!ServiceEndpoint.TryParse(section.Value, out var serviceEndpoint))
{
throw new KeyNotFoundException($"The endpoint configuration section for service '{_serviceName}' endpoint '{endpointName}' has an invalid value with key '{section.Key}'.");
}

endpoints.Add(CreateEndpoint(endPoint));
}

private static bool TryParseEndPoint(string value, [NotNullWhen(true)] out EndPoint? endPoint)
{
if (value.IndexOf("://") < 0 && Uri.TryCreate($"fakescheme://{value}", default, out var uri))
{
var port = uri.Port > 0 ? uri.Port : 0;
if (IPAddress.TryParse(uri.Host, out var ip))
{
endPoint = new IPEndPoint(ip, port);
}
else
{
endPoint = new DnsEndPoint(uri.Host, port);
}
}
else if (Uri.TryCreate(value, default, out uri))
{
endPoint = new UriEndPoint(uri);
}
else
{
endPoint = null;
return false;
}

return true;
}

private ServiceEndpoint CreateEndpoint(EndPoint endPoint)
{
var serviceEndpoint = ServiceEndpoint.Create(endPoint);
serviceEndpoint.Features.Set<IServiceEndpointProvider>(this);
if (_options.Value.ShouldApplyHostNameMetadata(serviceEndpoint))
{
serviceEndpoint.Features.Set<IHostNameFeature>(this);
}

return serviceEndpoint;
endpoints.Add(serviceEndpoint);
}

public override string ToString() => "Configuration";
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.ObjectModel;
using Microsoft.Extensions.Primitives;

namespace Microsoft.Extensions.ServiceDiscovery;
Expand Down Expand Up @@ -30,24 +31,30 @@ public sealed class ServiceDiscoveryOptions
/// </remarks>
public IList<string> AllowedSchemes { get; set; } = new List<string>();

internal static string[] ApplyAllowedSchemes(IReadOnlyList<string> schemes, IList<string> allowedSchemes, bool allowAllSchemes)
/// <summary>
/// Filters the specified URI schemes to include only those that are applicable, based on the current settings.
/// </summary>
/// <param name="requestedSchemes">The URI schemes to be evaluated against the allowed schemes.</param>
/// <returns>
/// The URI schemes that are applicable. If no schemes are requested, all allowed schemes are returned.
/// If all schemes are allowed, only the requested schemes are returned.
/// Otherwise, the intersection of requested and allowed schemes is returned.
/// </returns>
public IReadOnlyList<string> ApplyAllowedSchemes(IReadOnlyList<string> requestedSchemes)
{
if (schemes.Count > 0)
ArgumentNullException.ThrowIfNull(requestedSchemes);

if (requestedSchemes.Count > 0)
{
if (allowAllSchemes)
if (AllowAllSchemes)
{
if (schemes is string[] array && array.Length > 0)
{
return array;
}

return schemes.ToArray();
return requestedSchemes;
}

List<string> result = [];
foreach (var s in schemes)
foreach (var s in requestedSchemes)
{
foreach (var allowed in allowedSchemes)
foreach (var allowed in AllowedSchemes)
{
if (string.Equals(s, allowed, StringComparison.OrdinalIgnoreCase))
{
Expand All @@ -57,10 +64,10 @@ internal static string[] ApplyAllowedSchemes(IReadOnlyList<string> schemes, ILis
}
}

return result.ToArray();
return result.AsReadOnly();
}

// If no schemes were specified, but a set of allowed schemes were specified, allow those.
return allowedSchemes.ToArray();
return new ReadOnlyCollection<string>(AllowedSchemes);
}
}
Loading