Enable HTTP/2 client cert authentication in WinHttpHandler (#33158)
authorAlexander Nikolaev <55398552+alnikola@users.noreply.github.com>
Thu, 19 Mar 2020 14:37:00 +0000 (15:37 +0100)
committerGitHub <noreply@github.com>
Thu, 19 Mar 2020 14:37:00 +0000 (15:37 +0100)
Pre-release WinHTTP's version supports client cert authentication over HTTP/2, but the feature must be explicitly opted-in. PR sets WINHTTP_OPTION_ENABLE_HTTP2_PLUS_CLIENT_CERT to TRUE before invoking WinHttpConnect if the request's protocol is HTTP/2 and scheme is HTTPS.

This PR also enables all HTTP 1.1 tests for WinHttpHandler on .Net Core and Framework and the most of HTTP/2 tests on .Net Core.

33 files changed:
src/libraries/Common/src/Interop/Windows/WinHttp/Interop.winhttp_types.cs
src/libraries/Common/tests/System/Net/Http/DefaultCredentialsTest.cs
src/libraries/Common/tests/System/Net/Http/GenericLoopbackServer.cs
src/libraries/Common/tests/System/Net/Http/Http2Frames.cs
src/libraries/Common/tests/System/Net/Http/Http2LoopbackConnection.cs
src/libraries/Common/tests/System/Net/Http/Http2LoopbackServer.cs
src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.AcceptAllCerts.cs
src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.Authentication.cs
src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.Cancellation.cs
src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.ClientCertificates.cs
src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.Cookies.cs
src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.Decompression.cs
src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.MaxResponseHeadersLength.cs
src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.Proxy.cs
src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.ServerCertificates.cs
src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.SslProtocols.cs
src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTest.cs
src/libraries/Common/tests/System/Net/Http/HttpClientHandlerTestBase.cs
src/libraries/Common/tests/System/Net/Http/HttpProtocolTests.cs
src/libraries/Common/tests/System/Net/Http/LoopbackServer.cs
src/libraries/Common/tests/System/Net/Http/PostScenarioTest.cs
src/libraries/Common/tests/System/Net/Http/QPackTestDecoder.cs
src/libraries/Common/tests/System/Net/Http/ResponseStreamTest.cs
src/libraries/Common/tests/System/Net/StreamArrayExtensions.cs [new file with mode: 0644]
src/libraries/System.Net.Http.WinHttpHandler/src/System/Net/Http/WinHttpHandler.cs
src/libraries/System.Net.Http.WinHttpHandler/tests/FunctionalTests/BaseCertificateTest.cs [new file with mode: 0644]
src/libraries/System.Net.Http.WinHttpHandler/tests/FunctionalTests/ClientCertificateTest.cs [new file with mode: 0644]
src/libraries/System.Net.Http.WinHttpHandler/tests/FunctionalTests/HttpClientHandlerTestBase.WinHttpHandler.cs
src/libraries/System.Net.Http.WinHttpHandler/tests/FunctionalTests/PlatformHandlerTest.cs
src/libraries/System.Net.Http.WinHttpHandler/tests/FunctionalTests/ServerCertificateTest.cs
src/libraries/System.Net.Http.WinHttpHandler/tests/FunctionalTests/System.Net.Http.WinHttpHandler.Functional.Tests.csproj
src/libraries/System.Net.Http.WinHttpHandler/tests/FunctionalTests/WinHttpClientHandler.cs
src/libraries/System.Net.Http.WinHttpHandler/tests/FunctionalTests/WinHttpHandlerTest.cs

index 4c1824f..3948b54 100644 (file)
@@ -149,9 +149,11 @@ internal partial class Interop
 
         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;
index 40686b9..d5b9e4f 100644 (file)
@@ -335,7 +335,8 @@ namespace System.Net.Http.Functional.Tests
 
                     // 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();
                 }
index 9e505e3..779055b 100644 (file)
@@ -86,7 +86,7 @@ namespace System.Net.Test.Common
         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;
