Chapter 14

Creating an ISAPI Filter


In this chapter, you learn how to create an Internet Server Application Programming Interface (ISAPI). We begin by creating a dynamic-link library (DLL) in Microsoft Visual C++ (MSVC++) 4.x.

We outline the filter's initializations and termination, the entry points, the hypertext transport protocol (HTTP) flow events, and the API function calls. A tutorial shows you how to create a filter that does customized text replacement in an HTML page.

If you need a refresher, see Chapter 13, "Understanding ISAPI Filters," and review the rules for creating a filter.

Step 1: Create a 32-Bit DLL

In this section, we follow the steps to create an ISAPI filter DLL. We use the MSVC++ version 4.x development environment, running on Windows NT server 4.0, with Microsoft's Internet Information Server (IIS) 3.0 to serve the Web requests.

Create a 32-Bit DLL Project Workspace

To begin a new project with MSVC++, open Microsoft Developer Studio from the Microsoft Visual C++ programs folder. If you have a subscription to Microsoft Developer Network (MSDN) and it is integrated with the Microsoft Developer Studio, you see a window like the one in Fig. 14.1.

Fig. 14.1

Microsoft Developer Studio-ready to go.

Microsoft Published Resources


Time is money and knowledge is power. Microsoft publishes two CDs and offer both via MSDN and Microsoft TechNet. These are invaluable tools for the busy and the beginning developer.

MSDN is produced quarterly and is the official source from Microsoft for comprehensive programming information, development toolkits, and testing platforms.

TechNet, with over 150,000 pages of in-depth technical information on each CD, is produced monthly and is the comprehensive information resource for evaluating, using, and supporting Microsoft business products.

Additional ordering and pricing is at Microsoft's Web site: http://www.microsoft.com/msdn and http://www.microsoft.com/technet.

When you have an empty palette to work with, you are ready to create a DLL project workspace. From the File menu, select New. You see a New dialog box like the one in Fig. 14.2.


Fig. 14.2

New dialog box-select an object to create.

Select Project Workspace and click the OK button. You see a New Project Workspace dialog box like the one in Fig. 14.3.

Fig. 14.3

New Project Workspace-select a project type to create.

From the Type list box, select Dynamic-Link Library. In the Name text box, enter the name of the DLL to be created. Notice that in the Location text box, the project name is appended to the path where the Developer Studio projects are stored.

Make sure that Win32 is checked in the Platforms check box grouping. When you finish the New Project Workspace dialog box as shown in Fig. 14.4, you are ready to click the Create button. We have a new project workspace and we are ready to begin adding code.

Fig. 14.4

New project workspace ready for the sample project.

Add DllMain()for Startup/Shutdown

DllMain is the first function we use when creating a new ISAPI filter. DllMain is normally the entry point for 32-bit DLLs. This single function replaces LibMain and WEP in the Win32 model.

DllMain is optional but highly recommended. The name DllMain is also optional but it is the de facto standard. A DllMain function allows the server to notify your filter when per-process, and per-thread initialization and clean-up, are needed.

DllMain has a specific function prototype that you must adhere to so it works properly. This prototype is as follows:

BOOL WINAPI DllMain (
HINSTANCE hinstDLL,
DWORD fdwReason,
LPVOID lpvReserved
);

DllMain is called when the filter is loaded and unloaded from the server's address space. DllMain can determine why it is being called by checking the value of fdwReason.

The four possible values for fdwReason are DLL_PROCESS_ATTACH, DLL_PROCESS_DETACH, DLL_THREAD_ATTACH, and DLL_THREAD_DETACH. We use DLL_PROCESS_ATTACH to do filter initialization and DLL_PROCESS_DETACH to do filter clean-up.

An example of an initialization process is to read data from persistent storage to initialize the filter's global variables-for example, a Web-site visit counter.

An example of clean-up processing is writing global variables to persistent storage to save the state of the variables between the termination and restarting of the World Wide Web publishing service.

Listing 14.1 is an example of how to retrieve and store state variables in the DllMain function.

Listing 14.1 LST14_1.C-Retrieving and Storing State Variables

/*

Globals

*/

CRITICAL_SECTION gCS; // A critical section handle

// is used to protect global

// state properties

unsigned int giCurrentAd;

/*

This is the entry and exit point for the filter

it is called when the filter is loaded and unloaded

by IIS. This is where state properties need to be

retrieved and store on persistant storage.

*/

BOOL APIENTRY DllMain( HANDLE hModule,

DWORD fdwReason,

LPVOID lpReserved )

{

char szLastAd[4];

switch( fdwReason ) {

case DLL_PROCESS_ATTACH:

{

/*

On process attach we will set the state

variables for the filter. This entails

counting the number of advertisers we're

flipping ads for.

*/

InitializeCriticalSection(&gCS);

EnterCriticalSection(&gCS);

gbHTML=FALSE;

giCurrentAd = GetPrivateProfileInt(TEXT("Info"), TEXT("LastAd"),

1, TEXT("ads.ini"));

LeaveCriticalSection(&gCS);

break;

}

// case DLL_THREAD_ATTACH:

// case DLL_THREAD_DETACH:

case DLL_PROCESS_DETACH:

{

sprintf(szLastAd, "%d", giCurrentAd);

WritePrivateProfileString(TEXT("Info"), TEXT("LastAd"),

szLastAd, TEXT("ads.ini"));

DeleteCriticalSection(&gCS);

break;

}

}

return TRUE;

}

Listing 14.1 above includes an example of using critical-section code to make your filter thread-safe. For a information on making your extension thread-safe, see Chapter 18, "Making Your Extensions Thread-Safe."


To add a new source file and create a DllMain function for your new filter project in MSVC++, from the File menu, select New. You see the New dialog box shown in Figure 14.2 earlier in this chapter.

Select Text File from the New list box and click the OK button. You see an empty window that is ready for your DllMain function. With the DllMain function added, your development environment should look something like Figure 14.5.

