Figure 1   Filter Notifications and Data Structures

Notification Type (notificationType) Explanations Data Structure (pvNotification)
SF_NOTIFY_AUTHENTICATION The server is authenticating an incoming user. This happens once per session. HTTP_FILTER_AUTHENT
SF_NOTIFY_END_OF_REQUEST The server just finished processing the request. Note that the session to the client may still be open because of the HTTP Keep-Alive connection option. This notification is mainly for cleanup of any request-specific data used by your filter. . Not used
SF_NOTIFY_READ_RAW_DATA The server is reading raw data from the underlying socket. By handling this notification, a filter can access the raw data being sent by the client. This notification may be posted more than once for each request. HTTP_FILTER_RAW_DATA
SF_NOTIFY_PREPROC_HEADERS The server is just about to process the request's headers. Server variables will become available at the next notification for this request. HTTP_FILTER_ PREPROC_HEADERS
SF_NOTIFY_URL_MAP The server is mapping the URL address to its physical path. Your filter can specify a different physical path for this request at this notification HTTP_FILTER_URL_MAP
SF_NOTIFY_SEND_RAW_DATA The server is sending data back to the client on the underlying socket. This may occur more than once for each request. The filter may change outgoing data by handling this notification. HTTP_FILTER_RAW_DATA
SF_NOTIFY_LOG The server is writing an entry to the IIS log LOG The filter may alter this information by handling this notification.. HTTP_FILTER
SF_NOTIFY_END_OF_ NET_SESSION The session between the client and the server was closed. This notification is mainly provided to clean up any session-specific data. Not used
SF_NOTIFY_ACCESS_DENIED The server couldn't access the requested document, because access was denied. A "401 Access Denied" response status is about to be sent to the client.. HTTP_FILTER_ACCESS_DENIED
SF_NOTIFY_SEND_RESPONSE The server is about to send HTTP response headers to the client. The filter may alter them by handling this notification. HTTP_FILTER_SEND_RESPONSE


Figure 2   HttpFilterProc Return Values

Value Description
SF_STATUS_REQ_FINISHED The request is finished. The server needs to close the session with the client.
SF_STATUS_REQ_FINISHED_KEEP_CONN Just like SF_STATUS_REQ_FINISHED, except that if the Keep-Alive option was negotiated the session should stay open.
SF_STATUS_REQ_NEXT_NOTIFICATION If more than one filter is installed, the next filter in the chain should be called. For example, if a filter handles raw reads or writes (to do custom data encoding, for instance), then after changing the raw data the next filter should be called (to check certain custom headers, for instance).
SF_STATUS_REQ_HANDLED_NOTIFICATION The notification was handled by this filter. Other notifications should not be called for the same request.
SF_STATUS_REQ_ERROR An error occurred in the filter. The filter should obtain an error code (usually via GetLastError) and pass it to the client. The server will abort the HTTP session if HttpFilterProc returns this value.
SF_STATUS_REQ_READ_NEXT The filter is an opaque stream filter, reading large amounts of data from the server. This value indicates to the server that the filter did not read all of the data. The server should post another read notification to the filter so the filter can continue reading data.


Figure 3   HttpFilterProc

