diff --git a/src/Kestrel.Core/CoreStrings.resx b/src/Kestrel.Core/CoreStrings.resx index fdbebb8aaf..158bc6cc53 100644 --- a/src/Kestrel.Core/CoreStrings.resx +++ b/src/Kestrel.Core/CoreStrings.resx @@ -1,17 +1,17 @@  - @@ -462,4 +462,7 @@ Request headers contain connection-specific header field. - + + Unable to configure default https bindings because no IDefaultHttpsProvider service was provided. + + \ No newline at end of file diff --git a/src/Kestrel.Core/Internal/AddressBinder.cs b/src/Kestrel.Core/Internal/AddressBinder.cs index e4d1d18b93..808675c0fb 100644 --- a/src/Kestrel.Core/Internal/AddressBinder.cs +++ b/src/Kestrel.Core/Internal/AddressBinder.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; @@ -18,10 +18,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal internal class AddressBinder { public static async Task BindAsync(IServerAddressesFeature addresses, - List listenOptions, + KestrelServerOptions serverOptions, ILogger logger, + IDefaultHttpsProvider defaultHttpsProvider, Func createBinding) { + var listenOptions = serverOptions.ListenOptions; var strategy = CreateStrategy( listenOptions.ToArray(), addresses.Addresses.ToArray(), @@ -31,7 +33,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal { Addresses = addresses.Addresses, ListenOptions = listenOptions, + ServerOptions = serverOptions, Logger = logger, + DefaultHttpsProvider = defaultHttpsProvider ?? UnconfiguredDefaultHttpsProvider.Instance, CreateBinding = createBinding }; @@ -47,7 +51,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal { public ICollection Addresses { get; set; } public List ListenOptions { get; set; } + public KestrelServerOptions ServerOptions { get; set; } public ILogger Logger { get; set; } + public IDefaultHttpsProvider DefaultHttpsProvider { get; set; } public Func CreateBinding { get; set; } } @@ -120,7 +126,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal context.ListenOptions.Add(endpoint); } - private static async Task BindLocalhostAsync(ServerAddress address, AddressBindContext context) + private static async Task BindLocalhostAsync(ServerAddress address, AddressBindContext context, bool https) { if (address.Port == 0) { @@ -131,7 +137,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal try { - await BindEndpointAsync(new IPEndPoint(IPAddress.Loopback, address.Port), context).ConfigureAwait(false); + var options = new ListenOptions(new IPEndPoint(IPAddress.Loopback, address.Port)); + await BindEndpointAsync(options, context).ConfigureAwait(false); + + if (https) + { + options.KestrelServerOptions = context.ServerOptions; + context.DefaultHttpsProvider.ConfigureHttps(options); + } } catch (Exception ex) when (!(ex is IOException)) { @@ -141,7 +154,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal try { - await BindEndpointAsync(new IPEndPoint(IPAddress.IPv6Loopback, address.Port), context).ConfigureAwait(false); + var options = new ListenOptions(new IPEndPoint(IPAddress.IPv6Loopback, address.Port)); + await BindEndpointAsync(options, context).ConfigureAwait(false); + + if (https) + { + options.KestrelServerOptions = context.ServerOptions; + context.DefaultHttpsProvider.ConfigureHttps(options); + } } catch (Exception ex) when (!(ex is IOException)) { @@ -162,10 +182,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal private static async Task BindAddressAsync(string address, AddressBindContext context) { var parsedAddress = ServerAddress.FromUrl(address); + var https = false; if (parsedAddress.Scheme.Equals("https", StringComparison.OrdinalIgnoreCase)) { - throw new InvalidOperationException(CoreStrings.FormatConfigureHttpsFromMethodCall($"{nameof(KestrelServerOptions)}.{nameof(KestrelServerOptions.Listen)}()")); + https = true; } else if (!parsedAddress.Scheme.Equals("http", StringComparison.OrdinalIgnoreCase)) { @@ -177,20 +198,20 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal throw new InvalidOperationException(CoreStrings.FormatConfigurePathBaseFromMethodCall($"{nameof(IApplicationBuilder)}.UsePathBase()")); } + ListenOptions options = null; if (parsedAddress.IsUnixPipe) { - var endPoint = new ListenOptions(parsedAddress.UnixPipePath); - await BindEndpointAsync(endPoint, context).ConfigureAwait(false); - context.Addresses.Add(endPoint.GetDisplayName()); + options = new ListenOptions(parsedAddress.UnixPipePath); + await BindEndpointAsync(options, context).ConfigureAwait(false); + context.Addresses.Add(options.GetDisplayName()); } else if (string.Equals(parsedAddress.Host, "localhost", StringComparison.OrdinalIgnoreCase)) { // "localhost" for both IPv4 and IPv6 can't be represented as an IPEndPoint. - await BindLocalhostAsync(parsedAddress, context).ConfigureAwait(false); + await BindLocalhostAsync(parsedAddress, context, https).ConfigureAwait(false); } else { - ListenOptions options; if (TryCreateIPEndPoint(parsedAddress, out var endpoint)) { options = new ListenOptions(endpoint); @@ -216,6 +237,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal context.Addresses.Add(options.GetDisplayName()); } + + if (https && options != null) + { + options.KestrelServerOptions = context.ServerOptions; + context.DefaultHttpsProvider.ConfigureHttps(options); + } } private interface IStrategy @@ -229,7 +256,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal { context.Logger.LogDebug(CoreStrings.BindingToDefaultAddress, Constants.DefaultServerAddress); - await BindLocalhostAsync(ServerAddress.FromUrl(Constants.DefaultServerAddress), context).ConfigureAwait(false); + await BindLocalhostAsync(ServerAddress.FromUrl(Constants.DefaultServerAddress), context, https: false).ConfigureAwait(false); } } @@ -305,5 +332,22 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal } } } + + private class UnconfiguredDefaultHttpsProvider : IDefaultHttpsProvider + { + public static readonly UnconfiguredDefaultHttpsProvider Instance = new UnconfiguredDefaultHttpsProvider(); + + private UnconfiguredDefaultHttpsProvider() + { + } + + public void ConfigureHttps(ListenOptions listenOptions) + { + // We have to throw here. If this is called, it's because the user asked for "https" binding but for some + // reason didn't provide a certificate and didn't use the "DefaultHttpsProvider". This means if we no-op, + // we'll silently downgrade to HTTP, which is bad. + throw new InvalidOperationException(CoreStrings.UnableToConfigureHttpsBindings); + } + } } } diff --git a/src/Kestrel.Core/Internal/IDefaultHttpsProvider.cs b/src/Kestrel.Core/Internal/IDefaultHttpsProvider.cs new file mode 100644 index 0000000000..3ed67b0cda --- /dev/null +++ b/src/Kestrel.Core/Internal/IDefaultHttpsProvider.cs @@ -0,0 +1,10 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal +{ + public interface IDefaultHttpsProvider + { + void ConfigureHttps(ListenOptions listenOptions); + } +} diff --git a/src/Kestrel.Core/Internal/KestrelServerOptionsSetup.cs b/src/Kestrel.Core/Internal/KestrelServerOptionsSetup.cs index 18c96e2039..4d7a95ca5b 100644 --- a/src/Kestrel.Core/Internal/KestrelServerOptionsSetup.cs +++ b/src/Kestrel.Core/Internal/KestrelServerOptionsSetup.cs @@ -2,6 +2,8 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using Microsoft.AspNetCore.Hosting.Server.Features; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal diff --git a/src/Kestrel.Core/KestrelServer.cs b/src/Kestrel.Core/KestrelServer.cs index fac999d904..81536a44ab 100644 --- a/src/Kestrel.Core/KestrelServer.cs +++ b/src/Kestrel.Core/KestrelServer.cs @@ -22,6 +22,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core private readonly List _transports = new List(); private readonly Heartbeat _heartbeat; private readonly IServerAddressesFeature _serverAddresses; + private readonly IDefaultHttpsProvider _defaultHttpsProvider; private readonly ITransportFactory _transportFactory; private bool _hasStarted; @@ -33,6 +34,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core { } + public KestrelServer(IOptions options, ITransportFactory transportFactory, ILoggerFactory loggerFactory, IDefaultHttpsProvider defaultHttpsProvider) + : this(transportFactory, CreateServiceContext(options, loggerFactory)) + { + _defaultHttpsProvider = defaultHttpsProvider; + } + // For testing internal KestrelServer(ITransportFactory transportFactory, ServiceContext serviceContext) { @@ -152,7 +159,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core await transport.BindAsync().ConfigureAwait(false); } - await AddressBinder.BindAsync(_serverAddresses, Options.ListenOptions, Trace, OnBind).ConfigureAwait(false); + await AddressBinder.BindAsync(_serverAddresses, Options, Trace, _defaultHttpsProvider, OnBind).ConfigureAwait(false); } catch (Exception ex) { diff --git a/src/Kestrel.Core/Properties/CoreStrings.Designer.cs b/src/Kestrel.Core/Properties/CoreStrings.Designer.cs index 346b18a969..9321f1a2c9 100644 --- a/src/Kestrel.Core/Properties/CoreStrings.Designer.cs +++ b/src/Kestrel.Core/Properties/CoreStrings.Designer.cs @@ -1620,6 +1620,20 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core internal static string FormatHttp2ErrorConnectionSpecificHeaderField() => GetString("Http2ErrorConnectionSpecificHeaderField"); + /// + /// Unable to configure default https bindings because no IDefaultHttpsProvider service was provided. + /// + internal static string UnableToConfigureHttpsBindings + { + get => GetString("UnableToConfigureHttpsBindings"); + } + + /// + /// Unable to configure default https bindings because no IDefaultHttpsProvider service was provided. + /// + internal static string FormatUnableToConfigureHttpsBindings() + => GetString("UnableToConfigureHttpsBindings"); + private static string GetString(string name, params string[] formatterNames) { var value = _resourceManager.GetString(name); diff --git a/src/Kestrel.Https/ListenOptionsHttpsExtensions.cs b/src/Kestrel.Https/ListenOptionsHttpsExtensions.cs index 7cf8ab1b9e..44463ea10e 100644 --- a/src/Kestrel.Https/ListenOptionsHttpsExtensions.cs +++ b/src/Kestrel.Https/ListenOptionsHttpsExtensions.cs @@ -12,7 +12,7 @@ using Microsoft.Extensions.Logging; namespace Microsoft.AspNetCore.Hosting { /// - /// Extension methods fro that configure Kestrel to use HTTPS for a given endpoint. + /// Extension methods for that configure Kestrel to use HTTPS for a given endpoint. /// public static class ListenOptionsHttpsExtensions { diff --git a/src/Kestrel/Internal/DefaultHttpsProvider.cs b/src/Kestrel/Internal/DefaultHttpsProvider.cs new file mode 100644 index 0000000000..efc7d9fb10 --- /dev/null +++ b/src/Kestrel/Internal/DefaultHttpsProvider.cs @@ -0,0 +1,42 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Linq; +using System.Security.Cryptography.X509Certificates; +using Microsoft.AspNetCore.Certificates.Generation; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Server.Kestrel.Core; +using Microsoft.AspNetCore.Server.Kestrel.Core.Internal; +using Microsoft.Extensions.Logging; + +namespace Microsoft.AspNetCore.Server.Kestrel.Internal +{ + public class DefaultHttpsProvider : IDefaultHttpsProvider + { + private static readonly CertificateManager _certificateManager = new CertificateManager(); + + private readonly ILogger _logger; + + public DefaultHttpsProvider(ILogger logger) + { + _logger = logger; + } + + public void ConfigureHttps(ListenOptions listenOptions) + { + var certificate = _certificateManager.ListCertificates(CertificatePurpose.HTTPS, StoreName.My, StoreLocation.CurrentUser, isValid: true) + .FirstOrDefault(); + if (certificate != null) + { + _logger.LocatedDevelopmentCertificate(certificate); + listenOptions.UseHttps(certificate); + } + else + { + _logger.UnableToLocateDevelopmentCertificate(); + throw new InvalidOperationException(KestrelStrings.HttpsUrlProvidedButNoDevelopmentCertificateFound); + } + } + } +} diff --git a/src/Kestrel/Internal/LoggerExtensions.cs b/src/Kestrel/Internal/LoggerExtensions.cs new file mode 100644 index 0000000000..218f50ca10 --- /dev/null +++ b/src/Kestrel/Internal/LoggerExtensions.cs @@ -0,0 +1,20 @@ +using System; +using System.Security.Cryptography.X509Certificates; +using Microsoft.Extensions.Logging; + +namespace Microsoft.AspNetCore.Server.Kestrel.Internal +{ + internal static class LoggerExtensions + { + // Category: DefaultHttpsProvider + private static readonly Action _locatedDevelopmentCertificate = + LoggerMessage.Define(LogLevel.Debug, new EventId(0, nameof(LocatedDevelopmentCertificate)), "Using development certificate: {certificateSubjectName} (Thumbprint: {certificateThumbprint})"); + + private static readonly Action _unableToLocateDevelopmentCertificate = + LoggerMessage.Define(LogLevel.Error, new EventId(1, nameof(UnableToLocateDevelopmentCertificate)), "Unable to locate an appropriate development https certificate."); + + public static void LocatedDevelopmentCertificate(this ILogger logger, X509Certificate2 certificate) => _locatedDevelopmentCertificate(logger, certificate.Subject, certificate.Thumbprint, null); + + public static void UnableToLocateDevelopmentCertificate(this ILogger logger) => _unableToLocateDevelopmentCertificate(logger, null); + } +} diff --git a/src/Kestrel/Kestrel.csproj b/src/Kestrel/Kestrel.csproj index 1376f1e891..7f7f8de70b 100644 --- a/src/Kestrel/Kestrel.csproj +++ b/src/Kestrel/Kestrel.csproj @@ -12,10 +12,14 @@ + + + + diff --git a/src/Kestrel/KestrelStrings.resx b/src/Kestrel/KestrelStrings.resx new file mode 100644 index 0000000000..d39f8b5166 --- /dev/null +++ b/src/Kestrel/KestrelStrings.resx @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Unable to configure HTTPS endpoint. Try running 'dotnet developercertificates https -t' to setup a developer certificate for use with localhost. For information on configuring HTTPS see https://go.microsoft.com/fwlink/?linkid=848054 + + \ No newline at end of file diff --git a/src/Kestrel/Properties/KestrelStrings.Designer.cs b/src/Kestrel/Properties/KestrelStrings.Designer.cs new file mode 100644 index 0000000000..ede41f12cf --- /dev/null +++ b/src/Kestrel/Properties/KestrelStrings.Designer.cs @@ -0,0 +1,44 @@ +// +namespace Microsoft.AspNetCore.Server.Kestrel +{ + using System.Globalization; + using System.Reflection; + using System.Resources; + + internal static class KestrelStrings + { + private static readonly ResourceManager _resourceManager + = new ResourceManager("Microsoft.AspNetCore.Server.Kestrel.KestrelStrings", typeof(KestrelStrings).GetTypeInfo().Assembly); + + /// + /// An 'https' URL was provided, but a development certificate could not be found. + /// + internal static string HttpsUrlProvidedButNoDevelopmentCertificateFound + { + get => GetString("HttpsUrlProvidedButNoDevelopmentCertificateFound"); + } + + /// + /// An 'https' URL was provided, but a development certificate could not be found. + /// + internal static string FormatHttpsUrlProvidedButNoDevelopmentCertificateFound() + => GetString("HttpsUrlProvidedButNoDevelopmentCertificateFound"); + + private static string GetString(string name, params string[] formatterNames) + { + var value = _resourceManager.GetString(name); + + System.Diagnostics.Debug.Assert(value != null); + + if (formatterNames != null) + { + for (var i = 0; i < formatterNames.Length; i++) + { + value = value.Replace("{" + formatterNames[i] + "}", "{" + i + "}"); + } + } + + return value; + } + } +} diff --git a/src/Kestrel/WebHostBuilderKestrelExtensions.cs b/src/Kestrel/WebHostBuilderKestrelExtensions.cs index 5ed947e96a..f65a66ac4e 100644 --- a/src/Kestrel/WebHostBuilderKestrelExtensions.cs +++ b/src/Kestrel/WebHostBuilderKestrelExtensions.cs @@ -5,6 +5,7 @@ using System; using Microsoft.AspNetCore.Hosting.Server; using Microsoft.AspNetCore.Server.Kestrel.Core; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal; +using Microsoft.AspNetCore.Server.Kestrel.Internal; using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal; using Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets; using Microsoft.Extensions.DependencyInjection; @@ -33,6 +34,7 @@ namespace Microsoft.AspNetCore.Hosting services.AddTransient, KestrelServerOptionsSetup>(); services.AddSingleton(); + services.AddSingleton(); }); } diff --git a/test/Kestrel.Core.Tests/AddressBinderTests.cs b/test/Kestrel.Core.Tests/AddressBinderTests.cs index 2c957e6030..d42a598e9c 100644 --- a/test/Kestrel.Core.Tests/AddressBinderTests.cs +++ b/test/Kestrel.Core.Tests/AddressBinderTests.cs @@ -8,9 +8,9 @@ using System.Net; using System.Threading.Tasks; using Microsoft.AspNetCore.Protocols; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal; -using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal; using Microsoft.AspNetCore.Testing; using Microsoft.Extensions.Logging.Abstractions; +using Moq; using Xunit; namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests @@ -55,12 +55,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests { var addresses = new ServerAddressesFeature(); addresses.Addresses.Add($"http://{host}"); - var options = new List(); + var options = new KestrelServerOptions(); var tcs = new TaskCompletionSource(); await AddressBinder.BindAsync(addresses, options, NullLogger.Instance, + Mock.Of(), endpoint => { tcs.TrySetResult(endpoint); @@ -75,13 +76,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests { var addresses = new ServerAddressesFeature(); addresses.Addresses.Add("http://localhost:5000"); - var options = new List(); + var options = new KestrelServerOptions(); await Assert.ThrowsAsync(() => AddressBinder.BindAsync(addresses, - options, - NullLogger.Instance, - endpoint => throw new AddressInUseException("already in use"))); + options, + NullLogger.Instance, + Mock.Of(), + endpoint => throw new AddressInUseException("already in use"))); } [Theory] @@ -93,7 +95,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests var logger = new MockLogger(); var addresses = new ServerAddressesFeature(); addresses.Addresses.Add(address); - var options = new List(); + var options = new KestrelServerOptions(); var ipV6Attempt = false; var ipV4Attempt = false; @@ -101,6 +103,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests await AddressBinder.BindAsync(addresses, options, logger, + Mock.Of(), endpoint => { if (endpoint.IPEndPoint.Address == IPAddress.IPv6Any) diff --git a/test/Kestrel.Core.Tests/KestrelServerTests.cs b/test/Kestrel.Core.Tests/KestrelServerTests.cs index c6ae3b5a7a..51e9de0221 100644 --- a/test/Kestrel.Core.Tests/KestrelServerTests.cs +++ b/test/Kestrel.Core.Tests/KestrelServerTests.cs @@ -9,6 +9,7 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting.Server; using Microsoft.AspNetCore.Hosting.Server.Features; +using Microsoft.AspNetCore.Server.Kestrel.Core.Internal; using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal; using Microsoft.AspNetCore.Testing; using Microsoft.Extensions.Logging; @@ -37,20 +38,56 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests } [Fact] - public void StartWithHttpsAddressThrows() + public void StartWithHttpsAddressConfiguresHttpsEndpoints() { - var testLogger = new TestApplicationErrorLogger { ThrowOnCriticalErrors = false }; + var mockDefaultHttpsProvider = new Mock(); - using (var server = CreateServer(new KestrelServerOptions(), testLogger)) + using (var server = CreateServer(new KestrelServerOptions(), mockDefaultHttpsProvider.Object)) { server.Features.Get().Addresses.Add("https://127.0.0.1:0"); - var exception = Assert.Throws(() => StartDummyApplication(server)); + StartDummyApplication(server); - Assert.Equal( - $"HTTPS endpoints can only be configured using {nameof(KestrelServerOptions)}.{nameof(KestrelServerOptions.Listen)}().", - exception.Message); - Assert.Equal(1, testLogger.CriticalErrorsLogged); + mockDefaultHttpsProvider.Verify(provider => provider.ConfigureHttps(It.IsAny()), Times.Once); + } + } + + [Fact] + public void KestrelServerThrowsUsefulExceptionIfDefaultHttpsProviderNotAdded() + { + using (var server = CreateServer(new KestrelServerOptions(), defaultHttpsProvider: null, throwOnCriticalErrors: false)) + { + server.Features.Get().Addresses.Add("https://127.0.0.1:0"); + + var ex = Assert.Throws(() => StartDummyApplication(server)); + Assert.Equal(CoreStrings.UnableToConfigureHttpsBindings, ex.Message); + } + } + + [Fact] + public void KestrelServerDoesNotThrowIfNoDefaultHttpsProviderButNoHttpUrls() + { + using (var server = CreateServer(new KestrelServerOptions(), defaultHttpsProvider: null)) + { + server.Features.Get().Addresses.Add("http://127.0.0.1:0"); + + StartDummyApplication(server); + } + } + + [Fact] + public void KestrelServerDoesNotThrowIfNoDefaultHttpsProviderButManualListenOptions() + { + var mockDefaultHttpsProvider = new Mock(); + + var serverOptions = new KestrelServerOptions(); + serverOptions.Listen(new IPEndPoint(IPAddress.Loopback, 0)); + + using (var server = CreateServer(serverOptions, defaultHttpsProvider: null)) + { + server.Features.Get().Addresses.Add("https://127.0.0.1:0"); + + StartDummyApplication(server); } } @@ -274,6 +311,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests return new KestrelServer(Options.Create(options), new MockTransportFactory(), new LoggerFactory(new[] { new KestrelTestLoggerProvider(testLogger) })); } + private static KestrelServer CreateServer(KestrelServerOptions options, IDefaultHttpsProvider defaultHttpsProvider, bool throwOnCriticalErrors = true) + { + return new KestrelServer(Options.Create(options), new MockTransportFactory(), new LoggerFactory(new[] { new KestrelTestLoggerProvider(throwOnCriticalErrors) }), defaultHttpsProvider); + } + private static void StartDummyApplication(IServer server) { server.StartAsync(new DummyApplication(context => Task.CompletedTask), CancellationToken.None).GetAwaiter().GetResult(); diff --git a/test/Kestrel.FunctionalTests/AddressRegistrationTests.cs b/test/Kestrel.FunctionalTests/AddressRegistrationTests.cs index 7b9c9bdcda..446a201f71 100644 --- a/test/Kestrel.FunctionalTests/AddressRegistrationTests.cs +++ b/test/Kestrel.FunctionalTests/AddressRegistrationTests.cs @@ -511,8 +511,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests } [Theory] - [InlineData("https://localhost")] [InlineData("ftp://localhost")] + [InlineData("ssh://localhost")] public void ThrowsForUnsupportedAddressFromHosting(string addr) { var hostBuilder = TransportSelector.GetWebHostBuilder() diff --git a/test/shared/KestrelTestLoggerProvider.cs b/test/shared/KestrelTestLoggerProvider.cs index 45462791d1..48455557fb 100644 --- a/test/shared/KestrelTestLoggerProvider.cs +++ b/test/shared/KestrelTestLoggerProvider.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; @@ -10,8 +10,8 @@ namespace Microsoft.AspNetCore.Testing { private readonly ILogger _testLogger; - public KestrelTestLoggerProvider() - : this(new TestApplicationErrorLogger()) + public KestrelTestLoggerProvider(bool throwOnCriticalErrors = true) + : this(new TestApplicationErrorLogger() { ThrowOnCriticalErrors = throwOnCriticalErrors }) { } @@ -30,4 +30,4 @@ namespace Microsoft.AspNetCore.Testing throw new NotImplementedException(); } } -} \ No newline at end of file +}