Fig. 14.5

Workspace with new DllMain function added.

The last task is to add the new code to the filter project. From the File menu, select Save As and save the file under an appropriate source file name in the same directory as the other project files.

After you save the file to disk, from the Insert menu, select Files into Project. You see the Insert Files into Project dialog box shown in Figure 14.6.

Fig. 14.6

The Insert Files into Project dialog box.

With the source file selected, click the Add button. The new source file is added to the new filter project and displayed in the project workspace tree view. The single source file is normally all you need for a basic filter.

Now we are ready to move on to the function calls that enable an ISAPI filter to be recognized and to work as an extension of the Web service.

Step 2: Define GetFilterVersion()

GetFilterVersion is one of two necessary functions for every ISAPI filter. It is the initial entry point called by IIS. GetFilterVersion receives a pointer to the structure, HTTP_FILTER_VERSION, which must be completed by the function.

Items to be completed in the structure include the filter's version information, the events that the filter is to process, and the priority of these events in terms of other registered filters.

Register the filter only for events that the filter will process. Registering extraneous events can reduce the Web service's performance and scalability.

The GetFilterVersion Prototype

The prototype for GetFilterVersion is

BOOL WINAPI GetFilterVersion (PHTTP_FILTER_VERSION pVer)

The pVer parameter points to the HTTP_FILTER_VERSION structure and provides the communication channel between IIS and the filter. The structure holds version information for the server and fields for the client to denote the version, event notifications, filter priority, and a text description of the filter.

The structure is defined as

typedef struct _HTTP_FILTER_VERSION
{
DWORD dwServerFilterVersion;
DWORD dwFilterVersion;
CHAR lpszFilterDesc[SF_MAX_FILTER_DESC_LEN+1];
DWORD dwFlags;
} HTTP_FILTER_VERSION, *PHTTP_FILTER_VERSION;

The structure members are as follows:

dwServerFilterVersion [in]. The version of the specification used by the server. The version of the current header file is HTTP_FILTER_REVISION.

dwFilterVersion [out]. The version of the specification used by the server. The version of the current header file is HTTP_FILTER_REVISION.

lpszFilterDesc [out]. The location in which to store a short string description of the ISAPI filter.

dwFlags [out] The combination of SF_NOTIFY_* flags to specify which events this application is to process. Table 14.1 lists the valid SF_NOTIFY_* flags for IIS version 3.0.

Table 14.1 Valid SF_NOTIFY_* Flags for IIS Version 3.0

Flag

Description

SF_NOTIFY_SECURE_PORT

Notifies application only for sessions over a secure port.

SF_NOTIFY_NONSECURE_PORT

Notifies application only for sessions over a nonsecure port.

SF_NOTIFY_READ_RAW_DATA

Allows the application to see the raw data. The data returned holds both headers and data.

SF_NOTIFY_PREPROC_HEADERS

Server has reprocessed the headers.

SF_NOTIFY_AUTHENTICATION

Server is authenticating the client.

SF_NOTIFY_URL_MAP

Server is mapping a logical URL to a physical path.

SF_NOTIFY_SEND_RAW_DATA

Server is sending raw data back to the client.

SF_NOTIFY_LOG

Server is writing information to the server log.

SF_NOTIFY_END_OF_NET_SESSION

Session with the client is ending.

SF_NOTIFY_ACCESS_DENIED

Allows a filter to be notified when the server is about to return a 401 Access Denied. This allows the filter to analyze the failure and return a custom message.

SF_NOTIFY_ORDER_DEFAULT

Loads the filter at the default priority (recommended).

SF_NOTIFY_ORDER_LOW

Loads the filter at the low priority.

SF_NOTIFY_ORDER_MEDIUM

Loads the filter at a medium priority.

SF_NOTIFY_ORDER_HIGH

Loads the filter at a high priority.

Set the ISAPI Version Number and Description

As we said earlier, GetFilterVersion holds key information that must be set before the server begins notifying the filter of events. The first two items to be set in the HTTP_FILTER_VERSION structure are dwFilterVersion and lpszFilterDesc. These items give the Web service version information for the filter and a text description that can be displayed in processing logs.

You should test the HTTP_FILTER_VERSION dwServerFilterVersion member if you are developing the filter for a specific version of IIS. Use the HIWORD and LOWORD macros to extract the major version and minor version numbers of the Web service.

Listing 14.2 is an example of how to test the server's version information. When you set the filter's version number, use the MAKELONG macro to set the high and low word that make up the HTTP_FILTER_VERSION structure dwFilterVersion member.

Set the Event Notifications

The most important data element to set in the HTTP_FILTER_VERSION structure is dwFlags. This member tells the Web service which events the filter is to process.

Table 14.1 lists the event notification flags to set for IIS version 3.0. Typically, you can copy this example of a GetFilterVersion function and use it for each new filter. You need to change only the description and events notification flags.

Listing 14.2 LST14_2.C-GetFilterVersion Function

/*

GetFilterVersion - An ISAPI/Win32 API method

This method is required by IIS. It is called

following the process load to ensure that the

filter is compatable with the server.

*/

BOOL WINAPI GetFilterVersion(HTTP_FILTER_VERSION * pVer)

{

WORD wMajorVersion=0;

WORD wMinorVersion=0;

wMajorVersion = HIWORD( pVer->dwServerFilterVersion );

wMinorVersion = LOWORD( pVer->dwServerFilterVersion );

/*

This filter is intended for IIS version 2.0 or greater.

This is an example of how to test the server version.

*/

if (wMajorVersion < 2) return FALSE;

pVer->dwFilterVersion = MAKELONG( 0, 1 ); // Version 1.0

/*

Specify the security level of notifications

(secured port, nonsecured port, or both), the

types of events and order of notification for

this filter (high, medium or low, default=low).

*/

pVer->dwFlags = (

SF_NOTIFY_SECURE_PORT |

SF_NOTIFY_NONSECURE_PORT |

SF_NOTIFY_READ_RAW_DATA |

SF_NOTIFY_PREPROC_HEADERS |

SF_NOTIFY_URL_MAP |

SF_NOTIFY_AUTHENTICATION |

SF_NOTIFY_ACCESS_DENIED |

SF_NOTIFY_SEND_RAW_DATA |

SF_NOTIFY_LOG |

SF_NOTIFY_END_OF_NET_SESSION |

SF_NOTIFY_ORDER_DEFAULT

);

/*

A brief one line description of the filter

*/

strcpy( pVer->lpszFilterDesc, "Sample Filter, v1.0" );

return TRUE;

}