DWORD WINAPI HttpFilterProc (PHTTP_FILTER_CONTEXT pFC,
                             DWORD dwNotificationType,
                             LPVOID pvNotification)
{
    HTTP_FILTER_URL_MAP *pUrlMap;
    CHAR  szTemp [256], *lpszUserAgent, *lpszServer; 
    DWORD dwSize = 0;

    if (dwNotificationType == SF_NOTIFY_URL_MAP)
    {
        pUrlMap = (HTTP_FILTER_URL_MAP *) pvNotification;
        if (strstr (strlwr ((CHAR *)pUrlMap->pszURL), "ieonly") )
        {
            // This call should fail w/error ERROR_INSUFFICIENT_BUFFER
            // you should check, for other errors.
            pFC->GetServerVariable (pFC,"HTTP_USER_AGENT", 
                                    lpszUserAgent,&dwSize);
            lpszUserAgent = (CHAR *) pFC->AllocMem (pFC,dwSize,0);

            // request is to IEONLY directory, check user agent 
            if (!pFC->GetServerVariable (pFC,"HTTP_USER_AGENT", 
                                         lpszUserAgent,&dwSize))
            {
                pFC->ServerSupportFunction (pFC, 
                                            SF_REQ_SEND_RESPONSE_HEADER,
                                            (PVOID) "200 OK", 0,0);
                wsprintf (szTemp, "GetServerVariable failed: %d", 
                          GetLastError());
                dwSize = lstrlen (szTemp);
                pFC -> WriteClient (pFC, szTemp, &dwSize, 0);
               return SF_STATUS_REQ_FINISHED;
            }
            if (!strstr (strlwr (lpszUserAgent), "msie 4") ) 
            {
               // not an Internet Explorer 4.0 browser, redirect client
               // Get the name of the server. But first determine size
               // of the buffer.
               dwSize = 0;
               pFC->GetServerVariable (pFC,"SERVER_NAME", 
                                       lpszServer,&dwSize);
               lpszServer = (CHAR *) pFC->AllocMem (pFC,dwSize,0);
               if (!pFC->GetServerVariable (pFC,"SERVER_NAME", 
                                            lpszServer,&dwSize))
               {
                   // report error check here
                   return SF_STATUS_REQ_FINISHED;
               }

               wsprintf (szTemp, "Location: http://%s/%s\r\n\r\n", 
                         lpszServer, FILENAME);
               pFC->ServerSupportFunction (pFC,SF_REQ_SEND_RESPONSE_HEADER,
                                           (PVOID) "302 Redirect",
                                           (DWORD) szTemp,0); 
               return SF_STATUS_REQ_FINISHED; 
            }    
        }    
    } // if notification handler        
        return SF_STATUS_REQ_NEXT_NOTIFICATION; 
}       // end HttpFilterProc

Figure 4    Adding ISAPI Extension to SmartRedir

// declare critical section object and user agent substring
// as file scope data
CRITICAL_SECTION g_CritSect;
CHAR szUserAgentId[256];

BOOL WINAPI GetFilterVersion(. . .)
{
    // critical section should be initialized first.
    // It can be done either in GetFilterVersion or in DllMain()
    // when it is called for PROCESS_ATTACH
    InitializeCriticalSection (&g_CritSect);
    // set the default user agent
    strcpy (szUserAgentId, "msie 4");
•
•
•
}
DWORD WINAPI HttpFilterProc(. . .)
{
•
•
•
    // implement code that searches for the user agent substring
    EnterCriticalSection (&g_CritSect);
    if (!strstr (strlwr (lpszUserAgent), szUserAgentId) )
   {
•
•
•
    }
    LeaveCriticalSection (&g_CritSect);
•
•
•
}
DWORD WINAPI HttpExtensionProc(. . .)
{
•
•
•
    // implement code that changes the szUserAgentId based on the 
    // parameters passed to our extension
    EnterCriticalSection (&g_CritSect);
    Change_User_Agent (szUserAgentId);
    LeaveCriticalSection (&g_CritSect);
•
•
•
}
DWORD WINAPI TerminateFilter (. . .)
{
    // critical section must be deleted. It can be done here
    // or in DllMain, when it is called for the PROCESS_DETACH
    DeleteCriticalSection (&g_CritSect);
•
•
•
}

Figure 5    SSIDemo

#include <windows.h>
#include <httpext.h>
#include <stdio.h> 
#include <stdarg.h>

#pragma optimize("", off)

#define REGKEY "System\\CurrentControlSet\\Services\\W3SVC\\SSI"
#define ISWHITE( ch )      ((ch) && ((ch) == ' ' || (ch) == '\t' ||  \
                            (ch) == '\n' || (ch) == '\r'))



CHAR cVarChar = '@', *lpDataDict  = NULL; // Even though it is a global, it can 
				// only be read from multiple threads.	                                          

DWORD GetValueFromTag (CHAR *szVal, CHAR *szTag, EXTENSION_CONTROL_BLOCK *pECB);

#ifdef _DEBUG
    void DebugOut (CHAR *lpszFmt, ...)
    {
        CHAR szBuff[1024];

        va_list vargs;
        va_start (vargs,lpszFmt);
        vsprintf (szBuff, lpszFmt, vargs);
        va_end (vargs);
        
        OutputDebugString (szBuff);
        return;
    }
