Wednesday 14 August 2013

C# WebBrowser Control Proxy Authentication

Setting a proxy server for the WebBrowser control is not a straight forward affair. There are plenty of resources out there on the web that will show you how to do this. However, what is lacking is any solutions that show how to authenticate a proxy server, at least any solutions that would work for me. So in this post I am not only going to show you how to set a proxy for the WebBrowser control, I will also share with you the only solution I know of that successfully authenticates with a proxy.

To set the proxy server we will make a call to the Win32 API, the InternetSetOption function found in wininet.dll. To use this function in C# we need to declare an external function like so.

  1. [DllImport("wininet.dll", SetLastError = true)]
  2. private static extern bool InternetSetOption(IntPtr hInternet, int dwOption,
  3.     IntPtr lpBuffer, int lpdwBufferLength);

To use this function we are also going to have to declare a struct as one of the parameters required is a pointer to this struct. Here it is.

  1. public struct INTERNET_PROXY_INFO
  2. {
  3.     public int dwAccessType;
  4.     public IntPtr proxy;
  5.     public IntPtr proxyBypass;
  6. }

Next I have created a SetProxyServer method. It takes one string parameter, the IP address and port of the proxy server in the format "1.2.3.4:1234". In this method we create our INTERNET_PROXY_INFO struct and populate the values, we use some of the static methods of the Marshall class to copy the values into unmanaged memory and then we call the external function.

  1. private void SetProxyServer(string proxy)
  2. {         
  3.     //Create structure
  4.     INTERNET_PROXY_INFO proxyInfo = new INTERNET_PROXY_INFO();
  5.  
  6.     if (proxy == null)
  7.     {
  8.         proxyInfo.dwAccessType = INTERNET_OPEN_TYPE_DIRECT;
  9.     }
  10.     else
  11.     {
  12.         proxyInfo.dwAccessType = INTERNET_OPEN_TYPE_PROXY;
  13.         proxyInfo.proxy = Marshal.StringToHGlobalAnsi(proxy);
  14.         proxyInfo.proxyBypass = Marshal.StringToHGlobalAnsi("local");
  15.     }
  16.  
  17.     // Allocate memory
  18.     IntPtr proxyInfoPtr = Marshal.AllocCoTaskMem(Marshal.SizeOf(proxyInfo));
  19.  
  20.     // Convert structure to IntPtr
  21.     Marshal.StructureToPtr(proxyInfo, proxyInfoPtr, true);
  22.     bool returnValue = InternetSetOption(IntPtr.Zero, INTERNET_OPTION_PROXY,
  23.         proxyInfoPtr, Marshal.SizeOf(proxyInfo));
  24. }

If your proxy servers have IP authentication or no authentication at all then you don not need to do anymore. However, if you need to provide username and passwords then some more steps are required.

If we register our form as a client site object for the underlying instance of IE within the control and implements the IServiceProvider interface, the underlying IE instance will query our form when authentication is required thus allowing us to provide the required credentials.

To make all that work our form needs to implement three interfaces IOleClientSite, IServiceProvider and IAuthenticate. IOleClientSite has six methods but we only need to implement one of these, GetContainer. All we need to do here is set the incoming object parameter to the instance of our WinForm.

  1. #region IOleClientSite Members
  2.  
  3. public void SaveObject()
  4. {
  5.     // TODO:  Add Form1.SaveObject implementation
  6. }
  7.  
  8. public void GetMoniker(uint dwAssign, uint dwWhichMoniker, object ppmk)
  9. {
  10.     // TODO:  Add Form1.GetMoniker implementation
  11. }
  12.  
  13. public void GetContainer(object ppContainer)
  14. {
  15.     ppContainer = this;
  16. }
  17.  
  18. public void ShowObject()
  19. {
  20.     // TODO:  Add Form1.ShowObject implementation
  21. }
  22.  
  23. public void OnShowWindow(bool fShow)
  24. {
  25.     // TODO:  Add Form1.OnShowWindow implementation
  26. }
  27.  
  28. public void RequestNewObjectLayout()
  29. {
  30.     // TODO:  Add Form1.RequestNewObjectLayout implementation
  31. }
  32.  
  33. #endregion