IIS makes assumptions if certain notification flags are not specified. If a port notification flag is not specified, IIS assumes that the filter is to get notifications for both secure (SF_NOTIFY_SECURE_PORT) and nonsecure (SF_NOTIFY_NONSECURE_PORT) port requests.

If a priority order is not specified, IIS assumes the default priority (SF_NOTIFY_ORDER_DEFAULT). This is the same as specifying a low priority (SF_NOTIFY_ORDER_LOW).

Step 3: Define HttpFilterProc()

HttpFilterProc is the callback function that gets the event notification from the Web service. HttpFilterProc is to IIS what WindowProc is to the Windows operating system: the main event message loop. This is where the event messages are parceled out to the worker functions to do the task at hand.

The HttpFilterProc Prototype

The HttpFilterProc prototype is

DWORD WINAPI HttpFilterProc(
PHTTP_FILTER_CONTEXT pfc,
DWORD notificationType,

LPVOID pvNotification
);

The parameters for HttpFilterProc include a pointer, pfc, to the HTTP_FILTER_CONTEXT structure that can be used by the filter to associate any context information with the HTTP request. The HttpFilterProc parameters also include pointers to callback functions in the Web service for two-way communications.

The other two parameters, notificationType and pvNotification, have a direct correlation. The data pointed to by the void pointer, pvNotification, can take the shape of several structures, based on the event notification type, notificationType.

Table 14.2 shows the correlation between notificationType and the structures pointed to by pvNotification.

Table 14.2 Notification Types and Structures

Notification Type

Structure Name

SF_NOTIFY_READ_RAW_DATA

HTTP_FILTER_RAW_DATA

SF_NOTIFY_SEND_RAW_DATA

HTTP_FILTER_RAW_DATA

SF_NOTIFY_PREPROC_HEADERS

HTTP_FILTER_PREPROC_HEADERS

SF_NOTIFY_AUTHENTICATION

HTTP_FILTER_AUTHENT

SF_NOTIFY_URL_MAP

HTTP_FILTER_URL_MAP

SF_NOTIFY_LOG

HTTP_FILTER_LOG

SF_NOTIFY_ACCESS_DENIED

HTTP_FILTER_ACCESS_DENIED

A filter's HttpFilterProc serves as a traffic director for routing events to the right worker function. Listing 14.3 is an example of a typical HttpFilterProc set up to accept all the possible HTTP communication events.

Listing 14.3 LST14_3.C-HttpFilterProc Example

/*

HttpFilterProc - ISAPI / Win32 API method

This method is a required by IIS. It is called

for each notification event requested. This is

where the filter accomplishes its purpose in life.

*/

DWORD WINAPI HttpFilterProc(HTTP_FILTER_CONTEXT *pfc,

DWORD NotificationType,

VOID * pvData)

{

DWORD dwRet;

/*

Direct the notification to the appropriate

routine for processing.

*/

switch ( NotificationType )

{

case SF_NOTIFY_READ_RAW_DATA:

dwRet = OnReadRawData(pfc, (PHTTP_FILTER_RAW_DATA) pvData );

break;

case SF_NOTIFY_PREPROC_HEADERS:

dwRet = OnPreprocHeaders(pfc, (PHTTP_FILTER_PREPROC_HEADERS) pvData );

break;

case SF_NOTIFY_URL_MAP:

dwRet = OnUrlMap(pfc, (PHTTP_FILTER_URL_MAP) pvData );

break;

case SF_NOTIFY_AUTHENTICATION:

dwRet = OnAuthentication(pfc, (PHTTP_FILTER_AUTHENT) pvData );

break;

case SF_NOTIFY_ACCESS_DENIED:

dwRet = OnAccessDenied(pfc, (PHTTP_FILTER_ACCESS_DENIED) pvData );

break;

case SF_NOTIFY_SEND_RAW_DATA:

dwRet = OnSendRawData(pfc, (PHTTP_FILTER_RAW_DATA) pvData );

break;

case SF_NOTIFY_LOG:

dwRet = OnLog(pfc, (PHTTP_FILTER_LOG) pvData );

break;

case SF_NOTIFY_END_OF_NET_SESSION:

dwRet = OnEndOfNetSession(pfc);

break;

default:

dwRet = SF_STATUS_REQ_NEXT_NOTIFICATION;

break;

}

return dwRet;

}

Filter Context Structure and Key Members

pfc is a pointer to the HTTP_FILTER_CONTEXT structure for all request notification types. It keeps a consistent state between HTTP communication events and has this layout:

typedef struct _HTTP_FILTER_CONTEXT
{
DWORD cbSize; // structure size
DWORD Revision; // revision level
PVOID ServerContext; // context info for the server
DWORD ulReserved; // for future use
BOOL fIsSecurePort; // TRUE=request from secure port
PVOID pFilterContext; // context info for the filter
//
// Server callbacks
//
BOOL (WINAPI * GetServerVariable) (
struct _HTTP_FILTER_CONTEXT * pfc,
LPSTR lpszVariableName,
LPVOID lpvBuffer,
LPDWORD lpdwSize
);

BOOL (WINAPI * AddResponseHeaders) (
struct _HTTP_FILTER_CONTEXT * pfc,
LPSTR lpszHeaders,
DWORD dwReserved
);

BOOL (WINAPI * WriteClient) (
struct _HTTP_FILTER_CONTEXT * pfc,
LPVOID Buffer,
LPDWORD lpdwBytes,
DWORD dwReserved

);

VOID * (WINAPI * AllocMem) (

struct _HTTP_FILTER_CONTEXT * pfc,
DWORD cbSize,

DWORD dwReserved
);

BOOL (WINAPI * ServerSupportFunction) (

struct _HTTP_FILTER_CONTEXT * pfc,
enum SF_REQ_TYPE sfReq,
PVOID pData,
DWORD ul1,
DWORD ul2

);

} HTTP_FILTER_CONTEXT, *PHTTP_FILTER_CONT

