HOWTO: Redirect the browser to a new URL based on Referer

While I do not recommend users to freely write ISAPI Filters and install them on IIS, there are still tasks that favor/require ISAPI Filter... even on IIS6 with HSE_REQ_EXEC_URL.

Question:

Hello David,

I came across your blog several times when I am searching for "ISAPI extensions and filters for IIS6". As a newbie on ISAPI and C++, I am really confused and hope that you can provide me with some insights......

In IIS 6 default processing mode, it does not support filters which subsribed to the SF_NOTIFY_READ_RAW_DATA event and the MSDN recommandation would be to write an ISAPI extension that does wildcard application mapping and make use of the new HSE_REQ_EXEC_URL function.

I noticed that your blog also has some samples regarding ISAPI filters, do you have any samples\resources for creating ISAPI Extension? Especially how to use this HSE_REQ_EXEC_URL function? (All I want to do is to intercept the web request, check the referer info and modify a query string on the URL and output the new URL onto the client's URL address bar.)

Before I go on and create this extension object, do you know anything that I should be aware of (like side impacts)?

Long email...... hope you don't mind

Regards,

Answer:

Actually, what you want to do can be accomplished with either ISAPI Filter or ISAPI Extension. For just the tasks you stated, I recommend ISAPI Filter; if there are other related tasks, it may change my recommendation... but I do not have that information from you at the moment.

In other words, ISAPI Filter can easily read/modify all parts of the request except for entity body, and the entity-body is what requires SF_NOTIFY_READ_RAW_DATA. Since you only want to read the Referer header and then send back a 302 response with a Location header that has the querystring modified (there is no way for a server to change the client's URL bar without the client making a new request - so that is why you send back a 302 response instead of directly changing the URL on the server), this can easily be done with an ISAPI Filter. SF_NOTIFY_READ_RAW_DATA is not involved here, so there is no prejudice against the ISAPI Filter approach.

Of course, you can also do this with an ISAPI Extension configured as a wildcard application mapping which reads the Referer header from the ALL_RAW server variable and then either sends back a 302 response or calls HSE_REQ_EXEC_URL to pass the request along.

Personally, the tasks you want to perform are easily done with an ISAPI Filter whereas you need to do parsing and know how to do asynchronous programming with an ISAPI Extension. Which one you prefer is completely up to you.

The ISAPI Filter Route

If you want to go the ISAPI Filter route, then I have a sample ISAPI Filter which uses the Referer header to determine whether to do some action. The action that you want to perform is to send back a 302 Redirection with the new Location header. My sample filter already has code showing how to send back a "403 Forbidden" with some custom headers - you just need to modify it send back a "302 Redirection" (easy) and then a "Location: <new-url-with-querystring>" header. Your code modifications should look something like:

 #define MAX_URL_LENGTH 1024


    CHAR        szUrl[] = "url";
    CHAR        szBuf[ MAX_PATH ];
    CHAR *      pszUrl = szBuf;
    BOOL        fRet = FALSE;
    DWORD       cbUrl = MAX_PATH;

    //
    // First try to retrieve the URL with max
    // buffer size of MAX_PATH
    //
    fRet = pfc->GetHeader( szUrl, pszUrl, &cbUrl );
    if ( FALSE == fRet )
    {
        //
        // Failed to retrieve URL because it was
        // longer than MAX_PATH. If the URL is
        // less than MAX_URL_LENGTH, try again.
        // Otherwise, fail.
        //
        if ( ERROR_INSUFFICIENT_BUFFER == GetLastError() &&
             cbUrl < MAX_URL_LENGTH )
        {
            //
            // Always check return value of memory
            // allocation for failures so that
            // you do not crash
            //
            pszUrl = new CHAR[ cbUrl ];
            if ( NULL == pszUrl )
            {
                SetLastError( ERROR_NOT_ENOUGH_MEMORY );
                goto Finished;
            }

            fRet = pfc->GetHeader( szUrl, pszUrl, &cbUrl );
            if ( FALSE == fRet )
            {
                goto Finished;
            }
        }
        else
        {
            //
            // Failed to retrieve URL -- fail
            // since we cannot redirect without it
            //
            goto Finished;
        }
    }

    //
    // At this point, pszUrl contains the desired URL.
    // You can do whatever you want to manipulate the
    // querystring to come up with the desired Location:
    // header to send on SF_REQ_SEND_RESPONSE_HEADER
    //
...

Finished:

    if ( pszUrl != szBuff &&
         pszUrl != NULL )
    {
        delete pszUrl;
        pszUrl = NULL;
    }

The ISAPI Extension Route

If you want to go the ISAPI Extension route, then there is already a Wildcardmap sample in the IIS Platform SDK that does the bulk of what you need. All you need to do is add the following logic prior to the HSE_REQ_EXEC_URL invocation:

  1. Call GetServerVariable to retrieve the ALL_RAW variable and parse it for the Referer: header

  2. If it matches your criteria:

    Call GetServerVariable to retrieve the URL, PATH_INFO, and QUERY_STRING variables, manipulate the querystring, and generate the necessary Location header.

    Call HSE_REQ_SEND_RESPONSE_HEADER to send the 302 redirection

  3. Otherwise, call HSE_REQ_EXEC_URL

See the following blog entry on how to use GetServerVariable correctly.

Conclusion

For your task of using the Referer request header to send back a 302 redirection with a custom Location header, both ISAPI Filter and ISAPI Extension can be used. The facilities for request manipulation are a bit better in ISAPI Filter in this regard, so it is my preferred approach. However, if there are other arbitrary tasks that need to be simultaneously performed, it may change my preference.

//David