NetWebProxyFinder.cs source code in C# .NET

Source code for the .NET framework in C#

                        

Code:

/ 4.0 / 4.0 / untmp / DEVDIV_TFS / Dev10 / Releases / RTMRel / ndp / fx / src / Net / System / Net / NetWebProxyFinder.cs / 1305376 / NetWebProxyFinder.cs

                            using System.IO; 
using System.Collections;
using System.Collections.Specialized;
using System.Threading;
using System.Text; 
using System.Net.Cache;
using System.Globalization; 
using System.Net.Configuration; 
using System.Security.Permissions;
using System.Collections.Generic; 
using System.Runtime.InteropServices;
using Microsoft.Win32;
using System.Diagnostics.CodeAnalysis;
 
namespace System.Net
{ 
    // This WebProxyFinder implementation has the following purpose: 
    // - use WinHttp APIs to determine the location of the PAC file
    // - use System.Net classes (WebRequest) to download the PAC file 
    // - use Microsoft.JScript to compile and execute the JavaScript in the PAC file.
    internal sealed class NetWebProxyFinder : BaseWebProxyFinder
    {
        private static readonly char[] splitChars = new char[] { ';' }; 
        private static TimerThread.Queue timerQueue;
        private static readonly TimerThread.Callback timerCallback = new TimerThread.Callback(RequestTimeoutCallback); 
        private static readonly WaitCallback abortWrapper = new WaitCallback(AbortWrapper); 

        private RequestCache backupCache; 
        private AutoWebProxyScriptWrapper scriptInstance;
        private Uri engineScriptLocation;
        private Uri scriptLocation;
        private bool scriptDetectionFailed; 
        private object lockObject;
        // Keep the following fields volatile, since we're accessing them outside of lock blocks 
        private volatile WebRequest request; 
        private volatile bool aborted;
 
        public NetWebProxyFinder(AutoWebProxyScriptEngine engine)
            : base(engine)
        {
            backupCache = new SingleItemRequestCache(RequestCacheManager.IsCachingEnabled); 
            lockObject = new object();
        } 
 
        public override bool GetProxies(Uri destination, out IList proxyList)
        { 
            try
            {
                proxyList = null;
 
                EnsureEngineAvailable();
 
                // after EnsureEngineAvailable we expect State to be CompilationSuccess, otherwise return. 
                if (State != AutoWebProxyState.Completed)
                { 
                    // the script can't run, say we're not ready and bypass
                    return false;
                }
 
                bool result = false;
                try 
                { 
                    string proxyListString = scriptInstance.FindProxyForURL(destination.ToString(), destination.Host);
                    GlobalLog.Print("NetWebProxyFinder#" + ValidationHelper.HashString(this) + "::GetProxies() calling ExecuteFindProxyForURL() for destination:" + ValidationHelper.ToString(destination) + " returned scriptReturn:" + ValidationHelper.ToString(proxyList)); 

                    proxyList = ParseScriptResult(proxyListString);

                    result = true; 
                }
                catch (Exception exception) 
                { 
                    if (Logging.On) Logging.PrintWarning(Logging.Web, SR.GetString(SR.net_log_proxy_script_execution_error, exception));
                } 

                return result;
            }
            finally 
            {
                // Reset state of 'aborted', since next call to GetProxies() must not use previous aborted state. 
                aborted = false; 
            }
        } 

        public override void Abort()
        {
            // All we abort is a running WebRequest. The following lock (and the one in DownloadAndCompile) 
            // is used to "atomically" access the two fields 'aborted' and 'request': If Abort() gets
            // called before 'request' is set, the 'aborted' field will signal to DownloadAndCompile, that 
            // it should not bother creating a request and just throw. If 'request' was already created 
            // by DownloadAndCompile, the following code will make sure the request gets aborted.
            lock (lockObject) 
            {
                aborted = true;

                if (request != null) 
                {
                    ThreadPool.UnsafeQueueUserWorkItem(abortWrapper, request); 
                } 
            }
        } 

        protected override void Dispose(bool disposing)
        {
            if (disposing) 
            {
                if (scriptInstance != null) 
                { 
                    scriptInstance.Close();
                } 
            }
        }