fIsSecurePort

The filter context structure has a member named fIsSecurePort, which is a Boolean flag that indicates whether the HTTP request was made over a secured port using the secure socket layer (SSL) encryption protocol. If the request did originate from client requesting a secure port, this flag is set to TRUE or a nonzero value.

pFilterContext

The pFilterContext member of the filter context structure is a void pointer. It can be replaced with a pointer to data that is relevant in the context of the HTTP request.

Use the filter-context AllocMem callback function or a standard memory-allocation method to allocate memory and set pFilterContext to the allocated memory and data structure.

Since the SF_NOTIFY_END_OF_NET_SESSION is the last event in the HTTP communication flow, you can request it in the GetFilterVersion function. This allows the release of resources requested during the context of a single HTTP request.

If memory is allocated using the AllocMem callback function, resources are automatically released at the end of the HTTP communication.


Filter Context Callback Functions

In the HTTP_FILTER_CONTEXT structure are five callback functions, all for two-way communication between the filter and the Web service. These functions are detailed next.

GetServerVariable Callback Function

The first callback function found in HTTP_FILTER_CONTEXT is GetServerVariable. This function retrieves information about a connection or about the Web service. The function prototype and parameters are as follows:

BOOL WINAPI GetServerVariable (
PHTTP_FILTER_CONTEXT pfc,
LPSTR lpszVariableName,
LPVOID lpvBuffer
LPDWORD lpdwSizeofBuffer
);

The parameters for GetServerVariable include the following:

Table 14.3 Possible Server Variables

Variable Name

Description

AUTH_TYPE

Holds the type of authentication used. For example, if basic authentication is used, the string is Basic. For NT challenge-response, it is NTLM. Other authentication schemes have other strings.

CONTENT_LENGTH

Number of bytes the script can expect to receive from the client.

CONTENT_TYPE

Content type of the information supplied in the body of a POST request.

PATH_INFO

Additional path information, as given by the client. This consists of the trailing part of the URL after the script name but before the query string, if any.

PATH_TRANSLATED

Value of PATH_INFO but with any virtual path name expanded into a directory specification.

QUERY_STRING

Information that follows the "?" in the URL that referenced this script.

REMOTE_ADDR

IP address of the client or agent of the client (for example, a gateway or firewall) that sent the request.

REMOTE_HOST

Host name of the client or agent of the client (for example, a gateway or firewall) that sent the request.

REMOTE_USER

Holds the user name supplied by the client and authenticated by the server. This comes back as an empty string when the user is anonymous (but authenticated).

UNMAPPED_REMOTE_USER

User name before a filter maps the user making the request to an NT user account (which appears as REMOTE_USER).

REQUEST_METHOD

HTTP request method.

SCRIPT_NAME

Name of the script program executed.

SERVER_NAME

Server's host name or IP address as it should appear in self-referencing URLs.

SERVER_PORT

TCP/IP port on which the request was received.

SERVER_PORT_SECURE

String of either 0 or 1. If the request is handled on the secure port, this is 1. Otherwise, it is 0.

SERVER_PROTOCOL

Name and version of the information- retrieval protocol relating to this request. This is normally HTTP/1.0.

SERVER_SOFTWARE

Name and version of the server under which the ISAPI filter DLL program is running.

ALL_HTTP

All HTTP headers not already parsed into one of the previous variables. These variables are of the HTTP_<header field name> form. The headers consist of a null-terminated string with the individual headers separated by line feeds.

HTTP_ACCEPT

Special-case HTTP header. Values of the Accept: fields are concatenated and separated by a comma (","). For example, if the following lines are part of the HTTP header:
accept: */*; q=0.1
accept: text/html
accept: image/jpeg
the HTTP_ACCEPT variable has a value of: */*; q=0.1, text/html, image/jpeg

URL

Gives the base portion of the URL.

If GetServerVariable returns successfully, the return value is TRUE. If the function fails, you can use the Win32 GetLastError function to determine why the function call failed.

Table 14.4 lists the errors that can be returned by GetLastError, following a failed return from GetServerVariable.

Table 14.4 GetServerVariable: Possible Errors

Value

Description

ERROR_INVALID_PARAMETER

Bad connection handle.

ERROR_INVALID_INDEX

Bad or incompatible variable identifier

ERROR_INSUFFICIENT_BUFFER

Buffer too small. The necessary buffer size is lpdwSize.

ERROR_MORE_DATA

Buffer too small. Only part of the data is returned. The total size of the data is not known.

ERROR_NO_DATA

The data requested is not available.

Listing 14.4 is an example of how to access the Web service variables using GetServerVariables.

Listing 14.4 LST14_4.C-GetServerVariable Example

/*

The following is an example of how to use GetServerVariable to

retrieve information concerning the web server or the communication

in process. In this example pfc is a pointer to the

HTTP_FILTER_CONTEXT structure.

*/

CHAR achUserAgent[4182];

DWORD cbUserAgent= 4182;

// Execute the server callback function GetServerVariable

if ( !pfc->GetServerVariable( pfc,"HTTP_USER_AGENT",achUserAgent,&cbUserAgent ))

{

/*

There was an error in processing the request. This is an example

of checking the error type using the Win32 function GetLastError.

*/

if (ERROR_INVALID_INDEX != GetLastError()) (

return SF_STATUS_REQ_ERROR;

}

}

