|
// 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.ComponentModel;
using System.Diagnostics.Contracts;
using System.Globalization;
using System.IdentityModel.Selectors;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Net.Security;
using System.Runtime;
using System.Runtime.CompilerServices;
using System.Security.Cryptography;
using System.Security.Principal;
using System.ServiceModel.Description;
using System.ServiceModel.Diagnostics;
using System.ServiceModel.Security;
using System.ServiceModel.Security.Tokens;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using SecurityUtils = System.ServiceModel.Security.SecurityUtils;
namespace System.ServiceModel.Channels
{
internal class HttpChannelFactory<TChannel> : TransportChannelFactory<TChannel>
{
private static CacheControlHeaderValue s_requestCacheHeader = new CacheControlHeaderValue { NoCache = true, MaxAge = new TimeSpan(0) };
private HttpCookieContainerManager _httpCookieContainerManager;
// Double-checked locking pattern requires volatile for read/write synchronization
private volatile MruCache<Uri, Uri> _credentialCacheUriPrefixCache;
private volatile MruCache<string, string> _credentialHashCache;
private volatile MruCache<string, HttpClient> _httpClientCache;
private SecurityCredentialsManager _channelCredentials;
private ISecurityCapabilities _securityCapabilities;
private Func<HttpClientHandler, HttpMessageHandler> _httpMessageHandlerFactory;
private Lazy<string> _webSocketSoapContentType;
private SHA512 _hashAlgorithm;
private bool _keepAliveEnabled;
internal HttpChannelFactory(HttpTransportBindingElement bindingElement, BindingContext context)
: base(bindingElement, context, HttpTransportDefaults.GetDefaultMessageEncoderFactory())
{
// validate setting interactions
if (bindingElement.TransferMode == TransferMode.Buffered)
{
if (bindingElement.MaxReceivedMessageSize > int.MaxValue)
{
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(
new ArgumentOutOfRangeException("bindingElement.MaxReceivedMessageSize",
SR.MaxReceivedMessageSizeMustBeInIntegerRange));
}
if (bindingElement.MaxBufferSize != bindingElement.MaxReceivedMessageSize)
{
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument("bindingElement",
SR.MaxBufferSizeMustMatchMaxReceivedMessageSize);
}
}
else
{
if (bindingElement.MaxBufferSize > bindingElement.MaxReceivedMessageSize)
{
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument("bindingElement",
SR.MaxBufferSizeMustNotExceedMaxReceivedMessageSize);
}
}
if (TransferModeHelper.IsRequestStreamed(bindingElement.TransferMode) &&
bindingElement.AuthenticationScheme != AuthenticationSchemes.Anonymous)
{
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument("bindingElement",
SR.HttpAuthDoesNotSupportRequestStreaming);
}
AllowCookies = bindingElement.AllowCookies;
if (AllowCookies)
{
_httpCookieContainerManager = new HttpCookieContainerManager();
}
if (!bindingElement.AuthenticationScheme.IsSingleton() && bindingElement.AuthenticationScheme != AuthenticationSchemes.IntegratedWindowsAuthentication)
{
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument("value", SR.Format(SR.HttpRequiresSingleAuthScheme,
bindingElement.AuthenticationScheme));
}
AuthenticationScheme = bindingElement.AuthenticationScheme;
DecompressionEnabled = bindingElement.DecompressionEnabled;
MaxBufferSize = bindingElement.MaxBufferSize;
TransferMode = bindingElement.TransferMode;
_keepAliveEnabled = bindingElement.KeepAliveEnabled;
if (bindingElement.Proxy != null)
{
Proxy = bindingElement.Proxy;
}
else if (bindingElement.ProxyAddress != null)
{
if (bindingElement.UseDefaultWebProxy)
{
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SR.UseDefaultWebProxyCantBeUsedWithExplicitProxyAddress));
}
if (bindingElement.ProxyAuthenticationScheme == AuthenticationSchemes.Anonymous)
{
Proxy = new WebProxy(bindingElement.ProxyAddress, bindingElement.BypassProxyOnLocal);
}
else
{
Proxy = null;
ProxyFactory =
new WebProxyFactory(bindingElement.ProxyAddress, bindingElement.BypassProxyOnLocal,
bindingElement.ProxyAuthenticationScheme);
}
}
else if (!bindingElement.UseDefaultWebProxy)
{
Proxy = new WebProxy();
}
_channelCredentials = context.BindingParameters.Find<SecurityCredentialsManager>();
_securityCapabilities = bindingElement.GetProperty<ISecurityCapabilities>(context);
_httpMessageHandlerFactory = context.BindingParameters.Find<Func<HttpClientHandler, HttpMessageHandler>>();
WebSocketSettings = WebSocketHelper.GetRuntimeWebSocketSettings(bindingElement.WebSocketSettings);
_webSocketSoapContentType = new Lazy<string>(() => MessageEncoderFactory.CreateSessionEncoder().ContentType, LazyThreadSafetyMode.ExecutionAndPublication);
_httpClientCache = bindingElement.GetProperty<MruCache<string, HttpClient>>(context);
}
public bool AllowCookies { get; }
public AuthenticationSchemes AuthenticationScheme { get; }
public bool DecompressionEnabled { get; }
public virtual bool IsChannelBindingSupportEnabled
{
get
{
return false;
}
}
public SecurityTokenManager SecurityTokenManager { get; private set; }
public int MaxBufferSize { get; }
internal IWebProxy Proxy { get; set; }
internal WebProxyFactory ProxyFactory { get; set; }
public TransferMode TransferMode { get; }
public override string Scheme
{
get
{
return UriEx.UriSchemeHttp;
}
}
public WebSocketTransportSettings WebSocketSettings { get; }
internal string WebSocketSoapContentType
{
get
{
return _webSocketSoapContentType.Value;
}
}
private HashAlgorithm HashAlgorithm
{
get
{
if (_hashAlgorithm == null)
{
_hashAlgorithm = SHA512.Create();
}
else
{
_hashAlgorithm.Initialize();
}
return _hashAlgorithm;
}
}
private bool AuthenticationSchemeMayRequireResend()
{
return AuthenticationScheme != AuthenticationSchemes.Anonymous;
}
public override T GetProperty<T>()
{
if (typeof(T) == typeof(ISecurityCapabilities))
{
return (T)(object)_securityCapabilities;
}
if (typeof(T) == typeof(IHttpCookieContainerManager))
{
return (T)(object)GetHttpCookieContainerManager();
}
return base.GetProperty<T>();
}
internal HttpCookieContainerManager GetHttpCookieContainerManager()
{
return _httpCookieContainerManager;
}
internal Uri GetCredentialCacheUriPrefix(Uri via)
{
Uri result;
if (_credentialCacheUriPrefixCache == null)
{
lock (ThisLock)
{
if (_credentialCacheUriPrefixCache == null)
{
_credentialCacheUriPrefixCache = new MruCache<Uri, Uri>(10);
}
}
}
lock (_credentialCacheUriPrefixCache)
{
if (!_credentialCacheUriPrefixCache.TryGetValue(via, out result))
{
result = new UriBuilder(via.Scheme, via.Host, via.Port).Uri;
_credentialCacheUriPrefixCache.Add(via, result);
}
}
return result;
}
internal async Task<HttpClient> GetHttpClientAsync(EndpointAddress to, Uri via,
SecurityTokenProviderContainer tokenProvider, SecurityTokenProviderContainer proxyTokenProvider,
SecurityTokenContainer clientCertificateToken, TimeSpan timeout)
{
(NetworkCredential credential, TokenImpersonationLevel impersonationLevel, AuthenticationLevel authenticationLevel) = await HttpChannelUtilities.GetCredentialAsync(AuthenticationScheme,
tokenProvider, timeout);
HttpClient httpClient;
string connectionGroupName = GetConnectionGroupName(credential, authenticationLevel, impersonationLevel, clientCertificateToken);
X509CertificateEndpointIdentity remoteCertificateIdentity = to.Identity as X509CertificateEndpointIdentity;
if (remoteCertificateIdentity != null)
{
connectionGroupName = string.Format(CultureInfo.InvariantCulture, "{0}[{1}]", connectionGroupName,
remoteCertificateIdentity.Certificates[0].Thumbprint);
}
connectionGroupName = connectionGroupName ?? string.Empty;
bool foundHttpClient;
lock (_httpClientCache)
{
foundHttpClient = _httpClientCache.TryGetValue(connectionGroupName, out httpClient);
}
if (!foundHttpClient)
{
var clientHandler = GetHttpClientHandler(to, clientCertificateToken);
if (DecompressionEnabled)
{
clientHandler.AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip;
}
else
{
clientHandler.AutomaticDecompression = DecompressionMethods.None;
}
if (clientHandler.SupportsProxy)
{
if (Proxy != null)
{
clientHandler.Proxy = Proxy;
clientHandler.UseProxy = true;
}
else if (ProxyFactory != null)
{
clientHandler.Proxy = await ProxyFactory.CreateWebProxyAsync(authenticationLevel,
impersonationLevel, proxyTokenProvider, timeout);
clientHandler.UseProxy = true;
}
}
clientHandler.UseCookies = AllowCookies;
if (AllowCookies)
{
clientHandler.CookieContainer = _httpCookieContainerManager.CookieContainer;
}
clientHandler.PreAuthenticate = true;
clientHandler.UseDefaultCredentials = false;
if (credential == CredentialCache.DefaultCredentials || credential == null)
{
if (AuthenticationScheme != AuthenticationSchemes.Anonymous)
{
clientHandler.UseDefaultCredentials = true;
}
}
else
{
if (Fx.IsUap)
{
clientHandler.Credentials = credential;
}
else
{
CredentialCache credentials = new CredentialCache();
Uri credentialCacheUriPrefix = GetCredentialCacheUriPrefix(via);
if (AuthenticationScheme == AuthenticationSchemes.IntegratedWindowsAuthentication)
{
credentials.Add(credentialCacheUriPrefix, AuthenticationSchemesHelper.ToString(AuthenticationSchemes.Negotiate),
credential);
credentials.Add(credentialCacheUriPrefix, AuthenticationSchemesHelper.ToString(AuthenticationSchemes.Ntlm),
credential);
}
else
{
credentials.Add(credentialCacheUriPrefix, AuthenticationSchemesHelper.ToString(AuthenticationScheme),
credential);
}
clientHandler.Credentials = credentials;
}
}
HttpMessageHandler handler = clientHandler;
if (_httpMessageHandlerFactory != null)
{
handler = _httpMessageHandlerFactory(clientHandler);
}
httpClient = new HttpClient(handler);
if (!_keepAliveEnabled)
{
httpClient.DefaultRequestHeaders.ConnectionClose = true;
}
if (IsExpectContinueHeaderRequired && !Fx.IsUap)
{
httpClient.DefaultRequestHeaders.ExpectContinue = true;
}
// We provide our own CancellationToken for each request. Setting HttpClient.Timeout to -1
// prevents a call to CancellationToken.CancelAfter that HttpClient does internally which
// causes TimerQueue contention at high load.
httpClient.Timeout = Timeout.InfiniteTimeSpan;
lock (_httpClientCache)
{
HttpClient tempHttpClient;
if (_httpClientCache.TryGetValue(connectionGroupName, out tempHttpClient))
{
httpClient.Dispose();
httpClient = tempHttpClient;
}
else
{
_httpClientCache.Add(connectionGroupName, httpClient);
}
}
}
return httpClient;
}
internal virtual bool IsExpectContinueHeaderRequired => AuthenticationSchemeMayRequireResend();
internal virtual HttpClientHandler GetHttpClientHandler(EndpointAddress to, SecurityTokenContainer clientCertificateToken)
{
return new HttpClientHandler();
}
internal ICredentials GetCredentials()
{
ICredentials creds = null;
if (AuthenticationScheme != AuthenticationSchemes.Anonymous)
{
creds = CredentialCache.DefaultCredentials;
ClientCredentials credentials = _channelCredentials as ClientCredentials;
if (credentials != null)
{
switch (AuthenticationScheme)
{
case AuthenticationSchemes.Basic:
if (credentials.UserName.UserName == null)
{
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull(nameof(ClientCredentials.UserName.UserName));
}
if (credentials.UserName.UserName == string.Empty)
{
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument(SR.UserNameCannotBeEmpty);
}
creds = new NetworkCredential(credentials.UserName.UserName, credentials.UserName.Password);
break;
case AuthenticationSchemes.Digest:
if (credentials.HttpDigest.ClientCredential.UserName != string.Empty)
{
creds = credentials.HttpDigest.ClientCredential;
}
break;
case AuthenticationSchemes.Ntlm:
case AuthenticationSchemes.IntegratedWindowsAuthentication:
case AuthenticationSchemes.Negotiate:
if (credentials.Windows.ClientCredential.UserName != string.Empty)
{
creds = credentials.Windows.ClientCredential;
}
break;
}
}
}
return creds;
}
internal Exception CreateToMustEqualViaException(Uri to, Uri via)
{
return new ArgumentException(SR.Format(SR.HttpToMustEqualVia, to, via));
}
public override int GetMaxBufferSize()
{
return MaxBufferSize;
}
private async Task<SecurityTokenProviderContainer> CreateAndOpenTokenProviderAsync(TimeSpan timeout, AuthenticationSchemes authenticationScheme,
EndpointAddress target, Uri via, ChannelParameterCollection channelParameters)
{
SecurityTokenProvider tokenProvider = null;
switch (authenticationScheme)
{
case AuthenticationSchemes.Anonymous:
break;
case AuthenticationSchemes.Basic:
tokenProvider = TransportSecurityHelpers.GetUserNameTokenProvider(SecurityTokenManager, target, via, Scheme, authenticationScheme, channelParameters);
break;
case AuthenticationSchemes.Negotiate:
case AuthenticationSchemes.Ntlm:
case AuthenticationSchemes.IntegratedWindowsAuthentication:
tokenProvider = TransportSecurityHelpers.GetSspiTokenProvider(SecurityTokenManager, target, via, Scheme, authenticationScheme, channelParameters);
break;
case AuthenticationSchemes.Digest:
tokenProvider = TransportSecurityHelpers.GetDigestTokenProvider(SecurityTokenManager, target, via, Scheme, authenticationScheme, channelParameters);
break;
default:
// The setter for this property should prevent this.
throw Fx.AssertAndThrow("CreateAndOpenTokenProvider: Invalid authentication scheme");
}
SecurityTokenProviderContainer result;
if (tokenProvider != null)
{
result = new SecurityTokenProviderContainer(tokenProvider);
await result.OpenAsync(timeout);
}
else
{
result = null;
}
return result;
}
protected virtual void ValidateCreateChannelParameters(EndpointAddress remoteAddress, Uri via)
{
if (string.Compare(via.Scheme, "ws", StringComparison.OrdinalIgnoreCase) != 0)
{
ValidateScheme(via);
}
if (MessageVersion.Addressing == AddressingVersion.None && remoteAddress.Uri != via)
{
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(CreateToMustEqualViaException(remoteAddress.Uri, via));
}
}
protected override TChannel OnCreateChannel(EndpointAddress remoteAddress, Uri via)
{
if (typeof(TChannel) != typeof(IRequestChannel))
{
remoteAddress = remoteAddress != null && !WebSocketHelper.IsWebSocketUri(remoteAddress.Uri) ?
new EndpointAddress(WebSocketHelper.NormalizeHttpSchemeWithWsScheme(remoteAddress.Uri), remoteAddress) :
remoteAddress;
via = !WebSocketHelper.IsWebSocketUri(via) ? WebSocketHelper.NormalizeHttpSchemeWithWsScheme(via) : via;
}
return OnCreateChannelCore(remoteAddress, via);
}
protected virtual TChannel OnCreateChannelCore(EndpointAddress remoteAddress, Uri via)
{
ValidateCreateChannelParameters(remoteAddress, via);
ValidateWebSocketTransportUsage();
if (typeof(TChannel) == typeof(IRequestChannel))
{
return (TChannel)(object)new HttpClientRequestChannel((HttpChannelFactory<IRequestChannel>)(object)this, remoteAddress, via, ManualAddressing);
}
else
{
return (TChannel)(object)new ClientWebSocketTransportDuplexSessionChannel((HttpChannelFactory<IDuplexSessionChannel>)(object)this, remoteAddress, via);
}
}
protected void ValidateWebSocketTransportUsage()
{
Type channelType = typeof(TChannel);
if (channelType == typeof(IRequestChannel) && WebSocketSettings.TransportUsage == WebSocketTransportUsage.Always)
{
throw FxTrace.Exception.AsError(new InvalidOperationException(SR.Format(
SR.WebSocketCannotCreateRequestClientChannelWithCertainWebSocketTransportUsage,
typeof(TChannel),
WebSocketTransportSettings.TransportUsageMethodName,
typeof(WebSocketTransportSettings).Name,
WebSocketSettings.TransportUsage)));
}
if (channelType == typeof(IDuplexSessionChannel))
{
if (WebSocketSettings.TransportUsage == WebSocketTransportUsage.Never)
{
throw FxTrace.Exception.AsError(new InvalidOperationException(SR.Format(
SR.WebSocketCannotCreateRequestClientChannelWithCertainWebSocketTransportUsage,
typeof(TChannel),
WebSocketTransportSettings.TransportUsageMethodName,
typeof(WebSocketTransportSettings).Name,
WebSocketSettings.TransportUsage)));
}
}
}
[MethodImpl(MethodImplOptions.NoInlining)]
private void InitializeSecurityTokenManager()
{
if (_channelCredentials == null)
{
_channelCredentials = ClientCredentials.CreateDefaultCredentials();
}
SecurityTokenManager = _channelCredentials.CreateSecurityTokenManager();
}
protected virtual bool IsSecurityTokenManagerRequired()
{
if (AuthenticationScheme != AuthenticationSchemes.Anonymous)
{
return true;
}
if (ProxyFactory != null && ProxyFactory.AuthenticationScheme != AuthenticationSchemes.Anonymous)
{
return true;
}
else
{
return false;
}
}
protected override IAsyncResult OnBeginOpen(TimeSpan timeout, AsyncCallback callback, object state)
{
return OnOpenAsync(timeout).ToApm(callback, state);
}
protected override void OnEndOpen(IAsyncResult result)
{
result.ToApmEnd();
}
protected override void OnOpen(TimeSpan timeout)
{
if (IsSecurityTokenManagerRequired())
{
InitializeSecurityTokenManager();
}
if (AllowCookies &&
!_httpCookieContainerManager.IsInitialized) // We don't want to overwrite the CookieContainer if someone has set it already.
{
_httpCookieContainerManager.CookieContainer = new CookieContainer();
}
}
internal protected override Task OnOpenAsync(TimeSpan timeout)
{
OnOpen(timeout);
return TaskHelpers.CompletedTask();
}
protected internal override Task OnCloseAsync(TimeSpan timeout)
{
return base.OnCloseAsync(timeout);
}
protected override void OnClosed()
{
base.OnClosed();
if (_httpClientCache != null && !_httpClientCache.IsDisposed)
{
lock (_httpClientCache)
{
_httpClientCache.Dispose();
_httpClientCache = null;
}
}
}
private string AppendWindowsAuthenticationInfo(string inputString, NetworkCredential credential,
AuthenticationLevel authenticationLevel, TokenImpersonationLevel impersonationLevel)
{
return SecurityUtils.AppendWindowsAuthenticationInfo(inputString, credential, authenticationLevel, impersonationLevel);
}
protected virtual string OnGetConnectionGroupPrefix(SecurityTokenContainer clientCertificateToken)
{
return string.Empty;
}
internal static bool IsWindowsAuth(AuthenticationSchemes authScheme)
{
Fx.Assert(authScheme.IsSingleton() || authScheme == AuthenticationSchemes.IntegratedWindowsAuthentication, "authenticationScheme used in an Http(s)ChannelFactory must be a singleton value.");
return authScheme == AuthenticationSchemes.Negotiate ||
authScheme == AuthenticationSchemes.Ntlm ||
authScheme == AuthenticationSchemes.IntegratedWindowsAuthentication;
}
private string GetConnectionGroupName(NetworkCredential credential, AuthenticationLevel authenticationLevel,
TokenImpersonationLevel impersonationLevel, SecurityTokenContainer clientCertificateToken)
{
if (_credentialHashCache == null)
{
lock (ThisLock)
{
if (_credentialHashCache == null)
{
_credentialHashCache = new MruCache<string, string>(5);
}
}
}
string inputString = TransferModeHelper.IsRequestStreamed(TransferMode) ? "streamed" : string.Empty;
if (IsWindowsAuth(AuthenticationScheme))
{
inputString = AppendWindowsAuthenticationInfo(inputString, credential, authenticationLevel, impersonationLevel);
}
inputString = string.Concat(OnGetConnectionGroupPrefix(clientCertificateToken), inputString);
string credentialHash = null;
// we have to lock around each call to TryGetValue since the MruCache modifies the
// contents of it's mruList in a single-threaded manner underneath TryGetValue
if (!string.IsNullOrEmpty(inputString))
{
lock (_credentialHashCache)
{
if (!_credentialHashCache.TryGetValue(inputString, out credentialHash))
{
byte[] inputBytes = new UTF8Encoding().GetBytes(inputString);
byte[] digestBytes = HashAlgorithm.ComputeHash(inputBytes);
credentialHash = Convert.ToBase64String(digestBytes);
_credentialHashCache.Add(inputString, credentialHash);
}
}
}
return credentialHash;
}
internal HttpRequestMessage GetHttpRequestMessage(Uri via)
{
Uri httpRequestUri = via;
var requestMessage = new HttpRequestMessage(HttpMethod.Post, httpRequestUri);
if (TransferModeHelper.IsRequestStreamed(TransferMode))
{
requestMessage.Headers.TransferEncodingChunked = true;
}
requestMessage.Headers.CacheControl = s_requestCacheHeader;
return requestMessage;
}
private void ApplyManualAddressing(ref EndpointAddress to, ref Uri via, Message message)
{
if (ManualAddressing)
{
Uri toHeader = message.Headers.To;
if (toHeader == null)
{
throw TraceUtility.ThrowHelperError(new InvalidOperationException(SR.ManualAddressingRequiresAddressedMessages), message);
}
to = new EndpointAddress(toHeader);
if (MessageVersion.Addressing == AddressingVersion.None)
{
via = toHeader;
}
}
// now apply query string property
object property;
if (message.Properties.TryGetValue(HttpRequestMessageProperty.Name, out property))
{
HttpRequestMessageProperty requestProperty = (HttpRequestMessageProperty)property;
if (!string.IsNullOrEmpty(requestProperty.QueryString))
{
UriBuilder uriBuilder = new UriBuilder(via);
if (requestProperty.QueryString.StartsWith("?", StringComparison.Ordinal))
{
uriBuilder.Query = requestProperty.QueryString.Substring(1);
}
else
{
uriBuilder.Query = requestProperty.QueryString;
}
via = uriBuilder.Uri;
}
}
}
[MethodImpl(MethodImplOptions.NoInlining)]
private async Task<(SecurityTokenProviderContainer tokenProvider, SecurityTokenProviderContainer proxyTokenProvider)> CreateAndOpenTokenProvidersCoreAsync(EndpointAddress to, Uri via, ChannelParameterCollection channelParameters, TimeSpan timeout)
{
TimeoutHelper timeoutHelper = new TimeoutHelper(timeout);
SecurityTokenProviderContainer tokenProvider = await CreateAndOpenTokenProviderAsync(timeoutHelper.RemainingTime(), AuthenticationScheme, to, via, channelParameters);
SecurityTokenProviderContainer proxyTokenProvider;
if (ProxyFactory != null)
{
proxyTokenProvider = await CreateAndOpenTokenProviderAsync(timeoutHelper.RemainingTime(), ProxyFactory.AuthenticationScheme, to, via, channelParameters);
}
else
{
proxyTokenProvider = null;
}
return (tokenProvider, proxyTokenProvider);
}
internal Task<(SecurityTokenProviderContainer tokenProvider, SecurityTokenProviderContainer proxyTokenProvider)> CreateAndOpenTokenProvidersAsync(EndpointAddress to, Uri via, ChannelParameterCollection channelParameters, TimeSpan timeout)
{
if (!IsSecurityTokenManagerRequired())
{
(SecurityTokenProviderContainer tokenProvider, SecurityTokenProviderContainer proxyTokenProvider) result = (null, null);
return Task.FromResult(result);
}
else
{
return CreateAndOpenTokenProvidersCoreAsync(to, via, channelParameters, timeout);
}
}
internal static bool MapIdentity(EndpointAddress target, AuthenticationSchemes authenticationScheme)
{
if (target.Identity == null)
{
return false;
}
return IsWindowsAuth(authenticationScheme);
}
private bool MapIdentity(EndpointAddress target)
{
return MapIdentity(target, AuthenticationScheme);
}
protected class HttpClientRequestChannel : RequestChannel
{
private SecurityTokenProviderContainer _tokenProvider;
private SecurityTokenProviderContainer _proxyTokenProvider;
public HttpClientRequestChannel(HttpChannelFactory<IRequestChannel> factory, EndpointAddress to, Uri via, bool manualAddressing)
: base(factory, to, via, manualAddressing)
{
Factory = factory;
}
public HttpChannelFactory<IRequestChannel> Factory { get; }
protected ChannelParameterCollection ChannelParameters { get; private set; }
public override T GetProperty<T>()
{
if (typeof(T) == typeof(ChannelParameterCollection))
{
if (State == CommunicationState.Created)
{
lock (ThisLock)
{
if (ChannelParameters == null)
{
ChannelParameters = new ChannelParameterCollection();
}
}
}
return (T)(object)ChannelParameters;
}
return base.GetProperty<T>();
}
private void PrepareOpen()
{
Factory.MapIdentity(RemoteAddress);
}
private async Task CreateAndOpenTokenProvidersAsync(TimeSpan timeout)
{
TimeoutHelper timeoutHelper = new TimeoutHelper(timeout);
if (!ManualAddressing)
{
(_tokenProvider, _proxyTokenProvider) = await Factory.CreateAndOpenTokenProvidersAsync(RemoteAddress, Via, ChannelParameters, timeoutHelper.RemainingTime());
}
}
private void CloseTokenProviders(TimeSpan timeout)
{
TimeoutHelper timeoutHelper = new TimeoutHelper(timeout);
if (_tokenProvider != null)
{
_tokenProvider.Close(timeoutHelper.RemainingTime());
}
}
private void AbortTokenProviders()
{
if (_tokenProvider != null)
{
_tokenProvider.Abort();
}
}
protected override IAsyncResult OnBeginOpen(TimeSpan timeout, AsyncCallback callback, object state)
{
return CommunicationObjectInternal.OnBeginOpen(this, timeout, callback, state);
}
protected override void OnEndOpen(IAsyncResult result)
{
CommunicationObjectInternal.OnEnd(result);
}
protected override void OnOpen(TimeSpan timeout)
{
CommunicationObjectInternal.OnOpen(this, timeout);
}
internal protected override Task OnOpenAsync(TimeSpan timeout)
{
PrepareOpen();
return CreateAndOpenTokenProvidersAsync(timeout);
}
private void PrepareClose(bool aborting)
{
}
protected override void OnAbort()
{
PrepareClose(true);
AbortTokenProviders();
base.OnAbort();
}
protected override IAsyncResult OnBeginClose(TimeSpan timeout, AsyncCallback callback, object state)
{
return CommunicationObjectInternal.OnBeginClose(this, timeout, callback, state);
}
protected override void OnEndClose(IAsyncResult result)
{
CommunicationObjectInternal.OnEnd(result);
}
protected override void OnClose(TimeSpan timeout)
{
CommunicationObjectInternal.OnClose(this, timeout);
}
protected internal override async Task OnCloseAsync(TimeSpan timeout)
{
var timeoutHelper = new TimeoutHelper(timeout);
PrepareClose(false);
CloseTokenProviders(timeoutHelper.RemainingTime());
await WaitForPendingRequestsAsync(timeoutHelper.RemainingTime());
}
protected override IAsyncRequest CreateAsyncRequest(Message message)
{
return new HttpClientChannelAsyncRequest(this);
}
internal virtual Task<HttpClient> GetHttpClientAsync(EndpointAddress to, Uri via, TimeoutHelper timeoutHelper)
{
return GetHttpClientAsync(to, via, null, timeoutHelper);
}
protected async Task<HttpClient> GetHttpClientAsync(EndpointAddress to, Uri via, SecurityTokenContainer clientCertificateToken, TimeoutHelper timeoutHelper)
{
SecurityTokenProviderContainer requestTokenProvider;
SecurityTokenProviderContainer requestProxyTokenProvider;
if (ManualAddressing)
{
(requestTokenProvider, requestProxyTokenProvider) = await Factory.CreateAndOpenTokenProvidersAsync(to, via, ChannelParameters, timeoutHelper.RemainingTime());
}
else
{
requestTokenProvider = _tokenProvider;
requestProxyTokenProvider = _proxyTokenProvider;
}
try
{
return await Factory.GetHttpClientAsync(to, via, requestTokenProvider, requestProxyTokenProvider, clientCertificateToken, timeoutHelper.RemainingTime());
}
finally
{
if (ManualAddressing)
{
if (requestTokenProvider != null)
{
requestTokenProvider.Abort();
}
}
}
}
internal HttpRequestMessage GetHttpRequestMessage(Uri via)
{
return Factory.GetHttpRequestMessage(via);
}
internal virtual void OnHttpRequestCompleted(HttpRequestMessage request)
{
// empty
}
internal class HttpClientChannelAsyncRequest : IAsyncRequest
{
private static readonly Action<object> s_cancelCts = state =>
{
try
{
((CancellationTokenSource)state).Cancel();
}
catch (ObjectDisposedException)
{
// ignore
}
};
private HttpClientRequestChannel _channel;
private HttpChannelFactory<IRequestChannel> _factory;
private EndpointAddress _to;
private Uri _via;
private HttpRequestMessage _httpRequestMessage;
private HttpResponseMessage _httpResponseMessage;
private HttpAbortReason _abortReason;
private TimeoutHelper _timeoutHelper;
private int _httpRequestCompleted;
private HttpClient _httpClient;
private readonly CancellationTokenSource _httpSendCts;
public HttpClientChannelAsyncRequest(HttpClientRequestChannel channel)
{
_channel = channel;
_to = channel.RemoteAddress;
_via = channel.Via;
_factory = channel.Factory;
_httpSendCts = new CancellationTokenSource();
}
public async Task SendRequestAsync(Message message, TimeoutHelper timeoutHelper)
{
_timeoutHelper = timeoutHelper;
if (_channel.Factory.MapIdentity(_to))
{
HttpTransportSecurityHelpers.AddIdentityMapping(_to, message);
}
_factory.ApplyManualAddressing(ref _to, ref _via, message);
_httpClient = await _channel.GetHttpClientAsync(_to, _via, _timeoutHelper);
// The _httpRequestMessage field will be set to null by Cleanup() due to faulting
// or aborting, so use a local copy for exception handling within this method.
HttpRequestMessage httpRequestMessage = _channel.GetHttpRequestMessage(_via);
_httpRequestMessage = httpRequestMessage;
Message request = message;
try
{
if (_channel.State != CommunicationState.Opened)
{
// if we were aborted while getting our request or doing correlation,
// we need to abort the request and bail
Cleanup();
_channel.ThrowIfDisposedOrNotOpen();
}
bool suppressEntityBody = PrepareMessageHeaders(request);
if (!suppressEntityBody)
{
httpRequestMessage.Content = MessageContent.Create(_factory, request, _timeoutHelper);
var contentType = httpRequestMessage.Content.Headers.ContentType;
if (contentType!= null &&
contentType.MediaType == "multipart/related" &&
contentType.Parameters.Contains(new NameValueHeaderValue("type", "\"application/xop+xml\"")))
{
// For MTOM messages, add a MIME version header
AddMimeVersion("1.0");
request.Properties.Add("System.ServiceModel.Channel.MtomMessageEncoder.WriteMessageHeaders", false);
}
}
if (Fx.IsUap)
{
try
{
// There is a possibility that a HEAD pre-auth request might fail when the actual request
// will succeed. For example, when the web service refuses HEAD requests. We don't want
// to fail the actual request because of some subtlety which causes the HEAD request.
await SendPreauthenticationHeadRequestIfNeeded();
}
catch { /* ignored */ }
}
bool success = false;
var timeoutToken = await _timeoutHelper.GetCancellationTokenAsync();
try
{
using (timeoutToken.Register(s_cancelCts, _httpSendCts))
{
_httpResponseMessage = await _httpClient.SendAsync(httpRequestMessage, HttpCompletionOption.ResponseHeadersRead, _httpSendCts.Token);
}
// As we have the response message and no exceptions have been thrown, the request message has completed it's job.
// Calling Dispose() on the request message to free up resources in HttpContent, but keeping the object around
// as we can still query properties once dispose'd.
httpRequestMessage.Dispose();
success = true;
}
catch (HttpRequestException requestException)
{
HttpChannelUtilities.ProcessGetResponseWebException(requestException, httpRequestMessage,
_abortReason);
}
catch (OperationCanceledException)
{
if (timeoutToken.IsCancellationRequested)
{
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new TimeoutException(SR.Format(
SR.HttpRequestTimedOut, httpRequestMessage.RequestUri, _timeoutHelper.OriginalTimeout)));
}
else
{
// Cancellation came from somewhere other than timeoutToken and needs to be handled differently.
throw;
}
}
finally
{
if (!success)
{
Abort(_channel);
}
}
}
finally
{
if (!ReferenceEquals(request, message))
{
request.Close();
}
}
}
private void AddMimeVersion(string version)
{
_httpRequestMessage.Headers.Add(HttpChannelUtilities.MIMEVersionHeader, version);
}
private void Cleanup()
{
s_cancelCts(_httpSendCts);
if (_httpRequestMessage != null)
{
var httpRequestMessageSnapshot = _httpRequestMessage;
_httpRequestMessage = null;
TryCompleteHttpRequest(httpRequestMessageSnapshot);
httpRequestMessageSnapshot.Dispose();
}
}
public void Abort(RequestChannel channel)
{
Cleanup();
_abortReason = HttpAbortReason.Aborted;
}
public void Fault(RequestChannel channel)
{
Cleanup();
}
public async Task<Message> ReceiveReplyAsync(TimeoutHelper timeoutHelper)
{
try
{
_timeoutHelper = timeoutHelper;
var responseHelper = new HttpResponseMessageHelper(_httpResponseMessage, _factory);
var replyMessage = await responseHelper.ParseIncomingResponse(timeoutHelper);
TryCompleteHttpRequest(_httpRequestMessage);
return replyMessage;
}
catch (OperationCanceledException)
{
var cancelToken = _timeoutHelper.GetCancellationToken();
if (cancelToken.IsCancellationRequested)
{
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new TimeoutException(SR.Format(
SR.HttpResponseTimedOut, _httpRequestMessage.RequestUri, timeoutHelper.OriginalTimeout)));
}
else
{
// Cancellation came from somewhere other than timeoutCts and needs to be handled differently.
throw;
}
}
}
private bool PrepareMessageHeaders(Message message)
{
string action = message.Headers.Action;
if (action != null)
{
action = string.Format(CultureInfo.InvariantCulture, "\"{0}\"", UrlUtility.UrlPathEncode(action));
}
bool suppressEntityBody = message is NullMessage;
object property;
if (message.Properties.TryGetValue(HttpRequestMessageProperty.Name, out property))
{
HttpRequestMessageProperty requestProperty = (HttpRequestMessageProperty)property;
_httpRequestMessage.Method = new HttpMethod(requestProperty.Method);
// Query string was applied in HttpChannelFactory.ApplyManualAddressing
WebHeaderCollection requestHeaders = requestProperty.Headers;
suppressEntityBody = suppressEntityBody || requestProperty.SuppressEntityBody;
var headerKeys = requestHeaders.AllKeys;
for (int i = 0; i < headerKeys.Length; i++)
{
string name = headerKeys[i];
string value = requestHeaders[name];
if (string.Compare(name, "accept", StringComparison.OrdinalIgnoreCase) == 0)
{
_httpRequestMessage.Headers.Accept.TryParseAdd(value);
}
else if (string.Compare(name, "connection", StringComparison.OrdinalIgnoreCase) == 0)
{
if (value.IndexOf("keep-alive", StringComparison.OrdinalIgnoreCase) != -1)
{
_httpRequestMessage.Headers.ConnectionClose = false;
}
else
{
_httpRequestMessage.Headers.Connection.TryParseAdd(value);
}
}
else if (string.Compare(name, "SOAPAction", StringComparison.OrdinalIgnoreCase) == 0)
{
if (action == null)
{
action = value;
}
else
{
if (!String.IsNullOrEmpty(value) && string.Compare(value, action, StringComparison.Ordinal) != 0)
{
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(
new ProtocolException(SR.Format(SR.HttpSoapActionMismatch, action, value)));
}
}
}
else if (string.Compare(name, "content-length", StringComparison.OrdinalIgnoreCase) == 0)
{
// this will be taken care of by System.Net when we write to the content
}
else if (string.Compare(name, "content-type", StringComparison.OrdinalIgnoreCase) == 0)
{
// Handled by MessageContent
}
else if (string.Compare(name, "expect", StringComparison.OrdinalIgnoreCase) == 0)
{
if (value.ToUpperInvariant().IndexOf("100-CONTINUE", StringComparison.OrdinalIgnoreCase) != -1)
{
_httpRequestMessage.Headers.ExpectContinue = true;
}
else
{
_httpRequestMessage.Headers.Expect.TryParseAdd(value);
}
}
else if (string.Compare(name, "referer", StringComparison.OrdinalIgnoreCase) == 0)
{
// referrer is proper spelling, but referer is the what is in the protocol.
_httpRequestMessage.Headers.Referrer = new Uri(value);
}
else if (string.Compare(name, "transfer-encoding", StringComparison.OrdinalIgnoreCase) == 0)
{
if (value.ToUpperInvariant().IndexOf("CHUNKED", StringComparison.OrdinalIgnoreCase) != -1)
{
_httpRequestMessage.Headers.TransferEncodingChunked = true;
}
else
{
_httpRequestMessage.Headers.TransferEncoding.TryParseAdd(value);
}
}
else if (string.Compare(name, "user-agent", StringComparison.OrdinalIgnoreCase) == 0)
{
_httpRequestMessage.Headers.Add(name, value);
}
else if (string.Compare(name, "if-modified-since", StringComparison.OrdinalIgnoreCase) == 0)
{
DateTimeOffset modifiedSinceDate;
if (DateTimeOffset.TryParse(value, DateTimeFormatInfo.InvariantInfo, DateTimeStyles.AllowWhiteSpaces | DateTimeStyles.AssumeLocal, out modifiedSinceDate))
{
_httpRequestMessage.Headers.IfModifiedSince = modifiedSinceDate;
}
else
{
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(
new ProtocolException(SR.Format(SR.HttpIfModifiedSinceParseError, value)));
}
}
else if (string.Compare(name, "date", StringComparison.OrdinalIgnoreCase) == 0)
{
// this will be taken care of by System.Net when we make the request
}
else if (string.Compare(name, "proxy-connection", StringComparison.OrdinalIgnoreCase) == 0)
{
throw ExceptionHelper.PlatformNotSupported("proxy-connection");
}
else if (string.Compare(name, "range", StringComparison.OrdinalIgnoreCase) == 0)
{
// specifying a range doesn't make sense in the context of WCF
}
else
{
try
{
_httpRequestMessage.Headers.Add(name, value);
}
catch (Exception addHeaderException)
{
throw FxTrace.Exception.AsError(new InvalidOperationException(SR.Format(
SR.CopyHttpHeaderFailed,
name,
value,
HttpChannelUtilities.HttpRequestHeadersTypeName),
addHeaderException));
}
}
}
}
if (action != null)
{
if (message.Version.Envelope == EnvelopeVersion.Soap11)
{
_httpRequestMessage.Headers.TryAddWithoutValidation("SOAPAction", action);
}
else if (message.Version.Envelope == EnvelopeVersion.Soap12)
{
// Handled by MessageContent
}
else if (message.Version.Envelope != EnvelopeVersion.None)
{
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(
new ProtocolException(SR.Format(SR.EnvelopeVersionUnknown,
message.Version.Envelope.ToString())));
}
}
// since we don't get the output stream in send when retVal == true,
// we need to disable chunking for some verbs (DELETE/PUT)
if (suppressEntityBody)
{
_httpRequestMessage.Headers.TransferEncodingChunked = false;
}
return suppressEntityBody;
}
public void OnReleaseRequest()
{
TryCompleteHttpRequest(_httpRequestMessage);
}
private void TryCompleteHttpRequest(HttpRequestMessage request)
{
if (request == null)
{
return;
}
if (Interlocked.CompareExchange(ref _httpRequestCompleted, 1, 0) == 0)
{
_channel.OnHttpRequestCompleted(request);
}
}
private async Task SendPreauthenticationHeadRequestIfNeeded()
{
if (!_factory.AuthenticationSchemeMayRequireResend())
{
return;
}
var requestUri = _httpRequestMessage.RequestUri;
// sends a HEAD request to the specificed requestUri for authentication purposes
Contract.Assert(requestUri != null);
HttpRequestMessage headHttpRequestMessage = new HttpRequestMessage()
{
Method = HttpMethod.Head,
RequestUri = requestUri
};
var cancelToken = await _timeoutHelper.GetCancellationTokenAsync();
await _httpClient.SendAsync(headHttpRequestMessage, cancelToken);
}
}
}
internal class WebProxyFactory
{
private Uri _address;
private bool _bypassOnLocal;
public WebProxyFactory(Uri address, bool bypassOnLocal, AuthenticationSchemes authenticationScheme)
{
_address = address;
_bypassOnLocal = bypassOnLocal;
if (!authenticationScheme.IsSingleton() && authenticationScheme != AuthenticationSchemes.IntegratedWindowsAuthentication)
{
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument(nameof(authenticationScheme), SR.Format(SR.HttpRequiresSingleAuthScheme,
authenticationScheme));
}
AuthenticationScheme = authenticationScheme;
}
internal AuthenticationSchemes AuthenticationScheme { get; }
public async Task<IWebProxy> CreateWebProxyAsync(AuthenticationLevel requestAuthenticationLevel, TokenImpersonationLevel requestImpersonationLevel, SecurityTokenProviderContainer tokenProvider, TimeSpan timeout)
{
WebProxy result = new WebProxy(_address, _bypassOnLocal);
if (AuthenticationScheme != AuthenticationSchemes.Anonymous)
{
(NetworkCredential credential, TokenImpersonationLevel impersonationLevel, AuthenticationLevel authenticationLevel) = await HttpChannelUtilities.GetCredentialAsync(AuthenticationScheme,
tokenProvider, timeout);
// The impersonation level for target auth is also used for proxy auth (by System.Net). Therefore,
// fail if the level stipulated for proxy auth is more restrictive than that for target auth.
if (!TokenImpersonationLevelHelper.IsGreaterOrEqual(impersonationLevel, requestImpersonationLevel))
{
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SR.Format(
SR.ProxyImpersonationLevelMismatch, impersonationLevel, requestImpersonationLevel)));
}
// The authentication level for target auth is also used for proxy auth (by System.Net).
// Therefore, fail if proxy auth requires mutual authentication but target auth does not.
if ((authenticationLevel == AuthenticationLevel.MutualAuthRequired) &&
(requestAuthenticationLevel != AuthenticationLevel.MutualAuthRequired))
{
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SR.Format(
SR.ProxyAuthenticationLevelMismatch, authenticationLevel, requestAuthenticationLevel)));
}
CredentialCache credentials = new CredentialCache();
if (AuthenticationScheme == AuthenticationSchemes.IntegratedWindowsAuthentication)
{
credentials.Add(_address, AuthenticationSchemesHelper.ToString(AuthenticationSchemes.Negotiate),
credential);
credentials.Add(_address, AuthenticationSchemesHelper.ToString(AuthenticationSchemes.Ntlm),
credential);
}
else
{
credentials.Add(_address, AuthenticationSchemesHelper.ToString(AuthenticationScheme),
credential);
}
result.Credentials = credentials;
}
return result;
}
}
}
}
|