        // Ensures that (if state is AutoWebProxyState.CompilationSuccess) there is an engine available to execute script. 
        // Figures out the script location (might discover if needed).
        // Calls DownloadAndCompile(). 
        private void EnsureEngineAvailable() 
        {
            GlobalLog.Enter("NetWebProxyFinder#" + ValidationHelper.HashString(this) + "::EnsureEngineAvailable"); 

            if (State == AutoWebProxyState.Uninitialized || engineScriptLocation == null)
            {
#if !FEATURE_PAL 
                if (Engine.AutomaticallyDetectSettings)
                { 
                    GlobalLog.Print("NetWebProxyFinder#" + ValidationHelper.HashString(this) + "::EnsureEngineAvailable() Attempting auto-detection."); 
                    DetectScriptLocation();
                    if (scriptLocation != null) 
                    {
                        //
                        // Successfully detected or user has flipped the automaticallyDetectSettings bit.
                        // Attempt a non conclusive DownloadAndCompile() so we can fallback 
                        //
                        GlobalLog.Print("NetWebProxyFinder#" + ValidationHelper.HashString(this) + "::EnsureEngineAvailable() discovered:" + ValidationHelper.ToString(scriptLocation) + " engineScriptLocation:" + ValidationHelper.ToString(engineScriptLocation)); 
                        if (scriptLocation.Equals(engineScriptLocation)) 
                        {
                            State = AutoWebProxyState.Completed; 
                            GlobalLog.Leave("NetWebProxyFinder#" + ValidationHelper.HashString(this) + "::EnsureEngineAvailable", ValidationHelper.ToString(State));
                            return;
                        }
                        AutoWebProxyState newState = DownloadAndCompile(scriptLocation); 
                        if (newState == AutoWebProxyState.Completed)
                        { 
                            State = AutoWebProxyState.Completed; 
                            engineScriptLocation = scriptLocation;
                            GlobalLog.Leave("NetWebProxyFinder#" + ValidationHelper.HashString(this) + "::EnsureEngineAvailable", ValidationHelper.ToString(State)); 
                            return;
                        }
                    }
                } 
#endif // !FEATURE_PAL
 
                // Either Auto-Detect wasn't enabled or something failed with it.  Try the manual script location. 
                if ((Engine.AutomaticConfigurationScript != null) && !aborted)
                { 
                    GlobalLog.Print("NetWebProxyFinder#" + ValidationHelper.HashString(this) + "::EnsureEngineAvailable() using automaticConfigurationScript:" + ValidationHelper.ToString(Engine.AutomaticConfigurationScript) + " engineScriptLocation:" + ValidationHelper.ToString(engineScriptLocation));
                    if (Engine.AutomaticConfigurationScript.Equals(engineScriptLocation))
                    {
                        State = AutoWebProxyState.Completed; 
                        GlobalLog.Leave("NetWebProxyFinder#" + ValidationHelper.HashString(this) + "::EnsureEngineAvailable", ValidationHelper.ToString(State));
                        return; 
                    } 
                    State = DownloadAndCompile(Engine.AutomaticConfigurationScript);
                    if (State == AutoWebProxyState.Completed) 
                    {
                        engineScriptLocation = Engine.AutomaticConfigurationScript;
                        GlobalLog.Leave("NetWebProxyFinder#" + ValidationHelper.HashString(this) + "::EnsureEngineAvailable", ValidationHelper.ToString(State));
                        return; 
                    }
                } 
            } 
            else
            { 
                // We always want to call DownloadAndCompile to check the expiration.
                GlobalLog.Print("NetWebProxyFinder#" + ValidationHelper.HashString(this) + "::EnsureEngineAvailable() State:" + State + " engineScriptLocation:" + ValidationHelper.ToString(engineScriptLocation));
                State = DownloadAndCompile(engineScriptLocation);
                if (State == AutoWebProxyState.Completed) 
                {
                    GlobalLog.Leave("NetWebProxyFinder#" + ValidationHelper.HashString(this) + "::EnsureEngineAvailable", ValidationHelper.ToString(State)); 
                    return; 
                }
 
                // There's still an opportunity to fail over to the automaticConfigurationScript.
                if (!engineScriptLocation.Equals(Engine.AutomaticConfigurationScript) && !aborted)
                {
                    GlobalLog.Print("NetWebProxyFinder#" + ValidationHelper.HashString(this) + "::EnsureEngineAvailable() Update failed.  Falling back to automaticConfigurationScript:" + ValidationHelper.ToString(Engine.AutomaticConfigurationScript)); 
                    State = DownloadAndCompile(Engine.AutomaticConfigurationScript);
                    if (State == AutoWebProxyState.Completed) 
                    { 
                        engineScriptLocation = Engine.AutomaticConfigurationScript;
                        GlobalLog.Leave("NetWebProxyFinder#" + ValidationHelper.HashString(this) + "::EnsureEngineAvailable", ValidationHelper.ToString(State)); 
                        return;
                    }
                }
            } 

            // Everything failed.  Set this instance to mostly-dead.  It will wake up again if there's a reg/connectoid change. 
            GlobalLog.Print("NetWebProxyFinder#" + ValidationHelper.HashString(this) + "::EnsureEngineAvailable() All failed."); 
            State = AutoWebProxyState.DiscoveryFailure;
 
            if (scriptInstance != null)
            {
                scriptInstance.Close();
                scriptInstance = null; 
            }
 
            engineScriptLocation = null; 

            GlobalLog.Leave("NetWebProxyFinder#" + ValidationHelper.HashString(this) + "::EnsureEngineAvailable", ValidationHelper.ToString(State)); 
        }