AddResponseHeaders Callback Function

The AddResponseHeaders callback function allows the server to add response header information to the HTTP communication response to the client. The function prototype for AddResponseHeaders is

BOOL WINAPI AddResponseHeaders) (
PHTTP_FILTER_CONTEXT pfc,
LPSTR lpszHeaders,
DWORD dwReserved
);

The parameters for AddResponseHeaders include the following:

AddResponseHeaders is normally used with the WriteClient callback function, which allows HTTP or binary data to be written directly to the client. Response headers supply information to the client about the data to be transmitted.

You can formulate a complete communication reply by using AddResponseHeader to notify the client of a data type to be sent to a client and WriteClient to write the data to the client.

Listing 14.5 is an example of how to add response headers to an HTTP request using AddResponseHeaders.

Listing 14.5 LST14_5.C-AddResponseHeaders Example

/*

The following is an example of how to use AddResponseHeaders to inform

the client browser of the content length of the data about to be

transmitted.

*/

// Execute the server callback function AddResponseHeaders

if ( !pfc->AddResponseHeaders( pfc, "Content-Length = 100", NULL))

{

/*

There was an error in processing the request so

do error processing here

*/

}

WriteClient Callback Function

The WriteClient callback function sends data to the client from a buffer provided by the filter. WriteClient enables the transfer of binary data because it does not assume that a null-terminated string is passed back to the client.

The function prototype for WriteClient is

BOOL WINAPI WriteClient (
PHTTP_FILTER_CONTEXT pfc,
LPVOID lpvBuffer,
LPDWORD lpdwSizeofBuffer,
DWORD dwReserved
);

The parameters for WriteClient include the following:

WriteClient is often used with AddResponseHeaders to complete an HTTP request. If lpvBuffer points to a null-terminated string and the whole string is to be sent, lpdwSizeofBuffer should be set to the length of the string.

On its return from the call, lpdwSizeofBuffer is equal to the number of bytes transferred. This number is less than the original amount only if the original call returns FALSE.

Listing 14.6 is an example of how to transfer data to the client using WriteClient.

Listing 14.6 LST14_6.C-WriteClient Example

/*

The following is an example of how to use WriteClient to transfer

data to the client browser.

*/

char szResponse[100];

char szTime[20];

DWORD dwRespLen=0;

DWORD dwError=0;

// Create a forced response string

_strtime(szTime);

sprintf(szResponse,

"<HTML><BODY><H1>Forced Response at %s</H1></BODY></HTML>",

szTime);

dwRespLen=strlen(szResponse);

// Execute the server callback function WriteClient

if ( !pfc->WriteClient( pfc, (LPVOID)&szResponse,(LPDWORD)&dwRespLen, NULL))

{

dwError = GetLastError();

}

AllocMem Callback Function

The AllocMem callback function allocates a generic memory buffer that is automatically freed at the end of the HTTP request. The function prototype for AllocMem is

LPVOID AllocMem (

DWORD cbSize,

DWORD dwReserved

);

The parameters for AllocMem include the following:

The return value is a void pointer (LPVOID) to a memory buffer that was allocated by the function call.

You can't manage memory blocks allocated with AllocMem with the normal C runtime or Windows API memory management functions.


Calls to AllocMem can reduce performance because the burden of memory management is on the Web service. You might increase the filter's performance by allowing the filter to manage its own memory allocations.

Allocate memory as needed and then release it at the end of the HTTP communication request. The last event in the HTTP request is SF_NOTIFY_END_OF_NET_SESSION.

The filter has to register for this event in the GetFilterVersion function to get a notification of the end-of-net-sessions event.


Listing 14.7 is an example of how to allocate a block of memory with a call to AllocMem.

Listing 14.7 LST14_7.C-AllocMem Example

/*

The following is an example of how to use AllocMem to

allocate a block of memory.

*/

char* pszBuffer;

pszBuffer=(CHAR*)pfc->AllocMem(pfc, 400, (DWORD)NULL);

memset(pszBuffer,'\0',sizeof(pszBuffer));

ServerSupportFunction Callback Function

The ServerSupportFunction callback function gives the filter general-purpose functions, plus methods specific to the Web service. The function prototype for ServerSupportFunction is

BOOL WINAPI ServerSupportFunction (
PHTTP_FILTER_CONTEXT pfc,
enum SF_REQ_TYPE sfRequest,
PVOID pvData,
LPDWORD lpdwSizeofBuffer,
LPDWORD lpdwDataType
);

The parameters for ServerSupportFunction include:

Table 14.5 SF_REQ_* Options

Value

Description

SF_REQ_SEND_RESPONSE_HEADER

Sends a complete HTTP server response header, including the status, server version, message time, and Content-type MIME version. Server extensions should append other information at the end, such as and Content-length, etc. followed by an extra '\r\n'.

SF_REQ_ADD_HEADERS_ON_DENIAL

If the server denies the HTTP request, add the specified headers to the server error response. This allows an authentication filter to advertise its services without filtering every request.

Generally, the headers are WWW-Authenticate headers with custom authentication schemes but no restriction on what headers can be specified.

SF_REQ_SET_NEXT_READ_SIZE

Used only by raw-data filters that return SF_STATUS_READ_NEXT lpdwSizeofBuffer size in bytes for the next read.

Listing 14.8 is an example of one of the possible function calls using ServerSupportFunction.

Listing 14.8 LST14_8.C-ServerSupportFunction Example

/*

The following is an example of how to use ServerSupportFunction to read

additional data from the client browser.

*/

char szBuffer[100];

DWORD dwBufferLen=0;

dwszBufferLen=sizeof(szBuffer);

pfc->ServerSupportFunction(pfc,

SF_REQ_SET_NEXT_READ_SIZE,

(PVOID)&szBuffer,

dwBufferLen,

(DWORD)NULL );

