Introduction

This article explains how to set a proxy using PAC files. PAC files are Proxy Automatic Configuration files, which define a proxy for a specific URL. The solution presented in the article uses the WinHttp.dll for obtaining a proxy URL.

Background

Using the code

I’ve created a simple testing application that sends a web request to www.google.com using a proxy obtained from a PAC file.

In order to run the application you need to put the proxy.pac file into your home directory for the default web site. Usually this directory is c:\inetpub\wwwroot.

The main function that returns the proxy for a specific URL is GetProxyForUrlUsingPac that is defined in the Proxy class:

public static string GetProxyForUrlUsingPac ( string DestinationUrl, string PacUri ){

     IntPtr WinHttpSession = Win32Api.WinHttpOpen("User",
                                    Win32Api.WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, 
                                    IntPtr.Zero, 
                                    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 {
         Console.WriteLine("Error: {0}", Win32Api.GetLastError() );
         return null;
     }
}

The Win32Api class contains a definition of the Win32 functions:

/// <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)]
public static extern bool WinHttpGetProxyForUrl(
            IntPtr hSession,
            string lpcwszUrl,
            ref WINHTTP_AUTOPROXY_OPTIONS pAutoProxyOptions,
            ref WINHTTP_PROXY_INFO pProxyInfo);

/// <summary>
/// The function initializes, for an application, the use of WinHTTP
/// functions and returns a WinHTTP-session handle
/// </summary>
/// <param name="pwszUserAgent">A pointer
/// to a string variable that contains the name of the
/// application or entity calling the WinHTTP functions.</param>
/// <param name="dwAccessType">Type of access required.
///     This can be one of the following values</param>
/// <param name="pwszProxyName"> A pointer
/// to a string variable that contains the name of the
/// proxy server to use when proxy access
/// is specified by setting dwAccessType to
/// WINHTTP_ACCESS_TYPE_NAMED_PROXY. The WinHTTP functions
/// recognize only CERN type proxies for HTTP.
/// If dwAccessType is not set to WINHTTP_ACCESS_TYPE_NAMED_PROXY,
/// this parameter must be set
/// to WINHTTP_NO_PROXY_NAME</param>
/// <param name="pwszProxyBypass">A pointer
/// to a string variable that contains an optional list
/// of host names or IP addresses, or both,
/// that should not be routed through the proxy when
/// dwAccessType is set to WINHTTP_ACCESS_TYPE_NAMED_PROXY.
/// The list can contain wildcard characters.
/// Do not use an empty string, because
/// the WinHttpOpen function uses it as the proxy bypass list.
/// If this parameter specifies the "<local>" macro
/// as the only entry, this function bypasses
/// any host name that does not contain a period.
/// If dwAccessType is not set to WINHTTP_ACCESS_TYPE_NAMED_PROXY,
/// this parameter must be set to WINHTTP_NO_PROXY_BYPASS.</param>
/// <param name="dwFlags">Unsigned long integer value
/// that contains the flags that indicate various options
/// affecting the behavior of this function</param>
/// <returns>Returns a valid session handle
///    if successful, or NULL otherwise</returns>
[DllImport("winhttp.dll", SetLastError=true, CharSet=CharSet.Unicode)]
public static extern IntPtr WinHttpOpen(
            string pwszUserAgent,
            int dwAccessType,
            IntPtr pwszProxyName,
            IntPtr pwszProxyBypass,
            int dwFlags
            );

/// <summary>
/// The function closes a single HINTERNET handle
/// </summary>
/// <param name="hInternet">Valid HINTERNET handle to be closed.</param>
/// <returns>Returns TRUE if the handle
///    is successfully closed, or FALSE otherwise</returns>
[DllImport("winhttp.dll", SetLastError=true, CharSet=CharSet.Unicode)]
public static extern bool WinHttpCloseHandle(IntPtr hInternet);

The main function that uses the GetProxyForUrlUsingPac is:

// Create test request
WebRequest TestRequest = WebRequest.Create ( DestinationUrl );
           
// Optain Proxy address for the URL
string ProxyAddresForUrl = Proxy.GetProxyForUrlUsingPac (DestinationUrl, PacUri);
if ( ProxyAddresForUrl != null ){
   Console.WriteLine ("Found Proxy: {0}", ProxyAddresForUrl );
   TestRequest.Proxy = new WebProxy ( ProxyAddresForUrl ) ;
}else{
   Console.WriteLine ( "Proxy Not Found. Send request directly."  );
}

That's it.