        // Downloads and compiles the script from a given Uri. 
        // This code can be called by config for a downloaded control, we need to assert.
        // This code is called holding the lock. 
        private AutoWebProxyState DownloadAndCompile(Uri location) 
        {
            GlobalLog.Print("NetWebProxyFinder#" + ValidationHelper.HashString(this) + "::DownloadAndCompile() location:" + ValidationHelper.ToString(location)); 
            AutoWebProxyState newState = AutoWebProxyState.DownloadFailure;
            WebResponse response = null;
            TimerThread.Timer timer = null;
            AutoWebProxyScriptWrapper newScriptInstance = null; 

            // Can't assert this in declarative form (DCR?). This Assert() is needed to be able to create the request to download the proxy script. 
            ExceptionHelper.WebPermissionUnrestricted.Assert(); 
            try
            { 
                lock (lockObject)
                {
                    if (aborted)
                    { 
                        throw new WebException(NetRes.GetWebStatusString("net_requestaborted",
                            WebExceptionStatus.RequestCanceled), WebExceptionStatus.RequestCanceled); 
                    } 

                    request = WebRequest.Create(location); 
                }

                request.Timeout = Timeout.Infinite;
                request.CachePolicy = new RequestCachePolicy(RequestCacheLevel.Default); 
                request.ConnectionGroupName = "__WebProxyScript";
 
                // We have an opportunity here, if caching is disabled AppDomain-wide, to override it with a 
                // custom, trivial cache-provider to get a similar semantic.
                // 
                // We also want to have a backup caching key in the case when IE has locked an expired script response
                //
                if (request.CacheProtocol != null)
                { 
                    GlobalLog.Print("NetWebProxyFinder#" + ValidationHelper.HashString(this) + "::DownloadAndCompile() Using backup caching.");
                    request.CacheProtocol = new RequestCacheProtocol(backupCache, request.CacheProtocol.Validator); 
                } 

                HttpWebRequest httpWebRequest = request as HttpWebRequest; 
                if (httpWebRequest != null)
                {
                    httpWebRequest.Accept = "*/*";
                    httpWebRequest.UserAgent = this.GetType().FullName + "/" + Environment.Version; 
                    httpWebRequest.KeepAlive = false;
                    httpWebRequest.Pipelined = false; 
                    httpWebRequest.InternalConnectionGroup = true; 
                }
                else 
                {
                    FtpWebRequest ftpWebRequest = request as FtpWebRequest;
                    if (ftpWebRequest != null)
                    { 
                        ftpWebRequest.KeepAlive = false;
                    } 
                } 

                // Use no proxy, default cache - initiate the download. 
                request.Proxy = null;
                request.Credentials = Engine.Credentials;

                // Use our own timeout timer so that it can encompass the whole request, not just the headers. 
                if (timerQueue == null)
                { 
                    timerQueue = TimerThread.GetOrCreateQueue(SettingsSectionInternal.Section.DownloadTimeout); 
                }
                timer = timerQueue.CreateTimer(timerCallback, request); 
                response = request.GetResponse();

                // Check Last Modified.
                DateTime lastModified = DateTime.MinValue; 
                HttpWebResponse httpResponse = response as HttpWebResponse;
                if (httpResponse != null) 
                { 
                    lastModified = httpResponse.LastModified;
                } 
                else
                {
                    FtpWebResponse ftpResponse = response as FtpWebResponse;
                    if (ftpResponse != null) 
                    {
                        lastModified = ftpResponse.LastModified; 
                    } 
                }
                GlobalLog.Print("NetWebProxyFinder#" + ValidationHelper.HashString(this) + "::DownloadAndCompile() lastModified:" + lastModified.ToString() + " (script):" + (scriptInstance == null ? "(null)" : scriptInstance.LastModified.ToString())); 
                if (scriptInstance != null && lastModified != DateTime.MinValue && scriptInstance.LastModified == lastModified)
                {
                    newScriptInstance = scriptInstance;
                    newState = AutoWebProxyState.Completed; 
                }
                else 
                { 
                    string scriptBody = null;
                    byte[] scriptBuffer = null; 
                    using (Stream responseStream = response.GetResponseStream())
                    {
                        SingleItemRequestCache.ReadOnlyStream ros = responseStream as SingleItemRequestCache.ReadOnlyStream;
                        if (ros != null) 
                        {
                            scriptBuffer = ros.Buffer; 
                        } 
                        if (scriptInstance != null && scriptBuffer != null && scriptBuffer == scriptInstance.Buffer)
                        { 
                            scriptInstance.LastModified = lastModified;
                            newScriptInstance = scriptInstance;
                            newState = AutoWebProxyState.Completed;
                            GlobalLog.Print("NetWebProxyFinder#" + ValidationHelper.HashString(this) + "::DownloadAndCompile() Buffer matched - reusing Engine."); 
                        }
                        else 
                        { 
                            using (StreamReader streamReader = new StreamReader(responseStream))
                            { 
                                scriptBody = streamReader.ReadToEnd();
                            }
                        }
                    } 

                    WebResponse tempResponse = response; 
                    response = null; 
                    tempResponse.Close();
                    timer.Cancel(); 
                    timer = null;

                    if (newState != AutoWebProxyState.Completed)
                    { 
                        GlobalLog.Print("NetWebProxyFinder#" + ValidationHelper.HashString(this) + "::DownloadAndCompile() IsFromCache:" + tempResponse.IsFromCache.ToString() + " scriptInstance:" + ValidationHelper.HashString(scriptInstance));
                        if (scriptInstance != null && scriptBody == scriptInstance.ScriptBody) 
                        { 
                            GlobalLog.Print("NetWebProxyFinder#" + ValidationHelper.HashString(this) + "::DownloadAndCompile() Script matched - using existing Engine.");
                            scriptInstance.LastModified = lastModified; 
                            if (scriptBuffer != null)
                            {
                                scriptInstance.Buffer = scriptBuffer;
                            } 
                            newScriptInstance = scriptInstance;
                            newState = AutoWebProxyState.Completed; 
                        } 
                        else
                        { 
                            GlobalLog.Print("NetWebProxyFinder#" + ValidationHelper.HashString(this) + "::DownloadAndCompile() Creating AutoWebProxyScriptWrapper.");
                            newScriptInstance = new AutoWebProxyScriptWrapper();
                            newScriptInstance.LastModified = lastModified;
 
                            if (newScriptInstance.Compile(location, scriptBody, scriptBuffer))
                            { 
                                newState = AutoWebProxyState.Completed; 
                            }
                            else 
                            {
                                newState = AutoWebProxyState.CompilationFailure;
                            }
                        } 
                    }
                } 
            } 
            catch (Exception exception)
            { 
                if (Logging.On) Logging.PrintWarning(Logging.Web, SR.GetString(SR.net_log_proxy_script_download_compile_error, exception));
                GlobalLog.Print("NetWebProxyFinder#" + ValidationHelper.HashString(this) + "::DownloadAndCompile() Download() threw:" + ValidationHelper.ToString(exception));
            }
            finally 
            {
                if (timer != null) 
                { 
                    timer.Cancel();
                } 

                //
                try
                { 
                    if (response != null)
                    { 
                        response.Close(); 
                    }
                } 
                finally
                {
                    WebPermission.RevertAssert();
 
                    // The request is not needed anymore. Set it to null, so if Abort() gets called,
                    // after this point, it will result in a no-op. 
                    request = null; 
                }
            } 

            if ((newState == AutoWebProxyState.Completed) && (scriptInstance != newScriptInstance))
            {
                if (scriptInstance != null) 
                {
                    scriptInstance.Close(); 
                } 

                scriptInstance = newScriptInstance; 
            }

            GlobalLog.Print("NetWebProxyFinder#" + ValidationHelper.HashString(this) + "::DownloadAndCompile() retuning newState:" + ValidationHelper.ToString(newState));
            return newState; 
        }
 