Do the Processing

When the necessary functions are in your filter and the notification events have been requested, you're ready to do your processing. But when and where does the code get written?

Chapter 12, "Using ISAPI Filters," outlines the sequence of events in the HTTP request. Chapter 12 also gives you ideas for the kinds of filters you can create.

Listing 14.3 earlier in this chapter is the backbone for HTTP request event processing. Worker functions are called from the switch/case statement or processing is done in the case statement.

The tutorial at the end of chapter brings together all the pieces we discussed with an example of how to change the raw data sent back to the browser.

Return Status Code

When the filter's processing is done, the right return code has to be passed back to the Web service. The return code tells the Web service how the event was handled and what process is needed next.

Table 14.6 lists the valid return codes and their meanings for the HttpFilterProc function.

Table 14.6 HttpFilterProc Return Codes

Value

Description

SF_STATUS_REQ_FINISHED

The filter has handled the HTTP request. The server should disconnect the session.

SF_STATUS_REQ_FINISHED_KEEP_CONN

This is the same as SF_STATUS_REQ_FINISHED, except that the server should keep the TCP session open if the option was negotiated.

SF_STATUS_REQ_NEXT_NOTIFICATION

The next filter in the notification chain should be called.

SF_STATUS_REQ_HANDLED_NOTIFICATION

This filter handled the notification. No other handlers should be called for this notification type.

SF_STATUS_REQ_ERROR

An error occurred. The server should call GetLastError and indicate the error to the client.

SF_STATUS_REQ_READ_NEXT

The filter is an opaque stream filter and the session parameters are being negotiated. This is valid only for raw- read notification.

Tutorial 4: Building the Ad Flipper Filter

The AdFlipperFilter project is on the companion CD to this book. AdFlipperFilter replaces a text string in an HTML page to create advertisement graphics with hyperlinks to the advertiser's Web site.

The filter uses a DllMain function to initialize filter-wide parameters. When DllMain is called with the flag, DLL_PROCESS_ATTACH, a critical-section handle is created for thread-safe processing.

The contents of a configuration file are loaded into global variables for be used throughout the filter. When DllMain is called with DLL_PROCESS_DETACH, clean-up processing is done by deleting the critical section handle.

AdFlipperFilter's GetFilterVersion function requests notifications for two specific events, SF_NOTIFY_URL_MAP and SF_NOTIFY_SEND_RAW_DATA. The URL map event is requested to analyze the name of the HTML page requested. The send raw data event is requested because that is were the raw data being sent to the client browser can be manipulated.

By default, IIS assumes that a filter will be used for processing on both secure and nonsecure ports. IIS also assumes that the filter will process at a default priority, which is defined as low priority. For our tutorial, the SF_NOTIFY_* flags are requested specifically to ensure proper processing.

AdFlipperFilter's HttpFilterProc function directs the event traffic to the proper worker function. The function's switch/case statement directs notification events of SF_NOTIFY_URL_MAP to the OnUrlMap function and SF_NOTIFY_SEND_RAW_DATA to the OnSendRawData function. All other extraneous events are bypassed.

The OnUrlMap function analyzes the URL resource that is requested. In this example, we want to do processing only for resources that end with the ads.htm string. So URLs like http://www.yoursite.com/ads.htm and http://www.yoursite.com/HOMads.htm are valid for processing.

When a URL passes the test for AdFlipperFilter processing, the filter-context callback function AllocMem is called to create a memory buffer to hold the image and link string. The InsertAd function is called with the memory buffer just created.

The InsertAd function uses the filter's global variables and configuration file to determine which advertisement is next in the rotation. The function then constructs the image-and- link strings to be inserted into the body of HTML page at the special tag placeholder.

The image-and-link string that is created in the InsertAd function also calls an ISAPI ClickThruExtension.dll extension. This extension is on the companion CD in the AdFlipperFilter project directory.

The ClickThruExtension.dll extension tallies the number of times a user clicks on an advertisement that passes control to the advertiser's Web site. Chick-thru statistics can be reviewed by placing a request with the ISAPI SiteStatsExtension.dll extension (also on the companion CD in the AdFlipperFilter project directory).

A request to the http://www.yoursite.com/scripts/SiteStatsExtension.dll?ClickThrus resource produces an HTML page with the number of click thrus and the first date a click thru occurred, by URL. You should put the supplemental extensions mentioned above in the IIS scripts directory before you try to use them.

The OnSendRawData function is called twice in the HTTP communication flow. It's first called with the response headers to be passed back to the client browser.

These headers include the Content-Length header, which tells the browser how much data to expect. Since AdFlipperFilter expands the HTML page size, the Content-Length header needs to be increased to include the number of bytes added by the image-and-link string.

The second call to the OnSendRawData function is just before the server passes the HTML page to the browser. This is when the function inserts the image-and-link string into the page.

Listing 14.9 is the source code for the AdFlipperFilter.

Listing 14.9 ADFLIPPERFILTER.C-AdFlipperFilter Source Code

/*

Copyright (c) 1996 ClearMind, Inc.

Module Name:

AdFlipperFilter.c

Abstract:

This filter checks HTML pages accessed at the server for

the special advertisement tags. Then it looks into its

database for the next ad to run on the page. It then

inserts the hyperlink, graphic and alternate text for the

advertiser.

*/

#include <windows.h>

#include <httpfilt.h>

#include <string.h>

#include <stdio.h>

#include <stdarg.h>

#define ADINI "d:\\inetsrv\\scripts\\filters\\Ad.INI"

#define ADLINKS "AdLinks"

#define ADGRAPHICS "AdGraphics"

#define ADHEIGHTS "AdHeights"

#define ADWIDTHS "AdWidths"

#define ADTARGETS "AdTargets"

/*

Private prototypes

*/

DWORD OnUrlMap(HTTP_FILTER_CONTEXT *pfc,

HTTP_FILTER_URL_MAP *pvData);

