//------------------------------------------------------------ // Copyright (c) Microsoft Corporation. All rights reserved. //------------------------------------------------------------ namespace System.ServiceModel.Channels { using System.Collections.ObjectModel; using System.Diagnostics; using System.IdentityModel.Policy; using System.IdentityModel.Selectors; using System.IdentityModel.Tokens; using System.IO; using System.Net; using System.Net.Cache; using System.Net.Security; using System.Runtime; using System.Runtime.CompilerServices; using System.Runtime.Diagnostics; using System.Security; using System.Security.Authentication.ExtendedProtection; using System.Security.Cryptography; using System.Security.Permissions; using System.Security.Principal; using System.ServiceModel; using System.ServiceModel.Description; using System.ServiceModel.Diagnostics; using System.ServiceModel.Diagnostics.Application; using System.ServiceModel.Security; using System.ServiceModel.Security.Tokens; using System.Text; using System.Threading; class HttpChannelFactory : TransportChannelFactory, IHttpTransportFactorySettings { static bool httpWebRequestWebPermissionDenied = false; static RequestCachePolicy requestCachePolicy = new RequestCachePolicy(RequestCacheLevel.BypassCache); static long connectionGroupNamePrefix = 0; readonly ClientWebSocketFactory clientWebSocketFactory; bool allowCookies; AuthenticationSchemes authenticationScheme; HttpCookieContainerManager httpCookieContainerManager; // Double-checked locking pattern requires volatile for read/write synchronization volatile MruCache credentialCacheUriPrefixCache; bool decompressionEnabled; // Double-checked locking pattern requires volatile for read/write synchronization [Fx.Tag.SecurityNote(Critical = "This cache stores strings that contain domain/user name/password. Must not be settable from PT code.")] [SecurityCritical] volatile MruCache credentialHashCache; [Fx.Tag.SecurityNote(Critical = "This hash algorithm takes strings that contain domain/user name/password. Must not be settable from PT code.")] [SecurityCritical] HashAlgorithm hashAlgorithm; bool keepAliveEnabled; int maxBufferSize; IWebProxy proxy; WebProxyFactory proxyFactory; SecurityCredentialsManager channelCredentials; SecurityTokenManager securityTokenManager; TransferMode transferMode; ISecurityCapabilities securityCapabilities; WebSocketTransportSettings webSocketSettings; ConnectionBufferPool bufferPool; Lazy webSocketSoapContentType; string uniqueConnectionGroupNamePrefix; 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.GetString(SR.MaxReceivedMessageSizeMustBeInIntegerRange))); } if (bindingElement.MaxBufferSize != bindingElement.MaxReceivedMessageSize) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument("bindingElement", SR.GetString(SR.MaxBufferSizeMustMatchMaxReceivedMessageSize)); } } else { if (bindingElement.MaxBufferSize > bindingElement.MaxReceivedMessageSize) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument("bindingElement", SR.GetString(SR.MaxBufferSizeMustNotExceedMaxReceivedMessageSize)); } } if (TransferModeHelper.IsRequestStreamed(bindingElement.TransferMode) && bindingElement.AuthenticationScheme != AuthenticationSchemes.Anonymous) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument("bindingElement", SR.GetString( SR.HttpAuthDoesNotSupportRequestStreaming)); } this.allowCookies = bindingElement.AllowCookies; #pragma warning disable 618 if (!this.allowCookies) { Collection httpCookieContainerBindingElements = context.BindingParameters.FindAll(); if (httpCookieContainerBindingElements.Count > 1) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SR.GetString(SR.MultipleCCbesInParameters, typeof(HttpCookieContainerBindingElement)))); } if (httpCookieContainerBindingElements.Count == 1) { this.allowCookies = true; context.BindingParameters.Remove(); } } #pragma warning restore 618 if (this.allowCookies) { this.httpCookieContainerManager = new HttpCookieContainerManager(); } if (!bindingElement.AuthenticationScheme.IsSingleton()) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument("value", SR.GetString(SR.HttpRequiresSingleAuthScheme, bindingElement.AuthenticationScheme)); } this.authenticationScheme = bindingElement.AuthenticationScheme; this.decompressionEnabled = bindingElement.DecompressionEnabled; this.keepAliveEnabled = bindingElement.KeepAliveEnabled; this.maxBufferSize = bindingElement.MaxBufferSize; this.transferMode = bindingElement.TransferMode; if (bindingElement.Proxy != null) { this.proxy = bindingElement.Proxy; } else if (bindingElement.ProxyAddress != null) { if (bindingElement.UseDefaultWebProxy) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SR.GetString(SR.UseDefaultWebProxyCantBeUsedWithExplicitProxyAddress))); } if (bindingElement.ProxyAuthenticationScheme == AuthenticationSchemes.Anonymous) { this.proxy = new WebProxy(bindingElement.ProxyAddress, bindingElement.BypassProxyOnLocal); } else { this.proxy = null; this.proxyFactory = new WebProxyFactory(bindingElement.ProxyAddress, bindingElement.BypassProxyOnLocal, bindingElement.ProxyAuthenticationScheme); } } else if (!bindingElement.UseDefaultWebProxy) { this.proxy = new WebProxy(); } this.channelCredentials = context.BindingParameters.Find(); this.securityCapabilities = bindingElement.GetProperty(context); this.webSocketSettings = WebSocketHelper.GetRuntimeWebSocketSettings(bindingElement.WebSocketSettings); int webSocketBufferSize = WebSocketHelper.ComputeClientBufferSize(this.MaxReceivedMessageSize); this.bufferPool = new ConnectionBufferPool(webSocketBufferSize); Collection clientWebSocketFactories = context.BindingParameters.FindAll(); if (clientWebSocketFactories.Count > 1) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument( "context", SR.GetString(SR.MultipleClientWebSocketFactoriesSpecified, typeof(BindingContext).Name, typeof(ClientWebSocketFactory).Name)); } else { this.clientWebSocketFactory = clientWebSocketFactories.Count == 0 ? null : clientWebSocketFactories[0]; } this.webSocketSoapContentType = new Lazy(() => { return this.MessageEncoderFactory.CreateSessionEncoder().ContentType; }, LazyThreadSafetyMode.ExecutionAndPublication); if (ServiceModelAppSettings.HttpTransportPerFactoryConnectionPool) { this.uniqueConnectionGroupNamePrefix = Interlocked.Increment(ref connectionGroupNamePrefix).ToString(); } else { this.uniqueConnectionGroupNamePrefix = string.Empty; } } public bool AllowCookies { get { return this.allowCookies; } } public AuthenticationSchemes AuthenticationScheme { get { return this.authenticationScheme; } } public bool DecompressionEnabled { get { return this.decompressionEnabled; } } public virtual bool IsChannelBindingSupportEnabled { get { return false; } } public bool KeepAliveEnabled { get { return this.keepAliveEnabled; } } public SecurityTokenManager SecurityTokenManager { get { return this.securityTokenManager; } } public int MaxBufferSize { get { return maxBufferSize; } } public IWebProxy Proxy { get { return this.proxy; } } public TransferMode TransferMode { get { return transferMode; } } public override string Scheme { get { return Uri.UriSchemeHttp; } } public WebSocketTransportSettings WebSocketSettings { get { return this.webSocketSettings; } } internal string WebSocketSoapContentType { get { return this.webSocketSoapContentType.Value; } } protected ConnectionBufferPool WebSocketBufferPool { get { return this.bufferPool; } } // must be called under lock (this.credentialHashCache) HashAlgorithm HashAlgorithm { [SecurityCritical] get { if (this.hashAlgorithm == null) { this.hashAlgorithm = CryptoHelper.CreateHashAlgorithm(SecurityAlgorithms.Sha256Digest); } else { this.hashAlgorithm.Initialize(); } return this.hashAlgorithm; } } int IHttpTransportFactorySettings.MaxBufferSize { get { return MaxBufferSize; } } TransferMode IHttpTransportFactorySettings.TransferMode { get { return TransferMode; } } protected ClientWebSocketFactory ClientWebSocketFactory { get { return this.clientWebSocketFactory; } } public override T GetProperty() { if (typeof(T) == typeof(ISecurityCapabilities)) { return (T)(object)this.securityCapabilities; } if (typeof(T) == typeof(IHttpCookieContainerManager)) { return (T)(object)this.GetHttpCookieContainerManager(); } return base.GetProperty(); } [PermissionSet(SecurityAction.Demand, Unrestricted = true), SecuritySafeCritical] [MethodImpl(MethodImplOptions.NoInlining)] private HttpCookieContainerManager GetHttpCookieContainerManager() { return this.httpCookieContainerManager; } internal virtual SecurityMessageProperty CreateReplySecurityProperty(HttpWebRequest request, HttpWebResponse response) { // Don't pull in System.Authorization if we don't need to! if (!response.IsMutuallyAuthenticated) { return null; } return CreateMutuallyAuthenticatedReplySecurityProperty(response); } internal Exception CreateToMustEqualViaException(Uri to, Uri via) { return new ArgumentException(SR.GetString(SR.HttpToMustEqualVia, to, via)); } [MethodImpl(MethodImplOptions.NoInlining)] SecurityMessageProperty CreateMutuallyAuthenticatedReplySecurityProperty(HttpWebResponse response) { string spn = AuthenticationManager.CustomTargetNameDictionary[response.ResponseUri.AbsoluteUri]; if (spn == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new MessageSecurityException(SR.GetString(SR.HttpSpnNotFound, response.ResponseUri))); } ReadOnlyCollection spnPolicies = SecurityUtils.CreatePrincipalNameAuthorizationPolicies(spn); SecurityMessageProperty remoteSecurity = new SecurityMessageProperty(); remoteSecurity.TransportToken = new SecurityTokenSpecification(null, spnPolicies); remoteSecurity.ServiceSecurityContext = new ServiceSecurityContext(spnPolicies); return remoteSecurity; } internal override int GetMaxBufferSize() { return MaxBufferSize; } SecurityTokenProviderContainer CreateAndOpenTokenProvider(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(this.SecurityTokenManager, target, via, this.Scheme, authenticationScheme, channelParameters); break; case AuthenticationSchemes.Negotiate: case AuthenticationSchemes.Ntlm: tokenProvider = TransportSecurityHelpers.GetSspiTokenProvider(this.SecurityTokenManager, target, via, this.Scheme, authenticationScheme, channelParameters); break; case AuthenticationSchemes.Digest: tokenProvider = TransportSecurityHelpers.GetDigestTokenProvider(this.SecurityTokenManager, target, via, this.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); result.Open(timeout); } else { result = null; } return result; } protected virtual void ValidateCreateChannelParameters(EndpointAddress remoteAddress, Uri via) { base.ValidateScheme(via); if (this.MessageVersion.Addressing == AddressingVersion.None && remoteAddress.Uri != via) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(CreateToMustEqualViaException(remoteAddress.Uri, via)); } } protected override TChannel OnCreateChannel(EndpointAddress remoteAddress, Uri via) { EndpointAddress httpRemoteAddress = remoteAddress != null && WebSocketHelper.IsWebSocketUri(remoteAddress.Uri) ? new EndpointAddress(WebSocketHelper.NormalizeWsSchemeWithHttpScheme(remoteAddress.Uri), remoteAddress) : remoteAddress; Uri httpVia = WebSocketHelper.IsWebSocketUri(via) ? WebSocketHelper.NormalizeWsSchemeWithHttpScheme(via) : via; return this.OnCreateChannelCore(httpRemoteAddress, httpVia); } protected virtual TChannel OnCreateChannelCore(EndpointAddress remoteAddress, Uri via) { ValidateCreateChannelParameters(remoteAddress, via); this.ValidateWebSocketTransportUsage(); if (typeof(TChannel) == typeof(IRequestChannel)) { return (TChannel)(object)new HttpRequestChannel((HttpChannelFactory)(object)this, remoteAddress, via, ManualAddressing); } else { return (TChannel)(object)new ClientWebSocketTransportDuplexSessionChannel((HttpChannelFactory)(object)this, this.clientWebSocketFactory, remoteAddress, via, this.WebSocketBufferPool); } } protected void ValidateWebSocketTransportUsage() { Type channelType = typeof(TChannel); if (channelType == typeof(IRequestChannel) && this.WebSocketSettings.TransportUsage == WebSocketTransportUsage.Always) { throw FxTrace.Exception.AsError(new InvalidOperationException(SR.GetString( SR.WebSocketCannotCreateRequestClientChannelWithCertainWebSocketTransportUsage, typeof(TChannel), WebSocketTransportSettings.TransportUsageMethodName, typeof(WebSocketTransportSettings).Name, this.WebSocketSettings.TransportUsage))); } else if (channelType == typeof(IDuplexSessionChannel)) { if (this.WebSocketSettings.TransportUsage == WebSocketTransportUsage.Never) { throw FxTrace.Exception.AsError(new InvalidOperationException(SR.GetString( SR.WebSocketCannotCreateRequestClientChannelWithCertainWebSocketTransportUsage, typeof(TChannel), WebSocketTransportSettings.TransportUsageMethodName, typeof(WebSocketTransportSettings).Name, this.WebSocketSettings.TransportUsage))); } else if (!WebSocketHelper.OSSupportsWebSockets() && this.ClientWebSocketFactory == null) { throw FxTrace.Exception.AsError(new PlatformNotSupportedException(SR.GetString(SR.WebSocketsClientSideNotSupported, typeof(ClientWebSocketFactory).FullName))); } } } [MethodImpl(MethodImplOptions.NoInlining)] void InitializeSecurityTokenManager() { if (this.channelCredentials == null) { this.channelCredentials = ClientCredentials.CreateDefaultCredentials(); } this.securityTokenManager = this.channelCredentials.CreateSecurityTokenManager(); } protected virtual bool IsSecurityTokenManagerRequired() { if (this.AuthenticationScheme != AuthenticationSchemes.Anonymous) { return true; } if (this.proxyFactory != null && this.proxyFactory.AuthenticationScheme != AuthenticationSchemes.Anonymous) { return true; } else { return false; } } protected override IAsyncResult OnBeginOpen(TimeSpan timeout, AsyncCallback callback, object state) { this.OnOpen(timeout); return new CompletedAsyncResult(callback, state); } protected override void OnEndOpen(IAsyncResult result) { CompletedAsyncResult.End(result); } protected override void OnOpen(TimeSpan timeout) { if (IsSecurityTokenManagerRequired()) { this.InitializeSecurityTokenManager(); } if (this.AllowCookies && !this.httpCookieContainerManager.IsInitialized) // We don't want to overwrite the CookieContainer if someone has set it already. { this.httpCookieContainerManager.CookieContainer = new CookieContainer(); } // we need to make sure System.Net will buffer faults (sent as 500 requests) up to our allowed size // Their value is in Kbytes and ours is in bytes. We round up so that the KB value is large enough to // encompass our MaxReceivedMessageSize. See MB#20860 and related for details if (!httpWebRequestWebPermissionDenied && HttpWebRequest.DefaultMaximumErrorResponseLength != -1) { int MaxReceivedMessageSizeKbytes; if (MaxBufferSize >= (int.MaxValue - 1024)) // make sure NCL doesn't overflow { MaxReceivedMessageSizeKbytes = -1; } else { MaxReceivedMessageSizeKbytes = (int)(MaxBufferSize / 1024); if (MaxReceivedMessageSizeKbytes * 1024 < MaxBufferSize) { MaxReceivedMessageSizeKbytes++; } } if (MaxReceivedMessageSizeKbytes == -1 || MaxReceivedMessageSizeKbytes > HttpWebRequest.DefaultMaximumErrorResponseLength) { try { HttpWebRequest.DefaultMaximumErrorResponseLength = MaxReceivedMessageSizeKbytes; } catch (SecurityException exception) { // CSDMain\33725 - setting DefaultMaximumErrorResponseLength should not fail HttpChannelFactory.OnOpen // if the user does not have the permission to do so. httpWebRequestWebPermissionDenied = true; DiagnosticUtility.TraceHandledException(exception, TraceEventType.Warning); } } } } protected override void OnClosed() { base.OnClosed(); if (this.bufferPool != null) { this.bufferPool.Close(); } } static internal void TraceResponseReceived(HttpWebResponse response, Message message, object receiver) { if (DiagnosticUtility.ShouldTraceVerbose) { if (response != null && response.ResponseUri != null) { TraceUtility.TraceEvent(TraceEventType.Verbose, TraceCode.HttpResponseReceived, SR.GetString(SR.TraceCodeHttpResponseReceived), new StringTraceRecord("ResponseUri", response.ResponseUri.ToString()), receiver, null, message); } else { TraceUtility.TraceEvent(TraceEventType.Verbose, TraceCode.HttpResponseReceived, SR.GetString(SR.TraceCodeHttpResponseReceived), receiver, message); } } } [Fx.Tag.SecurityNote(Critical = "Uses unsafe critical method AppendWindowsAuthenticationInfo to access the credential domain/user name/password.")] [SecurityCritical] [MethodImpl(MethodImplOptions.NoInlining)] string AppendWindowsAuthenticationInfo(string inputString, NetworkCredential credential, AuthenticationLevel authenticationLevel, TokenImpersonationLevel impersonationLevel) { return SecurityUtils.AppendWindowsAuthenticationInfo(inputString, credential, authenticationLevel, impersonationLevel); } protected virtual string OnGetConnectionGroupPrefix(HttpWebRequest httpWebRequest, SecurityTokenContainer clientCertificateToken) { return string.Empty; } internal static bool IsWindowsAuth(AuthenticationSchemes authScheme) { Fx.Assert(authScheme.IsSingleton(), "authenticationScheme used in an Http(s)ChannelFactory must be a singleton value."); return authScheme == AuthenticationSchemes.Negotiate || authScheme == AuthenticationSchemes.Ntlm; } [Fx.Tag.SecurityNote(Critical = "Uses unsafe critical method AppendWindowsAuthenticationInfo to access the credential domain/user name/password.", Safe = "Uses the domain/user name/password to store and compute a hash. The store is SecurityCritical. The hash leaks but" + "the hash cannot be reversed to the domain/user name/password.")] [SecuritySafeCritical] string GetConnectionGroupName(HttpWebRequest httpWebRequest, NetworkCredential credential, AuthenticationLevel authenticationLevel, TokenImpersonationLevel impersonationLevel, SecurityTokenContainer clientCertificateToken) { if (this.credentialHashCache == null) { lock (ThisLock) { if (this.credentialHashCache == null) { this.credentialHashCache = new MruCache(5); } } } // The following line is a work-around for VSWhidbey 558605. In particular, we need to isolate our // connection groups based on whether we are streaming the request. string inputString = TransferModeHelper.IsRequestStreamed(this.TransferMode) ? "streamed" : string.Empty; if (IsWindowsAuth(this.AuthenticationScheme)) { // for NTLM & Negotiate, System.Net doesn't pool connections by default. This is because // IIS doesn't re-authenticate NTLM connections (made for a perf reason), and HttpWebRequest // shared connections among multiple callers. // This causes Indigo a performance problem in turn. We mitigate this by (1) enabling // connection sharing for NTLM connections on our pool, and (2) scoping the pool we use // to be based on the NetworkCredential that is being used to authenticate the connection. // Therefore we're only sharing connections among the same Credential. // Setting this will fail in partial trust, and that's ok since this is an optimization. if (!httpWebRequestWebPermissionDenied) { try { httpWebRequest.UnsafeAuthenticatedConnectionSharing = true; } catch (SecurityException e) { DiagnosticUtility.TraceHandledException(e, TraceEventType.Information); httpWebRequestWebPermissionDenied = true; } } inputString = AppendWindowsAuthenticationInfo(inputString, credential, authenticationLevel, impersonationLevel); } string prefix = this.OnGetConnectionGroupPrefix(httpWebRequest, clientCertificateToken); inputString = string.Concat(this.uniqueConnectionGroupNamePrefix, prefix, 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 (this.credentialHashCache) { if (!this.credentialHashCache.TryGetValue(inputString, out credentialHash)) { byte[] inputBytes = new UTF8Encoding().GetBytes(inputString); byte[] digestBytes = this.HashAlgorithm.ComputeHash(inputBytes); credentialHash = Convert.ToBase64String(digestBytes); this.credentialHashCache.Add(inputString, credentialHash); } } } return credentialHash; } Uri GetCredentialCacheUriPrefix(Uri via) { Uri result; if (this.credentialCacheUriPrefixCache == null) { lock (ThisLock) { if (this.credentialCacheUriPrefixCache == null) { this.credentialCacheUriPrefixCache = new MruCache(10); } } } lock (this.credentialCacheUriPrefixCache) { if (!this.credentialCacheUriPrefixCache.TryGetValue(via, out result)) { result = new UriBuilder(via.Scheme, via.Host, via.Port).Uri; this.credentialCacheUriPrefixCache.Add(via, result); } } return result; } // core code for creating an HttpWebRequest HttpWebRequest GetWebRequest(EndpointAddress to, Uri via, NetworkCredential credential, TokenImpersonationLevel impersonationLevel, AuthenticationLevel authenticationLevel, SecurityTokenProviderContainer proxyTokenProvider, SecurityTokenContainer clientCertificateToken, TimeSpan timeout, bool isWebSocketRequest) { Uri httpWebRequestUri = isWebSocketRequest ? WebSocketHelper.GetWebSocketUri(via) : via; HttpWebRequest httpWebRequest = (HttpWebRequest)WebRequest.Create(httpWebRequestUri); Fx.Assert(httpWebRequest.Method.Equals("GET", StringComparison.OrdinalIgnoreCase), "the default HTTP method of HttpWebRequest should be 'Get'."); if (!isWebSocketRequest) { httpWebRequest.Method = "POST"; if (TransferModeHelper.IsRequestStreamed(TransferMode)) { httpWebRequest.SendChunked = true; httpWebRequest.AllowWriteStreamBuffering = false; } else { httpWebRequest.AllowWriteStreamBuffering = true; } } httpWebRequest.CachePolicy = requestCachePolicy; httpWebRequest.KeepAlive = this.keepAliveEnabled; if (this.decompressionEnabled) { httpWebRequest.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate; } else { httpWebRequest.AutomaticDecompression = DecompressionMethods.None; } if (credential != null) { CredentialCache credentials = new CredentialCache(); credentials.Add(this.GetCredentialCacheUriPrefix(via), AuthenticationSchemesHelper.ToString(this.authenticationScheme), credential); httpWebRequest.Credentials = credentials; } httpWebRequest.AuthenticationLevel = authenticationLevel; httpWebRequest.ImpersonationLevel = impersonationLevel; string connectionGroupName = GetConnectionGroupName(httpWebRequest, credential, authenticationLevel, impersonationLevel, clientCertificateToken); X509CertificateEndpointIdentity remoteCertificateIdentity = to.Identity as X509CertificateEndpointIdentity; if (remoteCertificateIdentity != null) { connectionGroupName = string.Format(System.Globalization.CultureInfo.InvariantCulture, "{0}[{1}]", connectionGroupName, remoteCertificateIdentity.Certificates[0].Thumbprint); } if (!string.IsNullOrEmpty(connectionGroupName)) { httpWebRequest.ConnectionGroupName = connectionGroupName; } if (AuthenticationScheme == AuthenticationSchemes.Basic) { httpWebRequest.PreAuthenticate = true; } if (this.proxy != null) { httpWebRequest.Proxy = this.proxy; } else if (this.proxyFactory != null) { httpWebRequest.Proxy = this.proxyFactory.CreateWebProxy(httpWebRequest, proxyTokenProvider, timeout); } if (this.AllowCookies) { httpWebRequest.CookieContainer = this.httpCookieContainerManager.CookieContainer; } // we do this at the end so that we access the correct ServicePoint httpWebRequest.ServicePoint.UseNagleAlgorithm = false; return httpWebRequest; } 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.GetString(SR.ManualAddressingRequiresAddressedMessages)), message); } to = new EndpointAddress(toHeader); if (this.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)] void CreateAndOpenTokenProvidersCore(EndpointAddress to, Uri via, ChannelParameterCollection channelParameters, TimeSpan timeout, out SecurityTokenProviderContainer tokenProvider, out SecurityTokenProviderContainer proxyTokenProvider) { TimeoutHelper timeoutHelper = new TimeoutHelper(timeout); tokenProvider = CreateAndOpenTokenProvider(timeoutHelper.RemainingTime(), this.AuthenticationScheme, to, via, channelParameters); if (this.proxyFactory != null) { proxyTokenProvider = CreateAndOpenTokenProvider(timeoutHelper.RemainingTime(), this.proxyFactory.AuthenticationScheme, to, via, channelParameters); } else { proxyTokenProvider = null; } } internal void CreateAndOpenTokenProviders(EndpointAddress to, Uri via, ChannelParameterCollection channelParameters, TimeSpan timeout, out SecurityTokenProviderContainer tokenProvider, out SecurityTokenProviderContainer proxyTokenProvider) { if (!IsSecurityTokenManagerRequired()) { tokenProvider = null; proxyTokenProvider = null; } else { CreateAndOpenTokenProvidersCore(to, via, channelParameters, timeout, out tokenProvider, out proxyTokenProvider); } } internal HttpWebRequest GetWebRequest(EndpointAddress to, Uri via, SecurityTokenProviderContainer tokenProvider, SecurityTokenProviderContainer proxyTokenProvider, SecurityTokenContainer clientCertificateToken, TimeSpan timeout, bool isWebSocketRequest) { TokenImpersonationLevel impersonationLevel; AuthenticationLevel authenticationLevel; TimeoutHelper timeoutHelper = new TimeoutHelper(timeout); NetworkCredential credential = HttpChannelUtilities.GetCredential(this.authenticationScheme, tokenProvider, timeoutHelper.RemainingTime(), out impersonationLevel, out authenticationLevel); return GetWebRequest(to, via, credential, impersonationLevel, authenticationLevel, proxyTokenProvider, clientCertificateToken, timeoutHelper.RemainingTime(), isWebSocketRequest); } internal static bool MapIdentity(EndpointAddress target, AuthenticationSchemes authenticationScheme) { if ((target.Identity == null) || (target.Identity is X509CertificateEndpointIdentity)) { return false; } return IsWindowsAuth(authenticationScheme); } bool MapIdentity(EndpointAddress target) { return MapIdentity(target, this.AuthenticationScheme); } protected class HttpRequestChannel : RequestChannel { // Double-checked locking pattern requires volatile for read/write synchronization volatile bool cleanupIdentity; HttpChannelFactory factory; SecurityTokenProviderContainer tokenProvider; SecurityTokenProviderContainer proxyTokenProvider; ServiceModelActivity activity = null; ChannelParameterCollection channelParameters; public HttpRequestChannel(HttpChannelFactory factory, EndpointAddress to, Uri via, bool manualAddressing) : base(factory, to, via, manualAddressing) { this.factory = factory; } public HttpChannelFactory Factory { get { return this.factory; } } internal ServiceModelActivity Activity { get { return this.activity; } } protected ChannelParameterCollection ChannelParameters { get { return this.channelParameters; } } public override T GetProperty() { if (typeof(T) == typeof(ChannelParameterCollection)) { if (this.State == CommunicationState.Created) { lock (ThisLock) { if (this.channelParameters == null) { this.channelParameters = new ChannelParameterCollection(); } } } return (T)(object)this.channelParameters; } else { return base.GetProperty(); } } void PrepareOpen() { if (Factory.MapIdentity(RemoteAddress)) { lock (ThisLock) { cleanupIdentity = HttpTransportSecurityHelpers.AddIdentityMapping(Via, RemoteAddress); } } } void CreateAndOpenTokenProviders(TimeSpan timeout) { TimeoutHelper timeoutHelper = new TimeoutHelper(timeout); if (!ManualAddressing) { Factory.CreateAndOpenTokenProviders(this.RemoteAddress, this.Via, this.channelParameters, timeoutHelper.RemainingTime(), out this.tokenProvider, out this.proxyTokenProvider); } } void CloseTokenProviders(TimeSpan timeout) { TimeoutHelper timeoutHelper = new TimeoutHelper(timeout); if (this.tokenProvider != null) { tokenProvider.Close(timeoutHelper.RemainingTime()); } if (this.proxyTokenProvider != null) { proxyTokenProvider.Close(timeoutHelper.RemainingTime()); } } void AbortTokenProviders() { if (this.tokenProvider != null) { tokenProvider.Abort(); } if (this.proxyTokenProvider != null) { proxyTokenProvider.Abort(); } } protected override IAsyncResult OnBeginOpen(TimeSpan timeout, AsyncCallback callback, object state) { PrepareOpen(); TimeoutHelper timeoutHelper = new TimeoutHelper(timeout); CreateAndOpenTokenProviders(timeoutHelper.RemainingTime()); return new CompletedAsyncResult(callback, state); } protected override void OnOpen(TimeSpan timeout) { PrepareOpen(); CreateAndOpenTokenProviders(timeout); } protected override void OnEndOpen(IAsyncResult result) { CompletedAsyncResult.End(result); } void PrepareClose(bool aborting) { if (cleanupIdentity) { lock (ThisLock) { if (cleanupIdentity) { cleanupIdentity = false; HttpTransportSecurityHelpers.RemoveIdentityMapping(Via, RemoteAddress, !aborting); } } } } protected override void OnAbort() { PrepareClose(true); AbortTokenProviders(); base.OnAbort(); } protected override IAsyncResult OnBeginClose(TimeSpan timeout, AsyncCallback callback, object state) { IAsyncResult retval = null; using (ServiceModelActivity.BoundOperation(this.activity)) { PrepareClose(false); TimeoutHelper timeoutHelper = new TimeoutHelper(timeout); CloseTokenProviders(timeoutHelper.RemainingTime()); retval = base.BeginWaitForPendingRequests(timeoutHelper.RemainingTime(), callback, state); } ServiceModelActivity.Stop(this.activity); return retval; } protected override void OnEndClose(IAsyncResult result) { using (ServiceModelActivity.BoundOperation(this.activity)) { base.EndWaitForPendingRequests(result); } ServiceModelActivity.Stop(this.activity); } protected override void OnClose(TimeSpan timeout) { using (ServiceModelActivity.BoundOperation(this.activity)) { PrepareClose(false); TimeoutHelper timeoutHelper = new TimeoutHelper(timeout); CloseTokenProviders(timeoutHelper.RemainingTime()); base.WaitForPendingRequests(timeoutHelper.RemainingTime()); } ServiceModelActivity.Stop(this.activity); } protected override IAsyncRequest CreateAsyncRequest(Message message, AsyncCallback callback, object state) { if (DiagnosticUtility.ShouldUseActivity && this.activity == null) { this.activity = ServiceModelActivity.CreateActivity(); if (null != FxTrace.Trace) { FxTrace.Trace.TraceTransfer(this.activity.Id); } ServiceModelActivity.Start(this.activity, SR.GetString(SR.ActivityReceiveBytes, this.RemoteAddress.Uri.ToString()), ActivityType.ReceiveBytes); } return new HttpChannelAsyncRequest(this, callback, state); } protected override IRequest CreateRequest(Message message) { return new HttpChannelRequest(this, Factory); } public virtual HttpWebRequest GetWebRequest(EndpointAddress to, Uri via, ref TimeoutHelper timeoutHelper) { return GetWebRequest(to, via, null, ref timeoutHelper); } protected HttpWebRequest GetWebRequest(EndpointAddress to, Uri via, SecurityTokenContainer clientCertificateToken, ref TimeoutHelper timeoutHelper) { SecurityTokenProviderContainer webRequestTokenProvider; SecurityTokenProviderContainer webRequestProxyTokenProvider; if (this.ManualAddressing) { this.Factory.CreateAndOpenTokenProviders(to, via, this.channelParameters, timeoutHelper.RemainingTime(), out webRequestTokenProvider, out webRequestProxyTokenProvider); } else { webRequestTokenProvider = this.tokenProvider; webRequestProxyTokenProvider = this.proxyTokenProvider; } try { return this.Factory.GetWebRequest(to, via, webRequestTokenProvider, webRequestProxyTokenProvider, clientCertificateToken, timeoutHelper.RemainingTime(), false); } finally { if (this.ManualAddressing) { if (webRequestTokenProvider != null) { webRequestTokenProvider.Abort(); } if (webRequestProxyTokenProvider != null) { webRequestProxyTokenProvider.Abort(); } } } } protected IAsyncResult BeginGetWebRequest( EndpointAddress to, Uri via, SecurityTokenContainer clientCertificateToken, ref TimeoutHelper timeoutHelper, AsyncCallback callback, object state) { return new GetWebRequestAsyncResult(this, to, via, clientCertificateToken, ref timeoutHelper, callback, state); } public virtual IAsyncResult BeginGetWebRequest( EndpointAddress to, Uri via, ref TimeoutHelper timeoutHelper, AsyncCallback callback, object state) { return BeginGetWebRequest(to, via, null, ref timeoutHelper, callback, state); } public virtual HttpWebRequest EndGetWebRequest(IAsyncResult result) { return GetWebRequestAsyncResult.End(result); } public virtual bool WillGetWebRequestCompleteSynchronously() { return ((this.tokenProvider == null) && !Factory.ManualAddressing); } internal virtual void OnWebRequestCompleted(HttpWebRequest request) { // empty } class HttpChannelRequest : IRequest { HttpRequestChannel channel; HttpChannelFactory factory; EndpointAddress to; Uri via; HttpWebRequest webRequest; HttpAbortReason abortReason; ChannelBinding channelBinding; int webRequestCompleted; EventTraceActivity eventTraceActivity; const string ConnectionGroupPrefixMessagePropertyName = "HttpTransportConnectionGroupNamePrefix"; public HttpChannelRequest(HttpRequestChannel channel, HttpChannelFactory factory) { this.channel = channel; this.to = channel.RemoteAddress; this.via = channel.Via; this.factory = factory; } private string GetConnectionGroupPrefix(Message message) { object property; if (message.Properties.TryGetValue(ConnectionGroupPrefixMessagePropertyName, out property)) { string prefix = property as string; if (prefix != null) { return prefix; } } return string.Empty; } public void SendRequest(Message message, TimeSpan timeout) { TimeoutHelper timeoutHelper = new TimeoutHelper(timeout); factory.ApplyManualAddressing(ref this.to, ref this.via, message); this.webRequest = channel.GetWebRequest(this.to, this.via, ref timeoutHelper); this.webRequest.ConnectionGroupName = GetConnectionGroupPrefix(message) + this.webRequest.ConnectionGroupName; 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 web request and bail Cleanup(); channel.ThrowIfDisposedOrNotOpen(); } HttpChannelUtilities.SetRequestTimeout(this.webRequest, timeoutHelper.RemainingTime()); HttpOutput httpOutput = HttpOutput.CreateHttpOutput(this.webRequest, this.factory, request, this.factory.IsChannelBindingSupportEnabled); bool success = false; try { httpOutput.Send(timeoutHelper.RemainingTime()); this.channelBinding = httpOutput.TakeChannelBinding(); httpOutput.Close(); success = true; if (FxTrace.Trace.IsEnd2EndActivityTracingEnabled) { this.eventTraceActivity = EventTraceActivityHelper.TryExtractActivity(message); if (TD.MessageSentByTransportIsEnabled()) { TD.MessageSentByTransport(eventTraceActivity, this.to.Uri.AbsoluteUri); } } } finally { if (!success) { httpOutput.Abort(HttpAbortReason.Aborted); } } } finally { if (!object.ReferenceEquals(request, message)) { request.Close(); } } } void Cleanup() { if (this.webRequest != null) { HttpChannelUtilities.AbortRequest(this.webRequest); this.TryCompleteWebRequest(this.webRequest); } ChannelBindingUtility.Dispose(ref this.channelBinding); } public void Abort(RequestChannel channel) { Cleanup(); abortReason = HttpAbortReason.Aborted; } public void Fault(RequestChannel channel) { Cleanup(); } [System.Diagnostics.CodeAnalysis.SuppressMessage(FxCop.Category.ReliabilityBasic, "Reliability104", Justification = "This is an old method from previous release.")] public Message WaitForReply(TimeSpan timeout) { if (TD.HttpResponseReceiveStartIsEnabled()) { TD.HttpResponseReceiveStart(this.eventTraceActivity); } HttpWebResponse response = null; WebException responseException = null; try { try { response = (HttpWebResponse)webRequest.GetResponse(); } catch (NullReferenceException nullReferenceException) { // workaround for Whidbey bug #558605 - only happens in streamed case. if (TransferModeHelper.IsRequestStreamed(this.factory.transferMode)) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError( HttpChannelUtilities.CreateNullReferenceResponseException(nullReferenceException)); } throw; } if (TD.MessageReceivedByTransportIsEnabled()) { TD.MessageReceivedByTransport(this.eventTraceActivity ?? EventTraceActivity.Empty, response.ResponseUri != null ? response.ResponseUri.AbsoluteUri : string.Empty, EventTraceActivity.GetActivityIdFromThread()); } if (DiagnosticUtility.ShouldTraceVerbose) { HttpChannelFactory.TraceResponseReceived(response, null, this); } } catch (WebException webException) { responseException = webException; response = HttpChannelUtilities.ProcessGetResponseWebException(webException, this.webRequest, abortReason); } HttpInput httpInput = HttpChannelUtilities.ValidateRequestReplyResponse(this.webRequest, response, this.factory, responseException, this.channelBinding); this.channelBinding = null; Message replyMessage = null; if (httpInput != null) { Exception exception = null; replyMessage = httpInput.ParseIncomingMessage(out exception); Fx.Assert(exception == null, "ParseIncomingMessage should not set an exception after parsing a response message."); if (replyMessage != null) { HttpChannelUtilities.AddReplySecurityProperty(this.factory, this.webRequest, response, replyMessage); if (FxTrace.Trace.IsEnd2EndActivityTracingEnabled && (eventTraceActivity != null)) { EventTraceActivityHelper.TryAttachActivity(replyMessage, eventTraceActivity); } } } this.TryCompleteWebRequest(this.webRequest); return replyMessage; } public void OnReleaseRequest() { this.TryCompleteWebRequest(this.webRequest); } void TryCompleteWebRequest(HttpWebRequest request) { if (request == null) { return; } if (Interlocked.CompareExchange(ref this.webRequestCompleted, 1, 0) == 0) { this.channel.OnWebRequestCompleted(request); } } } class HttpChannelAsyncRequest : TraceAsyncResult, IAsyncRequest { static AsyncCallback onProcessIncomingMessage = Fx.ThunkCallback(new AsyncCallback(OnParseIncomingMessage)); static AsyncCallback onGetResponse = Fx.ThunkCallback(new AsyncCallback(OnGetResponse)); static AsyncCallback onGetWebRequestCompleted; static AsyncCallback onSend = Fx.ThunkCallback(new AsyncCallback(OnSend)); static Action onSendTimeout; ChannelBinding channelBinding; HttpChannelFactory factory; HttpRequestChannel channel; HttpOutput httpOutput; HttpInput httpInput; Message message; Message requestMessage; Message replyMessage; HttpWebResponse response; HttpWebRequest request; object sendLock = new object(); IOThreadTimer sendTimer; TimeoutHelper timeoutHelper; EndpointAddress to; Uri via; HttpAbortReason abortReason; int webRequestCompleted; EventTraceActivity eventTraceActivity; public HttpChannelAsyncRequest(HttpRequestChannel channel, AsyncCallback callback, object state) : base(callback, state) { this.channel = channel; this.to = channel.RemoteAddress; this.via = channel.Via; this.factory = channel.Factory; } IOThreadTimer SendTimer { get { if (this.sendTimer == null) { if (onSendTimeout == null) { onSendTimeout = new Action(OnSendTimeout); } this.sendTimer = new IOThreadTimer(onSendTimeout, this, false); } return this.sendTimer; } } public static void End(IAsyncResult result) { AsyncResult.End(result); } public void BeginSendRequest(Message message, TimeSpan timeout) { this.message = this.requestMessage = message; this.timeoutHelper = new TimeoutHelper(timeout); if (FxTrace.Trace.IsEnd2EndActivityTracingEnabled) { this.eventTraceActivity = EventTraceActivityHelper.TryExtractActivity(message); } factory.ApplyManualAddressing(ref this.to, ref this.via, this.requestMessage); if (this.channel.WillGetWebRequestCompleteSynchronously()) { SetWebRequest(channel.GetWebRequest(this.to, this.via, ref this.timeoutHelper)); if (this.SendWebRequest()) { base.Complete(true); } } else { if (onGetWebRequestCompleted == null) { onGetWebRequestCompleted = Fx.ThunkCallback( new AsyncCallback(OnGetWebRequestCompletedCallback)); } IAsyncResult result = channel.BeginGetWebRequest( to, via, ref this.timeoutHelper, onGetWebRequestCompleted, this); if (result.CompletedSynchronously) { if (TD.MessageSentByTransportIsEnabled()) { TD.MessageSentByTransport(this.eventTraceActivity, this.to.Uri.AbsoluteUri); } if (this.OnGetWebRequestCompleted(result)) { base.Complete(true); } } } } static void OnGetWebRequestCompletedCallback(IAsyncResult result) { if (result.CompletedSynchronously) { return; } HttpChannelAsyncRequest thisPtr = (HttpChannelAsyncRequest)result.AsyncState; Exception completionException = null; bool completeSelf; try { completeSelf = thisPtr.OnGetWebRequestCompleted(result); } #pragma warning suppress 56500 // Microsoft, transferring exception to another thread catch (Exception e) { if (Fx.IsFatal(e)) { throw; } completeSelf = true; completionException = e; } if (completeSelf) { thisPtr.Complete(false, completionException); } } void AbortSend() { CancelSendTimer(); if (this.request != null) { this.TryCompleteWebRequest(this.request); this.abortReason = HttpAbortReason.TimedOut; httpOutput.Abort(this.abortReason); } } void CancelSendTimer() { lock (sendLock) { if (this.sendTimer != null) { this.sendTimer.Cancel(); this.sendTimer = null; } } } bool OnGetWebRequestCompleted(IAsyncResult result) { SetWebRequest(this.channel.EndGetWebRequest(result)); return this.SendWebRequest(); } bool SendWebRequest() { this.httpOutput = HttpOutput.CreateHttpOutput(this.request, this.factory, this.requestMessage, this.factory.IsChannelBindingSupportEnabled); bool success = false; try { bool result = false; SetSendTimeout(timeoutHelper.RemainingTime()); IAsyncResult asyncResult = httpOutput.BeginSend(timeoutHelper.RemainingTime(), onSend, this); success = true; if (asyncResult.CompletedSynchronously) { result = CompleteSend(asyncResult); } return result; } finally { if (!success) { this.httpOutput.Abort(HttpAbortReason.Aborted); if (!object.ReferenceEquals(this.message, this.requestMessage)) { this.requestMessage.Close(); } } } } bool CompleteSend(IAsyncResult result) { bool success = false; try { httpOutput.EndSend(result); this.channelBinding = httpOutput.TakeChannelBinding(); httpOutput.Close(); success = true; if (TD.MessageSentByTransportIsEnabled()) { TD.MessageSentByTransport(this.eventTraceActivity, this.to.Uri.AbsoluteUri); } } finally { if (!success) { httpOutput.Abort(HttpAbortReason.Aborted); } if (!object.ReferenceEquals(this.message, this.requestMessage)) { this.requestMessage.Close(); } } try { IAsyncResult getResponseResult; try { getResponseResult = request.BeginGetResponse(onGetResponse, this); } catch (NullReferenceException nullReferenceException) { // workaround for Whidbey bug #558605 - only happens in streamed case. if (TransferModeHelper.IsRequestStreamed(this.factory.transferMode)) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError( HttpChannelUtilities.CreateNullReferenceResponseException(nullReferenceException)); } throw; } if (getResponseResult.CompletedSynchronously) { return CompleteGetResponse(getResponseResult); } return false; } catch (IOException ioException) { throw TraceUtility.ThrowHelperError(new CommunicationException(ioException.Message, ioException), this.requestMessage); } catch (WebException webException) { throw TraceUtility.ThrowHelperError(new CommunicationException(webException.Message, webException), this.requestMessage); } catch (ObjectDisposedException objectDisposedException) { if (abortReason == HttpAbortReason.Aborted) { throw TraceUtility.ThrowHelperError(new CommunicationObjectAbortedException(SR.GetString(SR.HttpRequestAborted, to.Uri), objectDisposedException), this.requestMessage); } throw TraceUtility.ThrowHelperError(new TimeoutException(SR.GetString(SR.HttpRequestTimedOut, to.Uri, this.timeoutHelper.OriginalTimeout), objectDisposedException), this.requestMessage); } } [System.Diagnostics.CodeAnalysis.SuppressMessage(FxCop.Category.ReliabilityBasic, "Reliability104", Justification = "This is an old method from previous release.")] bool CompleteGetResponse(IAsyncResult result) { using (ServiceModelActivity.BoundOperation(this.channel.Activity)) { HttpWebResponse response = null; WebException responseException = null; try { try { CancelSendTimer(); response = (HttpWebResponse)request.EndGetResponse(result); } catch (NullReferenceException nullReferenceException) { // workaround for Whidbey bug #558605 - only happens in streamed case. if (TransferModeHelper.IsRequestStreamed(this.factory.transferMode)) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError( HttpChannelUtilities.CreateNullReferenceResponseException(nullReferenceException)); } throw; } if (TD.MessageReceivedByTransportIsEnabled()) { TD.MessageReceivedByTransport( this.eventTraceActivity ?? EventTraceActivity.Empty, this.to.Uri.AbsoluteUri, EventTraceActivity.GetActivityIdFromThread()); } if (DiagnosticUtility.ShouldTraceVerbose) { HttpChannelFactory.TraceResponseReceived(response, this.message, this); } } catch (WebException webException) { responseException = webException; response = HttpChannelUtilities.ProcessGetResponseWebException(webException, request, abortReason); } return ProcessResponse(response, responseException); } } void Cleanup() { if (this.request != null) { HttpChannelUtilities.AbortRequest(this.request); this.TryCompleteWebRequest(this.request); } ChannelBindingUtility.Dispose(ref this.channelBinding); } void SetSendTimeout(TimeSpan timeout) { // We also set the timeout on the HttpWebRequest so that we can subsequently use it in the // exception message in the event of a timeout. HttpChannelUtilities.SetRequestTimeout(this.request, timeout); if (timeout == TimeSpan.MaxValue) { CancelSendTimer(); } else { SendTimer.Set(timeout); } } public void Abort(RequestChannel channel) { Cleanup(); abortReason = HttpAbortReason.Aborted; } public void Fault(RequestChannel channel) { Cleanup(); } void SetWebRequest(HttpWebRequest webRequest) { this.request = webRequest; if (channel.State != CommunicationState.Opened) { // if we were aborted while getting our request, we need to abort the web request and bail Cleanup(); channel.ThrowIfDisposedOrNotOpen(); } } public Message End() { HttpChannelAsyncRequest.End(this); return replyMessage; } bool ProcessResponse(HttpWebResponse response, WebException responseException) { this.httpInput = HttpChannelUtilities.ValidateRequestReplyResponse(this.request, response, this.factory, responseException, this.channelBinding); this.channelBinding = null; if (httpInput != null) { this.response = response; IAsyncResult result = httpInput.BeginParseIncomingMessage(onProcessIncomingMessage, this); if (!result.CompletedSynchronously) { return false; } CompleteParseIncomingMessage(result); } else { this.replyMessage = null; } this.TryCompleteWebRequest(this.request); return true; } void CompleteParseIncomingMessage(IAsyncResult result) { Exception exception = null; this.replyMessage = this.httpInput.EndParseIncomingMessage(result, out exception); Fx.Assert(exception == null, "ParseIncomingMessage should not set an exception after parsing a response message."); if (this.replyMessage != null) { HttpChannelUtilities.AddReplySecurityProperty(this.factory, this.request, this.response, this.replyMessage); } } static void OnParseIncomingMessage(IAsyncResult result) { if (result.CompletedSynchronously) { return; } HttpChannelAsyncRequest thisPtr = (HttpChannelAsyncRequest)result.AsyncState; Exception completionException = null; try { thisPtr.CompleteParseIncomingMessage(result); } #pragma warning suppress 56500 // Microsoft, transferring exception to another thread catch (Exception e) { if (Fx.IsFatal(e)) { throw; } completionException = e; } thisPtr.Complete(false, completionException); } static void OnSend(IAsyncResult result) { if (result.CompletedSynchronously) { return; } HttpChannelAsyncRequest thisPtr = (HttpChannelAsyncRequest)result.AsyncState; Exception completionException = null; bool completeSelf; try { completeSelf = thisPtr.CompleteSend(result); } #pragma warning suppress 56500 // Microsoft, transferring exception to another thread catch (Exception e) { if (Fx.IsFatal(e)) { throw; } completeSelf = true; completionException = e; } if (completeSelf) { thisPtr.Complete(false, completionException); } } static void OnSendTimeout(object state) { HttpChannelAsyncRequest thisPtr = (HttpChannelAsyncRequest)state; thisPtr.AbortSend(); } [System.Diagnostics.CodeAnalysis.SuppressMessage(FxCop.Category.ReliabilityBasic, "Reliability104", Justification = "This is an old method from previous release.")] static void OnGetResponse(IAsyncResult result) { if (result.CompletedSynchronously) { return; } HttpChannelAsyncRequest thisPtr = (HttpChannelAsyncRequest)result.AsyncState; Exception completionException = null; bool completeSelf; try { completeSelf = thisPtr.CompleteGetResponse(result); } catch (WebException webException) { completeSelf = true; completionException = new CommunicationException(webException.Message, webException); } #pragma warning suppress 56500 // Microsoft, transferring exception to another thread catch (Exception e) { if (Fx.IsFatal(e)) { throw; } completeSelf = true; completionException = e; } if (completeSelf) { thisPtr.Complete(false, completionException); } } public void OnReleaseRequest() { this.TryCompleteWebRequest(this.request); } void TryCompleteWebRequest(HttpWebRequest request) { if (request == null) { return; } if (Interlocked.CompareExchange(ref this.webRequestCompleted, 1, 0) == 0) { this.channel.OnWebRequestCompleted(request); } } } class GetWebRequestAsyncResult : AsyncResult { static AsyncCallback onGetSspiCredential; static AsyncCallback onGetUserNameCredential; SecurityTokenContainer clientCertificateToken; HttpChannelFactory factory; SecurityTokenProviderContainer proxyTokenProvider; HttpWebRequest request; EndpointAddress to; TimeoutHelper timeoutHelper; SecurityTokenProviderContainer tokenProvider; Uri via; public GetWebRequestAsyncResult(HttpRequestChannel channel, EndpointAddress to, Uri via, SecurityTokenContainer clientCertificateToken, ref TimeoutHelper timeoutHelper, AsyncCallback callback, object state) : base(callback, state) { this.to = to; this.via = via; this.clientCertificateToken = clientCertificateToken; this.timeoutHelper = timeoutHelper; this.factory = channel.Factory; this.tokenProvider = channel.tokenProvider; this.proxyTokenProvider = channel.proxyTokenProvider; if (factory.ManualAddressing) { this.factory.CreateAndOpenTokenProviders(to, via, channel.channelParameters, timeoutHelper.RemainingTime(), out this.tokenProvider, out this.proxyTokenProvider); } bool completeSelf = false; IAsyncResult result = null; if (factory.AuthenticationScheme == AuthenticationSchemes.Anonymous) { SetupWebRequest(AuthenticationLevel.None, TokenImpersonationLevel.None, null); completeSelf = true; } else if (factory.AuthenticationScheme == AuthenticationSchemes.Basic) { if (onGetUserNameCredential == null) { onGetUserNameCredential = Fx.ThunkCallback(new AsyncCallback(OnGetUserNameCredential)); } result = TransportSecurityHelpers.BeginGetUserNameCredential( tokenProvider, timeoutHelper.RemainingTime(), onGetUserNameCredential, this); if (result.CompletedSynchronously) { CompleteGetUserNameCredential(result); completeSelf = true; } } else { if (onGetSspiCredential == null) { onGetSspiCredential = Fx.ThunkCallback(new AsyncCallback(OnGetSspiCredential)); } result = TransportSecurityHelpers.BeginGetSspiCredential( tokenProvider, timeoutHelper.RemainingTime(), onGetSspiCredential, this); if (result.CompletedSynchronously) { CompleteGetSspiCredential(result); completeSelf = true; } } if (completeSelf) { CloseTokenProvidersIfRequired(); base.Complete(true); } } public static HttpWebRequest End(IAsyncResult result) { GetWebRequestAsyncResult thisPtr = AsyncResult.End(result); return thisPtr.request; } void CompleteGetUserNameCredential(IAsyncResult result) { NetworkCredential credential = TransportSecurityHelpers.EndGetUserNameCredential(result); SetupWebRequest(AuthenticationLevel.None, TokenImpersonationLevel.None, credential); } void CompleteGetSspiCredential(IAsyncResult result) { AuthenticationLevel authenticationLevel; TokenImpersonationLevel impersonationLevel; NetworkCredential credential = TransportSecurityHelpers.EndGetSspiCredential(result, out impersonationLevel, out authenticationLevel); if (factory.AuthenticationScheme == AuthenticationSchemes.Digest) { HttpChannelUtilities.ValidateDigestCredential(ref credential, impersonationLevel); } else if (factory.AuthenticationScheme == AuthenticationSchemes.Ntlm) { if (authenticationLevel == AuthenticationLevel.MutualAuthRequired) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException( SR.GetString(SR.CredentialDisallowsNtlm))); } } SetupWebRequest(authenticationLevel, impersonationLevel, credential); } void SetupWebRequest(AuthenticationLevel authenticationLevel, TokenImpersonationLevel impersonationLevel, NetworkCredential credential) { this.request = factory.GetWebRequest(to, via, credential, impersonationLevel, authenticationLevel, this.proxyTokenProvider, this.clientCertificateToken, timeoutHelper.RemainingTime(), false); } void CloseTokenProvidersIfRequired() { if (this.factory.ManualAddressing) { if (this.tokenProvider != null) { tokenProvider.Abort(); } if (this.proxyTokenProvider != null) { proxyTokenProvider.Abort(); } } } static void OnGetSspiCredential(IAsyncResult result) { if (result.CompletedSynchronously) { return; } GetWebRequestAsyncResult thisPtr = (GetWebRequestAsyncResult)result.AsyncState; Exception completionException = null; try { thisPtr.CompleteGetSspiCredential(result); thisPtr.CloseTokenProvidersIfRequired(); } #pragma warning suppress 56500 // Microsoft, transferring exception to another thread catch (Exception e) { if (Fx.IsFatal(e)) { throw; } completionException = e; } thisPtr.Complete(false, completionException); } static void OnGetUserNameCredential(IAsyncResult result) { if (result.CompletedSynchronously) { return; } GetWebRequestAsyncResult thisPtr = (GetWebRequestAsyncResult)result.AsyncState; Exception completionException = null; try { thisPtr.CompleteGetUserNameCredential(result); thisPtr.CloseTokenProvidersIfRequired(); } #pragma warning suppress 56500 // Microsoft, transferring exception to another thread catch (Exception e) { if (Fx.IsFatal(e)) { throw; } completionException = e; } thisPtr.Complete(false, completionException); } } } class WebProxyFactory { Uri address; bool bypassOnLocal; AuthenticationSchemes authenticationScheme; public WebProxyFactory(Uri address, bool bypassOnLocal, AuthenticationSchemes authenticationScheme) { this.address = address; this.bypassOnLocal = bypassOnLocal; if (!authenticationScheme.IsSingleton()) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument("value", SR.GetString(SR.HttpRequiresSingleAuthScheme, authenticationScheme)); } this.authenticationScheme = authenticationScheme; } internal AuthenticationSchemes AuthenticationScheme { get { return authenticationScheme; } } public IWebProxy CreateWebProxy(HttpWebRequest request, SecurityTokenProviderContainer tokenProvider, TimeSpan timeout) { WebProxy result = new WebProxy(this.address, this.bypassOnLocal); if (this.authenticationScheme != AuthenticationSchemes.Anonymous) { TokenImpersonationLevel impersonationLevel; AuthenticationLevel authenticationLevel; NetworkCredential credential = HttpChannelUtilities.GetCredential(this.authenticationScheme, tokenProvider, timeout, out impersonationLevel, out authenticationLevel); // 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, request.ImpersonationLevel)) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SR.GetString( SR.ProxyImpersonationLevelMismatch, impersonationLevel, request.ImpersonationLevel))); } // 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) && (request.AuthenticationLevel != AuthenticationLevel.MutualAuthRequired)) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SR.GetString( SR.ProxyAuthenticationLevelMismatch, authenticationLevel, request.AuthenticationLevel))); } CredentialCache credentials = new CredentialCache(); credentials.Add(this.address, AuthenticationSchemesHelper.ToString(this.authenticationScheme), credential); result.Credentials = credentials; } return result; } } } }