index 4fe0f5b..af0f245 100644 (file)
@@ -548,10 +548,12 @@ namespace System.Net.Test.Common
             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}";
index 43be924..ee774e0 100644 (file)
@@ -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 @@ namespace System.Net.Test.Common
         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 @@ namespace System.Net.Test.Common
 
                 using (var cert = Configuration.Certificates.GetServerCertificate())
                 {
+#if !NETFRAMEWORK
                     SslServerAuthenticationOptions options = new SslServerAuthenticationOptions();
 
                     options.EnabledSslProtocols = httpOptions.SslProtocols;
@@ -51,9 +53,12 @@ namespace System.Net.Test.Common
 
                     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 @@ namespace System.Net.Test.Common
             {
                 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 @@ namespace System.Net.Test.Common
             }
             else
             {
-                string value = Encoding.ASCII.GetString(headerBlock.Slice(bytesConsumed, stringLength));
+                string value = Encoding.ASCII.GetString(headerBlock.Slice(bytesConsumed, stringLength).ToArray());
                 return (bytesConsumed + stringLength, value);
             }
         }
index f2aea4c..4d3486c 100644 (file)
@@ -181,9 +181,14 @@ namespace System.Net.Test.Common
             }
         }
 
-        public static async Task CreateClientAndServerAsync(Func<Uri, Task> clientFunc, Func<Http2LoopbackServer, Task> serverFunc, int timeout = 60_000)
+        public static Task CreateClientAndServerAsync(Func<Uri, Task> clientFunc, Func<Http2LoopbackServer, Task> serverFunc, int timeout = 60_000)
         {
-            using (var server = Http2LoopbackServer.CreateServer())
+            return CreateClientAndServerAsync(clientFunc, serverFunc, null, timeout);
+        }
+
+        public static async Task CreateClientAndServerAsync(Func<Uri, Task> clientFunc, Func<Http2LoopbackServer, Task> 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 @@ namespace System.Net.Test.Common
     {
         public int ListenBacklog { get; set; } = 1;
 
+        public bool ClientCertificateRequired { get; set; }
+
         public Http2Options()
         {
             UseSsl = PlatformDetection.SupportsAlpn && !Capability.Http2ForceUnencryptedLoopback();
@@ -237,7 +244,7 @@ namespace System.Net.Test.Common
             }
         }
 
-        public override Version Version => HttpVersion.Version20;
+    public override Version Version => HttpVersion20.Value;
     }
 
     public enum ProtocolErrors
@@ -257,4 +264,9 @@ namespace System.Net.Test.Common
         INADEQUATE_SECURITY = 0xc,
         HTTP_1_1_REQUIRED = 0xd
     }
+
+    public static class HttpVersion20
+    {
+        public static readonly Version Value = new Version(2, 0);
+    }
 }
index 53d45ad..99ef96c 100644 (file)
@@ -39,8 +39,10 @@ namespace System.Net.Http.Functional.Tests
         [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 @@ namespace System.Net.Http.Functional.Tests
                     // 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 };
index 3b19535..9c2c1e2 100644 (file)
@@ -602,6 +602,12 @@ namespace System.Net.Http.Functional.Tests
         [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 =>
                 {
index d7e519f..b248f34 100644 (file)
@@ -28,17 +28,24 @@ namespace System.Net.Http.Functional.Tests
     {
         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<bool>();
             await LoopbackServerFactory.CreateClientAndServerAsync(async uri =>
             {
@@ -76,16 +83,23 @@ namespace System.Net.Http.Functional.Tests
             });
         }
 
-        [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 @@ namespace System.Net.Http.Functional.Tests
         [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 @@ namespace System.Net.Http.Functional.Tests
             }
         }
 
-        [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 @@ namespace System.Net.Http.Functional.Tests
                 });
             }
         }
-
-        [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 @@ namespace System.Net.Http.Functional.Tests
         [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 @@ namespace System.Net.Http.Functional.Tests
             }
         }
 