DWORD OnSendRawData(HTTP_FILTER_CONTEXT *pfc,

HTTP_FILTER_RAW_DATA *pvData);

//void InsertAd(char* pszBuffer, char* pszAdBuffer, int b);

void InsertAd(char* pszAdBuffer);

/*

Globals

*/

CRITICAL_SECTION gCS; // A critical section handle

// is used to protect global

// state properties

char gszAdLinks[2048];

char* gpszAdLink;

char* gpszNewBuffer;

char* gpszAdBuffer;

unsigned int giCurrentAd;

unsigned int giLastAd;

unsigned int giExpandedLen;

/*

This the the entry and exit point for the filter

it is called when the filter is loaded and unloaded

by IIS. This is where state properties need to be

retrieved and store on persistant storage.

*/

BOOL APIENTRY DllMain( HANDLE hModule,

DWORD ul_reason_for_call,

LPVOID lpReserved )

{

unsigned int i = 0;

switch( ul_reason_for_call ) {

case DLL_PROCESS_ATTACH:

{

/*

On process attach we will set the state

variables for the filter. This entails

counting the number of advertisers we're

flipping ads for.

*/

InitializeCriticalSection(&gCS);

EnterCriticalSection(&gCS);

giCurrentAd=1;

giLastAd=1;

GetPrivateProfileString(ADLINKS, NULL, TEXT(" "),

gszAdLinks, sizeof(gszAdLinks),

ADINI);

gpszAdLink = (char*)gszAdLinks;

while (gszAdLinks[i])

{

giLastAd++;

i+=strlen(gpszAdLink)+1;

gpszAdLink+=strlen(gpszAdLink)+1;

}

gpszAdLink = (char*)gszAdLinks;

LeaveCriticalSection(&gCS);

break;

}

// case DLL_THREAD_ATTACH:

// case DLL_THREAD_DETACH:

case DLL_PROCESS_DETACH:

{

DeleteCriticalSection(&gCS);

break;

}

}

return TRUE;

}

/*

GetFilterVersion - An ISAPI/Win32 API method

This method is required by IIS. It is called

following the process load to ensure that the

filter is compatable with the server.

*/

BOOL WINAPI GetFilterVersion(HTTP_FILTER_VERSION * pVer)

{

pVer->dwFilterVersion = MAKELONG( 0, 1 ); // Version 1.0

/*

Specify the security level of notifications

(secured port, nonsecured port, or both), the

types of events and order of notification for

this filter (high, medium or low, default=low).

*/

pVer->dwFlags = (

SF_NOTIFY_SECURE_PORT |

SF_NOTIFY_NONSECURE_PORT |

SF_NOTIFY_URL_MAP |

SF_NOTIFY_SEND_RAW_DATA |

SF_NOTIFY_ORDER_DEFAULT

);

/*

A brief one line description of the filter

*/

strcpy( pVer->lpszFilterDesc, TEXT("Ad Flipper Filter, v1.1"));

return TRUE;

}

/*

HttpFilterProc - ISAPI / Win32 API method

This method is a required by IIS. It is called

for each notification event requested. This is

where the filter accomplishes its purpose in life.

*/

DWORD WINAPI HttpFilterProc(HTTP_FILTER_CONTEXT *pfc,

DWORD NotificationType,

VOID * pvData)

{

DWORD dwRet;

/*

Direct the notification to the appropriate

routine for processing.

*/

switch ( NotificationType )

{

case SF_NOTIFY_URL_MAP:

dwRet = OnUrlMap(pfc, (PHTTP_FILTER_URL_MAP) pvData );

break;

case SF_NOTIFY_SEND_RAW_DATA:

dwRet = OnSendRawData(pfc, (PHTTP_FILTER_RAW_DATA) pvData );

break;

default:

dwRet = SF_STATUS_REQ_NEXT_NOTIFICATION;

break;

}

return dwRet;

}

/*

IIS Filter Event Routines

*/

/*

OnUrlMap -

The data returned within pvData includes the URL

requested (pvData->pszURL) and the full path to

the physical data (pvData->pszPhysicalPath.

*/

DWORD OnUrlMap(HTTP_FILTER_CONTEXT *pfc,

HTTP_FILTER_URL_MAP *pvData)

{

/*

In this routine we will check to see if the page begin

requested ends with "ads.htm". This will signify an

advertisement page. Then we will create the text link

and image line that will create the ad...

*/

if (strstr(pvData->pszURL, TEXT("ads.htm"))) {

gpszAdBuffer=(CHAR*)pfc->AllocMem(pfc, 400, (DWORD)NULL);

memset(gpszAdBuffer,'\0',sizeof(gpszAdBuffer));

InsertAd(gpszAdBuffer);

pfc->pFilterContext = (void*)gpszAdBuffer;

}

return SF_STATUS_REQ_NEXT_NOTIFICATION;

}

/*

OnSendRawData -

This routine is called twice for this event.

The first time it is called is when it sends

the browser a notification of the actual data

it is about to transmit(e.g. text/html, image/gif,

etc.) The second time this routine is called

is when the actual data (e.g. text, gif, etc.)

is being transmitted to the browser.

*/

DWORD OnSendRawData(HTTP_FILTER_CONTEXT *pfc,

HTTP_FILTER_RAW_DATA *pvData)

