Visual C++ 6 Unleashed

Visual C++ 6 Unleashed

By MICKEY WILLIAMS and David Bennett

ISAPI Filters

In addition to the ISAPI server extensions you already have seen, ISAPI also enables you to create extension DLLs that act as filters, processing various HTTP events either before or after the server has processed a request. You can use these filters to provide your own custom authentication, encryption, compression, or logging functions, as well as many other filtering operations.

Installing a Filter

For the Microsoft Internet Information Server (IIS), the filters that are used are specified in the Registry under the following value:

HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services\W3Svcx\Parameters\Filter DLLs

This value is a comma-separated list of the filters that will currently be used by the server. The filters are processed in order of their priority, as you will see. In the event of a tie in priority, the filters will be processed in the order in which they are listed in the Registry.

ISAPI Filter Architecture

Like the extension DLLs you saw earlier, ISAPI filters are DLLs that are loaded into the Web server's process. Filter DLLs communicate with the server by way of a pair of entry points that are exported by the filter DLL.

The first of these entry points is GetFilterVersion(), which is called when your filter DLL is loaded, allowing your filter to report its supported version and to register for the events that it wants to handle.

When an event that your filter has registered for occurs, the second entry point, HttpFilterProc(), will be called. This function should perform its processing on the event before passing control back to the server. At this point, your filter may decide whether the event also should be handled by other filters in the current filter chain.

GetFilterVersion()

When the Web server loads a filter DLL, it will call the GetFilterVersion() function that is exported by your DLL. This function should have the following prototype:

BOOL WINAPI GetFilterVersion( PHTTP_FILTER_VERSION pVer);

The single parameter is a pointer to an HTTP_FILTER_VERSION structure, which looks like this:

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

When GetFilterVersion() is called, the server will pass its version in the dwServerFilterVersion field. You should pass the version that your filter is using back to the server in the dwFilterVersion field. For the current version of ISAPI, you can use the HTTP_FILTER_REVISION constant. You also can write a short ASCII string description of your filter in the buffer passed at lpszFilterDesc.

The real meat of this function is what you return in the dwFlags parameter. This value includes a combination (bitwise-OR) of the following flags, which specify which events your filter is interested in processing:

The following flags also should be included to specify whether you are interested in events on only secure connections, nonsecure connections, or both:

You use the last set of flags that can be included in the dwFlags field to set the priority of your filter in relation to the other filters present on the system:

You also may include any additional initialization code for your filter in the GetFilterVersion() function. (You also could implement a DllMain().) If all goes well, and your filter has initialized properly, you should return TRUE from GetFilterVersion(). If you return FALSE, the filter will not be loaded.

TerminateFilter()

Before unloading your filter, it is recommended that you call the TerminateFilter() function. This function should have the following prototype:

BOOL WINAPI TerminateFilter( DWORD dwFlags);

This filter enables you to free up any allocated or locked resources before you unload your filter. This filter is considered optional but is strongly recommended for the sake of releasing resources. Before calling this function, you should make sure that all attachments to system resources have been closed.

HttpFilterProc()

After your filter is loaded and has registered for the notifications that it wants to receive, the server will make a call to your HttpFilterProc() whenever one of the requested events occurs.

In this section, you will take a quick look at the basics of implementing HttpFilterProc(). In the following sections, you will take a closer look at more of the specifics of certain operations.

The prototype for your HttpFilterProc() should look something like this:

DWORD WINAPI HttpFilterProc(PHTTP_FILTER_CONTEXT pfc,
    DWORD notificationType, VOID *pvNotification);

Your filter is passed an HTTP_FILTER_CONTEXT structure via pfc. This structure provides information about the request, as well as pointers to several utility functions, as you will see in just a bit. The type of event that generated the notification is passed in notificationType. This may include any of the SF_NOTIFY_... values that were used in the GetFilterVersion() call. The value of notificationType also determines how the pvNotification pointer is used. You will look at the specifics of handling each notification type in the following sections.

Your implementation of HttpFilterProc() should perform a switch on the value passed in notificationType, doing whatever processing is necessary, and then should return one of the following values:

The following example shows the complete implementation of a simple filter, which just adds a bit of graffiti to the Web server's log file:

#include <windows.h>
#include <stdio.h>
#include <httpfilt.h>
BOOL WINAPI GetFilterVersion( PHTTP_FILTER_VERSION pVer)
{
    pVer->dwFilterVersion = HTTP_FILTER_REVISION;
    strcpy(pVer->lpszFilterDesc, "My Sample Extension");
    pVer->dwFlags =
        SF_NOTIFY_SECURE_PORT |     // Notify for both port types
        SF_NOTIFY_NONSECURE_PORT |
        SF_NOTIFY_LOG |             // Notify when writing log
        SF_NOTIFY_ORDER_LOW;        // Filter at low priority
    return TRUE;
}
DWORD WINAPI HttpFilterProc( PHTTP_FILTER_CONTEXT pfc,
                            DWORD notificationType,
                            VOID *pvNotification)
{
    PHTTP_FILTER_LOG pLog;
    char *pBuf;

    switch(notificationType)
    {
        case SF_NOTIFY_LOG:
            // This is the only case we are interested in
            // We will modify the server name to show we were here
            pLog = (PHTTP_FILTER_LOG) pvNotification;
            // Allocate new memory for the new string
            // The server will deallocate this when the request ends
            pBuf = (char *) pfc->AllocMem(pfc, 100, 0);
            // Write to our new string
            sprintf(pBuf, "Server: [%s] Logged with MyFilt",
                           pLog->pszServerName);

            // Replace the server name pointer
            pLog->pszServerName = pBuf;

            break;
        default:
            // We should not receive any other notifications
            // Since we only registered for SF_NOTIFY_LOG

            break;
    }
    // Tell the server to call the next filter
    return(SF_STATUS_REQ_NEXT_NOTIFICATION);
} // end HttpExtensionProc

Although this filter may not be horribly practical, it does show the basic structure of a filter, including the processing for the SF_NOTIFY_LOG notification and the use of the AllocMem() function, which you will learn about later.

The HTTP_FILTER_CONTEXT Structure

Much of the interaction between your filter and the Web server is done through the HTTP_FILTER_CONTEXT structure that is passed in the call to HttpFilterProc():