#else
    void DebugOut (CHAR *lpszFmt, ...)
    {
        return;
    }
#endif

BOOL   WINAPI   DllMain (HANDLE hInst, 
                        ULONG ul_reason_for_call,
                        LPVOID lpReserved)

{
    if (ul_reason_for_call == DLL_PROCESS_DETACH)
    {
            DebugOut ("DLL_PROCESS_DETACH for instance: 0x%X\n", hInst);
            LocalFree (lpDataDict);
    }
    return TRUE;
}

/****************************************************************************
*
*   FUNCTION: GetExtensionVersion
*   
*   PURPOSE:  Standart procedure which needs to be exported from the DLL.
*   
*   PARAMETRS: 
*             pVer - pointer to version information structure.
*
*   RETURN:   BOOL
*
*   COMMENTS: Will be called by the server.
*
****************************************************************************/

BOOL WINAPI GetExtensionVersion (HSE_VERSION_INFO *pVer)
{

    // We will open a dictinary file here and will read its
    // content into the global memory.
    //
    // If for any reason we can't do it, set the global pointer to 0;

    HANDLE hFileDict;
    DWORD dwSizeDict, dwError, dwRead;
    int i = 0;
    HKEY hKey;
    CHAR szDictFile [256], temp[2];

    pVer->dwExtensionVersion = MAKELONG(HSE_VERSION_MINOR, HSE_VERSION_MAJOR);
    lstrcpyn(pVer->lpszExtensionDesc,"HTTP SSI Application",
             HSE_MAX_EXT_DLL_NAME_LEN );


    if (ERROR_SUCCESS == RegOpenKeyEx(HKEY_LOCAL_MACHINE,REGKEY,0,KEY_READ,&hKey))
    {
        // We don't care if we  can't read reg
        dwRead = sizeof (temp);
        if (RegQueryValueEx (hKey, "VariableCharacter", NULL, NULL, (LPBYTE) temp, 
                             &dwRead) == ERROR_SUCCESS)
            cVarChar = temp[0];

        dwRead = sizeof (szDictFile);
        if (RegQueryValueEx (hKey, "DictFile", NULL, NULL, (LPBYTE) szDictFile, 
                             &dwRead) != ERROR_SUCCESS)
        {
            DebugOut ("Can't get reg value DictFile, error: %d\n", 
                      GetLastError());
            return TRUE;
        }
 
    }
    else
    {
        DebugOut ("Could not read registry.\n, No data read, vachar is @\n");
        return TRUE;
    }
    RegCloseKey (hKey);

    Again:
    dwError = GetLastError();
    hFileDict = CreateFile(szDictFile, GENERIC_READ, 
         FILE_SHARE_READ | FILE_SHARE_WRITE, (LPSECURITY_ATTRIBUTES) NULL, 
         OPEN_EXISTING, FILE_ATTRIBUTE_READONLY,  (HANDLE) NULL);
    if  ( (hFileDict == INVALID_HANDLE_VALUE) && 
          (dwError == ERROR_SHARING_VIOLATION))
    {
        DebugOut ("File: %s is locked (sharing). Still trying...\n", szDictFile);
        Sleep (100);
        i++;
        if (i>3)
            goto Again;
        else
        {
            lpDataDict = NULL;
            return TRUE;
        }

    }
    else if ( (hFileDict == INVALID_HANDLE_VALUE) && 
              (dwError != ERROR_SHARING_VIOLATION))
    {
        DebugOut ("Error opening: %s Error: %d\n", szDictFile);
        lpDataDict = NULL;
        return TRUE;
    }
    dwSizeDict = GetFileSize (hFileDict, NULL); 
    lpDataDict = (CHAR *) LocalAlloc (LPTR, dwSizeDict + 1 );

    i=0;
    AgainRead:
    if (!ReadFile (hFileDict, lpDataDict, dwSizeDict, &dwRead, NULL))
    {
        if  ( (dwError = GetLastError()) == ERROR_LOCK_VIOLATION)
        {
            DebugOut ("File: %s is locked (lock). Still trying...\n", szDictFile);
            Sleep (100);
            if (i>3)
                goto AgainRead;
            else
            {
                lpDataDict = NULL;
                return TRUE;
            }

        }
        else
        {
            DebugOut ("Error opening: %s Error: %d\n",szDictFile,dwError);
            lpDataDict = NULL;
            return TRUE;

        }
    }   
    CloseHandle (hFileDict);
    lpDataDict [dwRead] = '\0';

    DebugOut ("Dictionary file %s is read into memory\n", szDictFile);
    return TRUE;
} // GetExtensionVersion()