Next we implement IServiceProvider which has only one method, QueryService. In this method we check the service and interface being requested. If the request is for authentication we return a pointer to the implementation of IAuthenticate in our form.

  1. #region IServiceProvider Members
  2.  
  3. public int QueryService(ref Guid guidService, ref Guid riid, out IntPtr ppvObject)
  4. {
  5.     int nRet = guidService.CompareTo(IID_IAuthenticate);
  6.     if (nRet == 0)
  7.     {
  8.         nRet = riid.CompareTo(IID_IAuthenticate);
  9.         if (nRet == 0)
  10.         {
  11.             ppvObject = Marshal.GetComInterfaceForObject(this, typeof(IAuthenticate));
  12.             return S_OK;
  13.         }
  14.     }
  15.  
  16.     ppvObject = new IntPtr();
  17.     return INET_E_DEFAULT_ACTION;
  18. }
  19.  
  20. #endregion

IAuthenticate also has only one method, Authenticate. Here all we need to do is create pointers to the required username and password.

  1. #region IAuthenticate Members
  2.  
  3. public int Authenticate(ref IntPtr phwnd, ref IntPtr pszUsername, ref IntPtr pszPassword)
  4. {
  5.     IntPtr sUser = Marshal.StringToCoTaskMemAuto(_currentUsername);
  6.     IntPtr sPassword = Marshal.StringToCoTaskMemAuto(_currentPassword);
  7.  
  8.     pszUsername = sUser;
  9.     pszPassword = sPassword;
  10.     return S_OK;
  11. }
  12.  
  13. #endregion

