Asynchronous Pluggable Protocol Implementation with ATL

CodeGuru content and product recommendations are editorially independent. We may make money when you click on links to our partners. Learn More.

Introduction

Like most people who contribute, I think it’s a part of
wanting to contribute something back into the system. However,
I’ve always felt that perhaps I’m not worthy being a
mere social programmer rather than by profession. Anyhow, a
caveat, I’m self taught and I don’t profess to be an expert,
however I do like to find out how things work, and this article
is related to some of my findings regarding Asynchronous
Pluggable Protocols (APPs). I came up with an idea for a piece of
code whereby I could create some functionality that would sit
between Internet Explorer and the HTTP protocol such that I could
monitor and audit the traffic. After much research I discovered
the Asynchronous Pluggable Protocol technology that allows one to
create a protocol scheme (eg, much like http:, file:, etc) and
that the architecture was extensible and flexible enough to
introduce new schemes. To gain some exposure to the architecture
I decided to create a simple protocol scheme (app:) that would
retrieve an HTTP page. The code samples you will see merely
demonstrate a simple request using WinInet (not MFC) and using
callbacks for asynchronous operation, the program will not stand
on it’s own and do much useful. There is hardly any error
handling. </exit excuse mode>

The Registry

If you look under HKEY_CLASSES_ROOT/PROTOCOLS/Handler
you’ll see a very familiar list of protocol schemes, eg http:,
file:, local:, mailto:. Under each of these keys is a STRING
value with the name of CLSID. For
HKEY_CLASSES_ROOT/PROTOCOLS/Handler/http/CLSID this is
{79eac9e2-baf9-11ce-8c82-00aa004ba90b}. So, right now you’re
probably thinking is it that easy to create new schemes, well,
fundamentally, the answer is yes. Create a new COM object (more
on it’s interfaces soon), create a new key under the Handler
hierarchy and create CLSID STRING value and off you go.

The Interfaces

So, what of interfaces, well, the COM object needs to support
IInternetProtocol, this is mandatory. Others are optional and
I’ll talk about those later. IInternetProtocol is inherited from
IInternetProtocolRoot and together these interfaces have a number
of methods that must be implemented in the Pluggable protocol.
They are:

// IInternetProtocolRoot

Start( LPCWSTR szUrl, IInternetProtocolSink *pOIProtSink, IInternetBindInfo *pOIBindInfo, DWORD grfPI, DWORD dwReserved);
Continue( PROTOCOLDATA *pProtocolData);
Abort( HRESULT hrReason, DWORD dwOptions);
Terminate(DWORD dwOptions);
Suspend( void);
Resume( void);

// IInternetProtocol based on IInternetProtocolRoot

Read( void *pv, ULONG cb, ULONG *pcbRead);
Seek( LARGE_INTEGER dlibMove, DWORD dwOrigin, ULARGE_INTEGER *plibNewPosition);
LockRequest(DWORD dwOptions);
UnlockRequest( void);

For the purposes of this article, the only real important ones
are IInternetProtocolRoot::Start ()
and IInternetProtocol::Read. Both Suspend () and Resume
()
are not implemented and should return E_NOTIMPL. One of
the optional interfaces is IInternetProtocolInfo, this is
provided should you wish to process URL strings and break them
up. I have not provided this, thus Internet Explorer will provide
it’s own processing.

Interface Definition – IDL

The IDL is relatively simple, no need to expose any dual
interfaces, only IUnknown is
required, then the client (Internet Explorer) can go querying for
other mandatory/option interfaces.

import "oaidl.idl";
import "ocidl.idl";
[
	uuid(615A023A-8069-11D2-A019-204C4F4F5020),
	version(1.0),
	helpstring("Pluggable 1.0 Type Library")
]
library PLUGGABLELib
{
	importlib("stdole32.tlb");
	importlib("stdole2.tlb");

	[
		uuid(615A0247-8069-11D2-A019-204C4F4F5020),
		helpstring("HttpFilter Class")
	]
	coclass HttpFilter
	{
		interface IUnknown;
	};
};

Requesting the data

Essentially, when Internet Explorer sees the protocol scheme
name and an URL following, it will create the object (using the
CLSID from the registry) and pass information to the Start () method. The first parameter is
simple, this is the URL, including the scheme name, eg app:. The
second parameter is a pointer to an interface,
IInternetProtocolSink. This interface is provided by the client
of an Asynchronous Pluggable Protocol such that the handler can
notify the client of any events, eg Data Available. The rest of
the parameters are trivial and not important for this particular
example. So, we have the URL and need to start an HTTP transfer.
First thing that must be done is to save the ProtocolSink
pointer:

// Save pointers, we'll need them later
m_pProtocolSink = pOIProtSink;

As we’ll be using the WinInet functions, we need to first get
a handle such that we can perform some I/O functions, we do this
by using the InternetOpen () function. Note that we specify the INTERNET_FLAG_ASYNC
option, because we do not want operations to go blocked and hold
up Internet Explorer.

// Get handle for internet I/O

m_hInternetSession = InternetOpen (AgentName, INTERNET_OPEN_TYPE_DIRECT, NULL, NULL, INTERNET_FLAG_ASYNC);

if (!m_hInternetSession) {
	ATLTRACE(_T("InternetOpen () failedn"));
	return S_FALSE;
}
else
	ATLTRACE(_T("InternetOpen () succeeded, handle is 0x%lXn"), m_hInternetSession);

    

