Today I spent several hours trying to figure out why an embedded web browser component (in this case TEmbeddedWB
) in a Delphi test app never received the appropriate IHttpSecurity
and IWindowForBindingUI
QueryService
requests.
I was doing this in order to provide more nuanced handling of self-signed certificates in an intranet context. We all do this, right? Here the term “nuanced” means “Of course I trust self signed certificates on my intranet, don’t you?” Feel free to rant and rave on this. 😉
But no matter what I did, what incantations I tried, or what StackOverflow posts I perused, I was unable to find an answer. Until finally I stumbled on a side comment in a thread from 2010. Igor Tandetnik notes that:
Right after creating the control, navigate it to about:blank. Right after that, navigate it to the page you wanted to go to. It’s a known problem that IServiceProvider doesn’t work for the very first navigation.
And this was something that I kinda knew in the back of my head, but of course had forgotten. Thank you Igor.
This post would not be complete without some splendiferous code. Just for reference, it’s so simple if you don’t blank out and forget about:blank
.
unit InsecureBrowser;
interface
uses
Winapi.Windows,
Winapi.Messages,
Winapi.Urlmon,
Winapi.WinInet,
System.SysUtils,
System.Variants,
System.Classes,
Vcl.Graphics,
Vcl.Controls,
Vcl.Forms,
Vcl.Dialogs,
Vcl.OleCtrls,
Vcl.StdCtrls,
SHDocVw_EWB,
EwbCore,
EmbeddedWB;
type
TInsecureBrowserForm = class(TForm, IHttpSecurity, IWindowForBindingUI)
web: TEmbeddedWB;
cmdGoInsecure: TButton;
procedure webQueryService(Sender: TObject; const [Ref] rsid,
iid: TGUID; var Obj: IInterface);
procedure FormCreate(Sender: TObject);
procedure cmdGoInsecureClick(Sender: TObject);
private
{ IWindowForBindingUI }
function GetWindow(const guidReason: TGUID; out hwnd): HRESULT; stdcall;
{ IHttpSecurity }
function OnSecurityProblem(dwProblem: Cardinal): HRESULT; stdcall;
end;
var
InsecureBrowserForm: TInsecureBrowserForm;
implementation
{$R *.dfm}
function TInsecureBrowserForm.GetWindow(const guidReason: TGUID;
out hwnd): HRESULT;
begin
Result := S_FALSE;
end;
function TInsecureBrowserForm.OnSecurityProblem(dwProblem: Cardinal): HRESULT;
begin
if (dwProblem = ERROR_INTERNET_INVALID_CA) or
(dwProblem = ERROR_INTERNET_SEC_CERT_CN_INVALID)
then Result := S_OK
else Result := E_ABORT;
end;
procedure TInsecureBrowserForm.webQueryService(Sender: TObject;
const [Ref] rsid, iid: TGUID; var Obj: IInterface);
begin
if IsEqualGUID(IID_IWindowForBindingUI, iid) then
Obj := Self as IWindowForBindingUI
else if IsEqualGUID(IID_IHttpSecurity, iid) then
Obj := Self as IHttpSecurity;
end;
procedure TInsecureBrowserForm.cmdGoInsecureClick(Sender: TObject);
begin
web.Navigate('https://evil.intranet.site/');
end;
procedure TInsecureBrowserForm.FormCreate(Sender: TObject);
begin
web.Navigate('about:blank');
end;
end.
What a troubling insight. Thank you for shining a light on this. Navigating first to about:blank really did solve the “navigation canceled” page.