typedef struct _HTTP_FILTER_CONTEXT
{
    DWORD    cbSize;
    DWORD    Revision;
    PVOID    ServerContext;
    DWORD    ulReserved;
    BOOL     fIsSecurePort;
    PVOID    pFilterContext;
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_CONTEXT;

The cbSize field gives the size of this structure, and the Revision field gives the version of ISAPI being used. The ServerContext and ulReserved fields are reserved for use by the Web server—keep yer grubbies off. If the notification is for a secure connection, fIsSecurePort will be TRUE; otherwise, it will be FALSE.

If your filter wants to store any context information for this request, you can assign a context value (usually a pointer to a structure) to the pFilterContext field. If you store a value here, it also will be given to subsequent notifications that your filter receives in processing this request.

If you do allocate memory to store data for a request, you should free the memory when the SF_NOTIFY_END_OF_NET_SESSION notification is received (or whenever you know that you are finished with the data). You also should look at the AllocMem() function later in this section.

The remainder of the HTTP_FILTER_CONTEXT structure includes pointers to various utility functions the server provides to your filter.

GetServerVariable()

The GetServerVariable() function pointer passed to your filter DLL is not quite the same as the GetServerVariable() function that you saw for ISAPI extensions. This ver sion takes a pointer to the HTTP_FILTER_CONTEXT structure, which is passed to your HttpFilterProc() function instead of an HCONN. However, the variables that are available via this function and the way it uses the other parameters are the same as the GetServerVariable() function you saw earlier in this chapter.

AddResponseHeaders()

The AddResponseHeaders() function pointer passed to HttpFilterProc() can be used to attach additional HTTP headers to the response sent to the client. Here is the prototype for AddResponseHeaders():

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

This function takes a pointer to the filter context (pfc), which is passed to your HttpFilterProc() function, and a pointer to a null-terminated string containing the additional HTTP headers. dwReserved is reserved for future expansion.

WriteClient()

Like the WriteClient() callback that you saw for ISAPI applications, this function enables you to send data directly to the client. The following is the prototype for WriteClient():

BOOL (WINAPI * WriteClient) (PHTTP_FILTER_CONTEXT pfc,
    LPVOID buffer, LPDWORD lpdwBytes, DWORD dwReserved);

When calling WriteClient(), you should pass the pointer to the filter context (as passed to your HttpFilterProc() function) in pfc, a pointer to the data to send in buffer, and a pointer to the length of the data in lpdwBytes. The dwReserved parameter currently is not used.

AllocMem()

If your filter needs to allocate memory when working with a request, you may find the AllocMem() function handy. This will allocate a block of memory that automatically is deallocated when the server is done with a request and tears down the connection. The prototype for AllocMem() follows:

VOID * (WINAPI * AllocMem) (PHTTP_FILTER_CONTEXT pfc,
    DWORD cbSize, DWORD dwReserved);

Once again, pfc takes the pointer to the filter context, as passed to HttpFilterProc(), and dwReserved currently is not used. Upon successful completion, AllocMem() will return a pointer to a new block of memory of the size specified in cbSize.

ServerSupportFunction()

Like the ServerSupportFunction() that you saw for ISAPI applications, this function provides a variety of different utility functions to your filter. The following is the prototype for ServerSupportFunction():

BOOL (WINAPI * ServerSupportFunction) (struct _HTTP_FILTER_CONTEXT *pfc,
    enum SF_REQ_TYPE sfReq, PVOID pData, DWORD ul1, DWORD ul2);

The operation performed is determined by the value of sfReq, which may have one of the following values:

SF_REQ_NORMALIZE_URL

This function is used to normalize the URL. Normalization involves changes such as decoding hex codes, internationalization coversions, and removing illegal characters.

SF_REQ_DISABLE_NOTIFICATIONS

This option is used to disable specific notification types for the ISAPI filter. It remains disabled for the remaining lifetime of the request.

SF_REQ_GET_PROPERTY

This option is used to retrieve IIS properties defined in SF_PROPERTY_IIS.

SF_REQ_SEND_RESPONSE_HEADER

This option sends a complete HTTP response header to the client. You may choose to specify a status string—for example, 401 Access Denied—in a string pointed to by pData. You also may specify additional headers, such as the content type, to append in a string pointed to by ul1. This string should include a terminating carriage return/linefeed (\r\n).

SF_REQ_ADD_HEADERS_ON_DENIAL

This option enables you to specify additional headers that will be sent to the client in the event that the server denies the HTTP request. The additional headers are passed in a string pointed to by pData. This string should include a terminating cr/lf (\r\n).

SF_REQ_SET_NEXT_READ_SIZE

For raw data filters that return SF_STATUS_READ_NEXT from HttpFilterProc(), you can use this option to specify the number of bytes to read in the next read. The number of bytes to read is passed in ul1.

SF_REQ_SET_PROXY_INFO

You can use this option to specify that a request is a proxy request. You should set ul1 to 1.

SF_REQ_GET_CONNID

This option returns the connection ID that is passed to ISAPI applications for this request. You can use this value to coordinate operations between your ISAPI applications and filters. pData should point to a DWORD that will receive the connection ID. This option is not supported in ISAPI 4.0 or higher.

Handling Filter Notifications

In this section, you will look at the different notifications that can be received by an ISAPI filter and the data that is passed with each type of notification.

SF_NOTIFY_READ_RAW_DATA

This notification is sent to your filter whenever the server is receiving raw data from the client. The pvNotification parameter of HttpFilterProc() will point to an HTTP_ FILTER_RAW_DATA structure:

typedef struct _HTTP_FILTER_RAW_DATA
{
    PVOID         pvInData;
    DWORD         cbInData;
    DWORD         cbInBuffer;
    DWORD         dwReserved;
} HTTP_FILTER_RAW_DATA, *PHTTP_FILTER_RAW_DATA;

The pvInData field points to a buffer containing the raw data, and the length of the data is passed in the cbInData field. This will include both HTTP headers and additional data. The total size of this buffer is passed in cbInBuffer.

SF_NOTIFY_SEND_RAW_DATA

This notification is sent to your filter when the server is sending data back to the client. The pvNotification parameter of HttpFilterProc() will point to an HTTP_FILTER_RAW_DATA structure, as you saw previously, which refers to the outgoing data.

SF_NOTIFY_PREPROC_HEADERS

This notification is sent to your filter when a request is received from a client. The pvNotification parameter of HttpFilterProc() will point to an HTTP_FILTER_ PREPROC_HEADERS structure, which includes pointers to utility functions that can be used to manipulate the headers for a request. These utility functions are discussed in the following sections.

GetHeader()

The first of these functions is GetHeader():

BOOL (WINAPI * GetHeader) (PHTTP_FILTER_CONTEXT pfc,
    LPSTR lpszName, LPVOID lpvBuffer, LPDWORD lpdwSize);

This function enables you to retrieve the header with the name you specify in a string at lpszName. This name should include the trailing colon—for example, Content-Type:. You also may specify the special values of method, url, or version to retrieve portions of the HTTP request line.

If a header is found for the name passed in lpszName, it will be returned in a buffer at lpvBuffer.

SetHeader()

The second of the utility functions is SetHeader(), which you can use to change the value of a header or even delete an existing header:

BOOL (WINAPI * SetHeader) (PHTTP_FILTER_CONTEXT pfc,
    LPSTR lpszName, LPSTR lpszValue);

This function enables you to specify a new value for the header named in the string at lpszName. The new value for the header is passed in a string at lpszValue. If this string is empty, the specified header will be deleted.

AddHeader()

You can use the third utility function, AddHeader(), to add additional headers:

BOOL (WINAPI * SetHeader) (PHTTP_FILTER_CONTEXT pfc,
    LPSTR lpszName, LPSTR lpszValue);

The name of the new header is specified in a string at lpszName, and the value of the new header is passed in a string at lpszValue.

SF_NOTIFY_AUTHENTICATION

This notification is sent to your filter when the server is about to authenticate a user making a request. The pvNotification parameter of HttpFilterProc() will point to an HTTP_FILTER_AUTHENT structure:

typedef struct _HTTP_FILTER_AUTHENT
{
    CHAR * pszUser;
    DWORD  cbUserBuff;
    CHAR * pszPassword;
    DWORD  cbPasswordBuff;
} HTTP_FILTER_AUTHENT, *PHTTP_FILTER_AUTHENT;

This structure contains pointers to the username (pszUser) and password (pszPassword) for the user being authenticated. If the user is anonymous, these strings will be empty. The cbUserBuff and cbPasswordBuff fields give the total size of the buffers holding the username and password. Your filter can change the contents of these buffers, provided they are not overflowed. These buffers will be at least SF_MAX_USERNAME and SF_MAX_PASSWORD bytes long, respectively.

SF_NOTIFY_URL_MAP

This notification is sent when the server is attempting to map a URL to a physical path. The lpvNotification parameter of HttpFilterProc() will point to an HTTP_FILTER_URL_MAP structure:

typedef struct _HTTP_FILTER_URL_MAP
{
    const CHAR * pszURL;
    CHAR *       pszPhysicalPath;
    DWORD        cbPathBuff;
} HTTP_FILTER_URL_MAP, *PHTTP_FILTER_URL_MAP;

The requested URL is passed in the string at pszURL, and the physical path that it is being mapped to is given at pszPhysicalPath. Your filter may change the string at pszPhysicalPath, provided you do not go over the size of the buffer given in cbPathBuf.

SF_NOTIFY_LOG

This notification is passed to your filter when the server is about to write an entry to its log file. The lpvNotification parameter of HttpFilterProc() is passed a pointer to an HTTP_FILTER_LOG structure:

typedef struct _HTTP_FILTER_LOG
{
    const CHAR * pszClientHostName;
    const CHAR * pszClientUserName;
    const CHAR * pszServerName;
    const CHAR * pszOperation;
    const CHAR * pszTarget;
    const CHAR * pszParameters;
    DWORD  dwHttpStatus;
    DWORD  dwWin32Status;
} HTTP_FILTER_LOG, *PHTTP_FILTER_LOG;

The pointers in this structure refer to the client's hostname, username, and server name, as well as the operation, target, and parameters for the HTTP request. In addition, dwHttpStatus holds the HTTP status of the request, and dwWin32Status holds any Win32 error code.

You cannot modify the contents of the strings passed by the HTTP_FILTER_LOG structure, although you can replace the values of the pointers. If you change the pointer values, you should assign them to new buffers that are allocated with the AllocMem() function provided by the HTTP_FILTER_CONTEXT structure so that the memory can be released properly when the session is closed.

SF_NOTIFY_END_OF_NET_SESSION

This notification is sent to your filter when the server is disconnecting a session. No specific data are associated with this notification, although it is a good place to clean up any information you have stored about a request in progress.

SF_NOTIFY_ACCESS_DENIED

When a request has been denied by the server, your filter will receive this notification. The lpvNotification parameter of HttpFilterProc() will be passed a pointer to an HTTP_FILTER_ACCESS_DENIED structure:

typedef struct _HTTP_FILTER_ACCESS_DENIED
{
    const CHAR * pszURL;
    const CHAR * pszPhysicalPath;
    DWORD        dwReason;
} HTTP_FILTER_ACCESS_DENIED, *PHTTP_FILTER_ACCESS_DENIED;

This structure includes the requested URL at pszURL and the physical path it was mapped to at pszPhysicalPath. The dwReason field contains a bitmap that specifies why the request was denied. This can include the following values:

Share ThisShare This

Informit Network