HOWTO: ISAPI Filter which rejects requests from SF_NOTIFY_PREPROC_HEADERS based on HTTP Referer


There was a recent question about how to reject requests (based on the HTTP Referer header in this case) in SF_NOTIFY_PREPROC_HEADERS.


Question:


Hello everyone,


I’m trying to solve a problem of referer spam: People will fake requests to the web server using online casino, “drug” retailers and other questionable web sites as referer. Not only does that messes up my web stats but I also happen to run a blog software that keeps track of these referer and actually display them to users.


Anyway, in order to solve that problem, I wrote a small ISAPI filter in Delphi 6. it works well so far with one problem: when it blocks a request, it sends back a 401.4 response: “A filter denied access to this resource”.


I would like to send back a “403” which has a better chance to be properly interpreted as a “get out of my yard, buddy”.


What I’m doing now:


1/ Calling SetLastError with ERROR_ACCESS_DENIED as parameter
2/ Returning SF_STATUS_REQ_ERROR as the result of the filter callback.


Since I’m filtering the even as soon as possible, when I get a SF_NOTIFY_PREPROC_HEADERS notification, I don’t seem to have access to the return code itself.


Is there a way to send back that 403 without modifing to filter to run in the SF_NOTIFY_END_OF_REQUEST notification ? I’d like to minimize the impact of the request on the server and I see no reason to run multiple DB queries to generate a web page that will be truncated by the filter.


Thank you,


Answer:


Unfortunately, for an ISAPI Filter, there is no API call to send an IIS-configured custom error page. The same for ISAPI Extension until in IIS 6.0 when the HSE_REQ_SEND_CUSTOM_ERROR ServerSupportFunction was introduced to allow it to send IIS-configured custom error page by explicitly specifying HTTP status/sub-status code.


However, ISAPI Filter does have an “arbitrary” behavior which can cause a couple of HTTP responses to be sent when you call SetLastError() with the following values and return SF_STATUS_REQ_ERROR from SF_NOTIFY_PREPROC_HEADERS.



  1. ERROR_FILE_NOT_FOUND – returns 404 error
  2. ERROR_PATH_NOT_FOUND – returns 404 error
  3. ERROR_ACCESS_DENIED – returns 401.4 error
  4. Any other Win32 error code – returns 500 error with FormatMessage of the error code

Personally, from a security perspective, I would send back a 404 response using ERROR_FILE_NOT_FOUND instead of sending back 401.4 (tells attacker that you have an ISAPI Filter looking at responses, which can induce more attacks) or 403 (tells attacker that they are doing something the server does not want to do).


Of course, if you must send back the 403, you can certainly call SF_REQ_SEND_RESPONSE_HEADER with a 403 status string and return SF_REQ_STATUS_FINISHED from SF_NOTIFY_PREPROC_HEADERS, which would be just as efficient.


For example, UrlScan does this – SetLastError( ERROR_FILE_NOT_FOUND ) and return SF_REQ_STATUS_FINISHED from SF_NOTIFY_PREPROC_HEADERS for the fast path rejection.


Regarding your other question about “when I get a SF_NOTIFY_PREPREC_HEADERS notification, I don’t seem to have access to the return code itself”. I am not certain what return code you are referring to, but it is probably not needed.


There is no “return code” in SF_NOTIFY_PREPROC_HEADERS because it happens as IIS is parsing the request headers and before processing the request. Since an ISAPI Filter can write arbitrary data via WriteClient or SF_REQ_SEND_RESPONSE_HEADER to the client and terminate the request processing right then and there, you really do not need access to any return code. You have control of the entire request/response right then and there.


Also, rejecting a request in SF_NOTIFY_END_OF_REQUEST is not optimal. You have already done all the request processing, and you also need to consume response data in SF_NOTIFY_SEND_RAW_DATA (so that the client gets a proper HTTP response) as well as send the entire rejection response in SF_NOTIFY_END_OF_REQUEST. Too much work for a rejection.


Here is a complete filter sample in C (sorry, I really do not know Delphi) that requires that all requests have a Referer header whose value starts with “http://myserver”. It has a whole bunch of comments that show how to send a 404 or a 403 response as well as how to filter on the Referer header.


Enjoy.


//David

#include <windows.h>
#include <httpfilt.h>

#define DEFAULT_BUFFER_SIZE 1024
#define MAX_BUFFER_SIZE 4096

DWORD
OnPreprocHeaders(
IN HTTP_FILTER_CONTEXT * pfc,
IN HTTP_FILTER_PREPROC_HEADERS * pPPH
);

BOOL
WINAPI
GetFilterVersion(
HTTP_FILTER_VERSION * pVer
)
/*++

Purpose:

Required entry point for ISAPI filters. This function
is called when the server initially loads this DLL.

Arguments:

pVer – Points to the filter version info structure

Returns:

TRUE on successful initialization
FALSE on initialization failure

–*/
{
pVer->dwFilterVersion = HTTP_FILTER_REVISION;
lstrcpyn( pVer->lpszFilterDesc,
“Filter to reject based on HTTP Referer header”,
SF_MAX_FILTER_DESC_LEN );
pVer->dwFlags =
SF_NOTIFY_ORDER_HIGH |
SF_NOTIFY_PREPROC_HEADERS
;

return TRUE;
}

DWORD
WINAPI
HttpFilterProc(
IN HTTP_FILTER_CONTEXT * pfc,
DWORD dwNotificationType,
LPVOID pvNotification
)
/*++

Purpose:

Required filter notification entry point. This function is called
whenever one of the events (as registered in GetFilterVersion) occurs.

Arguments:

pfc – A pointer to the filter context for this notification
NotificationType – The type of notification
pvNotification – A pointer to the notification data

Returns:

One of the following valid filter return codes:
– SF_STATUS_REQ_FINISHED
– SF_STATUS_REQ_FINISHED_KEEP_CONN
– SF_STATUS_REQ_NEXT_NOTIFICATION
– SF_STATUS_REQ_HANDLED_NOTIFICATION
– SF_STATUS_REQ_ERROR
– SF_STATUS_REQ_READ_NEXT

–*/
{
switch ( dwNotificationType )
{
case SF_NOTIFY_PREPROC_HEADERS:
return OnPreprocHeaders(
pfc,
(HTTP_FILTER_PREPROC_HEADERS *) pvNotification );
}

return SF_STATUS_REQ_NEXT_NOTIFICATION;
}

DWORD
OnPreprocHeaders(
HTTP_FILTER_CONTEXT * pfc,
HTTP_FILTER_PREPROC_HEADERS * pPPH
)
{
DWORD dwRet = SF_STATUS_REQ_NEXT_NOTIFICATION;
BOOL fRet = FALSE;
CHAR pBuf[ DEFAULT_BUFFER_SIZE ];
CHAR * pszBuf = pBuf;
DWORD cbBuf = DEFAULT_BUFFER_SIZE;
CHAR szReferer[] = “Referer:”;
CHAR szMyServerABS[] = “http://myserver”;
CHAR cbMyServerABS = 15;

SetLastError( NO_ERROR );

if ( pfc == NULL ||
pPPH == NULL )
{
SetLastError( ERROR_INVALID_PARAMETER );
goto Finished;
}

fRet = pPPH->GetHeader( pfc, szReferer, pszBuf, &cbBuf );
if ( fRet == FALSE )
{
if ( GetLastError() == ERROR_INVALID_INDEX )
{
//
// Referer header was not given
// Two options:
//
// If not having Referer header is ok.
//
// OutputDebugString( “Nothing to do.\n” );
// SetLastError( NO_ERROR );
//
// If Referer header is required
//
// OutputDebugString( “Referer header was not found.\n” );
// SetLastError( ERROR_ACCESS_DENIED );
//
goto Finished;
}
else if ( GetLastError() == ERROR_INSUFFICIENT_BUFFER &&
cbBuf < MAX_BUFFER_SIZE )
{
pszBuf = new CHAR[ cbBuf ];
if ( pszBuf == NULL )
{
SetLastError( ERROR_NOT_ENOUGH_MEMORY );
goto Finished;
}

fRet = pPPH->GetHeader( pfc, szReferer, pszBuf, &cbBuf );
if ( fRet == FALSE )
{
goto Finished;
}

OutputDebugString( “Found Referer header.\n” );
}
else
{
goto Finished;
}
}

//
// At this point, pszBuf points to the value of Referer header.
// Figure out what to do.
//
OutputDebugString( pszBuf );
OutputDebugString( “\n” );

//
// If it is not an absolute URL from my server, reject it
//
if ( _strnicmp( pszBuf, szMyServerABS, cbMyServerABS ) != 0 )
{
//
// Option 1: Send a 404 response
//
// SetLastError( ERROR_FILE_NOT_FOUND );
// goto Finished;
//
// Options 2: Send a 403 response
//
fRet = pfc->ServerSupportFunction( pfc,
SF_REQ_SEND_RESPONSE_HEADER,
(PVOID)”403 Forbidden”,
(ULONG_PTR)”Content-Length: 0\r\n”
“Content-Type: text/html\r\n\r\n”,
NULL );
if ( fRet == FALSE )
{
goto Finished;
}

dwRet = SF_STATUS_REQ_FINISHED;
}

SetLastError( NO_ERROR );

Finished:

if ( pszBuf != pBuf )
{
delete pszBuf;
}

if ( GetLastError() != NO_ERROR )
{
OutputDebugString( “Error!\n” );
dwRet = SF_STATUS_REQ_ERROR;
}

return dwRet;
}