        private static IList ParseScriptResult(string scriptReturn) 
        {
            IList result = new List(); 

            if (scriptReturn == null)
            {
                return result; 
            }
 
            string[] proxyListStrings = scriptReturn.Split(splitChars); 
            string proxyAuthority;
            foreach (string s in proxyListStrings) 
            {
                string proxyString = s.Trim(' ');
                if (!proxyString.StartsWith("PROXY ", StringComparison.OrdinalIgnoreCase))
                { 
                    if (string.Compare("DIRECT", proxyString, StringComparison.OrdinalIgnoreCase) == 0)
                    { 
                        proxyAuthority = null; 
                    }
                    else 
                    {
                        continue;
                    }
                } 
                else
                { 
                    // remove prefix "PROXY " (6 chars) from the string and trim additional leading spaces. 
                    proxyAuthority = proxyString.Substring(6).TrimStart(' ');
                    Uri uri = null; 
                    bool tryParse = Uri.TryCreate("http://" + proxyAuthority, UriKind.Absolute, out uri);
                    if (!tryParse || uri.UserInfo.Length > 0 || uri.HostNameType == UriHostNameType.Basic || uri.AbsolutePath.Length != 1 || proxyAuthority[proxyAuthority.Length - 1] == '/' || proxyAuthority[proxyAuthority.Length - 1] == '#' || proxyAuthority[proxyAuthority.Length - 1] == '?')
                    {
                        continue; 
                    }
                } 
                result.Add(proxyAuthority); 
            }
 
            return result;
        }