/****************************************************************************
*
*   FUNCTION: HttpExtensionProc
*   
*   PURPOSE:  Standart procedure which needs to be exported from the DLL.
*   
*   PARAMETRS: 
*             pECB - pointer to version extension control block.
*
*   RETURN:   DWORD, status code.
*
*   COMMENTS: Will be called by the server.
*
****************************************************************************/

DWORD WINAPI HttpExtensionProc (EXTENSION_CONTROL_BLOCK *pECB)
{
    HANDLE hFile;
    DWORD dwLen=0, dwError, dwSize, dwRead;
    int i = 0;
    CHAR *lpData = NULL, *lpIncStart, *lpIncEnd,  *lpDataForDelete;
    CHAR szTagBuffer[256], szValueBuffer [256];
    CHAR szBuff [256];

    DebugOut ("Opening file: %s for HTML\n", pECB->lpszPathTranslated);

    Again:
 
    hFile = CreateFile(pECB->lpszPathTranslated, GENERIC_READ, 
         FILE_SHARE_READ | FILE_SHARE_WRITE, (LPSECURITY_ATTRIBUTES) NULL, 
         OPEN_EXISTING, FILE_ATTRIBUTE_READONLY,  (HANDLE) NULL);
    
    dwError = GetLastError();

    if  ( (hFile == INVALID_HANDLE_VALUE) && (dwError == ERROR_SHARING_VIOLATION))
    {
        DebugOut ("File: %s is locked (sharing). Still trying...\n",
                  pECB->lpszPathTranslated);
        Sleep (100);
        i++;
        if (i>3)
            goto Again;
        else
            return HSE_STATUS_ERROR;

    }
    else if ( (hFile == INVALID_HANDLE_VALUE) &&
              (dwError != ERROR_SHARING_VIOLATION))
    {
        DebugOut ("Error opening: %s Error: %d\n",
                  pECB->lpszPathTranslated,dwError);
        return HSE_STATUS_ERROR;
    }
     
    dwSize = GetFileSize (hFile, NULL);
    lpData = (CHAR *) LocalAlloc (LPTR, dwSize + 1 );

    i=0;
    AgainRead:
    if (!ReadFile (hFile, lpData, dwSize, &dwRead, NULL))
    {
        if  ( (dwError = GetLastError()) == ERROR_LOCK_VIOLATION)
        {
            DebugOut ("File: %s is locked (lock). Still trying...\n",
                      pECB->lpszPathTranslated);
            Sleep (100);
            if (i>3)
                goto AgainRead;
            else
                return HSE_STATUS_ERROR;

        }
        else
        {
            DebugOut ("Error opening: %s Error: %d\n",
                      pECB->lpszPathTranslated,dwError);
            return HSE_STATUS_ERROR;
        }
    }
    CloseHandle (hFile);

    lpData[dwRead] = '\0';
    // we will loose lpData as we parse the file.
    // preserve its value for buffer deletion.

    lpDataForDelete = lpData;

    wsprintf ( szBuff, "Content-Type: text/html\r\n\r\n");
    dwLen = lstrlen ( szBuff );
    pECB -> ServerSupportFunction ( pECB -> ConnID,
                                    HSE_REQ_SEND_RESPONSE_HEADER, 
                                    "200 OK",
                                    &dwLen, 
                                    (LPDWORD) szBuff );

    // this is a <!-- #TAG --> sample

    while ( (lpIncStart = strstr (lpData, "<!--")) != NULL )
    {
        // print stuff before <!--
        dwSize = (lpIncStart - lpData) / sizeof (CHAR);
        pECB -> WriteClient (pECB -> ConnID, lpData, &dwSize, 0);
        
         // find end of tag -->
        if ( (lpIncEnd = strstr (lpData, "-->")) == NULL )
        {
            // we did not find end of tag bail out
            DebugOut ("No --> found. Bailing out\n");
            dwSize = lstrlen (lpData);
            pECB -> WriteClient (pECB -> ConnID, lpData, &dwSize, 0);
            return HSE_STATUS_SUCCESS;
        }
        
         // move lpData to the beginning of the tag
        lpData = lpIncStart + lstrlen ("<!--");
        while ( ISWHITE( *lpData ) )
            lpData++;

        // start coping the tag buffer
        i = 0;
        while ( ( !ISWHITE (*lpData)) && (lpData != lpIncEnd) )
            szTagBuffer[i++] = *lpData++;
        szTagBuffer[i]='\0';
        DebugOut ("Tag found: %s\n", szTagBuffer);
        
        lpData= lpIncEnd + lstrlen ("-->");
        // output proper value here
        if (szTagBuffer[0] == '#')
        {
            // it was a tag, get rid of the #
            CHAR *p = szTagBuffer + sizeof (CHAR);
            DebugOut ("Looking up: %s\n", p);

            dwSize = GetValueFromTag (szValueBuffer, p, pECB);
            pECB -> WriteClient (pECB -> ConnID, szValueBuffer, &dwSize, 0);
        } 
        else
        {
            // it was a comment, just print it
            DebugOut ("Comment found (not a tag)\n");
            dwSize = (lpData - lpIncStart) / sizeof (CHAR);
            pECB -> WriteClient (pECB -> ConnID, lpIncStart, &dwSize, 0);
        }

    }

    dwSize = lstrlen (lpData);
    pECB -> WriteClient (pECB -> ConnID, lpData, &dwSize, 0);

    LocalFree (lpDataForDelete);

return HSE_STATUS_SUCCESS;
}

