/* 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);
}
}