ISAPI Proxy Filters for IIS
By Mike Mongale, October 01, 2002
Some ISAPI filters conflict with FrontPage Server Extensions or other filters that process HTTP requests. Mike shows a workaround that uses a custom ISAPI DLL as a "proxy" DLL for a vendor's ISAPI filter to make these mixed environments work.
October 2002/ISAPI Proxy Filters for IIS
Listing 1: Source for JRunProxy.cpp
// JRunProxy.cpp
#include "stdafx.h"
// stdafx.h defines WIN32_LEAN_AND_MEAN and includes
// windows.h, httpfilt.h, and malloc.h.
// Typedefs for the filter's exported functions:
typedef BOOL (WINAPI *JRunGetFilterVersion)(PHTTP_FILTER_VERSION);
typedef DWORD (WINAPI *JRunHttpFilterProc)(PHTTP_FILTER_CONTEXT, DWORD, LPVOID);
typedef BOOL (WINAPI *JRunTerminateFilter)(DWORD);
// Addresses of procedures in the JRun DLL:
JRunGetFilterVersion GetFilterVersionProcAddress = NULL;
JRunHttpFilterProc HttpFilterProcProcAddress = NULL;
JRunTerminateFilter TerminateFilterProcAddress = NULL;
// A handle to the JRun module:
HMODULE hModule = NULL;
// Called by IIS to obtain information about our filter:
BOOL WINAPI GetFilterVersion(PHTTP_FILTER_VERSION pfv)
{
// Load the JRun DLL:
hModule = LoadLibrary("C:\\INetPub\\scripts\\jrun.dll");
if(hModule == NULL) // failed to load the JRun DLL.
return FALSE; // can't continue.
// Get the address of GetFilterVersion:
GetFilterVersionProcAddress =
(JRunGetFilterVersion)GetProcAddress(hModule, "GetFilterVersion");
if(GetFilterVersionProcAddress == NULL) // couldn't find GetFilterVersion.
return FALSE; // can't continue.
// Get the address of HttpFilterProc:
HttpFilterProcProcAddress=
(JRunHttpFilterProc)GetProcAddress(hModule, "HttpFilterProc");
if(HttpFilterProcProcAddress == NULL) // couldn't find HttpFilterProc.
return FALSE; // can't continue;
// TerminateFilter is an optionally exported function.
// We don't return FALSE if we can't find it.
TerminateFilterProcAddress =
(JRunTerminateFilter)GetProcAddress(hModule, "TerminateFilter");
// Call JRun's GetFilterVersion:
if((GetFilterVersionProcAddress)(pfv))
{
// Fill in our own values in the HTTP_FILTER_VERSION structure:
pfv->dwFilterVersion = MAKELONG(0, 1); // version 1.0.
strcpy(pfv->lpszFilterDesc, "JRunProxy Filter"); // description.
// Change the filter priority to LOW:
pfv->dwFlags &= ~SF_NOTIFY_ORDER_MASK; // remove existing priority.
pfv->dwFlags |= SF_NOTIFY_ORDER_LOW; // set low priority.
return TRUE; // OK.
}
else // JRun's GetFilterVersion failed.
{
return FALSE; // can't continue.
}
}
// Get the value of an HTTP header. Must free returned char*.
char* GetHeader(
PHTTP_FILTER_CONTEXT pFilterContext,
PHTTP_FILTER_PREPROC_HEADERS pHeaders,
char *szHeader)
{
DWORD dwBufferSize = 0;
// This will fail because the receiving buffer is NULL. We
// do this do determine the size of buffer that is needed.
if(!pHeaders->GetHeader(pFilterContext, szHeader, NULL, &dwBufferSize) &&
GetLastError() == ERROR_INSUFFICIENT_BUFFER)
{
// GetHeader failed above as expected, now
// allocate a buffer of the required size:
char *szHeaderValue = (char*)malloc(dwBufferSize);
if(szHeaderValue) // Memory allocated OK.
{
// Now get the value of the HTTP header:
if(pHeaders->GetHeader(pFilterContext,
szHeader, szHeaderValue, &dwBufferSize))
{
// Found the value of the HTTP header OK.
return szHeaderValue;
}
// Couldn't find the value of the requested HTTP
// header, so to free the allocated memory:
free(szHeaderValue);
}
}
// Couldn't find a value for this header or some other error occurred.
return NULL;
}
// Returns TRUE if the notification should be handled by JRun.
BOOL RequestIsForJRun(
PHTTP_FILTER_CONTEXT pFilterContext,
PHTTP_FILTER_PREPROC_HEADERS pHeaders)
{
// Assume request is not for JRun:
BOOL bRequestIsForJRun = FALSE;
// Get the value of the requested URL:
char *szURL = GetHeader(pFilterContext, pHeaders, "URL");
if(szURL) // Got the URL OK.
{
// Assume there is a default page set up in JRun:
if(!strcmp(szURL, "/"))
{
bRequestIsForJRun = TRUE;
}
else // Is the request for a JavaServer Page (JSP) or servlet?
{
// Check for .jsp extension:
char *pch = strrchr(szURL, '.');
if(pch && !stricmp(pch, ".jsp"))
{
bRequestIsForJRun = TRUE; // request is for a JSP.
}
else
{
// make all lower case:
for(pch = szURL; *pch; pch++)
*pch = (char)tolower(*pch);
// check for /servlet/ and /WEB-INF/:
if(strstr(szURL, "/servlet/") || // request is for a servlet.
strstr(szURL, "/web-inf/")) // let JRun reject this.
{
bRequestIsForJRun = TRUE;
}
}
// Free memory allocated by GetHeader:
free(szURL);
}
return bRequestIsForJRun;
}
// Called by IIS whenever a notification event
// for which the filter has registered occurs.
DWORD WINAPI HttpFilterProc(
PHTTP_FILTER_CONTEXT pFilterContext,
DWORD dwNotificationType,
LPVOID pvNotification)
{
if(dwNotificationType == SF_NOTIFY_PREPROC_HEADERS)
{
PHTTP_FILTER_PREPROC_HEADERS pHeaders =
static_cast<PHTTP_FILTER_PREPROC_HEADERS>(pvNotification);
// If the request isn't for JRun...
if(!RequestIsForJRun(pFilterContext, pHeaders))
{
// ... bypass JRun and do what IIS would otherwise do:
return SF_STATUS_REQ_NEXT_NOTIFICATION;
}
}
else if(dwNotificationType == SF_NOTIFY_LOG)
{
PHTTP_FILTER_LOG pLog =
static_cast<PHTTP_FILTER_LOG>(pvNotification);
// If the target of the HTTP command is not JRun's filter DLL...
if(strcmp(pLog->pszTarget, "/scripts/jrun.dll"))
{
// ... bypass JRun and do what IIS would otherwise do:
return SF_STATUS_REQ_NEXT_NOTIFICATION;
}
}
// Call JRun's HttpFilterProc:
return (HttpFilterProcProcAddress)(pFilterContext,
dwNotificationType, pvNotification);
}
// Called by IIS to notify our filter that
// it is going to be removed from memory.
BOOL WINAPI TerminateFilter(DWORD dwFlags)
{
if(hModule)
{
// Call JRun's TerminateFilter function if
// JRun exports this function (which it currently doesn't).
if(TerminateFilterProcAddress != NULL)
(TerminateFilterProcAddress)(dwFlags);
// Unload the JRun DLL:
FreeLibrary(hModule);
}
return TRUE;
}