+#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 @@ namespace System.Net.Http.Functional.Tests
                     catch (Exception) { }
                 });
         }
+#endif
 
         private async Task ValidateClientCancellationAsync(Func<Task> clientBodyAsync)
         {
index b75156a..a17334e 100644 (file)
@@ -113,7 +113,7 @@ namespace System.Net.Http.Functional.Tests
                         {
                             _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
index 75f440a..3fa53fe 100644 (file)
@@ -182,7 +182,11 @@ namespace System.Net.Http.Functional.Tests
 
         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 @@ namespace System.Net.Http.Functional.Tests
                 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[]
index 0fb03c3..fa0b630 100644 (file)
@@ -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<object[]> RemoteServersAndCompressionUris()
@@ -41,20 +46,22 @@ namespace System.Net.Http.Functional.Tests
                 {
                     "deflate",
                     new Func<Stream, Stream>(s => new DeflateStream(s, CompressionLevel.Optimal, leaveOpen: true)),
-                    specifyAllMethods ? DecompressionMethods.Deflate : DecompressionMethods.All
+                    specifyAllMethods ? DecompressionMethods.Deflate : _all
                 };
                 yield return new object[]
                 {
                     "gzip",
                     new Func<Stream, Stream>(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<Stream, Stream>(s => new BrotliStream(s, CompressionLevel.Optimal, leaveOpen: true)),
-                    specifyAllMethods ? DecompressionMethods.Brotli : DecompressionMethods.All
+                    specifyAllMethods ? DecompressionMethods.Brotli : _all
                 };
+#endif
             }
         }
 
@@ -102,6 +109,7 @@ namespace System.Net.Http.Functional.Tests
                 new Func<Stream, Stream>(s => new DeflateStream(s, CompressionLevel.Optimal, leaveOpen: true)),
                 DecompressionMethods.None
             };
+#if !NETFRAMEWORK
             yield return new object[]
             {
                 "gzip",
@@ -114,6 +122,7 @@ namespace System.Net.Http.Functional.Tests
                 new Func<Stream, Stream>(s => new BrotliStream(s, CompressionLevel.Optimal, leaveOpen: true)),
                 DecompressionMethods.Deflate | DecompressionMethods.GZip
             };
+#endif
         }
 
         [Theory]
@@ -189,10 +198,12 @@ namespace System.Net.Http.Functional.Tests
         }
 
         [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", "")]
index 7c6db38..dd6daf3 100644 (file)
@@ -7,6 +7,7 @@ using System.Net.Test.Common;
 using System.Text;
 using System.Threading;
 using System.Threading.Tasks;
+using Microsoft.DotNet.XUnitExtensions;
 using Xunit;
 using Xunit.Abstractions;
 
@@ -46,9 +47,15 @@ namespace System.Net.Http.Functional.Tests
             }
         }
 
-        [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();
index ef7b570..80ec9e2 100644 (file)
@@ -26,9 +26,15 @@ namespace System.Net.Http.Functional.Tests
     {
         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 =>
index 2a04997..393c024 100644 (file)
@@ -13,6 +13,7 @@ using System.Security.Authentication.ExtendedProtection;
 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 @@ namespace System.Net.Http.Functional.Tests
 
         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 @@ namespace System.Net.Http.Functional.Tests
             }
         }
 
-        [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);
index b6bf8b5..b264aa6 100644 (file)
@@ -43,11 +43,13 @@ namespace System.Net.Http.Functional.Tests
         [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 @@ namespace System.Net.Http.Functional.Tests
 #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 @@ namespace System.Net.Http.Functional.Tests
             // 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 @@ namespace System.Net.Http.Functional.Tests
                     // 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 };
index 671401f..a866a5d 100644 (file)
@@ -226,6 +226,12 @@ namespace System.Net.Http.Functional.Tests
         [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 @@ namespace System.Net.Http.Functional.Tests
             }
         }
 
