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;
}

Comments (14)

  1. Louis says:

    David,

    thank you for your view on the subject!

    This morning I found a solution to the problem.

    When I set the Isolation Mode for IIS6.0 to the IIS5.0 compatibility mode, the problem is gone.

    It clears the fact that the GetServerVariable could be used incorrectly. But why does it not work in IIS6.0 Isolation mode?

  2. David Wang says:

    Can you give whether this is on Windows Server 2003 RTM, with patches, or SP1.

    Also, please give the other five pieces of information that I identified at the top of this blog entry (exact server variable name, buffer sizes, return value of GetServerVariable, and if FALSE, value of GetLastError()).

    Finally, is the GetServerVariable() call made in an ISAPI Filter on ISAPI Extension? And if it is made from a filter, in what filter event(s)?

    Without this basic information, it will be difficult to determine what you are observing.

    For example, some server variable values are invalid and others may not be available, depending on IIS6 process model. But, I am not going to guess — If you do not tell me exactly what you are doing, I cannot give any more information.

    //David

  3. Louis says:

    Hi David,

    i’ve asked the programmer to look at this posting. I hope you two will figure it out 😉

    The server i’m using is Windows 2003 WebEdition SP1 with all the Patches installed.

    The ISAPI is an Extension and the ServerVariable I want is configurable. So I can ask through the ISAPI whatever variable I want.

    regards,

    Louis

  4. Louis says:

    Ok… this one is going to make is more difficult.

    I told you earlier that I was running a WebEdition. I am, but I was testing the Isolation mode with a Windows 2003 SBS.

    The ISAPI works on the Windows 2003 SBS with IIS5.0 Isolation mode active.

    The ISAPI does not work on the WebEdition either with or without the Isolation mode active.

  5. David Wang says:

    Louis – since I am trying to help you, but you refuse to give me the information I need, there is nothing more I can do.

    I consider this a non-issue until you can provide substantial information to prove otherwise.

    //David

  6. Louis says:

    And I thank you for your help!!

    Here are some answers to your questions, provided by the programmer.

    If you need more, I’ll notice! 😉

    1. The exact name of the ServerVariable being retrieved.

    "SERVER_NAME", "LOCAL_ADDR", "PATH_INFO", etc.

    2. The value of the buffer pointer. Is it NULL or points to valid memory.

    In the call, it is just "buf", where buf is an automatic variable

    declared in the function as:

    char buf[MAX_SERVER_VAR];

    and MAX_SERVER_VAR has been defined as:

    #define MAX_SERVER_VAR 2048

    (If the call is successful, the data is moved to dynamically

    allocated memory of the necessary size.)

    3. The value of the DWORD being passed in. Is it 0 or something else

    In the call, it is "&size", where size is initialized in the

    declaration as:

    DWORD size = MAX_SERVER_VAR;

    (This is an automatic variable in a function that retrieves a

    single server variable, so it is declared on each call.)

    4. The return value from the GetServerVariable call. Is it TRUE or FALSE

    FALSE

    5. If the return value is FALSE, what does GetLastError() return?

    ERROR_INVALID_INDEX (which the function reports as "ERROR: undefined

    server variable").

  7. David Wang says:

    1. Since you have the name parameterized on the GetServerVariable() call, are you sure that named string is NULL-terminated when used by GetServerVariable().

    2. Is the server variable name all upper-case (since you are parsing out a parameterized value).

    3. Any other stray random characters involved with the parameterized name – what happens if you try a fixed value for the variable name like "SERVER_NAME" (or whatever fails for you) in your ISAPI (and change no other code) – does that work or not.

    //David

  8. Louis says:

    David,

    sorry for the delay

    1. Since you have the name parameterized on the GetServerVariable() call, are you sure that named string is NULL-terminated when used by GetServerVariable().

    The variable names are not parameterized; they are null-terminated strings.

    2. Is the server variable name all upper-case (since you are parsing out a parameterized value).

    "One problem I found with the ISAPI under IIS 6.0 was that server variable names need to be all uppercase, so my function case-shifts the name that’s passed in before calling GetServerVariable. The script works on other IISs (and on the system where it doesn’t

    work, the CGI version running that same script can get the "environment" variables by those names), it would appear that you have the names correct, with no extraneous characters.

    3. Any other stray random characters involved with the parameterized name – what happens if you try a fixed value for the variable name like "SERVER_NAME" (or whatever fails for you) in your ISAPI (and change no other code) – does that work or not.

    The ISAPI automatically sets up several standard HTTP variables when it

    initializes, using fixed literal strings such as "PATH_INFO", those strings work when I run the script

  9. David Wang says:

    Hmm… if it works with fixed literal string of "PATH_INFO" but fails with your null-terminated strings that are parsed, I have to say the problem still sits with your strings somehow. Are they CHAR, TCHAR, or WCHAR?

    You said the GetServerVariable() call fails with ERROR_INVALID_INDEX. And the same string, but as fixed literal string, works.

    Oh, and is this call made within ISAPI Extension or ISAPI Filter, and if ISAPI Filter, what filter event(s)?

    And you say that the string are null terminated, correct case, and no extraneous characters. So it should work but doesn’t.

    Weird.

    Well, I’m sorry but I have no further suggestions. Your claims are mysterious but do not indicate any problems with IIS6.

    //David

  10. David Wang says:

    Question:

    I’m trying to write a Filter that handles writing a W3C-compliant log file based on a special…

  11. dazhou says:

    老王,这与编译配置有关。

    project property-> C++->code generation->basic runtime checks:

    both    (as debug)  can get ServerVariables.

    project property-> C++->code generation->basic runtime checks:

    both    (as debug)  can get ServerVariables.

    default  can’t get ServerVariables.

  12. Murat says:

    Louis – Do you use a second function to call GetServerVariable()?

    If you use the "ECB.GetServerVariable" directly in HttpExtensionProc there will be no problem! But if you use a function like GetMyVariable(ECB) in HttpExtensionProc and that function calls "GetServerVariable" using the parameter "ECB" it may fail!

    In GetMyVariable you have to define ECB as a variable parameter then it works. But if you define it as a value parameter it doesnt work.

  13. Bruno Marotta says:

    Hi,

    is it possible to set an environment variable? I am trying to set the remote_addr to the x-forwarded-for http header with no success…

  14. Karim Jebali says:

    is it possible to set an environment variable?