/* Copyright (c) Citrix Systems, Inc. * All rights reserved. * * Redistribution and use in source and binary forms, * with or without modification, are permitted provided * that the following conditions are met: * * * Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other * materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ using System; using System.Collections.Generic; using System.Net; using System.Net.Sockets; using System.Runtime.InteropServices; using System.Security.Permissions; using System.Windows.Forms; using XenCenterLib; namespace XenAdmin.Core { /// /// This extension to System.Windows.Forms.WebBrowser adds two capabilities: an event fired whenever /// browser navigation fails (NavigateError), and an event fired whenever the browser wants to /// prompt for credentials (AuthenticationPrompt). /// /// The event sink for NavigateError comes from /// http://msdn.microsoft.com/en-us/library/system.windows.forms.webbrowser.createsink(VS.80).aspx. /// The handling of authentication prompts comes partially from /// http://izlooite.blogspot.com/2009/06/bypass-integrated-authentication-using.html. /// The bit that he missed is that we can override CreateWebBrowserSiteBase to get ourselves /// registered rather than having to override IOleClientSite as well. See /// http://msdn2.microsoft.com/en-us/library/system.windows.forms.webbrowser.createwebbrowsersitebase.aspx. /// /// (Final note: Contrary to the docs, you can't use the CreateWebBrowserSiteBase override to reimplement /// IDocHostUIHandler. This is an acknowledged Microsoft bug /// https://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=115198. /// Fortunately, we only need to implement IAuthenticate, not IDocHostUIHandler, so this isn't a problem /// for us, but it was confusing for a couple of days.) /// public class WebBrowser2 : WebBrowser { private AxHost.ConnectionPointCookie cookie; private WebBrowser2EventHelper helper; public event Action NavigateError; public event EventHandler WindowClosed; public event Action AuthenticationPrompt; private bool Authenticate(out string username, out string password) { if (AuthenticationPrompt == null) { username = ""; password = ""; return false; } else { var args = new AuthenticationPromptEventArgs(); AuthenticationPrompt(this, args); username = args.Username; password = args.Password; return args.Success; } } [PermissionSet(SecurityAction.LinkDemand, Name = "FullTrust")] protected override void CreateSink() { base.CreateSink(); // Create an instance of the client that will handle the event // and associate it with the underlying ActiveX control. helper = new WebBrowser2EventHelper(this); cookie = new AxHost.ConnectionPointCookie(ActiveXInstance, helper, typeof(DWebBrowserEvents2)); } [PermissionSet(SecurityAction.LinkDemand, Name = "FullTrust")] protected override void DetachSink() { // Disconnect the client that handles the event // from the underlying ActiveX control. if (cookie != null) { cookie.Disconnect(); cookie = null; } base.DetachSink(); } protected override WebBrowserSiteBase CreateWebBrowserSiteBase() { return new WebBrowserSite2(this); } protected virtual void OnNavigateError(NavigateErrorEventArgs e) { if (NavigateError != null) NavigateError(this, e); } protected override void WndProc(ref Message m) { switch (m.Msg) { case Win32.WM_PARENTNOTIFY: if (!DesignMode && (int)m.WParam == Win32.WM_DESTROY) { if (WindowClosed != null) WindowClosed(this, EventArgs.Empty); } break; default: base.WndProc(ref m); break; } } #region Nested classes private class WebBrowserSite2 : WebBrowserSite, Win32.IAuthenticate, Win32.IServiceProvider { private readonly WebBrowser2 Browser; public WebBrowserSite2(WebBrowser2 browser) : base(browser) { Browser = browser; } #region IAuthenticate Members public int Authenticate(ref IntPtr phwnd, ref IntPtr pszUsername, ref IntPtr pszPassword) { string username, password; if (Browser.Authenticate(out username, out password)) { IntPtr sUser = Marshal.StringToCoTaskMemAuto(username); IntPtr sPassword = Marshal.StringToCoTaskMemAuto(password); pszUsername = sUser; pszPassword = sPassword; return Win32.S_OK; } else { return Win32.E_ACCESSDENIED; } } #endregion #region IServiceProvider Members public int QueryService(ref Guid guidService, ref Guid riid, out IntPtr ppvObject) { if (guidService.CompareTo(Win32.IID_IAuthenticate) == 0 && riid.CompareTo(Win32.IID_IAuthenticate) == 0) { ppvObject = Marshal.GetComInterfaceForObject(this, typeof(Win32.IAuthenticate)); return Win32.S_OK; } else { ppvObject = IntPtr.Zero; return Win32.INET_E_DEFAULT_ACTION; } } #endregion } /// /// Handles the NavigateError event from the underlying ActiveX /// control by raising the NavigateError event defined in this class. /// private class WebBrowser2EventHelper : StandardOleMarshalObject, DWebBrowserEvents2 { private WebBrowser2 parent; public WebBrowser2EventHelper(WebBrowser2 parent) { this.parent = parent; } public void NavigateError(object pDisp, ref object url, ref object frame, ref object statusCode, ref bool cancel) { parent.OnNavigateError(new NavigateErrorEventArgs((string)url, (string)frame, (Int32)statusCode, cancel)); } } public class NavigateErrorEventArgs : EventArgs { public string Url; public string Frame; public Int32 StatusCode; public bool Cancel; public NavigateErrorEventArgs(string url, string frame, Int32 statusCode, bool cancel) { Url = url; Frame = frame; StatusCode = statusCode; Cancel = cancel; } } public class AuthenticationPromptEventArgs : EventArgs { /// /// Set to true by the event handler if the user clicked OK on the authentication prompt. /// public bool Success; /// /// Set by the event handler, iff Success is true. /// public string Username; /// /// Set by the event handler, iff Success is true. /// public string Password; } #endregion } [ComImport, Guid("34A715A0-6587-11D0-924A-0020AFC7AC4D"), InterfaceType(ComInterfaceType.InterfaceIsIDispatch), TypeLibType(TypeLibTypeFlags.FHidden)] public interface DWebBrowserEvents2 { [DispId(271)] void NavigateError([In, MarshalAs(UnmanagedType.IDispatch)] object pDisp, [In] ref object URL, [In] ref object frame, [In] ref object statusCode, [In, Out] ref bool cancel); } }