-        [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 @@ namespace System.Net.Http.Functional.Tests
         [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 @@ namespace System.Net.Http.Functional.Tests
             }
         }
 
-        [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 @@ namespace System.Net.Http.Functional.Tests
                    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 @@ namespace System.Net.Http.Functional.Tests
                     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 @@ namespace System.Net.Http.Functional.Tests
         [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 @@ namespace System.Net.Http.Functional.Tests
         [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 @@ namespace System.Net.Http.Functional.Tests
         [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 @@ namespace System.Net.Http.Functional.Tests
                         Assert.Throws<NotSupportedException>(() => responseStream.Seek(0, SeekOrigin.Begin));
                         Assert.Throws<NotSupportedException>(() => responseStream.SetLength(0));
                         Assert.Throws<NotSupportedException>(() => responseStream.Write(new byte[1], 0, 1));
+#if !NETFRAMEWORK
                         Assert.Throws<NotSupportedException>(() => responseStream.Write(new Span<byte>(new byte[1])));
                         Assert.Throws<NotSupportedException>(() => { responseStream.WriteAsync(new Memory<byte>(new byte[1])); });
+#endif
                         Assert.Throws<NotSupportedException>(() => { responseStream.WriteAsync(new byte[1], 0, 1); });
                         Assert.Throws<NotSupportedException>(() => responseStream.WriteByte(1));
 
@@ -1329,13 +1361,21 @@ namespace System.Net.Http.Functional.Tests
                         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<byte>(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<byte>(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 @@ namespace System.Net.Http.Functional.Tests
 
                         // 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<byte>(), 0, 0, null));
+#if !NETFRAMEWORK
                         Assert.Equal(0, await responseStream.ReadAsync(Memory<byte>.Empty));
+#endif
                         Assert.Equal(0, await responseStream.ReadAsync(Array.Empty<byte>(), 0, 0));
+#if !NETFRAMEWORK
                         Assert.Equal(0, responseStream.Read(Span<byte>.Empty));
+#endif
                         Assert.Equal(0, responseStream.Read(Array.Empty<byte>(), 0, 0));
 
                         // And copying
@@ -1360,9 +1404,13 @@ namespace System.Net.Http.Functional.Tests
                         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<byte>(buffer)));
+#endif
                         Assert.Equal(0, await responseStream.ReadAsync(buffer, 0, 1));
+#if !NETFRAMEWORK
                         Assert.Equal(0, await responseStream.ReadAsync(new Memory<byte>(buffer)));
+#endif
                         Assert.Equal(0, await Task.Factory.FromAsync(responseStream.BeginRead, responseStream.EndRead, buffer, 0, 1, null));
                     }
                 }
@@ -1392,9 +1440,15 @@ namespace System.Net.Http.Functional.Tests
             });
         }
 
-        [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 @@ namespace System.Net.Http.Functional.Tests
                         Assert.Throws<NotSupportedException>(() => responseStream.Seek(0, SeekOrigin.Begin));
                         Assert.Throws<NotSupportedException>(() => responseStream.SetLength(0));
                         Assert.Throws<NotSupportedException>(() => responseStream.Write(new byte[1], 0, 1));
+#if !NETFRAMEWORK
                         Assert.Throws<NotSupportedException>(() => responseStream.Write(new Span<byte>(new byte[1])));
                         await Assert.ThrowsAsync<NotSupportedException>(async () => await responseStream.WriteAsync(new Memory<byte>(new byte[1])));
+#endif
                         await Assert.ThrowsAsync<NotSupportedException>(async () => await responseStream.WriteAsync(new byte[1], 0, 1));
                         Assert.Throws<NotSupportedException>(() => responseStream.WriteByte(1));
 
@@ -1455,9 +1511,13 @@ namespace System.Net.Http.Functional.Tests
                         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<byte>(buffer)));
+#endif
                         Assert.Equal(0, await responseStream.ReadAsync(buffer, 0, 1));
