2

I need to parse a proxy.pac in the C# code. Getting the proxy via WebProxy-method is not an option, because I read the proxy settings from a file, not from the current system.

I tried Jint and ClearScript to use a nested JavaScript engine inside the C# code. But both end up failing to read the "shExpMatch"-method inside my proxy.pac.

The file is very simple and works on my current system:

function FindProxyForURL(url, host) {
   if (shExpMatch(host, "192.*")) {
      return "DIRECT";
   }

   return "PROXY proxyServer:8080; PROXY proxyServer2:80";
}

The old implementation of this tool was using Microsoft.JScript.Vsa.VsaEngine.CreateEngine(); which is deprecated since Visual Studio 2005 (over 10 years ago!) and all they did was: start a JS engine, read or download and read file, result = engine.evaluate(script). Then they could use this result-object with the new url and host et voilà.

Why does none of these JS Engines know the shExpMatch-method?

ecth
  • 1,215
  • 1
  • 16
  • 33
  • What has this to do with WPF? –  Jun 01 '16 at 08:19
  • It's C# WPF, not C# WinForms, C# Universal App. There might be solutions that work for 1 platform but not for another. So to make it clear, I tagged it as WPF. – ecth Jun 01 '16 at 08:21
  • As I said, I can't see how this relates to _Windows Presentation Foundation_ –  Jun 01 '16 at 08:47
  • Okay. I doubt I am the only confused person, when MS throws a lot of platforms and technology and mixes them up together. But this is a C# WPF-Project. There are methods and Libraries that don't work with this type of project because they only work with Windows Universal App Projects or WinForms Projects. So to point that out I tagged this as WPF. – ecth Jun 01 '16 at 08:59

2 Answers2

4

The API that browsers make available to proxy.pac code isn't part of the JavaScript standard, so generic JavaScript engines such as Jint and V8 don't provide methods such as shExpMatch. It is the responsibility of the browser (or other host) to expose this API.

EDIT: Take a look at Mozilla's implementation here. Most of it is in the pacUtils string. You could define this string in your C# code and pass it into your script engine. That would just leave dnsResolve, myIpAddress, and alert, all of which need some help from the host. Here's a ClearScript sample:

Func<string, string> dnsResolve = name => {
    var addrs = Dns.GetHostEntry(name).AddressList;
    return addrs.First(a => a.AddressFamily == AddressFamily.InterNetwork).ToString();
};

engine.Execute(pacUtils);
engine.Script.dnsResolve = dnsResolve;
engine.Script.myIpAddress = new Func<string>(() => dnsResolve(Dns.GetHostName()));
engine.Script.alert = new Action<string>(msg => Console.WriteLine("PAC-alert: " + msg));

Once this is set up, you should be able to execute your proxy.pac code.

[Edit2:] Updated link to Mozilla's implementation.

Community
  • 1
  • 1
BitCortex
  • 3,328
  • 1
  • 15
  • 19
  • Makes perfectly sense. But is there any way to do it nicely? The VsaEngine just used ``Eval(script, engine)`` and the work was done. – ecth Jun 01 '16 at 11:38
  • I've added a potential solution to my answer above. – BitCortex Jun 01 '16 at 13:16
  • Thank you, I've marked this as the answer because it actually might help people. I will use this WinApi call instead: https://msdn.microsoft.com/de-de/library/windows/desktop/aa384097%28v=vs.85%29.aspx That includes marshalling, but it is able to download, read and parse the proxy.pac like I need it :) Still, using Mozilla Foundation code in my project would be awesome! – ecth Jun 01 '16 at 16:00
0

So, if the accepted answer is a bit vague - and the links to nsProxyAutoConfig.js are dark, here's how it works.

The first use case is with Jint, in .NET Core (works on Linux, too).
Interprete the PAC-file and retrieve the result.
(the result itself can be a list of proxy/socks/direct servers)

namespace RestTestAppCore.Proxy
{


    class PacExecuter
    {


