A Sample ISAPI Filter

What can we do with the power of ISAPI filters? One use for an ISAPI filter is to modify content on its way to the client. While the same effect can be achieved with brute force editing, sometimes an ISAPI filter can be a cleaner solution. Suppose you have hundreds of pages, each with its own concept of what a Web page should look like. The HTML <BODY> tag alone has at least 20 attributes that can be set. If this was a growing ad hoc collection of HTML files rather than a static set of HTML files, one way to create consistency among the files would be to have an ISAPI filter modify the styles that go to the client.

The following example will take the HTML code to be sent to the client in Listing 11-1 and change it to the code in Listing 11-2. Notice that the original code has a plain <BODY> tag, whereas the modified code specifies a background color and a text color .

Listing 11-1

 <html> <head> <title>ISAPI Filter Test</title> <meta NAME="FORMATTER" CONTENT="Microsoft Visual InterDev 1.0"> </head> <body> <CENTER> <H1> This is my test. This page originally had no options in the BODY  tag, but it has been modified by the ISAPI filter FilterRaw to  have blue background and yellow text. </H1> </body> </html> 

Listing 11-2

 <html> <head> <title>ISAPI Filter Test</title> <meta NAME="FORMATTER" CONTENT="Microsoft Visual InterDev 1.0"> </head> <BODY BGCOLOR=BLUE TEXT=YELLOW> <CENTER> <H1> This is my test. This page originally had no options in the BODY  tag, but it has been modified by the ISAPI filter FilterRaw to  have blue background and yellow text. </H1> </body> </html> 

Making this change is not quite as straightforward as it might appear. We obviously need to request the SF_NOTIFY_SEND_RAW_DATA notification to get to the raw data before it is sent to the client. What is less obvious is that we need to also request the SF_NOTIFY_URL_MAP to ensure that we know when the first block is received.

When the SF_NOTIFY_URL_MAP notification is received, we simply set the pFilterContext member of the filter notification to 1. This is so that the first request to send data to the client is recognized and treated specially. We need to ensure that we do not modify this portion of the output to the client. When the first SF_NOTIFY_SEND_RAW_DATA notification arrives (signaled by the value in pFilterContext ) we make sure the headers are skipped . We then set pFilterContext to 2 so that we know future notifications do not require any special handling.

When the SF_NOTIFY_SEND_RAW_DATA notification arrives, the pvNotification parameter to HttpFilterProc points 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; 

The actual raw data is pointed to by pvInData . The next two parameters, cbInData and cbInBuffer , hold the amount of useful data in pvInData and the size of the buffer allocated, respectively. The last member is reserved and should be ignored. While in some cases you can modify pvInData in place, this is not always possible. If you attempt to modify the data in place, you should use exception handling. If an exception occurs, allocate space and modify the allocated buffer. The new buffer must remain valid until the end of the request. This is best handled by using AllocMem , one of the callback functions in the filter context structure. This is the approach I take in the example in the FilterRaw program, shown in Listing 11-3, and it is especially prudent in light of the fact that we will likely change the length of the data.

Listing 11-3