{

CHAR* pszBuffer;

DWORD cbNewInData=0;

BOOL bAdPage=FALSE;

DWORD i=0;

DWORD j=0;

char* pcBefore;

char* pcBetween;

char* pcAfter;

char cCurrentLen[5];

int c=0;

HTTP_FILTER_RAW_DATA* pRawData = (PHTTP_FILTER_RAW_DATA) pvData;

pszBuffer=pvData->pvInData;

if (pfc->pFilterContext) // Is this an ad?

{

/* This first call to OnSendRawData are the response

headers. Since we are expanding the page size, we

need to alter the "Content-Length" header.

*/

if (strstr((char*) pRawData->pvInData,"HTTP/1.0"))

{

giExpandedLen=strlen((char*) pfc->pFilterContext);

memset(cCurrentLen, '\0', sizeof(cCurrentLen));

pcBefore=(char*)pRawData->pvInData;

pcBetween=strstr(pcBefore,"Content-Length:");

if (pcBetween)

{

pcAfter=pcBetween;

while (*pcAfter>=' ')

{

if (*pcAfter>='\x30' && *pcAfter <='\x39')

cCurrentLen[c++]=*pcAfter;

pcAfter++;

}

c = atoi((char*)cCurrentLen);

giExpandedLen+=c;

*pcBetween='\0';

sprintf((char*) pRawData->pvInData,

"%sContent-Length: %i%s",

pcBefore,giExpandedLen,pcAfter);

pRawData->cbInData=(DWORD) strlen((char*) pRawData->pvInData);

}

}

else

{

/* This is the second call to OnSendRawData, this call

contains the actual html page. This section checks

for the special tag we're using. "<!ADIN>" as a place

holder for the link and image line.

*/

if (strstr((char*)pvData->pvInData,"<!%ADIN%>"))

{

gpszNewBuffer=(CHAR*)pfc->AllocMem(pfc, giExpandedLen+1, (DWORD)NULL);

memset(gpszNewBuffer,'\0',giExpandedLen+1);

pcBefore=(char*)pRawData->pvInData;

pcBetween=strstr(pcBefore,"<!%ADIN%>");

if (pcBetween)

{

pcAfter=pcBetween;

while (*pcAfter!='>') { pcAfter++; }

pcAfter++;

*pcBetween='\0';

sprintf(gpszNewBuffer,"%s%s%s",pcBefore,gpszAdBuffer,pcAfter);

pRawData->pvInData=(void*)gpszNewBuffer;

pRawData->cbInData=giExpandedLen;

}

}

}

}

return SF_STATUS_REQ_NEXT_NOTIFICATION;

}

/*

The following #define define all the possible string

combinations that may inserted into the html page

*/

#define LINK_STRING "<A HREF=\"http:/scripts/ClickThruExtension.dll?http://%s\"><IMG SRC=\"/images/%s\" ALT=\"%s\" BORDER=0></A>"

#define LINK_STRING2 "<A HREF=\"http:/scripts/ClickThruExtension.dll?http://%s\"><IMG SRC=\"/images/%s\" ALT=\"%s\" BORDER=0 height=\"%s\" width=\"%s\"></A>"

#define LINK_STRING3 "<A HREF=\"http:/scripts/ClickThruExtension.dll?http://%s\" target=\"%s\"><IMG SRC=\"/images/%s\" ALT=\"%s\" BORDER=0 height=\"%s\" width=\"%s\"></A>"

/*

InsertAd -

The routine gets the graphic and alternate text from

the database and creates the graphic link HTML line

and inserts it into the HTML page.

*/

void InsertAd(char* pszAdBuffer){

char szGraphic[100];

char szAltText[100];

char szHeight[7];

char szWidth[7];

char szTarget[20];

unsigned int i=0;

GetPrivateProfileString(ADGRAPHICS, gpszAdLink,

TEXT("clearmind.gif"), szGraphic,

sizeof(szGraphic), ADINI);

GetPrivateProfileString(ADLINKS, gpszAdLink,

TEXT("ClearMind Inc."), szAltText,

sizeof(szAltText), ADINI);

GetPrivateProfileString(ADHEIGHTS, gpszAdLink,

TEXT("\0"), szHeight,

sizeof(szHeight), ADINI);

GetPrivateProfileString(ADWIDTHS, gpszAdLink,

TEXT("\0"), szWidth,

sizeof(szWidth), ADINI);

GetPrivateProfileString(ADTARGETS, gpszAdLink,

TEXT("\0"), szTarget,

sizeof(szTarget), ADINI);

if (szHeight[0] == '\0' || szWidth[0] == '\0') {

wsprintf(pszAdBuffer, LINK_STRING,

gpszAdLink, szGraphic, szAltText);

} else {

if (szTarget[0] =='\0') {

wsprintf(pszAdBuffer, LINK_STRING2,

gpszAdLink, szGraphic, szAltText, szHeight, szWidth);

} else {

wsprintf(pszAdBuffer, LINK_STRING3,

gpszAdLink, szTarget, szGraphic, szAltText, szHeight, szWidth);

}

}

giCurrentAd++;

if (giLastAd==giCurrentAd){

giCurrentAd = 1;

gpszAdLink=(char*)gszAdLinks;

}

else {

gpszAdLink+=strlen(gpszAdLink)+1;

}

return;

}

To Build the AdFlipperFilter.dll

Use these instructions to built the AdFlipperFilter.dll:

  1. Copy the project from the companion CD to an accessible drive.
  2. Open a command window and navigate to the directory where the AdFlipperFilter project is stored.
  3. Make sure nmake.exe is in a directory in the system's path.
  4. From the AdFlipperFilter project's directory execute the following:

    nmake /f makefile CFG="AdFlipperFilter - Win32 Release

To Install the AdFlipperFilter.dll

Use these instructions to install the AdFlipperFilter.dll:

  1. Copy AdFlipperFilter.dll into your IIS install directory on the server.
  2. Run REGEDT32.EXE.
  3. Go to the following registry key:

    HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services\W3SCVC\Parameters

  4. Edit the filter DLL's entry by double-clicking it.
  5. Add ,c:\path\to\AdFlipperFilter.dll to the end of the value.
  6. Save the value.
  7. Exit REGEDT32.EXE.
  8. Stop and restart the Web service.

From Here...

ISAPI filters enable you to interact with the request event data and Web server data at a low level. The following chapters tell you more about how to construct filters, and how filters interact with the Web service and the HTTP communication flow.


© 1996, QUE Corporation, an imprint of Macmillan Publishing USA, a Simon and Schuster Company.