+#if !NETFRAMEWORK
                         Assert.Equal(0, responseStream.Read(new Span<byte>(buffer)));
+#endif
                         Assert.Equal(0, responseStream.Read(buffer, 0, 1));
 
                         // Empty copies
@@ -1471,10 +1531,15 @@ namespace System.Net.Http.Functional.Tests
             },
             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 @@ namespace System.Net.Http.Functional.Tests
             }
         }
 
-        [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<bool>();
             await LoopbackServerFactory.CreateClientAndServerAsync(async uri =>
@@ -1876,10 +1947,16 @@ namespace System.Net.Http.Functional.Tests
             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<bool>();
             const string TestString = "test";
             const string CookieHeaderExpected = "yummy_cookie=choco";
@@ -1940,10 +2017,16 @@ namespace System.Net.Http.Functional.Tests
             });
         }
 
-        [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<bool>();
             const string TestString = "test";
 
@@ -1980,9 +2063,15 @@ namespace System.Net.Http.Functional.Tests
             });
         }
 
-        [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<bool>();
             const string TestString = "test";
 
@@ -2019,9 +2108,15 @@ namespace System.Net.Http.Functional.Tests
             });
         }
 
-        [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<bool>();
             const string RequestString = "request";
             const string ResponseString = "response";
@@ -2069,7 +2164,7 @@ namespace System.Net.Http.Functional.Tests
                 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 @@ namespace System.Net.Http.Functional.Tests
             }
         }
 
+#if !NETFRAMEWORK
         [OuterLoop("Uses external server")]
         [Theory, MemberData(nameof(RemoteServersMemberData))]
         public async Task PostAsync_ReuseRequestContent_Success(Configuration.Http.RemoteServer remoteServer)
@@ -2233,13 +2329,14 @@ namespace System.Net.Http.Functional.Tests
                 }
             }
         }
+#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 @@ namespace System.Net.Http.Functional.Tests
         [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 @@ namespace System.Net.Http.Functional.Tests
             }
         }
 
-        [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 @@ namespace System.Net.Http.Functional.Tests
 
             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[]");
index 8330874..003e344 100644 (file)
@@ -32,13 +32,21 @@ namespace System.Net.Http.Functional.Tests
         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 @@ namespace System.Net.Http.Functional.Tests
         {
             return useVersion.Major switch
             {
-#if NETCOREAPP
+#if NETCOREAPP || WINHTTPHANDLER_TEST
 #if HTTP3
                 3 => Http3LoopbackServerFactory.Singleton,
 #endif
@@ -81,7 +89,11 @@ namespace System.Net.Http.Functional.Tests
                 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
index 62a46c2..635cb2b 100644 (file)
@@ -20,9 +20,15 @@ namespace System.Net.Http.Functional.Tests
 
         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 @@ namespace System.Net.Http.Functional.Tests
             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 @@ namespace System.Net.Http.Functional.Tests
                     {
                         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);
                         }
index 6433f53..48d75c8 100644 (file)
@@ -394,7 +394,7 @@ namespace System.Net.Test.Common
             {
                 UseSsl = false;
                 SslProtocols =
-#if !NETSTANDARD2_0
+#if !NETSTANDARD2_0 && !NETFRAMEWORK
                 SslProtocols.Tls13 |
 #endif
                 SslProtocols.Tls | SslProtocols.Tls11 | SslProtocols.Tls12;
@@ -451,6 +451,11 @@ namespace System.Net.Test.Common
                 }
 
                 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
index 733b29f..34de8ff 100644 (file)
@@ -28,6 +28,7 @@ namespace System.Net.Http.Functional.Tests
 
         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 @@ namespace System.Net.Http.Functional.Tests
                 }
             }
         }
+#endif
 
         [OuterLoop("Uses external servers")]
         [Theory, MemberData(nameof(RemoteServersMemberData))]
index da276e1..432b868 100644 (file)
@@ -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 @@ namespace System.Net.Test.Common
             }
 
             (int varIntLength, int stringLength) = DecodeInteger(buffer, prefixMask);