FilterRaw.cpp

 // FilterRaw.cpp : Raw ISAPI filter application. // #include "stdafx.h" BOOL APIENTRY DllMain(HANDLE hModule,                        DWORD  ul_reason_for_call,                        LPVOID lpReserved) {     return TRUE; } BOOL WINAPI GetFilterVersion(PHTTP_FILTER_VERSION pVer) {     pVer->dwFlags = SF_NOTIFY_SECURE_PORT          SF_NOTIFY_NONSECURE_PORT          SF_NOTIFY_URL_MAP          SF_NOTIFY_ORDER_DEFAULT          SF_NOTIFY_SEND_RAW_DATA;     strcpy(pVer->lpszFilterDesc, "Test Filter to turn pages blue");     return TRUE; } DWORD WINAPI HttpFilterProc(PHTTP_FILTER_CONTEXT pfc,       DWORD notificationType,       LPVOID pvNotification) {     DWORD ret;     DWORD loop = 0, dloop = 0, bodylen;     char *ptr;     char *ptrIn;     char *szNewBody = "<BODY BGCOLOR=BLUE TEXT=YELLOW>";     bool changed = false;     PHTTP_FILTER_RAW_DATA filterRaw;     switch (notificationType)     {     case SF_NOTIFY_URL_MAP:         pfc->pFilterContext = (VOID *)1;         ret = SF_STATUS_REQ_NEXT_NOTIFICATION;         break;     case SF_NOTIFY_SEND_RAW_DATA:         filterRaw = (PHTTP_FILTER_RAW_DATA)pvNotification;         ptr = (char *)pfc->AllocMem(pfc,             filterRaw->cbInBuffer+100, 0);         memset(ptr, 0, filterRaw->cbInBuffer+100);         ptrIn = (char *) filterRaw->pvInData;         // First block?           if (pfc->pFilterContext == (VOID *)1)         {             // Skip the header.             while (loop < filterRaw->cbInData)             {                 if (ptrIn[loop] == '\n' &&                     ptrIn[loop] == '\n')                 {                     loop += 3;                     dloop += 3;                     break;                 }                 loop++;                 dloop++;             }             pfc->pFilterContext = (VOID *)2;         }                      ptrIn = (char *)filterRaw->pvInData;         while (loop<filterRaw->cbInData)         {             if (*(ptrIn+loop) == '<' &&                  !(strnicmp((ptrIn+loop+1), "BODY", 4)))             {                 bodylen = 0;                 while (*(ptrIn+loop) != '>')                 {                     loop++;                     bodylen++;                 }                 loop++;                 strcat(ptr, szNewBody);                 dloop += strlen(szNewBody);                 changed = true;             }                 else             {                 *(ptr+dloop) = *(ptrIn+loop);                 loop++;                 dloop++;             }         }         if (changed)         {             filterRaw->pvInData = (void *)ptr;             filterRaw->cbInBuffer += 100;             filterRaw->cbInData = dloop;         }         ret = SF_STATUS_REQ_NEXT_NOTIFICATION;         break;     }     return(ret); } 

The GetFilterVersion function in Listing 11-3 is simple but representative of what is present in most ISAPI filters. It simply sets the requested notifications, sets the priority and secure port status flags in the dwFlags member of the HTTP_FILTER_VERSION structure pointed to by pVer, and copies a name into lpszFilterDesc .

HttpFilterProc behaves in a similarly reasonable way, using a case statement to react to the two notifications it will receive and setting the return value to SF_STATUS_REQ_NEXT_NOTIFICATION. HttpFilterProc has several other possible return values. Table 11-3 summarizes the constants and their meanings.

The FilterRaw example in Listing 11-3 is less than complete. For example, in the real world it is ill-advised to simply chop out one <BODY> tag and replace it with another. A more intelligent analysis should be used so that other attributes of the tag command can be properly modified and the end result can be more reasonable. A more real-worldthough much more complexexample would be a filter that would create a frameset to place a navigation bar on one side and the original HTML on the other.

Table 11-3 Return Codes from HttpFilterProc

HttpFilterProc Return Value Description
SF_STATUS_REQ_FINISHED The filter has finished this HTTP request and the session should be disconnected.
SF_STATUS_REQ_FINISHED_KEEP_CONN This value is the same as SF_STATUS_REQ_FINISHED, but the TCP/IP session should be kept open if keep- alive is enabled.
SF_STATUS_REQ_NEXT_NOTIFICATION The next filter in the chain should be called. This is the most common return value.
SF_STATUS_REQ_HANDLED_NOTIFICATION The filter has handled the request. No other filters for this notification should be called.
SF_STATUS_REQ_ERROR An error has occurred. Generally the request should be aborted. The filter should call SetLastError with an appropriate error code.
SF_STATUS_REQ_READ_NEXT This is valid only for raw read notifications and is designed for encryption and other operations that could be described as opaque stream filters. See MSDN for details.

Once you've created the filter, it is time to install it. Figure 11-2 shows the Default Web Site Properties dialog in Windows 2000 with the ISAPI Filters tab selected. To add a new filter, click on Add. The Filter Properties dialog appears, as shown in Figure 11-3. Simply enter the filter name and the filter filename and click OK. Click on Apply in the Default Web Site Properties dialog. The filter is now in place. The status column shown in Figure 11-2 will contain either a green up-arrow or a red down-arrow, giving a virtual thumbs-up or thumbs-down to your filter.

click to view at full size.

Figure 11-2 The Default Web Site Properties dialog in Windows 2000.

Figure 11-3 Adding an ISAPI filter.



Inside Server-Based Applications
Inside Server-Based Applications (DV-MPS General)
ISBN: 1572318171
EAN: 2147483647
Year: 1999
Pages: 91

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net