DWORD GetValueFromTag (CHAR *szValue, CHAR *szTag, EXTENSION_CONTROL_BLOCK *pECB)
{
    // we are assuming that our dictionary file is small enough to fit
    // into memory. This is a datafile format:

    // TaG2 = This_is_tag2

    // Tags proceeded with @ are the server variables.
    // Tags can't have spaces.

    DWORD dwLen = 256, dwError, dwTag = lstrlen (szTag);
    CHAR * pch = lpDataDict;
    CHAR *p;
    int i;

    if ( *szTag == cVarChar )
    {
        p=szTag + sizeof (CHAR);
        // This is a server variable 
        if ( !pECB -> GetServerVariable (pECB -> ConnID,  // Connection id
                                 p,                       // Variable
                                 szValue,                 // Buffer 
                                 &dwLen))                 // Length
        {
            if ( (dwError= GetLastError ()) == ERROR_INVALID_INDEX  )
                wsprintf  ( szValue, "<br><i>Variable %s does not exist. Please notify Webmaster about this.</i><br>", p);
            else
                wsprintf  ( szValue, "<br><i>Error %d occured while getting %s variable</i><br>", dwError, p);

        }

    }
    else
    {
        p=szTag;
        // this is a real tag. Do nasty job of looking for it.
        if ( !pch )
        {
            // Our buffer is empty
            wsprintf  ( szValue, "<br><i>Can't find match for %s</i><br>", p);
            return lstrlen (szValue);
        }

        while (*pch)
        {
            while ( ISWHITE( *pch ) )
                pch++;

            if ( toupper( *pch ) == toupper( *szTag ) &&
                 !strnicmp( szTag, pch, dwTag )     &&
                 ( pch [dwTag] == ' ' || pch [dwTag] == '=') )
            {
                pch += dwTag + 1;
                goto Found;
            }

            pch = strchr( pch+1, '\n' );
        }
        Found:
        while ( ISWHITE( *pch ) || *pch == '=')
            pch++;

        i = 0;
        while ( ( *pch != '\n' ) && (i < 256) )
            szValue [i++] = *pch++;
        szValue[i] = '\0';
        
    }

    return lstrlen (szValue);
}

Figure 6    GetExtensionVersion

