File: System\ServiceModel\Channels\HttpChannelFactory.cs
Project: ndp\cdf\src\WCF\ServiceModel\System.ServiceModel.csproj (System.ServiceModel)
//------------------------------------------------------------
// 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<TChannel>
        : TransportChannelFactory<TChannel>,
        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<Uri, Uri> 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<string, string> 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<string> 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<HttpCookieContainerBindingElement> httpCookieContainerBindingElements = context.BindingParameters.FindAll<HttpCookieContainerBindingElement>();
                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<HttpCookieContainerBindingElement>();
                }
            }
#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<SecurityCredentialsManager>();
            this.securityCapabilities = bindingElement.GetProperty<ISecurityCapabilities>(context);
            this.webSocketSettings = WebSocketHelper.GetRuntimeWebSocketSettings(bindingElement.WebSocketSettings);
 
            int webSocketBufferSize = WebSocketHelper.ComputeClientBufferSize(this.MaxReceivedMessageSize);
            this.bufferPool = new ConnectionBufferPool(webSocketBufferSize);
 
            Collection<ClientWebSocketFactory> clientWebSocketFactories = context.BindingParameters.FindAll<ClientWebSocketFactory>();
            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<string>(() => { 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<T>()
        {
            if (typeof(T) == typeof(ISecurityCapabilities))
            {
                return (T)(object)this.securityCapabilities;
            }
            if (typeof(T) == typeof(IHttpCookieContainerManager))
            {
                return (T)(object)this.GetHttpCookieContainerManager();
            }
 
            return base.GetProperty<T>();
        }
 
        [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<IAuthorizationPolicy> 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<IRequestChannel>)(object)this, remoteAddress, via, ManualAddressing);
            }
            else
            {
                return (TChannel)(object)new ClientWebSocketTransportDuplexSessionChannel((HttpChannelFactory<IDuplexSessionChannel>)(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<string, string>(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<Uri, Uri>(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<IRequestChannel> factory;
            SecurityTokenProviderContainer tokenProvider;
            SecurityTokenProviderContainer proxyTokenProvider;
            ServiceModelActivity activity = null;
            ChannelParameterCollection channelParameters;
 
            public HttpRequestChannel(HttpChannelFactory<IRequestChannel> factory, EndpointAddress to, Uri via, bool manualAddressing)
                : base(factory, to, via, manualAddressing)
            {
                this.factory = factory;
            }
 
            public HttpChannelFactory<IRequestChannel> Factory
            {
                get { return this.factory; }
            }
 
            internal ServiceModelActivity Activity
            {
                get { return this.activity; }
            }
 
            protected ChannelParameterCollection ChannelParameters
            {
                get
                {
                    return this.channelParameters;
                }
            }
 
            public override T GetProperty<T>()
            {
                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<T>();
                }
            }
 
            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<IRequestChannel> factory;
                EndpointAddress to;
                Uri via;
                HttpWebRequest webRequest;
                HttpAbortReason abortReason;
                ChannelBinding channelBinding;
                int webRequestCompleted;
                EventTraceActivity eventTraceActivity;
                const string ConnectionGroupPrefixMessagePropertyName = "HttpTransportConnectionGroupNamePrefix";
 
                public HttpChannelRequest(HttpRequestChannel channel, HttpChannelFactory<IRequestChannel> 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<TChannel>.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<object> onSendTimeout;
                ChannelBinding channelBinding;
 
                HttpChannelFactory<IRequestChannel> 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<object>(OnSendTimeout);
                            }
 
                            this.sendTimer = new IOThreadTimer(onSendTimeout, this, false);
                        }
 
                        return this.sendTimer;
                    }
                }
 
                public static void End(IAsyncResult result)
                {
                    AsyncResult.End<HttpChannelAsyncRequest>(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<TChannel>.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<IRequestChannel> 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<GetWebRequestAsyncResult>(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;
            }
        }
    }
}