Comments (31)

  1. Stephane Grobety says:

    Hello, I’m the one who asked that question. Thanks a lot for your answer. Thanks a lot for your answer. I’ve implemented the change you suggest (with a few modifications) and it works just as I wanted. Thank you,

  2. David Wang says:

    People frequently ask about the &quot;Referer Authentication&quot; custom protocol that Apache offers with a custom…

  3. Your filter resources are great, I have been compiling the examples since I looking at rewriting some of our filters and wanted to get back up to speed.

    There are a couple of minor typo’s in the above filter.

    The initial declaration should be

    DWORD

    OnPreprocHeaders(

    IN HTTP_FILTER_CONTEXT * pfc,

    IN HTTP_FILTER_PREPROC_HEADERS * pPPH

    );

    //there is a WINAPI on line 9 that is incorrect

    and the 403 redirect needs to be one of the following. at around line 179

    //either

    CHAR szTemp [2048];

    sprintf (szTemp, "%s 0rn%s%srnrn",

    "Content-Length:", "Content-Type: ","text/html");

    fRet = pfc->ServerSupportFunction( pfc,

    SF_REQ_SEND_RESPONSE_HEADER,

    (PVOID) "403 Forbidden" ,

    (ULONG_PTR)szTemp,

    NULL );

    //or

    fRet = pfc->ServerSupportFunction( pfc,

    SF_REQ_SEND_RESPONSE_HEADER,

    (PVOID) "403 Forbidden" ,

    (ULONG_PTR)"Content-Length: 0rnContent-Type: text/htmlrnrn",

    NULL );

    Thanks once again for an excellent resource.

  4. David Wang says:

    Thanks for the compiler check. I made the minor changes so that it should compile clean from copy/paste (well, assuming one adds the necessary .DEF file).

    //David

  5. Junaid says:

    Hi David,

    I’m new to ISAPI programming. I have a isapi filter which logs the url to a text file. What i want is an isapi filter which will restrict the downloads of a particular extension file from server(that is u cant download it more than 5 times). say http://xyz/abc.mp3?user=tom

    so we want to restrict the user tom to a maximum of 5 downloads.

    Please let me know your ideas, how we can implement this.

    Regards

    Junaid

  6. David Wang says:

    Junaid – you need to design and implement a mechanism to uniquely identify a remote client. This mechanism is commonly known as "authentication".

    Since you are the one designing the system, you need to first come up with the necessary requirements that must be fulfilled by the authentication protocol… then you search and use the minimal necessary protocol.

    After you figure out how to authenticate a remote client on a per-request basis, then you can easily implement restrictions.

    //David

  7. Junaid says:

    Hi David,

    Please help me out in this issue. For example someone is downloading a file from the server and in the middle the connection gets broke or the user himself cancels the download. How do i know using isapi filter that this has happened. Which event to capture or what will get return if this is the scenario. Please help.

    Thanks & Regards

    Junaid

  8. David Wang says:

    Junaid – No reliable way to detect this condition in ISAPI. No filter event fires on errors.

    On WS03SP1, you can’t detect it. HTTP.SYS will buffer the response such that ISAPI Filter will not know when/if the client disconnects

    Prior to WS03SP1, you can try to count bytes sent via SF_NOTIFY_SEND_RAW_DATA and expected bytes obtained via Content-Length: in SF_NOTIFY_SEND_RESPONSE. But this only works for response entity body that has Content-Length.

    For Transfer-Encoding: chunked entity body or responses without either Content-Length or Transfer-Encoding: chunked headers, I cannot think of a good way.

    Some people have tried GetLastError() in SF_NOTIFY_SEND_RAW_DATA, SF_NOTIFY_END_OF_REQUEST, and SF_NOTIFY_END_OF_NET_SESSION to attempt to get some error code indicating error… but this is not defined in any interface, thus it is not guaranteed to work from version to version.

    //David

  9. Junaid says:

    Hi David ,

    The problems is.

     I make request to download a file. say http://www.abc.comhello.wmv

     say hello.wmv is a 5 MB. I want to HTTP byte range to restrict and download only the part of the movie. How can i accomplish the task.

    Regards

    Junaid

     

  10. David.Wang says:

    Junaid – write an ISAPI Extension scriptmapped to handle all .wmv requests, determine the physical file location of the URL (similar to how ASP does it), and then call HSE_REQ_TRANSMIT_FILE (or HSE_REQ_VECTOR_SEND on IIS6) to transmit any portion(s) of the physical file.

    Of course, you are responsible for making the client understand how to receive and use the partial download of the file. IIS just allows you to send the byte stream.

    //David

  11. Junaid says:

    Thanks David for the info.  If have any link that shows how to use

    HSE_REQ_TRANSMIT_FILE, plese forward to me. As i’m new to this ISAPI stuffs. And Can we use HSE_REQ_TRANSMIT_FILE in ISAPI filter.

    Regards

    Junaid

  12. David.Wang says:

    Junaid – I believe you can read ISAPI Documentation on MSDN for sample code and answers to your latest questions.

    //David

  13. Junaid says:

    Hi David,

        I tried to write a ISAPI Filter to download the partial content. I have used Addheader Range. But its not working and the full gets downloaded.  

       I have also tried with ISAPI Extension using HSE_REQ_TRANSMIT_FILE. i am able to download part of the file using say byteswritten= 10000000 but when i use offset, i’m not able to play the file after download. Please help me about AddHeader and setheader usage. I am using it as specified in the

    http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35.2 but i’m able to download the partial content.

    Please need your help.

    Thanks & Regards

    Junaid

  14. David.Wang says:

    Junaid – I suspect you are not adding correct headers to the RESPONSE. Since you are modifying response data stream, you really need to use Network Monitor or tools like that to make sure your altered HTTP response is kosher.

    You can add response headers with an ISAPI Filter either by:

    1. Calling pfc->AddResponseHeader( "header: valuern" ) before SF_NOTIFY_SEND_RESPONSE fires

    2. Calling AddHeader/SetHeader in SF_NOTIFY_SEND_RESPONSE

    But, I do not know why you are trying to write an ISAPI Filter to do this…

    Now, if you are going with the ISAPI Extension solution, you don’t need an ISAPI Filter to add the response header – just send the right headers on the HSE_REQ_TRANSMIT_FILE call itself.

    As for what happens with the partial download delivered by HSE_REQ_TRANSMIT_FILE – you need to figure out how to make the resulting partial data playable by the media player. All media formats have a certain layout, and your partial download of a given file needs to remain kosher relative to that layout.

    In other words, suppose hello.wmv is 5MB and contains 1 minute of content. If you want to just deliver content between 0:15s and 0:45s, you obviously need to figure out the right header as well as partial file content to stream.

    You are response for figuring out that "make it work" aspect. After you figure it out, the IIS portion is trivial – HSE_REQ_TRANSMIT_FILE to send the headers and partial file content, and simple Scriptmap setting to wire your ISAPI onto the .wmv requests.

    //David

  15. Junaid says:

    Thanks a lot for your help. Below is the code that i’m using. I have added AddResponseHeader as you have mentioned. But it not working. Please review the below code…..

    I would be grateful to you.

    Thanks & Regards

    Junaid

    HANDLE g_hLogFile = INVALID_HANDLE_VALUE;

    CRITICAL_SECTION    g_LogFileLock;

    ///////////////////////////////////////////////////////////////////////

    // The one and only CRedirectorFilter object

    CRedirectorFilter theFilter;

    CHAR* CRedirectorFilter::m_pszUserFile = NULL;

    ///////////////////////////////////////////////////////////////////////

    // CRedirectorFilter implementation

    CRedirectorFilter::CRedirectorFilter()

    {

     

    }

    CRedirectorFilter::~CRedirectorFilter()

    {

    }

    BOOL CRedirectorFilter::GetFilterVersion(PHTTP_FILTER_VERSION pVer)

    {

    // Call default implementation for initialization

    CHttpFilter::GetFilterVersion(pVer);

    // Clear the flags set by base class

    pVer->dwFlags &= ~SF_NOTIFY_ORDER_MASK;

    // Set the flags we are interested in

    pVer->dwFlags |= SF_NOTIFY_ORDER_HIGH | SF_NOTIFY_PREPROC_HEADERS;

    // Initialize the logging critical section

       //

       InitializeCriticalSection( &g_LogFileLock );

    g_hLogFile = CreateFile("D:\MyLog.txt",GENERIC_WRITE,FILE_SHARE_READ | FILE_SHARE_WRITE,

                                NULL,

    OPEN_ALWAYS,

    FILE_ATTRIBUTE_NORMAL,

    NULL

    );

           

      if ( g_hLogFile == INVALID_HANDLE_VALUE )

       {

           //

           // Failed to open log file due to GetLastError(). Bail.

           //

           return FALSE;

       }

       pVer->dwFilterVersion = HTTP_FILTER_REVISION;

       lstrcpyn( pVer->lpszFilterDesc,

                "ISAPI Filter to log request headers based on User-Agent",

                SF_MAX_FILTER_DESC_LEN );

     

       return TRUE;

    }

    DWORD CRedirectorFilter::OnPreprocHeaders(HTTP_FILTER_CONTEXT*  pCtxt,

    PHTTP_FILTER_PREPROC_HEADERS pHeaderInfo)

    {

    DWORD  dwRet = SF_STATUS_REQ_NEXT_NOTIFICATION;

    BOOL   fRet = FALSE;

       CHAR   pBuf[ DEFAULT_BUFFER_SIZE ];

       CHAR   pBuf2[ DEFAULT_BUFFER_SIZE ];

       CHAR *pszBuf = pBuf;

       CHAR *pszBuf2 = pBuf2;

       CHAR  szUserAgent[] = "User-Agent:";

    CHAR  szUrl[] = "url";

       CHAR  szMSNBot[] = "Mozilla/4.0";

    CHAR  cbMSNBot = 11;

       DWORD cbBuf = DEFAULT_BUFFER_SIZE;

    DWORD cbBuf2 = DEFAULT_BUFFER_SIZE;

    DWORD cbBuf3 = DEFAULT_BUFFER_SIZE;

       

    SetLastError( NO_ERROR );

       if ( pCtxt == NULL ||

            pHeaderInfo == NULL )

       {

           SetLastError( ERROR_INVALID_PARAMETER );

           goto Finished;

       }

    //pCtxt->AddResponseHeaders(pCtxt, "Content-Range: bytes 0-1048576rn", 0);

    // pHeaderInfo->AddHeader(pCtxt,"Range:","bytes 0-500");

    // pHeaderInfo->AddHeader(pCtxt,"Content-Range:","bytes 0-500");

    // pHeaderInfo->AddHeader(pCtxt,"Accept-Ranges:","bytes");

    pCtxt->AddResponseHeaders(pCtxt,"Range: bytes 0-500rn",0);

    pCtxt->AddResponseHeaders(pCtxt,"Content-Range: bytes 0-500rn",0);

    pCtxt->AddResponseHeaders(pCtxt,"Accept-Ranges: bytesrn",0);

    fRet = pHeaderInfo->GetHeader(pCtxt,szUserAgent,pszBuf,&cbBuf);

    if ( fRet == FALSE )

           {

    if ( GetLastError() == ERROR_INVALID_INDEX )

    {

    //

    // User-Agent was not given

    // Since we are looking for a particular user-agent,

    // not finding on is ok.

    //

    OutputDebugString( "Nothing to do.n" );

    SetLastError( NO_ERROR );

    goto Finished;

    }

    else if ( GetLastError() == ERROR_INSUFFICIENT_BUFFER && cbBuf < MAX_BUFFER_SIZE )

               {

                   pszBuf = new CHAR[ cbBuf ];

                   if ( pszBuf == NULL )

                   {

                       SetLastError( ERROR_NOT_ENOUGH_MEMORY );

                       goto Finished;

                   }

                   fRet = pHeaderInfo->GetHeader(pCtxt,szUserAgent,pszBuf,&cbBuf);

                   if ( fRet == FALSE )

                   {

           goto Finished;

                   }

                   OutputDebugString( "Found User-Agent header.n" );

               }

               else

               {

      goto Finished;

               }

    }

    //

    // If the User-Agent matches the MSNBot, log request headers

    //

       

    if ( _strnicmp( pszBuf, szMSNBot, cbMSNBot ) == 0 )

       {

    pCtxt->AddResponseHeaders(pCtxt,"Range: bytes 0-500rn",0);

    pCtxt->AddResponseHeaders(pCtxt,"Content-Range: bytes 0-500rn",0);

    pCtxt->AddResponseHeaders(pCtxt,"Accept-Ranges: bytesrn",0);

           fRet = pCtxt->GetServerVariable( pCtxt, "ALL_RAW", pszBuf2, &cbBuf2 );

           if ( fRet == FALSE )

           {

               if ( GetLastError() == ERROR_INSUFFICIENT_BUFFER &&

                    cbBuf2 < MAX_BUFFER_SIZE )

               {

                   pszBuf2 = new CHAR[ cbBuf2 ];

                   if ( pszBuf2 == NULL )

                   {

                       SetLastError( ERROR_NOT_ENOUGH_MEMORY );

                       goto Finished;

                   }

                   fRet = pCtxt->GetServerVariable( pCtxt,

                                                  "ALL_RAW",

                                                  pszBuf2,

                                                  &cbBuf2 );

                   if ( fRet == FALSE )

                   {

                       goto Finished;

                   }

                   OutputDebugString( "Read ALL_RAWn" );

               }

               else

               {

                   goto Finished;

               }

           }

    //

           // At this point, pszBuf2 points to the raw request headers

           // But first, retrieve the URL of this request by reusing pszBuf.

           // Then append it to disk, thread-safe.

           //

    if ( pszBuf != pBuf )

           {

               delete pszBuf;

               pszBuf = pBuf;

           }

    fRet = pHeaderInfo->GetHeader( pCtxt, szUrl, pszBuf, &cbBuf );

           if ( fRet == FALSE )

           {

               if ( GetLastError() == ERROR_INSUFFICIENT_BUFFER &&

                    cbBuf < MAX_BUFFER_SIZE )

               {

                   pszBuf = new CHAR[ cbBuf ];

                   if ( pszBuf == NULL )

                   {

                       SetLastError( ERROR_NOT_ENOUGH_MEMORY );

                       goto Finished;

                   }

                   fRet = pHeaderInfo->GetHeader( pCtxt, szUrl, pszBuf, &cbBuf );

                   if ( fRet == FALSE )

                   {

                       goto Finished;

                   }

                   OutputDebugString( "Found URL.n" );

                }

                else

                {

                    goto Finished;

                }

           }

    //

           // Write the URL and raw headers to disk

           //

    if ( g_hLogFile != INVALID_HANDLE_VALUE )

           {

               EnterCriticalSection( &g_LogFileLock );

               SetFilePointer( g_hLogFile, 0, NULL, FILE_END );

               cbBuf–;

               fRet = WriteFile( g_hLogFile, pszBuf, cbBuf, &cbBuf, NULL );

               cbBuf = 1;

               fRet = WriteFile( g_hLogFile, "n", cbBuf, &cbBuf, NULL );

    cbBuf2–;

               fRet = WriteFile( g_hLogFile, pszBuf2, cbBuf2, &cbBuf2, NULL );

    LeaveCriticalSection( &g_LogFileLock );

           }

           

    }

    SetLastError( NO_ERROR );

    Finished:

       if ( pszBuf != pBuf )

       {

       delete pszBuf;

    }

    if ( GetLastError() != NO_ERROR )

    {

    OutputDebugString( "Error!n" );

    dwRet = SF_STATUS_REQ_NEXT_NOTIFICATION;

    }

    return dwRet;

    }

    DWORD CRedirectorFilter::OnSendResponse(IN HTTP_FILTER_CONTEXT *   pfc,    IN HTTP_FILTER_SEND_RESPONSE *  pSR)

    {

       DWORD                           dwRet = SF_STATUS_REQ_NEXT_NOTIFICATION;

       BOOL                            fRet = FALSE;

       CHAR                            pBuf[ DEFAULT_BUFFER_SIZE ];

       CHAR *                          pszBuf = pBuf;

       DWORD                           cbBuf = DEFAULT_BUFFER_SIZE;

       CHAR                            szCacheControl[] = "Content-Range:";

       CHAR                            szPrivate[] = "bytes 0-1048576";

       SetLastError( NO_ERROR );

       if ( pfc == NULL ||

            pSR == NULL )

       {

           SetLastError( ERROR_INVALID_PARAMETER );

           goto Finished;

       }

    //pfc->AddResponseHeaders(pfc,"Content-Range: bytes 0-1048576rn",0);

    //pfc->AddResponseHeaders(pfc,"Content-Type: video/x-ms-wmvrn",0);

    //pSR->AddHeader(pfc,(char *) _T("Accept-Ranges:"),(char *) _T("bytes"));

    //pSR->AddHeader(pfc,(char *) _T("Content-Range:"),(char *) _T("bytes 0-500/1048576"));

       

       //

       // At this point, pszBuf points to the value of Content-Type header.

       // Figure out if the Content-Type matches and if so, add the

       // Cache-Control header.

       //

       

           //

           // Replace any Cache-Control header

           //

           //fRet = pSR->SetHeader( pfc, szCacheControl, szPrivate );

           if ( fRet == FALSE )

           {

               //

               // Failed to set header for some reason

               // Fail the request

               //

               goto Finished;

           }

               

       

    Finished:

       if ( pszBuf != pBuf )

       {

           delete pszBuf;

       }

       if ( GetLastError() != NO_ERROR )

       {

           OutputDebugString( "Error!n" );

           dwRet = SF_STATUS_REQ_NEXT_NOTIFICATION;

       }

       return dwRet;

    }

    DWORD CRedirectorFilter::TerminateFilter(DWORD dwFlags)

    /*++

     Optional filter entry point.  This function is called by the server

     before this DLL is unloaded.

     Arguments:

       dwFlags – No flags have been defined at this time

     Returns:

       Always returns TRUE;

    –*/

    {

       if ( g_hLogFile != INVALID_HANDLE_VALUE )

       {

           CloseHandle( g_hLogFile );

           g_hLogFile = INVALID_HANDLE_VALUE;

       }

    DeleteCriticalSection( &g_LogFileLock );

       return TRUE;

    }

    DWORD CRedirectorFilter:: HttpFilterProc(

       IN HTTP_FILTER_CONTEXT *      pfc,

       DWORD                      NotificationType,

       PVOID                      pvData )

    /*++

     Routine Description:

     

       This is the main function for the filter. It processes all

        filter notifications sent by the server and takes appropriate action.

     Arguments:

       pfc              –   pointer to filter context

       NotificationType –   type of notification

       pvData           –   pointer to data structure specific for the

                             notification

     Returns:

       DWORD value containing the SF_STATUS_TYPE is returned

    –*/

    {

    CHttpFilter::HttpFilterProc(pfc,NotificationType,pvData);

       DWORD   sfReturn = SF_STATUS_REQ_NEXT_NOTIFICATION;

       switch ( NotificationType) {

         case SF_NOTIFY_PREPROC_HEADERS:

    sfReturn =  OnPreprocHeaders(pfc,(HTTP_FILTER_PREPROC_HEADERS*) pvData );

                                        break;

     //case SF_NOTIFY_SEND_RESPONSE:  sfReturn =  OnSendResponse( pfc,(HTTP_FILTER_SEND_RESPONSE *) pvData );

                                break;//

     default:       // No Action to take here. pass it on to other filters in chain.

     break;

       } // switch

       return (sfReturn);

    } // HttpFilterProc()

  16. David.Wang says:

    Junaid – I’m sorry, but this is mostly my code with a couple of copy/paste of your code. Can you clearly describe what you want to accomplish, how are you trying to accomplish it, YOUR concise code to do it, and how is it exactly failing (Network Monitor trace or WFetch output is sufficient).

    But, please do not try to make me write and debug your code for you…

    FYI: What I said earlier still holds true — I do not see a need for ISAPI Filter, and once you figure out how HTTP and client-side interpretation of partial content works, the ISAPI portion should be easy.

    Finally, I do not support ISAPI using the MFC ISAPI Framework.

    //David

  17. Junaid says:

    Hi David,

      Yes its your code. What i want is..

     say i make a request http://www.example.com/movie.wmv

     say if its a 2 MB file. i want to download only the last 1 MB.

     so what  i’m trying to use is Range header. But it seems not  

     working. That’s all.  In other words as you told.. as below.

    suppose hello.wmv is 5MB and contains 1 minute  of content. If you want to just deliver content between 0:15s and 0:45s, you obviously need to figure out the right header as well as partial file content to stream.

    I trying to acheive this.

    Sorry for the trouble….and disturbance.

    Thanks & Regards

    Junaid

  18. David.Wang says:

    Junaid – Hey, I have no problems helping users understand what is going on, but pardon me for having problems doing your work for you.

    You can’t just hand me code and say "I did what you told me and it still doesn’t work, so here is my code please fix it". That crosses the line.

    Range has to be supported by the handler of the request-type. IIS Static File handler handles Range. For example, Adobe Acrobat makes use of this all the time.

    You still have not answered the real problem – how is the client supposed to take the snippet of content and interpret it correctly.

    Remember, the client never asked for Range, so it’s not expecting to stitch together response entity. And since you are sending a part of a media resource while the client is expecting an entire resource, you need to reconstruct the response entity to be a kosher, entire media resource.

    This task is clearly outside of ISAPI. As soon as you figure out the correct bytes to send, ISAPI Extension is more than happy to help you send it using HSE_REQ_TRANSMIT_FILE (and HSE_REQ_VECTOR_SEND on IIS6 makes the stitching of arbitrary file ranges trivially easy).

    //David

  19. Junaid says:

    Hi David.

    I’m extreamly sorry…………

    Thanks a lot…………… for your help. I found the problem why i was not able to get the requested ranges. Its b’coz of some authentication in my IIS server. I downloaded wfetch and then i found out the problem…

    Thanks again………..

    Regards

    Junaid

  20. Junaid says:

    Hi David,

          I’m downloading a partial content. Obiviously it returns 206 response. When it opens the save dialog box and i click the save button, it says IE was not able to open the Internet site. how can i avoid this… i also note that in the dialog box the file name is changed to waiting_wmv from waiting.wmv. Please need your opinion.

    Thanks & Regards

    Junaid

  21. lunyi476 says:

    (1) ISAPI filter can not open and write to local disk file on IIS 6.0

    (2) Can I share filter between different web sites by using synchronizing the shared source ?

    I wrote a filter  to log some data into DB and catch the failed DB process into local file.  The DB process is ok, but the process of writing to local file failed.  Is that security problem (I did not singn in as administrator) ?

    The Filter Source Code: (.h and .cpp)

    // LOGCSBJ.H – Header file for your Internet Server

    //    LogCsbj Filter

    #include "resource.h"

    class CLogCsbjFilter : public CHttpFilter

    {

    public:

    CLogCsbjFilter();

    ~CLogCsbjFilter();

    // Overrides

    // ClassWizard generated virtual function overrides

    // NOTE – the ClassWizard will add and remove member functions here.

    //    DO NOT EDIT what you see in these blocks of generated code !

    //{{AFX_VIRTUAL(CLogCsbjFilter)

    public:

    virtual BOOL GetFilterVersion(PHTTP_FILTER_VERSION pVer);

    virtual DWORD OnLog(CHttpFilterContext* pfc, PHTTP_FILTER_LOG pLog);

    //}}AFX_VIRTUAL

    //{{AFX_MSG(CLogCsbjFilter)

    //}}AFX_MSG

    private:

    CString m_CompName;

    CCriticalSection m_cs;

    CString cs2;

    INT counter;

    public:

    void init();

    UINT ComputeThreadProc(CString cs);

    };

    //{{AFX_INSERT_LOCATION}}

    #include "stdafx.h"

    #include "LogCsbj.h"

    ///////////////////////////////////////////////////////////////////////

    // The one and only CLogCsbjFilter object

    CLogCsbjFilter theFilterCsbj;

    ///////////////////////////////////////////////////////////////////////

    // CLogCsbjFilter implementation

    CLogCsbjFilter::CLogCsbjFilter()

    {

    cs2 = _T(" ");

    m_CompName = _T(" ");

    counter = 0;

    init ();

    CStdioFile  f;

    CString pFileName = "f:\logfiltercsbj.txt";

    f.Open( pFileName,  CFile::modeCreate );

    f.Close();

    }

    CLogCsbjFilter::~CLogCsbjFilter()

    {

    }

    void CLogCsbjFilter::init () {

    WSADATA Data;

    INT status = 0;

    status=WSAStartup(MAKEWORD(1, 1), &Data);

    if (status == 0)

    {

    char     szHostname[100];

    INT A = 0;

    A = gethostname( szHostname, sizeof( szHostname ));

    if (A == 0) {

    CString cn(szHostname);

    m_CompName = cn;

    }

    }

    }

    BOOL CLogCsbjFilter::GetFilterVersion(PHTTP_FILTER_VERSION pVer)

    {

    // Call default implementation for initialization

    CHttpFilter::GetFilterVersion(pVer);

    // Clear the flags set by base class

    pVer->dwFlags &= ~SF_NOTIFY_ORDER_MASK;

    // Set the flags we are interested in

    pVer->dwFlags |= SF_NOTIFY_ORDER_MEDIUM | SF_NOTIFY_SECURE_PORT | SF_NOTIFY_NONSECURE_PORT

    | SF_NOTIFY_LOG;

    // Load description string

    TCHAR sz[SF_MAX_FILTER_DESC_LEN+1];

    ISAPIVERIFY(::LoadString(AfxGetResourceHandle(),

    IDS_FILTER, sz, SF_MAX_FILTER_DESC_LEN));

    _tcscpy(pVer->lpszFilterDesc, sz);

    return TRUE;

    }

    DWORD CLogCsbjFilter::OnLog(CHttpFilterContext *pCtxt, PHTTP_FILTER_LOG pLog)

    {

    CString Ct1(pLog->pszTarget);

    Ct1.MakeLower();

    CString Ct2(pLog->pszParameters);

    if ( Ct1.Find(".jpg") == -1 && Ct1.Find(".gif") == -1 &&

    Ct1.Find(".css") == -1 && Ct1.Find(".js") == -1 &&

    Ct1.Find(".ico") == -1  && Ct1.Find("robots.txt") == -1 &&

    Ct1.Find("akamai.cfm") == -1

      )

    {

    DWORD dwSize = 50;

    char pchVar3[50] = "";

    pCtxt->GetServerVariable("REMOTE_ADDR", pchVar3, &dwSize);

    CString Ct3(pchVar3);

    INT dotP = -1;

    dotP = Ct3.ReverseFind(‘.’);

    CString cstPart1 = Ct3.Left(dotP);

    //if ( cstPart1.Find("10.1." ) == -1 )

    //{

    char pchVar4[50] = "";

    pCtxt->GetServerVariable("SERVER_NAME", pchVar4, &dwSize);

    CString Ct4(pchVar4);

    Ct4.MakeLower();

    //CString siteId ("csbj");

    CString siteId ("pdftest");

    INT A = -1;

    INT B = -1;

    DWORD dwSize0 = 2000;

    char pchVar0[2000] = "";

    pCtxt->GetServerVariable("ALL_HTTP", pchVar0, &dwSize0);

    CString st(pchVar0);

    st.MakeLower();

    INT nLength = 0;

    nLength = st.GetLength();

    CString agent = _T(" ");

    A = st.Find("http_user_agent:");

    if ( A != -1)  {

    CString rightSt = st.Right(nLength – A);

    B = rightSt.Find("n");

    if (B != -1) {

    CString leftSt = rightSt.Left(B);

    leftSt.Replace("http_user_agent:", " ");

    leftSt.TrimLeft();

    leftSt.TrimRight();

    agent = leftSt;

    }

    }

    if (agent.GetLength() < 3  || ( agent.Find("nagios") ==-1 &&  agent.Find("bot") ==-1 &&

    agent.Find("robot") ==-1 && agent.Find("crawler") == -1 &&

    agent.Find("spider") == -1 &&

    agent.Find("/us/ysearch/slurp") == -1 &&

    agent.Find("http://sp.ask.com/&quot;) == -1  && agent.Find("mnogosearch") == -1 &&

    agent.Find("sohu-search") == -1 && agent.Find("linkwalker") == -1 &&

    agent.Find("ia_archiver") == -1 && agent.Find("wish-la") == -1 &&

    agent.Find("cfschedule") == -1  && agent.Find("java/1.") ==-1 )

    )

    {

    CTime t = CTime::GetCurrentTime( );

    CString strGmt = t.FormatGmt("%m/%d/%Y %H:%M:%S");

    CString cs1 = "{CALL procLogPdf (‘"+ strGmt+ "’,’" +Ct4 +

    "’,’"+Ct1+"’,’"+ Ct2+"’,’"+Ct3+"’,’"+agent+"’,’"+m_CompName+"’,’"+siteId+"’)};";

    CString csToDb(" ");

    BOOL goin = FALSE;

    m_cs.Lock();

    cs2 = cs2 + cs1;

    counter++;

    if ( counter > 99 ) {

    counter = 0;

    csToDb = cs2;

    cs2 = _T(" ");

    goin = TRUE;

    }

    m_cs.Unlock();

    if (goin )

    ComputeThreadProc(csToDb);

    }

    //}

    }

    return SF_STATUS_REQ_NEXT_NOTIFICATION;

    }

    UINT CLogCsbjFilter::ComputeThreadProc(CString cs)

    {

    BOOL lostit = TRUE;

    cs.Delete (cs.GetLength() -1, 1);

    HRESULT hr = ::AfxOleInit();

    if ( !FAILED(hr))

    {

    CSession m_session;

    CDataSource m_db;

    hr = m_db.Open(_T("MSDASQL"), _T("trans_log"), _T("cfUser"), _T("rsfwtny"), DB_MODE_WRITE);

    if (! FAILED(hr)) {

    hr = m_session.Open(m_db);

    if (! FAILED(hr)) {

    CCommand <CNoAccessor, CNoRowset> myCommand;

    hr = myCommand.Open(m_session,cs);

    if (! FAILED(hr)) {

    myCommand.Close();

    lostit = FALSE;

    }

    m_session.Close();

    }

    m_db.Close();

    }

    }

    ::AfxOleTerm(0);

    if ( lostit) {

    CString ct = cs +  " n";

    CStdioFile  f;

    CString pFileName = "f:\logfiltercsbj.txt";

    f.Open( pFileName,  CFile::typeText | CFile::modeWrite );

    f.SeekToEnd();

    f.WriteString(ct);

    f.Close();

    }

    return 0;

    }

    // Do not edit the following lines, which are needed by ClassWizard.

    #if 0

    BEGIN_MESSAGE_MAP(CLogCsbjFilter, CHttpFilter)

    //{{AFX_MSG_MAP(CLogCsbjFilter)

    //}}AFX_MSG_MAP

    END_MESSAGE_MAP()

    #endif // 0

    Thanks ,

    Lun

  22. David.Wang says:

    Lun –

    1. ISAPI Filter always runs as process identity. We changed it from LocalSystem on IIS5 to Network Service on IIS6 for security reasons.

    http://blogs.msdn.com/david.wang/archive/2005/06/29/IIS_User_Identity_to_Run_Code_Part_2.aspx

    Thus, you either need to give the process identity NTFS permissions to write to a directory, or you need to configure the process identity to one that can write to your directory. Depending on your security requirements you choose one or the other.

    2. You need to open the file for shared writing and then synchronize access to that file.

    http://blogs.msdn.com/david.wang/archive/2005/08/03/HOWTO_ISAPI_Filter_logging_request_URL_and_headers_based_on_User_Agent.aspx

    Your approach to open/write/close the file under lock synchronization can work, but the filter now creates contention under load, potentially throttling performance.

    In general, this Filter code has many design and coding problems beyond inability to write to a file. Some of those problems show up as instability, other are serious security vulnerabilities:

    1. Filter open/close the log file once during constructor which is not really necessary other than to create a 0-byte file, but you now need to synchronize it or start checking return values

    2. Filter will not correctly log SERVER_NAME if the Host name is more than 15 characters on IPv4, or variable size on IPv6

    3. Filter fails if request headers > 2000 bytes (Default limit in IIS6 is 16K, configurable). This often happens if the request is negotiating Kerberos.

    4. ComputeThreadProc is not re-entrant but you do not assure it. If the reset counters get to 99 before the first ComputeThreadProc finishes, Filter fails.

    5. Having constructor call init() is strange – why not put that code in the constructor itself.

    And in all the failing cases, the Filter does not check return value. So, it won’t realize failure and perform string operations on uninitialized buffers anyway. The buffers may/not be null terminated and thus can randomly crash or give wrong results.

    //David

  23. Lun says:

    Hi, David Wang:

    Thanks for your helps.

    I changed my coding as attachment.  It seems working well. I still have two questions(see below).  The local file is just for in case of DB disconnection. Open and close will be not problem because it happens rarely and I synchronized it.  Performance will not be hurted much because  DB operation only happens once for 100 logs and most of requests are .gif and .css extensions which will be blocked at the begin of onLog call.

    (1)   "ProcessLogProc is not re-entrant". ( I changed the name to ProcessLogProc)

    I am not really understand  "re-entrant" and why it is not re-entrant  ?

    All threads can share the globle filter object and call its onLog method seperately and inside call  local method ProcessLogProc.

    I did not see any problem.  

    (2)  "in all the failing cases, the Filter does not check return value"

    "The buffers may/not be null terminated and thus can randomly crash or give wrong results"

    What is the problem of

    " char pchVarIP[50] ;

    pCtxt->GetServerVariable("REMOTE_ADDR", pchVarIP, &dwSize);

    CString clientIP(pchVarIP); "           ?

    even the returned pchVarIP = ‘’, there is no problem to make clientIP and then use string operation on clientIP.

    How can it crash  ?

    If possible, please make a simple sample which may crash for me.

    Than you very much,

    Lun

    Source code:

    #include "resource.h"

    class CPdfLogFilterFilter : public CHttpFilter

    {

    public:

    CPdfLogFilterFilter();

    ~CPdfLogFilterFilter();

    // Overrides

    // ClassWizard generated virtual function overrides

    // NOTE – the ClassWizard will add and remove member functions here.

    //    DO NOT EDIT what you see in these blocks of generated code !

    //{{AFX_VIRTUAL(CPdfLogFilterFilter)

    public:

    virtual BOOL GetFilterVersion(PHTTP_FILTER_VERSION pVer);

    virtual DWORD OnLog(CHttpFilterContext* pfc, PHTTP_FILTER_LOG pLog);

    //}}AFX_VIRTUAL

    //{{AFX_MSG(CPdfLogFilterFilter)

    //}}AFX_MSG

    private:

    CMapStringToString m_mapStringToString;

    CString m_CompName;

    void init();

    CCriticalSection m_cs;

    CString csShared;

    INT counter;

    CString csCatcher;

    CStdioFile  cfile;

    public:

    void ProcessLogProc(CString csLocal);

    };

    #include "stdafx.h"

    #include "PdfLogFilter.h"

    CPdfLogFilterFilter theFilter;

    HANDLE event;

    CPdfLogFilterFilter::CPdfLogFilterFilter()

    {

    csShared = _T(" ");

    csCatcher = _T(" ");

    counter = 0;

    m_CompName = _T(" ");

    WSADATA Data;

    INT status = 0;

    status=WSAStartup(MAKEWORD(1, 1), &Data);

    if (status == 0)

    {

    char     szHostname[100];

    INT A = 0;

    A = gethostname( szHostname, sizeof( szHostname ));

    if (A == 0) {

    CString cn(szHostname);

    m_CompName = cn;

    }

    }

    CString cst1 ("financecommerce");

    CString cst2 ("mke");

    CString cst3 ("minnlawyer");

    CString cst4 ("dolan");

    CString cst5 ("okc");

    CString cst6 ("dolannews");

    CString cst7 ("csmng");

    CString cst8 ("kcdailyrecord");

    CString cst9 ("henrygreene");

    CString cst10 ("legalledger");

    CString cst11 ("ibr");

    CString cst12 ("tinker");

    CString cst13 ("nopg");

    CString cst14 ("nydailyrecord");

    CString cst15("wibuilder");

    CString cst16("csbj");

    CString cst17("stl");

    CString cst18("djcno");

    CString cst19("mydrnews");

    CString cst20("jrdolannews");

    CString cst21 ("libn");

    CString cst22 ("djc");

    CString cst23 ("acih");

    CString cst24("books");

    CString cst25("class");

    CString cst26("corp");

    CString cst27("ih");

    CString cst28("jobs");

    CString cst29("ma");

    CString cst30("mi");

    CString cst31("mo");

    CString cst32("nc");

    CString cst33("ri");

    CString cst34("sc");

    CString cst35("va");

    CString cst36("mlr");

    CString cst37("mlrma");

    CString cst38("mlrmi");

    CString cst39("mlrmo");

    CString cst40("mlrva");

    CString cst41("mwih");

    CString cst42("neih");

    CString cst43("news");

    CString cst44("rules_ma");

    CString cst45("sfc");

    CString cst46("usa");

    m_mapStringToString.SetAt("www.finance-commerce.com", cst1);

    m_mapStringToString.SetAt("www.dailyreporter.com", cst2  );

    m_mapStringToString.SetAt("www.minnlawyer.com", cst3  );

    m_mapStringToString.SetAt("www.dolanmedia.com", cst4 );

    m_mapStringToString.SetAt("www.journalrecord.com", cst5 );

    m_mapStringToString.SetAt("www.dolanmedianewswires.com", cst6 );

    m_mapStringToString.SetAt("www.csmng.com",   cst7);

    m_mapStringToString.SetAt("www.kcdailyrecord.com", cst8 );

    m_mapStringToString.SetAt("www.greeneassoc.com", cst9 );

    m_mapStringToString.SetAt("www.legal-ledger.com", cst10  );

    m_mapStringToString.SetAt("www.idahobusiness.net", cst11 );

    m_mapStringToString.SetAt("www.tinkertakeoff.com", cst12  );

    m_mapStringToString.SetAt("www.neworleanscitybusiness.com", cst13  );

    m_mapStringToString.SetAt("www.nydailyrecord.com", cst14 );

    m_mapStringToString.SetAt("www.wibuilder.com", cst15 );

    m_mapStringToString.SetAt("www.csbj.com",cst16 );

    m_mapStringToString.SetAt("www.thepbj.com",cst16 );

    m_mapStringToString.SetAt("www.thedailyrecord.com", cst17);

    m_mapStringToString.SetAt("www.djc-gp.com", cst18  );

    m_mapStringToString.SetAt("www.djcgulfcoast.com", cst18  );

    m_mapStringToString.SetAt("maryland.thedailyrecordnewswire.com", cst19  );

    m_mapStringToString.SetAt("www.mddailyrecord.com", cst19  );

    m_mapStringToString.SetAt("journalrecord.dolanmedianewswires.com", cst20 );

    m_mapStringToString.SetAt("www.libn.com", cst21 );

    m_mapStringToString.SetAt("libn.com", cst21 );

    m_mapStringToString.SetAt("www.djc-or.com", cst22 );

    m_mapStringToString.SetAt("www.atlanticcoastinhouse.com", cst23 );

    m_mapStringToString.SetAt("books.lawyersweekly.com", cst24  );

    m_mapStringToString.SetAt("www.lawyersweeklyclassifieds.com", cst25  );

    m_mapStringToString.SetAt("www.lawyersweekly.com", cst26  );

    m_mapStringToString.SetAt("inhouse.lawyersweekly.com", cst27  );

    m_mapStringToString.SetAt("www.lawyersweeklyjobs.com", cst28  );

    m_mapStringToString.SetAt("www.malawyersweekly.com", cst29  );

    m_mapStringToString.SetAt("www.masslawyersweekly.com", cst29  );

    m_mapStringToString.SetAt("www.masslaw.com", cst29  );

    m_mapStringToString.SetAt("www.malawyersweekly.com", cst29  );

    m_mapStringToString.SetAt("www.milawyersweekly.com", cst30  );

    m_mapStringToString.SetAt("www.www.michlaw.com", cst30  );

    m_mapStringToString.SetAt("www.molawyersweekly.com", cst31  );

    m_mapStringToString.SetAt("www.nclawyersweekly.com", cst32 );

    m_mapStringToString.SetAt("www.rilawyersweekly.com", cst33  );

    m_mapStringToString.SetAt("www.sclawyersweekly.com", cst34  );

    m_mapStringToString.SetAt("www.valawyersweekly.com", cst35  );

    m_mapStringToString.SetAt("www.virginialaw.com", cst35  );

    m_mapStringToString.SetAt("medicallaw.lawyersweekly.com", cst36  );

    m_mapStringToString.SetAt("www.momedicallaw.com", cst37  );

    m_mapStringToString.SetAt("www.mimedicallaw.com", cst38  );

    m_mapStringToString.SetAt("www.mamedicallaw.com", cst39  );

    m_mapStringToString.SetAt("www.vamedicallaw.com", cst40  );

    m_mapStringToString.SetAt("www.midwestinhouse.com", cst41  );

    m_mapStringToString.SetAt("www.newenglandinhouse.com", cst42 );

    m_mapStringToString.SetAt("newslinks.lawyersweekly.com", cst43  );

    m_mapStringToString.SetAt("massrules.lawyersweekly.com", cst44  );

    m_mapStringToString.SetAt("www.smallfirmconnection.com", cst45  );

    m_mapStringToString.SetAt("www.lawyersweeklyusa.com", cst46 );

            // named for use by different processes

    // if existing, just open it with the name.

    event = ::CreateEvent(0,FALSE,TRUE,"plfEvent");

    CString pFileName = _T("f:\inetpub\lwadmin\ly\pdflogfilter.txt");

    cfile.Open( pFileName,  CFile::modeCreate | CFile::modeNoTruncate);

    cfile.Close();

    }

    CPdfLogFilterFilter::~CPdfLogFilterFilter()

    {

    CloseHandle(event);

    }

    BOOL CPdfLogFilterFilter::GetFilterVersion(PHTTP_FILTER_VERSION pVer)

    {

    // Call default implementation for initialization

    CHttpFilter::GetFilterVersion(pVer);

    // Clear the flags set by base class

    pVer->dwFlags &= ~SF_NOTIFY_ORDER_MASK;

    // Set the flags we are INTerested in

    pVer->dwFlags |= SF_NOTIFY_ORDER_LOW | SF_NOTIFY_SECURE_PORT | SF_NOTIFY_NONSECURE_PORT

    |  SF_NOTIFY_LOG;

    // Load description string

    TCHAR sz[SF_MAX_FILTER_DESC_LEN+1];

    ISAPIVERIFY(::LoadString(AfxGetResourceHandle(),

    IDS_FILTER, sz, SF_MAX_FILTER_DESC_LEN));

    _tcscpy(pVer->lpszFilterDesc, sz);

    return TRUE;

    }

    DWORD CPdfLogFilterFilter::OnLog(CHttpFilterContext *pCtxt, PHTTP_FILTER_LOG pLog)

    {

    // check page style

    CString page(pLog->pszTarget);    

    page.MakeLower();

    if ( page.Find("akamai.cfm") != -1 || page.Find(".gif") != -1  ||

    page.Find(".jpg") != -1   || page.Find(".css") != -1  ||

    page.Find(".js") != -1        || page.Find(".ico") != -1  ||

    page.Find("robots.txt") != -1

      )

    return SF_STATUS_REQ_NEXT_NOTIFICATION;

    // check ip range

    DWORD dwSize = 50;

    char pchVarIP[50];

    pCtxt->GetServerVariable("REMOTE_ADDR", pchVarIP, &dwSize);

    CString clientIP(pchVarIP);

    INT dotP = -1;

    dotP = clientIP.ReverseFind(‘.’);

    CString cstPart1 = clientIP.Left(dotP);

    if ( cstPart1.Find("209.98.226" ) != -1 || cstPart1.Find("216.183.118" ) != -1 ||

    cstPart1.Find("10.1." ) != -1      || cstPart1.Find("66.45.104" ) != -1

      )

    return SF_STATUS_REQ_NEXT_NOTIFICATION;

    dwSize = 2000;

    char pchVarAll;

    pCtxt->GetServerVariable("ALL_HTTP", pchVarAll, &dwSize);

    CString stAll(pchVarAll);

    stAll.MakeLower();

    INT nLength = 0;

    nLength = stAll.GetLength();

    // check agent style

    INT A = -1;

    INT B = -1;

    CString agent = _T(" ");

    A = stAll.Find("http_user_agent:");

    if ( A != -1)  {

    CString rightSt = stAll.Right(nLength – A);

    B = rightSt.Find("n");

    if (B != -1) {

    agent = rightSt.Left(B);

    agent.Replace("http_user_agent:", " ");

    agent.TrimLeft();

    agent.TrimRight();

    } else {

    rightSt.Replace("http_user_agent:", " ");

    rightSt.TrimLeft();

    rightSt.TrimRight();

    agent = rightSt;

    }

    }

    if (agent.GetLength() > 2 ) {

    if ( agent.Find("nagios") !=-1 ||  agent.Find("bot") !=-1 ||

    agent.Find("robot") !=-1 || agent.Find("crawler") != -1 ||

    agent.Find("spider") != -1 || agent.Find("java/1.") !=-1  ||

    agent.Find("/us/ysearch/slurp") != -1 ||  

    agent.Find("http://sp.ask.com/&quot;) != -1 || agent.Find("mnogosearch") != -1 ||

    agent.Find("sohu-search") != -1 || agent.Find("linkwalker") != -1 ||

    agent.Find("ia_archiver") != -1 || agent.Find("wish-la") != -1 ||

    agent.Find("cfschedule") != -1

    )

    return SF_STATUS_REQ_NEXT_NOTIFICATION;

    }

    // get domain name

    A = -1;

    B = -1;

    CString host = _T(" ");

    A = stAll.Find("http_host:");

    CString siteId =_T(" ");

    BOOL found = FALSE;

    if ( A != -1)  {

    CString rightSt = stAll.Right(nLength – A);

    B = rightSt.Find("n");

    if (B != -1) {

    host = rightSt.Left(B);

    host.Replace("http_host:", " ");

    } else {

    host = rightSt.Replace("http_host:", " ");

    }

    host.Replace(":80", " ");

    host.TrimLeft();

    host.TrimRight();

    found = m_mapStringToString.Lookup(host, siteId );

    }

    /* pay much attention to this, otherwise can not find DB table

     host was not reliable to get from  

    "pCtxt->GetServerVariable("SERVER_NAME", pchVarAll, &dwSize0);"

    */

    if (!found )

    siteId = _T("pdftest");

    // do the rest jobs

    CString queryParam(pLog->pszParameters);  

    CTime t = CTime::GetCurrentTime( );

    CString strGmt = t.FormatGmt("%m/%d/%Y %H:%M:%S");

    CString cs1 = "{CALL procLogPdf (‘"+ strGmt+ "’,’" +host +

    "’,’"+page+"’,’"+ queryParam+"’,’"+clientIP+"’,’"+agent+"’,’"+m_CompName+"’,’"+siteId+"’)};";

    BOOL goin = FALSE;

    CString csLocal = _T(" ");

    m_cs.Lock();

    csShared = csShared + cs1;

    counter++;

    if ( counter > 99 ) {  // >199

    counter = 0;

    csLocal = csShared;

    csShared = _T(" ");

    goin = TRUE;

    }

    m_cs.Unlock();

    if (goin )

    ProcessLogProc(csLocal);

    return SF_STATUS_REQ_NEXT_NOTIFICATION;

    }

    void CPdfLogFilterFilter::ProcessLogProc(CString cs)

    {

    cs.TrimLeft();

    BOOL lostit = TRUE;

    try {

    m_cs.Lock();

    cs = cs + csCatcher;

    m_cs.Unlock();

    cs.TrimRight();

    cs.TrimRight(‘;’);

    HRESULT hr = ::AfxOleInit();

    if ( !FAILED(hr))

    {

    CSession m_session;

    CDataSource m_db;

    hr = m_db.Open(_T("MSDASQL"), _T("trans_log"), _T("cfUser"), _T("rsfwtny"), DB_MODE_WRITE);

    if (! FAILED(hr)) {

    hr = m_session.Open(m_db);

    if (! FAILED(hr)) {

    CCommand <CNoAccessor, CNoRowset> myCommand;

    hr = myCommand.Open(m_session,cs);

    if (! FAILED(hr)) {

    lostit = FALSE;

    myCommand.Close();

    }

    m_session.Close();

    }

    m_db.Close();

    }

    }

    ::AfxOleTerm(0);

    if ( lostit) {

    CString logSt = _T(" ");

    BOOL logIt = FALSE;

     // protect memory crash, just throw the catched data

     //  But, limited filter thread memory by system

    //  csCatcher can not be longer than 40,000

    m_cs.Lock();

    if ( cs.GetLength() > 50000 )  

    {

    logSt = cs +  _T("n");

    csCatcher = _T(" ");

    logIt = TRUE;

    }

    else

    csCatcher = cs;

    m_cs.Unlock();

    if (logIt) {

    CString pFileName = _T("f:\inetpub\lwadmin\ly\pdflogfilter.txt");

    ::WaitForSingleObject(event,30000);

    cfile.Open( pFileName,  CFile::typeText | CFile::modeWrite );

    cfile.SeekToEnd();

    cfile.WriteString(logSt);

    cfile.Close();

    ::SetEvent(event);

    }

    } else {

    m_cs.Lock();

    csCatcher = _T(" ");

    m_cs.Unlock();

    }

    } catch (CException  *eor) {  //CMemoryException

    // protect memory crash, just throw the catched data

    m_cs.Lock();

    csCatcher = _T(" ");

    m_cs.Unlock();

    eor->Delete();

    }

    }

  24. Lun says:

    David:

    I am trying to understand the re-entrant problem.

    If process did not stop between " m_cs.Lock();  ………… m_cs.UnLock(); "  it seems to me that would not be a problem.

    Do you think the process may be stopped betweent CriticalSection time ?

    During the CriticalSection time,   insertion string has been assigned to local CString which local method ProcessLogProc will use.  So, if the process stopped here and then came back later, there is nothing changed.  Why it is not re-entrant ?

    Ok.  If you say the the re-entrant is referring to the local  method "ProcessLogProc".

    for safe, Can I just put the code of ProcessLogProc  into  OnLog and eliminated the local call ProcessLogProc  re-entrant problem ?

    I hered re-entrant before but this is first time deal with it.

    Thanks lot,

    Lun

  25. Lun says:

    David:

    Please see better checking error code>

    Thanks lot,

    Lun

    Source code:

    DWORD CPdfLogFilterFilter::OnLog(CHttpFilterContext *pCtxt, PHTTP_FILTER_LOG pLog)

    {

    // check page style

    CString page(pLog->pszTarget);    

    page.MakeLower();

    if ( page.Find("akamai.cfm") != -1 || page.Find(".gif") != -1  ||

    page.Find(".jpg") != -1   || page.Find(".css") != -1  ||

    page.Find(".js") != -1        || page.Find(".ico") != -1  ||

    page.Find("robots.txt") != -1

      )

    return SF_STATUS_REQ_NEXT_NOTIFICATION;

    // check ip range

    DWORD dwSize = 50;

    char pchVarIP[50];

    CString clientIP(" ");

    BOOL boolRtAdd = pCtxt->GetServerVariable("REMOTE_ADDR", pchVarIP, &dwSize);

    if ( boolRtAdd == TRUE)

    {

    CString cip(pchVarIP);

    clientIP = cip;

    INT dotP = -1;

    dotP = clientIP.ReverseFind(‘.’);

    CString cstPart1 = clientIP.Left(dotP);

    if ( cstPart1.Find("209.98.226" ) != -1 || cstPart1.Find("216.183.118" ) != -1 ||

    cstPart1.Find("10.1." ) != -1      || cstPart1.Find("66.45.104" ) != -1

    )

    return SF_STATUS_REQ_NEXT_NOTIFICATION;

    }

    dwSize = 2000;

    char pchVarAll[2000];

    BOOL boolAllHttp = pCtxt->GetServerVariable("ALL_HTTP", pchVarAll, &dwSize);

    CString agent = _T(" ");

    CString host = _T(" ");

    CString siteId =_T(" ");

    BOOL found = FALSE;

    if ( boolAllHttp == TRUE)

    {

    CString stAll(pchVarAll);

    stAll.MakeLower();

    INT nLength = 0;

    nLength = stAll.GetLength();

    // check agent style

    INT A = -1;

    INT B = -1;

    A = stAll.Find("http_user_agent:");

    if ( A != -1)  {

    CString rightSt = stAll.Right(nLength – A);

    B = rightSt.Find("n");

    if (B != -1) {

    agent = rightSt.Left(B);

    agent.Replace("http_user_agent:", " ");

    agent.TrimLeft();

    agent.TrimRight();

    } else {

    rightSt.Replace("http_user_agent:", " ");

    rightSt.TrimLeft();

    rightSt.TrimRight();

    agent = rightSt;

    }

    }

    if (agent.GetLength() > 2 ) {

    if ( agent.Find("nagios") !=-1 ||  agent.Find("bot") !=-1 ||

    agent.Find("robot") !=-1 || agent.Find("crawler") != -1 ||

    agent.Find("spider") != -1 || agent.Find("java/1.") !=-1  ||

    agent.Find("/us/ysearch/slurp") != -1 ||  

    agent.Find("http://sp.ask.com/&quot;) != -1 || agent.Find("mnogosearch") != -1 ||

    agent.Find("sohu-search") != -1 || agent.Find("linkwalker") != -1 ||

    agent.Find("ia_archiver") != -1 || agent.Find("wish-la") != -1 ||

    agent.Find("cfschedule") != -1

    )

    return SF_STATUS_REQ_NEXT_NOTIFICATION;

    }

    // get domain name

    A = -1;

    B = -1;

    A = stAll.Find("http_host:");

    if ( A != -1)  {

    CString rightSt = stAll.Right(nLength – A);

    B = rightSt.Find("n");

    if (B != -1) {

    host = rightSt.Left(B);

    host.Replace("http_host:", " ");

    } else {

    host = rightSt.Replace("http_host:", " ");

    }

    host.Replace(":80", " ");

    host.TrimLeft();

    host.TrimRight();

    found = m_mapStringToString.Lookup(host, siteId );

    }

    }

    /* pay much attention to this, otherwise can not find DB table

    host was not reliable to get from  

    "pCtxt->GetServerVariable("SERVER_NAME", pchVarAll, &dwSize0);"

    */

    if (!found )

    siteId = _T("pdftest");

    // do the rest jobs

    CString queryParam(pLog->pszParameters);  

    CTime t = CTime::GetCurrentTime( );

    CString strGmt = t.FormatGmt("%m/%d/%Y %H:%M:%S");

    CString cs1 = "{CALL procLogPdf (‘"+ strGmt+ "’,’" +host +

    "’,’"+page+"’,’"+ queryParam+"’,’"+clientIP+"’,’"+agent+"’,’"+m_CompName+"’,’"+siteId+"’)};";

    BOOL goin = FALSE;

    CString csLocal = _T(" ");

    m_cs.Lock();

    csShared = csShared + cs1;

    counter++;

    if ( counter > 99 ) {  // >199

    counter = 0;

    csLocal = csShared;

    csShared = _T(" ");

    goin = TRUE;

    }

    m_cs.Unlock();

    if (goin )

    ProcessLogProc(csLocal);

    return SF_STATUS_REQ_NEXT_NOTIFICATION;

    }

  26. Steve says:

    David,

    We have an ISAPI filter that works fine with IIS 6.0 and Framwork1.1, After installing .net Framework 2.0 the Filter is not working properly. Any page i try to open ends up in http 503. I tried to debug and looks like CHttpFilter::GetFilterVersion(pVer); returns null in below code. Can you advice why this would happen.

    BOOL CMavFiltFilter::GetFilterVersion(PHTTP_FILTER_VERSION pVer)

    {

    // Call default implementation for initialization

    CHttpFilter::GetFilterVersion(pVer);

    // Clear the flags set by base class

    pVer->dwFlags &= ~SF_NOTIFY_ORDER_MASK;

    // Set the flags we are interested in

    pVer->dwFlags |= SF_NOTIFY_SECURE_PORT | SF_NOTIFY_NONSECURE_PORT | SF_NOTIFY_PREPROC_HEADERS;

    // Set Priority

    pVer->dwFlags |= SF_NOTIFY_ORDER_HIGH;

    // Load description string

    TCHAR sz[SF_MAX_FILTER_DESC_LEN+1];

    ISAPIVERIFY(::LoadString(AfxGetResourceHandle(),

    IDS_FILTER, sz, SF_MAX_FILTER_DESC_LEN));

    _tcscpy(pVer->lpszFilterDesc, sz);

    //_tcscpy_s(pVer->lpszFilterDesc, sz);

    return TRUE;

    }

    Thx

  27. Steve says:

    David,

    I debuged further and looks like the pFilter is null (0X00000). Can you tell me why this would happen.

    extern "C" BOOL WINAPI GetFilterVersion(PHTTP_FILTER_VERSION pVer)

    {

    try

    {

    #ifdef _AFXDLL

    AFX_MANAGE_STATE(AfxGetStaticModuleState());

    #endif

    BOOL bRet;

    ISAPIASSERT(pFilter != NULL);

    if (pFilter == NULL)

    bRet = FALSE;

    else

    bRet = pFilter->GetFilterVersion(pVer);

    return bRet;

    }

    catch(…)

    {

    ISAPITRACE("Error: unhandled exception caught!n");

    return FALSE;

    }

    }

    Thnx

  28. David.Wang says:

    Steve – I have no idea. The issue does not look to be with IIS/ISAPI, though.

    You are using the MFC ISAPI Template, which is unsupported, causing you problems, and not necessary/useful, so I advise you to remove usage of it. See:

    http://blogs.msdn.com/david.wang/archive/2006/03/25/MFC_ISAPI_Template_and_ISAPI_Development.aspx

    Pure ISAPI, which is how all my ISAPI code samples are written, rarely have such issues. I suspect CLR versioning may be causing you issue, but that is a CLR issue; just uncovered via ISAPI Filter.

    //David

  29. sudhish says:

    Hi David, I am trying to doing an ISAPI filter for IIS 6.0 to redirect a user  to a particular URL whenever a user clicks on a pdf file.

    For eg : when a user clicks on a link http://localhost/downloads/download.pdf, I want to redirect user as follows

    http://localhost/validate.asp?retUrl=http://localhost/downloads/download.pdf.

    Can you please help me with the code?

  30. Dorris says:

    Hi david ,

    I have this code , I do not have much (almost nill ) knowledge of ISAPI, i have asked to write clean up code for "OnPreprocHeaders" as the code i have  giving us memory leak . I read your code on ISAPI filters , but still not able to get it much . Please help me in writing cleanup code for "OnPreprocHeaders" .

    Thanks!

    // HEADERVALUE4.CPP – Implementation file for your Internet Server

    //    HeaderValue4 Filter

    #include <iostream>

    #include <cstring>

    #include "stdafx.h"

    #include "HeaderValue4.h"

    #include "IniReader.h"

    #include "conio.h"

    #include "httpfilt.h"

    #include "httpext.h"

    #define DEFAULT_BUFFER_SIZE         1024

    #define MAX_BUFFER_SIZE             4096

    #define CONFIG_FILENAME                         "C:\inetpub\isapi\HeaderValue\Config.ini"

    BOOL hasLoadedConfig = false;

    ///////////////////////////////////////////////////////////////////////

    // The one and only CHeaderValue4Filter object

    CHeaderValue4Filter theFilter;

    ///////////////////////////////////////////////////////////////////////

    // CHeaderValue4Filter implementation

    CHeaderValue4Filter::CHeaderValue4Filter()

    {

    }

    CHeaderValue4Filter::~CHeaderValue4Filter()

    {

    }

    BOOL CHeaderValue4Filter::GetFilterVersion(PHTTP_FILTER_VERSION pVer)

    {

           // Call default implementation for initialization

           CHttpFilter::GetFilterVersion(pVer);

           // Clear the flags set by base class

           pVer->dwFlags &= ~SF_NOTIFY_ORDER_MASK;

           // Set the flags we are interested in

           pVer->dwFlags |= SF_NOTIFY_ORDER_MEDIUM | SF_NOTIFY_SECURE_PORT | SF_NOTIFY_NONSECURE_PORT

                            | SF_NOTIFY_PREPROC_HEADERS | SF_NOTIFY_END_OF_NET_SESSION;

           // Load description string

           TCHAR sz[SF_MAX_FILTER_DESC_LEN+1];

           ISAPIVERIFY(::LoadString(AfxGetResourceHandle(),

                           IDS_FILTER, sz, SF_MAX_FILTER_DESC_LEN));

           _tcscpy(pVer->lpszFilterDesc, sz);

           return TRUE;

    }

    DWORD CHeaderValue4Filter::OnPreprocHeaders(CHttpFilterContext* pCtxt,

           PHTTP_FILTER_PREPROC_HEADERS pHeaderInfo)

    {

           // TODO: React to this notification accordingly and

           // return the appropriate status code

           // This line below would add a Reponse Header (..duh…just like it says)

           //pCtxt->AddResponseHeaders("X-CustomDebug: OnPreprocHeadersrn", 0);

           // Sample insert into the Requet header.  It will always put HTTP_ in the front

           //pHeaderInfo->SetHeader( pCtxt->m_pFC, "USERNAME:", "xxxx\xxxx");

       // Check to see if the globals have

       // been loaded. If not, then load them

    //    if (!hasLoadedConfig) {

                   // Change this from a BOOL to a LastUpdated::DateTime check on the file

                   // to allow for real-time file updates.  No need to restart the web

                   // site/pool to re-read the file

           // Load the globals from a file

           /*

                   DWORD           dwRet = HSE_STATUS_SUCCESS;

                   BOOL            fRet = FALSE;

                   CHAR            pBuf[ DEFAULT_BUFFER_SIZE ];

                   CHAR *          pszBuf = pBuf;

                   DWORD           cbBuf = DEFAULT_BUFFER_SIZE;

                   fRet = pCtxt->GetServerVariable("INSTANCE_ID", pszBuf, &cbBuf);

           */

                   /*      

                           Got the Instance ID (but not checking for a failure) in pszBuf

                           Now Read the specific HeaderValue.Instance.Ini and

                           Find the Config Value "Headers", then split on | and add each

                           to the Headers (escaping "")

                                   pHeaderInfo->SetHeader( pCtxt->m_pFC, "USERNAME:", "xxxx\xxxx");

                   */

           //  This section is the partial working one

                   CIniReader iniReader(CONFIG_FILENAME);

                   char *szGLame = iniReader.ReadString("Global", "Keys", "");

                   //char input[] = _T("Tom,Archer,Programmer/Trainer,CodeGuru");

                   char input[ DEFAULT_BUFFER_SIZE ];

                   strcpy(input, szGLame);

                   char delimiters[] = _T(",");

                   pHeaderInfo->SetHeader( pCtxt->m_pFC, "ABC-HeaderValue4:", "Alpha Release");

                   pCtxt->AddResponseHeaders("X-ABC-HeaderValue4: Alpha Releasern", 0);

                   //pHeaderInfo->SetHeader( pCtxt->m_pFC, "Global:", szGLame);

                   char tmpResult[100];

                   char *result = NULL;

                   result = strtok( input, delimiters );

                   while( result != NULL ) {

                           char* val =  iniReader.ReadString("Setting", result, "");

                           strncpy(tmpResult, result, 99);

                           strcat(tmpResult, ":");

                           pHeaderInfo->SetHeader( pCtxt->m_pFC, tmpResult, val);

                           //printf( "result is "%s"n", result );

                           result = strtok( NULL, delimiters );

                   }

                   // Add Them

                   //pHeaderInfo->SetHeader( pCtxt->m_pFC, "W3C_INSTANCE_ID:", pBuf);

           hasLoadedConfig=true;

    //    }

           return SF_STATUS_REQ_NEXT_NOTIFICATION;

    }

    /*

           BOOL WINAPI GetServerVariable(

                     PHTTP_FILTER_CONTEXT pfc,

                     LPSTR lpszVariableName,

                     LPVOID lpvBuffer,

                     LPDWORD lpdwSize

           );

    */

    DWORD CHeaderValue4Filter::OnEndOfNetSession(CHttpFilterContext* pCtxt)

    {

           // TODO: React to this notification accordingly and

           // return the appropriate status code

           return SF_STATUS_REQ_NEXT_NOTIFICATION;

    }

    // Do not edit the following lines, which are needed by ClassWizard.

    #if 0

    BEGIN_MESSAGE_MAP(CHeaderValue4Filter, CHttpFilter)

           //{{AFX_MSG_MAP(CHeaderValue4Filter)

           //}}AFX_MSG_MAP

    END_MESSAGE_MAP()

    #endif  // 0

    ///////////////////////////////////////////////////////////////////////

    // If your extension will not use MFC, you’ll need this code to make

    // sure the extension objects can find the resource handle for the

    // module.  If you convert your extension to not be dependent on MFC,

    // remove the comments arounn the following AfxGetResourceHandle()

    // and DllMain() functions, as well as the g_hInstance global.

    /****

    static HINSTANCE g_hInstance;

    HINSTANCE AFXISAPI AfxGetResourceHandle()

    {

           return g_hInstance;

    }

    BOOL WINAPI DllMain(HINSTANCE hInst, ULONG ulReason,

                                           LPVOID lpReserved)

    {

           if (ulReason == DLL_PROCESS_ATTACH)

           {

                   g_hInstance = hInst;

           }

           return TRUE;

    }

  31. Pratheesh says:

    Hi David,

    Is there a way to determine the physical file location of the URL in MFC?

    I am not using asp.net so that I can’t use Server.MapPath().

    Thanks,

    Pratheesh