BOOL WINAPI GetExtensionVersion (HSE_VERSION_INFO *pVer)
{
    HANDLE hFileDict;
    DWORD dwSizeDict, dwError, dwRead;
    int i = 0;
    HKEY hKey;
    CHAR szDictFile [256], temp[2];
    pVer->dwExtensionVersion = MAKELONG (HSE_VERSION_MINOR,
    HSE_VERSION_MAJOR);
    lstrcpyn(pVer->lpszExtensionDesc,"HTTP SSI Application",
             HSE_MAX_EXT_DLL_NAME_LEN );


      if (ERROR_SUCCESS == RegOpenKeyEx(HKEY_LOCAL_MACHINE,
       REGKEY,0,KEY_READ,&hKey))
    {
        // We don't care if we can't read registry
        dwRead = sizeof (temp);
        if (RegQueryValueEx (hKey, "VariableCharacter", 
            NULL, NULL, (LPBYTE) temp, &dwRead) == ERROR_SUCCESS)
            cVarChar = temp[0];

        dwRead = sizeof (szDictFile);
         if (RegQueryValueEx (hKey, "DictFile", NULL, NULL, 
           (LPBYTE) szDictFile, &dwRead) != ERROR_SUCCESS)
        {
            DebugOut ("Can't get reg value DictFile, error: %d\n",
            GetLastError());
            return TRUE;
        }
    }
   else
    {
        DebugOut ("Could not read registry.\n, "
                  "No data read, vachar is @\n");
        return TRUE;
    }
    RegCloseKey (hKey);
    Again:
    dwError = GetLastError();
    hFileDict = CreateFile(szDictFile, GENERIC_READ,
                           FILE_SHARE_READ | FILE_SHARE_WRITE, 
                          (LPSECURITY_ATTRIBUTES) NULL, 
                           OPEN_EXISTING, FILE_ATTRIBUTE_READONLY,  
                          (HANDLE) NULL);
    if  ( (hFileDict == INVALID_HANDLE_VALUE) &&
          (dwError == ERROR_SHARING_VIOLATION))
    {
        DebugOut ("File: %s is locked (sharing). Still trying...\n", 
                  szDictFile);
        Sleep (100);
        i++;
        if (i>3) // if file is locked, try 3 times
            goto Again;
        else
        {
            lpDataDict = NULL;
            return TRUE;
        }
    }
    else if ( (hFileDict == INVALID_HANDLE_VALUE) && 
              (dwError != ERROR_SHARING_VIOLATION))
    {
        DebugOut ("Error opening: %s Error: %d\n", szDictFile);
        lpDataDict = NULL;
        return TRUE;
    }
    dwSizeDict = GetFileSize (hFileDict, NULL); 
    lpDataDict = (CHAR *) LocalAlloc (LPTR, dwSizeDict + 1 );
    i=0;
    AgainRead:
    if (!ReadFile (hFileDict, lpDataDict, dwSizeDict, &dwRead, NULL))
    {
        if  ( (dwError = GetLastError()) == ERROR_LOCK_VIOLATION)
        {                
            DebugOut ("File: %s is locked (lock). Still trying...\n",
                      szDictFile);
            Sleep (100);
            if (i>3)
                goto AgainRead;
            else
            {
                lpDataDict = NULL;
                return TRUE;
            }
    }
        else
        {
            DebugOut ("Error opening: %s Error: %d\n",szDictFile,dwError);
            lpDataDict = NULL;
            return TRUE;
        }
    }   
    CloseHandle (hFileDict);
    lpDataDict [dwRead] = '\0';
    DebugOut ("Dictionary file %s is read into memory\n", szDictFile);
    return TRUE;
} // GetExtensionVersion()

Figure 7    Tag Substitution Code