        private void DetectScriptLocation() 
        {
            if (scriptDetectionFailed || scriptLocation != null) 
            { 
                return;
            } 

            GlobalLog.Print("NetWebProxyFinder::DetectScriptLocation() Attempting discovery PROXY_AUTO_DETECT_TYPE_DHCP.");
            scriptLocation = SafeDetectAutoProxyUrl(UnsafeNclNativeMethods.WinHttp.AutoDetectType.Dhcp);
 
            if (scriptLocation == null)
            { 
                GlobalLog.Print("NetWebProxyFinder::DetectScriptLocation() Attempting discovery AUTO_DETECT_TYPE_DNS_A."); 
                scriptLocation = SafeDetectAutoProxyUrl(UnsafeNclNativeMethods.WinHttp.AutoDetectType.DnsA);
            } 

            if (scriptLocation == null)
            {
                GlobalLog.Print("NetWebProxyFinder::DetectScriptLocation() Discovery failed."); 
                scriptDetectionFailed = true;
            } 
        } 

        // from wininet.h 
        //
        //  #define INTERNET_MAX_PATH_LENGTH        2048
        //  #define INTERNET_MAX_PROTOCOL_NAME      "gopher"    // longest protocol name
        //  #define INTERNET_MAX_URL_LENGTH         ((sizeof(INTERNET_MAX_PROTOCOL_NAME) - 1) \ 
        //                                          + sizeof("://") \
        //                                          + INTERNET_MAX_PATH_LENGTH) 
        // 
        private const int MaximumProxyStringLength = 2058;
 