Our three interfaces IOleClientSite, IServiceProvider and IAuthenticate are COM interfaces so we need to declare them, along with IOleObject. Here is the full code tying everything together.

  1. using System;
  2. using System.Runtime.InteropServices;
  3. using System.Windows.Forms;
  4. using System.Collections.Generic;
  5.  
  6. namespace WebBrowserProxyAuthentication
  7. {
  8.     public partial class Form1 : Form, IOleClientSite, IServiceProvider, IAuthenticate
  9.     {
  10.         [DllImport("wininet.dll", SetLastError = true)]
  11.         private static extern bool InternetSetOption(IntPtr hInternet, int dwOption,
  12.             IntPtr lpBuffer, int lpdwBufferLength);
  13.         private Guid IID_IAuthenticate = new Guid("79eac9d0-baf9-11ce-8c82-00aa004ba90b");
  14.         private const int INET_E_DEFAULT_ACTION = unchecked((int)0x800C0011);
  15.         private const int S_OK = unchecked((int)0x00000000);
  16.         private const int INTERNET_OPTION_PROXY = 38;
  17.         private const int INTERNET_OPEN_TYPE_DIRECT = 1;
  18.         private const int INTERNET_OPEN_TYPE_PROXY = 3;
  19.         private string _currentUsername;
  20.         private string _currentPassword;
  21.  
  22.         public Form1()
  23.         {
  24.             InitializeComponent();
  25.         }
  26.  
  27.         private void Form1_Load(object sender, EventArgs e)
  28.         {
  29.             webBrowser1.Navigate("about:blank");
  30.             object obj = webBrowser1.ActiveXInstance;
  31.             IOleObject oc = obj as IOleObject;
  32.             oc.SetClientSite(this as IOleClientSite);
  33.  
  34.             List<Tuple<string, string, string>> proxies = new List<Tuple<string, string, string>>
  35.             {
  36.                 new Tuple<string, string, string>("{proxy_ip_here}:{proxy_port_here}",
  37.                     "username", "password")
  38.             };
  39.  
  40.             foreach (Tuple<string, string, string> proxy in proxies)
  41.             {
  42.                 _currentUsername = proxy.Item2;
  43.                 _currentPassword = proxy.Item3;
  44.                 SetProxyServer(proxy.Item1);
  45.  
  46.                 webBrowser1.Navigate("about:blank");
  47.                 Application.DoEvents();
  48.                 webBrowser1.Navigate("http://www.showmyip.co.uk/");
  49.                 Application.DoEvents();
  50.  
  51.                 while (webBrowser1.StatusText != "Done" || webBrowser1.Document.Body.InnerText == null)
  52.                 {
  53.                     Application.DoEvents();
  54.                 }
  55.  
  56.                 MessageBox.Show(webBrowser1.Document.Body.InnerText.Trim());
  57.             }
  58.         }
  59.  
  60.         private void SetProxyServer(string proxy)
  61.         {         
  62.             //Create structure
  63.             INTERNET_PROXY_INFO proxyInfo = new INTERNET_PROXY_INFO();
  64.  
  65.             if (proxy == null)
  66.             {
  67.                 proxyInfo.dwAccessType = INTERNET_OPEN_TYPE_DIRECT;
  68.             }
  69.             else
  70.             {
  71.                 proxyInfo.dwAccessType = INTERNET_OPEN_TYPE_PROXY;
  72.                 proxyInfo.proxy = Marshal.StringToHGlobalAnsi(proxy);
  73.                 proxyInfo.proxyBypass = Marshal.StringToHGlobalAnsi("local");
  74.             }
  75.  
  76.             // Allocate memory
  77.             IntPtr proxyInfoPtr = Marshal.AllocCoTaskMem(Marshal.SizeOf(proxyInfo));
  78.  
  79.             // Convert structure to IntPtr
  80.             Marshal.StructureToPtr(proxyInfo, proxyInfoPtr, true);
  81.             bool returnValue = InternetSetOption(IntPtr.Zero, INTERNET_OPTION_PROXY,
  82.                 proxyInfoPtr, Marshal.SizeOf(proxyInfo));
  83.         }
  84.  
  85.         #region IOleClientSite Members
  86.  
  87.         public void SaveObject()
  88.         {
  89.             // TODO:  Add Form1.SaveObject implementation
  90.         }
  91.  
  92.         public void GetMoniker(uint dwAssign, uint dwWhichMoniker, object ppmk)
  93.         {
  94.             // TODO:  Add Form1.GetMoniker implementation
  95.         }
  96.  
  97.         public void GetContainer(object ppContainer)
  98.         {
  99.             ppContainer = this;
  100.         }
  101.  
  102.         public void ShowObject()
  103.         {
  104.             // TODO:  Add Form1.ShowObject implementation
  105.         }
  106.  
  107.         public void OnShowWindow(bool fShow)
  108.         {
  109.             // TODO:  Add Form1.OnShowWindow implementation
  110.         }
  111.  
  112.         public void RequestNewObjectLayout()
  113.         {
  114.             // TODO:  Add Form1.RequestNewObjectLayout implementation
  115.         }
  116.  
  117.         #endregion
  118.  
  119.         #region IServiceProvider Members
  120.  
  121.         public int QueryService(ref Guid guidService, ref Guid riid, out IntPtr ppvObject)
  122.         {
  123.             int nRet = guidService.CompareTo(IID_IAuthenticate);
  124.             if (nRet == 0)
  125.             {
  126.                 nRet = riid.CompareTo(IID_IAuthenticate);
  127.                 if (nRet == 0)
  128.                 {
  129.                     ppvObject = Marshal.GetComInterfaceForObject(this, typeof(IAuthenticate));
  130.                     return S_OK;
  131.                 }
  132.             }
  133.  
  134.             ppvObject = new IntPtr();
  135.             return INET_E_DEFAULT_ACTION;
  136.         }
  137.  
  138.         #endregion
  139.  
  140.         #region IAuthenticate Members
  141.  
  142.         public int Authenticate(ref IntPtr phwnd, ref IntPtr pszUsername, ref IntPtr pszPassword)
  143.         {
  144.             IntPtr sUser = Marshal.StringToCoTaskMemAuto(_currentUsername);
  145.             IntPtr sPassword = Marshal.StringToCoTaskMemAuto(_currentPassword);
  146.  
  147.             pszUsername = sUser;
  148.             pszPassword = sPassword;
  149.             return S_OK;
  150.         }
  151.  
  152.         #endregion
  153.     }
  154.  
  155.     public struct INTERNET_PROXY_INFO
  156.     {
  157.         public int dwAccessType;
  158.         public IntPtr proxy;
  159.         public IntPtr proxyBypass;
  160.     }
  161.  
  162.     #region COM Interfaces
  163.  
  164.     [ComImport, Guid("00000112-0000-0000-C000-000000000046"),
  165.     InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
  166.     public interface IOleObject
  167.     {
  168.         void SetClientSite(IOleClientSite pClientSite);
  169.         void GetClientSite(IOleClientSite ppClientSite);
  170.         void SetHostNames(object szContainerApp, object szContainerObj);
  171.         void Close(uint dwSaveOption);
  172.         void SetMoniker(uint dwWhichMoniker, object pmk);
  173.         void GetMoniker(uint dwAssign, uint dwWhichMoniker, object ppmk);
  174.         void InitFromData(IDataObject pDataObject, bool
  175.             fCreation, uint dwReserved);
  176.         void GetClipboardData(uint dwReserved, IDataObject ppDataObject);
  177.         void DoVerb(uint iVerb, uint lpmsg, object pActiveSite,
  178.             uint lindex, uint hwndParent, uint lprcPosRect);
  179.         void EnumVerbs(object ppEnumOleVerb);
  180.         void Update();
  181.         void IsUpToDate();
  182.         void GetUserClassID(uint pClsid);
  183.         void GetUserType(uint dwFormOfType, uint pszUserType);
  184.         void SetExtent(uint dwDrawAspect, uint psizel);
  185.         void GetExtent(uint dwDrawAspect, uint psizel);
  186.         void Advise(object pAdvSink, uint pdwConnection);
  187.         void Unadvise(uint dwConnection);
  188.         void EnumAdvise(object ppenumAdvise);
  189.         void GetMiscStatus(uint dwAspect, uint pdwStatus);
  190.         void SetColorScheme(object pLogpal);
  191.     }
  192.  
  193.     [ComImport, Guid("00000118-0000-0000-C000-000000000046"),
  194.     InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
  195.     public interface IOleClientSite
  196.     {
  197.         void SaveObject();
  198.         void GetMoniker(uint dwAssign, uint dwWhichMoniker, object ppmk);
  199.         void GetContainer(object ppContainer);
  200.         void ShowObject();
  201.         void OnShowWindow(bool fShow);
  202.         void RequestNewObjectLayout();
  203.     }
  204.  
  205.     [ComImport, GuidAttribute("6d5140c1-7436-11ce-8034-00aa006009fa"),
  206.     InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown),
  207.     ComVisible(false)]
  208.     public interface IServiceProvider
  209.     {
  210.         [return: MarshalAs(UnmanagedType.I4)]
  211.         [PreserveSig]
  212.         int QueryService(ref Guid guidService, ref Guid riid, out IntPtr ppvObject);
  213.     }
  214.  
  215.     [ComImport, GuidAttribute("79EAC9D0-BAF9-11CE-8C82-00AA004BA90B"),
  216.     InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown),
  217.     ComVisible(false)]
  218.     public interface IAuthenticate
  219.     {
  220.         [return: MarshalAs(UnmanagedType.I4)]
  221.         [PreserveSig]
  222.         int Authenticate(ref IntPtr phwnd, ref IntPtr pszUsername, ref IntPtr pszPassword);
  223.     }
  224.  
  225.     #endregion
  226. }

No comments:

Post a Comment