while ( (lpIncStart = strstr (lpData, "<!--")) != NULL )
{
        // print stuff before <!--
        dwSize = (lpIncStart - lpData) / sizeof (CHAR);
        pECB -> WriteClient (pECB -> ConnID, lpData, &dwSize, 0);
        
         // find end of tag -->
        if ( (lpIncEnd = strstr (lpData, "-->")) == NULL )
        {
            // we did not find end of tag bail out
            DebugOut ("No --> found. Bailing out\n");
            dwSize = lstrlen (lpData);
            pECB -> WriteClient (pECB -> ConnID, lpData, &dwSize, 0);
            return HSE_STATUS_SUCCESS;
        }       
         // move lpData to the beggining of the tag
        lpData = lpIncStart + lstrlen ("<!--");
        while ( ISWHITE( *lpData ) )
            lpData++;
        // start coping the tag buffer
        i = 0;
        while ( ( !ISWHITE (*lpData)) && (lpData != lpIncEnd) )
            szTagBuffer[i++] = *lpData++;
        szTagBuffer[i]='\0';
        DebugOut ("Tag found: %s\n", szTagBuffer);
        
        lpData= lpIncEnd + lstrlen ("-->");
        // output proper value here
        if (szTagBuffer[0] == '#')
        {
            // it was a tag, get rid of the #
            CHAR *p = szTagBuffer + sizeof (CHAR);
            DebugOut ("Looking up: %s\n", p);

            dwSize = GetValueFromTag (szValueBuffer, p, pECB);
            pECB -> WriteClient (pECB -> ConnID, szValueBuffer, 
                    &dwSize, 0);
        } 
        else
        {
            // it was a comment, just print it
            DebugOut ("Found comment, not a tag\n");
            dwSize = (lpData - lpIncStart) / sizeof (CHAR);
            pECB -> WriteClient (pECB -> ConnID, lpIncStart, &dwSize, 0);
        }
} // end while
dwSize = lstrlen (lpData);
pECB -> WriteClient (pECB -> ConnID, lpData, &dwSize, 0);

Figure 8    GetValueFromTag

DWORD GetValueFromTag (CHAR *szValue, CHAR *szTag,
                       EXTENSION_CONTROL_BLOCK *pECB)
{

    DWORD dwLen = 256, dwError, dwTag = lstrlen (szTag);
    CHAR * pch = lpDataDict;
    CHAR *p;
    int i;

    if ( *szTag == cVarChar ) // check if tag starts with the
                       // special character, indicating server
                              // variable.     
    {
        p=szTag + sizeof (CHAR);
        // This is a server variable 
        if ( !pECB -> GetServerVariable (pECB -> ConnID,   // Connection id
                                 p,        // Variable  
                                 szValue,             // Buffer 
                                 &dwLen))          // Length
        {    
            if ( (dwError= GetLastError ()) == ERROR_INVALID_INDEX  )
                wsprintf  ( szValue, "<br><i>Variable %s does"
                   " not exist. Please notify Webmaster about"
                   " this.</i><br>",
                    p);
            else
                wsprintf  ( szValue, "<br><i>Error %d occured while"
                              " getting %s variable</i><br>", dwError, p);
        }
    }
    else
    {
        p=szTag;
        // this is a real tag. Do nasty job of looking for it.
        if ( !pch )
        {
            // Our buffer is empty
            wsprintf  ( szValue, "<br><i>Can't find match for %s</i><br>",
                        p);
            return lstrlen (szValue);
        }

        while (*pch)
        {
            while ( ISWHITE( *pch ) )
                pch++;

            if ( toupper( *pch ) == toupper( *szTag ) &&
                 !strnicmp( szTag, pch, dwTag )     &&
                 ( pch [dwTag] == ' ' || pch [dwTag] == '=') )
            {
                pch += dwTag + 1;
                goto Found;
            }
            pch = strchr( pch+1, '\n' );
        }
        Found:
        while ( ISWHITE( *pch ) || *pch == '=')
            pch++;
        i = 0;
        while ( ( *pch != '\n' ) && (i < 256) )
            szValue [i++] = *pch++;
        szValue[i] = '\0';
    }
    return lstrlen (szValue);
}

Figure 9   HELLO.BPI Template File


<HTML>
<!-- Leon Braginski and Matt Powell (c) -->

<BODY>
<h2><center> BPI Custom Script Processor Test </h2></center><br>

My name is <b><!-- #FName --> <!-- #LName --></b><br>
I live in <b><!--#City-->, <!-- #State --> <!-- #Country --> </b> <hr>

<h2><center> Server Variables </h2></center><br>

Your browser is:<b> <!-- #@HTTP_USER_AGENT --> </b><br>
Your IP address: <b> <!-- #@REMOTE_ADDR  --> </b><br>
IIS Server software: <b> <!-- #@SERVER_SOFTWARE  --> </b><br>

</BODY>
</HTML>