        /// 
        ///     
        ///         Called to discover script location. This performs
        ///         autodetection using the method specified in the detectFlags. 
        ///     
        ///  
        [SuppressMessage("Microsoft.Reliability","CA2001:AvoidCallingProblematicMethods", MessageId="System.Runtime.InteropServices.SafeHandle.DangerousGetHandle", Justification="Implementation requires DangerousGetHandle")] 
        private static unsafe Uri SafeDetectAutoProxyUrl(
            UnsafeNclNativeMethods.WinHttp.AutoDetectType discoveryMethod) 
        {
            Uri autoProxy = null;

#if !FEATURE_PAL 
            string url = null;
            if (ComNetOS.IsWinHttp51) 
            { 
                GlobalLog.Print("NetWebProxyFinder::SafeDetectAutoProxyUrl() Using WinHttp.");
                SafeGlobalFree autoProxyUrl; 
                bool success = UnsafeNclNativeMethods.WinHttp.WinHttpDetectAutoProxyConfigUrl(discoveryMethod, out autoProxyUrl);
                if (!success)
                {
                    if (autoProxyUrl != null) 
                    {
                        autoProxyUrl.SetHandleAsInvalid(); 
                    } 
                }
                else 
                {
                    url = new string((char*)autoProxyUrl.DangerousGetHandle());
                    autoProxyUrl.Close();
                } 
            }
            else 
            { 
                GlobalLog.Print("NetWebProxyFinder::SafeDetectAutoProxyUrl() Using WinInet.");
                StringBuilder autoProxyUrl = new StringBuilder(MaximumProxyStringLength); 
                bool success = UnsafeNclNativeMethods.WinInet.DetectAutoProxyUrl(
                    autoProxyUrl,
                    MaximumProxyStringLength,
                    (int)discoveryMethod); 

                if (success) 
                { 
                    url = autoProxyUrl.ToString();
                } 
            }

            if (url != null)
            { 
                bool parsed = Uri.TryCreate(url, UriKind.Absolute, out autoProxy);
                if (!parsed) 
                { 
                    if (Logging.On) Logging.PrintWarning(Logging.Web, SR.GetString(SR.net_log_proxy_autodetect_script_location_parse_error, ValidationHelper.ToString(url)));
                    GlobalLog.Print("NetWebProxyFinder::SafeDetectAutoProxyUrl() Uri.TryParse() failed url:" + ValidationHelper.ToString(url)); 
                }
            }
            else
            { 
                if (Logging.On) Logging.PrintWarning(Logging.Web, SR.GetString(SR.net_log_proxy_autodetect_failed));
                GlobalLog.Print("NetWebProxyFinder::SafeDetectAutoProxyUrl() DetectAutoProxyUrl() returned false"); 
            } 
#endif // !FEATURE_PAL
 
            return autoProxy;
        }

        // RequestTimeoutCallback - Called by the TimerThread to abort a request.  This just posts ThreadPool work item - Abort() does too 
        // much to be done on the timer thread (timer thread should never block or call user code).
        private static void RequestTimeoutCallback(TimerThread.Timer timer, int timeNoticed, object context) 
        { 
            ThreadPool.UnsafeQueueUserWorkItem(abortWrapper, context);
        } 

        private static void AbortWrapper(object context)
        {
#if DEBUG 
            GlobalLog.SetThreadSource(ThreadKinds.Worker);
            using (GlobalLog.SetThreadKind(ThreadKinds.System)) 
            { 
#endif
                if (context != null) 
                {
                    ((WebRequest)context).Abort();
                }
#if DEBUG 
            }
#endif 
        } 
    }
} 

// File provided for Reference Use Only by Microsoft Corporation (c) 2007.
// Copyright (c) Microsoft Corporation. All rights reserved.


                        

Link Menu

Network programming in C#, Network Programming in VB.NET, Network Programming in .NET
This book is available now!
Buy at Amazon US or
Buy at Amazon UK