-            string value = Encoding.ASCII.GetString(buffer.Slice(varIntLength, stringLength));
+#if !NETFRAMEWORK
+            ReadOnlySpan<byte> 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 @@ namespace System.Net.Test.Common
             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
+    }
 }
index 8df08a7..67812f9 100644 (file)
@@ -80,7 +80,11 @@ namespace System.Net.Http.Functional.Tests
 
                         case 4:
                             // Individual calls to Read(Span)
+#if !NETFRAMEWORK
                             while ((bytesRead = stream.Read(new Span<byte>(buffer))) != 0)
+#else
+                            while ((bytesRead = stream.Read(buffer, 0, buffer.Length)) != 0)
+#endif
                             {
                                 ms.Write(buffer, 0, bytesRead);
                             }
@@ -173,7 +177,9 @@ namespace System.Net.Http.Functional.Tests
             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<byte>(new byte[1], 0, 0)));
+#endif
                 Assert.Equal(0, await stream.ReadAsync(new byte[1], 0, 0));
             }
         }
@@ -221,7 +227,7 @@ namespace System.Net.Http.Functional.Tests
                 }
             }
         }
-
+#if NETCOREAPP
         [Theory]
         [InlineData(TransferType.ContentLength, TransferError.ContentLengthTooLarge)]
         [InlineData(TransferType.Chunked, TransferError.MissingChunkTerminator)]
@@ -249,6 +255,7 @@ namespace System.Net.Http.Functional.Tests
                 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 (file)
index 0000000..0d21fd2
--- /dev/null
@@ -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<byte> memory)
+        {
+            bool isArray = MemoryMarshal.TryGetArray(memory, out ArraySegment<byte> 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<int> ReadAsync(this Stream stream, ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken)
+        {
+            bool isArray = MemoryMarshal.TryGetArray(buffer, out ArraySegment<byte> segment);
+            Assert.True(isArray);
+
+            return new ValueTask<int>(stream.ReadAsync(segment.Array, segment.Offset, segment.Count, cancellationToken));
+        }
+    }
+}
index 6913359..8219a43 100644 (file)
@@ -786,6 +786,8 @@ namespace System.Net.Http
             {
                 EnsureSessionHandleExists(state);
 
+                SetEnableHttp2PlusClientCertificate(state.RequestMessage.RequestUri, state.RequestMessage.Version);
+
                 // Specify an HTTP server.
                 connectHandle = Interop.WinHttp.WinHttpConnect(
                     _sessionHandle,
@@ -996,7 +998,7 @@ namespace System.Net.Http
             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 @@ namespace System.Net.Http
             }
         }
 
-        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 @@ namespace System.Net.Http
             }
         }
 
+        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 (file)
index 0000000..58ebefb
--- /dev/null
@@ -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 (file)
index 0000000..9dfdeef
--- /dev/null
@@ -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");
+            };
+        }
+    }
+}
index d1798cd..516e9f7 100644 (file)
@@ -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 @@ namespace System.Net.Http.Functional.Tests
     {
         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;
             }
index a871abc..3bd5bc4 100644 (file)
@@ -192,16 +192,192 @@ namespace System.Net.Http.Functional.Tests
         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) { }
+    }
 }
index 5ad5587..f459c29 100644 (file)
@@ -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 @@ namespace System.Net.Http.WinHttpHandlerFunctional.Tests
         {
             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 @@ namespace System.Net.Http.WinHttpHandlerFunctional.Tests
             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 @@ namespace System.Net.Http.WinHttpHandlerFunctional.Tests
                 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 @@ namespace System.Net.Http.WinHttpHandlerFunctional.Tests
         [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 @@ namespace System.Net.Http.WinHttpHandlerFunctional.Tests
                 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 @@ namespace System.Net.Http.WinHttpHandlerFunctional.Tests
             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<HttpRequestException>(() =>
                     client.GetAsync(System.Net.Test.Common.Configuration.Http.SecureRemoteEchoServer));
@@ -122,70 +112,5 @@ namespace System.Net.Http.WinHttpHandlerFunctional.Tests
                 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;
-            }
-        }
     }
 }