        public static void Test()
        {
            // 
            Jint.Engine? JSEngine = new Jint.Engine()
                // Expose the alert function in JavaScript that triggers the native function (previously created) Alert
                .SetValue("alert", new System.Action<string>(PacAlert))
                .SetValue("dnsResolve", new System.Func<string, string>(PacDnsResolve))
                .SetValue("myIpAddress", new System.Func<string, string>(PacMyIpAddress))
                .SetValue("extractHostFromUrl", new System.Func<string, string>(PacExtractHostFromUrl))
            ;

            try
            {

                string js = @"function dnsDomainIs(host, domain) {
    return (host.length >= domain.length &&
        host.substring(host.length - domain.length) == domain);
}
function dnsDomainLevels(host) {
    return host.split('.').length - 1;
}
function convert_addr(ipchars) {
    var bytes = ipchars.split('.');
    var result = ((bytes[0] & 0xff) << 24) |
        ((bytes[1] & 0xff) << 16) |
        ((bytes[2] & 0xff) << 8) |
        (bytes[3] & 0xff);
    return result;
}
function isInNet(ipaddr, pattern, maskstr) {
    var test = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/.exec(ipaddr);
    if (test == null) {
        ipaddr = dnsResolve(ipaddr);
        if (ipaddr == null)
            return false;
    } else if (test[1] > 255 || test[2] > 255 ||
        test[3] > 255 || test[4] > 255) {
        return false;    // not an IP address
    }
    var host = convert_addr(ipaddr);
    var pat = convert_addr(pattern);
    var mask = convert_addr(maskstr);
    return ((host & mask) == (pat & mask));

}
function isPlainHostName(host) {
    return (host.search('\\.') == -1);
}
function isResolvable(host) {
    var ip = dnsResolve(host);
    return (ip != null);
}
function localHostOrDomainIs(host, hostdom) {
    return (host == hostdom) ||
        (hostdom.lastIndexOf(host + '.', 0) == 0);
}
function shExpMatch(url, pattern) {
    pattern = pattern.replace(/\./g, '\\.');
    pattern = pattern.replace(/\*/g, '.*');
    pattern = pattern.replace(/\?/g, '.');
    var newRe = new RegExp('^' + pattern + '$');
    return newRe.test(url);
}
var wdays = { SUN: 0, MON: 1, TUE: 2, WED: 3, THU: 4, FRI: 5, SAT: 6 };
var months = { JAN: 0, FEB: 1, MAR: 2, APR: 3, MAY: 4, JUN: 5, JUL: 6, AUG: 7, SEP: 8, OCT: 9, NOV: 10, DEC: 11 };
function weekdayRange() {
    function getDay(weekday) {
        if (weekday in wdays) {
            return wdays[weekday];
        }
        return -1;
    }
    var date = new Date();
    var argc = arguments.length;
    var wday;
    if (argc < 1)
        return false;
    if (arguments[argc - 1] == 'GMT') {
        argc--;
        wday = date.getUTCDay();
    } else {
        wday = date.getDay();
    }
    var wd1 = getDay(arguments[0]);
    var wd2 = (argc == 2) ? getDay(arguments[1]) : wd1;
    return (wd1 == -1 || wd2 == -1) ? false
        : (wd1 <= wday && wday <= wd2);
}
function dateRange() {
    function getMonth(name) {
        if (name in months) {
            return months[name];
        }
        return -1;
    }
    var date = new Date();
    var argc = arguments.length;
    if (argc < 1) {
        return false;
    }
    var isGMT = (arguments[argc - 1] == 'GMT');

    if (isGMT) {
        argc--;
    }
    // function will work even without explict handling of this case
    if (argc == 1) {
        var tmp = parseInt(arguments[0]);
        if (isNaN(tmp)) {
            return ((isGMT ? date.getUTCMonth() : date.getMonth()) ==
                getMonth(arguments[0]));
        } else if (tmp < 32) {
            return ((isGMT ? date.getUTCDate() : date.getDate()) == tmp);
        } else {
            return ((isGMT ? date.getUTCFullYear() : date.getFullYear()) ==
                tmp);
        }
    }
    var year = date.getFullYear();
    var date1, date2;
    date1 = new Date(year, 0, 1, 0, 0, 0);
    date2 = new Date(year, 11, 31, 23, 59, 59);
    var adjustMonth = false;
    for (var i = 0; i < (argc >> 1); i++) {
        var tmp = parseInt(arguments[i]);
        if (isNaN(tmp)) {
            var mon = getMonth(arguments[i]);
            date1.setMonth(mon);
        } else if (tmp < 32) {
            adjustMonth = (argc <= 2);
            date1.setDate(tmp);
        } else {
            date1.setFullYear(tmp);
        }
    }
    for (var i = (argc >> 1); i < argc; i++) {
        var tmp = parseInt(arguments[i]);
        if (isNaN(tmp)) {
            var mon = getMonth(arguments[i]);
            date2.setMonth(mon);
        } else if (tmp < 32) {
            date2.setDate(tmp);
        } else {
            date2.setFullYear(tmp);
        }
    }
    if (adjustMonth) {
        date1.setMonth(date.getMonth());
        date2.setMonth(date.getMonth());
    }
    if (isGMT) {
        var tmp = date;
        tmp.setFullYear(date.getUTCFullYear());
        tmp.setMonth(date.getUTCMonth());
        tmp.setDate(date.getUTCDate());
        tmp.setHours(date.getUTCHours());
        tmp.setMinutes(date.getUTCMinutes());
        tmp.setSeconds(date.getUTCSeconds());
        date = tmp;
    }
    return ((date1 <= date) && (date <= date2));
}
function timeRange() {
    var argc = arguments.length;
    var date = new Date();
    var isGMT = false;

    if (argc < 1) {
        return false;
    }
    if (arguments[argc - 1] == 'GMT') {
        isGMT = true;
        argc--;
    }

    var hour = isGMT ? date.getUTCHours() : date.getHours();
    var date1, date2;
    date1 = new Date();
    date2 = new Date();

    if (argc == 1) {
        return (hour == arguments[0]);
    } else if (argc == 2) {
        return ((arguments[0] <= hour) && (hour <= arguments[1]));
    } else {
        switch (argc) {
            case 6:
                date1.setSeconds(arguments[2]);
                date2.setSeconds(arguments[5]);
            case 4:
                var middle = argc >> 1;
                date1.setHours(arguments[0]);
                date1.setMinutes(arguments[1]);
                date2.setHours(arguments[middle]);
                date2.setMinutes(arguments[middle + 1]);
                if (middle == 2) {
                    date2.setSeconds(59);
                }
                break;
            default:
                throw 'timeRange: bad number of arguments'
        }
    }

    if (isGMT) {
        date.setFullYear(date.getUTCFullYear());
        date.setMonth(date.getUTCMonth());
        date.setDate(date.getUTCDate());
        date.setHours(date.getUTCHours());
        date.setMinutes(date.getUTCMinutes());
        date.setSeconds(date.getUTCSeconds());
    }
    return ((date1 <= date) && (date <= date2));
} 

// this one function is not from Mozilla  ... 
// just in case there was any doubt ;) 
function crapExtractHost(href) 
{
    // return new URL(href).host;
    
    // var l = document.createElement(""a"");
    // l.href = href;
    // return l.hostname;
    
    let pos = href.indexOf(""://"");
    if(pos !== -1)
        href = href.substr(pos+3);
    
    let posColon = href.indexOf("":"");
    if(posColon !== -1)
        href = href.substr(0, posColon);
    
    let posSlash = href.indexOf(""/"");
    if(posSlash !== -1)
        href = href.substr(0, posSlash);
    
    let posHash = href.indexOf(""#"");
    if(posHash !== -1)
        href = href.substr(0, posHash);
    
    return href; 
} 


";



                // here comes the PAC-file's content 
                // https://www.websense.com/content/support/library/web/v76/pac_file_best_practices/PAC_file_sample.aspx
                // https://developer.mozilla.org/en-US/docs/Web/HTTP/Proxy_servers_and_tunneling/Proxy_Auto-Configuration_PAC_file
                js += @"

// url:   The URL being accessed.
// host:  The hostname extracted from the URL. 
//        This is only for convenience; 
//        it is the same string as between :// and the first : or / after that. 
//        The port number is not included in this parameter. 
//        It can be extracted from the URL when necessary. 
function FindProxyForURL(url, host)
{
     // return crapExtractHost(url) + "" xxx "" + extractHostFromUrl(url) + "": "" + dnsResolve(url) + "" "" + dnsResolve(host);

    //  Don't proxy local hostnames
    if (isPlainHostName(host))
    {
        return 'DIRECT';
    }
    
    if (dnsDomainIs(host, "".example1.com"") || (host == ""example1.com"") ||
        dnsDomainIs(host, "".example2.com"") || (host == ""example2.com"") ||
        dnsDomainIs(host, "".example3.com"") || (host == ""example3.com"")
    )
    {
        return 'DIRECT';
    }

    if (isResolvable(host))
    {
        var hostIP = dnsResolve(host);
 
        // Don't proxy non-routable addresses (RFC 3330) 
        if (isInNet(hostIP, '0.0.0.0', '255.0.0.0') ||
            isInNet(hostIP, '10.0.0.0', '255.0.0.0') ||
            isInNet(hostIP, '127.0.0.0', '255.0.0.0') ||
            isInNet(hostIP, '169.254.0.0', '255.255.0.0') ||
            isInNet(hostIP, '172.16.0.0', '255.240.0.0') ||
            isInNet(hostIP, '192.0.2.0', '255.255.255.0') ||
            isInNet(hostIP, '192.88.99.0', '255.255.255.0') ||
            isInNet(hostIP, '192.168.0.0', '255.255.0.0') ||
            isInNet(hostIP, '198.18.0.0', '255.254.0.0') ||
            isInNet(hostIP, '224.0.0.0', '240.0.0.0') ||
            isInNet(hostIP, '240.0.0.0', '240.0.0.0')
        )
        {
            return 'DIRECT';
        }
    }

    // Don't proxy Windows Update 
    if ((host == ""download.microsoft.com"") ||
        (host == ""ntservicepack.microsoft.com"") ||
        (host == ""cdm.microsoft.com"") ||
        (host == ""wustat.windows.com"") ||
        (host == ""windowsupdate.microsoft.com"") ||
        (dnsDomainIs(host, "".windowsupdate.microsoft.com"")) ||
        (host == ""update.microsoft.com"") ||
        (dnsDomainIs(host, "".update.microsoft.com"")) ||
        (dnsDomainIs(host, "".windowsupdate.com""))
    )
    {
        return 'DIRECT';
    }
    
    // isInNet: True if and only if the IP address of the host matches the specified IP address pattern (masks).
    // mask: 0 means ignore, 255 means match
    // so if myIpAddress == 10.168.115.54
    if (isInNet(myIpAddress(), ""10.168.115.54"", ""255.255.255.255""))
    {
        return ""PROXY wcg1.example.com:8080; "" + ""PROXY wcg2.example.com:8080"";
    }
    
    if (url.substring(0, 5) == 'http:' || url.substring(0, 6) == 'https:' || url.substring(0, 4) == 'ftp:')
    {
        return 'PROXY proxy.example.com:8080';
    }

    return ""foobar"";
}

";

                // string myIp = PacDnsResolve(System.Net.Dns.GetHostName());
                // System.Console.WriteLine(myIp);


                // https://github.com/sebastienros/jint
                // https://ourcodeworld.com/articles/read/426/how-to-implement-jint-a-javascript-interpreter-within-a-winforms-application-in-c-sharp
                // #:~:text=Jint%20is%20a%20Javascript%20interpreter,runs%20relatively%20small%20scripts%20faster.


                // Jint.Native.JsValue? FunctionAddJavascript = JSEngine.Execute("alert(dnsResolve('COR-W10-112'));").GetValue("FindProxyForURL");
                Jint.Native.JsValue? FunctionAddJavascript = JSEngine.Execute(js).GetValue("FindProxyForURL");

                string url1 = "https://google.com";
                string url2 = "https://microsoft.com/foo/bar.aspx";
                string url3 = "https://COR-W10-110/foobar.aspx";

                Jint.Native.JsValue? jsResult1 = FunctionAddJavascript.Invoke(url1, new System.Uri(url1, System.UriKind.Absolute).Host);
                Jint.Native.JsValue? jsResult2 = FunctionAddJavascript.Invoke(url2, new System.Uri(url2, System.UriKind.Absolute).Host);
                Jint.Native.JsValue? jsResult3 = FunctionAddJavascript.Invoke(url3, new System.Uri(url3, System.UriKind.Absolute).Host);

                string result1 = jsResult1.AsString();
                string result2 = jsResult2.AsString();
                string result3 = jsResult3.AsString();
                System.Console.WriteLine("Result 1:{0} 2:{1} 3: {2}", result1, result2, result3);
            }
            catch (Jint.Runtime.JavaScriptException Ex)
            {
                System.Console.WriteLine(Ex.Message);
            }

        }


        // http://wp.netscape.com/eng/mozilla/2.0/relnotes/demo/proxy-live.html
        // https://stackoverflow.com/questions/37563083/parsing-a-proxy-pac-file-in-c-sharp-wpf
        // https://developer.mozilla.org/en-US/docs/Web/HTTP/Proxy_servers_and_tunneling/Proxy_Auto-Configuration_PAC_file#shexpmatch
        // Take a look at Mozilla's implementation here.
        // Most of it is in the pacUtils string.
        // You could define this string in your C# code and pass it into your script engine.
        // That would just leave dnsResolve, myIpAddress, and alert, all of which need some help from the host. 
        // https://www.websense.com/content/support/library/web/v76/pac_file_best_practices/PAC_file_sample.aspx
        // afterwards, call FindProxyForURL(url, host)
        //   { "pac", "application/x-ns-proxy-autoconfig" }
        // https://web.archive.org/web/20120329222856/http://msdn.microsoft.com/en-gb/magazine/cc300743.aspx


        public static string? PacExtractHostFromUrl(string url)
        {
            try
            {
                return new System.Uri(url, System.UriKind.Absolute).Host;
            }
            catch (System.Exception)
            {
                // return ex.Message + "\r\n\r\n" + ex.StackTrace;
            }

            return null;
        }

        public static string? PacDnsResolve(string name)
        {
            try
            {
                System.Net.IPAddress[]? addrs = System.Net.Dns.GetHostEntry(name).AddressList;
                return System.Linq.Enumerable.First(addrs, a => a.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork).ToString();
            }
            catch (System.Exception)
            {
                // return ex.Message + "\r\n\r\n" + ex.StackTrace;
            }

            return null;
        }

        public static string? PacMyIpAddress(string name)
        {
            try
            {
                return PacDnsResolve(System.Net.Dns.GetHostName());
            }
            catch (System.Exception)
            {
                // return ex.Message + "\r\n\r\n" + ex.StackTrace;
            }

            return null;
        }

        public static void PacAlert(string msg)
        {
            System.Console.WriteLine("PAC-alert: " + msg);
        }

        // engine.Script.dnsResolve = dnsResolve;
        // engine.Script.myIpAddress = new Func<string>(() => dnsResolve(Dns.GetHostName()));
        // engine.Script.alert = new Action<string>(msg => Console.WriteLine("PAC-alert: " + msg));
        // engine.Execute(pacUtils);


    }


}

The second use case is for old versions of .NET, where it's not possible to use Jint. In that case, you can invoke the WinAPI (of course, that works on Windows-only).

Proxy:

namespace RestTestAppCore
{


