HOWTO: Use ISAPI GetServerVariable

Ah, yes... GetServerVariable is one of the most frequently used function call of ISAPI Extension and ISAPI Filter APIs, but unfortunately, it is also one of the most incorrectly used.

Very few people heed the documentation to check the return value and handle errors correctly, and considering this API call reads data from the client, it is a very dangerous thing to do...

Question:

Hi David,

I found your page when searching for a solution on a problem I'm having.

I was hoping you could tell me something about it.

I hope you have time to take a look at it.

The situation:

I use an ISAPI application to get some ServerVariables. As far as I know the programmer uses the GetServerVariable.

In my IIS6.0 (Server 2003 WebEdition) this doesn't work. I've tried it on a IIS5.1 (Windows XP Pro system) and it does work.

So there must be something with IIS6.0

Do you know about any issues regarding IIS6.0 and the GetServerVariable ?

The strange thing is that when we use the ie. UNICODE_REMOTE_ADDR we get a value. But the ISAPI program isn't written for UNICODE.

Besides the scripts we create using the ISAPI code must work on all IIS systems.

We also have a CGI version, and that works with no problems.

I hope you have a solution....

Answer:

I am not aware of any issues with GetServerVariable on any IIS version.

If you think there is an issue, please provide the following information so that we can reproduce it:

  1. The exact name of the ServerVariable being retrieved. i.e. ALL_RAW, REMOTE_ADDR, HTTP_HOST, etc
  2. The value of the buffer pointer. Is it NULL or points to valid memory.
  3. The value of the DWORD being passed in. Is it 0 or something else
  4. The return value from the GetServerVariable call. Is it TRUE or FALSE
  5. If the return value is FALSE, what does GetLastError() return?

Now, let's get to the details of what you have asked...

  1. UNICODE_REMOTE_ADDR (or any of the Unicode Server Variables) simply means that the byte buffer returned from GetServerVariable should be treated as WCHAR instead of CHAR. It has nothing to do with whether the ISAPI program is written for Unicode or not. In fact, the ISAPI interface will always stay ANSI for backward and forward compatibility.

    However, the ISAPI API in IIS 6.0 has been improved to support I18N and globalization because it allows you to retrieve and use Unicode strings in all the necessary places - retrieving server variables (like the URL), rewriting URLs, etc.

    For example, it is now possible to successfully manipulate and rewrite URLs that contain characters from multiple non-system locales.

  2. It is perfectly possible to write an ISAPI that works on all IIS versions yet still leverage the benefits of each. The fact that the ISAPI interface stayed ANSI ensures that the same binary can be loaded from IIS4 through IIS7. Also, you can easily detect the version of IIS from GetExtensionVersion and easily conditionalize your ISAPI function calls to take advantage of IIS6 functionality to be fully I18N and globalization ready when run on IIS6, yet still run fine on older IIS versions.

  3. The fact your CGI works is irrelevant to this discussion. According to CGI 1.1 specification, CGIs retrieve Server Variables from the process environment, so IIS only populates a limited subset of all its Server Variables as required for CGI 1.1 compatibility. Meanwhile, ISAPI GetServerVariable directly reads from the full set of IIS Server Variables, which has nothing to do with the process environment which is manipulated via a different API.

Now, I can only offer empirical evidence with my test ISAPI Extension DLL which retrieves all possible server variables (including Unicode versions), and the exact same binary loads and runs just fine on all IIS versions from IIS4 through IIS7. I realize that you believe your programmer writes perfectly flawless and bug-free ISAPI code, but please humor me and show me some better evidence to back up your conclusion of "so there must be something [wrong] with IIS 6.0" with regards to GetServerVariable. :-)

For example, the fact that something "worked" on IIS 5.1 is insufficient. Your programmer could have written code that looks like:

 if ( OS_Version != "5.1" )
{
    // Fail
}
else
{
    // Succeed
}

This code also works on IIS 5.1 but fails on IIS 6.0, but I think we both agree that it would not be correct to conclude that "there must be something [wrong] with IIS 6.0".

Below, I am going to show below the canonical way to use GetServerVariable. I look forward to your reply.

//David

 #define DEFAULT_BUFFER_SIZE         1024
#define MAX_BUFFER_SIZE             4096

...

DWORD
WINAPI
HttpExtensionProc(
    EXTENSION_CONTROL_BLOCK *       pecb
)
{
    DWORD                           dwRet = HSE_STATUS_SUCCESS;
    BOOL                            fRet = FALSE;
    CHAR                            pBuf[ DEFAULT_BUFFER_SIZE ];
    CHAR *                          pszBuf = pBuf;
    DWORD                           cbBuf = DEFAULT_BUFFER_SIZE;

    fRet = pfc->GetServerVariable( pecb->ConnID, "ALL_RAW", pszBuf, &cbBuf );
    if ( fRet == FALSE )
    {
        //
        // Buffer is not large enough.
        // Reallocate but bound it to MAX_BUFFER_SIZE
        //
        if ( GetLastError() == ERROR_INSUFFICIENT_BUFFER &&
             cbBuf < MAX_BUFFER_SIZE )
        {
            pszBuf = new CHAR[ cbBuf ];
            if ( pszBuf == NULL )
            {
                SetLastError( ERROR_NOT_ENOUGH_MEMORY );
                goto Finished;
            }

            fRet = pfc->GetServerVariable( pecb->ConnID,
                                           "ALL_RAW",
                                           pszBuf,
                                           &cbBuf );
            if ( fRet == FALSE )
            {
                //
                // Unexpected failure. Bail.
                //
                goto Finished;
            }
        }
        else if ( GetLastError() == ERROR_INVALID_INDEX )
        {
            //
            // Did not find the named Server Variable.
            // May be an error or not. Depends on your logic.
            //
        }
        else
        {
            //
            // Something unexpected happened. Bail.
            //
            goto Finished;
        }
    }

    //
    // At this point, pszBuf points to the variable value and
    // cbBuf indicates size of buffer, including terminating NULL.
    //

...
    SetLastError( NO_ERROR );

Finished:

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

    if ( GetLastError() != NO_ERROR )
    {
        //
        // Handle the error case. Send back 500, etc.
        //
        dwRet = HSE_STATUS_ERROR;
    }

    return dwRet;
}