diff --git a/src/libraries/Common/src/Interop/Windows/WinHttp/Interop.winhttp_types.cs b/src/libraries/Common/src/Interop/Windows/WinHttp/Interop.winhttp_types.cs index 4c1824f543873..3948b540f6179 100644 --- a/src/libraries/Common/src/Interop/Windows/WinHttp/Interop.winhttp_types.cs +++ b/src/libraries/Common/src/Interop/Windows/WinHttp/Interop.winhttp_types.cs @@ -149,9 +149,11 @@ internal partial class WinHttp public const uint WINHTTP_OPTION_ASSURED_NON_BLOCKING_CALLBACKS = 111; + public const uint WINHTTP_OPTION_ENABLE_HTTP2_PLUS_CLIENT_CERT = 161; public const uint WINHTTP_OPTION_ENABLE_HTTP_PROTOCOL = 133; public const uint WINHTTP_OPTION_HTTP_PROTOCOL_USED = 134; public const uint WINHTTP_PROTOCOL_FLAG_HTTP2 = 0x1; + public const uint WINHTTP_HTTP2_PLUS_CLIENT_CERT_FLAG = 0x1; public const uint WINHTTP_OPTION_UPGRADE_TO_WEB_SOCKET = 114; public const uint WINHTTP_OPTION_WEB_SOCKET_CLOSE_TIMEOUT = 115; diff --git a/src/libraries/Common/tests/System/Net/Http/DefaultCredentialsTest.cs b/src/libraries/Common/tests/System/Net/Http/DefaultCredentialsTest.cs index 40686b97bc519..d5b9e4fb940cb 100644 --- a/src/libraries/Common/tests/System/Net/Http/DefaultCredentialsTest.cs +++ b/src/libraries/Common/tests/System/Net/Http/DefaultCredentialsTest.cs @@ -335,7 +335,8 @@ private async Task ProcessRequests() // Send a response in the JSON format that the client expects string username = context.User.Identity.Name; - await context.Response.OutputStream.WriteAsync(System.Text.Encoding.UTF8.GetBytes($"{{\"authenticated\": \"true\", \"user\": \"{username}\" }}")); + byte[] bytes = System.Text.Encoding.UTF8.GetBytes($"{{\"authenticated\": \"true\", \"user\": \"{username}\" }}"); + await context.Response.OutputStream.WriteAsync(bytes); context.Response.Close(); } diff --git a/src/libraries/Common/tests/System/Net/Http/GenericLoopbackServer.cs b/src/libraries/Common/tests/System/Net/Http/GenericLoopbackServer.cs index 9e505e37314b6..779055b921e4d 100644 --- a/src/libraries/Common/tests/System/Net/Http/GenericLoopbackServer.cs +++ b/src/libraries/Common/tests/System/Net/Http/GenericLoopbackServer.cs @@ -86,7 +86,7 @@ public class GenericLoopbackOptions public IPAddress Address { get; set; } = IPAddress.Loopback; public bool UseSsl { get; set; } = PlatformDetection.SupportsAlpn && !Capability.Http2ForceUnencryptedLoopback(); public SslProtocols SslProtocols { get; set; } = -#if !NETSTANDARD2_0 +#if !NETSTANDARD2_0 && !NETFRAMEWORK SslProtocols.Tls13 | #endif SslProtocols.Tls12; diff --git a/src/libraries/Common/tests/System/Net/Http/Http2Frames.cs b/src/libraries/Common/tests/System/Net/Http/Http2Frames.cs index 4fe0f5bddd979..af0f245b1b821 100644 --- a/src/libraries/Common/tests/System/Net/Http/Http2Frames.cs +++ b/src/libraries/Common/tests/System/Net/Http/Http2Frames.cs @@ -548,10 +548,12 @@ public override void WriteTo(Span buffer) BinaryPrimitives.WriteUInt16BigEndian(buffer, checked((ushort)Origin.Length)); buffer = buffer.Slice(2); - Encoding.ASCII.GetBytes(Origin, buffer); + var tmpBuffer = Encoding.ASCII.GetBytes(Origin); + tmpBuffer.CopyTo(buffer); buffer = buffer.Slice(Origin.Length); - Encoding.ASCII.GetBytes(AltSvc, buffer); + tmpBuffer = Encoding.ASCII.GetBytes(AltSvc); + tmpBuffer.CopyTo(buffer); } public override string ToString() => $"{base.ToString()}\n{nameof(Origin)}: {Origin}\n{nameof(AltSvc)}: {AltSvc}"; diff --git a/src/libraries/Common/tests/System/Net/Http/Http2LoopbackConnection.cs b/src/libraries/Common/tests/System/Net/Http/Http2LoopbackConnection.cs index 43be924c03550..ee774e0f23090 100644 --- a/src/libraries/Common/tests/System/Net/Http/Http2LoopbackConnection.cs +++ b/src/libraries/Common/tests/System/Net/Http/Http2LoopbackConnection.cs @@ -3,8 +3,8 @@ // See the LICENSE file in the project root for more information. using System.Collections.Generic; -using System.Diagnostics; using System.IO; +using System.Linq; using System.Net.Http.Functional.Tests; using System.Net.Security; using System.Net.Sockets; @@ -28,6 +28,7 @@ public class Http2LoopbackConnection : GenericLoopbackConnection private readonly byte[] _prefix; public string PrefixString => Encoding.UTF8.GetString(_prefix, 0, _prefix.Length); public bool IsInvalid => _connectionSocket == null; + public Stream Stream => _connectionStream; public Http2LoopbackConnection(Socket socket, Http2Options httpOptions) { @@ -40,6 +41,7 @@ public Http2LoopbackConnection(Socket socket, Http2Options httpOptions) using (var cert = Configuration.Certificates.GetServerCertificate()) { +#if !NETFRAMEWORK SslServerAuthenticationOptions options = new SslServerAuthenticationOptions(); options.EnabledSslProtocols = httpOptions.SslProtocols; @@ -51,9 +53,12 @@ public Http2LoopbackConnection(Socket socket, Http2Options httpOptions) options.ServerCertificate = cert; - options.ClientCertificateRequired = false; + options.ClientCertificateRequired = httpOptions.ClientCertificateRequired; sslStream.AuthenticateAsServerAsync(options, CancellationToken.None).Wait(); +#else + sslStream.AuthenticateAsServerAsync(cert, httpOptions.ClientCertificateRequired, httpOptions.SslProtocols, checkCertificateRevocation: false).Wait(); +#endif } _connectionStream = sslStream; @@ -64,6 +69,10 @@ public Http2LoopbackConnection(Socket socket, Http2Options httpOptions) { throw new Exception("Connection stream closed while attempting to read connection preface."); } + else if (Text.Encoding.ASCII.GetString(_prefix).Contains("HTTP/1.1")) + { + throw new Exception("HTTP 1.1 request received."); + } } public async Task SendConnectionPrefaceAsync() @@ -331,7 +340,7 @@ private static (int bytesConsumed, string value) DecodeString(ReadOnlySpan } else { - string value = Encoding.ASCII.GetString(headerBlock.Slice(bytesConsumed, stringLength)); + string value = Encoding.ASCII.GetString(headerBlock.Slice(bytesConsumed, stringLength).ToArray()); return (bytesConsumed + stringLength, value); } } diff --git a/src/libraries/Common/tests/System/Net/Http/Http2LoopbackServer.cs b/src/libraries/Common/tests/System/Net/Http/Http2LoopbackServer.cs index f2aea4cfb4f23..4d3486c3a80c7 100644 --- a/src/libraries/Common/tests/System/Net/Http/Http2LoopbackServer.cs +++ b/src/libraries/Common/tests/System/Net/Http/Http2LoopbackServer.cs @@ -181,9 +181,14 @@ public override async Task AcceptConnectionAsync(Func clientFunc, Func serverFunc, int timeout = 60_000) + public static Task CreateClientAndServerAsync(Func clientFunc, Func serverFunc, int timeout = 60_000) { - using (var server = Http2LoopbackServer.CreateServer()) + return CreateClientAndServerAsync(clientFunc, serverFunc, null, timeout); + } + + public static async Task CreateClientAndServerAsync(Func clientFunc, Func serverFunc, Http2Options http2Options, int timeout = 60_000) + { + using (var server = Http2LoopbackServer.CreateServer(http2Options ?? new Http2Options())) { Task clientTask = clientFunc(server.Address); Task serverTask = serverFunc(server); @@ -197,6 +202,8 @@ public class Http2Options : GenericLoopbackOptions { public int ListenBacklog { get; set; } = 1; + public bool ClientCertificateRequired { get; set; } + public Http2Options() { UseSsl = PlatformDetection.SupportsAlpn && !Capability.Http2ForceUnencryptedLoopback(); @@ -237,7 +244,7 @@ public override async Task CreateServerAsync(Func HttpVersion.Version20; + public override Version Version => HttpVersion20.Value; } public enum ProtocolErrors @@ -257,4 +264,9 @@ public enum ProtocolErrors INADEQUATE_SECURITY = 0xc, HTTP_1_1_REQUIRED = 0xd } + + public static class HttpVersion20 + { + public static readonly Version Value = new Version(2, 0); + } } diff --git a/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.AcceptAllCerts.cs b/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.AcceptAllCerts.cs index 53d45add78b83..99ef96c4b645b 100644 --- a/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.AcceptAllCerts.cs +++ b/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.AcceptAllCerts.cs @@ -39,8 +39,10 @@ public void SingletonReturnsTrue() [InlineData(SslProtocols.Tls, true)] [InlineData(SslProtocols.Tls12 | SslProtocols.Tls11 | SslProtocols.Tls, false)] [InlineData(SslProtocols.Tls12 | SslProtocols.Tls11 | SslProtocols.Tls, true)] +#if !NETFRAMEWORK [InlineData(SslProtocols.Tls13 | SslProtocols.Tls12 | SslProtocols.Tls11 | SslProtocols.Tls, false)] [InlineData(SslProtocols.Tls13 | SslProtocols.Tls12 | SslProtocols.Tls11 | SslProtocols.Tls, true)] +#endif [InlineData(SslProtocols.None, false)] [InlineData(SslProtocols.None, true)] public async Task SetDelegate_ConnectionSucceeds(SslProtocols acceptedProtocol, bool requestOnlyThisProtocol) @@ -64,7 +66,11 @@ public async Task SetDelegate_ConnectionSucceeds(SslProtocols acceptedProtocol, // restrictions on minimum TLS/SSL version // We currently know that some platforms like Debian 10 OpenSSL // will by default block < TLS 1.2 +#if !NETFRAMEWORK handler.SslProtocols = SslProtocols.Tls13 | SslProtocols.Tls12 | SslProtocols.Tls11 | SslProtocols.Tls; +#else + handler.SslProtocols = SslProtocols.Tls12 | SslProtocols.Tls11 | SslProtocols.Tls; +#endif } var options = new LoopbackServer.Options { UseSsl = true, SslProtocols = acceptedProtocol }; diff --git a/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.Authentication.cs b/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.Authentication.cs index 3b195350040d9..9c2c1e2c74e53 100644 --- a/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.Authentication.cs +++ b/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.Authentication.cs @@ -602,6 +602,12 @@ public async Task Credentials_ServerUsesWindowsAuthentication_Success(string ser [InlineData("Negotiate")] public async Task Credentials_ServerChallengesWithWindowsAuth_ClientSendsWindowsAuthHeader(string authScheme) { +#if WINHTTPHANDLER_TEST + if (UseVersion > HttpVersion.Version11) + { + throw new SkipTestException($"Test doesn't support {UseVersion} protocol."); + } +#endif await LoopbackServerFactory.CreateClientAndServerAsync( async uri => { diff --git a/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.Cancellation.cs b/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.Cancellation.cs index d7e519fa202ed..b248f3470bbdd 100644 --- a/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.Cancellation.cs +++ b/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.Cancellation.cs @@ -28,17 +28,24 @@ public abstract class HttpClientHandler_Cancellation_Test : HttpClientHandlerTes { public HttpClientHandler_Cancellation_Test(ITestOutputHelper output) : base(output) { } - [Theory] + [ConditionalTheory] [InlineData(false, CancellationMode.Token)] [InlineData(true, CancellationMode.Token)] public async Task PostAsync_CancelDuringRequestContentSend_TaskCanceledQuickly(bool chunkedTransfer, CancellationMode mode) { - if (LoopbackServerFactory.Version >= HttpVersion.Version20 && chunkedTransfer) + if (LoopbackServerFactory.Version >= HttpVersion20.Value && chunkedTransfer) { // There is no chunked encoding in HTTP/2 and later return; } +#if WINHTTPHANDLER_TEST + if (UseVersion >= HttpVersion20.Value) + { + throw new SkipTestException($"Test doesn't support {UseVersion} protocol."); + } +#endif + var serverRelease = new TaskCompletionSource(); await LoopbackServerFactory.CreateClientAndServerAsync(async uri => { @@ -76,16 +83,23 @@ public async Task PostAsync_CancelDuringRequestContentSend_TaskCanceledQuickly(b }); } - [Theory] + [ConditionalTheory] [MemberData(nameof(OneBoolAndCancellationMode))] public async Task GetAsync_CancelDuringResponseHeadersReceived_TaskCanceledQuickly(bool connectionClose, CancellationMode mode) { - if (LoopbackServerFactory.Version >= HttpVersion.Version20 && connectionClose) + if (LoopbackServerFactory.Version >= HttpVersion20.Value && connectionClose) { // There is no Connection header in HTTP/2 and later return; } +#if WINHTTPHANDLER_TEST + if (UseVersion >= HttpVersion20.Value) + { + throw new SkipTestException($"Test doesn't support {UseVersion} protocol."); + } +#endif + using (HttpClient client = CreateHttpClient()) { client.Timeout = Timeout.InfiniteTimeSpan; @@ -130,7 +144,7 @@ public async Task GetAsync_CancelDuringResponseHeadersReceived_TaskCanceledQuick [MemberData(nameof(TwoBoolsAndCancellationMode))] public async Task GetAsync_CancelDuringResponseBodyReceived_Buffered_TaskCanceledQuickly(bool chunkedTransfer, bool connectionClose, CancellationMode mode) { - if (LoopbackServerFactory.Version >= HttpVersion.Version20 && (chunkedTransfer || connectionClose)) + if (LoopbackServerFactory.Version >= HttpVersion20.Value && (chunkedTransfer || connectionClose)) { // There is no chunked encoding or connection header in HTTP/2 and later return; @@ -182,16 +196,23 @@ public async Task GetAsync_CancelDuringResponseBodyReceived_Buffered_TaskCancele } } - [Theory] + [ConditionalTheory] [MemberData(nameof(ThreeBools))] public async Task GetAsync_CancelDuringResponseBodyReceived_Unbuffered_TaskCanceledQuickly(bool chunkedTransfer, bool connectionClose, bool readOrCopyToAsync) { - if (LoopbackServerFactory.Version >= HttpVersion.Version20 && (chunkedTransfer || connectionClose)) + if (LoopbackServerFactory.Version >= HttpVersion20.Value && (chunkedTransfer || connectionClose)) { // There is no chunked encoding or connection header in HTTP/2 and later return; } +#if WINHTTPHANDLER_TEST + if (UseVersion >= HttpVersion20.Value) + { + throw new SkipTestException($"Test doesn't support {UseVersion} protocol."); + } +#endif + using (HttpClient client = CreateHttpClient()) { client.Timeout = Timeout.InfiniteTimeSpan; @@ -237,14 +258,19 @@ public async Task GetAsync_CancelDuringResponseBodyReceived_Unbuffered_TaskCance }); } } - - [Theory] + [ConditionalTheory] [InlineData(CancellationMode.CancelPendingRequests, false)] [InlineData(CancellationMode.DisposeHttpClient, false)] [InlineData(CancellationMode.CancelPendingRequests, true)] [InlineData(CancellationMode.DisposeHttpClient, true)] public async Task GetAsync_CancelPendingRequests_DoesntCancelReadAsyncOnResponseStream(CancellationMode mode, bool copyToAsync) { +#if WINHTTPHANDLER_TEST + if (UseVersion >= HttpVersion20.Value) + { + throw new SkipTestException($"Test doesn't support {UseVersion} protocol."); + } +#endif using (HttpClient client = CreateHttpClient()) { client.Timeout = Timeout.InfiniteTimeSpan; @@ -312,7 +338,7 @@ public async Task GetAsync_CancelPendingRequests_DoesntCancelReadAsyncOnResponse [ConditionalFact] public async Task MaxConnectionsPerServer_WaitingConnectionsAreCancelable() { - if (LoopbackServerFactory.Version >= HttpVersion.Version20) + if (LoopbackServerFactory.Version >= HttpVersion20.Value) { // HTTP/2 does not use connection limits. throw new SkipTestException("Not supported on HTTP/2 and later"); @@ -490,11 +516,18 @@ public static IEnumerable PostAsync_Cancel_CancellationTokenPassedToCo } } +#if !NETFRAMEWORK [OuterLoop("Uses Task.Delay")] - [Theory] + [ConditionalTheory] [MemberData(nameof(PostAsync_Cancel_CancellationTokenPassedToContent_MemberData))] public async Task PostAsync_Cancel_CancellationTokenPassedToContent(HttpContent content, CancellationTokenSource cancellationTokenSource) { +#if WINHTTPHANDLER_TEST + if (UseVersion > HttpVersion.Version11) + { + throw new SkipTestException($"Test doesn't support {UseVersion} protocol."); + } +#endif await LoopbackServerFactory.CreateClientAndServerAsync( async uri => { @@ -518,6 +551,7 @@ public async Task PostAsync_Cancel_CancellationTokenPassedToContent(HttpContent catch (Exception) { } }); } +#endif private async Task ValidateClientCancellationAsync(Func clientBodyAsync) { diff --git a/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.ClientCertificates.cs b/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.ClientCertificates.cs index b75156a6f2556..a17334e247f32 100644 --- a/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.ClientCertificates.cs +++ b/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.ClientCertificates.cs @@ -113,7 +113,7 @@ public async Task Manual_CertificateOnlySentWhenValid_Success(int certIndex, boo { _output.WriteLine( "Client cert: {0}", - ((X509Certificate2)sslStream.RemoteCertificate).GetNameInfo(X509NameType.SimpleName, false)); + new X509Certificate2(sslStream.RemoteCertificate.Export(X509ContentType.Cert)).GetNameInfo(X509NameType.SimpleName, false)); Assert.Equal(cert, sslStream.RemoteCertificate); } else diff --git a/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.Cookies.cs b/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.Cookies.cs index 75f440ac1bcdc..3fa53fe5a9be2 100644 --- a/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.Cookies.cs +++ b/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.Cookies.cs @@ -182,7 +182,11 @@ public async Task GetAsync_AddMultipleCookieHeaders_CookiesSent() private string GetCookieValue(HttpRequestData request) { +#if !NETFRAMEWORK if (LoopbackServerFactory.Version < HttpVersion.Version20) +#else + if (LoopbackServerFactory.Version < HttpVersion20.Value) +#endif { // HTTP/1.x must have only one value. return request.GetSingleHeaderValue("Cookie"); @@ -603,7 +607,9 @@ public static IEnumerable CookieNamesValuesAndUseCookies() yield return new object[] { "ABC", "123", useCookies }; yield return new object[] { "Hello", "World", useCookies }; yield return new object[] { "foo", "bar", useCookies }; +#if !NETFRAMEWORK yield return new object[] { "Hello World", "value", useCookies }; +#endif yield return new object[] { ".AspNetCore.Session", "RAExEmXpoCbueP_QYM", useCookies }; yield return new object[] diff --git a/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.Decompression.cs b/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.Decompression.cs index 0fb03c3e612b8..fa0b630594b7f 100644 --- a/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.Decompression.cs +++ b/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.Decompression.cs @@ -22,6 +22,11 @@ namespace System.Net.Http.Functional.Tests public abstract class HttpClientHandler_Decompression_Test : HttpClientHandlerTestBase { +#if !NETFRAMEWORK + private static readonly DecompressionMethods _all = DecompressionMethods.All; +#else + private static readonly DecompressionMethods _all = DecompressionMethods.Deflate | DecompressionMethods.GZip; +#endif public HttpClientHandler_Decompression_Test(ITestOutputHelper output) : base(output) { } public static IEnumerable RemoteServersAndCompressionUris() @@ -41,20 +46,22 @@ public static IEnumerable DecompressedResponse_MethodSpecified_Decompr { "deflate", new Func(s => new DeflateStream(s, CompressionLevel.Optimal, leaveOpen: true)), - specifyAllMethods ? DecompressionMethods.Deflate : DecompressionMethods.All + specifyAllMethods ? DecompressionMethods.Deflate : _all }; yield return new object[] { "gzip", new Func(s => new GZipStream(s, CompressionLevel.Optimal, leaveOpen: true)), - specifyAllMethods ? DecompressionMethods.GZip : DecompressionMethods.All + specifyAllMethods ? DecompressionMethods.GZip : _all }; +#if !NETFRAMEWORK yield return new object[] { "br", new Func(s => new BrotliStream(s, CompressionLevel.Optimal, leaveOpen: true)), - specifyAllMethods ? DecompressionMethods.Brotli : DecompressionMethods.All + specifyAllMethods ? DecompressionMethods.Brotli : _all }; +#endif } } @@ -102,6 +109,7 @@ public static IEnumerable DecompressedResponse_MethodNotSpecified_Orig new Func(s => new DeflateStream(s, CompressionLevel.Optimal, leaveOpen: true)), DecompressionMethods.None }; +#if !NETFRAMEWORK yield return new object[] { "gzip", @@ -114,6 +122,7 @@ public static IEnumerable DecompressedResponse_MethodNotSpecified_Orig new Func(s => new BrotliStream(s, CompressionLevel.Optimal, leaveOpen: true)), DecompressionMethods.Deflate | DecompressionMethods.GZip }; +#endif } [Theory] @@ -189,10 +198,12 @@ public async Task GetAsync_SetAutomaticDecompression_HeadersRemoved(Configuratio } [Theory] +#if NETCORE [InlineData(DecompressionMethods.Brotli, "br", "")] [InlineData(DecompressionMethods.Brotli, "br", "br")] [InlineData(DecompressionMethods.Brotli, "br", "gzip")] [InlineData(DecompressionMethods.Brotli, "br", "gzip, deflate")] +#endif [InlineData(DecompressionMethods.GZip, "gzip", "")] [InlineData(DecompressionMethods.Deflate, "deflate", "")] [InlineData(DecompressionMethods.GZip | DecompressionMethods.Deflate, "gzip, deflate", "")] diff --git a/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.MaxResponseHeadersLength.cs b/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.MaxResponseHeadersLength.cs index 7c6db382a0686..dd6daf3e35957 100644 --- a/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.MaxResponseHeadersLength.cs +++ b/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.MaxResponseHeadersLength.cs @@ -7,6 +7,7 @@ using System.Text; using System.Threading; using System.Threading.Tasks; +using Microsoft.DotNet.XUnitExtensions; using Xunit; using Xunit.Abstractions; @@ -46,9 +47,15 @@ public void ValidValue_SetGet_Roundtrips(int validValue) } } - [Fact] + [ConditionalFact] public async Task SetAfterUse_Throws() { +#if WINHTTPHANDLER_TEST + if (UseVersion >= HttpVersion20.Value) + { + throw new SkipTestException($"Test doesn't support {UseVersion} protocol."); + } +#endif await LoopbackServerFactory.CreateClientAndServerAsync(async uri => { using HttpClientHandler handler = CreateHttpClientHandler(); diff --git a/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.Proxy.cs b/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.Proxy.cs index ef7b570d16ce4..80ec9e25b244d 100644 --- a/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.Proxy.cs +++ b/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.Proxy.cs @@ -26,9 +26,15 @@ public abstract class HttpClientHandler_Proxy_Test : HttpClientHandlerTestBase { public HttpClientHandler_Proxy_Test(ITestOutputHelper output) : base(output) { } - [Fact] + [ConditionalFact] public async Task Dispose_HandlerWithProxy_ProxyNotDisposed() { +#if WINHTTPHANDLER_TEST + if (UseVersion >= HttpVersion20.Value) + { + throw new SkipTestException($"Test doesn't support {UseVersion} protocol."); + } +#endif var proxy = new TrackDisposalProxy(); await LoopbackServerFactory.CreateClientAndServerAsync(async uri => diff --git a/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.ServerCertificates.cs b/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.ServerCertificates.cs index 2a04997b80953..393c0241a1928 100644 --- a/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.ServerCertificates.cs +++ b/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.ServerCertificates.cs @@ -13,6 +13,7 @@ using System.Security.Cryptography.X509Certificates; using System.Threading.Tasks; using Microsoft.DotNet.RemoteExecutor; +using Microsoft.DotNet.XUnitExtensions; using Xunit; using Xunit.Abstractions; @@ -30,9 +31,15 @@ public abstract partial class HttpClientHandler_ServerCertificates_Test : HttpCl public HttpClientHandler_ServerCertificates_Test(ITestOutputHelper output) : base(output) { } - [Fact] + [ConditionalFact] public void Ctor_ExpectedDefaultValues() { +#if WINHTTPHANDLER_TEST + if (UseVersion > HttpVersion.Version11) + { + throw new SkipTestException($"Test doesn't support {UseVersion} protocol."); + } +#endif using (HttpClientHandler handler = CreateHttpClientHandler()) { Assert.Null(handler.ServerCertificateCustomValidationCallback); @@ -40,9 +47,16 @@ public void Ctor_ExpectedDefaultValues() } } - [Fact] + [ConditionalFact] public void ServerCertificateCustomValidationCallback_SetGet_Roundtrips() { +#if WINHTTPHANDLER_TEST + if (UseVersion > HttpVersion.Version11) + { + throw new SkipTestException($"Test doesn't support {UseVersion} protocol."); + } +#endif + using (HttpClientHandler handler = CreateHttpClientHandler()) { Assert.Null(handler.ServerCertificateCustomValidationCallback); diff --git a/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.SslProtocols.cs b/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.SslProtocols.cs index b6bf8b5143c2c..b264aa6bc6c5d 100644 --- a/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.SslProtocols.cs +++ b/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.SslProtocols.cs @@ -43,11 +43,13 @@ public void DefaultProtocols_MatchesExpected() [InlineData(SslProtocols.Tls11 | SslProtocols.Tls12)] [InlineData(SslProtocols.Tls | SslProtocols.Tls12)] [InlineData(SslProtocols.Tls | SslProtocols.Tls11 | SslProtocols.Tls12)] +#if !NETFRAMEWORK [InlineData(SslProtocols.Tls13)] [InlineData(SslProtocols.Tls11 | SslProtocols.Tls13)] [InlineData(SslProtocols.Tls12 | SslProtocols.Tls13)] [InlineData(SslProtocols.Tls | SslProtocols.Tls13)] [InlineData(SslProtocols.Tls | SslProtocols.Tls11 | SslProtocols.Tls12 | SslProtocols.Tls13)] +#endif public void SetGetProtocols_Roundtrips(SslProtocols protocols) { using (HttpClientHandler handler = CreateHttpClientHandler()) @@ -90,7 +92,9 @@ public static IEnumerable GetAsync_AllowedSSLVersion_Succeeds_MemberDa #pragma warning disable 0618 if (PlatformDetection.SupportsSsl3) { +#if !NETFRAMEWORK yield return new object[] { SslProtocols.Ssl3, true }; +#endif } if (PlatformDetection.IsWindows && !PlatformDetection.IsWindows10Version1607OrGreater) { @@ -100,8 +104,10 @@ public static IEnumerable GetAsync_AllowedSSLVersion_Succeeds_MemberDa // These protocols are new, and might not be enabled everywhere yet if (PlatformDetection.IsUbuntu1810OrHigher) { +#if !NETFRAMEWORK yield return new object[] { SslProtocols.Tls13, false }; yield return new object[] { SslProtocols.Tls13, true }; +#endif } } @@ -124,7 +130,11 @@ public async Task GetAsync_AllowedSSLVersion_Succeeds(SslProtocols acceptedProto // restrictions on minimum TLS/SSL version // We currently know that some platforms like Debian 10 OpenSSL // will by default block < TLS 1.2 +#if !NETFRAMEWORK handler.SslProtocols = SslProtocols.Tls | SslProtocols.Tls11 | SslProtocols.Tls12 | SslProtocols.Tls13; +#else + handler.SslProtocols = SslProtocols.Tls | SslProtocols.Tls11 | SslProtocols.Tls12; +#endif } var options = new LoopbackServer.Options { UseSsl = true, SslProtocols = acceptedProtocol }; diff --git a/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.cs b/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.cs index 671401f92852c..a866a5d076a99 100644 --- a/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.cs +++ b/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.cs @@ -226,6 +226,12 @@ public async Task SendAsync_SimpleGet_Success(Configuration.Http.RemoteServer re [ConditionalFact] public async Task GetAsync_IPv6LinkLocalAddressUri_Success() { +#if WINHTTPHANDLER_TEST + if (UseVersion >= HttpVersion20.Value) + { + throw new SkipTestException($"Test doesn't support {UseVersion} protocol."); + } +#endif using (HttpClient client = CreateHttpClient()) { var options = new GenericLoopbackOptions { Address = TestHelper.GetIPv6LinkLocalAddress() }; @@ -244,10 +250,16 @@ public async Task GetAsync_IPv6LinkLocalAddressUri_Success() } } - [Theory] + [ConditionalTheory] [MemberData(nameof(GetAsync_IPBasedUri_Success_MemberData))] public async Task GetAsync_IPBasedUri_Success(IPAddress address) { +#if WINHTTPHANDLER_TEST + if (UseVersion >= HttpVersion20.Value) + { + throw new SkipTestException($"Test doesn't support {UseVersion} protocol."); + } +#endif using (HttpClient client = CreateHttpClient()) { var options = new GenericLoopbackOptions { Address = address }; @@ -479,7 +491,7 @@ public async Task ProxyTunnelRequest_UserAgentHeaderAdded(bool addUserAgentHeade [MemberData(nameof(SecureAndNonSecure_IPBasedUri_MemberData))] public async Task GetAsync_SecureAndNonSecureIPBasedUri_CorrectlyFormatted(IPAddress address, bool useSsl) { - if (LoopbackServerFactory.Version >= HttpVersion.Version20) + if (LoopbackServerFactory.Version >= HttpVersion20.Value) { throw new SkipTestException("Host header is not supported on HTTP/2 and later."); } @@ -556,11 +568,17 @@ public async Task GetAsync_ServerNeedsAuthAndNoCredential_StatusCodeUnauthorized } } - [Theory] + [ConditionalTheory] [InlineData("WWW-Authenticate", "CustomAuth")] [InlineData("", "")] // RFC7235 requires servers to send this header with 401 but some servers don't. public async Task GetAsync_ServerNeedsNonStandardAuthAndSetCredential_StatusCodeUnauthorized(string authHeadrName, string authHeaderValue) { +#if WINHTTPHANDLER_TEST + if (UseVersion >= HttpVersion20.Value) + { + throw new SkipTestException($"Test doesn't support {UseVersion} protocol."); + } +#endif await LoopbackServerFactory.CreateServerAsync(async (server, url) => { HttpClientHandler handler = CreateHttpClientHandler(); @@ -735,9 +753,15 @@ public async Task GetAsync_IncompleteData_ThrowsHttpRequestException(bool failDu server.AcceptConnectionSendCustomResponseAndCloseAsync("HTTP/1.1 200 OK\r\nContent-Length: 5\r\n\r\nhe")); } - [Fact] + [ConditionalFact] public async Task PostAsync_ManyDifferentRequestHeaders_SentCorrectly() { +#if WINHTTPHANDLER_TEST + if (UseVersion > HttpVersion.Version11) + { + throw new SkipTestException($"Test doesn't support {UseVersion} protocol."); + } +#endif const string content = "hello world"; // Using examples from https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#Request_fields @@ -871,7 +895,7 @@ public async Task PostAsync_ManyDifferentRequestHeaders_SentCorrectly() Assert.Equal("X-Underscore_Name", requestData.GetSingleHeaderValue("X-Underscore_Name")); Assert.Equal("End", requestData.GetSingleHeaderValue("X-End")); - if (LoopbackServerFactory.Version >= HttpVersion.Version20) + if (LoopbackServerFactory.Version >= HttpVersion20.Value) { // HTTP/2 and later forbids certain headers or values. Assert.Equal("trailers", requestData.GetSingleHeaderValue("TE")); @@ -904,7 +928,7 @@ public async Task PostAsync_ManyDifferentRequestHeaders_SentCorrectly() [MemberData(nameof(GetAsync_ManyDifferentResponseHeaders_ParsedCorrectly_MemberData))] public async Task GetAsync_ManyDifferentResponseHeaders_ParsedCorrectly(string newline, string fold, bool dribble) { - if (LoopbackServerFactory.Version >= HttpVersion.Version20) + if (LoopbackServerFactory.Version >= HttpVersion20.Value) { throw new SkipTestException("Folding is not supported on HTTP/2 and later."); } @@ -1036,7 +1060,7 @@ public async Task GetAsync_ManyDifferentResponseHeaders_ParsedCorrectly(string n [ConditionalFact] public async Task GetAsync_NonTraditionalChunkSizes_Accepted() { - if (LoopbackServerFactory.Version >= HttpVersion.Version20) + if (LoopbackServerFactory.Version >= HttpVersion20.Value) { throw new SkipTestException("Chunking is not supported on HTTP/2 and later."); } @@ -1260,7 +1284,13 @@ public async Task SendAsync_ReadFromSlowStreamingServer_PartialDataReturned() [InlineData(null)] public async Task ReadAsStreamAsync_HandlerProducesWellBehavedResponseStream(bool? chunked) { - if (LoopbackServerFactory.Version >= HttpVersion.Version20 && chunked == true) +#if WINHTTPHANDLER_TEST + if (UseVersion >= HttpVersion20.Value) + { + throw new SkipTestException($"Test doesn't support {UseVersion} protocol."); + } +#endif + if (LoopbackServerFactory.Version >= HttpVersion20.Value && chunked == true) { throw new SkipTestException("Chunking is not supported on HTTP/2 and later."); } @@ -1288,8 +1318,10 @@ public async Task ReadAsStreamAsync_HandlerProducesWellBehavedResponseStream(boo Assert.Throws(() => responseStream.Seek(0, SeekOrigin.Begin)); Assert.Throws(() => responseStream.SetLength(0)); Assert.Throws(() => responseStream.Write(new byte[1], 0, 1)); +#if !NETFRAMEWORK Assert.Throws(() => responseStream.Write(new Span(new byte[1]))); Assert.Throws(() => { responseStream.WriteAsync(new Memory(new byte[1])); }); +#endif Assert.Throws(() => { responseStream.WriteAsync(new byte[1], 0, 1); }); Assert.Throws(() => responseStream.WriteByte(1)); @@ -1329,13 +1361,21 @@ public async Task ReadAsStreamAsync_HandlerProducesWellBehavedResponseStream(boo Assert.Equal(1, await Task.Factory.FromAsync(responseStream.BeginRead, responseStream.EndRead, buffer, 0, 1, null)); Assert.Equal((byte)'e', buffer[0]); +#if !NETFRAMEWORK Assert.Equal(1, await responseStream.ReadAsync(new Memory(buffer))); +#else + Assert.Equal(1, await responseStream.ReadAsync(buffer, 0, 1)); +#endif Assert.Equal((byte)'l', buffer[0]); Assert.Equal(1, await responseStream.ReadAsync(buffer, 0, 1)); Assert.Equal((byte)'l', buffer[0]); +#if !NETFRAMEWORK Assert.Equal(1, responseStream.Read(new Span(buffer))); +#else + Assert.Equal(1, await responseStream.ReadAsync(buffer, 0, 1)); +#endif Assert.Equal((byte)'o', buffer[0]); Assert.Equal(1, responseStream.Read(buffer, 0, 1)); @@ -1343,9 +1383,13 @@ public async Task ReadAsStreamAsync_HandlerProducesWellBehavedResponseStream(boo // Doing any of these 0-byte reads causes the connection to fail. Assert.Equal(0, await Task.Factory.FromAsync(responseStream.BeginRead, responseStream.EndRead, Array.Empty(), 0, 0, null)); +#if !NETFRAMEWORK Assert.Equal(0, await responseStream.ReadAsync(Memory.Empty)); +#endif Assert.Equal(0, await responseStream.ReadAsync(Array.Empty(), 0, 0)); +#if !NETFRAMEWORK Assert.Equal(0, responseStream.Read(Span.Empty)); +#endif Assert.Equal(0, responseStream.Read(Array.Empty(), 0, 0)); // And copying @@ -1360,9 +1404,13 @@ public async Task ReadAsStreamAsync_HandlerProducesWellBehavedResponseStream(boo Assert.Equal(0, ms.Length); Assert.Equal(-1, responseStream.ReadByte()); Assert.Equal(0, responseStream.Read(buffer, 0, 1)); +#if !NETFRAMEWORK Assert.Equal(0, responseStream.Read(new Span(buffer))); +#endif Assert.Equal(0, await responseStream.ReadAsync(buffer, 0, 1)); +#if !NETFRAMEWORK Assert.Equal(0, await responseStream.ReadAsync(new Memory(buffer))); +#endif Assert.Equal(0, await Task.Factory.FromAsync(responseStream.BeginRead, responseStream.EndRead, buffer, 0, 1, null)); } } @@ -1392,9 +1440,15 @@ public async Task ReadAsStreamAsync_HandlerProducesWellBehavedResponseStream(boo }); } - [Fact] + [ConditionalFact] public async Task ReadAsStreamAsync_EmptyResponseBody_HandlerProducesWellBehavedResponseStream() { +#if WINHTTPHANDLER_TEST + if (UseVersion >= HttpVersion20.Value) + { + throw new SkipTestException($"Test doesn't support {UseVersion} protocol."); + } +#endif await LoopbackServerFactory.CreateClientAndServerAsync(async uri => { using (var client = new HttpMessageInvoker(CreateHttpClientHandler())) @@ -1417,8 +1471,10 @@ public async Task ReadAsStreamAsync_EmptyResponseBody_HandlerProducesWellBehaved Assert.Throws(() => responseStream.Seek(0, SeekOrigin.Begin)); Assert.Throws(() => responseStream.SetLength(0)); Assert.Throws(() => responseStream.Write(new byte[1], 0, 1)); +#if !NETFRAMEWORK Assert.Throws(() => responseStream.Write(new Span(new byte[1]))); await Assert.ThrowsAsync(async () => await responseStream.WriteAsync(new Memory(new byte[1]))); +#endif await Assert.ThrowsAsync(async () => await responseStream.WriteAsync(new byte[1], 0, 1)); Assert.Throws(() => responseStream.WriteByte(1)); @@ -1455,9 +1511,13 @@ public async Task ReadAsStreamAsync_EmptyResponseBody_HandlerProducesWellBehaved var buffer = new byte[1]; Assert.Equal(-1, responseStream.ReadByte()); Assert.Equal(0, await Task.Factory.FromAsync(responseStream.BeginRead, responseStream.EndRead, buffer, 0, 1, null)); +#if !NETFRAMEWORK Assert.Equal(0, await responseStream.ReadAsync(new Memory(buffer))); +#endif Assert.Equal(0, await responseStream.ReadAsync(buffer, 0, 1)); +#if !NETFRAMEWORK Assert.Equal(0, responseStream.Read(new Span(buffer))); +#endif Assert.Equal(0, responseStream.Read(buffer, 0, 1)); // Empty copies @@ -1471,10 +1531,15 @@ public async Task ReadAsStreamAsync_EmptyResponseBody_HandlerProducesWellBehaved }, server => server.AcceptConnectionSendResponseAndCloseAsync()); } - - [Fact] + [ConditionalFact] public async Task Dispose_DisposingHandlerCancelsActiveOperationsWithoutResponses() { +#if WINHTTPHANDLER_TEST + if (UseVersion >= HttpVersion20.Value) + { + throw new SkipTestException($"Test doesn't support {UseVersion} protocol."); + } +#endif await LoopbackServerFactory.CreateServerAsync(async (server1, url1) => { await LoopbackServerFactory.CreateServerAsync(async (server2, url2) => @@ -1837,9 +1902,15 @@ public async Task PostAsync_ExpectContinue_Success(bool? expectContinue, string } } - [Fact] + [ConditionalFact] public async Task GetAsync_ExpectContinueTrue_NoContent_StillSendsHeader() { +#if WINHTTPHANDLER_TEST + if (UseVersion >= HttpVersion20.Value) + { + throw new SkipTestException($"Test doesn't support {UseVersion} protocol."); + } +#endif const string ExpectedContent = "Hello, expecting and continuing world."; var clientCompleted = new TaskCompletionSource(); await LoopbackServerFactory.CreateClientAndServerAsync(async uri => @@ -1876,10 +1947,16 @@ public static IEnumerable Interim1xxStatusCode() yield return new object[] { (HttpStatusCode) 199 }; } - [Theory] + [ConditionalTheory] [MemberData(nameof(Interim1xxStatusCode))] public async Task SendAsync_1xxResponsesWithHeaders_InterimResponsesHeadersIgnored(HttpStatusCode responseStatusCode) { +#if WINHTTPHANDLER_TEST + if (UseVersion >= HttpVersion20.Value) + { + throw new SkipTestException($"Test doesn't support {UseVersion} protocol."); + } +#endif var clientFinished = new TaskCompletionSource(); const string TestString = "test"; const string CookieHeaderExpected = "yummy_cookie=choco"; @@ -1940,10 +2017,16 @@ public async Task SendAsync_1xxResponsesWithHeaders_InterimResponsesHeadersIgnor }); } - [Theory] + [ConditionalTheory] [MemberData(nameof(Interim1xxStatusCode))] public async Task SendAsync_Unexpected1xxResponses_DropAllInterimResponses(HttpStatusCode responseStatusCode) { +#if WINHTTPHANDLER_TEST + if (UseVersion >= HttpVersion20.Value) + { + throw new SkipTestException($"Test doesn't support {UseVersion} protocol."); + } +#endif var clientFinished = new TaskCompletionSource(); const string TestString = "test"; @@ -1980,9 +2063,15 @@ public async Task SendAsync_Unexpected1xxResponses_DropAllInterimResponses(HttpS }); } - [Fact] + [ConditionalFact] public async Task SendAsync_MultipleExpected100Responses_ReceivesCorrectResponse() { +#if WINHTTPHANDLER_TEST + if (UseVersion >= HttpVersion20.Value) + { + throw new SkipTestException($"Test doesn't support {UseVersion} protocol."); + } +#endif var clientFinished = new TaskCompletionSource(); const string TestString = "test"; @@ -2019,9 +2108,15 @@ public async Task SendAsync_MultipleExpected100Responses_ReceivesCorrectResponse }); } - [Fact] + [ConditionalFact] public async Task SendAsync_No100ContinueReceived_RequestBodySentEventually() { +#if WINHTTPHANDLER_TEST + if (UseVersion >= HttpVersion20.Value) + { + throw new SkipTestException($"Test doesn't support {UseVersion} protocol."); + } +#endif var clientFinished = new TaskCompletionSource(); const string RequestString = "request"; const string ResponseString = "response"; @@ -2069,7 +2164,7 @@ public async Task SendAsync_101SwitchingProtocolsResponse_Success() return; } - if (LoopbackServerFactory.Version >= HttpVersion.Version20) + if (LoopbackServerFactory.Version >= HttpVersion20.Value) { throw new SkipTestException("Upgrade is not supported on HTTP/2 and later"); } @@ -2215,6 +2310,7 @@ public async Task PostAsync_Redirect_LargePayload_Helper(Configuration.Http.Remo } } +#if !NETFRAMEWORK [OuterLoop("Uses external server")] [Theory, MemberData(nameof(RemoteServersMemberData))] public async Task PostAsync_ReuseRequestContent_Success(Configuration.Http.RemoteServer remoteServer) @@ -2233,13 +2329,14 @@ public async Task PostAsync_ReuseRequestContent_Success(Configuration.Http.Remot } } } +#endif [Theory] [InlineData(HttpStatusCode.MethodNotAllowed, "Custom description")] [InlineData(HttpStatusCode.MethodNotAllowed, "")] public async Task GetAsync_CallMethod_ExpectedStatusLine(HttpStatusCode statusCode, string reasonPhrase) { - if (LoopbackServerFactory.Version >= HttpVersion.Version20) + if (LoopbackServerFactory.Version >= HttpVersion20.Value) { // Custom messages are not supported on HTTP2 and later. return; @@ -2386,6 +2483,12 @@ public async Task SendAsync_SendSameRequestMultipleTimesDirectlyOnHandler_Succes [Fact] public async Task SendAsync_RequestVersion10_ServerReceivesVersion10Request() { + // Test is not supported for WinHttpHandler and HTTP/2 + if(IsWinHttpHandler && UseVersion >= HttpVersion20.Value) + { + return; + } + Version receivedRequestVersion = await SendRequestAndGetRequestVersionAsync(new Version(1, 0)); Assert.Equal(new Version(1, 0), receivedRequestVersion); } @@ -2440,9 +2543,15 @@ public async Task SendAsync_RequestVersion20_ResponseVersion20IfHttp2Supported(U } } - [Fact] + [ConditionalFact] public async Task SendAsync_RequestVersion20_HttpNotHttps_NoUpgradeRequest() { +#if WINHTTPHANDLER_TEST + if (UseVersion >= HttpVersion20.Value) + { + throw new SkipTestException($"Test doesn't support {UseVersion} protocol."); + } +#endif await LoopbackServerFactory.CreateClientAndServerAsync(async uri => { using (HttpClient client = CreateHttpClient()) @@ -2514,12 +2623,18 @@ private async Task SendRequestAndGetRequestVersionAsync(Version request return receivedRequestVersion; } -#endregion + #endregion -#region Uri wire transmission encoding tests - [Fact] + #region Uri wire transmission encoding tests + [ConditionalFact] public async Task SendRequest_UriPathHasReservedChars_ServerReceivedExpectedPath() { +#if WINHTTPHANDLER_TEST + if (UseVersion >= HttpVersion20.Value) + { + throw new SkipTestException($"Test doesn't support {UseVersion} protocol."); + } +#endif await LoopbackServerFactory.CreateServerAsync(async (server, rootUrl) => { var uri = new Uri($"{rootUrl.Scheme}://{rootUrl.Host}:{rootUrl.Port}/test[]"); diff --git a/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTestBase.cs b/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTestBase.cs index 83308747801bf..003e344d74042 100644 --- a/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTestBase.cs +++ b/src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTestBase.cs @@ -32,13 +32,21 @@ public HttpClientHandlerTestBase(ITestOutputHelper output) protected virtual HttpClient CreateHttpClient() => CreateHttpClient(CreateHttpClientHandler()); protected HttpClient CreateHttpClient(HttpMessageHandler handler) => - new HttpClient(handler) { DefaultRequestVersion = UseVersion }; + new HttpClient(handler) { +#if !NETFRAMEWORK + DefaultRequestVersion = UseVersion +#endif + }; protected static HttpClient CreateHttpClient(string useVersionString) => CreateHttpClient(CreateHttpClientHandler(useVersionString), useVersionString); protected static HttpClient CreateHttpClient(HttpMessageHandler handler, string useVersionString) => - new HttpClient(handler) { DefaultRequestVersion = Version.Parse(useVersionString) }; + new HttpClient(handler) { +#if !NETFRAMEWORK + DefaultRequestVersion = Version.Parse(useVersionString) +#endif + }; protected HttpClientHandler CreateHttpClientHandler() => CreateHttpClientHandler(UseVersion); @@ -51,7 +59,7 @@ protected static LoopbackServerFactory GetFactoryForVersion(Version useVersion) { return useVersion.Major switch { -#if NETCOREAPP +#if NETCOREAPP || WINHTTPHANDLER_TEST #if HTTP3 3 => Http3LoopbackServerFactory.Singleton, #endif @@ -81,7 +89,11 @@ protected HttpClient CreateHttpClientForRemoteServer(Configuration.Http.RemoteSe wrappedHandler = new VersionCheckerHttpHandler(httpClientHandler, remoteServer.HttpVersion); } - return new HttpClient(wrappedHandler) { DefaultRequestVersion = remoteServer.HttpVersion }; + return new HttpClient(wrappedHandler) { +#if !NETFRAMEWORK + DefaultRequestVersion = remoteServer.HttpVersion +#endif + }; } private sealed class VersionCheckerHttpHandler : DelegatingHandler diff --git a/src/libraries/Common/tests/System/Net/Http/HttpProtocolTests.cs b/src/libraries/Common/tests/System/Net/Http/HttpProtocolTests.cs index 62a46c21b0afc..635cb2bed65c1 100644 --- a/src/libraries/Common/tests/System/Net/Http/HttpProtocolTests.cs +++ b/src/libraries/Common/tests/System/Net/Http/HttpProtocolTests.cs @@ -20,9 +20,15 @@ public abstract class HttpProtocolTests : HttpClientHandlerTestBase public HttpProtocolTests(ITestOutputHelper output) : base(output) { } - [Fact] + [ConditionalFact] public async Task GetAsync_RequestVersion10_Success() { +#if WINHTTPHANDLER_TEST + if (UseVersion > HttpVersion.Version11) + { + throw new SkipTestException($"Test doesn't support {UseVersion} protocol."); + } +#endif await LoopbackServer.CreateServerAsync(async (server, url) => { using (HttpClient client = CreateHttpClient()) @@ -454,7 +460,7 @@ public async Task GetAsync_Chunked_VaryingSizeChunks_ReceivedCorrectly(int maxCh await LoopbackServer.CreateClientAndServerAsync(async uri => { using (HttpMessageInvoker client = new HttpMessageInvoker(CreateHttpClientHandler())) - using (HttpResponseMessage resp = await client.SendAsync(new HttpRequestMessage(HttpMethod.Get, uri) { Version = UseVersion }, CancellationToken.None)) + using (HttpResponseMessage resp = await client.SendAsync(new HttpRequestMessage(HttpMethod.Get, uri) { Version = base.UseVersion }, CancellationToken.None)) using (Stream respStream = await resp.Content.ReadAsStreamAsync()) { var actualData = new MemoryStream(); @@ -467,7 +473,11 @@ public async Task GetAsync_Chunked_VaryingSizeChunks_ReceivedCorrectly(int maxCh { byte[] buffer = new byte[4096]; int bytesRead; +#if !NETFRAMEWORK while ((bytesRead = await respStream.ReadAsync(buffer)) > 0) +#else + while ((bytesRead = await respStream.ReadAsync(buffer, 0, buffer.Length)) > 0) +#endif { actualData.Write(buffer, 0, bytesRead); } diff --git a/src/libraries/Common/tests/System/Net/Http/LoopbackServer.cs b/src/libraries/Common/tests/System/Net/Http/LoopbackServer.cs index 6433f539c0dc6..48d75c889b171 100644 --- a/src/libraries/Common/tests/System/Net/Http/LoopbackServer.cs +++ b/src/libraries/Common/tests/System/Net/Http/LoopbackServer.cs @@ -394,7 +394,7 @@ public Options() { UseSsl = false; SslProtocols = -#if !NETSTANDARD2_0 +#if !NETSTANDARD2_0 && !NETFRAMEWORK SslProtocols.Tls13 | #endif SslProtocols.Tls | SslProtocols.Tls11 | SslProtocols.Tls12; @@ -451,6 +451,11 @@ public async Task ReadAsync(Memory buffer, int offset, int size) } return readLength; +#elif NETFRAMEWORK + var tmpBuffer = new byte[buffer.Length]; + int readBytes = await _stream.ReadAsync(tmpBuffer, offset, size).ConfigureAwait(false); + tmpBuffer.CopyTo(buffer); + return readBytes; #else return await _stream.ReadAsync(buffer.Slice(offset, size)).ConfigureAwait(false); #endif diff --git a/src/libraries/Common/tests/System/Net/Http/PostScenarioTest.cs b/src/libraries/Common/tests/System/Net/Http/PostScenarioTest.cs index 733b29f0c6d6b..34de8fffac58f 100644 --- a/src/libraries/Common/tests/System/Net/Http/PostScenarioTest.cs +++ b/src/libraries/Common/tests/System/Net/Http/PostScenarioTest.cs @@ -28,6 +28,7 @@ public abstract class PostScenarioTest : HttpClientHandlerTestBase public PostScenarioTest(ITestOutputHelper output) : base(output) { } +#if !NETFRAMEWORK [OuterLoop("Uses external servers")] [Theory, MemberData(nameof(RemoteServersMemberData))] public async Task PostRewindableStreamContentMultipleTimes_StreamContentFullySent(Configuration.Http.RemoteServer remoteServer) @@ -50,6 +51,7 @@ public async Task PostRewindableStreamContentMultipleTimes_StreamContentFullySen } } } +#endif [OuterLoop("Uses external servers")] [Theory, MemberData(nameof(RemoteServersMemberData))] diff --git a/src/libraries/Common/tests/System/Net/Http/QPackTestDecoder.cs b/src/libraries/Common/tests/System/Net/Http/QPackTestDecoder.cs index da276e1ce9d65..432b868b3352a 100644 --- a/src/libraries/Common/tests/System/Net/Http/QPackTestDecoder.cs +++ b/src/libraries/Common/tests/System/Net/Http/QPackTestDecoder.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System.Linq; using System.Numerics; using System.Text; @@ -72,7 +73,12 @@ private static (int bytesConsumed, string value) DecodeString(ReadOnlySpan } (int varIntLength, int stringLength) = DecodeInteger(buffer, prefixMask); - string value = Encoding.ASCII.GetString(buffer.Slice(varIntLength, stringLength)); +#if !NETFRAMEWORK + ReadOnlySpan bytes = buffer.Slice(varIntLength, stringLength); +#else + byte[] bytes = buffer.Slice(varIntLength, stringLength).ToArray(); +#endif + string value = Encoding.ASCII.GetString(bytes); return (varIntLength + stringLength, value); } @@ -203,6 +209,32 @@ public static (int bytesConsumed, int value) DecodeInteger(ReadOnlySpan he new HttpHeaderData("x-frame-options", "deny"), new HttpHeaderData("x-frame-options", "sameorigin"), }; - } +#if NETFRAMEWORK + private static class BitOperations + { + public static int LeadingZeroCount(byte value) + { + int count = 0; + while ((value & 0b1000_0000) != 0) + { + count++; + value <<= 1; + } + return count; + } + + public static int TrailingZeroCount(int value) + { + int count = 0; + while ((value & 1) != 0) + { + count++; + value >>= 1; + } + return count; + } + } +#endif + } } diff --git a/src/libraries/Common/tests/System/Net/Http/ResponseStreamTest.cs b/src/libraries/Common/tests/System/Net/Http/ResponseStreamTest.cs index 8df08a7282145..67812f9cb73f4 100644 --- a/src/libraries/Common/tests/System/Net/Http/ResponseStreamTest.cs +++ b/src/libraries/Common/tests/System/Net/Http/ResponseStreamTest.cs @@ -80,7 +80,11 @@ public async Task GetStreamAsync_ReadToEnd_Success(Configuration.Http.RemoteServ case 4: // Individual calls to Read(Span) +#if !NETFRAMEWORK while ((bytesRead = stream.Read(new Span(buffer))) != 0) +#else + while ((bytesRead = stream.Read(buffer, 0, buffer.Length)) != 0) +#endif { ms.Write(buffer, 0, bytesRead); } @@ -173,7 +177,9 @@ public async Task GetStreamAsync_ReadZeroBytes_Success(Configuration.Http.Remote using (Stream stream = await client.GetStreamAsync(remoteServer.EchoUri)) { Assert.Equal(0, stream.Read(new byte[1], 0, 0)); +#if !NETFRAMEWORK Assert.Equal(0, stream.Read(new Span(new byte[1], 0, 0))); +#endif Assert.Equal(0, await stream.ReadAsync(new byte[1], 0, 0)); } } @@ -221,7 +227,7 @@ await client.GetAsync(remoteServer.EchoUri, HttpCompletionOption.ResponseHeaders } } } - +#if NETCOREAPP [Theory] [InlineData(TransferType.ContentLength, TransferError.ContentLengthTooLarge)] [InlineData(TransferType.Chunked, TransferError.MissingChunkTerminator)] @@ -249,6 +255,7 @@ await client.GetAsync(remoteServer.EchoUri, HttpCompletionOption.ResponseHeaders await ReadAsStreamHelper(uri); }); } +#endif public enum TransferType { diff --git a/src/libraries/Common/tests/System/Net/StreamArrayExtensions.cs b/src/libraries/Common/tests/System/Net/StreamArrayExtensions.cs new file mode 100644 index 0000000000000..0d21fd2cab84c --- /dev/null +++ b/src/libraries/Common/tests/System/Net/StreamArrayExtensions.cs @@ -0,0 +1,36 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.IO; +using System.Runtime.InteropServices; +using System.Threading; +using System.Threading.Tasks; +using Xunit; + +namespace System.Net +{ + public static class StreamArrayExtensions + { + public static ValueTask WriteAsync(this Stream stream, ReadOnlyMemory memory) + { + bool isArray = MemoryMarshal.TryGetArray(memory, out ArraySegment segment); + Assert.True(isArray); + + return new ValueTask(stream.WriteAsync(segment.Array, segment.Offset, segment.Count)); + } + + public static ValueTask WriteAsync(this StreamWriter writer, string text) + { + return new ValueTask(writer.WriteAsync(text.ToCharArray(), 0, text.Length)); + } + + public static ValueTask ReadAsync(this Stream stream, ReadOnlyMemory buffer, CancellationToken cancellationToken) + { + bool isArray = MemoryMarshal.TryGetArray(buffer, out ArraySegment segment); + Assert.True(isArray); + + return new ValueTask(stream.ReadAsync(segment.Array, segment.Offset, segment.Count, cancellationToken)); + } + } +} diff --git a/src/libraries/System.Net.Http.WinHttpHandler/src/System/Net/Http/WinHttpHandler.cs b/src/libraries/System.Net.Http.WinHttpHandler/src/System/Net/Http/WinHttpHandler.cs index 6913359c608e5..8219a438e72be 100644 --- a/src/libraries/System.Net.Http.WinHttpHandler/src/System/Net/Http/WinHttpHandler.cs +++ b/src/libraries/System.Net.Http.WinHttpHandler/src/System/Net/Http/WinHttpHandler.cs @@ -786,6 +786,8 @@ private async Task StartRequestAsync(WinHttpRequestState state) { EnsureSessionHandleExists(state); + SetEnableHttp2PlusClientCertificate(state.RequestMessage.RequestUri, state.RequestMessage.Version); + // Specify an HTTP server. connectHandle = Interop.WinHttp.WinHttpConnect( _sessionHandle, @@ -996,7 +998,7 @@ private void SetRequestHandleOptions(WinHttpRequestState state) SetRequestHandleRedirectionOptions(state.RequestHandle); SetRequestHandleCookieOptions(state.RequestHandle); SetRequestHandleTlsOptions(state.RequestHandle); - SetRequestHandleClientCertificateOptions(state.RequestHandle, state.RequestMessage.RequestUri); + SetRequestHandleClientCertificateOptions(state.RequestHandle, state.RequestMessage.RequestUri, state.RequestMessage.Version); SetRequestHandleCredentialsOptions(state); SetRequestHandleBufferingOptions(state.RequestHandle); SetRequestHandleHttp2Options(state.RequestHandle, state.RequestMessage.Version); @@ -1153,7 +1155,7 @@ private void SetRequestHandleTlsOptions(SafeWinHttpHandle requestHandle) } } - private void SetRequestHandleClientCertificateOptions(SafeWinHttpHandle requestHandle, Uri requestUri) + private void SetRequestHandleClientCertificateOptions(SafeWinHttpHandle requestHandle, Uri requestUri, Version requestVersion) { if (requestUri.Scheme != UriScheme.Https) { @@ -1184,6 +1186,29 @@ private void SetRequestHandleClientCertificateOptions(SafeWinHttpHandle requestH } } + private void SetEnableHttp2PlusClientCertificate(Uri requestUri, Version requestVersion) + { + if (requestUri.Scheme != UriScheme.Https || requestVersion != HttpVersion20) + { + return; + } + + // Newer versions of WinHTTP fully support HTTP/2 with TLS client certificates. + // But the support must be opted in. + uint optionData = Interop.WinHttp.WINHTTP_HTTP2_PLUS_CLIENT_CERT_FLAG; + if (Interop.WinHttp.WinHttpSetOption( + _sessionHandle, + Interop.WinHttp.WINHTTP_OPTION_ENABLE_HTTP2_PLUS_CLIENT_CERT, + ref optionData)) + { + if (NetEventSource.IsEnabled) NetEventSource.Info(this, "HTTP/2 with TLS client cert supported"); + } + else + { + if (NetEventSource.IsEnabled) NetEventSource.Info(this, "HTTP/2 with TLS client cert not supported"); + } + } + internal static void SetNoClientCertificate(SafeWinHttpHandle requestHandle) { SetWinHttpOption( diff --git a/src/libraries/System.Net.Http.WinHttpHandler/tests/FunctionalTests/BaseCertificateTest.cs b/src/libraries/System.Net.Http.WinHttpHandler/tests/FunctionalTests/BaseCertificateTest.cs new file mode 100644 index 0000000000000..58ebefb99dc54 --- /dev/null +++ b/src/libraries/System.Net.Http.WinHttpHandler/tests/FunctionalTests/BaseCertificateTest.cs @@ -0,0 +1,89 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Net.Security; +using System.Security.Cryptography.X509Certificates; +using Xunit; +using Xunit.Abstractions; + +namespace System.Net.Http.WinHttpHandlerFunctional.Tests +{ + public abstract class BaseCertificateTest + { + private readonly ITestOutputHelper _output; + + protected readonly ValidationCallbackHistory _validationCallbackHistory; + + public BaseCertificateTest(ITestOutputHelper output) + { + _output = output; + _validationCallbackHistory = new ValidationCallbackHistory(); + } + + public class ValidationCallbackHistory + { + public bool ThrowException; + public bool ReturnFailure; + public bool WasCalled; + public SslPolicyErrors SslPolicyErrors; + public string CertificateSubject; + public X509CertificateCollection CertificateChain; + public X509ChainStatus[] ChainStatus; + + public ValidationCallbackHistory() + { + ThrowException = false; + ReturnFailure = false; + WasCalled = false; + SslPolicyErrors = SslPolicyErrors.None; + CertificateSubject = null; + CertificateChain = new X509CertificateCollection(); + ChainStatus = null; + } + } + + protected bool CustomServerCertificateValidationCallback( + HttpRequestMessage sender, + X509Certificate2 certificate, + X509Chain chain, + SslPolicyErrors sslPolicyErrors) + { + _validationCallbackHistory.WasCalled = true; + _validationCallbackHistory.CertificateSubject = certificate.Subject; + foreach (var element in chain.ChainElements) + { + _validationCallbackHistory.CertificateChain.Add(element.Certificate); + } + _validationCallbackHistory.ChainStatus = chain.ChainStatus; + _validationCallbackHistory.SslPolicyErrors = sslPolicyErrors; + + if (_validationCallbackHistory.ThrowException) + { + throw new CustomException(); + } + + if (_validationCallbackHistory.ReturnFailure) + { + return false; + } + + return true; + } + + protected void ConfirmValidCertificate(string expectedHostName) + { + Assert.Equal(SslPolicyErrors.None, _validationCallbackHistory.SslPolicyErrors); + Assert.True(_validationCallbackHistory.CertificateChain.Count > 0); + _output.WriteLine("Certificate.Subject: {0}", _validationCallbackHistory.CertificateSubject); + _output.WriteLine("Expected HostName: {0}", expectedHostName); + } + + public class CustomException : Exception + { + public CustomException() + { + } + } + } +} diff --git a/src/libraries/System.Net.Http.WinHttpHandler/tests/FunctionalTests/ClientCertificateTest.cs b/src/libraries/System.Net.Http.WinHttpHandler/tests/FunctionalTests/ClientCertificateTest.cs new file mode 100644 index 0000000000000..9dfdeefe8a9a9 --- /dev/null +++ b/src/libraries/System.Net.Http.WinHttpHandler/tests/FunctionalTests/ClientCertificateTest.cs @@ -0,0 +1,119 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Net.Security; +using System.Net.Test.Common; +using System.Security.Cryptography.X509Certificates; +using System.Threading; +using System.Threading.Tasks; +using Newtonsoft.Json; +using Xunit; +using Xunit.Abstractions; + +namespace System.Net.Http.WinHttpHandlerFunctional.Tests +{ + public class ClientCertificateTest : BaseCertificateTest + { + public static bool DowngradeToHTTP1IfClientCertSet => PlatformDetection.WindowsVersion < 2004; + + public ClientCertificateTest(ITestOutputHelper output) : base(output) + { } + + [ConditionalFact(typeof(ServerCertificateTest), nameof(DowngradeToHTTP1IfClientCertSet))] + public async Task UseClientCertOnHttp2_DowngradedToHttp1MutualAuth_Success() + { + using X509Certificate2 clientCert = Test.Common.Configuration.Certificates.GetClientCertificate(); + await LoopbackServer.CreateClientAndServerAsync( + async address => + { + var handler = new WinHttpHandler(); + handler.ServerCertificateValidationCallback = CustomServerCertificateValidationCallback; + handler.ClientCertificates.Add(clientCert); + handler.ClientCertificateOption = ClientCertificateOption.Manual; + using (var client = new HttpClient(handler)) + using (HttpResponseMessage response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Get, address) { Version = HttpVersion20.Value })) + { + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.True(_validationCallbackHistory.WasCalled); + Assert.NotEmpty(_validationCallbackHistory.CertificateChain); + Assert.Equal(Test.Common.Configuration.Certificates.GetServerCertificate(), _validationCallbackHistory.CertificateChain[0]); + } + }, + async s => + { + using (LoopbackServer.Connection connection = await s.EstablishConnectionAsync().ConfigureAwait(false)) + { + SslStream sslStream = connection.Stream as SslStream; + Assert.NotNull(sslStream); + Assert.True(sslStream.IsMutuallyAuthenticated); + Assert.Equal(clientCert, sslStream.RemoteCertificate); + await connection.ReadRequestHeaderAndSendResponseAsync(HttpStatusCode.OK); + } + }, new LoopbackServer.Options { UseSsl = true }); + } + +// Disabling it for full .Net Framework due to a missing ALPN API which leads to a protocol downgrade +#if !NETFRAMEWORK + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsWindows10Version2004OrGreater))] + public async Task UseClientCertOnHttp2_OSSupportsIt_Success() + { + using X509Certificate2 clientCert = Test.Common.Configuration.Certificates.GetClientCertificate(); + await Http2LoopbackServer.CreateClientAndServerAsync( + async address => + { + var handler = new WinHttpHandler(); + handler.ServerCertificateValidationCallback = CustomServerCertificateValidationCallback; + handler.ClientCertificates.Add(clientCert); + handler.ClientCertificateOption = ClientCertificateOption.Manual; + using (var client = new HttpClient(handler)) + using (HttpResponseMessage response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Get, address) { Version = HttpVersion20.Value })) + { + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.True(_validationCallbackHistory.WasCalled); + Assert.NotEmpty(_validationCallbackHistory.CertificateChain); + Assert.Equal(Test.Common.Configuration.Certificates.GetServerCertificate(), _validationCallbackHistory.CertificateChain[0]); + } + }, + async s => + { + using (Http2LoopbackConnection connection = await s.EstablishConnectionAsync().ConfigureAwait(false)) + { + SslStream sslStream = connection.Stream as SslStream; + Assert.NotNull(sslStream); + Assert.True(sslStream.IsMutuallyAuthenticated); + Assert.Equal(clientCert, sslStream.RemoteCertificate); + + int streamId = await connection.ReadRequestHeaderAsync(); + await connection.SendDefaultResponseAsync(streamId); + } + }, new Http2Options { ClientCertificateRequired = true }); + } +#endif + [OuterLoop] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsWindows10Version1607OrGreater))] + public async Task UseClientCertOnHttp2_OSSupportsItButCertNotSet_SuccessWithOneWayAuth() + { + WinHttpHandler handler = new WinHttpHandler(); + handler.ServerCertificateValidationCallback = CustomServerCertificateValidationCallback; + string payload = "Mutual Authentication Test"; + HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, Test.Common.Configuration.Http.Http2RemoteEchoServer) { Version = HttpVersion20.Value }; + request.Content = new StringContent(payload); + using (var client = new HttpClient(handler)) + using (HttpResponseMessage response = await client.SendAsync(request)) + { + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.Equal(HttpVersion20.Value, response.Version); + string responsePayload = await response.Content.ReadAsStringAsync(); + var responseContent = JsonConvert.DeserializeAnonymousType(responsePayload, new { Method = "_", BodyContent = "_", ClientCertificatePresent = "_", ClientCertificate = "_" }); + Assert.Equal("POST", responseContent.Method); + Assert.Equal(payload, responseContent.BodyContent); + Assert.Equal("false", responseContent.ClientCertificatePresent); + Assert.Null(responseContent.ClientCertificate); + Assert.True(_validationCallbackHistory.WasCalled); + Assert.NotEmpty(_validationCallbackHistory.CertificateChain); + ConfirmValidCertificate("*.azurewebsites.net"); + }; + } + } +} diff --git a/src/libraries/System.Net.Http.WinHttpHandler/tests/FunctionalTests/HttpClientHandlerTestBase.WinHttpHandler.cs b/src/libraries/System.Net.Http.WinHttpHandler/tests/FunctionalTests/HttpClientHandlerTestBase.WinHttpHandler.cs index d1798cdf03257..516e9f7d3508b 100644 --- a/src/libraries/System.Net.Http.WinHttpHandler/tests/FunctionalTests/HttpClientHandlerTestBase.WinHttpHandler.cs +++ b/src/libraries/System.Net.Http.WinHttpHandler/tests/FunctionalTests/HttpClientHandlerTestBase.WinHttpHandler.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System.Net.Test.Common; using System.IO; namespace System.Net.Http.Functional.Tests @@ -10,13 +11,15 @@ public abstract partial class HttpClientHandlerTestBase : FileCleanupTestBase { protected static bool IsWinHttpHandler => true; + protected static bool AllowAllCertificates { get; set; } = true; + protected static WinHttpClientHandler CreateHttpClientHandler(Version useVersion = null) { useVersion ??= HttpVersion.Version11; - WinHttpClientHandler handler = new WinHttpClientHandler(); + WinHttpClientHandler handler = new WinHttpClientHandler(useVersion); - if (useVersion >= HttpVersion.Version20) + if (useVersion >= HttpVersion20.Value && AllowAllCertificates) { handler.ServerCertificateCustomValidationCallback = TestHelper.AllowAllCertificates; } diff --git a/src/libraries/System.Net.Http.WinHttpHandler/tests/FunctionalTests/PlatformHandlerTest.cs b/src/libraries/System.Net.Http.WinHttpHandler/tests/FunctionalTests/PlatformHandlerTest.cs index a871abc502df4..3bd5bc40488ff 100644 --- a/src/libraries/System.Net.Http.WinHttpHandler/tests/FunctionalTests/PlatformHandlerTest.cs +++ b/src/libraries/System.Net.Http.WinHttpHandler/tests/FunctionalTests/PlatformHandlerTest.cs @@ -192,16 +192,192 @@ public sealed class PlatformHandler_HttpClientHandler_Authentication_Test : Http public PlatformHandler_HttpClientHandler_Authentication_Test(ITestOutputHelper output) : base(output) { } } - // Enable this to run HTTP2 tests on platform handler -#if PLATFORM_HANDLER_HTTP2_TESTS - public sealed class PlatformHandlerTest_Http2 : HttpClientHandlerTest_Http2 +#if NETCOREAPP + [ConditionalClass(typeof(PlatformDetection), nameof(PlatformDetection.IsWindows10Version1607OrGreater))] + public sealed class PlatformHandlerTest_Cookies_Http2 : HttpClientHandlerTest_Cookies { + protected override Version UseVersion => HttpVersion20.Value; + + public PlatformHandlerTest_Cookies_Http2(ITestOutputHelper output) : base(output) { } } - - [ConditionalClass(typeof(PlatformDetection), nameof(PlatformDetection.SupportsAlpn))] - public sealed class PlatformHandlerTest_Cookies_Http2 : HttpClientHandlerTest_Cookies + + public sealed class PlatformHandler_HttpClientHandler_Asynchrony_Http2_Test : HttpClientHandler_Asynchrony_Test + { + protected override Version UseVersion => HttpVersion20.Value; + + public PlatformHandler_HttpClientHandler_Asynchrony_Http2_Test(ITestOutputHelper output) : base(output) { } + } + + public sealed class PlatformHandler_HttpProtocol_Http2_Tests : HttpProtocolTests + { + protected override Version UseVersion => HttpVersion20.Value; + + public PlatformHandler_HttpProtocol_Http2_Tests(ITestOutputHelper output) : base(output) { } + } + + public sealed class PlatformHandler_HttpProtocolTests_Http2_Dribble : HttpProtocolTests_Dribble + { + protected override Version UseVersion => HttpVersion20.Value; + + public PlatformHandler_HttpProtocolTests_Http2_Dribble(ITestOutputHelper output) : base(output) { } + } + + public sealed class PlatformHandler_HttpClient_SelectedSites_Http2_Test : HttpClient_SelectedSites_Test + { + protected override Version UseVersion => HttpVersion20.Value; + + public PlatformHandler_HttpClient_SelectedSites_Http2_Test(ITestOutputHelper output) : base(output) { } + } + + public sealed class PlatformHandler_HttpClientEKU_Http2_Test : HttpClientEKUTest + { + protected override Version UseVersion => HttpVersion20.Value; + + public PlatformHandler_HttpClientEKU_Http2_Test(ITestOutputHelper output) : base(output) { } + } + + public sealed class PlatformHandler_HttpClientHandler_Decompression_Http2_Tests : HttpClientHandler_Decompression_Test + { + protected override Version UseVersion => HttpVersion20.Value; + + public PlatformHandler_HttpClientHandler_Decompression_Http2_Tests(ITestOutputHelper output) : base(output) { } + } + + public sealed class PlatformHandler_HttpClientHandler_DangerousAcceptAllCertificatesValidator_Http2_Test : HttpClientHandler_DangerousAcceptAllCertificatesValidator_Test + { + protected override Version UseVersion => HttpVersion20.Value; + + public PlatformHandler_HttpClientHandler_DangerousAcceptAllCertificatesValidator_Http2_Test(ITestOutputHelper output) : base(output) { } + } + + public sealed class PlatformHandler_HttpClientHandler_ClientCertificates_Http2_Test : HttpClientHandler_ClientCertificates_Test + { + protected override Version UseVersion => HttpVersion20.Value; + + public PlatformHandler_HttpClientHandler_ClientCertificates_Http2_Test(ITestOutputHelper output) : base(output) { } + } + + public sealed class PlatformHandler_HttpClientHandler_DefaultProxyCredentials_Http2_Test : HttpClientHandler_DefaultProxyCredentials_Test + { + protected override Version UseVersion => HttpVersion20.Value; + + public PlatformHandler_HttpClientHandler_DefaultProxyCredentials_Http2_Test(ITestOutputHelper output) : base(output) { } + } + + public sealed class PlatformHandler_HttpClientHandler_MaxConnectionsPerServer_Http2_Test : HttpClientHandler_MaxConnectionsPerServer_Test + { + protected override Version UseVersion => HttpVersion20.Value; + + public PlatformHandler_HttpClientHandler_MaxConnectionsPerServer_Http2_Test(ITestOutputHelper output) : base(output) { } + } + + public sealed class PlatformHandler_HttpClientHandler_ServerCertificates_Http2_Test : HttpClientHandler_ServerCertificates_Test + { + protected override Version UseVersion => HttpVersion20.Value; + + public PlatformHandler_HttpClientHandler_ServerCertificates_Http2_Test(ITestOutputHelper output) : base(output) { + AllowAllCertificates = false; + } + } + + public sealed class PlatformHandler_PostScenario_Http2_Test : PostScenarioTest + { + protected override Version UseVersion => HttpVersion20.Value; + + public PlatformHandler_PostScenario_Http2_Test(ITestOutputHelper output) : base(output) { } + } + + public sealed class PlatformHandler_HttpClientHandler_SslProtocols_Http2_Test : HttpClientHandler_SslProtocols_Test + { + protected override Version UseVersion => HttpVersion20.Value; + + public PlatformHandler_HttpClientHandler_SslProtocols_Http2_Test(ITestOutputHelper output) : base(output) { } + } + + public sealed class PlatformHandler_HttpClientHandler_Proxy_Http2_Test : HttpClientHandler_Proxy_Test + { + protected override Version UseVersion => HttpVersion20.Value; + + public PlatformHandler_HttpClientHandler_Proxy_Http2_Test(ITestOutputHelper output) : base(output) { } + } + + public sealed class PlatformHandler_SchSendAuxRecordHttp_Http2_Test : SchSendAuxRecordHttpTest + { + protected override Version UseVersion => HttpVersion20.Value; + + public PlatformHandler_SchSendAuxRecordHttp_Http2_Test(ITestOutputHelper output) : base(output) { } + } + + [ConditionalClass(typeof(PlatformDetection), nameof(PlatformDetection.IsWindows10Version1607OrGreater))] + public sealed class PlatformHandler_HttpClientHandler_Http2_Test : HttpClientHandlerTest + { + protected override Version UseVersion => HttpVersion20.Value; + + public PlatformHandler_HttpClientHandler_Http2_Test(ITestOutputHelper output) : base(output) { } + } + + public sealed class PlatformHandlerTest_AutoRedirect_Http2 : HttpClientHandlerTest_AutoRedirect + { + protected override Version UseVersion => HttpVersion20.Value; + + public PlatformHandlerTest_AutoRedirect_Http2(ITestOutputHelper output) : base(output) { } + } + + public sealed class PlatformHandler_DefaultCredentials_Http2_Test : DefaultCredentialsTest + { + protected override Version UseVersion => HttpVersion20.Value; + + public PlatformHandler_DefaultCredentials_Http2_Test(ITestOutputHelper output) : base(output) { } + } + + public sealed class PlatformHandler_IdnaProtocol_Http2_Tests : IdnaProtocolTests { - protected override bool UseHttp2LoopbackServer => true; + protected override Version UseVersion => HttpVersion20.Value; + + public PlatformHandler_IdnaProtocol_Http2_Tests(ITestOutputHelper output) : base(output) { } + // WinHttp on Win7 does not support IDNA + protected override bool SupportsIdna => !PlatformDetection.IsWindows7; + } + + public sealed class PlatformHandler_HttpRetryProtocol_Http2_Tests : HttpRetryProtocolTests + { + protected override Version UseVersion => HttpVersion20.Value; + + public PlatformHandler_HttpRetryProtocol_Http2_Tests(ITestOutputHelper output) : base(output) { } + } + + public sealed class PlatformHandlerTest_Cookies_Http11_Http2 : HttpClientHandlerTest_Cookies_Http11 + { + protected override Version UseVersion => HttpVersion20.Value; + + public PlatformHandlerTest_Cookies_Http11_Http2(ITestOutputHelper output) : base(output) { } + } + + public sealed class PlatformHandler_HttpClientHandler_MaxResponseHeadersLength_Http2_Test : HttpClientHandler_MaxResponseHeadersLength_Test + { + protected override Version UseVersion => HttpVersion20.Value; + + public PlatformHandler_HttpClientHandler_MaxResponseHeadersLength_Http2_Test(ITestOutputHelper output) : base(output) { } + } + + public sealed class PlatformHandler_HttpClientHandler_Cancellation_Http2_Test : HttpClientHandler_Cancellation_Test + { + protected override Version UseVersion => HttpVersion20.Value; + + public PlatformHandler_HttpClientHandler_Cancellation_Http2_Test(ITestOutputHelper output) : base(output) { } + } + + public sealed class PlatformHandler_HttpClientHandler_Authentication_Http2_Test : HttpClientHandler_Authentication_Test + { + protected override Version UseVersion => HttpVersion20.Value; + + public PlatformHandler_HttpClientHandler_Authentication_Http2_Test(ITestOutputHelper output) : base(output) { } } #endif + public sealed class PlatformHandler_ResponseStream_Http2_Test : ResponseStreamTest + { + protected override Version UseVersion => HttpVersion20.Value; + + public PlatformHandler_ResponseStream_Http2_Test(ITestOutputHelper output) : base(output) { } + } } diff --git a/src/libraries/System.Net.Http.WinHttpHandler/tests/FunctionalTests/ServerCertificateTest.cs b/src/libraries/System.Net.Http.WinHttpHandler/tests/FunctionalTests/ServerCertificateTest.cs index 5ad5587abb0c9..f459c2957d214 100644 --- a/src/libraries/System.Net.Http.WinHttpHandler/tests/FunctionalTests/ServerCertificateTest.cs +++ b/src/libraries/System.Net.Http.WinHttpHandler/tests/FunctionalTests/ServerCertificateTest.cs @@ -2,31 +2,21 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System; using System.ComponentModel; -using System.Net; -using System.Net.Http; using System.Net.Security; -using System.Net.Test.Common; -using System.Security.Authentication; using System.Security.Cryptography.X509Certificates; using System.Threading.Tasks; - using Xunit; using Xunit.Abstractions; namespace System.Net.Http.WinHttpHandlerFunctional.Tests { - public class ServerCertificateTest + public class ServerCertificateTest : BaseCertificateTest { - private readonly ITestOutputHelper _output; - private readonly ValidationCallbackHistory _validationCallbackHistory; + public ServerCertificateTest(ITestOutputHelper output) : base(output) + { } - public ServerCertificateTest(ITestOutputHelper output) - { - _output = output; - _validationCallbackHistory = new ValidationCallbackHistory(); - } + public static bool DowngradeToHTTP1IfClientCertSet => PlatformDetection.WindowsVersion < 2004; [OuterLoop] [Fact] @@ -34,7 +24,7 @@ public async Task NoCallback_ValidCertificate_CallbackNotCalled() { var handler = new WinHttpHandler(); using (var client = new HttpClient(handler)) - using (HttpResponseMessage response = await client.GetAsync(System.Net.Test.Common.Configuration.Http.SecureRemoteEchoServer)) + using (HttpResponseMessage response = await client.GetAsync(Test.Common.Configuration.Http.SecureRemoteEchoServer)) { Assert.Equal(HttpStatusCode.OK, response.StatusCode); Assert.False(_validationCallbackHistory.WasCalled); @@ -48,7 +38,7 @@ public async Task UseCallback_NotSecureConnection_CallbackNotCalled() var handler = new WinHttpHandler(); handler.ServerCertificateValidationCallback = CustomServerCertificateValidationCallback; using (var client = new HttpClient(handler)) - using (HttpResponseMessage response = await client.GetAsync(System.Net.Test.Common.Configuration.Http.RemoteEchoServer)) + using (HttpResponseMessage response = await client.GetAsync(Test.Common.Configuration.Http.RemoteEchoServer)) { Assert.Equal(HttpStatusCode.OK, response.StatusCode); Assert.False(_validationCallbackHistory.WasCalled); @@ -67,7 +57,7 @@ public async Task UseCallback_ValidCertificate_ExpectedValuesDuringCallback() Assert.Equal(HttpStatusCode.OK, response.StatusCode); Assert.True(_validationCallbackHistory.WasCalled); - ConfirmValidCertificate(System.Net.Test.Common.Configuration.Http.Host); + ConfirmValidCertificate(Test.Common.Configuration.Http.Host); } } @@ -75,7 +65,7 @@ public async Task UseCallback_ValidCertificate_ExpectedValuesDuringCallback() [Fact] public async Task UseCallback_RedirectandValidCertificate_ExpectedValuesDuringCallback() { - Uri uri = System.Net.Test.Common.Configuration.Http.RemoteSecureHttp11Server.RedirectUriForDestinationUri(302, System.Net.Test.Common.Configuration.Http.SecureRemoteEchoServer, 1); + Uri uri = Test.Common.Configuration.Http.RemoteSecureHttp11Server.RedirectUriForDestinationUri(302, System.Net.Test.Common.Configuration.Http.SecureRemoteEchoServer, 1); var handler = new WinHttpHandler(); handler.ServerCertificateValidationCallback = CustomServerCertificateValidationCallback; @@ -85,7 +75,7 @@ public async Task UseCallback_RedirectandValidCertificate_ExpectedValuesDuringCa Assert.Equal(HttpStatusCode.OK, response.StatusCode); Assert.True(_validationCallbackHistory.WasCalled); - ConfirmValidCertificate(System.Net.Test.Common.Configuration.Http.Host); + ConfirmValidCertificate(Test.Common.Configuration.Http.Host); } } @@ -99,7 +89,7 @@ public async Task UseCallback_CallbackReturnsFailure_ThrowsInnerSecurityFailureE handler.ServerCertificateValidationCallback = CustomServerCertificateValidationCallback; using (var client = new HttpClient(handler)) { - var request = new HttpRequestMessage(HttpMethod.Get, System.Net.Test.Common.Configuration.Http.SecureRemoteEchoServer); + var request = new HttpRequestMessage(HttpMethod.Get, Test.Common.Configuration.Http.SecureRemoteEchoServer); _validationCallbackHistory.ReturnFailure = true; HttpRequestException ex = await Assert.ThrowsAsync(() => client.GetAsync(System.Net.Test.Common.Configuration.Http.SecureRemoteEchoServer)); @@ -122,70 +112,5 @@ public async Task UseCallback_CallbackThrowsSpecificException_SpecificExceptionP Assert.True(ex.GetBaseException() is CustomException); } } - - private void ConfirmValidCertificate(string expectedHostName) - { - Assert.Equal(SslPolicyErrors.None, _validationCallbackHistory.SslPolicyErrors); - Assert.True(_validationCallbackHistory.CertificateChain.Count > 0); - _output.WriteLine("Certificate.Subject: {0}", _validationCallbackHistory.CertificateSubject); - _output.WriteLine("Expected HostName: {0}", expectedHostName); - } - - private bool CustomServerCertificateValidationCallback( - HttpRequestMessage sender, - X509Certificate2 certificate, - X509Chain chain, - SslPolicyErrors sslPolicyErrors) - { - _validationCallbackHistory.WasCalled = true; - _validationCallbackHistory.CertificateSubject = certificate.Subject; - foreach (var element in chain.ChainElements) - { - _validationCallbackHistory.CertificateChain.Add(element.Certificate); - } - _validationCallbackHistory.ChainStatus = chain.ChainStatus; - _validationCallbackHistory.SslPolicyErrors = sslPolicyErrors; - - if (_validationCallbackHistory.ThrowException) - { - throw new CustomException(); - } - - if (_validationCallbackHistory.ReturnFailure) - { - return false; - } - - return true; - } - - public class CustomException : Exception - { - public CustomException() - { - } - } - - public class ValidationCallbackHistory - { - public bool ThrowException; - public bool ReturnFailure; - public bool WasCalled; - public SslPolicyErrors SslPolicyErrors; - public string CertificateSubject; - public X509CertificateCollection CertificateChain; - public X509ChainStatus[] ChainStatus; - - public ValidationCallbackHistory() - { - ThrowException = false; - ReturnFailure = false; - WasCalled = false; - SslPolicyErrors = SslPolicyErrors.None; - CertificateSubject = null; - CertificateChain = new X509CertificateCollection(); - ChainStatus = null; - } - } } } diff --git a/src/libraries/System.Net.Http.WinHttpHandler/tests/FunctionalTests/System.Net.Http.WinHttpHandler.Functional.Tests.csproj b/src/libraries/System.Net.Http.WinHttpHandler/tests/FunctionalTests/System.Net.Http.WinHttpHandler.Functional.Tests.csproj index 82e2835aa8995..599da9a519e91 100644 --- a/src/libraries/System.Net.Http.WinHttpHandler/tests/FunctionalTests/System.Net.Http.WinHttpHandler.Functional.Tests.csproj +++ b/src/libraries/System.Net.Http.WinHttpHandler/tests/FunctionalTests/System.Net.Http.WinHttpHandler.Functional.Tests.csproj @@ -1,8 +1,9 @@ - + $(NetCoreAppCurrent)-Windows_NT;$(NetFrameworkCurrent)-Windows_NT true $(DefineConstants);WINHTTPHANDLER_TEST + 8.0 @@ -11,11 +12,10 @@ Common\System\Net\Configuration.Http.cs + - - - + Common\System\Net\Http\HttpHandlerDefaults.cs @@ -52,6 +52,9 @@ Common\System\Net\TestWebProxies.cs + + Common\System\Net\StreamArrayExtensions.cs + Common\System\Net\Http\ByteAtATimeContent.cs @@ -193,6 +196,7 @@ + diff --git a/src/libraries/System.Net.Http.WinHttpHandler/tests/FunctionalTests/WinHttpClientHandler.cs b/src/libraries/System.Net.Http.WinHttpHandler/tests/FunctionalTests/WinHttpClientHandler.cs index 2f8ccd311a3d0..611e6c510a7c4 100644 --- a/src/libraries/System.Net.Http.WinHttpHandler/tests/FunctionalTests/WinHttpClientHandler.cs +++ b/src/libraries/System.Net.Http.WinHttpHandler/tests/FunctionalTests/WinHttpClientHandler.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Net.Security; +using System.Net.Test.Common; using System.Security.Authentication; using System.Security.Cryptography.X509Certificates; using System.Threading; @@ -18,8 +19,9 @@ namespace System.Net.Http public class WinHttpClientHandler : WinHttpHandler { private bool _useProxy; + private readonly Version _requestVersion; - public WinHttpClientHandler() + public WinHttpClientHandler(Version requestVersion) { // Adjust defaults to match current .NET Desktop HttpClientHandler (based on HWR stack). AllowAutoRedirect = true; @@ -41,6 +43,8 @@ public WinHttpClientHandler() ReceiveHeadersTimeout = Timeout.InfiniteTimeSpan; ReceiveDataTimeout = Timeout.InfiniteTimeSpan; SendTimeout = Timeout.InfiniteTimeSpan; + + _requestVersion = requestVersion; } public virtual bool SupportsAutomaticDecompression => true; @@ -178,6 +182,11 @@ protected override Task SendAsync(HttpRequestMessage reques } } + if(_requestVersion >= HttpVersion20.Value) + { + request.Version = _requestVersion; + } + return base.SendAsync(request, cancellationToken); } } diff --git a/src/libraries/System.Net.Http.WinHttpHandler/tests/FunctionalTests/WinHttpHandlerTest.cs b/src/libraries/System.Net.Http.WinHttpHandler/tests/FunctionalTests/WinHttpHandlerTest.cs index 5c728fced2eef..0519437ca296e 100644 --- a/src/libraries/System.Net.Http.WinHttpHandler/tests/FunctionalTests/WinHttpHandlerTest.cs +++ b/src/libraries/System.Net.Http.WinHttpHandler/tests/FunctionalTests/WinHttpHandlerTest.cs @@ -3,9 +3,10 @@ // See the LICENSE file in the project root for more information. using System; -using System.Net; -using System.Net.Http; +using System.Collections.Generic; +using System.Linq; using System.Net.Test.Common; +using System.Text.Json; using System.Threading; using System.Threading.Tasks; @@ -131,6 +132,46 @@ public async Task SendAsync_GetUsingChunkedEncoding_ThrowsHttpRequestException() } } + [OuterLoop] + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsWindows10Version1607OrGreater))] + public async Task GetAsync_SetCookieContainerMultipleCookies_CookiesSent() + { + var cookies = new Cookie[] + { + new Cookie("hello", "world"), + new Cookie("foo", "bar"), + new Cookie("ABC", "123") + }; + + WinHttpHandler handler = new WinHttpHandler(); + var cookieContainer = new CookieContainer(); + + foreach (Cookie c in cookies) + { + cookieContainer.Add(Configuration.Http.Http2RemoteEchoServer, c); + } + + handler.CookieContainer = cookieContainer; + handler.CookieUsePolicy = CookieUsePolicy.UseSpecifiedCookieContainer; + handler.ServerCertificateValidationCallback = (m, cert, chain, err) => true; + string payload = "Cookie Test"; + HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, Configuration.Http.Http2RemoteEchoServer) { Version = HttpVersion20.Value }; + request.Content = new StringContent(payload); + using (var client = new HttpClient(handler)) + using (HttpResponseMessage response = await client.SendAsync(request)) + { + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.Equal(HttpVersion20.Value, response.Version); + string responsePayload = await response.Content.ReadAsStringAsync(); + var responseContent = Newtonsoft.Json.JsonConvert + .DeserializeAnonymousType(responsePayload, new { Method = "_", BodyContent = "_", Cookies = new Dictionary() }); + Assert.Equal("POST", responseContent.Method); + Assert.Equal(payload, responseContent.BodyContent); + Assert.Equal(cookies.ToDictionary(c => c.Name, c => c.Value), responseContent.Cookies); + + }; + } + public static bool JsonMessageContainsKeyValue(string message, string key, string value) { string pattern = string.Format(@"""{0}"": ""{1}""", key, value);