The tests used by PlatformHandlerTest moved to Common/tests in order to share them between System.Net.Http and System.Net.Http.WinHttpHandler.
The added conditions correspond to the state before WinHttpHandler removal. They're just based on IsWinHttpHandler instead of UseSocketHandler.
Some of the code must be conditionally compiled since there is no usable common base class for HttpClientHandler and WinHttpHandler. Other issues solved by introducing WinHttpClientHandler for testing purposes. It corresponds to HttpClientHandler.Windows.cs code from before its removal.
--- /dev/null
+// 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.Threading.Tasks;
+
+namespace System.Net.Http.Functional.Tests
+{
+ internal sealed class ByteAtATimeContent : HttpContent
+ {
+ private readonly Task _waitToSend;
+ private readonly TaskCompletionSource<bool> _startedSend;
+ private readonly int _length;
+ private readonly int _millisecondDelayBetweenBytes;
+
+ public ByteAtATimeContent(int length) : this(length, Task.CompletedTask, new TaskCompletionSource<bool>(), millisecondDelayBetweenBytes: 0) { }
+
+ public ByteAtATimeContent(int length, Task waitToSend, TaskCompletionSource<bool> startedSend, int millisecondDelayBetweenBytes)
+ {
+ _length = length;
+ _waitToSend = waitToSend;
+ _startedSend = startedSend;
+ _millisecondDelayBetweenBytes = millisecondDelayBetweenBytes;
+ }
+
+ protected override async Task SerializeToStreamAsync(Stream stream, TransportContext context)
+ {
+ await _waitToSend;
+ _startedSend.SetResult(true);
+
+ var buffer = new byte[1];
+ for (int i = 0; i < _length; i++)
+ {
+ buffer[0] = (byte)i;
+ await stream.WriteAsync(buffer);
+ await stream.FlushAsync();
+ await Task.Delay(_millisecondDelayBetweenBytes);
+ }
+ }
+
+ protected override bool TryComputeLength(out long length)
+ {
+ length = _length;
+ return true;
+ }
+ }
+}
--- /dev/null
+// 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.Security.Authentication.ExtendedProtection;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace System.Net.Http.Functional.Tests
+{
+ internal sealed class ChannelBindingAwareContent : HttpContent
+ {
+ private readonly byte[] _content;
+
+ public ChannelBindingAwareContent(string content)
+ {
+ _content = Encoding.UTF8.GetBytes(content);
+ }
+
+ public ChannelBinding ChannelBinding { get ; private set; }
+
+ protected override Task SerializeToStreamAsync(Stream stream, TransportContext context)
+ {
+ ChannelBinding = context.GetChannelBinding(ChannelBindingKind.Endpoint);
+ return stream.WriteAsync(_content, 0, _content.Length);
+ }
+
+ protected override bool TryComputeLength(out long length)
+ {
+ length = _content.Length;
+ return true;
+ }
+
+ protected override Task<Stream> CreateContentReadStreamAsync()
+ {
+ return Task.FromResult<Stream>(new MemoryStream(_content, 0, _content.Length, writable: false));
+ }
+ }
+}
--- /dev/null
+// 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.Threading.Tasks;
+
+namespace System.Net.Http.Functional.Tests
+{
+ internal partial class CustomContent : HttpContent
+ {
+ private readonly Stream _stream;
+
+ public CustomContent(Stream stream) => _stream = stream;
+
+ protected override Task<Stream> CreateContentReadStreamAsync() =>
+ Task.FromResult(_stream);
+
+ protected override Task SerializeToStreamAsync(Stream stream, TransportContext context) =>
+ _stream.CopyToAsync(stream);
+
+ protected override bool TryComputeLength(out long length)
+ {
+ if (_stream.CanSeek)
+ {
+ length = _stream.Length;
+ return true;
+ }
+ else
+ {
+ length = 0;
+ return false;
+ }
+ }
+
+ internal class CustomStream : Stream
+ {
+ private byte[] _buffer;
+ private long _position;
+ private bool _rewindable;
+ internal Exception _failException;
+
+ public CustomStream(byte[] buffer, bool rewindable)
+ {
+ _buffer = buffer;
+ _position = 0;
+ _rewindable = rewindable;
+ }
+
+ public override bool CanRead
+ {
+ get { return true; }
+ }
+
+ public override bool CanSeek
+ {
+ get { return _rewindable; }
+ }
+
+ public override bool CanWrite
+ {
+ get { return false; }
+ }
+
+ public override void Flush()
+ {
+ throw new NotSupportedException("CustomStream.Flush");
+ }
+
+ public override long Length
+ {
+ get
+ {
+ if (_rewindable)
+ {
+ return (long)_buffer.Length;
+ }
+ else
+ {
+ throw new NotSupportedException("CustomStream.Length");
+ }
+ }
+ }
+
+ public override long Position
+ {
+ get
+ {
+ return _position;
+ }
+ set
+ {
+ if (_rewindable)
+ {
+ _position = value;
+ }
+ else
+ {
+ throw new NotSupportedException("CustomStream.Position");
+ }
+ }
+ }
+
+ public override int Read(byte[] buffer, int offset, int count)
+ {
+ int bytesRead = 0;
+
+ if (_failException != null)
+ {
+ throw _failException;
+ }
+
+ for (int i = 0; i < count; i++)
+ {
+ if (_position >= _buffer.Length)
+ {
+ break;
+ }
+
+ buffer[offset] = _buffer[_position];
+ bytesRead++;
+ offset++;
+ _position++;
+ }
+
+ return bytesRead;
+ }
+
+ public override long Seek(long offset, SeekOrigin origin)
+ {
+ if (_rewindable)
+ {
+ switch (origin)
+ {
+ case SeekOrigin.Begin:
+ Position = offset;
+ break;
+ case SeekOrigin.Current:
+ Position += offset;
+ break;
+ case SeekOrigin.End:
+ Position = _buffer.Length + offset;
+ break;
+ default:
+ throw new NotImplementedException("CustomStream.Seek");
+ }
+ return Position;
+ }
+ else
+ {
+ throw new NotImplementedException("CustomStream.Seek");
+ }
+ }
+
+ public override void SetLength(long value)
+ {
+ throw new NotSupportedException("CustomStream.SetLength");
+ }
+
+ public override void Write(byte[] buffer, int offset, int count)
+ {
+ throw new NotSupportedException("CustomStream.Write");
+ }
+
+ public void SetException(Exception e)
+ {
+ _failException = e;
+ }
+ }
+ }
+}
--- /dev/null
+// 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.Collections.Generic;
+using System.Security.Principal;
+using System.Threading.Tasks;
+
+using Xunit;
+using Xunit.Abstractions;
+
+namespace System.Net.Http.Functional.Tests
+{
+ using Configuration = System.Net.Test.Common.Configuration;
+
+#if WINHTTPHANDLER_TEST
+ using HttpClientHandler = System.Net.Http.WinHttpClientHandler;
+#endif
+
+ [PlatformSpecific(TestPlatforms.Windows)]
+ public abstract class DefaultCredentialsTest : HttpClientHandlerTestBase
+ {
+ private static bool DomainJoinedTestsEnabled => !string.IsNullOrEmpty(Configuration.Http.DomainJoinedHttpHost);
+
+ private static bool DomainProxyTestsEnabled => !string.IsNullOrEmpty(Configuration.Http.DomainJoinedProxyHost);
+
+ // Enable this to test against local HttpListener over loopback
+ // Note this doesn't work as expected with WinHttpHandler, because WinHttpHandler will always authenticate the
+ // current user against a loopback server using NTLM or Negotiate.
+ private static bool LocalHttpListenerTestsEnabled = false;
+
+ public static bool ServerAuthenticationTestsEnabled => (LocalHttpListenerTestsEnabled || DomainJoinedTestsEnabled);
+
+ private static string s_specificUserName = Configuration.Security.ActiveDirectoryUserName;
+ private static string s_specificPassword = Configuration.Security.ActiveDirectoryUserPassword;
+ private static string s_specificDomain = Configuration.Security.ActiveDirectoryName;
+ private readonly NetworkCredential _specificCredential =
+ new NetworkCredential(s_specificUserName, s_specificPassword, s_specificDomain);
+ private static Uri s_authenticatedServer = DomainJoinedTestsEnabled ?
+ new Uri($"http://{Configuration.Http.DomainJoinedHttpHost}/test/auth/negotiate/showidentity.ashx") : null;
+
+ public DefaultCredentialsTest(ITestOutputHelper output) : base(output) { }
+
+ [OuterLoop("Uses external server")]
+ [ConditionalTheory(nameof(ServerAuthenticationTestsEnabled))]
+ [MemberData(nameof(AuthenticatedServers))]
+ public async Task UseDefaultCredentials_DefaultValue_Unauthorized(string uri, bool useProxy)
+ {
+ HttpClientHandler handler = CreateHttpClientHandler();
+ handler.UseProxy = useProxy;
+
+ using (HttpClient client = CreateHttpClient(handler))
+ using (HttpResponseMessage response = await client.GetAsync(uri))
+ {
+ Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
+ }
+ }
+
+ [OuterLoop("Uses external server")]
+ [ConditionalTheory(nameof(ServerAuthenticationTestsEnabled))]
+ [MemberData(nameof(AuthenticatedServers))]
+ public async Task UseDefaultCredentials_SetFalse_Unauthorized(string uri, bool useProxy)
+ {
+ HttpClientHandler handler = CreateHttpClientHandler();
+ handler.UseProxy = useProxy;
+ handler.UseDefaultCredentials = false;
+
+ using (HttpClient client = CreateHttpClient(handler))
+ using (HttpResponseMessage response = await client.GetAsync(uri))
+ {
+ Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
+ }
+ }
+
+ [OuterLoop("Uses external server")]
+ [ConditionalTheory(nameof(ServerAuthenticationTestsEnabled))]
+ [MemberData(nameof(AuthenticatedServers))]
+ public async Task UseDefaultCredentials_SetTrue_ConnectAsCurrentIdentity(string uri, bool useProxy)
+ {
+ HttpClientHandler handler = CreateHttpClientHandler();
+ handler.UseProxy = useProxy;
+ handler.UseDefaultCredentials = true;
+
+ using (HttpClient client = CreateHttpClient(handler))
+ using (HttpResponseMessage response = await client.GetAsync(uri))
+ {
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+
+ string responseBody = await response.Content.ReadAsStringAsync();
+ WindowsIdentity currentIdentity = WindowsIdentity.GetCurrent();
+ _output.WriteLine("currentIdentity={0}", currentIdentity.Name);
+ VerifyAuthentication(responseBody, true, currentIdentity.Name);
+ }
+ }
+
+ [OuterLoop("Uses external server")]
+ [ConditionalTheory(nameof(ServerAuthenticationTestsEnabled))]
+ [MemberData(nameof(AuthenticatedServers))]
+ public async Task Credentials_SetToWrappedDefaultCredential_ConnectAsCurrentIdentity(string uri, bool useProxy)
+ {
+ HttpClientHandler handler = CreateHttpClientHandler();
+ handler.UseProxy = useProxy;
+ handler.Credentials = new CredentialWrapper
+ {
+ InnerCredentials = CredentialCache.DefaultCredentials
+ };
+
+ using (HttpClient client = CreateHttpClient(handler))
+ using (HttpResponseMessage response = await client.GetAsync(uri))
+ {
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+
+ string responseBody = await response.Content.ReadAsStringAsync();
+ WindowsIdentity currentIdentity = WindowsIdentity.GetCurrent();
+ _output.WriteLine("currentIdentity={0}", currentIdentity.Name);
+ VerifyAuthentication(responseBody, true, currentIdentity.Name);
+ }
+ }
+
+ [OuterLoop("Uses external server")]
+ [ConditionalTheory(nameof(ServerAuthenticationTestsEnabled))]
+ [MemberData(nameof(AuthenticatedServers))]
+ public async Task Credentials_SetToBadCredential_Unauthorized(string uri, bool useProxy)
+ {
+ HttpClientHandler handler = CreateHttpClientHandler();
+ handler.UseProxy = useProxy;
+ handler.Credentials = new NetworkCredential("notarealuser", "123456");
+
+ using (HttpClient client = CreateHttpClient(handler))
+ using (HttpResponseMessage response = await client.GetAsync(uri))
+ {
+ Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
+ }
+ }
+
+ [OuterLoop("Uses external server")]
+ [ActiveIssue("https://github.com/dotnet/corefx/issues/10041")]
+ [ConditionalTheory(nameof(DomainJoinedTestsEnabled))]
+ [InlineData(false)]
+ [InlineData(true)]
+ public async Task Credentials_SetToSpecificCredential_ConnectAsSpecificIdentity(bool useProxy)
+ {
+ HttpClientHandler handler = CreateHttpClientHandler();
+ handler.UseProxy = useProxy;
+ handler.UseDefaultCredentials = false;
+ handler.Credentials = _specificCredential;
+
+ using (HttpClient client = CreateHttpClient(handler))
+ using (HttpResponseMessage response = await client.GetAsync(s_authenticatedServer))
+ {
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+
+ string responseBody = await response.Content.ReadAsStringAsync();
+ VerifyAuthentication(responseBody, true, s_specificDomain + "\\" + s_specificUserName);
+ }
+ }
+
+ [OuterLoop("Uses external server")]
+ [ActiveIssue("https://github.com/dotnet/corefx/issues/10041")]
+ [ConditionalFact(nameof(DomainProxyTestsEnabled))]
+ public async Task Proxy_UseAuthenticatedProxyWithNoCredentials_ProxyAuthenticationRequired()
+ {
+ HttpClientHandler handler = CreateHttpClientHandler();
+ handler.Proxy = new AuthenticatedProxy(null);
+
+ using (HttpClient client = CreateHttpClient(handler))
+ using (HttpResponseMessage response = await client.GetAsync(Configuration.Http.RemoteEchoServer))
+ {
+ Assert.Equal(HttpStatusCode.ProxyAuthenticationRequired, response.StatusCode);
+ }
+ }
+
+ [OuterLoop("Uses external server")]
+ [ActiveIssue("https://github.com/dotnet/corefx/issues/10041")]
+ [ConditionalFact(nameof(DomainProxyTestsEnabled))]
+ public async Task Proxy_UseAuthenticatedProxyWithDefaultCredentials_OK()
+ {
+ HttpClientHandler handler = CreateHttpClientHandler();
+ handler.Proxy = new AuthenticatedProxy(CredentialCache.DefaultCredentials);
+
+ using (HttpClient client = CreateHttpClient(handler))
+ using (HttpResponseMessage response = await client.GetAsync(Configuration.Http.RemoteEchoServer))
+ {
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ }
+ }
+
+ [OuterLoop("Uses external server")]
+ [ConditionalFact(nameof(DomainProxyTestsEnabled))]
+ public async Task Proxy_UseAuthenticatedProxyWithWrappedDefaultCredentials_OK()
+ {
+ ICredentials wrappedCreds = new CredentialWrapper
+ {
+ InnerCredentials = CredentialCache.DefaultCredentials
+ };
+
+ HttpClientHandler handler = CreateHttpClientHandler();
+ handler.Proxy = new AuthenticatedProxy(wrappedCreds);
+
+ using (HttpClient client = CreateHttpClient(handler))
+ using (HttpResponseMessage response = await client.GetAsync(Configuration.Http.RemoteEchoServer))
+ {
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ }
+ }
+
+ public static IEnumerable<object[]> AuthenticatedServers()
+ {
+ // Note that localhost will not actually use the proxy, but there's no harm in testing it.
+ foreach (bool b in new bool[] { true, false })
+ {
+ if (LocalHttpListenerTestsEnabled)
+ {
+ yield return new object[] { HttpListenerAuthenticatedLoopbackServer.NtlmOnly.Uri, b };
+ yield return new object[] { HttpListenerAuthenticatedLoopbackServer.NegotiateOnly.Uri, b };
+ yield return new object[] { HttpListenerAuthenticatedLoopbackServer.NegotiateAndNtlm.Uri, b };
+ yield return new object[] { HttpListenerAuthenticatedLoopbackServer.BasicAndNtlm.Uri, b };
+ }
+
+ if (!string.IsNullOrEmpty(Configuration.Http.DomainJoinedHttpHost))
+ {
+ yield return new object[] { $"http://{Configuration.Http.DomainJoinedHttpHost}/test/auth/negotiate/showidentity.ashx", b };
+ yield return new object[] { $"http://{Configuration.Http.DomainJoinedHttpHost}/test/auth/multipleschemes/showidentity.ashx", b };
+ }
+ }
+ }
+
+ private void VerifyAuthentication(string response, bool authenticated, string user)
+ {
+ // Convert all strings to lowercase to compare. Windows treats domain and username as case-insensitive.
+ response = response.ToLower();
+ user = user.ToLower();
+ _output.WriteLine(response);
+
+ if (!authenticated)
+ {
+ Assert.True(
+ TestHelper.JsonMessageContainsKeyValue(response, "authenticated", "false"),
+ "authenticated == false");
+ }
+ else
+ {
+ Assert.True(
+ TestHelper.JsonMessageContainsKeyValue(response, "authenticated", "true"),
+ "authenticated == true");
+ Assert.True(
+ TestHelper.JsonMessageContainsKeyValue(response, "user", user),
+ $"user == {user}");
+ }
+ }
+
+ private class CredentialWrapper : ICredentials
+ {
+ public ICredentials InnerCredentials { get; set; }
+
+ public NetworkCredential GetCredential(Uri uri, string authType) =>
+ InnerCredentials?.GetCredential(uri, authType);
+ }
+
+ private class AuthenticatedProxy : IWebProxy
+ {
+ ICredentials _credentials;
+ Uri _proxyUri;
+
+ public AuthenticatedProxy(ICredentials credentials)
+ {
+ _credentials = credentials;
+
+ string host = Configuration.Http.DomainJoinedProxyHost;
+ Assert.False(string.IsNullOrEmpty(host), "DomainJoinedProxyHost must specify proxy hostname");
+
+ string portString = Configuration.Http.DomainJoinedProxyPort;
+ Assert.False(string.IsNullOrEmpty(portString), "DomainJoinedProxyPort must specify proxy port number");
+
+ int port;
+ Assert.True(int.TryParse(portString, out port), "DomainJoinedProxyPort must be a valid port number");
+
+ _proxyUri = new Uri(string.Format("http://{0}:{1}", host, port));
+ }
+
+ public ICredentials Credentials
+ {
+ get
+ {
+ return _credentials;
+ }
+
+ set
+ {
+ throw new NotImplementedException();
+ }
+ }
+
+ public Uri GetProxy(Uri destination)
+ {
+ return _proxyUri;
+ }
+
+ public bool IsBypassed(Uri host)
+ {
+ return false;
+ }
+ }
+
+ private sealed class HttpListenerAuthenticatedLoopbackServer
+ {
+ private readonly HttpListener _listener;
+ private readonly string _uri;
+
+ public static readonly HttpListenerAuthenticatedLoopbackServer NtlmOnly = new HttpListenerAuthenticatedLoopbackServer("http://localhost:8080/", AuthenticationSchemes.Ntlm);
+ public static readonly HttpListenerAuthenticatedLoopbackServer NegotiateOnly = new HttpListenerAuthenticatedLoopbackServer("http://localhost:8081/", AuthenticationSchemes.Negotiate);
+ public static readonly HttpListenerAuthenticatedLoopbackServer NegotiateAndNtlm = new HttpListenerAuthenticatedLoopbackServer("http://localhost:8082/", AuthenticationSchemes.Negotiate | AuthenticationSchemes.Ntlm);
+ public static readonly HttpListenerAuthenticatedLoopbackServer BasicAndNtlm = new HttpListenerAuthenticatedLoopbackServer("http://localhost:8083/", AuthenticationSchemes.Basic | AuthenticationSchemes.Ntlm);
+
+ // Don't construct directly, use instances above
+ private HttpListenerAuthenticatedLoopbackServer(string uri, AuthenticationSchemes authenticationSchemes)
+ {
+ _uri = uri;
+
+ _listener = new HttpListener();
+ _listener.Prefixes.Add(uri);
+ _listener.AuthenticationSchemes = authenticationSchemes;
+ _listener.Start();
+
+ Task.Run(() => ProcessRequests());
+ }
+
+ public string Uri => _uri;
+
+ private async Task ProcessRequests()
+ {
+ while (true)
+ {
+ var context = await _listener.GetContextAsync();
+
+ // 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}\" }}"));
+
+ context.Response.Close();
+ }
+ }
+ }
+ }
+}
--- /dev/null
+// 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.Threading;
+using System.Threading.Tasks;
+
+namespace System.Net.Http.Functional.Tests
+{
+ public sealed class DribbleStream : Stream
+ {
+ private readonly Stream _wrapped;
+ private readonly bool _clientDisconnectAllowed;
+
+ public DribbleStream(Stream wrapped, bool clientDisconnectAllowed = false)
+ {
+ _wrapped = wrapped;
+ _clientDisconnectAllowed = clientDisconnectAllowed;
+ }
+
+ public override async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
+ {
+ try
+ {
+ for (int i = 0; i < count; i++)
+ {
+ await _wrapped.WriteAsync(buffer, offset + i, 1);
+ await _wrapped.FlushAsync();
+ await Task.Yield(); // introduce short delays, enough to send packets individually but not so long as to extend test duration significantly
+ }
+ }
+ catch (IOException) when (_clientDisconnectAllowed)
+ {
+ }
+ }
+
+ public override void Write(byte[] buffer, int offset, int count)
+ {
+ try
+ {
+ for (int i = 0; i < count; i++)
+ {
+ _wrapped.Write(buffer, offset + i, 1);
+ _wrapped.Flush();
+ }
+ }
+ catch (IOException) when (_clientDisconnectAllowed)
+ {
+ }
+ }
+
+ public override bool CanRead => _wrapped.CanRead;
+ public override bool CanSeek => _wrapped.CanSeek;
+ public override bool CanWrite => _wrapped.CanWrite;
+ public override long Length => _wrapped.Length;
+ public override long Position { get => _wrapped.Position; set => _wrapped.Position = value; }
+ public override void Flush() => _wrapped.Flush();
+ public override Task FlushAsync(CancellationToken cancellationToken) => _wrapped.FlushAsync(cancellationToken);
+ public override int Read(byte[] buffer, int offset, int count) => _wrapped.Read(buffer, offset, count);
+ public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) => _wrapped.ReadAsync(buffer, offset, count, cancellationToken);
+ public override long Seek(long offset, SeekOrigin origin) => _wrapped.Seek(offset, origin);
+ public override void SetLength(long value) => _wrapped.SetLength(value);
+ public override Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken) => _wrapped.CopyToAsync(destination, bufferSize, cancellationToken);
+ public override void Close() => _wrapped.Close();
+ protected override void Dispose(bool disposing) => _wrapped.Dispose();
+ }
+}
--- /dev/null
+// 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.Collections.Generic;
+using System.IO;
+using System.Reflection;
+using System.Threading.Tasks;
+
+using Xunit;
+using Xunit.Abstractions;
+
+namespace System.Net.Http.Functional.Tests
+{
+ public abstract class HttpClient_SelectedSites_Test : HttpClientHandlerTestBase
+ {
+ public HttpClient_SelectedSites_Test(ITestOutputHelper output) : base(output) { }
+
+ public static bool IsSelectedSitesTestEnabled()
+ {
+ string envVar = Environment.GetEnvironmentVariable("CORFX_NET_HTTP_SELECTED_SITES");
+ return envVar != null &&
+ (envVar.Equals("true", StringComparison.OrdinalIgnoreCase) || envVar.Equals("1"));
+ }
+
+ [ConditionalTheory(nameof(IsSelectedSitesTestEnabled))]
+ [Trait("SelectedSites", "true")]
+ [MemberData(nameof(GetSelectedSites))]
+ public async Task RetrieveSite_Succeeds(string site)
+ {
+ int remainingAttempts = 2;
+ while (remainingAttempts-- > 0)
+ {
+ try
+ {
+ await VisitSite(site);
+ return;
+ }
+ catch
+ {
+ if (remainingAttempts < 1)
+ throw;
+ await Task.Delay(1500);
+ }
+ }
+
+ throw new Exception("Not expected to reach here");
+ }
+
+ [ConditionalTheory(nameof(IsSelectedSitesTestEnabled))]
+ [Trait("SiteInvestigation", "true")]
+ [InlineData("http://microsoft.com")]
+ public async Task RetrieveSite_Debug_Helper(string site)
+ {
+ await VisitSite(site);
+ }
+
+ public static IEnumerable<object[]> GetSelectedSites()
+ {
+ const string resourceName = "SelectedSitesTest.txt";
+ Assembly assembly = typeof(HttpClient_SelectedSites_Test).Assembly;
+ Stream s = assembly.GetManifestResourceStream(resourceName);
+ if (s == null)
+ {
+ throw new Exception("Couldn't find resource " + resourceName);
+ }
+
+ using (var reader = new StreamReader(s))
+ {
+ string site;
+ while (null != (site = reader.ReadLine()))
+ {
+ yield return new[] { site };
+ }
+ }
+ }
+
+ private async Task VisitSite(string site)
+ {
+ using (HttpClient httpClient = CreateHttpClientForSiteVisit())
+ {
+ await VisitSiteWithClient(site, httpClient);
+ }
+ }
+
+ private async Task VisitSiteWithClient(string site, HttpClient httpClient)
+ {
+ using (HttpResponseMessage response = await httpClient.GetAsync(site))
+ {
+ switch (response.StatusCode)
+ {
+ case HttpStatusCode.Redirect:
+ case HttpStatusCode.OK:
+ if (response.Content.Headers.ContentLength > 0)
+ Assert.Equal(response.Content.Headers.ContentLength.Value, (await response.Content.ReadAsByteArrayAsync()).Length);
+ break;
+ case HttpStatusCode.BadGateway:
+ case HttpStatusCode.Forbidden:
+ case HttpStatusCode.Moved:
+ case HttpStatusCode.NotFound:
+ case HttpStatusCode.ServiceUnavailable:
+ case HttpStatusCode.Unauthorized:
+ case HttpStatusCode.InternalServerError:
+ break;
+ default:
+ throw new Exception($"{site} returned: {response.StatusCode}");
+ }
+ }
+ }
+
+ private HttpClient CreateHttpClientForSiteVisit()
+ {
+ HttpClient httpClient = CreateHttpClient(CreateHttpClientHandler());
+
+ // Some extra headers since some sites only give proper responses when they are present.
+ httpClient.DefaultRequestHeaders.Add(
+ "User-Agent",
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.186 Safari/537.36");
+ httpClient.DefaultRequestHeaders.Add(
+ "Accept-Language",
+ "en-US,en;q=0.9");
+ httpClient.DefaultRequestHeaders.Add(
+ "Accept-Encoding",
+ "gzip, deflate, br");
+ httpClient.DefaultRequestHeaders.Add(
+ "Accept",
+ "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8");
+
+ return httpClient;
+ }
+ }
+}
--- /dev/null
+// 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.Runtime.InteropServices;
+using System.Security.Authentication;
+using System.Security.Cryptography.X509Certificates;
+using System.Threading.Tasks;
+
+using Xunit;
+using Xunit.Abstractions;
+
+namespace System.Net.Http.Functional.Tests
+{
+ using Configuration = System.Net.Test.Common.Configuration;
+
+#if WINHTTPHANDLER_TEST
+ using HttpClientHandler = System.Net.Http.WinHttpClientHandler;
+#endif
+
+ public abstract class HttpClientEKUTest : HttpClientHandlerTestBase
+ {
+ private static bool CanTestCertificates =>
+ Capability.IsTrustedRootCertificateInstalled() && Capability.AreHostsFileNamesInstalled();
+
+ public const int TestTimeoutMilliseconds = 15 * 1000;
+
+ public static X509Certificate2 serverCertificateServerEku = Configuration.Certificates.GetServerCertificate();
+ public static X509Certificate2 serverCertificateNoEku = Configuration.Certificates.GetNoEKUCertificate();
+ public static X509Certificate2 serverCertificateWrongEku = Configuration.Certificates.GetClientCertificate();
+
+ public static X509Certificate2 clientCertificateWrongEku = Configuration.Certificates.GetServerCertificate();
+ public static X509Certificate2 clientCertificateNoEku = Configuration.Certificates.GetNoEKUCertificate();
+ public static X509Certificate2 clientCertificateClientEku = Configuration.Certificates.GetClientCertificate();
+
+ private VerboseTestLogging _log = VerboseTestLogging.GetInstance();
+
+ public HttpClientEKUTest(ITestOutputHelper output) : base(output) { }
+
+ [ConditionalFact(nameof(CanTestCertificates))]
+ public async Task HttpClient_NoEKUServerAuth_Ok()
+ {
+ var options = new HttpsTestServer.Options();
+ options.ServerCertificate = serverCertificateNoEku;
+
+ using (var server = new HttpsTestServer(options))
+ using (HttpClientHandler handler = CreateHttpClientHandler())
+ using (HttpClient client = CreateHttpClient(handler))
+ {
+ server.Start();
+
+ var tasks = new Task[2];
+ tasks[0] = server.AcceptHttpsClientAsync();
+
+ string requestUriString = GetUriStringAndConfigureHandler(options, server, handler);
+ tasks[1] = client.GetStringAsync(requestUriString);
+
+ await tasks.WhenAllOrAnyFailed(TestTimeoutMilliseconds);
+ }
+ }
+
+ [ConditionalFact(nameof(CanTestCertificates))]
+ public async Task HttpClient_ClientEKUServerAuth_Fails()
+ {
+ var options = new HttpsTestServer.Options();
+ options.ServerCertificate = serverCertificateWrongEku;
+
+ using (var server = new HttpsTestServer(options))
+ using (HttpClientHandler handler = CreateHttpClientHandler())
+ using (HttpClient client = CreateHttpClient(handler))
+ {
+ server.Start();
+
+ var tasks = new Task[2];
+ tasks[0] = server.AcceptHttpsClientAsync();
+
+ string requestUriString = GetUriStringAndConfigureHandler(options, server, handler);
+ tasks[1] = client.GetStringAsync(requestUriString);
+
+ await Assert.ThrowsAsync<HttpRequestException>(() => tasks[1]);
+ }
+ }
+
+ [ConditionalFact(nameof(CanTestCertificates))]
+ public async Task HttpClient_NoEKUClientAuth_Ok()
+ {
+ var options = new HttpsTestServer.Options();
+ options.ServerCertificate = serverCertificateServerEku;
+ options.RequireClientAuthentication = true;
+
+ using (var server = new HttpsTestServer(options))
+ using (HttpClientHandler handler = CreateHttpClientHandler())
+ using (HttpClient client = CreateHttpClient(handler))
+ {
+ server.Start();
+
+ var tasks = new Task[2];
+ tasks[0] = server.AcceptHttpsClientAsync();
+
+ string requestUriString = GetUriStringAndConfigureHandler(options, server, handler);
+ handler.ClientCertificates.Add(clientCertificateNoEku);
+ tasks[1] = client.GetStringAsync(requestUriString);
+
+ await tasks.WhenAllOrAnyFailed(TestTimeoutMilliseconds);
+ }
+ }
+
+ [ConditionalFact(nameof(CanTestCertificates))]
+ public async Task HttpClient_ServerEKUClientAuth_Fails()
+ {
+ var options = new HttpsTestServer.Options();
+ options.ServerCertificate = serverCertificateServerEku;
+ options.RequireClientAuthentication = true;
+
+ using (var server = new HttpsTestServer(options))
+ using (HttpClientHandler handler = CreateHttpClientHandler())
+ using (HttpClient client = CreateHttpClient(handler))
+ {
+ server.Start();
+
+ var tasks = new Task[2];
+ tasks[0] = server.AcceptHttpsClientAsync();
+
+ string requestUriString = GetUriStringAndConfigureHandler(options, server, handler);
+ handler.ClientCertificates.Add(clientCertificateWrongEku);
+ tasks[1] = client.GetStringAsync(requestUriString);
+
+ // Server aborts the TCP channel.
+ await Assert.ThrowsAsync<HttpRequestException>(() => tasks[1]);
+
+ await Assert.ThrowsAsync<AuthenticationException>(() => tasks[0]);
+ }
+ }
+
+ private string GetUriStringAndConfigureHandler(HttpsTestServer.Options options, HttpsTestServer server, HttpClientHandler handler)
+ {
+ if (Capability.AreHostsFileNamesInstalled())
+ {
+ string hostName =
+ (new UriBuilder("https", options.ServerCertificate.GetNameInfo(X509NameType.SimpleName, false), server.Port)).ToString();
+
+ Console.WriteLine("[E2E testing] - Using hostname {0}", hostName);
+ return hostName;
+ }
+ else
+ {
+ handler.ServerCertificateCustomValidationCallback = AllowRemoteCertificateNameMismatch;
+ return "https://localhost:" + server.Port.ToString();
+ }
+ }
+
+ private bool AllowRemoteCertificateNameMismatch(HttpRequestMessage httpMessage, X509Certificate2 certificate, X509Chain chain, SslPolicyErrors errors)
+ {
+ if (errors == SslPolicyErrors.RemoteCertificateNameMismatch)
+ {
+ return true;
+ }
+
+ return false;
+ }
+ }
+}
--- /dev/null
+// 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.Authentication;
+using System.Threading.Tasks;
+using Microsoft.DotNet.XUnitExtensions;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace System.Net.Http.Functional.Tests
+{
+ using Configuration = System.Net.Test.Common.Configuration;
+
+#if WINHTTPHANDLER_TEST
+ using HttpClientHandler = System.Net.Http.WinHttpClientHandler;
+#endif
+
+ public abstract class HttpClientHandler_DangerousAcceptAllCertificatesValidator_Test : HttpClientHandlerTestBase
+ {
+ private static bool ClientSupportsDHECipherSuites => (!PlatformDetection.IsWindows || PlatformDetection.IsWindows10Version1607OrGreater);
+
+ public HttpClientHandler_DangerousAcceptAllCertificatesValidator_Test(ITestOutputHelper output) : base(output) { }
+
+#if !WINHTTPHANDLER_TEST
+ [Fact]
+ public void SingletonReturnsTrue()
+ {
+ Assert.NotNull(HttpClientHandler.DangerousAcceptAnyServerCertificateValidator);
+ Assert.Same(HttpClientHandler.DangerousAcceptAnyServerCertificateValidator, HttpClientHandler.DangerousAcceptAnyServerCertificateValidator);
+ Assert.True(HttpClientHandler.DangerousAcceptAnyServerCertificateValidator(null, null, null, SslPolicyErrors.None));
+ }
+#endif
+
+ [Theory]
+ [InlineData(SslProtocols.Tls, false)] // try various protocols to ensure we correctly set versions even when accepting all certs
+ [InlineData(SslProtocols.Tls, true)]
+ [InlineData(SslProtocols.Tls12 | SslProtocols.Tls11 | SslProtocols.Tls, false)]
+ [InlineData(SslProtocols.Tls12 | SslProtocols.Tls11 | SslProtocols.Tls, true)]
+ [InlineData(SslProtocols.Tls13 | SslProtocols.Tls12 | SslProtocols.Tls11 | SslProtocols.Tls, false)]
+ [InlineData(SslProtocols.Tls13 | SslProtocols.Tls12 | SslProtocols.Tls11 | SslProtocols.Tls, true)]
+ [InlineData(SslProtocols.None, false)]
+ [InlineData(SslProtocols.None, true)]
+ public async Task SetDelegate_ConnectionSucceeds(SslProtocols acceptedProtocol, bool requestOnlyThisProtocol)
+ {
+ // Overriding flag for the same reason we skip tests on Catalina
+ // On OSX 10.13-10.14 we can override this flag to enable the scenario
+ requestOnlyThisProtocol |= PlatformDetection.IsMacOsHighSierraOrHigher && acceptedProtocol == SslProtocols.Tls;
+
+ using (HttpClientHandler handler = CreateHttpClientHandler())
+ using (HttpClient client = CreateHttpClient(handler))
+ {
+ handler.ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator;
+
+ if (requestOnlyThisProtocol)
+ {
+ handler.SslProtocols = acceptedProtocol;
+ }
+ else
+ {
+ // Explicitly setting protocols clears implementation default
+ // restrictions on minimum TLS/SSL version
+ // We currently know that some platforms like Debian 10 OpenSSL
+ // will by default block < TLS 1.2
+ handler.SslProtocols = SslProtocols.Tls13 | SslProtocols.Tls12 | SslProtocols.Tls11 | SslProtocols.Tls;
+ }
+
+ var options = new LoopbackServer.Options { UseSsl = true, SslProtocols = acceptedProtocol };
+ await LoopbackServer.CreateServerAsync(async (server, url) =>
+ {
+ await TestHelper.WhenAllCompletedOrAnyFailed(
+ server.AcceptConnectionSendResponseAndCloseAsync(),
+ client.GetAsync(url));
+ }, options);
+ }
+ }
+
+ public static readonly object[][] InvalidCertificateServers =
+ {
+ new object[] { Configuration.Http.ExpiredCertRemoteServer },
+ new object[] { Configuration.Http.SelfSignedCertRemoteServer },
+ new object[] { Configuration.Http.WrongHostNameCertRemoteServer },
+ };
+
+ [OuterLoop]
+ [ConditionalTheory(nameof(ClientSupportsDHECipherSuites))]
+ [MemberData(nameof(InvalidCertificateServers))]
+ public async Task InvalidCertificateServers_CertificateValidationDisabled_Succeeds(string url)
+ {
+ using (HttpClientHandler handler = CreateHttpClientHandler())
+ using (HttpClient client = CreateHttpClient(handler))
+ {
+ handler.ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator;
+ (await client.GetAsync(url)).Dispose();
+ }
+ }
+ }
+}
--- /dev/null
+// 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.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Net.Test.Common;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Threading.Tests;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace System.Net.Http.Functional.Tests
+{
+ public abstract class HttpClientHandler_Asynchrony_Test : HttpClientHandlerTestBase
+ {
+ public HttpClientHandler_Asynchrony_Test(ITestOutputHelper output) : base(output) { }
+
+ public static IEnumerable<object[]> ResponseHeadersRead_SynchronizationContextNotUsedByHandler_MemberData() =>
+ from responseHeadersRead in new[] { false, true }
+ from contentMode in Enum.GetValues(typeof(LoopbackServer.ContentMode)).Cast<LoopbackServer.ContentMode>()
+ select new object[] { responseHeadersRead, contentMode };
+
+ [Theory]
+ [MemberData(nameof(ResponseHeadersRead_SynchronizationContextNotUsedByHandler_MemberData))]
+ public async Task ResponseHeadersRead_SynchronizationContextNotUsedByHandler(bool responseHeadersRead, LoopbackServer.ContentMode contentMode)
+ {
+ await Task.Run(async delegate // escape xunit's sync ctx
+ {
+ await LoopbackServer.CreateClientAndServerAsync(uri =>
+ {
+ return Task.Run(() => // allow client and server to run concurrently even though this is all synchronous/blocking
+ {
+ var sc = new TrackingSynchronizationContext();
+ SynchronizationContext.SetSynchronizationContext(sc);
+
+ using (HttpClient client = CreateHttpClient())
+ {
+ if (responseHeadersRead)
+ {
+ using (HttpResponseMessage resp = client.GetAsync(uri, HttpCompletionOption.ResponseHeadersRead).GetAwaiter().GetResult())
+ using (Stream respStream = resp.Content.ReadAsStreamAsync().GetAwaiter().GetResult())
+ {
+ byte[] buffer = new byte[0x1000];
+ while (respStream.ReadAsync(buffer, 0, buffer.Length).GetAwaiter().GetResult() > 0);
+ }
+ }
+ else
+ {
+ client.GetStringAsync(uri).GetAwaiter().GetResult();
+ }
+ }
+
+ Assert.True(sc.CallStacks.Count == 0, "Sync Ctx used: " + string.Join(Environment.NewLine + Environment.NewLine, sc.CallStacks));
+ });
+ }, async server =>
+ {
+ await server.AcceptConnectionAsync(async connection =>
+ {
+ await connection.ReadRequestHeaderAsync();
+ await connection.Writer.WriteAsync(
+ LoopbackServer.GetContentModeResponse(
+ contentMode,
+ string.Concat(Enumerable.Repeat('s', 10_000)),
+ connectionClose: true));
+ });
+ }, new LoopbackServer.Options { StreamWrapper = s => new DribbleStream(s) });
+ });
+ }
+ }
+}
--- /dev/null
+// 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.Collections.Generic;
+using System.Linq;
+using System.Net.Sockets;
+using System.Net.Test.Common;
+using System.Text;
+using System.Threading.Tasks;
+
+using Microsoft.DotNet.XUnitExtensions;
+
+using Xunit;
+using Xunit.Abstractions;
+
+namespace System.Net.Http.Functional.Tests
+{
+ using Configuration = System.Net.Test.Common.Configuration;
+
+#if WINHTTPHANDLER_TEST
+ using HttpClientHandler = System.Net.Http.WinHttpClientHandler;
+#endif
+
+ public abstract class HttpClientHandler_Authentication_Test : HttpClientHandlerTestBase
+ {
+ private const string Username = "testusername";
+ private const string Password = "testpassword";
+ private const string Domain = "testdomain";
+
+ private static readonly NetworkCredential s_credentials = new NetworkCredential(Username, Password, Domain);
+ private static readonly NetworkCredential s_credentialsNoDomain = new NetworkCredential(Username, Password);
+
+ private async Task CreateAndValidateRequest(HttpClientHandler handler, Uri url, HttpStatusCode expectedStatusCode, ICredentials credentials)
+ {
+ handler.Credentials = credentials;
+
+ using (HttpClient client = CreateHttpClient(handler))
+ using (HttpResponseMessage response = await client.GetAsync(url))
+ {
+ Assert.Equal(expectedStatusCode, response.StatusCode);
+ }
+ }
+
+ public HttpClientHandler_Authentication_Test(ITestOutputHelper output) : base(output) { }
+
+ [Theory]
+ [MemberData(nameof(Authentication_TestData))]
+ public async Task HttpClientHandler_Authentication_Succeeds(string authenticateHeader, bool result)
+ {
+ if (PlatformDetection.IsWindowsNanoServer)
+ {
+ return;
+ }
+
+ var options = new LoopbackServer.Options { Domain = Domain, Username = Username, Password = Password };
+ await LoopbackServer.CreateServerAsync(async (server, url) =>
+ {
+ string serverAuthenticateHeader = $"WWW-Authenticate: {authenticateHeader}\r\n";
+ HttpClientHandler handler = CreateHttpClientHandler();
+ Task serverTask = result ?
+ server.AcceptConnectionPerformAuthenticationAndCloseAsync(serverAuthenticateHeader) :
+ server.AcceptConnectionSendResponseAndCloseAsync(HttpStatusCode.Unauthorized, serverAuthenticateHeader);
+
+ await TestHelper.WhenAllCompletedOrAnyFailedWithTimeout(TestHelper.PassingTestTimeoutMilliseconds,
+ CreateAndValidateRequest(handler, url, result ? HttpStatusCode.OK : HttpStatusCode.Unauthorized, s_credentials), serverTask);
+ }, options);
+ }
+
+ [Theory]
+ [InlineData("WWW-Authenticate: Basic realm=\"hello1\"\r\nWWW-Authenticate: Basic realm=\"hello2\"\r\n")]
+ [InlineData("WWW-Authenticate: Basic realm=\"hello\"\r\nWWW-Authenticate: Basic realm=\"hello\"\r\n")]
+ [InlineData("WWW-Authenticate: Digest realm=\"hello\", nonce=\"hello\", algorithm=MD5\r\nWWW-Authenticate: Digest realm=\"hello\", nonce=\"hello\", algorithm=MD5\r\n")]
+ [InlineData("WWW-Authenticate: Digest realm=\"hello1\", nonce=\"hello\", algorithm=MD5\r\nWWW-Authenticate: Digest realm=\"hello\", nonce=\"hello\", algorithm=MD5\r\n")]
+ public async Task HttpClientHandler_MultipleAuthenticateHeaders_WithSameAuth_Succeeds(string authenticateHeader)
+ {
+ if (IsWinHttpHandler)
+ {
+ // TODO: https://github.com/dotnet/corefx/issues/28065: Fix failing authentication test cases on different httpclienthandlers.
+ return;
+ }
+
+ await HttpClientHandler_MultipleAuthenticateHeaders_Succeeds(authenticateHeader);
+ }
+
+ [Theory]
+ [InlineData("WWW-Authenticate: Basic realm=\"hello\"\r\nWWW-Authenticate: Digest realm=\"hello\", nonce=\"hello\", algorithm=MD5\r\n")]
+ [InlineData("WWW-Authenticate: Digest realm=\"hello\", nonce=\"hello\", algorithm=MD5\r\nWWW-Authenticate: Basic realm=\"hello\"\r\n")]
+ public async Task HttpClientHandler_MultipleAuthenticateHeaders_Succeeds(string authenticateHeader)
+ {
+ if (PlatformDetection.IsWindowsNanoServer)
+ {
+ return;
+ }
+
+ var options = new LoopbackServer.Options { Domain = Domain, Username = Username, Password = Password };
+ await LoopbackServer.CreateServerAsync(async (server, url) =>
+ {
+ HttpClientHandler handler = CreateHttpClientHandler();
+ Task serverTask = server.AcceptConnectionPerformAuthenticationAndCloseAsync(authenticateHeader);
+ await TestHelper.WhenAllCompletedOrAnyFailed(CreateAndValidateRequest(handler, url, HttpStatusCode.OK, s_credentials), serverTask);
+ }, options);
+ }
+
+ [Theory]
+ [InlineData("WWW-Authenticate: Basic realm=\"hello\"\r\nWWW-Authenticate: NTLM\r\n", "Basic", "Negotiate")]
+ [InlineData("WWW-Authenticate: Basic realm=\"hello\"\r\nWWW-Authenticate: Digest realm=\"hello\", nonce=\"hello\", algorithm=MD5\r\nWWW-Authenticate: NTLM\r\n", "Digest", "Negotiate")]
+ public async Task HttpClientHandler_MultipleAuthenticateHeaders_PicksSupported(string authenticateHeader, string supportedAuth, string unsupportedAuth)
+ {
+ if (PlatformDetection.IsWindowsNanoServer)
+ {
+ return;
+ }
+
+ var options = new LoopbackServer.Options { Domain = Domain, Username = Username, Password = Password };
+ await LoopbackServer.CreateServerAsync(async (server, url) =>
+ {
+ HttpClientHandler handler = CreateHttpClientHandler();
+ handler.UseDefaultCredentials = false;
+
+ var credentials = new CredentialCache();
+ credentials.Add(url, supportedAuth, new NetworkCredential(Username, Password, Domain));
+ credentials.Add(url, unsupportedAuth, new NetworkCredential(Username, Password, Domain));
+
+ Task serverTask = server.AcceptConnectionPerformAuthenticationAndCloseAsync(authenticateHeader);
+ await TestHelper.WhenAllCompletedOrAnyFailed(CreateAndValidateRequest(handler, url, HttpStatusCode.OK, credentials), serverTask);
+ }, options);
+ }
+
+ [Theory]
+ [InlineData("WWW-Authenticate: Basic realm=\"hello\"\r\n")]
+ [InlineData("WWW-Authenticate: Digest realm=\"hello\", nonce=\"testnonce\"\r\n")]
+ public async Task HttpClientHandler_IncorrectCredentials_Fails(string authenticateHeader)
+ {
+ var options = new LoopbackServer.Options { Domain = Domain, Username = Username, Password = Password };
+ await LoopbackServer.CreateServerAsync(async (server, url) =>
+ {
+ HttpClientHandler handler = CreateHttpClientHandler();
+ Task serverTask = server.AcceptConnectionPerformAuthenticationAndCloseAsync(authenticateHeader);
+ await TestHelper.WhenAllCompletedOrAnyFailed(CreateAndValidateRequest(handler, url, HttpStatusCode.Unauthorized, new NetworkCredential("wronguser", "wrongpassword")), serverTask);
+ }, options);
+ }
+
+ public static IEnumerable<object[]> Authentication_TestData()
+ {
+ yield return new object[] { "Basic realm=\"testrealm\"", true };
+ yield return new object[] { "Basic ", true };
+ yield return new object[] { "Basic realm=withoutquotes", true };
+ yield return new object[] { "basic ", true };
+ yield return new object[] { "bAsiC ", true };
+ yield return new object[] { "basic", true };
+
+ yield return new object[] { $"Digest realm=\"testrealm\", nonce=\"{Convert.ToBase64String(Encoding.UTF8.GetBytes($"{DateTimeOffset.UtcNow}:XMh;z+$5|`i6Hx}}\", qop=auth-int, algorithm=MD5"))}\"", true };
+ yield return new object[] { $"Digest realm=\"testrealm\", nonce=\"{Convert.ToBase64String(Encoding.UTF8.GetBytes($"{DateTimeOffset.UtcNow}:XMh;z+$5|`i6Hx}}\", qop=auth-int, algorithm=md5"))}\"", true };
+ yield return new object[] { $"Basic realm=\"testrealm\", " +
+ $"Digest realm=\"testrealm\", nonce=\"{Convert.ToBase64String(Encoding.UTF8.GetBytes($"{DateTimeOffset.UtcNow}:XMh;z+$5|`i6Hx}}"))}\", algorithm=MD5", true };
+
+ yield return new object[] { "Basic something, Digest something", false };
+ yield return new object[] { $"Digest realm=\"testrealm\", nonce=\"testnonce\", algorithm=MD5 " +
+ $"Basic realm=\"testrealm\"", false };
+ }
+
+ [Theory]
+ [InlineData(null)]
+ [InlineData("Basic")]
+ [InlineData("Digest")]
+ [InlineData("NTLM")]
+ [InlineData("Kerberos")]
+ [InlineData("Negotiate")]
+ public async Task PreAuthenticate_NoPreviousAuthenticatedRequests_NoCredentialsSent(string credCacheScheme)
+ {
+ const int NumRequests = 3;
+ await LoopbackServer.CreateClientAndServerAsync(async uri =>
+ {
+ using (HttpClientHandler handler = CreateHttpClientHandler())
+ using (HttpClient client = CreateHttpClient(handler))
+ {
+ client.DefaultRequestHeaders.ConnectionClose = true; // for simplicity of not needing to know every handler's pooling policy
+ handler.PreAuthenticate = true;
+ switch (credCacheScheme)
+ {
+ case null:
+ handler.Credentials = s_credentials;
+ break;
+
+ default:
+ var cc = new CredentialCache();
+ cc.Add(uri, credCacheScheme, s_credentials);
+ handler.Credentials = cc;
+ break;
+ }
+
+ for (int i = 0; i < NumRequests; i++)
+ {
+ Assert.Equal("hello world", await client.GetStringAsync(uri));
+ }
+ }
+ },
+ async server =>
+ {
+ for (int i = 0; i < NumRequests; i++)
+ {
+ List<string> headers = await server.AcceptConnectionSendResponseAndCloseAsync(content: "hello world");
+ Assert.All(headers, header => Assert.DoesNotContain("Authorization", header));
+ }
+ });
+ }
+
+ [Theory]
+ [InlineData(null, "WWW-Authenticate: Basic realm=\"hello\"\r\n")]
+ [InlineData("Basic", "WWW-Authenticate: Basic realm=\"hello\"\r\n")]
+ public async Task PreAuthenticate_FirstRequestNoHeaderAndAuthenticates_SecondRequestPreauthenticates(string credCacheScheme, string authResponse)
+ {
+ await LoopbackServer.CreateClientAndServerAsync(async uri =>
+ {
+ using (HttpClientHandler handler = CreateHttpClientHandler())
+ using (HttpClient client = CreateHttpClient(handler))
+ {
+ client.DefaultRequestHeaders.ConnectionClose = true; // for simplicity of not needing to know every handler's pooling policy
+ handler.PreAuthenticate = true;
+ switch (credCacheScheme)
+ {
+ case null:
+ handler.Credentials = s_credentials;
+ break;
+
+ default:
+ var cc = new CredentialCache();
+ cc.Add(uri, credCacheScheme, s_credentials);
+ handler.Credentials = cc;
+ break;
+ }
+
+ Assert.Equal("hello world", await client.GetStringAsync(uri));
+ Assert.Equal("hello world", await client.GetStringAsync(uri));
+ }
+ },
+ async server =>
+ {
+ List<string> headers = await server.AcceptConnectionSendResponseAndCloseAsync(HttpStatusCode.Unauthorized, authResponse);
+ Assert.All(headers, header => Assert.DoesNotContain("Authorization", header));
+
+ for (int i = 0; i < 2; i++)
+ {
+ headers = await server.AcceptConnectionSendResponseAndCloseAsync(content: "hello world");
+ Assert.Contains(headers, header => header.Contains("Authorization"));
+ }
+ });
+ }
+
+ // InlineDatas for all values that pass on WinHttpHandler, which is the most restrictive.
+ // Uses numerical values for values named in .NET Core and not in .NET Framework.
+ [Theory]
+ [InlineData(HttpStatusCode.OK)]
+ [InlineData(HttpStatusCode.Created)]
+ [InlineData(HttpStatusCode.Accepted)]
+ [InlineData(HttpStatusCode.NonAuthoritativeInformation)]
+ [InlineData(HttpStatusCode.NoContent)]
+ [InlineData(HttpStatusCode.ResetContent)]
+ [InlineData(HttpStatusCode.PartialContent)]
+ [InlineData((HttpStatusCode)207)] // MultiStatus
+ [InlineData((HttpStatusCode)208)] // AlreadyReported
+ [InlineData((HttpStatusCode)226)] // IMUsed
+ [InlineData(HttpStatusCode.Ambiguous)]
+ [InlineData(HttpStatusCode.NotModified)]
+ [InlineData(HttpStatusCode.UseProxy)]
+ [InlineData(HttpStatusCode.Unused)]
+ [InlineData(HttpStatusCode.BadRequest)]
+ [InlineData(HttpStatusCode.PaymentRequired)]
+ [InlineData(HttpStatusCode.Forbidden)]
+ [InlineData(HttpStatusCode.NotFound)]
+ [InlineData(HttpStatusCode.MethodNotAllowed)]
+ [InlineData(HttpStatusCode.NotAcceptable)]
+ [InlineData(HttpStatusCode.RequestTimeout)]
+ [InlineData(HttpStatusCode.Conflict)]
+ [InlineData(HttpStatusCode.Gone)]
+ [InlineData(HttpStatusCode.LengthRequired)]
+ [InlineData(HttpStatusCode.PreconditionFailed)]
+ [InlineData(HttpStatusCode.RequestEntityTooLarge)]
+ [InlineData(HttpStatusCode.RequestUriTooLong)]
+ [InlineData(HttpStatusCode.UnsupportedMediaType)]
+ [InlineData(HttpStatusCode.RequestedRangeNotSatisfiable)]
+ [InlineData(HttpStatusCode.ExpectationFailed)]
+ [InlineData((HttpStatusCode)421)] // MisdirectedRequest
+ [InlineData((HttpStatusCode)422)] // UnprocessableEntity
+ [InlineData((HttpStatusCode)423)] // Locked
+ [InlineData((HttpStatusCode)424)] // FailedDependency
+ [InlineData(HttpStatusCode.UpgradeRequired)]
+ [InlineData((HttpStatusCode)428)] // PreconditionRequired
+ [InlineData((HttpStatusCode)429)] // TooManyRequests
+ [InlineData((HttpStatusCode)431)] // RequestHeaderFieldsTooLarge
+ [InlineData((HttpStatusCode)451)] // UnavailableForLegalReasons
+ [InlineData(HttpStatusCode.InternalServerError)]
+ [InlineData(HttpStatusCode.NotImplemented)]
+ [InlineData(HttpStatusCode.BadGateway)]
+ [InlineData(HttpStatusCode.ServiceUnavailable)]
+ [InlineData(HttpStatusCode.GatewayTimeout)]
+ [InlineData(HttpStatusCode.HttpVersionNotSupported)]
+ [InlineData((HttpStatusCode)506)] // VariantAlsoNegotiates
+ [InlineData((HttpStatusCode)507)] // InsufficientStorage
+ [InlineData((HttpStatusCode)508)] // LoopDetected
+ [InlineData((HttpStatusCode)510)] // NotExtended
+ [InlineData((HttpStatusCode)511)] // NetworkAuthenticationRequired
+ public async Task PreAuthenticate_FirstRequestNoHeader_SecondRequestVariousStatusCodes_ThirdRequestPreauthenticates(HttpStatusCode statusCode)
+ {
+ const string AuthResponse = "WWW-Authenticate: Basic realm=\"hello\"\r\n";
+
+ await LoopbackServer.CreateClientAndServerAsync(async uri =>
+ {
+ using (HttpClientHandler handler = CreateHttpClientHandler())
+ using (HttpClient client = CreateHttpClient(handler))
+ {
+ client.DefaultRequestHeaders.ConnectionClose = true; // for simplicity of not needing to know every handler's pooling policy
+ handler.PreAuthenticate = true;
+ handler.Credentials = s_credentials;
+ client.DefaultRequestHeaders.ExpectContinue = false;
+
+ using (HttpResponseMessage resp = await client.GetAsync(uri))
+ {
+ Assert.Equal(statusCode, resp.StatusCode);
+ }
+ Assert.Equal("hello world", await client.GetStringAsync(uri));
+ }
+ },
+ async server =>
+ {
+ List<string> headers = await server.AcceptConnectionSendResponseAndCloseAsync(HttpStatusCode.Unauthorized, AuthResponse);
+ Assert.All(headers, header => Assert.DoesNotContain("Authorization", header));
+
+ headers = await server.AcceptConnectionSendResponseAndCloseAsync(statusCode);
+ Assert.Contains(headers, header => header.Contains("Authorization"));
+
+ headers = await server.AcceptConnectionSendResponseAndCloseAsync(HttpStatusCode.OK, content: "hello world");
+ Assert.Contains(headers, header => header.Contains("Authorization"));
+ });
+ }
+
+ [Theory]
+ [InlineData("/something/hello.html", "/something/hello.html", true)]
+ [InlineData("/something/hello.html", "/something/world.html", true)]
+ [InlineData("/something/hello.html", "/something/", true)]
+ [InlineData("/something/hello.html", "/", false)]
+ [InlineData("/something/hello.html", "/hello.html", false)]
+ [InlineData("/something/hello.html", "/world.html", false)]
+ [InlineData("/something/hello.html", "/another/", false)]
+ [InlineData("/something/hello.html", "/another/hello.html", false)]
+ public async Task PreAuthenticate_AuthenticatedUrl_ThenTryDifferentUrl_SendsAuthHeaderOnlyIfPrefixMatches(
+ string originalRelativeUri, string secondRelativeUri, bool expectedAuthHeader)
+ {
+ const string AuthResponse = "WWW-Authenticate: Basic realm=\"hello\"\r\n";
+
+ await LoopbackServer.CreateClientAndServerAsync(async uri =>
+ {
+ using (HttpClientHandler handler = CreateHttpClientHandler())
+ using (HttpClient client = CreateHttpClient(handler))
+ {
+ client.DefaultRequestHeaders.ConnectionClose = true; // for simplicity of not needing to know every handler's pooling policy
+ handler.PreAuthenticate = true;
+ handler.Credentials = s_credentials;
+
+ Assert.Equal("hello world 1", await client.GetStringAsync(new Uri(uri, originalRelativeUri)));
+ Assert.Equal("hello world 2", await client.GetStringAsync(new Uri(uri, secondRelativeUri)));
+ }
+ },
+ async server =>
+ {
+ List<string> headers = await server.AcceptConnectionSendResponseAndCloseAsync(HttpStatusCode.Unauthorized, AuthResponse);
+ Assert.All(headers, header => Assert.DoesNotContain("Authorization", header));
+
+ headers = await server.AcceptConnectionSendResponseAndCloseAsync(content: "hello world 1");
+ Assert.Contains(headers, header => header.Contains("Authorization"));
+
+ headers = await server.AcceptConnectionSendResponseAndCloseAsync(content: "hello world 2");
+ if (expectedAuthHeader)
+ {
+ Assert.Contains(headers, header => header.Contains("Authorization"));
+ }
+ else
+ {
+ Assert.All(headers, header => Assert.DoesNotContain("Authorization", header));
+ }
+ });
+ }
+
+ [Fact]
+ public async Task PreAuthenticate_SuccessfulBasicButThenFails_DoesntLoopInfinitely()
+ {
+ await LoopbackServer.CreateClientAndServerAsync(async uri =>
+ {
+ using (HttpClientHandler handler = CreateHttpClientHandler())
+ using (HttpClient client = CreateHttpClient(handler))
+ {
+ client.DefaultRequestHeaders.ConnectionClose = true; // for simplicity of not needing to know every handler's pooling policy
+ handler.PreAuthenticate = true;
+ handler.Credentials = s_credentials;
+
+ // First two requests: initially without auth header, then with
+ Assert.Equal("hello world", await client.GetStringAsync(uri));
+
+ // Attempt preauth, and when that fails, give up.
+ using (HttpResponseMessage resp = await client.GetAsync(uri))
+ {
+ Assert.Equal(HttpStatusCode.Unauthorized, resp.StatusCode);
+ }
+ }
+ },
+ async server =>
+ {
+ // First request, no auth header, challenge Basic
+ List<string> headers = headers = await server.AcceptConnectionSendResponseAndCloseAsync(HttpStatusCode.Unauthorized, "WWW-Authenticate: Basic realm=\"hello\"\r\n");
+ Assert.All(headers, header => Assert.DoesNotContain("Authorization", header));
+
+ // Second request, contains Basic auth header
+ headers = await server.AcceptConnectionSendResponseAndCloseAsync(content: "hello world");
+ Assert.Contains(headers, header => header.Contains("Authorization"));
+
+ // Third request, contains Basic auth header but challenges anyway
+ headers = headers = await server.AcceptConnectionSendResponseAndCloseAsync(HttpStatusCode.Unauthorized, "WWW-Authenticate: Basic realm=\"hello\"\r\n");
+ Assert.Contains(headers, header => header.Contains("Authorization"));
+ });
+ }
+
+ [Fact]
+ public async Task PreAuthenticate_SuccessfulBasic_ThenDigestChallenged()
+ {
+ if (IsWinHttpHandler)
+ {
+ // WinHttpHandler fails with Unauthorized after the basic preauth fails.
+ return;
+ }
+
+ await LoopbackServer.CreateClientAndServerAsync(async uri =>
+ {
+ using (HttpClientHandler handler = CreateHttpClientHandler())
+ using (HttpClient client = CreateHttpClient(handler))
+ {
+ client.DefaultRequestHeaders.ConnectionClose = true; // for simplicity of not needing to know every handler's pooling policy
+ handler.PreAuthenticate = true;
+ handler.Credentials = s_credentials;
+
+ Assert.Equal("hello world", await client.GetStringAsync(uri));
+ Assert.Equal("hello world", await client.GetStringAsync(uri));
+ }
+ },
+ async server =>
+ {
+ // First request, no auth header, challenge Basic
+ List<string> headers = headers = await server.AcceptConnectionSendResponseAndCloseAsync(HttpStatusCode.Unauthorized, "WWW-Authenticate: Basic realm=\"hello\"\r\n");
+ Assert.All(headers, header => Assert.DoesNotContain("Authorization", header));
+
+ // Second request, contains Basic auth header, success
+ headers = await server.AcceptConnectionSendResponseAndCloseAsync(content: "hello world");
+ Assert.Contains(headers, header => header.Contains("Authorization"));
+
+ // Third request, contains Basic auth header, challenge digest
+ headers = await server.AcceptConnectionSendResponseAndCloseAsync(HttpStatusCode.Unauthorized, "WWW-Authenticate: Digest realm=\"hello\", nonce=\"testnonce\"\r\n");
+ Assert.Contains(headers, header => header.Contains("Authorization: Basic"));
+
+ // Fourth request, contains Digest auth header, success
+ headers = await server.AcceptConnectionSendResponseAndCloseAsync(content: "hello world");
+ Assert.Contains(headers, header => header.Contains("Authorization: Digest"));
+ });
+ }
+
+ public static IEnumerable<object[]> ServerUsesWindowsAuthentication_MemberData()
+ {
+ string server = Configuration.Http.WindowsServerHttpHost;
+ string authEndPoint = "showidentity.ashx";
+
+ yield return new object[] { $"http://{server}/test/auth/ntlm/{authEndPoint}" };
+ yield return new object[] { $"https://{server}/test/auth/ntlm/{authEndPoint}" };
+
+ yield return new object[] { $"http://{server}/test/auth/negotiate/{authEndPoint}" };
+ yield return new object[] { $"https://{server}/test/auth/negotiate/{authEndPoint}" };
+
+ // Server requires TLS channel binding token (cbt) with NTLM authentication.
+ yield return new object[] { $"https://{server}/test/auth/ntlm-epa/{authEndPoint}" };
+ }
+
+ private static bool IsNtlmInstalled => Capability.IsNtlmInstalled();
+ private static bool IsWindowsServerAvailable => !string.IsNullOrEmpty(Configuration.Http.WindowsServerHttpHost);
+ private static bool IsDomainJoinedServerAvailable => !string.IsNullOrEmpty(Configuration.Http.DomainJoinedHttpHost);
+ private static NetworkCredential DomainCredential = new NetworkCredential(
+ Configuration.Security.ActiveDirectoryUserName,
+ Configuration.Security.ActiveDirectoryUserPassword,
+ Configuration.Security.ActiveDirectoryName);
+
+ public static IEnumerable<object[]> EchoServersData()
+ {
+ foreach (Uri serverUri in Configuration.Http.EchoServerList)
+ {
+ yield return new object[] { serverUri };
+ }
+ }
+
+ [MemberData(nameof(EchoServersData))]
+ [ConditionalTheory(nameof(IsDomainJoinedServerAvailable))]
+ public async Task Proxy_DomainJoinedProxyServerUsesKerberos_Success(Uri server)
+ {
+ // We skip the test unless it is running on a Windows client machine. That is because only Windows
+ // automatically registers an SPN for HTTP/<hostname> of the machine. This will enable Kerberos to properly
+ // work with the loopback proxy server.
+ if (!PlatformDetection.IsWindows || !PlatformDetection.IsNotWindowsNanoServer)
+ {
+ throw new SkipTestException("Test can only run on domain joined Windows client machine");
+ }
+
+ var options = new LoopbackProxyServer.Options { AuthenticationSchemes = AuthenticationSchemes.Negotiate };
+ using (LoopbackProxyServer proxyServer = LoopbackProxyServer.Create(options))
+ {
+ using (HttpClientHandler handler = CreateHttpClientHandler())
+ using (HttpClient client = CreateHttpClient(handler))
+ {
+ // Use 'localhost' DNS name for loopback proxy server (instead of IP address) so that the SPN will
+ // get calculated properly to use Kerberos.
+ _output.WriteLine(proxyServer.Uri.AbsoluteUri.ToString());
+ handler.Proxy = new WebProxy("localhost", proxyServer.Uri.Port) { Credentials = DomainCredential };
+
+ using (HttpResponseMessage response = await client.GetAsync(server))
+ {
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+
+ int requestCount = proxyServer.Requests.Count;
+
+ // We expect 2 requests to the proxy server. One without the 'Proxy-Authorization' header and
+ // one with the header.
+ Assert.Equal(2, requestCount);
+ Assert.Equal("Negotiate", proxyServer.Requests[requestCount - 1].AuthorizationHeaderValueScheme);
+
+ // Base64 tokens that use SPNEGO protocol start with 'Y'. NTLM tokens start with 'T'.
+ Assert.Equal('Y', proxyServer.Requests[requestCount - 1].AuthorizationHeaderValueToken[0]);
+ }
+ }
+ }
+ }
+
+ [ConditionalFact(nameof(IsDomainJoinedServerAvailable))]
+ public async Task Credentials_DomainJoinedServerUsesKerberos_Success()
+ {
+ using (HttpClientHandler handler = CreateHttpClientHandler())
+ using (HttpClient client = CreateHttpClient(handler))
+ {
+ handler.Credentials = DomainCredential;
+
+ string server = $"http://{Configuration.Http.DomainJoinedHttpHost}/test/auth/kerberos/showidentity.ashx";
+ using (HttpResponseMessage response = await client.GetAsync(server))
+ {
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ string body = await response.Content.ReadAsStringAsync();
+ _output.WriteLine(body);
+ }
+ }
+ }
+
+ [ConditionalFact(nameof(IsDomainJoinedServerAvailable))]
+ public async Task Credentials_DomainJoinedServerUsesKerberos_UseIpAddressAndHostHeader_Success()
+ {
+ using (HttpClientHandler handler = CreateHttpClientHandler())
+ using (HttpClient client = CreateHttpClient(handler))
+ {
+ handler.Credentials = DomainCredential;
+
+ IPAddress[] addresses = Dns.GetHostAddresses(Configuration.Http.DomainJoinedHttpHost);
+ IPAddress hostIP = addresses.Where(a => a.AddressFamily == AddressFamily.InterNetwork).Select(a => a).First();
+
+ var request = new HttpRequestMessage();
+ request.RequestUri = new Uri($"http://{hostIP}/test/auth/kerberos/showidentity.ashx");
+ request.Headers.Host = Configuration.Http.DomainJoinedHttpHost;
+ _output.WriteLine(request.RequestUri.AbsoluteUri.ToString());
+ _output.WriteLine($"Host: {request.Headers.Host}");
+
+ using (HttpResponseMessage response = await client.SendAsync(request))
+ {
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ string body = await response.Content.ReadAsStringAsync();
+ _output.WriteLine(body);
+ }
+ }
+ }
+
+ [ConditionalTheory(nameof(IsNtlmInstalled), nameof(IsWindowsServerAvailable))]
+ [MemberData(nameof(ServerUsesWindowsAuthentication_MemberData))]
+ public async Task Credentials_ServerUsesWindowsAuthentication_Success(string server)
+ {
+ using (HttpClientHandler handler = CreateHttpClientHandler())
+ using (HttpClient client = CreateHttpClient(handler))
+ {
+ handler.Credentials = new NetworkCredential(
+ Configuration.Security.WindowsServerUserName,
+ Configuration.Security.WindowsServerUserPassword);
+
+ using (HttpResponseMessage response = await client.GetAsync(server))
+ {
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ string body = await response.Content.ReadAsStringAsync();
+ _output.WriteLine(body);
+ }
+ }
+ }
+
+ [ConditionalTheory(nameof(IsNtlmInstalled))]
+ [InlineData("NTLM")]
+ [InlineData("Negotiate")]
+ public async Task Credentials_ServerChallengesWithWindowsAuth_ClientSendsWindowsAuthHeader(string authScheme)
+ {
+ await LoopbackServerFactory.CreateClientAndServerAsync(
+ async uri =>
+ {
+ using (HttpClientHandler handler = CreateHttpClientHandler())
+ using (HttpClient client = CreateHttpClient(handler))
+ {
+ handler.Credentials = new NetworkCredential("username", "password");
+ await client.GetAsync(uri);
+ }
+ },
+ async server =>
+ {
+ var responseHeader = new HttpHeaderData[] { new HttpHeaderData("Www-authenticate", authScheme) };
+ HttpRequestData requestData = await server.HandleRequestAsync(
+ HttpStatusCode.Unauthorized, responseHeader);
+ Assert.Equal(0, requestData.GetHeaderValueCount("Authorization"));
+
+ requestData = await server.HandleRequestAsync();
+ string authHeaderValue = requestData.GetSingleHeaderValue("Authorization");
+ Assert.Contains(authScheme, authHeaderValue);
+ _output.WriteLine(authHeaderValue);
+ });
+ }
+ }
+}
--- /dev/null
+// 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.Collections.Generic;
+using System.Net.Http.Headers;
+using System.Net.Sockets;
+using System.Net.Test.Common;
+using System.Threading.Tasks;
+
+using Xunit;
+using Xunit.Abstractions;
+
+namespace System.Net.Http.Functional.Tests
+{
+ using Configuration = System.Net.Test.Common.Configuration;
+
+#if WINHTTPHANDLER_TEST
+ using HttpClientHandler = System.Net.Http.WinHttpClientHandler;
+#endif
+
+ public abstract class HttpClientHandlerTest_AutoRedirect : HttpClientHandlerTestBase
+ {
+ private const string ExpectedContent = "Test content";
+ private const string Username = "testuser";
+ private const string Password = "password";
+
+ private readonly NetworkCredential _credential = new NetworkCredential(Username, Password);
+
+ public static IEnumerable<object[]> RemoteServersAndRedirectStatusCodes()
+ {
+ foreach (Configuration.Http.RemoteServer remoteServer in Configuration.Http.RemoteServers)
+ {
+ yield return new object[] { remoteServer, 300 };
+ yield return new object[] { remoteServer, 301 };
+ yield return new object[] { remoteServer, 302 };
+ yield return new object[] { remoteServer, 303 };
+ yield return new object[] { remoteServer, 307 };
+ yield return new object[] { remoteServer, 308 };
+ }
+ }
+
+ public static readonly object[][] RedirectStatusCodesOldMethodsNewMethods = {
+ new object[] { 300, "GET", "GET" },
+ new object[] { 300, "POST", "GET" },
+ new object[] { 300, "HEAD", "HEAD" },
+
+ new object[] { 301, "GET", "GET" },
+ new object[] { 301, "POST", "GET" },
+ new object[] { 301, "HEAD", "HEAD" },
+
+ new object[] { 302, "GET", "GET" },
+ new object[] { 302, "POST", "GET" },
+ new object[] { 302, "HEAD", "HEAD" },
+
+ new object[] { 303, "GET", "GET" },
+ new object[] { 303, "POST", "GET" },
+ new object[] { 303, "HEAD", "HEAD" },
+
+ new object[] { 307, "GET", "GET" },
+ new object[] { 307, "POST", "POST" },
+ new object[] { 307, "HEAD", "HEAD" },
+
+ new object[] { 308, "GET", "GET" },
+ new object[] { 308, "POST", "POST" },
+ new object[] { 308, "HEAD", "HEAD" },
+ };
+
+ public HttpClientHandlerTest_AutoRedirect(ITestOutputHelper output) : base(output) { }
+
+ [OuterLoop("Uses external server")]
+ [Theory, MemberData(nameof(RemoteServersAndRedirectStatusCodes))]
+ public async Task GetAsync_AllowAutoRedirectFalse_RedirectFromHttpToHttp_StatusCodeRedirect(Configuration.Http.RemoteServer remoteServer, int statusCode)
+ {
+ if (statusCode == 308 && (IsWinHttpHandler && PlatformDetection.WindowsVersion < 10))
+ {
+ // 308 redirects are not supported on old versions of WinHttp, or on .NET Framework.
+ return;
+ }
+
+ HttpClientHandler handler = CreateHttpClientHandler();
+ handler.AllowAutoRedirect = false;
+ using (HttpClient client = CreateHttpClientForRemoteServer(remoteServer, handler))
+ {
+ Uri uri = remoteServer.RedirectUriForDestinationUri(
+ statusCode: statusCode,
+ destinationUri: remoteServer.EchoUri,
+ hops: 1);
+ _output.WriteLine("Uri: {0}", uri);
+ using (HttpResponseMessage response = await client.GetAsync(uri))
+ {
+ Assert.Equal(statusCode, (int)response.StatusCode);
+ Assert.Equal(uri, response.RequestMessage.RequestUri);
+ }
+ }
+ }
+
+ [Theory, MemberData(nameof(RedirectStatusCodesOldMethodsNewMethods))]
+ public async Task AllowAutoRedirect_True_ValidateNewMethodUsedOnRedirection(
+ int statusCode, string oldMethod, string newMethod)
+ {
+ if (statusCode == 308 && (IsWinHttpHandler && PlatformDetection.WindowsVersion < 10))
+ {
+ // 308 redirects are not supported on old versions of WinHttp, or on .NET Framework.
+ return;
+ }
+
+ HttpClientHandler handler = CreateHttpClientHandler();
+ using (HttpClient client = CreateHttpClient(handler))
+ {
+ await LoopbackServer.CreateServerAsync(async (origServer, origUrl) =>
+ {
+ var request = new HttpRequestMessage(new HttpMethod(oldMethod), origUrl) { Version = VersionFromUseHttp2 };
+
+ Task<HttpResponseMessage> getResponseTask = client.SendAsync(request);
+
+ await LoopbackServer.CreateServerAsync(async (redirServer, redirUrl) =>
+ {
+ // Original URL will redirect to a different URL
+ Task<List<string>> serverTask = origServer.AcceptConnectionSendResponseAndCloseAsync((HttpStatusCode)statusCode, $"Location: {redirUrl}\r\n");
+
+ await Task.WhenAny(getResponseTask, serverTask);
+ Assert.False(getResponseTask.IsCompleted, $"{getResponseTask.Status}: {getResponseTask.Exception}");
+ await serverTask;
+
+ // Redirected URL answers with success
+ serverTask = redirServer.AcceptConnectionSendResponseAndCloseAsync();
+ await TestHelper.WhenAllCompletedOrAnyFailed(getResponseTask, serverTask);
+
+ List<string> receivedRequest = await serverTask;
+
+ string[] statusLineParts = receivedRequest[0].Split(' ');
+
+ using (HttpResponseMessage response = await getResponseTask)
+ {
+ Assert.Equal(200, (int)response.StatusCode);
+ Assert.Equal(newMethod, statusLineParts[0]);
+ }
+ });
+ });
+ }
+ }
+
+ [Theory]
+ [InlineData(300)]
+ [InlineData(301)]
+ [InlineData(302)]
+ [InlineData(303)]
+ public async Task AllowAutoRedirect_True_PostToGetDoesNotSendTE(int statusCode)
+ {
+ HttpClientHandler handler = CreateHttpClientHandler();
+ using (HttpClient client = CreateHttpClient(handler))
+ {
+ await LoopbackServer.CreateServerAsync(async (origServer, origUrl) =>
+ {
+ var request = new HttpRequestMessage(HttpMethod.Post, origUrl) { Version = VersionFromUseHttp2 };
+ request.Content = new StringContent(ExpectedContent);
+ request.Headers.TransferEncodingChunked = true;
+
+ Task<HttpResponseMessage> getResponseTask = client.SendAsync(request);
+
+ await LoopbackServer.CreateServerAsync(async (redirServer, redirUrl) =>
+ {
+ // Original URL will redirect to a different URL
+ Task serverTask = origServer.AcceptConnectionAsync(async connection =>
+ {
+ // Send Connection: close so the client will close connection after request is sent,
+ // meaning we can just read to the end to get the content
+ await connection.ReadRequestHeaderAndSendResponseAsync((HttpStatusCode)statusCode, $"Location: {redirUrl}\r\nConnection: close\r\n");
+ connection.Socket.Shutdown(SocketShutdown.Send);
+ await connection.ReadToEndAsync();
+ });
+
+ await Task.WhenAny(getResponseTask, serverTask);
+ Assert.False(getResponseTask.IsCompleted, $"{getResponseTask.Status}: {getResponseTask.Exception}");
+ await serverTask;
+
+ // Redirected URL answers with success
+ List<string> receivedRequest = null;
+ string receivedContent = null;
+ Task serverTask2 = redirServer.AcceptConnectionAsync(async connection =>
+ {
+ // Send Connection: close so the client will close connection after request is sent,
+ // meaning we can just read to the end to get the content
+ receivedRequest = await connection.ReadRequestHeaderAndSendResponseAsync(additionalHeaders: "Connection: close\r\n");
+ connection.Socket.Shutdown(SocketShutdown.Send);
+ receivedContent = await connection.ReadToEndAsync();
+ });
+
+ await TestHelper.WhenAllCompletedOrAnyFailed(getResponseTask, serverTask2);
+
+ string[] statusLineParts = receivedRequest[0].Split(' ');
+ Assert.Equal("GET", statusLineParts[0]);
+ Assert.DoesNotContain(receivedRequest, line => line.StartsWith("Transfer-Encoding"));
+ Assert.DoesNotContain(receivedRequest, line => line.StartsWith("Content-Length"));
+
+ using (HttpResponseMessage response = await getResponseTask)
+ {
+ Assert.Equal(200, (int)response.StatusCode);
+ }
+ });
+ });
+ }
+ }
+
+ [OuterLoop("Uses external server")]
+ [Theory, MemberData(nameof(RemoteServersAndRedirectStatusCodes))]
+ public async Task GetAsync_AllowAutoRedirectTrue_RedirectFromHttpToHttp_StatusCodeOK(Configuration.Http.RemoteServer remoteServer, int statusCode)
+ {
+ if (statusCode == 308 && (IsWinHttpHandler && PlatformDetection.WindowsVersion < 10))
+ {
+ // 308 redirects are not supported on old versions of WinHttp, or on .NET Framework.
+ return;
+ }
+
+ HttpClientHandler handler = CreateHttpClientHandler();
+ handler.AllowAutoRedirect = true;
+ using (HttpClient client = CreateHttpClientForRemoteServer(remoteServer, handler))
+ {
+ Uri uri = remoteServer.RedirectUriForDestinationUri(
+ statusCode: statusCode,
+ destinationUri: remoteServer.EchoUri,
+ hops: 1);
+ _output.WriteLine("Uri: {0}", uri);
+ using (HttpResponseMessage response = await client.GetAsync(uri))
+ {
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ Assert.Equal(remoteServer.EchoUri, response.RequestMessage.RequestUri);
+ }
+ }
+ }
+
+ [OuterLoop("Uses external server")]
+ [Fact]
+ public async Task GetAsync_AllowAutoRedirectTrue_RedirectFromHttpToHttps_StatusCodeOK()
+ {
+ HttpClientHandler handler = CreateHttpClientHandler();
+ handler.AllowAutoRedirect = true;
+ using (HttpClient client = CreateHttpClient(handler))
+ {
+ Uri uri = Configuration.Http.RemoteHttp11Server.RedirectUriForDestinationUri(
+ statusCode: 302,
+ destinationUri: Configuration.Http.RemoteSecureHttp11Server.EchoUri,
+ hops: 1);
+ _output.WriteLine("Uri: {0}", uri);
+ using (HttpResponseMessage response = await client.GetAsync(uri))
+ {
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ Assert.Equal(Configuration.Http.SecureRemoteEchoServer, response.RequestMessage.RequestUri);
+ }
+ }
+ }
+
+ [OuterLoop("Uses external server")]
+ [Fact]
+ public async Task GetAsync_AllowAutoRedirectTrue_RedirectFromHttpsToHttp_StatusCodeRedirect()
+ {
+ HttpClientHandler handler = CreateHttpClientHandler();
+ handler.AllowAutoRedirect = true;
+ using (HttpClient client = CreateHttpClient(handler))
+ {
+ Uri uri = Configuration.Http.RemoteSecureHttp11Server.RedirectUriForDestinationUri(
+ statusCode: 302,
+ destinationUri: Configuration.Http.RemoteHttp11Server.EchoUri,
+ hops: 1);
+ _output.WriteLine("Uri: {0}", uri);
+
+ using (HttpResponseMessage response = await client.GetAsync(uri))
+ {
+ Assert.Equal(HttpStatusCode.Redirect, response.StatusCode);
+ Assert.Equal(uri, response.RequestMessage.RequestUri);
+ }
+ }
+ }
+
+ [Fact]
+ public async Task GetAsync_AllowAutoRedirectTrue_RedirectWithoutLocation_ReturnsOriginalResponse()
+ {
+ // [ActiveIssue("https://github.com/dotnet/corefx/issues/24819", TestPlatforms.Windows)]
+ if (IsWinHttpHandler)
+ {
+ return;
+ }
+
+ HttpClientHandler handler = CreateHttpClientHandler();
+ handler.AllowAutoRedirect = true;
+ using (HttpClient client = CreateHttpClient(handler))
+ {
+ await LoopbackServer.CreateServerAsync(async (server, url) =>
+ {
+ Task<HttpResponseMessage> getTask = client.GetAsync(url);
+ Task<List<string>> serverTask = server.AcceptConnectionSendResponseAndCloseAsync(HttpStatusCode.Found);
+ await TestHelper.WhenAllCompletedOrAnyFailed(getTask, serverTask);
+
+ using (HttpResponseMessage response = await getTask)
+ {
+ Assert.Equal(302, (int)response.StatusCode);
+ }
+ });
+ }
+ }
+
+ [OuterLoop("Uses external server")]
+ [Theory, MemberData(nameof(RemoteServersMemberData))]
+ public async Task GetAsync_AllowAutoRedirectTrue_RedirectToUriWithParams_RequestMsgUriSet(Configuration.Http.RemoteServer remoteServer)
+ {
+ HttpClientHandler handler = CreateHttpClientHandler();
+ handler.AllowAutoRedirect = true;
+ Uri targetUri = remoteServer.BasicAuthUriForCreds(userName: Username, password: Password);
+ using (HttpClient client = CreateHttpClientForRemoteServer(remoteServer, handler))
+ {
+ Uri uri = remoteServer.RedirectUriForDestinationUri(
+ statusCode: 302,
+ destinationUri: targetUri,
+ hops: 1);
+ _output.WriteLine("Uri: {0}", uri);
+ using (HttpResponseMessage response = await client.GetAsync(uri))
+ {
+ Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
+ Assert.Equal(targetUri, response.RequestMessage.RequestUri);
+ }
+ }
+ }
+
+ [OuterLoop("Uses external server")]
+ [Theory]
+ [InlineData(3, 2)]
+ [InlineData(3, 3)]
+ [InlineData(3, 4)]
+ public async Task GetAsync_MaxAutomaticRedirectionsNServerHops_ThrowsIfTooMany(int maxHops, int hops)
+ {
+ if (IsWinHttpHandler && !PlatformDetection.IsWindows10Version1703OrGreater)
+ {
+ // Skip this test if using WinHttpHandler but on a release prior to Windows 10 Creators Update.
+ _output.WriteLine("Skipping test due to Windows 10 version prior to Version 1703.");
+ return;
+ }
+
+ HttpClientHandler handler = CreateHttpClientHandler();
+ handler.MaxAutomaticRedirections = maxHops;
+ using (HttpClient client = CreateHttpClient(handler))
+ {
+ Task<HttpResponseMessage> t = client.GetAsync(Configuration.Http.RemoteHttp11Server.RedirectUriForDestinationUri(
+ statusCode: 302,
+ destinationUri: Configuration.Http.RemoteHttp11Server.EchoUri,
+ hops: hops));
+
+ if (hops <= maxHops)
+ {
+ using (HttpResponseMessage response = await t)
+ {
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ Assert.Equal(Configuration.Http.RemoteEchoServer, response.RequestMessage.RequestUri);
+ }
+ }
+ else
+ {
+ if (!IsWinHttpHandler)
+ {
+ using (HttpResponseMessage response = await t)
+ {
+ Assert.Equal(HttpStatusCode.Redirect, response.StatusCode);
+ }
+ }
+ else
+ {
+ await Assert.ThrowsAsync<HttpRequestException>(() => t);
+ }
+ }
+ }
+ }
+
+ [OuterLoop("Uses external server")]
+ [Theory, MemberData(nameof(RemoteServersMemberData))]
+ public async Task GetAsync_AllowAutoRedirectTrue_RedirectWithRelativeLocation(Configuration.Http.RemoteServer remoteServer)
+ {
+ HttpClientHandler handler = CreateHttpClientHandler();
+ handler.AllowAutoRedirect = true;
+ using (HttpClient client = CreateHttpClientForRemoteServer(remoteServer, handler))
+ {
+ Uri uri = remoteServer.RedirectUriForDestinationUri(
+ statusCode: 302,
+ destinationUri: remoteServer.EchoUri,
+ hops: 1,
+ relative: true);
+ _output.WriteLine("Uri: {0}", uri);
+
+ using (HttpResponseMessage response = await client.GetAsync(uri))
+ {
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ Assert.Equal(remoteServer.EchoUri, response.RequestMessage.RequestUri);
+ }
+ }
+ }
+
+ [Theory]
+ [InlineData(200)]
+ [InlineData(201)]
+ [InlineData(400)]
+ public async Task GetAsync_AllowAutoRedirectTrue_NonRedirectStatusCode_LocationHeader_NoRedirect(int statusCode)
+ {
+ HttpClientHandler handler = CreateHttpClientHandler();
+ handler.AllowAutoRedirect = true;
+ using (HttpClient client = CreateHttpClient(handler))
+ {
+ await LoopbackServer.CreateServerAsync(async (origServer, origUrl) =>
+ {
+ await LoopbackServer.CreateServerAsync(async (redirectServer, redirectUrl) =>
+ {
+ Task<HttpResponseMessage> getResponseTask = client.GetAsync(origUrl);
+
+ Task redirectTask = redirectServer.AcceptConnectionSendResponseAndCloseAsync();
+
+ await TestHelper.WhenAllCompletedOrAnyFailed(
+ getResponseTask,
+ origServer.AcceptConnectionSendResponseAndCloseAsync((HttpStatusCode)statusCode, $"Location: {redirectUrl}\r\n"));
+
+ using (HttpResponseMessage response = await getResponseTask)
+ {
+ Assert.Equal(statusCode, (int)response.StatusCode);
+ Assert.Equal(origUrl, response.RequestMessage.RequestUri);
+ Assert.False(redirectTask.IsCompleted, "Should not have redirected to Location");
+ }
+ });
+ });
+ }
+ }
+
+ [Theory]
+ [InlineData("#origFragment", "", "#origFragment", false)]
+ [InlineData("#origFragment", "", "#origFragment", true)]
+ [InlineData("", "#redirFragment", "#redirFragment", false)]
+ [InlineData("", "#redirFragment", "#redirFragment", true)]
+ [InlineData("#origFragment", "#redirFragment", "#redirFragment", false)]
+ [InlineData("#origFragment", "#redirFragment", "#redirFragment", true)]
+ public async Task GetAsync_AllowAutoRedirectTrue_RetainsOriginalFragmentIfAppropriate(
+ string origFragment, string redirFragment, string expectedFragment, bool useRelativeRedirect)
+ {
+ if (IsWinHttpHandler)
+ {
+ // According to https://tools.ietf.org/html/rfc7231#section-7.1.2,
+ // "If the Location value provided in a 3xx (Redirection) response does
+ // not have a fragment component, a user agent MUST process the
+ // redirection as if the value inherits the fragment component of the
+ // URI reference used to generate the request target(i.e., the
+ // redirection inherits the original reference's fragment, if any)."
+ // WINHTTP is not doing this, and thus neither is WinHttpHandler.
+ // It also sometimes doesn't include the fragments for redirects
+ // even in other cases.
+ return;
+ }
+
+ HttpClientHandler handler = CreateHttpClientHandler();
+ handler.AllowAutoRedirect = true;
+ using (HttpClient client = CreateHttpClient(handler))
+ {
+ await LoopbackServer.CreateServerAsync(async (origServer, origUrl) =>
+ {
+ origUrl = new UriBuilder(origUrl) { Fragment = origFragment }.Uri;
+ Uri redirectUrl = new UriBuilder(origUrl) { Fragment = redirFragment }.Uri;
+ if (useRelativeRedirect)
+ {
+ redirectUrl = new Uri(redirectUrl.GetComponents(UriComponents.PathAndQuery | UriComponents.Fragment, UriFormat.SafeUnescaped), UriKind.Relative);
+ }
+ Uri expectedUrl = new UriBuilder(origUrl) { Fragment = expectedFragment }.Uri;
+
+ // Make and receive the first request that'll be redirected.
+ Task<HttpResponseMessage> getResponse = client.GetAsync(origUrl);
+ Task firstRequest = origServer.AcceptConnectionSendResponseAndCloseAsync(HttpStatusCode.Found, $"Location: {redirectUrl}\r\n");
+ Assert.Equal(firstRequest, await Task.WhenAny(firstRequest, getResponse));
+
+ // Receive the second request.
+ Task<List<string>> secondRequest = origServer.AcceptConnectionSendResponseAndCloseAsync();
+ await TestHelper.WhenAllCompletedOrAnyFailed(secondRequest, getResponse);
+
+ // Make sure the server received the second request for the right Uri.
+ Assert.NotEmpty(secondRequest.Result);
+ string[] statusLineParts = secondRequest.Result[0].Split(' ');
+ Assert.Equal(3, statusLineParts.Length);
+ Assert.Equal(expectedUrl.GetComponents(UriComponents.PathAndQuery, UriFormat.SafeUnescaped), statusLineParts[1]);
+
+ // Make sure the request message was updated with the correct redirected location.
+ using (HttpResponseMessage response = await getResponse)
+ {
+ Assert.Equal(200, (int)response.StatusCode);
+ Assert.Equal(expectedUrl.ToString(), response.RequestMessage.RequestUri.ToString());
+ }
+ });
+ }
+ }
+
+ [Theory, MemberData(nameof(RemoteServersMemberData))]
+ [OuterLoop("Uses external server")]
+ public async Task GetAsync_CredentialIsNetworkCredentialUriRedirect_StatusCodeUnauthorized(Configuration.Http.RemoteServer remoteServer)
+ {
+ HttpClientHandler handler = CreateHttpClientHandler();
+ handler.Credentials = _credential;
+ using (HttpClient client = CreateHttpClientForRemoteServer(remoteServer, handler))
+ {
+ Uri redirectUri = remoteServer.RedirectUriForCreds(
+ statusCode: 302,
+ userName: Username,
+ password: Password);
+ using (HttpResponseMessage unAuthResponse = await client.GetAsync(redirectUri))
+ {
+ Assert.Equal(HttpStatusCode.Unauthorized, unAuthResponse.StatusCode);
+ }
+ }
+ }
+
+ [Theory, MemberData(nameof(RemoteServersMemberData))]
+ [OuterLoop("Uses external server")]
+ public async Task HttpClientHandler_CredentialIsNotCredentialCacheAfterRedirect_StatusCodeOK(Configuration.Http.RemoteServer remoteServer)
+ {
+ HttpClientHandler handler = CreateHttpClientHandler();
+ handler.Credentials = _credential;
+ using (HttpClient client = CreateHttpClientForRemoteServer(remoteServer, handler))
+ {
+ Uri redirectUri = remoteServer.RedirectUriForCreds(
+ statusCode: 302,
+ userName: Username,
+ password: Password);
+ using (HttpResponseMessage unAuthResponse = await client.GetAsync(redirectUri))
+ {
+ Assert.Equal(HttpStatusCode.Unauthorized, unAuthResponse.StatusCode);
+ }
+
+ // Use the same handler to perform get request, authentication should succeed after redirect.
+ Uri uri = remoteServer.BasicAuthUriForCreds(userName: Username, password: Password);
+ using (HttpResponseMessage authResponse = await client.GetAsync(uri))
+ {
+ Assert.Equal(HttpStatusCode.OK, authResponse.StatusCode);
+ }
+ }
+ }
+
+ [OuterLoop("Uses external server")]
+ [Theory, MemberData(nameof(RemoteServersAndRedirectStatusCodes))]
+ public async Task GetAsync_CredentialIsCredentialCacheUriRedirect_StatusCodeOK(Configuration.Http.RemoteServer remoteServer, int statusCode)
+ {
+ if (statusCode == 308 && (IsWinHttpHandler && PlatformDetection.WindowsVersion < 10))
+ {
+ // 308 redirects are not supported on old versions of WinHttp, or on .NET Framework.
+ return;
+ }
+
+ Uri uri = remoteServer.BasicAuthUriForCreds(userName: Username, password: Password);
+ Uri redirectUri = remoteServer.RedirectUriForCreds(
+ statusCode: statusCode,
+ userName: Username,
+ password: Password);
+ _output.WriteLine(uri.AbsoluteUri);
+ _output.WriteLine(redirectUri.AbsoluteUri);
+ var credentialCache = new CredentialCache();
+ credentialCache.Add(uri, "Basic", _credential);
+
+ HttpClientHandler handler = CreateHttpClientHandler();
+ handler.Credentials = credentialCache;
+ using (HttpClient client = CreateHttpClientForRemoteServer(remoteServer, handler))
+ {
+ using (HttpResponseMessage response = await client.GetAsync(redirectUri))
+ {
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ Assert.Equal(uri, response.RequestMessage.RequestUri);
+ }
+ }
+ }
+
+ [OuterLoop("Uses external server")]
+ [Theory, MemberData(nameof(RemoteServersAndRedirectStatusCodes))]
+ public async Task DefaultHeaders_SetCredentials_ClearedOnRedirect(Configuration.Http.RemoteServer remoteServer, int statusCode)
+ {
+ if (statusCode == 308 && (IsWinHttpHandler && PlatformDetection.WindowsVersion < 10))
+ {
+ // 308 redirects are not supported on old versions of WinHttp, or on .NET Framework.
+ return;
+ }
+
+ HttpClientHandler handler = CreateHttpClientHandler();
+ using (HttpClient client = CreateHttpClientForRemoteServer(remoteServer, handler))
+ {
+ string credentialString = _credential.UserName + ":" + _credential.Password;
+ client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", credentialString);
+ Uri uri = remoteServer.RedirectUriForDestinationUri(
+ statusCode: statusCode,
+ destinationUri: remoteServer.EchoUri,
+ hops: 1);
+ _output.WriteLine("Uri: {0}", uri);
+ using (HttpResponseMessage response = await client.GetAsync(uri))
+ {
+ string responseText = await response.Content.ReadAsStringAsync();
+ _output.WriteLine(responseText);
+ Assert.False(TestHelper.JsonMessageContainsKey(responseText, "Authorization"));
+ }
+ }
+ }
+ }
+}
--- /dev/null
+// 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.Collections.Generic;
+using System.Diagnostics;
+using System.DirectoryServices.Protocols;
+using System.IO;
+using System.Linq;
+using System.Net.Test.Common;
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Threading;
+using System.Threading.Tasks;
+
+using Microsoft.DotNet.XUnitExtensions;
+
+using Xunit;
+using Xunit.Abstractions;
+
+namespace System.Net.Http.Functional.Tests
+{
+#if WINHTTPHANDLER_TEST
+ using HttpClientHandler = System.Net.Http.WinHttpClientHandler;
+#endif
+
+ public abstract class HttpClientHandler_Cancellation_Test : HttpClientHandlerTestBase
+ {
+ public HttpClientHandler_Cancellation_Test(ITestOutputHelper output) : base(output) { }
+
+ [Theory]
+ [InlineData(false, CancellationMode.Token)]
+ [InlineData(true, CancellationMode.Token)]
+ public async Task PostAsync_CancelDuringRequestContentSend_TaskCanceledQuickly(bool chunkedTransfer, CancellationMode mode)
+ {
+ if (LoopbackServerFactory.IsHttp2 && chunkedTransfer)
+ {
+ // There is no chunked encoding in HTTP/2
+ return;
+ }
+
+ var serverRelease = new TaskCompletionSource<bool>();
+ await LoopbackServerFactory.CreateClientAndServerAsync(async uri =>
+ {
+ try
+ {
+ using (HttpClient client = CreateHttpClient())
+ {
+ client.Timeout = Timeout.InfiniteTimeSpan;
+ var cts = new CancellationTokenSource();
+
+ var waitToSend = new TaskCompletionSource<bool>();
+ var contentSending = new TaskCompletionSource<bool>();
+ var req = new HttpRequestMessage(HttpMethod.Post, uri) { Version = VersionFromUseHttp2 };
+ req.Content = new ByteAtATimeContent(int.MaxValue, waitToSend.Task, contentSending, millisecondDelayBetweenBytes: 1);
+ req.Headers.TransferEncodingChunked = chunkedTransfer;
+
+ Task<HttpResponseMessage> resp = client.SendAsync(req, HttpCompletionOption.ResponseHeadersRead, cts.Token);
+ waitToSend.SetResult(true);
+ await contentSending.Task;
+ Cancel(mode, client, cts);
+ await ValidateClientCancellationAsync(() => resp);
+ }
+ }
+ finally
+ {
+ serverRelease.SetResult(true);
+ }
+ }, async server =>
+ {
+ try
+ {
+ await server.AcceptConnectionAsync(connection => serverRelease.Task);
+ }
+ catch { }; // Ignore any closing errors since we did not really process anything.
+ });
+ }
+
+ [Theory]
+ [MemberData(nameof(OneBoolAndCancellationMode))]
+ public async Task GetAsync_CancelDuringResponseHeadersReceived_TaskCanceledQuickly(bool connectionClose, CancellationMode mode)
+ {
+ if (LoopbackServerFactory.IsHttp2 && connectionClose)
+ {
+ // There is no Connection header in HTTP/2
+ return;
+ }
+
+ using (HttpClient client = CreateHttpClient())
+ {
+ client.Timeout = Timeout.InfiniteTimeSpan;
+ var cts = new CancellationTokenSource();
+
+ await LoopbackServerFactory.CreateServerAsync(async (server, url) =>
+ {
+ var partialResponseHeadersSent = new TaskCompletionSource<bool>();
+ var clientFinished = new TaskCompletionSource<bool>();
+
+ Task serverTask = server.AcceptConnectionAsync(async connection =>
+ {
+ await connection.ReadRequestDataAsync();
+ await connection.SendResponseAsync(HttpStatusCode.OK, content: null, isFinal: false);
+
+ partialResponseHeadersSent.TrySetResult(true);
+ await clientFinished.Task;
+ });
+
+ await ValidateClientCancellationAsync(async () =>
+ {
+ var req = new HttpRequestMessage(HttpMethod.Get, url) { Version = VersionFromUseHttp2 };
+ req.Headers.ConnectionClose = connectionClose;
+
+ Task<HttpResponseMessage> getResponse = client.SendAsync(req, HttpCompletionOption.ResponseHeadersRead, cts.Token);
+ await partialResponseHeadersSent.Task;
+ Cancel(mode, client, cts);
+ await getResponse;
+ });
+
+ try
+ {
+ clientFinished.SetResult(true);
+ await serverTask;
+ } catch { }
+ });
+ }
+ }
+
+ [Theory]
+ [ActiveIssue("https://github.com/dotnet/corefx/issues/28805")]
+ [MemberData(nameof(TwoBoolsAndCancellationMode))]
+ public async Task GetAsync_CancelDuringResponseBodyReceived_Buffered_TaskCanceledQuickly(bool chunkedTransfer, bool connectionClose, CancellationMode mode)
+ {
+ if (LoopbackServerFactory.IsHttp2 && (chunkedTransfer || connectionClose))
+ {
+ // There is no chunked encoding or connection header in HTTP/2
+ return;
+ }
+
+ using (HttpClient client = CreateHttpClient())
+ {
+ client.Timeout = Timeout.InfiniteTimeSpan;
+ var cts = new CancellationTokenSource();
+
+ await LoopbackServerFactory.CreateServerAsync(async (server, url) =>
+ {
+ var responseHeadersSent = new TaskCompletionSource<bool>();
+ var clientFinished = new TaskCompletionSource<bool>();
+
+ Task serverTask = server.AcceptConnectionAsync(async connection =>
+ {
+ var headers = new List<HttpHeaderData>();
+ headers.Add(chunkedTransfer ? new HttpHeaderData("Transfer-Encoding", "chunked") : new HttpHeaderData("Content-Length", "20"));
+ if (connectionClose)
+ {
+ headers.Add(new HttpHeaderData("Connection", "close"));
+ }
+
+ await connection.ReadRequestDataAsync();
+ await connection.SendResponseAsync(HttpStatusCode.OK, headers: headers, content: "123", isFinal: false);
+ responseHeadersSent.TrySetResult(true);
+ await clientFinished.Task;
+ });
+
+ await ValidateClientCancellationAsync(async () =>
+ {
+ var req = new HttpRequestMessage(HttpMethod.Get, url) { Version = VersionFromUseHttp2 };
+ req.Headers.ConnectionClose = connectionClose;
+
+ Task<HttpResponseMessage> getResponse = client.SendAsync(req, HttpCompletionOption.ResponseContentRead, cts.Token);
+ await responseHeadersSent.Task;
+ await Task.Delay(1); // make it more likely that client will have started processing response body
+ Cancel(mode, client, cts);
+ await getResponse;
+ });
+
+ try
+ {
+ clientFinished.SetResult(true);
+ await serverTask;
+ } catch { }
+ });
+ }
+ }
+
+ [Theory]
+ [MemberData(nameof(ThreeBools))]
+ public async Task GetAsync_CancelDuringResponseBodyReceived_Unbuffered_TaskCanceledQuickly(bool chunkedTransfer, bool connectionClose, bool readOrCopyToAsync)
+ {
+ if (LoopbackServerFactory.IsHttp2 && (chunkedTransfer || connectionClose))
+ {
+ // There is no chunked encoding or connection header in HTTP/2
+ return;
+ }
+
+ using (HttpClient client = CreateHttpClient())
+ {
+ client.Timeout = Timeout.InfiniteTimeSpan;
+ var cts = new CancellationTokenSource();
+
+ await LoopbackServerFactory.CreateServerAsync(async (server, url) =>
+ {
+ var clientFinished = new TaskCompletionSource<bool>();
+
+ Task serverTask = server.AcceptConnectionAsync(async connection =>
+ {
+ var headers = new List<HttpHeaderData>();
+ headers.Add(chunkedTransfer ? new HttpHeaderData("Transfer-Encoding", "chunked") : new HttpHeaderData("Content-Length", "20"));
+ if (connectionClose)
+ {
+ headers.Add(new HttpHeaderData("Connection", "close"));
+ }
+
+ await connection.ReadRequestDataAsync();
+ await connection.SendResponseAsync(HttpStatusCode.OK, headers: headers, isFinal: false);
+ await clientFinished.Task;
+ });
+
+ var req = new HttpRequestMessage(HttpMethod.Get, url) { Version = VersionFromUseHttp2 };
+ req.Headers.ConnectionClose = connectionClose;
+ Task<HttpResponseMessage> getResponse = client.SendAsync(req, HttpCompletionOption.ResponseHeadersRead, cts.Token);
+ await ValidateClientCancellationAsync(async () =>
+ {
+ HttpResponseMessage resp = await getResponse;
+ Stream respStream = await resp.Content.ReadAsStreamAsync();
+ Task readTask = readOrCopyToAsync ?
+ respStream.ReadAsync(new byte[1], 0, 1, cts.Token) :
+ respStream.CopyToAsync(Stream.Null, 10, cts.Token);
+ cts.Cancel();
+ await readTask;
+ });
+
+ try
+ {
+ clientFinished.SetResult(true);
+ await serverTask;
+ } catch { }
+ });
+ }
+ }
+
+ [Theory]
+ [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)
+ {
+ using (HttpClient client = CreateHttpClient())
+ {
+ client.Timeout = Timeout.InfiniteTimeSpan;
+
+ await LoopbackServerFactory.CreateServerAsync(async (server, url) =>
+ {
+ var clientReadSomeBody = new TaskCompletionSource<bool>();
+ var clientFinished = new TaskCompletionSource<bool>();
+
+ var responseContentSegment = new string('s', 3000);
+ int responseSegments = 4;
+ int contentLength = responseContentSegment.Length * responseSegments;
+
+ Task serverTask = server.AcceptConnectionAsync(async connection =>
+ {
+ await connection.ReadRequestDataAsync();
+ await connection.SendResponseAsync(HttpStatusCode.OK, headers: new HttpHeaderData[] { new HttpHeaderData("Content-Length", contentLength.ToString()) }, isFinal: false);
+ for (int i = 0; i < responseSegments; i++)
+ {
+ await connection.SendResponseBodyAsync(responseContentSegment, isFinal: i == responseSegments - 1);
+ if (i == 0)
+ {
+ await clientReadSomeBody.Task;
+ }
+ }
+
+ await clientFinished.Task;
+ });
+
+
+ using (HttpResponseMessage resp = await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead))
+ using (Stream respStream = await resp.Content.ReadAsStreamAsync())
+ {
+ var result = new MemoryStream();
+ int b = respStream.ReadByte();
+ Assert.NotEqual(-1, b);
+ result.WriteByte((byte)b);
+
+ Cancel(mode, client, null); // should not cancel the operation, as using ResponseHeadersRead
+ clientReadSomeBody.SetResult(true);
+
+ if (copyToAsync)
+ {
+ await respStream.CopyToAsync(result, 10, new CancellationTokenSource().Token);
+ }
+ else
+ {
+ byte[] buffer = new byte[10];
+ int bytesRead;
+ while ((bytesRead = await respStream.ReadAsync(buffer, 0, buffer.Length)) > 0)
+ {
+ result.Write(buffer, 0, bytesRead);
+ }
+ }
+
+ Assert.Equal(contentLength, result.Length);
+ }
+
+ clientFinished.SetResult(true);
+ await serverTask;
+ });
+ }
+ }
+
+ [ConditionalFact]
+ public async Task MaxConnectionsPerServer_WaitingConnectionsAreCancelable()
+ {
+ if (LoopbackServerFactory.IsHttp2)
+ {
+ // HTTP/2 does not use connection limits.
+ throw new SkipTestException("Not supported on HTTP/2");
+ }
+
+ using (HttpClientHandler handler = CreateHttpClientHandler())
+ using (HttpClient client = CreateHttpClient(handler))
+ {
+ handler.MaxConnectionsPerServer = 1;
+ client.Timeout = Timeout.InfiniteTimeSpan;
+
+ await LoopbackServer.CreateServerAsync(async (server, url) =>
+ {
+ var serverAboutToBlock = new TaskCompletionSource<bool>();
+ var blockServerResponse = new TaskCompletionSource<bool>();
+
+ Task serverTask1 = server.AcceptConnectionAsync(async connection1 =>
+ {
+ await connection1.ReadRequestHeaderAsync();
+ await connection1.Writer.WriteAsync($"HTTP/1.1 200 OK\r\nConnection: close\r\nDate: {DateTimeOffset.UtcNow:R}\r\n");
+ serverAboutToBlock.SetResult(true);
+ await blockServerResponse.Task;
+ await connection1.Writer.WriteAsync("Content-Length: 5\r\n\r\nhello");
+ });
+
+ Task get1 = client.GetAsync(url);
+ await serverAboutToBlock.Task;
+
+ var cts = new CancellationTokenSource();
+ Task get2 = ValidateClientCancellationAsync(() => client.GetAsync(url, cts.Token));
+ Task get3 = ValidateClientCancellationAsync(() => client.GetAsync(url, cts.Token));
+
+ Task get4 = client.GetAsync(url);
+
+ cts.Cancel();
+ await get2;
+ await get3;
+
+ blockServerResponse.SetResult(true);
+ await new[] { get1, serverTask1 }.WhenAllOrAnyFailed();
+
+ Task serverTask4 = server.AcceptConnectionSendResponseAndCloseAsync();
+
+ await new[] { get4, serverTask4 }.WhenAllOrAnyFailed();
+ });
+ }
+ }
+
+ [Fact]
+ public async Task SendAsync_Cancel_CancellationTokenPropagates()
+ {
+ TaskCompletionSource<bool> clientCanceled = new TaskCompletionSource<bool>();
+ await LoopbackServerFactory.CreateClientAndServerAsync(
+ async uri =>
+ {
+ var cts = new CancellationTokenSource();
+ cts.Cancel();
+
+ using (HttpClient client = CreateHttpClient())
+ {
+ OperationCanceledException ex = null;
+ try
+ {
+ await client.GetAsync(uri, cts.Token);
+ }
+ catch (OperationCanceledException e)
+ {
+ ex = e;
+ }
+ Assert.True(ex != null, "Expected OperationCancelledException, but no exception was thrown.");
+
+ Assert.True(cts.Token.IsCancellationRequested, "cts token IsCancellationRequested");
+
+ // .NET Framework has bug where it doesn't propagate token information.
+ Assert.True(ex.CancellationToken.IsCancellationRequested, "exception token IsCancellationRequested");
+
+ clientCanceled.SetResult(true);
+ }
+ },
+ async server =>
+ {
+ Task serverTask = server.HandleRequestAsync();
+ await clientCanceled.Task;
+ });
+ }
+
+ public static IEnumerable<object[]> PostAsync_Cancel_CancellationTokenPassedToContent_MemberData()
+ {
+ // Note: For HTTP2, the actual token will be a linked token and will not be an exact match for the original token.
+ // Verify that it behaves as expected by cancelling it and validating that cancellation propagates.
+
+ // StreamContent
+ {
+ CancellationTokenSource tokenSource = new CancellationTokenSource();
+ var actualToken = new StrongBox<CancellationToken>();
+ bool called = false;
+ var content = new StreamContent(new DelegateStream(
+ canReadFunc: () => true,
+ readAsyncFunc: (buffer, offset, count, cancellationToken) =>
+ {
+ int result = 1;
+ if (called)
+ {
+ result = 0;
+ Assert.False(cancellationToken.IsCancellationRequested);
+ tokenSource.Cancel();
+ Assert.True(cancellationToken.IsCancellationRequested);
+ }
+
+ called = true;
+ return Task.FromResult(result);
+ }
+ ));
+ yield return new object[] { content, tokenSource };
+ }
+
+ // MultipartContent
+ {
+ CancellationTokenSource tokenSource = new CancellationTokenSource();
+ var actualToken = new StrongBox<CancellationToken>();
+ bool called = false;
+ var content = new MultipartContent();
+ content.Add(new StreamContent(new DelegateStream(
+ canReadFunc: () => true,
+ canSeekFunc: () => true,
+ lengthFunc: () => 1,
+ positionGetFunc: () => 0,
+ positionSetFunc: _ => {},
+ readAsyncFunc: (buffer, offset, count, cancellationToken) =>
+ {
+ int result = 1;
+ if (called)
+ {
+ result = 0;
+ Assert.False(cancellationToken.IsCancellationRequested);
+ tokenSource.Cancel();
+ Assert.True(cancellationToken.IsCancellationRequested);
+ }
+
+ called = true;
+ return Task.FromResult(result);
+ }
+ )));
+ yield return new object[] { content, tokenSource };
+ }
+
+ // MultipartFormDataContent
+ {
+ CancellationTokenSource tokenSource = new CancellationTokenSource();
+ var actualToken = new StrongBox<CancellationToken>();
+ bool called = false;
+ var content = new MultipartFormDataContent();
+ content.Add(new StreamContent(new DelegateStream(
+ canReadFunc: () => true,
+ canSeekFunc: () => true,
+ lengthFunc: () => 1,
+ positionGetFunc: () => 0,
+ positionSetFunc: _ => {},
+ readAsyncFunc: (buffer, offset, count, cancellationToken) =>
+ {
+ int result = 1;
+ if (called)
+ {
+ result = 0;
+ Assert.False(cancellationToken.IsCancellationRequested);
+ tokenSource.Cancel();
+ Assert.True(cancellationToken.IsCancellationRequested);
+ }
+
+ called = true;
+ return Task.FromResult(result);
+ }
+ )));
+ yield return new object[] { content, tokenSource };
+ }
+ }
+
+ [OuterLoop("Uses Task.Delay")]
+ [Theory]
+ [MemberData(nameof(PostAsync_Cancel_CancellationTokenPassedToContent_MemberData))]
+ public async Task PostAsync_Cancel_CancellationTokenPassedToContent(HttpContent content, CancellationTokenSource cancellationTokenSource)
+ {
+ await LoopbackServerFactory.CreateClientAndServerAsync(
+ async uri =>
+ {
+ using (var invoker = new HttpMessageInvoker(CreateHttpClientHandler()))
+ using (var req = new HttpRequestMessage(HttpMethod.Post, uri) { Content = content, Version = VersionFromUseHttp2 })
+ try
+ {
+ using (HttpResponseMessage resp = await invoker.SendAsync(req, cancellationTokenSource.Token))
+ {
+ Assert.Equal("Hello World", await resp.Content.ReadAsStringAsync());
+ }
+ }
+ catch (OperationCanceledException) { }
+ },
+ async server =>
+ {
+ try
+ {
+ await server.HandleRequestAsync(content: "Hello World");
+ }
+ catch (Exception) { }
+ });
+ }
+
+ private async Task ValidateClientCancellationAsync(Func<Task> clientBodyAsync)
+ {
+ var stopwatch = Stopwatch.StartNew();
+ Exception error = await Record.ExceptionAsync(clientBodyAsync);
+ stopwatch.Stop();
+
+ Assert.NotNull(error);
+
+ Assert.True(
+ error is OperationCanceledException,
+ "Expected cancellation exception, got:" + Environment.NewLine + error);
+
+ Assert.True(stopwatch.Elapsed < new TimeSpan(0, 0, 60), $"Elapsed time {stopwatch.Elapsed} should be less than 60 seconds, was {stopwatch.Elapsed.TotalSeconds}");
+ }
+
+ private static void Cancel(CancellationMode mode, HttpClient client, CancellationTokenSource cts)
+ {
+ if ((mode & CancellationMode.Token) != 0)
+ {
+ cts?.Cancel();
+ }
+
+ if ((mode & CancellationMode.CancelPendingRequests) != 0)
+ {
+ client?.CancelPendingRequests();
+ }
+
+ if ((mode & CancellationMode.DisposeHttpClient) != 0)
+ {
+ client?.Dispose();
+ }
+ }
+
+ [Flags]
+ public enum CancellationMode
+ {
+ Token = 0x1,
+ CancelPendingRequests = 0x2,
+ DisposeHttpClient = 0x4
+ }
+
+ private static readonly bool[] s_bools = new[] { true, false };
+
+ public static IEnumerable<object[]> OneBoolAndCancellationMode() =>
+ from first in s_bools
+ from mode in new[] { CancellationMode.Token, CancellationMode.CancelPendingRequests, CancellationMode.DisposeHttpClient, CancellationMode.Token | CancellationMode.CancelPendingRequests }
+ select new object[] { first, mode };
+
+ public static IEnumerable<object[]> TwoBoolsAndCancellationMode() =>
+ from first in s_bools
+ from second in s_bools
+ from mode in new[] { CancellationMode.Token, CancellationMode.CancelPendingRequests, CancellationMode.DisposeHttpClient, CancellationMode.Token | CancellationMode.CancelPendingRequests }
+ select new object[] { first, second, mode };
+
+ public static IEnumerable<object[]> ThreeBools() =>
+ from first in s_bools
+ from second in s_bools
+ from third in s_bools
+ select new object[] { first, second, third };
+ }
+}
--- /dev/null
+// 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.Collections.Generic;
+using System.Net.Security;
+using System.Net.Sockets;
+using System.Net.Test.Common;
+using System.Runtime.InteropServices;
+using System.Security.Cryptography.X509Certificates;
+using System.Threading.Tasks;
+using Microsoft.DotNet.RemoteExecutor;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace System.Net.Http.Functional.Tests
+{
+ using Configuration = System.Net.Test.Common.Configuration;
+
+#if WINHTTPHANDLER_TEST
+ using HttpClientHandler = System.Net.Http.WinHttpClientHandler;
+#endif
+
+ public abstract class HttpClientHandler_ClientCertificates_Test : HttpClientHandlerTestBase
+ {
+ public HttpClientHandler_ClientCertificates_Test(ITestOutputHelper output) : base(output) { }
+
+ [Fact]
+ public void ClientCertificateOptions_Default()
+ {
+ using (HttpClientHandler handler = CreateHttpClientHandler())
+ {
+ Assert.Equal(ClientCertificateOption.Manual, handler.ClientCertificateOptions);
+ }
+ }
+
+ [Theory]
+ [InlineData((ClientCertificateOption)2)]
+ [InlineData((ClientCertificateOption)(-1))]
+ public void ClientCertificateOptions_InvalidArg_ThrowsException(ClientCertificateOption option)
+ {
+ using (HttpClientHandler handler = CreateHttpClientHandler())
+ {
+ AssertExtensions.Throws<ArgumentOutOfRangeException>("value", () => handler.ClientCertificateOptions = option);
+ }
+ }
+
+ [Theory]
+ [InlineData(ClientCertificateOption.Automatic)]
+ [InlineData(ClientCertificateOption.Manual)]
+ public void ClientCertificateOptions_ValueArg_Roundtrips(ClientCertificateOption option)
+ {
+ using (HttpClientHandler handler = CreateHttpClientHandler())
+ {
+ handler.ClientCertificateOptions = option;
+ Assert.Equal(option, handler.ClientCertificateOptions);
+ }
+ }
+
+ [Fact]
+ public void ClientCertificates_ClientCertificateOptionsAutomatic_ThrowsException()
+ {
+ using (HttpClientHandler handler = CreateHttpClientHandler())
+ {
+ handler.ClientCertificateOptions = ClientCertificateOption.Automatic;
+ Assert.Throws<InvalidOperationException>(() => handler.ClientCertificates);
+ }
+ }
+
+ private HttpClient CreateHttpClientWithCert(X509Certificate2 cert)
+ {
+ HttpClientHandler handler = CreateHttpClientHandler();
+ handler.ServerCertificateCustomValidationCallback = TestHelper.AllowAllCertificates;
+ Assert.NotNull(cert);
+ handler.ClientCertificates.Add(cert);
+ Assert.True(handler.ClientCertificates.Contains(cert));
+
+ return CreateHttpClient(handler);
+ }
+
+ [Theory]
+ [InlineData(1, true)]
+ [InlineData(2, true)]
+ [InlineData(3, false)]
+ public async Task Manual_CertificateOnlySentWhenValid_Success(int certIndex, bool serverExpectsClientCertificate)
+ {
+ var options = new LoopbackServer.Options { UseSsl = true };
+
+ X509Certificate2 GetClientCertificate(int certIndex) => certIndex switch
+ {
+ // This is a valid client cert since it has an EKU with a ClientAuthentication OID.
+ 1 => Configuration.Certificates.GetClientCertificate(),
+
+ // This is a valid client cert since it has no EKU thus all usages are permitted.
+ 2 => Configuration.Certificates.GetNoEKUCertificate(),
+
+ // This is an invalid client cert since it has an EKU but is missing ClientAuthentication OID.
+ 3 => Configuration.Certificates.GetServerCertificate(),
+ _ => null
+ };
+
+ await LoopbackServer.CreateServerAsync(async (server, url) =>
+ {
+ using X509Certificate2 cert = GetClientCertificate(certIndex);
+ using HttpClient client = CreateHttpClientWithCert(cert);
+
+ await TestHelper.WhenAllCompletedOrAnyFailed(
+ client.GetStringAsync(url),
+ server.AcceptConnectionAsync(async connection =>
+ {
+ SslStream sslStream = Assert.IsType<SslStream>(connection.Stream);
+ if (serverExpectsClientCertificate)
+ {
+ _output.WriteLine(
+ "Client cert: {0}",
+ ((X509Certificate2)sslStream.RemoteCertificate).GetNameInfo(X509NameType.SimpleName, false));
+ Assert.Equal(cert, sslStream.RemoteCertificate);
+ }
+ else
+ {
+ Assert.Null(sslStream.RemoteCertificate);
+ }
+
+ await connection.ReadRequestHeaderAndSendResponseAsync(additionalHeaders: "Connection: close\r\n");
+ }));
+ }, options);
+ }
+
+ [OuterLoop("Uses GC and waits for finalizers")]
+ [Theory]
+ [InlineData(6, false)]
+ [InlineData(3, true)]
+ public async Task Manual_CertificateSentMatchesCertificateReceived_Success(
+ int numberOfRequests,
+ bool reuseClient) // validate behavior with and without connection pooling, which impacts client cert usage
+ {
+ var options = new LoopbackServer.Options { UseSsl = true };
+
+ async Task MakeAndValidateRequest(HttpClient client, LoopbackServer server, Uri url, X509Certificate2 cert)
+ {
+ await TestHelper.WhenAllCompletedOrAnyFailed(
+ client.GetStringAsync(url),
+ server.AcceptConnectionAsync(async connection =>
+ {
+ SslStream sslStream = Assert.IsType<SslStream>(connection.Stream);
+ Assert.Equal(cert, sslStream.RemoteCertificate);
+
+ await connection.ReadRequestHeaderAndSendResponseAsync(additionalHeaders: "Connection: close\r\n");
+ }));
+ };
+
+ await LoopbackServer.CreateServerAsync(async (server, url) =>
+ {
+ using (X509Certificate2 cert = Configuration.Certificates.GetClientCertificate())
+ {
+ if (reuseClient)
+ {
+ using (HttpClient client = CreateHttpClientWithCert(cert))
+ {
+ for (int i = 0; i < numberOfRequests; i++)
+ {
+ await MakeAndValidateRequest(client, server, url, cert);
+
+ GC.Collect();
+ GC.WaitForPendingFinalizers();
+ }
+ }
+ }
+ else
+ {
+ for (int i = 0; i < numberOfRequests; i++)
+ {
+ using (HttpClient client = CreateHttpClientWithCert(cert))
+ {
+ await MakeAndValidateRequest(client, server, url, cert);
+ }
+
+ GC.Collect();
+ GC.WaitForPendingFinalizers();
+ }
+ }
+ }
+ }, options);
+ }
+
+ [ActiveIssue("https://github.com/dotnet/corefx/issues/37336")]
+ [Theory]
+ [InlineData(ClientCertificateOption.Manual)]
+ [InlineData(ClientCertificateOption.Automatic)]
+ public async Task AutomaticOrManual_DoesntFailRegardlessOfWhetherClientCertsAreAvailable(ClientCertificateOption mode)
+ {
+ using (HttpClientHandler handler = CreateHttpClientHandler())
+ using (HttpClient client = CreateHttpClient(handler))
+ {
+ handler.ServerCertificateCustomValidationCallback = TestHelper.AllowAllCertificates;
+ handler.ClientCertificateOptions = mode;
+
+ await LoopbackServer.CreateServerAsync(async server =>
+ {
+ Task clientTask = client.GetStringAsync(server.Uri);
+ Task serverTask = server.AcceptConnectionAsync(async connection =>
+ {
+ SslStream sslStream = Assert.IsType<SslStream>(connection.Stream);
+ await connection.ReadRequestHeaderAndSendResponseAsync();
+ });
+
+ await new Task[] { clientTask, serverTask }.WhenAllOrAnyFailed();
+ }, new LoopbackServer.Options { UseSsl = true });
+ }
+ }
+ }
+}
--- /dev/null
+// 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.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Net.Test.Common;
+using System.Threading.Tasks;
+using Microsoft.DotNet.XUnitExtensions;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace System.Net.Http.Functional.Tests
+{
+#if WINHTTPHANDLER_TEST
+ using HttpClientHandler = System.Net.Http.WinHttpClientHandler;
+#endif
+
+ public abstract class HttpClientHandlerTest_Cookies : HttpClientHandlerTestBase
+ {
+ private const string s_cookieName = "ABC";
+ private const string s_cookieValue = "123";
+ private const string s_expectedCookieHeaderValue = "ABC=123";
+
+ private const string s_customCookieHeaderValue = "CustomCookie=456";
+
+ private const string s_simpleContent = "Hello world!";
+
+ public HttpClientHandlerTest_Cookies(ITestOutputHelper output) : base(output) { }
+
+ //
+ // Send cookie tests
+ //
+
+ private static CookieContainer CreateSingleCookieContainer(Uri uri) => CreateSingleCookieContainer(uri, s_cookieName, s_cookieValue);
+
+ private static CookieContainer CreateSingleCookieContainer(Uri uri, string cookieName, string cookieValue)
+ {
+ var container = new CookieContainer();
+ container.Add(uri, new Cookie(cookieName, cookieValue));
+ return container;
+ }
+
+ private static string GetCookieHeaderValue(string cookieName, string cookieValue) => $"{cookieName}={cookieValue}";
+
+ [Fact]
+ public async Task GetAsync_DefaultCoookieContainer_NoCookieSent()
+ {
+ await LoopbackServerFactory.CreateClientAndServerAsync(
+ async uri =>
+ {
+ using (HttpClient client = CreateHttpClient())
+ {
+ await client.GetAsync(uri);
+ }
+ },
+ async server =>
+ {
+ HttpRequestData requestData = await server.HandleRequestAsync();
+ Assert.Equal(0, requestData.GetHeaderValueCount("Cookie"));
+ });
+ }
+
+ [Theory]
+ [MemberData(nameof(CookieNamesValuesAndUseCookies))]
+ public async Task GetAsync_SetCookieContainer_CookieSent(string cookieName, string cookieValue, bool useCookies)
+ {
+ await LoopbackServerFactory.CreateClientAndServerAsync(
+ async uri =>
+ {
+ HttpClientHandler handler = CreateHttpClientHandler();
+ handler.CookieContainer = CreateSingleCookieContainer(uri, cookieName, cookieValue);
+ handler.UseCookies = useCookies;
+
+ using (HttpClient client = CreateHttpClient(handler))
+ {
+ await client.GetAsync(uri);
+ }
+ },
+ async server =>
+ {
+ HttpRequestData requestData = await server.HandleRequestAsync();
+ if (useCookies)
+ {
+ Assert.Equal(GetCookieHeaderValue(cookieName, cookieValue), requestData.GetSingleHeaderValue("Cookie"));
+ }
+ else
+ {
+ Assert.Equal(0, requestData.GetHeaderValueCount("Cookie"));
+ }
+ });
+ }
+
+ [Fact]
+ public async Task GetAsync_SetCookieContainerMultipleCookies_CookiesSent()
+ {
+ var cookies = new Cookie[]
+ {
+ new Cookie("hello", "world"),
+ new Cookie("foo", "bar"),
+ new Cookie("ABC", "123")
+ };
+
+ await LoopbackServerFactory.CreateClientAndServerAsync(
+ async uri =>
+ {
+ HttpClientHandler handler = CreateHttpClientHandler();
+ var cookieContainer = new CookieContainer();
+ foreach (Cookie c in cookies)
+ {
+ cookieContainer.Add(uri, c);
+ }
+ handler.CookieContainer = cookieContainer;
+
+ using (HttpClient client = CreateHttpClient(handler))
+ {
+ await client.GetAsync(uri);
+ }
+ },
+ async server =>
+ {
+ HttpRequestData requestData = await server.HandleRequestAsync();
+ string expectedHeaderValue = string.Join("; ", cookies.Select(c => $"{c.Name}={c.Value}").ToArray());
+ Assert.Equal(expectedHeaderValue, requestData.GetSingleHeaderValue("Cookie"));
+ });
+ }
+
+ [Fact]
+ public async Task GetAsync_AddCookieHeader_CookieHeaderSent()
+ {
+ await LoopbackServerFactory.CreateClientAndServerAsync(
+ async uri =>
+ {
+ using (HttpClient client = CreateHttpClient())
+ {
+ var requestMessage = new HttpRequestMessage(HttpMethod.Get, uri) { Version = VersionFromUseHttp2 };
+ requestMessage.Headers.Add("Cookie", s_customCookieHeaderValue);
+
+ await client.SendAsync(requestMessage);
+ }
+ },
+ async server =>
+ {
+ HttpRequestData requestData = await server.HandleRequestAsync();
+ Assert.Equal(s_customCookieHeaderValue, requestData.GetSingleHeaderValue("Cookie"));
+ });
+ }
+
+ [Fact]
+ public async Task GetAsync_AddMultipleCookieHeaders_CookiesSent()
+ {
+ await LoopbackServerFactory.CreateClientAndServerAsync(
+ async uri =>
+ {
+ using (HttpClient client = CreateHttpClient())
+ {
+ var requestMessage = new HttpRequestMessage(HttpMethod.Get, uri) { Version = VersionFromUseHttp2 };
+ requestMessage.Headers.Add("Cookie", "A=1");
+ requestMessage.Headers.Add("Cookie", "B=2");
+ requestMessage.Headers.Add("Cookie", "C=3");
+
+ await client.SendAsync(requestMessage);
+ }
+ },
+ async server =>
+ {
+ HttpRequestData requestData = await server.HandleRequestAsync();
+
+ // Multiple Cookie header values are treated as any other header values and are
+ // concatenated using ", " as the separator.
+
+ string cookieHeaderValue = requestData.GetSingleHeaderValue("Cookie");
+
+ var cookieValues = cookieHeaderValue.Split(new string[] { ", " }, StringSplitOptions.None);
+ Assert.Contains("A=1", cookieValues);
+ Assert.Contains("B=2", cookieValues);
+ Assert.Contains("C=3", cookieValues);
+ Assert.Equal(3, cookieValues.Count());
+ });
+ }
+
+ private string GetCookieValue(HttpRequestData request)
+ {
+ if (!LoopbackServerFactory.IsHttp2)
+ {
+ // HTTP/1.x must have only one value.
+ return request.GetSingleHeaderValue("Cookie");
+ }
+
+ string cookieHeaderValue = null;
+ string[] cookieHeaderValues = request.GetHeaderValues("Cookie");
+
+ foreach (string header in cookieHeaderValues)
+ {
+ if (cookieHeaderValue == null)
+ {
+ cookieHeaderValue = header;
+ }
+ else
+ {
+ // rfc7540 8.1.2.5 states multiple cookie headers should be represented as single value.
+ cookieHeaderValue = String.Concat(cookieHeaderValue, "; ", header);
+ }
+ }
+
+ return cookieHeaderValue;
+ }
+
+ [ConditionalFact]
+ public async Task GetAsync_SetCookieContainerAndCookieHeader_BothCookiesSent()
+ {
+ await LoopbackServerFactory.CreateServerAsync(async (server, url) =>
+ {
+ HttpClientHandler handler = CreateHttpClientHandler();
+ handler.CookieContainer = CreateSingleCookieContainer(url);
+
+ using (HttpClient client = CreateHttpClient(handler))
+ {
+ var requestMessage = new HttpRequestMessage(HttpMethod.Get, url) { Version = VersionFromUseHttp2 };
+ requestMessage.Headers.Add("Cookie", s_customCookieHeaderValue);
+
+ Task<HttpResponseMessage> getResponseTask = client.SendAsync(requestMessage);
+ Task<HttpRequestData> serverTask = server.HandleRequestAsync();
+ await TestHelper.WhenAllCompletedOrAnyFailed(getResponseTask, serverTask);
+
+ HttpRequestData requestData = await serverTask;
+ string cookieHeaderValue = GetCookieValue(requestData);
+ var cookies = cookieHeaderValue.Split(new string[] { "; " }, StringSplitOptions.None);
+ Assert.Contains(s_expectedCookieHeaderValue, cookies);
+ Assert.Contains(s_customCookieHeaderValue, cookies);
+ Assert.Equal(2, cookies.Count());
+ }
+ });
+ }
+
+ [ConditionalFact]
+ public async Task GetAsync_SetCookieContainerAndMultipleCookieHeaders_BothCookiesSent()
+ {
+ await LoopbackServerFactory.CreateServerAsync(async (server, url) =>
+ {
+ HttpClientHandler handler = CreateHttpClientHandler();
+ handler.CookieContainer = CreateSingleCookieContainer(url);
+
+ using (HttpClient client = CreateHttpClient(handler))
+ {
+ var requestMessage = new HttpRequestMessage(HttpMethod.Get, url) { Version = VersionFromUseHttp2 };
+ requestMessage.Headers.Add("Cookie", "A=1");
+ requestMessage.Headers.Add("Cookie", "B=2");
+
+ Task<HttpResponseMessage> getResponseTask = client.SendAsync(requestMessage);
+ Task<HttpRequestData> serverTask = server.HandleRequestAsync();
+ await TestHelper.WhenAllCompletedOrAnyFailed(getResponseTask, serverTask);
+
+ HttpRequestData requestData = await serverTask;
+ string cookieHeaderValue = GetCookieValue(requestData);
+
+ // Multiple Cookie header values are treated as any other header values and are
+ // concatenated using ", " as the separator. The container cookie is concatenated to
+ // one of these values using the "; " cookie separator.
+
+ var cookieValues = cookieHeaderValue.Split(new string[] { ", " }, StringSplitOptions.None);
+ Assert.Equal(2, cookieValues.Count());
+
+ // Find container cookie and remove it so we can validate the rest of the cookie header values
+ bool sawContainerCookie = false;
+ for (int i = 0; i < cookieValues.Length; i++)
+ {
+ if (cookieValues[i].Contains(';'))
+ {
+ Assert.False(sawContainerCookie);
+
+ var cookies = cookieValues[i].Split(new string[] { "; " }, StringSplitOptions.None);
+ Assert.Equal(2, cookies.Count());
+ Assert.Contains(s_expectedCookieHeaderValue, cookies);
+
+ sawContainerCookie = true;
+ cookieValues[i] = cookies.Where(c => c != s_expectedCookieHeaderValue).Single();
+ }
+ }
+
+ Assert.Contains("A=1", cookieValues);
+ Assert.Contains("B=2", cookieValues);
+ }
+ });
+ }
+
+ [Fact]
+ public async Task GetAsyncWithRedirect_SetCookieContainer_CorrectCookiesSent()
+ {
+ const string path1 = "/foo";
+ const string path2 = "/bar";
+
+ await LoopbackServerFactory.CreateClientAndServerAsync(async url =>
+ {
+ Uri url1 = new Uri(url, path1);
+ Uri url2 = new Uri(url, path2);
+ Uri unusedUrl = new Uri(url, "/unused");
+
+ HttpClientHandler handler = CreateHttpClientHandler();
+ handler.CookieContainer = new CookieContainer();
+ handler.CookieContainer.Add(url1, new Cookie("cookie1", "value1"));
+ handler.CookieContainer.Add(url2, new Cookie("cookie2", "value2"));
+ handler.CookieContainer.Add(unusedUrl, new Cookie("cookie3", "value3"));
+
+ using (HttpClient client = CreateHttpClient(handler))
+ {
+ client.DefaultRequestHeaders.ConnectionClose = true; // to avoid issues with connection pooling
+ await client.GetAsync(url1);
+ }
+ },
+ async server =>
+ {
+ HttpRequestData requestData1 = await server.HandleRequestAsync(HttpStatusCode.Found, new HttpHeaderData[] { new HttpHeaderData("Location", path2) });
+ Assert.Equal("cookie1=value1", requestData1.GetSingleHeaderValue("Cookie"));
+
+ HttpRequestData requestData2 = await server.HandleRequestAsync(content: s_simpleContent);
+ Assert.Equal("cookie2=value2", requestData2.GetSingleHeaderValue("Cookie"));
+ });
+ }
+
+ //
+ // Receive cookie tests
+ //
+
+ [Theory]
+ [MemberData(nameof(CookieNamesValuesAndUseCookies))]
+ public async Task GetAsync_ReceiveSetCookieHeader_CookieAdded(string cookieName, string cookieValue, bool useCookies)
+ {
+ await LoopbackServerFactory.CreateServerAsync(async (server, url) =>
+ {
+ HttpClientHandler handler = CreateHttpClientHandler();
+ handler.UseCookies = useCookies;
+
+ using (HttpClient client = CreateHttpClient(handler))
+ {
+ Task<HttpResponseMessage> getResponseTask = client.GetAsync(url);
+ Task<HttpRequestData> serverTask = server.HandleRequestAsync(
+ HttpStatusCode.OK, new HttpHeaderData[] { new HttpHeaderData("Set-Cookie", GetCookieHeaderValue(cookieName, cookieValue)) }, s_simpleContent);
+ await TestHelper.WhenAllCompletedOrAnyFailed(getResponseTask, serverTask);
+
+ CookieCollection collection = handler.CookieContainer.GetCookies(url);
+
+ if (useCookies)
+ {
+ Assert.Equal(1, collection.Count);
+ Assert.Equal(cookieName, collection[0].Name);
+ Assert.Equal(cookieValue, collection[0].Value);
+ }
+ else
+ {
+ Assert.Equal(0, collection.Count);
+ }
+ }
+ });
+ }
+
+ [Fact]
+ public async Task GetAsync_ReceiveMultipleSetCookieHeaders_CookieAdded()
+ {
+ await LoopbackServerFactory.CreateServerAsync(async (server, url) =>
+ {
+ HttpClientHandler handler = CreateHttpClientHandler();
+
+ using (HttpClient client = CreateHttpClient(handler))
+ {
+ Task<HttpResponseMessage> getResponseTask = client.GetAsync(url);
+ Task<HttpRequestData> serverTask = server.HandleRequestAsync(
+ HttpStatusCode.OK,
+ new HttpHeaderData[]
+ {
+ new HttpHeaderData("Set-Cookie", "A=1; Path=/"),
+ new HttpHeaderData("Set-Cookie", "B=2; Path=/"),
+ new HttpHeaderData("Set-Cookie", "C=3; Path=/")
+ },
+ s_simpleContent);
+ await TestHelper.WhenAllCompletedOrAnyFailed(getResponseTask, serverTask);
+
+ CookieCollection collection = handler.CookieContainer.GetCookies(url);
+ Assert.Equal(3, collection.Count);
+
+ // Convert to array so we can more easily process contents, since CookieCollection does not implement IEnumerable<Cookie>
+ Cookie[] cookies = new Cookie[3];
+ collection.CopyTo(cookies, 0);
+
+ Assert.Contains(cookies, c => c.Name == "A" && c.Value == "1");
+ Assert.Contains(cookies, c => c.Name == "B" && c.Value == "2");
+ Assert.Contains(cookies, c => c.Name == "C" && c.Value == "3");
+ }
+ });
+ }
+
+ [Fact]
+ public async Task GetAsync_ReceiveSetCookieHeader_CookieUpdated()
+ {
+ const string newCookieValue = "789";
+
+ await LoopbackServerFactory.CreateServerAsync(async (server, url) =>
+ {
+ HttpClientHandler handler = CreateHttpClientHandler();
+ handler.CookieContainer = CreateSingleCookieContainer(url);
+
+ using (HttpClient client = CreateHttpClient(handler))
+ {
+ Task<HttpResponseMessage> getResponseTask = client.GetAsync(url);
+ Task<HttpRequestData> serverTask = server.HandleRequestAsync(
+ HttpStatusCode.OK,
+ new HttpHeaderData[] { new HttpHeaderData("Set-Cookie", $"{s_cookieName}={newCookieValue}") },
+ s_simpleContent);
+ await TestHelper.WhenAllCompletedOrAnyFailed(getResponseTask, serverTask);
+
+ CookieCollection collection = handler.CookieContainer.GetCookies(url);
+ Assert.Equal(1, collection.Count);
+ Assert.Equal(s_cookieName, collection[0].Name);
+ Assert.Equal(newCookieValue, collection[0].Value);
+ }
+ });
+ }
+
+ [Fact]
+ public async Task GetAsync_ReceiveSetCookieHeader_CookieRemoved()
+ {
+ await LoopbackServerFactory.CreateServerAsync(async (server, url) =>
+ {
+ HttpClientHandler handler = CreateHttpClientHandler();
+ handler.CookieContainer = CreateSingleCookieContainer(url);
+
+ using (HttpClient client = CreateHttpClient(handler))
+ {
+ Task<HttpResponseMessage> getResponseTask = client.GetAsync(url);
+ Task<HttpRequestData> serverTask = server.HandleRequestAsync(
+ HttpStatusCode.OK,
+ new HttpHeaderData[] { new HttpHeaderData("Set-Cookie", $"{s_cookieName}=; Expires=Sun, 06 Nov 1994 08:49:37 GMT") },
+ s_simpleContent);
+ await TestHelper.WhenAllCompletedOrAnyFailed(getResponseTask, serverTask);
+
+ CookieCollection collection = handler.CookieContainer.GetCookies(url);
+ Assert.Equal(0, collection.Count);
+ }
+ });
+ }
+
+ [Fact]
+ public async Task GetAsync_ReceiveInvalidSetCookieHeader_ValidCookiesAdded()
+ {
+ await LoopbackServerFactory.CreateServerAsync(async (server, url) =>
+ {
+ HttpClientHandler handler = CreateHttpClientHandler();
+
+ using (HttpClient client = CreateHttpClient(handler))
+ {
+ Task<HttpResponseMessage> getResponseTask = client.GetAsync(url);
+ Task<HttpRequestData> serverTask = server.HandleRequestAsync(
+ HttpStatusCode.OK,
+ new HttpHeaderData[]
+ {
+ new HttpHeaderData("Set-Cookie", "A=1; Path=/;Expires=asdfsadgads"), // invalid Expires
+ new HttpHeaderData("Set-Cookie", "B=2; Path=/"),
+ new HttpHeaderData("Set-Cookie", "C=3; Path=/")
+ },
+ s_simpleContent);
+ await TestHelper.WhenAllCompletedOrAnyFailed(getResponseTask, serverTask);
+
+ CookieCollection collection = handler.CookieContainer.GetCookies(url);
+ Assert.Equal(2, collection.Count);
+
+ // Convert to array so we can more easily process contents, since CookieCollection does not implement IEnumerable<Cookie>
+ Cookie[] cookies = new Cookie[3];
+ collection.CopyTo(cookies, 0);
+
+ Assert.Contains(cookies, c => c.Name == "B" && c.Value == "2");
+ Assert.Contains(cookies, c => c.Name == "C" && c.Value == "3");
+ }
+ });
+ }
+
+ [Fact]
+ public async Task GetAsyncWithRedirect_ReceiveSetCookie_CookieSent()
+ {
+ const string path1 = "/foo";
+ const string path2 = "/bar";
+
+ await LoopbackServerFactory.CreateClientAndServerAsync(async url =>
+ {
+ Uri url1 = new Uri(url, path1);
+
+ HttpClientHandler handler = CreateHttpClientHandler();
+
+ using (HttpClient client = CreateHttpClient(handler))
+ {
+ client.DefaultRequestHeaders.ConnectionClose = true; // to avoid issues with connection pooling
+ await client.GetAsync(url1);
+
+ CookieCollection collection = handler.CookieContainer.GetCookies(url);
+
+ Assert.Equal(2, collection.Count);
+
+ // Convert to array so we can more easily process contents, since CookieCollection does not implement IEnumerable<Cookie>
+ Cookie[] cookies = new Cookie[2];
+ collection.CopyTo(cookies, 0);
+
+ Assert.Contains(cookies, c => c.Name == "A" && c.Value == "1");
+ Assert.Contains(cookies, c => c.Name == "B" && c.Value == "2");
+ }
+ },
+ async server =>
+ {
+ HttpRequestData requestData1 = await server.HandleRequestAsync(
+ HttpStatusCode.Found,
+ new HttpHeaderData[]
+ {
+ new HttpHeaderData("Location", $"{path2}"),
+ new HttpHeaderData("Set-Cookie", "A=1; Path=/")
+ });
+
+ Assert.Equal(0, requestData1.GetHeaderValueCount("Cookie"));
+
+ HttpRequestData requestData2 = await server.HandleRequestAsync(
+ HttpStatusCode.OK,
+ new HttpHeaderData[]
+ {
+ new HttpHeaderData("Set-Cookie", "B=2; Path=/")
+ },
+ s_simpleContent);
+
+ Assert.Equal("A=1", requestData2.GetSingleHeaderValue("Cookie"));
+ });
+ }
+
+ [Fact]
+ public async Task GetAsyncWithBasicAuth_ReceiveSetCookie_CookieSent()
+ {
+ if (IsWinHttpHandler)
+ {
+ // Issue https://github.com/dotnet/corefx/issues/26986
+ // WinHttpHandler does not process the cookie.
+ return;
+ }
+
+ await LoopbackServerFactory.CreateClientAndServerAsync(async url =>
+ {
+ HttpClientHandler handler = CreateHttpClientHandler();
+ handler.Credentials = new NetworkCredential("user", "pass");
+
+ using (HttpClient client = CreateHttpClient(handler))
+ {
+ await client.GetAsync(url);
+
+ CookieCollection collection = handler.CookieContainer.GetCookies(url);
+
+ Assert.Equal(2, collection.Count);
+
+ // Convert to array so we can more easily process contents, since CookieCollection does not implement IEnumerable<Cookie>
+ Cookie[] cookies = new Cookie[2];
+ collection.CopyTo(cookies, 0);
+
+ Assert.Contains(cookies, c => c.Name == "A" && c.Value == "1");
+ Assert.Contains(cookies, c => c.Name == "B" && c.Value == "2");
+ }
+ },
+ async server =>
+ {
+ HttpRequestData requestData1 = await server.HandleRequestAsync(
+ HttpStatusCode.Unauthorized,
+ new HttpHeaderData[]
+ {
+ new HttpHeaderData("WWW-Authenticate", "Basic realm=\"WallyWorld\""),
+ new HttpHeaderData("Set-Cookie", "A=1; Path=/")
+ });
+
+ Assert.Equal(0, requestData1.GetHeaderValueCount("Cookie"));
+
+ HttpRequestData requestData2 = await server.HandleRequestAsync(
+ HttpStatusCode.OK,
+ new HttpHeaderData[]
+ {
+ new HttpHeaderData("Set-Cookie", "B=2; Path=/")
+ },
+ s_simpleContent);
+
+ Assert.Equal("A=1", requestData2.GetSingleHeaderValue("Cookie"));
+ });
+ }
+
+ //
+ // MemberData stuff
+ //
+
+ private static string GenerateCookie(string name, char repeat, int overallHeaderValueLength)
+ {
+ string emptyHeaderValue = $"{name}=; Path=/";
+
+ Debug.Assert(overallHeaderValueLength > emptyHeaderValue.Length);
+
+ int valueCount = overallHeaderValueLength - emptyHeaderValue.Length;
+ return new string(repeat, valueCount);
+ }
+
+ public static IEnumerable<object[]> CookieNamesValuesAndUseCookies()
+ {
+ foreach (bool useCookies in new[] { true, false })
+ {
+ yield return new object[] { "ABC", "123", useCookies };
+ yield return new object[] { "Hello", "World", useCookies };
+ yield return new object[] { "foo", "bar", useCookies };
+ yield return new object[] { "Hello World", "value", useCookies };
+ yield return new object[] { ".AspNetCore.Session", "RAExEmXpoCbueP_QYM", useCookies };
+
+ yield return new object[]
+ {
+ ".AspNetCore.Antiforgery.Xam7_OeLcN4",
+ "CfDJ8NGNxAt7CbdClq3UJ8_6w_4661wRQZT1aDtUOIUKshbcV4P0NdS8klCL5qGSN-PNBBV7w23G6MYpQ81t0PMmzIN4O04fqhZ0u1YPv66mixtkX3iTi291DgwT3o5kozfQhe08-RAExEmXpoCbueP_QYM",
+ useCookies
+ };
+
+ // WinHttpHandler calls WinHttpQueryHeaders to iterate through multiple Set-Cookie header values,
+ // using an initial buffer size of 128 chars. If the buffer is not large enough, WinHttpQueryHeaders
+ // returns an insufficient buffer error, allowing WinHttpHandler to try again with a larger buffer.
+ // Sometimes when WinHttpQueryHeaders fails due to insufficient buffer, it still advances the
+ // iteration index, which would cause header values to be missed if not handled correctly.
+ //
+ // In particular, WinHttpQueryHeader behaves as follows for the following header value lengths:
+ // * 0-127 chars: succeeds, index advances from 0 to 1.
+ // * 128-255 chars: fails due to insufficient buffer, index advances from 0 to 1.
+ // * 256+ chars: fails due to insufficient buffer, index stays at 0.
+ //
+ // The below overall header value lengths were chosen to exercise reading header values at these
+ // edges, to ensure WinHttpHandler does not miss multiple Set-Cookie headers.
+
+ yield return new object[] { "foo", GenerateCookie(name: "foo", repeat: 'a', overallHeaderValueLength: 126), useCookies };
+ yield return new object[] { "foo", GenerateCookie(name: "foo", repeat: 'a', overallHeaderValueLength: 127), useCookies };
+ yield return new object[] { "foo", GenerateCookie(name: "foo", repeat: 'a', overallHeaderValueLength: 128), useCookies };
+ yield return new object[] { "foo", GenerateCookie(name: "foo", repeat: 'a', overallHeaderValueLength: 129), useCookies };
+
+ yield return new object[] { "foo", GenerateCookie(name: "foo", repeat: 'a', overallHeaderValueLength: 254), useCookies };
+ yield return new object[] { "foo", GenerateCookie(name: "foo", repeat: 'a', overallHeaderValueLength: 255), useCookies };
+ yield return new object[] { "foo", GenerateCookie(name: "foo", repeat: 'a', overallHeaderValueLength: 256), useCookies };
+ yield return new object[] { "foo", GenerateCookie(name: "foo", repeat: 'a', overallHeaderValueLength: 257), useCookies };
+ }
+ }
+ }
+
+ public abstract class HttpClientHandlerTest_Cookies_Http11 : HttpClientHandlerTestBase
+ {
+ public HttpClientHandlerTest_Cookies_Http11(ITestOutputHelper output) : base(output) { }
+
+ [Fact]
+ public async Task GetAsync_ReceiveMultipleSetCookieHeaders_CookieAdded()
+ {
+ await LoopbackServer.CreateServerAsync(async (server, url) =>
+ {
+ HttpClientHandler handler = CreateHttpClientHandler();
+
+ using (HttpClient client = CreateHttpClient(handler))
+ {
+ Task<HttpResponseMessage> getResponseTask = client.GetAsync(url);
+ Task<List<string>> serverTask = server.AcceptConnectionSendResponseAndCloseAsync(
+ HttpStatusCode.OK,
+ $"Set-Cookie: A=1; Path=/\r\n" +
+ $"Set-Cookie : B=2; Path=/\r\n" + // space before colon to verify header is trimmed and recognized
+ $"Set-Cookie: C=3; Path=/\r\n",
+ "Hello world!");
+ await TestHelper.WhenAllCompletedOrAnyFailed(getResponseTask, serverTask);
+
+ CookieCollection collection = handler.CookieContainer.GetCookies(url);
+ Assert.Equal(3, collection.Count);
+
+ // Convert to array so we can more easily process contents, since CookieCollection does not implement IEnumerable<Cookie>
+ Cookie[] cookies = new Cookie[3];
+ collection.CopyTo(cookies, 0);
+
+ Assert.Contains(cookies, c => c.Name == "A" && c.Value == "1");
+ Assert.Contains(cookies, c => c.Name == "B" && c.Value == "2");
+ Assert.Contains(cookies, c => c.Name == "C" && c.Value == "3");
+ }
+ });
+ }
+ }
+}
--- /dev/null
+// 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.Collections.Generic;
+using System.IO;
+using System.IO.Compression;
+using System.Net.Test.Common;
+using System.Text.RegularExpressions;
+using System.Threading.Tasks;
+
+using Xunit;
+using Xunit.Abstractions;
+
+namespace System.Net.Http.Functional.Tests
+{
+ using Configuration = System.Net.Test.Common.Configuration;
+
+#if WINHTTPHANDLER_TEST
+ using HttpClientHandler = System.Net.Http.WinHttpClientHandler;
+#endif
+
+ public abstract class HttpClientHandler_Decompression_Test : HttpClientHandlerTestBase
+ {
+ public HttpClientHandler_Decompression_Test(ITestOutputHelper output) : base(output) { }
+
+ public static IEnumerable<object[]> RemoteServersAndCompressionUris()
+ {
+ foreach (Configuration.Http.RemoteServer remoteServer in Configuration.Http.RemoteServers)
+ {
+ yield return new object[] { remoteServer, remoteServer.GZipUri };
+ yield return new object[] { remoteServer, remoteServer.DeflateUri };
+ }
+ }
+
+ public static IEnumerable<object[]> DecompressedResponse_MethodSpecified_DecompressedContentReturned_MemberData()
+ {
+ foreach (bool specifyAllMethods in new[] { false, true })
+ {
+ yield return new object[]
+ {
+ "deflate",
+ new Func<Stream, Stream>(s => new DeflateStream(s, CompressionLevel.Optimal, leaveOpen: true)),
+ specifyAllMethods ? DecompressionMethods.Deflate : DecompressionMethods.All
+ };
+ yield return new object[]
+ {
+ "gzip",
+ new Func<Stream, Stream>(s => new GZipStream(s, CompressionLevel.Optimal, leaveOpen: true)),
+ specifyAllMethods ? DecompressionMethods.GZip : DecompressionMethods.All
+ };
+ yield return new object[]
+ {
+ "br",
+ new Func<Stream, Stream>(s => new BrotliStream(s, CompressionLevel.Optimal, leaveOpen: true)),
+ specifyAllMethods ? DecompressionMethods.Brotli : DecompressionMethods.All
+ };
+ }
+ }
+
+ [Theory]
+ [MemberData(nameof(DecompressedResponse_MethodSpecified_DecompressedContentReturned_MemberData))]
+ public async Task DecompressedResponse_MethodSpecified_DecompressedContentReturned(
+ string encodingName, Func<Stream, Stream> compress, DecompressionMethods methods)
+ {
+ // Brotli only supported on SocketsHttpHandler.
+ if (IsWinHttpHandler && encodingName == "br")
+ {
+ return;
+ }
+
+ var expectedContent = new byte[12345];
+ new Random(42).NextBytes(expectedContent);
+
+ await LoopbackServer.CreateClientAndServerAsync(async uri =>
+ {
+ using (HttpClientHandler handler = CreateHttpClientHandler())
+ using (HttpClient client = CreateHttpClient(handler))
+ {
+ handler.AutomaticDecompression = methods;
+ Assert.Equal<byte>(expectedContent, await client.GetByteArrayAsync(uri));
+ }
+ }, async server =>
+ {
+ await server.AcceptConnectionAsync(async connection =>
+ {
+ await connection.ReadRequestHeaderAsync();
+ await connection.Writer.WriteAsync($"HTTP/1.1 200 OK\r\nContent-Encoding: {encodingName}\r\n\r\n");
+ using (Stream compressedStream = compress(connection.Stream))
+ {
+ await compressedStream.WriteAsync(expectedContent);
+ }
+ });
+ });
+ }
+
+ public static IEnumerable<object[]> DecompressedResponse_MethodNotSpecified_OriginalContentReturned_MemberData()
+ {
+ yield return new object[]
+ {
+ "deflate",
+ new Func<Stream, Stream>(s => new DeflateStream(s, CompressionLevel.Optimal, leaveOpen: true)),
+ DecompressionMethods.None
+ };
+ yield return new object[]
+ {
+ "gzip",
+ new Func<Stream, Stream>(s => new GZipStream(s, CompressionLevel.Optimal, leaveOpen: true)),
+ DecompressionMethods.Brotli
+ };
+ yield return new object[]
+ {
+ "br",
+ new Func<Stream, Stream>(s => new BrotliStream(s, CompressionLevel.Optimal, leaveOpen: true)),
+ DecompressionMethods.Deflate | DecompressionMethods.GZip
+ };
+ }
+
+ [Theory]
+ [MemberData(nameof(DecompressedResponse_MethodNotSpecified_OriginalContentReturned_MemberData))]
+ public async Task DecompressedResponse_MethodNotSpecified_OriginalContentReturned(
+ string encodingName, Func<Stream, Stream> compress, DecompressionMethods methods)
+ {
+ var expectedContent = new byte[12345];
+ new Random(42).NextBytes(expectedContent);
+
+ var compressedContentStream = new MemoryStream();
+ using (Stream s = compress(compressedContentStream))
+ {
+ await s.WriteAsync(expectedContent);
+ }
+ byte[] compressedContent = compressedContentStream.ToArray();
+
+ await LoopbackServer.CreateClientAndServerAsync(async uri =>
+ {
+ using (HttpClientHandler handler = CreateHttpClientHandler())
+ using (HttpClient client = CreateHttpClient(handler))
+ {
+ handler.AutomaticDecompression = methods;
+ Assert.Equal<byte>(compressedContent, await client.GetByteArrayAsync(uri));
+ }
+ }, async server =>
+ {
+ await server.AcceptConnectionAsync(async connection =>
+ {
+ await connection.ReadRequestHeaderAsync();
+ await connection.Writer.WriteAsync($"HTTP/1.1 200 OK\r\nContent-Encoding: {encodingName}\r\n\r\n");
+ await connection.Stream.WriteAsync(compressedContent);
+ });
+ });
+ }
+
+ [OuterLoop("Uses external servers")]
+ [Theory, MemberData(nameof(RemoteServersAndCompressionUris))]
+ public async Task GetAsync_SetAutomaticDecompression_ContentDecompressed(Configuration.Http.RemoteServer remoteServer, Uri uri)
+ {
+ HttpClientHandler handler = CreateHttpClientHandler();
+ handler.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;
+ using (HttpClient client = CreateHttpClientForRemoteServer(remoteServer, handler))
+ {
+ using (HttpResponseMessage response = await client.GetAsync(uri))
+ {
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ string responseContent = await response.Content.ReadAsStringAsync();
+ _output.WriteLine(responseContent);
+ TestHelper.VerifyResponseBody(
+ responseContent,
+ response.Content.Headers.ContentMD5,
+ false,
+ null);
+ }
+ }
+ }
+
+ [OuterLoop("Uses external server")]
+ [Theory, MemberData(nameof(RemoteServersAndCompressionUris))]
+ public async Task GetAsync_SetAutomaticDecompression_HeadersRemoved(Configuration.Http.RemoteServer remoteServer, Uri uri)
+ {
+ HttpClientHandler handler = CreateHttpClientHandler();
+ handler.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;
+ using (HttpClient client = CreateHttpClientForRemoteServer(remoteServer, handler))
+ using (HttpResponseMessage response = await client.GetAsync(uri, HttpCompletionOption.ResponseHeadersRead))
+ {
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+
+ Assert.False(response.Content.Headers.Contains("Content-Encoding"), "Content-Encoding unexpectedly found");
+ Assert.False(response.Content.Headers.Contains("Content-Length"), "Content-Length unexpectedly found");
+ }
+ }
+
+ [Theory]
+ [InlineData(DecompressionMethods.Brotli, "br", "")]
+ [InlineData(DecompressionMethods.Brotli, "br", "br")]
+ [InlineData(DecompressionMethods.Brotli, "br", "gzip")]
+ [InlineData(DecompressionMethods.Brotli, "br", "gzip, deflate")]
+ [InlineData(DecompressionMethods.GZip, "gzip", "")]
+ [InlineData(DecompressionMethods.Deflate, "deflate", "")]
+ [InlineData(DecompressionMethods.GZip | DecompressionMethods.Deflate, "gzip, deflate", "")]
+ [InlineData(DecompressionMethods.GZip, "gzip", "gzip")]
+ [InlineData(DecompressionMethods.Deflate, "deflate", "deflate")]
+ [InlineData(DecompressionMethods.GZip, "gzip", "deflate")]
+ [InlineData(DecompressionMethods.GZip, "gzip", "br")]
+ [InlineData(DecompressionMethods.Deflate, "deflate", "gzip")]
+ [InlineData(DecompressionMethods.Deflate, "deflate", "br")]
+ [InlineData(DecompressionMethods.GZip | DecompressionMethods.Deflate, "gzip, deflate", "gzip, deflate")]
+ public async Task GetAsync_SetAutomaticDecompression_AcceptEncodingHeaderSentWithNoDuplicates(
+ DecompressionMethods methods,
+ string encodings,
+ string manualAcceptEncodingHeaderValues)
+ {
+ // Brotli only supported on SocketsHttpHandler.
+ if (IsWinHttpHandler && (encodings.Contains("br") || manualAcceptEncodingHeaderValues.Contains("br")))
+ {
+ return;
+ }
+
+ await LoopbackServer.CreateServerAsync(async (server, url) =>
+ {
+ HttpClientHandler handler = CreateHttpClientHandler();
+ handler.AutomaticDecompression = methods;
+
+ using (HttpClient client = CreateHttpClient(handler))
+ {
+ if (!string.IsNullOrEmpty(manualAcceptEncodingHeaderValues))
+ {
+ client.DefaultRequestHeaders.Add("Accept-Encoding", manualAcceptEncodingHeaderValues);
+ }
+
+ Task<HttpResponseMessage> clientTask = client.GetAsync(url);
+ Task<List<string>> serverTask = server.AcceptConnectionSendResponseAndCloseAsync();
+ await TaskTimeoutExtensions.WhenAllOrAnyFailed(new Task[] { clientTask, serverTask });
+
+ List<string> requestLines = await serverTask;
+ string requestLinesString = string.Join("\r\n", requestLines);
+ _output.WriteLine(requestLinesString);
+
+ Assert.InRange(Regex.Matches(requestLinesString, "Accept-Encoding").Count, 1, 1);
+ Assert.InRange(Regex.Matches(requestLinesString, encodings).Count, 1, 1);
+ if (!string.IsNullOrEmpty(manualAcceptEncodingHeaderValues))
+ {
+ Assert.InRange(Regex.Matches(requestLinesString, manualAcceptEncodingHeaderValues).Count, 1, 1);
+ }
+
+ using (HttpResponseMessage response = await clientTask)
+ {
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ }
+ }
+ });
+ }
+ }
+}
--- /dev/null
+// 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.Collections.Generic;
+using System.Diagnostics;
+using System.Net.Test.Common;
+using System.Runtime.InteropServices;
+using System.Text;
+using System.Threading.Tasks;
+using Microsoft.DotNet.RemoteExecutor;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace System.Net.Http.Functional.Tests
+{
+ using Configuration = System.Net.Test.Common.Configuration;
+
+#if WINHTTPHANDLER_TEST
+ using HttpClientHandler = System.Net.Http.WinHttpClientHandler;
+#endif
+
+ public abstract class HttpClientHandler_DefaultProxyCredentials_Test : HttpClientHandlerTestBase
+ {
+ public HttpClientHandler_DefaultProxyCredentials_Test(ITestOutputHelper output) : base(output) { }
+
+ [Fact]
+ public void Default_Get_Null()
+ {
+ using (HttpClientHandler handler = CreateHttpClientHandler())
+ {
+ Assert.Null(handler.DefaultProxyCredentials);
+ }
+ }
+
+ [Fact]
+ public void SetGet_Roundtrips()
+ {
+ using (HttpClientHandler handler = CreateHttpClientHandler())
+ {
+ var creds = new NetworkCredential("username", "password", "domain");
+
+ handler.DefaultProxyCredentials = null;
+ Assert.Null(handler.DefaultProxyCredentials);
+
+ handler.DefaultProxyCredentials = creds;
+ Assert.Same(creds, handler.DefaultProxyCredentials);
+
+ handler.DefaultProxyCredentials = CredentialCache.DefaultCredentials;
+ Assert.Same(CredentialCache.DefaultCredentials, handler.DefaultProxyCredentials);
+ }
+ }
+
+ [Fact]
+ public async Task ProxyExplicitlyProvided_DefaultCredentials_Ignored()
+ {
+ var explicitProxyCreds = new NetworkCredential("rightusername", "rightpassword");
+ var defaultSystemProxyCreds = new NetworkCredential("wrongusername", "wrongpassword");
+ string expectCreds = "Basic " + Convert.ToBase64String(Encoding.UTF8.GetBytes($"{explicitProxyCreds.UserName}:{explicitProxyCreds.Password}"));
+
+ await LoopbackServer.CreateClientAndServerAsync(async proxyUrl =>
+ {
+ using (HttpClientHandler handler = CreateHttpClientHandler())
+ using (HttpClient client = CreateHttpClient(handler))
+ {
+ handler.Proxy = new UseSpecifiedUriWebProxy(proxyUrl, explicitProxyCreds);
+ handler.DefaultProxyCredentials = defaultSystemProxyCreds;
+ using (HttpResponseMessage response = await client.GetAsync("http://notatrealserver.com/")) // URL does not matter
+ {
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ }
+ }
+ }, async server =>
+ {
+ await server.AcceptConnectionSendResponseAndCloseAsync(
+ HttpStatusCode.ProxyAuthenticationRequired, "Proxy-Authenticate: Basic\r\n");
+
+ List<string> headers = await server.AcceptConnectionSendResponseAndCloseAsync(HttpStatusCode.OK);
+ Assert.Equal(expectCreds, LoopbackServer.GetRequestHeaderValue(headers, "Proxy-Authorization"));
+ });
+ }
+
+#if !WINHTTPHANDLER_TEST
+ [ActiveIssue("https://github.com/dotnet/corefx/issues/42323")]
+ [OuterLoop("Uses external server")]
+ [PlatformSpecific(TestPlatforms.AnyUnix)] // The default proxy is resolved via WinINet on Windows.
+ [Theory]
+ [InlineData(false)]
+ [InlineData(true)]
+ public async Task ProxySetViaEnvironmentVariable_DefaultProxyCredentialsUsed(bool useProxy)
+ {
+ const string ExpectedUsername = "rightusername";
+ const string ExpectedPassword = "rightpassword";
+ LoopbackServer.Options options = new LoopbackServer.Options { IsProxy = true, Username = ExpectedUsername, Password = ExpectedPassword };
+
+ await LoopbackServer.CreateServerAsync(async (proxyServer, proxyUri) =>
+ {
+ // SocketsHttpHandler can read a default proxy from the http_proxy environment variable. Ensure that when it does,
+ // our default proxy credentials are used. To avoid messing up anything else in this process, we run the
+ // test in another process.
+ var psi = new ProcessStartInfo();
+ Task<List<string>> proxyTask = null;
+
+ if (useProxy)
+ {
+ proxyTask = proxyServer.AcceptConnectionPerformAuthenticationAndCloseAsync("Proxy-Authenticate: Basic realm=\"NetCore\"\r\n");
+ psi.Environment.Add("http_proxy", $"http://{proxyUri.Host}:{proxyUri.Port}");
+ }
+
+ RemoteExecutor.Invoke(async (useProxyString, useHttp2String) =>
+ {
+ using (HttpClientHandler handler = CreateHttpClientHandler(useHttp2String))
+ using (HttpClient client = CreateHttpClient(handler, useHttp2String))
+ {
+ var creds = new NetworkCredential(ExpectedUsername, ExpectedPassword);
+ handler.DefaultProxyCredentials = creds;
+ handler.UseProxy = bool.Parse(useProxyString);
+
+ HttpResponseMessage response = await client.GetAsync(Configuration.Http.RemoteEchoServer);
+ // Correctness of user and password is done in server part.
+ Assert.True(response.StatusCode == HttpStatusCode.OK);
+ }
+ }, useProxy.ToString(), UseHttp2.ToString(), new RemoteInvokeOptions { StartInfo = psi }).Dispose();
+ if (useProxy)
+ {
+ await proxyTask;
+ }
+ }, options);
+ }
+#endif
+
+ // The purpose of this test is mainly to validate the .NET Framework OOB System.Net.Http implementation
+ // since it has an underlying dependency to WebRequest. While .NET Core implementations of System.Net.Http
+ // are not using any WebRequest code, the test is still useful to validate correctness.
+ [OuterLoop("Uses external server")]
+ [Fact]
+ public async Task ProxyNotExplicitlyProvided_DefaultCredentialsSet_DefaultWebProxySetToNull_Success()
+ {
+ WebRequest.DefaultWebProxy = null;
+
+ using (HttpClientHandler handler = CreateHttpClientHandler())
+ using (HttpClient client = CreateHttpClient(handler))
+ {
+ handler.DefaultProxyCredentials = new NetworkCredential("UsernameNotUsed", "PasswordNotUsed");
+ HttpResponseMessage response = await client.GetAsync(Configuration.Http.RemoteEchoServer);
+
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ }
+ }
+ }
+}
--- /dev/null
+// 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.Linq;
+using System.Net.Test.Common;
+using System.Runtime.InteropServices;
+using System.Threading;
+using System.Threading.Tasks;
+
+using Xunit;
+using Xunit.Abstractions;
+
+namespace System.Net.Http.Functional.Tests
+{
+ using Configuration = System.Net.Test.Common.Configuration;
+
+#if WINHTTPHANDLER_TEST
+ using HttpClientHandler = System.Net.Http.WinHttpClientHandler;
+#endif
+
+ public abstract class HttpClientHandler_MaxConnectionsPerServer_Test : HttpClientHandlerTestBase
+ {
+ public HttpClientHandler_MaxConnectionsPerServer_Test(ITestOutputHelper output) : base(output) { }
+
+ [Fact]
+ public void Default_ExpectedValue()
+ {
+ using (HttpClientHandler handler = CreateHttpClientHandler())
+ {
+ Assert.Equal(int.MaxValue, handler.MaxConnectionsPerServer);
+ }
+ }
+
+ [Theory]
+ [InlineData(0)]
+ [InlineData(-1)]
+ public void Set_InvalidValues_Throws(int invalidValue)
+ {
+ using (HttpClientHandler handler = CreateHttpClientHandler())
+ {
+ Assert.Throws<ArgumentOutOfRangeException>(() => handler.MaxConnectionsPerServer = invalidValue);
+ }
+ }
+
+ [Theory]
+ [InlineData(1)]
+ [InlineData(2)]
+ [InlineData(int.MaxValue)]
+ [InlineData(int.MaxValue - 1)]
+ public void Set_ValidValues_Success(int validValue)
+ {
+ using (HttpClientHandler handler = CreateHttpClientHandler())
+ {
+ handler.MaxConnectionsPerServer = validValue;
+ }
+ }
+
+ [Theory]
+ [InlineData(1, 5, false)]
+ [InlineData(1, 5, true)]
+ [InlineData(2, 2, false)]
+ [InlineData(2, 2, true)]
+ [InlineData(3, 2, false)]
+ [InlineData(3, 2, true)]
+ [InlineData(3, 5, false)]
+ [OuterLoop("Uses external servers")]
+ public async Task GetAsync_MaxLimited_ConcurrentCallsStillSucceed(int maxConnections, int numRequests, bool secure)
+ {
+ using (HttpClientHandler handler = CreateHttpClientHandler())
+ using (HttpClient client = CreateHttpClient(handler))
+ {
+ handler.MaxConnectionsPerServer = maxConnections;
+ await Task.WhenAll(
+ from i in Enumerable.Range(0, numRequests)
+ select client.GetAsync(secure ? Configuration.Http.RemoteEchoServer : Configuration.Http.SecureRemoteEchoServer));
+ }
+ }
+
+ [OuterLoop("Relies on kicking off GC and waiting for finalizers")]
+ [Fact]
+ public async Task GetAsync_DontDisposeResponse_EventuallyUnblocksWaiters()
+ {
+ await LoopbackServer.CreateServerAsync(async (server, uri) =>
+ {
+ using (HttpClientHandler handler = CreateHttpClientHandler())
+ using (HttpClient client = CreateHttpClient(handler))
+ {
+ handler.MaxConnectionsPerServer = 1;
+
+ // Let server handle two requests.
+ const string ResponseContent = "abcdefghijklmnopqrstuvwxyz";
+ Task serverTask1 = server.AcceptConnectionSendResponseAndCloseAsync(content: ResponseContent);
+ Task serverTask2 = server.AcceptConnectionSendResponseAndCloseAsync(content: ResponseContent);
+
+ // Make first request and drop the response, not explicitly disposing of it.
+ void MakeAndDropRequest() => client.GetAsync(uri, HttpCompletionOption.ResponseHeadersRead); // separated out to enable GC of response
+ MakeAndDropRequest();
+
+ // A second request should eventually succeed, once the first one is cleaned up.
+ Task<HttpResponseMessage> secondResponse = client.GetAsync(uri);
+ Assert.True(SpinWait.SpinUntil(() =>
+ {
+ GC.Collect();
+ GC.WaitForPendingFinalizers();
+ return secondResponse.IsCompleted;
+ }, 30 * 1000), "Expected second response to have completed");
+
+ await new[] { serverTask1, serverTask2, secondResponse }.WhenAllOrAnyFailed();
+ }
+ });
+ }
+ }
+}
--- /dev/null
+// 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.Collections.Generic;
+using System.Net.Test.Common;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace System.Net.Http.Functional.Tests
+{
+ using Configuration = System.Net.Test.Common.Configuration;
+
+#if WINHTTPHANDLER_TEST
+ using HttpClientHandler = System.Net.Http.WinHttpClientHandler;
+#endif
+
+ public abstract class HttpClientHandler_MaxResponseHeadersLength_Test : HttpClientHandlerTestBase
+ {
+ public HttpClientHandler_MaxResponseHeadersLength_Test(ITestOutputHelper output) : base(output) { }
+
+ [Theory]
+ [InlineData(0)]
+ [InlineData(-1)]
+ public void InvalidValue_ThrowsException(int invalidValue)
+ {
+ using (HttpClientHandler handler = CreateHttpClientHandler())
+ {
+ AssertExtensions.Throws<ArgumentOutOfRangeException>("value", () => handler.MaxResponseHeadersLength = invalidValue);
+ }
+ }
+
+ [Theory]
+ [InlineData(1)]
+ [InlineData(65)]
+ [InlineData(int.MaxValue)]
+ public void ValidValue_SetGet_Roundtrips(int validValue)
+ {
+ using (HttpClientHandler handler = CreateHttpClientHandler())
+ {
+ handler.MaxResponseHeadersLength = validValue;
+ Assert.Equal(validValue, handler.MaxResponseHeadersLength);
+ }
+ }
+
+ [Fact]
+ public async Task SetAfterUse_Throws()
+ {
+ await LoopbackServerFactory.CreateClientAndServerAsync(async uri =>
+ {
+ using HttpClientHandler handler = CreateHttpClientHandler();
+ using HttpClient client = CreateHttpClient(handler);
+
+ handler.MaxResponseHeadersLength = 1;
+ (await client.GetStreamAsync(uri)).Dispose();
+ Assert.Throws<InvalidOperationException>(() => handler.MaxResponseHeadersLength = 1);
+ },
+ server => server.AcceptConnectionSendResponseAndCloseAsync());
+ }
+
+ [OuterLoop]
+ [Fact]
+ public async Task InfiniteSingleHeader_ThrowsException()
+ {
+ await LoopbackServer.CreateServerAsync(async (server, url) =>
+ {
+ using (HttpClientHandler handler = CreateHttpClientHandler())
+ using (HttpClient client = CreateHttpClient(handler))
+ {
+ Task<HttpResponseMessage> getAsync = client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead);
+ await server.AcceptConnectionAsync(async connection =>
+ {
+ var cts = new CancellationTokenSource();
+ Task serverTask = Task.Run(async delegate
+ {
+ await connection.ReadRequestHeaderAndSendCustomResponseAsync("HTTP/1.1 200 OK\r\nContent-Length: 0\r\nMyInfiniteHeader: ");
+ try
+ {
+ while (!cts.IsCancellationRequested)
+ {
+ await connection.Writer.WriteAsync(new string('s', 16000));
+ await Task.Delay(1);
+ }
+ }
+ catch { }
+ });
+
+ Exception e = await Assert.ThrowsAsync<HttpRequestException>(() => getAsync);
+ cts.Cancel();
+ if (!IsWinHttpHandler)
+ {
+ Assert.Contains((handler.MaxResponseHeadersLength * 1024).ToString(), e.ToString());
+ }
+ await serverTask;
+ });
+ }
+ });
+ }
+
+ [OuterLoop]
+ [Theory, MemberData(nameof(ResponseWithManyHeadersData))]
+ public async Task ThresholdExceeded_ThrowsException(string responseHeaders, int? maxResponseHeadersLength, bool shouldSucceed)
+ {
+ await LoopbackServer.CreateServerAsync(async (server, url) =>
+ {
+ using (HttpClientHandler handler = CreateHttpClientHandler())
+ using (HttpClient client = CreateHttpClient(handler))
+ {
+ if (maxResponseHeadersLength.HasValue)
+ {
+ handler.MaxResponseHeadersLength = maxResponseHeadersLength.Value;
+ }
+ Task<HttpResponseMessage> getAsync = client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead);
+
+ await server.AcceptConnectionAsync(async connection =>
+ {
+ Task serverTask = connection.ReadRequestHeaderAndSendCustomResponseAsync(responseHeaders);
+
+ if (shouldSucceed)
+ {
+ (await getAsync).Dispose();
+ await serverTask;
+ }
+ else
+ {
+ Exception e = await Assert.ThrowsAsync<HttpRequestException>(() => getAsync);
+ if (!IsWinHttpHandler)
+ {
+ Assert.Contains((handler.MaxResponseHeadersLength * 1024).ToString(), e.ToString());
+ }
+ try { await serverTask; } catch { }
+ }
+ });
+ }
+ });
+ }
+
+ public static IEnumerable<object[]> ResponseWithManyHeadersData
+ {
+ get
+ {
+ foreach (int? max in new int?[] { null, 1, 31, 128 })
+ {
+ int actualSize = max.HasValue ? max.Value : 64;
+
+ yield return new object[] { GenerateLargeResponseHeaders(actualSize * 1024 - 1), max, true }; // Small enough
+ yield return new object[] { GenerateLargeResponseHeaders(actualSize * 1024), max, true }; // Just right
+ yield return new object[] { GenerateLargeResponseHeaders(actualSize * 1024 + 1), max, false }; // Too big
+ }
+ }
+ }
+
+ private static string GenerateLargeResponseHeaders(int responseHeadersSizeInBytes)
+ {
+ var buffer = new StringBuilder();
+ buffer.Append("HTTP/1.1 200 OK\r\n");
+ buffer.Append("Content-Length: 0\r\n");
+ for (int i = 0; i < 24; i++)
+ {
+ buffer.Append($"Custom-{i:D4}: 1234567890123456789012345\r\n");
+ }
+ buffer.Append($"Custom-24: ");
+ buffer.Append(new string('c', responseHeadersSizeInBytes - (buffer.Length + 4)));
+ buffer.Append("\r\n\r\n");
+
+ string response = buffer.ToString();
+ Assert.Equal(responseHeadersSizeInBytes, response.Length);
+ return response;
+ }
+ }
+}
--- /dev/null
+// 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.Collections.Generic;
+using System.Net.Sockets;
+using System.Net.Test.Common;
+using System.Text;
+using System.Threading.Tasks;
+
+using Microsoft.DotNet.XUnitExtensions;
+using Microsoft.DotNet.RemoteExecutor;
+
+using Xunit;
+using Xunit.Abstractions;
+
+namespace System.Net.Http.Functional.Tests
+{
+ using Configuration = System.Net.Test.Common.Configuration;
+
+#if WINHTTPHANDLER_TEST
+ using HttpClientHandler = System.Net.Http.WinHttpClientHandler;
+#endif
+
+ public abstract class HttpClientHandler_Proxy_Test : HttpClientHandlerTestBase
+ {
+ public HttpClientHandler_Proxy_Test(ITestOutputHelper output) : base(output) { }
+
+ [Fact]
+ public async Task Dispose_HandlerWithProxy_ProxyNotDisposed()
+ {
+ var proxy = new TrackDisposalProxy();
+
+ await LoopbackServerFactory.CreateClientAndServerAsync(async uri =>
+ {
+ using (HttpClientHandler handler = CreateHttpClientHandler())
+ {
+ handler.UseProxy = true;
+ handler.Proxy = proxy;
+ using (HttpClient client = CreateHttpClient(handler))
+ {
+ Assert.Equal("hello", await client.GetStringAsync(uri));
+ }
+ }
+ }, async server =>
+ {
+ await server.HandleRequestAsync(content: "hello");
+ });
+
+ Assert.True(proxy.ProxyUsed);
+ Assert.False(proxy.Disposed);
+ }
+
+ [ActiveIssue("https://github.com/dotnet/corefx/issues/32809")]
+ [OuterLoop("Uses external server")]
+ [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindowsNanoServer))]
+ [InlineData(AuthenticationSchemes.Ntlm, true, false)]
+ [InlineData(AuthenticationSchemes.Negotiate, true, false)]
+ [InlineData(AuthenticationSchemes.Basic, false, false)]
+ [InlineData(AuthenticationSchemes.Basic, true, false)]
+ [InlineData(AuthenticationSchemes.Digest, false, false)]
+ [InlineData(AuthenticationSchemes.Digest, true, false)]
+ [InlineData(AuthenticationSchemes.Ntlm, false, false)]
+ [InlineData(AuthenticationSchemes.Negotiate, false, false)]
+ [InlineData(AuthenticationSchemes.Basic, false, true)]
+ [InlineData(AuthenticationSchemes.Basic, true, true)]
+ [InlineData(AuthenticationSchemes.Digest, false, true)]
+ [InlineData(AuthenticationSchemes.Digest, true, true)]
+ public async Task AuthProxy__ValidCreds_ProxySendsRequestToServer(
+ AuthenticationSchemes proxyAuthScheme,
+ bool secureServer,
+ bool proxyClosesConnectionAfterFirst407Response)
+ {
+ if (!PlatformDetection.IsWindows &&
+ (proxyAuthScheme == AuthenticationSchemes.Negotiate || proxyAuthScheme == AuthenticationSchemes.Ntlm))
+ {
+ // CI machines don't have GSSAPI module installed and will fail with error from
+ // System.Net.Security.NegotiateStreamPal.AcquireCredentialsHandle():
+ // "GSSAPI operation failed with error - An invalid status code was supplied
+ // Configuration file does not specify default realm)."
+ return;
+ }
+
+ Uri serverUri = secureServer ? Configuration.Http.SecureRemoteEchoServer : Configuration.Http.RemoteEchoServer;
+
+ var options = new LoopbackProxyServer.Options
+ { AuthenticationSchemes = proxyAuthScheme,
+ ConnectionCloseAfter407 = proxyClosesConnectionAfterFirst407Response
+ };
+ using (LoopbackProxyServer proxyServer = LoopbackProxyServer.Create(options))
+ {
+ using (HttpClientHandler handler = CreateHttpClientHandler())
+ using (HttpClient client = CreateHttpClient(handler))
+ {
+ handler.Proxy = new WebProxy(proxyServer.Uri);
+ handler.Proxy.Credentials = new NetworkCredential("username", "password");
+ using (HttpResponseMessage response = await client.GetAsync(serverUri))
+ {
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ TestHelper.VerifyResponseBody(
+ await response.Content.ReadAsStringAsync(),
+ response.Content.Headers.ContentMD5,
+ false,
+ null);
+ }
+ }
+ }
+ }
+
+ [OuterLoop("Uses external server")]
+ [ConditionalFact]
+ public void Proxy_UseEnvironmentVariableToSetSystemProxy_RequestGoesThruProxy()
+ {
+ RemoteExecutor.Invoke(async (useHttp2String) =>
+ {
+ var options = new LoopbackProxyServer.Options { AddViaRequestHeader = true };
+ using (LoopbackProxyServer proxyServer = LoopbackProxyServer.Create(options))
+ {
+ Environment.SetEnvironmentVariable("http_proxy", proxyServer.Uri.AbsoluteUri.ToString());
+
+ using (HttpClient client = CreateHttpClient(useHttp2String))
+ using (HttpResponseMessage response = await client.GetAsync(Configuration.Http.RemoteEchoServer))
+ {
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ string body = await response.Content.ReadAsStringAsync();
+ Assert.Contains(proxyServer.ViaHeader, body);
+ }
+ }
+ }, UseHttp2.ToString()).Dispose();
+ }
+
+ [ActiveIssue("https://github.com/dotnet/corefx/issues/32809")]
+ [OuterLoop("Uses external server")]
+ [Theory]
+ [MemberData(nameof(CredentialsForProxy))]
+ public async Task Proxy_BypassFalse_GetRequestGoesThroughCustomProxy(ICredentials creds, bool wrapCredsInCache)
+ {
+ var options = new LoopbackProxyServer.Options
+ { AuthenticationSchemes = creds != null ? AuthenticationSchemes.Basic : AuthenticationSchemes.None
+ };
+ using (LoopbackProxyServer proxyServer = LoopbackProxyServer.Create(options))
+ {
+ const string BasicAuth = "Basic";
+ if (wrapCredsInCache)
+ {
+ Assert.IsAssignableFrom<NetworkCredential>(creds);
+ var cache = new CredentialCache();
+ cache.Add(proxyServer.Uri, BasicAuth, (NetworkCredential)creds);
+ creds = cache;
+ }
+
+ using (HttpClientHandler handler = CreateHttpClientHandler())
+ using (HttpClient client = CreateHttpClient(handler))
+ {
+ handler.Proxy = new WebProxy(proxyServer.Uri) { Credentials = creds };
+
+ using (HttpResponseMessage response = await client.GetAsync(Configuration.Http.RemoteEchoServer))
+ {
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ TestHelper.VerifyResponseBody(
+ await response.Content.ReadAsStringAsync(),
+ response.Content.Headers.ContentMD5,
+ false,
+ null);
+
+ if (options.AuthenticationSchemes != AuthenticationSchemes.None)
+ {
+ NetworkCredential nc = creds?.GetCredential(proxyServer.Uri, BasicAuth);
+ Assert.NotNull(nc);
+ string expectedAuth =
+ string.IsNullOrEmpty(nc.Domain) ? $"{nc.UserName}:{nc.Password}" :
+ $"{nc.Domain}\\{nc.UserName}:{nc.Password}";
+ _output.WriteLine($"expectedAuth={expectedAuth}");
+ string expectedAuthHash = Convert.ToBase64String(Encoding.UTF8.GetBytes(expectedAuth));
+
+ // Check last request to proxy server. Handlers that don't use
+ // pre-auth for proxy will make 2 requests.
+ int requestCount = proxyServer.Requests.Count;
+ _output.WriteLine($"proxyServer.Requests.Count={requestCount}");
+ Assert.Equal(BasicAuth, proxyServer.Requests[requestCount - 1].AuthorizationHeaderValueScheme);
+ Assert.Equal(expectedAuthHash, proxyServer.Requests[requestCount - 1].AuthorizationHeaderValueToken);
+ }
+ }
+ }
+ }
+ }
+
+ [OuterLoop("Uses external server")]
+ [Theory]
+ [MemberData(nameof(BypassedProxies))]
+ public async Task Proxy_BypassTrue_GetRequestDoesntGoesThroughCustomProxy(IWebProxy proxy)
+ {
+ HttpClientHandler handler = CreateHttpClientHandler();
+ handler.Proxy = proxy;
+ using (HttpClient client = CreateHttpClient(handler))
+ using (HttpResponseMessage response = await client.GetAsync(Configuration.Http.RemoteEchoServer))
+ {
+ TestHelper.VerifyResponseBody(
+ await response.Content.ReadAsStringAsync(),
+ response.Content.Headers.ContentMD5,
+ false,
+ null);
+ }
+ }
+
+ [OuterLoop("Uses external server")]
+ [Fact]
+ public async Task Proxy_HaveNoCredsAndUseAuthenticatedCustomProxy_ProxyAuthenticationRequiredStatusCode()
+ {
+ var options = new LoopbackProxyServer.Options { AuthenticationSchemes = AuthenticationSchemes.Basic };
+ using (LoopbackProxyServer proxyServer = LoopbackProxyServer.Create(options))
+ {
+ HttpClientHandler handler = CreateHttpClientHandler();
+ handler.Proxy = new WebProxy(proxyServer.Uri);
+ using (HttpClient client = CreateHttpClient(handler))
+ using (HttpResponseMessage response = await client.GetAsync(Configuration.Http.RemoteEchoServer))
+ {
+ Assert.Equal(HttpStatusCode.ProxyAuthenticationRequired, response.StatusCode);
+ }
+ }
+ }
+
+ [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindowsSubsystemForLinux))] // [ActiveIssue("https://github.com/dotnet/corefx/issues/11057")]
+ public async Task Proxy_SslProxyUnsupported_Throws()
+ {
+ using (HttpClientHandler handler = CreateHttpClientHandler())
+ using (HttpClient client = CreateHttpClient(handler))
+ {
+ handler.Proxy = new WebProxy("https://" + Guid.NewGuid().ToString("N"));
+
+ Type expectedType = IsWinHttpHandler ? typeof(HttpRequestException) : typeof(NotSupportedException);
+
+ await Assert.ThrowsAsync(expectedType, () => client.GetAsync("http://" + Guid.NewGuid().ToString("N")));
+ }
+ }
+
+ [OuterLoop("Uses external server")]
+ [Fact]
+ public async Task Proxy_SendSecureRequestThruProxy_ConnectTunnelUsed()
+ {
+ using (LoopbackProxyServer proxyServer = LoopbackProxyServer.Create())
+ {
+ HttpClientHandler handler = CreateHttpClientHandler();
+ handler.Proxy = new WebProxy(proxyServer.Uri);
+ using (HttpClient client = CreateHttpClient(handler))
+ using (HttpResponseMessage response = await client.GetAsync(Configuration.Http.SecureRemoteEchoServer))
+ {
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ _output.WriteLine($"Proxy request line: {proxyServer.Requests[0].RequestLine}");
+ Assert.Contains("CONNECT", proxyServer.Requests[0].RequestLine);
+ }
+ }
+ }
+
+ [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindowsNanoServer))]
+ public async Task ProxyAuth_Digest_Succeeds()
+ {
+ const string expectedUsername = "testusername";
+ const string expectedPassword = "testpassword";
+ const string authHeader = "Proxy-Authenticate: Digest realm=\"NetCore\", nonce=\"PwOnWgAAAAAAjnbW438AAJSQi1kAAAAA\", qop=\"auth\", stale=false\r\n";
+ LoopbackServer.Options options = new LoopbackServer.Options { IsProxy = true, Username = expectedUsername, Password = expectedPassword };
+ var proxyCreds = new NetworkCredential(expectedUsername, expectedPassword);
+
+ await LoopbackServer.CreateServerAsync(async (proxyServer, proxyUrl) =>
+ {
+ using (HttpClientHandler handler = CreateHttpClientHandler())
+ using (HttpClient client = CreateHttpClient(handler))
+ {
+ handler.Proxy = new WebProxy(proxyUrl) { Credentials = proxyCreds };
+
+ // URL does not matter. We will get response from "proxy" code below.
+ Task<HttpResponseMessage> clientTask = client.GetAsync($"http://notarealserver.com/");
+
+ // Send Digest challenge.
+ Task<List<string>> serverTask = proxyServer.AcceptConnectionSendResponseAndCloseAsync(HttpStatusCode.ProxyAuthenticationRequired, authHeader);
+ if (clientTask == await Task.WhenAny(clientTask, serverTask).TimeoutAfter(TestHelper.PassingTestTimeoutMilliseconds))
+ {
+ // Client task shouldn't have completed successfully; propagate failure.
+ Assert.NotEqual(TaskStatus.RanToCompletion, clientTask.Status);
+ await clientTask;
+ }
+
+ // Verify user & password.
+ serverTask = proxyServer.AcceptConnectionPerformAuthenticationAndCloseAsync("");
+ await TaskTimeoutExtensions.WhenAllOrAnyFailed(new Task[] { clientTask, serverTask }, TestHelper.PassingTestTimeoutMilliseconds);
+
+ Assert.Equal(HttpStatusCode.OK, clientTask.Result.StatusCode);
+ }
+ }, options);
+
+ }
+
+ [Fact]
+ [PlatformSpecific(TestPlatforms.Windows)]
+ public async Task MultiProxy_PAC_Failover_Succeeds()
+ {
+ if (IsWinHttpHandler)
+ {
+ // PAC-based failover is only supported on Windows/SocketsHttpHandler
+ return;
+ }
+
+ // Create our failing proxy server.
+ // Bind a port to reserve it, but don't start listening yet. The first Connect() should fail and cause a fail-over.
+ using Socket failingProxyServer = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
+ failingProxyServer.Bind(new IPEndPoint(IPAddress.Loopback, 0));
+ var failingEndPoint = (IPEndPoint)failingProxyServer.LocalEndPoint;
+
+ using LoopbackProxyServer succeedingProxyServer = LoopbackProxyServer.Create();
+ string proxyConfigString = $"{failingEndPoint.Address}:{failingEndPoint.Port} {succeedingProxyServer.Uri.Host}:{succeedingProxyServer.Uri.Port}";
+
+ // Create a WinInetProxyHelper and override its values with our own.
+ object winInetProxyHelper = Activator.CreateInstance(typeof(HttpClient).Assembly.GetType("System.Net.Http.WinInetProxyHelper", true), true);
+ winInetProxyHelper.GetType().GetField("_autoConfigUrl", Reflection.BindingFlags.Instance | Reflection.BindingFlags.NonPublic).SetValue(winInetProxyHelper, null);
+ winInetProxyHelper.GetType().GetField("_autoDetect", Reflection.BindingFlags.Instance | Reflection.BindingFlags.NonPublic).SetValue(winInetProxyHelper, false);
+ winInetProxyHelper.GetType().GetField("_proxy", Reflection.BindingFlags.Instance | Reflection.BindingFlags.NonPublic).SetValue(winInetProxyHelper, proxyConfigString);
+ winInetProxyHelper.GetType().GetField("_proxyBypass", Reflection.BindingFlags.Instance | Reflection.BindingFlags.NonPublic).SetValue(winInetProxyHelper, null);
+
+ // Create a HttpWindowsProxy with our custom WinInetProxyHelper.
+ IWebProxy httpWindowsProxy = (IWebProxy)Activator.CreateInstance(typeof(HttpClient).Assembly.GetType("System.Net.Http.HttpWindowsProxy", true), Reflection.BindingFlags.NonPublic | Reflection.BindingFlags.Instance, null, new[] { winInetProxyHelper, null }, null);
+
+ Task<bool> nextFailedConnection = null;
+
+ // Run a request with that proxy.
+ Task requestTask = LoopbackServerFactory.CreateClientAndServerAsync(
+ async uri =>
+ {
+ using HttpClientHandler handler = CreateHttpClientHandler();
+ using HttpClient client = CreateHttpClient(handler);
+ handler.Proxy = httpWindowsProxy;
+
+ // First request is expected to hit the failing proxy server, then failover to the succeeding proxy server.
+ Assert.Equal("foo", await client.GetStringAsync(uri));
+
+ // Second request should start directly at the succeeding proxy server.
+ // So, start listening on our failing proxy server to catch if it tries to connect.
+ failingProxyServer.Listen(1);
+ nextFailedConnection = WaitForNextFailedConnection();
+ Assert.Equal("bar", await client.GetStringAsync(uri));
+ },
+ async server =>
+ {
+ await server.HandleRequestAsync(statusCode: HttpStatusCode.OK, content: "foo");
+ await server.HandleRequestAsync(statusCode: HttpStatusCode.OK, content: "bar");
+ });
+
+ // Wait for request to finish.
+ await requestTask;
+
+ // Triggers WaitForNextFailedConnection to stop, and then check
+ // to ensure we haven't got any further requests against it.
+ failingProxyServer.Dispose();
+ Assert.False(await nextFailedConnection);
+
+ Assert.Equal(2, succeedingProxyServer.Requests.Count);
+
+ async Task<bool> WaitForNextFailedConnection()
+ {
+ try
+ {
+ (await failingProxyServer.AcceptAsync()).Dispose();
+ return true;
+ }
+ catch (SocketException ex) when (ex.SocketErrorCode == SocketError.OperationAborted)
+ {
+ // Dispose() of the loopback server will cause AcceptAsync() in EstablishConnectionAsync() to abort.
+ return false;
+ }
+ }
+ }
+
+ public static IEnumerable<object[]> BypassedProxies()
+ {
+ yield return new object[] { null };
+ yield return new object[] { new UseSpecifiedUriWebProxy(new Uri($"http://{Guid.NewGuid().ToString().Substring(0, 15)}:12345"), bypass: true) };
+ }
+
+ public static IEnumerable<object[]> CredentialsForProxy()
+ {
+ yield return new object[] { null, false };
+ foreach (bool wrapCredsInCache in new[] { true, false })
+ {
+ yield return new object[] { new NetworkCredential("username", "password"), wrapCredsInCache };
+ yield return new object[] { new NetworkCredential("username", "password", "domain"), wrapCredsInCache };
+ }
+ }
+
+ private sealed class TrackDisposalProxy : IWebProxy, IDisposable
+ {
+ public bool Disposed;
+ public bool ProxyUsed;
+
+ public void Dispose() => Disposed = true;
+ public Uri GetProxy(Uri destination)
+ {
+ ProxyUsed = true;
+ return null;
+ }
+ public bool IsBypassed(Uri host)
+ {
+ ProxyUsed = true;
+ return true;
+ }
+ public ICredentials Credentials { get => null; set { } }
+ }
+ }
+}
--- /dev/null
+// 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.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using System.Net.Security;
+using System.Net.Test.Common;
+using System.Runtime.InteropServices;
+using System.Security.Authentication.ExtendedProtection;
+using System.Security.Cryptography.X509Certificates;
+using System.Threading.Tasks;
+using Microsoft.DotNet.RemoteExecutor;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace System.Net.Http.Functional.Tests
+{
+ using Configuration = System.Net.Test.Common.Configuration;
+
+#if WINHTTPHANDLER_TEST
+ using HttpClientHandler = System.Net.Http.WinHttpClientHandler;
+#endif
+
+ public abstract partial class HttpClientHandler_ServerCertificates_Test : HttpClientHandlerTestBase
+ {
+ private static bool ClientSupportsDHECipherSuites => (!PlatformDetection.IsWindows || PlatformDetection.IsWindows10Version1607OrGreater);
+
+ public HttpClientHandler_ServerCertificates_Test(ITestOutputHelper output) : base(output) { }
+
+ [Fact]
+ public void Ctor_ExpectedDefaultValues()
+ {
+ using (HttpClientHandler handler = CreateHttpClientHandler())
+ {
+ Assert.Null(handler.ServerCertificateCustomValidationCallback);
+ Assert.False(handler.CheckCertificateRevocationList);
+ }
+ }
+
+ [Fact]
+ public void ServerCertificateCustomValidationCallback_SetGet_Roundtrips()
+ {
+ using (HttpClientHandler handler = CreateHttpClientHandler())
+ {
+ Assert.Null(handler.ServerCertificateCustomValidationCallback);
+
+ Func<HttpRequestMessage, X509Certificate2, X509Chain, SslPolicyErrors, bool> callback1 = (req, cert, chain, policy) => throw new NotImplementedException("callback1");
+ Func<HttpRequestMessage, X509Certificate2, X509Chain, SslPolicyErrors, bool> callback2 = (req, cert, chain, policy) => throw new NotImplementedException("callback2");
+
+ handler.ServerCertificateCustomValidationCallback = callback1;
+ Assert.Same(callback1, handler.ServerCertificateCustomValidationCallback);
+
+ handler.ServerCertificateCustomValidationCallback = callback2;
+ Assert.Same(callback2, handler.ServerCertificateCustomValidationCallback);
+
+ handler.ServerCertificateCustomValidationCallback = null;
+ Assert.Null(handler.ServerCertificateCustomValidationCallback);
+ }
+ }
+
+ [OuterLoop("Uses external server")]
+ [Fact]
+ public async Task NoCallback_ValidCertificate_SuccessAndExpectedPropertyBehavior()
+ {
+ HttpClientHandler handler = CreateHttpClientHandler();
+ using (HttpClient client = CreateHttpClient(handler))
+ {
+ using (HttpResponseMessage response = await client.GetAsync(Configuration.Http.SecureRemoteEchoServer))
+ {
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ }
+
+ Assert.Throws<InvalidOperationException>(() => handler.ServerCertificateCustomValidationCallback = null);
+ Assert.Throws<InvalidOperationException>(() => handler.CheckCertificateRevocationList = false);
+ }
+ }
+
+ [OuterLoop("Uses external server")]
+ [Fact]
+ public async Task UseCallback_HaveCredsAndUseAuthenticatedCustomProxyAndPostToSecureServer_Success()
+ {
+ if (IsWinHttpHandler && PlatformDetection.IsWindows7)
+ {
+ // Issue https://github.com/dotnet/corefx/issues/27612
+ return;
+ }
+
+ var options = new LoopbackProxyServer.Options
+ { AuthenticationSchemes = AuthenticationSchemes.Basic,
+ ConnectionCloseAfter407 = true
+ };
+ using (LoopbackProxyServer proxyServer = LoopbackProxyServer.Create(options))
+ {
+ HttpClientHandler handler = CreateHttpClientHandler();
+ handler.ServerCertificateCustomValidationCallback = TestHelper.AllowAllCertificates;
+ handler.Proxy = new WebProxy(proxyServer.Uri)
+ {
+ Credentials = new NetworkCredential("rightusername", "rightpassword")
+ };
+
+ const string content = "This is a test";
+
+ using (HttpClient client = CreateHttpClient(handler))
+ using (HttpResponseMessage response = await client.PostAsync(
+ Configuration.Http.SecureRemoteEchoServer,
+ new StringContent(content)))
+ {
+ string responseContent = await response.Content.ReadAsStringAsync();
+
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ TestHelper.VerifyResponseBody(
+ responseContent,
+ response.Content.Headers.ContentMD5,
+ false,
+ content);
+ }
+ }
+ }
+
+ [OuterLoop("Uses external server")]
+ [Fact]
+ public async Task UseCallback_HaveNoCredsAndUseAuthenticatedCustomProxyAndPostToSecureServer_ProxyAuthenticationRequiredStatusCode()
+ {
+ var options = new LoopbackProxyServer.Options
+ { AuthenticationSchemes = AuthenticationSchemes.Basic,
+ ConnectionCloseAfter407 = true
+ };
+ using (LoopbackProxyServer proxyServer = LoopbackProxyServer.Create(options))
+ {
+ HttpClientHandler handler = CreateHttpClientHandler();
+ handler.Proxy = new WebProxy(proxyServer.Uri);
+ handler.ServerCertificateCustomValidationCallback = TestHelper.AllowAllCertificates;
+ using (HttpClient client = CreateHttpClient(handler))
+ using (HttpResponseMessage response = await client.PostAsync(
+ Configuration.Http.SecureRemoteEchoServer,
+ new StringContent("This is a test")))
+ {
+ Assert.Equal(HttpStatusCode.ProxyAuthenticationRequired, response.StatusCode);
+ }
+ }
+ }
+
+ [OuterLoop("Uses external server")]
+ [Fact]
+ public async Task UseCallback_NotSecureConnection_CallbackNotCalled()
+ {
+ HttpClientHandler handler = CreateHttpClientHandler();
+ using (HttpClient client = CreateHttpClient(handler))
+ {
+ bool callbackCalled = false;
+ handler.ServerCertificateCustomValidationCallback = delegate { callbackCalled = true; return true; };
+
+ using (HttpResponseMessage response = await client.GetAsync(Configuration.Http.RemoteEchoServer))
+ {
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ }
+
+ Assert.False(callbackCalled);
+ }
+ }
+
+ public static IEnumerable<object[]> UseCallback_ValidCertificate_ExpectedValuesDuringCallback_Urls()
+ {
+ foreach (Configuration.Http.RemoteServer remoteServer in Configuration.Http.RemoteServers)
+ {
+ if (remoteServer.IsSecure)
+ {
+ foreach (bool checkRevocation in new[] { true, false })
+ {
+ yield return new object[] {
+ remoteServer,
+ remoteServer.EchoUri,
+ checkRevocation };
+ yield return new object[] {
+ remoteServer,
+ remoteServer.RedirectUriForDestinationUri(
+ statusCode:302,
+ remoteServer.EchoUri,
+ hops:1),
+ checkRevocation };
+ }
+ }
+ }
+ }
+
+ [OuterLoop("Uses external server")]
+ [Theory]
+ [MemberData(nameof(UseCallback_ValidCertificate_ExpectedValuesDuringCallback_Urls))]
+ public async Task UseCallback_ValidCertificate_ExpectedValuesDuringCallback(Configuration.Http.RemoteServer remoteServer, Uri url, bool checkRevocation)
+ {
+ HttpClientHandler handler = CreateHttpClientHandler();
+ using (HttpClient client = CreateHttpClientForRemoteServer(remoteServer, handler))
+ {
+ bool callbackCalled = false;
+ handler.CheckCertificateRevocationList = checkRevocation;
+ handler.ServerCertificateCustomValidationCallback = (request, cert, chain, errors) => {
+ callbackCalled = true;
+ Assert.NotNull(request);
+
+ X509ChainStatusFlags flags = chain.ChainStatus.Aggregate(X509ChainStatusFlags.NoError, (cur, status) => cur | status.Status);
+ bool ignoreErrors = // https://github.com/dotnet/corefx/issues/21922#issuecomment-315555237
+ RuntimeInformation.IsOSPlatform(OSPlatform.OSX) &&
+ checkRevocation &&
+ errors == SslPolicyErrors.RemoteCertificateChainErrors &&
+ flags == X509ChainStatusFlags.RevocationStatusUnknown;
+ Assert.True(ignoreErrors || errors == SslPolicyErrors.None, $"Expected {SslPolicyErrors.None}, got {errors} with chain status {flags}");
+
+ Assert.True(chain.ChainElements.Count > 0);
+ Assert.NotEmpty(cert.Subject);
+
+ // UWP always uses CheckCertificateRevocationList=true regardless of setting the property and
+ // the getter always returns true. So, for this next Assert, it is better to get the property
+ // value back from the handler instead of using the parameter value of the test.
+ Assert.Equal(
+ handler.CheckCertificateRevocationList ? X509RevocationMode.Online : X509RevocationMode.NoCheck,
+ chain.ChainPolicy.RevocationMode);
+ return true;
+ };
+
+ using (HttpResponseMessage response = await client.GetAsync(url))
+ {
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ }
+
+ Assert.True(callbackCalled);
+ }
+ }
+
+ [OuterLoop("Uses external server")]
+ [Fact]
+ public async Task UseCallback_CallbackReturnsFailure_ThrowsException()
+ {
+ HttpClientHandler handler = CreateHttpClientHandler();
+ using (HttpClient client = CreateHttpClient(handler))
+ {
+ handler.ServerCertificateCustomValidationCallback = delegate { return false; };
+ await Assert.ThrowsAsync<HttpRequestException>(() => client.GetAsync(Configuration.Http.SecureRemoteEchoServer));
+ }
+ }
+
+ [OuterLoop("Uses external server")]
+ [Fact]
+ public async Task UseCallback_CallbackThrowsException_ExceptionPropagatesAsBaseException()
+ {
+ HttpClientHandler handler = CreateHttpClientHandler();
+ using (HttpClient client = CreateHttpClient(handler))
+ {
+ var e = new DivideByZeroException();
+ handler.ServerCertificateCustomValidationCallback = delegate { throw e; };
+
+ HttpRequestException ex = await Assert.ThrowsAsync<HttpRequestException>(() => client.GetAsync(Configuration.Http.SecureRemoteEchoServer));
+ Assert.Same(e, ex.GetBaseException());
+ }
+ }
+
+ public static readonly object[][] CertificateValidationServers =
+ {
+ new object[] { Configuration.Http.ExpiredCertRemoteServer },
+ new object[] { Configuration.Http.SelfSignedCertRemoteServer },
+ new object[] { Configuration.Http.WrongHostNameCertRemoteServer },
+ };
+
+ [OuterLoop("Uses external server")]
+ [ConditionalTheory(nameof(ClientSupportsDHECipherSuites))]
+ [MemberData(nameof(CertificateValidationServers))]
+ public async Task NoCallback_BadCertificate_ThrowsException(string url)
+ {
+ using (HttpClient client = CreateHttpClient())
+ {
+ await Assert.ThrowsAsync<HttpRequestException>(() => client.GetAsync(url));
+ }
+ }
+
+ [OuterLoop("Uses external server")]
+ [ConditionalFact(nameof(ClientSupportsDHECipherSuites))]
+ public async Task NoCallback_RevokedCertificate_NoRevocationChecking_Succeeds()
+ {
+ using (HttpClient client = CreateHttpClient())
+ using (HttpResponseMessage response = await client.GetAsync(Configuration.Http.RevokedCertRemoteServer))
+ {
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ }
+ }
+
+ [OuterLoop("Uses external server")]
+ [Fact]
+ public async Task NoCallback_RevokedCertificate_RevocationChecking_Fails()
+ {
+ HttpClientHandler handler = CreateHttpClientHandler();
+ handler.CheckCertificateRevocationList = true;
+ using (HttpClient client = CreateHttpClient(handler))
+ {
+ await Assert.ThrowsAsync<HttpRequestException>(() => client.GetAsync(Configuration.Http.RevokedCertRemoteServer));
+ }
+ }
+
+ public static readonly object[][] CertificateValidationServersAndExpectedPolicies =
+ {
+ new object[] { Configuration.Http.ExpiredCertRemoteServer, SslPolicyErrors.RemoteCertificateChainErrors },
+ new object[] { Configuration.Http.SelfSignedCertRemoteServer, SslPolicyErrors.RemoteCertificateChainErrors },
+ new object[] { Configuration.Http.WrongHostNameCertRemoteServer , SslPolicyErrors.RemoteCertificateNameMismatch},
+ };
+
+ private async Task UseCallback_BadCertificate_ExpectedPolicyErrors_Helper(string url, string useHttp2String, SslPolicyErrors expectedErrors)
+ {
+ HttpClientHandler handler = CreateHttpClientHandler(useHttp2String);
+ using (HttpClient client = CreateHttpClient(handler, useHttp2String))
+ {
+ bool callbackCalled = false;
+
+ handler.ServerCertificateCustomValidationCallback = (request, cert, chain, errors) =>
+ {
+ callbackCalled = true;
+ Assert.NotNull(request);
+ Assert.NotNull(cert);
+ Assert.NotNull(chain);
+ Assert.Equal(expectedErrors, errors);
+ return true;
+ };
+
+ using (HttpResponseMessage response = await client.GetAsync(url))
+ {
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ }
+
+ Assert.True(callbackCalled);
+ }
+ }
+
+ [OuterLoop("Uses external server")]
+ [Theory]
+ [MemberData(nameof(CertificateValidationServersAndExpectedPolicies))]
+ public async Task UseCallback_BadCertificate_ExpectedPolicyErrors(string url, SslPolicyErrors expectedErrors)
+ {
+ const int SEC_E_BUFFER_TOO_SMALL = unchecked((int)0x80090321);
+
+ if (!ClientSupportsDHECipherSuites)
+ {
+ return;
+ }
+
+ try
+ {
+ await UseCallback_BadCertificate_ExpectedPolicyErrors_Helper(url, UseHttp2.ToString(), expectedErrors);
+ }
+ catch (HttpRequestException e) when (e.InnerException?.GetType().Name == "WinHttpException" &&
+ e.InnerException.HResult == SEC_E_BUFFER_TOO_SMALL &&
+ !PlatformDetection.IsWindows10Version1607OrGreater)
+ {
+ // Testing on old Windows versions can hit https://github.com/dotnet/corefx/issues/7812
+ // Ignore SEC_E_BUFFER_TOO_SMALL error on such cases.
+ }
+ }
+
+ [OuterLoop("Uses external server")]
+ [PlatformSpecific(TestPlatforms.Windows)] // CopyToAsync(Stream, TransportContext) isn't used on unix
+ [Fact]
+ public async Task PostAsync_Post_ChannelBinding_ConfiguredCorrectly()
+ {
+ var content = new ChannelBindingAwareContent("Test contest");
+ using (HttpClient client = CreateHttpClient())
+ using (HttpResponseMessage response = await client.PostAsync(Configuration.Http.SecureRemoteEchoServer, content))
+ {
+ // Validate status.
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+
+ // Validate the ChannelBinding object exists.
+ ChannelBinding channelBinding = content.ChannelBinding;
+ Assert.NotNull(channelBinding);
+
+ // Validate the ChannelBinding's validity.
+ Assert.False(channelBinding.IsInvalid, "Expected valid binding");
+ Assert.NotEqual(IntPtr.Zero, channelBinding.DangerousGetHandle());
+
+ // Validate the ChannelBinding's description.
+ string channelBindingDescription = channelBinding.ToString();
+ Assert.NotNull(channelBindingDescription);
+ Assert.NotEmpty(channelBindingDescription);
+ Assert.True((channelBindingDescription.Length + 1) % 3 == 0, $"Unexpected length {channelBindingDescription.Length}");
+ for (int i = 0; i < channelBindingDescription.Length; i++)
+ {
+ char c = channelBindingDescription[i];
+ if (i % 3 == 2)
+ {
+ Assert.Equal(' ', c);
+ }
+ else
+ {
+ Assert.True((c >= '0' && c <= '9') || (c >= 'A' && c <= 'F'), $"Expected hex, got {c}");
+ }
+ }
+ }
+ }
+
+ [Fact]
+ [PlatformSpecific(~TestPlatforms.Linux)]
+ public void HttpClientUsesSslCertEnvironmentVariables()
+ {
+ // We set SSL_CERT_DIR and SSL_CERT_FILE to empty locations.
+ // The HttpClient should fail to validate the server certificate.
+ var psi = new ProcessStartInfo();
+ string sslCertDir = GetTestFilePath();
+ Directory.CreateDirectory(sslCertDir);
+ psi.Environment.Add("SSL_CERT_DIR", sslCertDir);
+
+ string sslCertFile = GetTestFilePath();
+ File.WriteAllText(sslCertFile, "");
+ psi.Environment.Add("SSL_CERT_FILE", sslCertFile);
+
+ RemoteExecutor.Invoke(async (useHttp2String) =>
+ {
+ const string Url = "https://www.microsoft.com";
+
+ using (HttpClient client = CreateHttpClient(useHttp2String))
+ {
+ await Assert.ThrowsAsync<HttpRequestException>(() => client.GetAsync(Url));
+ }
+ }, UseHttp2.ToString(), new RemoteInvokeOptions { StartInfo = psi }).Dispose();
+ }
+ }
+}
--- /dev/null
+// 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.Collections.Generic;
+using System.IO;
+using System.Net.Security;
+using System.Net.Test.Common;
+using System.Runtime.InteropServices;
+using System.Security.Authentication;
+using System.Threading.Tasks;
+using Microsoft.DotNet.XUnitExtensions;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace System.Net.Http.Functional.Tests
+{
+ using Configuration = System.Net.Test.Common.Configuration;
+
+#if WINHTTPHANDLER_TEST
+ using HttpClientHandler = System.Net.Http.WinHttpClientHandler;
+#endif
+
+ public abstract partial class HttpClientHandler_SslProtocols_Test : HttpClientHandlerTestBase
+ {
+ public HttpClientHandler_SslProtocols_Test(ITestOutputHelper output) : base(output) { }
+
+ [Fact]
+ public void DefaultProtocols_MatchesExpected()
+ {
+ using (HttpClientHandler handler = CreateHttpClientHandler())
+ {
+ Assert.Equal(SslProtocols.None, handler.SslProtocols);
+ }
+ }
+
+ [Theory]
+ [InlineData(SslProtocols.None)]
+ [InlineData(SslProtocols.Tls)]
+ [InlineData(SslProtocols.Tls11)]
+ [InlineData(SslProtocols.Tls12)]
+ [InlineData(SslProtocols.Tls | SslProtocols.Tls11)]
+ [InlineData(SslProtocols.Tls11 | SslProtocols.Tls12)]
+ [InlineData(SslProtocols.Tls | SslProtocols.Tls12)]
+ [InlineData(SslProtocols.Tls | SslProtocols.Tls11 | SslProtocols.Tls12)]
+ [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)]
+ public void SetGetProtocols_Roundtrips(SslProtocols protocols)
+ {
+ using (HttpClientHandler handler = CreateHttpClientHandler())
+ {
+ handler.SslProtocols = protocols;
+ Assert.Equal(protocols, handler.SslProtocols);
+ }
+ }
+
+ [Fact]
+ public async Task SetProtocols_AfterRequest_ThrowsException()
+ {
+ using (HttpClientHandler handler = CreateHttpClientHandler())
+ using (HttpClient client = CreateHttpClient(handler))
+ {
+ handler.ServerCertificateCustomValidationCallback = TestHelper.AllowAllCertificates;
+ await LoopbackServer.CreateServerAsync(async (server, url) =>
+ {
+ await TestHelper.WhenAllCompletedOrAnyFailed(
+ server.AcceptConnectionSendResponseAndCloseAsync(),
+ client.GetAsync(url));
+ });
+ Assert.Throws<InvalidOperationException>(() => handler.SslProtocols = SslProtocols.Tls12);
+ }
+ }
+
+
+ public static IEnumerable<object[]> GetAsync_AllowedSSLVersion_Succeeds_MemberData()
+ {
+ // These protocols are all enabled by default, so we can connect with them both when
+ // explicitly specifying it in the client and when not.
+ foreach (SslProtocols protocol in new[] { SslProtocols.Tls, SslProtocols.Tls11, SslProtocols.Tls12 })
+ {
+ yield return new object[] { protocol, false };
+ yield return new object[] { protocol, true };
+ }
+
+ // These protocols are disabled by default, so we can only connect with them explicitly.
+ // On certain platforms these are completely disabled and cannot be used at all.
+#pragma warning disable 0618
+ if (PlatformDetection.SupportsSsl3)
+ {
+ yield return new object[] { SslProtocols.Ssl3, true };
+ }
+ if (PlatformDetection.IsWindows && !PlatformDetection.IsWindows10Version1607OrGreater)
+ {
+ yield return new object[] { SslProtocols.Ssl2, true };
+ }
+#pragma warning restore 0618
+ // These protocols are new, and might not be enabled everywhere yet
+ if (PlatformDetection.IsUbuntu1810OrHigher)
+ {
+ yield return new object[] { SslProtocols.Tls13, false };
+ yield return new object[] { SslProtocols.Tls13, true };
+ }
+ }
+
+ [Theory]
+ [MemberData(nameof(GetAsync_AllowedSSLVersion_Succeeds_MemberData))]
+ public async Task GetAsync_AllowedSSLVersion_Succeeds(SslProtocols acceptedProtocol, bool requestOnlyThisProtocol)
+ {
+ using (HttpClientHandler handler = CreateHttpClientHandler())
+ using (HttpClient client = CreateHttpClient(handler))
+ {
+ handler.ServerCertificateCustomValidationCallback = TestHelper.AllowAllCertificates;
+
+ if (requestOnlyThisProtocol)
+ {
+ handler.SslProtocols = acceptedProtocol;
+ }
+ else
+ {
+ // Explicitly setting protocols clears implementation default
+ // restrictions on minimum TLS/SSL version
+ // We currently know that some platforms like Debian 10 OpenSSL
+ // will by default block < TLS 1.2
+ handler.SslProtocols = SslProtocols.Tls | SslProtocols.Tls11 | SslProtocols.Tls12 | SslProtocols.Tls13;
+ }
+
+ var options = new LoopbackServer.Options { UseSsl = true, SslProtocols = acceptedProtocol };
+ await LoopbackServer.CreateServerAsync(async (server, url) =>
+ {
+ await TestHelper.WhenAllCompletedOrAnyFailed(
+ server.AcceptConnectionSendResponseAndCloseAsync(),
+ client.GetAsync(url));
+ }, options);
+ }
+ }
+
+ public static IEnumerable<object[]> SupportedSSLVersionServers()
+ {
+#pragma warning disable 0618 // SSL2/3 are deprecated
+ if (PlatformDetection.IsWindows ||
+ PlatformDetection.IsOSX ||
+ (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) && PlatformDetection.OpenSslVersion < new Version(1, 0, 2) && !PlatformDetection.IsDebian))
+ {
+ yield return new object[] { SslProtocols.Ssl3, Configuration.Http.SSLv3RemoteServer };
+ }
+#pragma warning restore 0618
+ yield return new object[] { SslProtocols.Tls, Configuration.Http.TLSv10RemoteServer };
+ yield return new object[] { SslProtocols.Tls11, Configuration.Http.TLSv11RemoteServer };
+ yield return new object[] { SslProtocols.Tls12, Configuration.Http.TLSv12RemoteServer };
+ }
+
+ // We have tests that validate with SslStream, but that's limited by what the current OS supports.
+ // This tests provides additional validation against an external server.
+ [ActiveIssue("https://github.com/dotnet/corefx/issues/26186")]
+ [OuterLoop("Avoid www.ssllabs.com dependency in innerloop.")]
+ [Theory]
+ [MemberData(nameof(SupportedSSLVersionServers))]
+ public async Task GetAsync_SupportedSSLVersion_Succeeds(SslProtocols sslProtocols, string url)
+ {
+ using (HttpClientHandler handler = CreateHttpClientHandler())
+ {
+ handler.SslProtocols = sslProtocols;
+ using (HttpClient client = CreateHttpClient(handler))
+ {
+ (await RemoteServerQuery.Run(() => client.GetAsync(url), remoteServerExceptionWrapper, url)).Dispose();
+ }
+ }
+ }
+
+ public Func<Exception, bool> remoteServerExceptionWrapper = (exception) =>
+ {
+ Type exceptionType = exception.GetType();
+ if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
+ {
+ // On linux, taskcanceledexception is thrown.
+ return exceptionType.Equals(typeof(TaskCanceledException));
+ }
+ else
+ {
+ // The internal exceptions return operation timed out.
+ return exceptionType.Equals(typeof(HttpRequestException)) && exception.InnerException.Message.Contains("timed out");
+ }
+ };
+
+ public static IEnumerable<object[]> NotSupportedSSLVersionServers()
+ {
+#pragma warning disable 0618
+ if (PlatformDetection.IsWindows10Version1607OrGreater)
+ {
+ yield return new object[] { SslProtocols.Ssl2, Configuration.Http.SSLv2RemoteServer };
+ }
+#pragma warning restore 0618
+ }
+
+ // We have tests that validate with SslStream, but that's limited by what the current OS supports.
+ // This tests provides additional validation against an external server.
+ [OuterLoop("Avoid www.ssllabs.com dependency in innerloop.")]
+ [Theory]
+ [MemberData(nameof(NotSupportedSSLVersionServers))]
+ public async Task GetAsync_UnsupportedSSLVersion_Throws(SslProtocols sslProtocols, string url)
+ {
+ using (HttpClientHandler handler = CreateHttpClientHandler())
+ using (HttpClient client = CreateHttpClient(handler))
+ {
+ handler.SslProtocols = sslProtocols;
+ await Assert.ThrowsAsync<HttpRequestException>(() => RemoteServerQuery.Run(() => client.GetAsync(url), remoteServerExceptionWrapper, url));
+ }
+ }
+
+ [Fact]
+ public async Task GetAsync_NoSpecifiedProtocol_DefaultsToTls12()
+ {
+ using (HttpClientHandler handler = CreateHttpClientHandler())
+ using (HttpClient client = CreateHttpClient(handler))
+ {
+ handler.ServerCertificateCustomValidationCallback = TestHelper.AllowAllCertificates;
+
+ var options = new LoopbackServer.Options { UseSsl = true, SslProtocols = SslProtocols.Tls12 };
+ await LoopbackServer.CreateServerAsync(async (server, url) =>
+ {
+ await TestHelper.WhenAllCompletedOrAnyFailed(
+ client.GetAsync(url),
+ server.AcceptConnectionAsync(async connection =>
+ {
+ Assert.Equal(SslProtocols.Tls12, Assert.IsType<SslStream>(connection.Stream).SslProtocol);
+ await connection.ReadRequestHeaderAndSendResponseAsync();
+ }));
+ }, options);
+ }
+ }
+
+ [Theory]
+#pragma warning disable 0618 // SSL2/3 are deprecated
+ [InlineData(SslProtocols.Ssl2, SslProtocols.Tls12)]
+ [InlineData(SslProtocols.Ssl3, SslProtocols.Tls12)]
+#pragma warning restore 0618
+ [InlineData(SslProtocols.Tls11, SslProtocols.Tls)]
+ [InlineData(SslProtocols.Tls11 | SslProtocols.Tls12, SslProtocols.Tls)] // Skip this on WinHttpHandler.
+ [InlineData(SslProtocols.Tls12, SslProtocols.Tls11)]
+ [InlineData(SslProtocols.Tls, SslProtocols.Tls12)]
+ public async Task GetAsync_AllowedClientSslVersionDiffersFromServer_ThrowsException(
+ SslProtocols allowedClientProtocols, SslProtocols acceptedServerProtocols)
+ {
+ if (IsWinHttpHandler &&
+ allowedClientProtocols == (SslProtocols.Tls11 | SslProtocols.Tls12) &&
+ acceptedServerProtocols == SslProtocols.Tls)
+ {
+ // Native WinHTTP sometimes uses multiple TCP connections to try other TLS protocols when
+ // getting TLS protocol failures as part of its TLS fallback algorithm. The loopback server
+ // doesn't expect this and stops listening for more connections. This causes unexpected test
+ // failures. See dotnet/corefx https://github.com/dotnet/corefx/issues/8538.
+ return;
+ }
+
+ using (HttpClientHandler handler = CreateHttpClientHandler())
+ using (HttpClient client = CreateHttpClient(handler))
+ {
+ handler.SslProtocols = allowedClientProtocols;
+ handler.ServerCertificateCustomValidationCallback = TestHelper.AllowAllCertificates;
+
+ var options = new LoopbackServer.Options { UseSsl = true, SslProtocols = acceptedServerProtocols };
+ await LoopbackServer.CreateServerAsync(async (server, url) =>
+ {
+ Task serverTask = server.AcceptConnectionSendResponseAndCloseAsync();
+ await Assert.ThrowsAsync<HttpRequestException>(() => client.GetAsync(url));
+ try
+ {
+ await serverTask;
+ }
+ catch (Exception e) when (e is IOException || e is AuthenticationException)
+ {
+ // Some SSL implementations simply close or reset connection after protocol mismatch.
+ // Newer OpenSSL sends Fatal Alert message before closing.
+ return;
+ }
+ // We expect negotiation to fail so one or the other expected exception should be thrown.
+ Assert.True(false, "Expected exception did not happen.");
+ }, options);
+ }
+ }
+ }
+}
--- /dev/null
+// 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;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Net.Http.Headers;
+using System.Net.Sockets;
+using System.Net.Test.Common;
+using System.Runtime.InteropServices;
+using System.Security.Authentication;
+using System.Security.Cryptography;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.DotNet.XUnitExtensions;
+using Microsoft.DotNet.RemoteExecutor;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace System.Net.Http.Functional.Tests
+{
+ using Configuration = System.Net.Test.Common.Configuration;
+
+#if WINHTTPHANDLER_TEST
+ using HttpClientHandler = System.Net.Http.WinHttpClientHandler;
+#endif
+
+ // Note: Disposing the HttpClient object automatically disposes the handler within. So, it is not necessary
+ // to separately Dispose (or have a 'using' statement) for the handler.
+ public abstract class HttpClientHandlerTest : HttpClientHandlerTestBase
+ {
+ private const string ExpectedContent = "Test content";
+ private const string Username = "testuser";
+ private const string Password = "password";
+ private const string HttpDefaultPort = "80";
+
+ private readonly NetworkCredential _credential = new NetworkCredential(Username, Password);
+
+ public static readonly object[][] Http2Servers = Configuration.Http.Http2Servers;
+ public static readonly object[][] Http2NoPushServers = Configuration.Http.Http2NoPushServers;
+
+ // Standard HTTP methods defined in RFC7231: http://tools.ietf.org/html/rfc7231#section-4.3
+ // "GET", "HEAD", "POST", "PUT", "DELETE", "OPTIONS", "TRACE"
+ public static readonly IEnumerable<object[]> HttpMethods =
+ GetMethods("GET", "HEAD", "POST", "PUT", "DELETE", "OPTIONS", "TRACE", "CUSTOM1");
+ public static readonly IEnumerable<object[]> HttpMethodsThatAllowContent =
+ GetMethods("GET", "POST", "PUT", "DELETE", "OPTIONS", "CUSTOM1");
+ public static readonly IEnumerable<object[]> HttpMethodsThatDontAllowContent =
+ GetMethods("HEAD", "TRACE");
+
+ private static bool IsWindows10Version1607OrGreater => PlatformDetection.IsWindows10Version1607OrGreater;
+
+ private static IEnumerable<object[]> GetMethods(params string[] methods)
+ {
+ foreach (string method in methods)
+ {
+ foreach (Uri serverUri in Configuration.Http.EchoServerList)
+ {
+ yield return new object[] { method, serverUri };
+ }
+ }
+ }
+
+ public HttpClientHandlerTest(ITestOutputHelper output) : base(output)
+ {
+ }
+
+ [Fact]
+ public void CookieContainer_SetNull_ThrowsArgumentNullException()
+ {
+ using (HttpClientHandler handler = CreateHttpClientHandler())
+ {
+ Assert.Throws<ArgumentNullException>(() => handler.CookieContainer = null);
+ }
+ }
+
+ [Fact]
+ public void Ctor_ExpectedDefaultPropertyValues_CommonPlatform()
+ {
+ using (HttpClientHandler handler = CreateHttpClientHandler())
+ {
+ Assert.Equal(DecompressionMethods.None, handler.AutomaticDecompression);
+ Assert.True(handler.AllowAutoRedirect);
+ Assert.Equal(ClientCertificateOption.Manual, handler.ClientCertificateOptions);
+ CookieContainer cookies = handler.CookieContainer;
+ Assert.NotNull(cookies);
+ Assert.Equal(0, cookies.Count);
+ Assert.Null(handler.Credentials);
+ Assert.Equal(50, handler.MaxAutomaticRedirections);
+ Assert.NotNull(handler.Properties);
+ Assert.Null(handler.Proxy);
+ Assert.True(handler.SupportsAutomaticDecompression);
+ Assert.True(handler.UseCookies);
+ Assert.False(handler.UseDefaultCredentials);
+ Assert.True(handler.UseProxy);
+ }
+ }
+
+ [Fact]
+ public void Ctor_ExpectedDefaultPropertyValues()
+ {
+ using (HttpClientHandler handler = CreateHttpClientHandler())
+ {
+ Assert.Equal(64, handler.MaxResponseHeadersLength);
+ Assert.False(handler.PreAuthenticate);
+ Assert.True(handler.SupportsProxy);
+ Assert.True(handler.SupportsRedirectConfiguration);
+
+ // Changes from .NET Framework.
+ Assert.False(handler.CheckCertificateRevocationList);
+ Assert.Equal(0, handler.MaxRequestContentBufferSize);
+ Assert.Equal(SslProtocols.None, handler.SslProtocols);
+ }
+ }
+
+ [Fact]
+ public void Credentials_SetGet_Roundtrips()
+ {
+ using (HttpClientHandler handler = CreateHttpClientHandler())
+ {
+ var creds = new NetworkCredential("username", "password", "domain");
+
+ handler.Credentials = null;
+ Assert.Null(handler.Credentials);
+
+ handler.Credentials = creds;
+ Assert.Same(creds, handler.Credentials);
+
+ handler.Credentials = CredentialCache.DefaultCredentials;
+ Assert.Same(CredentialCache.DefaultCredentials, handler.Credentials);
+ }
+ }
+
+ [Theory]
+ [InlineData(-1)]
+ [InlineData(0)]
+ public void MaxAutomaticRedirections_InvalidValue_Throws(int redirects)
+ {
+ using (HttpClientHandler handler = CreateHttpClientHandler())
+ {
+ Assert.Throws<ArgumentOutOfRangeException>(() => handler.MaxAutomaticRedirections = redirects);
+ }
+ }
+
+#if !WINHTTPHANDLER_TEST
+ [Theory]
+ [InlineData(-1)]
+ [InlineData((long)int.MaxValue + (long)1)]
+ public void MaxRequestContentBufferSize_SetInvalidValue_ThrowsArgumentOutOfRangeException(long value)
+ {
+ using (HttpClientHandler handler = CreateHttpClientHandler())
+ {
+ Assert.Throws<ArgumentOutOfRangeException>(() => handler.MaxRequestContentBufferSize = value);
+ }
+ }
+#endif
+
+ [OuterLoop("Uses external servers")]
+ [Theory]
+ [InlineData(false)]
+ [InlineData(true)]
+ [OuterLoop("Uses external servers")]
+ public async Task UseDefaultCredentials_SetToFalseAndServerNeedsAuth_StatusCodeUnauthorized(bool useProxy)
+ {
+ HttpClientHandler handler = CreateHttpClientHandler();
+ handler.UseProxy = useProxy;
+ handler.UseDefaultCredentials = false;
+ using (HttpClient client = CreateHttpClient(handler))
+ {
+ Uri uri = Configuration.Http.RemoteHttp11Server.NegotiateAuthUriForDefaultCreds;
+ _output.WriteLine("Uri: {0}", uri);
+ using (HttpResponseMessage response = await client.GetAsync(uri))
+ {
+ Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
+ }
+ }
+ }
+
+ [Fact]
+ public void Properties_Get_CountIsZero()
+ {
+ using (HttpClientHandler handler = CreateHttpClientHandler())
+ {
+ IDictionary<string, object> dict = handler.Properties;
+ Assert.Same(dict, handler.Properties);
+ Assert.Equal(0, dict.Count);
+ }
+ }
+
+ [Fact]
+ public void Properties_AddItemToDictionary_ItemPresent()
+ {
+ using (HttpClientHandler handler = CreateHttpClientHandler())
+ {
+ IDictionary<string, object> dict = handler.Properties;
+
+ var item = new object();
+ dict.Add("item", item);
+
+ object value;
+ Assert.True(dict.TryGetValue("item", out value));
+ Assert.Equal(item, value);
+ }
+ }
+
+ [OuterLoop("Uses external servers")]
+ [Theory, MemberData(nameof(RemoteServersMemberData))]
+ public async Task SendAsync_SimpleGet_Success(Configuration.Http.RemoteServer remoteServer)
+ {
+ using (HttpClient client = CreateHttpClientForRemoteServer(remoteServer))
+ using (HttpResponseMessage response = await client.GetAsync(remoteServer.EchoUri))
+ {
+ string responseContent = await response.Content.ReadAsStringAsync();
+ _output.WriteLine(responseContent);
+ TestHelper.VerifyResponseBody(
+ responseContent,
+ response.Content.Headers.ContentMD5,
+ false,
+ null);
+ }
+ }
+
+ [ConditionalFact]
+ public async Task GetAsync_IPv6LinkLocalAddressUri_Success()
+ {
+ using (HttpClient client = CreateHttpClient())
+ {
+ var options = new GenericLoopbackOptions { Address = TestHelper.GetIPv6LinkLocalAddress() };
+ if (options.Address == null)
+ {
+ throw new SkipTestException("Unable to find valid IPv6 LL address.");
+ }
+
+ await LoopbackServerFactory.CreateServerAsync(async (server, url) =>
+ {
+ _output.WriteLine(url.ToString());
+ await TestHelper.WhenAllCompletedOrAnyFailed(
+ server.AcceptConnectionSendResponseAndCloseAsync(),
+ client.GetAsync(url));
+ }, options: options);
+ }
+ }
+
+ [Theory]
+ [MemberData(nameof(GetAsync_IPBasedUri_Success_MemberData))]
+ public async Task GetAsync_IPBasedUri_Success(IPAddress address)
+ {
+ using (HttpClient client = CreateHttpClient())
+ {
+ var options = new GenericLoopbackOptions { Address = address };
+ await LoopbackServerFactory.CreateServerAsync(async (server, url) =>
+ {
+ _output.WriteLine(url.ToString());
+ await TestHelper.WhenAllCompletedOrAnyFailed(
+ server.AcceptConnectionSendResponseAndCloseAsync(),
+ client.GetAsync(url));
+ }, options: options);
+ }
+ }
+
+ public static IEnumerable<object[]> GetAsync_IPBasedUri_Success_MemberData()
+ {
+ foreach (var addr in new[] { IPAddress.Loopback, IPAddress.IPv6Loopback })
+ {
+ if (addr != null)
+ {
+ yield return new object[] { addr };
+ }
+ }
+ }
+
+ [OuterLoop("Uses external server")]
+ [Theory, MemberData(nameof(RemoteServersMemberData))]
+ public async Task SendAsync_MultipleRequestsReusingSameClient_Success(Configuration.Http.RemoteServer remoteServer)
+ {
+ using (HttpClient client = CreateHttpClientForRemoteServer(remoteServer))
+ {
+ for (int i = 0; i < 3; i++)
+ {
+ using (HttpResponseMessage response = await client.GetAsync(remoteServer.EchoUri))
+ {
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ }
+ }
+ }
+ }
+
+ [OuterLoop("Uses external server")]
+ [Theory, MemberData(nameof(RemoteServersMemberData))]
+ public async Task GetAsync_ResponseContentAfterClientAndHandlerDispose_Success(Configuration.Http.RemoteServer remoteServer)
+ {
+ using (HttpClient client = CreateHttpClientForRemoteServer(remoteServer))
+ using (HttpResponseMessage response = await client.GetAsync(remoteServer.EchoUri))
+ {
+ client.Dispose();
+ Assert.NotNull(response);
+ string responseContent = await response.Content.ReadAsStringAsync();
+ _output.WriteLine(responseContent);
+ TestHelper.VerifyResponseBody(responseContent, response.Content.Headers.ContentMD5, false, null);
+ }
+ }
+
+ [Theory]
+ [InlineData("[::1234]")]
+ [InlineData("[::1234]:8080")]
+ public async Task GetAsync_IPv6AddressInHostHeader_CorrectlyFormatted(string host)
+ {
+ string ipv6Address = "http://" + host;
+ bool connectionAccepted = false;
+
+ await LoopbackServer.CreateClientAndServerAsync(async proxyUri =>
+ {
+ using (HttpClientHandler handler = CreateHttpClientHandler())
+ using (HttpClient client = CreateHttpClient(handler))
+ {
+ handler.Proxy = new WebProxy(proxyUri);
+ try { await client.GetAsync(ipv6Address); } catch { }
+ }
+ }, server => server.AcceptConnectionAsync(async connection =>
+ {
+ connectionAccepted = true;
+ List<string> headers = await connection.ReadRequestHeaderAndSendResponseAsync();
+ Assert.Contains($"Host: {host}", headers);
+ }));
+
+ Assert.True(connectionAccepted);
+ }
+
+ [Theory]
+ [InlineData("1.2.3.4")]
+ [InlineData("1.2.3.4:8080")]
+ [InlineData("[::1234]")]
+ [InlineData("[::1234]:8080")]
+ public async Task ProxiedIPAddressRequest_NotDefaultPort_CorrectlyFormatted(string host)
+ {
+ string uri = "http://" + host;
+ bool connectionAccepted = false;
+
+ await LoopbackServer.CreateClientAndServerAsync(async proxyUri =>
+ {
+ using (HttpClientHandler handler = CreateHttpClientHandler())
+ using (HttpClient client = CreateHttpClient(handler))
+ {
+ handler.Proxy = new WebProxy(proxyUri);
+ try { await client.GetAsync(uri); } catch { }
+ }
+ }, server => server.AcceptConnectionAsync(async connection =>
+ {
+ connectionAccepted = true;
+ List<string> headers = await connection.ReadRequestHeaderAndSendResponseAsync();
+ Assert.Contains($"GET {uri}/ HTTP/1.1", headers);
+ }));
+
+ Assert.True(connectionAccepted);
+ }
+
+ public static IEnumerable<object[]> DestinationHost_MemberData()
+ {
+ yield return new object[] { Configuration.Http.Host };
+ yield return new object[] { "1.2.3.4" };
+ yield return new object[] { "[::1234]" };
+ }
+
+ [Theory]
+ [OuterLoop("Uses external server")]
+ [MemberData(nameof(DestinationHost_MemberData))]
+ public async Task ProxiedRequest_DefaultPort_PortStrippedOffInUri(string host)
+ {
+ string addressUri = $"http://{host}:{HttpDefaultPort}/";
+ string expectedAddressUri = $"http://{host}/";
+ bool connectionAccepted = false;
+
+ await LoopbackServer.CreateClientAndServerAsync(async proxyUri =>
+ {
+ using (HttpClientHandler handler = CreateHttpClientHandler())
+ using (HttpClient client = CreateHttpClient(handler))
+ {
+ handler.Proxy = new WebProxy(proxyUri);
+ try { await client.GetAsync(addressUri); } catch { }
+ }
+ }, server => server.AcceptConnectionAsync(async connection =>
+ {
+ connectionAccepted = true;
+ List<string> headers = await connection.ReadRequestHeaderAndSendResponseAsync();
+ Assert.Contains($"GET {expectedAddressUri} HTTP/1.1", headers);
+ }));
+
+ Assert.True(connectionAccepted);
+ }
+
+ [Fact]
+ [OuterLoop("Uses external server")]
+ public async Task ProxyTunnelRequest_PortSpecified_NotStrippedOffInUri()
+ {
+ // Https proxy request will use CONNECT tunnel, even the default 443 port is specified, it will not be stripped off.
+ string requestTarget = $"{Configuration.Http.SecureHost}:443";
+ string addressUri = $"https://{requestTarget}/";
+ bool connectionAccepted = false;
+
+ await LoopbackServer.CreateClientAndServerAsync(async proxyUri =>
+ {
+ using (HttpClientHandler handler = CreateHttpClientHandler())
+ using (HttpClient client = CreateHttpClient(handler))
+ {
+ handler.Proxy = new WebProxy(proxyUri);
+ handler.ServerCertificateCustomValidationCallback = TestHelper.AllowAllCertificates;
+ try { await client.GetAsync(addressUri); } catch { }
+ }
+ }, server => server.AcceptConnectionAsync(async connection =>
+ {
+ connectionAccepted = true;
+ List<string> headers = await connection.ReadRequestHeaderAndSendResponseAsync();
+ Assert.Contains($"CONNECT {requestTarget} HTTP/1.1", headers);
+ }));
+
+ Assert.True(connectionAccepted);
+ }
+
+ [Theory]
+ [InlineData(true)]
+ [InlineData(false)]
+ [OuterLoop("Uses external server")]
+ public async Task ProxyTunnelRequest_UserAgentHeaderAdded(bool addUserAgentHeader)
+ {
+ if (IsWinHttpHandler)
+ {
+ return; // Skip test since the fix is only in SocketsHttpHandler.
+ }
+
+ string addressUri = $"https://{Configuration.Http.SecureHost}/";
+ bool connectionAccepted = false;
+
+ await LoopbackServer.CreateClientAndServerAsync(async proxyUri =>
+ {
+ using (HttpClientHandler handler = CreateHttpClientHandler())
+ using (var client = new HttpClient(handler))
+ {
+ handler.Proxy = new WebProxy(proxyUri);
+ handler.ServerCertificateCustomValidationCallback = TestHelper.AllowAllCertificates;
+ if (addUserAgentHeader)
+ {
+ client.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue("Mozilla", "5.0"));
+ }
+ try
+ {
+ await client.GetAsync(addressUri);
+ }
+ catch
+ {
+ }
+ }
+ }, server => server.AcceptConnectionAsync(async connection =>
+ {
+ connectionAccepted = true;
+ List<string> headers = await connection.ReadRequestHeaderAndSendResponseAsync();
+ Assert.Contains($"CONNECT {Configuration.Http.SecureHost}:443 HTTP/1.1", headers);
+ if (addUserAgentHeader)
+ {
+ Assert.Contains("User-Agent: Mozilla/5.0", headers);
+ }
+ else
+ {
+ Assert.DoesNotContain("User-Agent:", headers);
+ }
+ }));
+
+ Assert.True(connectionAccepted);
+ }
+
+ public static IEnumerable<object[]> SecureAndNonSecure_IPBasedUri_MemberData() =>
+ from address in new[] { IPAddress.Loopback, IPAddress.IPv6Loopback }
+ from useSsl in new[] { true, false }
+ select new object[] { address, useSsl };
+
+ [ConditionalTheory]
+ [MemberData(nameof(SecureAndNonSecure_IPBasedUri_MemberData))]
+ public async Task GetAsync_SecureAndNonSecureIPBasedUri_CorrectlyFormatted(IPAddress address, bool useSsl)
+ {
+ if (LoopbackServerFactory.IsHttp2)
+ {
+ throw new SkipTestException("Host header is not supported on HTTP/2.");
+ }
+
+ var options = new LoopbackServer.Options { Address = address, UseSsl= useSsl };
+ bool connectionAccepted = false;
+ string host = "";
+
+ await LoopbackServer.CreateClientAndServerAsync(async url =>
+ {
+ host = $"{url.Host}:{url.Port}";
+ using (HttpClientHandler handler = CreateHttpClientHandler())
+ using (HttpClient client = CreateHttpClient(handler))
+ {
+ if (useSsl)
+ {
+ handler.ServerCertificateCustomValidationCallback = TestHelper.AllowAllCertificates;
+ }
+ try { await client.GetAsync(url); } catch { }
+ }
+ }, server => server.AcceptConnectionAsync(async connection =>
+ {
+ connectionAccepted = true;
+ List<string> headers = await connection.ReadRequestHeaderAndSendResponseAsync();
+ Assert.Contains($"Host: {host}", headers);
+ }), options);
+
+ Assert.True(connectionAccepted);
+ }
+
+ [OuterLoop("Uses external server")]
+ [Theory, MemberData(nameof(RemoteServersMemberData))]
+ public async Task GetAsync_ServerNeedsBasicAuthAndSetDefaultCredentials_StatusCodeUnauthorized(Configuration.Http.RemoteServer remoteServer)
+ {
+ HttpClientHandler handler = CreateHttpClientHandler();
+ handler.Credentials = CredentialCache.DefaultCredentials;
+ using (HttpClient client = CreateHttpClientForRemoteServer(remoteServer, handler))
+ {
+ Uri uri = remoteServer.BasicAuthUriForCreds(userName: Username, password: Password);
+ using (HttpResponseMessage response = await client.GetAsync(uri))
+ {
+ Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
+ }
+ }
+ }
+
+ [OuterLoop("Uses external server")]
+ [Theory, MemberData(nameof(RemoteServersMemberData))]
+ public async Task GetAsync_ServerNeedsAuthAndSetCredential_StatusCodeOK(Configuration.Http.RemoteServer remoteServer)
+ {
+ HttpClientHandler handler = CreateHttpClientHandler();
+ handler.Credentials = _credential;
+ using (HttpClient client = CreateHttpClientForRemoteServer(remoteServer, handler))
+ {
+ Uri uri = remoteServer.BasicAuthUriForCreds(userName: Username, password: Password);
+ using (HttpResponseMessage response = await client.GetAsync(uri))
+ {
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ }
+ }
+ }
+
+ [OuterLoop("Uses external server")]
+ [Fact]
+ public async Task GetAsync_ServerNeedsAuthAndNoCredential_StatusCodeUnauthorized()
+ {
+ using (HttpClient client = CreateHttpClient(UseHttp2.ToString()))
+ {
+ Uri uri = Configuration.Http.RemoteHttp11Server.BasicAuthUriForCreds(userName: Username, password: Password);
+ using (HttpResponseMessage response = await client.GetAsync(uri))
+ {
+ Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
+ }
+ }
+ }
+
+ [Theory]
+ [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)
+ {
+ await LoopbackServerFactory.CreateServerAsync(async (server, url) =>
+ {
+ HttpClientHandler handler = CreateHttpClientHandler();
+ handler.Credentials = new NetworkCredential("unused", "unused");
+ using (HttpClient client = CreateHttpClient(handler))
+ {
+ Task<HttpResponseMessage> getResponseTask = client.GetAsync(url);
+
+ Task<HttpRequestData> serverTask = server.AcceptConnectionSendResponseAndCloseAsync(HttpStatusCode.Unauthorized, additionalHeaders: string.IsNullOrEmpty(authHeadrName) ? null : new HttpHeaderData[] { new HttpHeaderData(authHeadrName, authHeaderValue) });
+
+ await TestHelper.WhenAllCompletedOrAnyFailed(getResponseTask, serverTask);
+ using (HttpResponseMessage response = await getResponseTask)
+ {
+ Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
+ }
+ }
+ });
+ }
+
+ [OuterLoop("Uses external server")]
+ [Theory]
+ [MemberData(nameof(RemoteServersAndHeaderEchoUrisMemberData))]
+ public async Task GetAsync_RequestHeadersAddCustomHeaders_HeaderAndEmptyValueSent(Configuration.Http.RemoteServer remoteServer, Uri uri)
+ {
+ if (IsWinHttpHandler && !PlatformDetection.IsWindows10Version1709OrGreater)
+ {
+ return;
+ }
+
+ string name = "X-Cust-Header-NoValue";
+ string value = "";
+ using (HttpClient client = CreateHttpClientForRemoteServer(remoteServer))
+ {
+ _output.WriteLine($"name={name}, value={value}");
+ client.DefaultRequestHeaders.Add(name, value);
+ using (HttpResponseMessage httpResponse = await client.GetAsync(uri))
+ {
+ Assert.Equal(HttpStatusCode.OK, httpResponse.StatusCode);
+ string responseText = await httpResponse.Content.ReadAsStringAsync();
+ _output.WriteLine(responseText);
+ Assert.True(TestHelper.JsonMessageContainsKeyValue(responseText, name, value));
+ }
+ }
+ }
+
+ [OuterLoop("Uses external server")]
+ [Theory, MemberData(nameof(RemoteServersHeaderValuesAndUris))]
+ public async Task GetAsync_RequestHeadersAddCustomHeaders_HeaderAndValueSent(Configuration.Http.RemoteServer remoteServer, string name, string value, Uri uri)
+ {
+ using (HttpClient client = CreateHttpClientForRemoteServer(remoteServer))
+ {
+ _output.WriteLine($"name={name}, value={value}");
+ client.DefaultRequestHeaders.Add(name, value);
+ using (HttpResponseMessage httpResponse = await client.GetAsync(uri))
+ {
+ Assert.Equal(HttpStatusCode.OK, httpResponse.StatusCode);
+ string responseText = await httpResponse.Content.ReadAsStringAsync();
+ _output.WriteLine(responseText);
+ Assert.True(TestHelper.JsonMessageContainsKeyValue(responseText, name, value));
+ }
+ }
+ }
+
+ [OuterLoop("Uses external server")]
+ [Theory, MemberData(nameof(RemoteServersAndHeaderEchoUrisMemberData))]
+ public async Task GetAsync_LargeRequestHeader_HeadersAndValuesSent(Configuration.Http.RemoteServer remoteServer, Uri uri)
+ {
+ // Unfortunately, our remote servers seem to have pretty strict limits (around 16K?)
+ // on the total size of the request header.
+ // TODO: Figure out how to reconfigure remote endpoints to allow larger request headers,
+ // and then increase the limits in this test.
+
+ string headerValue = new string('a', 2048);
+ const int headerCount = 6;
+
+ using (HttpClient client = CreateHttpClientForRemoteServer(remoteServer))
+ {
+ for (int i = 0; i < headerCount; i++)
+ {
+ client.DefaultRequestHeaders.Add($"Header-{i}", headerValue);
+ }
+
+ using (HttpResponseMessage httpResponse = await client.GetAsync(uri))
+ {
+ Assert.Equal(HttpStatusCode.OK, httpResponse.StatusCode);
+ string responseText = await httpResponse.Content.ReadAsStringAsync();
+
+ for (int i = 0; i < headerCount; i++)
+ {
+ Assert.True(TestHelper.JsonMessageContainsKeyValue(responseText, $"Header-{i}", headerValue));
+ }
+ }
+ }
+ }
+
+ public static IEnumerable<object[]> RemoteServersHeaderValuesAndUris()
+ {
+ foreach ((Configuration.Http.RemoteServer remoteServer, Uri uri) in RemoteServersAndHeaderEchoUris())
+ {
+ yield return new object[] { remoteServer, "X-CustomHeader", "x-value", uri };
+ yield return new object[] { remoteServer, "MyHeader", "1, 2, 3", uri };
+
+ // Construct a header value with every valid character (except space)
+ string allchars = "";
+ for (int i = 0x21; i <= 0x7E; i++)
+ {
+ allchars = allchars + (char)i;
+ }
+
+ // Put a space in the middle so it's not interpreted as insignificant leading/trailing whitespace
+ allchars = allchars + " " + allchars;
+
+ yield return new object[] { remoteServer, "All-Valid-Chars-Header", allchars, uri };
+ }
+ }
+
+ public static IEnumerable<(Configuration.Http.RemoteServer remoteServer, Uri uri)> RemoteServersAndHeaderEchoUris()
+ {
+ foreach (Configuration.Http.RemoteServer remoteServer in Configuration.Http.RemoteServers)
+ {
+ yield return (remoteServer, remoteServer.EchoUri);
+ yield return (remoteServer, remoteServer.RedirectUriForDestinationUri(
+ statusCode: 302,
+ destinationUri: remoteServer.EchoUri,
+ hops: 1));
+ }
+ }
+
+ public static IEnumerable<object[]> RemoteServersAndHeaderEchoUrisMemberData() => RemoteServersAndHeaderEchoUris().Select(x => new object[] { x.remoteServer, x.uri });
+
+ [Theory]
+ [InlineData(":")]
+ [InlineData("\x1234: \x5678")]
+ [InlineData("nocolon")]
+ [InlineData("no colon")]
+ [InlineData("Content-Length ")]
+ public async Task GetAsync_InvalidHeaderNameValue_ThrowsHttpRequestException(string invalidHeader)
+ {
+ await LoopbackServer.CreateClientAndServerAsync(async uri =>
+ {
+ using (HttpClient client = CreateHttpClient())
+ {
+ await Assert.ThrowsAsync<HttpRequestException>(() => client.GetStringAsync(uri));
+ }
+ }, server => server.AcceptConnectionSendCustomResponseAndCloseAsync($"HTTP/1.1 200 OK\r\n{invalidHeader}\r\nContent-Length: 11\r\n\r\nhello world"));
+ }
+
+ [Theory]
+ [InlineData(false, false)]
+ [InlineData(true, false)]
+ [InlineData(false, true)]
+ [InlineData(true, true)]
+ public async Task GetAsync_IncompleteData_ThrowsHttpRequestException(bool failDuringHeaders, bool getString)
+ {
+ if (IsWinHttpHandler)
+ {
+ // [ActiveIssue("https://github.com/dotnet/corefx/issues/39136")]
+ return;
+ }
+
+ await LoopbackServer.CreateClientAndServerAsync(async uri =>
+ {
+ using (HttpClient client = CreateHttpClient())
+ {
+ Task t = getString ? (Task)
+ client.GetStringAsync(uri) :
+ client.GetByteArrayAsync(uri);
+ await Assert.ThrowsAsync<HttpRequestException>(() => t);
+ }
+ }, server =>
+ failDuringHeaders ?
+ server.AcceptConnectionSendCustomResponseAndCloseAsync("HTTP/1.1 200 OK\r\nContent-Length: 5\r\n") :
+ server.AcceptConnectionSendCustomResponseAndCloseAsync("HTTP/1.1 200 OK\r\nContent-Length: 5\r\n\r\nhe"));
+ }
+
+ [Fact]
+ public async Task PostAsync_ManyDifferentRequestHeaders_SentCorrectly()
+ {
+ const string content = "hello world";
+
+ // Using examples from https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#Request_fields
+ // Exercises all exposed request.Headers and request.Content.Headers strongly-typed properties
+ await LoopbackServerFactory.CreateClientAndServerAsync(async uri =>
+ {
+ using (HttpClient client = CreateHttpClient())
+ {
+ byte[] contentArray = Encoding.ASCII.GetBytes(content);
+ var request = new HttpRequestMessage(HttpMethod.Post, uri) { Content = new ByteArrayContent(contentArray), Version = VersionFromUseHttp2 };
+
+ request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("text/plain"));
+ request.Headers.AcceptCharset.Add(new StringWithQualityHeaderValue("utf-8"));
+ request.Headers.AcceptEncoding.Add(new StringWithQualityHeaderValue("gzip"));
+ request.Headers.AcceptEncoding.Add(new StringWithQualityHeaderValue("deflate"));
+ request.Headers.AcceptLanguage.Add(new StringWithQualityHeaderValue("en-US"));
+ request.Headers.Add("Accept-Datetime", "Thu, 31 May 2007 20:35:00 GMT");
+ request.Headers.Add("Access-Control-Request-Method", "GET");
+ request.Headers.Add("Access-Control-Request-Headers", "GET");
+ request.Headers.Add("Age", "12");
+ request.Headers.Authorization = new AuthenticationHeaderValue("Basic", "QWxhZGRpbjpvcGVuIHNlc2FtZQ==");
+ request.Headers.CacheControl = new CacheControlHeaderValue() { NoCache = true };
+ request.Headers.Connection.Add("close");
+ request.Headers.Add("Cookie", "$Version=1; Skin=new");
+ request.Content.Headers.ContentLength = contentArray.Length;
+ request.Content.Headers.ContentMD5 = MD5.Create().ComputeHash(contentArray);
+ request.Content.Headers.ContentType = new MediaTypeHeaderValue("application/x-www-form-urlencoded");
+ request.Headers.Date = DateTimeOffset.Parse("Tue, 15 Nov 1994 08:12:31 GMT");
+ request.Headers.Expect.Add(new NameValueWithParametersHeaderValue("100-continue"));
+ request.Headers.Add("Forwarded", "for=192.0.2.60;proto=http;by=203.0.113.43");
+ request.Headers.Add("From", "User Name <user@example.com>");
+ request.Headers.Host = "en.wikipedia.org:8080";
+ request.Headers.IfMatch.Add(new EntityTagHeaderValue("\"37060cd8c284d8af7ad3082f209582d\""));
+ request.Headers.IfModifiedSince = DateTimeOffset.Parse("Sat, 29 Oct 1994 19:43:31 GMT");
+ request.Headers.IfNoneMatch.Add(new EntityTagHeaderValue("\"737060cd8c284d8af7ad3082f209582d\""));
+ request.Headers.IfRange = new RangeConditionHeaderValue(DateTimeOffset.Parse("Wed, 21 Oct 2015 07:28:00 GMT"));
+ request.Headers.IfUnmodifiedSince = DateTimeOffset.Parse("Sat, 29 Oct 1994 19:43:31 GMT");
+ request.Headers.MaxForwards = 10;
+ request.Headers.Add("Origin", "http://www.example-social-network.com");
+ request.Headers.Pragma.Add(new NameValueHeaderValue("no-cache"));
+ request.Headers.ProxyAuthorization = new AuthenticationHeaderValue("Basic", "QWxhZGRpbjpvcGVuIHNlc2FtZQ==");
+ request.Headers.Range = new RangeHeaderValue(500, 999);
+ request.Headers.Referrer = new Uri("http://en.wikipedia.org/wiki/Main_Page");
+ request.Headers.TE.Add(new TransferCodingWithQualityHeaderValue("trailers"));
+ request.Headers.TE.Add(new TransferCodingWithQualityHeaderValue("deflate"));
+ request.Headers.Trailer.Add("MyTrailer");
+ request.Headers.TransferEncoding.Add(new TransferCodingHeaderValue("chunked"));
+ request.Headers.UserAgent.Add(new ProductInfoHeaderValue(new ProductHeaderValue("Mozilla", "5.0")));
+ request.Headers.Upgrade.Add(new ProductHeaderValue("HTTPS", "1.3"));
+ request.Headers.Upgrade.Add(new ProductHeaderValue("IRC", "6.9"));
+ request.Headers.Upgrade.Add(new ProductHeaderValue("RTA", "x11"));
+ request.Headers.Upgrade.Add(new ProductHeaderValue("websocket"));
+ request.Headers.Via.Add(new ViaHeaderValue("1.0", "fred"));
+ request.Headers.Via.Add(new ViaHeaderValue("1.1", "example.com", null, "(Apache/1.1)"));
+ request.Headers.Warning.Add(new WarningHeaderValue(199, "-", "\"Miscellaneous warning\""));
+ request.Headers.Add("X-Requested-With", "XMLHttpRequest");
+ request.Headers.Add("DNT", "1 (Do Not Track Enabled)");
+ request.Headers.Add("X-Forwarded-For", "client1");
+ request.Headers.Add("X-Forwarded-For", "proxy1");
+ request.Headers.Add("X-Forwarded-For", "proxy2");
+ request.Headers.Add("X-Forwarded-Host", "en.wikipedia.org:8080");
+ request.Headers.Add("X-Forwarded-Proto", "https");
+ request.Headers.Add("Front-End-Https", "https");
+ request.Headers.Add("X-Http-Method-Override", "DELETE");
+ request.Headers.Add("X-ATT-DeviceId", "GT-P7320/P7320XXLPG");
+ request.Headers.Add("X-Wap-Profile", "http://wap.samsungmobile.com/uaprof/SGH-I777.xml");
+ request.Headers.Add("Proxy-Connection", "keep-alive");
+ request.Headers.Add("X-UIDH", "...");
+ request.Headers.Add("X-Csrf-Token", "i8XNjC4b8KVok4uw5RftR38Wgp2BFwql");
+ request.Headers.Add("X-Request-ID", "f058ebd6-02f7-4d3f-942e-904344e8cde5");
+ request.Headers.Add("X-Request-ID", "f058ebd6-02f7-4d3f-942e-904344e8cde5");
+ request.Headers.Add("X-Empty", "");
+ request.Headers.Add("X-Null", (string)null);
+ request.Headers.Add("X-Underscore_Name", "X-Underscore_Name");
+ request.Headers.Add("X-End", "End");
+
+ (await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead)).Dispose();
+ }
+ }, async server =>
+ {
+ {
+ HttpRequestData requestData = await server.HandleRequestAsync(HttpStatusCode.OK);
+
+ var headersSet = requestData.Headers;
+
+ Assert.Equal(content, Encoding.ASCII.GetString(requestData.Body));
+
+ Assert.Equal("utf-8", requestData.GetSingleHeaderValue("Accept-Charset"));
+ Assert.Equal("gzip, deflate", requestData.GetSingleHeaderValue("Accept-Encoding"));
+ Assert.Equal("en-US", requestData.GetSingleHeaderValue("Accept-Language"));
+ Assert.Equal("Thu, 31 May 2007 20:35:00 GMT", requestData.GetSingleHeaderValue("Accept-Datetime"));
+ Assert.Equal("GET", requestData.GetSingleHeaderValue("Access-Control-Request-Method"));
+ Assert.Equal("GET", requestData.GetSingleHeaderValue("Access-Control-Request-Headers"));
+ Assert.Equal("12", requestData.GetSingleHeaderValue("Age"));
+ Assert.Equal("Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==", requestData.GetSingleHeaderValue("Authorization"));
+ Assert.Equal("no-cache", requestData.GetSingleHeaderValue("Cache-Control"));
+ Assert.Equal("$Version=1; Skin=new", requestData.GetSingleHeaderValue("Cookie"));
+ Assert.Equal("Tue, 15 Nov 1994 08:12:31 GMT", requestData.GetSingleHeaderValue("Date"));
+ Assert.Equal("100-continue", requestData.GetSingleHeaderValue("Expect"));
+ Assert.Equal("for=192.0.2.60;proto=http;by=203.0.113.43", requestData.GetSingleHeaderValue("Forwarded"));
+ Assert.Equal("User Name <user@example.com>", requestData.GetSingleHeaderValue("From"));
+ Assert.Equal("\"37060cd8c284d8af7ad3082f209582d\"", requestData.GetSingleHeaderValue("If-Match"));
+ Assert.Equal("Sat, 29 Oct 1994 19:43:31 GMT", requestData.GetSingleHeaderValue("If-Modified-Since"));
+ Assert.Equal("\"737060cd8c284d8af7ad3082f209582d\"", requestData.GetSingleHeaderValue("If-None-Match"));
+ Assert.Equal("Wed, 21 Oct 2015 07:28:00 GMT", requestData.GetSingleHeaderValue("If-Range"));
+ Assert.Equal("Sat, 29 Oct 1994 19:43:31 GMT", requestData.GetSingleHeaderValue("If-Unmodified-Since"));
+ Assert.Equal("10", requestData.GetSingleHeaderValue("Max-Forwards"));
+ Assert.Equal("http://www.example-social-network.com", requestData.GetSingleHeaderValue("Origin"));
+ Assert.Equal("no-cache", requestData.GetSingleHeaderValue("Pragma"));
+ Assert.Equal("Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==", requestData.GetSingleHeaderValue("Proxy-Authorization"));
+ Assert.Equal("bytes=500-999", requestData.GetSingleHeaderValue("Range"));
+ Assert.Equal("http://en.wikipedia.org/wiki/Main_Page", requestData.GetSingleHeaderValue("Referer"));
+ Assert.Equal("MyTrailer", requestData.GetSingleHeaderValue("Trailer"));
+ Assert.Equal("Mozilla/5.0", requestData.GetSingleHeaderValue("User-Agent"));
+ Assert.Equal("1.0 fred, 1.1 example.com (Apache/1.1)", requestData.GetSingleHeaderValue("Via"));
+ Assert.Equal("199 - \"Miscellaneous warning\"", requestData.GetSingleHeaderValue("Warning"));
+ Assert.Equal("XMLHttpRequest", requestData.GetSingleHeaderValue("X-Requested-With"));
+ Assert.Equal("1 (Do Not Track Enabled)", requestData.GetSingleHeaderValue("DNT"));
+ Assert.Equal("client1, proxy1, proxy2", requestData.GetSingleHeaderValue("X-Forwarded-For"));
+ Assert.Equal("en.wikipedia.org:8080", requestData.GetSingleHeaderValue("X-Forwarded-Host"));
+ Assert.Equal("https", requestData.GetSingleHeaderValue("X-Forwarded-Proto"));
+ Assert.Equal("https", requestData.GetSingleHeaderValue("Front-End-Https"));
+ Assert.Equal("DELETE", requestData.GetSingleHeaderValue("X-Http-Method-Override"));
+ Assert.Equal("GT-P7320/P7320XXLPG", requestData.GetSingleHeaderValue("X-ATT-DeviceId"));
+ Assert.Equal("http://wap.samsungmobile.com/uaprof/SGH-I777.xml", requestData.GetSingleHeaderValue("X-Wap-Profile"));
+ Assert.Equal("...", requestData.GetSingleHeaderValue("X-UIDH"));
+ Assert.Equal("i8XNjC4b8KVok4uw5RftR38Wgp2BFwql", requestData.GetSingleHeaderValue("X-Csrf-Token"));
+ Assert.Equal("f058ebd6-02f7-4d3f-942e-904344e8cde5, f058ebd6-02f7-4d3f-942e-904344e8cde5", requestData.GetSingleHeaderValue("X-Request-ID"));
+ Assert.Equal("", requestData.GetSingleHeaderValue("X-Null"));
+ Assert.Equal("", requestData.GetSingleHeaderValue("X-Empty"));
+ Assert.Equal("X-Underscore_Name", requestData.GetSingleHeaderValue("X-Underscore_Name"));
+ Assert.Equal("End", requestData.GetSingleHeaderValue("X-End"));
+
+ if (LoopbackServerFactory.IsHttp2)
+ {
+ // HTTP/2 forbids certain headers or values.
+ Assert.Equal("trailers", requestData.GetSingleHeaderValue("TE"));
+ Assert.Equal(0, requestData.GetHeaderValueCount("Upgrade"));
+ Assert.Equal(0, requestData.GetHeaderValueCount("Proxy-Connection"));
+ Assert.Equal(0, requestData.GetHeaderValueCount("Host"));
+ Assert.Equal(0, requestData.GetHeaderValueCount("Connection"));
+ Assert.Equal(0, requestData.GetHeaderValueCount("Transfer-Encoding"));
+ }
+ else
+ {
+ // Verify HTTP/1.x headers
+ Assert.Equal("close", requestData.GetSingleHeaderValue("Connection"), StringComparer.OrdinalIgnoreCase); // NetFxHandler uses "Close" vs "close"
+ Assert.Equal("en.wikipedia.org:8080", requestData.GetSingleHeaderValue("Host"));
+ Assert.Equal("trailers, deflate", requestData.GetSingleHeaderValue("TE"));
+ Assert.Equal("HTTPS/1.3, IRC/6.9, RTA/x11, websocket", requestData.GetSingleHeaderValue("Upgrade"));
+ Assert.Equal("keep-alive", requestData.GetSingleHeaderValue("Proxy-Connection"));
+ }
+ }
+ });
+ }
+
+ public static IEnumerable<object[]> GetAsync_ManyDifferentResponseHeaders_ParsedCorrectly_MemberData() =>
+ from newline in new[] { "\n", "\r\n" }
+ from fold in new[] { "", newline + " ", newline + "\t", newline + " " }
+ from dribble in new[] { false, true }
+ select new object[] { newline, fold, dribble };
+
+ [ConditionalTheory]
+ [MemberData(nameof(GetAsync_ManyDifferentResponseHeaders_ParsedCorrectly_MemberData))]
+ public async Task GetAsync_ManyDifferentResponseHeaders_ParsedCorrectly(string newline, string fold, bool dribble)
+ {
+ if (LoopbackServerFactory.IsHttp2)
+ {
+ throw new SkipTestException("Folding is not supported on HTTP/2.");
+ }
+
+ // Using examples from https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#Response_fields
+ // Exercises all exposed response.Headers and response.Content.Headers strongly-typed properties
+ await LoopbackServer.CreateClientAndServerAsync(async uri =>
+ {
+ using (HttpClient client = CreateHttpClient())
+ using (HttpResponseMessage resp = await client.GetAsync(uri))
+ {
+ Assert.Equal("1.1", resp.Version.ToString());
+ Assert.Equal(HttpStatusCode.OK, resp.StatusCode);
+ Assert.Contains("*", resp.Headers.GetValues("Access-Control-Allow-Origin"));
+ Assert.Contains("text/example;charset=utf-8", resp.Headers.GetValues("Accept-Patch"));
+ Assert.Contains("bytes", resp.Headers.AcceptRanges);
+ Assert.Equal(TimeSpan.FromSeconds(12), resp.Headers.Age.GetValueOrDefault());
+ Assert.Contains("Bearer 63123a47139a49829bcd8d03005ca9d7", resp.Headers.GetValues("Authorization"));
+ Assert.Contains("GET", resp.Content.Headers.Allow);
+ Assert.Contains("HEAD", resp.Content.Headers.Allow);
+ Assert.Contains("http/1.1=\"http2.example.com:8001\"; ma=7200", resp.Headers.GetValues("Alt-Svc"));
+ Assert.Equal(TimeSpan.FromSeconds(3600), resp.Headers.CacheControl.MaxAge.GetValueOrDefault());
+ Assert.Contains("close", resp.Headers.Connection);
+ Assert.True(resp.Headers.ConnectionClose.GetValueOrDefault());
+ Assert.Equal("attachment", resp.Content.Headers.ContentDisposition.DispositionType);
+ Assert.Equal("\"fname.ext\"", resp.Content.Headers.ContentDisposition.FileName);
+ Assert.Contains("gzip", resp.Content.Headers.ContentEncoding);
+ Assert.Contains("da", resp.Content.Headers.ContentLanguage);
+ Assert.Equal(new Uri("/index.htm", UriKind.Relative), resp.Content.Headers.ContentLocation);
+ Assert.Equal(Convert.FromBase64String("Q2hlY2sgSW50ZWdyaXR5IQ=="), resp.Content.Headers.ContentMD5);
+ Assert.Equal("bytes", resp.Content.Headers.ContentRange.Unit);
+ Assert.Equal(21010, resp.Content.Headers.ContentRange.From.GetValueOrDefault());
+ Assert.Equal(47021, resp.Content.Headers.ContentRange.To.GetValueOrDefault());
+ Assert.Equal(47022, resp.Content.Headers.ContentRange.Length.GetValueOrDefault());
+ Assert.Equal("text/html", resp.Content.Headers.ContentType.MediaType);
+ Assert.Equal("utf-8", resp.Content.Headers.ContentType.CharSet);
+ Assert.Equal(DateTimeOffset.Parse("Tue, 15 Nov 1994 08:12:31 GMT"), resp.Headers.Date.GetValueOrDefault());
+ Assert.Equal("\"737060cd8c284d8af7ad3082f209582d\"", resp.Headers.ETag.Tag);
+ Assert.Equal(DateTimeOffset.Parse("Thu, 01 Dec 1994 16:00:00 GMT"), resp.Content.Headers.Expires.GetValueOrDefault());
+ Assert.Equal(DateTimeOffset.Parse("Tue, 15 Nov 1994 12:45:26 GMT"), resp.Content.Headers.LastModified.GetValueOrDefault());
+ Assert.Contains("</feed>; rel=\"alternate\"", resp.Headers.GetValues("Link"));
+ Assert.Equal(new Uri("http://www.w3.org/pub/WWW/People.html"), resp.Headers.Location);
+ Assert.Contains("CP=\"This is not a P3P policy!\"", resp.Headers.GetValues("P3P"));
+ Assert.Contains(new NameValueHeaderValue("no-cache"), resp.Headers.Pragma);
+ Assert.Contains(new AuthenticationHeaderValue("basic"), resp.Headers.ProxyAuthenticate);
+ Assert.Contains("max-age=2592000; pin-sha256=\"E9CZ9INDbd+2eRQozYqqbQ2yXLVKB9+xcprMF+44U1g=\"", resp.Headers.GetValues("Public-Key-Pins"));
+ Assert.Equal(TimeSpan.FromSeconds(120), resp.Headers.RetryAfter.Delta.GetValueOrDefault());
+ Assert.Contains(new ProductInfoHeaderValue("Apache", "2.4.1"), resp.Headers.Server);
+ Assert.Contains("UserID=JohnDoe; Max-Age=3600; Version=1", resp.Headers.GetValues("Set-Cookie"));
+ Assert.Contains("max-age=16070400; includeSubDomains", resp.Headers.GetValues("Strict-Transport-Security"));
+ Assert.Contains("Max-Forwards", resp.Headers.Trailer);
+ Assert.Contains("?", resp.Headers.GetValues("Tk"));
+ Assert.Contains(new ProductHeaderValue("HTTPS", "1.3"), resp.Headers.Upgrade);
+ Assert.Contains(new ProductHeaderValue("IRC", "6.9"), resp.Headers.Upgrade);
+ Assert.Contains(new ProductHeaderValue("websocket"), resp.Headers.Upgrade);
+ Assert.Contains("Accept-Language", resp.Headers.Vary);
+ Assert.Contains(new ViaHeaderValue("1.0", "fred"), resp.Headers.Via);
+ Assert.Contains(new ViaHeaderValue("1.1", "example.com", null, "(Apache/1.1)"), resp.Headers.Via);
+ Assert.Contains(new WarningHeaderValue(199, "-", "\"Miscellaneous warning\"", DateTimeOffset.Parse("Wed, 21 Oct 2015 07:28:00 GMT")), resp.Headers.Warning);
+ Assert.Contains(new AuthenticationHeaderValue("Basic"), resp.Headers.WwwAuthenticate);
+ Assert.Contains("deny", resp.Headers.GetValues("X-Frame-Options"));
+ Assert.Contains("default-src 'self'", resp.Headers.GetValues("X-WebKit-CSP"));
+ Assert.Contains("5; url=http://www.w3.org/pub/WWW/People.html", resp.Headers.GetValues("Refresh"));
+ Assert.Contains("200 OK", resp.Headers.GetValues("Status"));
+ Assert.Contains("<origin>[, <origin>]*", resp.Headers.GetValues("Timing-Allow-Origin"));
+ Assert.Contains("42.666", resp.Headers.GetValues("X-Content-Duration"));
+ Assert.Contains("nosniff", resp.Headers.GetValues("X-Content-Type-Options"));
+ Assert.Contains("PHP/5.4.0", resp.Headers.GetValues("X-Powered-By"));
+ Assert.Contains("f058ebd6-02f7-4d3f-942e-904344e8cde5", resp.Headers.GetValues("X-Request-ID"));
+ Assert.Contains("IE=EmulateIE7", resp.Headers.GetValues("X-UA-Compatible"));
+ Assert.Contains("1; mode=block", resp.Headers.GetValues("X-XSS-Protection"));
+ }
+ }, server => server.AcceptConnectionSendCustomResponseAndCloseAsync(
+ $"HTTP/1.1 200 OK{newline}" +
+ $"Access-Control-Allow-Origin:{fold} *{newline}" +
+ $"Accept-Patch:{fold} text/example;charset=utf-8{newline}" +
+ $"Accept-Ranges:{fold} bytes{newline}" +
+ $"Age: {fold}12{newline}" +
+ $"Authorization: Bearer 63123a47139a49829bcd8d03005ca9d7{newline}" +
+ $"Allow: {fold}GET, HEAD{newline}" +
+ $"Alt-Svc:{fold} http/1.1=\"http2.example.com:8001\"; ma=7200{newline}" +
+ $"Cache-Control: {fold}max-age=3600{newline}" +
+ $"Connection:{fold} close{newline}" +
+ $"Content-Disposition: {fold}attachment;{fold} filename=\"fname.ext\"{newline}" +
+ $"Content-Encoding: {fold}gzip{newline}" +
+ $"Content-Language:{fold} da{newline}" +
+ $"Content-Location: {fold}/index.htm{newline}" +
+ $"Content-MD5:{fold} Q2hlY2sgSW50ZWdyaXR5IQ=={newline}" +
+ $"Content-Range: {fold}bytes {fold}21010-47021/47022{newline}" +
+ $"Content-Type: text/html;{fold} charset=utf-8{newline}" +
+ $"Date: Tue, 15 Nov 1994{fold} 08:12:31 GMT{newline}" +
+ $"ETag: {fold}\"737060cd8c284d8af7ad3082f209582d\"{newline}" +
+ $"Expires: Thu,{fold} 01 Dec 1994 16:00:00 GMT{newline}" +
+ $"Last-Modified:{fold} Tue, 15 Nov 1994 12:45:26 GMT{newline}" +
+ $"Link:{fold} </feed>; rel=\"alternate\"{newline}" +
+ $"Location:{fold} http://www.w3.org/pub/WWW/People.html{newline}" +
+ $"P3P: {fold}CP=\"This is not a P3P policy!\"{newline}" +
+ $"Pragma: {fold}no-cache{newline}" +
+ $"Proxy-Authenticate:{fold} Basic{newline}" +
+ $"Public-Key-Pins:{fold} max-age=2592000; pin-sha256=\"E9CZ9INDbd+2eRQozYqqbQ2yXLVKB9+xcprMF+44U1g=\"{newline}" +
+ $"Retry-After: {fold}120{newline}" +
+ $"Server: {fold}Apache/2.4.1{fold} (Unix){newline}" +
+ $"Set-Cookie: {fold}UserID=JohnDoe; Max-Age=3600; Version=1{newline}" +
+ $"Strict-Transport-Security: {fold}max-age=16070400; includeSubDomains{newline}" +
+ $"Trailer: {fold}Max-Forwards{newline}" +
+ $"Tk: {fold}?{newline}" +
+ $"Upgrade: HTTPS/1.3,{fold} IRC/6.9,{fold} RTA/x11, {fold}websocket{newline}" +
+ $"Vary:{fold} Accept-Language{newline}" +
+ $"Via:{fold} 1.0 fred, 1.1 example.com{fold} (Apache/1.1){newline}" +
+ $"Warning:{fold} 199 - \"Miscellaneous warning\" \"Wed, 21 Oct 2015 07:28:00 GMT\"{newline}" +
+ $"WWW-Authenticate: {fold}Basic{newline}" +
+ $"X-Frame-Options: {fold}deny{newline}" +
+ $"X-WebKit-CSP: default-src 'self'{newline}" +
+ $"Refresh: {fold}5; url=http://www.w3.org/pub/WWW/People.html{newline}" +
+ $"Status: {fold}200 OK{newline}" +
+ $"Timing-Allow-Origin: {fold}<origin>[, <origin>]*{newline}" +
+ $"Upgrade-Insecure-Requests:{fold} 1{newline}" +
+ $"X-Content-Duration:{fold} 42.666{newline}" +
+ $"X-Content-Type-Options: {fold}nosniff{newline}" +
+ $"X-Powered-By: {fold}PHP/5.4.0{newline}" +
+ $"X-Request-ID:{fold} f058ebd6-02f7-4d3f-942e-904344e8cde5{newline}" +
+ $"X-UA-Compatible: {fold}IE=EmulateIE7{newline}" +
+ $"X-XSS-Protection:{fold} 1; mode=block{newline}" +
+ $"{newline}"),
+ dribble ? new LoopbackServer.Options { StreamWrapper = s => new DribbleStream(s) } : null);
+ }
+
+ [ConditionalFact]
+ public async Task GetAsync_NonTraditionalChunkSizes_Accepted()
+ {
+ if (LoopbackServerFactory.IsHttp2)
+ {
+ throw new SkipTestException("Chunking is not supported on HTTP/2.");
+ }
+ await LoopbackServer.CreateServerAsync(async (server, url) =>
+ {
+ using (HttpClient client = CreateHttpClient())
+ {
+ Task<HttpResponseMessage> getResponseTask = client.GetAsync(url);
+ await TestHelper.WhenAllCompletedOrAnyFailed(
+ getResponseTask,
+ server.AcceptConnectionSendCustomResponseAndCloseAsync(
+ "HTTP/1.1 200 OK\r\n" +
+ "Connection: close\r\n" +
+ "Transfer-Encoding: chunked\r\n" +
+ "\r\n" +
+ "4 \r\n" + // whitespace after size
+ "data\r\n" +
+ "5;somekey=somevalue\r\n" + // chunk extension
+ "hello\r\n" +
+ "7\t ;chunkextension\r\n" + // tabs/spaces then chunk extension
+ "netcore\r\n" +
+ "0\r\n" +
+ "\r\n"));
+
+ using (HttpResponseMessage response = await getResponseTask)
+ {
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ string data = await response.Content.ReadAsStringAsync();
+ Assert.Contains("data", data);
+ Assert.Contains("hello", data);
+ Assert.Contains("netcore", data);
+ Assert.DoesNotContain("somekey", data);
+ Assert.DoesNotContain("somevalue", data);
+ Assert.DoesNotContain("chunkextension", data);
+ }
+ }
+ });
+ }
+
+ [Theory]
+ [InlineData("")] // missing size
+ [InlineData(" ")] // missing size
+ [InlineData("10000000000000000")] // overflowing size
+ [InlineData("xyz")] // non-hex
+ [InlineData("7gibberish")] // valid size then gibberish
+ [InlineData("7\v\f")] // unacceptable whitespace
+ public async Task GetAsync_InvalidChunkSize_ThrowsHttpRequestException(string chunkSize)
+ {
+ await LoopbackServer.CreateServerAsync(async (server, url) =>
+ {
+ using (HttpClient client = CreateHttpClient())
+ {
+ string partialResponse = "HTTP/1.1 200 OK\r\n" +
+ "Transfer-Encoding: chunked\r\n" +
+ "\r\n" +
+ $"{chunkSize}\r\n";
+
+ var tcs = new TaskCompletionSource<bool>();
+ Task serverTask = server.AcceptConnectionAsync(async connection =>
+ {
+ await connection.ReadRequestHeaderAndSendCustomResponseAsync(partialResponse);
+ await tcs.Task;
+ });
+
+ await Assert.ThrowsAsync<HttpRequestException>(() => client.GetAsync(url));
+ tcs.SetResult(true);
+ await serverTask;
+ }
+ });
+ }
+
+ [Fact]
+ public async Task GetAsync_InvalidChunkTerminator_ThrowsHttpRequestException()
+ {
+ await LoopbackServer.CreateClientAndServerAsync(async url =>
+ {
+ using (HttpClient client = CreateHttpClient())
+ {
+ await Assert.ThrowsAsync<HttpRequestException>(() => client.GetAsync(url));
+ }
+ }, server => server.AcceptConnectionSendCustomResponseAndCloseAsync(
+ "HTTP/1.1 200 OK\r\n" +
+ "Connection: close\r\n" +
+ "Transfer-Encoding: chunked\r\n" +
+ "\r\n" +
+ "5\r\n" +
+ "hello" + // missing \r\n terminator
+ //"5\r\n" +
+ //"world" + // missing \r\n terminator
+ "0\r\n" +
+ "\r\n"));
+ }
+
+ [Fact]
+ public async Task GetAsync_InfiniteChunkSize_ThrowsHttpRequestException()
+ {
+ await LoopbackServer.CreateServerAsync(async (server, url) =>
+ {
+ using (HttpClient client = CreateHttpClient())
+ {
+ client.Timeout = Timeout.InfiniteTimeSpan;
+
+ var cts = new CancellationTokenSource();
+ var tcs = new TaskCompletionSource<bool>();
+ Task serverTask = server.AcceptConnectionAsync(async connection =>
+ {
+ await connection.ReadRequestHeaderAndSendCustomResponseAsync("HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n");
+ TextWriter writer = connection.Writer;
+ try
+ {
+ while (!cts.IsCancellationRequested) // infinite to make sure implementation doesn't OOM
+ {
+ await writer.WriteAsync(new string(' ', 10000));
+ await Task.Delay(1);
+ }
+ }
+ catch { }
+ });
+
+ await Assert.ThrowsAsync<HttpRequestException>(() => client.GetAsync(url));
+ cts.Cancel();
+ await serverTask;
+ }
+ });
+ }
+
+ [Fact]
+ public async Task SendAsync_TransferEncodingSetButNoRequestContent_Throws()
+ {
+ var req = new HttpRequestMessage(HttpMethod.Post, "http://bing.com") { Version = VersionFromUseHttp2 };
+ req.Headers.TransferEncodingChunked = true;
+ using (HttpClient c = CreateHttpClient())
+ {
+ HttpRequestException error = await Assert.ThrowsAsync<HttpRequestException>(() => c.SendAsync(req));
+ Assert.IsType<InvalidOperationException>(error.InnerException);
+ }
+ }
+
+ [OuterLoop("Uses external server")]
+ [Theory, MemberData(nameof(RemoteServersMemberData))]
+ public async Task GetAsync_ResponseHeadersRead_ReadFromEachIterativelyDoesntDeadlock(Configuration.Http.RemoteServer remoteServer)
+ {
+ using (HttpClient client = CreateHttpClientForRemoteServer(remoteServer))
+ {
+ const int NumGets = 5;
+ Task<HttpResponseMessage>[] responseTasks = (from _ in Enumerable.Range(0, NumGets)
+ select client.GetAsync(remoteServer.EchoUri, HttpCompletionOption.ResponseHeadersRead)).ToArray();
+ for (int i = responseTasks.Length - 1; i >= 0; i--) // read backwards to increase likelihood that we wait on a different task than has data available
+ {
+ using (HttpResponseMessage response = await responseTasks[i])
+ {
+ string responseContent = await response.Content.ReadAsStringAsync();
+ _output.WriteLine(responseContent);
+ TestHelper.VerifyResponseBody(
+ responseContent,
+ response.Content.Headers.ContentMD5,
+ false,
+ null);
+ }
+ }
+ }
+ }
+
+ [OuterLoop("Uses external server")]
+ [Theory, MemberData(nameof(RemoteServersMemberData))]
+ public async Task SendAsync_HttpRequestMsgResponseHeadersRead_StatusCodeOK(Configuration.Http.RemoteServer remoteServer)
+ {
+ HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, remoteServer.EchoUri) { Version = remoteServer.HttpVersion };
+ using (HttpClient client = CreateHttpClientForRemoteServer(remoteServer))
+ {
+ using (HttpResponseMessage response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead))
+ {
+ string responseContent = await response.Content.ReadAsStringAsync();
+ _output.WriteLine(responseContent);
+ TestHelper.VerifyResponseBody(
+ responseContent,
+ response.Content.Headers.ContentMD5,
+ false,
+ null);
+ }
+ }
+ }
+
+ [OuterLoop("Slow response")]
+ [Fact]
+ public async Task SendAsync_ReadFromSlowStreamingServer_PartialDataReturned()
+ {
+ await LoopbackServer.CreateServerAsync(async (server, url) =>
+ {
+ using (HttpClient client = CreateHttpClient())
+ {
+ Task<HttpResponseMessage> getResponse = client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead);
+
+ await server.AcceptConnectionAsync(async connection =>
+ {
+ await connection.ReadRequestHeaderAndSendCustomResponseAsync(
+ "HTTP/1.1 200 OK\r\n" +
+ $"Date: {DateTimeOffset.UtcNow:R}\r\n" +
+ "Content-Length: 16000\r\n" +
+ "\r\n" +
+ "less than 16000 bytes");
+
+ using (HttpResponseMessage response = await getResponse)
+ {
+ var buffer = new byte[8000];
+ using (Stream clientStream = await response.Content.ReadAsStreamAsync())
+ {
+ int bytesRead = await clientStream.ReadAsync(buffer, 0, buffer.Length);
+ _output.WriteLine($"Bytes read from stream: {bytesRead}");
+ Assert.True(bytesRead < buffer.Length, "bytesRead should be less than buffer.Length");
+ }
+ }
+ });
+ }
+ });
+ }
+
+ [ConditionalTheory]
+ [InlineData(true)]
+ [InlineData(false)]
+ [InlineData(null)]
+ public async Task ReadAsStreamAsync_HandlerProducesWellBehavedResponseStream(bool? chunked)
+ {
+ if (LoopbackServerFactory.IsHttp2 && chunked == true)
+ {
+ throw new SkipTestException("Chunking is not supported on HTTP/2.");
+ }
+
+ await LoopbackServerFactory.CreateClientAndServerAsync(async uri =>
+ {
+ var request = new HttpRequestMessage(HttpMethod.Get, uri) { Version = VersionFromUseHttp2 };
+ using (var client = new HttpMessageInvoker(CreateHttpClientHandler()))
+ using (HttpResponseMessage response = await client.SendAsync(request, CancellationToken.None))
+ {
+ using (Stream responseStream = await response.Content.ReadAsStreamAsync())
+ {
+ Assert.Same(responseStream, await response.Content.ReadAsStreamAsync());
+
+ // Boolean properties returning correct values
+ Assert.True(responseStream.CanRead);
+ Assert.False(responseStream.CanWrite);
+ Assert.False(responseStream.CanSeek);
+
+ // Not supported operations
+ Assert.Throws<NotSupportedException>(() => responseStream.BeginWrite(new byte[1], 0, 1, null, null));
+ Assert.Throws<NotSupportedException>(() => responseStream.Length);
+ Assert.Throws<NotSupportedException>(() => responseStream.Position);
+ Assert.Throws<NotSupportedException>(() => responseStream.Position = 0);
+ Assert.Throws<NotSupportedException>(() => responseStream.Seek(0, SeekOrigin.Begin));
+ Assert.Throws<NotSupportedException>(() => responseStream.SetLength(0));
+ Assert.Throws<NotSupportedException>(() => responseStream.Write(new byte[1], 0, 1));
+ Assert.Throws<NotSupportedException>(() => responseStream.Write(new Span<byte>(new byte[1])));
+ Assert.Throws<NotSupportedException>(() => { responseStream.WriteAsync(new Memory<byte>(new byte[1])); });
+ Assert.Throws<NotSupportedException>(() => { responseStream.WriteAsync(new byte[1], 0, 1); });
+ Assert.Throws<NotSupportedException>(() => responseStream.WriteByte(1));
+
+ // Invalid arguments
+ var nonWritableStream = new MemoryStream(new byte[1], false);
+ var disposedStream = new MemoryStream();
+ disposedStream.Dispose();
+ Assert.Throws<ArgumentNullException>(() => responseStream.CopyTo(null));
+ Assert.Throws<ArgumentOutOfRangeException>(() => responseStream.CopyTo(Stream.Null, 0));
+ Assert.Throws<ArgumentNullException>(() => { responseStream.CopyToAsync(null, 100, default); });
+ Assert.Throws<ArgumentOutOfRangeException>(() => { responseStream.CopyToAsync(Stream.Null, 0, default); });
+ Assert.Throws<ArgumentOutOfRangeException>(() => { responseStream.CopyToAsync(Stream.Null, -1, default); });
+ Assert.Throws<NotSupportedException>(() => { responseStream.CopyToAsync(nonWritableStream, 100, default); });
+ Assert.Throws<ObjectDisposedException>(() => { responseStream.CopyToAsync(disposedStream, 100, default); });
+ Assert.Throws<ArgumentNullException>(() => responseStream.Read(null, 0, 100));
+ Assert.Throws<ArgumentOutOfRangeException>(() => responseStream.Read(new byte[1], -1, 1));
+ Assert.ThrowsAny<ArgumentException>(() => responseStream.Read(new byte[1], 2, 1));
+ Assert.Throws<ArgumentOutOfRangeException>(() => responseStream.Read(new byte[1], 0, -1));
+ Assert.ThrowsAny<ArgumentException>(() => responseStream.Read(new byte[1], 0, 2));
+ Assert.Throws<ArgumentNullException>(() => responseStream.BeginRead(null, 0, 100, null, null));
+ Assert.Throws<ArgumentOutOfRangeException>(() => responseStream.BeginRead(new byte[1], -1, 1, null, null));
+ Assert.ThrowsAny<ArgumentException>(() => responseStream.BeginRead(new byte[1], 2, 1, null, null));
+ Assert.Throws<ArgumentOutOfRangeException>(() => responseStream.BeginRead(new byte[1], 0, -1, null, null));
+ Assert.ThrowsAny<ArgumentException>(() => responseStream.BeginRead(new byte[1], 0, 2, null, null));
+ Assert.Throws<ArgumentNullException>(() => responseStream.EndRead(null));
+ Assert.Throws<ArgumentNullException>(() => { responseStream.ReadAsync(null, 0, 100, default); });
+ Assert.Throws<ArgumentOutOfRangeException>(() => { responseStream.ReadAsync(new byte[1], -1, 1, default); });
+ Assert.ThrowsAny<ArgumentException>(() => { responseStream.ReadAsync(new byte[1], 2, 1, default); });
+ Assert.Throws<ArgumentOutOfRangeException>(() => { responseStream.ReadAsync(new byte[1], 0, -1, default); });
+ Assert.ThrowsAny<ArgumentException>(() => { responseStream.ReadAsync(new byte[1], 0, 2, default); });
+
+ // Various forms of reading
+ var buffer = new byte[1];
+
+ Assert.Equal('h', responseStream.ReadByte());
+
+ Assert.Equal(1, await Task.Factory.FromAsync(responseStream.BeginRead, responseStream.EndRead, buffer, 0, 1, null));
+ Assert.Equal((byte)'e', buffer[0]);
+
+ Assert.Equal(1, await responseStream.ReadAsync(new Memory<byte>(buffer)));
+ Assert.Equal((byte)'l', buffer[0]);
+
+ Assert.Equal(1, await responseStream.ReadAsync(buffer, 0, 1));
+ Assert.Equal((byte)'l', buffer[0]);
+
+ Assert.Equal(1, responseStream.Read(new Span<byte>(buffer)));
+ Assert.Equal((byte)'o', buffer[0]);
+
+ Assert.Equal(1, responseStream.Read(buffer, 0, 1));
+ Assert.Equal((byte)' ', buffer[0]);
+
+ // 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));
+ Assert.Equal(0, await responseStream.ReadAsync(Memory<byte>.Empty));
+ Assert.Equal(0, await responseStream.ReadAsync(Array.Empty<byte>(), 0, 0));
+ Assert.Equal(0, responseStream.Read(Span<byte>.Empty));
+ Assert.Equal(0, responseStream.Read(Array.Empty<byte>(), 0, 0));
+
+ // And copying
+ var ms = new MemoryStream();
+ await responseStream.CopyToAsync(ms);
+ Assert.Equal("world", Encoding.ASCII.GetString(ms.ToArray()));
+
+ // Read and copy again once we've exhausted all data
+ ms = new MemoryStream();
+ await responseStream.CopyToAsync(ms);
+ responseStream.CopyTo(ms);
+ Assert.Equal(0, ms.Length);
+ Assert.Equal(-1, responseStream.ReadByte());
+ Assert.Equal(0, responseStream.Read(buffer, 0, 1));
+ Assert.Equal(0, responseStream.Read(new Span<byte>(buffer)));
+ Assert.Equal(0, await responseStream.ReadAsync(buffer, 0, 1));
+ Assert.Equal(0, await responseStream.ReadAsync(new Memory<byte>(buffer)));
+ Assert.Equal(0, await Task.Factory.FromAsync(responseStream.BeginRead, responseStream.EndRead, buffer, 0, 1, null));
+ }
+ }
+ }, async server =>
+ {
+ await server.AcceptConnectionAsync(async connection =>
+ {
+ await connection.ReadRequestDataAsync();
+ switch (chunked)
+ {
+ case true:
+ await connection.SendResponseAsync(HttpStatusCode.OK, headers: new HttpHeaderData[] { new HttpHeaderData("Transfer-Encoding", "chunked") }, isFinal: false);
+ await connection.SendResponseBodyAsync("3\r\nhel\r\n8\r\nlo world\r\n0\r\n\r\n");
+ break;
+
+ case false:
+ await connection.SendResponseAsync(HttpStatusCode.OK, headers: new HttpHeaderData[] { new HttpHeaderData("Content-Length", "11")}, content: "hello world");
+ break;
+
+ case null:
+ // This inject Content-Length header with null value to hint Loopback code to not include one automatically.
+ await connection.SendResponseAsync(HttpStatusCode.OK, headers: new HttpHeaderData[] { new HttpHeaderData("Content-Length", null)}, isFinal: false);
+ await connection.SendResponseBodyAsync("hello world");
+ break;
+ }
+ });
+ });
+ }
+
+ [Fact]
+ public async Task ReadAsStreamAsync_EmptyResponseBody_HandlerProducesWellBehavedResponseStream()
+ {
+ await LoopbackServerFactory.CreateClientAndServerAsync(async uri =>
+ {
+ using (var client = new HttpMessageInvoker(CreateHttpClientHandler()))
+ {
+ var request = new HttpRequestMessage(HttpMethod.Get, uri) { Version = VersionFromUseHttp2 };
+
+ using (HttpResponseMessage response = await client.SendAsync(request, CancellationToken.None))
+ using (Stream responseStream = await response.Content.ReadAsStreamAsync())
+ {
+ // Boolean properties returning correct values
+ Assert.True(responseStream.CanRead);
+ Assert.False(responseStream.CanWrite);
+ Assert.False(responseStream.CanSeek);
+
+ // Not supported operations
+ Assert.Throws<NotSupportedException>(() => responseStream.BeginWrite(new byte[1], 0, 1, null, null));
+ Assert.Throws<NotSupportedException>(() => responseStream.Length);
+ Assert.Throws<NotSupportedException>(() => responseStream.Position);
+ Assert.Throws<NotSupportedException>(() => responseStream.Position = 0);
+ Assert.Throws<NotSupportedException>(() => responseStream.Seek(0, SeekOrigin.Begin));
+ Assert.Throws<NotSupportedException>(() => responseStream.SetLength(0));
+ Assert.Throws<NotSupportedException>(() => responseStream.Write(new byte[1], 0, 1));
+ 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])));
+ await Assert.ThrowsAsync<NotSupportedException>(async () => await responseStream.WriteAsync(new byte[1], 0, 1));
+ Assert.Throws<NotSupportedException>(() => responseStream.WriteByte(1));
+
+ // Invalid arguments
+ var nonWritableStream = new MemoryStream(new byte[1], false);
+ var disposedStream = new MemoryStream();
+ disposedStream.Dispose();
+ Assert.Throws<ArgumentNullException>(() => responseStream.CopyTo(null));
+ Assert.Throws<ArgumentOutOfRangeException>(() => responseStream.CopyTo(Stream.Null, 0));
+ Assert.Throws<ArgumentNullException>(() => { responseStream.CopyToAsync(null, 100, default); });
+ Assert.Throws<ArgumentOutOfRangeException>(() => { responseStream.CopyToAsync(Stream.Null, 0, default); });
+ Assert.Throws<ArgumentOutOfRangeException>(() => { responseStream.CopyToAsync(Stream.Null, -1, default); });
+ Assert.Throws<NotSupportedException>(() => { responseStream.CopyToAsync(nonWritableStream, 100, default); });
+ Assert.Throws<ObjectDisposedException>(() => { responseStream.CopyToAsync(disposedStream, 100, default); });
+ Assert.Throws<ArgumentNullException>(() => responseStream.Read(null, 0, 100));
+ Assert.Throws<ArgumentOutOfRangeException>(() => responseStream.Read(new byte[1], -1, 1));
+ Assert.ThrowsAny<ArgumentException>(() => responseStream.Read(new byte[1], 2, 1));
+ Assert.Throws<ArgumentOutOfRangeException>(() => responseStream.Read(new byte[1], 0, -1));
+ Assert.ThrowsAny<ArgumentException>(() => responseStream.Read(new byte[1], 0, 2));
+ Assert.Throws<ArgumentNullException>(() => responseStream.BeginRead(null, 0, 100, null, null));
+ Assert.Throws<ArgumentOutOfRangeException>(() => responseStream.BeginRead(new byte[1], -1, 1, null, null));
+ Assert.ThrowsAny<ArgumentException>(() => responseStream.BeginRead(new byte[1], 2, 1, null, null));
+ Assert.Throws<ArgumentOutOfRangeException>(() => responseStream.BeginRead(new byte[1], 0, -1, null, null));
+ Assert.ThrowsAny<ArgumentException>(() => responseStream.BeginRead(new byte[1], 0, 2, null, null));
+ Assert.Throws<ArgumentNullException>(() => responseStream.EndRead(null));
+ Assert.Throws<ArgumentNullException>(() => { responseStream.CopyTo(null); });
+ Assert.Throws<ArgumentNullException>(() => { responseStream.CopyToAsync(null, 100, default); });
+ Assert.Throws<ArgumentNullException>(() => { responseStream.CopyToAsync(null, 100, default); });
+ Assert.Throws<ArgumentNullException>(() => { responseStream.Read(null, 0, 100); });
+ Assert.Throws<ArgumentNullException>(() => { responseStream.ReadAsync(null, 0, 100, default); });
+ Assert.Throws<ArgumentNullException>(() => { responseStream.BeginRead(null, 0, 100, null, null); });
+
+ // Empty reads
+ 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));
+ Assert.Equal(0, await responseStream.ReadAsync(new Memory<byte>(buffer)));
+ Assert.Equal(0, await responseStream.ReadAsync(buffer, 0, 1));
+ Assert.Equal(0, responseStream.Read(new Span<byte>(buffer)));
+ Assert.Equal(0, responseStream.Read(buffer, 0, 1));
+
+ // Empty copies
+ var ms = new MemoryStream();
+ await responseStream.CopyToAsync(ms);
+ Assert.Equal(0, ms.Length);
+ responseStream.CopyTo(ms);
+ Assert.Equal(0, ms.Length);
+ }
+ }
+ },
+ server => server.AcceptConnectionSendResponseAndCloseAsync());
+ }
+
+ [Fact]
+ public async Task Dispose_DisposingHandlerCancelsActiveOperationsWithoutResponses()
+ {
+ await LoopbackServerFactory.CreateServerAsync(async (server1, url1) =>
+ {
+ await LoopbackServerFactory.CreateServerAsync(async (server2, url2) =>
+ {
+ await LoopbackServerFactory.CreateServerAsync(async (server3, url3) =>
+ {
+ var unblockServers = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
+
+ // First server connects but doesn't send any response yet
+ Task serverTask1 = server1.AcceptConnectionAsync(async connection1 =>
+ {
+ await unblockServers.Task;
+ });
+
+ // Second server connects and sends some but not all headers
+ Task serverTask2 = server2.AcceptConnectionAsync(async connection2 =>
+ {
+ await connection2.ReadRequestDataAsync();
+ await connection2.SendResponseAsync(HttpStatusCode.OK, content: null, isFinal : false);
+ await unblockServers.Task;
+ });
+
+ // Third server connects and sends all headers and some but not all of the body
+ Task serverTask3 = server3.AcceptConnectionAsync(async connection3 =>
+ {
+ await connection3.ReadRequestDataAsync();
+ await connection3.SendResponseAsync(HttpStatusCode.OK, new HttpHeaderData[] { new HttpHeaderData("Content-Length", "20") }, isFinal : false);
+ await connection3.SendResponseBodyAsync("1234567890", isFinal : false);
+ await unblockServers.Task;
+ await connection3.SendResponseBodyAsync("1234567890", isFinal : true);
+ });
+
+ // Make three requests
+ Task<HttpResponseMessage> get1, get2;
+ HttpResponseMessage response3;
+ using (HttpClient client = CreateHttpClient())
+ {
+ get1 = client.GetAsync(url1, HttpCompletionOption.ResponseHeadersRead);
+ get2 = client.GetAsync(url2, HttpCompletionOption.ResponseHeadersRead);
+ response3 = await client.GetAsync(url3, HttpCompletionOption.ResponseHeadersRead);
+ } // Dispose the handler while requests are still outstanding
+
+ // Requests 1 and 2 should be canceled as we haven't finished receiving their headers
+ await Assert.ThrowsAnyAsync<OperationCanceledException>(() => get1);
+ await Assert.ThrowsAnyAsync<OperationCanceledException>(() => get2);
+
+ // Request 3 should still be active, and we should be able to receive all of the data.
+ unblockServers.SetResult(true);
+ using (response3)
+ {
+ Assert.Equal("12345678901234567890", await response3.Content.ReadAsStringAsync());
+ }
+ });
+ });
+ });
+ }
+
+ [Theory]
+ [InlineData(99)]
+ [InlineData(1000)]
+ public async Task GetAsync_StatusCodeOutOfRange_ExpectedException(int statusCode)
+ {
+ await LoopbackServer.CreateServerAsync(async (server, url) =>
+ {
+ using (HttpClient client = CreateHttpClient())
+ {
+ Task<HttpResponseMessage> getResponseTask = client.GetAsync(url);
+ await server.AcceptConnectionSendCustomResponseAndCloseAsync(
+ $"HTTP/1.1 {statusCode}\r\n" +
+ $"Date: {DateTimeOffset.UtcNow:R}\r\n" +
+ "Connection: close\r\n" +
+ "\r\n");
+
+ await Assert.ThrowsAsync<HttpRequestException>(() => getResponseTask);
+ }
+ });
+ }
+
+ [OuterLoop("Uses external server")]
+ [Fact]
+ public async Task GetAsync_UnicodeHostName_SuccessStatusCodeInResponse()
+ {
+ using (HttpClient client = CreateHttpClient())
+ {
+ // international version of the Starbucks website
+ // punycode: xn--oy2b35ckwhba574atvuzkc.com
+ string server = "http://\uc2a4\ud0c0\ubc85\uc2a4\ucf54\ub9ac\uc544.com";
+ using (HttpResponseMessage response = await client.GetAsync(server))
+ {
+ response.EnsureSuccessStatusCode();
+ }
+ }
+ }
+
+#region Post Methods Tests
+
+ [OuterLoop("Uses external server")]
+ [Theory, MemberData(nameof(RemoteServersMemberData))]
+ public async Task PostAsync_CallMethodTwice_StringContent(Configuration.Http.RemoteServer remoteServer)
+ {
+ using (HttpClient client = CreateHttpClientForRemoteServer(remoteServer))
+ {
+ string data = "Test String";
+ var content = new StringContent(data, Encoding.UTF8);
+ content.Headers.ContentMD5 = TestHelper.ComputeMD5Hash(data);
+ HttpResponseMessage response;
+ using (response = await client.PostAsync(remoteServer.VerifyUploadUri, content))
+ {
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ }
+
+ // Repeat call.
+ content = new StringContent(data, Encoding.UTF8);
+ content.Headers.ContentMD5 = TestHelper.ComputeMD5Hash(data);
+ using (response = await client.PostAsync(remoteServer.VerifyUploadUri, content))
+ {
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ }
+ }
+ }
+
+ [OuterLoop("Uses external server")]
+ [Theory, MemberData(nameof(RemoteServersMemberData))]
+ public async Task PostAsync_CallMethod_UnicodeStringContent(Configuration.Http.RemoteServer remoteServer)
+ {
+ using (HttpClient client = CreateHttpClientForRemoteServer(remoteServer))
+ {
+ string data = "\ub4f1\uffc7\u4e82\u67ab4\uc6d4\ud1a0\uc694\uc77c\uffda3\u3155\uc218\uffdb";
+ var content = new StringContent(data, Encoding.UTF8);
+ content.Headers.ContentMD5 = TestHelper.ComputeMD5Hash(data);
+
+ using (HttpResponseMessage response = await client.PostAsync(remoteServer.VerifyUploadUri, content))
+ {
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ }
+ }
+ }
+
+ [OuterLoop("Uses external server")]
+ [Theory, MemberData(nameof(VerifyUploadServersStreamsAndExpectedData))]
+ public async Task PostAsync_CallMethod_StreamContent(Configuration.Http.RemoteServer remoteServer, HttpContent content, byte[] expectedData)
+ {
+ using (HttpClient client = CreateHttpClientForRemoteServer(remoteServer))
+ {
+ content.Headers.ContentMD5 = TestHelper.ComputeMD5Hash(expectedData);
+
+ using (HttpResponseMessage response = await client.PostAsync(remoteServer.VerifyUploadUri, content))
+ {
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ }
+ }
+ }
+
+ private sealed class StreamContentWithSyncAsyncCopy : StreamContent
+ {
+ private readonly Stream _stream;
+ private readonly bool _syncCopy;
+
+ public StreamContentWithSyncAsyncCopy(Stream stream, bool syncCopy) : base(stream)
+ {
+ _stream = stream;
+ _syncCopy = syncCopy;
+ }
+
+ protected override Task SerializeToStreamAsync(Stream stream, TransportContext context)
+ {
+ if (_syncCopy)
+ {
+ try
+ {
+ _stream.CopyTo(stream, 128); // arbitrary size likely to require multiple read/writes
+ return Task.CompletedTask;
+ }
+ catch (Exception exc)
+ {
+ return Task.FromException(exc);
+ }
+ }
+
+ return base.SerializeToStreamAsync(stream, context);
+ }
+ }
+
+ public static IEnumerable<object[]> VerifyUploadServersStreamsAndExpectedData
+ {
+ get
+ {
+ foreach (Configuration.Http.RemoteServer remoteServer in Configuration.Http.RemoteServers) // target server
+ foreach (bool syncCopy in new[] { true, false }) // force the content copy to happen via Read/Write or ReadAsync/WriteAsync
+ {
+ byte[] data = new byte[1234];
+ new Random(42).NextBytes(data);
+
+ // A MemoryStream
+ {
+ var memStream = new MemoryStream(data, writable: false);
+ yield return new object[] { remoteServer, new StreamContentWithSyncAsyncCopy(memStream, syncCopy: syncCopy), data };
+ }
+
+ // A multipart content that provides its own stream from CreateContentReadStreamAsync
+ {
+ var mc = new MultipartContent();
+ mc.Add(new ByteArrayContent(data));
+ var memStream = new MemoryStream();
+ mc.CopyToAsync(memStream).GetAwaiter().GetResult();
+ yield return new object[] { remoteServer, mc, memStream.ToArray() };
+ }
+
+ // A stream that provides the data synchronously and has a known length
+ {
+ var wrappedMemStream = new MemoryStream(data, writable: false);
+ var syncKnownLengthStream = new DelegateStream(
+ canReadFunc: () => wrappedMemStream.CanRead,
+ canSeekFunc: () => wrappedMemStream.CanSeek,
+ lengthFunc: () => wrappedMemStream.Length,
+ positionGetFunc: () => wrappedMemStream.Position,
+ positionSetFunc: p => wrappedMemStream.Position = p,
+ readFunc: (buffer, offset, count) => wrappedMemStream.Read(buffer, offset, count),
+ readAsyncFunc: (buffer, offset, count, token) => wrappedMemStream.ReadAsync(buffer, offset, count, token));
+ yield return new object[] { remoteServer, new StreamContentWithSyncAsyncCopy(syncKnownLengthStream, syncCopy: syncCopy), data };
+ }
+
+ // A stream that provides the data synchronously and has an unknown length
+ {
+ int syncUnknownLengthStreamOffset = 0;
+
+ Func<byte[], int, int, int> readFunc = (buffer, offset, count) =>
+ {
+ int bytesRemaining = data.Length - syncUnknownLengthStreamOffset;
+ int bytesToCopy = Math.Min(bytesRemaining, count);
+ Array.Copy(data, syncUnknownLengthStreamOffset, buffer, offset, bytesToCopy);
+ syncUnknownLengthStreamOffset += bytesToCopy;
+ return bytesToCopy;
+ };
+
+ var syncUnknownLengthStream = new DelegateStream(
+ canReadFunc: () => true,
+ canSeekFunc: () => false,
+ readFunc: readFunc,
+ readAsyncFunc: (buffer, offset, count, token) => Task.FromResult(readFunc(buffer, offset, count)));
+ yield return new object[] { remoteServer, new StreamContentWithSyncAsyncCopy(syncUnknownLengthStream, syncCopy: syncCopy), data };
+ }
+
+ // A stream that provides the data asynchronously
+ {
+ int asyncStreamOffset = 0, maxDataPerRead = 100;
+
+ Func<byte[], int, int, int> readFunc = (buffer, offset, count) =>
+ {
+ int bytesRemaining = data.Length - asyncStreamOffset;
+ int bytesToCopy = Math.Min(bytesRemaining, Math.Min(maxDataPerRead, count));
+ Array.Copy(data, asyncStreamOffset, buffer, offset, bytesToCopy);
+ asyncStreamOffset += bytesToCopy;
+ return bytesToCopy;
+ };
+
+ var asyncStream = new DelegateStream(
+ canReadFunc: () => true,
+ canSeekFunc: () => false,
+ readFunc: readFunc,
+ readAsyncFunc: async (buffer, offset, count, token) =>
+ {
+ await Task.Delay(1).ConfigureAwait(false);
+ return readFunc(buffer, offset, count);
+ });
+ yield return new object[] { remoteServer, new StreamContentWithSyncAsyncCopy(asyncStream, syncCopy: syncCopy), data };
+ }
+
+ // Providing data from a FormUrlEncodedContent's stream
+ {
+ var formContent = new FormUrlEncodedContent(new[] { new KeyValuePair<string, string>("key", "val") });
+ yield return new object[] { remoteServer, formContent, Encoding.GetEncoding("iso-8859-1").GetBytes("key=val") };
+ }
+ }
+ }
+ }
+
+ [OuterLoop("Uses external server")]
+ [Theory, MemberData(nameof(RemoteServersMemberData))]
+ public async Task PostAsync_CallMethod_NullContent(Configuration.Http.RemoteServer remoteServer)
+ {
+ using (HttpClient client = CreateHttpClientForRemoteServer(remoteServer))
+ {
+ using (HttpResponseMessage response = await client.PostAsync(remoteServer.EchoUri, null))
+ {
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+
+ string responseContent = await response.Content.ReadAsStringAsync();
+ _output.WriteLine(responseContent);
+ TestHelper.VerifyResponseBody(
+ responseContent,
+ response.Content.Headers.ContentMD5,
+ false,
+ string.Empty);
+ }
+ }
+ }
+
+ [OuterLoop("Uses external server")]
+ [Theory, MemberData(nameof(RemoteServersMemberData))]
+ public async Task PostAsync_CallMethod_EmptyContent(Configuration.Http.RemoteServer remoteServer)
+ {
+ using (HttpClient client = CreateHttpClientForRemoteServer(remoteServer))
+ {
+ var content = new StringContent(string.Empty);
+ using (HttpResponseMessage response = await client.PostAsync(remoteServer.EchoUri, content))
+ {
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+
+ string responseContent = await response.Content.ReadAsStringAsync();
+ _output.WriteLine(responseContent);
+ TestHelper.VerifyResponseBody(
+ responseContent,
+ response.Content.Headers.ContentMD5,
+ false,
+ string.Empty);
+ }
+ }
+ }
+
+ [OuterLoop("Uses external server")]
+ [Theory]
+ [InlineData(false, "1.0")]
+ [InlineData(true, "1.0")]
+ [InlineData(null, "1.0")]
+ [InlineData(false, "1.1")]
+ [InlineData(true, "1.1")]
+ [InlineData(null, "1.1")]
+ [InlineData(false, "2.0")]
+ [InlineData(true, "2.0")]
+ [InlineData(null, "2.0")]
+ public async Task PostAsync_ExpectContinue_Success(bool? expectContinue, string version)
+ {
+ using (HttpClient client = CreateHttpClient())
+ {
+ var req = new HttpRequestMessage(HttpMethod.Post, version == "2.0" ? Configuration.Http.Http2RemoteEchoServer : Configuration.Http.RemoteEchoServer)
+ {
+ Content = new StringContent("Test String", Encoding.UTF8),
+ Version = new Version(version)
+ };
+ req.Headers.ExpectContinue = expectContinue;
+
+ using (HttpResponseMessage response = await client.SendAsync(req))
+ {
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+
+ if (!IsWinHttpHandler)
+ {
+ const string ExpectedReqHeader = "\"Expect\": \"100-continue\"";
+ if (expectContinue == true && (version == "1.1" || version == "2.0"))
+ {
+ Assert.Contains(ExpectedReqHeader, await response.Content.ReadAsStringAsync());
+ }
+ else
+ {
+ Assert.DoesNotContain(ExpectedReqHeader, await response.Content.ReadAsStringAsync());
+ }
+ }
+ }
+ }
+ }
+
+ [Fact]
+ public async Task GetAsync_ExpectContinueTrue_NoContent_StillSendsHeader()
+ {
+ const string ExpectedContent = "Hello, expecting and continuing world.";
+ var clientCompleted = new TaskCompletionSource<bool>();
+ await LoopbackServerFactory.CreateClientAndServerAsync(async uri =>
+ {
+ using (HttpClient client = CreateHttpClient())
+ {
+ client.DefaultRequestHeaders.ExpectContinue = true;
+ Assert.Equal(ExpectedContent, await client.GetStringAsync(uri));
+ clientCompleted.SetResult(true);
+ }
+ }, async server =>
+ {
+ await server.AcceptConnectionAsync(async connection =>
+ {
+ HttpRequestData requestData = await connection.ReadRequestDataAsync();
+ Assert.Equal("100-continue", requestData.GetSingleHeaderValue("Expect"));
+
+ await connection.SendResponseAsync(HttpStatusCode.Continue, isFinal: false);
+ await connection.SendResponseAsync(HttpStatusCode.OK, new HttpHeaderData[] { new HttpHeaderData("Content-Length", ExpectedContent.Length.ToString()) }, isFinal: false);
+ await connection.SendResponseBodyAsync(ExpectedContent);
+ await clientCompleted.Task; // make sure server closing the connection isn't what let the client complete
+ });
+ });
+ }
+
+ public static IEnumerable<object[]> Interim1xxStatusCode()
+ {
+ yield return new object[] { (HttpStatusCode) 100 }; // 100 Continue.
+ // 101 SwitchingProtocols will be treated as a final status code.
+ yield return new object[] { (HttpStatusCode) 102 }; // 102 Processing.
+ yield return new object[] { (HttpStatusCode) 103 }; // 103 EarlyHints.
+ yield return new object[] { (HttpStatusCode) 150 };
+ yield return new object[] { (HttpStatusCode) 180 };
+ yield return new object[] { (HttpStatusCode) 199 };
+ }
+
+ [Theory]
+ [MemberData(nameof(Interim1xxStatusCode))]
+ public async Task SendAsync_1xxResponsesWithHeaders_InterimResponsesHeadersIgnored(HttpStatusCode responseStatusCode)
+ {
+ var clientFinished = new TaskCompletionSource<bool>();
+ const string TestString = "test";
+ const string CookieHeaderExpected = "yummy_cookie=choco";
+ const string SetCookieExpected = "theme=light";
+ const string ContentTypeHeaderExpected = "text/html";
+
+ const string SetCookieIgnored1 = "hello=world";
+ const string SetCookieIgnored2 = "net=core";
+
+ await LoopbackServerFactory.CreateClientAndServerAsync(async uri =>
+ {
+ using (var handler = CreateHttpClientHandler())
+ using (HttpClient client = CreateHttpClient(handler))
+ {
+ HttpRequestMessage initialMessage = new HttpRequestMessage(HttpMethod.Post, uri) { Version = VersionFromUseHttp2 };
+ initialMessage.Content = new StringContent(TestString);
+ initialMessage.Headers.ExpectContinue = true;
+ HttpResponseMessage response = await client.SendAsync(initialMessage);
+
+ // Verify status code.
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ // Verify Cookie header.
+ Assert.Equal(1, response.Headers.GetValues("Cookie").Count());
+ Assert.Equal(CookieHeaderExpected, response.Headers.GetValues("Cookie").First().ToString());
+ // Verify Set-Cookie header.
+ Assert.Equal(1, handler.CookieContainer.Count);
+ Assert.Equal(SetCookieExpected, handler.CookieContainer.GetCookieHeader(uri));
+ // Verify Content-type header.
+ Assert.Equal(ContentTypeHeaderExpected, response.Content.Headers.ContentType.ToString());
+ clientFinished.SetResult(true);
+ }
+ }, async server =>
+ {
+ await server.AcceptConnectionAsync(async connection =>
+ {
+ // Send 100-Continue responses with additional headers.
+ HttpRequestData requestData = await connection.ReadRequestDataAsync(readBody: true);
+ await connection.SendResponseAsync(responseStatusCode, headers: new HttpHeaderData[] {
+ new HttpHeaderData("Cookie", "ignore_cookie=choco1"),
+ new HttpHeaderData("Content-type", "text/xml"),
+ new HttpHeaderData("Set-Cookie", SetCookieIgnored1)}, isFinal: false);
+
+ await connection.SendResponseAsync(responseStatusCode, headers: new HttpHeaderData[] {
+ new HttpHeaderData("Cookie", "ignore_cookie=choco2"),
+ new HttpHeaderData("Content-type", "text/plain"),
+ new HttpHeaderData("Set-Cookie", SetCookieIgnored2)}, isFinal: false);
+
+ Assert.Equal(TestString, Encoding.ASCII.GetString(requestData.Body));
+
+ // Send final status code.
+ await connection.SendResponseAsync(HttpStatusCode.OK, headers: new HttpHeaderData[] {
+ new HttpHeaderData("Cookie", CookieHeaderExpected),
+ new HttpHeaderData("Content-type", ContentTypeHeaderExpected),
+ new HttpHeaderData("Set-Cookie", SetCookieExpected)});
+
+ await clientFinished.Task;
+ });
+ });
+ }
+
+ [Theory]
+ [MemberData(nameof(Interim1xxStatusCode))]
+ public async Task SendAsync_Unexpected1xxResponses_DropAllInterimResponses(HttpStatusCode responseStatusCode)
+ {
+ var clientFinished = new TaskCompletionSource<bool>();
+ const string TestString = "test";
+
+ await LoopbackServerFactory.CreateClientAndServerAsync(async uri =>
+ {
+ using (HttpClient client = CreateHttpClient())
+ {
+ HttpRequestMessage initialMessage = new HttpRequestMessage(HttpMethod.Post, uri) { Version = VersionFromUseHttp2 };
+ initialMessage.Content = new StringContent(TestString);
+ // No ExpectContinue header.
+ initialMessage.Headers.ExpectContinue = false;
+ HttpResponseMessage response = await client.SendAsync(initialMessage);
+
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ clientFinished.SetResult(true);
+ }
+ }, async server =>
+ {
+ await server.AcceptConnectionAsync(async connection =>
+ {
+ // Send unexpected 1xx responses.
+ HttpRequestData requestData = await connection.ReadRequestDataAsync(readBody: false);
+ await connection.SendResponseAsync(responseStatusCode, isFinal: false);
+ await connection.SendResponseAsync(responseStatusCode, isFinal: false);
+ await connection.SendResponseAsync(responseStatusCode, isFinal: false);
+
+ byte[] body = await connection.ReadRequestBodyAsync();
+ Assert.Equal(TestString, Encoding.ASCII.GetString(body));
+
+ // Send final status code.
+ await connection.SendResponseAsync(HttpStatusCode.OK);
+ await clientFinished.Task;
+ });
+ });
+ }
+
+ [Fact]
+ public async Task SendAsync_MultipleExpected100Responses_ReceivesCorrectResponse()
+ {
+ var clientFinished = new TaskCompletionSource<bool>();
+ const string TestString = "test";
+
+ await LoopbackServerFactory.CreateClientAndServerAsync(async uri =>
+ {
+ using (HttpClient client = CreateHttpClient())
+ {
+ HttpRequestMessage initialMessage = new HttpRequestMessage(HttpMethod.Post, uri) { Version = VersionFromUseHttp2 };
+ initialMessage.Content = new StringContent(TestString);
+ initialMessage.Headers.ExpectContinue = true;
+ HttpResponseMessage response = await client.SendAsync(initialMessage);
+
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ clientFinished.SetResult(true);
+ }
+ }, async server =>
+ {
+ await server.AcceptConnectionAsync(async connection =>
+ {
+ await connection.ReadRequestDataAsync(readBody: false);
+ // Send multiple 100-Continue responses.
+ for (int count = 0 ; count < 4; count++)
+ {
+ await connection.SendResponseAsync(HttpStatusCode.Continue, isFinal: false);
+ }
+
+ byte[] body = await connection.ReadRequestBodyAsync();
+ Assert.Equal(TestString, Encoding.ASCII.GetString(body));
+
+ // Send final status code.
+ await connection.SendResponseAsync(HttpStatusCode.OK);
+ await clientFinished.Task;
+ });
+ });
+ }
+
+ [Fact]
+ public async Task SendAsync_No100ContinueReceived_RequestBodySentEventually()
+ {
+ var clientFinished = new TaskCompletionSource<bool>();
+ const string RequestString = "request";
+ const string ResponseString = "response";
+
+ await LoopbackServerFactory.CreateClientAndServerAsync(async uri =>
+ {
+ using (HttpClient client = CreateHttpClient())
+ {
+ HttpRequestMessage initialMessage = new HttpRequestMessage(HttpMethod.Post, uri) { Version = VersionFromUseHttp2 };
+ initialMessage.Content = new StringContent(RequestString);
+ initialMessage.Headers.ExpectContinue = true;
+ using (HttpResponseMessage response = await client.SendAsync(initialMessage))
+ {
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ Assert.Equal(ResponseString, await response.Content.ReadAsStringAsync());
+ }
+
+ clientFinished.SetResult(true);
+ }
+ }, async server =>
+ {
+ await server.AcceptConnectionAsync(async connection =>
+ {
+ await connection.ReadRequestDataAsync(readBody: false);
+
+ await connection.SendResponseAsync(HttpStatusCode.OK, headers: new HttpHeaderData[] {new HttpHeaderData("Content-Length", $"{ResponseString.Length}")}, isFinal : false);
+
+ byte[] body = await connection.ReadRequestBodyAsync();
+ Assert.Equal(RequestString, Encoding.ASCII.GetString(body));
+
+ await connection.SendResponseBodyAsync(ResponseString);
+
+ await clientFinished.Task;
+ });
+ });
+ }
+
+ [ConditionalFact]
+ public async Task SendAsync_101SwitchingProtocolsResponse_Success()
+ {
+ // WinHttpHandler and CurlHandler will hang, waiting for additional response.
+ // Other handlers will accept 101 as a final response.
+ if (IsWinHttpHandler)
+ {
+ return;
+ }
+
+ if (LoopbackServerFactory.IsHttp2)
+ {
+ throw new SkipTestException("Upgrade is not supported on HTTP/2");
+ }
+
+ var clientFinished = new TaskCompletionSource<bool>();
+
+ await LoopbackServer.CreateClientAndServerAsync(async uri =>
+ {
+ using (HttpClient client = CreateHttpClient())
+ {
+ HttpResponseMessage response = await client.GetAsync(uri, HttpCompletionOption.ResponseHeadersRead);
+ Assert.Equal(HttpStatusCode.SwitchingProtocols, response.StatusCode);
+ clientFinished.SetResult(true);
+ }
+ }, async server =>
+ {
+ await server.AcceptConnectionAsync(async connection =>
+ {
+ // Send a valid 101 Switching Protocols response.
+ await connection.ReadRequestHeaderAndSendCustomResponseAsync(
+ "HTTP/1.1 101 Switching Protocols\r\n" + "Upgrade: websocket\r\n" + "Connection: Upgrade\r\n\r\n");
+ await clientFinished.Task;
+ });
+ });
+ }
+
+ [Theory]
+ [InlineData(false)]
+ [InlineData(true)]
+ public async Task PostAsync_ThrowFromContentCopy_RequestFails(bool syncFailure)
+ {
+ await LoopbackServer.CreateServerAsync(async (server, uri) =>
+ {
+ Task responseTask = server.AcceptConnectionAsync(async connection =>
+ {
+ var buffer = new byte[1000];
+ while (await connection.Socket.ReceiveAsync(new ArraySegment<byte>(buffer, 0, buffer.Length), SocketFlags.None) != 0);
+ });
+
+ using (HttpClient client = CreateHttpClient())
+ {
+ Exception error = new FormatException();
+ var content = new StreamContent(new DelegateStream(
+ canSeekFunc: () => true,
+ lengthFunc: () => 12345678,
+ positionGetFunc: () => 0,
+ canReadFunc: () => true,
+ readFunc: (buffer, offset, count) => throw error,
+ readAsyncFunc: (buffer, offset, count, cancellationToken) => syncFailure ? throw error : Task.Delay(1).ContinueWith<int>(_ => throw error)));
+
+ Assert.Same(error, await Assert.ThrowsAsync<FormatException>(() => client.PostAsync(uri, content)));
+ }
+ });
+ }
+
+ [OuterLoop("Uses external server")]
+ [Theory, MemberData(nameof(RemoteServersMemberData))]
+ public async Task PostAsync_Redirect_ResultingGetFormattedCorrectly(Configuration.Http.RemoteServer remoteServer)
+ {
+ const string ContentString = "This is the content string.";
+ var content = new StringContent(ContentString);
+ Uri redirectUri = remoteServer.RedirectUriForDestinationUri(
+ 302,
+ remoteServer.EchoUri,
+ 1);
+
+ using (HttpClient client = CreateHttpClientForRemoteServer(remoteServer))
+ using (HttpResponseMessage response = await client.PostAsync(redirectUri, content))
+ {
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ string responseContent = await response.Content.ReadAsStringAsync();
+ Assert.DoesNotContain(ContentString, responseContent);
+ Assert.DoesNotContain("Content-Length", responseContent);
+ }
+ }
+
+ [OuterLoop("Takes several seconds")]
+ [Theory, MemberData(nameof(RemoteServersMemberData))]
+ public async Task PostAsync_RedirectWith307_LargePayload(Configuration.Http.RemoteServer remoteServer)
+ {
+ if (remoteServer.HttpVersion == new Version(2, 0))
+ {
+ // This is occasionally timing out in CI with SocketsHttpHandler and HTTP2, particularly on Linux
+ // Likely this is just a very slow test and not a product issue, so just increasing the timeout may be the right fix.
+ // Disable until we can investigate further.
+ return;
+ }
+
+ await PostAsync_Redirect_LargePayload_Helper(remoteServer, 307, true);
+ }
+
+ [OuterLoop("Takes several seconds")]
+ [Theory, MemberData(nameof(RemoteServersMemberData))]
+ public async Task PostAsync_RedirectWith302_LargePayload(Configuration.Http.RemoteServer remoteServer)
+ {
+ await PostAsync_Redirect_LargePayload_Helper(remoteServer, 302, false);
+ }
+
+ public async Task PostAsync_Redirect_LargePayload_Helper(Configuration.Http.RemoteServer remoteServer, int statusCode, bool expectRedirectToPost)
+ {
+ using (var fs = new FileStream(
+ Path.Combine(Path.GetTempPath(), Path.GetTempFileName()),
+ FileMode.Create,
+ FileAccess.ReadWrite,
+ FileShare.None,
+ 0x1000,
+ FileOptions.DeleteOnClose))
+ {
+ string contentString = string.Join("", Enumerable.Repeat("Content", 100000));
+ byte[] contentBytes = Encoding.UTF32.GetBytes(contentString);
+ fs.Write(contentBytes, 0, contentBytes.Length);
+ fs.Flush(flushToDisk: true);
+ fs.Position = 0;
+
+ Uri redirectUri = remoteServer.RedirectUriForDestinationUri(
+ statusCode: statusCode,
+ destinationUri: remoteServer.VerifyUploadUri,
+ hops: 1);
+ var content = new StreamContent(fs);
+
+ // Compute MD5 of request body data. This will be verified by the server when it receives the request.
+ content.Headers.ContentMD5 = TestHelper.ComputeMD5Hash(contentBytes);
+
+ using (HttpClient client = CreateHttpClientForRemoteServer(remoteServer))
+ using (HttpResponseMessage response = await client.PostAsync(redirectUri, content))
+ {
+ try
+ {
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ }
+ catch
+ {
+ _output.WriteLine($"{(int)response.StatusCode} {response.ReasonPhrase}");
+ throw;
+ }
+
+ if (expectRedirectToPost)
+ {
+ IEnumerable<string> headerValue = response.Headers.GetValues("X-HttpRequest-Method");
+ Assert.Equal("POST", headerValue.First());
+ }
+ }
+ }
+ }
+
+ [OuterLoop("Uses external server")]
+ [Theory, MemberData(nameof(RemoteServersMemberData))]
+ [ActiveIssue("https://github.com/dotnet/corefx/issues/31104", TestPlatforms.AnyUnix)]
+ public async Task PostAsync_ReuseRequestContent_Success(Configuration.Http.RemoteServer remoteServer)
+ {
+ const string ContentString = "This is the content string.";
+ using (HttpClient client = CreateHttpClientForRemoteServer(remoteServer))
+ {
+ var content = new StringContent(ContentString);
+ for (int i = 0; i < 2; i++)
+ {
+ using (HttpResponseMessage response = await client.PostAsync(remoteServer.EchoUri, content))
+ {
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ Assert.Contains(ContentString, await response.Content.ReadAsStringAsync());
+ }
+ }
+ }
+ }
+
+ [Theory]
+ [InlineData(HttpStatusCode.MethodNotAllowed, "Custom description")]
+ [InlineData(HttpStatusCode.MethodNotAllowed, "")]
+ public async Task GetAsync_CallMethod_ExpectedStatusLine(HttpStatusCode statusCode, string reasonPhrase)
+ {
+ if (LoopbackServerFactory.IsHttp2)
+ {
+ // Custom messages are not supported on HTTP2.
+ return;
+ }
+
+ await LoopbackServer.CreateClientAndServerAsync(async uri =>
+ {
+ using (HttpClient client = CreateHttpClient())
+ using (HttpResponseMessage response = await client.GetAsync(uri))
+ {
+ Assert.Equal(statusCode, response.StatusCode);
+ Assert.Equal(reasonPhrase, response.ReasonPhrase);
+ }
+ }, server => server.AcceptConnectionSendCustomResponseAndCloseAsync(
+ $"HTTP/1.1 {(int)statusCode} {reasonPhrase}\r\nContent-Length: 0\r\n\r\n"));
+ }
+
+#endregion
+
+#region Various HTTP Method Tests
+
+ [OuterLoop("Uses external server")]
+ [Theory, MemberData(nameof(HttpMethods))]
+ public async Task SendAsync_SendRequestUsingMethodToEchoServerWithNoContent_MethodCorrectlySent(
+ string method,
+ Uri serverUri)
+ {
+ using (HttpClient client = CreateHttpClient())
+ {
+ var request = new HttpRequestMessage(
+ new HttpMethod(method),
+ serverUri) { Version = VersionFromUseHttp2 };
+
+ using (HttpResponseMessage response = await client.SendAsync(request))
+ {
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ TestHelper.VerifyRequestMethod(response, method);
+ }
+ }
+ }
+
+ [OuterLoop("Uses external server")]
+ [Theory, MemberData(nameof(HttpMethodsThatAllowContent))]
+ public async Task SendAsync_SendRequestUsingMethodToEchoServerWithContent_Success(
+ string method,
+ Uri serverUri)
+ {
+ using (HttpClient client = CreateHttpClient())
+ {
+ var request = new HttpRequestMessage(
+ new HttpMethod(method),
+ serverUri) { Version = VersionFromUseHttp2 };
+ request.Content = new StringContent(ExpectedContent);
+ using (HttpResponseMessage response = await client.SendAsync(request))
+ {
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ TestHelper.VerifyRequestMethod(response, method);
+ string responseContent = await response.Content.ReadAsStringAsync();
+ _output.WriteLine(responseContent);
+
+ Assert.Contains($"\"Content-Length\": \"{request.Content.Headers.ContentLength.Value}\"", responseContent);
+ TestHelper.VerifyResponseBody(
+ responseContent,
+ response.Content.Headers.ContentMD5,
+ false,
+ ExpectedContent);
+ }
+ }
+ }
+
+ [OuterLoop("Uses external server")]
+ [Theory]
+ [InlineData("12345678910", 0)]
+ [InlineData("12345678910", 5)]
+ public async Task SendAsync_SendSameRequestMultipleTimesDirectlyOnHandler_Success(string stringContent, int startingPosition)
+ {
+ using (var handler = new HttpMessageInvoker(CreateHttpClientHandler()))
+ {
+ byte[] byteContent = Encoding.ASCII.GetBytes(stringContent);
+ var content = new MemoryStream();
+ content.Write(byteContent, 0, byteContent.Length);
+ content.Position = startingPosition;
+ var request = new HttpRequestMessage(HttpMethod.Post, Configuration.Http.RemoteEchoServer) { Content = new StreamContent(content), Version = VersionFromUseHttp2 };
+
+ for (int iter = 0; iter < 2; iter++)
+ {
+ using (HttpResponseMessage response = await handler.SendAsync(request, CancellationToken.None))
+ {
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+
+ string responseContent = await response.Content.ReadAsStringAsync();
+
+ Assert.Contains($"\"Content-Length\": \"{request.Content.Headers.ContentLength.Value}\"", responseContent);
+
+ Assert.Contains(stringContent.Substring(startingPosition), responseContent);
+ if (startingPosition != 0)
+ {
+ Assert.DoesNotContain(stringContent.Substring(0, startingPosition), responseContent);
+ }
+ }
+ }
+ }
+ }
+
+ [OuterLoop("Uses external server")]
+ [Theory, MemberData(nameof(HttpMethodsThatDontAllowContent))]
+ public async Task SendAsync_SendRequestUsingNoBodyMethodToEchoServerWithContent_NoBodySent(
+ string method,
+ Uri serverUri)
+ {
+ using (HttpClient client = CreateHttpClient())
+ {
+ var request = new HttpRequestMessage(
+ new HttpMethod(method),
+ serverUri)
+ {
+ Content = new StringContent(ExpectedContent),
+ Version = VersionFromUseHttp2
+ };
+
+ using (HttpResponseMessage response = await client.SendAsync(request))
+ {
+ if (method == "TRACE")
+ {
+ // .NET Framework also allows the HttpWebRequest and HttpClient APIs to send a request using 'TRACE'
+ // verb and a request body. The usual response from a server is "400 Bad Request".
+ // See here for more info: https://github.com/dotnet/corefx/issues/9023
+ Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
+ }
+ else
+ {
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ TestHelper.VerifyRequestMethod(response, method);
+ string responseContent = await response.Content.ReadAsStringAsync();
+ Assert.DoesNotContain(ExpectedContent, responseContent);
+ }
+ }
+ }
+ }
+#endregion
+
+#region Version tests
+ [OuterLoop("Uses external server")]
+ [Fact]
+ public async Task SendAsync_RequestVersion10_ServerReceivesVersion10Request()
+ {
+ Version receivedRequestVersion = await SendRequestAndGetRequestVersionAsync(new Version(1, 0));
+ Assert.Equal(new Version(1, 0), receivedRequestVersion);
+ }
+
+ [OuterLoop("Uses external server")]
+ [Fact]
+ public async Task SendAsync_RequestVersion11_ServerReceivesVersion11Request()
+ {
+ Version receivedRequestVersion = await SendRequestAndGetRequestVersionAsync(new Version(1, 1));
+ Assert.Equal(new Version(1, 1), receivedRequestVersion);
+ }
+
+ [OuterLoop("Uses external server")]
+ [Fact]
+ public async Task SendAsync_RequestVersionNotSpecified_ServerReceivesVersion11Request()
+ {
+ // SocketsHttpHandler treats 0.0 as a bad version, and throws.
+ if (!IsWinHttpHandler)
+ {
+ return;
+ }
+
+ // The default value for HttpRequestMessage.Version is Version(1,1).
+ // So, we need to set something different (0,0), to test the "unknown" version.
+ Version receivedRequestVersion = await SendRequestAndGetRequestVersionAsync(new Version(0, 0));
+ Assert.Equal(new Version(1, 1), receivedRequestVersion);
+ }
+
+ [OuterLoop("Uses external server")]
+ [Theory]
+ [MemberData(nameof(Http2Servers))]
+ public async Task SendAsync_RequestVersion20_ResponseVersion20IfHttp2Supported(Uri server)
+ {
+ // We don't currently have a good way to test whether HTTP/2 is supported without
+ // using the same mechanism we're trying to test, so for now we allow both 2.0 and 1.1 responses.
+ var request = new HttpRequestMessage(HttpMethod.Get, server);
+ request.Version = new Version(2, 0);
+
+ using (HttpClient client = CreateHttpClient())
+ {
+ // It is generally expected that the test hosts will be trusted, so we don't register a validation
+ // callback in the usual case.
+
+ using (HttpResponseMessage response = await client.SendAsync(request))
+ {
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ Assert.True(
+ response.Version == new Version(2, 0) ||
+ response.Version == new Version(1, 1),
+ "Response version " + response.Version);
+ }
+ }
+ }
+
+ [Fact]
+ public async Task SendAsync_RequestVersion20_HttpNotHttps_NoUpgradeRequest()
+ {
+ await LoopbackServerFactory.CreateClientAndServerAsync(async uri =>
+ {
+ using (HttpClient client = CreateHttpClient())
+ {
+ (await client.SendAsync(new HttpRequestMessage(HttpMethod.Get, uri) { Version = new Version(2, 0) })).Dispose();
+ }
+ }, async server =>
+ {
+ HttpRequestData requestData = await server.AcceptConnectionSendResponseAndCloseAsync();
+ Assert.Equal(0, requestData.GetHeaderValues("Upgrade").Length);
+ });
+ }
+
+ [OuterLoop("Uses external server")]
+ [ConditionalTheory(nameof(IsWindows10Version1607OrGreater)), MemberData(nameof(Http2NoPushServers))]
+ public async Task SendAsync_RequestVersion20_ResponseVersion20(Uri server)
+ {
+ _output.WriteLine(server.AbsoluteUri.ToString());
+ var request = new HttpRequestMessage(HttpMethod.Get, server);
+ request.Version = new Version(2, 0);
+
+ using (HttpClient client = CreateHttpClient())
+ {
+ using (HttpResponseMessage response = await client.SendAsync(request))
+ {
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ Assert.Equal(new Version(2, 0), response.Version);
+ }
+ }
+ }
+
+ private async Task<Version> SendRequestAndGetRequestVersionAsync(Version requestVersion)
+ {
+ Version receivedRequestVersion = null;
+
+ await LoopbackServer.CreateServerAsync(async (server, url) =>
+ {
+ var request = new HttpRequestMessage(HttpMethod.Get, url);
+ request.Version = requestVersion;
+
+ using (HttpClient client = CreateHttpClient())
+ {
+ Task<HttpResponseMessage> getResponse = client.SendAsync(request);
+ Task<List<string>> serverTask = server.AcceptConnectionSendResponseAndCloseAsync();
+ await TestHelper.WhenAllCompletedOrAnyFailed(getResponse, serverTask);
+
+ List<string> receivedRequest = await serverTask;
+ using (HttpResponseMessage response = await getResponse)
+ {
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ }
+
+ string statusLine = receivedRequest[0];
+ if (statusLine.Contains("/1.0"))
+ {
+ receivedRequestVersion = new Version(1, 0);
+ }
+ else if (statusLine.Contains("/1.1"))
+ {
+ receivedRequestVersion = new Version(1, 1);
+ }
+ else
+ {
+ Assert.True(false, "Invalid HTTP request version");
+ }
+
+ }
+ });
+
+ return receivedRequestVersion;
+ }
+#endregion
+
+#region Uri wire transmission encoding tests
+ [Fact]
+ public async Task SendRequest_UriPathHasReservedChars_ServerReceivedExpectedPath()
+ {
+ await LoopbackServerFactory.CreateServerAsync(async (server, rootUrl) =>
+ {
+ var uri = new Uri($"{rootUrl.Scheme}://{rootUrl.Host}:{rootUrl.Port}/test[]");
+ _output.WriteLine(uri.AbsoluteUri.ToString());
+
+ using (HttpClient client = CreateHttpClient())
+ {
+ Task<HttpResponseMessage> getResponseTask = client.GetAsync(uri);
+ Task<HttpRequestData> serverTask = server.AcceptConnectionSendResponseAndCloseAsync();
+
+ await TestHelper.WhenAllCompletedOrAnyFailed(getResponseTask, serverTask);
+ HttpRequestData receivedRequest = await serverTask;
+ using (HttpResponseMessage response = await getResponseTask)
+ {
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ Assert.Equal(uri.PathAndQuery, receivedRequest.Path);
+ }
+ }
+ });
+ }
+#endregion
+
+ [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindowsSubsystemForLinux))] // [ActiveIssue("https://github.com/dotnet/corefx/issues/11057")]
+ public async Task GetAsync_InvalidUrl_ExpectedExceptionThrown()
+ {
+ string invalidUri = $"http://{Configuration.Sockets.InvalidHost}";
+ _output.WriteLine($"{DateTime.Now} connecting to {invalidUri}");
+ using (HttpClient client = CreateHttpClient())
+ {
+ await Assert.ThrowsAsync<HttpRequestException>(() => client.GetAsync(invalidUri));
+ await Assert.ThrowsAsync<HttpRequestException>(() => client.GetStringAsync(invalidUri));
+ }
+ }
+ }
+}
--- /dev/null
+// 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.Collections.Generic;
+using System.IO;
+using System.Net.Test.Common;
+using System.Threading;
+using System.Threading.Tasks;
+using Xunit.Abstractions;
+
+namespace System.Net.Http.Functional.Tests
+{
+ using Configuration = System.Net.Test.Common.Configuration;
+
+#if WINHTTPHANDLER_TEST
+ using HttpClientHandler = System.Net.Http.WinHttpClientHandler;
+#endif
+
+ public abstract partial class HttpClientHandlerTestBase : FileCleanupTestBase
+ {
+ public readonly ITestOutputHelper _output;
+
+ protected virtual bool UseHttp2 => false;
+
+ public HttpClientHandlerTestBase(ITestOutputHelper output)
+ {
+ _output = output;
+ }
+
+ protected Version VersionFromUseHttp2 => GetVersion(UseHttp2);
+
+ protected static Version GetVersion(bool http2) => http2 ? new Version(2, 0) : HttpVersion.Version11;
+
+ protected virtual HttpClient CreateHttpClient() => CreateHttpClient(CreateHttpClientHandler());
+
+ protected HttpClient CreateHttpClient(HttpMessageHandler handler) =>
+ new HttpClient(handler) { DefaultRequestVersion = VersionFromUseHttp2 };
+
+ protected static HttpClient CreateHttpClient(string useHttp2String) =>
+ CreateHttpClient(CreateHttpClientHandler(useHttp2String), useHttp2String);
+
+ protected static HttpClient CreateHttpClient(HttpMessageHandler handler, string useHttp2String) =>
+ new HttpClient(handler) { DefaultRequestVersion = GetVersion(bool.Parse(useHttp2String)) };
+
+ protected LoopbackServerFactory LoopbackServerFactory =>
+#if NETCOREAPP
+ UseHttp2 ?
+ (LoopbackServerFactory)Http2LoopbackServerFactory.Singleton :
+#endif
+ Http11LoopbackServerFactory.Singleton;
+
+ // For use by remote server tests
+
+ public static readonly IEnumerable<object[]> RemoteServersMemberData = Configuration.Http.RemoteServersMemberData;
+
+ protected HttpClient CreateHttpClientForRemoteServer(Configuration.Http.RemoteServer remoteServer)
+ {
+ return CreateHttpClientForRemoteServer(remoteServer, CreateHttpClientHandler());
+ }
+
+ protected HttpClient CreateHttpClientForRemoteServer(Configuration.Http.RemoteServer remoteServer, HttpMessageHandler httpClientHandler)
+ {
+ HttpMessageHandler wrappedHandler = httpClientHandler;
+
+ // WinHttpHandler will downgrade to 1.1 if you set Transfer-Encoding: chunked.
+ // So, skip this verification if we're not using SocketsHttpHandler.
+ if (PlatformDetection.SupportsAlpn && !IsWinHttpHandler)
+ {
+ wrappedHandler = new VersionCheckerHttpHandler(httpClientHandler, remoteServer.HttpVersion);
+ }
+
+ return new HttpClient(wrappedHandler) { DefaultRequestVersion = remoteServer.HttpVersion };
+ }
+
+ private sealed class VersionCheckerHttpHandler : DelegatingHandler
+ {
+ private readonly Version _expectedVersion;
+
+ public VersionCheckerHttpHandler(HttpMessageHandler innerHandler, Version expectedVersion)
+ : base(innerHandler)
+ {
+ _expectedVersion = expectedVersion;
+ }
+
+ protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
+ {
+ if (request.Version != _expectedVersion)
+ {
+ throw new Exception($"Unexpected request version: expected {_expectedVersion}, saw {request.Version}");
+ }
+
+ HttpResponseMessage response = await base.SendAsync(request, cancellationToken);
+
+ if (response.Version != _expectedVersion)
+ {
+ throw new Exception($"Unexpected response version: expected {_expectedVersion}, saw {response.Version}");
+ }
+
+ return response;
+ }
+ }
+ }
+}
--- /dev/null
+// 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.Collections.Generic;
+using System.IO;
+using System.Net.Test.Common;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.DotNet.XUnitExtensions;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace System.Net.Http.Functional.Tests
+{
+ public abstract class HttpProtocolTests : HttpClientHandlerTestBase
+ {
+ protected virtual Stream GetStream(Stream s) => s;
+ protected virtual Stream GetStream_ClientDisconnectOk(Stream s) => s;
+
+ public HttpProtocolTests(ITestOutputHelper output) : base(output) { }
+
+ [Fact]
+ public async Task GetAsync_RequestVersion10_Success()
+ {
+ await LoopbackServer.CreateServerAsync(async (server, url) =>
+ {
+ using (HttpClient client = CreateHttpClient())
+ {
+ HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, url);
+ request.Version = HttpVersion.Version10;
+
+ Task<HttpResponseMessage> getResponseTask = client.SendAsync(request);
+ Task<List<string>> serverTask = server.AcceptConnectionSendResponseAndCloseAsync();
+
+ await TestHelper.WhenAllCompletedOrAnyFailed(getResponseTask, serverTask);
+
+ var requestLines = await serverTask;
+ Assert.Equal($"GET {url.PathAndQuery} HTTP/1.0", requestLines[0]);
+ }
+ }, new LoopbackServer.Options { StreamWrapper = GetStream });
+ }
+
+ [Fact]
+ public async Task GetAsync_RequestVersion11_Success()
+ {
+ await LoopbackServer.CreateServerAsync(async (server, url) =>
+ {
+ using (HttpClient client = CreateHttpClient())
+ {
+ HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, url);
+ request.Version = HttpVersion.Version11;
+
+ Task<HttpResponseMessage> getResponseTask = client.SendAsync(request);
+ Task<List<string>> serverTask = server.AcceptConnectionSendResponseAndCloseAsync();
+
+ await TestHelper.WhenAllCompletedOrAnyFailed(getResponseTask, serverTask);
+
+ var requestLines = await serverTask;
+ Assert.Equal($"GET {url.PathAndQuery} HTTP/1.1", requestLines[0]);
+ }
+ }, new LoopbackServer.Options { StreamWrapper = GetStream });
+ }
+
+ [Theory]
+ [InlineData(0)]
+ [InlineData(1)]
+ [InlineData(9)]
+ public async Task GetAsync_RequestVersion0X_ThrowsOr11(int minorVersion)
+ {
+ await LoopbackServer.CreateServerAsync(async (server, url) =>
+ {
+ using (HttpClient client = CreateHttpClient())
+ {
+ HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, url);
+ request.Version = new Version(0, minorVersion);
+
+ Task<HttpResponseMessage> getResponseTask = client.SendAsync(request);
+ Task<List<string>> serverTask = server.AcceptConnectionSendResponseAndCloseAsync();
+
+ if (IsWinHttpHandler)
+ {
+ await TestHelper.WhenAllCompletedOrAnyFailed(getResponseTask, serverTask);
+ var requestLines = await serverTask;
+ Assert.Equal($"GET {url.PathAndQuery} HTTP/1.1", requestLines[0]);
+ }
+ else
+ {
+ await Assert.ThrowsAsync<NotSupportedException>(() => TestHelper.WhenAllCompletedOrAnyFailed(getResponseTask, serverTask));
+ }
+ }
+ }, new LoopbackServer.Options { StreamWrapper = GetStream_ClientDisconnectOk});
+ }
+
+ [Theory]
+ [InlineData(1, 2)]
+ [InlineData(1, 6)]
+ [InlineData(2, 0)] // Note, this is plain HTTP (not HTTPS), so 2.0 is not supported and should degrade to 1.1
+ [InlineData(2, 1)]
+ [InlineData(2, 7)]
+ [InlineData(3, 0)]
+ [InlineData(4, 2)]
+ public async Task GetAsync_UnknownRequestVersion_ThrowsOrDegradesTo11(int majorVersion, int minorVersion)
+ {
+ Type exceptionType = null;
+
+ await LoopbackServer.CreateServerAsync(async (server, url) =>
+ {
+ using (HttpClient client = CreateHttpClient())
+ {
+ HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, url);
+ request.Version = new Version(majorVersion, minorVersion);
+
+ Task<HttpResponseMessage> getResponseTask = client.SendAsync(request);
+ Task<List<string>> serverTask = server.AcceptConnectionSendResponseAndCloseAsync();
+
+ if (exceptionType == null)
+ {
+ await TestHelper.WhenAllCompletedOrAnyFailed(getResponseTask, serverTask);
+ var requestLines = await serverTask;
+ Assert.Equal($"GET {url.PathAndQuery} HTTP/1.1", requestLines[0]);
+ }
+ else
+ {
+ await Assert.ThrowsAsync(exceptionType, (() => TestHelper.WhenAllCompletedOrAnyFailed(getResponseTask, serverTask)));
+ }
+ }
+ }, new LoopbackServer.Options { StreamWrapper = GetStream_ClientDisconnectOk });
+ }
+
+ [Theory]
+ [InlineData(0)]
+ [InlineData(1)]
+ public async Task GetAsync_ResponseVersion10or11_Success(int responseMinorVersion)
+ {
+ await LoopbackServer.CreateServerAsync(async (server, url) =>
+ {
+ using (HttpClient client = CreateHttpClient())
+ {
+ HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, url);
+ request.Version = HttpVersion.Version11;
+
+ Task<HttpResponseMessage> getResponseTask = client.SendAsync(request);
+ Task<List<string>> serverTask =
+ server.AcceptConnectionSendCustomResponseAndCloseAsync(
+ $"HTTP/1.{responseMinorVersion} 200 OK\r\nConnection: close\r\nDate: {DateTimeOffset.UtcNow:R}\r\nContent-Length: 0\r\n\r\n");
+
+ await TestHelper.WhenAllCompletedOrAnyFailed(getResponseTask, serverTask);
+
+ using (HttpResponseMessage response = await getResponseTask)
+ {
+ Assert.Equal(1, response.Version.Major);
+ Assert.Equal(responseMinorVersion, response.Version.Minor);
+ }
+ }
+ }, new LoopbackServer.Options { StreamWrapper = GetStream });
+ }
+
+ [Theory]
+ [InlineData(2)]
+ [InlineData(7)]
+ public async Task GetAsync_ResponseUnknownVersion1X_Success(int responseMinorVersion)
+ {
+ await LoopbackServer.CreateServerAsync(async (server, url) =>
+ {
+ using (HttpClient client = CreateHttpClient())
+ {
+ HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, url);
+ request.Version = HttpVersion.Version11;
+
+ Task<HttpResponseMessage> getResponseTask = client.SendAsync(request);
+ Task<List<string>> serverTask =
+ server.AcceptConnectionSendCustomResponseAndCloseAsync(
+ $"HTTP/1.{responseMinorVersion} 200 OK\r\nConnection: close\r\nDate: {DateTimeOffset.UtcNow:R}\r\nContent-Length: 0\r\n\r\n");
+
+ await TestHelper.WhenAllCompletedOrAnyFailed(getResponseTask, serverTask);
+
+ using (HttpResponseMessage response = await getResponseTask)
+ {
+ if (IsWinHttpHandler)
+ {
+ Assert.Equal(0, response.Version.Major);
+ Assert.Equal(0, response.Version.Minor);
+ }
+ else
+ {
+ Assert.Equal(1, response.Version.Major);
+ Assert.Equal(responseMinorVersion, response.Version.Minor);
+ }
+ }
+ }
+ }, new LoopbackServer.Options { StreamWrapper = GetStream });
+ }
+
+ [Theory]
+ [InlineData(0)]
+ [InlineData(1)]
+ [InlineData(9)]
+ public async Task GetAsync_ResponseVersion0X_ThrowsOr10(int responseMinorVersion)
+ {
+ bool reportAs10 = false;
+
+ await LoopbackServer.CreateServerAsync(async (server, url) =>
+ {
+ using (HttpClient client = CreateHttpClient())
+ {
+ HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, url);
+ request.Version = HttpVersion.Version11;
+
+ Task<HttpResponseMessage> getResponseTask = client.SendAsync(request);
+ Task<List<string>> serverTask =
+ server.AcceptConnectionSendCustomResponseAndCloseAsync(
+ $"HTTP/0.{responseMinorVersion} 200 OK\r\nConnection: close\r\nDate: {DateTimeOffset.UtcNow:R}\r\nContent-Length: 0\r\n\r\n");
+
+ if (reportAs10)
+ {
+ await TestHelper.WhenAllCompletedOrAnyFailed(getResponseTask, serverTask);
+
+ using (HttpResponseMessage response = await getResponseTask)
+ {
+ Assert.Equal(1, response.Version.Major);
+ Assert.Equal(0, response.Version.Minor);
+ }
+ }
+ else
+ {
+ await Assert.ThrowsAsync<HttpRequestException>(async () => await TestHelper.WhenAllCompletedOrAnyFailed(getResponseTask, serverTask));
+ }
+ }
+ }, new LoopbackServer.Options { StreamWrapper = GetStream_ClientDisconnectOk });
+ }
+
+ [Theory]
+ [InlineData(2, 0)]
+ [InlineData(2, 1)]
+ [InlineData(3, 0)]
+ [InlineData(4, 2)]
+ public async Task GetAsyncVersion11_BadResponseVersion_ThrowsOr00(int responseMajorVersion, int responseMinorVersion)
+ {
+ await LoopbackServer.CreateServerAsync(async (server, url) =>
+ {
+ using (HttpClient client = CreateHttpClient())
+ {
+ HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, url);
+ request.Version = HttpVersion.Version11;
+
+ Task<HttpResponseMessage> getResponseTask = client.SendAsync(request);
+ Task<List<string>> serverTask =
+ server.AcceptConnectionSendCustomResponseAndCloseAsync(
+ $"HTTP/{responseMajorVersion}.{responseMinorVersion} 200 OK\r\nConnection: close\r\nDate: {DateTimeOffset.UtcNow:R}\r\nContent-Length: 0\r\n\r\n");
+
+ await Assert.ThrowsAsync<HttpRequestException>(async () => await TestHelper.WhenAllCompletedOrAnyFailed(getResponseTask, serverTask));
+ }
+ }, new LoopbackServer.Options { StreamWrapper = GetStream_ClientDisconnectOk });
+ }
+
+ [Theory]
+ [InlineData("HTTP/1.1 200 OK", 200, "OK")]
+ [InlineData("HTTP/1.1 200 Sure why not?", 200, "Sure why not?")]
+ [InlineData("HTTP/1.1 200 OK\x0080", 200, "OK?")]
+ [InlineData("HTTP/1.1 200 O K", 200, "O K")]
+ [InlineData("HTTP/1.1 201 Created", 201, "Created")]
+ [InlineData("HTTP/1.1 202 Accepted", 202, "Accepted")]
+ [InlineData("HTTP/1.1 299 This is not a real status code", 299, "This is not a real status code")]
+ [InlineData("HTTP/1.1 345 redirect to nowhere", 345, "redirect to nowhere")]
+ [InlineData("HTTP/1.1 400 Bad Request", 400, "Bad Request")]
+ [InlineData("HTTP/1.1 500 Internal Server Error", 500, "Internal Server Error")]
+ [InlineData("HTTP/1.1 555 we just don't like you", 555, "we just don't like you")]
+ [InlineData("HTTP/1.1 600 still valid", 600, "still valid")]
+ public async Task GetAsync_ExpectedStatusCodeAndReason_Success(string statusLine, int expectedStatusCode, string expectedReason)
+ {
+ if (IsWinHttpHandler)
+ {
+ return;
+ }
+
+ await GetAsyncSuccessHelper(statusLine, expectedStatusCode, expectedReason);
+ }
+
+ [Theory]
+ [InlineData("HTTP/1.1 200 ", 200, " ")]
+ [InlineData("HTTP/1.1 200 Something", 200, " Something")]
+ public async Task GetAsync_ExpectedStatusCodeAndReason_PlatformBehaviorTest(string statusLine, int expectedStatusCode, string reason)
+ {
+ if (IsWinHttpHandler)
+ {
+ // WinHttpHandler will trim space characters.
+ await GetAsyncSuccessHelper(statusLine, expectedStatusCode, reason.Trim());
+ }
+ else
+ {
+ // SocketsHttpHandler and .NET Framework will keep the space characters.
+ await GetAsyncSuccessHelper(statusLine, expectedStatusCode, reason);
+ }
+ }
+
+ [Theory]
+ [InlineData("HTTP/1.1 200", 200, "")] // This test data requires the fix in .NET Framework 4.7.3
+ [InlineData("HTTP/1.1 200 O\tK", 200, "O\tK")]
+ [InlineData("HTTP/1.1 200 O \t\t \t\t\t\t \t K", 200, "O \t\t \t\t\t\t \t K")]
+ public async Task GetAsync_StatusLineNotFollowRFC_SuccessOnCore(string statusLine, int expectedStatusCode, string expectedReason)
+ {
+ await GetAsyncSuccessHelper(statusLine, expectedStatusCode, expectedReason);
+ }
+
+ private async Task GetAsyncSuccessHelper(string statusLine, int expectedStatusCode, string expectedReason)
+ {
+ await LoopbackServer.CreateServerAsync(async (server, url) =>
+ {
+ using (HttpClient client = CreateHttpClient())
+ {
+ Task<HttpResponseMessage> getResponseTask = client.GetAsync(url);
+ await TestHelper.WhenAllCompletedOrAnyFailed(
+ getResponseTask,
+ server.AcceptConnectionSendCustomResponseAndCloseAsync(
+ $"{statusLine}\r\n" +
+ "Connection: close\r\n" +
+ $"Date: {DateTimeOffset.UtcNow:R}\r\n" +
+ "Content-Length: 0\r\n" +
+ "\r\n"));
+ using (HttpResponseMessage response = await getResponseTask)
+ {
+ Assert.Equal(expectedStatusCode, (int)response.StatusCode);
+ Assert.Equal(expectedReason, response.ReasonPhrase);
+ }
+ }
+ }, new LoopbackServer.Options { StreamWrapper = GetStream });
+ }
+
+ public static IEnumerable<string> GetInvalidStatusLine()
+ {
+ yield return "HTTP/1.1 2345";
+ yield return "HTTP/A.1 200 OK";
+ yield return "HTTP/X.Y.Z 200 OK";
+
+ yield return "HTTP/0.1 200 OK";
+ yield return "HTTP/3.5 200 OK";
+ yield return "HTTP/1.12 200 OK";
+ yield return "HTTP/12.1 200 OK";
+ yield return "HTTP/1.1 200 O\rK";
+
+ yield return "HTTP/1.A 200 OK";
+ yield return "HTTP/1.1 ";
+ yield return "HTTP/1.1 !11";
+ yield return "HTTP/1.1 a11";
+ yield return "HTTP/1.1 abc";
+ yield return "HTTP/1.1\t\t";
+ yield return "HTTP/1.1\t";
+ yield return "HTTP/1.1 ";
+
+ yield return "HTTP/1.1 200OK";
+ yield return "HTTP/1.1 20c";
+ yield return "HTTP/1.1 23";
+ yield return "HTTP/1.1 2bc";
+
+ yield return "NOTHTTP/1.1";
+ yield return "HTTP 1.1 200 OK";
+ yield return "ABCD/1.1 200 OK";
+ yield return "HTTP/1.1";
+ yield return "HTTP\\1.1 200 OK";
+ yield return "NOTHTTP/1.1 200 OK";
+ }
+
+ public static TheoryData InvalidStatusLine = GetInvalidStatusLine().ToTheoryData();
+
+ [Theory]
+ [MemberData(nameof(InvalidStatusLine))]
+ public async Task GetAsync_InvalidStatusLine_ThrowsException(string responseString)
+ {
+ await GetAsyncThrowsExceptionHelper(responseString);
+ }
+
+ [Fact]
+ public async Task GetAsync_ReasonPhraseHasLF_BehaviorDifference()
+ {
+ await GetAsyncSuccessHelper("HTTP/1.1 200 O\n", 200, "O");
+ }
+
+ [Theory]
+ [InlineData("HTTP/1.1\t200 OK")]
+ [InlineData("HTTP/1.1 200\tOK")]
+ [InlineData("HTTP/1.1 200\t")]
+ public async Task GetAsync_InvalidStatusLine_ThrowsExceptionOnSocketsHttpHandler(string responseString)
+ {
+ if (!IsWinHttpHandler)
+ {
+ // SocketsHttpHandler and .NET Framework will throw HttpRequestException.
+ await GetAsyncThrowsExceptionHelper(responseString);
+ }
+ // WinHttpHandler will succeed.
+ }
+
+ private async Task GetAsyncThrowsExceptionHelper(string responseString)
+ {
+ await LoopbackServer.CreateServerAsync(async (server, url) =>
+ {
+ using (HttpClient client = CreateHttpClient())
+ {
+ Task ignoredServerTask = server.AcceptConnectionSendCustomResponseAndCloseAsync(
+ responseString + "\r\nConnection: close\r\nContent-Length: 0\r\n\r\n");
+
+ await Assert.ThrowsAsync<HttpRequestException>(() => client.GetAsync(url));
+ }
+ }, new LoopbackServer.Options { StreamWrapper = GetStream });
+ }
+
+ [Theory]
+ [InlineData("\r\n")]
+ [InlineData("\n")]
+ public async Task GetAsync_ResponseHasNormalLineEndings_Success(string lineEnding)
+ {
+ await LoopbackServer.CreateServerAsync(async (server, url) =>
+ {
+ using (HttpClient client = CreateHttpClient())
+ {
+ Task<HttpResponseMessage> getResponseTask = client.GetAsync(url);
+ Task<List<string>> serverTask = server.AcceptConnectionSendCustomResponseAndCloseAsync(
+ $"HTTP/1.1 200 OK{lineEnding}Connection: close\r\nDate: {DateTimeOffset.UtcNow:R}{lineEnding}Server: TestServer{lineEnding}Content-Length: 0{lineEnding}{lineEnding}");
+
+ await TestHelper.WhenAllCompletedOrAnyFailed(getResponseTask, serverTask);
+
+ using (HttpResponseMessage response = await getResponseTask)
+ {
+ Assert.Equal(200, (int)response.StatusCode);
+ Assert.Equal("OK", response.ReasonPhrase);
+ Assert.Equal("TestServer", response.Headers.Server.ToString());
+ }
+ }
+ }, new LoopbackServer.Options { StreamWrapper = GetStream });
+ }
+
+ public static IEnumerable<object[]> GetAsync_Chunked_VaryingSizeChunks_ReceivedCorrectly_MemberData()
+ {
+ foreach (int maxChunkSize in new[] { 1, 10_000 })
+ foreach (string lineEnding in new[] { "\n", "\r\n" })
+ foreach (bool useCopyToAsync in new[] { false, true })
+ yield return new object[] { maxChunkSize, lineEnding, useCopyToAsync };
+ }
+
+ [OuterLoop]
+ [Theory]
+ [MemberData(nameof(GetAsync_Chunked_VaryingSizeChunks_ReceivedCorrectly_MemberData))]
+ public async Task GetAsync_Chunked_VaryingSizeChunks_ReceivedCorrectly(int maxChunkSize, string lineEnding, bool useCopyToAsync)
+ {
+ if (IsWinHttpHandler)
+ {
+ return;
+ }
+
+ var rand = new Random(42);
+ byte[] expectedData = new byte[100_000];
+ rand.NextBytes(expectedData);
+
+ await LoopbackServer.CreateClientAndServerAsync(async uri =>
+ {
+ using (HttpMessageInvoker client = new HttpMessageInvoker(CreateHttpClientHandler()))
+ using (HttpResponseMessage resp = await client.SendAsync(new HttpRequestMessage(HttpMethod.Get, uri) { Version = VersionFromUseHttp2 }, CancellationToken.None))
+ using (Stream respStream = await resp.Content.ReadAsStreamAsync())
+ {
+ var actualData = new MemoryStream();
+
+ if (useCopyToAsync)
+ {
+ await respStream.CopyToAsync(actualData);
+ }
+ else
+ {
+ byte[] buffer = new byte[4096];
+ int bytesRead;
+ while ((bytesRead = await respStream.ReadAsync(buffer)) > 0)
+ {
+ actualData.Write(buffer, 0, bytesRead);
+ }
+ }
+
+ Assert.Equal<byte>(expectedData, actualData.ToArray());
+ }
+ }, async server =>
+ {
+ await server.AcceptConnectionAsync(async connection =>
+ {
+ await connection.ReadRequestHeaderAsync();
+
+ await connection.Writer.WriteAsync($"HTTP/1.1 200 OK{lineEnding}Transfer-Encoding: chunked{lineEnding}{lineEnding}");
+ for (int bytesSent = 0; bytesSent < expectedData.Length;)
+ {
+ int bytesRemaining = expectedData.Length - bytesSent;
+ int bytesToSend = rand.Next(1, Math.Min(bytesRemaining, maxChunkSize + 1));
+ await connection.Writer.WriteAsync(bytesToSend.ToString("X") + lineEnding);
+ await connection.Stream.WriteAsync(new Memory<byte>(expectedData, bytesSent, bytesToSend));
+ await connection.Writer.WriteAsync(lineEnding);
+ bytesSent += bytesToSend;
+ }
+ await connection.Writer.WriteAsync($"0{lineEnding}");
+ await connection.Writer.WriteAsync(lineEnding);
+ });
+ });
+ }
+
+ [Theory]
+ [InlineData("get", "GET")]
+ [InlineData("head", "HEAD")]
+ [InlineData("post", "POST")]
+ [InlineData("put", "PUT")]
+ [InlineData("other", "other")]
+ [InlineData("SometHING", "SometHING")]
+ public async Task CustomMethod_SentUppercasedIfKnown(string specifiedMethod, string expectedMethod)
+ {
+ await LoopbackServer.CreateClientAndServerAsync(async uri =>
+ {
+ using (HttpClient client = CreateHttpClient())
+ {
+ var m = new HttpRequestMessage(new HttpMethod(specifiedMethod), uri) { Version = VersionFromUseHttp2 };
+ (await client.SendAsync(m)).Dispose();
+ }
+ }, async server =>
+ {
+ List<string> headers = await server.AcceptConnectionSendResponseAndCloseAsync();
+ Assert.StartsWith(expectedMethod + " ", headers[0]);
+ });
+ }
+ }
+
+ public abstract class HttpProtocolTests_Dribble : HttpProtocolTests
+ {
+ public HttpProtocolTests_Dribble(ITestOutputHelper output) : base(output) { }
+
+ protected override Stream GetStream(Stream s) => new DribbleStream(s);
+ protected override Stream GetStream_ClientDisconnectOk(Stream s) => new DribbleStream(s, true);
+ }
+}
--- /dev/null
+// 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.Collections.Generic;
+using System.IO;
+using System.Net.Sockets;
+using System.Net.Test.Common;
+using System.Text;
+using System.Threading.Tasks;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace System.Net.Http.Functional.Tests
+{
+ public abstract class HttpRetryProtocolTests : HttpClientHandlerTestBase
+ {
+ private static readonly string s_simpleContent = "Hello World\r\n";
+
+ public HttpRetryProtocolTests(ITestOutputHelper output) : base(output) { }
+
+ [Fact]
+ public async Task GetAsync_RetryOnConnectionClosed_Success()
+ {
+ if (IsWinHttpHandler)
+ {
+ // WinHttpHandler does not support Expect: 100-continue.
+ return;
+ }
+
+ await LoopbackServer.CreateClientAndServerAsync(async url =>
+ {
+ using (HttpClient client = CreateHttpClient())
+ {
+ // Send initial request and receive response so connection is established
+ HttpResponseMessage response1 = await client.GetAsync(url);
+ Assert.Equal(HttpStatusCode.OK, response1.StatusCode);
+ Assert.Equal(s_simpleContent, await response1.Content.ReadAsStringAsync());
+
+ // Send second request. Should reuse same connection.
+ // The server will close the connection, but HttpClient should retry the request.
+ HttpResponseMessage response2 = await client.GetAsync(url);
+ Assert.Equal(HttpStatusCode.OK, response1.StatusCode);
+ Assert.Equal(s_simpleContent, await response1.Content.ReadAsStringAsync());
+ }
+ },
+ async server =>
+ {
+ // Accept first connection
+ await server.AcceptConnectionAsync(async connection =>
+ {
+ // Initial response
+ await connection.ReadRequestHeaderAndSendResponseAsync(content: s_simpleContent);
+
+ // Second response: Read request headers, then close connection
+ await connection.ReadRequestHeaderAsync();
+ });
+
+ // Client should reconnect. Accept that connection and send response.
+ await server.AcceptConnectionSendResponseAndCloseAsync(content: s_simpleContent);
+ });
+ }
+
+ [Fact]
+ public async Task PostAsyncExpect100Continue_FailsAfterContentSendStarted_Throws()
+ {
+ if (IsWinHttpHandler)
+ {
+ // WinHttpHandler does not support Expect: 100-continue.
+ return;
+ }
+
+ var contentSending = new TaskCompletionSource<bool>();
+ var connectionClosed = new TaskCompletionSource<bool>();
+
+ await LoopbackServer.CreateClientAndServerAsync(async url =>
+ {
+ using (HttpClient client = CreateHttpClient())
+ {
+ // Send initial request and receive response so connection is established
+ HttpResponseMessage response1 = await client.GetAsync(url);
+ Assert.Equal(HttpStatusCode.OK, response1.StatusCode);
+ Assert.Equal(s_simpleContent, await response1.Content.ReadAsStringAsync());
+
+ // Send second request on same connection. When the Expect: 100-continue timeout
+ // expires, the content will start to be serialized and will signal the server to
+ // close the connection; then once the connection is closed, the send will be allowed
+ // to continue and will fail.
+ var request = new HttpRequestMessage(HttpMethod.Post, url) { Version = VersionFromUseHttp2 };
+ request.Headers.ExpectContinue = true;
+ request.Content = new SynchronizedSendContent(contentSending, connectionClosed.Task);
+ await Assert.ThrowsAsync<HttpRequestException>(() => client.SendAsync(request));
+ }
+ },
+ async server =>
+ {
+ // Accept connection
+ await server.AcceptConnectionAsync(async connection =>
+ {
+ // Shut down the listen socket so no additional connections can happen
+ server.ListenSocket.Close();
+
+ // Initial response
+ await connection.ReadRequestHeaderAndSendResponseAsync(content: s_simpleContent);
+
+ // Second response: Read request headers, then close connection
+ List<string> lines = await connection.ReadRequestHeaderAsync();
+ Assert.Contains("Expect: 100-continue", lines);
+ await contentSending.Task;
+ });
+ connectionClosed.SetResult(true);
+ });
+ }
+
+ private sealed class SynchronizedSendContent : HttpContent
+ {
+ private readonly Task _connectionClosed;
+ private readonly TaskCompletionSource<bool> _sendingContent;
+
+ // The content needs to be large enough to force Expect: 100-Continue behavior in SocketsHttpHandler.
+ private readonly string _longContent = new String('a', 1025);
+
+ public SynchronizedSendContent(TaskCompletionSource<bool> sendingContent, Task connectionClosed)
+ {
+ _connectionClosed = connectionClosed;
+ _sendingContent = sendingContent;
+ }
+
+ protected override async Task SerializeToStreamAsync(Stream stream, TransportContext context)
+ {
+ _sendingContent.SetResult(true);
+ await _connectionClosed;
+ await stream.WriteAsync(Encoding.UTF8.GetBytes(_longContent));
+ }
+
+ protected override bool TryComputeLength(out long length)
+ {
+ length = _longContent.Length;
+ return true;
+ }
+ }
+ }
+}
--- /dev/null
+// 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.Collections.Generic;
+using System.Net.Test.Common;
+using System.Threading.Tasks;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace System.Net.Http.Functional.Tests
+{
+#if WINHTTPHANDLER_TEST
+ using HttpClientHandler = System.Net.Http.WinHttpClientHandler;
+#endif
+
+ public abstract class IdnaProtocolTests : HttpClientHandlerTestBase
+ {
+ protected abstract bool SupportsIdna { get; }
+
+ public IdnaProtocolTests(ITestOutputHelper output) : base(output) { }
+
+ [Theory]
+ [MemberData(nameof(InternationalHostNames))]
+ public async Task InternationalUrl_UsesIdnaEncoding_Success(string hostname)
+ {
+ if (!SupportsIdna)
+ {
+ return;
+ }
+
+ Uri uri = new Uri($"http://{hostname}/");
+
+ await LoopbackServer.CreateServerAsync(async (server, serverUrl) =>
+ {
+ // We don't actually want to do DNS lookup on the IDNA host name in the URL.
+ // So instead, configure the loopback server as a proxy so we will send to it.
+ HttpClientHandler handler = CreateHttpClientHandler();
+ handler.UseProxy = true;
+ handler.Proxy = new WebProxy(serverUrl.ToString());
+
+ using (HttpClient client = CreateHttpClient(handler))
+ {
+ Task<HttpResponseMessage> getResponseTask = client.GetAsync(uri);
+ Task<List<string>> serverTask = server.AcceptConnectionSendResponseAndCloseAsync();
+
+ await TestHelper.WhenAllCompletedOrAnyFailed(getResponseTask, serverTask);
+
+ List<string> requestLines = await serverTask;
+
+ // Note since we're using a proxy, host name is included in the request line
+ Assert.Equal($"GET http://{uri.IdnHost}/ HTTP/1.1", requestLines[0]);
+ Assert.Contains($"Host: {uri.IdnHost}", requestLines);
+ }
+ });
+ }
+
+ [ActiveIssue("https://github.com/dotnet/corefx/issues/26355")] // We aren't doing IDNA encoding properly
+ [Theory]
+ [MemberData(nameof(InternationalHostNames))]
+ public async Task InternationalRequestHeaderValues_UsesIdnaEncoding_Success(string hostname)
+ {
+ if (!SupportsIdna)
+ {
+ return;
+ }
+
+ Uri uri = new Uri($"http://{hostname}/");
+
+ await LoopbackServer.CreateServerAsync(async (server, serverUrl) =>
+ {
+ using (HttpClient client = CreateHttpClient())
+ {
+ var request = new HttpRequestMessage(HttpMethod.Get, serverUrl) { Version = VersionFromUseHttp2 };
+ request.Headers.Host = hostname;
+ request.Headers.Referrer = uri;
+ Task<HttpResponseMessage> getResponseTask = client.SendAsync(request);
+ Task<List<string>> serverTask = server.AcceptConnectionSendResponseAndCloseAsync();
+
+ await TestHelper.WhenAllCompletedOrAnyFailed(getResponseTask, serverTask);
+
+ List<string> requestLines = await serverTask;
+
+ Assert.Contains($"Host: {uri.IdnHost}", requestLines);
+ Assert.Contains($"Referer: http://{uri.IdnHost}/", requestLines);
+ }
+ });
+ }
+
+ [ActiveIssue("https://github.com/dotnet/corefx/issues/26355")] // We aren't doing IDNA decoding properly
+ [Theory]
+ [MemberData(nameof(InternationalHostNames))]
+ public async Task InternationalResponseHeaderValues_UsesIdnaDecoding_Success(string hostname)
+ {
+ if (!SupportsIdna)
+ {
+ return;
+ }
+
+ Uri uri = new Uri($"http://{hostname}/");
+
+ await LoopbackServer.CreateServerAsync(async (server, serverUrl) =>
+ {
+ HttpClientHandler handler = CreateHttpClientHandler();
+ handler.AllowAutoRedirect = false;
+
+ using (HttpClient client = CreateHttpClient(handler))
+ {
+ Task<HttpResponseMessage> getResponseTask = client.GetAsync(serverUrl);
+ Task<List<string>> serverTask = server.AcceptConnectionSendResponseAndCloseAsync(
+ HttpStatusCode.Found, "Location: http://{uri.IdnHost}/\r\n");
+
+ await TestHelper.WhenAllCompletedOrAnyFailed(getResponseTask, serverTask);
+
+ HttpResponseMessage response = await getResponseTask;
+
+ Assert.Equal(uri, response.Headers.Location);
+ }
+ });
+ }
+
+ public static IEnumerable<object[]> InternationalHostNames()
+ {
+ // Latin-1 supplement
+ yield return new object[] { "\u00E1.com" };
+ yield return new object[] { "\u00E1b\u00E7d\u00EB.com" };
+ yield return new object[] { "b\u00E7.com" };
+ yield return new object[] { "b\u00E7d.com" };
+
+ // Hebrew
+ yield return new object[] { "\u05E1.com" };
+ yield return new object[] { "\u05D1\u05F1.com" };
+
+ // Katakana
+ yield return new object[] { "\u30A5.com" };
+ yield return new object[] { "\u30B6\u30C7\u30D8.com" };
+ }
+ }
+}
--- /dev/null
+// 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.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace System.Net.Http.Functional.Tests
+{
+ using Configuration = System.Net.Test.Common.Configuration;
+
+#if WINHTTPHANDLER_TEST
+ using HttpClientHandler = System.Net.Http.WinHttpClientHandler;
+#endif
+
+ // Note: Disposing the HttpClient object automatically disposes the handler within. So, it is not necessary
+ // to separately Dispose (or have a 'using' statement) for the handler.
+ public abstract class PostScenarioTest : HttpClientHandlerTestBase
+ {
+ private const string ExpectedContent = "Test contest";
+ private const string UserName = "user1";
+ private const string Password = "password1";
+
+ public PostScenarioTest(ITestOutputHelper output) : base(output) { }
+
+ [OuterLoop("Uses external servers")]
+ [Theory, MemberData(nameof(RemoteServersMemberData))]
+ public async Task PostRewindableStreamContentMultipleTimes_StreamContentFullySent(Configuration.Http.RemoteServer remoteServer)
+ {
+ const string requestBody = "ABC";
+
+ using (HttpClient client = CreateHttpClientForRemoteServer(remoteServer))
+ using (var ms = new MemoryStream(Encoding.UTF8.GetBytes(requestBody)))
+ {
+ var content = new StreamContent(ms);
+
+ for (int i = 1; i <= 3; i++)
+ {
+ HttpResponseMessage response = await client.PostAsync(remoteServer.EchoUri, content);
+ Assert.Equal(requestBody.Length, ms.Position); // Stream left at end after send.
+
+ string responseBody = await response.Content.ReadAsStringAsync();
+ _output.WriteLine(responseBody);
+ Assert.True(TestHelper.JsonMessageContainsKeyValue(responseBody, "BodyContent", requestBody));
+ }
+ }
+ }
+
+ [OuterLoop("Uses external servers")]
+ [Theory, MemberData(nameof(RemoteServersMemberData))]
+ public async Task PostNoContentUsingContentLengthSemantics_Success(Configuration.Http.RemoteServer remoteServer)
+ {
+ await PostHelper(remoteServer, string.Empty, null,
+ useContentLengthUpload: true, useChunkedEncodingUpload: false);
+ }
+
+ [OuterLoop("Uses external servers")]
+ [Theory, MemberData(nameof(RemoteServersMemberData))]
+ public async Task PostEmptyContentUsingContentLengthSemantics_Success(Configuration.Http.RemoteServer remoteServer)
+ {
+ await PostHelper(remoteServer, string.Empty, new StringContent(string.Empty),
+ useContentLengthUpload: true, useChunkedEncodingUpload: false);
+ }
+
+ [OuterLoop("Uses external servers")]
+ [Theory, MemberData(nameof(RemoteServersMemberData))]
+ public async Task PostEmptyContentUsingChunkedEncoding_Success(Configuration.Http.RemoteServer remoteServer)
+ {
+ await PostHelper(remoteServer, string.Empty, new StringContent(string.Empty),
+ useContentLengthUpload: false, useChunkedEncodingUpload: true);
+ }
+
+ [OuterLoop("Uses external servers")]
+ [Theory, MemberData(nameof(RemoteServersMemberData))]
+ public async Task PostEmptyContentUsingConflictingSemantics_Success(Configuration.Http.RemoteServer remoteServer)
+ {
+ await PostHelper(remoteServer, string.Empty, new StringContent(string.Empty),
+ useContentLengthUpload: true, useChunkedEncodingUpload: true);
+ }
+
+ [OuterLoop("Uses external servers")]
+ [Theory, MemberData(nameof(RemoteServersMemberData))]
+ public async Task PostUsingContentLengthSemantics_Success(Configuration.Http.RemoteServer remoteServer)
+ {
+ await PostHelper(remoteServer, ExpectedContent, new StringContent(ExpectedContent),
+ useContentLengthUpload: true, useChunkedEncodingUpload: false);
+ }
+
+ [OuterLoop("Uses external servers")]
+ [Theory, MemberData(nameof(RemoteServersMemberData))]
+ public async Task PostUsingChunkedEncoding_Success(Configuration.Http.RemoteServer remoteServer)
+ {
+ await PostHelper(remoteServer, ExpectedContent, new StringContent(ExpectedContent),
+ useContentLengthUpload: false, useChunkedEncodingUpload: true);
+ }
+
+ [OuterLoop("Uses external servers")]
+ [Theory, MemberData(nameof(RemoteServersMemberData))]
+ public async Task PostSyncBlockingContentUsingChunkedEncoding_Success(Configuration.Http.RemoteServer remoteServer)
+ {
+ await PostHelper(remoteServer, ExpectedContent, new SyncBlockingContent(ExpectedContent),
+ useContentLengthUpload: false, useChunkedEncodingUpload: true);
+ }
+
+ [OuterLoop("Uses external servers")]
+ [Theory, MemberData(nameof(RemoteServersMemberData))]
+ public async Task PostRepeatedFlushContentUsingChunkedEncoding_Success(Configuration.Http.RemoteServer remoteServer)
+ {
+ await PostHelper(remoteServer, ExpectedContent, new RepeatedFlushContent(ExpectedContent),
+ useContentLengthUpload: false, useChunkedEncodingUpload: true);
+ }
+
+ [OuterLoop("Uses external servers")]
+ [Theory, MemberData(nameof(RemoteServersMemberData))]
+ public async Task PostUsingUsingConflictingSemantics_UsesChunkedSemantics(Configuration.Http.RemoteServer remoteServer)
+ {
+ await PostHelper(remoteServer, ExpectedContent, new StringContent(ExpectedContent),
+ useContentLengthUpload: true, useChunkedEncodingUpload: true);
+ }
+
+ [OuterLoop("Uses external servers")]
+ [Theory, MemberData(nameof(RemoteServersMemberData))]
+ public async Task PostUsingNoSpecifiedSemantics_UsesChunkedSemantics(Configuration.Http.RemoteServer remoteServer)
+ {
+ await PostHelper(remoteServer, ExpectedContent, new StringContent(ExpectedContent),
+ useContentLengthUpload: false, useChunkedEncodingUpload: false);
+ }
+
+ public static IEnumerable<object[]> RemoteServersAndLargeContentSizes()
+ {
+ foreach (Configuration.Http.RemoteServer remoteServer in Configuration.Http.RemoteServers)
+ {
+ yield return new object[] { remoteServer, 5 * 1024 };
+ yield return new object[] { remoteServer, 63 * 1024 };
+ yield return new object[] { remoteServer, 129 * 1024 };
+ }
+ }
+
+ [OuterLoop("Uses external server")]
+ [Theory]
+ [MemberData(nameof(RemoteServersAndLargeContentSizes))]
+ public async Task PostLargeContentUsingContentLengthSemantics_Success(Configuration.Http.RemoteServer remoteServer, int contentLength)
+ {
+ var rand = new Random(42);
+ var sb = new StringBuilder(contentLength);
+ for (int i = 0; i < contentLength; i++)
+ {
+ sb.Append((char)(rand.Next(0, 26) + 'a'));
+ }
+ string content = sb.ToString();
+
+ await PostHelper(remoteServer, content, new StringContent(content),
+ useContentLengthUpload: true, useChunkedEncodingUpload: false);
+ }
+
+ [OuterLoop("Uses external servers")]
+ [Theory, MemberData(nameof(RemoteServersMemberData))]
+ public async Task PostRewindableContentUsingAuth_NoPreAuthenticate_Success(Configuration.Http.RemoteServer remoteServer)
+ {
+ if (remoteServer.HttpVersion == new Version(2, 0))
+ {
+ // This is occasionally timing out in CI with SocketsHttpHandler and HTTP2, particularly on Linux
+ // Likely this is just a very slow test and not a product issue, so just increasing the timeout may be the right fix.
+ // Disable until we can investigate further.
+ return;
+ }
+
+ HttpContent content = new StreamContent(new CustomContent.CustomStream(Encoding.UTF8.GetBytes(ExpectedContent), true));
+ var credential = new NetworkCredential(UserName, Password);
+ await PostUsingAuthHelper(remoteServer, ExpectedContent, content, credential, false);
+ }
+
+ [OuterLoop("Uses external servers")]
+ [Theory, MemberData(nameof(RemoteServersMemberData))]
+ public async Task PostNonRewindableContentUsingAuth_NoPreAuthenticate_ThrowsHttpRequestException(Configuration.Http.RemoteServer remoteServer)
+ {
+ HttpContent content = new StreamContent(new CustomContent.CustomStream(Encoding.UTF8.GetBytes(ExpectedContent), false));
+ var credential = new NetworkCredential(UserName, Password);
+ await Assert.ThrowsAsync<HttpRequestException>(() =>
+ PostUsingAuthHelper(remoteServer, ExpectedContent, content, credential, preAuthenticate: false));
+ }
+
+ [OuterLoop("Uses external servers")]
+ [Theory, MemberData(nameof(RemoteServersMemberData))]
+ public async Task PostNonRewindableContentUsingAuth_PreAuthenticate_Success(Configuration.Http.RemoteServer remoteServer)
+ {
+ HttpContent content = new StreamContent(new CustomContent.CustomStream(Encoding.UTF8.GetBytes(ExpectedContent), false));
+ var credential = new NetworkCredential(UserName, Password);
+ await PostUsingAuthHelper(remoteServer, ExpectedContent, content, credential, preAuthenticate: true);
+ }
+
+ [OuterLoop("Uses external servers")]
+ [Theory, MemberData(nameof(RemoteServersMemberData))]
+ public async Task PostAsync_EmptyContent_ContentTypeHeaderNotSent(Configuration.Http.RemoteServer remoteServer)
+ {
+ using (HttpClient client = CreateHttpClientForRemoteServer(remoteServer))
+ using (HttpResponseMessage response = await client.PostAsync(remoteServer.EchoUri, null))
+ {
+ string responseContent = await response.Content.ReadAsStringAsync();
+ bool sentContentType = TestHelper.JsonMessageContainsKey(responseContent, "Content-Type");
+
+ Assert.False(sentContentType);
+ }
+ }
+
+ private async Task PostHelper(
+ Configuration.Http.RemoteServer remoteServer,
+ string requestBody,
+ HttpContent requestContent,
+ bool useContentLengthUpload,
+ bool useChunkedEncodingUpload)
+ {
+ using (HttpClient client = CreateHttpClientForRemoteServer(remoteServer))
+ {
+ if (requestContent != null)
+ {
+ if (useContentLengthUpload)
+ {
+ // Ensure that Content-Length is populated (see https://github.com/dotnet/corefx/issues/27245)
+ requestContent.Headers.ContentLength = requestContent.Headers.ContentLength;
+ }
+ else
+ {
+ requestContent.Headers.ContentLength = null;
+ }
+
+ // Compute MD5 of request body data. This will be verified by the server when it
+ // receives the request.
+ requestContent.Headers.ContentMD5 = TestHelper.ComputeMD5Hash(requestBody);
+ }
+
+ if (useChunkedEncodingUpload)
+ {
+ client.DefaultRequestHeaders.TransferEncodingChunked = true;
+ }
+
+ using (HttpResponseMessage response = await client.PostAsync(remoteServer.VerifyUploadUri, requestContent))
+ {
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ }
+ }
+ }
+
+ private async Task PostUsingAuthHelper(
+ Configuration.Http.RemoteServer remoteServer,
+ string requestBody,
+ HttpContent requestContent,
+ NetworkCredential credential,
+ bool preAuthenticate)
+ {
+ Uri serverUri = remoteServer.BasicAuthUriForCreds(UserName, Password);
+
+ HttpClientHandler handler = CreateHttpClientHandler();
+ handler.PreAuthenticate = preAuthenticate;
+ handler.Credentials = credential;
+ using (HttpClient client = CreateHttpClientForRemoteServer(remoteServer, handler))
+ {
+ // Send HEAD request to help bypass the 401 auth challenge for the latter POST assuming
+ // that the authentication will be cached and re-used later when PreAuthenticate is true.
+ var request = new HttpRequestMessage(HttpMethod.Head, serverUri) { Version = remoteServer.HttpVersion };
+ using (HttpResponseMessage response = await client.SendAsync(request))
+ {
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ }
+
+ // Now send POST request.
+ request = new HttpRequestMessage(HttpMethod.Post, serverUri) { Version = remoteServer.HttpVersion };
+ request.Content = requestContent;
+ requestContent.Headers.ContentLength = null;
+ request.Headers.TransferEncodingChunked = true;
+
+ using (HttpResponseMessage response = await client.SendAsync(request))
+ {
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ string responseContent = await response.Content.ReadAsStringAsync();
+ _output.WriteLine(responseContent);
+
+ TestHelper.VerifyResponseBody(
+ responseContent,
+ response.Content.Headers.ContentMD5,
+ true,
+ requestBody);
+ }
+ }
+ }
+ }
+}
--- /dev/null
+// 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;
+using System.IO;
+using System.Threading.Tasks;
+
+namespace System.Net.Http.Functional.Tests
+{
+ public sealed class RepeatedFlushContent : StringContent
+ {
+ public RepeatedFlushContent(string content) : base(content)
+ {
+ }
+
+ protected override Task SerializeToStreamAsync(Stream stream, TransportContext context)
+ {
+ stream.Flush();
+ stream.Flush();
+ return base.SerializeToStreamAsync(stream, context);
+ }
+ }
+}
--- /dev/null
+// 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.Collections.Generic;
+using System.IO;
+using System.Net.Test.Common;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+
+using Xunit;
+using Xunit.Abstractions;
+
+namespace System.Net.Http.Functional.Tests
+{
+ using Configuration = System.Net.Test.Common.Configuration;
+
+ public abstract class ResponseStreamTest : HttpClientHandlerTestBase
+ {
+ public ResponseStreamTest(ITestOutputHelper output) : base(output) { }
+
+ public static IEnumerable<object[]> RemoteServersAndReadModes()
+ {
+ foreach (Configuration.Http.RemoteServer remoteServer in Configuration.Http.RemoteServers)
+ {
+ for (int i = 0; i < 8; i++)
+ {
+ yield return new object[] { remoteServer, i };
+ }
+ }
+
+ }
+ [OuterLoop("Uses external server")]
+ [Theory, MemberData(nameof(RemoteServersAndReadModes))]
+ public async Task GetStreamAsync_ReadToEnd_Success(Configuration.Http.RemoteServer remoteServer, int readMode)
+ {
+ using (HttpClient client = CreateHttpClientForRemoteServer(remoteServer))
+ {
+ string customHeaderValue = Guid.NewGuid().ToString("N");
+ client.DefaultRequestHeaders.Add("X-ResponseStreamTest", customHeaderValue);
+
+ using (Stream stream = await client.GetStreamAsync(remoteServer.EchoUri))
+ {
+ var ms = new MemoryStream();
+ int bytesRead;
+ var buffer = new byte[10];
+ string responseBody;
+
+ // Read all of the response content in various ways
+ switch (readMode)
+ {
+ case 0:
+ // StreamReader.ReadToEnd
+ responseBody = new StreamReader(stream).ReadToEnd();
+ break;
+
+ case 1:
+ // StreamReader.ReadToEndAsync
+ responseBody = await new StreamReader(stream).ReadToEndAsync();
+ break;
+
+ case 2:
+ // Individual calls to Read(Array)
+ while ((bytesRead = stream.Read(buffer, 0, buffer.Length)) != 0)
+ {
+ ms.Write(buffer, 0, bytesRead);
+ }
+ responseBody = Encoding.UTF8.GetString(ms.ToArray());
+ break;
+
+ case 3:
+ // Individual calls to ReadAsync(Array)
+ while ((bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length)) != 0)
+ {
+ ms.Write(buffer, 0, bytesRead);
+ }
+ responseBody = Encoding.UTF8.GetString(ms.ToArray());
+ break;
+
+ case 4:
+ // Individual calls to Read(Span)
+ while ((bytesRead = stream.Read(new Span<byte>(buffer))) != 0)
+ {
+ ms.Write(buffer, 0, bytesRead);
+ }
+ responseBody = Encoding.UTF8.GetString(ms.ToArray());
+ break;
+
+ case 5:
+ // ReadByte
+ int byteValue;
+ while ((byteValue = stream.ReadByte()) != -1)
+ {
+ ms.WriteByte((byte)byteValue);
+ }
+ responseBody = Encoding.UTF8.GetString(ms.ToArray());
+ break;
+
+ case 6:
+ // CopyTo
+ stream.CopyTo(ms);
+ responseBody = Encoding.UTF8.GetString(ms.ToArray());
+ break;
+
+ case 7:
+ // CopyToAsync
+ await stream.CopyToAsync(ms);
+ responseBody = Encoding.UTF8.GetString(ms.ToArray());
+ break;
+
+ default:
+ throw new Exception($"Unexpected test mode {readMode}");
+ }
+
+ // Calling GetStreamAsync() means we don't have access to the HttpResponseMessage.
+ // So, we can't use the MD5 hash validation to verify receipt of the response body.
+ // For this test, we can use a simpler verification of a custom header echo'ing back.
+ _output.WriteLine(responseBody);
+ Assert.Contains(customHeaderValue, responseBody);
+ }
+ }
+ }
+
+ [OuterLoop("Uses external server")]
+ [Theory, MemberData(nameof(RemoteServersMemberData))]
+ public async Task GetAsync_UseResponseHeadersReadAndCallLoadIntoBuffer_Success(Configuration.Http.RemoteServer remoteServer)
+ {
+ using (HttpClient client = CreateHttpClientForRemoteServer(remoteServer))
+ using (HttpResponseMessage response = await client.GetAsync(remoteServer.EchoUri, HttpCompletionOption.ResponseHeadersRead))
+ {
+ await response.Content.LoadIntoBufferAsync();
+
+ string responseBody = await response.Content.ReadAsStringAsync();
+ _output.WriteLine(responseBody);
+ TestHelper.VerifyResponseBody(
+ responseBody,
+ response.Content.Headers.ContentMD5,
+ false,
+ null);
+ }
+ }
+
+ [OuterLoop("Uses external server")]
+ [Theory, MemberData(nameof(RemoteServersMemberData))]
+ public async Task GetAsync_UseResponseHeadersReadAndCopyToMemoryStream_Success(Configuration.Http.RemoteServer remoteServer)
+ {
+ using (HttpClient client = CreateHttpClientForRemoteServer(remoteServer))
+ using (HttpResponseMessage response = await client.GetAsync(remoteServer.EchoUri, HttpCompletionOption.ResponseHeadersRead))
+ {
+ var memoryStream = new MemoryStream();
+ await response.Content.CopyToAsync(memoryStream);
+ memoryStream.Position = 0;
+
+ using (var reader = new StreamReader(memoryStream))
+ {
+ string responseBody = reader.ReadToEnd();
+ _output.WriteLine(responseBody);
+ TestHelper.VerifyResponseBody(
+ responseBody,
+ response.Content.Headers.ContentMD5,
+ false,
+ null);
+ }
+ }
+ }
+
+ [OuterLoop("Uses external server")]
+ [Theory, MemberData(nameof(RemoteServersMemberData))]
+ public async Task GetStreamAsync_ReadZeroBytes_Success(Configuration.Http.RemoteServer remoteServer)
+ {
+ using (HttpClient client = CreateHttpClientForRemoteServer(remoteServer))
+ using (Stream stream = await client.GetStreamAsync(remoteServer.EchoUri))
+ {
+ Assert.Equal(0, stream.Read(new byte[1], 0, 0));
+ Assert.Equal(0, stream.Read(new Span<byte>(new byte[1], 0, 0)));
+ Assert.Equal(0, await stream.ReadAsync(new byte[1], 0, 0));
+ }
+ }
+
+ [OuterLoop("Uses external server")]
+ [Theory, MemberData(nameof(RemoteServersMemberData))]
+ public async Task ReadAsStreamAsync_Cancel_TaskIsCanceled(Configuration.Http.RemoteServer remoteServer)
+ {
+ var cts = new CancellationTokenSource();
+
+ using (HttpClient client = CreateHttpClientForRemoteServer(remoteServer))
+ using (HttpResponseMessage response =
+ await client.GetAsync(remoteServer.EchoUri, HttpCompletionOption.ResponseHeadersRead))
+ using (Stream stream = await response.Content.ReadAsStreamAsync())
+ {
+ var buffer = new byte[2048];
+ Task task = stream.ReadAsync(buffer, 0, buffer.Length, cts.Token);
+ cts.Cancel();
+
+ // Verify that the task completed.
+ Assert.True(((IAsyncResult)task).AsyncWaitHandle.WaitOne(new TimeSpan(0, 5, 0)));
+ Assert.True(task.IsCompleted, "Task was not yet completed");
+
+ // Verify that the task completed successfully or is canceled.
+ if (IsWinHttpHandler)
+ {
+ // With WinHttpHandler, we may fault because canceling the task destroys the request handle
+ // which may randomly cause an ObjectDisposedException (or other exception).
+ Assert.True(
+ task.Status == TaskStatus.RanToCompletion ||
+ task.Status == TaskStatus.Canceled ||
+ task.Status == TaskStatus.Faulted);
+ }
+ else
+ {
+ if (task.IsFaulted)
+ {
+ // Propagate exception for debugging
+ task.GetAwaiter().GetResult();
+ }
+
+ Assert.True(
+ task.Status == TaskStatus.RanToCompletion ||
+ task.Status == TaskStatus.Canceled);
+ }
+ }
+ }
+
+ [Theory]
+ [InlineData(TransferType.ContentLength, TransferError.ContentLengthTooLarge)]
+ [InlineData(TransferType.Chunked, TransferError.MissingChunkTerminator)]
+ [InlineData(TransferType.Chunked, TransferError.ChunkSizeTooLarge)]
+ public async Task ReadAsStreamAsync_InvalidServerResponse_ThrowsIOException(
+ TransferType transferType,
+ TransferError transferError)
+ {
+ await StartTransferTypeAndErrorServer(transferType, transferError, async uri =>
+ {
+ await Assert.ThrowsAsync<IOException>(() => ReadAsStreamHelper(uri));
+ });
+ }
+
+ [Theory]
+ [InlineData(TransferType.None, TransferError.None)]
+ [InlineData(TransferType.ContentLength, TransferError.None)]
+ [InlineData(TransferType.Chunked, TransferError.None)]
+ public async Task ReadAsStreamAsync_ValidServerResponse_Success(
+ TransferType transferType,
+ TransferError transferError)
+ {
+ await StartTransferTypeAndErrorServer(transferType, transferError, async uri =>
+ {
+ await ReadAsStreamHelper(uri);
+ });
+ }
+
+ public enum TransferType
+ {
+ None = 0,
+ ContentLength,
+ Chunked
+ }
+
+ public enum TransferError
+ {
+ None = 0,
+ ContentLengthTooLarge,
+ ChunkSizeTooLarge,
+ MissingChunkTerminator
+ }
+
+ public static Task StartTransferTypeAndErrorServer(
+ TransferType transferType,
+ TransferError transferError,
+ Func<Uri, Task> clientFunc)
+ {
+ return LoopbackServer.CreateClientAndServerAsync(
+ clientFunc,
+ server => server.AcceptConnectionAsync(async connection =>
+ {
+ // Read past request headers.
+ await connection.ReadRequestHeaderAsync();
+
+ // Determine response transfer headers.
+ string transferHeader = null;
+ string content = "This is some response content.";
+ if (transferType == TransferType.ContentLength)
+ {
+ transferHeader = transferError == TransferError.ContentLengthTooLarge ?
+ $"Content-Length: {content.Length + 42}\r\n" :
+ $"Content-Length: {content.Length}\r\n";
+ }
+ else if (transferType == TransferType.Chunked)
+ {
+ transferHeader = "Transfer-Encoding: chunked\r\n";
+ }
+
+ // Write response header
+ TextWriter writer = connection.Writer;
+ await writer.WriteAsync("HTTP/1.1 200 OK\r\n").ConfigureAwait(false);
+ await writer.WriteAsync($"Date: {DateTimeOffset.UtcNow:R}\r\n").ConfigureAwait(false);
+ await writer.WriteAsync("Content-Type: text/plain\r\n").ConfigureAwait(false);
+ if (!string.IsNullOrEmpty(transferHeader))
+ {
+ await writer.WriteAsync(transferHeader).ConfigureAwait(false);
+ }
+ await writer.WriteAsync("\r\n").ConfigureAwait(false);
+
+ // Write response body
+ if (transferType == TransferType.Chunked)
+ {
+ string chunkSizeInHex = string.Format(
+ "{0:x}\r\n",
+ content.Length + (transferError == TransferError.ChunkSizeTooLarge ? 42 : 0));
+ await writer.WriteAsync(chunkSizeInHex).ConfigureAwait(false);
+ await writer.WriteAsync($"{content}\r\n").ConfigureAwait(false);
+ if (transferError != TransferError.MissingChunkTerminator)
+ {
+ await writer.WriteAsync("0\r\n\r\n").ConfigureAwait(false);
+ }
+ }
+ else
+ {
+ await writer.WriteAsync($"{content}").ConfigureAwait(false);
+ }
+ }));
+ }
+
+ private async Task ReadAsStreamHelper(Uri serverUri)
+ {
+ using (HttpClient client = CreateHttpClient())
+ {
+ using (var response = await client.GetAsync(
+ serverUri,
+ HttpCompletionOption.ResponseHeadersRead))
+ using (var stream = await response.Content.ReadAsStreamAsync())
+ {
+ var buffer = new byte[1];
+ while (await stream.ReadAsync(buffer, 0, 1) > 0) ;
+ }
+ }
+ }
+ }
+}
--- /dev/null
+// 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.Test.Common;
+using System.Security.Authentication;
+using System.Threading.Tasks;
+
+using Xunit;
+using Xunit.Abstractions;
+
+namespace System.Net.Http.Functional.Tests
+{
+#if WINHTTPHANDLER_TEST
+ using HttpClientHandler = System.Net.Http.WinHttpClientHandler;
+#endif
+
+ [ActiveIssue("https://github.com/dotnet/corefx/issues/26539")] // Flaky test
+ public abstract class SchSendAuxRecordHttpTest : HttpClientHandlerTestBase
+ {
+ public SchSendAuxRecordHttpTest(ITestOutputHelper output) : base(output) { }
+
+ [Fact]
+ [PlatformSpecific(TestPlatforms.Windows)]
+ public async Task HttpClient_ClientUsesAuxRecord_Ok()
+ {
+ var options = new HttpsTestServer.Options();
+ options.AllowedProtocols = SslProtocols.Tls;
+
+ using (var server = new HttpsTestServer(options))
+ using (HttpClientHandler handler = CreateHttpClientHandler())
+ using (HttpClient client = CreateHttpClient(handler))
+ {
+ handler.ServerCertificateCustomValidationCallback = TestHelper.AllowAllCertificates;
+ server.Start();
+
+ var tasks = new Task[2];
+
+ bool serverAuxRecordDetected = false;
+ bool serverAuxRecordDetectedInconclusive = false;
+ int serverTotalBytesReceived = 0;
+ int serverChunks = 0;
+
+ tasks[0] = server.AcceptHttpsClientAsync((requestString) =>
+ {
+ serverTotalBytesReceived += requestString.Length;
+
+ if (serverTotalBytesReceived == 1 && serverChunks == 0)
+ {
+ serverAuxRecordDetected = true;
+ }
+
+ serverChunks++;
+
+ // Test is inconclusive if any non-CBC cipher is used:
+ if (server.Stream.CipherAlgorithm == CipherAlgorithmType.None ||
+ server.Stream.CipherAlgorithm == CipherAlgorithmType.Null ||
+ server.Stream.CipherAlgorithm == CipherAlgorithmType.Rc4)
+ {
+ serverAuxRecordDetectedInconclusive = true;
+ }
+
+ if (serverTotalBytesReceived < 5)
+ {
+ return Task.FromResult<string>(null);
+ }
+ else
+ {
+ return Task.FromResult(HttpsTestServer.Options.DefaultResponseString);
+ }
+ });
+
+ string requestUriString = "https://localhost:" + server.Port.ToString();
+ tasks[1] = client.GetStringAsync(requestUriString);
+
+ await tasks.WhenAllOrAnyFailed(15 * 1000);
+
+ if (serverAuxRecordDetectedInconclusive)
+ {
+ _output.WriteLine("Test inconclusive: The Operating system preferred a non-CBC or Null cipher.");
+ }
+ else
+ {
+ Assert.True(serverAuxRecordDetected, "Server reports: Client auxiliary record not detected.");
+ }
+ }
+ }
+ }
+}
--- /dev/null
+// 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;
+using System.IO;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace System.Net.Http.Functional.Tests
+{
+ public sealed class SyncBlockingContent : StringContent
+ {
+ byte[] _content;
+
+ public SyncBlockingContent(string content) : base(content)
+ {
+ _content = Encoding.UTF8.GetBytes(content);
+ }
+
+ protected override Task SerializeToStreamAsync(Stream stream, TransportContext context)
+ {
+ stream.Write(_content, 0, _content.Length);
+ return Task.CompletedTask;
+ }
+ }
+}
--- /dev/null
+// 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.Collections.Generic;
+using System.Linq;
+using System.Net.NetworkInformation;
+using System.Net.Security;
+using System.Net.Test.Common;
+using System.Reflection;
+using System.Runtime.InteropServices;
+using System.Security.Cryptography;
+using System.Security.Cryptography.X509Certificates;
+using System.Text;
+using System.Threading.Tasks;
+using Xunit;
+
+namespace System.Net.Http.Functional.Tests
+{
+ public static class TestHelper
+ {
+ public static int PassingTestTimeoutMilliseconds => 60 * 1000;
+ public static bool JsonMessageContainsKeyValue(string message, string key, string value)
+ {
+ // Deal with JSON encoding of '\' and '"' in value
+ value = value.Replace("\\", "\\\\").Replace("\"", "\\\"");
+
+ // In HTTP2, all header names are in lowercase. So accept either the original header name or the lowercase version.
+ return message.Contains($"\"{key}\": \"{value}\"") ||
+ message.Contains($"\"{key.ToLowerInvariant()}\": \"{value}\"");
+ }
+
+ public static bool JsonMessageContainsKey(string message, string key)
+ {
+ return JsonMessageContainsKeyValue(message, key, "");
+ }
+
+ public static void VerifyResponseBody(
+ string responseContent,
+ byte[] expectedMD5Hash,
+ bool chunkedUpload,
+ string requestBody)
+ {
+ // Verify that response body from the server was corrected received by comparing MD5 hash.
+ byte[] actualMD5Hash = ComputeMD5Hash(responseContent);
+ Assert.Equal(expectedMD5Hash, actualMD5Hash);
+
+ // Verify upload semantics: 'Content-Length' vs. 'Transfer-Encoding: chunked'.
+ if (requestBody != null)
+ {
+ bool requestUsedContentLengthUpload =
+ JsonMessageContainsKeyValue(responseContent, "Content-Length", requestBody.Length.ToString());
+ bool requestUsedChunkedUpload =
+ JsonMessageContainsKeyValue(responseContent, "Transfer-Encoding", "chunked");
+ if (requestBody.Length > 0)
+ {
+ Assert.NotEqual(requestUsedContentLengthUpload, requestUsedChunkedUpload);
+ Assert.Equal(chunkedUpload, requestUsedChunkedUpload);
+ Assert.Equal(!chunkedUpload, requestUsedContentLengthUpload);
+ }
+
+ // Verify that request body content was correctly sent to server.
+ Assert.True(JsonMessageContainsKeyValue(responseContent, "BodyContent", requestBody), "Valid request body");
+ }
+ }
+
+ public static void VerifyRequestMethod(HttpResponseMessage response, string expectedMethod)
+ {
+ IEnumerable<string> values = response.Headers.GetValues("X-HttpRequest-Method");
+ foreach (string value in values)
+ {
+ Assert.Equal(expectedMethod, value);
+ }
+ }
+
+ public static byte[] ComputeMD5Hash(string data)
+ {
+ return ComputeMD5Hash(Encoding.UTF8.GetBytes(data));
+ }
+
+ public static byte[] ComputeMD5Hash(byte[] data)
+ {
+ using (MD5 md5 = MD5.Create())
+ {
+ return md5.ComputeHash(data);
+ }
+ }
+
+ public static Task WhenAllCompletedOrAnyFailed(params Task[] tasks)
+ {
+ return TaskTimeoutExtensions.WhenAllOrAnyFailed(tasks, PlatformDetection.IsArmProcess || PlatformDetection.IsArm64Process ? PassingTestTimeoutMilliseconds * 5 : PassingTestTimeoutMilliseconds);
+ }
+
+ public static Task WhenAllCompletedOrAnyFailedWithTimeout(int timeoutInMilliseconds, params Task[] tasks)
+ {
+ return TaskTimeoutExtensions.WhenAllOrAnyFailed(tasks, timeoutInMilliseconds);
+ }
+
+#if NETCOREAPP
+ public static Func<HttpRequestMessage, X509Certificate2, X509Chain, SslPolicyErrors, bool> AllowAllCertificates = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator;
+#else
+ public static Func<HttpRequestMessage, X509Certificate2, X509Chain, SslPolicyErrors, bool> AllowAllCertificates = (_, __, ___, ____) => true;
+#endif
+
+ public static IPAddress GetIPv6LinkLocalAddress() =>
+ NetworkInterface
+ .GetAllNetworkInterfaces()
+ .Where(i => !i.Description.StartsWith("PANGP Virtual Ethernet")) // This is a VPN adapter, but is reported as a regular Ethernet interface with
+ // a valid link-local address, but the link-local address doesn't actually work.
+ // So just manually filter it out.
+ .SelectMany(i => i.GetIPProperties().UnicastAddresses)
+ .Select(a => a.Address)
+ .Where(a => a.IsIPv6LinkLocal)
+ .FirstOrDefault();
+
+ public static void EnableUnencryptedHttp2IfNecessary(HttpClientHandler handler)
+ {
+ if (PlatformDetection.SupportsAlpn && !Capability.Http2ForceUnencryptedLoopback())
+ {
+ return;
+ }
+
+ FieldInfo socketsHttpHandlerField = typeof(HttpClientHandler).GetField("_socketsHttpHandler", BindingFlags.NonPublic | BindingFlags.Instance);
+ if (socketsHttpHandlerField == null)
+ {
+ // Not using .NET Core implementation, i.e. could be .NET Framework.
+ return;
+ }
+
+ object socketsHttpHandler = socketsHttpHandlerField.GetValue(handler);
+ Assert.NotNull(socketsHttpHandler);
+
+ // Get HttpConnectionSettings object from SocketsHttpHandler.
+ Type socketsHttpHandlerType = typeof(HttpClientHandler).Assembly.GetType("System.Net.Http.SocketsHttpHandler");
+ FieldInfo settingsField = socketsHttpHandlerType.GetField("_settings", BindingFlags.NonPublic | BindingFlags.Instance);
+ Assert.NotNull(settingsField);
+ object settings = settingsField.GetValue(socketsHttpHandler);
+ Assert.NotNull(settings);
+
+ // Allow HTTP/2.0 via unencrypted socket if ALPN is not supported on platform.
+ Type httpConnectionSettingsType = typeof(HttpClientHandler).Assembly.GetType("System.Net.Http.HttpConnectionSettings");
+ FieldInfo allowUnencryptedHttp2Field = httpConnectionSettingsType.GetField("_allowUnencryptedHttp2", BindingFlags.NonPublic | BindingFlags.Instance);
+ Assert.NotNull(allowUnencryptedHttp2Field);
+ allowUnencryptedHttp2Field.SetValue(settings, true);
+ }
+
+ public static byte[] GenerateRandomContent(int size)
+ {
+ byte[] data = new byte[size];
+ new Random(42).NextBytes(data);
+ return data;
+ }
+ }
+}
--- /dev/null
+// 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;
+
+namespace System.Net.Http.Functional.Tests
+{
+ public abstract partial class HttpClientHandlerTestBase : FileCleanupTestBase
+ {
+ protected static bool IsWinHttpHandler => true;
+
+ protected WinHttpClientHandler CreateHttpClientHandler() => CreateHttpClientHandler(UseHttp2);
+
+ protected static WinHttpClientHandler CreateHttpClientHandler(string useHttp2LoopbackServerString) =>
+ CreateHttpClientHandler(bool.Parse(useHttp2LoopbackServerString));
+
+ protected static WinHttpClientHandler CreateHttpClientHandler(bool useHttp2LoopbackServer = false)
+ {
+ WinHttpClientHandler handler = new WinHttpClientHandler();
+
+ if (useHttp2LoopbackServer)
+ {
+ handler.ServerCertificateCustomValidationCallback = TestHelper.AllowAllCertificates;
+ }
+
+ return handler;
+ }
+ }
+}
\ No newline at end of file
--- /dev/null
+// 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.Collections.Generic;
+using System.Net.Test.Common;
+using System.Threading.Tasks;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace System.Net.Http.Functional.Tests
+{
+ public class PlatformHandler_HttpClientHandler : HttpClientHandlerTestBase
+ {
+ public PlatformHandler_HttpClientHandler(ITestOutputHelper output) : base(output) { }
+
+ [Theory]
+ [InlineData(false)]
+ [InlineData(true)]
+ public async Task GetAsync_TrailingHeaders_Ignored(bool includeTrailerHeader)
+ {
+ await LoopbackServer.CreateServerAsync(async (server, url) =>
+ {
+ using (HttpClient client = CreateHttpClient(new WinHttpHandler()))
+ {
+ Task<HttpResponseMessage> getResponseTask = client.GetAsync(url);
+ await TestHelper.WhenAllCompletedOrAnyFailed(
+ getResponseTask,
+ server.AcceptConnectionSendCustomResponseAndCloseAsync(
+ "HTTP/1.1 200 OK\r\n" +
+ "Connection: close\r\n" +
+ "Transfer-Encoding: chunked\r\n" +
+ (includeTrailerHeader ? "Trailer: MyCoolTrailerHeader\r\n" : "") +
+ "\r\n" +
+ "4\r\n" +
+ "data\r\n" +
+ "0\r\n" +
+ "MyCoolTrailerHeader: amazingtrailer\r\n" +
+ "\r\n"));
+
+ using (HttpResponseMessage response = await getResponseTask)
+ {
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ if (includeTrailerHeader)
+ {
+ Assert.Contains("MyCoolTrailerHeader", response.Headers.GetValues("Trailer"));
+ }
+ Assert.False(response.Headers.Contains("MyCoolTrailerHeader"), "Trailer should have been ignored");
+
+ string data = await response.Content.ReadAsStringAsync();
+ Assert.Contains("data", data);
+ Assert.DoesNotContain("MyCoolTrailerHeader", data);
+ Assert.DoesNotContain("amazingtrailer", data);
+ }
+ }
+ });
+ }
+ }
+
+ public sealed class PlatformHandler_HttpClientHandler_Asynchrony_Test : HttpClientHandler_Asynchrony_Test
+ {
+ public PlatformHandler_HttpClientHandler_Asynchrony_Test(ITestOutputHelper output) : base(output) { }
+ }
+
+ public sealed class PlatformHandler_HttpProtocolTests : HttpProtocolTests
+ {
+ public PlatformHandler_HttpProtocolTests(ITestOutputHelper output) : base(output) { }
+ }
+
+ public sealed class PlatformHandler_HttpProtocolTests_Dribble : HttpProtocolTests_Dribble
+ {
+ public PlatformHandler_HttpProtocolTests_Dribble(ITestOutputHelper output) : base(output) { }
+ }
+
+ public sealed class PlatformHandler_HttpClient_SelectedSites_Test : HttpClient_SelectedSites_Test
+ {
+ public PlatformHandler_HttpClient_SelectedSites_Test(ITestOutputHelper output) : base(output) { }
+ }
+
+ public sealed class PlatformHandler_HttpClientEKUTest : HttpClientEKUTest
+ {
+ public PlatformHandler_HttpClientEKUTest(ITestOutputHelper output) : base(output) { }
+ }
+
+#if NETCOREAPP
+ public sealed class PlatformHandler_HttpClientHandler_Decompression_Tests : HttpClientHandler_Decompression_Test
+ {
+ public PlatformHandler_HttpClientHandler_Decompression_Tests(ITestOutputHelper output) : base(output) { }
+ }
+
+ public sealed class PlatformHandler_HttpClientHandler_DangerousAcceptAllCertificatesValidator_Test : HttpClientHandler_DangerousAcceptAllCertificatesValidator_Test
+ {
+ public PlatformHandler_HttpClientHandler_DangerousAcceptAllCertificatesValidator_Test(ITestOutputHelper output) : base(output) { }
+ }
+#endif
+
+ public sealed class PlatformHandler_HttpClientHandler_ClientCertificates_Test : HttpClientHandler_ClientCertificates_Test
+ {
+ public PlatformHandler_HttpClientHandler_ClientCertificates_Test(ITestOutputHelper output) : base(output) { }
+ }
+
+ public sealed class PlatformHandler_HttpClientHandler_DefaultProxyCredentials_Test : HttpClientHandler_DefaultProxyCredentials_Test
+ {
+ public PlatformHandler_HttpClientHandler_DefaultProxyCredentials_Test(ITestOutputHelper output) : base(output) { }
+ }
+
+ public sealed class PlatformHandler_HttpClientHandler_MaxConnectionsPerServer_Test : HttpClientHandler_MaxConnectionsPerServer_Test
+ {
+ public PlatformHandler_HttpClientHandler_MaxConnectionsPerServer_Test(ITestOutputHelper output) : base(output) { }
+ }
+
+ public sealed class PlatformHandler_HttpClientHandler_ServerCertificates_Test : HttpClientHandler_ServerCertificates_Test
+ {
+ public PlatformHandler_HttpClientHandler_ServerCertificates_Test(ITestOutputHelper output) : base(output) { }
+ }
+
+ public sealed class PlatformHandler_PostScenarioTest : PostScenarioTest
+ {
+ public PlatformHandler_PostScenarioTest(ITestOutputHelper output) : base(output) { }
+ }
+
+ public sealed class PlatformHandler_ResponseStreamTest : ResponseStreamTest
+ {
+ public PlatformHandler_ResponseStreamTest(ITestOutputHelper output) : base(output) { }
+ }
+
+ public sealed class PlatformHandler_HttpClientHandler_SslProtocols_Test : HttpClientHandler_SslProtocols_Test
+ {
+ public PlatformHandler_HttpClientHandler_SslProtocols_Test(ITestOutputHelper output) : base(output) { }
+ }
+
+ public sealed class PlatformHandler_HttpClientHandler_Proxy_Test : HttpClientHandler_Proxy_Test
+ {
+ public PlatformHandler_HttpClientHandler_Proxy_Test(ITestOutputHelper output) : base(output) { }
+ }
+
+ public sealed class PlatformHandler_SchSendAuxRecordHttpTest : SchSendAuxRecordHttpTest
+ {
+ public PlatformHandler_SchSendAuxRecordHttpTest(ITestOutputHelper output) : base(output) { }
+ }
+
+ public sealed class PlatformHandler_HttpClientHandlerTest : HttpClientHandlerTest
+ {
+ public PlatformHandler_HttpClientHandlerTest(ITestOutputHelper output) : base(output) { }
+ }
+
+ public sealed class PlatformHandlerTest_AutoRedirect : HttpClientHandlerTest_AutoRedirect
+ {
+ public PlatformHandlerTest_AutoRedirect(ITestOutputHelper output) : base(output) { }
+ }
+
+ public sealed class PlatformHandler_DefaultCredentialsTest : DefaultCredentialsTest
+ {
+ public PlatformHandler_DefaultCredentialsTest(ITestOutputHelper output) : base(output) { }
+ }
+
+ public sealed class PlatformHandler_IdnaProtocolTests : IdnaProtocolTests
+ {
+ public PlatformHandler_IdnaProtocolTests(ITestOutputHelper output) : base(output) { }
+ // WinHttp on Win7 does not support IDNA
+ protected override bool SupportsIdna => !PlatformDetection.IsWindows7;
+ }
+
+ public sealed class PlatformHandler_HttpRetryProtocolTests : HttpRetryProtocolTests
+ {
+ public PlatformHandler_HttpRetryProtocolTests(ITestOutputHelper output) : base(output) { }
+ }
+
+ public sealed class PlatformHandlerTest_Cookies : HttpClientHandlerTest_Cookies
+ {
+ public PlatformHandlerTest_Cookies(ITestOutputHelper output) : base(output) { }
+ }
+
+ public sealed class PlatformHandlerTest_Cookies_Http11 : HttpClientHandlerTest_Cookies_Http11
+ {
+ public PlatformHandlerTest_Cookies_Http11(ITestOutputHelper output) : base(output) { }
+ }
+
+ public sealed class PlatformHandler_HttpClientHandler_MaxResponseHeadersLength_Test : HttpClientHandler_MaxResponseHeadersLength_Test
+ {
+ public PlatformHandler_HttpClientHandler_MaxResponseHeadersLength_Test(ITestOutputHelper output) : base(output) { }
+ }
+
+ public sealed class PlatformHandler_HttpClientHandler_Cancellation_Test : HttpClientHandler_Cancellation_Test
+ {
+ public PlatformHandler_HttpClientHandler_Cancellation_Test(ITestOutputHelper output) : base(output) { }
+ }
+
+ public sealed class PlatformHandler_HttpClientHandler_Authentication_Test : HttpClientHandler_Authentication_Test
+ {
+ 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
+ {
+ }
+
+ [ConditionalClass(typeof(PlatformDetection), nameof(PlatformDetection.SupportsAlpn))]
+ public sealed class PlatformHandlerTest_Cookies_Http2 : HttpClientHandlerTest_Cookies
+ {
+ protected override bool UseHttp2LoopbackServer => true;
+ }
+#endif
+}
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Configurations>$(NetCoreAppCurrent)-Windows_NT-Debug;$(NetCoreAppCurrent)-Windows_NT-Release;$(NetFrameworkCurrent)-Windows_NT-Debug;$(NetFrameworkCurrent)-Windows_NT-Release</Configurations>
+ <IncludeRemoteExecutor>true</IncludeRemoteExecutor>
+ <DefineConstants>$(DefineConstants);WINHTTPHANDLER_TEST</DefineConstants>
</PropertyGroup>
<ItemGroup Condition=" '$(TargetsWindows)' == 'true' ">
<Compile Include="$(CommonTestPath)System\Net\Configuration.cs">
<Compile Include="ServerCertificateTest.cs" />
<Compile Include="WinHttpHandlerTest.cs" />
<Compile Include="XunitTestAssemblyAtrributes.cs" />
+ </ItemGroup>
+ <ItemGroup Condition=" '$(TargetsWindows)' == 'true' And '$(TargetsNetFx)' != 'true' ">
+ <Compile Include="$(CommonPath)\System\Net\Http\HttpHandlerDefaults.cs">
+ <Link>Common\System\Net\Http\HttpHandlerDefaults.cs</Link>
+ </Compile>
+ <Compile Include="$(CommonTestPath)System\IO\DelegateStream.cs">
+ <Link>Common\System\IO\DelegateStream.cs</Link>
+ </Compile>
+ <Compile Include="$(CommonTestPath)System\Net\Configuration.Certificates.cs">
+ <Link>Common\System\Net\Configuration.Certificates.cs</Link>
+ </Compile>
+ <Compile Include="$(CommonTestPath)System\Net\Configuration.Security.cs">
+ <Link>Common\System\Net\Configuration.Security.cs</Link>
+ </Compile>
+ <Compile Include="$(CommonTestPath)System\Net\Configuration.Sockets.cs">
+ <Link>Common\System\Net\Configuration.Sockets.cs</Link>
+ </Compile>
+ <Compile Include="$(CommonTestPath)System\Net\Capability.Security.cs">
+ <Link>Common\System\Net\Capability.Security.cs</Link>
+ </Compile>
+ <Compile Include="$(CommonTestPath)System\Net\Capability.Security.Windows.cs">
+ <Link>Common\System\Net\Capability.Security.Windows.cs</Link>
+ </Compile>
+ <Compile Include="$(CommonTestPath)System\Net\EventSourceTestLogging.cs">
+ <Link>Common\System\Net\EventSourceTestLogging.cs</Link>
+ </Compile>
+ <Compile Include="$(CommonTestPath)System\Net\HttpsTestServer.cs">
+ <Link>Common\System\Net\HttpsTestServer.cs</Link>
+ </Compile>
+ <Compile Include="$(CommonTestPath)System\Net\RemoteServerQuery.cs">
+ <Link>Common\System\Net\RemoteServerQuery.cs</Link>
+ </Compile>
+ <Compile Include="$(CommonTestPath)System\Net\VerboseTestLogging.cs">
+ <Link>Common\System\Net\VerboseTestLogging.cs</Link>
+ </Compile>
+ <Compile Include="$(CommonTestPath)System\Net\TestWebProxies.cs">
+ <Link>Common\System\Net\TestWebProxies.cs</Link>
+ </Compile>
+ <Compile Include="$(CommonTestPath)System\Net\Http\ByteAtATimeContent.cs">
+ <Link>Common\System\Net\Http\ByteAtATimeContent.cs</Link>
+ </Compile>
+ <Compile Include="$(CommonTestPath)System\Net\Http\ChannelBindingAwareContent.cs">
+ <Link>Common\System\Net\Http\ChannelBindingAwareContent.cs</Link>
+ </Compile>
+ <Compile Include="$(CommonTestPath)System\Net\Http\CustomContent.cs">
+ <Link>Common\System\Net\Http\CustomContent.cs</Link>
+ </Compile>
+ <Compile Include="$(CommonTestPath)System\Net\Http\DefaultCredentialsTest.cs">
+ <Link>Common\System\Net\Http\DefaultCredentialsTest.cs</Link>
+ </Compile>
+ <Compile Include="$(CommonTestPath)System\Net\Http\DribbleStream.cs">
+ <Link>Common\System\Net\Http\DribbleStream.cs</Link>
+ </Compile>
+ <Compile Include="$(CommonTestPath)System\Net\Http\GenericLoopbackServer.cs">
+ <Link>Common\System\Net\Http\GenericLoopbackServer.cs</Link>
+ </Compile>
+ <Compile Include="$(CommonTestPath)System\Net\Http\HttpClientHandlerTestBase.cs">
+ <Link>Common\System\Net\Http\HttpClientHandlerTestBase.cs</Link>
+ </Compile>
+ <Compile Include="$(CommonTestPath)System\Net\Http\Http2Frames.cs">
+ <Link>Common\System\Net\Http\Http2Frames.cs</Link>
+ </Compile>
+ <Compile Include="$(CommonTestPath)System\Net\Http\HPackEncoder.cs">
+ <Link>Common\System\Net\Http\HPackEncoder.cs</Link>
+ </Compile>
+ <Compile Include="$(CommonTestPath)System\Net\Http\Http2LoopbackServer.cs">
+ <Link>Common\System\Net\Http\Http2LoopbackServer.cs</Link>
+ </Compile>
+ <Compile Include="$(CommonTestPath)System\Net\Http\Http2LoopbackConnection.cs">
+ <Link>Common\System\Net\Http\Http2LoopbackConnection.cs</Link>
+ </Compile>
+ <Compile Include="$(CommonTestPath)System\Net\Http\HuffmanDecoder.cs">
+ <Link>Common\System\Net\Http\HuffmanDecoder.cs</Link>
+ </Compile>
+ <Compile Include="$(CommonTestPath)System\Net\Http\HuffmanEncoder.cs">
+ <Link>Common\System\Net\Http\HuffmanEncoder.cs</Link>
+ </Compile>
+ <Compile Include="$(CommonTestPath)System\Net\Http\HttpClientHandlerTest.cs">
+ <Link>Common\System\Net\Http\HttpClientHandlerTest.cs</Link>
+ </Compile>
+ <Compile Include="$(CommonTestPath)System\Net\Http\HttpClientHandlerTest.AcceptAllCerts.cs">
+ <Link>Common\System\Net\Http\HttpClientHandlerTest.AcceptAllCerts.cs</Link>
+ </Compile>
+ <Compile Include="$(CommonTestPath)System\Net\Http\HttpClientHandlerTest.Asynchrony.cs">
+ <Link>Common\System\Net\Http\HttpClientHandlerTest.Asynchrony.cs</Link>
+ </Compile>
+ <Compile Include="$(CommonTestPath)System\Net\Http\HttpClientHandlerTest.Authentication.cs">
+ <Link>Common\System\Net\Http\HttpClientHandlerTest.Authentication.cs</Link>
+ </Compile>
+ <Compile Include="$(CommonTestPath)System\Net\Http\HttpClientHandlerTest.AutoRedirect.cs">
+ <Link>Common\System\Net\Http\HttpClientHandlerTest.AutoRedirect.cs</Link>
+ </Compile>
+ <Compile Include="$(CommonTestPath)System\Net\Http\HttpClientHandlerTest.Cancellation.cs">
+ <Link>Common\System\Net\Http\HttpClientHandlerTest.Cancellation.cs</Link>
+ </Compile>
+ <Compile Include="$(CommonTestPath)System\Net\Http\HttpClientHandlerTest.ClientCertificates.cs">
+ <Link>Common\System\Net\Http\HttpClientHandlerTest.ClientCertificates.cs</Link>
+ </Compile>
+ <Compile Include="$(CommonTestPath)System\Net\Http\HttpClientHandlerTest.Cookies.cs">
+ <Link>Common\System\Net\Http\HttpClientHandlerTest.Cookies.cs</Link>
+ </Compile>
+ <Compile Include="$(CommonTestPath)System\Net\Http\HttpClientHandlerTest.Decompression.cs">
+ <Link>Common\System\Net\Http\HttpClientHandlerTest.Decompression.cs</Link>
+ </Compile>
+ <Compile Include="$(CommonTestPath)System\Net\Http\HttpClientHandlerTest.DefaultProxyCredentials.cs">
+ <Link>Common\System\Net\Http\HttpClientHandlerTest.DefaultProxyCredentials.cs</Link>
+ </Compile>
+ <Compile Include="$(CommonTestPath)System\Net\Http\HttpClientHandlerTest.MaxConnectionsPerServer.cs">
+ <Link>Common\System\Net\Http\HttpClientHandlerTest.MaxConnectionsPerServer.cs</Link>
+ </Compile>
+ <Compile Include="$(CommonTestPath)System\Net\Http\HttpClientHandlerTest.MaxResponseHeadersLength.cs">
+ <Link>Common\System\Net\Http\HttpClientHandlerTest.MaxResponseHeadersLength.cs</Link>
+ </Compile>
+ <Compile Include="$(CommonTestPath)System\Net\Http\HttpClientHandlerTest.Proxy.cs">
+ <Link>Common\System\Net\Http\HttpClientHandlerTest.Proxy.cs</Link>
+ </Compile>
+ <Compile Include="$(CommonTestPath)System\Net\Http\HttpClientHandlerTest.ServerCertificates.cs">
+ <Link>Common\System\Net\Http\HttpClientHandlerTest.ServerCertificates.cs</Link>
+ </Compile>
+ <Compile Include="$(CommonTestPath)System\Net\Http\HttpClientHandlerTest.SslProtocols.cs">
+ <Link>Common\System\Net\Http\HttpClientHandlerTest.SslProtocols.cs</Link>
+ </Compile>
+ <Compile Include="$(CommonTestPath)System\Net\Http\HttpProtocolTests.cs">
+ <Link>Common\System\Net\Http\HttpProtocolTests.cs</Link>
+ </Compile>
+ <Compile Include="$(CommonTestPath)System\Net\Http\HttpClient.SelectedSitesTest.cs">
+ <Link>Common\System\Net\Http\HttpClient.SelectedSitesTest.cs</Link>
+ </Compile>
+ <Compile Include="$(CommonTestPath)System\Net\Http\HttpClientEKUTest.cs">
+ <Link>Common\System\Net\Http\HttpClientEKUTest.cs</Link>
+ </Compile>
+ <Compile Include="$(CommonTestPath)System\Net\Http\HttpRetryProtocolTests.cs">
+ <Link>Common\System\Net\Http\HttpRetryProtocolTests.cs</Link>
+ </Compile>
+ <Compile Include="$(CommonTestPath)System\Net\Http\IdnaProtocolTests.cs">
+ <Link>Common\System\Net\Http\IdnaProtocolTests.cs</Link>
+ </Compile>
+ <Compile Include="$(CommonTestPath)System\Net\Http\LoopbackProxyServer.cs">
+ <Link>Common\System\Net\Http\LoopbackProxyServer.cs</Link>
+ </Compile>
+ <Compile Include="$(CommonTestPath)System\Net\Http\LoopbackServer.cs">
+ <Link>Common\System\Net\Http\LoopbackServer.cs</Link>
+ </Compile>
+ <Compile Include="$(CommonTestPath)System\Net\Http\LoopbackServer.AuthenticationHelpers.cs">
+ <Link>Common\System\Net\Http\LoopbackServer.AuthenticationHelpers.cs</Link>
+ </Compile>
+ <Compile Include="$(CommonTestPath)System\Net\Http\TestHelper.cs">
+ <Link>Common\System\Net\Http\TestHelper.cs</Link>
+ </Compile>
+ <Compile Include="$(CommonTestPath)System\Net\Http\PostScenarioTest.cs">
+ <Link>Common\System\Net\Http\PostScenarioTest.cs</Link>
+ </Compile>
+ <Compile Include="$(CommonTestPath)System\Net\Http\RepeatedFlushContent.cs">
+ <Link>Common\System\Net\Http\RepeatedFlushContent.cs</Link>
+ </Compile>
+ <Compile Include="$(CommonTestPath)System\Net\Http\ResponseStreamTest.cs">
+ <Link>Common\System\Net\Http\ResponseStreamTest.cs</Link>
+ </Compile>
+ <Compile Include="$(CommonTestPath)System\Net\Http\SchSendAuxRecordHttpTest.cs">
+ <Link>Common\System\Net\Http\SchSendAuxRecordHttpTest.cs</Link>
+ </Compile>
+ <Compile Include="$(CommonTestPath)System\Net\Http\SyncBlockingContent.cs">
+ <Link>Common\System\Net\Http\SyncBlockingContent.cs</Link>
+ </Compile>
+ <Compile Include="$(CommonTestPath)System\Threading\Tasks\TaskTimeoutExtensions.cs">
+ <Link>Common\System\Threading\Tasks\TaskTimeoutExtensions.cs</Link>
+ </Compile>
+ <Compile Include="$(CommonTestPath)System\Threading\TrackingSynchronizationContext.cs">
+ <Link>Common\System\Threading\TrackingSynchronizationContext.cs</Link>
+ </Compile>
+ <Compile Include="HttpClientHandlerTestBase.WinHttpHandler.cs" />
+ <Compile Include="WinHttpClientHandler.cs" />
+ <Compile Include="PlatformHandlerTest.cs" />
+ </ItemGroup>
+ <ItemGroup>
+ <PackageReference Include="System.Net.TestData" Version="$(SystemNetTestDataVersion)" />
</ItemGroup>
</Project>
\ No newline at end of file
--- /dev/null
+// 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.Collections.Generic;
+using System.Net.Security;
+using System.Security.Authentication;
+using System.Security.Cryptography.X509Certificates;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace System.Net.Http
+{
+ // Only for testing purposes.
+ // Contains HttpClientHandler.Windows.cs code from before its System.Net.Http removal.
+ // Follows HttpClientHandler contract so that its tests can be easily reused for WinHttpHandler.
+ // The only difference is in removal of DiagnosticsHandler since it's internal to System.Net.Http.
+ public class WinHttpClientHandler : WinHttpHandler
+ {
+ private bool _useProxy;
+
+ public WinHttpClientHandler()
+ {
+ // Adjust defaults to match current .NET Desktop HttpClientHandler (based on HWR stack).
+ AllowAutoRedirect = true;
+ AutomaticDecompression = HttpHandlerDefaults.DefaultAutomaticDecompression;
+ UseProxy = true;
+ UseCookies = true;
+ CookieContainer = new CookieContainer();
+ DefaultProxyCredentials = null;
+ ServerCredentials = null;
+
+ // The existing .NET Desktop HttpClientHandler based on the HWR stack uses only WinINet registry
+ // settings for the proxy. This also includes supporting the "Automatic Detect a proxy" using
+ // WPAD protocol and PAC file. So, for app-compat, we will do the same for the default proxy setting.
+ WindowsProxyUsePolicy = WindowsProxyUsePolicy.UseWinInetProxy;
+ Proxy = null;
+
+ // Since the granular WinHttpHandler timeout properties are not exposed via the HttpClientHandler API,
+ // we need to set them to infinite and allow the HttpClient.Timeout property to have precedence.
+ ReceiveHeadersTimeout = Timeout.InfiniteTimeSpan;
+ ReceiveDataTimeout = Timeout.InfiniteTimeSpan;
+ SendTimeout = Timeout.InfiniteTimeSpan;
+ }
+
+ public virtual bool SupportsAutomaticDecompression => true;
+ public virtual bool SupportsProxy => true;
+ public virtual bool SupportsRedirectConfiguration => true;
+
+ public bool UseCookies
+ {
+ get => CookieUsePolicy == CookieUsePolicy.UseSpecifiedCookieContainer;
+ set => CookieUsePolicy = value ? CookieUsePolicy.UseSpecifiedCookieContainer : CookieUsePolicy.IgnoreCookies;
+ }
+
+ public new CookieContainer CookieContainer
+ {
+ get => base.CookieContainer;
+ set
+ {
+ if (value == null)
+ {
+ throw new ArgumentNullException(nameof(value));
+ }
+ base.CookieContainer = value;
+ }
+ }
+
+ public bool UseProxy
+ {
+ get => _useProxy;
+ set => _useProxy = value;
+ }
+
+ public bool UseDefaultCredentials
+ {
+ // WinHttpHandler doesn't have a separate UseDefaultCredentials property. There
+ // is just a ServerCredentials property. So, we need to map the behavior.
+ // Do the same for SocketsHttpHandler.Credentials.
+ //
+ // This property only affect .ServerCredentials and not .DefaultProxyCredentials.
+ get => ServerCredentials == CredentialCache.DefaultCredentials;
+ set
+ {
+ if (value)
+ {
+ ServerCredentials = CredentialCache.DefaultCredentials;
+ }
+ else
+ {
+ if (ServerCredentials == CredentialCache.DefaultCredentials)
+ {
+ // Only clear out the ServerCredentials property if it was a DefaultCredentials.
+ ServerCredentials = null;
+ }
+ }
+ }
+ }
+
+ public ICredentials Credentials
+ {
+ get => ServerCredentials;
+ set => ServerCredentials = value;
+ }
+
+ public bool AllowAutoRedirect
+ {
+ get => AutomaticRedirection;
+ set => AutomaticRedirection = value;
+ }
+
+ public long MaxRequestContentBufferSize
+ {
+ // This property is not supported. In the .NET Framework it was only used when the handler needed to
+ // automatically buffer the request content. That only happened if neither 'Content-Length' nor
+ // 'Transfer-Encoding: chunked' request headers were specified. So, the handler thus needed to buffer
+ // in the request content to determine its length and then would choose 'Content-Length' semantics when
+ // POST'ing. In .NET Core, the handler will resolve the ambiguity by always choosing
+ // 'Transfer-Encoding: chunked'. The handler will never automatically buffer in the request content.
+ get
+ {
+ return 0; // Returning zero is appropriate since in .NET Framework it means no limit.
+ }
+
+ set
+ {
+ if (value < 0 || value > int.MaxValue)
+ {
+ throw new ArgumentOutOfRangeException(nameof(value));
+ }
+
+ // No-op on property setter.
+ }
+ }
+
+ public ClientCertificateOption ClientCertificateOptions
+ {
+ get => ClientCertificateOption;
+ set => ClientCertificateOption = value;
+ }
+
+ public Func<HttpRequestMessage, X509Certificate2, X509Chain, SslPolicyErrors, bool> ServerCertificateCustomValidationCallback
+ {
+ get => ServerCertificateValidationCallback;
+ set => ServerCertificateValidationCallback = value;
+ }
+
+ public static Func<HttpRequestMessage, X509Certificate2, X509Chain, SslPolicyErrors, bool> DangerousAcceptAnyServerCertificateValidator { get; } = delegate { return true; };
+
+ protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
+ {
+ // Get current value of WindowsProxyUsePolicy. Only call its WinHttpHandler
+ // property setter if the value needs to change.
+ var oldProxyUsePolicy = base.WindowsProxyUsePolicy;
+
+ if (_useProxy)
+ {
+ if (base.Proxy == null)
+ {
+ if (oldProxyUsePolicy != WindowsProxyUsePolicy.UseWinInetProxy)
+ {
+ base.WindowsProxyUsePolicy = WindowsProxyUsePolicy.UseWinInetProxy;
+ }
+ }
+ else
+ {
+ if (oldProxyUsePolicy != WindowsProxyUsePolicy.UseCustomProxy)
+ {
+ base.WindowsProxyUsePolicy = WindowsProxyUsePolicy.UseCustomProxy;
+ }
+ }
+ }
+ else
+ {
+ if (oldProxyUsePolicy != WindowsProxyUsePolicy.DoNotUseProxy)
+ {
+ base.WindowsProxyUsePolicy = WindowsProxyUsePolicy.DoNotUseProxy;
+ }
+ }
+
+ return base.SendAsync(request, cancellationToken);
+ }
+ }
+}
+++ /dev/null
-// 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.Threading.Tasks;
-
-namespace System.Net.Http.Functional.Tests
-{
- internal sealed class ByteAtATimeContent : HttpContent
- {
- private readonly Task _waitToSend;
- private readonly TaskCompletionSource<bool> _startedSend;
- private readonly int _length;
- private readonly int _millisecondDelayBetweenBytes;
-
- public ByteAtATimeContent(int length) : this(length, Task.CompletedTask, new TaskCompletionSource<bool>(), millisecondDelayBetweenBytes: 0) { }
-
- public ByteAtATimeContent(int length, Task waitToSend, TaskCompletionSource<bool> startedSend, int millisecondDelayBetweenBytes)
- {
- _length = length;
- _waitToSend = waitToSend;
- _startedSend = startedSend;
- _millisecondDelayBetweenBytes = millisecondDelayBetweenBytes;
- }
-
- protected override async Task SerializeToStreamAsync(Stream stream, TransportContext context)
- {
- await _waitToSend;
- _startedSend.SetResult(true);
-
- var buffer = new byte[1];
- for (int i = 0; i < _length; i++)
- {
- buffer[0] = (byte)i;
- await stream.WriteAsync(buffer);
- await stream.FlushAsync();
- await Task.Delay(_millisecondDelayBetweenBytes);
- }
- }
-
- protected override bool TryComputeLength(out long length)
- {
- length = _length;
- return true;
- }
- }
-}
+++ /dev/null
-// 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.Security.Authentication.ExtendedProtection;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace System.Net.Http.Functional.Tests
-{
- internal sealed class ChannelBindingAwareContent : HttpContent
- {
- private readonly byte[] _content;
-
- public ChannelBindingAwareContent(string content)
- {
- _content = Encoding.UTF8.GetBytes(content);
- }
-
- public ChannelBinding ChannelBinding { get ; private set; }
-
- protected override Task SerializeToStreamAsync(Stream stream, TransportContext context)
- {
- ChannelBinding = context.GetChannelBinding(ChannelBindingKind.Endpoint);
- return stream.WriteAsync(_content, 0, _content.Length);
- }
-
- protected override bool TryComputeLength(out long length)
- {
- length = _content.Length;
- return true;
- }
-
- protected override Task<Stream> CreateContentReadStreamAsync()
- {
- return Task.FromResult<Stream>(new MemoryStream(_content, 0, _content.Length, writable: false));
- }
- }
-}
+++ /dev/null
-// 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.Threading.Tasks;
-
-namespace System.Net.Http.Functional.Tests
-{
- internal partial class CustomContent : HttpContent
- {
- private readonly Stream _stream;
-
- public CustomContent(Stream stream) => _stream = stream;
-
- protected override Task<Stream> CreateContentReadStreamAsync() =>
- Task.FromResult(_stream);
-
- protected override Task SerializeToStreamAsync(Stream stream, TransportContext context) =>
- _stream.CopyToAsync(stream);
-
- protected override bool TryComputeLength(out long length)
- {
- if (_stream.CanSeek)
- {
- length = _stream.Length;
- return true;
- }
- else
- {
- length = 0;
- return false;
- }
- }
-
- internal class CustomStream : Stream
- {
- private byte[] _buffer;
- private long _position;
- private bool _rewindable;
- internal Exception _failException;
-
- public CustomStream(byte[] buffer, bool rewindable)
- {
- _buffer = buffer;
- _position = 0;
- _rewindable = rewindable;
- }
-
- public override bool CanRead
- {
- get { return true; }
- }
-
- public override bool CanSeek
- {
- get { return _rewindable; }
- }
-
- public override bool CanWrite
- {
- get { return false; }
- }
-
- public override void Flush()
- {
- throw new NotSupportedException("CustomStream.Flush");
- }
-
- public override long Length
- {
- get
- {
- if (_rewindable)
- {
- return (long)_buffer.Length;
- }
- else
- {
- throw new NotSupportedException("CustomStream.Length");
- }
- }
- }
-
- public override long Position
- {
- get
- {
- return _position;
- }
- set
- {
- if (_rewindable)
- {
- _position = value;
- }
- else
- {
- throw new NotSupportedException("CustomStream.Position");
- }
- }
- }
-
- public override int Read(byte[] buffer, int offset, int count)
- {
- int bytesRead = 0;
-
- if (_failException != null)
- {
- throw _failException;
- }
-
- for (int i = 0; i < count; i++)
- {
- if (_position >= _buffer.Length)
- {
- break;
- }
-
- buffer[offset] = _buffer[_position];
- bytesRead++;
- offset++;
- _position++;
- }
-
- return bytesRead;
- }
-
- public override long Seek(long offset, SeekOrigin origin)
- {
- if (_rewindable)
- {
- switch (origin)
- {
- case SeekOrigin.Begin:
- Position = offset;
- break;
- case SeekOrigin.Current:
- Position += offset;
- break;
- case SeekOrigin.End:
- Position = _buffer.Length + offset;
- break;
- default:
- throw new NotImplementedException("CustomStream.Seek");
- }
- return Position;
- }
- else
- {
- throw new NotImplementedException("CustomStream.Seek");
- }
- }
-
- public override void SetLength(long value)
- {
- throw new NotSupportedException("CustomStream.SetLength");
- }
-
- public override void Write(byte[] buffer, int offset, int count)
- {
- throw new NotSupportedException("CustomStream.Write");
- }
-
- public void SetException(Exception e)
- {
- _failException = e;
- }
- }
- }
-}
+++ /dev/null
-// 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.Collections.Generic;
-using System.Security.Principal;
-using System.Threading.Tasks;
-
-using Xunit;
-using Xunit.Abstractions;
-
-namespace System.Net.Http.Functional.Tests
-{
- using Configuration = System.Net.Test.Common.Configuration;
-
- [PlatformSpecific(TestPlatforms.Windows)]
- public abstract class DefaultCredentialsTest : HttpClientHandlerTestBase
- {
- private static bool DomainJoinedTestsEnabled => !string.IsNullOrEmpty(Configuration.Http.DomainJoinedHttpHost);
-
- private static bool DomainProxyTestsEnabled => !string.IsNullOrEmpty(Configuration.Http.DomainJoinedProxyHost);
-
- // Enable this to test against local HttpListener over loopback
- // Note this doesn't work as expected with WinHttpHandler, because WinHttpHandler will always authenticate the
- // current user against a loopback server using NTLM or Negotiate.
- private static bool LocalHttpListenerTestsEnabled = false;
-
- public static bool ServerAuthenticationTestsEnabled => (LocalHttpListenerTestsEnabled || DomainJoinedTestsEnabled);
-
- private static string s_specificUserName = Configuration.Security.ActiveDirectoryUserName;
- private static string s_specificPassword = Configuration.Security.ActiveDirectoryUserPassword;
- private static string s_specificDomain = Configuration.Security.ActiveDirectoryName;
- private readonly NetworkCredential _specificCredential =
- new NetworkCredential(s_specificUserName, s_specificPassword, s_specificDomain);
- private static Uri s_authenticatedServer = DomainJoinedTestsEnabled ?
- new Uri($"http://{Configuration.Http.DomainJoinedHttpHost}/test/auth/negotiate/showidentity.ashx") : null;
-
- public DefaultCredentialsTest(ITestOutputHelper output) : base(output) { }
-
- [OuterLoop("Uses external server")]
- [ConditionalTheory(nameof(ServerAuthenticationTestsEnabled))]
- [MemberData(nameof(AuthenticatedServers))]
- public async Task UseDefaultCredentials_DefaultValue_Unauthorized(string uri, bool useProxy)
- {
- HttpClientHandler handler = CreateHttpClientHandler();
- handler.UseProxy = useProxy;
-
- using (HttpClient client = CreateHttpClient(handler))
- using (HttpResponseMessage response = await client.GetAsync(uri))
- {
- Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
- }
- }
-
- [OuterLoop("Uses external server")]
- [ConditionalTheory(nameof(ServerAuthenticationTestsEnabled))]
- [MemberData(nameof(AuthenticatedServers))]
- public async Task UseDefaultCredentials_SetFalse_Unauthorized(string uri, bool useProxy)
- {
- HttpClientHandler handler = CreateHttpClientHandler();
- handler.UseProxy = useProxy;
- handler.UseDefaultCredentials = false;
-
- using (HttpClient client = CreateHttpClient(handler))
- using (HttpResponseMessage response = await client.GetAsync(uri))
- {
- Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
- }
- }
-
- [OuterLoop("Uses external server")]
- [ConditionalTheory(nameof(ServerAuthenticationTestsEnabled))]
- [MemberData(nameof(AuthenticatedServers))]
- public async Task UseDefaultCredentials_SetTrue_ConnectAsCurrentIdentity(string uri, bool useProxy)
- {
- HttpClientHandler handler = CreateHttpClientHandler();
- handler.UseProxy = useProxy;
- handler.UseDefaultCredentials = true;
-
- using (HttpClient client = CreateHttpClient(handler))
- using (HttpResponseMessage response = await client.GetAsync(uri))
- {
- Assert.Equal(HttpStatusCode.OK, response.StatusCode);
-
- string responseBody = await response.Content.ReadAsStringAsync();
- WindowsIdentity currentIdentity = WindowsIdentity.GetCurrent();
- _output.WriteLine("currentIdentity={0}", currentIdentity.Name);
- VerifyAuthentication(responseBody, true, currentIdentity.Name);
- }
- }
-
- [OuterLoop("Uses external server")]
- [ConditionalTheory(nameof(ServerAuthenticationTestsEnabled))]
- [MemberData(nameof(AuthenticatedServers))]
- public async Task Credentials_SetToWrappedDefaultCredential_ConnectAsCurrentIdentity(string uri, bool useProxy)
- {
- HttpClientHandler handler = CreateHttpClientHandler();
- handler.UseProxy = useProxy;
- handler.Credentials = new CredentialWrapper
- {
- InnerCredentials = CredentialCache.DefaultCredentials
- };
-
- using (HttpClient client = CreateHttpClient(handler))
- using (HttpResponseMessage response = await client.GetAsync(uri))
- {
- Assert.Equal(HttpStatusCode.OK, response.StatusCode);
-
- string responseBody = await response.Content.ReadAsStringAsync();
- WindowsIdentity currentIdentity = WindowsIdentity.GetCurrent();
- _output.WriteLine("currentIdentity={0}", currentIdentity.Name);
- VerifyAuthentication(responseBody, true, currentIdentity.Name);
- }
- }
-
- [OuterLoop("Uses external server")]
- [ConditionalTheory(nameof(ServerAuthenticationTestsEnabled))]
- [MemberData(nameof(AuthenticatedServers))]
- public async Task Credentials_SetToBadCredential_Unauthorized(string uri, bool useProxy)
- {
- HttpClientHandler handler = CreateHttpClientHandler();
- handler.UseProxy = useProxy;
- handler.Credentials = new NetworkCredential("notarealuser", "123456");
-
- using (HttpClient client = CreateHttpClient(handler))
- using (HttpResponseMessage response = await client.GetAsync(uri))
- {
- Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
- }
- }
-
- [OuterLoop("Uses external server")]
- [ActiveIssue("https://github.com/dotnet/corefx/issues/10041")]
- [ConditionalTheory(nameof(DomainJoinedTestsEnabled))]
- [InlineData(false)]
- [InlineData(true)]
- public async Task Credentials_SetToSpecificCredential_ConnectAsSpecificIdentity(bool useProxy)
- {
- HttpClientHandler handler = CreateHttpClientHandler();
- handler.UseProxy = useProxy;
- handler.UseDefaultCredentials = false;
- handler.Credentials = _specificCredential;
-
- using (HttpClient client = CreateHttpClient(handler))
- using (HttpResponseMessage response = await client.GetAsync(s_authenticatedServer))
- {
- Assert.Equal(HttpStatusCode.OK, response.StatusCode);
-
- string responseBody = await response.Content.ReadAsStringAsync();
- VerifyAuthentication(responseBody, true, s_specificDomain + "\\" + s_specificUserName);
- }
- }
-
- [OuterLoop("Uses external server")]
- [ActiveIssue("https://github.com/dotnet/corefx/issues/10041")]
- [ConditionalFact(nameof(DomainProxyTestsEnabled))]
- public async Task Proxy_UseAuthenticatedProxyWithNoCredentials_ProxyAuthenticationRequired()
- {
- HttpClientHandler handler = CreateHttpClientHandler();
- handler.Proxy = new AuthenticatedProxy(null);
-
- using (HttpClient client = CreateHttpClient(handler))
- using (HttpResponseMessage response = await client.GetAsync(Configuration.Http.RemoteEchoServer))
- {
- Assert.Equal(HttpStatusCode.ProxyAuthenticationRequired, response.StatusCode);
- }
- }
-
- [OuterLoop("Uses external server")]
- [ActiveIssue("https://github.com/dotnet/corefx/issues/10041")]
- [ConditionalFact(nameof(DomainProxyTestsEnabled))]
- public async Task Proxy_UseAuthenticatedProxyWithDefaultCredentials_OK()
- {
- HttpClientHandler handler = CreateHttpClientHandler();
- handler.Proxy = new AuthenticatedProxy(CredentialCache.DefaultCredentials);
-
- using (HttpClient client = CreateHttpClient(handler))
- using (HttpResponseMessage response = await client.GetAsync(Configuration.Http.RemoteEchoServer))
- {
- Assert.Equal(HttpStatusCode.OK, response.StatusCode);
- }
- }
-
- [OuterLoop("Uses external server")]
- [ConditionalFact(nameof(DomainProxyTestsEnabled))]
- public async Task Proxy_UseAuthenticatedProxyWithWrappedDefaultCredentials_OK()
- {
- ICredentials wrappedCreds = new CredentialWrapper
- {
- InnerCredentials = CredentialCache.DefaultCredentials
- };
-
- HttpClientHandler handler = CreateHttpClientHandler();
- handler.Proxy = new AuthenticatedProxy(wrappedCreds);
-
- using (HttpClient client = CreateHttpClient(handler))
- using (HttpResponseMessage response = await client.GetAsync(Configuration.Http.RemoteEchoServer))
- {
- Assert.Equal(HttpStatusCode.OK, response.StatusCode);
- }
- }
-
- public static IEnumerable<object[]> AuthenticatedServers()
- {
- // Note that localhost will not actually use the proxy, but there's no harm in testing it.
- foreach (bool b in new bool[] { true, false })
- {
- if (LocalHttpListenerTestsEnabled)
- {
- yield return new object[] { HttpListenerAuthenticatedLoopbackServer.NtlmOnly.Uri, b };
- yield return new object[] { HttpListenerAuthenticatedLoopbackServer.NegotiateOnly.Uri, b };
- yield return new object[] { HttpListenerAuthenticatedLoopbackServer.NegotiateAndNtlm.Uri, b };
- yield return new object[] { HttpListenerAuthenticatedLoopbackServer.BasicAndNtlm.Uri, b };
- }
-
- if (!string.IsNullOrEmpty(Configuration.Http.DomainJoinedHttpHost))
- {
- yield return new object[] { $"http://{Configuration.Http.DomainJoinedHttpHost}/test/auth/negotiate/showidentity.ashx", b };
- yield return new object[] { $"http://{Configuration.Http.DomainJoinedHttpHost}/test/auth/multipleschemes/showidentity.ashx", b };
- }
- }
- }
-
- private void VerifyAuthentication(string response, bool authenticated, string user)
- {
- // Convert all strings to lowercase to compare. Windows treats domain and username as case-insensitive.
- response = response.ToLower();
- user = user.ToLower();
- _output.WriteLine(response);
-
- if (!authenticated)
- {
- Assert.True(
- TestHelper.JsonMessageContainsKeyValue(response, "authenticated", "false"),
- "authenticated == false");
- }
- else
- {
- Assert.True(
- TestHelper.JsonMessageContainsKeyValue(response, "authenticated", "true"),
- "authenticated == true");
- Assert.True(
- TestHelper.JsonMessageContainsKeyValue(response, "user", user),
- $"user == {user}");
- }
- }
-
- private class CredentialWrapper : ICredentials
- {
- public ICredentials InnerCredentials { get; set; }
-
- public NetworkCredential GetCredential(Uri uri, string authType) =>
- InnerCredentials?.GetCredential(uri, authType);
- }
-
- private class AuthenticatedProxy : IWebProxy
- {
- ICredentials _credentials;
- Uri _proxyUri;
-
- public AuthenticatedProxy(ICredentials credentials)
- {
- _credentials = credentials;
-
- string host = Configuration.Http.DomainJoinedProxyHost;
- Assert.False(string.IsNullOrEmpty(host), "DomainJoinedProxyHost must specify proxy hostname");
-
- string portString = Configuration.Http.DomainJoinedProxyPort;
- Assert.False(string.IsNullOrEmpty(portString), "DomainJoinedProxyPort must specify proxy port number");
-
- int port;
- Assert.True(int.TryParse(portString, out port), "DomainJoinedProxyPort must be a valid port number");
-
- _proxyUri = new Uri(string.Format("http://{0}:{1}", host, port));
- }
-
- public ICredentials Credentials
- {
- get
- {
- return _credentials;
- }
-
- set
- {
- throw new NotImplementedException();
- }
- }
-
- public Uri GetProxy(Uri destination)
- {
- return _proxyUri;
- }
-
- public bool IsBypassed(Uri host)
- {
- return false;
- }
- }
-
- private sealed class HttpListenerAuthenticatedLoopbackServer
- {
- private readonly HttpListener _listener;
- private readonly string _uri;
-
- public static readonly HttpListenerAuthenticatedLoopbackServer NtlmOnly = new HttpListenerAuthenticatedLoopbackServer("http://localhost:8080/", AuthenticationSchemes.Ntlm);
- public static readonly HttpListenerAuthenticatedLoopbackServer NegotiateOnly = new HttpListenerAuthenticatedLoopbackServer("http://localhost:8081/", AuthenticationSchemes.Negotiate);
- public static readonly HttpListenerAuthenticatedLoopbackServer NegotiateAndNtlm = new HttpListenerAuthenticatedLoopbackServer("http://localhost:8082/", AuthenticationSchemes.Negotiate | AuthenticationSchemes.Ntlm);
- public static readonly HttpListenerAuthenticatedLoopbackServer BasicAndNtlm = new HttpListenerAuthenticatedLoopbackServer("http://localhost:8083/", AuthenticationSchemes.Basic | AuthenticationSchemes.Ntlm);
-
- // Don't construct directly, use instances above
- private HttpListenerAuthenticatedLoopbackServer(string uri, AuthenticationSchemes authenticationSchemes)
- {
- _uri = uri;
-
- _listener = new HttpListener();
- _listener.Prefixes.Add(uri);
- _listener.AuthenticationSchemes = authenticationSchemes;
- _listener.Start();
-
- Task.Run(() => ProcessRequests());
- }
-
- public string Uri => _uri;
-
- private async Task ProcessRequests()
- {
- while (true)
- {
- var context = await _listener.GetContextAsync();
-
- // 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}\" }}"));
-
- context.Response.Close();
- }
- }
- }
- }
-}
+++ /dev/null
-// 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.Threading;
-using System.Threading.Tasks;
-
-namespace System.Net.Http.Functional.Tests
-{
- public sealed class DribbleStream : Stream
- {
- private readonly Stream _wrapped;
- private readonly bool _clientDisconnectAllowed;
-
- public DribbleStream(Stream wrapped, bool clientDisconnectAllowed = false)
- {
- _wrapped = wrapped;
- _clientDisconnectAllowed = clientDisconnectAllowed;
- }
-
- public override async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
- {
- try
- {
- for (int i = 0; i < count; i++)
- {
- await _wrapped.WriteAsync(buffer, offset + i, 1);
- await _wrapped.FlushAsync();
- await Task.Yield(); // introduce short delays, enough to send packets individually but not so long as to extend test duration significantly
- }
- }
- catch (IOException) when (_clientDisconnectAllowed)
- {
- }
- }
-
- public override void Write(byte[] buffer, int offset, int count)
- {
- try
- {
- for (int i = 0; i < count; i++)
- {
- _wrapped.Write(buffer, offset + i, 1);
- _wrapped.Flush();
- }
- }
- catch (IOException) when (_clientDisconnectAllowed)
- {
- }
- }
-
- public override bool CanRead => _wrapped.CanRead;
- public override bool CanSeek => _wrapped.CanSeek;
- public override bool CanWrite => _wrapped.CanWrite;
- public override long Length => _wrapped.Length;
- public override long Position { get => _wrapped.Position; set => _wrapped.Position = value; }
- public override void Flush() => _wrapped.Flush();
- public override Task FlushAsync(CancellationToken cancellationToken) => _wrapped.FlushAsync(cancellationToken);
- public override int Read(byte[] buffer, int offset, int count) => _wrapped.Read(buffer, offset, count);
- public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) => _wrapped.ReadAsync(buffer, offset, count, cancellationToken);
- public override long Seek(long offset, SeekOrigin origin) => _wrapped.Seek(offset, origin);
- public override void SetLength(long value) => _wrapped.SetLength(value);
- public override Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken) => _wrapped.CopyToAsync(destination, bufferSize, cancellationToken);
- public override void Close() => _wrapped.Close();
- protected override void Dispose(bool disposing) => _wrapped.Dispose();
- }
-}
+++ /dev/null
-// 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.Collections.Generic;
-using System.IO;
-using System.Reflection;
-using System.Threading.Tasks;
-
-using Xunit;
-using Xunit.Abstractions;
-
-namespace System.Net.Http.Functional.Tests
-{
- public abstract class HttpClient_SelectedSites_Test : HttpClientHandlerTestBase
- {
- public HttpClient_SelectedSites_Test(ITestOutputHelper output) : base(output) { }
-
- public static bool IsSelectedSitesTestEnabled()
- {
- string envVar = Environment.GetEnvironmentVariable("CORFX_NET_HTTP_SELECTED_SITES");
- return envVar != null &&
- (envVar.Equals("true", StringComparison.OrdinalIgnoreCase) || envVar.Equals("1"));
- }
-
- [ConditionalTheory(nameof(IsSelectedSitesTestEnabled))]
- [Trait("SelectedSites", "true")]
- [MemberData(nameof(GetSelectedSites))]
- public async Task RetrieveSite_Succeeds(string site)
- {
- int remainingAttempts = 2;
- while (remainingAttempts-- > 0)
- {
- try
- {
- await VisitSite(site);
- return;
- }
- catch
- {
- if (remainingAttempts < 1)
- throw;
- await Task.Delay(1500);
- }
- }
-
- throw new Exception("Not expected to reach here");
- }
-
- [ConditionalTheory(nameof(IsSelectedSitesTestEnabled))]
- [Trait("SiteInvestigation", "true")]
- [InlineData("http://microsoft.com")]
- public async Task RetrieveSite_Debug_Helper(string site)
- {
- await VisitSite(site);
- }
-
- public static IEnumerable<object[]> GetSelectedSites()
- {
- const string resourceName = "SelectedSitesTest.txt";
- Assembly assembly = typeof(HttpClient_SelectedSites_Test).Assembly;
- Stream s = assembly.GetManifestResourceStream(resourceName);
- if (s == null)
- {
- throw new Exception("Couldn't find resource " + resourceName);
- }
-
- using (var reader = new StreamReader(s))
- {
- string site;
- while (null != (site = reader.ReadLine()))
- {
- yield return new[] { site };
- }
- }
- }
-
- private async Task VisitSite(string site)
- {
- using (HttpClient httpClient = CreateHttpClientForSiteVisit())
- {
- await VisitSiteWithClient(site, httpClient);
- }
- }
-
- private async Task VisitSiteWithClient(string site, HttpClient httpClient)
- {
- using (HttpResponseMessage response = await httpClient.GetAsync(site))
- {
- switch (response.StatusCode)
- {
- case HttpStatusCode.Redirect:
- case HttpStatusCode.OK:
- if (response.Content.Headers.ContentLength > 0)
- Assert.Equal(response.Content.Headers.ContentLength.Value, (await response.Content.ReadAsByteArrayAsync()).Length);
- break;
- case HttpStatusCode.BadGateway:
- case HttpStatusCode.Forbidden:
- case HttpStatusCode.Moved:
- case HttpStatusCode.NotFound:
- case HttpStatusCode.ServiceUnavailable:
- case HttpStatusCode.Unauthorized:
- case HttpStatusCode.InternalServerError:
- break;
- default:
- throw new Exception($"{site} returned: {response.StatusCode}");
- }
- }
- }
-
- private HttpClient CreateHttpClientForSiteVisit()
- {
- HttpClient httpClient = CreateHttpClient(CreateHttpClientHandler());
-
- // Some extra headers since some sites only give proper responses when they are present.
- httpClient.DefaultRequestHeaders.Add(
- "User-Agent",
- "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.186 Safari/537.36");
- httpClient.DefaultRequestHeaders.Add(
- "Accept-Language",
- "en-US,en;q=0.9");
- httpClient.DefaultRequestHeaders.Add(
- "Accept-Encoding",
- "gzip, deflate, br");
- httpClient.DefaultRequestHeaders.Add(
- "Accept",
- "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8");
-
- return httpClient;
- }
- }
-}
+++ /dev/null
-// 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.Runtime.InteropServices;
-using System.Security.Authentication;
-using System.Security.Cryptography.X509Certificates;
-using System.Threading.Tasks;
-
-using Xunit;
-using Xunit.Abstractions;
-
-namespace System.Net.Http.Functional.Tests
-{
- using Configuration = System.Net.Test.Common.Configuration;
-
- public abstract class HttpClientEKUTest : HttpClientHandlerTestBase
- {
- private static bool CanTestCertificates =>
- Capability.IsTrustedRootCertificateInstalled() && Capability.AreHostsFileNamesInstalled();
-
- public const int TestTimeoutMilliseconds = 15 * 1000;
-
- public static X509Certificate2 serverCertificateServerEku = Configuration.Certificates.GetServerCertificate();
- public static X509Certificate2 serverCertificateNoEku = Configuration.Certificates.GetNoEKUCertificate();
- public static X509Certificate2 serverCertificateWrongEku = Configuration.Certificates.GetClientCertificate();
-
- public static X509Certificate2 clientCertificateWrongEku = Configuration.Certificates.GetServerCertificate();
- public static X509Certificate2 clientCertificateNoEku = Configuration.Certificates.GetNoEKUCertificate();
- public static X509Certificate2 clientCertificateClientEku = Configuration.Certificates.GetClientCertificate();
-
- private VerboseTestLogging _log = VerboseTestLogging.GetInstance();
-
- public HttpClientEKUTest(ITestOutputHelper output) : base(output) { }
-
- [ConditionalFact(nameof(CanTestCertificates))]
- public async Task HttpClient_NoEKUServerAuth_Ok()
- {
- var options = new HttpsTestServer.Options();
- options.ServerCertificate = serverCertificateNoEku;
-
- using (var server = new HttpsTestServer(options))
- using (HttpClientHandler handler = CreateHttpClientHandler())
- using (HttpClient client = CreateHttpClient(handler))
- {
- server.Start();
-
- var tasks = new Task[2];
- tasks[0] = server.AcceptHttpsClientAsync();
-
- string requestUriString = GetUriStringAndConfigureHandler(options, server, handler);
- tasks[1] = client.GetStringAsync(requestUriString);
-
- await tasks.WhenAllOrAnyFailed(TestTimeoutMilliseconds);
- }
- }
-
- [ConditionalFact(nameof(CanTestCertificates))]
- public async Task HttpClient_ClientEKUServerAuth_Fails()
- {
- var options = new HttpsTestServer.Options();
- options.ServerCertificate = serverCertificateWrongEku;
-
- using (var server = new HttpsTestServer(options))
- using (HttpClientHandler handler = CreateHttpClientHandler())
- using (HttpClient client = CreateHttpClient(handler))
- {
- server.Start();
-
- var tasks = new Task[2];
- tasks[0] = server.AcceptHttpsClientAsync();
-
- string requestUriString = GetUriStringAndConfigureHandler(options, server, handler);
- tasks[1] = client.GetStringAsync(requestUriString);
-
- await Assert.ThrowsAsync<HttpRequestException>(() => tasks[1]);
- }
- }
-
- [ConditionalFact(nameof(CanTestCertificates))]
- public async Task HttpClient_NoEKUClientAuth_Ok()
- {
- var options = new HttpsTestServer.Options();
- options.ServerCertificate = serverCertificateServerEku;
- options.RequireClientAuthentication = true;
-
- using (var server = new HttpsTestServer(options))
- using (HttpClientHandler handler = CreateHttpClientHandler())
- using (HttpClient client = CreateHttpClient(handler))
- {
- server.Start();
-
- var tasks = new Task[2];
- tasks[0] = server.AcceptHttpsClientAsync();
-
- string requestUriString = GetUriStringAndConfigureHandler(options, server, handler);
- handler.ClientCertificates.Add(clientCertificateNoEku);
- tasks[1] = client.GetStringAsync(requestUriString);
-
- await tasks.WhenAllOrAnyFailed(TestTimeoutMilliseconds);
- }
- }
-
- [ConditionalFact(nameof(CanTestCertificates))]
- public async Task HttpClient_ServerEKUClientAuth_Fails()
- {
- var options = new HttpsTestServer.Options();
- options.ServerCertificate = serverCertificateServerEku;
- options.RequireClientAuthentication = true;
-
- using (var server = new HttpsTestServer(options))
- using (HttpClientHandler handler = CreateHttpClientHandler())
- using (HttpClient client = CreateHttpClient(handler))
- {
- server.Start();
-
- var tasks = new Task[2];
- tasks[0] = server.AcceptHttpsClientAsync();
-
- string requestUriString = GetUriStringAndConfigureHandler(options, server, handler);
- handler.ClientCertificates.Add(clientCertificateWrongEku);
- tasks[1] = client.GetStringAsync(requestUriString);
-
- // Server aborts the TCP channel.
- await Assert.ThrowsAsync<HttpRequestException>(() => tasks[1]);
-
- await Assert.ThrowsAsync<AuthenticationException>(() => tasks[0]);
- }
- }
-
- private string GetUriStringAndConfigureHandler(HttpsTestServer.Options options, HttpsTestServer server, HttpClientHandler handler)
- {
- if (Capability.AreHostsFileNamesInstalled())
- {
- string hostName =
- (new UriBuilder("https", options.ServerCertificate.GetNameInfo(X509NameType.SimpleName, false), server.Port)).ToString();
-
- Console.WriteLine("[E2E testing] - Using hostname {0}", hostName);
- return hostName;
- }
- else
- {
- handler.ServerCertificateCustomValidationCallback = AllowRemoteCertificateNameMismatch;
- return "https://localhost:" + server.Port.ToString();
- }
- }
-
- private bool AllowRemoteCertificateNameMismatch(HttpRequestMessage httpMessage, X509Certificate2 certificate, X509Chain chain, SslPolicyErrors errors)
- {
- if (errors == SslPolicyErrors.RemoteCertificateNameMismatch)
- {
- return true;
- }
-
- return false;
- }
- }
-}
+++ /dev/null
-// 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.Authentication;
-using System.Threading.Tasks;
-using Microsoft.DotNet.XUnitExtensions;
-using Xunit;
-using Xunit.Abstractions;
-
-namespace System.Net.Http.Functional.Tests
-{
- using Configuration = System.Net.Test.Common.Configuration;
-
- public abstract class HttpClientHandler_DangerousAcceptAllCertificatesValidator_Test : HttpClientHandlerTestBase
- {
- private static bool ClientSupportsDHECipherSuites => (!PlatformDetection.IsWindows || PlatformDetection.IsWindows10Version1607OrGreater);
-
- public HttpClientHandler_DangerousAcceptAllCertificatesValidator_Test(ITestOutputHelper output) : base(output) { }
-
- [Fact]
- public void SingletonReturnsTrue()
- {
- Assert.NotNull(HttpClientHandler.DangerousAcceptAnyServerCertificateValidator);
- Assert.Same(HttpClientHandler.DangerousAcceptAnyServerCertificateValidator, HttpClientHandler.DangerousAcceptAnyServerCertificateValidator);
- Assert.True(HttpClientHandler.DangerousAcceptAnyServerCertificateValidator(null, null, null, SslPolicyErrors.None));
- }
-
- [ConditionalTheory]
- [InlineData(SslProtocols.Tls, false)] // try various protocols to ensure we correctly set versions even when accepting all certs
- [InlineData(SslProtocols.Tls, true)]
- [InlineData(SslProtocols.Tls12 | SslProtocols.Tls11 | SslProtocols.Tls, false)]
- [InlineData(SslProtocols.Tls12 | SslProtocols.Tls11 | SslProtocols.Tls, true)]
- [InlineData(SslProtocols.Tls13 | SslProtocols.Tls12 | SslProtocols.Tls11 | SslProtocols.Tls, false)]
- [InlineData(SslProtocols.Tls13 | SslProtocols.Tls12 | SslProtocols.Tls11 | SslProtocols.Tls, true)]
- [InlineData(SslProtocols.None, false)]
- [InlineData(SslProtocols.None, true)]
- public async Task SetDelegate_ConnectionSucceeds(SslProtocols acceptedProtocol, bool requestOnlyThisProtocol)
- {
- // Overriding flag for the same reason we skip tests on Catalina
- // On OSX 10.13-10.14 we can override this flag to enable the scenario
- requestOnlyThisProtocol |= PlatformDetection.IsMacOsHighSierraOrHigher && acceptedProtocol == SslProtocols.Tls;
-
- using (HttpClientHandler handler = CreateHttpClientHandler())
- using (HttpClient client = CreateHttpClient(handler))
- {
- handler.ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator;
-
- if (requestOnlyThisProtocol)
- {
- handler.SslProtocols = acceptedProtocol;
- }
- else
- {
- // Explicitly setting protocols clears implementation default
- // restrictions on minimum TLS/SSL version
- // We currently know that some platforms like Debian 10 OpenSSL
- // will by default block < TLS 1.2
- handler.SslProtocols = SslProtocols.Tls13 | SslProtocols.Tls12 | SslProtocols.Tls11 | SslProtocols.Tls;
- }
-
- var options = new LoopbackServer.Options { UseSsl = true, SslProtocols = acceptedProtocol };
- await LoopbackServer.CreateServerAsync(async (server, url) =>
- {
- await TestHelper.WhenAllCompletedOrAnyFailed(
- server.AcceptConnectionSendResponseAndCloseAsync(),
- client.GetAsync(url));
- }, options);
- }
- }
-
- public static readonly object[][] InvalidCertificateServers =
- {
- new object[] { Configuration.Http.ExpiredCertRemoteServer },
- new object[] { Configuration.Http.SelfSignedCertRemoteServer },
- new object[] { Configuration.Http.WrongHostNameCertRemoteServer },
- };
-
- [OuterLoop]
- [ConditionalTheory(nameof(ClientSupportsDHECipherSuites))]
- [MemberData(nameof(InvalidCertificateServers))]
- public async Task InvalidCertificateServers_CertificateValidationDisabled_Succeeds(string url)
- {
- using (HttpClientHandler handler = CreateHttpClientHandler())
- using (HttpClient client = CreateHttpClient(handler))
- {
- handler.ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator;
- (await client.GetAsync(url)).Dispose();
- }
- }
- }
-}
+++ /dev/null
-// 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.Collections.Generic;
-using System.IO;
-using System.Linq;
-using System.Net.Test.Common;
-using System.Threading;
-using System.Threading.Tasks;
-using System.Threading.Tests;
-using Xunit;
-using Xunit.Abstractions;
-
-namespace System.Net.Http.Functional.Tests
-{
- public abstract class HttpClientHandler_Asynchrony_Test : HttpClientHandlerTestBase
- {
- public HttpClientHandler_Asynchrony_Test(ITestOutputHelper output) : base(output) { }
-
- public static IEnumerable<object[]> ResponseHeadersRead_SynchronizationContextNotUsedByHandler_MemberData() =>
- from responseHeadersRead in new[] { false, true }
- from contentMode in Enum.GetValues(typeof(LoopbackServer.ContentMode)).Cast<LoopbackServer.ContentMode>()
- select new object[] { responseHeadersRead, contentMode };
-
- [Theory]
- [MemberData(nameof(ResponseHeadersRead_SynchronizationContextNotUsedByHandler_MemberData))]
- public async Task ResponseHeadersRead_SynchronizationContextNotUsedByHandler(bool responseHeadersRead, LoopbackServer.ContentMode contentMode)
- {
- await Task.Run(async delegate // escape xunit's sync ctx
- {
- await LoopbackServer.CreateClientAndServerAsync(uri =>
- {
- return Task.Run(() => // allow client and server to run concurrently even though this is all synchronous/blocking
- {
- var sc = new TrackingSynchronizationContext();
- SynchronizationContext.SetSynchronizationContext(sc);
-
- using (HttpClient client = CreateHttpClient())
- {
- if (responseHeadersRead)
- {
- using (HttpResponseMessage resp = client.GetAsync(uri, HttpCompletionOption.ResponseHeadersRead).GetAwaiter().GetResult())
- using (Stream respStream = resp.Content.ReadAsStreamAsync().GetAwaiter().GetResult())
- {
- byte[] buffer = new byte[0x1000];
- while (respStream.ReadAsync(buffer, 0, buffer.Length).GetAwaiter().GetResult() > 0);
- }
- }
- else
- {
- client.GetStringAsync(uri).GetAwaiter().GetResult();
- }
- }
-
- Assert.True(sc.CallStacks.Count == 0, "Sync Ctx used: " + string.Join(Environment.NewLine + Environment.NewLine, sc.CallStacks));
- });
- }, async server =>
- {
- await server.AcceptConnectionAsync(async connection =>
- {
- await connection.ReadRequestHeaderAsync();
- await connection.Writer.WriteAsync(
- LoopbackServer.GetContentModeResponse(
- contentMode,
- string.Concat(Enumerable.Repeat('s', 10_000)),
- connectionClose: true));
- });
- }, new LoopbackServer.Options { StreamWrapper = s => new DribbleStream(s) });
- });
- }
- }
-}
+++ /dev/null
-// 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.Collections.Generic;
-using System.Linq;
-using System.Net.Sockets;
-using System.Net.Test.Common;
-using System.Text;
-using System.Threading.Tasks;
-
-using Microsoft.DotNet.XUnitExtensions;
-
-using Xunit;
-using Xunit.Abstractions;
-
-namespace System.Net.Http.Functional.Tests
-{
- using Configuration = System.Net.Test.Common.Configuration;
-
- public abstract class HttpClientHandler_Authentication_Test : HttpClientHandlerTestBase
- {
- private const string Username = "testusername";
- private const string Password = "testpassword";
- private const string Domain = "testdomain";
-
- private static readonly NetworkCredential s_credentials = new NetworkCredential(Username, Password, Domain);
- private static readonly NetworkCredential s_credentialsNoDomain = new NetworkCredential(Username, Password);
-
- private async Task CreateAndValidateRequest(HttpClientHandler handler, Uri url, HttpStatusCode expectedStatusCode, ICredentials credentials)
- {
- handler.Credentials = credentials;
-
- using (HttpClient client = CreateHttpClient(handler))
- using (HttpResponseMessage response = await client.GetAsync(url))
- {
- Assert.Equal(expectedStatusCode, response.StatusCode);
- }
- }
-
- public HttpClientHandler_Authentication_Test(ITestOutputHelper output) : base(output) { }
-
- [Theory]
- [MemberData(nameof(Authentication_TestData))]
- public async Task HttpClientHandler_Authentication_Succeeds(string authenticateHeader, bool result)
- {
- if (PlatformDetection.IsWindowsNanoServer)
- {
- return;
- }
-
- var options = new LoopbackServer.Options { Domain = Domain, Username = Username, Password = Password };
- await LoopbackServer.CreateServerAsync(async (server, url) =>
- {
- string serverAuthenticateHeader = $"WWW-Authenticate: {authenticateHeader}\r\n";
- HttpClientHandler handler = CreateHttpClientHandler();
- Task serverTask = result ?
- server.AcceptConnectionPerformAuthenticationAndCloseAsync(serverAuthenticateHeader) :
- server.AcceptConnectionSendResponseAndCloseAsync(HttpStatusCode.Unauthorized, serverAuthenticateHeader);
-
- await TestHelper.WhenAllCompletedOrAnyFailedWithTimeout(TestHelper.PassingTestTimeoutMilliseconds,
- CreateAndValidateRequest(handler, url, result ? HttpStatusCode.OK : HttpStatusCode.Unauthorized, s_credentials), serverTask);
- }, options);
- }
-
- [Theory]
- [InlineData("WWW-Authenticate: Basic realm=\"hello1\"\r\nWWW-Authenticate: Basic realm=\"hello2\"\r\n")]
- [InlineData("WWW-Authenticate: Basic realm=\"hello\"\r\nWWW-Authenticate: Basic realm=\"hello\"\r\n")]
- [InlineData("WWW-Authenticate: Digest realm=\"hello\", nonce=\"hello\", algorithm=MD5\r\nWWW-Authenticate: Digest realm=\"hello\", nonce=\"hello\", algorithm=MD5\r\n")]
- [InlineData("WWW-Authenticate: Digest realm=\"hello1\", nonce=\"hello\", algorithm=MD5\r\nWWW-Authenticate: Digest realm=\"hello\", nonce=\"hello\", algorithm=MD5\r\n")]
- public async Task HttpClientHandler_MultipleAuthenticateHeaders_WithSameAuth_Succeeds(string authenticateHeader)
- {
- await HttpClientHandler_MultipleAuthenticateHeaders_Succeeds(authenticateHeader);
- }
-
- [Theory]
- [InlineData("WWW-Authenticate: Basic realm=\"hello\"\r\nWWW-Authenticate: Digest realm=\"hello\", nonce=\"hello\", algorithm=MD5\r\n")]
- [InlineData("WWW-Authenticate: Digest realm=\"hello\", nonce=\"hello\", algorithm=MD5\r\nWWW-Authenticate: Basic realm=\"hello\"\r\n")]
- public async Task HttpClientHandler_MultipleAuthenticateHeaders_Succeeds(string authenticateHeader)
- {
- if (PlatformDetection.IsWindowsNanoServer)
- {
- return;
- }
-
- var options = new LoopbackServer.Options { Domain = Domain, Username = Username, Password = Password };
- await LoopbackServer.CreateServerAsync(async (server, url) =>
- {
- HttpClientHandler handler = CreateHttpClientHandler();
- Task serverTask = server.AcceptConnectionPerformAuthenticationAndCloseAsync(authenticateHeader);
- await TestHelper.WhenAllCompletedOrAnyFailed(CreateAndValidateRequest(handler, url, HttpStatusCode.OK, s_credentials), serverTask);
- }, options);
- }
-
- [Theory]
- [InlineData("WWW-Authenticate: Basic realm=\"hello\"\r\nWWW-Authenticate: NTLM\r\n", "Basic", "Negotiate")]
- [InlineData("WWW-Authenticate: Basic realm=\"hello\"\r\nWWW-Authenticate: Digest realm=\"hello\", nonce=\"hello\", algorithm=MD5\r\nWWW-Authenticate: NTLM\r\n", "Digest", "Negotiate")]
- public async Task HttpClientHandler_MultipleAuthenticateHeaders_PicksSupported(string authenticateHeader, string supportedAuth, string unsupportedAuth)
- {
- if (PlatformDetection.IsWindowsNanoServer)
- {
- return;
- }
-
- var options = new LoopbackServer.Options { Domain = Domain, Username = Username, Password = Password };
- await LoopbackServer.CreateServerAsync(async (server, url) =>
- {
- HttpClientHandler handler = CreateHttpClientHandler();
- handler.UseDefaultCredentials = false;
-
- var credentials = new CredentialCache();
- credentials.Add(url, supportedAuth, new NetworkCredential(Username, Password, Domain));
- credentials.Add(url, unsupportedAuth, new NetworkCredential(Username, Password, Domain));
-
- Task serverTask = server.AcceptConnectionPerformAuthenticationAndCloseAsync(authenticateHeader);
- await TestHelper.WhenAllCompletedOrAnyFailed(CreateAndValidateRequest(handler, url, HttpStatusCode.OK, credentials), serverTask);
- }, options);
- }
-
- [Theory]
- [InlineData("WWW-Authenticate: Basic realm=\"hello\"\r\n")]
- [InlineData("WWW-Authenticate: Digest realm=\"hello\", nonce=\"testnonce\"\r\n")]
- public async Task HttpClientHandler_IncorrectCredentials_Fails(string authenticateHeader)
- {
- var options = new LoopbackServer.Options { Domain = Domain, Username = Username, Password = Password };
- await LoopbackServer.CreateServerAsync(async (server, url) =>
- {
- HttpClientHandler handler = CreateHttpClientHandler();
- Task serverTask = server.AcceptConnectionPerformAuthenticationAndCloseAsync(authenticateHeader);
- await TestHelper.WhenAllCompletedOrAnyFailed(CreateAndValidateRequest(handler, url, HttpStatusCode.Unauthorized, new NetworkCredential("wronguser", "wrongpassword")), serverTask);
- }, options);
- }
-
- public static IEnumerable<object[]> Authentication_TestData()
- {
- yield return new object[] { "Basic realm=\"testrealm\"", true };
- yield return new object[] { "Basic ", true };
- yield return new object[] { "Basic realm=withoutquotes", true };
- yield return new object[] { "basic ", true };
- yield return new object[] { "bAsiC ", true };
- yield return new object[] { "basic", true };
-
- yield return new object[] { $"Digest realm=\"testrealm\", nonce=\"{Convert.ToBase64String(Encoding.UTF8.GetBytes($"{DateTimeOffset.UtcNow}:XMh;z+$5|`i6Hx}}\", qop=auth-int, algorithm=MD5"))}\"", true };
- yield return new object[] { $"Digest realm=\"testrealm\", nonce=\"{Convert.ToBase64String(Encoding.UTF8.GetBytes($"{DateTimeOffset.UtcNow}:XMh;z+$5|`i6Hx}}\", qop=auth-int, algorithm=md5"))}\"", true };
- yield return new object[] { $"Basic realm=\"testrealm\", " +
- $"Digest realm=\"testrealm\", nonce=\"{Convert.ToBase64String(Encoding.UTF8.GetBytes($"{DateTimeOffset.UtcNow}:XMh;z+$5|`i6Hx}}"))}\", algorithm=MD5", true };
-
- yield return new object[] { "Basic something, Digest something", false };
- yield return new object[] { $"Digest realm=\"testrealm\", nonce=\"testnonce\", algorithm=MD5 " +
- $"Basic realm=\"testrealm\"", false };
- }
-
- [Theory]
- [InlineData(null)]
- [InlineData("Basic")]
- [InlineData("Digest")]
- [InlineData("NTLM")]
- [InlineData("Kerberos")]
- [InlineData("Negotiate")]
- public async Task PreAuthenticate_NoPreviousAuthenticatedRequests_NoCredentialsSent(string credCacheScheme)
- {
- const int NumRequests = 3;
- await LoopbackServer.CreateClientAndServerAsync(async uri =>
- {
- using (HttpClientHandler handler = CreateHttpClientHandler())
- using (HttpClient client = CreateHttpClient(handler))
- {
- client.DefaultRequestHeaders.ConnectionClose = true; // for simplicity of not needing to know every handler's pooling policy
- handler.PreAuthenticate = true;
- switch (credCacheScheme)
- {
- case null:
- handler.Credentials = s_credentials;
- break;
-
- default:
- var cc = new CredentialCache();
- cc.Add(uri, credCacheScheme, s_credentials);
- handler.Credentials = cc;
- break;
- }
-
- for (int i = 0; i < NumRequests; i++)
- {
- Assert.Equal("hello world", await client.GetStringAsync(uri));
- }
- }
- },
- async server =>
- {
- for (int i = 0; i < NumRequests; i++)
- {
- List<string> headers = await server.AcceptConnectionSendResponseAndCloseAsync(content: "hello world");
- Assert.All(headers, header => Assert.DoesNotContain("Authorization", header));
- }
- });
- }
-
- [Theory]
- [InlineData(null, "WWW-Authenticate: Basic realm=\"hello\"\r\n")]
- [InlineData("Basic", "WWW-Authenticate: Basic realm=\"hello\"\r\n")]
- public async Task PreAuthenticate_FirstRequestNoHeaderAndAuthenticates_SecondRequestPreauthenticates(string credCacheScheme, string authResponse)
- {
- await LoopbackServer.CreateClientAndServerAsync(async uri =>
- {
- using (HttpClientHandler handler = CreateHttpClientHandler())
- using (HttpClient client = CreateHttpClient(handler))
- {
- client.DefaultRequestHeaders.ConnectionClose = true; // for simplicity of not needing to know every handler's pooling policy
- handler.PreAuthenticate = true;
- switch (credCacheScheme)
- {
- case null:
- handler.Credentials = s_credentials;
- break;
-
- default:
- var cc = new CredentialCache();
- cc.Add(uri, credCacheScheme, s_credentials);
- handler.Credentials = cc;
- break;
- }
-
- Assert.Equal("hello world", await client.GetStringAsync(uri));
- Assert.Equal("hello world", await client.GetStringAsync(uri));
- }
- },
- async server =>
- {
- List<string> headers = await server.AcceptConnectionSendResponseAndCloseAsync(HttpStatusCode.Unauthorized, authResponse);
- Assert.All(headers, header => Assert.DoesNotContain("Authorization", header));
-
- for (int i = 0; i < 2; i++)
- {
- headers = await server.AcceptConnectionSendResponseAndCloseAsync(content: "hello world");
- Assert.Contains(headers, header => header.Contains("Authorization"));
- }
- });
- }
-
- // InlineDatas for all values that pass on WinHttpHandler, which is the most restrictive.
- // Uses numerical values for values named in .NET Core and not in .NET Framework.
- [Theory]
- [InlineData(HttpStatusCode.OK)]
- [InlineData(HttpStatusCode.Created)]
- [InlineData(HttpStatusCode.Accepted)]
- [InlineData(HttpStatusCode.NonAuthoritativeInformation)]
- [InlineData(HttpStatusCode.NoContent)]
- [InlineData(HttpStatusCode.ResetContent)]
- [InlineData(HttpStatusCode.PartialContent)]
- [InlineData((HttpStatusCode)207)] // MultiStatus
- [InlineData((HttpStatusCode)208)] // AlreadyReported
- [InlineData((HttpStatusCode)226)] // IMUsed
- [InlineData(HttpStatusCode.Ambiguous)]
- [InlineData(HttpStatusCode.NotModified)]
- [InlineData(HttpStatusCode.UseProxy)]
- [InlineData(HttpStatusCode.Unused)]
- [InlineData(HttpStatusCode.BadRequest)]
- [InlineData(HttpStatusCode.PaymentRequired)]
- [InlineData(HttpStatusCode.Forbidden)]
- [InlineData(HttpStatusCode.NotFound)]
- [InlineData(HttpStatusCode.MethodNotAllowed)]
- [InlineData(HttpStatusCode.NotAcceptable)]
- [InlineData(HttpStatusCode.RequestTimeout)]
- [InlineData(HttpStatusCode.Conflict)]
- [InlineData(HttpStatusCode.Gone)]
- [InlineData(HttpStatusCode.LengthRequired)]
- [InlineData(HttpStatusCode.PreconditionFailed)]
- [InlineData(HttpStatusCode.RequestEntityTooLarge)]
- [InlineData(HttpStatusCode.RequestUriTooLong)]
- [InlineData(HttpStatusCode.UnsupportedMediaType)]
- [InlineData(HttpStatusCode.RequestedRangeNotSatisfiable)]
- [InlineData(HttpStatusCode.ExpectationFailed)]
- [InlineData((HttpStatusCode)421)] // MisdirectedRequest
- [InlineData((HttpStatusCode)422)] // UnprocessableEntity
- [InlineData((HttpStatusCode)423)] // Locked
- [InlineData((HttpStatusCode)424)] // FailedDependency
- [InlineData(HttpStatusCode.UpgradeRequired)]
- [InlineData((HttpStatusCode)428)] // PreconditionRequired
- [InlineData((HttpStatusCode)429)] // TooManyRequests
- [InlineData((HttpStatusCode)431)] // RequestHeaderFieldsTooLarge
- [InlineData((HttpStatusCode)451)] // UnavailableForLegalReasons
- [InlineData(HttpStatusCode.InternalServerError)]
- [InlineData(HttpStatusCode.NotImplemented)]
- [InlineData(HttpStatusCode.BadGateway)]
- [InlineData(HttpStatusCode.ServiceUnavailable)]
- [InlineData(HttpStatusCode.GatewayTimeout)]
- [InlineData(HttpStatusCode.HttpVersionNotSupported)]
- [InlineData((HttpStatusCode)506)] // VariantAlsoNegotiates
- [InlineData((HttpStatusCode)507)] // InsufficientStorage
- [InlineData((HttpStatusCode)508)] // LoopDetected
- [InlineData((HttpStatusCode)510)] // NotExtended
- [InlineData((HttpStatusCode)511)] // NetworkAuthenticationRequired
- public async Task PreAuthenticate_FirstRequestNoHeader_SecondRequestVariousStatusCodes_ThirdRequestPreauthenticates(HttpStatusCode statusCode)
- {
- const string AuthResponse = "WWW-Authenticate: Basic realm=\"hello\"\r\n";
-
- await LoopbackServer.CreateClientAndServerAsync(async uri =>
- {
- using (HttpClientHandler handler = CreateHttpClientHandler())
- using (HttpClient client = CreateHttpClient(handler))
- {
- client.DefaultRequestHeaders.ConnectionClose = true; // for simplicity of not needing to know every handler's pooling policy
- handler.PreAuthenticate = true;
- handler.Credentials = s_credentials;
- client.DefaultRequestHeaders.ExpectContinue = false;
-
- using (HttpResponseMessage resp = await client.GetAsync(uri))
- {
- Assert.Equal(statusCode, resp.StatusCode);
- }
- Assert.Equal("hello world", await client.GetStringAsync(uri));
- }
- },
- async server =>
- {
- List<string> headers = await server.AcceptConnectionSendResponseAndCloseAsync(HttpStatusCode.Unauthorized, AuthResponse);
- Assert.All(headers, header => Assert.DoesNotContain("Authorization", header));
-
- headers = await server.AcceptConnectionSendResponseAndCloseAsync(statusCode);
- Assert.Contains(headers, header => header.Contains("Authorization"));
-
- headers = await server.AcceptConnectionSendResponseAndCloseAsync(HttpStatusCode.OK, content: "hello world");
- Assert.Contains(headers, header => header.Contains("Authorization"));
- });
- }
-
- [Theory]
- [InlineData("/something/hello.html", "/something/hello.html", true)]
- [InlineData("/something/hello.html", "/something/world.html", true)]
- [InlineData("/something/hello.html", "/something/", true)]
- [InlineData("/something/hello.html", "/", false)]
- [InlineData("/something/hello.html", "/hello.html", false)]
- [InlineData("/something/hello.html", "/world.html", false)]
- [InlineData("/something/hello.html", "/another/", false)]
- [InlineData("/something/hello.html", "/another/hello.html", false)]
- public async Task PreAuthenticate_AuthenticatedUrl_ThenTryDifferentUrl_SendsAuthHeaderOnlyIfPrefixMatches(
- string originalRelativeUri, string secondRelativeUri, bool expectedAuthHeader)
- {
- const string AuthResponse = "WWW-Authenticate: Basic realm=\"hello\"\r\n";
-
- await LoopbackServer.CreateClientAndServerAsync(async uri =>
- {
- using (HttpClientHandler handler = CreateHttpClientHandler())
- using (HttpClient client = CreateHttpClient(handler))
- {
- client.DefaultRequestHeaders.ConnectionClose = true; // for simplicity of not needing to know every handler's pooling policy
- handler.PreAuthenticate = true;
- handler.Credentials = s_credentials;
-
- Assert.Equal("hello world 1", await client.GetStringAsync(new Uri(uri, originalRelativeUri)));
- Assert.Equal("hello world 2", await client.GetStringAsync(new Uri(uri, secondRelativeUri)));
- }
- },
- async server =>
- {
- List<string> headers = await server.AcceptConnectionSendResponseAndCloseAsync(HttpStatusCode.Unauthorized, AuthResponse);
- Assert.All(headers, header => Assert.DoesNotContain("Authorization", header));
-
- headers = await server.AcceptConnectionSendResponseAndCloseAsync(content: "hello world 1");
- Assert.Contains(headers, header => header.Contains("Authorization"));
-
- headers = await server.AcceptConnectionSendResponseAndCloseAsync(content: "hello world 2");
- if (expectedAuthHeader)
- {
- Assert.Contains(headers, header => header.Contains("Authorization"));
- }
- else
- {
- Assert.All(headers, header => Assert.DoesNotContain("Authorization", header));
- }
- });
- }
-
- [Fact]
- public async Task PreAuthenticate_SuccessfulBasicButThenFails_DoesntLoopInfinitely()
- {
- await LoopbackServer.CreateClientAndServerAsync(async uri =>
- {
- using (HttpClientHandler handler = CreateHttpClientHandler())
- using (HttpClient client = CreateHttpClient(handler))
- {
- client.DefaultRequestHeaders.ConnectionClose = true; // for simplicity of not needing to know every handler's pooling policy
- handler.PreAuthenticate = true;
- handler.Credentials = s_credentials;
-
- // First two requests: initially without auth header, then with
- Assert.Equal("hello world", await client.GetStringAsync(uri));
-
- // Attempt preauth, and when that fails, give up.
- using (HttpResponseMessage resp = await client.GetAsync(uri))
- {
- Assert.Equal(HttpStatusCode.Unauthorized, resp.StatusCode);
- }
- }
- },
- async server =>
- {
- // First request, no auth header, challenge Basic
- List<string> headers = headers = await server.AcceptConnectionSendResponseAndCloseAsync(HttpStatusCode.Unauthorized, "WWW-Authenticate: Basic realm=\"hello\"\r\n");
- Assert.All(headers, header => Assert.DoesNotContain("Authorization", header));
-
- // Second request, contains Basic auth header
- headers = await server.AcceptConnectionSendResponseAndCloseAsync(content: "hello world");
- Assert.Contains(headers, header => header.Contains("Authorization"));
-
- // Third request, contains Basic auth header but challenges anyway
- headers = headers = await server.AcceptConnectionSendResponseAndCloseAsync(HttpStatusCode.Unauthorized, "WWW-Authenticate: Basic realm=\"hello\"\r\n");
- Assert.Contains(headers, header => header.Contains("Authorization"));
- });
- }
-
- [Fact]
- public async Task PreAuthenticate_SuccessfulBasic_ThenDigestChallenged()
- {
- await LoopbackServer.CreateClientAndServerAsync(async uri =>
- {
- using (HttpClientHandler handler = CreateHttpClientHandler())
- using (HttpClient client = CreateHttpClient(handler))
- {
- client.DefaultRequestHeaders.ConnectionClose = true; // for simplicity of not needing to know every handler's pooling policy
- handler.PreAuthenticate = true;
- handler.Credentials = s_credentials;
-
- Assert.Equal("hello world", await client.GetStringAsync(uri));
- Assert.Equal("hello world", await client.GetStringAsync(uri));
- }
- },
- async server =>
- {
- // First request, no auth header, challenge Basic
- List<string> headers = headers = await server.AcceptConnectionSendResponseAndCloseAsync(HttpStatusCode.Unauthorized, "WWW-Authenticate: Basic realm=\"hello\"\r\n");
- Assert.All(headers, header => Assert.DoesNotContain("Authorization", header));
-
- // Second request, contains Basic auth header, success
- headers = await server.AcceptConnectionSendResponseAndCloseAsync(content: "hello world");
- Assert.Contains(headers, header => header.Contains("Authorization"));
-
- // Third request, contains Basic auth header, challenge digest
- headers = await server.AcceptConnectionSendResponseAndCloseAsync(HttpStatusCode.Unauthorized, "WWW-Authenticate: Digest realm=\"hello\", nonce=\"testnonce\"\r\n");
- Assert.Contains(headers, header => header.Contains("Authorization: Basic"));
-
- // Fourth request, contains Digest auth header, success
- headers = await server.AcceptConnectionSendResponseAndCloseAsync(content: "hello world");
- Assert.Contains(headers, header => header.Contains("Authorization: Digest"));
- });
- }
-
- public static IEnumerable<object[]> ServerUsesWindowsAuthentication_MemberData()
- {
- string server = Configuration.Http.WindowsServerHttpHost;
- string authEndPoint = "showidentity.ashx";
-
- yield return new object[] { $"http://{server}/test/auth/ntlm/{authEndPoint}" };
- yield return new object[] { $"https://{server}/test/auth/ntlm/{authEndPoint}" };
-
- yield return new object[] { $"http://{server}/test/auth/negotiate/{authEndPoint}" };
- yield return new object[] { $"https://{server}/test/auth/negotiate/{authEndPoint}" };
-
- // Server requires TLS channel binding token (cbt) with NTLM authentication.
- yield return new object[] { $"https://{server}/test/auth/ntlm-epa/{authEndPoint}" };
- }
-
- private static bool IsNtlmInstalled => Capability.IsNtlmInstalled();
- private static bool IsWindowsServerAvailable => !string.IsNullOrEmpty(Configuration.Http.WindowsServerHttpHost);
- private static bool IsDomainJoinedServerAvailable => !string.IsNullOrEmpty(Configuration.Http.DomainJoinedHttpHost);
- private static NetworkCredential DomainCredential = new NetworkCredential(
- Configuration.Security.ActiveDirectoryUserName,
- Configuration.Security.ActiveDirectoryUserPassword,
- Configuration.Security.ActiveDirectoryName);
-
- public static IEnumerable<object[]> EchoServersData()
- {
- foreach (Uri serverUri in Configuration.Http.EchoServerList)
- {
- yield return new object[] { serverUri };
- }
- }
-
- [MemberData(nameof(EchoServersData))]
- [ConditionalTheory(nameof(IsDomainJoinedServerAvailable))]
- public async Task Proxy_DomainJoinedProxyServerUsesKerberos_Success(Uri server)
- {
- // We skip the test unless it is running on a Windows client machine. That is because only Windows
- // automatically registers an SPN for HTTP/<hostname> of the machine. This will enable Kerberos to properly
- // work with the loopback proxy server.
- if (!PlatformDetection.IsWindows || !PlatformDetection.IsNotWindowsNanoServer)
- {
- throw new SkipTestException("Test can only run on domain joined Windows client machine");
- }
-
- var options = new LoopbackProxyServer.Options { AuthenticationSchemes = AuthenticationSchemes.Negotiate };
- using (LoopbackProxyServer proxyServer = LoopbackProxyServer.Create(options))
- {
- using (HttpClientHandler handler = CreateHttpClientHandler())
- using (HttpClient client = CreateHttpClient(handler))
- {
- // Use 'localhost' DNS name for loopback proxy server (instead of IP address) so that the SPN will
- // get calculated properly to use Kerberos.
- _output.WriteLine(proxyServer.Uri.AbsoluteUri.ToString());
- handler.Proxy = new WebProxy("localhost", proxyServer.Uri.Port) { Credentials = DomainCredential };
-
- using (HttpResponseMessage response = await client.GetAsync(server))
- {
- Assert.Equal(HttpStatusCode.OK, response.StatusCode);
-
- int requestCount = proxyServer.Requests.Count;
-
- // We expect 2 requests to the proxy server. One without the 'Proxy-Authorization' header and
- // one with the header.
- Assert.Equal(2, requestCount);
- Assert.Equal("Negotiate", proxyServer.Requests[requestCount - 1].AuthorizationHeaderValueScheme);
-
- // Base64 tokens that use SPNEGO protocol start with 'Y'. NTLM tokens start with 'T'.
- Assert.Equal('Y', proxyServer.Requests[requestCount - 1].AuthorizationHeaderValueToken[0]);
- }
- }
- }
- }
-
- [ConditionalFact(nameof(IsDomainJoinedServerAvailable))]
- public async Task Credentials_DomainJoinedServerUsesKerberos_Success()
- {
- using (HttpClientHandler handler = CreateHttpClientHandler())
- using (HttpClient client = CreateHttpClient(handler))
- {
- handler.Credentials = DomainCredential;
-
- string server = $"http://{Configuration.Http.DomainJoinedHttpHost}/test/auth/kerberos/showidentity.ashx";
- using (HttpResponseMessage response = await client.GetAsync(server))
- {
- Assert.Equal(HttpStatusCode.OK, response.StatusCode);
- string body = await response.Content.ReadAsStringAsync();
- _output.WriteLine(body);
- }
- }
- }
-
- [ConditionalFact(nameof(IsDomainJoinedServerAvailable))]
- public async Task Credentials_DomainJoinedServerUsesKerberos_UseIpAddressAndHostHeader_Success()
- {
- using (HttpClientHandler handler = CreateHttpClientHandler())
- using (HttpClient client = CreateHttpClient(handler))
- {
- handler.Credentials = DomainCredential;
-
- IPAddress[] addresses = Dns.GetHostAddresses(Configuration.Http.DomainJoinedHttpHost);
- IPAddress hostIP = addresses.Where(a => a.AddressFamily == AddressFamily.InterNetwork).Select(a => a).First();
-
- var request = new HttpRequestMessage();
- request.RequestUri = new Uri($"http://{hostIP}/test/auth/kerberos/showidentity.ashx");
- request.Headers.Host = Configuration.Http.DomainJoinedHttpHost;
- _output.WriteLine(request.RequestUri.AbsoluteUri.ToString());
- _output.WriteLine($"Host: {request.Headers.Host}");
-
- using (HttpResponseMessage response = await client.SendAsync(request))
- {
- Assert.Equal(HttpStatusCode.OK, response.StatusCode);
- string body = await response.Content.ReadAsStringAsync();
- _output.WriteLine(body);
- }
- }
- }
-
- [ConditionalTheory(nameof(IsNtlmInstalled), nameof(IsWindowsServerAvailable))]
- [MemberData(nameof(ServerUsesWindowsAuthentication_MemberData))]
- public async Task Credentials_ServerUsesWindowsAuthentication_Success(string server)
- {
- using (HttpClientHandler handler = CreateHttpClientHandler())
- using (HttpClient client = CreateHttpClient(handler))
- {
- handler.Credentials = new NetworkCredential(
- Configuration.Security.WindowsServerUserName,
- Configuration.Security.WindowsServerUserPassword);
-
- using (HttpResponseMessage response = await client.GetAsync(server))
- {
- Assert.Equal(HttpStatusCode.OK, response.StatusCode);
- string body = await response.Content.ReadAsStringAsync();
- _output.WriteLine(body);
- }
- }
- }
-
- [ConditionalTheory(nameof(IsNtlmInstalled))]
- [InlineData("NTLM")]
- [InlineData("Negotiate")]
- public async Task Credentials_ServerChallengesWithWindowsAuth_ClientSendsWindowsAuthHeader(string authScheme)
- {
- await LoopbackServerFactory.CreateClientAndServerAsync(
- async uri =>
- {
- using (HttpClientHandler handler = CreateHttpClientHandler())
- using (HttpClient client = CreateHttpClient(handler))
- {
- handler.Credentials = new NetworkCredential("username", "password");
- await client.GetAsync(uri);
- }
- },
- async server =>
- {
- var responseHeader = new HttpHeaderData[] { new HttpHeaderData("Www-authenticate", authScheme) };
- HttpRequestData requestData = await server.HandleRequestAsync(
- HttpStatusCode.Unauthorized, responseHeader);
- Assert.Equal(0, requestData.GetHeaderValueCount("Authorization"));
-
- requestData = await server.HandleRequestAsync();
- string authHeaderValue = requestData.GetSingleHeaderValue("Authorization");
- Assert.Contains(authScheme, authHeaderValue);
- _output.WriteLine(authHeaderValue);
- });
- }
- }
-}
+++ /dev/null
-// 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.Collections.Generic;
-using System.Net.Http.Headers;
-using System.Net.Sockets;
-using System.Net.Test.Common;
-using System.Threading.Tasks;
-
-using Xunit;
-using Xunit.Abstractions;
-
-namespace System.Net.Http.Functional.Tests
-{
- using Configuration = System.Net.Test.Common.Configuration;
-
- public abstract class HttpClientHandlerTest_AutoRedirect : HttpClientHandlerTestBase
- {
- private const string ExpectedContent = "Test content";
- private const string Username = "testuser";
- private const string Password = "password";
-
- private readonly NetworkCredential _credential = new NetworkCredential(Username, Password);
-
- public static IEnumerable<object[]> RemoteServersAndRedirectStatusCodes()
- {
- foreach (Configuration.Http.RemoteServer remoteServer in Configuration.Http.RemoteServers)
- {
- yield return new object[] { remoteServer, 300 };
- yield return new object[] { remoteServer, 301 };
- yield return new object[] { remoteServer, 302 };
- yield return new object[] { remoteServer, 303 };
- yield return new object[] { remoteServer, 307 };
- yield return new object[] { remoteServer, 308 };
- }
- }
-
- public static readonly object[][] RedirectStatusCodesOldMethodsNewMethods = {
- new object[] { 300, "GET", "GET" },
- new object[] { 300, "POST", "GET" },
- new object[] { 300, "HEAD", "HEAD" },
-
- new object[] { 301, "GET", "GET" },
- new object[] { 301, "POST", "GET" },
- new object[] { 301, "HEAD", "HEAD" },
-
- new object[] { 302, "GET", "GET" },
- new object[] { 302, "POST", "GET" },
- new object[] { 302, "HEAD", "HEAD" },
-
- new object[] { 303, "GET", "GET" },
- new object[] { 303, "POST", "GET" },
- new object[] { 303, "HEAD", "HEAD" },
-
- new object[] { 307, "GET", "GET" },
- new object[] { 307, "POST", "POST" },
- new object[] { 307, "HEAD", "HEAD" },
-
- new object[] { 308, "GET", "GET" },
- new object[] { 308, "POST", "POST" },
- new object[] { 308, "HEAD", "HEAD" },
- };
-
- public HttpClientHandlerTest_AutoRedirect(ITestOutputHelper output) : base(output) { }
-
- [OuterLoop("Uses external server")]
- [Theory, MemberData(nameof(RemoteServersAndRedirectStatusCodes))]
- public async Task GetAsync_AllowAutoRedirectFalse_RedirectFromHttpToHttp_StatusCodeRedirect(Configuration.Http.RemoteServer remoteServer, int statusCode)
- {
- HttpClientHandler handler = CreateHttpClientHandler();
- handler.AllowAutoRedirect = false;
- using (HttpClient client = CreateHttpClientForRemoteServer(remoteServer, handler))
- {
- Uri uri = remoteServer.RedirectUriForDestinationUri(
- statusCode: statusCode,
- destinationUri: remoteServer.EchoUri,
- hops: 1);
- _output.WriteLine("Uri: {0}", uri);
- using (HttpResponseMessage response = await client.GetAsync(uri))
- {
- Assert.Equal(statusCode, (int)response.StatusCode);
- Assert.Equal(uri, response.RequestMessage.RequestUri);
- }
- }
- }
-
- [Theory, MemberData(nameof(RedirectStatusCodesOldMethodsNewMethods))]
- public async Task AllowAutoRedirect_True_ValidateNewMethodUsedOnRedirection(
- int statusCode, string oldMethod, string newMethod)
- {
- HttpClientHandler handler = CreateHttpClientHandler();
- using (HttpClient client = CreateHttpClient(handler))
- {
- await LoopbackServer.CreateServerAsync(async (origServer, origUrl) =>
- {
- var request = new HttpRequestMessage(new HttpMethod(oldMethod), origUrl) { Version = VersionFromUseHttp2 };
-
- Task<HttpResponseMessage> getResponseTask = client.SendAsync(request);
-
- await LoopbackServer.CreateServerAsync(async (redirServer, redirUrl) =>
- {
- // Original URL will redirect to a different URL
- Task<List<string>> serverTask = origServer.AcceptConnectionSendResponseAndCloseAsync((HttpStatusCode)statusCode, $"Location: {redirUrl}\r\n");
-
- await Task.WhenAny(getResponseTask, serverTask);
- Assert.False(getResponseTask.IsCompleted, $"{getResponseTask.Status}: {getResponseTask.Exception}");
- await serverTask;
-
- // Redirected URL answers with success
- serverTask = redirServer.AcceptConnectionSendResponseAndCloseAsync();
- await TestHelper.WhenAllCompletedOrAnyFailed(getResponseTask, serverTask);
-
- List<string> receivedRequest = await serverTask;
-
- string[] statusLineParts = receivedRequest[0].Split(' ');
-
- using (HttpResponseMessage response = await getResponseTask)
- {
- Assert.Equal(200, (int)response.StatusCode);
- Assert.Equal(newMethod, statusLineParts[0]);
- }
- });
- });
- }
- }
-
- [Theory]
- [InlineData(300)]
- [InlineData(301)]
- [InlineData(302)]
- [InlineData(303)]
- public async Task AllowAutoRedirect_True_PostToGetDoesNotSendTE(int statusCode)
- {
- HttpClientHandler handler = CreateHttpClientHandler();
- using (HttpClient client = CreateHttpClient(handler))
- {
- await LoopbackServer.CreateServerAsync(async (origServer, origUrl) =>
- {
- var request = new HttpRequestMessage(HttpMethod.Post, origUrl) { Version = VersionFromUseHttp2 };
- request.Content = new StringContent(ExpectedContent);
- request.Headers.TransferEncodingChunked = true;
-
- Task<HttpResponseMessage> getResponseTask = client.SendAsync(request);
-
- await LoopbackServer.CreateServerAsync(async (redirServer, redirUrl) =>
- {
- // Original URL will redirect to a different URL
- Task serverTask = origServer.AcceptConnectionAsync(async connection =>
- {
- // Send Connection: close so the client will close connection after request is sent,
- // meaning we can just read to the end to get the content
- await connection.ReadRequestHeaderAndSendResponseAsync((HttpStatusCode)statusCode, $"Location: {redirUrl}\r\nConnection: close\r\n");
- connection.Socket.Shutdown(SocketShutdown.Send);
- await connection.ReadToEndAsync();
- });
-
- await Task.WhenAny(getResponseTask, serverTask);
- Assert.False(getResponseTask.IsCompleted, $"{getResponseTask.Status}: {getResponseTask.Exception}");
- await serverTask;
-
- // Redirected URL answers with success
- List<string> receivedRequest = null;
- string receivedContent = null;
- Task serverTask2 = redirServer.AcceptConnectionAsync(async connection =>
- {
- // Send Connection: close so the client will close connection after request is sent,
- // meaning we can just read to the end to get the content
- receivedRequest = await connection.ReadRequestHeaderAndSendResponseAsync(additionalHeaders: "Connection: close\r\n");
- connection.Socket.Shutdown(SocketShutdown.Send);
- receivedContent = await connection.ReadToEndAsync();
- });
-
- await TestHelper.WhenAllCompletedOrAnyFailed(getResponseTask, serverTask2);
-
- string[] statusLineParts = receivedRequest[0].Split(' ');
- Assert.Equal("GET", statusLineParts[0]);
- Assert.DoesNotContain(receivedRequest, line => line.StartsWith("Transfer-Encoding"));
- Assert.DoesNotContain(receivedRequest, line => line.StartsWith("Content-Length"));
-
- using (HttpResponseMessage response = await getResponseTask)
- {
- Assert.Equal(200, (int)response.StatusCode);
- }
- });
- });
- }
- }
-
- [OuterLoop("Uses external server")]
- [Theory, MemberData(nameof(RemoteServersAndRedirectStatusCodes))]
- public async Task GetAsync_AllowAutoRedirectTrue_RedirectFromHttpToHttp_StatusCodeOK(Configuration.Http.RemoteServer remoteServer, int statusCode)
- {
- HttpClientHandler handler = CreateHttpClientHandler();
- handler.AllowAutoRedirect = true;
- using (HttpClient client = CreateHttpClientForRemoteServer(remoteServer, handler))
- {
- Uri uri = remoteServer.RedirectUriForDestinationUri(
- statusCode: statusCode,
- destinationUri: remoteServer.EchoUri,
- hops: 1);
- _output.WriteLine("Uri: {0}", uri);
- using (HttpResponseMessage response = await client.GetAsync(uri))
- {
- Assert.Equal(HttpStatusCode.OK, response.StatusCode);
- Assert.Equal(remoteServer.EchoUri, response.RequestMessage.RequestUri);
- }
- }
- }
-
- [OuterLoop("Uses external server")]
- [Fact]
- public async Task GetAsync_AllowAutoRedirectTrue_RedirectFromHttpToHttps_StatusCodeOK()
- {
- HttpClientHandler handler = CreateHttpClientHandler();
- handler.AllowAutoRedirect = true;
- using (HttpClient client = CreateHttpClient(handler))
- {
- Uri uri = Configuration.Http.RemoteHttp11Server.RedirectUriForDestinationUri(
- statusCode: 302,
- destinationUri: Configuration.Http.RemoteSecureHttp11Server.EchoUri,
- hops: 1);
- _output.WriteLine("Uri: {0}", uri);
- using (HttpResponseMessage response = await client.GetAsync(uri))
- {
- Assert.Equal(HttpStatusCode.OK, response.StatusCode);
- Assert.Equal(Configuration.Http.SecureRemoteEchoServer, response.RequestMessage.RequestUri);
- }
- }
- }
-
- [OuterLoop("Uses external server")]
- [Fact]
- public async Task GetAsync_AllowAutoRedirectTrue_RedirectFromHttpsToHttp_StatusCodeRedirect()
- {
- HttpClientHandler handler = CreateHttpClientHandler();
- handler.AllowAutoRedirect = true;
- using (HttpClient client = CreateHttpClient(handler))
- {
- Uri uri = Configuration.Http.RemoteSecureHttp11Server.RedirectUriForDestinationUri(
- statusCode: 302,
- destinationUri: Configuration.Http.RemoteHttp11Server.EchoUri,
- hops: 1);
- _output.WriteLine("Uri: {0}", uri);
-
- using (HttpResponseMessage response = await client.GetAsync(uri))
- {
- Assert.Equal(HttpStatusCode.Redirect, response.StatusCode);
- Assert.Equal(uri, response.RequestMessage.RequestUri);
- }
- }
- }
-
- [Fact]
- public async Task GetAsync_AllowAutoRedirectTrue_RedirectWithoutLocation_ReturnsOriginalResponse()
- {
- HttpClientHandler handler = CreateHttpClientHandler();
- handler.AllowAutoRedirect = true;
- using (HttpClient client = CreateHttpClient(handler))
- {
- await LoopbackServer.CreateServerAsync(async (server, url) =>
- {
- Task<HttpResponseMessage> getTask = client.GetAsync(url);
- Task<List<string>> serverTask = server.AcceptConnectionSendResponseAndCloseAsync(HttpStatusCode.Found);
- await TestHelper.WhenAllCompletedOrAnyFailed(getTask, serverTask);
-
- using (HttpResponseMessage response = await getTask)
- {
- Assert.Equal(302, (int)response.StatusCode);
- }
- });
- }
- }
-
- [OuterLoop("Uses external server")]
- [Theory, MemberData(nameof(RemoteServersMemberData))]
- public async Task GetAsync_AllowAutoRedirectTrue_RedirectToUriWithParams_RequestMsgUriSet(Configuration.Http.RemoteServer remoteServer)
- {
- HttpClientHandler handler = CreateHttpClientHandler();
- handler.AllowAutoRedirect = true;
- Uri targetUri = remoteServer.BasicAuthUriForCreds(userName: Username, password: Password);
- using (HttpClient client = CreateHttpClientForRemoteServer(remoteServer, handler))
- {
- Uri uri = remoteServer.RedirectUriForDestinationUri(
- statusCode: 302,
- destinationUri: targetUri,
- hops: 1);
- _output.WriteLine("Uri: {0}", uri);
- using (HttpResponseMessage response = await client.GetAsync(uri))
- {
- Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
- Assert.Equal(targetUri, response.RequestMessage.RequestUri);
- }
- }
- }
-
- [OuterLoop("Uses external server")]
- [Theory]
- [InlineData(3, 2)]
- [InlineData(3, 3)]
- [InlineData(3, 4)]
- public async Task GetAsync_MaxAutomaticRedirectionsNServerHops_ThrowsIfTooMany(int maxHops, int hops)
- {
- HttpClientHandler handler = CreateHttpClientHandler();
- handler.MaxAutomaticRedirections = maxHops;
- using (HttpClient client = CreateHttpClient(handler))
- {
- Task<HttpResponseMessage> t = client.GetAsync(Configuration.Http.RemoteHttp11Server.RedirectUriForDestinationUri(
- statusCode: 302,
- destinationUri: Configuration.Http.RemoteHttp11Server.EchoUri,
- hops: hops));
-
- if (hops <= maxHops)
- {
- using (HttpResponseMessage response = await t)
- {
- Assert.Equal(HttpStatusCode.OK, response.StatusCode);
- Assert.Equal(Configuration.Http.RemoteEchoServer, response.RequestMessage.RequestUri);
- }
- }
- else
- {
- using (HttpResponseMessage response = await t)
- {
- Assert.Equal(HttpStatusCode.Redirect, response.StatusCode);
- }
- }
- }
- }
-
- [OuterLoop("Uses external server")]
- [Theory, MemberData(nameof(RemoteServersMemberData))]
- public async Task GetAsync_AllowAutoRedirectTrue_RedirectWithRelativeLocation(Configuration.Http.RemoteServer remoteServer)
- {
- HttpClientHandler handler = CreateHttpClientHandler();
- handler.AllowAutoRedirect = true;
- using (HttpClient client = CreateHttpClientForRemoteServer(remoteServer, handler))
- {
- Uri uri = remoteServer.RedirectUriForDestinationUri(
- statusCode: 302,
- destinationUri: remoteServer.EchoUri,
- hops: 1,
- relative: true);
- _output.WriteLine("Uri: {0}", uri);
-
- using (HttpResponseMessage response = await client.GetAsync(uri))
- {
- Assert.Equal(HttpStatusCode.OK, response.StatusCode);
- Assert.Equal(remoteServer.EchoUri, response.RequestMessage.RequestUri);
- }
- }
- }
-
- [Theory]
- [InlineData(200)]
- [InlineData(201)]
- [InlineData(400)]
- public async Task GetAsync_AllowAutoRedirectTrue_NonRedirectStatusCode_LocationHeader_NoRedirect(int statusCode)
- {
- HttpClientHandler handler = CreateHttpClientHandler();
- handler.AllowAutoRedirect = true;
- using (HttpClient client = CreateHttpClient(handler))
- {
- await LoopbackServer.CreateServerAsync(async (origServer, origUrl) =>
- {
- await LoopbackServer.CreateServerAsync(async (redirectServer, redirectUrl) =>
- {
- Task<HttpResponseMessage> getResponseTask = client.GetAsync(origUrl);
-
- Task redirectTask = redirectServer.AcceptConnectionSendResponseAndCloseAsync();
-
- await TestHelper.WhenAllCompletedOrAnyFailed(
- getResponseTask,
- origServer.AcceptConnectionSendResponseAndCloseAsync((HttpStatusCode)statusCode, $"Location: {redirectUrl}\r\n"));
-
- using (HttpResponseMessage response = await getResponseTask)
- {
- Assert.Equal(statusCode, (int)response.StatusCode);
- Assert.Equal(origUrl, response.RequestMessage.RequestUri);
- Assert.False(redirectTask.IsCompleted, "Should not have redirected to Location");
- }
- });
- });
- }
- }
-
- [Theory]
- [InlineData("#origFragment", "", "#origFragment", false)]
- [InlineData("#origFragment", "", "#origFragment", true)]
- [InlineData("", "#redirFragment", "#redirFragment", false)]
- [InlineData("", "#redirFragment", "#redirFragment", true)]
- [InlineData("#origFragment", "#redirFragment", "#redirFragment", false)]
- [InlineData("#origFragment", "#redirFragment", "#redirFragment", true)]
- public async Task GetAsync_AllowAutoRedirectTrue_RetainsOriginalFragmentIfAppropriate(
- string origFragment, string redirFragment, string expectedFragment, bool useRelativeRedirect)
- {
- HttpClientHandler handler = CreateHttpClientHandler();
- handler.AllowAutoRedirect = true;
- using (HttpClient client = CreateHttpClient(handler))
- {
- await LoopbackServer.CreateServerAsync(async (origServer, origUrl) =>
- {
- origUrl = new UriBuilder(origUrl) { Fragment = origFragment }.Uri;
- Uri redirectUrl = new UriBuilder(origUrl) { Fragment = redirFragment }.Uri;
- if (useRelativeRedirect)
- {
- redirectUrl = new Uri(redirectUrl.GetComponents(UriComponents.PathAndQuery | UriComponents.Fragment, UriFormat.SafeUnescaped), UriKind.Relative);
- }
- Uri expectedUrl = new UriBuilder(origUrl) { Fragment = expectedFragment }.Uri;
-
- // Make and receive the first request that'll be redirected.
- Task<HttpResponseMessage> getResponse = client.GetAsync(origUrl);
- Task firstRequest = origServer.AcceptConnectionSendResponseAndCloseAsync(HttpStatusCode.Found, $"Location: {redirectUrl}\r\n");
- Assert.Equal(firstRequest, await Task.WhenAny(firstRequest, getResponse));
-
- // Receive the second request.
- Task<List<string>> secondRequest = origServer.AcceptConnectionSendResponseAndCloseAsync();
- await TestHelper.WhenAllCompletedOrAnyFailed(secondRequest, getResponse);
-
- // Make sure the server received the second request for the right Uri.
- Assert.NotEmpty(secondRequest.Result);
- string[] statusLineParts = secondRequest.Result[0].Split(' ');
- Assert.Equal(3, statusLineParts.Length);
- Assert.Equal(expectedUrl.GetComponents(UriComponents.PathAndQuery, UriFormat.SafeUnescaped), statusLineParts[1]);
-
- // Make sure the request message was updated with the correct redirected location.
- using (HttpResponseMessage response = await getResponse)
- {
- Assert.Equal(200, (int)response.StatusCode);
- Assert.Equal(expectedUrl.ToString(), response.RequestMessage.RequestUri.ToString());
- }
- });
- }
- }
-
- [Theory, MemberData(nameof(RemoteServersMemberData))]
- [OuterLoop("Uses external server")]
- public async Task GetAsync_CredentialIsNetworkCredentialUriRedirect_StatusCodeUnauthorized(Configuration.Http.RemoteServer remoteServer)
- {
- HttpClientHandler handler = CreateHttpClientHandler();
- handler.Credentials = _credential;
- using (HttpClient client = CreateHttpClientForRemoteServer(remoteServer, handler))
- {
- Uri redirectUri = remoteServer.RedirectUriForCreds(
- statusCode: 302,
- userName: Username,
- password: Password);
- using (HttpResponseMessage unAuthResponse = await client.GetAsync(redirectUri))
- {
- Assert.Equal(HttpStatusCode.Unauthorized, unAuthResponse.StatusCode);
- }
- }
- }
-
- [Theory, MemberData(nameof(RemoteServersMemberData))]
- [OuterLoop("Uses external server")]
- public async Task HttpClientHandler_CredentialIsNotCredentialCacheAfterRedirect_StatusCodeOK(Configuration.Http.RemoteServer remoteServer)
- {
- HttpClientHandler handler = CreateHttpClientHandler();
- handler.Credentials = _credential;
- using (HttpClient client = CreateHttpClientForRemoteServer(remoteServer, handler))
- {
- Uri redirectUri = remoteServer.RedirectUriForCreds(
- statusCode: 302,
- userName: Username,
- password: Password);
- using (HttpResponseMessage unAuthResponse = await client.GetAsync(redirectUri))
- {
- Assert.Equal(HttpStatusCode.Unauthorized, unAuthResponse.StatusCode);
- }
-
- // Use the same handler to perform get request, authentication should succeed after redirect.
- Uri uri = remoteServer.BasicAuthUriForCreds(userName: Username, password: Password);
- using (HttpResponseMessage authResponse = await client.GetAsync(uri))
- {
- Assert.Equal(HttpStatusCode.OK, authResponse.StatusCode);
- }
- }
- }
-
- [OuterLoop("Uses external server")]
- [Theory, MemberData(nameof(RemoteServersAndRedirectStatusCodes))]
- public async Task GetAsync_CredentialIsCredentialCacheUriRedirect_StatusCodeOK(Configuration.Http.RemoteServer remoteServer, int statusCode)
- {
- Uri uri = remoteServer.BasicAuthUriForCreds(userName: Username, password: Password);
- Uri redirectUri = remoteServer.RedirectUriForCreds(
- statusCode: statusCode,
- userName: Username,
- password: Password);
- _output.WriteLine(uri.AbsoluteUri);
- _output.WriteLine(redirectUri.AbsoluteUri);
- var credentialCache = new CredentialCache();
- credentialCache.Add(uri, "Basic", _credential);
-
- HttpClientHandler handler = CreateHttpClientHandler();
- handler.Credentials = credentialCache;
- using (HttpClient client = CreateHttpClientForRemoteServer(remoteServer, handler))
- {
- using (HttpResponseMessage response = await client.GetAsync(redirectUri))
- {
- Assert.Equal(HttpStatusCode.OK, response.StatusCode);
- Assert.Equal(uri, response.RequestMessage.RequestUri);
- }
- }
- }
-
- [OuterLoop("Uses external server")]
- [Theory, MemberData(nameof(RemoteServersAndRedirectStatusCodes))]
- public async Task DefaultHeaders_SetCredentials_ClearedOnRedirect(Configuration.Http.RemoteServer remoteServer, int statusCode)
- {
- HttpClientHandler handler = CreateHttpClientHandler();
- using (HttpClient client = CreateHttpClientForRemoteServer(remoteServer, handler))
- {
- string credentialString = _credential.UserName + ":" + _credential.Password;
- client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", credentialString);
- Uri uri = remoteServer.RedirectUriForDestinationUri(
- statusCode: statusCode,
- destinationUri: remoteServer.EchoUri,
- hops: 1);
- _output.WriteLine("Uri: {0}", uri);
- using (HttpResponseMessage response = await client.GetAsync(uri))
- {
- string responseText = await response.Content.ReadAsStringAsync();
- _output.WriteLine(responseText);
- Assert.False(TestHelper.JsonMessageContainsKey(responseText, "Authorization"));
- }
- }
- }
- }
-}
+++ /dev/null
-// 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.Collections.Generic;
-using System.Diagnostics;
-using System.DirectoryServices.Protocols;
-using System.IO;
-using System.Linq;
-using System.Net.Test.Common;
-using System.Reflection;
-using System.Runtime.CompilerServices;
-using System.Threading;
-using System.Threading.Tasks;
-
-using Microsoft.DotNet.XUnitExtensions;
-
-using Xunit;
-using Xunit.Abstractions;
-
-namespace System.Net.Http.Functional.Tests
-{
- public abstract class HttpClientHandler_Cancellation_Test : HttpClientHandlerTestBase
- {
- public HttpClientHandler_Cancellation_Test(ITestOutputHelper output) : base(output) { }
-
- [Theory]
- [InlineData(false, CancellationMode.Token)]
- [InlineData(true, CancellationMode.Token)]
- public async Task PostAsync_CancelDuringRequestContentSend_TaskCanceledQuickly(bool chunkedTransfer, CancellationMode mode)
- {
- if (LoopbackServerFactory.IsHttp2 && chunkedTransfer)
- {
- // There is no chunked encoding in HTTP/2
- return;
- }
-
- var serverRelease = new TaskCompletionSource<bool>();
- await LoopbackServerFactory.CreateClientAndServerAsync(async uri =>
- {
- try
- {
- using (HttpClient client = CreateHttpClient())
- {
- client.Timeout = Timeout.InfiniteTimeSpan;
- var cts = new CancellationTokenSource();
-
- var waitToSend = new TaskCompletionSource<bool>();
- var contentSending = new TaskCompletionSource<bool>();
- var req = new HttpRequestMessage(HttpMethod.Post, uri) { Version = VersionFromUseHttp2 };
- req.Content = new ByteAtATimeContent(int.MaxValue, waitToSend.Task, contentSending, millisecondDelayBetweenBytes: 1);
- req.Headers.TransferEncodingChunked = chunkedTransfer;
-
- Task<HttpResponseMessage> resp = client.SendAsync(req, HttpCompletionOption.ResponseHeadersRead, cts.Token);
- waitToSend.SetResult(true);
- await contentSending.Task;
- Cancel(mode, client, cts);
- await ValidateClientCancellationAsync(() => resp);
- }
- }
- finally
- {
- serverRelease.SetResult(true);
- }
- }, async server =>
- {
- try
- {
- await server.AcceptConnectionAsync(connection => serverRelease.Task);
- }
- catch { }; // Ignore any closing errors since we did not really process anything.
- });
- }
-
- [Theory]
- [MemberData(nameof(OneBoolAndCancellationMode))]
- public async Task GetAsync_CancelDuringResponseHeadersReceived_TaskCanceledQuickly(bool connectionClose, CancellationMode mode)
- {
- if (LoopbackServerFactory.IsHttp2 && connectionClose)
- {
- // There is no Connection header in HTTP/2
- return;
- }
-
- using (HttpClient client = CreateHttpClient())
- {
- client.Timeout = Timeout.InfiniteTimeSpan;
- var cts = new CancellationTokenSource();
-
- await LoopbackServerFactory.CreateServerAsync(async (server, url) =>
- {
- var partialResponseHeadersSent = new TaskCompletionSource<bool>();
- var clientFinished = new TaskCompletionSource<bool>();
-
- Task serverTask = server.AcceptConnectionAsync(async connection =>
- {
- await connection.ReadRequestDataAsync();
- await connection.SendResponseAsync(HttpStatusCode.OK, content: null, isFinal: false);
-
- partialResponseHeadersSent.TrySetResult(true);
- await clientFinished.Task;
- });
-
- await ValidateClientCancellationAsync(async () =>
- {
- var req = new HttpRequestMessage(HttpMethod.Get, url) { Version = VersionFromUseHttp2 };
- req.Headers.ConnectionClose = connectionClose;
-
- Task<HttpResponseMessage> getResponse = client.SendAsync(req, HttpCompletionOption.ResponseHeadersRead, cts.Token);
- await partialResponseHeadersSent.Task;
- Cancel(mode, client, cts);
- await getResponse;
- });
-
- try
- {
- clientFinished.SetResult(true);
- await serverTask;
- } catch { }
- });
- }
- }
-
- [Theory]
- [ActiveIssue("https://github.com/dotnet/corefx/issues/28805")]
- [MemberData(nameof(TwoBoolsAndCancellationMode))]
- public async Task GetAsync_CancelDuringResponseBodyReceived_Buffered_TaskCanceledQuickly(bool chunkedTransfer, bool connectionClose, CancellationMode mode)
- {
- if (LoopbackServerFactory.IsHttp2 && (chunkedTransfer || connectionClose))
- {
- // There is no chunked encoding or connection header in HTTP/2
- return;
- }
-
- using (HttpClient client = CreateHttpClient())
- {
- client.Timeout = Timeout.InfiniteTimeSpan;
- var cts = new CancellationTokenSource();
-
- await LoopbackServerFactory.CreateServerAsync(async (server, url) =>
- {
- var responseHeadersSent = new TaskCompletionSource<bool>();
- var clientFinished = new TaskCompletionSource<bool>();
-
- Task serverTask = server.AcceptConnectionAsync(async connection =>
- {
- var headers = new List<HttpHeaderData>();
- headers.Add(chunkedTransfer ? new HttpHeaderData("Transfer-Encoding", "chunked") : new HttpHeaderData("Content-Length", "20"));
- if (connectionClose)
- {
- headers.Add(new HttpHeaderData("Connection", "close"));
- }
-
- await connection.ReadRequestDataAsync();
- await connection.SendResponseAsync(HttpStatusCode.OK, headers: headers, content: "123", isFinal: false);
- responseHeadersSent.TrySetResult(true);
- await clientFinished.Task;
- });
-
- await ValidateClientCancellationAsync(async () =>
- {
- var req = new HttpRequestMessage(HttpMethod.Get, url) { Version = VersionFromUseHttp2 };
- req.Headers.ConnectionClose = connectionClose;
-
- Task<HttpResponseMessage> getResponse = client.SendAsync(req, HttpCompletionOption.ResponseContentRead, cts.Token);
- await responseHeadersSent.Task;
- await Task.Delay(1); // make it more likely that client will have started processing response body
- Cancel(mode, client, cts);
- await getResponse;
- });
-
- try
- {
- clientFinished.SetResult(true);
- await serverTask;
- } catch { }
- });
- }
- }
-
- [Theory]
- [MemberData(nameof(ThreeBools))]
- public async Task GetAsync_CancelDuringResponseBodyReceived_Unbuffered_TaskCanceledQuickly(bool chunkedTransfer, bool connectionClose, bool readOrCopyToAsync)
- {
- if (LoopbackServerFactory.IsHttp2 && (chunkedTransfer || connectionClose))
- {
- // There is no chunked encoding or connection header in HTTP/2
- return;
- }
-
- using (HttpClient client = CreateHttpClient())
- {
- client.Timeout = Timeout.InfiniteTimeSpan;
- var cts = new CancellationTokenSource();
-
- await LoopbackServerFactory.CreateServerAsync(async (server, url) =>
- {
- var clientFinished = new TaskCompletionSource<bool>();
-
- Task serverTask = server.AcceptConnectionAsync(async connection =>
- {
- var headers = new List<HttpHeaderData>();
- headers.Add(chunkedTransfer ? new HttpHeaderData("Transfer-Encoding", "chunked") : new HttpHeaderData("Content-Length", "20"));
- if (connectionClose)
- {
- headers.Add(new HttpHeaderData("Connection", "close"));
- }
-
- await connection.ReadRequestDataAsync();
- await connection.SendResponseAsync(HttpStatusCode.OK, headers: headers, isFinal: false);
- await clientFinished.Task;
- });
-
- var req = new HttpRequestMessage(HttpMethod.Get, url) { Version = VersionFromUseHttp2 };
- req.Headers.ConnectionClose = connectionClose;
- Task<HttpResponseMessage> getResponse = client.SendAsync(req, HttpCompletionOption.ResponseHeadersRead, cts.Token);
- await ValidateClientCancellationAsync(async () =>
- {
- HttpResponseMessage resp = await getResponse;
- Stream respStream = await resp.Content.ReadAsStreamAsync();
- Task readTask = readOrCopyToAsync ?
- respStream.ReadAsync(new byte[1], 0, 1, cts.Token) :
- respStream.CopyToAsync(Stream.Null, 10, cts.Token);
- cts.Cancel();
- await readTask;
- });
-
- try
- {
- clientFinished.SetResult(true);
- await serverTask;
- } catch { }
- });
- }
- }
-
- [Theory]
- [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)
- {
- using (HttpClient client = CreateHttpClient())
- {
- client.Timeout = Timeout.InfiniteTimeSpan;
-
- await LoopbackServerFactory.CreateServerAsync(async (server, url) =>
- {
- var clientReadSomeBody = new TaskCompletionSource<bool>();
- var clientFinished = new TaskCompletionSource<bool>();
-
- var responseContentSegment = new string('s', 3000);
- int responseSegments = 4;
- int contentLength = responseContentSegment.Length * responseSegments;
-
- Task serverTask = server.AcceptConnectionAsync(async connection =>
- {
- await connection.ReadRequestDataAsync();
- await connection.SendResponseAsync(HttpStatusCode.OK, headers: new HttpHeaderData[] { new HttpHeaderData("Content-Length", contentLength.ToString()) }, isFinal: false);
- for (int i = 0; i < responseSegments; i++)
- {
- await connection.SendResponseBodyAsync(responseContentSegment, isFinal: i == responseSegments - 1);
- if (i == 0)
- {
- await clientReadSomeBody.Task;
- }
- }
-
- await clientFinished.Task;
- });
-
-
- using (HttpResponseMessage resp = await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead))
- using (Stream respStream = await resp.Content.ReadAsStreamAsync())
- {
- var result = new MemoryStream();
- int b = respStream.ReadByte();
- Assert.NotEqual(-1, b);
- result.WriteByte((byte)b);
-
- Cancel(mode, client, null); // should not cancel the operation, as using ResponseHeadersRead
- clientReadSomeBody.SetResult(true);
-
- if (copyToAsync)
- {
- await respStream.CopyToAsync(result, 10, new CancellationTokenSource().Token);
- }
- else
- {
- byte[] buffer = new byte[10];
- int bytesRead;
- while ((bytesRead = await respStream.ReadAsync(buffer, 0, buffer.Length)) > 0)
- {
- result.Write(buffer, 0, bytesRead);
- }
- }
-
- Assert.Equal(contentLength, result.Length);
- }
-
- clientFinished.SetResult(true);
- await serverTask;
- });
- }
- }
-
- [ConditionalFact]
- public async Task MaxConnectionsPerServer_WaitingConnectionsAreCancelable()
- {
- if (LoopbackServerFactory.IsHttp2)
- {
- // HTTP/2 does not use connection limits.
- throw new SkipTestException("Not supported on HTTP/2");
- }
-
- using (HttpClientHandler handler = CreateHttpClientHandler())
- using (HttpClient client = CreateHttpClient(handler))
- {
- handler.MaxConnectionsPerServer = 1;
- client.Timeout = Timeout.InfiniteTimeSpan;
-
- await LoopbackServer.CreateServerAsync(async (server, url) =>
- {
- var serverAboutToBlock = new TaskCompletionSource<bool>();
- var blockServerResponse = new TaskCompletionSource<bool>();
-
- Task serverTask1 = server.AcceptConnectionAsync(async connection1 =>
- {
- await connection1.ReadRequestHeaderAsync();
- await connection1.Writer.WriteAsync($"HTTP/1.1 200 OK\r\nConnection: close\r\nDate: {DateTimeOffset.UtcNow:R}\r\n");
- serverAboutToBlock.SetResult(true);
- await blockServerResponse.Task;
- await connection1.Writer.WriteAsync("Content-Length: 5\r\n\r\nhello");
- });
-
- Task get1 = client.GetAsync(url);
- await serverAboutToBlock.Task;
-
- var cts = new CancellationTokenSource();
- Task get2 = ValidateClientCancellationAsync(() => client.GetAsync(url, cts.Token));
- Task get3 = ValidateClientCancellationAsync(() => client.GetAsync(url, cts.Token));
-
- Task get4 = client.GetAsync(url);
-
- cts.Cancel();
- await get2;
- await get3;
-
- blockServerResponse.SetResult(true);
- await new[] { get1, serverTask1 }.WhenAllOrAnyFailed();
-
- Task serverTask4 = server.AcceptConnectionSendResponseAndCloseAsync();
-
- await new[] { get4, serverTask4 }.WhenAllOrAnyFailed();
- });
- }
- }
-
- [Fact]
- public async Task SendAsync_Cancel_CancellationTokenPropagates()
- {
- TaskCompletionSource<bool> clientCanceled = new TaskCompletionSource<bool>();
- await LoopbackServerFactory.CreateClientAndServerAsync(
- async uri =>
- {
- var cts = new CancellationTokenSource();
- cts.Cancel();
-
- using (HttpClient client = CreateHttpClient())
- {
- OperationCanceledException ex = null;
- try
- {
- await client.GetAsync(uri, cts.Token);
- }
- catch (OperationCanceledException e)
- {
- ex = e;
- }
- Assert.True(ex != null, "Expected OperationCancelledException, but no exception was thrown.");
-
- Assert.True(cts.Token.IsCancellationRequested, "cts token IsCancellationRequested");
-
- // .NET Framework has bug where it doesn't propagate token information.
- Assert.True(ex.CancellationToken.IsCancellationRequested, "exception token IsCancellationRequested");
-
- clientCanceled.SetResult(true);
- }
- },
- async server =>
- {
- Task serverTask = server.HandleRequestAsync();
- await clientCanceled.Task;
- });
- }
-
- public static IEnumerable<object[]> PostAsync_Cancel_CancellationTokenPassedToContent_MemberData()
- {
- // Note: For HTTP2, the actual token will be a linked token and will not be an exact match for the original token.
- // Verify that it behaves as expected by cancelling it and validating that cancellation propagates.
-
- // StreamContent
- {
- CancellationTokenSource tokenSource = new CancellationTokenSource();
- var actualToken = new StrongBox<CancellationToken>();
- bool called = false;
- var content = new StreamContent(new DelegateStream(
- canReadFunc: () => true,
- readAsyncFunc: (buffer, offset, count, cancellationToken) =>
- {
- int result = 1;
- if (called)
- {
- result = 0;
- Assert.False(cancellationToken.IsCancellationRequested);
- tokenSource.Cancel();
- Assert.True(cancellationToken.IsCancellationRequested);
- }
-
- called = true;
- return Task.FromResult(result);
- }
- ));
- yield return new object[] { content, tokenSource };
- }
-
- // MultipartContent
- {
- CancellationTokenSource tokenSource = new CancellationTokenSource();
- var actualToken = new StrongBox<CancellationToken>();
- bool called = false;
- var content = new MultipartContent();
- content.Add(new StreamContent(new DelegateStream(
- canReadFunc: () => true,
- canSeekFunc: () => true,
- lengthFunc: () => 1,
- positionGetFunc: () => 0,
- positionSetFunc: _ => {},
- readAsyncFunc: (buffer, offset, count, cancellationToken) =>
- {
- int result = 1;
- if (called)
- {
- result = 0;
- Assert.False(cancellationToken.IsCancellationRequested);
- tokenSource.Cancel();
- Assert.True(cancellationToken.IsCancellationRequested);
- }
-
- called = true;
- return Task.FromResult(result);
- }
- )));
- yield return new object[] { content, tokenSource };
- }
-
- // MultipartFormDataContent
- {
- CancellationTokenSource tokenSource = new CancellationTokenSource();
- var actualToken = new StrongBox<CancellationToken>();
- bool called = false;
- var content = new MultipartFormDataContent();
- content.Add(new StreamContent(new DelegateStream(
- canReadFunc: () => true,
- canSeekFunc: () => true,
- lengthFunc: () => 1,
- positionGetFunc: () => 0,
- positionSetFunc: _ => {},
- readAsyncFunc: (buffer, offset, count, cancellationToken) =>
- {
- int result = 1;
- if (called)
- {
- result = 0;
- Assert.False(cancellationToken.IsCancellationRequested);
- tokenSource.Cancel();
- Assert.True(cancellationToken.IsCancellationRequested);
- }
-
- called = true;
- return Task.FromResult(result);
- }
- )));
- yield return new object[] { content, tokenSource };
- }
- }
-
- [OuterLoop("Uses Task.Delay")]
- [Theory]
- [MemberData(nameof(PostAsync_Cancel_CancellationTokenPassedToContent_MemberData))]
- public async Task PostAsync_Cancel_CancellationTokenPassedToContent(HttpContent content, CancellationTokenSource cancellationTokenSource)
- {
- await LoopbackServerFactory.CreateClientAndServerAsync(
- async uri =>
- {
- using (var invoker = new HttpMessageInvoker(CreateHttpClientHandler()))
- using (var req = new HttpRequestMessage(HttpMethod.Post, uri) { Content = content, Version = VersionFromUseHttp2 })
- try
- {
- using (HttpResponseMessage resp = await invoker.SendAsync(req, cancellationTokenSource.Token))
- {
- Assert.Equal("Hello World", await resp.Content.ReadAsStringAsync());
- }
- }
- catch (OperationCanceledException) { }
- },
- async server =>
- {
- try
- {
- await server.HandleRequestAsync(content: "Hello World");
- }
- catch (Exception) { }
- });
- }
-
- private async Task ValidateClientCancellationAsync(Func<Task> clientBodyAsync)
- {
- var stopwatch = Stopwatch.StartNew();
- Exception error = await Record.ExceptionAsync(clientBodyAsync);
- stopwatch.Stop();
-
- Assert.NotNull(error);
-
- Assert.True(
- error is OperationCanceledException,
- "Expected cancellation exception, got:" + Environment.NewLine + error);
-
- Assert.True(stopwatch.Elapsed < new TimeSpan(0, 0, 60), $"Elapsed time {stopwatch.Elapsed} should be less than 60 seconds, was {stopwatch.Elapsed.TotalSeconds}");
- }
-
- private static void Cancel(CancellationMode mode, HttpClient client, CancellationTokenSource cts)
- {
- if ((mode & CancellationMode.Token) != 0)
- {
- cts?.Cancel();
- }
-
- if ((mode & CancellationMode.CancelPendingRequests) != 0)
- {
- client?.CancelPendingRequests();
- }
-
- if ((mode & CancellationMode.DisposeHttpClient) != 0)
- {
- client?.Dispose();
- }
- }
-
- [Flags]
- public enum CancellationMode
- {
- Token = 0x1,
- CancelPendingRequests = 0x2,
- DisposeHttpClient = 0x4
- }
-
- private static readonly bool[] s_bools = new[] { true, false };
-
- public static IEnumerable<object[]> OneBoolAndCancellationMode() =>
- from first in s_bools
- from mode in new[] { CancellationMode.Token, CancellationMode.CancelPendingRequests, CancellationMode.DisposeHttpClient, CancellationMode.Token | CancellationMode.CancelPendingRequests }
- select new object[] { first, mode };
-
- public static IEnumerable<object[]> TwoBoolsAndCancellationMode() =>
- from first in s_bools
- from second in s_bools
- from mode in new[] { CancellationMode.Token, CancellationMode.CancelPendingRequests, CancellationMode.DisposeHttpClient, CancellationMode.Token | CancellationMode.CancelPendingRequests }
- select new object[] { first, second, mode };
-
- public static IEnumerable<object[]> ThreeBools() =>
- from first in s_bools
- from second in s_bools
- from third in s_bools
- select new object[] { first, second, third };
- }
-}
+++ /dev/null
-// 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.Collections.Generic;
-using System.Net.Security;
-using System.Net.Sockets;
-using System.Net.Test.Common;
-using System.Runtime.InteropServices;
-using System.Security.Cryptography.X509Certificates;
-using System.Threading.Tasks;
-using Microsoft.DotNet.RemoteExecutor;
-using Xunit;
-using Xunit.Abstractions;
-
-namespace System.Net.Http.Functional.Tests
-{
- using Configuration = System.Net.Test.Common.Configuration;
-
- public abstract class HttpClientHandler_ClientCertificates_Test : HttpClientHandlerTestBase
- {
- public HttpClientHandler_ClientCertificates_Test(ITestOutputHelper output) : base(output) { }
-
- [Fact]
- public void ClientCertificateOptions_Default()
- {
- using (HttpClientHandler handler = CreateHttpClientHandler())
- {
- Assert.Equal(ClientCertificateOption.Manual, handler.ClientCertificateOptions);
- }
- }
-
- [Theory]
- [InlineData((ClientCertificateOption)2)]
- [InlineData((ClientCertificateOption)(-1))]
- public void ClientCertificateOptions_InvalidArg_ThrowsException(ClientCertificateOption option)
- {
- using (HttpClientHandler handler = CreateHttpClientHandler())
- {
- AssertExtensions.Throws<ArgumentOutOfRangeException>("value", () => handler.ClientCertificateOptions = option);
- }
- }
-
- [Theory]
- [InlineData(ClientCertificateOption.Automatic)]
- [InlineData(ClientCertificateOption.Manual)]
- public void ClientCertificateOptions_ValueArg_Roundtrips(ClientCertificateOption option)
- {
- using (HttpClientHandler handler = CreateHttpClientHandler())
- {
- handler.ClientCertificateOptions = option;
- Assert.Equal(option, handler.ClientCertificateOptions);
- }
- }
-
- [Fact]
- public void ClientCertificates_ClientCertificateOptionsAutomatic_ThrowsException()
- {
- using (HttpClientHandler handler = CreateHttpClientHandler())
- {
- handler.ClientCertificateOptions = ClientCertificateOption.Automatic;
- Assert.Throws<InvalidOperationException>(() => handler.ClientCertificates);
- }
- }
-
- private HttpClient CreateHttpClientWithCert(X509Certificate2 cert)
- {
- HttpClientHandler handler = CreateHttpClientHandler();
- handler.ServerCertificateCustomValidationCallback = TestHelper.AllowAllCertificates;
- Assert.NotNull(cert);
- handler.ClientCertificates.Add(cert);
- Assert.True(handler.ClientCertificates.Contains(cert));
-
- return CreateHttpClient(handler);
- }
-
- [Theory]
- [InlineData(1, true)]
- [InlineData(2, true)]
- [InlineData(3, false)]
- public async Task Manual_CertificateOnlySentWhenValid_Success(int certIndex, bool serverExpectsClientCertificate)
- {
- var options = new LoopbackServer.Options { UseSsl = true };
-
- X509Certificate2 GetClientCertificate(int certIndex) => certIndex switch
- {
- // This is a valid client cert since it has an EKU with a ClientAuthentication OID.
- 1 => Configuration.Certificates.GetClientCertificate(),
-
- // This is a valid client cert since it has no EKU thus all usages are permitted.
- 2 => Configuration.Certificates.GetNoEKUCertificate(),
-
- // This is an invalid client cert since it has an EKU but is missing ClientAuthentication OID.
- 3 => Configuration.Certificates.GetServerCertificate(),
- _ => null
- };
-
- await LoopbackServer.CreateServerAsync(async (server, url) =>
- {
- using X509Certificate2 cert = GetClientCertificate(certIndex);
- using HttpClient client = CreateHttpClientWithCert(cert);
-
- await TestHelper.WhenAllCompletedOrAnyFailed(
- client.GetStringAsync(url),
- server.AcceptConnectionAsync(async connection =>
- {
- SslStream sslStream = Assert.IsType<SslStream>(connection.Stream);
- if (serverExpectsClientCertificate)
- {
- _output.WriteLine(
- "Client cert: {0}",
- ((X509Certificate2)sslStream.RemoteCertificate).GetNameInfo(X509NameType.SimpleName, false));
- Assert.Equal(cert, sslStream.RemoteCertificate);
- }
- else
- {
- Assert.Null(sslStream.RemoteCertificate);
- }
-
- await connection.ReadRequestHeaderAndSendResponseAsync(additionalHeaders: "Connection: close\r\n");
- }));
- }, options);
- }
-
- [OuterLoop("Uses GC and waits for finalizers")]
- [Theory]
- [InlineData(6, false)]
- [InlineData(3, true)]
- public async Task Manual_CertificateSentMatchesCertificateReceived_Success(
- int numberOfRequests,
- bool reuseClient) // validate behavior with and without connection pooling, which impacts client cert usage
- {
- var options = new LoopbackServer.Options { UseSsl = true };
-
- async Task MakeAndValidateRequest(HttpClient client, LoopbackServer server, Uri url, X509Certificate2 cert)
- {
- await TestHelper.WhenAllCompletedOrAnyFailed(
- client.GetStringAsync(url),
- server.AcceptConnectionAsync(async connection =>
- {
- SslStream sslStream = Assert.IsType<SslStream>(connection.Stream);
- Assert.Equal(cert, sslStream.RemoteCertificate);
-
- await connection.ReadRequestHeaderAndSendResponseAsync(additionalHeaders: "Connection: close\r\n");
- }));
- };
-
- await LoopbackServer.CreateServerAsync(async (server, url) =>
- {
- using (X509Certificate2 cert = Configuration.Certificates.GetClientCertificate())
- {
- if (reuseClient)
- {
- using (HttpClient client = CreateHttpClientWithCert(cert))
- {
- for (int i = 0; i < numberOfRequests; i++)
- {
- await MakeAndValidateRequest(client, server, url, cert);
-
- GC.Collect();
- GC.WaitForPendingFinalizers();
- }
- }
- }
- else
- {
- for (int i = 0; i < numberOfRequests; i++)
- {
- using (HttpClient client = CreateHttpClientWithCert(cert))
- {
- await MakeAndValidateRequest(client, server, url, cert);
- }
-
- GC.Collect();
- GC.WaitForPendingFinalizers();
- }
- }
- }
- }, options);
- }
-
- [ActiveIssue("https://github.com/dotnet/corefx/issues/37336")]
- [Theory]
- [InlineData(ClientCertificateOption.Manual)]
- [InlineData(ClientCertificateOption.Automatic)]
- public async Task AutomaticOrManual_DoesntFailRegardlessOfWhetherClientCertsAreAvailable(ClientCertificateOption mode)
- {
- using (HttpClientHandler handler = CreateHttpClientHandler())
- using (HttpClient client = CreateHttpClient(handler))
- {
- handler.ServerCertificateCustomValidationCallback = TestHelper.AllowAllCertificates;
- handler.ClientCertificateOptions = mode;
-
- await LoopbackServer.CreateServerAsync(async server =>
- {
- Task clientTask = client.GetStringAsync(server.Uri);
- Task serverTask = server.AcceptConnectionAsync(async connection =>
- {
- SslStream sslStream = Assert.IsType<SslStream>(connection.Stream);
- await connection.ReadRequestHeaderAndSendResponseAsync();
- });
-
- await new Task[] { clientTask, serverTask }.WhenAllOrAnyFailed();
- }, new LoopbackServer.Options { UseSsl = true });
- }
- }
- }
-}
+++ /dev/null
-// 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.Collections.Generic;
-using System.Diagnostics;
-using System.Linq;
-using System.Net.Test.Common;
-using System.Threading.Tasks;
-using Microsoft.DotNet.XUnitExtensions;
-using Xunit;
-using Xunit.Abstractions;
-
-namespace System.Net.Http.Functional.Tests
-{
- public abstract class HttpClientHandlerTest_Cookies : HttpClientHandlerTestBase
- {
- private const string s_cookieName = "ABC";
- private const string s_cookieValue = "123";
- private const string s_expectedCookieHeaderValue = "ABC=123";
-
- private const string s_customCookieHeaderValue = "CustomCookie=456";
-
- private const string s_simpleContent = "Hello world!";
-
- public HttpClientHandlerTest_Cookies(ITestOutputHelper output) : base(output) { }
-
- //
- // Send cookie tests
- //
-
- private static CookieContainer CreateSingleCookieContainer(Uri uri) => CreateSingleCookieContainer(uri, s_cookieName, s_cookieValue);
-
- private static CookieContainer CreateSingleCookieContainer(Uri uri, string cookieName, string cookieValue)
- {
- var container = new CookieContainer();
- container.Add(uri, new Cookie(cookieName, cookieValue));
- return container;
- }
-
- private static string GetCookieHeaderValue(string cookieName, string cookieValue) => $"{cookieName}={cookieValue}";
-
- [Fact]
- public async Task GetAsync_DefaultCoookieContainer_NoCookieSent()
- {
- await LoopbackServerFactory.CreateClientAndServerAsync(
- async uri =>
- {
- using (HttpClient client = CreateHttpClient())
- {
- await client.GetAsync(uri);
- }
- },
- async server =>
- {
- HttpRequestData requestData = await server.HandleRequestAsync();
- Assert.Equal(0, requestData.GetHeaderValueCount("Cookie"));
- });
- }
-
- [Theory]
- [MemberData(nameof(CookieNamesValuesAndUseCookies))]
- public async Task GetAsync_SetCookieContainer_CookieSent(string cookieName, string cookieValue, bool useCookies)
- {
- await LoopbackServerFactory.CreateClientAndServerAsync(
- async uri =>
- {
- HttpClientHandler handler = CreateHttpClientHandler();
- handler.CookieContainer = CreateSingleCookieContainer(uri, cookieName, cookieValue);
- handler.UseCookies = useCookies;
-
- using (HttpClient client = CreateHttpClient(handler))
- {
- await client.GetAsync(uri);
- }
- },
- async server =>
- {
- HttpRequestData requestData = await server.HandleRequestAsync();
- if (useCookies)
- {
- Assert.Equal(GetCookieHeaderValue(cookieName, cookieValue), requestData.GetSingleHeaderValue("Cookie"));
- }
- else
- {
- Assert.Equal(0, requestData.GetHeaderValueCount("Cookie"));
- }
- });
- }
-
- [Fact]
- public async Task GetAsync_SetCookieContainerMultipleCookies_CookiesSent()
- {
- var cookies = new Cookie[]
- {
- new Cookie("hello", "world"),
- new Cookie("foo", "bar"),
- new Cookie("ABC", "123")
- };
-
- await LoopbackServerFactory.CreateClientAndServerAsync(
- async uri =>
- {
- HttpClientHandler handler = CreateHttpClientHandler();
- var cookieContainer = new CookieContainer();
- foreach (Cookie c in cookies)
- {
- cookieContainer.Add(uri, c);
- }
- handler.CookieContainer = cookieContainer;
-
- using (HttpClient client = CreateHttpClient(handler))
- {
- await client.GetAsync(uri);
- }
- },
- async server =>
- {
- HttpRequestData requestData = await server.HandleRequestAsync();
- string expectedHeaderValue = string.Join("; ", cookies.Select(c => $"{c.Name}={c.Value}").ToArray());
- Assert.Equal(expectedHeaderValue, requestData.GetSingleHeaderValue("Cookie"));
- });
- }
-
- [Fact]
- public async Task GetAsync_AddCookieHeader_CookieHeaderSent()
- {
- await LoopbackServerFactory.CreateClientAndServerAsync(
- async uri =>
- {
- using (HttpClient client = CreateHttpClient())
- {
- var requestMessage = new HttpRequestMessage(HttpMethod.Get, uri) { Version = VersionFromUseHttp2 };
- requestMessage.Headers.Add("Cookie", s_customCookieHeaderValue);
-
- await client.SendAsync(requestMessage);
- }
- },
- async server =>
- {
- HttpRequestData requestData = await server.HandleRequestAsync();
- Assert.Equal(s_customCookieHeaderValue, requestData.GetSingleHeaderValue("Cookie"));
- });
- }
-
- [Fact]
- public async Task GetAsync_AddMultipleCookieHeaders_CookiesSent()
- {
- await LoopbackServerFactory.CreateClientAndServerAsync(
- async uri =>
- {
- using (HttpClient client = CreateHttpClient())
- {
- var requestMessage = new HttpRequestMessage(HttpMethod.Get, uri) { Version = VersionFromUseHttp2 };
- requestMessage.Headers.Add("Cookie", "A=1");
- requestMessage.Headers.Add("Cookie", "B=2");
- requestMessage.Headers.Add("Cookie", "C=3");
-
- await client.SendAsync(requestMessage);
- }
- },
- async server =>
- {
- HttpRequestData requestData = await server.HandleRequestAsync();
-
- // Multiple Cookie header values are treated as any other header values and are
- // concatenated using ", " as the separator.
-
- string cookieHeaderValue = requestData.GetSingleHeaderValue("Cookie");
-
- var cookieValues = cookieHeaderValue.Split(new string[] { ", " }, StringSplitOptions.None);
- Assert.Contains("A=1", cookieValues);
- Assert.Contains("B=2", cookieValues);
- Assert.Contains("C=3", cookieValues);
- Assert.Equal(3, cookieValues.Count());
- });
- }
-
- private string GetCookieValue(HttpRequestData request)
- {
- if (!LoopbackServerFactory.IsHttp2)
- {
- // HTTP/1.x must have only one value.
- return request.GetSingleHeaderValue("Cookie");
- }
-
- string cookieHeaderValue = null;
- string[] cookieHeaderValues = request.GetHeaderValues("Cookie");
-
- foreach (string header in cookieHeaderValues)
- {
- if (cookieHeaderValue == null)
- {
- cookieHeaderValue = header;
- }
- else
- {
- // rfc7540 8.1.2.5 states multiple cookie headers should be represented as single value.
- cookieHeaderValue = String.Concat(cookieHeaderValue, "; ", header);
- }
- }
-
- return cookieHeaderValue;
- }
-
- [ConditionalFact]
- public async Task GetAsync_SetCookieContainerAndCookieHeader_BothCookiesSent()
- {
- await LoopbackServerFactory.CreateServerAsync(async (server, url) =>
- {
- HttpClientHandler handler = CreateHttpClientHandler();
- handler.CookieContainer = CreateSingleCookieContainer(url);
-
- using (HttpClient client = CreateHttpClient(handler))
- {
- var requestMessage = new HttpRequestMessage(HttpMethod.Get, url) { Version = VersionFromUseHttp2 };
- requestMessage.Headers.Add("Cookie", s_customCookieHeaderValue);
-
- Task<HttpResponseMessage> getResponseTask = client.SendAsync(requestMessage);
- Task<HttpRequestData> serverTask = server.HandleRequestAsync();
- await TestHelper.WhenAllCompletedOrAnyFailed(getResponseTask, serverTask);
-
- HttpRequestData requestData = await serverTask;
- string cookieHeaderValue = GetCookieValue(requestData);
- var cookies = cookieHeaderValue.Split(new string[] { "; " }, StringSplitOptions.None);
- Assert.Contains(s_expectedCookieHeaderValue, cookies);
- Assert.Contains(s_customCookieHeaderValue, cookies);
- Assert.Equal(2, cookies.Count());
- }
- });
- }
-
- [ConditionalFact]
- public async Task GetAsync_SetCookieContainerAndMultipleCookieHeaders_BothCookiesSent()
- {
- await LoopbackServerFactory.CreateServerAsync(async (server, url) =>
- {
- HttpClientHandler handler = CreateHttpClientHandler();
- handler.CookieContainer = CreateSingleCookieContainer(url);
-
- using (HttpClient client = CreateHttpClient(handler))
- {
- var requestMessage = new HttpRequestMessage(HttpMethod.Get, url) { Version = VersionFromUseHttp2 };
- requestMessage.Headers.Add("Cookie", "A=1");
- requestMessage.Headers.Add("Cookie", "B=2");
-
- Task<HttpResponseMessage> getResponseTask = client.SendAsync(requestMessage);
- Task<HttpRequestData> serverTask = server.HandleRequestAsync();
- await TestHelper.WhenAllCompletedOrAnyFailed(getResponseTask, serverTask);
-
- HttpRequestData requestData = await serverTask;
- string cookieHeaderValue = GetCookieValue(requestData);
-
- // Multiple Cookie header values are treated as any other header values and are
- // concatenated using ", " as the separator. The container cookie is concatenated to
- // one of these values using the "; " cookie separator.
-
- var cookieValues = cookieHeaderValue.Split(new string[] { ", " }, StringSplitOptions.None);
- Assert.Equal(2, cookieValues.Count());
-
- // Find container cookie and remove it so we can validate the rest of the cookie header values
- bool sawContainerCookie = false;
- for (int i = 0; i < cookieValues.Length; i++)
- {
- if (cookieValues[i].Contains(';'))
- {
- Assert.False(sawContainerCookie);
-
- var cookies = cookieValues[i].Split(new string[] { "; " }, StringSplitOptions.None);
- Assert.Equal(2, cookies.Count());
- Assert.Contains(s_expectedCookieHeaderValue, cookies);
-
- sawContainerCookie = true;
- cookieValues[i] = cookies.Where(c => c != s_expectedCookieHeaderValue).Single();
- }
- }
-
- Assert.Contains("A=1", cookieValues);
- Assert.Contains("B=2", cookieValues);
- }
- });
- }
-
- [Fact]
- public async Task GetAsyncWithRedirect_SetCookieContainer_CorrectCookiesSent()
- {
- const string path1 = "/foo";
- const string path2 = "/bar";
-
- await LoopbackServerFactory.CreateClientAndServerAsync(async url =>
- {
- Uri url1 = new Uri(url, path1);
- Uri url2 = new Uri(url, path2);
- Uri unusedUrl = new Uri(url, "/unused");
-
- HttpClientHandler handler = CreateHttpClientHandler();
- handler.CookieContainer = new CookieContainer();
- handler.CookieContainer.Add(url1, new Cookie("cookie1", "value1"));
- handler.CookieContainer.Add(url2, new Cookie("cookie2", "value2"));
- handler.CookieContainer.Add(unusedUrl, new Cookie("cookie3", "value3"));
-
- using (HttpClient client = CreateHttpClient(handler))
- {
- client.DefaultRequestHeaders.ConnectionClose = true; // to avoid issues with connection pooling
- await client.GetAsync(url1);
- }
- },
- async server =>
- {
- HttpRequestData requestData1 = await server.HandleRequestAsync(HttpStatusCode.Found, new HttpHeaderData[] { new HttpHeaderData("Location", path2) });
- Assert.Equal("cookie1=value1", requestData1.GetSingleHeaderValue("Cookie"));
-
- HttpRequestData requestData2 = await server.HandleRequestAsync(content: s_simpleContent);
- Assert.Equal("cookie2=value2", requestData2.GetSingleHeaderValue("Cookie"));
- });
- }
-
- //
- // Receive cookie tests
- //
-
- [Theory]
- [MemberData(nameof(CookieNamesValuesAndUseCookies))]
- public async Task GetAsync_ReceiveSetCookieHeader_CookieAdded(string cookieName, string cookieValue, bool useCookies)
- {
- await LoopbackServerFactory.CreateServerAsync(async (server, url) =>
- {
- HttpClientHandler handler = CreateHttpClientHandler();
- handler.UseCookies = useCookies;
-
- using (HttpClient client = CreateHttpClient(handler))
- {
- Task<HttpResponseMessage> getResponseTask = client.GetAsync(url);
- Task<HttpRequestData> serverTask = server.HandleRequestAsync(
- HttpStatusCode.OK, new HttpHeaderData[] { new HttpHeaderData("Set-Cookie", GetCookieHeaderValue(cookieName, cookieValue)) }, s_simpleContent);
- await TestHelper.WhenAllCompletedOrAnyFailed(getResponseTask, serverTask);
-
- CookieCollection collection = handler.CookieContainer.GetCookies(url);
-
- if (useCookies)
- {
- Assert.Equal(1, collection.Count);
- Assert.Equal(cookieName, collection[0].Name);
- Assert.Equal(cookieValue, collection[0].Value);
- }
- else
- {
- Assert.Equal(0, collection.Count);
- }
- }
- });
- }
-
- [Fact]
- public async Task GetAsync_ReceiveMultipleSetCookieHeaders_CookieAdded()
- {
- await LoopbackServerFactory.CreateServerAsync(async (server, url) =>
- {
- HttpClientHandler handler = CreateHttpClientHandler();
-
- using (HttpClient client = CreateHttpClient(handler))
- {
- Task<HttpResponseMessage> getResponseTask = client.GetAsync(url);
- Task<HttpRequestData> serverTask = server.HandleRequestAsync(
- HttpStatusCode.OK,
- new HttpHeaderData[]
- {
- new HttpHeaderData("Set-Cookie", "A=1; Path=/"),
- new HttpHeaderData("Set-Cookie", "B=2; Path=/"),
- new HttpHeaderData("Set-Cookie", "C=3; Path=/")
- },
- s_simpleContent);
- await TestHelper.WhenAllCompletedOrAnyFailed(getResponseTask, serverTask);
-
- CookieCollection collection = handler.CookieContainer.GetCookies(url);
- Assert.Equal(3, collection.Count);
-
- // Convert to array so we can more easily process contents, since CookieCollection does not implement IEnumerable<Cookie>
- Cookie[] cookies = new Cookie[3];
- collection.CopyTo(cookies, 0);
-
- Assert.Contains(cookies, c => c.Name == "A" && c.Value == "1");
- Assert.Contains(cookies, c => c.Name == "B" && c.Value == "2");
- Assert.Contains(cookies, c => c.Name == "C" && c.Value == "3");
- }
- });
- }
-
- [Fact]
- public async Task GetAsync_ReceiveSetCookieHeader_CookieUpdated()
- {
- const string newCookieValue = "789";
-
- await LoopbackServerFactory.CreateServerAsync(async (server, url) =>
- {
- HttpClientHandler handler = CreateHttpClientHandler();
- handler.CookieContainer = CreateSingleCookieContainer(url);
-
- using (HttpClient client = CreateHttpClient(handler))
- {
- Task<HttpResponseMessage> getResponseTask = client.GetAsync(url);
- Task<HttpRequestData> serverTask = server.HandleRequestAsync(
- HttpStatusCode.OK,
- new HttpHeaderData[] { new HttpHeaderData("Set-Cookie", $"{s_cookieName}={newCookieValue}") },
- s_simpleContent);
- await TestHelper.WhenAllCompletedOrAnyFailed(getResponseTask, serverTask);
-
- CookieCollection collection = handler.CookieContainer.GetCookies(url);
- Assert.Equal(1, collection.Count);
- Assert.Equal(s_cookieName, collection[0].Name);
- Assert.Equal(newCookieValue, collection[0].Value);
- }
- });
- }
-
- [Fact]
- public async Task GetAsync_ReceiveSetCookieHeader_CookieRemoved()
- {
- await LoopbackServerFactory.CreateServerAsync(async (server, url) =>
- {
- HttpClientHandler handler = CreateHttpClientHandler();
- handler.CookieContainer = CreateSingleCookieContainer(url);
-
- using (HttpClient client = CreateHttpClient(handler))
- {
- Task<HttpResponseMessage> getResponseTask = client.GetAsync(url);
- Task<HttpRequestData> serverTask = server.HandleRequestAsync(
- HttpStatusCode.OK,
- new HttpHeaderData[] { new HttpHeaderData("Set-Cookie", $"{s_cookieName}=; Expires=Sun, 06 Nov 1994 08:49:37 GMT") },
- s_simpleContent);
- await TestHelper.WhenAllCompletedOrAnyFailed(getResponseTask, serverTask);
-
- CookieCollection collection = handler.CookieContainer.GetCookies(url);
- Assert.Equal(0, collection.Count);
- }
- });
- }
-
- [Fact]
- public async Task GetAsync_ReceiveInvalidSetCookieHeader_ValidCookiesAdded()
- {
- await LoopbackServerFactory.CreateServerAsync(async (server, url) =>
- {
- HttpClientHandler handler = CreateHttpClientHandler();
-
- using (HttpClient client = CreateHttpClient(handler))
- {
- Task<HttpResponseMessage> getResponseTask = client.GetAsync(url);
- Task<HttpRequestData> serverTask = server.HandleRequestAsync(
- HttpStatusCode.OK,
- new HttpHeaderData[]
- {
- new HttpHeaderData("Set-Cookie", "A=1; Path=/;Expires=asdfsadgads"), // invalid Expires
- new HttpHeaderData("Set-Cookie", "B=2; Path=/"),
- new HttpHeaderData("Set-Cookie", "C=3; Path=/")
- },
- s_simpleContent);
- await TestHelper.WhenAllCompletedOrAnyFailed(getResponseTask, serverTask);
-
- CookieCollection collection = handler.CookieContainer.GetCookies(url);
- Assert.Equal(2, collection.Count);
-
- // Convert to array so we can more easily process contents, since CookieCollection does not implement IEnumerable<Cookie>
- Cookie[] cookies = new Cookie[3];
- collection.CopyTo(cookies, 0);
-
- Assert.Contains(cookies, c => c.Name == "B" && c.Value == "2");
- Assert.Contains(cookies, c => c.Name == "C" && c.Value == "3");
- }
- });
- }
-
- [Fact]
- public async Task GetAsyncWithRedirect_ReceiveSetCookie_CookieSent()
- {
- const string path1 = "/foo";
- const string path2 = "/bar";
-
- await LoopbackServerFactory.CreateClientAndServerAsync(async url =>
- {
- Uri url1 = new Uri(url, path1);
-
- HttpClientHandler handler = CreateHttpClientHandler();
-
- using (HttpClient client = CreateHttpClient(handler))
- {
- client.DefaultRequestHeaders.ConnectionClose = true; // to avoid issues with connection pooling
- await client.GetAsync(url1);
-
- CookieCollection collection = handler.CookieContainer.GetCookies(url);
-
- Assert.Equal(2, collection.Count);
-
- // Convert to array so we can more easily process contents, since CookieCollection does not implement IEnumerable<Cookie>
- Cookie[] cookies = new Cookie[2];
- collection.CopyTo(cookies, 0);
-
- Assert.Contains(cookies, c => c.Name == "A" && c.Value == "1");
- Assert.Contains(cookies, c => c.Name == "B" && c.Value == "2");
- }
- },
- async server =>
- {
- HttpRequestData requestData1 = await server.HandleRequestAsync(
- HttpStatusCode.Found,
- new HttpHeaderData[]
- {
- new HttpHeaderData("Location", $"{path2}"),
- new HttpHeaderData("Set-Cookie", "A=1; Path=/")
- });
-
- Assert.Equal(0, requestData1.GetHeaderValueCount("Cookie"));
-
- HttpRequestData requestData2 = await server.HandleRequestAsync(
- HttpStatusCode.OK,
- new HttpHeaderData[]
- {
- new HttpHeaderData("Set-Cookie", "B=2; Path=/")
- },
- s_simpleContent);
-
- Assert.Equal("A=1", requestData2.GetSingleHeaderValue("Cookie"));
- });
- }
-
- [Fact]
- public async Task GetAsyncWithBasicAuth_ReceiveSetCookie_CookieSent()
- {
- await LoopbackServerFactory.CreateClientAndServerAsync(async url =>
- {
- HttpClientHandler handler = CreateHttpClientHandler();
- handler.Credentials = new NetworkCredential("user", "pass");
-
- using (HttpClient client = CreateHttpClient(handler))
- {
- await client.GetAsync(url);
-
- CookieCollection collection = handler.CookieContainer.GetCookies(url);
-
- Assert.Equal(2, collection.Count);
-
- // Convert to array so we can more easily process contents, since CookieCollection does not implement IEnumerable<Cookie>
- Cookie[] cookies = new Cookie[2];
- collection.CopyTo(cookies, 0);
-
- Assert.Contains(cookies, c => c.Name == "A" && c.Value == "1");
- Assert.Contains(cookies, c => c.Name == "B" && c.Value == "2");
- }
- },
- async server =>
- {
- HttpRequestData requestData1 = await server.HandleRequestAsync(
- HttpStatusCode.Unauthorized,
- new HttpHeaderData[]
- {
- new HttpHeaderData("WWW-Authenticate", "Basic realm=\"WallyWorld\""),
- new HttpHeaderData("Set-Cookie", "A=1; Path=/")
- });
-
- Assert.Equal(0, requestData1.GetHeaderValueCount("Cookie"));
-
- HttpRequestData requestData2 = await server.HandleRequestAsync(
- HttpStatusCode.OK,
- new HttpHeaderData[]
- {
- new HttpHeaderData("Set-Cookie", "B=2; Path=/")
- },
- s_simpleContent);
-
- Assert.Equal("A=1", requestData2.GetSingleHeaderValue("Cookie"));
- });
- }
-
- //
- // MemberData stuff
- //
-
- private static string GenerateCookie(string name, char repeat, int overallHeaderValueLength)
- {
- string emptyHeaderValue = $"{name}=; Path=/";
-
- Debug.Assert(overallHeaderValueLength > emptyHeaderValue.Length);
-
- int valueCount = overallHeaderValueLength - emptyHeaderValue.Length;
- return new string(repeat, valueCount);
- }
-
- public static IEnumerable<object[]> CookieNamesValuesAndUseCookies()
- {
- foreach (bool useCookies in new[] { true, false })
- {
- yield return new object[] { "ABC", "123", useCookies };
- yield return new object[] { "Hello", "World", useCookies };
- yield return new object[] { "foo", "bar", useCookies };
- yield return new object[] { "Hello World", "value", useCookies };
- yield return new object[] { ".AspNetCore.Session", "RAExEmXpoCbueP_QYM", useCookies };
-
- yield return new object[]
- {
- ".AspNetCore.Antiforgery.Xam7_OeLcN4",
- "CfDJ8NGNxAt7CbdClq3UJ8_6w_4661wRQZT1aDtUOIUKshbcV4P0NdS8klCL5qGSN-PNBBV7w23G6MYpQ81t0PMmzIN4O04fqhZ0u1YPv66mixtkX3iTi291DgwT3o5kozfQhe08-RAExEmXpoCbueP_QYM",
- useCookies
- };
-
- // WinHttpHandler calls WinHttpQueryHeaders to iterate through multiple Set-Cookie header values,
- // using an initial buffer size of 128 chars. If the buffer is not large enough, WinHttpQueryHeaders
- // returns an insufficient buffer error, allowing WinHttpHandler to try again with a larger buffer.
- // Sometimes when WinHttpQueryHeaders fails due to insufficient buffer, it still advances the
- // iteration index, which would cause header values to be missed if not handled correctly.
- //
- // In particular, WinHttpQueryHeader behaves as follows for the following header value lengths:
- // * 0-127 chars: succeeds, index advances from 0 to 1.
- // * 128-255 chars: fails due to insufficient buffer, index advances from 0 to 1.
- // * 256+ chars: fails due to insufficient buffer, index stays at 0.
- //
- // The below overall header value lengths were chosen to exercise reading header values at these
- // edges, to ensure WinHttpHandler does not miss multiple Set-Cookie headers.
-
- yield return new object[] { "foo", GenerateCookie(name: "foo", repeat: 'a', overallHeaderValueLength: 126), useCookies };
- yield return new object[] { "foo", GenerateCookie(name: "foo", repeat: 'a', overallHeaderValueLength: 127), useCookies };
- yield return new object[] { "foo", GenerateCookie(name: "foo", repeat: 'a', overallHeaderValueLength: 128), useCookies };
- yield return new object[] { "foo", GenerateCookie(name: "foo", repeat: 'a', overallHeaderValueLength: 129), useCookies };
-
- yield return new object[] { "foo", GenerateCookie(name: "foo", repeat: 'a', overallHeaderValueLength: 254), useCookies };
- yield return new object[] { "foo", GenerateCookie(name: "foo", repeat: 'a', overallHeaderValueLength: 255), useCookies };
- yield return new object[] { "foo", GenerateCookie(name: "foo", repeat: 'a', overallHeaderValueLength: 256), useCookies };
- yield return new object[] { "foo", GenerateCookie(name: "foo", repeat: 'a', overallHeaderValueLength: 257), useCookies };
- }
- }
- }
-
- public abstract class HttpClientHandlerTest_Cookies_Http11 : HttpClientHandlerTestBase
- {
- public HttpClientHandlerTest_Cookies_Http11(ITestOutputHelper output) : base(output) { }
-
- [Fact]
- public async Task GetAsync_ReceiveMultipleSetCookieHeaders_CookieAdded()
- {
- await LoopbackServer.CreateServerAsync(async (server, url) =>
- {
- HttpClientHandler handler = CreateHttpClientHandler();
-
- using (HttpClient client = CreateHttpClient(handler))
- {
- Task<HttpResponseMessage> getResponseTask = client.GetAsync(url);
- Task<List<string>> serverTask = server.AcceptConnectionSendResponseAndCloseAsync(
- HttpStatusCode.OK,
- $"Set-Cookie: A=1; Path=/\r\n" +
- $"Set-Cookie : B=2; Path=/\r\n" + // space before colon to verify header is trimmed and recognized
- $"Set-Cookie: C=3; Path=/\r\n",
- "Hello world!");
- await TestHelper.WhenAllCompletedOrAnyFailed(getResponseTask, serverTask);
-
- CookieCollection collection = handler.CookieContainer.GetCookies(url);
- Assert.Equal(3, collection.Count);
-
- // Convert to array so we can more easily process contents, since CookieCollection does not implement IEnumerable<Cookie>
- Cookie[] cookies = new Cookie[3];
- collection.CopyTo(cookies, 0);
-
- Assert.Contains(cookies, c => c.Name == "A" && c.Value == "1");
- Assert.Contains(cookies, c => c.Name == "B" && c.Value == "2");
- Assert.Contains(cookies, c => c.Name == "C" && c.Value == "3");
- }
- });
- }
- }
-}
+++ /dev/null
-// 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.Collections.Generic;
-using System.IO;
-using System.IO.Compression;
-using System.Net.Test.Common;
-using System.Text.RegularExpressions;
-using System.Threading.Tasks;
-
-using Xunit;
-using Xunit.Abstractions;
-
-namespace System.Net.Http.Functional.Tests
-{
- using Configuration = System.Net.Test.Common.Configuration;
-
- public abstract class HttpClientHandler_Decompression_Test : HttpClientHandlerTestBase
- {
- public HttpClientHandler_Decompression_Test(ITestOutputHelper output) : base(output) { }
-
- public static IEnumerable<object[]> RemoteServersAndCompressionUris()
- {
- foreach (Configuration.Http.RemoteServer remoteServer in Configuration.Http.RemoteServers)
- {
- yield return new object[] { remoteServer, remoteServer.GZipUri };
- yield return new object[] { remoteServer, remoteServer.DeflateUri };
- }
- }
-
- public static IEnumerable<object[]> DecompressedResponse_MethodSpecified_DecompressedContentReturned_MemberData()
- {
- foreach (bool specifyAllMethods in new[] { false, true })
- {
- yield return new object[]
- {
- "deflate",
- new Func<Stream, Stream>(s => new DeflateStream(s, CompressionLevel.Optimal, leaveOpen: true)),
- specifyAllMethods ? DecompressionMethods.Deflate : DecompressionMethods.All
- };
- yield return new object[]
- {
- "gzip",
- new Func<Stream, Stream>(s => new GZipStream(s, CompressionLevel.Optimal, leaveOpen: true)),
- specifyAllMethods ? DecompressionMethods.GZip : DecompressionMethods.All
- };
- yield return new object[]
- {
- "br",
- new Func<Stream, Stream>(s => new BrotliStream(s, CompressionLevel.Optimal, leaveOpen: true)),
- specifyAllMethods ? DecompressionMethods.Brotli : DecompressionMethods.All
- };
- }
- }
-
- [Theory]
- [MemberData(nameof(DecompressedResponse_MethodSpecified_DecompressedContentReturned_MemberData))]
- public async Task DecompressedResponse_MethodSpecified_DecompressedContentReturned(
- string encodingName, Func<Stream, Stream> compress, DecompressionMethods methods)
- {
- var expectedContent = new byte[12345];
- new Random(42).NextBytes(expectedContent);
-
- await LoopbackServer.CreateClientAndServerAsync(async uri =>
- {
- using (HttpClientHandler handler = CreateHttpClientHandler())
- using (HttpClient client = CreateHttpClient(handler))
- {
- handler.AutomaticDecompression = methods;
- Assert.Equal<byte>(expectedContent, await client.GetByteArrayAsync(uri));
- }
- }, async server =>
- {
- await server.AcceptConnectionAsync(async connection =>
- {
- await connection.ReadRequestHeaderAsync();
- await connection.Writer.WriteAsync($"HTTP/1.1 200 OK\r\nContent-Encoding: {encodingName}\r\n\r\n");
- using (Stream compressedStream = compress(connection.Stream))
- {
- await compressedStream.WriteAsync(expectedContent);
- }
- });
- });
- }
-
- public static IEnumerable<object[]> DecompressedResponse_MethodNotSpecified_OriginalContentReturned_MemberData()
- {
- yield return new object[]
- {
- "deflate",
- new Func<Stream, Stream>(s => new DeflateStream(s, CompressionLevel.Optimal, leaveOpen: true)),
- DecompressionMethods.None
- };
- yield return new object[]
- {
- "gzip",
- new Func<Stream, Stream>(s => new GZipStream(s, CompressionLevel.Optimal, leaveOpen: true)),
- DecompressionMethods.Brotli
- };
- yield return new object[]
- {
- "br",
- new Func<Stream, Stream>(s => new BrotliStream(s, CompressionLevel.Optimal, leaveOpen: true)),
- DecompressionMethods.Deflate | DecompressionMethods.GZip
- };
- }
-
- [Theory]
- [MemberData(nameof(DecompressedResponse_MethodNotSpecified_OriginalContentReturned_MemberData))]
- public async Task DecompressedResponse_MethodNotSpecified_OriginalContentReturned(
- string encodingName, Func<Stream, Stream> compress, DecompressionMethods methods)
- {
- var expectedContent = new byte[12345];
- new Random(42).NextBytes(expectedContent);
-
- var compressedContentStream = new MemoryStream();
- using (Stream s = compress(compressedContentStream))
- {
- await s.WriteAsync(expectedContent);
- }
- byte[] compressedContent = compressedContentStream.ToArray();
-
- await LoopbackServer.CreateClientAndServerAsync(async uri =>
- {
- using (HttpClientHandler handler = CreateHttpClientHandler())
- using (HttpClient client = CreateHttpClient(handler))
- {
- handler.AutomaticDecompression = methods;
- Assert.Equal<byte>(compressedContent, await client.GetByteArrayAsync(uri));
- }
- }, async server =>
- {
- await server.AcceptConnectionAsync(async connection =>
- {
- await connection.ReadRequestHeaderAsync();
- await connection.Writer.WriteAsync($"HTTP/1.1 200 OK\r\nContent-Encoding: {encodingName}\r\n\r\n");
- await connection.Stream.WriteAsync(compressedContent);
- });
- });
- }
-
- [OuterLoop("Uses external servers")]
- [Theory, MemberData(nameof(RemoteServersAndCompressionUris))]
- public async Task GetAsync_SetAutomaticDecompression_ContentDecompressed(Configuration.Http.RemoteServer remoteServer, Uri uri)
- {
- HttpClientHandler handler = CreateHttpClientHandler();
- handler.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;
- using (HttpClient client = CreateHttpClientForRemoteServer(remoteServer, handler))
- {
- using (HttpResponseMessage response = await client.GetAsync(uri))
- {
- Assert.Equal(HttpStatusCode.OK, response.StatusCode);
- string responseContent = await response.Content.ReadAsStringAsync();
- _output.WriteLine(responseContent);
- TestHelper.VerifyResponseBody(
- responseContent,
- response.Content.Headers.ContentMD5,
- false,
- null);
- }
- }
- }
-
- [OuterLoop("Uses external server")]
- [Theory, MemberData(nameof(RemoteServersAndCompressionUris))]
- public async Task GetAsync_SetAutomaticDecompression_HeadersRemoved(Configuration.Http.RemoteServer remoteServer, Uri uri)
- {
- HttpClientHandler handler = CreateHttpClientHandler();
- handler.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;
- using (HttpClient client = CreateHttpClientForRemoteServer(remoteServer, handler))
- using (HttpResponseMessage response = await client.GetAsync(uri, HttpCompletionOption.ResponseHeadersRead))
- {
- Assert.Equal(HttpStatusCode.OK, response.StatusCode);
-
- Assert.False(response.Content.Headers.Contains("Content-Encoding"), "Content-Encoding unexpectedly found");
- Assert.False(response.Content.Headers.Contains("Content-Length"), "Content-Length unexpectedly found");
- }
- }
-
- [Theory]
-#if NETCOREAPP
- [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", "")]
- [InlineData(DecompressionMethods.GZip, "gzip", "gzip")]
- [InlineData(DecompressionMethods.Deflate, "deflate", "deflate")]
- [InlineData(DecompressionMethods.GZip, "gzip", "deflate")]
- [InlineData(DecompressionMethods.GZip, "gzip", "br")]
- [InlineData(DecompressionMethods.Deflate, "deflate", "gzip")]
- [InlineData(DecompressionMethods.Deflate, "deflate", "br")]
- [InlineData(DecompressionMethods.GZip | DecompressionMethods.Deflate, "gzip, deflate", "gzip, deflate")]
- public async Task GetAsync_SetAutomaticDecompression_AcceptEncodingHeaderSentWithNoDuplicates(
- DecompressionMethods methods,
- string encodings,
- string manualAcceptEncodingHeaderValues)
- {
- await LoopbackServer.CreateServerAsync(async (server, url) =>
- {
- HttpClientHandler handler = CreateHttpClientHandler();
- handler.AutomaticDecompression = methods;
-
- using (HttpClient client = CreateHttpClient(handler))
- {
- if (!string.IsNullOrEmpty(manualAcceptEncodingHeaderValues))
- {
- client.DefaultRequestHeaders.Add("Accept-Encoding", manualAcceptEncodingHeaderValues);
- }
-
- Task<HttpResponseMessage> clientTask = client.GetAsync(url);
- Task<List<string>> serverTask = server.AcceptConnectionSendResponseAndCloseAsync();
- await TaskTimeoutExtensions.WhenAllOrAnyFailed(new Task[] { clientTask, serverTask });
-
- List<string> requestLines = await serverTask;
- string requestLinesString = string.Join("\r\n", requestLines);
- _output.WriteLine(requestLinesString);
-
- Assert.InRange(Regex.Matches(requestLinesString, "Accept-Encoding").Count, 1, 1);
- Assert.InRange(Regex.Matches(requestLinesString, encodings).Count, 1, 1);
- if (!string.IsNullOrEmpty(manualAcceptEncodingHeaderValues))
- {
- Assert.InRange(Regex.Matches(requestLinesString, manualAcceptEncodingHeaderValues).Count, 1, 1);
- }
-
- using (HttpResponseMessage response = await clientTask)
- {
- Assert.Equal(HttpStatusCode.OK, response.StatusCode);
- }
- }
- });
- }
- }
-}
+++ /dev/null
-// 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.Collections.Generic;
-using System.Diagnostics;
-using System.Net.Test.Common;
-using System.Runtime.InteropServices;
-using System.Text;
-using System.Threading.Tasks;
-using Microsoft.DotNet.RemoteExecutor;
-using Xunit;
-using Xunit.Abstractions;
-
-namespace System.Net.Http.Functional.Tests
-{
- using Configuration = System.Net.Test.Common.Configuration;
-
- public abstract class HttpClientHandler_DefaultProxyCredentials_Test : HttpClientHandlerTestBase
- {
- public HttpClientHandler_DefaultProxyCredentials_Test(ITestOutputHelper output) : base(output) { }
-
- [Fact]
- public void Default_Get_Null()
- {
- using (HttpClientHandler handler = CreateHttpClientHandler())
- {
- Assert.Null(handler.DefaultProxyCredentials);
- }
- }
-
- [Fact]
- public void SetGet_Roundtrips()
- {
- using (HttpClientHandler handler = CreateHttpClientHandler())
- {
- var creds = new NetworkCredential("username", "password", "domain");
-
- handler.DefaultProxyCredentials = null;
- Assert.Null(handler.DefaultProxyCredentials);
-
- handler.DefaultProxyCredentials = creds;
- Assert.Same(creds, handler.DefaultProxyCredentials);
-
- handler.DefaultProxyCredentials = CredentialCache.DefaultCredentials;
- Assert.Same(CredentialCache.DefaultCredentials, handler.DefaultProxyCredentials);
- }
- }
-
- [Fact]
- public async Task ProxyExplicitlyProvided_DefaultCredentials_Ignored()
- {
- var explicitProxyCreds = new NetworkCredential("rightusername", "rightpassword");
- var defaultSystemProxyCreds = new NetworkCredential("wrongusername", "wrongpassword");
- string expectCreds = "Basic " + Convert.ToBase64String(Encoding.UTF8.GetBytes($"{explicitProxyCreds.UserName}:{explicitProxyCreds.Password}"));
-
- await LoopbackServer.CreateClientAndServerAsync(async proxyUrl =>
- {
- using (HttpClientHandler handler = CreateHttpClientHandler())
- using (HttpClient client = CreateHttpClient(handler))
- {
- handler.Proxy = new UseSpecifiedUriWebProxy(proxyUrl, explicitProxyCreds);
- handler.DefaultProxyCredentials = defaultSystemProxyCreds;
- using (HttpResponseMessage response = await client.GetAsync("http://notatrealserver.com/")) // URL does not matter
- {
- Assert.Equal(HttpStatusCode.OK, response.StatusCode);
- }
- }
- }, async server =>
- {
- await server.AcceptConnectionSendResponseAndCloseAsync(
- HttpStatusCode.ProxyAuthenticationRequired, "Proxy-Authenticate: Basic\r\n");
-
- List<string> headers = await server.AcceptConnectionSendResponseAndCloseAsync(HttpStatusCode.OK);
- Assert.Equal(expectCreds, LoopbackServer.GetRequestHeaderValue(headers, "Proxy-Authorization"));
- });
- }
-
- [ActiveIssue("https://github.com/dotnet/corefx/issues/42323")]
- [OuterLoop("Uses external server")]
- [PlatformSpecific(TestPlatforms.AnyUnix)] // The default proxy is resolved via WinINet on Windows.
- [Theory]
- [InlineData(false)]
- [InlineData(true)]
- public async Task ProxySetViaEnvironmentVariable_DefaultProxyCredentialsUsed(bool useProxy)
- {
- const string ExpectedUsername = "rightusername";
- const string ExpectedPassword = "rightpassword";
- LoopbackServer.Options options = new LoopbackServer.Options { IsProxy = true, Username = ExpectedUsername, Password = ExpectedPassword };
-
- await LoopbackServer.CreateServerAsync(async (proxyServer, proxyUri) =>
- {
- // SocketsHttpHandler can read a default proxy from the http_proxy environment variable. Ensure that when it does,
- // our default proxy credentials are used. To avoid messing up anything else in this process, we run the
- // test in another process.
- var psi = new ProcessStartInfo();
- Task<List<string>> proxyTask = null;
-
- if (useProxy)
- {
- proxyTask = proxyServer.AcceptConnectionPerformAuthenticationAndCloseAsync("Proxy-Authenticate: Basic realm=\"NetCore\"\r\n");
- psi.Environment.Add("http_proxy", $"http://{proxyUri.Host}:{proxyUri.Port}");
- }
-
- RemoteExecutor.Invoke(async (useProxyString, useHttp2String) =>
- {
- using (HttpClientHandler handler = CreateHttpClientHandler(useHttp2String))
- using (HttpClient client = CreateHttpClient(handler, useHttp2String))
- {
- var creds = new NetworkCredential(ExpectedUsername, ExpectedPassword);
- handler.DefaultProxyCredentials = creds;
- handler.UseProxy = bool.Parse(useProxyString);
-
- HttpResponseMessage response = await client.GetAsync(Configuration.Http.RemoteEchoServer);
- // Correctness of user and password is done in server part.
- Assert.True(response.StatusCode == HttpStatusCode.OK);
- }
- }, useProxy.ToString(), UseHttp2.ToString(), new RemoteInvokeOptions { StartInfo = psi }).Dispose();
- if (useProxy)
- {
- await proxyTask;
- }
- }, options);
- }
-
- // The purpose of this test is mainly to validate the .NET Framework OOB System.Net.Http implementation
- // since it has an underlying dependency to WebRequest. While .NET Core implementations of System.Net.Http
- // are not using any WebRequest code, the test is still useful to validate correctness.
- [OuterLoop("Uses external server")]
- [Fact]
- public async Task ProxyNotExplicitlyProvided_DefaultCredentialsSet_DefaultWebProxySetToNull_Success()
- {
- WebRequest.DefaultWebProxy = null;
-
- using (HttpClientHandler handler = CreateHttpClientHandler())
- using (HttpClient client = CreateHttpClient(handler))
- {
- handler.DefaultProxyCredentials = new NetworkCredential("UsernameNotUsed", "PasswordNotUsed");
- HttpResponseMessage response = await client.GetAsync(Configuration.Http.RemoteEchoServer);
-
- Assert.Equal(HttpStatusCode.OK, response.StatusCode);
- }
- }
- }
-}
+++ /dev/null
-// 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.Linq;
-using System.Net.Test.Common;
-using System.Runtime.InteropServices;
-using System.Threading;
-using System.Threading.Tasks;
-
-using Xunit;
-using Xunit.Abstractions;
-
-namespace System.Net.Http.Functional.Tests
-{
- using Configuration = System.Net.Test.Common.Configuration;
-
- public abstract class HttpClientHandler_MaxConnectionsPerServer_Test : HttpClientHandlerTestBase
- {
- public HttpClientHandler_MaxConnectionsPerServer_Test(ITestOutputHelper output) : base(output) { }
-
- [Fact]
- public void Default_ExpectedValue()
- {
- using (HttpClientHandler handler = CreateHttpClientHandler())
- {
- Assert.Equal(int.MaxValue, handler.MaxConnectionsPerServer);
- }
- }
-
- [Theory]
- [InlineData(0)]
- [InlineData(-1)]
- public void Set_InvalidValues_Throws(int invalidValue)
- {
- using (HttpClientHandler handler = CreateHttpClientHandler())
- {
- Assert.Throws<ArgumentOutOfRangeException>(() => handler.MaxConnectionsPerServer = invalidValue);
- }
- }
-
- [Theory]
- [InlineData(1)]
- [InlineData(2)]
- [InlineData(int.MaxValue)]
- [InlineData(int.MaxValue - 1)]
- public void Set_ValidValues_Success(int validValue)
- {
- using (HttpClientHandler handler = CreateHttpClientHandler())
- {
- handler.MaxConnectionsPerServer = validValue;
- }
- }
-
- [Theory]
- [InlineData(1, 5, false)]
- [InlineData(1, 5, true)]
- [InlineData(2, 2, false)]
- [InlineData(2, 2, true)]
- [InlineData(3, 2, false)]
- [InlineData(3, 2, true)]
- [InlineData(3, 5, false)]
- [OuterLoop("Uses external servers")]
- public async Task GetAsync_MaxLimited_ConcurrentCallsStillSucceed(int maxConnections, int numRequests, bool secure)
- {
- using (HttpClientHandler handler = CreateHttpClientHandler())
- using (HttpClient client = CreateHttpClient(handler))
- {
- handler.MaxConnectionsPerServer = maxConnections;
- await Task.WhenAll(
- from i in Enumerable.Range(0, numRequests)
- select client.GetAsync(secure ? Configuration.Http.RemoteEchoServer : Configuration.Http.SecureRemoteEchoServer));
- }
- }
-
- [OuterLoop("Relies on kicking off GC and waiting for finalizers")]
- [Fact]
- public async Task GetAsync_DontDisposeResponse_EventuallyUnblocksWaiters()
- {
- await LoopbackServer.CreateServerAsync(async (server, uri) =>
- {
- using (HttpClientHandler handler = CreateHttpClientHandler())
- using (HttpClient client = CreateHttpClient(handler))
- {
- handler.MaxConnectionsPerServer = 1;
-
- // Let server handle two requests.
- const string ResponseContent = "abcdefghijklmnopqrstuvwxyz";
- Task serverTask1 = server.AcceptConnectionSendResponseAndCloseAsync(content: ResponseContent);
- Task serverTask2 = server.AcceptConnectionSendResponseAndCloseAsync(content: ResponseContent);
-
- // Make first request and drop the response, not explicitly disposing of it.
- void MakeAndDropRequest() => client.GetAsync(uri, HttpCompletionOption.ResponseHeadersRead); // separated out to enable GC of response
- MakeAndDropRequest();
-
- // A second request should eventually succeed, once the first one is cleaned up.
- Task<HttpResponseMessage> secondResponse = client.GetAsync(uri);
- Assert.True(SpinWait.SpinUntil(() =>
- {
- GC.Collect();
- GC.WaitForPendingFinalizers();
- return secondResponse.IsCompleted;
- }, 30 * 1000), "Expected second response to have completed");
-
- await new[] { serverTask1, serverTask2, secondResponse }.WhenAllOrAnyFailed();
- }
- });
- }
- }
-}
+++ /dev/null
-// 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.Collections.Generic;
-using System.Net.Test.Common;
-using System.Text;
-using System.Threading;
-using System.Threading.Tasks;
-using Xunit;
-using Xunit.Abstractions;
-
-namespace System.Net.Http.Functional.Tests
-{
- using Configuration = System.Net.Test.Common.Configuration;
-
- public abstract class HttpClientHandler_MaxResponseHeadersLength_Test : HttpClientHandlerTestBase
- {
- public HttpClientHandler_MaxResponseHeadersLength_Test(ITestOutputHelper output) : base(output) { }
-
- [Theory]
- [InlineData(0)]
- [InlineData(-1)]
- public void InvalidValue_ThrowsException(int invalidValue)
- {
- using (HttpClientHandler handler = CreateHttpClientHandler())
- {
- AssertExtensions.Throws<ArgumentOutOfRangeException>("value", () => handler.MaxResponseHeadersLength = invalidValue);
- }
- }
-
- [Theory]
- [InlineData(1)]
- [InlineData(65)]
- [InlineData(int.MaxValue)]
- public void ValidValue_SetGet_Roundtrips(int validValue)
- {
- using (HttpClientHandler handler = CreateHttpClientHandler())
- {
- handler.MaxResponseHeadersLength = validValue;
- Assert.Equal(validValue, handler.MaxResponseHeadersLength);
- }
- }
-
- [Fact]
- public async Task SetAfterUse_Throws()
- {
- await LoopbackServerFactory.CreateClientAndServerAsync(async uri =>
- {
- using HttpClientHandler handler = CreateHttpClientHandler();
- using HttpClient client = CreateHttpClient(handler);
-
- handler.MaxResponseHeadersLength = 1;
- (await client.GetStreamAsync(uri)).Dispose();
- Assert.Throws<InvalidOperationException>(() => handler.MaxResponseHeadersLength = 1);
- },
- server => server.AcceptConnectionSendResponseAndCloseAsync());
- }
-
- [OuterLoop]
- [Fact]
- public async Task InfiniteSingleHeader_ThrowsException()
- {
- await LoopbackServer.CreateServerAsync(async (server, url) =>
- {
- using (HttpClientHandler handler = CreateHttpClientHandler())
- using (HttpClient client = CreateHttpClient(handler))
- {
- Task<HttpResponseMessage> getAsync = client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead);
- await server.AcceptConnectionAsync(async connection =>
- {
- var cts = new CancellationTokenSource();
- Task serverTask = Task.Run(async delegate
- {
- await connection.ReadRequestHeaderAndSendCustomResponseAsync("HTTP/1.1 200 OK\r\nContent-Length: 0\r\nMyInfiniteHeader: ");
- try
- {
- while (!cts.IsCancellationRequested)
- {
- await connection.Writer.WriteAsync(new string('s', 16000));
- await Task.Delay(1);
- }
- }
- catch { }
- });
-
- Exception e = await Assert.ThrowsAsync<HttpRequestException>(() => getAsync);
- cts.Cancel();
- Assert.Contains((handler.MaxResponseHeadersLength * 1024).ToString(), e.ToString());
- await serverTask;
- });
- }
- });
- }
-
- [OuterLoop]
- [Theory, MemberData(nameof(ResponseWithManyHeadersData))]
- public async Task ThresholdExceeded_ThrowsException(string responseHeaders, int? maxResponseHeadersLength, bool shouldSucceed)
- {
- await LoopbackServer.CreateServerAsync(async (server, url) =>
- {
- using (HttpClientHandler handler = CreateHttpClientHandler())
- using (HttpClient client = CreateHttpClient(handler))
- {
- if (maxResponseHeadersLength.HasValue)
- {
- handler.MaxResponseHeadersLength = maxResponseHeadersLength.Value;
- }
- Task<HttpResponseMessage> getAsync = client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead);
-
- await server.AcceptConnectionAsync(async connection =>
- {
- Task serverTask = connection.ReadRequestHeaderAndSendCustomResponseAsync(responseHeaders);
-
- if (shouldSucceed)
- {
- (await getAsync).Dispose();
- await serverTask;
- }
- else
- {
- Exception e = await Assert.ThrowsAsync<HttpRequestException>(() => getAsync);
- Assert.Contains((handler.MaxResponseHeadersLength * 1024).ToString(), e.ToString());
- try { await serverTask; } catch { }
- }
- });
- }
- });
- }
-
- public static IEnumerable<object[]> ResponseWithManyHeadersData
- {
- get
- {
- foreach (int? max in new int?[] { null, 1, 31, 128 })
- {
- int actualSize = max.HasValue ? max.Value : 64;
-
- yield return new object[] { GenerateLargeResponseHeaders(actualSize * 1024 - 1), max, true }; // Small enough
- yield return new object[] { GenerateLargeResponseHeaders(actualSize * 1024), max, true }; // Just right
- yield return new object[] { GenerateLargeResponseHeaders(actualSize * 1024 + 1), max, false }; // Too big
- }
- }
- }
-
- private static string GenerateLargeResponseHeaders(int responseHeadersSizeInBytes)
- {
- var buffer = new StringBuilder();
- buffer.Append("HTTP/1.1 200 OK\r\n");
- buffer.Append("Content-Length: 0\r\n");
- for (int i = 0; i < 24; i++)
- {
- buffer.Append($"Custom-{i:D4}: 1234567890123456789012345\r\n");
- }
- buffer.Append($"Custom-24: ");
- buffer.Append(new string('c', responseHeadersSizeInBytes - (buffer.Length + 4)));
- buffer.Append("\r\n\r\n");
-
- string response = buffer.ToString();
- Assert.Equal(responseHeadersSizeInBytes, response.Length);
- return response;
- }
- }
-}
+++ /dev/null
-// 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.Collections.Generic;
-using System.Net.Sockets;
-using System.Net.Test.Common;
-using System.Text;
-using System.Threading.Tasks;
-
-using Microsoft.DotNet.XUnitExtensions;
-using Microsoft.DotNet.RemoteExecutor;
-
-using Xunit;
-using Xunit.Abstractions;
-
-namespace System.Net.Http.Functional.Tests
-{
- using Configuration = System.Net.Test.Common.Configuration;
-
- public abstract class HttpClientHandler_Proxy_Test : HttpClientHandlerTestBase
- {
- public HttpClientHandler_Proxy_Test(ITestOutputHelper output) : base(output) { }
-
- [Fact]
- public async Task Dispose_HandlerWithProxy_ProxyNotDisposed()
- {
- var proxy = new TrackDisposalProxy();
-
- await LoopbackServerFactory.CreateClientAndServerAsync(async uri =>
- {
- using (HttpClientHandler handler = CreateHttpClientHandler())
- {
- handler.UseProxy = true;
- handler.Proxy = proxy;
- using (HttpClient client = CreateHttpClient(handler))
- {
- Assert.Equal("hello", await client.GetStringAsync(uri));
- }
- }
- }, async server =>
- {
- await server.HandleRequestAsync(content: "hello");
- });
-
- Assert.True(proxy.ProxyUsed);
- Assert.False(proxy.Disposed);
- }
-
- [ActiveIssue("https://github.com/dotnet/corefx/issues/32809")]
- [OuterLoop("Uses external server")]
- [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindowsNanoServer))]
- [InlineData(AuthenticationSchemes.Ntlm, true, false)]
- [InlineData(AuthenticationSchemes.Negotiate, true, false)]
- [InlineData(AuthenticationSchemes.Basic, false, false)]
- [InlineData(AuthenticationSchemes.Basic, true, false)]
- [InlineData(AuthenticationSchemes.Digest, false, false)]
- [InlineData(AuthenticationSchemes.Digest, true, false)]
- [InlineData(AuthenticationSchemes.Ntlm, false, false)]
- [InlineData(AuthenticationSchemes.Negotiate, false, false)]
- [InlineData(AuthenticationSchemes.Basic, false, true)]
- [InlineData(AuthenticationSchemes.Basic, true, true)]
- [InlineData(AuthenticationSchemes.Digest, false, true)]
- [InlineData(AuthenticationSchemes.Digest, true, true)]
- public async Task AuthProxy__ValidCreds_ProxySendsRequestToServer(
- AuthenticationSchemes proxyAuthScheme,
- bool secureServer,
- bool proxyClosesConnectionAfterFirst407Response)
- {
- if (!PlatformDetection.IsWindows &&
- (proxyAuthScheme == AuthenticationSchemes.Negotiate || proxyAuthScheme == AuthenticationSchemes.Ntlm))
- {
- // CI machines don't have GSSAPI module installed and will fail with error from
- // System.Net.Security.NegotiateStreamPal.AcquireCredentialsHandle():
- // "GSSAPI operation failed with error - An invalid status code was supplied
- // Configuration file does not specify default realm)."
- return;
- }
-
- Uri serverUri = secureServer ? Configuration.Http.SecureRemoteEchoServer : Configuration.Http.RemoteEchoServer;
-
- var options = new LoopbackProxyServer.Options
- { AuthenticationSchemes = proxyAuthScheme,
- ConnectionCloseAfter407 = proxyClosesConnectionAfterFirst407Response
- };
- using (LoopbackProxyServer proxyServer = LoopbackProxyServer.Create(options))
- {
- using (HttpClientHandler handler = CreateHttpClientHandler())
- using (HttpClient client = CreateHttpClient(handler))
- {
- handler.Proxy = new WebProxy(proxyServer.Uri);
- handler.Proxy.Credentials = new NetworkCredential("username", "password");
- using (HttpResponseMessage response = await client.GetAsync(serverUri))
- {
- Assert.Equal(HttpStatusCode.OK, response.StatusCode);
- TestHelper.VerifyResponseBody(
- await response.Content.ReadAsStringAsync(),
- response.Content.Headers.ContentMD5,
- false,
- null);
- }
- }
- }
- }
-
- [OuterLoop("Uses external server")]
- [ConditionalFact]
- public void Proxy_UseEnvironmentVariableToSetSystemProxy_RequestGoesThruProxy()
- {
- RemoteExecutor.Invoke(async (useHttp2String) =>
- {
- var options = new LoopbackProxyServer.Options { AddViaRequestHeader = true };
- using (LoopbackProxyServer proxyServer = LoopbackProxyServer.Create(options))
- {
- Environment.SetEnvironmentVariable("http_proxy", proxyServer.Uri.AbsoluteUri.ToString());
-
- using (HttpClient client = CreateHttpClient(useHttp2String))
- using (HttpResponseMessage response = await client.GetAsync(Configuration.Http.RemoteEchoServer))
- {
- Assert.Equal(HttpStatusCode.OK, response.StatusCode);
- string body = await response.Content.ReadAsStringAsync();
- Assert.Contains(proxyServer.ViaHeader, body);
- }
- }
- }, UseHttp2.ToString()).Dispose();
- }
-
- [ActiveIssue("https://github.com/dotnet/corefx/issues/32809")]
- [OuterLoop("Uses external server")]
- [Theory]
- [MemberData(nameof(CredentialsForProxy))]
- public async Task Proxy_BypassFalse_GetRequestGoesThroughCustomProxy(ICredentials creds, bool wrapCredsInCache)
- {
- var options = new LoopbackProxyServer.Options
- { AuthenticationSchemes = creds != null ? AuthenticationSchemes.Basic : AuthenticationSchemes.None
- };
- using (LoopbackProxyServer proxyServer = LoopbackProxyServer.Create(options))
- {
- const string BasicAuth = "Basic";
- if (wrapCredsInCache)
- {
- Assert.IsAssignableFrom<NetworkCredential>(creds);
- var cache = new CredentialCache();
- cache.Add(proxyServer.Uri, BasicAuth, (NetworkCredential)creds);
- creds = cache;
- }
-
- using (HttpClientHandler handler = CreateHttpClientHandler())
- using (HttpClient client = CreateHttpClient(handler))
- {
- handler.Proxy = new WebProxy(proxyServer.Uri) { Credentials = creds };
-
- using (HttpResponseMessage response = await client.GetAsync(Configuration.Http.RemoteEchoServer))
- {
- Assert.Equal(HttpStatusCode.OK, response.StatusCode);
- TestHelper.VerifyResponseBody(
- await response.Content.ReadAsStringAsync(),
- response.Content.Headers.ContentMD5,
- false,
- null);
-
- if (options.AuthenticationSchemes != AuthenticationSchemes.None)
- {
- NetworkCredential nc = creds?.GetCredential(proxyServer.Uri, BasicAuth);
- Assert.NotNull(nc);
- string expectedAuth =
- string.IsNullOrEmpty(nc.Domain) ? $"{nc.UserName}:{nc.Password}" :
- $"{nc.Domain}\\{nc.UserName}:{nc.Password}";
- _output.WriteLine($"expectedAuth={expectedAuth}");
- string expectedAuthHash = Convert.ToBase64String(Encoding.UTF8.GetBytes(expectedAuth));
-
- // Check last request to proxy server. Handlers that don't use
- // pre-auth for proxy will make 2 requests.
- int requestCount = proxyServer.Requests.Count;
- _output.WriteLine($"proxyServer.Requests.Count={requestCount}");
- Assert.Equal(BasicAuth, proxyServer.Requests[requestCount - 1].AuthorizationHeaderValueScheme);
- Assert.Equal(expectedAuthHash, proxyServer.Requests[requestCount - 1].AuthorizationHeaderValueToken);
- }
- }
- }
- }
- }
-
- [OuterLoop("Uses external server")]
- [Theory]
- [MemberData(nameof(BypassedProxies))]
- public async Task Proxy_BypassTrue_GetRequestDoesntGoesThroughCustomProxy(IWebProxy proxy)
- {
- HttpClientHandler handler = CreateHttpClientHandler();
- handler.Proxy = proxy;
- using (HttpClient client = CreateHttpClient(handler))
- using (HttpResponseMessage response = await client.GetAsync(Configuration.Http.RemoteEchoServer))
- {
- TestHelper.VerifyResponseBody(
- await response.Content.ReadAsStringAsync(),
- response.Content.Headers.ContentMD5,
- false,
- null);
- }
- }
-
- [OuterLoop("Uses external server")]
- [Fact]
- public async Task Proxy_HaveNoCredsAndUseAuthenticatedCustomProxy_ProxyAuthenticationRequiredStatusCode()
- {
- var options = new LoopbackProxyServer.Options { AuthenticationSchemes = AuthenticationSchemes.Basic };
- using (LoopbackProxyServer proxyServer = LoopbackProxyServer.Create(options))
- {
- HttpClientHandler handler = CreateHttpClientHandler();
- handler.Proxy = new WebProxy(proxyServer.Uri);
- using (HttpClient client = CreateHttpClient(handler))
- using (HttpResponseMessage response = await client.GetAsync(Configuration.Http.RemoteEchoServer))
- {
- Assert.Equal(HttpStatusCode.ProxyAuthenticationRequired, response.StatusCode);
- }
- }
- }
-
- [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindowsSubsystemForLinux))] // [ActiveIssue("https://github.com/dotnet/corefx/issues/11057")]
- public async Task Proxy_SslProxyUnsupported_Throws()
- {
- using (HttpClientHandler handler = CreateHttpClientHandler())
- using (HttpClient client = CreateHttpClient(handler))
- {
- handler.Proxy = new WebProxy("https://" + Guid.NewGuid().ToString("N"));
-
- await Assert.ThrowsAsync<NotSupportedException>(() => client.GetAsync("http://" + Guid.NewGuid().ToString("N")));
- }
- }
-
- [OuterLoop("Uses external server")]
- [Fact]
- public async Task Proxy_SendSecureRequestThruProxy_ConnectTunnelUsed()
- {
- using (LoopbackProxyServer proxyServer = LoopbackProxyServer.Create())
- {
- HttpClientHandler handler = CreateHttpClientHandler();
- handler.Proxy = new WebProxy(proxyServer.Uri);
- using (HttpClient client = CreateHttpClient(handler))
- using (HttpResponseMessage response = await client.GetAsync(Configuration.Http.SecureRemoteEchoServer))
- {
- Assert.Equal(HttpStatusCode.OK, response.StatusCode);
- _output.WriteLine($"Proxy request line: {proxyServer.Requests[0].RequestLine}");
- Assert.Contains("CONNECT", proxyServer.Requests[0].RequestLine);
- }
- }
- }
-
- [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindowsNanoServer))]
- public async Task ProxyAuth_Digest_Succeeds()
- {
- const string expectedUsername = "testusername";
- const string expectedPassword = "testpassword";
- const string authHeader = "Proxy-Authenticate: Digest realm=\"NetCore\", nonce=\"PwOnWgAAAAAAjnbW438AAJSQi1kAAAAA\", qop=\"auth\", stale=false\r\n";
- LoopbackServer.Options options = new LoopbackServer.Options { IsProxy = true, Username = expectedUsername, Password = expectedPassword };
- var proxyCreds = new NetworkCredential(expectedUsername, expectedPassword);
-
- await LoopbackServer.CreateServerAsync(async (proxyServer, proxyUrl) =>
- {
- using (HttpClientHandler handler = CreateHttpClientHandler())
- using (HttpClient client = CreateHttpClient(handler))
- {
- handler.Proxy = new WebProxy(proxyUrl) { Credentials = proxyCreds };
-
- // URL does not matter. We will get response from "proxy" code below.
- Task<HttpResponseMessage> clientTask = client.GetAsync($"http://notarealserver.com/");
-
- // Send Digest challenge.
- Task<List<string>> serverTask = proxyServer.AcceptConnectionSendResponseAndCloseAsync(HttpStatusCode.ProxyAuthenticationRequired, authHeader);
- if (clientTask == await Task.WhenAny(clientTask, serverTask).TimeoutAfter(TestHelper.PassingTestTimeoutMilliseconds))
- {
- // Client task shouldn't have completed successfully; propagate failure.
- Assert.NotEqual(TaskStatus.RanToCompletion, clientTask.Status);
- await clientTask;
- }
-
- // Verify user & password.
- serverTask = proxyServer.AcceptConnectionPerformAuthenticationAndCloseAsync("");
- await TaskTimeoutExtensions.WhenAllOrAnyFailed(new Task[] { clientTask, serverTask }, TestHelper.PassingTestTimeoutMilliseconds);
-
- Assert.Equal(HttpStatusCode.OK, clientTask.Result.StatusCode);
- }
- }, options);
-
- }
-
- [Fact]
- [PlatformSpecific(TestPlatforms.Windows)]
- public async Task MultiProxy_PAC_Failover_Succeeds()
- {
- // Create our failing proxy server.
- // Bind a port to reserve it, but don't start listening yet. The first Connect() should fail and cause a fail-over.
- using Socket failingProxyServer = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
- failingProxyServer.Bind(new IPEndPoint(IPAddress.Loopback, 0));
- var failingEndPoint = (IPEndPoint)failingProxyServer.LocalEndPoint;
-
- using LoopbackProxyServer succeedingProxyServer = LoopbackProxyServer.Create();
- string proxyConfigString = $"{failingEndPoint.Address}:{failingEndPoint.Port} {succeedingProxyServer.Uri.Host}:{succeedingProxyServer.Uri.Port}";
-
- // Create a WinInetProxyHelper and override its values with our own.
- object winInetProxyHelper = Activator.CreateInstance(typeof(HttpClient).Assembly.GetType("System.Net.Http.WinInetProxyHelper", true), true);
- winInetProxyHelper.GetType().GetField("_autoConfigUrl", Reflection.BindingFlags.Instance | Reflection.BindingFlags.NonPublic).SetValue(winInetProxyHelper, null);
- winInetProxyHelper.GetType().GetField("_autoDetect", Reflection.BindingFlags.Instance | Reflection.BindingFlags.NonPublic).SetValue(winInetProxyHelper, false);
- winInetProxyHelper.GetType().GetField("_proxy", Reflection.BindingFlags.Instance | Reflection.BindingFlags.NonPublic).SetValue(winInetProxyHelper, proxyConfigString);
- winInetProxyHelper.GetType().GetField("_proxyBypass", Reflection.BindingFlags.Instance | Reflection.BindingFlags.NonPublic).SetValue(winInetProxyHelper, null);
-
- // Create a HttpWindowsProxy with our custom WinInetProxyHelper.
- IWebProxy httpWindowsProxy = (IWebProxy)Activator.CreateInstance(typeof(HttpClient).Assembly.GetType("System.Net.Http.HttpWindowsProxy", true), Reflection.BindingFlags.NonPublic | Reflection.BindingFlags.Instance, null, new[] { winInetProxyHelper, null }, null);
-
- Task<bool> nextFailedConnection = null;
-
- // Run a request with that proxy.
- Task requestTask = LoopbackServerFactory.CreateClientAndServerAsync(
- async uri =>
- {
- using HttpClientHandler handler = CreateHttpClientHandler();
- using HttpClient client = CreateHttpClient(handler);
- handler.Proxy = httpWindowsProxy;
-
- // First request is expected to hit the failing proxy server, then failover to the succeeding proxy server.
- Assert.Equal("foo", await client.GetStringAsync(uri));
-
- // Second request should start directly at the succeeding proxy server.
- // So, start listening on our failing proxy server to catch if it tries to connect.
- failingProxyServer.Listen(1);
- nextFailedConnection = WaitForNextFailedConnection();
- Assert.Equal("bar", await client.GetStringAsync(uri));
- },
- async server =>
- {
- await server.HandleRequestAsync(statusCode: HttpStatusCode.OK, content: "foo");
- await server.HandleRequestAsync(statusCode: HttpStatusCode.OK, content: "bar");
- });
-
- // Wait for request to finish.
- await requestTask;
-
- // Triggers WaitForNextFailedConnection to stop, and then check
- // to ensure we haven't got any further requests against it.
- failingProxyServer.Dispose();
- Assert.False(await nextFailedConnection);
-
- Assert.Equal(2, succeedingProxyServer.Requests.Count);
-
- async Task<bool> WaitForNextFailedConnection()
- {
- try
- {
- (await failingProxyServer.AcceptAsync()).Dispose();
- return true;
- }
- catch (SocketException ex) when (ex.SocketErrorCode == SocketError.OperationAborted)
- {
- // Dispose() of the loopback server will cause AcceptAsync() in EstablishConnectionAsync() to abort.
- return false;
- }
- }
- }
-
- public static IEnumerable<object[]> BypassedProxies()
- {
- yield return new object[] { null };
- yield return new object[] { new UseSpecifiedUriWebProxy(new Uri($"http://{Guid.NewGuid().ToString().Substring(0, 15)}:12345"), bypass: true) };
- }
-
- public static IEnumerable<object[]> CredentialsForProxy()
- {
- yield return new object[] { null, false };
- foreach (bool wrapCredsInCache in new[] { true, false })
- {
- yield return new object[] { new NetworkCredential("username", "password"), wrapCredsInCache };
- yield return new object[] { new NetworkCredential("username", "password", "domain"), wrapCredsInCache };
- }
- }
-
- private sealed class TrackDisposalProxy : IWebProxy, IDisposable
- {
- public bool Disposed;
- public bool ProxyUsed;
-
- public void Dispose() => Disposed = true;
- public Uri GetProxy(Uri destination)
- {
- ProxyUsed = true;
- return null;
- }
- public bool IsBypassed(Uri host)
- {
- ProxyUsed = true;
- return true;
- }
- public ICredentials Credentials { get => null; set { } }
- }
- }
-}
+++ /dev/null
-// 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.Collections.Generic;
-using System.Diagnostics;
-using System.IO;
-using System.Linq;
-using System.Net.Security;
-using System.Net.Test.Common;
-using System.Runtime.InteropServices;
-using System.Security.Authentication.ExtendedProtection;
-using System.Security.Cryptography.X509Certificates;
-using System.Threading.Tasks;
-using Microsoft.DotNet.RemoteExecutor;
-using Xunit;
-using Xunit.Abstractions;
-
-namespace System.Net.Http.Functional.Tests
-{
- using Configuration = System.Net.Test.Common.Configuration;
-
- public abstract partial class HttpClientHandler_ServerCertificates_Test : HttpClientHandlerTestBase
- {
- private static bool ClientSupportsDHECipherSuites => (!PlatformDetection.IsWindows || PlatformDetection.IsWindows10Version1607OrGreater);
-
- public HttpClientHandler_ServerCertificates_Test(ITestOutputHelper output) : base(output) { }
-
- [Fact]
- public void Ctor_ExpectedDefaultValues()
- {
- using (HttpClientHandler handler = CreateHttpClientHandler())
- {
- Assert.Null(handler.ServerCertificateCustomValidationCallback);
- Assert.False(handler.CheckCertificateRevocationList);
- }
- }
-
- [Fact]
- public void ServerCertificateCustomValidationCallback_SetGet_Roundtrips()
- {
- using (HttpClientHandler handler = CreateHttpClientHandler())
- {
- Assert.Null(handler.ServerCertificateCustomValidationCallback);
-
- Func<HttpRequestMessage, X509Certificate2, X509Chain, SslPolicyErrors, bool> callback1 = (req, cert, chain, policy) => throw new NotImplementedException("callback1");
- Func<HttpRequestMessage, X509Certificate2, X509Chain, SslPolicyErrors, bool> callback2 = (req, cert, chain, policy) => throw new NotImplementedException("callback2");
-
- handler.ServerCertificateCustomValidationCallback = callback1;
- Assert.Same(callback1, handler.ServerCertificateCustomValidationCallback);
-
- handler.ServerCertificateCustomValidationCallback = callback2;
- Assert.Same(callback2, handler.ServerCertificateCustomValidationCallback);
-
- handler.ServerCertificateCustomValidationCallback = null;
- Assert.Null(handler.ServerCertificateCustomValidationCallback);
- }
- }
-
- [OuterLoop("Uses external server")]
- [Fact]
- public async Task NoCallback_ValidCertificate_SuccessAndExpectedPropertyBehavior()
- {
- HttpClientHandler handler = CreateHttpClientHandler();
- using (HttpClient client = CreateHttpClient(handler))
- {
- using (HttpResponseMessage response = await client.GetAsync(Configuration.Http.SecureRemoteEchoServer))
- {
- Assert.Equal(HttpStatusCode.OK, response.StatusCode);
- }
-
- Assert.Throws<InvalidOperationException>(() => handler.ServerCertificateCustomValidationCallback = null);
- Assert.Throws<InvalidOperationException>(() => handler.CheckCertificateRevocationList = false);
- }
- }
-
- [OuterLoop("Uses external server")]
- [Fact]
- public async Task UseCallback_HaveCredsAndUseAuthenticatedCustomProxyAndPostToSecureServer_Success()
- {
- var options = new LoopbackProxyServer.Options
- { AuthenticationSchemes = AuthenticationSchemes.Basic,
- ConnectionCloseAfter407 = true
- };
- using (LoopbackProxyServer proxyServer = LoopbackProxyServer.Create(options))
- {
- HttpClientHandler handler = CreateHttpClientHandler();
- handler.ServerCertificateCustomValidationCallback = TestHelper.AllowAllCertificates;
- handler.Proxy = new WebProxy(proxyServer.Uri)
- {
- Credentials = new NetworkCredential("rightusername", "rightpassword")
- };
-
- const string content = "This is a test";
-
- using (HttpClient client = CreateHttpClient(handler))
- using (HttpResponseMessage response = await client.PostAsync(
- Configuration.Http.SecureRemoteEchoServer,
- new StringContent(content)))
- {
- string responseContent = await response.Content.ReadAsStringAsync();
-
- Assert.Equal(HttpStatusCode.OK, response.StatusCode);
- TestHelper.VerifyResponseBody(
- responseContent,
- response.Content.Headers.ContentMD5,
- false,
- content);
- }
- }
- }
-
- [OuterLoop("Uses external server")]
- [Fact]
- public async Task UseCallback_HaveNoCredsAndUseAuthenticatedCustomProxyAndPostToSecureServer_ProxyAuthenticationRequiredStatusCode()
- {
- var options = new LoopbackProxyServer.Options
- { AuthenticationSchemes = AuthenticationSchemes.Basic,
- ConnectionCloseAfter407 = true
- };
- using (LoopbackProxyServer proxyServer = LoopbackProxyServer.Create(options))
- {
- HttpClientHandler handler = CreateHttpClientHandler();
- handler.Proxy = new WebProxy(proxyServer.Uri);
- handler.ServerCertificateCustomValidationCallback = TestHelper.AllowAllCertificates;
- using (HttpClient client = CreateHttpClient(handler))
- using (HttpResponseMessage response = await client.PostAsync(
- Configuration.Http.SecureRemoteEchoServer,
- new StringContent("This is a test")))
- {
- Assert.Equal(HttpStatusCode.ProxyAuthenticationRequired, response.StatusCode);
- }
- }
- }
-
- [OuterLoop("Uses external server")]
- [Fact]
- public async Task UseCallback_NotSecureConnection_CallbackNotCalled()
- {
- HttpClientHandler handler = CreateHttpClientHandler();
- using (HttpClient client = CreateHttpClient(handler))
- {
- bool callbackCalled = false;
- handler.ServerCertificateCustomValidationCallback = delegate { callbackCalled = true; return true; };
-
- using (HttpResponseMessage response = await client.GetAsync(Configuration.Http.RemoteEchoServer))
- {
- Assert.Equal(HttpStatusCode.OK, response.StatusCode);
- }
-
- Assert.False(callbackCalled);
- }
- }
-
- public static IEnumerable<object[]> UseCallback_ValidCertificate_ExpectedValuesDuringCallback_Urls()
- {
- foreach (Configuration.Http.RemoteServer remoteServer in Configuration.Http.RemoteServers)
- {
- if (remoteServer.IsSecure)
- {
- foreach (bool checkRevocation in new[] { true, false })
- {
- yield return new object[] {
- remoteServer,
- remoteServer.EchoUri,
- checkRevocation };
- yield return new object[] {
- remoteServer,
- remoteServer.RedirectUriForDestinationUri(
- statusCode:302,
- remoteServer.EchoUri,
- hops:1),
- checkRevocation };
- }
- }
- }
- }
-
- [OuterLoop("Uses external server")]
- [Theory]
- [MemberData(nameof(UseCallback_ValidCertificate_ExpectedValuesDuringCallback_Urls))]
- public async Task UseCallback_ValidCertificate_ExpectedValuesDuringCallback(Configuration.Http.RemoteServer remoteServer, Uri url, bool checkRevocation)
- {
- HttpClientHandler handler = CreateHttpClientHandler();
- using (HttpClient client = CreateHttpClientForRemoteServer(remoteServer, handler))
- {
- bool callbackCalled = false;
- handler.CheckCertificateRevocationList = checkRevocation;
- handler.ServerCertificateCustomValidationCallback = (request, cert, chain, errors) => {
- callbackCalled = true;
- Assert.NotNull(request);
-
- X509ChainStatusFlags flags = chain.ChainStatus.Aggregate(X509ChainStatusFlags.NoError, (cur, status) => cur | status.Status);
- bool ignoreErrors = // https://github.com/dotnet/corefx/issues/21922#issuecomment-315555237
- RuntimeInformation.IsOSPlatform(OSPlatform.OSX) &&
- checkRevocation &&
- errors == SslPolicyErrors.RemoteCertificateChainErrors &&
- flags == X509ChainStatusFlags.RevocationStatusUnknown;
- Assert.True(ignoreErrors || errors == SslPolicyErrors.None, $"Expected {SslPolicyErrors.None}, got {errors} with chain status {flags}");
-
- Assert.True(chain.ChainElements.Count > 0);
- Assert.NotEmpty(cert.Subject);
-
- // UWP always uses CheckCertificateRevocationList=true regardless of setting the property and
- // the getter always returns true. So, for this next Assert, it is better to get the property
- // value back from the handler instead of using the parameter value of the test.
- Assert.Equal(
- handler.CheckCertificateRevocationList ? X509RevocationMode.Online : X509RevocationMode.NoCheck,
- chain.ChainPolicy.RevocationMode);
- return true;
- };
-
- using (HttpResponseMessage response = await client.GetAsync(url))
- {
- Assert.Equal(HttpStatusCode.OK, response.StatusCode);
- }
-
- Assert.True(callbackCalled);
- }
- }
-
- [OuterLoop("Uses external server")]
- [Fact]
- public async Task UseCallback_CallbackReturnsFailure_ThrowsException()
- {
- HttpClientHandler handler = CreateHttpClientHandler();
- using (HttpClient client = CreateHttpClient(handler))
- {
- handler.ServerCertificateCustomValidationCallback = delegate { return false; };
- await Assert.ThrowsAsync<HttpRequestException>(() => client.GetAsync(Configuration.Http.SecureRemoteEchoServer));
- }
- }
-
- [OuterLoop("Uses external server")]
- [Fact]
- public async Task UseCallback_CallbackThrowsException_ExceptionPropagatesAsBaseException()
- {
- HttpClientHandler handler = CreateHttpClientHandler();
- using (HttpClient client = CreateHttpClient(handler))
- {
- var e = new DivideByZeroException();
- handler.ServerCertificateCustomValidationCallback = delegate { throw e; };
-
- HttpRequestException ex = await Assert.ThrowsAsync<HttpRequestException>(() => client.GetAsync(Configuration.Http.SecureRemoteEchoServer));
- Assert.Same(e, ex.GetBaseException());
- }
- }
-
- public static readonly object[][] CertificateValidationServers =
- {
- new object[] { Configuration.Http.ExpiredCertRemoteServer },
- new object[] { Configuration.Http.SelfSignedCertRemoteServer },
- new object[] { Configuration.Http.WrongHostNameCertRemoteServer },
- };
-
- [OuterLoop("Uses external server")]
- [ConditionalTheory(nameof(ClientSupportsDHECipherSuites))]
- [MemberData(nameof(CertificateValidationServers))]
- public async Task NoCallback_BadCertificate_ThrowsException(string url)
- {
- using (HttpClient client = CreateHttpClient())
- {
- await Assert.ThrowsAsync<HttpRequestException>(() => client.GetAsync(url));
- }
- }
-
- [OuterLoop("Uses external server")]
- [ConditionalFact(nameof(ClientSupportsDHECipherSuites))]
- public async Task NoCallback_RevokedCertificate_NoRevocationChecking_Succeeds()
- {
- using (HttpClient client = CreateHttpClient())
- using (HttpResponseMessage response = await client.GetAsync(Configuration.Http.RevokedCertRemoteServer))
- {
- Assert.Equal(HttpStatusCode.OK, response.StatusCode);
- }
- }
-
- [OuterLoop("Uses external server")]
- [Fact]
- public async Task NoCallback_RevokedCertificate_RevocationChecking_Fails()
- {
- HttpClientHandler handler = CreateHttpClientHandler();
- handler.CheckCertificateRevocationList = true;
- using (HttpClient client = CreateHttpClient(handler))
- {
- await Assert.ThrowsAsync<HttpRequestException>(() => client.GetAsync(Configuration.Http.RevokedCertRemoteServer));
- }
- }
-
- public static readonly object[][] CertificateValidationServersAndExpectedPolicies =
- {
- new object[] { Configuration.Http.ExpiredCertRemoteServer, SslPolicyErrors.RemoteCertificateChainErrors },
- new object[] { Configuration.Http.SelfSignedCertRemoteServer, SslPolicyErrors.RemoteCertificateChainErrors },
- new object[] { Configuration.Http.WrongHostNameCertRemoteServer , SslPolicyErrors.RemoteCertificateNameMismatch},
- };
-
- private async Task UseCallback_BadCertificate_ExpectedPolicyErrors_Helper(string url, string useHttp2String, SslPolicyErrors expectedErrors)
- {
- HttpClientHandler handler = CreateHttpClientHandler(useHttp2String);
- using (HttpClient client = CreateHttpClient(handler, useHttp2String))
- {
- bool callbackCalled = false;
-
- handler.ServerCertificateCustomValidationCallback = (request, cert, chain, errors) =>
- {
- callbackCalled = true;
- Assert.NotNull(request);
- Assert.NotNull(cert);
- Assert.NotNull(chain);
- Assert.Equal(expectedErrors, errors);
- return true;
- };
-
- using (HttpResponseMessage response = await client.GetAsync(url))
- {
- Assert.Equal(HttpStatusCode.OK, response.StatusCode);
- }
-
- Assert.True(callbackCalled);
- }
- }
-
- [OuterLoop("Uses external server")]
- [Theory]
- [MemberData(nameof(CertificateValidationServersAndExpectedPolicies))]
- public async Task UseCallback_BadCertificate_ExpectedPolicyErrors(string url, SslPolicyErrors expectedErrors)
- {
- const int SEC_E_BUFFER_TOO_SMALL = unchecked((int)0x80090321);
-
- if (!ClientSupportsDHECipherSuites)
- {
- return;
- }
-
- try
- {
- await UseCallback_BadCertificate_ExpectedPolicyErrors_Helper(url, UseHttp2.ToString(), expectedErrors);
- }
- catch (HttpRequestException e) when (e.InnerException?.GetType().Name == "WinHttpException" &&
- e.InnerException.HResult == SEC_E_BUFFER_TOO_SMALL &&
- !PlatformDetection.IsWindows10Version1607OrGreater)
- {
- // Testing on old Windows versions can hit https://github.com/dotnet/corefx/issues/7812
- // Ignore SEC_E_BUFFER_TOO_SMALL error on such cases.
- }
- }
-
- [OuterLoop("Uses external server")]
- [PlatformSpecific(TestPlatforms.Windows)] // CopyToAsync(Stream, TransportContext) isn't used on unix
- [Fact]
- public async Task PostAsync_Post_ChannelBinding_ConfiguredCorrectly()
- {
- var content = new ChannelBindingAwareContent("Test contest");
- using (HttpClient client = CreateHttpClient())
- using (HttpResponseMessage response = await client.PostAsync(Configuration.Http.SecureRemoteEchoServer, content))
- {
- // Validate status.
- Assert.Equal(HttpStatusCode.OK, response.StatusCode);
-
- // Validate the ChannelBinding object exists.
- ChannelBinding channelBinding = content.ChannelBinding;
- Assert.NotNull(channelBinding);
-
- // Validate the ChannelBinding's validity.
- Assert.False(channelBinding.IsInvalid, "Expected valid binding");
- Assert.NotEqual(IntPtr.Zero, channelBinding.DangerousGetHandle());
-
- // Validate the ChannelBinding's description.
- string channelBindingDescription = channelBinding.ToString();
- Assert.NotNull(channelBindingDescription);
- Assert.NotEmpty(channelBindingDescription);
- Assert.True((channelBindingDescription.Length + 1) % 3 == 0, $"Unexpected length {channelBindingDescription.Length}");
- for (int i = 0; i < channelBindingDescription.Length; i++)
- {
- char c = channelBindingDescription[i];
- if (i % 3 == 2)
- {
- Assert.Equal(' ', c);
- }
- else
- {
- Assert.True((c >= '0' && c <= '9') || (c >= 'A' && c <= 'F'), $"Expected hex, got {c}");
- }
- }
- }
- }
-
- [Fact]
- [PlatformSpecific(~TestPlatforms.Linux)]
- public void HttpClientUsesSslCertEnvironmentVariables()
- {
- // We set SSL_CERT_DIR and SSL_CERT_FILE to empty locations.
- // The HttpClient should fail to validate the server certificate.
- var psi = new ProcessStartInfo();
- string sslCertDir = GetTestFilePath();
- Directory.CreateDirectory(sslCertDir);
- psi.Environment.Add("SSL_CERT_DIR", sslCertDir);
-
- string sslCertFile = GetTestFilePath();
- File.WriteAllText(sslCertFile, "");
- psi.Environment.Add("SSL_CERT_FILE", sslCertFile);
-
- RemoteExecutor.Invoke(async (useHttp2String) =>
- {
- const string Url = "https://www.microsoft.com";
-
- using (HttpClient client = CreateHttpClient(useHttp2String))
- {
- await Assert.ThrowsAsync<HttpRequestException>(() => client.GetAsync(Url));
- }
- }, UseHttp2.ToString(), new RemoteInvokeOptions { StartInfo = psi }).Dispose();
- }
- }
-}
+++ /dev/null
-// 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.Collections.Generic;
-using System.IO;
-using System.Net.Security;
-using System.Net.Test.Common;
-using System.Runtime.InteropServices;
-using System.Security.Authentication;
-using System.Threading.Tasks;
-using Microsoft.DotNet.XUnitExtensions;
-using Xunit;
-using Xunit.Abstractions;
-
-namespace System.Net.Http.Functional.Tests
-{
- using Configuration = System.Net.Test.Common.Configuration;
-
- public abstract partial class HttpClientHandler_SslProtocols_Test : HttpClientHandlerTestBase
- {
- public HttpClientHandler_SslProtocols_Test(ITestOutputHelper output) : base(output) { }
-
- [Fact]
- public void DefaultProtocols_MatchesExpected()
- {
- using (HttpClientHandler handler = CreateHttpClientHandler())
- {
- Assert.Equal(SslProtocols.None, handler.SslProtocols);
- }
- }
-
- [Theory]
- [InlineData(SslProtocols.None)]
- [InlineData(SslProtocols.Tls)]
- [InlineData(SslProtocols.Tls11)]
- [InlineData(SslProtocols.Tls12)]
- [InlineData(SslProtocols.Tls | SslProtocols.Tls11)]
- [InlineData(SslProtocols.Tls11 | SslProtocols.Tls12)]
- [InlineData(SslProtocols.Tls | SslProtocols.Tls12)]
- [InlineData(SslProtocols.Tls | SslProtocols.Tls11 | SslProtocols.Tls12)]
- [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)]
- public void SetGetProtocols_Roundtrips(SslProtocols protocols)
- {
- using (HttpClientHandler handler = CreateHttpClientHandler())
- {
- handler.SslProtocols = protocols;
- Assert.Equal(protocols, handler.SslProtocols);
- }
- }
-
- [Fact]
- public async Task SetProtocols_AfterRequest_ThrowsException()
- {
- using (HttpClientHandler handler = CreateHttpClientHandler())
- using (HttpClient client = CreateHttpClient(handler))
- {
- handler.ServerCertificateCustomValidationCallback = TestHelper.AllowAllCertificates;
- await LoopbackServer.CreateServerAsync(async (server, url) =>
- {
- await TestHelper.WhenAllCompletedOrAnyFailed(
- server.AcceptConnectionSendResponseAndCloseAsync(),
- client.GetAsync(url));
- });
- Assert.Throws<InvalidOperationException>(() => handler.SslProtocols = SslProtocols.Tls12);
- }
- }
-
-
- public static IEnumerable<object[]> GetAsync_AllowedSSLVersion_Succeeds_MemberData()
- {
- // These protocols are all enabled by default, so we can connect with them both when
- // explicitly specifying it in the client and when not.
- foreach (SslProtocols protocol in new[] { SslProtocols.Tls, SslProtocols.Tls11, SslProtocols.Tls12 })
- {
- yield return new object[] { protocol, false };
- yield return new object[] { protocol, true };
- }
-
- // These protocols are disabled by default, so we can only connect with them explicitly.
- // On certain platforms these are completely disabled and cannot be used at all.
-#pragma warning disable 0618
- if (PlatformDetection.SupportsSsl3)
- {
- yield return new object[] { SslProtocols.Ssl3, true };
- }
- if (PlatformDetection.IsWindows && !PlatformDetection.IsWindows10Version1607OrGreater)
- {
- yield return new object[] { SslProtocols.Ssl2, true };
- }
-#pragma warning restore 0618
- // These protocols are new, and might not be enabled everywhere yet
- if (PlatformDetection.IsUbuntu1810OrHigher)
- {
- yield return new object[] { SslProtocols.Tls13, false };
- yield return new object[] { SslProtocols.Tls13, true };
- }
- }
-
- [ConditionalTheory]
- [MemberData(nameof(GetAsync_AllowedSSLVersion_Succeeds_MemberData))]
- public async Task GetAsync_AllowedSSLVersion_Succeeds(SslProtocols acceptedProtocol, bool requestOnlyThisProtocol)
- {
- using (HttpClientHandler handler = CreateHttpClientHandler())
- using (HttpClient client = CreateHttpClient(handler))
- {
- handler.ServerCertificateCustomValidationCallback = TestHelper.AllowAllCertificates;
-
- if (requestOnlyThisProtocol)
- {
- handler.SslProtocols = acceptedProtocol;
- }
- else
- {
- // Explicitly setting protocols clears implementation default
- // restrictions on minimum TLS/SSL version
- // We currently know that some platforms like Debian 10 OpenSSL
- // will by default block < TLS 1.2
- handler.SslProtocols = SslProtocols.Tls | SslProtocols.Tls11 | SslProtocols.Tls12 | SslProtocols.Tls13;
- }
-
- var options = new LoopbackServer.Options { UseSsl = true, SslProtocols = acceptedProtocol };
- await LoopbackServer.CreateServerAsync(async (server, url) =>
- {
- await TestHelper.WhenAllCompletedOrAnyFailed(
- server.AcceptConnectionSendResponseAndCloseAsync(),
- client.GetAsync(url));
- }, options);
- }
- }
-
- public static IEnumerable<object[]> SupportedSSLVersionServers()
- {
-#pragma warning disable 0618 // SSL2/3 are deprecated
- if (PlatformDetection.IsWindows ||
- PlatformDetection.IsOSX ||
- (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) && PlatformDetection.OpenSslVersion < new Version(1, 0, 2) && !PlatformDetection.IsDebian))
- {
- yield return new object[] { SslProtocols.Ssl3, Configuration.Http.SSLv3RemoteServer };
- }
-#pragma warning restore 0618
- yield return new object[] { SslProtocols.Tls, Configuration.Http.TLSv10RemoteServer };
- yield return new object[] { SslProtocols.Tls11, Configuration.Http.TLSv11RemoteServer };
- yield return new object[] { SslProtocols.Tls12, Configuration.Http.TLSv12RemoteServer };
- }
-
- // We have tests that validate with SslStream, but that's limited by what the current OS supports.
- // This tests provides additional validation against an external server.
- [ActiveIssue("https://github.com/dotnet/corefx/issues/26186")]
- [OuterLoop("Avoid www.ssllabs.com dependency in innerloop.")]
- [Theory]
- [MemberData(nameof(SupportedSSLVersionServers))]
- public async Task GetAsync_SupportedSSLVersion_Succeeds(SslProtocols sslProtocols, string url)
- {
- using (HttpClientHandler handler = CreateHttpClientHandler())
- {
- handler.SslProtocols = sslProtocols;
- using (HttpClient client = CreateHttpClient(handler))
- {
- (await RemoteServerQuery.Run(() => client.GetAsync(url), remoteServerExceptionWrapper, url)).Dispose();
- }
- }
- }
-
- public Func<Exception, bool> remoteServerExceptionWrapper = (exception) =>
- {
- Type exceptionType = exception.GetType();
- if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
- {
- // On linux, taskcanceledexception is thrown.
- return exceptionType.Equals(typeof(TaskCanceledException));
- }
- else
- {
- // The internal exceptions return operation timed out.
- return exceptionType.Equals(typeof(HttpRequestException)) && exception.InnerException.Message.Contains("timed out");
- }
- };
-
- public static IEnumerable<object[]> NotSupportedSSLVersionServers()
- {
-#pragma warning disable 0618
- if (PlatformDetection.IsWindows10Version1607OrGreater)
- {
- yield return new object[] { SslProtocols.Ssl2, Configuration.Http.SSLv2RemoteServer };
- }
-#pragma warning restore 0618
- }
-
- // We have tests that validate with SslStream, but that's limited by what the current OS supports.
- // This tests provides additional validation against an external server.
- [OuterLoop("Avoid www.ssllabs.com dependency in innerloop.")]
- [Theory]
- [MemberData(nameof(NotSupportedSSLVersionServers))]
- public async Task GetAsync_UnsupportedSSLVersion_Throws(SslProtocols sslProtocols, string url)
- {
- using (HttpClientHandler handler = CreateHttpClientHandler())
- using (HttpClient client = CreateHttpClient(handler))
- {
- handler.SslProtocols = sslProtocols;
- await Assert.ThrowsAsync<HttpRequestException>(() => RemoteServerQuery.Run(() => client.GetAsync(url), remoteServerExceptionWrapper, url));
- }
- }
-
- [Fact]
- public async Task GetAsync_NoSpecifiedProtocol_DefaultsToTls12()
- {
- using (HttpClientHandler handler = CreateHttpClientHandler())
- using (HttpClient client = CreateHttpClient(handler))
- {
- handler.ServerCertificateCustomValidationCallback = TestHelper.AllowAllCertificates;
-
- var options = new LoopbackServer.Options { UseSsl = true, SslProtocols = SslProtocols.Tls12 };
- await LoopbackServer.CreateServerAsync(async (server, url) =>
- {
- await TestHelper.WhenAllCompletedOrAnyFailed(
- client.GetAsync(url),
- server.AcceptConnectionAsync(async connection =>
- {
- Assert.Equal(SslProtocols.Tls12, Assert.IsType<SslStream>(connection.Stream).SslProtocol);
- await connection.ReadRequestHeaderAndSendResponseAsync();
- }));
- }, options);
- }
- }
-
- [Theory]
-#pragma warning disable 0618 // SSL2/3 are deprecated
- [InlineData(SslProtocols.Ssl2, SslProtocols.Tls12)]
- [InlineData(SslProtocols.Ssl3, SslProtocols.Tls12)]
-#pragma warning restore 0618
- [InlineData(SslProtocols.Tls11, SslProtocols.Tls)]
- [InlineData(SslProtocols.Tls11 | SslProtocols.Tls12, SslProtocols.Tls)] // Skip this on WinHttpHandler.
- [InlineData(SslProtocols.Tls12, SslProtocols.Tls11)]
- [InlineData(SslProtocols.Tls, SslProtocols.Tls12)]
- public async Task GetAsync_AllowedClientSslVersionDiffersFromServer_ThrowsException(
- SslProtocols allowedClientProtocols, SslProtocols acceptedServerProtocols)
- {
- using (HttpClientHandler handler = CreateHttpClientHandler())
- using (HttpClient client = CreateHttpClient(handler))
- {
- handler.SslProtocols = allowedClientProtocols;
- handler.ServerCertificateCustomValidationCallback = TestHelper.AllowAllCertificates;
-
- var options = new LoopbackServer.Options { UseSsl = true, SslProtocols = acceptedServerProtocols };
- await LoopbackServer.CreateServerAsync(async (server, url) =>
- {
- Task serverTask = server.AcceptConnectionSendResponseAndCloseAsync();
- await Assert.ThrowsAsync<HttpRequestException>(() => client.GetAsync(url));
- try
- {
- await serverTask;
- }
- catch (Exception e) when (e is IOException || e is AuthenticationException)
- {
- // Some SSL implementations simply close or reset connection after protocol mismatch.
- // Newer OpenSSL sends Fatal Alert message before closing.
- return;
- }
- // We expect negotiation to fail so one or the other expected exception should be thrown.
- Assert.True(false, "Expected exception did not happen.");
- }, options);
- }
- }
- }
-}
+++ /dev/null
-// 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;
-using System.Collections.Generic;
-using System.IO;
-using System.Linq;
-using System.Net.Http.Headers;
-using System.Net.Sockets;
-using System.Net.Test.Common;
-using System.Runtime.InteropServices;
-using System.Security.Authentication;
-using System.Security.Cryptography;
-using System.Text;
-using System.Threading;
-using System.Threading.Tasks;
-using Microsoft.DotNet.XUnitExtensions;
-using Microsoft.DotNet.RemoteExecutor;
-using Xunit;
-using Xunit.Abstractions;
-
-namespace System.Net.Http.Functional.Tests
-{
- using Configuration = System.Net.Test.Common.Configuration;
-
- // Note: Disposing the HttpClient object automatically disposes the handler within. So, it is not necessary
- // to separately Dispose (or have a 'using' statement) for the handler.
- public abstract class HttpClientHandlerTest : HttpClientHandlerTestBase
- {
- private const string ExpectedContent = "Test content";
- private const string Username = "testuser";
- private const string Password = "password";
- private const string HttpDefaultPort = "80";
-
- private readonly NetworkCredential _credential = new NetworkCredential(Username, Password);
-
- public static readonly object[][] Http2Servers = Configuration.Http.Http2Servers;
- public static readonly object[][] Http2NoPushServers = Configuration.Http.Http2NoPushServers;
-
- // Standard HTTP methods defined in RFC7231: http://tools.ietf.org/html/rfc7231#section-4.3
- // "GET", "HEAD", "POST", "PUT", "DELETE", "OPTIONS", "TRACE"
- public static readonly IEnumerable<object[]> HttpMethods =
- GetMethods("GET", "HEAD", "POST", "PUT", "DELETE", "OPTIONS", "TRACE", "CUSTOM1");
- public static readonly IEnumerable<object[]> HttpMethodsThatAllowContent =
- GetMethods("GET", "POST", "PUT", "DELETE", "OPTIONS", "CUSTOM1");
- public static readonly IEnumerable<object[]> HttpMethodsThatDontAllowContent =
- GetMethods("HEAD", "TRACE");
-
- private static bool IsWindows10Version1607OrGreater => PlatformDetection.IsWindows10Version1607OrGreater;
-
- private static IEnumerable<object[]> GetMethods(params string[] methods)
- {
- foreach (string method in methods)
- {
- foreach (Uri serverUri in Configuration.Http.EchoServerList)
- {
- yield return new object[] { method, serverUri };
- }
- }
- }
-
- public HttpClientHandlerTest(ITestOutputHelper output) : base(output)
- {
- }
-
- [Fact]
- public void CookieContainer_SetNull_ThrowsArgumentNullException()
- {
- using (HttpClientHandler handler = CreateHttpClientHandler())
- {
- Assert.Throws<ArgumentNullException>(() => handler.CookieContainer = null);
- }
- }
-
- [Fact]
- public void Ctor_ExpectedDefaultPropertyValues_CommonPlatform()
- {
- using (HttpClientHandler handler = CreateHttpClientHandler())
- {
- Assert.Equal(DecompressionMethods.None, handler.AutomaticDecompression);
- Assert.True(handler.AllowAutoRedirect);
- Assert.Equal(ClientCertificateOption.Manual, handler.ClientCertificateOptions);
- CookieContainer cookies = handler.CookieContainer;
- Assert.NotNull(cookies);
- Assert.Equal(0, cookies.Count);
- Assert.Null(handler.Credentials);
- Assert.Equal(50, handler.MaxAutomaticRedirections);
- Assert.NotNull(handler.Properties);
- Assert.Null(handler.Proxy);
- Assert.True(handler.SupportsAutomaticDecompression);
- Assert.True(handler.UseCookies);
- Assert.False(handler.UseDefaultCredentials);
- Assert.True(handler.UseProxy);
- }
- }
-
- [Fact]
- public void Ctor_ExpectedDefaultPropertyValues()
- {
- using (HttpClientHandler handler = CreateHttpClientHandler())
- {
- Assert.Equal(64, handler.MaxResponseHeadersLength);
- Assert.False(handler.PreAuthenticate);
- Assert.True(handler.SupportsProxy);
- Assert.True(handler.SupportsRedirectConfiguration);
-
- // Changes from .NET Framework.
- Assert.False(handler.CheckCertificateRevocationList);
- Assert.Equal(0, handler.MaxRequestContentBufferSize);
- Assert.Equal(SslProtocols.None, handler.SslProtocols);
- }
- }
-
- [Fact]
- public void Credentials_SetGet_Roundtrips()
- {
- using (HttpClientHandler handler = CreateHttpClientHandler())
- {
- var creds = new NetworkCredential("username", "password", "domain");
-
- handler.Credentials = null;
- Assert.Null(handler.Credentials);
-
- handler.Credentials = creds;
- Assert.Same(creds, handler.Credentials);
-
- handler.Credentials = CredentialCache.DefaultCredentials;
- Assert.Same(CredentialCache.DefaultCredentials, handler.Credentials);
- }
- }
-
- [Theory]
- [InlineData(-1)]
- [InlineData(0)]
- public void MaxAutomaticRedirections_InvalidValue_Throws(int redirects)
- {
- using (HttpClientHandler handler = CreateHttpClientHandler())
- {
- Assert.Throws<ArgumentOutOfRangeException>(() => handler.MaxAutomaticRedirections = redirects);
- }
- }
-
- [Theory]
- [InlineData(-1)]
- [InlineData((long)int.MaxValue + (long)1)]
- public void MaxRequestContentBufferSize_SetInvalidValue_ThrowsArgumentOutOfRangeException(long value)
- {
- using (HttpClientHandler handler = CreateHttpClientHandler())
- {
- Assert.Throws<ArgumentOutOfRangeException>(() => handler.MaxRequestContentBufferSize = value);
- }
- }
-
- [OuterLoop("Uses external servers")]
- [Theory]
- [InlineData(false)]
- [InlineData(true)]
- [OuterLoop("Uses external servers")]
- public async Task UseDefaultCredentials_SetToFalseAndServerNeedsAuth_StatusCodeUnauthorized(bool useProxy)
- {
- HttpClientHandler handler = CreateHttpClientHandler();
- handler.UseProxy = useProxy;
- handler.UseDefaultCredentials = false;
- using (HttpClient client = CreateHttpClient(handler))
- {
- Uri uri = Configuration.Http.RemoteHttp11Server.NegotiateAuthUriForDefaultCreds;
- _output.WriteLine("Uri: {0}", uri);
- using (HttpResponseMessage response = await client.GetAsync(uri))
- {
- Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
- }
- }
- }
-
- [Fact]
- public void Properties_Get_CountIsZero()
- {
- using (HttpClientHandler handler = CreateHttpClientHandler())
- {
- IDictionary<string, object> dict = handler.Properties;
- Assert.Same(dict, handler.Properties);
- Assert.Equal(0, dict.Count);
- }
- }
-
- [Fact]
- public void Properties_AddItemToDictionary_ItemPresent()
- {
- using (HttpClientHandler handler = CreateHttpClientHandler())
- {
- IDictionary<string, object> dict = handler.Properties;
-
- var item = new object();
- dict.Add("item", item);
-
- object value;
- Assert.True(dict.TryGetValue("item", out value));
- Assert.Equal(item, value);
- }
- }
-
- [OuterLoop("Uses external servers")]
- [Theory, MemberData(nameof(RemoteServersMemberData))]
- public async Task SendAsync_SimpleGet_Success(Configuration.Http.RemoteServer remoteServer)
- {
- using (HttpClient client = CreateHttpClientForRemoteServer(remoteServer))
- using (HttpResponseMessage response = await client.GetAsync(remoteServer.EchoUri))
- {
- string responseContent = await response.Content.ReadAsStringAsync();
- _output.WriteLine(responseContent);
- TestHelper.VerifyResponseBody(
- responseContent,
- response.Content.Headers.ContentMD5,
- false,
- null);
- }
- }
-
- [ConditionalFact]
- public async Task GetAsync_IPv6LinkLocalAddressUri_Success()
- {
- using (HttpClient client = CreateHttpClient())
- {
- var options = new GenericLoopbackOptions { Address = TestHelper.GetIPv6LinkLocalAddress() };
- if (options.Address == null)
- {
- throw new SkipTestException("Unable to find valid IPv6 LL address.");
- }
-
- await LoopbackServerFactory.CreateServerAsync(async (server, url) =>
- {
- _output.WriteLine(url.ToString());
- await TestHelper.WhenAllCompletedOrAnyFailed(
- server.AcceptConnectionSendResponseAndCloseAsync(),
- client.GetAsync(url));
- }, options: options);
- }
- }
-
- [Theory]
- [MemberData(nameof(GetAsync_IPBasedUri_Success_MemberData))]
- public async Task GetAsync_IPBasedUri_Success(IPAddress address)
- {
- using (HttpClient client = CreateHttpClient())
- {
- var options = new GenericLoopbackOptions { Address = address };
- await LoopbackServerFactory.CreateServerAsync(async (server, url) =>
- {
- _output.WriteLine(url.ToString());
- await TestHelper.WhenAllCompletedOrAnyFailed(
- server.AcceptConnectionSendResponseAndCloseAsync(),
- client.GetAsync(url));
- }, options: options);
- }
- }
-
- public static IEnumerable<object[]> GetAsync_IPBasedUri_Success_MemberData()
- {
- foreach (var addr in new[] { IPAddress.Loopback, IPAddress.IPv6Loopback })
- {
- if (addr != null)
- {
- yield return new object[] { addr };
- }
- }
- }
-
- [OuterLoop("Uses external server")]
- [Theory, MemberData(nameof(RemoteServersMemberData))]
- public async Task SendAsync_MultipleRequestsReusingSameClient_Success(Configuration.Http.RemoteServer remoteServer)
- {
- using (HttpClient client = CreateHttpClientForRemoteServer(remoteServer))
- {
- for (int i = 0; i < 3; i++)
- {
- using (HttpResponseMessage response = await client.GetAsync(remoteServer.EchoUri))
- {
- Assert.Equal(HttpStatusCode.OK, response.StatusCode);
- }
- }
- }
- }
-
- [OuterLoop("Uses external server")]
- [Theory, MemberData(nameof(RemoteServersMemberData))]
- public async Task GetAsync_ResponseContentAfterClientAndHandlerDispose_Success(Configuration.Http.RemoteServer remoteServer)
- {
- using (HttpClient client = CreateHttpClientForRemoteServer(remoteServer))
- using (HttpResponseMessage response = await client.GetAsync(remoteServer.EchoUri))
- {
- client.Dispose();
- Assert.NotNull(response);
- string responseContent = await response.Content.ReadAsStringAsync();
- _output.WriteLine(responseContent);
- TestHelper.VerifyResponseBody(responseContent, response.Content.Headers.ContentMD5, false, null);
- }
- }
-
- [Theory]
- [InlineData("[::1234]")]
- [InlineData("[::1234]:8080")]
- public async Task GetAsync_IPv6AddressInHostHeader_CorrectlyFormatted(string host)
- {
- string ipv6Address = "http://" + host;
- bool connectionAccepted = false;
-
- await LoopbackServer.CreateClientAndServerAsync(async proxyUri =>
- {
- using (HttpClientHandler handler = CreateHttpClientHandler())
- using (HttpClient client = CreateHttpClient(handler))
- {
- handler.Proxy = new WebProxy(proxyUri);
- try { await client.GetAsync(ipv6Address); } catch { }
- }
- }, server => server.AcceptConnectionAsync(async connection =>
- {
- connectionAccepted = true;
- List<string> headers = await connection.ReadRequestHeaderAndSendResponseAsync();
- Assert.Contains($"Host: {host}", headers);
- }));
-
- Assert.True(connectionAccepted);
- }
-
- [Theory]
- [InlineData("1.2.3.4")]
- [InlineData("1.2.3.4:8080")]
- [InlineData("[::1234]")]
- [InlineData("[::1234]:8080")]
- public async Task ProxiedIPAddressRequest_NotDefaultPort_CorrectlyFormatted(string host)
- {
- string uri = "http://" + host;
- bool connectionAccepted = false;
-
- await LoopbackServer.CreateClientAndServerAsync(async proxyUri =>
- {
- using (HttpClientHandler handler = CreateHttpClientHandler())
- using (HttpClient client = CreateHttpClient(handler))
- {
- handler.Proxy = new WebProxy(proxyUri);
- try { await client.GetAsync(uri); } catch { }
- }
- }, server => server.AcceptConnectionAsync(async connection =>
- {
- connectionAccepted = true;
- List<string> headers = await connection.ReadRequestHeaderAndSendResponseAsync();
- Assert.Contains($"GET {uri}/ HTTP/1.1", headers);
- }));
-
- Assert.True(connectionAccepted);
- }
-
- public static IEnumerable<object[]> DestinationHost_MemberData()
- {
- yield return new object[] { Configuration.Http.Host };
- yield return new object[] { "1.2.3.4" };
- yield return new object[] { "[::1234]" };
- }
-
- [Theory]
- [OuterLoop("Uses external server")]
- [MemberData(nameof(DestinationHost_MemberData))]
- public async Task ProxiedRequest_DefaultPort_PortStrippedOffInUri(string host)
- {
- string addressUri = $"http://{host}:{HttpDefaultPort}/";
- string expectedAddressUri = $"http://{host}/";
- bool connectionAccepted = false;
-
- await LoopbackServer.CreateClientAndServerAsync(async proxyUri =>
- {
- using (HttpClientHandler handler = CreateHttpClientHandler())
- using (HttpClient client = CreateHttpClient(handler))
- {
- handler.Proxy = new WebProxy(proxyUri);
- try { await client.GetAsync(addressUri); } catch { }
- }
- }, server => server.AcceptConnectionAsync(async connection =>
- {
- connectionAccepted = true;
- List<string> headers = await connection.ReadRequestHeaderAndSendResponseAsync();
- Assert.Contains($"GET {expectedAddressUri} HTTP/1.1", headers);
- }));
-
- Assert.True(connectionAccepted);
- }
-
- [Fact]
- [OuterLoop("Uses external server")]
- public async Task ProxyTunnelRequest_PortSpecified_NotStrippedOffInUri()
- {
- // Https proxy request will use CONNECT tunnel, even the default 443 port is specified, it will not be stripped off.
- string requestTarget = $"{Configuration.Http.SecureHost}:443";
- string addressUri = $"https://{requestTarget}/";
- bool connectionAccepted = false;
-
- await LoopbackServer.CreateClientAndServerAsync(async proxyUri =>
- {
- using (HttpClientHandler handler = CreateHttpClientHandler())
- using (HttpClient client = CreateHttpClient(handler))
- {
- handler.Proxy = new WebProxy(proxyUri);
- handler.ServerCertificateCustomValidationCallback = TestHelper.AllowAllCertificates;
- try { await client.GetAsync(addressUri); } catch { }
- }
- }, server => server.AcceptConnectionAsync(async connection =>
- {
- connectionAccepted = true;
- List<string> headers = await connection.ReadRequestHeaderAndSendResponseAsync();
- Assert.Contains($"CONNECT {requestTarget} HTTP/1.1", headers);
- }));
-
- Assert.True(connectionAccepted);
- }
-
- [Theory]
- [InlineData(true)]
- [InlineData(false)]
- [OuterLoop("Uses external server")]
- public async Task ProxyTunnelRequest_UserAgentHeaderAdded(bool addUserAgentHeader)
- {
- string addressUri = $"https://{Configuration.Http.SecureHost}/";
- bool connectionAccepted = false;
-
- await LoopbackServer.CreateClientAndServerAsync(async proxyUri =>
- {
- using (HttpClientHandler handler = CreateHttpClientHandler())
- using (var client = new HttpClient(handler))
- {
- handler.Proxy = new WebProxy(proxyUri);
- handler.ServerCertificateCustomValidationCallback = TestHelper.AllowAllCertificates;
- if (addUserAgentHeader)
- {
- client.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue("Mozilla", "5.0"));
- }
- try
- {
- await client.GetAsync(addressUri);
- }
- catch
- {
- }
- }
- }, server => server.AcceptConnectionAsync(async connection =>
- {
- connectionAccepted = true;
- List<string> headers = await connection.ReadRequestHeaderAndSendResponseAsync();
- Assert.Contains($"CONNECT {Configuration.Http.SecureHost}:443 HTTP/1.1", headers);
- if (addUserAgentHeader)
- {
- Assert.Contains("User-Agent: Mozilla/5.0", headers);
- }
- else
- {
- Assert.DoesNotContain("User-Agent:", headers);
- }
- }));
-
- Assert.True(connectionAccepted);
- }
-
- public static IEnumerable<object[]> SecureAndNonSecure_IPBasedUri_MemberData() =>
- from address in new[] { IPAddress.Loopback, IPAddress.IPv6Loopback }
- from useSsl in new[] { true, false }
- select new object[] { address, useSsl };
-
- [ConditionalTheory]
- [MemberData(nameof(SecureAndNonSecure_IPBasedUri_MemberData))]
- public async Task GetAsync_SecureAndNonSecureIPBasedUri_CorrectlyFormatted(IPAddress address, bool useSsl)
- {
- if (LoopbackServerFactory.IsHttp2)
- {
- throw new SkipTestException("Host header is not supported on HTTP/2.");
- }
-
- var options = new LoopbackServer.Options { Address = address, UseSsl= useSsl };
- bool connectionAccepted = false;
- string host = "";
-
- await LoopbackServer.CreateClientAndServerAsync(async url =>
- {
- host = $"{url.Host}:{url.Port}";
- using (HttpClientHandler handler = CreateHttpClientHandler())
- using (HttpClient client = CreateHttpClient(handler))
- {
- if (useSsl)
- {
- handler.ServerCertificateCustomValidationCallback = TestHelper.AllowAllCertificates;
- }
- try { await client.GetAsync(url); } catch { }
- }
- }, server => server.AcceptConnectionAsync(async connection =>
- {
- connectionAccepted = true;
- List<string> headers = await connection.ReadRequestHeaderAndSendResponseAsync();
- Assert.Contains($"Host: {host}", headers);
- }), options);
-
- Assert.True(connectionAccepted);
- }
-
- [OuterLoop("Uses external server")]
- [Theory, MemberData(nameof(RemoteServersMemberData))]
- public async Task GetAsync_ServerNeedsBasicAuthAndSetDefaultCredentials_StatusCodeUnauthorized(Configuration.Http.RemoteServer remoteServer)
- {
- HttpClientHandler handler = CreateHttpClientHandler();
- handler.Credentials = CredentialCache.DefaultCredentials;
- using (HttpClient client = CreateHttpClientForRemoteServer(remoteServer, handler))
- {
- Uri uri = remoteServer.BasicAuthUriForCreds(userName: Username, password: Password);
- using (HttpResponseMessage response = await client.GetAsync(uri))
- {
- Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
- }
- }
- }
-
- [OuterLoop("Uses external server")]
- [Theory, MemberData(nameof(RemoteServersMemberData))]
- public async Task GetAsync_ServerNeedsAuthAndSetCredential_StatusCodeOK(Configuration.Http.RemoteServer remoteServer)
- {
- HttpClientHandler handler = CreateHttpClientHandler();
- handler.Credentials = _credential;
- using (HttpClient client = CreateHttpClientForRemoteServer(remoteServer, handler))
- {
- Uri uri = remoteServer.BasicAuthUriForCreds(userName: Username, password: Password);
- using (HttpResponseMessage response = await client.GetAsync(uri))
- {
- Assert.Equal(HttpStatusCode.OK, response.StatusCode);
- }
- }
- }
-
- [OuterLoop("Uses external server")]
- [Fact]
- public async Task GetAsync_ServerNeedsAuthAndNoCredential_StatusCodeUnauthorized()
- {
- using (HttpClient client = CreateHttpClient(UseHttp2.ToString()))
- {
- Uri uri = Configuration.Http.RemoteHttp11Server.BasicAuthUriForCreds(userName: Username, password: Password);
- using (HttpResponseMessage response = await client.GetAsync(uri))
- {
- Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
- }
- }
- }
-
- [Theory]
- [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)
- {
- await LoopbackServerFactory.CreateServerAsync(async (server, url) =>
- {
- HttpClientHandler handler = CreateHttpClientHandler();
- handler.Credentials = new NetworkCredential("unused", "unused");
- using (HttpClient client = CreateHttpClient(handler))
- {
- Task<HttpResponseMessage> getResponseTask = client.GetAsync(url);
-
- Task<HttpRequestData> serverTask = server.AcceptConnectionSendResponseAndCloseAsync(HttpStatusCode.Unauthorized, additionalHeaders: string.IsNullOrEmpty(authHeadrName) ? null : new HttpHeaderData[] { new HttpHeaderData(authHeadrName, authHeaderValue) });
-
- await TestHelper.WhenAllCompletedOrAnyFailed(getResponseTask, serverTask);
- using (HttpResponseMessage response = await getResponseTask)
- {
- Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
- }
- }
- });
- }
-
- [OuterLoop("Uses external server")]
- [Theory]
- [MemberData(nameof(RemoteServersAndHeaderEchoUrisMemberData))]
- public async Task GetAsync_RequestHeadersAddCustomHeaders_HeaderAndEmptyValueSent(Configuration.Http.RemoteServer remoteServer, Uri uri)
- {
- string name = "X-Cust-Header-NoValue";
- string value = "";
- using (HttpClient client = CreateHttpClientForRemoteServer(remoteServer))
- {
- _output.WriteLine($"name={name}, value={value}");
- client.DefaultRequestHeaders.Add(name, value);
- using (HttpResponseMessage httpResponse = await client.GetAsync(uri))
- {
- Assert.Equal(HttpStatusCode.OK, httpResponse.StatusCode);
- string responseText = await httpResponse.Content.ReadAsStringAsync();
- _output.WriteLine(responseText);
- Assert.True(TestHelper.JsonMessageContainsKeyValue(responseText, name, value));
- }
- }
- }
-
- [OuterLoop("Uses external server")]
- [Theory, MemberData(nameof(RemoteServersHeaderValuesAndUris))]
- public async Task GetAsync_RequestHeadersAddCustomHeaders_HeaderAndValueSent(Configuration.Http.RemoteServer remoteServer, string name, string value, Uri uri)
- {
- using (HttpClient client = CreateHttpClientForRemoteServer(remoteServer))
- {
- _output.WriteLine($"name={name}, value={value}");
- client.DefaultRequestHeaders.Add(name, value);
- using (HttpResponseMessage httpResponse = await client.GetAsync(uri))
- {
- Assert.Equal(HttpStatusCode.OK, httpResponse.StatusCode);
- string responseText = await httpResponse.Content.ReadAsStringAsync();
- _output.WriteLine(responseText);
- Assert.True(TestHelper.JsonMessageContainsKeyValue(responseText, name, value));
- }
- }
- }
-
- [OuterLoop("Uses external server")]
- [Theory, MemberData(nameof(RemoteServersAndHeaderEchoUrisMemberData))]
- public async Task GetAsync_LargeRequestHeader_HeadersAndValuesSent(Configuration.Http.RemoteServer remoteServer, Uri uri)
- {
- // Unfortunately, our remote servers seem to have pretty strict limits (around 16K?)
- // on the total size of the request header.
- // TODO: Figure out how to reconfigure remote endpoints to allow larger request headers,
- // and then increase the limits in this test.
-
- string headerValue = new string('a', 2048);
- const int headerCount = 6;
-
- using (HttpClient client = CreateHttpClientForRemoteServer(remoteServer))
- {
- for (int i = 0; i < headerCount; i++)
- {
- client.DefaultRequestHeaders.Add($"Header-{i}", headerValue);
- }
-
- using (HttpResponseMessage httpResponse = await client.GetAsync(uri))
- {
- Assert.Equal(HttpStatusCode.OK, httpResponse.StatusCode);
- string responseText = await httpResponse.Content.ReadAsStringAsync();
-
- for (int i = 0; i < headerCount; i++)
- {
- Assert.True(TestHelper.JsonMessageContainsKeyValue(responseText, $"Header-{i}", headerValue));
- }
- }
- }
- }
-
- public static IEnumerable<object[]> RemoteServersHeaderValuesAndUris()
- {
- foreach ((Configuration.Http.RemoteServer remoteServer, Uri uri) in RemoteServersAndHeaderEchoUris())
- {
- yield return new object[] { remoteServer, "X-CustomHeader", "x-value", uri };
- yield return new object[] { remoteServer, "MyHeader", "1, 2, 3", uri };
-
- // Construct a header value with every valid character (except space)
- string allchars = "";
- for (int i = 0x21; i <= 0x7E; i++)
- {
- allchars = allchars + (char)i;
- }
-
- // Put a space in the middle so it's not interpreted as insignificant leading/trailing whitespace
- allchars = allchars + " " + allchars;
-
- yield return new object[] { remoteServer, "All-Valid-Chars-Header", allchars, uri };
- }
- }
-
- public static IEnumerable<(Configuration.Http.RemoteServer remoteServer, Uri uri)> RemoteServersAndHeaderEchoUris()
- {
- foreach (Configuration.Http.RemoteServer remoteServer in Configuration.Http.RemoteServers)
- {
- yield return (remoteServer, remoteServer.EchoUri);
- yield return (remoteServer, remoteServer.RedirectUriForDestinationUri(
- statusCode: 302,
- destinationUri: remoteServer.EchoUri,
- hops: 1));
- }
- }
-
- public static IEnumerable<object[]> RemoteServersAndHeaderEchoUrisMemberData() => RemoteServersAndHeaderEchoUris().Select(x => new object[] { x.remoteServer, x.uri });
-
- [Theory]
- [InlineData(":")]
- [InlineData("\x1234: \x5678")]
- [InlineData("nocolon")]
- [InlineData("no colon")]
- [InlineData("Content-Length ")]
- public async Task GetAsync_InvalidHeaderNameValue_ThrowsHttpRequestException(string invalidHeader)
- {
- await LoopbackServer.CreateClientAndServerAsync(async uri =>
- {
- using (HttpClient client = CreateHttpClient())
- {
- await Assert.ThrowsAsync<HttpRequestException>(() => client.GetStringAsync(uri));
- }
- }, server => server.AcceptConnectionSendCustomResponseAndCloseAsync($"HTTP/1.1 200 OK\r\n{invalidHeader}\r\nContent-Length: 11\r\n\r\nhello world"));
- }
-
- [Theory]
- [InlineData(false, false)]
- [InlineData(true, false)]
- [InlineData(false, true)]
- [InlineData(true, true)]
- public async Task GetAsync_IncompleteData_ThrowsHttpRequestException(bool failDuringHeaders, bool getString)
- {
- await LoopbackServer.CreateClientAndServerAsync(async uri =>
- {
- using (HttpClient client = CreateHttpClient())
- {
- Task t = getString ? (Task)
- client.GetStringAsync(uri) :
- client.GetByteArrayAsync(uri);
- await Assert.ThrowsAsync<HttpRequestException>(() => t);
- }
- }, server =>
- failDuringHeaders ?
- server.AcceptConnectionSendCustomResponseAndCloseAsync("HTTP/1.1 200 OK\r\nContent-Length: 5\r\n") :
- server.AcceptConnectionSendCustomResponseAndCloseAsync("HTTP/1.1 200 OK\r\nContent-Length: 5\r\n\r\nhe"));
- }
-
- [Fact]
- public async Task PostAsync_ManyDifferentRequestHeaders_SentCorrectly()
- {
- const string content = "hello world";
-
- // Using examples from https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#Request_fields
- // Exercises all exposed request.Headers and request.Content.Headers strongly-typed properties
- await LoopbackServerFactory.CreateClientAndServerAsync(async uri =>
- {
- using (HttpClient client = CreateHttpClient())
- {
- byte[] contentArray = Encoding.ASCII.GetBytes(content);
- var request = new HttpRequestMessage(HttpMethod.Post, uri) { Content = new ByteArrayContent(contentArray), Version = VersionFromUseHttp2 };
-
- request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("text/plain"));
- request.Headers.AcceptCharset.Add(new StringWithQualityHeaderValue("utf-8"));
- request.Headers.AcceptEncoding.Add(new StringWithQualityHeaderValue("gzip"));
- request.Headers.AcceptEncoding.Add(new StringWithQualityHeaderValue("deflate"));
- request.Headers.AcceptLanguage.Add(new StringWithQualityHeaderValue("en-US"));
- request.Headers.Add("Accept-Datetime", "Thu, 31 May 2007 20:35:00 GMT");
- request.Headers.Add("Access-Control-Request-Method", "GET");
- request.Headers.Add("Access-Control-Request-Headers", "GET");
- request.Headers.Add("Age", "12");
- request.Headers.Authorization = new AuthenticationHeaderValue("Basic", "QWxhZGRpbjpvcGVuIHNlc2FtZQ==");
- request.Headers.CacheControl = new CacheControlHeaderValue() { NoCache = true };
- request.Headers.Connection.Add("close");
- request.Headers.Add("Cookie", "$Version=1; Skin=new");
- request.Content.Headers.ContentLength = contentArray.Length;
- request.Content.Headers.ContentMD5 = MD5.Create().ComputeHash(contentArray);
- request.Content.Headers.ContentType = new MediaTypeHeaderValue("application/x-www-form-urlencoded");
- request.Headers.Date = DateTimeOffset.Parse("Tue, 15 Nov 1994 08:12:31 GMT");
- request.Headers.Expect.Add(new NameValueWithParametersHeaderValue("100-continue"));
- request.Headers.Add("Forwarded", "for=192.0.2.60;proto=http;by=203.0.113.43");
- request.Headers.Add("From", "User Name <user@example.com>");
- request.Headers.Host = "en.wikipedia.org:8080";
- request.Headers.IfMatch.Add(new EntityTagHeaderValue("\"37060cd8c284d8af7ad3082f209582d\""));
- request.Headers.IfModifiedSince = DateTimeOffset.Parse("Sat, 29 Oct 1994 19:43:31 GMT");
- request.Headers.IfNoneMatch.Add(new EntityTagHeaderValue("\"737060cd8c284d8af7ad3082f209582d\""));
- request.Headers.IfRange = new RangeConditionHeaderValue(DateTimeOffset.Parse("Wed, 21 Oct 2015 07:28:00 GMT"));
- request.Headers.IfUnmodifiedSince = DateTimeOffset.Parse("Sat, 29 Oct 1994 19:43:31 GMT");
- request.Headers.MaxForwards = 10;
- request.Headers.Add("Origin", "http://www.example-social-network.com");
- request.Headers.Pragma.Add(new NameValueHeaderValue("no-cache"));
- request.Headers.ProxyAuthorization = new AuthenticationHeaderValue("Basic", "QWxhZGRpbjpvcGVuIHNlc2FtZQ==");
- request.Headers.Range = new RangeHeaderValue(500, 999);
- request.Headers.Referrer = new Uri("http://en.wikipedia.org/wiki/Main_Page");
- request.Headers.TE.Add(new TransferCodingWithQualityHeaderValue("trailers"));
- request.Headers.TE.Add(new TransferCodingWithQualityHeaderValue("deflate"));
- request.Headers.Trailer.Add("MyTrailer");
- request.Headers.TransferEncoding.Add(new TransferCodingHeaderValue("chunked"));
- request.Headers.UserAgent.Add(new ProductInfoHeaderValue(new ProductHeaderValue("Mozilla", "5.0")));
- request.Headers.Upgrade.Add(new ProductHeaderValue("HTTPS", "1.3"));
- request.Headers.Upgrade.Add(new ProductHeaderValue("IRC", "6.9"));
- request.Headers.Upgrade.Add(new ProductHeaderValue("RTA", "x11"));
- request.Headers.Upgrade.Add(new ProductHeaderValue("websocket"));
- request.Headers.Via.Add(new ViaHeaderValue("1.0", "fred"));
- request.Headers.Via.Add(new ViaHeaderValue("1.1", "example.com", null, "(Apache/1.1)"));
- request.Headers.Warning.Add(new WarningHeaderValue(199, "-", "\"Miscellaneous warning\""));
- request.Headers.Add("X-Requested-With", "XMLHttpRequest");
- request.Headers.Add("DNT", "1 (Do Not Track Enabled)");
- request.Headers.Add("X-Forwarded-For", "client1");
- request.Headers.Add("X-Forwarded-For", "proxy1");
- request.Headers.Add("X-Forwarded-For", "proxy2");
- request.Headers.Add("X-Forwarded-Host", "en.wikipedia.org:8080");
- request.Headers.Add("X-Forwarded-Proto", "https");
- request.Headers.Add("Front-End-Https", "https");
- request.Headers.Add("X-Http-Method-Override", "DELETE");
- request.Headers.Add("X-ATT-DeviceId", "GT-P7320/P7320XXLPG");
- request.Headers.Add("X-Wap-Profile", "http://wap.samsungmobile.com/uaprof/SGH-I777.xml");
- request.Headers.Add("Proxy-Connection", "keep-alive");
- request.Headers.Add("X-UIDH", "...");
- request.Headers.Add("X-Csrf-Token", "i8XNjC4b8KVok4uw5RftR38Wgp2BFwql");
- request.Headers.Add("X-Request-ID", "f058ebd6-02f7-4d3f-942e-904344e8cde5");
- request.Headers.Add("X-Request-ID", "f058ebd6-02f7-4d3f-942e-904344e8cde5");
- request.Headers.Add("X-Empty", "");
- request.Headers.Add("X-Null", (string)null);
- request.Headers.Add("X-Underscore_Name", "X-Underscore_Name");
- request.Headers.Add("X-End", "End");
-
- (await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead)).Dispose();
- }
- }, async server =>
- {
- {
- HttpRequestData requestData = await server.HandleRequestAsync(HttpStatusCode.OK);
-
- var headersSet = requestData.Headers;
-
- Assert.Equal(content, Encoding.ASCII.GetString(requestData.Body));
-
- Assert.Equal("utf-8", requestData.GetSingleHeaderValue("Accept-Charset"));
- Assert.Equal("gzip, deflate", requestData.GetSingleHeaderValue("Accept-Encoding"));
- Assert.Equal("en-US", requestData.GetSingleHeaderValue("Accept-Language"));
- Assert.Equal("Thu, 31 May 2007 20:35:00 GMT", requestData.GetSingleHeaderValue("Accept-Datetime"));
- Assert.Equal("GET", requestData.GetSingleHeaderValue("Access-Control-Request-Method"));
- Assert.Equal("GET", requestData.GetSingleHeaderValue("Access-Control-Request-Headers"));
- Assert.Equal("12", requestData.GetSingleHeaderValue("Age"));
- Assert.Equal("Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==", requestData.GetSingleHeaderValue("Authorization"));
- Assert.Equal("no-cache", requestData.GetSingleHeaderValue("Cache-Control"));
- Assert.Equal("$Version=1; Skin=new", requestData.GetSingleHeaderValue("Cookie"));
- Assert.Equal("Tue, 15 Nov 1994 08:12:31 GMT", requestData.GetSingleHeaderValue("Date"));
- Assert.Equal("100-continue", requestData.GetSingleHeaderValue("Expect"));
- Assert.Equal("for=192.0.2.60;proto=http;by=203.0.113.43", requestData.GetSingleHeaderValue("Forwarded"));
- Assert.Equal("User Name <user@example.com>", requestData.GetSingleHeaderValue("From"));
- Assert.Equal("\"37060cd8c284d8af7ad3082f209582d\"", requestData.GetSingleHeaderValue("If-Match"));
- Assert.Equal("Sat, 29 Oct 1994 19:43:31 GMT", requestData.GetSingleHeaderValue("If-Modified-Since"));
- Assert.Equal("\"737060cd8c284d8af7ad3082f209582d\"", requestData.GetSingleHeaderValue("If-None-Match"));
- Assert.Equal("Wed, 21 Oct 2015 07:28:00 GMT", requestData.GetSingleHeaderValue("If-Range"));
- Assert.Equal("Sat, 29 Oct 1994 19:43:31 GMT", requestData.GetSingleHeaderValue("If-Unmodified-Since"));
- Assert.Equal("10", requestData.GetSingleHeaderValue("Max-Forwards"));
- Assert.Equal("http://www.example-social-network.com", requestData.GetSingleHeaderValue("Origin"));
- Assert.Equal("no-cache", requestData.GetSingleHeaderValue("Pragma"));
- Assert.Equal("Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==", requestData.GetSingleHeaderValue("Proxy-Authorization"));
- Assert.Equal("bytes=500-999", requestData.GetSingleHeaderValue("Range"));
- Assert.Equal("http://en.wikipedia.org/wiki/Main_Page", requestData.GetSingleHeaderValue("Referer"));
- Assert.Equal("MyTrailer", requestData.GetSingleHeaderValue("Trailer"));
- Assert.Equal("Mozilla/5.0", requestData.GetSingleHeaderValue("User-Agent"));
- Assert.Equal("1.0 fred, 1.1 example.com (Apache/1.1)", requestData.GetSingleHeaderValue("Via"));
- Assert.Equal("199 - \"Miscellaneous warning\"", requestData.GetSingleHeaderValue("Warning"));
- Assert.Equal("XMLHttpRequest", requestData.GetSingleHeaderValue("X-Requested-With"));
- Assert.Equal("1 (Do Not Track Enabled)", requestData.GetSingleHeaderValue("DNT"));
- Assert.Equal("client1, proxy1, proxy2", requestData.GetSingleHeaderValue("X-Forwarded-For"));
- Assert.Equal("en.wikipedia.org:8080", requestData.GetSingleHeaderValue("X-Forwarded-Host"));
- Assert.Equal("https", requestData.GetSingleHeaderValue("X-Forwarded-Proto"));
- Assert.Equal("https", requestData.GetSingleHeaderValue("Front-End-Https"));
- Assert.Equal("DELETE", requestData.GetSingleHeaderValue("X-Http-Method-Override"));
- Assert.Equal("GT-P7320/P7320XXLPG", requestData.GetSingleHeaderValue("X-ATT-DeviceId"));
- Assert.Equal("http://wap.samsungmobile.com/uaprof/SGH-I777.xml", requestData.GetSingleHeaderValue("X-Wap-Profile"));
- Assert.Equal("...", requestData.GetSingleHeaderValue("X-UIDH"));
- Assert.Equal("i8XNjC4b8KVok4uw5RftR38Wgp2BFwql", requestData.GetSingleHeaderValue("X-Csrf-Token"));
- Assert.Equal("f058ebd6-02f7-4d3f-942e-904344e8cde5, f058ebd6-02f7-4d3f-942e-904344e8cde5", requestData.GetSingleHeaderValue("X-Request-ID"));
- Assert.Equal("", requestData.GetSingleHeaderValue("X-Null"));
- Assert.Equal("", requestData.GetSingleHeaderValue("X-Empty"));
- Assert.Equal("X-Underscore_Name", requestData.GetSingleHeaderValue("X-Underscore_Name"));
- Assert.Equal("End", requestData.GetSingleHeaderValue("X-End"));
-
- if (LoopbackServerFactory.IsHttp2)
- {
- // HTTP/2 forbids certain headers or values.
- Assert.Equal("trailers", requestData.GetSingleHeaderValue("TE"));
- Assert.Equal(0, requestData.GetHeaderValueCount("Upgrade"));
- Assert.Equal(0, requestData.GetHeaderValueCount("Proxy-Connection"));
- Assert.Equal(0, requestData.GetHeaderValueCount("Host"));
- Assert.Equal(0, requestData.GetHeaderValueCount("Connection"));
- Assert.Equal(0, requestData.GetHeaderValueCount("Transfer-Encoding"));
- }
- else
- {
- // Verify HTTP/1.x headers
- Assert.Equal("close", requestData.GetSingleHeaderValue("Connection"), StringComparer.OrdinalIgnoreCase); // NetFxHandler uses "Close" vs "close"
- Assert.Equal("en.wikipedia.org:8080", requestData.GetSingleHeaderValue("Host"));
- Assert.Equal("trailers, deflate", requestData.GetSingleHeaderValue("TE"));
- Assert.Equal("HTTPS/1.3, IRC/6.9, RTA/x11, websocket", requestData.GetSingleHeaderValue("Upgrade"));
- Assert.Equal("keep-alive", requestData.GetSingleHeaderValue("Proxy-Connection"));
- }
- }
- });
- }
-
- public static IEnumerable<object[]> GetAsync_ManyDifferentResponseHeaders_ParsedCorrectly_MemberData() =>
- from newline in new[] { "\n", "\r\n" }
- from fold in new[] { "", newline + " ", newline + "\t", newline + " " }
- from dribble in new[] { false, true }
- select new object[] { newline, fold, dribble };
-
- [ConditionalTheory]
- [MemberData(nameof(GetAsync_ManyDifferentResponseHeaders_ParsedCorrectly_MemberData))]
- public async Task GetAsync_ManyDifferentResponseHeaders_ParsedCorrectly(string newline, string fold, bool dribble)
- {
- if (LoopbackServerFactory.IsHttp2)
- {
- throw new SkipTestException("Folding is not supported on HTTP/2.");
- }
-
- // Using examples from https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#Response_fields
- // Exercises all exposed response.Headers and response.Content.Headers strongly-typed properties
- await LoopbackServer.CreateClientAndServerAsync(async uri =>
- {
- using (HttpClient client = CreateHttpClient())
- using (HttpResponseMessage resp = await client.GetAsync(uri))
- {
- Assert.Equal("1.1", resp.Version.ToString());
- Assert.Equal(HttpStatusCode.OK, resp.StatusCode);
- Assert.Contains("*", resp.Headers.GetValues("Access-Control-Allow-Origin"));
- Assert.Contains("text/example;charset=utf-8", resp.Headers.GetValues("Accept-Patch"));
- Assert.Contains("bytes", resp.Headers.AcceptRanges);
- Assert.Equal(TimeSpan.FromSeconds(12), resp.Headers.Age.GetValueOrDefault());
- Assert.Contains("Bearer 63123a47139a49829bcd8d03005ca9d7", resp.Headers.GetValues("Authorization"));
- Assert.Contains("GET", resp.Content.Headers.Allow);
- Assert.Contains("HEAD", resp.Content.Headers.Allow);
- Assert.Contains("http/1.1=\"http2.example.com:8001\"; ma=7200", resp.Headers.GetValues("Alt-Svc"));
- Assert.Equal(TimeSpan.FromSeconds(3600), resp.Headers.CacheControl.MaxAge.GetValueOrDefault());
- Assert.Contains("close", resp.Headers.Connection);
- Assert.True(resp.Headers.ConnectionClose.GetValueOrDefault());
- Assert.Equal("attachment", resp.Content.Headers.ContentDisposition.DispositionType);
- Assert.Equal("\"fname.ext\"", resp.Content.Headers.ContentDisposition.FileName);
- Assert.Contains("gzip", resp.Content.Headers.ContentEncoding);
- Assert.Contains("da", resp.Content.Headers.ContentLanguage);
- Assert.Equal(new Uri("/index.htm", UriKind.Relative), resp.Content.Headers.ContentLocation);
- Assert.Equal(Convert.FromBase64String("Q2hlY2sgSW50ZWdyaXR5IQ=="), resp.Content.Headers.ContentMD5);
- Assert.Equal("bytes", resp.Content.Headers.ContentRange.Unit);
- Assert.Equal(21010, resp.Content.Headers.ContentRange.From.GetValueOrDefault());
- Assert.Equal(47021, resp.Content.Headers.ContentRange.To.GetValueOrDefault());
- Assert.Equal(47022, resp.Content.Headers.ContentRange.Length.GetValueOrDefault());
- Assert.Equal("text/html", resp.Content.Headers.ContentType.MediaType);
- Assert.Equal("utf-8", resp.Content.Headers.ContentType.CharSet);
- Assert.Equal(DateTimeOffset.Parse("Tue, 15 Nov 1994 08:12:31 GMT"), resp.Headers.Date.GetValueOrDefault());
- Assert.Equal("\"737060cd8c284d8af7ad3082f209582d\"", resp.Headers.ETag.Tag);
- Assert.Equal(DateTimeOffset.Parse("Thu, 01 Dec 1994 16:00:00 GMT"), resp.Content.Headers.Expires.GetValueOrDefault());
- Assert.Equal(DateTimeOffset.Parse("Tue, 15 Nov 1994 12:45:26 GMT"), resp.Content.Headers.LastModified.GetValueOrDefault());
- Assert.Contains("</feed>; rel=\"alternate\"", resp.Headers.GetValues("Link"));
- Assert.Equal(new Uri("http://www.w3.org/pub/WWW/People.html"), resp.Headers.Location);
- Assert.Contains("CP=\"This is not a P3P policy!\"", resp.Headers.GetValues("P3P"));
- Assert.Contains(new NameValueHeaderValue("no-cache"), resp.Headers.Pragma);
- Assert.Contains(new AuthenticationHeaderValue("basic"), resp.Headers.ProxyAuthenticate);
- Assert.Contains("max-age=2592000; pin-sha256=\"E9CZ9INDbd+2eRQozYqqbQ2yXLVKB9+xcprMF+44U1g=\"", resp.Headers.GetValues("Public-Key-Pins"));
- Assert.Equal(TimeSpan.FromSeconds(120), resp.Headers.RetryAfter.Delta.GetValueOrDefault());
- Assert.Contains(new ProductInfoHeaderValue("Apache", "2.4.1"), resp.Headers.Server);
- Assert.Contains("UserID=JohnDoe; Max-Age=3600; Version=1", resp.Headers.GetValues("Set-Cookie"));
- Assert.Contains("max-age=16070400; includeSubDomains", resp.Headers.GetValues("Strict-Transport-Security"));
- Assert.Contains("Max-Forwards", resp.Headers.Trailer);
- Assert.Contains("?", resp.Headers.GetValues("Tk"));
- Assert.Contains(new ProductHeaderValue("HTTPS", "1.3"), resp.Headers.Upgrade);
- Assert.Contains(new ProductHeaderValue("IRC", "6.9"), resp.Headers.Upgrade);
- Assert.Contains(new ProductHeaderValue("websocket"), resp.Headers.Upgrade);
- Assert.Contains("Accept-Language", resp.Headers.Vary);
- Assert.Contains(new ViaHeaderValue("1.0", "fred"), resp.Headers.Via);
- Assert.Contains(new ViaHeaderValue("1.1", "example.com", null, "(Apache/1.1)"), resp.Headers.Via);
- Assert.Contains(new WarningHeaderValue(199, "-", "\"Miscellaneous warning\"", DateTimeOffset.Parse("Wed, 21 Oct 2015 07:28:00 GMT")), resp.Headers.Warning);
- Assert.Contains(new AuthenticationHeaderValue("Basic"), resp.Headers.WwwAuthenticate);
- Assert.Contains("deny", resp.Headers.GetValues("X-Frame-Options"));
- Assert.Contains("default-src 'self'", resp.Headers.GetValues("X-WebKit-CSP"));
- Assert.Contains("5; url=http://www.w3.org/pub/WWW/People.html", resp.Headers.GetValues("Refresh"));
- Assert.Contains("200 OK", resp.Headers.GetValues("Status"));
- Assert.Contains("<origin>[, <origin>]*", resp.Headers.GetValues("Timing-Allow-Origin"));
- Assert.Contains("42.666", resp.Headers.GetValues("X-Content-Duration"));
- Assert.Contains("nosniff", resp.Headers.GetValues("X-Content-Type-Options"));
- Assert.Contains("PHP/5.4.0", resp.Headers.GetValues("X-Powered-By"));
- Assert.Contains("f058ebd6-02f7-4d3f-942e-904344e8cde5", resp.Headers.GetValues("X-Request-ID"));
- Assert.Contains("IE=EmulateIE7", resp.Headers.GetValues("X-UA-Compatible"));
- Assert.Contains("1; mode=block", resp.Headers.GetValues("X-XSS-Protection"));
- }
- }, server => server.AcceptConnectionSendCustomResponseAndCloseAsync(
- $"HTTP/1.1 200 OK{newline}" +
- $"Access-Control-Allow-Origin:{fold} *{newline}" +
- $"Accept-Patch:{fold} text/example;charset=utf-8{newline}" +
- $"Accept-Ranges:{fold} bytes{newline}" +
- $"Age: {fold}12{newline}" +
- $"Authorization: Bearer 63123a47139a49829bcd8d03005ca9d7{newline}" +
- $"Allow: {fold}GET, HEAD{newline}" +
- $"Alt-Svc:{fold} http/1.1=\"http2.example.com:8001\"; ma=7200{newline}" +
- $"Cache-Control: {fold}max-age=3600{newline}" +
- $"Connection:{fold} close{newline}" +
- $"Content-Disposition: {fold}attachment;{fold} filename=\"fname.ext\"{newline}" +
- $"Content-Encoding: {fold}gzip{newline}" +
- $"Content-Language:{fold} da{newline}" +
- $"Content-Location: {fold}/index.htm{newline}" +
- $"Content-MD5:{fold} Q2hlY2sgSW50ZWdyaXR5IQ=={newline}" +
- $"Content-Range: {fold}bytes {fold}21010-47021/47022{newline}" +
- $"Content-Type: text/html;{fold} charset=utf-8{newline}" +
- $"Date: Tue, 15 Nov 1994{fold} 08:12:31 GMT{newline}" +
- $"ETag: {fold}\"737060cd8c284d8af7ad3082f209582d\"{newline}" +
- $"Expires: Thu,{fold} 01 Dec 1994 16:00:00 GMT{newline}" +
- $"Last-Modified:{fold} Tue, 15 Nov 1994 12:45:26 GMT{newline}" +
- $"Link:{fold} </feed>; rel=\"alternate\"{newline}" +
- $"Location:{fold} http://www.w3.org/pub/WWW/People.html{newline}" +
- $"P3P: {fold}CP=\"This is not a P3P policy!\"{newline}" +
- $"Pragma: {fold}no-cache{newline}" +
- $"Proxy-Authenticate:{fold} Basic{newline}" +
- $"Public-Key-Pins:{fold} max-age=2592000; pin-sha256=\"E9CZ9INDbd+2eRQozYqqbQ2yXLVKB9+xcprMF+44U1g=\"{newline}" +
- $"Retry-After: {fold}120{newline}" +
- $"Server: {fold}Apache/2.4.1{fold} (Unix){newline}" +
- $"Set-Cookie: {fold}UserID=JohnDoe; Max-Age=3600; Version=1{newline}" +
- $"Strict-Transport-Security: {fold}max-age=16070400; includeSubDomains{newline}" +
- $"Trailer: {fold}Max-Forwards{newline}" +
- $"Tk: {fold}?{newline}" +
- $"Upgrade: HTTPS/1.3,{fold} IRC/6.9,{fold} RTA/x11, {fold}websocket{newline}" +
- $"Vary:{fold} Accept-Language{newline}" +
- $"Via:{fold} 1.0 fred, 1.1 example.com{fold} (Apache/1.1){newline}" +
- $"Warning:{fold} 199 - \"Miscellaneous warning\" \"Wed, 21 Oct 2015 07:28:00 GMT\"{newline}" +
- $"WWW-Authenticate: {fold}Basic{newline}" +
- $"X-Frame-Options: {fold}deny{newline}" +
- $"X-WebKit-CSP: default-src 'self'{newline}" +
- $"Refresh: {fold}5; url=http://www.w3.org/pub/WWW/People.html{newline}" +
- $"Status: {fold}200 OK{newline}" +
- $"Timing-Allow-Origin: {fold}<origin>[, <origin>]*{newline}" +
- $"Upgrade-Insecure-Requests:{fold} 1{newline}" +
- $"X-Content-Duration:{fold} 42.666{newline}" +
- $"X-Content-Type-Options: {fold}nosniff{newline}" +
- $"X-Powered-By: {fold}PHP/5.4.0{newline}" +
- $"X-Request-ID:{fold} f058ebd6-02f7-4d3f-942e-904344e8cde5{newline}" +
- $"X-UA-Compatible: {fold}IE=EmulateIE7{newline}" +
- $"X-XSS-Protection:{fold} 1; mode=block{newline}" +
- $"{newline}"),
- dribble ? new LoopbackServer.Options { StreamWrapper = s => new DribbleStream(s) } : null);
- }
-
- [ConditionalFact]
- public async Task GetAsync_NonTraditionalChunkSizes_Accepted()
- {
- if (LoopbackServerFactory.IsHttp2)
- {
- throw new SkipTestException("Chunking is not supported on HTTP/2.");
- }
- await LoopbackServer.CreateServerAsync(async (server, url) =>
- {
- using (HttpClient client = CreateHttpClient())
- {
- Task<HttpResponseMessage> getResponseTask = client.GetAsync(url);
- await TestHelper.WhenAllCompletedOrAnyFailed(
- getResponseTask,
- server.AcceptConnectionSendCustomResponseAndCloseAsync(
- "HTTP/1.1 200 OK\r\n" +
- "Connection: close\r\n" +
- "Transfer-Encoding: chunked\r\n" +
- "\r\n" +
- "4 \r\n" + // whitespace after size
- "data\r\n" +
- "5;somekey=somevalue\r\n" + // chunk extension
- "hello\r\n" +
- "7\t ;chunkextension\r\n" + // tabs/spaces then chunk extension
- "netcore\r\n" +
- "0\r\n" +
- "\r\n"));
-
- using (HttpResponseMessage response = await getResponseTask)
- {
- Assert.Equal(HttpStatusCode.OK, response.StatusCode);
- string data = await response.Content.ReadAsStringAsync();
- Assert.Contains("data", data);
- Assert.Contains("hello", data);
- Assert.Contains("netcore", data);
- Assert.DoesNotContain("somekey", data);
- Assert.DoesNotContain("somevalue", data);
- Assert.DoesNotContain("chunkextension", data);
- }
- }
- });
- }
-
- [Theory]
- [InlineData("")] // missing size
- [InlineData(" ")] // missing size
- [InlineData("10000000000000000")] // overflowing size
- [InlineData("xyz")] // non-hex
- [InlineData("7gibberish")] // valid size then gibberish
- [InlineData("7\v\f")] // unacceptable whitespace
- public async Task GetAsync_InvalidChunkSize_ThrowsHttpRequestException(string chunkSize)
- {
- await LoopbackServer.CreateServerAsync(async (server, url) =>
- {
- using (HttpClient client = CreateHttpClient())
- {
- string partialResponse = "HTTP/1.1 200 OK\r\n" +
- "Transfer-Encoding: chunked\r\n" +
- "\r\n" +
- $"{chunkSize}\r\n";
-
- var tcs = new TaskCompletionSource<bool>();
- Task serverTask = server.AcceptConnectionAsync(async connection =>
- {
- await connection.ReadRequestHeaderAndSendCustomResponseAsync(partialResponse);
- await tcs.Task;
- });
-
- await Assert.ThrowsAsync<HttpRequestException>(() => client.GetAsync(url));
- tcs.SetResult(true);
- await serverTask;
- }
- });
- }
-
- [Fact]
- public async Task GetAsync_InvalidChunkTerminator_ThrowsHttpRequestException()
- {
- await LoopbackServer.CreateClientAndServerAsync(async url =>
- {
- using (HttpClient client = CreateHttpClient())
- {
- await Assert.ThrowsAsync<HttpRequestException>(() => client.GetAsync(url));
- }
- }, server => server.AcceptConnectionSendCustomResponseAndCloseAsync(
- "HTTP/1.1 200 OK\r\n" +
- "Connection: close\r\n" +
- "Transfer-Encoding: chunked\r\n" +
- "\r\n" +
- "5\r\n" +
- "hello" + // missing \r\n terminator
- //"5\r\n" +
- //"world" + // missing \r\n terminator
- "0\r\n" +
- "\r\n"));
- }
-
- [Fact]
- public async Task GetAsync_InfiniteChunkSize_ThrowsHttpRequestException()
- {
- await LoopbackServer.CreateServerAsync(async (server, url) =>
- {
- using (HttpClient client = CreateHttpClient())
- {
- client.Timeout = Timeout.InfiniteTimeSpan;
-
- var cts = new CancellationTokenSource();
- var tcs = new TaskCompletionSource<bool>();
- Task serverTask = server.AcceptConnectionAsync(async connection =>
- {
- await connection.ReadRequestHeaderAndSendCustomResponseAsync("HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n");
- TextWriter writer = connection.Writer;
- try
- {
- while (!cts.IsCancellationRequested) // infinite to make sure implementation doesn't OOM
- {
- await writer.WriteAsync(new string(' ', 10000));
- await Task.Delay(1);
- }
- }
- catch { }
- });
-
- await Assert.ThrowsAsync<HttpRequestException>(() => client.GetAsync(url));
- cts.Cancel();
- await serverTask;
- }
- });
- }
-
- [Fact]
- public async Task SendAsync_TransferEncodingSetButNoRequestContent_Throws()
- {
- var req = new HttpRequestMessage(HttpMethod.Post, "http://bing.com") { Version = VersionFromUseHttp2 };
- req.Headers.TransferEncodingChunked = true;
- using (HttpClient c = CreateHttpClient())
- {
- HttpRequestException error = await Assert.ThrowsAsync<HttpRequestException>(() => c.SendAsync(req));
- Assert.IsType<InvalidOperationException>(error.InnerException);
- }
- }
-
- [OuterLoop("Uses external server")]
- [Theory, MemberData(nameof(RemoteServersMemberData))]
- public async Task GetAsync_ResponseHeadersRead_ReadFromEachIterativelyDoesntDeadlock(Configuration.Http.RemoteServer remoteServer)
- {
- using (HttpClient client = CreateHttpClientForRemoteServer(remoteServer))
- {
- const int NumGets = 5;
- Task<HttpResponseMessage>[] responseTasks = (from _ in Enumerable.Range(0, NumGets)
- select client.GetAsync(remoteServer.EchoUri, HttpCompletionOption.ResponseHeadersRead)).ToArray();
- for (int i = responseTasks.Length - 1; i >= 0; i--) // read backwards to increase likelihood that we wait on a different task than has data available
- {
- using (HttpResponseMessage response = await responseTasks[i])
- {
- string responseContent = await response.Content.ReadAsStringAsync();
- _output.WriteLine(responseContent);
- TestHelper.VerifyResponseBody(
- responseContent,
- response.Content.Headers.ContentMD5,
- false,
- null);
- }
- }
- }
- }
-
- [OuterLoop("Uses external server")]
- [Theory, MemberData(nameof(RemoteServersMemberData))]
- public async Task SendAsync_HttpRequestMsgResponseHeadersRead_StatusCodeOK(Configuration.Http.RemoteServer remoteServer)
- {
- HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, remoteServer.EchoUri) { Version = remoteServer.HttpVersion };
- using (HttpClient client = CreateHttpClientForRemoteServer(remoteServer))
- {
- using (HttpResponseMessage response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead))
- {
- string responseContent = await response.Content.ReadAsStringAsync();
- _output.WriteLine(responseContent);
- TestHelper.VerifyResponseBody(
- responseContent,
- response.Content.Headers.ContentMD5,
- false,
- null);
- }
- }
- }
-
- [OuterLoop("Slow response")]
- [Fact]
- public async Task SendAsync_ReadFromSlowStreamingServer_PartialDataReturned()
- {
- await LoopbackServer.CreateServerAsync(async (server, url) =>
- {
- using (HttpClient client = CreateHttpClient())
- {
- Task<HttpResponseMessage> getResponse = client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead);
-
- await server.AcceptConnectionAsync(async connection =>
- {
- await connection.ReadRequestHeaderAndSendCustomResponseAsync(
- "HTTP/1.1 200 OK\r\n" +
- $"Date: {DateTimeOffset.UtcNow:R}\r\n" +
- "Content-Length: 16000\r\n" +
- "\r\n" +
- "less than 16000 bytes");
-
- using (HttpResponseMessage response = await getResponse)
- {
- var buffer = new byte[8000];
- using (Stream clientStream = await response.Content.ReadAsStreamAsync())
- {
- int bytesRead = await clientStream.ReadAsync(buffer, 0, buffer.Length);
- _output.WriteLine($"Bytes read from stream: {bytesRead}");
- Assert.True(bytesRead < buffer.Length, "bytesRead should be less than buffer.Length");
- }
- }
- });
- }
- });
- }
-
- [ConditionalTheory]
- [InlineData(true)]
- [InlineData(false)]
- [InlineData(null)]
- public async Task ReadAsStreamAsync_HandlerProducesWellBehavedResponseStream(bool? chunked)
- {
- if (LoopbackServerFactory.IsHttp2 && chunked == true)
- {
- throw new SkipTestException("Chunking is not supported on HTTP/2.");
- }
-
- await LoopbackServerFactory.CreateClientAndServerAsync(async uri =>
- {
- var request = new HttpRequestMessage(HttpMethod.Get, uri) { Version = VersionFromUseHttp2 };
- using (var client = new HttpMessageInvoker(CreateHttpClientHandler()))
- using (HttpResponseMessage response = await client.SendAsync(request, CancellationToken.None))
- {
- using (Stream responseStream = await response.Content.ReadAsStreamAsync())
- {
- Assert.Same(responseStream, await response.Content.ReadAsStreamAsync());
-
- // Boolean properties returning correct values
- Assert.True(responseStream.CanRead);
- Assert.False(responseStream.CanWrite);
- Assert.False(responseStream.CanSeek);
-
- // Not supported operations
- Assert.Throws<NotSupportedException>(() => responseStream.BeginWrite(new byte[1], 0, 1, null, null));
- Assert.Throws<NotSupportedException>(() => responseStream.Length);
- Assert.Throws<NotSupportedException>(() => responseStream.Position);
- Assert.Throws<NotSupportedException>(() => responseStream.Position = 0);
- Assert.Throws<NotSupportedException>(() => responseStream.Seek(0, SeekOrigin.Begin));
- Assert.Throws<NotSupportedException>(() => responseStream.SetLength(0));
- Assert.Throws<NotSupportedException>(() => responseStream.Write(new byte[1], 0, 1));
- Assert.Throws<NotSupportedException>(() => responseStream.Write(new Span<byte>(new byte[1])));
- Assert.Throws<NotSupportedException>(() => { responseStream.WriteAsync(new Memory<byte>(new byte[1])); });
- Assert.Throws<NotSupportedException>(() => { responseStream.WriteAsync(new byte[1], 0, 1); });
- Assert.Throws<NotSupportedException>(() => responseStream.WriteByte(1));
-
- // Invalid arguments
- var nonWritableStream = new MemoryStream(new byte[1], false);
- var disposedStream = new MemoryStream();
- disposedStream.Dispose();
- Assert.Throws<ArgumentNullException>(() => responseStream.CopyTo(null));
- Assert.Throws<ArgumentOutOfRangeException>(() => responseStream.CopyTo(Stream.Null, 0));
- Assert.Throws<ArgumentNullException>(() => { responseStream.CopyToAsync(null, 100, default); });
- Assert.Throws<ArgumentOutOfRangeException>(() => { responseStream.CopyToAsync(Stream.Null, 0, default); });
- Assert.Throws<ArgumentOutOfRangeException>(() => { responseStream.CopyToAsync(Stream.Null, -1, default); });
- Assert.Throws<NotSupportedException>(() => { responseStream.CopyToAsync(nonWritableStream, 100, default); });
- Assert.Throws<ObjectDisposedException>(() => { responseStream.CopyToAsync(disposedStream, 100, default); });
- Assert.Throws<ArgumentNullException>(() => responseStream.Read(null, 0, 100));
- Assert.Throws<ArgumentOutOfRangeException>(() => responseStream.Read(new byte[1], -1, 1));
- Assert.ThrowsAny<ArgumentException>(() => responseStream.Read(new byte[1], 2, 1));
- Assert.Throws<ArgumentOutOfRangeException>(() => responseStream.Read(new byte[1], 0, -1));
- Assert.ThrowsAny<ArgumentException>(() => responseStream.Read(new byte[1], 0, 2));
- Assert.Throws<ArgumentNullException>(() => responseStream.BeginRead(null, 0, 100, null, null));
- Assert.Throws<ArgumentOutOfRangeException>(() => responseStream.BeginRead(new byte[1], -1, 1, null, null));
- Assert.ThrowsAny<ArgumentException>(() => responseStream.BeginRead(new byte[1], 2, 1, null, null));
- Assert.Throws<ArgumentOutOfRangeException>(() => responseStream.BeginRead(new byte[1], 0, -1, null, null));
- Assert.ThrowsAny<ArgumentException>(() => responseStream.BeginRead(new byte[1], 0, 2, null, null));
- Assert.Throws<ArgumentNullException>(() => responseStream.EndRead(null));
- Assert.Throws<ArgumentNullException>(() => { responseStream.ReadAsync(null, 0, 100, default); });
- Assert.Throws<ArgumentOutOfRangeException>(() => { responseStream.ReadAsync(new byte[1], -1, 1, default); });
- Assert.ThrowsAny<ArgumentException>(() => { responseStream.ReadAsync(new byte[1], 2, 1, default); });
- Assert.Throws<ArgumentOutOfRangeException>(() => { responseStream.ReadAsync(new byte[1], 0, -1, default); });
- Assert.ThrowsAny<ArgumentException>(() => { responseStream.ReadAsync(new byte[1], 0, 2, default); });
-
- // Various forms of reading
- var buffer = new byte[1];
-
- Assert.Equal('h', responseStream.ReadByte());
-
- Assert.Equal(1, await Task.Factory.FromAsync(responseStream.BeginRead, responseStream.EndRead, buffer, 0, 1, null));
- Assert.Equal((byte)'e', buffer[0]);
-
- Assert.Equal(1, await responseStream.ReadAsync(new Memory<byte>(buffer)));
- Assert.Equal((byte)'l', buffer[0]);
-
- Assert.Equal(1, await responseStream.ReadAsync(buffer, 0, 1));
- Assert.Equal((byte)'l', buffer[0]);
-
- Assert.Equal(1, responseStream.Read(new Span<byte>(buffer)));
- Assert.Equal((byte)'o', buffer[0]);
-
- Assert.Equal(1, responseStream.Read(buffer, 0, 1));
- Assert.Equal((byte)' ', buffer[0]);
-
- // 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));
- Assert.Equal(0, await responseStream.ReadAsync(Memory<byte>.Empty));
- Assert.Equal(0, await responseStream.ReadAsync(Array.Empty<byte>(), 0, 0));
- Assert.Equal(0, responseStream.Read(Span<byte>.Empty));
- Assert.Equal(0, responseStream.Read(Array.Empty<byte>(), 0, 0));
-
- // And copying
- var ms = new MemoryStream();
- await responseStream.CopyToAsync(ms);
- Assert.Equal("world", Encoding.ASCII.GetString(ms.ToArray()));
-
- // Read and copy again once we've exhausted all data
- ms = new MemoryStream();
- await responseStream.CopyToAsync(ms);
- responseStream.CopyTo(ms);
- Assert.Equal(0, ms.Length);
- Assert.Equal(-1, responseStream.ReadByte());
- Assert.Equal(0, responseStream.Read(buffer, 0, 1));
- Assert.Equal(0, responseStream.Read(new Span<byte>(buffer)));
- Assert.Equal(0, await responseStream.ReadAsync(buffer, 0, 1));
- Assert.Equal(0, await responseStream.ReadAsync(new Memory<byte>(buffer)));
- Assert.Equal(0, await Task.Factory.FromAsync(responseStream.BeginRead, responseStream.EndRead, buffer, 0, 1, null));
- }
- }
- }, async server =>
- {
- await server.AcceptConnectionAsync(async connection =>
- {
- await connection.ReadRequestDataAsync();
- switch (chunked)
- {
- case true:
- await connection.SendResponseAsync(HttpStatusCode.OK, headers: new HttpHeaderData[] { new HttpHeaderData("Transfer-Encoding", "chunked") }, isFinal: false);
- await connection.SendResponseBodyAsync("3\r\nhel\r\n8\r\nlo world\r\n0\r\n\r\n");
- break;
-
- case false:
- await connection.SendResponseAsync(HttpStatusCode.OK, headers: new HttpHeaderData[] { new HttpHeaderData("Content-Length", "11")}, content: "hello world");
- break;
-
- case null:
- // This inject Content-Length header with null value to hint Loopback code to not include one automatically.
- await connection.SendResponseAsync(HttpStatusCode.OK, headers: new HttpHeaderData[] { new HttpHeaderData("Content-Length", null)}, isFinal: false);
- await connection.SendResponseBodyAsync("hello world");
- break;
- }
- });
- });
- }
-
- [Fact]
- public async Task ReadAsStreamAsync_EmptyResponseBody_HandlerProducesWellBehavedResponseStream()
- {
- await LoopbackServerFactory.CreateClientAndServerAsync(async uri =>
- {
- using (var client = new HttpMessageInvoker(CreateHttpClientHandler()))
- {
- var request = new HttpRequestMessage(HttpMethod.Get, uri) { Version = VersionFromUseHttp2 };
-
- using (HttpResponseMessage response = await client.SendAsync(request, CancellationToken.None))
- using (Stream responseStream = await response.Content.ReadAsStreamAsync())
- {
- // Boolean properties returning correct values
- Assert.True(responseStream.CanRead);
- Assert.False(responseStream.CanWrite);
- Assert.False(responseStream.CanSeek);
-
- // Not supported operations
- Assert.Throws<NotSupportedException>(() => responseStream.BeginWrite(new byte[1], 0, 1, null, null));
- Assert.Throws<NotSupportedException>(() => responseStream.Length);
- Assert.Throws<NotSupportedException>(() => responseStream.Position);
- Assert.Throws<NotSupportedException>(() => responseStream.Position = 0);
- Assert.Throws<NotSupportedException>(() => responseStream.Seek(0, SeekOrigin.Begin));
- Assert.Throws<NotSupportedException>(() => responseStream.SetLength(0));
- Assert.Throws<NotSupportedException>(() => responseStream.Write(new byte[1], 0, 1));
- 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])));
- await Assert.ThrowsAsync<NotSupportedException>(async () => await responseStream.WriteAsync(new byte[1], 0, 1));
- Assert.Throws<NotSupportedException>(() => responseStream.WriteByte(1));
-
- // Invalid arguments
- var nonWritableStream = new MemoryStream(new byte[1], false);
- var disposedStream = new MemoryStream();
- disposedStream.Dispose();
- Assert.Throws<ArgumentNullException>(() => responseStream.CopyTo(null));
- Assert.Throws<ArgumentOutOfRangeException>(() => responseStream.CopyTo(Stream.Null, 0));
- Assert.Throws<ArgumentNullException>(() => { responseStream.CopyToAsync(null, 100, default); });
- Assert.Throws<ArgumentOutOfRangeException>(() => { responseStream.CopyToAsync(Stream.Null, 0, default); });
- Assert.Throws<ArgumentOutOfRangeException>(() => { responseStream.CopyToAsync(Stream.Null, -1, default); });
- Assert.Throws<NotSupportedException>(() => { responseStream.CopyToAsync(nonWritableStream, 100, default); });
- Assert.Throws<ObjectDisposedException>(() => { responseStream.CopyToAsync(disposedStream, 100, default); });
- Assert.Throws<ArgumentNullException>(() => responseStream.Read(null, 0, 100));
- Assert.Throws<ArgumentOutOfRangeException>(() => responseStream.Read(new byte[1], -1, 1));
- Assert.ThrowsAny<ArgumentException>(() => responseStream.Read(new byte[1], 2, 1));
- Assert.Throws<ArgumentOutOfRangeException>(() => responseStream.Read(new byte[1], 0, -1));
- Assert.ThrowsAny<ArgumentException>(() => responseStream.Read(new byte[1], 0, 2));
- Assert.Throws<ArgumentNullException>(() => responseStream.BeginRead(null, 0, 100, null, null));
- Assert.Throws<ArgumentOutOfRangeException>(() => responseStream.BeginRead(new byte[1], -1, 1, null, null));
- Assert.ThrowsAny<ArgumentException>(() => responseStream.BeginRead(new byte[1], 2, 1, null, null));
- Assert.Throws<ArgumentOutOfRangeException>(() => responseStream.BeginRead(new byte[1], 0, -1, null, null));
- Assert.ThrowsAny<ArgumentException>(() => responseStream.BeginRead(new byte[1], 0, 2, null, null));
- Assert.Throws<ArgumentNullException>(() => responseStream.EndRead(null));
- Assert.Throws<ArgumentNullException>(() => { responseStream.CopyTo(null); });
- Assert.Throws<ArgumentNullException>(() => { responseStream.CopyToAsync(null, 100, default); });
- Assert.Throws<ArgumentNullException>(() => { responseStream.CopyToAsync(null, 100, default); });
- Assert.Throws<ArgumentNullException>(() => { responseStream.Read(null, 0, 100); });
- Assert.Throws<ArgumentNullException>(() => { responseStream.ReadAsync(null, 0, 100, default); });
- Assert.Throws<ArgumentNullException>(() => { responseStream.BeginRead(null, 0, 100, null, null); });
-
- // Empty reads
- 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));
- Assert.Equal(0, await responseStream.ReadAsync(new Memory<byte>(buffer)));
- Assert.Equal(0, await responseStream.ReadAsync(buffer, 0, 1));
- Assert.Equal(0, responseStream.Read(new Span<byte>(buffer)));
- Assert.Equal(0, responseStream.Read(buffer, 0, 1));
-
- // Empty copies
- var ms = new MemoryStream();
- await responseStream.CopyToAsync(ms);
- Assert.Equal(0, ms.Length);
- responseStream.CopyTo(ms);
- Assert.Equal(0, ms.Length);
- }
- }
- },
- server => server.AcceptConnectionSendResponseAndCloseAsync());
- }
-
- [Fact]
- public async Task Dispose_DisposingHandlerCancelsActiveOperationsWithoutResponses()
- {
- await LoopbackServerFactory.CreateServerAsync(async (server1, url1) =>
- {
- await LoopbackServerFactory.CreateServerAsync(async (server2, url2) =>
- {
- await LoopbackServerFactory.CreateServerAsync(async (server3, url3) =>
- {
- var unblockServers = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
-
- // First server connects but doesn't send any response yet
- Task serverTask1 = server1.AcceptConnectionAsync(async connection1 =>
- {
- await unblockServers.Task;
- });
-
- // Second server connects and sends some but not all headers
- Task serverTask2 = server2.AcceptConnectionAsync(async connection2 =>
- {
- await connection2.ReadRequestDataAsync();
- await connection2.SendResponseAsync(HttpStatusCode.OK, content: null, isFinal : false);
- await unblockServers.Task;
- });
-
- // Third server connects and sends all headers and some but not all of the body
- Task serverTask3 = server3.AcceptConnectionAsync(async connection3 =>
- {
- await connection3.ReadRequestDataAsync();
- await connection3.SendResponseAsync(HttpStatusCode.OK, new HttpHeaderData[] { new HttpHeaderData("Content-Length", "20") }, isFinal : false);
- await connection3.SendResponseBodyAsync("1234567890", isFinal : false);
- await unblockServers.Task;
- await connection3.SendResponseBodyAsync("1234567890", isFinal : true);
- });
-
- // Make three requests
- Task<HttpResponseMessage> get1, get2;
- HttpResponseMessage response3;
- using (HttpClient client = CreateHttpClient())
- {
- get1 = client.GetAsync(url1, HttpCompletionOption.ResponseHeadersRead);
- get2 = client.GetAsync(url2, HttpCompletionOption.ResponseHeadersRead);
- response3 = await client.GetAsync(url3, HttpCompletionOption.ResponseHeadersRead);
- } // Dispose the handler while requests are still outstanding
-
- // Requests 1 and 2 should be canceled as we haven't finished receiving their headers
- await Assert.ThrowsAnyAsync<OperationCanceledException>(() => get1);
- await Assert.ThrowsAnyAsync<OperationCanceledException>(() => get2);
-
- // Request 3 should still be active, and we should be able to receive all of the data.
- unblockServers.SetResult(true);
- using (response3)
- {
- Assert.Equal("12345678901234567890", await response3.Content.ReadAsStringAsync());
- }
- });
- });
- });
- }
-
- [Theory]
- [InlineData(99)]
- [InlineData(1000)]
- public async Task GetAsync_StatusCodeOutOfRange_ExpectedException(int statusCode)
- {
- await LoopbackServer.CreateServerAsync(async (server, url) =>
- {
- using (HttpClient client = CreateHttpClient())
- {
- Task<HttpResponseMessage> getResponseTask = client.GetAsync(url);
- await server.AcceptConnectionSendCustomResponseAndCloseAsync(
- $"HTTP/1.1 {statusCode}\r\n" +
- $"Date: {DateTimeOffset.UtcNow:R}\r\n" +
- "Connection: close\r\n" +
- "\r\n");
-
- await Assert.ThrowsAsync<HttpRequestException>(() => getResponseTask);
- }
- });
- }
-
- [OuterLoop("Uses external server")]
- [Fact]
- public async Task GetAsync_UnicodeHostName_SuccessStatusCodeInResponse()
- {
- using (HttpClient client = CreateHttpClient())
- {
- // international version of the Starbucks website
- // punycode: xn--oy2b35ckwhba574atvuzkc.com
- string server = "http://\uc2a4\ud0c0\ubc85\uc2a4\ucf54\ub9ac\uc544.com";
- using (HttpResponseMessage response = await client.GetAsync(server))
- {
- response.EnsureSuccessStatusCode();
- }
- }
- }
-
-#region Post Methods Tests
-
- [OuterLoop("Uses external server")]
- [Theory, MemberData(nameof(RemoteServersMemberData))]
- public async Task PostAsync_CallMethodTwice_StringContent(Configuration.Http.RemoteServer remoteServer)
- {
- using (HttpClient client = CreateHttpClientForRemoteServer(remoteServer))
- {
- string data = "Test String";
- var content = new StringContent(data, Encoding.UTF8);
- content.Headers.ContentMD5 = TestHelper.ComputeMD5Hash(data);
- HttpResponseMessage response;
- using (response = await client.PostAsync(remoteServer.VerifyUploadUri, content))
- {
- Assert.Equal(HttpStatusCode.OK, response.StatusCode);
- }
-
- // Repeat call.
- content = new StringContent(data, Encoding.UTF8);
- content.Headers.ContentMD5 = TestHelper.ComputeMD5Hash(data);
- using (response = await client.PostAsync(remoteServer.VerifyUploadUri, content))
- {
- Assert.Equal(HttpStatusCode.OK, response.StatusCode);
- }
- }
- }
-
- [OuterLoop("Uses external server")]
- [Theory, MemberData(nameof(RemoteServersMemberData))]
- public async Task PostAsync_CallMethod_UnicodeStringContent(Configuration.Http.RemoteServer remoteServer)
- {
- using (HttpClient client = CreateHttpClientForRemoteServer(remoteServer))
- {
- string data = "\ub4f1\uffc7\u4e82\u67ab4\uc6d4\ud1a0\uc694\uc77c\uffda3\u3155\uc218\uffdb";
- var content = new StringContent(data, Encoding.UTF8);
- content.Headers.ContentMD5 = TestHelper.ComputeMD5Hash(data);
-
- using (HttpResponseMessage response = await client.PostAsync(remoteServer.VerifyUploadUri, content))
- {
- Assert.Equal(HttpStatusCode.OK, response.StatusCode);
- }
- }
- }
-
- [OuterLoop("Uses external server")]
- [Theory, MemberData(nameof(VerifyUploadServersStreamsAndExpectedData))]
- public async Task PostAsync_CallMethod_StreamContent(Configuration.Http.RemoteServer remoteServer, HttpContent content, byte[] expectedData)
- {
- using (HttpClient client = CreateHttpClientForRemoteServer(remoteServer))
- {
- content.Headers.ContentMD5 = TestHelper.ComputeMD5Hash(expectedData);
-
- using (HttpResponseMessage response = await client.PostAsync(remoteServer.VerifyUploadUri, content))
- {
- Assert.Equal(HttpStatusCode.OK, response.StatusCode);
- }
- }
- }
-
- private sealed class StreamContentWithSyncAsyncCopy : StreamContent
- {
- private readonly Stream _stream;
- private readonly bool _syncCopy;
-
- public StreamContentWithSyncAsyncCopy(Stream stream, bool syncCopy) : base(stream)
- {
- _stream = stream;
- _syncCopy = syncCopy;
- }
-
- protected override Task SerializeToStreamAsync(Stream stream, TransportContext context)
- {
- if (_syncCopy)
- {
- try
- {
- _stream.CopyTo(stream, 128); // arbitrary size likely to require multiple read/writes
- return Task.CompletedTask;
- }
- catch (Exception exc)
- {
- return Task.FromException(exc);
- }
- }
-
- return base.SerializeToStreamAsync(stream, context);
- }
- }
-
- public static IEnumerable<object[]> VerifyUploadServersStreamsAndExpectedData
- {
- get
- {
- foreach (Configuration.Http.RemoteServer remoteServer in Configuration.Http.RemoteServers) // target server
- foreach (bool syncCopy in new[] { true, false }) // force the content copy to happen via Read/Write or ReadAsync/WriteAsync
- {
- byte[] data = new byte[1234];
- new Random(42).NextBytes(data);
-
- // A MemoryStream
- {
- var memStream = new MemoryStream(data, writable: false);
- yield return new object[] { remoteServer, new StreamContentWithSyncAsyncCopy(memStream, syncCopy: syncCopy), data };
- }
-
- // A multipart content that provides its own stream from CreateContentReadStreamAsync
- {
- var mc = new MultipartContent();
- mc.Add(new ByteArrayContent(data));
- var memStream = new MemoryStream();
- mc.CopyToAsync(memStream).GetAwaiter().GetResult();
- yield return new object[] { remoteServer, mc, memStream.ToArray() };
- }
-
- // A stream that provides the data synchronously and has a known length
- {
- var wrappedMemStream = new MemoryStream(data, writable: false);
- var syncKnownLengthStream = new DelegateStream(
- canReadFunc: () => wrappedMemStream.CanRead,
- canSeekFunc: () => wrappedMemStream.CanSeek,
- lengthFunc: () => wrappedMemStream.Length,
- positionGetFunc: () => wrappedMemStream.Position,
- positionSetFunc: p => wrappedMemStream.Position = p,
- readFunc: (buffer, offset, count) => wrappedMemStream.Read(buffer, offset, count),
- readAsyncFunc: (buffer, offset, count, token) => wrappedMemStream.ReadAsync(buffer, offset, count, token));
- yield return new object[] { remoteServer, new StreamContentWithSyncAsyncCopy(syncKnownLengthStream, syncCopy: syncCopy), data };
- }
-
- // A stream that provides the data synchronously and has an unknown length
- {
- int syncUnknownLengthStreamOffset = 0;
-
- Func<byte[], int, int, int> readFunc = (buffer, offset, count) =>
- {
- int bytesRemaining = data.Length - syncUnknownLengthStreamOffset;
- int bytesToCopy = Math.Min(bytesRemaining, count);
- Array.Copy(data, syncUnknownLengthStreamOffset, buffer, offset, bytesToCopy);
- syncUnknownLengthStreamOffset += bytesToCopy;
- return bytesToCopy;
- };
-
- var syncUnknownLengthStream = new DelegateStream(
- canReadFunc: () => true,
- canSeekFunc: () => false,
- readFunc: readFunc,
- readAsyncFunc: (buffer, offset, count, token) => Task.FromResult(readFunc(buffer, offset, count)));
- yield return new object[] { remoteServer, new StreamContentWithSyncAsyncCopy(syncUnknownLengthStream, syncCopy: syncCopy), data };
- }
-
- // A stream that provides the data asynchronously
- {
- int asyncStreamOffset = 0, maxDataPerRead = 100;
-
- Func<byte[], int, int, int> readFunc = (buffer, offset, count) =>
- {
- int bytesRemaining = data.Length - asyncStreamOffset;
- int bytesToCopy = Math.Min(bytesRemaining, Math.Min(maxDataPerRead, count));
- Array.Copy(data, asyncStreamOffset, buffer, offset, bytesToCopy);
- asyncStreamOffset += bytesToCopy;
- return bytesToCopy;
- };
-
- var asyncStream = new DelegateStream(
- canReadFunc: () => true,
- canSeekFunc: () => false,
- readFunc: readFunc,
- readAsyncFunc: async (buffer, offset, count, token) =>
- {
- await Task.Delay(1).ConfigureAwait(false);
- return readFunc(buffer, offset, count);
- });
- yield return new object[] { remoteServer, new StreamContentWithSyncAsyncCopy(asyncStream, syncCopy: syncCopy), data };
- }
-
- // Providing data from a FormUrlEncodedContent's stream
- {
- var formContent = new FormUrlEncodedContent(new[] { new KeyValuePair<string, string>("key", "val") });
- yield return new object[] { remoteServer, formContent, Encoding.GetEncoding("iso-8859-1").GetBytes("key=val") };
- }
- }
- }
- }
-
- [OuterLoop("Uses external server")]
- [Theory, MemberData(nameof(RemoteServersMemberData))]
- public async Task PostAsync_CallMethod_NullContent(Configuration.Http.RemoteServer remoteServer)
- {
- using (HttpClient client = CreateHttpClientForRemoteServer(remoteServer))
- {
- using (HttpResponseMessage response = await client.PostAsync(remoteServer.EchoUri, null))
- {
- Assert.Equal(HttpStatusCode.OK, response.StatusCode);
-
- string responseContent = await response.Content.ReadAsStringAsync();
- _output.WriteLine(responseContent);
- TestHelper.VerifyResponseBody(
- responseContent,
- response.Content.Headers.ContentMD5,
- false,
- string.Empty);
- }
- }
- }
-
- [OuterLoop("Uses external server")]
- [Theory, MemberData(nameof(RemoteServersMemberData))]
- public async Task PostAsync_CallMethod_EmptyContent(Configuration.Http.RemoteServer remoteServer)
- {
- using (HttpClient client = CreateHttpClientForRemoteServer(remoteServer))
- {
- var content = new StringContent(string.Empty);
- using (HttpResponseMessage response = await client.PostAsync(remoteServer.EchoUri, content))
- {
- Assert.Equal(HttpStatusCode.OK, response.StatusCode);
-
- string responseContent = await response.Content.ReadAsStringAsync();
- _output.WriteLine(responseContent);
- TestHelper.VerifyResponseBody(
- responseContent,
- response.Content.Headers.ContentMD5,
- false,
- string.Empty);
- }
- }
- }
-
- [OuterLoop("Uses external server")]
- [Theory]
- [InlineData(false, "1.0")]
- [InlineData(true, "1.0")]
- [InlineData(null, "1.0")]
- [InlineData(false, "1.1")]
- [InlineData(true, "1.1")]
- [InlineData(null, "1.1")]
- [InlineData(false, "2.0")]
- [InlineData(true, "2.0")]
- [InlineData(null, "2.0")]
- public async Task PostAsync_ExpectContinue_Success(bool? expectContinue, string version)
- {
- using (HttpClient client = CreateHttpClient())
- {
- var req = new HttpRequestMessage(HttpMethod.Post, version == "2.0" ? Configuration.Http.Http2RemoteEchoServer : Configuration.Http.RemoteEchoServer)
- {
- Content = new StringContent("Test String", Encoding.UTF8),
- Version = new Version(version)
- };
- req.Headers.ExpectContinue = expectContinue;
-
- using (HttpResponseMessage response = await client.SendAsync(req))
- {
- Assert.Equal(HttpStatusCode.OK, response.StatusCode);
-
- const string ExpectedReqHeader = "\"Expect\": \"100-continue\"";
- if (expectContinue == true && (version == "1.1" || version == "2.0"))
- {
- Assert.Contains(ExpectedReqHeader, await response.Content.ReadAsStringAsync());
- }
- else
- {
- Assert.DoesNotContain(ExpectedReqHeader, await response.Content.ReadAsStringAsync());
- }
- }
- }
- }
-
- [Fact]
- public async Task GetAsync_ExpectContinueTrue_NoContent_StillSendsHeader()
- {
- const string ExpectedContent = "Hello, expecting and continuing world.";
- var clientCompleted = new TaskCompletionSource<bool>();
- await LoopbackServerFactory.CreateClientAndServerAsync(async uri =>
- {
- using (HttpClient client = CreateHttpClient())
- {
- client.DefaultRequestHeaders.ExpectContinue = true;
- Assert.Equal(ExpectedContent, await client.GetStringAsync(uri));
- clientCompleted.SetResult(true);
- }
- }, async server =>
- {
- await server.AcceptConnectionAsync(async connection =>
- {
- HttpRequestData requestData = await connection.ReadRequestDataAsync();
- Assert.Equal("100-continue", requestData.GetSingleHeaderValue("Expect"));
-
- await connection.SendResponseAsync(HttpStatusCode.Continue, isFinal: false);
- await connection.SendResponseAsync(HttpStatusCode.OK, new HttpHeaderData[] { new HttpHeaderData("Content-Length", ExpectedContent.Length.ToString()) }, isFinal: false);
- await connection.SendResponseBodyAsync(ExpectedContent);
- await clientCompleted.Task; // make sure server closing the connection isn't what let the client complete
- });
- });
- }
-
- public static IEnumerable<object[]> Interim1xxStatusCode()
- {
- yield return new object[] { (HttpStatusCode) 100 }; // 100 Continue.
- // 101 SwitchingProtocols will be treated as a final status code.
- yield return new object[] { (HttpStatusCode) 102 }; // 102 Processing.
- yield return new object[] { (HttpStatusCode) 103 }; // 103 EarlyHints.
- yield return new object[] { (HttpStatusCode) 150 };
- yield return new object[] { (HttpStatusCode) 180 };
- yield return new object[] { (HttpStatusCode) 199 };
- }
-
- [Theory]
- [MemberData(nameof(Interim1xxStatusCode))]
- public async Task SendAsync_1xxResponsesWithHeaders_InterimResponsesHeadersIgnored(HttpStatusCode responseStatusCode)
- {
- var clientFinished = new TaskCompletionSource<bool>();
- const string TestString = "test";
- const string CookieHeaderExpected = "yummy_cookie=choco";
- const string SetCookieExpected = "theme=light";
- const string ContentTypeHeaderExpected = "text/html";
-
- const string SetCookieIgnored1 = "hello=world";
- const string SetCookieIgnored2 = "net=core";
-
- await LoopbackServerFactory.CreateClientAndServerAsync(async uri =>
- {
- using (var handler = CreateHttpClientHandler())
- using (HttpClient client = CreateHttpClient(handler))
- {
- HttpRequestMessage initialMessage = new HttpRequestMessage(HttpMethod.Post, uri) { Version = VersionFromUseHttp2 };
- initialMessage.Content = new StringContent(TestString);
- initialMessage.Headers.ExpectContinue = true;
- HttpResponseMessage response = await client.SendAsync(initialMessage);
-
- // Verify status code.
- Assert.Equal(HttpStatusCode.OK, response.StatusCode);
- // Verify Cookie header.
- Assert.Equal(1, response.Headers.GetValues("Cookie").Count());
- Assert.Equal(CookieHeaderExpected, response.Headers.GetValues("Cookie").First().ToString());
- // Verify Set-Cookie header.
- Assert.Equal(1, handler.CookieContainer.Count);
- Assert.Equal(SetCookieExpected, handler.CookieContainer.GetCookieHeader(uri));
- // Verify Content-type header.
- Assert.Equal(ContentTypeHeaderExpected, response.Content.Headers.ContentType.ToString());
- clientFinished.SetResult(true);
- }
- }, async server =>
- {
- await server.AcceptConnectionAsync(async connection =>
- {
- // Send 100-Continue responses with additional headers.
- HttpRequestData requestData = await connection.ReadRequestDataAsync(readBody: true);
- await connection.SendResponseAsync(responseStatusCode, headers: new HttpHeaderData[] {
- new HttpHeaderData("Cookie", "ignore_cookie=choco1"),
- new HttpHeaderData("Content-type", "text/xml"),
- new HttpHeaderData("Set-Cookie", SetCookieIgnored1)}, isFinal: false);
-
- await connection.SendResponseAsync(responseStatusCode, headers: new HttpHeaderData[] {
- new HttpHeaderData("Cookie", "ignore_cookie=choco2"),
- new HttpHeaderData("Content-type", "text/plain"),
- new HttpHeaderData("Set-Cookie", SetCookieIgnored2)}, isFinal: false);
-
- Assert.Equal(TestString, Encoding.ASCII.GetString(requestData.Body));
-
- // Send final status code.
- await connection.SendResponseAsync(HttpStatusCode.OK, headers: new HttpHeaderData[] {
- new HttpHeaderData("Cookie", CookieHeaderExpected),
- new HttpHeaderData("Content-type", ContentTypeHeaderExpected),
- new HttpHeaderData("Set-Cookie", SetCookieExpected)});
-
- await clientFinished.Task;
- });
- });
- }
-
- [Theory]
- [MemberData(nameof(Interim1xxStatusCode))]
- public async Task SendAsync_Unexpected1xxResponses_DropAllInterimResponses(HttpStatusCode responseStatusCode)
- {
- var clientFinished = new TaskCompletionSource<bool>();
- const string TestString = "test";
-
- await LoopbackServerFactory.CreateClientAndServerAsync(async uri =>
- {
- using (HttpClient client = CreateHttpClient())
- {
- HttpRequestMessage initialMessage = new HttpRequestMessage(HttpMethod.Post, uri) { Version = VersionFromUseHttp2 };
- initialMessage.Content = new StringContent(TestString);
- // No ExpectContinue header.
- initialMessage.Headers.ExpectContinue = false;
- HttpResponseMessage response = await client.SendAsync(initialMessage);
-
- Assert.Equal(HttpStatusCode.OK, response.StatusCode);
- clientFinished.SetResult(true);
- }
- }, async server =>
- {
- await server.AcceptConnectionAsync(async connection =>
- {
- // Send unexpected 1xx responses.
- HttpRequestData requestData = await connection.ReadRequestDataAsync(readBody: false);
- await connection.SendResponseAsync(responseStatusCode, isFinal: false);
- await connection.SendResponseAsync(responseStatusCode, isFinal: false);
- await connection.SendResponseAsync(responseStatusCode, isFinal: false);
-
- byte[] body = await connection.ReadRequestBodyAsync();
- Assert.Equal(TestString, Encoding.ASCII.GetString(body));
-
- // Send final status code.
- await connection.SendResponseAsync(HttpStatusCode.OK);
- await clientFinished.Task;
- });
- });
- }
-
- [Fact]
- public async Task SendAsync_MultipleExpected100Responses_ReceivesCorrectResponse()
- {
- var clientFinished = new TaskCompletionSource<bool>();
- const string TestString = "test";
-
- await LoopbackServerFactory.CreateClientAndServerAsync(async uri =>
- {
- using (HttpClient client = CreateHttpClient())
- {
- HttpRequestMessage initialMessage = new HttpRequestMessage(HttpMethod.Post, uri) { Version = VersionFromUseHttp2 };
- initialMessage.Content = new StringContent(TestString);
- initialMessage.Headers.ExpectContinue = true;
- HttpResponseMessage response = await client.SendAsync(initialMessage);
-
- Assert.Equal(HttpStatusCode.OK, response.StatusCode);
- clientFinished.SetResult(true);
- }
- }, async server =>
- {
- await server.AcceptConnectionAsync(async connection =>
- {
- await connection.ReadRequestDataAsync(readBody: false);
- // Send multiple 100-Continue responses.
- for (int count = 0 ; count < 4; count++)
- {
- await connection.SendResponseAsync(HttpStatusCode.Continue, isFinal: false);
- }
-
- byte[] body = await connection.ReadRequestBodyAsync();
- Assert.Equal(TestString, Encoding.ASCII.GetString(body));
-
- // Send final status code.
- await connection.SendResponseAsync(HttpStatusCode.OK);
- await clientFinished.Task;
- });
- });
- }
-
- [Fact]
- public async Task SendAsync_No100ContinueReceived_RequestBodySentEventually()
- {
- var clientFinished = new TaskCompletionSource<bool>();
- const string RequestString = "request";
- const string ResponseString = "response";
-
- await LoopbackServerFactory.CreateClientAndServerAsync(async uri =>
- {
- using (HttpClient client = CreateHttpClient())
- {
- HttpRequestMessage initialMessage = new HttpRequestMessage(HttpMethod.Post, uri) { Version = VersionFromUseHttp2 };
- initialMessage.Content = new StringContent(RequestString);
- initialMessage.Headers.ExpectContinue = true;
- using (HttpResponseMessage response = await client.SendAsync(initialMessage))
- {
- Assert.Equal(HttpStatusCode.OK, response.StatusCode);
- Assert.Equal(ResponseString, await response.Content.ReadAsStringAsync());
- }
-
- clientFinished.SetResult(true);
- }
- }, async server =>
- {
- await server.AcceptConnectionAsync(async connection =>
- {
- await connection.ReadRequestDataAsync(readBody: false);
-
- await connection.SendResponseAsync(HttpStatusCode.OK, headers: new HttpHeaderData[] {new HttpHeaderData("Content-Length", $"{ResponseString.Length}")}, isFinal : false);
-
- byte[] body = await connection.ReadRequestBodyAsync();
- Assert.Equal(RequestString, Encoding.ASCII.GetString(body));
-
- await connection.SendResponseBodyAsync(ResponseString);
-
- await clientFinished.Task;
- });
- });
- }
-
- [ConditionalFact]
- public async Task SendAsync_101SwitchingProtocolsResponse_Success()
- {
- if (LoopbackServerFactory.IsHttp2)
- {
- throw new SkipTestException("Upgrade is not supported on HTTP/2");
- }
-
- var clientFinished = new TaskCompletionSource<bool>();
-
- await LoopbackServer.CreateClientAndServerAsync(async uri =>
- {
- using (HttpClient client = CreateHttpClient())
- {
- HttpResponseMessage response = await client.GetAsync(uri, HttpCompletionOption.ResponseHeadersRead);
- Assert.Equal(HttpStatusCode.SwitchingProtocols, response.StatusCode);
- clientFinished.SetResult(true);
- }
- }, async server =>
- {
- await server.AcceptConnectionAsync(async connection =>
- {
- // Send a valid 101 Switching Protocols response.
- await connection.ReadRequestHeaderAndSendCustomResponseAsync(
- "HTTP/1.1 101 Switching Protocols\r\n" + "Upgrade: websocket\r\n" + "Connection: Upgrade\r\n\r\n");
- await clientFinished.Task;
- });
- });
- }
-
- [Theory]
- [InlineData(false)]
- [InlineData(true)]
- public async Task PostAsync_ThrowFromContentCopy_RequestFails(bool syncFailure)
- {
- await LoopbackServer.CreateServerAsync(async (server, uri) =>
- {
- Task responseTask = server.AcceptConnectionAsync(async connection =>
- {
- var buffer = new byte[1000];
- while (await connection.Socket.ReceiveAsync(new ArraySegment<byte>(buffer, 0, buffer.Length), SocketFlags.None) != 0);
- });
-
- using (HttpClient client = CreateHttpClient())
- {
- Exception error = new FormatException();
- var content = new StreamContent(new DelegateStream(
- canSeekFunc: () => true,
- lengthFunc: () => 12345678,
- positionGetFunc: () => 0,
- canReadFunc: () => true,
- readFunc: (buffer, offset, count) => throw error,
- readAsyncFunc: (buffer, offset, count, cancellationToken) => syncFailure ? throw error : Task.Delay(1).ContinueWith<int>(_ => throw error)));
-
- Assert.Same(error, await Assert.ThrowsAsync<FormatException>(() => client.PostAsync(uri, content)));
- }
- });
- }
-
- [OuterLoop("Uses external server")]
- [Theory, MemberData(nameof(RemoteServersMemberData))]
- public async Task PostAsync_Redirect_ResultingGetFormattedCorrectly(Configuration.Http.RemoteServer remoteServer)
- {
- const string ContentString = "This is the content string.";
- var content = new StringContent(ContentString);
- Uri redirectUri = remoteServer.RedirectUriForDestinationUri(
- 302,
- remoteServer.EchoUri,
- 1);
-
- using (HttpClient client = CreateHttpClientForRemoteServer(remoteServer))
- using (HttpResponseMessage response = await client.PostAsync(redirectUri, content))
- {
- Assert.Equal(HttpStatusCode.OK, response.StatusCode);
- string responseContent = await response.Content.ReadAsStringAsync();
- Assert.DoesNotContain(ContentString, responseContent);
- Assert.DoesNotContain("Content-Length", responseContent);
- }
- }
-
- [OuterLoop("Takes several seconds")]
- [Theory, MemberData(nameof(RemoteServersMemberData))]
- public async Task PostAsync_RedirectWith307_LargePayload(Configuration.Http.RemoteServer remoteServer)
- {
- if (remoteServer.HttpVersion == new Version(2, 0))
- {
- // This is occasionally timing out in CI with SocketsHttpHandler and HTTP2, particularly on Linux
- // Likely this is just a very slow test and not a product issue, so just increasing the timeout may be the right fix.
- // Disable until we can investigate further.
- return;
- }
-
- await PostAsync_Redirect_LargePayload_Helper(remoteServer, 307, true);
- }
-
- [OuterLoop("Takes several seconds")]
- [Theory, MemberData(nameof(RemoteServersMemberData))]
- public async Task PostAsync_RedirectWith302_LargePayload(Configuration.Http.RemoteServer remoteServer)
- {
- await PostAsync_Redirect_LargePayload_Helper(remoteServer, 302, false);
- }
-
- public async Task PostAsync_Redirect_LargePayload_Helper(Configuration.Http.RemoteServer remoteServer, int statusCode, bool expectRedirectToPost)
- {
- using (var fs = new FileStream(
- Path.Combine(Path.GetTempPath(), Path.GetTempFileName()),
- FileMode.Create,
- FileAccess.ReadWrite,
- FileShare.None,
- 0x1000,
- FileOptions.DeleteOnClose))
- {
- string contentString = string.Join("", Enumerable.Repeat("Content", 100000));
- byte[] contentBytes = Encoding.UTF32.GetBytes(contentString);
- fs.Write(contentBytes, 0, contentBytes.Length);
- fs.Flush(flushToDisk: true);
- fs.Position = 0;
-
- Uri redirectUri = remoteServer.RedirectUriForDestinationUri(
- statusCode: statusCode,
- destinationUri: remoteServer.VerifyUploadUri,
- hops: 1);
- var content = new StreamContent(fs);
-
- // Compute MD5 of request body data. This will be verified by the server when it receives the request.
- content.Headers.ContentMD5 = TestHelper.ComputeMD5Hash(contentBytes);
-
- using (HttpClient client = CreateHttpClientForRemoteServer(remoteServer))
- using (HttpResponseMessage response = await client.PostAsync(redirectUri, content))
- {
- try
- {
- Assert.Equal(HttpStatusCode.OK, response.StatusCode);
- }
- catch
- {
- _output.WriteLine($"{(int)response.StatusCode} {response.ReasonPhrase}");
- throw;
- }
-
- if (expectRedirectToPost)
- {
- IEnumerable<string> headerValue = response.Headers.GetValues("X-HttpRequest-Method");
- Assert.Equal("POST", headerValue.First());
- }
- }
- }
- }
-
- [OuterLoop("Uses external server")]
- [Theory, MemberData(nameof(RemoteServersMemberData))]
- [ActiveIssue("https://github.com/dotnet/corefx/issues/31104", TestPlatforms.AnyUnix)]
- public async Task PostAsync_ReuseRequestContent_Success(Configuration.Http.RemoteServer remoteServer)
- {
- const string ContentString = "This is the content string.";
- using (HttpClient client = CreateHttpClientForRemoteServer(remoteServer))
- {
- var content = new StringContent(ContentString);
- for (int i = 0; i < 2; i++)
- {
- using (HttpResponseMessage response = await client.PostAsync(remoteServer.EchoUri, content))
- {
- Assert.Equal(HttpStatusCode.OK, response.StatusCode);
- Assert.Contains(ContentString, await response.Content.ReadAsStringAsync());
- }
- }
- }
- }
-
- [Theory]
- [InlineData(HttpStatusCode.MethodNotAllowed, "Custom description")]
- [InlineData(HttpStatusCode.MethodNotAllowed, "")]
- public async Task GetAsync_CallMethod_ExpectedStatusLine(HttpStatusCode statusCode, string reasonPhrase)
- {
- if (LoopbackServerFactory.IsHttp2)
- {
- // Custom messages are not supported on HTTP2.
- return;
- }
-
- await LoopbackServer.CreateClientAndServerAsync(async uri =>
- {
- using (HttpClient client = CreateHttpClient())
- using (HttpResponseMessage response = await client.GetAsync(uri))
- {
- Assert.Equal(statusCode, response.StatusCode);
- Assert.Equal(reasonPhrase, response.ReasonPhrase);
- }
- }, server => server.AcceptConnectionSendCustomResponseAndCloseAsync(
- $"HTTP/1.1 {(int)statusCode} {reasonPhrase}\r\nContent-Length: 0\r\n\r\n"));
- }
-
-#endregion
-
-#region Various HTTP Method Tests
-
- [OuterLoop("Uses external server")]
- [Theory, MemberData(nameof(HttpMethods))]
- public async Task SendAsync_SendRequestUsingMethodToEchoServerWithNoContent_MethodCorrectlySent(
- string method,
- Uri serverUri)
- {
- using (HttpClient client = CreateHttpClient())
- {
- var request = new HttpRequestMessage(
- new HttpMethod(method),
- serverUri) { Version = VersionFromUseHttp2 };
-
- using (HttpResponseMessage response = await client.SendAsync(request))
- {
- Assert.Equal(HttpStatusCode.OK, response.StatusCode);
- TestHelper.VerifyRequestMethod(response, method);
- }
- }
- }
-
- [OuterLoop("Uses external server")]
- [Theory, MemberData(nameof(HttpMethodsThatAllowContent))]
- public async Task SendAsync_SendRequestUsingMethodToEchoServerWithContent_Success(
- string method,
- Uri serverUri)
- {
- using (HttpClient client = CreateHttpClient())
- {
- var request = new HttpRequestMessage(
- new HttpMethod(method),
- serverUri) { Version = VersionFromUseHttp2 };
- request.Content = new StringContent(ExpectedContent);
- using (HttpResponseMessage response = await client.SendAsync(request))
- {
- Assert.Equal(HttpStatusCode.OK, response.StatusCode);
- TestHelper.VerifyRequestMethod(response, method);
- string responseContent = await response.Content.ReadAsStringAsync();
- _output.WriteLine(responseContent);
-
- Assert.Contains($"\"Content-Length\": \"{request.Content.Headers.ContentLength.Value}\"", responseContent);
- TestHelper.VerifyResponseBody(
- responseContent,
- response.Content.Headers.ContentMD5,
- false,
- ExpectedContent);
- }
- }
- }
-
- [OuterLoop("Uses external server")]
- [Theory]
- [InlineData("12345678910", 0)]
- [InlineData("12345678910", 5)]
- public async Task SendAsync_SendSameRequestMultipleTimesDirectlyOnHandler_Success(string stringContent, int startingPosition)
- {
- using (var handler = new HttpMessageInvoker(CreateHttpClientHandler()))
- {
- byte[] byteContent = Encoding.ASCII.GetBytes(stringContent);
- var content = new MemoryStream();
- content.Write(byteContent, 0, byteContent.Length);
- content.Position = startingPosition;
- var request = new HttpRequestMessage(HttpMethod.Post, Configuration.Http.RemoteEchoServer) { Content = new StreamContent(content), Version = VersionFromUseHttp2 };
-
- for (int iter = 0; iter < 2; iter++)
- {
- using (HttpResponseMessage response = await handler.SendAsync(request, CancellationToken.None))
- {
- Assert.Equal(HttpStatusCode.OK, response.StatusCode);
-
- string responseContent = await response.Content.ReadAsStringAsync();
-
- Assert.Contains($"\"Content-Length\": \"{request.Content.Headers.ContentLength.Value}\"", responseContent);
-
- Assert.Contains(stringContent.Substring(startingPosition), responseContent);
- if (startingPosition != 0)
- {
- Assert.DoesNotContain(stringContent.Substring(0, startingPosition), responseContent);
- }
- }
- }
- }
- }
-
- [OuterLoop("Uses external server")]
- [Theory, MemberData(nameof(HttpMethodsThatDontAllowContent))]
- public async Task SendAsync_SendRequestUsingNoBodyMethodToEchoServerWithContent_NoBodySent(
- string method,
- Uri serverUri)
- {
- using (HttpClient client = CreateHttpClient())
- {
- var request = new HttpRequestMessage(
- new HttpMethod(method),
- serverUri)
- {
- Content = new StringContent(ExpectedContent),
- Version = VersionFromUseHttp2
- };
-
- using (HttpResponseMessage response = await client.SendAsync(request))
- {
- if (method == "TRACE")
- {
- // .NET Framework also allows the HttpWebRequest and HttpClient APIs to send a request using 'TRACE'
- // verb and a request body. The usual response from a server is "400 Bad Request".
- // See here for more info: https://github.com/dotnet/corefx/issues/9023
- Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
- }
- else
- {
- Assert.Equal(HttpStatusCode.OK, response.StatusCode);
- TestHelper.VerifyRequestMethod(response, method);
- string responseContent = await response.Content.ReadAsStringAsync();
- Assert.DoesNotContain(ExpectedContent, responseContent);
- }
- }
- }
- }
-#endregion
-
-#region Version tests
- [OuterLoop("Uses external server")]
- [Fact]
- public async Task SendAsync_RequestVersion10_ServerReceivesVersion10Request()
- {
- Version receivedRequestVersion = await SendRequestAndGetRequestVersionAsync(new Version(1, 0));
- Assert.Equal(new Version(1, 0), receivedRequestVersion);
- }
-
- [OuterLoop("Uses external server")]
- [Fact]
- public async Task SendAsync_RequestVersion11_ServerReceivesVersion11Request()
- {
- Version receivedRequestVersion = await SendRequestAndGetRequestVersionAsync(new Version(1, 1));
- Assert.Equal(new Version(1, 1), receivedRequestVersion);
- }
-
- [OuterLoop("Uses external server")]
- [Fact]
- [ActiveIssue("https://github.com/dotnet/runtime/issues/339")]
- public async Task SendAsync_RequestVersionNotSpecified_ServerReceivesVersion11Request()
- {
- // The default value for HttpRequestMessage.Version is Version(1,1).
- // So, we need to set something different (0,0), to test the "unknown" version.
- Version receivedRequestVersion = await SendRequestAndGetRequestVersionAsync(new Version(0, 0));
- Assert.Equal(new Version(1, 1), receivedRequestVersion);
- }
-
- [OuterLoop("Uses external server")]
- [ConditionalTheory]
- [MemberData(nameof(Http2Servers))]
- public async Task SendAsync_RequestVersion20_ResponseVersion20IfHttp2Supported(Uri server)
- {
- // We don't currently have a good way to test whether HTTP/2 is supported without
- // using the same mechanism we're trying to test, so for now we allow both 2.0 and 1.1 responses.
- var request = new HttpRequestMessage(HttpMethod.Get, server);
- request.Version = new Version(2, 0);
-
- using (HttpClient client = CreateHttpClient())
- {
- // It is generally expected that the test hosts will be trusted, so we don't register a validation
- // callback in the usual case.
-
- using (HttpResponseMessage response = await client.SendAsync(request))
- {
- Assert.Equal(HttpStatusCode.OK, response.StatusCode);
- Assert.True(
- response.Version == new Version(2, 0) ||
- response.Version == new Version(1, 1),
- "Response version " + response.Version);
- }
- }
- }
-
- [Fact]
- public async Task SendAsync_RequestVersion20_HttpNotHttps_NoUpgradeRequest()
- {
- await LoopbackServerFactory.CreateClientAndServerAsync(async uri =>
- {
- using (HttpClient client = CreateHttpClient())
- {
- (await client.SendAsync(new HttpRequestMessage(HttpMethod.Get, uri) { Version = new Version(2, 0) })).Dispose();
- }
- }, async server =>
- {
- HttpRequestData requestData = await server.AcceptConnectionSendResponseAndCloseAsync();
- Assert.Equal(0, requestData.GetHeaderValues("Upgrade").Length);
- });
- }
-
- [OuterLoop("Uses external server")]
- [ConditionalTheory(nameof(IsWindows10Version1607OrGreater)), MemberData(nameof(Http2NoPushServers))]
- public async Task SendAsync_RequestVersion20_ResponseVersion20(Uri server)
- {
- _output.WriteLine(server.AbsoluteUri.ToString());
- var request = new HttpRequestMessage(HttpMethod.Get, server);
- request.Version = new Version(2, 0);
-
- using (HttpClient client = CreateHttpClient())
- {
- using (HttpResponseMessage response = await client.SendAsync(request))
- {
- Assert.Equal(HttpStatusCode.OK, response.StatusCode);
- Assert.Equal(new Version(2, 0), response.Version);
- }
- }
- }
-
- private async Task<Version> SendRequestAndGetRequestVersionAsync(Version requestVersion)
- {
- Version receivedRequestVersion = null;
-
- await LoopbackServer.CreateServerAsync(async (server, url) =>
- {
- var request = new HttpRequestMessage(HttpMethod.Get, url);
- request.Version = requestVersion;
-
- using (HttpClient client = CreateHttpClient())
- {
- Task<HttpResponseMessage> getResponse = client.SendAsync(request);
- Task<List<string>> serverTask = server.AcceptConnectionSendResponseAndCloseAsync();
- await TestHelper.WhenAllCompletedOrAnyFailed(getResponse, serverTask);
-
- List<string> receivedRequest = await serverTask;
- using (HttpResponseMessage response = await getResponse)
- {
- Assert.Equal(HttpStatusCode.OK, response.StatusCode);
- }
-
- string statusLine = receivedRequest[0];
- if (statusLine.Contains("/1.0"))
- {
- receivedRequestVersion = new Version(1, 0);
- }
- else if (statusLine.Contains("/1.1"))
- {
- receivedRequestVersion = new Version(1, 1);
- }
- else
- {
- Assert.True(false, "Invalid HTTP request version");
- }
-
- }
- });
-
- return receivedRequestVersion;
- }
-#endregion
-
-#region Uri wire transmission encoding tests
- [Fact]
- public async Task SendRequest_UriPathHasReservedChars_ServerReceivedExpectedPath()
- {
- await LoopbackServerFactory.CreateServerAsync(async (server, rootUrl) =>
- {
- var uri = new Uri($"{rootUrl.Scheme}://{rootUrl.Host}:{rootUrl.Port}/test[]");
- _output.WriteLine(uri.AbsoluteUri.ToString());
-
- using (HttpClient client = CreateHttpClient())
- {
- Task<HttpResponseMessage> getResponseTask = client.GetAsync(uri);
- Task<HttpRequestData> serverTask = server.AcceptConnectionSendResponseAndCloseAsync();
-
- await TestHelper.WhenAllCompletedOrAnyFailed(getResponseTask, serverTask);
- HttpRequestData receivedRequest = await serverTask;
- using (HttpResponseMessage response = await getResponseTask)
- {
- Assert.Equal(HttpStatusCode.OK, response.StatusCode);
- Assert.Equal(uri.PathAndQuery, receivedRequest.Path);
- }
- }
- });
- }
-#endregion
-
- [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWindowsSubsystemForLinux))] // [ActiveIssue("https://github.com/dotnet/corefx/issues/11057")]
- public async Task GetAsync_InvalidUrl_ExpectedExceptionThrown()
- {
- string invalidUri = $"http://{Configuration.Sockets.InvalidHost}";
- _output.WriteLine($"{DateTime.Now} connecting to {invalidUri}");
- using (HttpClient client = CreateHttpClient())
- {
- await Assert.ThrowsAsync<HttpRequestException>(() => client.GetAsync(invalidUri));
- await Assert.ThrowsAsync<HttpRequestException>(() => client.GetStringAsync(invalidUri));
- }
- }
- }
-}
--- /dev/null
+// 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.Reflection;
+
+namespace System.Net.Http.Functional.Tests
+{
+ public abstract partial class HttpClientHandlerTestBase : FileCleanupTestBase
+ {
+ protected static bool IsWinHttpHandler => false;
+
+ protected HttpClientHandler CreateHttpClientHandler() => CreateHttpClientHandler(UseHttp2);
+
+ protected static HttpClientHandler CreateHttpClientHandler(string useHttp2LoopbackServerString) =>
+ CreateHttpClientHandler(bool.Parse(useHttp2LoopbackServerString));
+
+ protected static HttpClientHandler CreateHttpClientHandler(bool useHttp2LoopbackServer = false)
+ {
+ HttpClientHandler handler = new HttpClientHandler();
+
+ if (useHttp2LoopbackServer)
+ {
+ TestHelper.EnableUnencryptedHttp2IfNecessary(handler);
+ handler.ServerCertificateCustomValidationCallback = TestHelper.AllowAllCertificates;
+ }
+
+ return handler;
+ }
+
+ protected static object GetUnderlyingSocketsHttpHandler(HttpClientHandler handler)
+ {
+ FieldInfo field = typeof(HttpClientHandler).GetField("_socketsHttpHandler", BindingFlags.Instance | BindingFlags.NonPublic);
+ return field?.GetValue(handler);
+ }
+ }
+}
+++ /dev/null
-// 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.Collections.Generic;
-using System.Diagnostics;
-using System.IO;
-using System.Net.Test.Common;
-using System.Reflection;
-using System.Threading;
-using System.Threading.Tasks;
-using Microsoft.DotNet.PlatformAbstractions;
-using Xunit.Abstractions;
-
-namespace System.Net.Http.Functional.Tests
-{
- using Configuration = System.Net.Test.Common.Configuration;
-
- public abstract class HttpClientHandlerTestBase : FileCleanupTestBase
- {
- public readonly ITestOutputHelper _output;
-
- protected virtual bool UseHttp2 => false;
-
- public HttpClientHandlerTestBase(ITestOutputHelper output)
- {
- _output = output;
- }
-
- protected Version VersionFromUseHttp2 => GetVersion(UseHttp2);
-
- protected static Version GetVersion(bool http2) => http2 ? new Version(2, 0) : HttpVersion.Version11;
-
- protected virtual HttpClient CreateHttpClient() => CreateHttpClient(CreateHttpClientHandler());
-
- protected HttpClient CreateHttpClient(HttpMessageHandler handler) =>
- new HttpClient(handler) { DefaultRequestVersion = VersionFromUseHttp2 };
-
- protected static HttpClient CreateHttpClient(string useHttp2String) =>
- CreateHttpClient(CreateHttpClientHandler(useHttp2String), useHttp2String);
-
- protected static HttpClient CreateHttpClient(HttpMessageHandler handler, string useHttp2String) =>
- new HttpClient(handler) { DefaultRequestVersion = GetVersion(bool.Parse(useHttp2String)) };
-
- protected HttpClientHandler CreateHttpClientHandler() => CreateHttpClientHandler(UseHttp2);
-
- protected static HttpClientHandler CreateHttpClientHandler(string useHttp2LoopbackServerString) =>
- CreateHttpClientHandler(bool.Parse(useHttp2LoopbackServerString));
-
- protected static HttpClientHandler CreateHttpClientHandler(bool useHttp2LoopbackServer = false)
- {
- HttpClientHandler handler = new HttpClientHandler();
-
- if (useHttp2LoopbackServer)
- {
- TestHelper.EnableUnencryptedHttp2IfNecessary(handler);
- handler.ServerCertificateCustomValidationCallback = TestHelper.AllowAllCertificates;
- }
-
- return handler;
- }
-
- protected static object GetUnderlyingSocketsHttpHandler(HttpClientHandler handler)
- {
- FieldInfo field = typeof(HttpClientHandler).GetField("_socketsHttpHandler", BindingFlags.Instance | BindingFlags.NonPublic);
- return field?.GetValue(handler);
- }
-
- protected LoopbackServerFactory LoopbackServerFactory =>
-#if NETCOREAPP
- UseHttp2 ?
- (LoopbackServerFactory)Http2LoopbackServerFactory.Singleton :
-#endif
- Http11LoopbackServerFactory.Singleton;
-
- // For use by remote server tests
-
- public static readonly IEnumerable<object[]> RemoteServersMemberData = Configuration.Http.RemoteServersMemberData;
-
- protected HttpClient CreateHttpClientForRemoteServer(Configuration.Http.RemoteServer remoteServer)
- {
- return CreateHttpClientForRemoteServer(remoteServer, CreateHttpClientHandler());
- }
-
- protected HttpClient CreateHttpClientForRemoteServer(Configuration.Http.RemoteServer remoteServer, HttpClientHandler httpClientHandler)
- {
- HttpMessageHandler wrappedHandler = httpClientHandler;
-
- // WinHttpHandler will downgrade to 1.1 if you set Transfer-Encoding: chunked.
- // So, skip this verification if we're not using SocketsHttpHandler.
- if (PlatformDetection.SupportsAlpn)
- {
- wrappedHandler = new VersionCheckerHttpHandler(httpClientHandler, remoteServer.HttpVersion);
- }
-
- return new HttpClient(wrappedHandler) { DefaultRequestVersion = remoteServer.HttpVersion };
- }
-
- private sealed class VersionCheckerHttpHandler : DelegatingHandler
- {
- private readonly Version _expectedVersion;
-
- public VersionCheckerHttpHandler(HttpMessageHandler innerHandler, Version expectedVersion)
- : base(innerHandler)
- {
- _expectedVersion = expectedVersion;
- }
-
- protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
- {
- if (request.Version != _expectedVersion)
- {
- throw new Exception($"Unexpected request version: expected {_expectedVersion}, saw {request.Version}");
- }
-
- HttpResponseMessage response = await base.SendAsync(request, cancellationToken);
-
- if (response.Version != _expectedVersion)
- {
- throw new Exception($"Unexpected response version: expected {_expectedVersion}, saw {response.Version}");
- }
-
- return response;
- }
- }
- }
-}
+++ /dev/null
-// 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.Collections.Generic;
-using System.IO;
-using System.Net.Test.Common;
-using System.Threading;
-using System.Threading.Tasks;
-using Microsoft.DotNet.XUnitExtensions;
-using Xunit;
-using Xunit.Abstractions;
-
-namespace System.Net.Http.Functional.Tests
-{
- public abstract class HttpProtocolTests : HttpClientHandlerTestBase
- {
- protected virtual Stream GetStream(Stream s) => s;
- protected virtual Stream GetStream_ClientDisconnectOk(Stream s) => s;
-
- public HttpProtocolTests(ITestOutputHelper output) : base(output) { }
-
- [Fact]
- public async Task GetAsync_RequestVersion10_Success()
- {
- await LoopbackServer.CreateServerAsync(async (server, url) =>
- {
- using (HttpClient client = CreateHttpClient())
- {
- HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, url);
- request.Version = HttpVersion.Version10;
-
- Task<HttpResponseMessage> getResponseTask = client.SendAsync(request);
- Task<List<string>> serverTask = server.AcceptConnectionSendResponseAndCloseAsync();
-
- await TestHelper.WhenAllCompletedOrAnyFailed(getResponseTask, serverTask);
-
- var requestLines = await serverTask;
- Assert.Equal($"GET {url.PathAndQuery} HTTP/1.0", requestLines[0]);
- }
- }, new LoopbackServer.Options { StreamWrapper = GetStream });
- }
-
- [Fact]
- public async Task GetAsync_RequestVersion11_Success()
- {
- await LoopbackServer.CreateServerAsync(async (server, url) =>
- {
- using (HttpClient client = CreateHttpClient())
- {
- HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, url);
- request.Version = HttpVersion.Version11;
-
- Task<HttpResponseMessage> getResponseTask = client.SendAsync(request);
- Task<List<string>> serverTask = server.AcceptConnectionSendResponseAndCloseAsync();
-
- await TestHelper.WhenAllCompletedOrAnyFailed(getResponseTask, serverTask);
-
- var requestLines = await serverTask;
- Assert.Equal($"GET {url.PathAndQuery} HTTP/1.1", requestLines[0]);
- }
- }, new LoopbackServer.Options { StreamWrapper = GetStream });
- }
-
- [Theory]
- [InlineData(0)]
- [InlineData(1)]
- [InlineData(9)]
- public async Task GetAsync_RequestVersion0X_ThrowsOr11(int minorVersion)
- {
- await LoopbackServer.CreateServerAsync(async (server, url) =>
- {
- using (HttpClient client = CreateHttpClient())
- {
- HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, url);
- request.Version = new Version(0, minorVersion);
-
- Task<HttpResponseMessage> getResponseTask = client.SendAsync(request);
- Task<List<string>> serverTask = server.AcceptConnectionSendResponseAndCloseAsync();
- await Assert.ThrowsAsync<NotSupportedException>(() => TestHelper.WhenAllCompletedOrAnyFailed(getResponseTask, serverTask));
- }
- }, new LoopbackServer.Options { StreamWrapper = GetStream_ClientDisconnectOk});
- }
-
- [Theory]
- [InlineData(1, 2)]
- [InlineData(1, 6)]
- [InlineData(2, 0)] // Note, this is plain HTTP (not HTTPS), so 2.0 is not supported and should degrade to 1.1
- [InlineData(2, 1)]
- [InlineData(2, 7)]
- [InlineData(3, 0)]
- [InlineData(4, 2)]
- public async Task GetAsync_UnknownRequestVersion_ThrowsOrDegradesTo11(int majorVersion, int minorVersion)
- {
- Type exceptionType = null;
-
- await LoopbackServer.CreateServerAsync(async (server, url) =>
- {
- using (HttpClient client = CreateHttpClient())
- {
- HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, url);
- request.Version = new Version(majorVersion, minorVersion);
-
- Task<HttpResponseMessage> getResponseTask = client.SendAsync(request);
- Task<List<string>> serverTask = server.AcceptConnectionSendResponseAndCloseAsync();
-
- if (exceptionType == null)
- {
- await TestHelper.WhenAllCompletedOrAnyFailed(getResponseTask, serverTask);
- var requestLines = await serverTask;
- Assert.Equal($"GET {url.PathAndQuery} HTTP/1.1", requestLines[0]);
- }
- else
- {
- await Assert.ThrowsAsync(exceptionType, (() => TestHelper.WhenAllCompletedOrAnyFailed(getResponseTask, serverTask)));
- }
- }
- }, new LoopbackServer.Options { StreamWrapper = GetStream_ClientDisconnectOk });
- }
-
- [Theory]
- [InlineData(0)]
- [InlineData(1)]
- public async Task GetAsync_ResponseVersion10or11_Success(int responseMinorVersion)
- {
- await LoopbackServer.CreateServerAsync(async (server, url) =>
- {
- using (HttpClient client = CreateHttpClient())
- {
- HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, url);
- request.Version = HttpVersion.Version11;
-
- Task<HttpResponseMessage> getResponseTask = client.SendAsync(request);
- Task<List<string>> serverTask =
- server.AcceptConnectionSendCustomResponseAndCloseAsync(
- $"HTTP/1.{responseMinorVersion} 200 OK\r\nConnection: close\r\nDate: {DateTimeOffset.UtcNow:R}\r\nContent-Length: 0\r\n\r\n");
-
- await TestHelper.WhenAllCompletedOrAnyFailed(getResponseTask, serverTask);
-
- using (HttpResponseMessage response = await getResponseTask)
- {
- Assert.Equal(1, response.Version.Major);
- Assert.Equal(responseMinorVersion, response.Version.Minor);
- }
- }
- }, new LoopbackServer.Options { StreamWrapper = GetStream });
- }
-
- [Theory]
- [InlineData(2)]
- [InlineData(7)]
- public async Task GetAsync_ResponseUnknownVersion1X_Success(int responseMinorVersion)
- {
- await LoopbackServer.CreateServerAsync(async (server, url) =>
- {
- using (HttpClient client = CreateHttpClient())
- {
- HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, url);
- request.Version = HttpVersion.Version11;
-
- Task<HttpResponseMessage> getResponseTask = client.SendAsync(request);
- Task<List<string>> serverTask =
- server.AcceptConnectionSendCustomResponseAndCloseAsync(
- $"HTTP/1.{responseMinorVersion} 200 OK\r\nConnection: close\r\nDate: {DateTimeOffset.UtcNow:R}\r\nContent-Length: 0\r\n\r\n");
-
- await TestHelper.WhenAllCompletedOrAnyFailed(getResponseTask, serverTask);
-
- using (HttpResponseMessage response = await getResponseTask)
- {
- Assert.Equal(1, response.Version.Major);
- Assert.Equal(responseMinorVersion, response.Version.Minor);
- }
- }
- }, new LoopbackServer.Options { StreamWrapper = GetStream });
- }
-
- [Theory]
- [InlineData(0)]
- [InlineData(1)]
- [InlineData(9)]
- public async Task GetAsync_ResponseVersion0X_ThrowsOr10(int responseMinorVersion)
- {
- bool reportAs10 = false;
-
- await LoopbackServer.CreateServerAsync(async (server, url) =>
- {
- using (HttpClient client = CreateHttpClient())
- {
- HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, url);
- request.Version = HttpVersion.Version11;
-
- Task<HttpResponseMessage> getResponseTask = client.SendAsync(request);
- Task<List<string>> serverTask =
- server.AcceptConnectionSendCustomResponseAndCloseAsync(
- $"HTTP/0.{responseMinorVersion} 200 OK\r\nConnection: close\r\nDate: {DateTimeOffset.UtcNow:R}\r\nContent-Length: 0\r\n\r\n");
-
- if (reportAs10)
- {
- await TestHelper.WhenAllCompletedOrAnyFailed(getResponseTask, serverTask);
-
- using (HttpResponseMessage response = await getResponseTask)
- {
- Assert.Equal(1, response.Version.Major);
- Assert.Equal(0, response.Version.Minor);
- }
- }
- else
- {
- await Assert.ThrowsAsync<HttpRequestException>(async () => await TestHelper.WhenAllCompletedOrAnyFailed(getResponseTask, serverTask));
- }
- }
- }, new LoopbackServer.Options { StreamWrapper = GetStream_ClientDisconnectOk });
- }
-
- [Theory]
- [InlineData(2, 0)]
- [InlineData(2, 1)]
- [InlineData(3, 0)]
- [InlineData(4, 2)]
- public async Task GetAsyncVersion11_BadResponseVersion_ThrowsOr00(int responseMajorVersion, int responseMinorVersion)
- {
- await LoopbackServer.CreateServerAsync(async (server, url) =>
- {
- using (HttpClient client = CreateHttpClient())
- {
- HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, url);
- request.Version = HttpVersion.Version11;
-
- Task<HttpResponseMessage> getResponseTask = client.SendAsync(request);
- Task<List<string>> serverTask =
- server.AcceptConnectionSendCustomResponseAndCloseAsync(
- $"HTTP/{responseMajorVersion}.{responseMinorVersion} 200 OK\r\nConnection: close\r\nDate: {DateTimeOffset.UtcNow:R}\r\nContent-Length: 0\r\n\r\n");
-
- await Assert.ThrowsAsync<HttpRequestException>(async () => await TestHelper.WhenAllCompletedOrAnyFailed(getResponseTask, serverTask));
- }
- }, new LoopbackServer.Options { StreamWrapper = GetStream_ClientDisconnectOk });
- }
-
- [Theory]
- [InlineData("HTTP/1.1 200 OK", 200, "OK")]
- [InlineData("HTTP/1.1 200 Sure why not?", 200, "Sure why not?")]
- [InlineData("HTTP/1.1 200 OK\x0080", 200, "OK?")]
- [InlineData("HTTP/1.1 200 O K", 200, "O K")]
- [InlineData("HTTP/1.1 201 Created", 201, "Created")]
- [InlineData("HTTP/1.1 202 Accepted", 202, "Accepted")]
- [InlineData("HTTP/1.1 299 This is not a real status code", 299, "This is not a real status code")]
- [InlineData("HTTP/1.1 345 redirect to nowhere", 345, "redirect to nowhere")]
- [InlineData("HTTP/1.1 400 Bad Request", 400, "Bad Request")]
- [InlineData("HTTP/1.1 500 Internal Server Error", 500, "Internal Server Error")]
- [InlineData("HTTP/1.1 555 we just don't like you", 555, "we just don't like you")]
- [InlineData("HTTP/1.1 600 still valid", 600, "still valid")]
- public async Task GetAsync_ExpectedStatusCodeAndReason_Success(string statusLine, int expectedStatusCode, string expectedReason)
- {
- await GetAsyncSuccessHelper(statusLine, expectedStatusCode, expectedReason);
- }
-
- [Theory]
- [InlineData("HTTP/1.1 200 ", 200, " ")]
- [InlineData("HTTP/1.1 200 Something", 200, " Something")]
- public async Task GetAsync_ExpectedStatusCodeAndReason_PlatformBehaviorTest(string statusLine, int expectedStatusCode, string reasonWithSpace)
- {
- // SocketsHttpHandler and .NET Framework will keep the space characters.
- await GetAsyncSuccessHelper(statusLine, expectedStatusCode, reasonWithSpace);
- }
-
- [Theory]
- [InlineData("HTTP/1.1 200", 200, "")] // This test data requires the fix in .NET Framework 4.7.3
- [InlineData("HTTP/1.1 200 O\tK", 200, "O\tK")]
- [InlineData("HTTP/1.1 200 O \t\t \t\t\t\t \t K", 200, "O \t\t \t\t\t\t \t K")]
- public async Task GetAsync_StatusLineNotFollowRFC_SuccessOnCore(string statusLine, int expectedStatusCode, string expectedReason)
- {
- await GetAsyncSuccessHelper(statusLine, expectedStatusCode, expectedReason);
- }
-
- private async Task GetAsyncSuccessHelper(string statusLine, int expectedStatusCode, string expectedReason)
- {
- await LoopbackServer.CreateServerAsync(async (server, url) =>
- {
- using (HttpClient client = CreateHttpClient())
- {
- Task<HttpResponseMessage> getResponseTask = client.GetAsync(url);
- await TestHelper.WhenAllCompletedOrAnyFailed(
- getResponseTask,
- server.AcceptConnectionSendCustomResponseAndCloseAsync(
- $"{statusLine}\r\n" +
- "Connection: close\r\n" +
- $"Date: {DateTimeOffset.UtcNow:R}\r\n" +
- "Content-Length: 0\r\n" +
- "\r\n"));
- using (HttpResponseMessage response = await getResponseTask)
- {
- Assert.Equal(expectedStatusCode, (int)response.StatusCode);
- Assert.Equal(expectedReason, response.ReasonPhrase);
- }
- }
- }, new LoopbackServer.Options { StreamWrapper = GetStream });
- }
-
- public static IEnumerable<string> GetInvalidStatusLine()
- {
- yield return "HTTP/1.1 2345";
- yield return "HTTP/A.1 200 OK";
- yield return "HTTP/X.Y.Z 200 OK";
-
- yield return "HTTP/0.1 200 OK";
- yield return "HTTP/3.5 200 OK";
- yield return "HTTP/1.12 200 OK";
- yield return "HTTP/12.1 200 OK";
- yield return "HTTP/1.1 200 O\rK";
-
- yield return "HTTP/1.A 200 OK";
- yield return "HTTP/1.1 ";
- yield return "HTTP/1.1 !11";
- yield return "HTTP/1.1 a11";
- yield return "HTTP/1.1 abc";
- yield return "HTTP/1.1\t\t";
- yield return "HTTP/1.1\t";
- yield return "HTTP/1.1 ";
-
- yield return "HTTP/1.1 200OK";
- yield return "HTTP/1.1 20c";
- yield return "HTTP/1.1 23";
- yield return "HTTP/1.1 2bc";
-
- yield return "NOTHTTP/1.1";
- yield return "HTTP 1.1 200 OK";
- yield return "ABCD/1.1 200 OK";
- yield return "HTTP/1.1";
- yield return "HTTP\\1.1 200 OK";
- yield return "NOTHTTP/1.1 200 OK";
- }
-
- public static TheoryData InvalidStatusLine = GetInvalidStatusLine().ToTheoryData();
-
- [Theory]
- [MemberData(nameof(InvalidStatusLine))]
- public async Task GetAsync_InvalidStatusLine_ThrowsException(string responseString)
- {
- await GetAsyncThrowsExceptionHelper(responseString);
- }
-
- [Fact]
- public async Task GetAsync_ReasonPhraseHasLF_BehaviorDifference()
- {
- await GetAsyncSuccessHelper("HTTP/1.1 200 O\n", 200, "O");
- }
-
- [Theory]
- [InlineData("HTTP/1.1\t200 OK")]
- [InlineData("HTTP/1.1 200\tOK")]
- [InlineData("HTTP/1.1 200\t")]
- public async Task GetAsync_InvalidStatusLine_ThrowsExceptionOnSocketsHttpHandler(string responseString)
- {
- // SocketsHttpHandler and .NET Framework will throw HttpRequestException.
- await GetAsyncThrowsExceptionHelper(responseString);
- }
-
- private async Task GetAsyncThrowsExceptionHelper(string responseString)
- {
- await LoopbackServer.CreateServerAsync(async (server, url) =>
- {
- using (HttpClient client = CreateHttpClient())
- {
- Task ignoredServerTask = server.AcceptConnectionSendCustomResponseAndCloseAsync(
- responseString + "\r\nConnection: close\r\nContent-Length: 0\r\n\r\n");
-
- await Assert.ThrowsAsync<HttpRequestException>(() => client.GetAsync(url));
- }
- }, new LoopbackServer.Options { StreamWrapper = GetStream });
- }
-
- [Theory]
- [InlineData("\r\n")]
- [InlineData("\n")]
- public async Task GetAsync_ResponseHasNormalLineEndings_Success(string lineEnding)
- {
- await LoopbackServer.CreateServerAsync(async (server, url) =>
- {
- using (HttpClient client = CreateHttpClient())
- {
- Task<HttpResponseMessage> getResponseTask = client.GetAsync(url);
- Task<List<string>> serverTask = server.AcceptConnectionSendCustomResponseAndCloseAsync(
- $"HTTP/1.1 200 OK{lineEnding}Connection: close\r\nDate: {DateTimeOffset.UtcNow:R}{lineEnding}Server: TestServer{lineEnding}Content-Length: 0{lineEnding}{lineEnding}");
-
- await TestHelper.WhenAllCompletedOrAnyFailed(getResponseTask, serverTask);
-
- using (HttpResponseMessage response = await getResponseTask)
- {
- Assert.Equal(200, (int)response.StatusCode);
- Assert.Equal("OK", response.ReasonPhrase);
- Assert.Equal("TestServer", response.Headers.Server.ToString());
- }
- }
- }, new LoopbackServer.Options { StreamWrapper = GetStream });
- }
-
- public static IEnumerable<object[]> GetAsync_Chunked_VaryingSizeChunks_ReceivedCorrectly_MemberData()
- {
- foreach (int maxChunkSize in new[] { 1, 10_000 })
- foreach (string lineEnding in new[] { "\n", "\r\n" })
- foreach (bool useCopyToAsync in new[] { false, true })
- yield return new object[] { maxChunkSize, lineEnding, useCopyToAsync };
- }
-
- [OuterLoop]
- [Theory]
- [MemberData(nameof(GetAsync_Chunked_VaryingSizeChunks_ReceivedCorrectly_MemberData))]
- public async Task GetAsync_Chunked_VaryingSizeChunks_ReceivedCorrectly(int maxChunkSize, string lineEnding, bool useCopyToAsync)
- {
- var rand = new Random(42);
- byte[] expectedData = new byte[100_000];
- rand.NextBytes(expectedData);
-
- await LoopbackServer.CreateClientAndServerAsync(async uri =>
- {
- using (HttpMessageInvoker client = new HttpMessageInvoker(CreateHttpClientHandler()))
- using (HttpResponseMessage resp = await client.SendAsync(new HttpRequestMessage(HttpMethod.Get, uri) { Version = VersionFromUseHttp2 }, CancellationToken.None))
- using (Stream respStream = await resp.Content.ReadAsStreamAsync())
- {
- var actualData = new MemoryStream();
-
- if (useCopyToAsync)
- {
- await respStream.CopyToAsync(actualData);
- }
- else
- {
- byte[] buffer = new byte[4096];
- int bytesRead;
- while ((bytesRead = await respStream.ReadAsync(buffer)) > 0)
- {
- actualData.Write(buffer, 0, bytesRead);
- }
- }
-
- Assert.Equal<byte>(expectedData, actualData.ToArray());
- }
- }, async server =>
- {
- await server.AcceptConnectionAsync(async connection =>
- {
- await connection.ReadRequestHeaderAsync();
-
- await connection.Writer.WriteAsync($"HTTP/1.1 200 OK{lineEnding}Transfer-Encoding: chunked{lineEnding}{lineEnding}");
- for (int bytesSent = 0; bytesSent < expectedData.Length;)
- {
- int bytesRemaining = expectedData.Length - bytesSent;
- int bytesToSend = rand.Next(1, Math.Min(bytesRemaining, maxChunkSize + 1));
- await connection.Writer.WriteAsync(bytesToSend.ToString("X") + lineEnding);
- await connection.Stream.WriteAsync(new Memory<byte>(expectedData, bytesSent, bytesToSend));
- await connection.Writer.WriteAsync(lineEnding);
- bytesSent += bytesToSend;
- }
- await connection.Writer.WriteAsync($"0{lineEnding}");
- await connection.Writer.WriteAsync(lineEnding);
- });
- });
- }
-
- [Theory]
- [InlineData("get", "GET")]
- [InlineData("head", "HEAD")]
- [InlineData("post", "POST")]
- [InlineData("put", "PUT")]
- [InlineData("other", "other")]
- [InlineData("SometHING", "SometHING")]
- public async Task CustomMethod_SentUppercasedIfKnown(string specifiedMethod, string expectedMethod)
- {
- await LoopbackServer.CreateClientAndServerAsync(async uri =>
- {
- using (HttpClient client = CreateHttpClient())
- {
- var m = new HttpRequestMessage(new HttpMethod(specifiedMethod), uri) { Version = VersionFromUseHttp2 };
- (await client.SendAsync(m)).Dispose();
- }
- }, async server =>
- {
- List<string> headers = await server.AcceptConnectionSendResponseAndCloseAsync();
- Assert.StartsWith(expectedMethod + " ", headers[0]);
- });
- }
- }
-
- public abstract class HttpProtocolTests_Dribble : HttpProtocolTests
- {
- public HttpProtocolTests_Dribble(ITestOutputHelper output) : base(output) { }
-
- protected override Stream GetStream(Stream s) => new DribbleStream(s);
- protected override Stream GetStream_ClientDisconnectOk(Stream s) => new DribbleStream(s, true);
- }
-}
+++ /dev/null
-// 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.Collections.Generic;
-using System.IO;
-using System.Net.Sockets;
-using System.Net.Test.Common;
-using System.Text;
-using System.Threading.Tasks;
-using Xunit;
-using Xunit.Abstractions;
-
-namespace System.Net.Http.Functional.Tests
-{
- public abstract class HttpRetryProtocolTests : HttpClientHandlerTestBase
- {
- private static readonly string s_simpleContent = "Hello World\r\n";
-
- public HttpRetryProtocolTests(ITestOutputHelper output) : base(output) { }
-
- [Fact]
- public async Task GetAsync_RetryOnConnectionClosed_Success()
- {
- await LoopbackServer.CreateClientAndServerAsync(async url =>
- {
- using (HttpClient client = CreateHttpClient())
- {
- // Send initial request and receive response so connection is established
- HttpResponseMessage response1 = await client.GetAsync(url);
- Assert.Equal(HttpStatusCode.OK, response1.StatusCode);
- Assert.Equal(s_simpleContent, await response1.Content.ReadAsStringAsync());
-
- // Send second request. Should reuse same connection.
- // The server will close the connection, but HttpClient should retry the request.
- HttpResponseMessage response2 = await client.GetAsync(url);
- Assert.Equal(HttpStatusCode.OK, response1.StatusCode);
- Assert.Equal(s_simpleContent, await response1.Content.ReadAsStringAsync());
- }
- },
- async server =>
- {
- // Accept first connection
- await server.AcceptConnectionAsync(async connection =>
- {
- // Initial response
- await connection.ReadRequestHeaderAndSendResponseAsync(content: s_simpleContent);
-
- // Second response: Read request headers, then close connection
- await connection.ReadRequestHeaderAsync();
- });
-
- // Client should reconnect. Accept that connection and send response.
- await server.AcceptConnectionSendResponseAndCloseAsync(content: s_simpleContent);
- });
- }
-
- [Fact]
- public async Task PostAsyncExpect100Continue_FailsAfterContentSendStarted_Throws()
- {
- var contentSending = new TaskCompletionSource<bool>();
- var connectionClosed = new TaskCompletionSource<bool>();
-
- await LoopbackServer.CreateClientAndServerAsync(async url =>
- {
- using (HttpClient client = CreateHttpClient())
- {
- // Send initial request and receive response so connection is established
- HttpResponseMessage response1 = await client.GetAsync(url);
- Assert.Equal(HttpStatusCode.OK, response1.StatusCode);
- Assert.Equal(s_simpleContent, await response1.Content.ReadAsStringAsync());
-
- // Send second request on same connection. When the Expect: 100-continue timeout
- // expires, the content will start to be serialized and will signal the server to
- // close the connection; then once the connection is closed, the send will be allowed
- // to continue and will fail.
- var request = new HttpRequestMessage(HttpMethod.Post, url) { Version = VersionFromUseHttp2 };
- request.Headers.ExpectContinue = true;
- request.Content = new SynchronizedSendContent(contentSending, connectionClosed.Task);
- await Assert.ThrowsAsync<HttpRequestException>(() => client.SendAsync(request));
- }
- },
- async server =>
- {
- // Accept connection
- await server.AcceptConnectionAsync(async connection =>
- {
- // Shut down the listen socket so no additional connections can happen
- server.ListenSocket.Close();
-
- // Initial response
- await connection.ReadRequestHeaderAndSendResponseAsync(content: s_simpleContent);
-
- // Second response: Read request headers, then close connection
- List<string> lines = await connection.ReadRequestHeaderAsync();
- Assert.Contains("Expect: 100-continue", lines);
- await contentSending.Task;
- });
- connectionClosed.SetResult(true);
- });
- }
-
- private sealed class SynchronizedSendContent : HttpContent
- {
- private readonly Task _connectionClosed;
- private readonly TaskCompletionSource<bool> _sendingContent;
-
- // The content needs to be large enough to force Expect: 100-Continue behavior in SocketsHttpHandler.
- private readonly string _longContent = new String('a', 1025);
-
- public SynchronizedSendContent(TaskCompletionSource<bool> sendingContent, Task connectionClosed)
- {
- _connectionClosed = connectionClosed;
- _sendingContent = sendingContent;
- }
-
- protected override async Task SerializeToStreamAsync(Stream stream, TransportContext context)
- {
- _sendingContent.SetResult(true);
- await _connectionClosed;
- await stream.WriteAsync(Encoding.UTF8.GetBytes(_longContent));
- }
-
- protected override bool TryComputeLength(out long length)
- {
- length = _longContent.Length;
- return true;
- }
- }
- }
-}
+++ /dev/null
-// 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.Collections.Generic;
-using System.Net.Test.Common;
-using System.Threading.Tasks;
-using Xunit;
-using Xunit.Abstractions;
-
-namespace System.Net.Http.Functional.Tests
-{
- public abstract class IdnaProtocolTests : HttpClientHandlerTestBase
- {
- protected abstract bool SupportsIdna { get; }
-
- public IdnaProtocolTests(ITestOutputHelper output) : base(output) { }
-
- [Theory]
- [MemberData(nameof(InternationalHostNames))]
- public async Task InternationalUrl_UsesIdnaEncoding_Success(string hostname)
- {
- if (!SupportsIdna)
- {
- return;
- }
-
- Uri uri = new Uri($"http://{hostname}/");
-
- await LoopbackServer.CreateServerAsync(async (server, serverUrl) =>
- {
- // We don't actually want to do DNS lookup on the IDNA host name in the URL.
- // So instead, configure the loopback server as a proxy so we will send to it.
- HttpClientHandler handler = CreateHttpClientHandler();
- handler.UseProxy = true;
- handler.Proxy = new WebProxy(serverUrl.ToString());
-
- using (HttpClient client = CreateHttpClient(handler))
- {
- Task<HttpResponseMessage> getResponseTask = client.GetAsync(uri);
- Task<List<string>> serverTask = server.AcceptConnectionSendResponseAndCloseAsync();
-
- await TestHelper.WhenAllCompletedOrAnyFailed(getResponseTask, serverTask);
-
- List<string> requestLines = await serverTask;
-
- // Note since we're using a proxy, host name is included in the request line
- Assert.Equal($"GET http://{uri.IdnHost}/ HTTP/1.1", requestLines[0]);
- Assert.Contains($"Host: {uri.IdnHost}", requestLines);
- }
- });
- }
-
- [ActiveIssue("https://github.com/dotnet/corefx/issues/26355")] // We aren't doing IDNA encoding properly
- [Theory]
- [MemberData(nameof(InternationalHostNames))]
- public async Task InternationalRequestHeaderValues_UsesIdnaEncoding_Success(string hostname)
- {
- if (!SupportsIdna)
- {
- return;
- }
-
- Uri uri = new Uri($"http://{hostname}/");
-
- await LoopbackServer.CreateServerAsync(async (server, serverUrl) =>
- {
- using (HttpClient client = CreateHttpClient())
- {
- var request = new HttpRequestMessage(HttpMethod.Get, serverUrl) { Version = VersionFromUseHttp2 };
- request.Headers.Host = hostname;
- request.Headers.Referrer = uri;
- Task<HttpResponseMessage> getResponseTask = client.SendAsync(request);
- Task<List<string>> serverTask = server.AcceptConnectionSendResponseAndCloseAsync();
-
- await TestHelper.WhenAllCompletedOrAnyFailed(getResponseTask, serverTask);
-
- List<string> requestLines = await serverTask;
-
- Assert.Contains($"Host: {uri.IdnHost}", requestLines);
- Assert.Contains($"Referer: http://{uri.IdnHost}/", requestLines);
- }
- });
- }
-
- [ActiveIssue("https://github.com/dotnet/corefx/issues/26355")] // We aren't doing IDNA decoding properly
- [Theory]
- [MemberData(nameof(InternationalHostNames))]
- public async Task InternationalResponseHeaderValues_UsesIdnaDecoding_Success(string hostname)
- {
- if (!SupportsIdna)
- {
- return;
- }
-
- Uri uri = new Uri($"http://{hostname}/");
-
- await LoopbackServer.CreateServerAsync(async (server, serverUrl) =>
- {
- HttpClientHandler handler = CreateHttpClientHandler();
- handler.AllowAutoRedirect = false;
-
- using (HttpClient client = CreateHttpClient(handler))
- {
- Task<HttpResponseMessage> getResponseTask = client.GetAsync(serverUrl);
- Task<List<string>> serverTask = server.AcceptConnectionSendResponseAndCloseAsync(
- HttpStatusCode.Found, "Location: http://{uri.IdnHost}/\r\n");
-
- await TestHelper.WhenAllCompletedOrAnyFailed(getResponseTask, serverTask);
-
- HttpResponseMessage response = await getResponseTask;
-
- Assert.Equal(uri, response.Headers.Location);
- }
- });
- }
-
- public static IEnumerable<object[]> InternationalHostNames()
- {
- // Latin-1 supplement
- yield return new object[] { "\u00E1.com" };
- yield return new object[] { "\u00E1b\u00E7d\u00EB.com" };
- yield return new object[] { "b\u00E7.com" };
- yield return new object[] { "b\u00E7d.com" };
-
- // Hebrew
- yield return new object[] { "\u05E1.com" };
- yield return new object[] { "\u05D1\u05F1.com" };
-
- // Katakana
- yield return new object[] { "\u30A5.com" };
- yield return new object[] { "\u30B6\u30C7\u30D8.com" };
- }
- }
-}
+++ /dev/null
-// 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.Collections.Generic;
-using System.Net.Test.Common;
-using System.Threading.Tasks;
-using Xunit;
-using Xunit.Abstractions;
-
-namespace System.Net.Http.Functional.Tests
-{
- // Test only WinHttpHandler since the CurlHandler was removed
- [ActiveIssue("https://github.com/dotnet/runtime/issues/339")]
- public class PlatformHandler_HttpClientHandler : HttpClientHandlerTestBase
- {
- public PlatformHandler_HttpClientHandler(ITestOutputHelper output) : base(output) { }
-
- [Theory]
- [InlineData(false)]
- [InlineData(true)]
- public async Task GetAsync_TrailingHeaders_Ignored(bool includeTrailerHeader)
- {
- await LoopbackServer.CreateServerAsync(async (server, url) =>
- {
- using (HttpClient client = CreateHttpClient())
- {
- Task<HttpResponseMessage> getResponseTask = client.GetAsync(url);
- await TestHelper.WhenAllCompletedOrAnyFailed(
- getResponseTask,
- server.AcceptConnectionSendCustomResponseAndCloseAsync(
- "HTTP/1.1 200 OK\r\n" +
- "Connection: close\r\n" +
- "Transfer-Encoding: chunked\r\n" +
- (includeTrailerHeader ? "Trailer: MyCoolTrailerHeader\r\n" : "") +
- "\r\n" +
- "4\r\n" +
- "data\r\n" +
- "0\r\n" +
- "MyCoolTrailerHeader: amazingtrailer\r\n" +
- "\r\n"));
-
- using (HttpResponseMessage response = await getResponseTask)
- {
- Assert.Equal(HttpStatusCode.OK, response.StatusCode);
- if (includeTrailerHeader)
- {
- Assert.Contains("MyCoolTrailerHeader", response.Headers.GetValues("Trailer"));
- }
- Assert.False(response.Headers.Contains("MyCoolTrailerHeader"), "Trailer should have been ignored");
-
- string data = await response.Content.ReadAsStringAsync();
- Assert.Contains("data", data);
- Assert.DoesNotContain("MyCoolTrailerHeader", data);
- Assert.DoesNotContain("amazingtrailer", data);
- }
- }
- });
- }
- }
-
- // Test only WinHttpHandler since the CurlHandler was removed
- [ActiveIssue("https://github.com/dotnet/runtime/issues/339")]
- public sealed class PlatformHandler_HttpClientHandler_Asynchrony_Test : HttpClientHandler_Asynchrony_Test
- {
- public PlatformHandler_HttpClientHandler_Asynchrony_Test(ITestOutputHelper output) : base(output) { }
- }
-
- // Test only WinHttpHandler since the CurlHandler was removed
- [ActiveIssue("https://github.com/dotnet/runtime/issues/339")]
- public sealed class PlatformHandler_HttpProtocolTests : HttpProtocolTests
- {
- public PlatformHandler_HttpProtocolTests(ITestOutputHelper output) : base(output) { }
- }
-
- // Test only WinHttpHandler since the CurlHandler was removed
- [ActiveIssue("https://github.com/dotnet/runtime/issues/339")]
- public sealed class PlatformHandler_HttpProtocolTests_Dribble : HttpProtocolTests_Dribble
- {
- public PlatformHandler_HttpProtocolTests_Dribble(ITestOutputHelper output) : base(output) { }
- }
-
- // Test only WinHttpHandler since the CurlHandler was removed
- [ActiveIssue("https://github.com/dotnet/runtime/issues/339")]
- public sealed class PlatformHandler_DiagnosticsTest : DiagnosticsTest
- {
- public PlatformHandler_DiagnosticsTest(ITestOutputHelper output) : base(output) { }
- }
-
- // Test only WinHttpHandler since the CurlHandler was removed
- [ActiveIssue("https://github.com/dotnet/runtime/issues/339")]
- public sealed class PlatformHandler_HttpClient_SelectedSites_Test : HttpClient_SelectedSites_Test
- {
- public PlatformHandler_HttpClient_SelectedSites_Test(ITestOutputHelper output) : base(output) { }
- }
-
- // Test only WinHttpHandler since the CurlHandler was removed
- [ActiveIssue("https://github.com/dotnet/runtime/issues/339")]
- public sealed class PlatformHandler_HttpClientEKUTest : HttpClientEKUTest
- {
- public PlatformHandler_HttpClientEKUTest(ITestOutputHelper output) : base(output) { }
- }
-
-#if NETCOREAPP
- // Test only WinHttpHandler since the CurlHandler was removed
- [ActiveIssue("https://github.com/dotnet/runtime/issues/339")]
- public sealed class PlatformHandler_HttpClientHandler_Decompression_Tests : HttpClientHandler_Decompression_Test
- {
- public PlatformHandler_HttpClientHandler_Decompression_Tests(ITestOutputHelper output) : base(output) { }
- }
-
- // Test only WinHttpHandler since the CurlHandler was removed
- [ActiveIssue("https://github.com/dotnet/runtime/issues/339")]
- public sealed class PlatformHandler_HttpClientHandler_DangerousAcceptAllCertificatesValidator_Test : HttpClientHandler_DangerousAcceptAllCertificatesValidator_Test
- {
- public PlatformHandler_HttpClientHandler_DangerousAcceptAllCertificatesValidator_Test(ITestOutputHelper output) : base(output) { }
- }
-#endif
-
- // Test only WinHttpHandler since the CurlHandler was removed
- [ActiveIssue("https://github.com/dotnet/runtime/issues/339")]
- public sealed class PlatformHandler_HttpClientHandler_ClientCertificates_Test : HttpClientHandler_ClientCertificates_Test
- {
- public PlatformHandler_HttpClientHandler_ClientCertificates_Test(ITestOutputHelper output) : base(output) { }
- }
-
- // Test only WinHttpHandler since the CurlHandler was removed
- [ActiveIssue("https://github.com/dotnet/runtime/issues/339")]
- public sealed class PlatformHandler_HttpClientHandler_DefaultProxyCredentials_Test : HttpClientHandler_DefaultProxyCredentials_Test
- {
- public PlatformHandler_HttpClientHandler_DefaultProxyCredentials_Test(ITestOutputHelper output) : base(output) { }
- }
-
- // Test only WinHttpHandler since the CurlHandler was removed
- [ActiveIssue("https://github.com/dotnet/runtime/issues/339")]
- public sealed class PlatformHandler_HttpClientHandler_MaxConnectionsPerServer_Test : HttpClientHandler_MaxConnectionsPerServer_Test
- {
- public PlatformHandler_HttpClientHandler_MaxConnectionsPerServer_Test(ITestOutputHelper output) : base(output) { }
- }
-
- // Test only WinHttpHandler since the CurlHandler was removed
- [ActiveIssue("https://github.com/dotnet/runtime/issues/339")]
- public sealed class PlatformHandler_HttpClientHandler_ServerCertificates_Test : HttpClientHandler_ServerCertificates_Test
- {
- public PlatformHandler_HttpClientHandler_ServerCertificates_Test(ITestOutputHelper output) : base(output) { }
- }
-
- // Test only WinHttpHandler since the CurlHandler was removed
- [ActiveIssue("https://github.com/dotnet/runtime/issues/339")]
- public sealed class PlatformHandler_PostScenarioTest : PostScenarioTest
- {
- public PlatformHandler_PostScenarioTest(ITestOutputHelper output) : base(output) { }
- }
-
- // Test only WinHttpHandler since the CurlHandler was removed
- [ActiveIssue("https://github.com/dotnet/runtime/issues/339")]
- public sealed class PlatformHandler_ResponseStreamTest : ResponseStreamTest
- {
- public PlatformHandler_ResponseStreamTest(ITestOutputHelper output) : base(output) { }
- }
-
- // Test only WinHttpHandler since the CurlHandler was removed
- [ActiveIssue("https://github.com/dotnet/runtime/issues/339")]
- public sealed class PlatformHandler_HttpClientHandler_SslProtocols_Test : HttpClientHandler_SslProtocols_Test
- {
- public PlatformHandler_HttpClientHandler_SslProtocols_Test(ITestOutputHelper output) : base(output) { }
- }
-
- // Test only WinHttpHandler since the CurlHandler was removed
- [ActiveIssue("https://github.com/dotnet/runtime/issues/339")]
- public sealed class PlatformHandler_HttpClientHandler_Proxy_Test : HttpClientHandler_Proxy_Test
- {
- public PlatformHandler_HttpClientHandler_Proxy_Test(ITestOutputHelper output) : base(output) { }
- }
-
- // Test only WinHttpHandler since the CurlHandler was removed
- [ActiveIssue("https://github.com/dotnet/runtime/issues/339")]
- public sealed class PlatformHandler_SchSendAuxRecordHttpTest : SchSendAuxRecordHttpTest
- {
- public PlatformHandler_SchSendAuxRecordHttpTest(ITestOutputHelper output) : base(output) { }
- }
-
- // Test only WinHttpHandler since the CurlHandler was removed
- [ActiveIssue("https://github.com/dotnet/runtime/issues/339")]
- public sealed class PlatformHandler_HttpClientHandlerTest : HttpClientHandlerTest
- {
- public PlatformHandler_HttpClientHandlerTest(ITestOutputHelper output) : base(output) { }
- }
-
- // Test only WinHttpHandler since the CurlHandler was removed
- [ActiveIssue("https://github.com/dotnet/runtime/issues/339")]
- public sealed class PlatformHandlerTest_AutoRedirect : HttpClientHandlerTest_AutoRedirect
- {
- public PlatformHandlerTest_AutoRedirect(ITestOutputHelper output) : base(output) { }
- }
-
- // Test only WinHttpHandler since the CurlHandler was removed
- [ActiveIssue("https://github.com/dotnet/runtime/issues/339")]
- public sealed class PlatformHandler_DefaultCredentialsTest : DefaultCredentialsTest
- {
- public PlatformHandler_DefaultCredentialsTest(ITestOutputHelper output) : base(output) { }
- }
-
- // Test only WinHttpHandler since the CurlHandler was removed
- [ActiveIssue("https://github.com/dotnet/runtime/issues/339")]
- public sealed class PlatformHandler_IdnaProtocolTests : IdnaProtocolTests
- {
- public PlatformHandler_IdnaProtocolTests(ITestOutputHelper output) : base(output) { }
- // WinHttp on Win7 does not support IDNA
- protected override bool SupportsIdna => !PlatformDetection.IsWindows7;
- }
-
- // Test only WinHttpHandler since the CurlHandler was removed
- [ActiveIssue("https://github.com/dotnet/runtime/issues/339")]
- public sealed class PlatformHandler_HttpRetryProtocolTests : HttpRetryProtocolTests
- {
- public PlatformHandler_HttpRetryProtocolTests(ITestOutputHelper output) : base(output) { }
- }
-
- // Test only WinHttpHandler since the CurlHandler was removed
- [ActiveIssue("https://github.com/dotnet/runtime/issues/339")]
- public sealed class PlatformHandlerTest_Cookies : HttpClientHandlerTest_Cookies
- {
- public PlatformHandlerTest_Cookies(ITestOutputHelper output) : base(output) { }
- }
-
- // Test only WinHttpHandler since the CurlHandler was removed
- [ActiveIssue("https://github.com/dotnet/runtime/issues/339")]
- public sealed class PlatformHandlerTest_Cookies_Http11 : HttpClientHandlerTest_Cookies_Http11
- {
- public PlatformHandlerTest_Cookies_Http11(ITestOutputHelper output) : base(output) { }
- }
-
- // Test only WinHttpHandler since the CurlHandler was removed
- [ActiveIssue("https://github.com/dotnet/runtime/issues/339")]
- public sealed class PlatformHandler_HttpClientHandler_MaxResponseHeadersLength_Test : HttpClientHandler_MaxResponseHeadersLength_Test
- {
- public PlatformHandler_HttpClientHandler_MaxResponseHeadersLength_Test(ITestOutputHelper output) : base(output) { }
- }
-
- // Test only WinHttpHandler since the CurlHandler was removed
- [ActiveIssue("https://github.com/dotnet/runtime/issues/339")]
- public sealed class PlatformHandler_HttpClientHandler_Cancellation_Test : HttpClientHandler_Cancellation_Test
- {
- public PlatformHandler_HttpClientHandler_Cancellation_Test(ITestOutputHelper output) : base(output) { }
- }
-
- // Test only WinHttpHandler since the CurlHandler was removed
- [ActiveIssue("https://github.com/dotnet/runtime/issues/339")]
- public sealed class PlatformHandler_HttpClientHandler_Authentication_Test : HttpClientHandler_Authentication_Test
- {
- public PlatformHandler_HttpClientHandler_Authentication_Test(ITestOutputHelper output) : base(output) { }
- }
-
- // Enable this to run HTTP2 tests on platform handler
-#if PLATFORM_HANDLER_HTTP2_TESTS
- // Test only WinHttpHandler since the CurlHandler was removed
- [ActiveIssue("https://github.com/dotnet/runtime/issues/339")]
- public sealed class PlatformHandlerTest_Http2 : HttpClientHandlerTest_Http2
- {
- }
-
- // Test only WinHttpHandler since the CurlHandler was removed
- [ActiveIssue("https://github.com/dotnet/runtime/issues/339")]
- [ConditionalClass(typeof(PlatformDetection), nameof(PlatformDetection.SupportsAlpn))]
- public sealed class PlatformHandlerTest_Cookies_Http2 : HttpClientHandlerTest_Cookies
- {
- protected override bool UseHttp2LoopbackServer => true;
- }
-#endif
-}
+++ /dev/null
-// 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.Collections.Generic;
-using System.IO;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-using Xunit;
-using Xunit.Abstractions;
-
-namespace System.Net.Http.Functional.Tests
-{
- using Configuration = System.Net.Test.Common.Configuration;
-
- // Note: Disposing the HttpClient object automatically disposes the handler within. So, it is not necessary
- // to separately Dispose (or have a 'using' statement) for the handler.
- public abstract class PostScenarioTest : HttpClientHandlerTestBase
- {
- private const string ExpectedContent = "Test contest";
- private const string UserName = "user1";
- private const string Password = "password1";
-
- public PostScenarioTest(ITestOutputHelper output) : base(output) { }
-
- [OuterLoop("Uses external servers")]
- [Theory, MemberData(nameof(RemoteServersMemberData))]
- public async Task PostRewindableStreamContentMultipleTimes_StreamContentFullySent(Configuration.Http.RemoteServer remoteServer)
- {
- const string requestBody = "ABC";
-
- using (HttpClient client = CreateHttpClientForRemoteServer(remoteServer))
- using (var ms = new MemoryStream(Encoding.UTF8.GetBytes(requestBody)))
- {
- var content = new StreamContent(ms);
-
- for (int i = 1; i <= 3; i++)
- {
- HttpResponseMessage response = await client.PostAsync(remoteServer.EchoUri, content);
- Assert.Equal(requestBody.Length, ms.Position); // Stream left at end after send.
-
- string responseBody = await response.Content.ReadAsStringAsync();
- _output.WriteLine(responseBody);
- Assert.True(TestHelper.JsonMessageContainsKeyValue(responseBody, "BodyContent", requestBody));
- }
- }
- }
-
- [OuterLoop("Uses external servers")]
- [Theory, MemberData(nameof(RemoteServersMemberData))]
- public async Task PostNoContentUsingContentLengthSemantics_Success(Configuration.Http.RemoteServer remoteServer)
- {
- await PostHelper(remoteServer, string.Empty, null,
- useContentLengthUpload: true, useChunkedEncodingUpload: false);
- }
-
- [OuterLoop("Uses external servers")]
- [Theory, MemberData(nameof(RemoteServersMemberData))]
- public async Task PostEmptyContentUsingContentLengthSemantics_Success(Configuration.Http.RemoteServer remoteServer)
- {
- await PostHelper(remoteServer, string.Empty, new StringContent(string.Empty),
- useContentLengthUpload: true, useChunkedEncodingUpload: false);
- }
-
- [OuterLoop("Uses external servers")]
- [Theory, MemberData(nameof(RemoteServersMemberData))]
- public async Task PostEmptyContentUsingChunkedEncoding_Success(Configuration.Http.RemoteServer remoteServer)
- {
- await PostHelper(remoteServer, string.Empty, new StringContent(string.Empty),
- useContentLengthUpload: false, useChunkedEncodingUpload: true);
- }
-
- [OuterLoop("Uses external servers")]
- [Theory, MemberData(nameof(RemoteServersMemberData))]
- public async Task PostEmptyContentUsingConflictingSemantics_Success(Configuration.Http.RemoteServer remoteServer)
- {
- await PostHelper(remoteServer, string.Empty, new StringContent(string.Empty),
- useContentLengthUpload: true, useChunkedEncodingUpload: true);
- }
-
- [OuterLoop("Uses external servers")]
- [Theory, MemberData(nameof(RemoteServersMemberData))]
- public async Task PostUsingContentLengthSemantics_Success(Configuration.Http.RemoteServer remoteServer)
- {
- await PostHelper(remoteServer, ExpectedContent, new StringContent(ExpectedContent),
- useContentLengthUpload: true, useChunkedEncodingUpload: false);
- }
-
- [OuterLoop("Uses external servers")]
- [Theory, MemberData(nameof(RemoteServersMemberData))]
- public async Task PostUsingChunkedEncoding_Success(Configuration.Http.RemoteServer remoteServer)
- {
- await PostHelper(remoteServer, ExpectedContent, new StringContent(ExpectedContent),
- useContentLengthUpload: false, useChunkedEncodingUpload: true);
- }
-
- [OuterLoop("Uses external servers")]
- [Theory, MemberData(nameof(RemoteServersMemberData))]
- public async Task PostSyncBlockingContentUsingChunkedEncoding_Success(Configuration.Http.RemoteServer remoteServer)
- {
- await PostHelper(remoteServer, ExpectedContent, new SyncBlockingContent(ExpectedContent),
- useContentLengthUpload: false, useChunkedEncodingUpload: true);
- }
-
- [OuterLoop("Uses external servers")]
- [Theory, MemberData(nameof(RemoteServersMemberData))]
- public async Task PostRepeatedFlushContentUsingChunkedEncoding_Success(Configuration.Http.RemoteServer remoteServer)
- {
- await PostHelper(remoteServer, ExpectedContent, new RepeatedFlushContent(ExpectedContent),
- useContentLengthUpload: false, useChunkedEncodingUpload: true);
- }
-
- [OuterLoop("Uses external servers")]
- [Theory, MemberData(nameof(RemoteServersMemberData))]
- public async Task PostUsingUsingConflictingSemantics_UsesChunkedSemantics(Configuration.Http.RemoteServer remoteServer)
- {
- await PostHelper(remoteServer, ExpectedContent, new StringContent(ExpectedContent),
- useContentLengthUpload: true, useChunkedEncodingUpload: true);
- }
-
- [OuterLoop("Uses external servers")]
- [Theory, MemberData(nameof(RemoteServersMemberData))]
- public async Task PostUsingNoSpecifiedSemantics_UsesChunkedSemantics(Configuration.Http.RemoteServer remoteServer)
- {
- await PostHelper(remoteServer, ExpectedContent, new StringContent(ExpectedContent),
- useContentLengthUpload: false, useChunkedEncodingUpload: false);
- }
-
- public static IEnumerable<object[]> RemoteServersAndLargeContentSizes()
- {
- foreach (Configuration.Http.RemoteServer remoteServer in Configuration.Http.RemoteServers)
- {
- yield return new object[] { remoteServer, 5 * 1024 };
- yield return new object[] { remoteServer, 63 * 1024 };
- yield return new object[] { remoteServer, 129 * 1024 };
- }
- }
-
- [OuterLoop("Uses external server")]
- [Theory]
- [MemberData(nameof(RemoteServersAndLargeContentSizes))]
- public async Task PostLargeContentUsingContentLengthSemantics_Success(Configuration.Http.RemoteServer remoteServer, int contentLength)
- {
- var rand = new Random(42);
- var sb = new StringBuilder(contentLength);
- for (int i = 0; i < contentLength; i++)
- {
- sb.Append((char)(rand.Next(0, 26) + 'a'));
- }
- string content = sb.ToString();
-
- await PostHelper(remoteServer, content, new StringContent(content),
- useContentLengthUpload: true, useChunkedEncodingUpload: false);
- }
-
- [OuterLoop("Uses external servers")]
- [Theory, MemberData(nameof(RemoteServersMemberData))]
- public async Task PostRewindableContentUsingAuth_NoPreAuthenticate_Success(Configuration.Http.RemoteServer remoteServer)
- {
- if (remoteServer.HttpVersion == new Version(2, 0))
- {
- // This is occasionally timing out in CI with SocketsHttpHandler and HTTP2, particularly on Linux
- // Likely this is just a very slow test and not a product issue, so just increasing the timeout may be the right fix.
- // Disable until we can investigate further.
- return;
- }
-
- HttpContent content = new StreamContent(new CustomContent.CustomStream(Encoding.UTF8.GetBytes(ExpectedContent), true));
- var credential = new NetworkCredential(UserName, Password);
- await PostUsingAuthHelper(remoteServer, ExpectedContent, content, credential, false);
- }
-
- [OuterLoop("Uses external servers")]
- [Theory, MemberData(nameof(RemoteServersMemberData))]
- public async Task PostNonRewindableContentUsingAuth_NoPreAuthenticate_ThrowsHttpRequestException(Configuration.Http.RemoteServer remoteServer)
- {
- HttpContent content = new StreamContent(new CustomContent.CustomStream(Encoding.UTF8.GetBytes(ExpectedContent), false));
- var credential = new NetworkCredential(UserName, Password);
- await Assert.ThrowsAsync<HttpRequestException>(() =>
- PostUsingAuthHelper(remoteServer, ExpectedContent, content, credential, preAuthenticate: false));
- }
-
- [OuterLoop("Uses external servers")]
- [Theory, MemberData(nameof(RemoteServersMemberData))]
- public async Task PostNonRewindableContentUsingAuth_PreAuthenticate_Success(Configuration.Http.RemoteServer remoteServer)
- {
- HttpContent content = new StreamContent(new CustomContent.CustomStream(Encoding.UTF8.GetBytes(ExpectedContent), false));
- var credential = new NetworkCredential(UserName, Password);
- await PostUsingAuthHelper(remoteServer, ExpectedContent, content, credential, preAuthenticate: true);
- }
-
- [OuterLoop("Uses external servers")]
- [Theory, MemberData(nameof(RemoteServersMemberData))]
- public async Task PostAsync_EmptyContent_ContentTypeHeaderNotSent(Configuration.Http.RemoteServer remoteServer)
- {
- using (HttpClient client = CreateHttpClientForRemoteServer(remoteServer))
- using (HttpResponseMessage response = await client.PostAsync(remoteServer.EchoUri, null))
- {
- string responseContent = await response.Content.ReadAsStringAsync();
- bool sentContentType = TestHelper.JsonMessageContainsKey(responseContent, "Content-Type");
-
- Assert.False(sentContentType);
- }
- }
-
- private async Task PostHelper(
- Configuration.Http.RemoteServer remoteServer,
- string requestBody,
- HttpContent requestContent,
- bool useContentLengthUpload,
- bool useChunkedEncodingUpload)
- {
- using (HttpClient client = CreateHttpClientForRemoteServer(remoteServer))
- {
- if (requestContent != null)
- {
- if (useContentLengthUpload)
- {
- // Ensure that Content-Length is populated (see https://github.com/dotnet/corefx/issues/27245)
- requestContent.Headers.ContentLength = requestContent.Headers.ContentLength;
- }
- else
- {
- requestContent.Headers.ContentLength = null;
- }
-
- // Compute MD5 of request body data. This will be verified by the server when it
- // receives the request.
- requestContent.Headers.ContentMD5 = TestHelper.ComputeMD5Hash(requestBody);
- }
-
- if (useChunkedEncodingUpload)
- {
- client.DefaultRequestHeaders.TransferEncodingChunked = true;
- }
-
- using (HttpResponseMessage response = await client.PostAsync(remoteServer.VerifyUploadUri, requestContent))
- {
- Assert.Equal(HttpStatusCode.OK, response.StatusCode);
- }
- }
- }
-
- private async Task PostUsingAuthHelper(
- Configuration.Http.RemoteServer remoteServer,
- string requestBody,
- HttpContent requestContent,
- NetworkCredential credential,
- bool preAuthenticate)
- {
- Uri serverUri = remoteServer.BasicAuthUriForCreds(UserName, Password);
-
- HttpClientHandler handler = CreateHttpClientHandler();
- handler.PreAuthenticate = preAuthenticate;
- handler.Credentials = credential;
- using (HttpClient client = CreateHttpClientForRemoteServer(remoteServer, handler))
- {
- // Send HEAD request to help bypass the 401 auth challenge for the latter POST assuming
- // that the authentication will be cached and re-used later when PreAuthenticate is true.
- var request = new HttpRequestMessage(HttpMethod.Head, serverUri) { Version = remoteServer.HttpVersion };
- using (HttpResponseMessage response = await client.SendAsync(request))
- {
- Assert.Equal(HttpStatusCode.OK, response.StatusCode);
- }
-
- // Now send POST request.
- request = new HttpRequestMessage(HttpMethod.Post, serverUri) { Version = remoteServer.HttpVersion };
- request.Content = requestContent;
- requestContent.Headers.ContentLength = null;
- request.Headers.TransferEncodingChunked = true;
-
- using (HttpResponseMessage response = await client.SendAsync(request))
- {
- Assert.Equal(HttpStatusCode.OK, response.StatusCode);
- string responseContent = await response.Content.ReadAsStringAsync();
- _output.WriteLine(responseContent);
-
- TestHelper.VerifyResponseBody(
- responseContent,
- response.Content.Headers.ContentMD5,
- true,
- requestBody);
- }
- }
- }
- }
-}
+++ /dev/null
-// 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;
-using System.Diagnostics;
-using System.IO;
-using System.Net.Http;
-using System.Text;
-using System.Threading.Tasks;
-using Microsoft.DotNet.RemoteExecutor;
-using Xunit;
-using Xunit.Abstractions;
-
-namespace System.Net.Http.Functional.Tests
-{
- using Configuration = System.Net.Test.Common.Configuration;
-
- public abstract class PostScenarioUWPTest : HttpClientHandlerTestBase
- {
- public PostScenarioUWPTest(ITestOutputHelper output) : base(output) { }
-
- [Fact]
- public void Authentication_UseStreamContent_Throws()
- {
- RemoteExecutor.Invoke(async (useHttp2String) =>
- {
- // This test validates the current limitation of CoreFx's NetFxToWinRtStreamAdapter
- // which throws exceptions when trying to rewind a .NET Stream when it needs to be
- // re-POST'd to the server.
- string username = "testuser";
- string password = "password";
- Uri uri = Configuration.Http.RemoteHttp11Server.BasicAuthUriForCreds(userName: username, password: password);
- HttpClientHandler handler = CreateHttpClientHandler(useHttp2String);
- handler.Credentials = new NetworkCredential(username, password);
-
- using (HttpClient client = CreateHttpClient(handler, useHttp2String))
- {
- byte[] postData = Encoding.UTF8.GetBytes("This is data to post.");
- var stream = new MemoryStream(postData, false);
- var content = new StreamContent(stream);
-
- await Assert.ThrowsAsync<HttpRequestException>(() => client.PostAsync(uri, content));
- }
- }, UseHttp2.ToString()).Dispose();
- }
-
- [Fact]
- public void Authentication_UseMultiInterfaceNonRewindableStreamContent_Throws()
- {
- RemoteExecutor.Invoke(async (useHttp2String) =>
- {
- string username = "testuser";
- string password = "password";
- Uri uri = Configuration.Http.RemoteHttp11Server.BasicAuthUriForCreds(userName: username, password: password);
- HttpClientHandler handler = CreateHttpClientHandler(useHttp2String);
- handler.Credentials = new NetworkCredential(username, password);
-
- using (HttpClient client = CreateHttpClient(handler, useHttp2String))
- {
- byte[] postData = Encoding.UTF8.GetBytes("This is data to post.");
- var stream = new MultiInterfaceNonRewindableReadOnlyStream(postData);
- var content = new MultiInterfaceStreamContent(stream);
-
- await Assert.ThrowsAsync<HttpRequestException>(() => client.PostAsync(uri, content));
- }
- }, UseHttp2.ToString()).Dispose();
- }
-
- [Fact]
- public void Authentication_UseMultiInterfaceStreamContent_Success()
- {
- RemoteExecutor.Invoke(async (useHttp2String) =>
- {
- string username = "testuser";
- string password = "password";
- Uri uri = Configuration.Http.RemoteHttp11Server.BasicAuthUriForCreds(userName: username, password: password);
- HttpClientHandler handler = CreateHttpClientHandler(useHttp2String);
- handler.Credentials = new NetworkCredential(username, password);
-
- using (HttpClient client = CreateHttpClient(handler, useHttp2String))
- {
- byte[] postData = Encoding.UTF8.GetBytes("This is data to post.");
- var stream = new MultiInterfaceReadOnlyStream(postData);
- var content = new MultiInterfaceStreamContent(stream);
-
- using (HttpResponseMessage response = await client.PostAsync(uri, content))
- {
- Assert.Equal(HttpStatusCode.OK, response.StatusCode);
- string responseContent = await response.Content.ReadAsStringAsync();
- }
- }
- }, UseHttp2.ToString()).Dispose();
- }
-
- [Fact]
- public void Authentication_UseMemoryStreamVisibleBufferContent_Success()
- {
- RemoteExecutor.Invoke(async (useHttp2String) =>
- {
- string username = "testuser";
- string password = "password";
- Uri uri = Configuration.Http.RemoteHttp11Server.BasicAuthUriForCreds(userName: username, password: password);
- HttpClientHandler handler = CreateHttpClientHandler(useHttp2String);
- handler.Credentials = new NetworkCredential(username, password);
-
- using (HttpClient client = CreateHttpClient(handler, useHttp2String))
- {
- byte[] postData = Encoding.UTF8.GetBytes("This is data to post.");
- var stream = new MemoryStream(postData, 0, postData.Length, false, true);
- var content = new MultiInterfaceStreamContent(stream);
-
- using (HttpResponseMessage response = await client.PostAsync(uri, content))
- {
- Assert.Equal(HttpStatusCode.OK, response.StatusCode);
- string responseContent = await response.Content.ReadAsStringAsync();
- }
- }
- }, UseHttp2.ToString()).Dispose();
- }
-
- [Fact]
- public void Authentication_UseMemoryStreamNotVisibleBufferContent_Success()
- {
- RemoteExecutor.Invoke(async (useHttp2String) =>
- {
- string username = "testuser";
- string password = "password";
- Uri uri = Configuration.Http.RemoteHttp11Server.BasicAuthUriForCreds(userName: username, password: password);
- HttpClientHandler handler = CreateHttpClientHandler(useHttp2String);
- handler.Credentials = new NetworkCredential(username, password);
-
- using (HttpClient client = CreateHttpClient(handler, useHttp2String))
- {
- byte[] postData = Encoding.UTF8.GetBytes("This is data to post.");
- var stream = new MemoryStream(postData, 0, postData.Length, false, false);
- var content = new MultiInterfaceStreamContent(stream);
-
- using (HttpResponseMessage response = await client.PostAsync(uri, content))
- {
- Assert.Equal(HttpStatusCode.OK, response.StatusCode);
- string responseContent = await response.Content.ReadAsStringAsync();
- }
- }
- }, UseHttp2.ToString()).Dispose();
- }
- }
-}
+++ /dev/null
-// 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;
-using System.IO;
-using System.Threading.Tasks;
-
-namespace System.Net.Http.Functional.Tests
-{
- public sealed class RepeatedFlushContent : StringContent
- {
- public RepeatedFlushContent(string content) : base(content)
- {
- }
-
- protected override Task SerializeToStreamAsync(Stream stream, TransportContext context)
- {
- stream.Flush();
- stream.Flush();
- return base.SerializeToStreamAsync(stream, context);
- }
- }
-}
+++ /dev/null
-// 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.Collections.Generic;
-using System.IO;
-using System.Net.Test.Common;
-using System.Text;
-using System.Threading;
-using System.Threading.Tasks;
-
-using Xunit;
-using Xunit.Abstractions;
-
-namespace System.Net.Http.Functional.Tests
-{
- using Configuration = System.Net.Test.Common.Configuration;
-
- public abstract class ResponseStreamTest : HttpClientHandlerTestBase
- {
- public ResponseStreamTest(ITestOutputHelper output) : base(output) { }
-
- public static IEnumerable<object[]> RemoteServersAndReadModes()
- {
- foreach (Configuration.Http.RemoteServer remoteServer in Configuration.Http.RemoteServers)
- {
- for (int i = 0; i < 8; i++)
- {
- yield return new object[] { remoteServer, i };
- }
- }
-
- }
- [OuterLoop("Uses external server")]
- [Theory, MemberData(nameof(RemoteServersAndReadModes))]
- public async Task GetStreamAsync_ReadToEnd_Success(Configuration.Http.RemoteServer remoteServer, int readMode)
- {
- using (HttpClient client = CreateHttpClientForRemoteServer(remoteServer))
- {
- string customHeaderValue = Guid.NewGuid().ToString("N");
- client.DefaultRequestHeaders.Add("X-ResponseStreamTest", customHeaderValue);
-
- using (Stream stream = await client.GetStreamAsync(remoteServer.EchoUri))
- {
- var ms = new MemoryStream();
- int bytesRead;
- var buffer = new byte[10];
- string responseBody;
-
- // Read all of the response content in various ways
- switch (readMode)
- {
- case 0:
- // StreamReader.ReadToEnd
- responseBody = new StreamReader(stream).ReadToEnd();
- break;
-
- case 1:
- // StreamReader.ReadToEndAsync
- responseBody = await new StreamReader(stream).ReadToEndAsync();
- break;
-
- case 2:
- // Individual calls to Read(Array)
- while ((bytesRead = stream.Read(buffer, 0, buffer.Length)) != 0)
- {
- ms.Write(buffer, 0, bytesRead);
- }
- responseBody = Encoding.UTF8.GetString(ms.ToArray());
- break;
-
- case 3:
- // Individual calls to ReadAsync(Array)
- while ((bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length)) != 0)
- {
- ms.Write(buffer, 0, bytesRead);
- }
- responseBody = Encoding.UTF8.GetString(ms.ToArray());
- break;
-
- case 4:
- // Individual calls to Read(Span)
- while ((bytesRead = stream.Read(new Span<byte>(buffer))) != 0)
- {
- ms.Write(buffer, 0, bytesRead);
- }
- responseBody = Encoding.UTF8.GetString(ms.ToArray());
- break;
-
- case 5:
- // ReadByte
- int byteValue;
- while ((byteValue = stream.ReadByte()) != -1)
- {
- ms.WriteByte((byte)byteValue);
- }
- responseBody = Encoding.UTF8.GetString(ms.ToArray());
- break;
-
- case 6:
- // CopyTo
- stream.CopyTo(ms);
- responseBody = Encoding.UTF8.GetString(ms.ToArray());
- break;
-
- case 7:
- // CopyToAsync
- await stream.CopyToAsync(ms);
- responseBody = Encoding.UTF8.GetString(ms.ToArray());
- break;
-
- default:
- throw new Exception($"Unexpected test mode {readMode}");
- }
-
- // Calling GetStreamAsync() means we don't have access to the HttpResponseMessage.
- // So, we can't use the MD5 hash validation to verify receipt of the response body.
- // For this test, we can use a simpler verification of a custom header echo'ing back.
- _output.WriteLine(responseBody);
- Assert.Contains(customHeaderValue, responseBody);
- }
- }
- }
-
- [OuterLoop("Uses external server")]
- [Theory, MemberData(nameof(RemoteServersMemberData))]
- public async Task GetAsync_UseResponseHeadersReadAndCallLoadIntoBuffer_Success(Configuration.Http.RemoteServer remoteServer)
- {
- using (HttpClient client = CreateHttpClientForRemoteServer(remoteServer))
- using (HttpResponseMessage response = await client.GetAsync(remoteServer.EchoUri, HttpCompletionOption.ResponseHeadersRead))
- {
- await response.Content.LoadIntoBufferAsync();
-
- string responseBody = await response.Content.ReadAsStringAsync();
- _output.WriteLine(responseBody);
- TestHelper.VerifyResponseBody(
- responseBody,
- response.Content.Headers.ContentMD5,
- false,
- null);
- }
- }
-
- [OuterLoop("Uses external server")]
- [Theory, MemberData(nameof(RemoteServersMemberData))]
- public async Task GetAsync_UseResponseHeadersReadAndCopyToMemoryStream_Success(Configuration.Http.RemoteServer remoteServer)
- {
- using (HttpClient client = CreateHttpClientForRemoteServer(remoteServer))
- using (HttpResponseMessage response = await client.GetAsync(remoteServer.EchoUri, HttpCompletionOption.ResponseHeadersRead))
- {
- var memoryStream = new MemoryStream();
- await response.Content.CopyToAsync(memoryStream);
- memoryStream.Position = 0;
-
- using (var reader = new StreamReader(memoryStream))
- {
- string responseBody = reader.ReadToEnd();
- _output.WriteLine(responseBody);
- TestHelper.VerifyResponseBody(
- responseBody,
- response.Content.Headers.ContentMD5,
- false,
- null);
- }
- }
- }
-
- [OuterLoop("Uses external server")]
- [Theory, MemberData(nameof(RemoteServersMemberData))]
- public async Task GetStreamAsync_ReadZeroBytes_Success(Configuration.Http.RemoteServer remoteServer)
- {
- using (HttpClient client = CreateHttpClientForRemoteServer(remoteServer))
- using (Stream stream = await client.GetStreamAsync(remoteServer.EchoUri))
- {
- Assert.Equal(0, stream.Read(new byte[1], 0, 0));
- Assert.Equal(0, stream.Read(new Span<byte>(new byte[1], 0, 0)));
- Assert.Equal(0, await stream.ReadAsync(new byte[1], 0, 0));
- }
- }
-
- [OuterLoop("Uses external server")]
- [Theory, MemberData(nameof(RemoteServersMemberData))]
- public async Task ReadAsStreamAsync_Cancel_TaskIsCanceled(Configuration.Http.RemoteServer remoteServer)
- {
- var cts = new CancellationTokenSource();
-
- using (HttpClient client = CreateHttpClientForRemoteServer(remoteServer))
- using (HttpResponseMessage response =
- await client.GetAsync(remoteServer.EchoUri, HttpCompletionOption.ResponseHeadersRead))
- using (Stream stream = await response.Content.ReadAsStreamAsync())
- {
- var buffer = new byte[2048];
- Task task = stream.ReadAsync(buffer, 0, buffer.Length, cts.Token);
- cts.Cancel();
-
- // Verify that the task completed.
- Assert.True(((IAsyncResult)task).AsyncWaitHandle.WaitOne(new TimeSpan(0, 5, 0)));
- Assert.True(task.IsCompleted, "Task was not yet completed");
-
- if (task.IsFaulted)
- {
- // Propagate exception for debugging
- task.GetAwaiter().GetResult();
- }
-
- Assert.True(
- task.Status == TaskStatus.RanToCompletion ||
- task.Status == TaskStatus.Canceled);
- }
- }
-
- [Theory]
- [InlineData(TransferType.ContentLength, TransferError.ContentLengthTooLarge)]
- [InlineData(TransferType.Chunked, TransferError.MissingChunkTerminator)]
- [InlineData(TransferType.Chunked, TransferError.ChunkSizeTooLarge)]
- public async Task ReadAsStreamAsync_InvalidServerResponse_ThrowsIOException(
- TransferType transferType,
- TransferError transferError)
- {
- await StartTransferTypeAndErrorServer(transferType, transferError, async uri =>
- {
- await Assert.ThrowsAsync<IOException>(() => ReadAsStreamHelper(uri));
- });
- }
-
- [Theory]
- [InlineData(TransferType.None, TransferError.None)]
- [InlineData(TransferType.ContentLength, TransferError.None)]
- [InlineData(TransferType.Chunked, TransferError.None)]
- public async Task ReadAsStreamAsync_ValidServerResponse_Success(
- TransferType transferType,
- TransferError transferError)
- {
- await StartTransferTypeAndErrorServer(transferType, transferError, async uri =>
- {
- await ReadAsStreamHelper(uri);
- });
- }
-
- public enum TransferType
- {
- None = 0,
- ContentLength,
- Chunked
- }
-
- public enum TransferError
- {
- None = 0,
- ContentLengthTooLarge,
- ChunkSizeTooLarge,
- MissingChunkTerminator
- }
-
- public static Task StartTransferTypeAndErrorServer(
- TransferType transferType,
- TransferError transferError,
- Func<Uri, Task> clientFunc)
- {
- return LoopbackServer.CreateClientAndServerAsync(
- clientFunc,
- server => server.AcceptConnectionAsync(async connection =>
- {
- // Read past request headers.
- await connection.ReadRequestHeaderAsync();
-
- // Determine response transfer headers.
- string transferHeader = null;
- string content = "This is some response content.";
- if (transferType == TransferType.ContentLength)
- {
- transferHeader = transferError == TransferError.ContentLengthTooLarge ?
- $"Content-Length: {content.Length + 42}\r\n" :
- $"Content-Length: {content.Length}\r\n";
- }
- else if (transferType == TransferType.Chunked)
- {
- transferHeader = "Transfer-Encoding: chunked\r\n";
- }
-
- // Write response header
- TextWriter writer = connection.Writer;
- await writer.WriteAsync("HTTP/1.1 200 OK\r\n").ConfigureAwait(false);
- await writer.WriteAsync($"Date: {DateTimeOffset.UtcNow:R}\r\n").ConfigureAwait(false);
- await writer.WriteAsync("Content-Type: text/plain\r\n").ConfigureAwait(false);
- if (!string.IsNullOrEmpty(transferHeader))
- {
- await writer.WriteAsync(transferHeader).ConfigureAwait(false);
- }
- await writer.WriteAsync("\r\n").ConfigureAwait(false);
-
- // Write response body
- if (transferType == TransferType.Chunked)
- {
- string chunkSizeInHex = string.Format(
- "{0:x}\r\n",
- content.Length + (transferError == TransferError.ChunkSizeTooLarge ? 42 : 0));
- await writer.WriteAsync(chunkSizeInHex).ConfigureAwait(false);
- await writer.WriteAsync($"{content}\r\n").ConfigureAwait(false);
- if (transferError != TransferError.MissingChunkTerminator)
- {
- await writer.WriteAsync("0\r\n\r\n").ConfigureAwait(false);
- }
- }
- else
- {
- await writer.WriteAsync($"{content}").ConfigureAwait(false);
- }
- }));
- }
-
- private async Task ReadAsStreamHelper(Uri serverUri)
- {
- using (HttpClient client = CreateHttpClient())
- {
- using (var response = await client.GetAsync(
- serverUri,
- HttpCompletionOption.ResponseHeadersRead))
- using (var stream = await response.Content.ReadAsStreamAsync())
- {
- var buffer = new byte[1];
- while (await stream.ReadAsync(buffer, 0, 1) > 0) ;
- }
- }
- }
- }
-}
+++ /dev/null
-// 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.Test.Common;
-using System.Security.Authentication;
-using System.Threading.Tasks;
-
-using Xunit;
-using Xunit.Abstractions;
-
-namespace System.Net.Http.Functional.Tests
-{
- [ActiveIssue("https://github.com/dotnet/corefx/issues/26539")] // Flaky test
- public abstract class SchSendAuxRecordHttpTest : HttpClientHandlerTestBase
- {
- public SchSendAuxRecordHttpTest(ITestOutputHelper output) : base(output) { }
-
- [Fact]
- [PlatformSpecific(TestPlatforms.Windows)]
- public async Task HttpClient_ClientUsesAuxRecord_Ok()
- {
- var options = new HttpsTestServer.Options();
- options.AllowedProtocols = SslProtocols.Tls;
-
- using (var server = new HttpsTestServer(options))
- using (HttpClientHandler handler = CreateHttpClientHandler())
- using (HttpClient client = CreateHttpClient(handler))
- {
- handler.ServerCertificateCustomValidationCallback = TestHelper.AllowAllCertificates;
- server.Start();
-
- var tasks = new Task[2];
-
- bool serverAuxRecordDetected = false;
- bool serverAuxRecordDetectedInconclusive = false;
- int serverTotalBytesReceived = 0;
- int serverChunks = 0;
-
- tasks[0] = server.AcceptHttpsClientAsync((requestString) =>
- {
- serverTotalBytesReceived += requestString.Length;
-
- if (serverTotalBytesReceived == 1 && serverChunks == 0)
- {
- serverAuxRecordDetected = true;
- }
-
- serverChunks++;
-
- // Test is inconclusive if any non-CBC cipher is used:
- if (server.Stream.CipherAlgorithm == CipherAlgorithmType.None ||
- server.Stream.CipherAlgorithm == CipherAlgorithmType.Null ||
- server.Stream.CipherAlgorithm == CipherAlgorithmType.Rc4)
- {
- serverAuxRecordDetectedInconclusive = true;
- }
-
- if (serverTotalBytesReceived < 5)
- {
- return Task.FromResult<string>(null);
- }
- else
- {
- return Task.FromResult(HttpsTestServer.Options.DefaultResponseString);
- }
- });
-
- string requestUriString = "https://localhost:" + server.Port.ToString();
- tasks[1] = client.GetStringAsync(requestUriString);
-
- await tasks.WhenAllOrAnyFailed(15 * 1000);
-
- if (serverAuxRecordDetectedInconclusive)
- {
- _output.WriteLine("Test inconclusive: The Operating system preferred a non-CBC or Null cipher.");
- }
- else
- {
- Assert.True(serverAuxRecordDetected, "Server reports: Client auxiliary record not detected.");
- }
- }
- }
- }
-}
+++ /dev/null
-// 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;
-using System.IO;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace System.Net.Http.Functional.Tests
-{
- public sealed class SyncBlockingContent : StringContent
- {
- byte[] _content;
-
- public SyncBlockingContent(string content) : base(content)
- {
- _content = Encoding.UTF8.GetBytes(content);
- }
-
- protected override Task SerializeToStreamAsync(Stream stream, TransportContext context)
- {
- stream.Write(_content, 0, _content.Length);
- return Task.CompletedTask;
- }
- }
-}
<Compile Include="$(CommonTestPath)System\Net\Http\GenericLoopbackServer.cs">
<Link>Common\System\Net\Http\GenericLoopbackServer.cs</Link>
</Compile>
+ <Compile Include="$(CommonTestPath)System\Net\Http\HttpClientHandlerTestBase.cs">
+ <Link>Common\System\Net\Http\HttpClientHandlerTestBase.cs</Link>
+ </Compile>
+ <Compile Include="$(CommonTestPath)System\Net\Http\TestHelper.cs">
+ <Link>Common\System\Net\Http\TestHelper.cs</Link>
+ </Compile>
<Compile Include="$(CommonTestPath)System\Threading\TrackingSynchronizationContext.cs">
<Link>Common\System\Threading\TrackingSynchronizationContext.cs</Link>
</Compile>
</Compile>
<Compile Include="AssemblyInfo.cs" />
<Compile Include="ByteArrayContentTest.cs" />
- <Compile Include="ChannelBindingAwareContent.cs" />
- <Compile Include="DribbleStream.cs" />
- <Compile Include="CustomContent.cs" />
+ <Compile Include="$(CommonTestPath)System\Net\Http\ChannelBindingAwareContent.cs">
+ <Link>Common\System\Net\Http\ChannelBindingAwareContent.cs</Link>
+ </Compile>
+ <Compile Include="$(CommonTestPath)System\Net\Http\DribbleStream.cs">
+ <Link>Common\System\Net\Http\DribbleStream.cs</Link>
+ </Compile>
+ <Compile Include="$(CommonTestPath)System\Net\Http\CustomContent.cs">
+ <Link>Common\System\Net\Http\CustomContent.cs</Link>
+ </Compile>
<Compile Include="DelegatingHandlerTest.cs" />
<Compile Include="FakeDiagnosticSourceListenerObserver.cs" />
<Compile Include="FormUrlEncodedContentTest.cs" />
- <Compile Include="ByteAtATimeContent.cs" />
- <Compile Include="HttpClientHandlerTest.cs" />
- <Compile Include="HttpClientHandlerTest.Asynchrony.cs" />
- <Compile Include="HttpClientHandlerTest.Authentication.cs" />
- <Compile Include="HttpClientHandlerTest.AutoRedirect.cs" />
- <Compile Include="HttpClientHandlerTest.Cancellation.cs" />
- <Compile Include="HttpClientHandlerTest.ClientCertificates.cs" />
- <Compile Include="HttpClientHandlerTest.Cookies.cs" />
- <Compile Include="HttpClientHandlerTest.DefaultProxyCredentials.cs" />
+ <Compile Include="$(CommonTestPath)System\Net\Http\ByteAtATimeContent.cs">
+ <Link>Common\System\Net\Http\ByteAtATimeContent.cs</Link>
+ </Compile>
+ <Compile Include="$(CommonTestPath)System\Net\Http\HttpClientHandlerTest.cs">
+ <Link>Common\System\Net\Http\HttpClientHandlerTest.cs</Link>
+ </Compile>
+ <Compile Include="$(CommonTestPath)System\Net\Http\HttpClientHandlerTest.Asynchrony.cs">
+ <Link>Common\System\Net\Http\HttpClientHandlerTest.Asynchrony.cs</Link>
+ </Compile>
+ <Compile Include="$(CommonTestPath)System\Net\Http\HttpClientHandlerTest.Authentication.cs">
+ <Link>Common\System\Net\Http\HttpClientHandlerTest.Authentication.cs</Link>
+ </Compile>
+ <Compile Include="$(CommonTestPath)System\Net\Http\HttpClientHandlerTest.AutoRedirect.cs">
+ <Link>Common\System\Net\Http\HttpClientHandlerTest.AutoRedirect.cs</Link>
+ </Compile>
+ <Compile Include="$(CommonTestPath)System\Net\Http\HttpClientHandlerTest.Cancellation.cs">
+ <Link>Common\System\Net\Http\HttpClientHandlerTest.Cancellation.cs</Link>
+ </Compile>
+ <Compile Include="$(CommonTestPath)System\Net\Http\HttpClientHandlerTest.ClientCertificates.cs">
+ <Link>Common\System\Net\Http\HttpClientHandlerTest.ClientCertificates.cs</Link>
+ </Compile>
+ <Compile Include="$(CommonTestPath)System\Net\Http\HttpClientHandlerTest.Cookies.cs">
+ <Link>Common\System\Net\Http\HttpClientHandlerTest.Cookies.cs</Link>
+ </Compile>
+ <Compile Include="$(CommonTestPath)System\Net\Http\HttpClientHandlerTest.DefaultProxyCredentials.cs">
+ <Link>Common\System\Net\Http\HttpClientHandlerTest.DefaultProxyCredentials.cs</Link>
+ </Compile>
<Compile Include="HttpClientHandlerTest.Finalization.cs" />
<Compile Include="HttpClientHandlerTest.Headers.cs" />
- <Compile Include="HttpClientHandlerTest.MaxConnectionsPerServer.cs" />
- <Compile Include="HttpClientHandlerTest.MaxResponseHeadersLength.cs" />
- <Compile Include="HttpClientHandlerTest.Proxy.cs" />
+ <Compile Include="$(CommonTestPath)System\Net\Http\HttpClientHandlerTest.MaxConnectionsPerServer.cs">
+ <Link>Common\System\Net\Http\HttpClientHandlerTest.MaxConnectionsPerServer.cs</Link>
+ </Compile>
+ <Compile Include="$(CommonTestPath)System\Net\Http\HttpClientHandlerTest.MaxResponseHeadersLength.cs">
+ <Link>Common\System\Net\Http\HttpClientHandlerTest.MaxResponseHeadersLength.cs</Link>
+ </Compile>
+ <Compile Include="$(CommonTestPath)System\Net\Http\HttpClientHandlerTest.Proxy.cs">
+ <Link>Common\System\Net\Http\HttpClientHandlerTest.Proxy.cs</Link>
+ </Compile>
<Compile Include="HttpClientHandlerTest.ResponseDrain.cs" />
- <Compile Include="HttpClientHandlerTest.ServerCertificates.cs" />
- <Compile Include="HttpClientHandlerTest.SslProtocols.cs" />
+ <Compile Include="$(CommonTestPath)System\Net\Http\HttpClientHandlerTest.ServerCertificates.cs">
+ <Link>Common\System\Net\Http\HttpClientHandlerTest.ServerCertificates.cs</Link>
+ </Compile>
+ <Compile Include="$(CommonTestPath)System\Net\Http\HttpClientHandlerTest.SslProtocols.cs">
+ <Link>Common\System\Net\Http\HttpClientHandlerTest.SslProtocols.cs</Link>
+ </Compile>
+ <Compile Include="HttpClientHandlerTestBase.SocketsHttpHandler.cs" />
<Compile Include="DiagnosticsTests.cs" />
- <Compile Include="HttpClientHandlerTestBase.cs" />
<Compile Include="HttpClientTest.cs" />
- <Compile Include="HttpClientEKUTest.cs" />
- <Compile Include="HttpClient.SelectedSitesTest.cs" />
+ <Compile Include="$(CommonTestPath)System\Net\Http\HttpClientEKUTest.cs">
+ <Link>Common\System\Net\Http\HttpClientEKUTest.cs</Link>
+ </Compile>
+ <Compile Include="$(CommonTestPath)System\Net\Http\HttpClient.SelectedSitesTest.cs">
+ <Link>Common\System\Net\Http\HttpClient.SelectedSitesTest.cs</Link>
+ </Compile>
<Compile Include="HttpConnectionKeyTest.cs" />
<Compile Include="HttpContentTest.cs" />
<Compile Include="HttpMessageInvokerTest.cs" />
<Compile Include="HttpMethodTest.cs" />
- <Compile Include="HttpRetryProtocolTests.cs" />
- <Compile Include="IdnaProtocolTests.cs" />
- <Compile Include="HttpProtocolTests.cs" />
+ <Compile Include="$(CommonTestPath)System\Net\Http\HttpRetryProtocolTests.cs">
+ <Link>Common\System\Net\Http\HttpRetryProtocolTests.cs</Link>
+ </Compile>
+ <Compile Include="$(CommonTestPath)System\Net\Http\IdnaProtocolTests.cs">
+ <Link>Common\System\Net\Http\IdnaProtocolTests.cs</Link>
+ </Compile>
+ <Compile Include="$(CommonTestPath)System\Net\Http\HttpProtocolTests.cs">
+ <Link>Common\System\Net\Http\HttpProtocolTests.cs</Link>
+ </Compile>
<Compile Include="HttpRequestMessageTest.cs" />
<Compile Include="HttpResponseMessageTest.cs" />
<Compile Include="MessageProcessingHandlerTest.cs" />
<Compile Include="MultipartContentTest.cs" />
<Compile Include="MultipartFormDataContentTest.cs" />
- <Compile Include="PlatformHandlerTest.cs" />
- <Compile Include="PostScenarioTest.cs" />
- <Compile Include="RepeatedFlushContent.cs" />
- <Compile Include="ResponseStreamTest.cs" />
- <Compile Include="SchSendAuxRecordHttpTest.cs" />
+ <Compile Include="$(CommonTestPath)System\Net\Http\PostScenarioTest.cs">
+ <Link>Common\System\Net\Http\PostScenarioTest.cs</Link>
+ </Compile>
+ <Compile Include="$(CommonTestPath)System\Net\Http\RepeatedFlushContent.cs">
+ <Link>Common\System\Net\Http\RepeatedFlushContent.cs</Link>
+ </Compile>
+ <Compile Include="$(CommonTestPath)System\Net\Http\ResponseStreamTest.cs">
+ <Link>Common\System\Net\Http\ResponseStreamTest.cs</Link>
+ </Compile>
+ <Compile Include="$(CommonTestPath)System\Net\Http\SchSendAuxRecordHttpTest.cs">
+ <Link>Common\System\Net\Http\SchSendAuxRecordHttpTest.cs</Link>
+ </Compile>
<Compile Include="StreamContentTest.cs" />
<Compile Include="StringContentTest.cs" />
- <Compile Include="SyncBlockingContent.cs" />
- <Compile Include="TestHelper.cs" />
- <Compile Include="DefaultCredentialsTest.cs" />
+ <Compile Include="$(CommonTestPath)System\Net\Http\SyncBlockingContent.cs">
+ <Link>Common\System\Net\Http\SyncBlockingContent.cs</Link>
+ </Compile>
+ <Compile Include="$(CommonTestPath)System\Net\Http\DefaultCredentialsTest.cs">
+ <Link>Common\System\Net\Http\DefaultCredentialsTest.cs</Link>
+ </Compile>
<Compile Include="ThrowingContent.cs" />
<Compile Include="Watchdog.cs" />
</ItemGroup>
<Compile Include="HPackTest.cs" />
<Compile Include="HttpClientHandlerTest.Http1.cs" />
<Compile Include="HttpClientHandlerTest.Http2.cs" />
- <Compile Include="HttpClientHandlerTest.AcceptAllCerts.cs" />
- <Compile Include="HttpClientHandlerTest.Decompression.cs" />
+ <Compile Include="$(CommonTestPath)System\Net\Http\HttpClientHandlerTest.AcceptAllCerts.cs">
+ <Link>Common\System\Net\Http\HttpClientHandlerTest.AcceptAllCerts.cs</Link>
+ </Compile>
+ <Compile Include="$(CommonTestPath)System\Net\Http\HttpClientHandlerTest.Decompression.cs">
+ <Link>Common\System\Net\Http\HttpClientHandlerTest.Decompression.cs</Link>
+ </Compile>
<Compile Include="HttpClientMiniStressTest.cs" />
<Compile Include="NtAuthTests.cs" />
<Compile Include="ReadOnlyMemoryContentTest.cs" />
+++ /dev/null
-// 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.Collections.Generic;
-using System.Linq;
-using System.Net.NetworkInformation;
-using System.Net.Security;
-using System.Net.Test.Common;
-using System.Reflection;
-using System.Runtime.InteropServices;
-using System.Security.Cryptography;
-using System.Security.Cryptography.X509Certificates;
-using System.Text;
-using System.Threading.Tasks;
-using Xunit;
-
-namespace System.Net.Http.Functional.Tests
-{
- public static class TestHelper
- {
- public static int PassingTestTimeoutMilliseconds => 60 * 1000;
- public static bool JsonMessageContainsKeyValue(string message, string key, string value)
- {
- // Deal with JSON encoding of '\' and '"' in value
- value = value.Replace("\\", "\\\\").Replace("\"", "\\\"");
-
- // In HTTP2, all header names are in lowercase. So accept either the original header name or the lowercase version.
- return message.Contains($"\"{key}\": \"{value}\"") ||
- message.Contains($"\"{key.ToLowerInvariant()}\": \"{value}\"");
- }
-
- public static bool JsonMessageContainsKey(string message, string key)
- {
- return JsonMessageContainsKeyValue(message, key, "");
- }
-
- public static void VerifyResponseBody(
- string responseContent,
- byte[] expectedMD5Hash,
- bool chunkedUpload,
- string requestBody)
- {
- // Verify that response body from the server was corrected received by comparing MD5 hash.
- byte[] actualMD5Hash = ComputeMD5Hash(responseContent);
- Assert.Equal(expectedMD5Hash, actualMD5Hash);
-
- // Verify upload semantics: 'Content-Length' vs. 'Transfer-Encoding: chunked'.
- if (requestBody != null)
- {
- bool requestUsedContentLengthUpload =
- JsonMessageContainsKeyValue(responseContent, "Content-Length", requestBody.Length.ToString());
- bool requestUsedChunkedUpload =
- JsonMessageContainsKeyValue(responseContent, "Transfer-Encoding", "chunked");
- if (requestBody.Length > 0)
- {
- Assert.NotEqual(requestUsedContentLengthUpload, requestUsedChunkedUpload);
- Assert.Equal(chunkedUpload, requestUsedChunkedUpload);
- Assert.Equal(!chunkedUpload, requestUsedContentLengthUpload);
- }
-
- // Verify that request body content was correctly sent to server.
- Assert.True(JsonMessageContainsKeyValue(responseContent, "BodyContent", requestBody), "Valid request body");
- }
- }
-
- public static void VerifyRequestMethod(HttpResponseMessage response, string expectedMethod)
- {
- IEnumerable<string> values = response.Headers.GetValues("X-HttpRequest-Method");
- foreach (string value in values)
- {
- Assert.Equal(expectedMethod, value);
- }
- }
-
- public static byte[] ComputeMD5Hash(string data)
- {
- return ComputeMD5Hash(Encoding.UTF8.GetBytes(data));
- }
-
- public static byte[] ComputeMD5Hash(byte[] data)
- {
- using (MD5 md5 = MD5.Create())
- {
- return md5.ComputeHash(data);
- }
- }
-
- public static Task WhenAllCompletedOrAnyFailed(params Task[] tasks)
- {
- return TaskTimeoutExtensions.WhenAllOrAnyFailed(tasks, PlatformDetection.IsArmProcess || PlatformDetection.IsArm64Process ? PassingTestTimeoutMilliseconds * 5 : PassingTestTimeoutMilliseconds);
- }
-
- public static Task WhenAllCompletedOrAnyFailedWithTimeout(int timeoutInMilliseconds, params Task[] tasks)
- {
- return TaskTimeoutExtensions.WhenAllOrAnyFailed(tasks, timeoutInMilliseconds);
- }
-
-#if NETCOREAPP
- public static Func<HttpRequestMessage, X509Certificate2, X509Chain, SslPolicyErrors, bool> AllowAllCertificates = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator;
-#else
- public static Func<HttpRequestMessage, X509Certificate2, X509Chain, SslPolicyErrors, bool> AllowAllCertificates = (_, __, ___, ____) => true;
-#endif
-
- public static IPAddress GetIPv6LinkLocalAddress() =>
- NetworkInterface
- .GetAllNetworkInterfaces()
- .Where(i => !i.Description.StartsWith("PANGP Virtual Ethernet")) // This is a VPN adapter, but is reported as a regular Ethernet interface with
- // a valid link-local address, but the link-local address doesn't actually work.
- // So just manually filter it out.
- .SelectMany(i => i.GetIPProperties().UnicastAddresses)
- .Select(a => a.Address)
- .Where(a => a.IsIPv6LinkLocal)
- .FirstOrDefault();
-
- public static void EnableUnencryptedHttp2IfNecessary(HttpClientHandler handler)
- {
- if (PlatformDetection.SupportsAlpn && !Capability.Http2ForceUnencryptedLoopback())
- {
- return;
- }
-
- FieldInfo socketsHttpHandlerField = typeof(HttpClientHandler).GetField("_socketsHttpHandler", BindingFlags.NonPublic | BindingFlags.Instance);
- if (socketsHttpHandlerField == null)
- {
- // Not using .NET Core implementation, i.e. could be .NET Framework.
- return;
- }
-
- object socketsHttpHandler = socketsHttpHandlerField.GetValue(handler);
- if (socketsHttpHandler == null)
- {
- // Not using SocketsHttpHandler, e.g. using WinHttpHandler.
- return;
- }
-
- // Get HttpConnectionSettings object from SocketsHttpHandler.
- Type socketsHttpHandlerType = typeof(HttpClientHandler).Assembly.GetType("System.Net.Http.SocketsHttpHandler");
- FieldInfo settingsField = socketsHttpHandlerType.GetField("_settings", BindingFlags.NonPublic | BindingFlags.Instance);
- Assert.NotNull(settingsField);
- object settings = settingsField.GetValue(socketsHttpHandler);
- Assert.NotNull(settings);
-
- // Allow HTTP/2.0 via unencrypted socket if ALPN is not supported on platform.
- Type httpConnectionSettingsType = typeof(HttpClientHandler).Assembly.GetType("System.Net.Http.HttpConnectionSettings");
- FieldInfo allowUnencryptedHttp2Field = httpConnectionSettingsType.GetField("_allowUnencryptedHttp2", BindingFlags.NonPublic | BindingFlags.Instance);
- Assert.NotNull(allowUnencryptedHttp2Field);
- allowUnencryptedHttp2Field.SetValue(settings, true);
- }
-
- public static byte[] GenerateRandomContent(int size)
- {
- byte[] data = new byte[size];
- new Random(42).NextBytes(data);
- return data;
- }
- }
-}