    // https://www.codeproject.com/Articles/12168/Using-PAC-files-proxy
    public class PacProxy
    {

        public PacProxy()
        { }


        public static void Test(string destinationUrl, string pacUri)
        {
            System.Net.WebRequest TestRequest = System.Net.WebRequest.Create(destinationUrl);

            // Obtain Proxy address for the URL
            string? ProxyAddresForUrl = PacProxy.GetProxyForUrlUsingPac(destinationUrl, pacUri);
            if (ProxyAddresForUrl != null)
            {
                System.Console.WriteLine("Found Proxy: {0}", ProxyAddresForUrl);
                TestRequest.Proxy = new System.Net.WebProxy(ProxyAddresForUrl);
            }
            else
            {
                System.Console.WriteLine("Proxy Not Found. Send request directly.");
            }
        }



        /// <summary>
        /// Return proxy for requested Url
        /// </summary>
        /// <param name="sUrl">url</param>
        /// <param name="sPacUri">Uri to PAC file</param>
        /// <returns>proxy info</returns>
        public static string? GetProxyForUrlUsingPac(string DestinationUrl, string PacUri)
        {
            if (System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(System.Runtime.InteropServices.OSPlatform.Windows))
            {
                System.IntPtr WinHttpSession = Win32Api.WinHttpOpen("User", Win32Api.WINHTTP_ACCESS_TYPE_DEFAULT_PROXY
                    , System.IntPtr.Zero, System.IntPtr.Zero, 0);

                Win32Api.WINHTTP_AUTOPROXY_OPTIONS ProxyOptions = new Win32Api.WINHTTP_AUTOPROXY_OPTIONS();
                Win32Api.WINHTTP_PROXY_INFO ProxyInfo = new Win32Api.WINHTTP_PROXY_INFO();

                ProxyOptions.dwFlags = Win32Api.WINHTTP_AUTOPROXY_CONFIG_URL;
                ProxyOptions.dwAutoDetectFlags = (Win32Api.WINHTTP_AUTO_DETECT_TYPE_DHCP | Win32Api.WINHTTP_AUTO_DETECT_TYPE_DNS_A);
                ProxyOptions.lpszAutoConfigUrl = PacUri;

                // Get Proxy 
                bool IsSuccess = Win32Api.WinHttpGetProxyForUrl(WinHttpSession, DestinationUrl, ref ProxyOptions, ref ProxyInfo);

                Win32Api.WinHttpCloseHandle(WinHttpSession);

                if (IsSuccess)
                {
                    return ProxyInfo.lpszProxy;
                }
                else 
                    System.Console.WriteLine("Error: {0}", Win32Api.GetLastError());
            }

            return null;
        }

    }
}

WinAPI:

namespace RestTestAppCore
{