Now that we have a handle to the ‘internet’ we can start doing
something useful. The first thing you might do is manipulate the
URL in some way so that you construct a new one, for the purposes
of the example, I’ll just use a canned URL, but before we do, we
need to establish a callback. This is done thus.

InternetSetStatusCallback (m_hInternetSession, CHttpFilter::StatusCallback);

Note that the StatusCallback () method is a static function as
it can’t be used in the context of a class/object. However, now
we are going to actually start some real work, this takes the
form of opening an URL (in asynchronous mode). The very last
parameter to this call is passing a user specified "context"
DWORD. I’ve chosen to use our object pointer (this) so that the callback method can
access some member variables.

#define CANNED_URL "http://www.corporate.com/default.htm"
InternetOpenUrl(m_hInternetSession, CANNED_URL, NULL, 0, INTERNET_FLAG_RELOAD, (DWORD) this);
return S_OK;

If we weren’t operating in an asynchronous environment, we
would have expected InternetOpenUrl () to immediately pass back a
handle, however, our callback will communicate any handles.

The Callback

That’s it, we now need to sit back and wait for those
callbacks. As I said previously, the "context"
DWORD passed is our object pointer, so now, in the callback:

CHttpFilter::StatusCallback (HINTERNET hSession, DWORD Context, DWORD Status, LPVOID pInformation, DWORD InfoLength)
{
	CHttpFilter *	pClass = (CHttpFilter *) Context;

we work it back the other way. A lot of the calls to this
callback reflect the status so-far, so you get a lot of
"resolving hostname", "host contacted",
"sending request", type messages. However, some of the
messages are (obviously) important and contain some sort of
information, this information is communicated through the pInformation and InfoLength
parameters. The first one we are interested in is the handle that
needs to be returned from the initial InternetOpenUrl () call. By
using a switch statement on the Status parameter we catch the appropriate
status:

case INTERNET_STATUS_HANDLE_CREATED:
	ATLTRACE(_T("INTERNET_STATUS_HANDLE_CREATED as 0x%lXn"), *(DWORD *)pInformation);
	pClass -> m_hHttpSession = * (HINTERNET *) pInformation;	// Copy to member variable
	break;

The handle is stored for future use. We now wait for WinInet
to retrieve the file, when it arrives we’ll receive the
INTERNET_STATUS_REQUEST_COMPLETE status. At that point we then
call back in to our client (Internet Explorer) through it’s
IInternetProtocolSink interface through the ReportData () method.
This method allows you to report intermediate progress. The 100, 100 reflects the fact that 100% of
the 100% is complete and that the client should request data.

case INTERNET_STATUS_REQUEST_COMPLETE:
	ATLTRACE(_T("INTERNET_STATUS_REQUEST_COMPLETEn"));

	// Tell sink that we have the data

	pClass -> m_pProtocolSink -> ReportData (BSCF_DATAFULLYAVAILABLE, 100, 100);

	break;

We now wait for the IInternetProtocolSink client, Internet
Explorer, to call back into us and request the data. This is
achieved by implementing the Read ()
method. Remember that the handle used by InternetRead
()
is the one that we received in the callback and saved
away in a member variable.

CHttpFilter::Read(void *pv, ULONG cb, ULONG *pcbRead)
{
	BOOL	Status;

	Status = InternetReadFile (m_hHttpSession, pv, cb, pcbRead);

	ATLTRACE(_T("nBuffer is %ldn"), *pcbRead);

	if (Status == FALSE) {
		DWORD error = GetLastError();
		ATLTRACE(_T("Errno = %dn"), error);
		return INET_E_DOWNLOAD_FAILURE;
	}
	else
		if (*pcbRead == cb) {	// more to read ?
			return S_OK;
	}

	return S_FALSE;
}

The documentation for IInternetProtocol::Read
()
states that the client may call the method several
times after having read to end of file, in which case you must
return S_FALSE. Apart from cleaning up by closing some handles
(via InternetCloseHandle ()) that’s
about it.

Final Note

As a small exercise, this Asynchronous Pluggable Protocol was
quite informative, in particular, just watching ATLTRACE
statements flashing by is an eye opener. My project took a
different turn shortly afterwards.The thinking was that
"Heck, Microsoft have done all the hard work in there HTTP
Asynchronous Pluggable Protocol, why not use that?". So,
I’ve started down that path. Now Internet Explorer calls my
Pluggable protocol, and I call the real HTTP Pluggable protocol
pretending to be Internet Explorer (you only have to implement
IInternetProtocolSink). However, I soon found that I became
unstuck in that Internet Explorer seems to special case the HTTP
Asynchronous Pluggable Protocol and made it non-Pluggable.
Additionally, I ended up implementing the URL processing
interface (IInternetProtocolInfo) and again used the WinInet
functions to do the hard work. However, HTTP aside, it does make
the mind think, what if I could create a ???? Pluggable Protocol,
eg a deciphering one (pgp:), or a database accessing one (sql:).
Have fun….

Any feedback gratefully received.

Reference Material

Asynchronous
Pluggable Protocols Reference

Asynchronous
Pluggable Protocols Overview

IInternetProtocolRoot
IInternetProtocol
IInternetProtocolSink
EtcProtocol
Demonstrates Pluggable Protocol Handler
(From KnowledgeBase:
never got this sample to work in VC5 or 6)

Downloads

Download demo project – 11 KB

 

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read