index 82e2835..599da9a 100644 (file)
@@ -1,8 +1,9 @@
-<Project Sdk="Microsoft.NET.Sdk">
+<Project Sdk="Microsoft.NET.Sdk">
   <PropertyGroup>
     <TargetFrameworks>$(NetCoreAppCurrent)-Windows_NT;$(NetFrameworkCurrent)-Windows_NT</TargetFrameworks>
     <IncludeRemoteExecutor>true</IncludeRemoteExecutor>
     <DefineConstants>$(DefineConstants);WINHTTPHANDLER_TEST</DefineConstants>
+    <LangVersion>8.0</LangVersion>
   </PropertyGroup>
   <ItemGroup Condition=" '$(TargetsWindows)' == 'true' ">
     <Compile Include="$(CommonTestPath)System\Net\Configuration.cs">
     <Compile Include="$(CommonTestPath)System\Net\Configuration.Http.cs">
       <Link>Common\System\Net\Configuration.Http.cs</Link>
     </Compile>
+    <Compile Include="BaseCertificateTest.cs" />
     <Compile Include="ServerCertificateTest.cs" />
     <Compile Include="WinHttpHandlerTest.cs" />
-    <Compile Include="XunitTestAssemblyAtrributes.cs" />
-  </ItemGroup>  
-  <ItemGroup Condition=" '$(TargetsWindows)' == 'true' And '$(TargetsNetFx)' != 'true' ">  
+    <Compile Include="XunitTestAssemblyAtrributes.cs" /> 
     <Compile Include="$(CommonPath)\System\Net\Http\HttpHandlerDefaults.cs">
       <Link>Common\System\Net\Http\HttpHandlerDefaults.cs</Link>
     </Compile>
@@ -52,6 +52,9 @@
     <Compile Include="$(CommonTestPath)System\Net\TestWebProxies.cs">
       <Link>Common\System\Net\TestWebProxies.cs</Link>
     </Compile>
+    <Compile Include="$(CommonTestPath)System\Net\StreamArrayExtensions.cs">
+      <Link>Common\System\Net\StreamArrayExtensions.cs</Link>
+    </Compile>
     <Compile Include="$(CommonTestPath)System\Net\Http\ByteAtATimeContent.cs">
       <Link>Common\System\Net\Http\ByteAtATimeContent.cs</Link>
     </Compile>
     <Compile Include="HttpClientHandlerTestBase.WinHttpHandler.cs" />
     <Compile Include="WinHttpClientHandler.cs" />
     <Compile Include="PlatformHandlerTest.cs" />
+    <Compile Include="ClientCertificateTest.cs" />
   </ItemGroup>
   <ItemGroup>
     <PackageReference Include="System.Net.TestData" Version="$(SystemNetTestDataVersion)" />
index 2f8ccd3..611e6c5 100644 (file)
@@ -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 @@ namespace System.Net.Http
             ReceiveHeadersTimeout = Timeout.InfiniteTimeSpan;
             ReceiveDataTimeout = Timeout.InfiniteTimeSpan;
             SendTimeout = Timeout.InfiniteTimeSpan;
+
+            _requestVersion = requestVersion;
         }
 
         public virtual bool SupportsAutomaticDecompression => true;
@@ -178,6 +182,11 @@ namespace System.Net.Http
                 }
             }
 
+            if(_requestVersion >= HttpVersion20.Value)
+            {
+                request.Version = _requestVersion;
+            }
+
             return base.SendAsync(request, cancellationToken);
         }
     }
index 5c728fc..0519437 100644 (file)
@@ -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 @@ namespace System.Net.Http.WinHttpHandlerFunctional.Tests
             }
         }
 
+        [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<string, string>() });
+                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);