    using System.Runtime.InteropServices;


    /// <summary>
    /// Summary description for Win32Api.
    /// </summary>
    internal class Win32Api
    {
        #region AutoProxy Constants 
        /// <summary>
        /// Applies only when setting proxy information
        /// </summary>
        internal const int WINHTTP_ACCESS_TYPE_DEFAULT_PROXY = 0;
        /// <summary>
        /// Internet accessed through a direct connection 
        /// </summary>
        internal const int WINHTTP_ACCESS_TYPE_NO_PROXY = 1;
        /// <summary>
        /// Internet accessed using a proxy
        /// </summary>
        internal const int WINHTTP_ACCESS_TYPE_NAMED_PROXY = 3;
        /// <summary>
        /// Attempt to automatically discover the URL of the 
        /// PAC file using both DHCP and DNS queries to the local network.
        /// </summary>
        internal const int WINHTTP_AUTOPROXY_AUTO_DETECT = 0x00000001;
        /// <summary>
        /// Download the PAC file from the URL in the WINHTTP_AUTOPROXY_OPTIONS structure.
        /// </summary>
        internal const int WINHTTP_AUTOPROXY_CONFIG_URL = 0x00000002;
        /// <summary>
        /// Executes the Web Proxy Auto-Discovery (WPAD) protocol in-process instead of 
        /// delegating to an out-of-process WinHTTP AutoProxy Service, if available. 
        /// This flag must be combined with one of the other flags
        /// </summary>
        internal const int WINHTTP_AUTOPROXY_RUN_INPROCESS = 0x00010000;
        /// <summary>
        /// By default, WinHTTP is configured to fall back to auto-discover a proxy 
        /// in-process. If this fallback behavior is undesirable in the event that 
        /// an out-of-process discovery fails, it can be disabled using this flag.
        /// </summary>
        internal const int WINHTTP_AUTOPROXY_RUN_OUTPROCESS_ONLY = 0x00020000;
        /// <summary>
        /// Use DHCP to locate the proxy auto-configuration file.
        /// </summary>
        internal const int WINHTTP_AUTO_DETECT_TYPE_DHCP = 0x00000001;
        /// <summary>
        /// Use DNS to attempt to locate the proxy auto-configuration file at a 
        /// well-known location on the domain of the local computer
        /// </summary>
        internal const int WINHTTP_AUTO_DETECT_TYPE_DNS_A = 0x00000002;
        #endregion

        #region Proxy Structures 
        /// <summary>
        /// The structure is used to indicate to the WinHttpGetProxyForURL 
        /// function whether to specify the URL of the Proxy Auto-Configuration 
        /// (PAC) file or to automatically locate the URL with DHCP or DNS 
        /// queries to the network
        /// </summary>
        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
        internal struct WINHTTP_AUTOPROXY_OPTIONS
        {
            /// <summary>
            /// Mechanisms should be used to obtain the PAC file
            /// </summary>
            [MarshalAs(UnmanagedType.U4)]
            internal int dwFlags;
            /// <summary>
            /// If dwflags includes the WINHTTP_AUTOPROXY_AUTO_DETECT flag, 
            /// then dwAutoDetectFlags specifies what protocols are to be 
            /// used to locate the PAC file. If both the DHCP and DNS auto 
            /// detect flags are specified, then DHCP is used first;
            /// if no PAC URL is discovered using DHCP, then DNS is used.
            /// If dwflags does not include the WINHTTP_AUTOPROXY_AUTO_DETECT 
            /// flag, then dwAutoDetectFlags must be zero.
            /// </summary>
            [MarshalAs(UnmanagedType.U4)]
            internal int dwAutoDetectFlags;
            /// <summary>
            /// If dwflags includes the WINHTTP_AUTOPROXY_CONFIG_URL flag, the 
            /// lpszAutoConfigUrl must point to a null-terminated Unicode string 
            /// that contains the URL of the proxy auto-configuration (PAC) file.
            /// If dwflags does not include the WINHTTP_AUTOPROXY_CONFIG_URL flag, 
            /// then lpszAutoConfigUrl must be NULL.
            /// </summary>
            internal string lpszAutoConfigUrl;
            /// <summary>
            /// Reserved for future use; must be NULL.
            /// </summary>
            internal System.IntPtr lpvReserved;
            /// <summary>
            /// Reserved for future use; must be zero.
            /// </summary>
            [MarshalAs(UnmanagedType.U4)]
            internal int dwReserved;
            /// <summary>
            /// Specifies whether the client's domain credentials should be automatically 
            /// sent in response to an NTLM or Negotiate Authentication challenge when 
            /// WinHTTP requests the PAC file.
            /// If this flag is TRUE, credentials should automatically be sent in response 
            /// to an authentication challenge. If this flag is FALSE and authentication 
            /// is required to download the PAC file, the WinHttpGetProxyForUrl fails.
            /// </summary>
            internal bool fAutoLoginIfChallenged;

        }

        /// <summary>
        /// The structure contains the session or default proxy configuration.
        /// </summary>
        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
        internal struct WINHTTP_PROXY_INFO
        {
            /// <summary>
            /// Unsigned long integer value that contains the access type
            /// </summary>   
            [MarshalAs(UnmanagedType.U4)]
            internal int dwAccessType;
            /// <summary>
            /// Pointer to a string value that contains the proxy server list
            /// </summary>
            internal string lpszProxy;
            /// <summary>
            /// Pointer to a string value that contains the proxy bypass list
            /// </summary>
            internal string lpszProxyBypass;
        }
        #endregion

        #region WinHttp
        /// <summary>
        /// This function implements the Web Proxy Auto-Discovery (WPAD) protocol 
        /// for automatically configuring the proxy settings for an HTTP request. 
        /// The WPAD protocol downloads a Proxy Auto-Configuration (PAC) file, 
        /// which is a script that identifies the proxy server to use for a given 
        /// target URL. PAC files are typically deployed by the IT department within 
        /// a corporate network environment. The URL of the PAC file can either be 
        /// specified explicitly or WinHttpGetProxyForUrl can be instructed to 
        /// automatically discover the location of the PAC file on the local network.
        /// </summary>
        /// <param name="hSession">The WinHTTP session handle returned by the WinHttpOpen function</param>
        /// <param name="lpcwszUrl">A pointer to a null-terminated Unicode string that contains the 
        /// URL of the HTTP request that the application is preparing to send.</param>
        /// <param name="pAutoProxyOptions">A pointer to a WINHTTP_AUTOPROXY_OPTIONS structure that 
        /// specifies the auto-proxy options to use.</param>
        /// <param name="pProxyInfo">A pointer to a WINHTTP_PROXY_INFO structure that receives the 
        /// proxy setting. This structure is then applied to the request handle using the 
        /// WINHTTP_OPTION_PROXY option.</param>
        /// <returns></returns>
        [DllImport("winhttp.dll", SetLastError = true, CharSet = CharSet.Unicode)]
        internal static extern bool WinHttpGetProxyForUrl(
            System.IntPtr hSession,
            string lpcwszUrl,
            ref WINHTTP_AUTOPROXY_OPTIONS pAutoProxyOptions,
            ref WINHTTP_PROXY_INFO pProxyInfo);

       
        [DllImport("winhttp.dll", SetLastError = true, CharSet = CharSet.Unicode)]
        internal static extern System.IntPtr WinHttpOpen(
            string pwszUserAgent,
            int dwAccessType,
            System.IntPtr pwszProxyName,
            System.IntPtr pwszProxyBypass,
            int dwFlags
            );

        [DllImport("winhttp.dll", SetLastError = true, CharSet = CharSet.Unicode)]
        internal static extern bool WinHttpCloseHandle(System.IntPtr hInternet);

        #endregion

        [DllImport("kernel32.dll")]
        internal static extern int GetLastError();

    }
}
Stefan Steiger
  • 78,642
  • 66
  • 377
  • 442