Chapter 8

Using Extension Capabilities


The two examples in this chapter should give you a feel for the capabilities of an Internet server application programming interface (ISAPI) extension. The first example introduces you to a comment entry form and shows you how to parse data sent by the client to the extension.

The second example prints out information on how you called the extension. This shows you how the extension has affected Microsoft's Internet Information Server (IIS) variables.

Handling GET Parameters

When a form uses the GET method to pass information to the extension, it is called like this:

http://www.fred.com/extension.dll?param1=value1&param2=value2

When the extension is executed, it reads the information either by reading the server variable QUERY_STRING or by looking at pECB->lpszQueryString. Working with pECB->lpszQueryString is the preferred way of reading the data because you don't have to call any functions to receive the data.

The snippet of code in Listing 8.1 shows how you can tell that GET is the request method used.

Listing 8.1 IISEMAIL.CPP-How to Determine a GET Request Method

DWORD
HttpExtensionProc( EXTENSION_CONTROL_BLOCK *pECB )

{
CHAR *lpszQuery = NULL;

// get the data from the form
if( 0 == stricmp(pECB->lpszMethod, "get") )
lpszQuery = pECB->lpszQueryString;

....................

}

But you still have some work to do before you can use the query string.

Contents of lpszQueryString

lpszQueryString is the equivalent of the server variable QUERY_STRING (explained later in this chapter). Like the server variable, lpszQueryString is encoded into name/value pairs.

Fig. 8.1

Comment entry page.

If you take the HTML form shown in Figure 8.1 as an example, you end up with a query string like this,

FirstName=Martin&LastName=Norman&WebUse=Web+Surfer&FromEMail=&HomePage=http%3A%2F%2Fstaff.ndl.co.uk%2Fmartin&text=

in which FirstName=Martin is a name/value pair.

Parsing URL-Encoded Parameters into Name/Value Pairs

Now that you have the query string, you have to parse it to turn it into something meaningful.

In this example, we first extract the names in the form. These are:

The name is separated from the value by the '=' character: for example, FirstName=Martin. So first you scan the query string for an occurrence of FirstName. If you take the '=' character into account, you have the start of the value.

The end of the value is either the "&"character or the end of the buffer. The function shown in Listing 8.2 returns the value part of the name/value pair.

Listing 8.2 PARSE.CPP-Getting One Name/Value Pair Out of the Query String

CHAR * CIissmtpExtension::

GetParamValue(CHAR *lpszQuery, CHAR *lpszParam)

{

CHAR *pValueStart = NULL;

CHAR *pValueEnd = NULL;

CHAR *lpszValue = NULL;

CHAR *szTemp1 = NULL;

ULONG cbValue;

pValueStart = strstr( lpszQuery, lpszParam );

if( !pValueStart ) // parameter doesn't exist

return NULL;

pValueStart += strlen( lpszParam ) + 1;

// Now determine the length of the value string.

pValueEnd = strchr(pValueStart, '&');

if(pValueEnd)

cbValue = pValueEnd - pValueStart;

else

// this was the last param in the list

cbValue = strlen(pValueStart);

// Return NULL if we have zero length string.

if( !cbValue )

return NULL;

if( !(lpszValue = (CHAR *)LocalAlloc(LPTR, cbValue + 1) ) )

return NULL;

strncat(lpszValue, pValueStart, cbValue);

szTemp1 = lpszValue;

while( * szTemp1 )

{

if( *szTemp1 == '+' )

*szTemp1 = ' ';

szTemp1++;

}

EscapeToAscii(lpszValue);

return lpszValue;

} // GetParamValue

In the query string example above, notice that the HomePage parameter has a lot of odd characters in it:

HomePage=http%3A%2F%2Fstaff.ndl.co.uk%2Fmartin

Some characters are reserved and can't be used. So the client converts them to hex values, denoted by the '%' character. All spaces are converted to the '+' character.

In Listing 8.2, you saw a reference to a function called EscapeToAscii. This function does the conversion from hex values to ASCII characters for you (see Listing 8.3).

Listing 8.3 PARSE.CPP-Converting a Hex Value into a Character

void CIissmtpExtension::

EscapeToAscii(CHAR *lpEscape)

{

int i, j;

for( i = 0, j = 0; lpEscape[j] ; ++i, ++j )

{

if( (lpEscape[i] = lpEscape[j]) == '%' )

{

lpEscape[i] = HexToAscii( &lpEscape[j+1] );

j+=2;

}

}

lpEscape[i] = '\0';

}

//----------------------------------------------------------------------

CHAR CIissmtpExtension::

HexToAscii(CHAR *lpString)

{

CHAR CH;

CH = (lpString[0] >= 'A' ? ( (lpString[0] & 0xDF) - 'A' ) + 10 : (lpString[0] - '0') );

CH *= 16;

CH += (lpString[1] >= 'A' ? ( (lpString[1] & 0xDF) - 'A' ) + 10 : (lpString[1] - '0') );

return CH;

}

Now you have converted your query string into something you can use.

Handling POST Parameters

Reading data that was sent using the POST request method is slightly more complex than reading data that was sent using the GET method. But it's worth it. You can read considerably more data from the client using the POST method and that data can be binary.

Listing 8.4 IISEMAIL.CPP-How to Determine a Post Method

DWORD
HttpExtensionProc( EXTENSION_CONTROL_BLOCK *pECB )

{
CHAR *lpszQuery = NULL;

// get the data from the form
if( 0 == stricmp(pECB->lpszMethod, "post") )

{

// extract post data here
....................
....................
....................
....................

}
....................
....................
....................
....................
}

The first 48 kbytes of data (if you have that much) from the POST request are pointed to by the EXTENSION_CONTROL_BLOCK pointer lpbData, with the total amount of data available shown by cbTotalBytes.


This 48 kBytes is a limitation of Microsoft's IIS. Different servers may have a different value.

In version 3.0 of Microsoft's IIS, the value is configurable. The default is still 48 kBytes. To change the default, you have to change the following registry value under

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\w3svc\parameters\UploadReadAhead


The value range for this parameter is 0 - 0x80000000

The maximum amount of data you can send using this request method is 4 Gbytes. Sending the full amount of data is covered later in this chapter.

There is no end-of-file (EOF) at the end of the lpbData buffer.


If the data is not binary, it is URL-encoded just like the GET method. So you have to parse the data. Then you can decode the data.

Difference Between cbTotalBytes and cbAvailable

The content of cbTotalBytes is the equivalent of the server variable CONTENT_LENGTH (explained later in this chapter). If its value is 0xffffffff, then the connecting client has just sent the over 4 Gbytes of data to the extension.

The contents of cbAvailable are the number of bytes immediately available (out of a total of cbTotalBytes). To read the remaining data, you use the ReadClient function, which is covered in the next section.

Both of these values refer to the content of the lpbData buffer. The maximum amount of data this can hold depends on the implementation of the ISAPI interface you are using. In Microsoft's IIS, the default value is 48 kbytes. But this value can be changed using the registry.

Getting the Full POST Buffer

Read the data to check if cbAvailable is equal to cbTotalBytes. If it is, just make a copy of the contents of lpbData. If cbAvailable is less than cbTotalBytes, then use ReadClient() until all the data has been read.

Listing 8.5 IISEMAIL.CPP-How to Get the Whole POST Buffer

// Start processing of input information here.
lpszTemp = (CHAR *)LocalAlloc( LPTR, pECB->cbTotalBytes);
if( NULL == lpszTemp )
return HSE_STATUS_ERROR;

// do not always trust the compiler to zero the memory
memset(lpszTemp, '\0', pECB->cbTotalBytes);
strncpy(lpszTemp, (CHAR *)pECB->lpbData, pECB->cbAvailable );
cbQuery = pECB->cbTotalBytes - pECB->cbAvailable;

if( cbQuery > 0 )
{
pECB->ReadClient(pECB->ConnID,
(LPVOID)(lpszTemp + pECB->cbAvailable),
&cbQuery);

}
lpszQuery = lpszTemp;

Using lpszContentType

The content type entry shows the type of data sent by the client. It is only used when the request method is the POST.

lpszContentType is the equivalent of the server variable CONTENT_TYPE.

When you use Internet Explorer 3.0 to send a POST request, lpszContentType is typically set to application/x-www-form-urlencoded.

Normally, the client browser sets the content type to ASCII. But it could be changed at the client side if you had to send binary data, for example.

Getting Server Variables

To extract a server variable, you use the GetServerVariable, which is one of the functions in the EXTENSION_CONTROL_BLOCK. GetServerVariable is the equivalent to the getenv CGI function.

It is worth noting that the EXTENSION_CONTROL_BLOCK includes some server variables, as shown in Table 8.1.

Table 8.1 Compatible Entries

Control block

Server variable

lpszMethod

REQUEST_METHOD

lpszQueryString

QUERY_STRING

lpszPathInfo

PATH_INFO

lpszPathTranslated

PATH_TRANSLATED

cbTotalBytes

CONTENT_LENGTH

It is far more efficient to look at these variables using the EXTENSION_CONTROL_BLOCK because it does not include any function calls. To use GetServerVariable(), you pass it the current connection ID, the variable name you want, a buffer address to receive the contents of the variable, and the size of the buffer you are using.

You must use a pointer to a DWORD for the size of the buffer. This is because when it is completed, the DWORD is set to the amount of transferred data, including a null terminating byte.

Listing 8.6 VARIABLES.CPP-How to Get a Server Variable

DWORD cbdwbuff2;

CHAR ServerPort[20] = { 0 };

cbdwbuff2 = 10;

if( FALSE == pECB->GetServerVariable(pECB->ConnID, "SERVER_PORT", &ServerPort, &cbdwbuff2) )

{

DisplayError( GetLastError() );

}

If the call fails, GetServerVariable returns a value of FALSE. You can find the reason for the error by calling GetLastError.


Table 8.2 Possible Error Returns from GetServerVariable

Value

Meaning

ERROR_INVALID_PARAMETER

Bad connection handle

ERROR INVLAID_INDEX

Bad or incompatible variable identifier

ERROR_INSUFFICIENT_BUFFER

Buffer too small, needed size returned in *lpdwSize

ERROR_MORE_DATA

Buffer too small, only part of data returned. The total size of the data is not returned.

ERROR_NO_DATA

The data request is not avalable.

What Are Server Variables?

You can use server variables to supply information about the currently connected client and about the server itself. For example, you can learn the Internet protocol (IP) address of the client that has just called your extension or the type of browser used.

Listed below are the server variables, and a simple extension that shows you how POST and GET request methods affect server variables.

An overview of most server variables can be found in the CGI specification. This is maintained by the NCSA Software Development Group at http://hoohoo.ncsa.uiuc.edu/cgi/env.html.


Information from the Browser on Every Request

Server variables supply information about where and what the browser is. The variables with this information usually start with REMOTE_<type>-for example, REMOTE_ADDR or REMOTE_USER.

Other variables also supply client information such as HTTP_USER_AGENT, as explained later in this chapter.

Some Server-Specific Variables

Some variables provide information about the server that the extension is running on.

For example you might being writing a secure extension that has to run on only one server machine. For this, you could use the SERVER_NAME variable to check the machine name.

Or your extension might need a certain version of IIS to be running. For this, you would check the SERVER_SOFTWARE variable.

Useful for Configuring Custom Results

Different browsers have different capabilities. For example, Internet Explorer 1.x does not allow frames or tables, and Netscape 2.x does not allow scripts such as Visual Basic. So when you run an extension, you want to give that client the best output possible.

The extension can detect the type of browser connected by looking at the HTTP_USER_AGENT server variable, as shown in Table 8.3.


Table 8.3 Examples of Browser Type and Variable Setting

Browser

HTTP_USER_AGENT

IE 2.0

MSIE 2.0/Mozilla-Spoofer Mozilla/1.22 (compatible; MSIE 2.0; Windows 95)

IE 3.0

Mozilla/2.0 (compatible; MSIE 3.0; Windows NT)

Netscape 3.0

Mozilla/3.0 (WinNT; I)

Netscape 2.02

Mozilla/2.02 (Win16; I)

Information collector for excite

ArchitextSpider


Not everything that connects is a browser. There are a lot of spiders and robots out there.

Spiders and robots are programs that automatically search the Web by following hypertext links that retrieve all documents referenced.


A list of robots and spiders is at http://info.webcrawler.com/mak/projects/robots/robots.html


Server Variables

Listing 8.7 is the code that shows the contents of the server variables, and how POST and GET can influence them. The listing includes all the server variables mentioned in this chapter. Use the Hypertext Markup Language (HTML) form in Listing 8.8 to try the extension with both request methods.

Listing 8.7 VARIABLES.CPP-Function That Displays Server Variables

//----------------------------------------------------------------------------

BOOL

DisplayVariable(char *lpszQuery, EXTENSION_CONTROL_BLOCK *pECB )

{

DWORD dwErr = 0;

DWORD cbdwbuff2;

DWORD dwLen = 0;

CHAR tmpbuf[4000] = { 0 };

CHAR ServerSoftware[200] = { 0 };

CHAR ServerProtocol[200] = { 0 };

CHAR RemoteAddress[200] = { 0 };

CHAR RemoteHost[200] = { 0 };

CHAR RemoteUser[200] = { 0 };

CHAR HttpAccept[200] = { 0 };

CHAR HttpUserAgent[356] = { 0 };

CHAR AuthType[200] = { 0 };

CHAR ContentLength[200] = { 0 };

CHAR ContentType[200] = { 0 };

CHAR GatewayInterface[200] = { 0 };

CHAR PathInfo[200] = { 0 };

CHAR PathTranslated[200] = { 0 };

CHAR QueryString[200] = { 0 };

CHAR RequestMethod[200] = { 0 };

CHAR ScriptName[200] = { 0 };

CHAR ServerName[200] = { 0 };

CHAR ServerPort[200] = { 0 };

CHAR AuthPass[200] = { 0 };

CHAR AllHttp[200] = { 0 };

cbdwbuff2 = 200;

pECB->GetServerVariable(pECB->ConnID, "AUTH_TYPE", &AuthType, &cbdwbuff2);

cbdwbuff2 = 200;

pECB->GetServerVariable(pECB->ConnID, "CONTENT_LENGTH", &ContentLength, &cbdwbuff2);

cbdwbuff2 = 200;

pECB->GetServerVariable(pECB->ConnID, "CONTENT_TYPE", &ContentType, &cbdwbuff2);

cbdwbuff2 = 200;

pECB->GetServerVariable(pECB->ConnID, "GATEWAY_INTERFACE", &GatewayInterface, &cbdwbuff2);

cbdwbuff2 = 200;

pECB->GetServerVariable(pECB->ConnID, "PATH_INFO", &PathInfo, &cbdwbuff2);

cbdwbuff2 = 200;

pECB->GetServerVariable(pECB->ConnID, "PATH_TRANSLATED", &PathTranslated, &cbdwbuff2);

cbdwbuff2 = 200;

pECB->GetServerVariable(pECB->ConnID, "QUERY_STRING", &QueryString, &cbdwbuff2);

cbdwbuff2 = 200;

pECB->GetServerVariable(pECB->ConnID, "REQUEST_METHOD", &RequestMethod, &cbdwbuff2);

cbdwbuff2 = 200;

pECB->GetServerVariable(pECB->ConnID, "SCRIPT_NAME", &ScriptName, &cbdwbuff2);

cbdwbuff2 = 200;

pECB->GetServerVariable(pECB->ConnID, "SERVER_NAME", &ServerName, &cbdwbuff2);

cbdwbuff2 = 200;

pECB->GetServerVariable(pECB->ConnID, "SERVER_PORT", &ServerPort, &cbdwbuff2);

cbdwbuff2 = 200;

pECB->GetServerVariable(pECB->ConnID, "AUTH_PASS", &AuthPass, &cbdwbuff2);

cbdwbuff2 = 200;

pECB->GetServerVariable(pECB->ConnID, "ALL_HTTP", &AllHttp, &cbdwbuff2);

cbdwbuff2 = 200;

pECB->GetServerVariable(pECB->ConnID, "SERVER_PROTOCOL", &ServerProtocol, &cbdwbuff2);

cbdwbuff2 = 200;

pECB->GetServerVariable(pECB->ConnID, "SERVER_SOFTWARE", &ServerSoftware, &cbdwbuff2);

cbdwbuff2 = 200;

pECB->GetServerVariable(pECB->ConnID, "REMOTE_ADDR", &RemoteAddress, &cbdwbuff2);

cbdwbuff2 = 200;

pECB->GetServerVariable(pECB->ConnID, "REMOTE_HOST", &RemoteHost, &cbdwbuff2);

cbdwbuff2 = 200;

pECB->GetServerVariable(pECB->ConnID, "REMOTE_USER", &RemoteUser, &cbdwbuff2);

cbdwbuff2 = 200;

pECB->GetServerVariable(pECB->ConnID, "HTTP_ACCEPT", &HttpAccept, &cbdwbuff2);

cbdwbuff2 = 356;

pECB->GetServerVariable(pECB->ConnID, "HTTP_USER_AGENT", &HttpUserAgent, &cbdwbuff2);

//---------------------------------------------------

wsprintf(tmpbuf,"<p>\n"

"Auth Type = %s<br>\n"

"Content Length = %s<br>\n"

"Content Type = %s<br>\n"

"Gateway Interface = %s<br>\n"

"Path Info = %s<br>\n"

"Path Translated = %s<br>\n"

"Query String = %s<br>\n"

"Remote Address = %s<br>\n"

"Remote Host = %s<br>\n"

"Remote User = %s<br>\n"

"Request Method = %s<br>\n"

"Script Name = %s<br>\n"

"Server Name = %s<br>\n"

"Server Port = %s<br>\n"

"Server Protocol = %s<br>\n"

"Server Software = %s<br>\n"

"Auth Pass = %s<br>\n"

"All Http = %s<br>\n"

"HTTP ACCEPT = %s<br>\n"

"HTTP User Agent = %s<br>\n"

"</p>\n",

AuthType,

ContentLength,

ContentType,

GatewayInterface,

PathInfo,

PathTranslated,

QueryString,

RemoteAddress,

RemoteHost,

RemoteUser,

RequestMethod,

ScriptName,

ServerName,

ServerPort,

ServerProtocol,

ServerSoftware,

AuthPass,

AllHttp,

HttpAccept,

HttpUserAgent);

dwLen = lstrlen( tmpbuf );

pECB->WriteClient(pECB->ConnID, tmpbuf, &dwLen, dwLen );

return TRUE;

}

Listing 8.8 VARIABLES.HTM-HTML Code for Calling the variables.dll Extension

<html>

<head>

<title>Server Variables Display</title>

</head>

<body>

<h2>Get:</h2>

<form action="/scripts/variables.dll" method=get>

<INPUT NAME="param1" VALUE="" >

<input type="submit" value="Submit get Entry">

<input type="reset" value="Reset Form">

</form>

<hr>

<h2>Post:</h2>

<form action="/scripts/variables.dll" method=post>

<INPUT NAME="param1" VALUE="" >

<input type="submit" value="Submit post Entry">

<input type="reset" value="Reset Form">

</form>

</body>

</html>

Figure 8.2 is an example of running the form in Listing 8.8 with the GET request method.

Fig. 8.2

Results of calling the display variables extension using the GET request method.

Following is a list of the server variables.

AUTH_TYPE. If the user name has been authenticated, this variable shows the authentication method used to validate the user. There is no set list because methods can be added to the base methods. The base methods are shown in Table 8.4.

Table 8.4 The Three Methods Supplied with IIS

Method

Variable contents

Allow Anonymous

the variable is empty

Basic clear text

basic

Windows NT challenge/response

NTLM

CONTENT_LENGTH. The total number of bytes received from the client when the request method is POST.

CONTENT_TYPE. Used to tell the extension the type of data sent by the client when the request method is POST.

For example, if the variable example in Figure 8.2 was called using the POST method with foo=bar as the passed data, we would get the following from this variable:

Content Type = application/x-www-form-urlencoded

and the CONTENT_LENGTH variable would be :

Content Length = 22

GATEWAY_INTERFACE. The revision of the CGI specification this server complies with. For IIS, this is CGI/1.1. The format is CGI/revision.

PATH_INFO. A variable with extra path information as given by the client. This is the trailing part of the URL after the extension name but before the query string. If you use the variables extension again, call it like this:

http://staff/scripts/variables.dll/martin/?foo=var.

Figure 8.3 shows the output from the extension.

Notice that /martin/ is included between the end of the extension name and the query string.


Fig. 8.3

Results of the PATH_INFO example.

PATH_TRANSLATED. This is the translated version of PATH_INFO, which is the physical path mapping.
QUERY_STRING.The information following the "?" in the URL that called the script. This is the request information and it should not be decoded. This variable should always be set when there is request information, regardless of command line decoding.
REMOTE_ADDR. The IP address of the remote client making the request.
REMOTE_HOST. Normally set to the DNS host name of the connected client if the server can do DNS lookups. IIS does not do DNS lookup, so it is just set equal to REMOTE_ADDR.
REMOTE_USER. The user name supplied by the connected client that IIS has authenticated.
UNMAPPED_REMOTE_USER. A copy of the user name before any IIS filter has changed it-for example, by mapping the user name to an NT user account name.
REQUEST_METHOD. The method used by the connected client to make a request-for example, GET or POST.
SCRIPT_NAME. The name of the extension being executed.
SERVER_NAME. The server's host name, DNS alias, or IP address.
SERVER_PORT. The port number to which the request was sent. This is normally 80.
SERVER_PORT_SECURE. A Boolean value to indicate if the request is on the secure port. If the result is 1, the request is on the secure port. If the result is 0, it is not.
SERVER_PROTOCOL. The name and revision of the information retrieval protocol this request came from. For example, the server protocol from Figure 8.3 is HTTP/1.0, where the format is protocol/revision.
SERVER_SOFTWARE. The name and version of the IIS that is running. The format of the variable is name/version. For example, for IIS version 2.0, which comes with NT 4.0, the name and version would be Microsoft-IIS/2.0.
AUTH_PASS. The password supplied by the user. This is only supplied to a CGI script if the first character of its name is "$." To guard against scripts "stealing" user passwords, install $ scripts only from trusted sources.

Although AUTH_PASS is not used by IIS, it is possible to read the variable if AUTH_TYPE is BASIC.
You would have to query the HTTP_AUTHORIZATION header and then decode it.

ALL_HTTP. All HTTP headers that were not already parsed into one of the above variables. These variables are of the form HTTP_<header field name>. Following is an example of output from the variable:
HTTP_ACCEPT:image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, */*

HTTP_CONNECTION:Keep-Alive HTTP_HOST:staff HTTP_REFERER:http://staff/martin/variables.htm


HTTP_USER_AGENT:Mozilla/3.0 (WinNT; I)

HTTP_USER_AGENT. The browser the client is using to send the request. The format for this variable is software/version library/version. For a client running IE 3.0 on Windows 95, the variable would have Mozilla/2.0 (compatible; MSIE 3.0; Windows 95).

HTTP_ACCEPT. The MIME type the client accepts, as given by HTTP headers. Each item in this list should be separated by commas, according to the HTTP spec, and has the format type/subtype,type/subtype. So for an IE 3.0 connection, the variable could have

image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, */*

URL. Gives the base portion of the URL.

Writing to the Client

The two ways of writing to the client from the extension are using WriteClient and ServerSupportFunction.

ServerSupportFunction has limitations in that it can only send ASCII data to the client. It is used for sending completion status information and other header details to the client, as shown in Listing 8.9. An example is the content type of the data you are going to send to the client.

Listing 8.9 VARIABLES.CPP-Partial Listing from HttpExtensionProc


DWORD dwLen = 0;

CHAR szBuff[1024] = { 0 };

wsprintf( szBuff, "Content-Type: text/html\r\n\r\n");

dwLen = lstrlen( szBuff );

pECB->ServerSupportFunction( pECB->ConnID,

HSE_REQ_SEND_RESPONSE_HEADER, "200 OK", &dwLen, ( LPDWORD )

szBuff );

WriteClient just sends the stated number of bytes to the client so it can send both ASCII and binary data, as shown in listing 8.10. It can also be used for sending completion status information back to the client. But to do this, you would have to format the full header yourself.

Listing 8.10 VARIABLES.CPP-Writing Data to the Client

DWORD dwLen = 0;

CHAR szBuff[1024] = { 0 };

wsprintf(szBuff, "<body>");

dwLen = lstrlen( szBuff );

if( FALSE == pECB->WriteClient( pECB->ConnID, szBuff, &dwLen, dwLen) )

{

DisplayError( GetLastError() );

}

If the function returns FALSE, call GetLastError to see what the problem is.

Reading from the Client

If the request method was GET, all the data from the client can be read from the lpszQueryString buffer or from the QUERY_STRING server variable(as shown earlier in this chapter).

If the request method is POST, part or all of the data is in lpbData. If all the data is not in lpbData, you have to use ReadClient to read it.

Listing 8.11 VARIABLES.CPP-Reading Data Sent by the Client


CHAR *lpszTemp = NULL;

DWORD cbQuery = 0;

cbQuery = pECB->cbTotalBytes - pECB->cbAvailable;

lpszTemp = (CHAR *)LocalAlloc( LPTR, cbQuery);

if( FALSE == pECB->ReadClient(pECB->ConnID,

(LPVOID)lpszTemp,

&cbQuery) )

{

DisplayError( GetLastError() );

}

If the buffer size that is passed to ReadClient is more than the amount of available data, the function blocks until either the client sending the data sends it or the communications socket that the server is using is closed.

If the socket is closed before all the data is sent, ReadClient returns TRUE but with zero bytes read. If the function returns FALSE, call GetLastError to see what the problem is.

TerminateExtension

If you do anything special when you start your extension that needs to be cleaned up when the extension is unloaded, this function is for you. But it is only available in Microsoft's IIS version 3.0 or later.

The function is called just before the server unloads the extension. A bit-field parameter is passed to the function by the server, indicating whether the extension has a choice about unloading.


Table 8.5 TerminateExtension Bit-Field Values

Value

Meaning

HSE_TERM_ADVISORY_UNLOAD

The server wants to unload the extension. The extension can return TRUE if that is OK, or FALSE if the extension does not want to unload.

HSE_TERM_MUST_UNLOAD

The server wants to unload the extension and does not the extension a choice. At this point, the extension must clean up and be unloaded.

Using URL Redirection

You may want to redirect a connected client to another page on your server or even to another server all together. This section introduces you to the idea of redirecting a client to another URL without the user doing the connect.

Why Redirection?

There are any number of reasons why you would want to redirect automatically from an extension. You might want a menu selection for your Web pages. Or you might want special pages for different kinds of browsers.

Listing 8.12 is for a simple Web page menu system using a list box. You select the target you want and that information is passed to an extension called redir.

Listing 8.12 REDIR.HTM-HTML Page with a Page Redirect Menu


<html>

<head><title>Redirect URL</title></head>


<body>

<hr>

<h2>Redirect</h2>

<FORM ACTION="/scripts/redir.dll" target="_top">


<SELECT NAME="Target">

<OPTION SELECTED VALUE="/martin/">Default


<OPTION VALUE = "/martin/york/">City of York


<OPTION VALUE = "/martin/wetherby/">Wetherby


<OPTION VALUE = "/martin/isapi.htm">IPASI


<OPTION VALUE = "/martin/weather.htm">The Weather


<OPTION VALUE = "/martin/films-tv.htm">Films and TV

<OPTION VALUE = "/martin/otherbee.htm">Sites about Beer

<OPTION VALUE = "/martin/maps.htm">Sites about Maps

</SELECT>

<input type="submit" value="Select page">


</FORM>

<hr>

</body>

</html>

Using ServerSupportFunction() to Redirect

The extension in Listing 8.12 uses ServerSupportFunction to redirect to the target page. The target page is passed into the extension as a query. The query is parsed and passed to the ServerSupportFunction with the dwHSERequest type set to HSE_REQ_SEND.

Listing 8.13 shows how the target was parsed and then used.


Listing 8.13 REDIR.CPP-Partial Listing for the HttpExtensionProc()


//----------------------------------------------------------------------------


DWORD CRedirExtension::

HttpExtensionProc( EXTENSION_CONTROL_BLOCK *pECB )

{

CHAR *lpszQuery = NULL;

CHAR szNewUrl[1024] = { 0 };

DWORD dwLen = 0;

if( !stricmp(pECB->lpszMethod, "get") )

// GET

lpszQuery = pECB->lpszQueryString;

else

{

// POST

return HSE_STATUS_ERROR;

}

//---------------------------------------------------

if( NULL != GetParamValue(lpszQuery, "Target") )


lstrcpy(szNewUrl, GetParamValue(lpszQuery, "Target") );

//---------------------------------------------------

dwLen = lstrlen( szNewUrl );

pECB->ServerSupportFunction( pECB->ConnID,

HSE_REQ_SEND_URL, szNewUrl, &dwLen, (LPDWORD)NULL );

return HSE_STATUS_SUCCESS;

}

The redirection does not involve the client at all: the work is done by the server. This kind of redirect only work for the current server. You can't redirect to another server.


To do this you would have to use the HSE_REQ_SEND_URL_REDIRECT_RESP request type, which sends a 302 (URL redirection) message back to the client. The client automatically connects to the new URL, which was also passed back by the extension.

If we convert our menu example to this new method, the new HTML code is in Listing 8.14 and the HttpExtensionProc is in Listing 8.15.

Listing 8.14 URLREDIR.HTM-HTML Code from a Web Page Menu


<html>

<head><title>Redirect URL</title></head>


<body>

<hr>

<h2>Redirect</h2>

<FORM ACTION="/scripts/urlredir.dll" target="_top">


<SELECT NAME="Target">

<OPTION SELECTED VALUE="http://staff/martin/">Default


<OPTION VALUE = "http://staff/martin/york/">City of York

<OPTION VALUE = "http://staff/martin/wetherby/">Wetherby


<OPTION VALUE = "http://staff/martin/isapi.htm">IPASI


<OPTION VALUE = "http://staff/martin/weather.htm">The Weather

<OPTION VALUE = "http://staff/martin/films-tv.htm">Films and TV

<OPTION VALUE = "http://staff/martin/otherbee.htm">Sites about Beer

<OPTION VALUE = "http://staff/martin/maps.htm">Sites about Maps

<OPTION VALUE = "http://www.ndl.co.uk/">Network Designers

<OPTION VALUE = "http://www.exponet.co.uk">Exponet


</SELECT>

<input type="submit" value="Select page">


</FORM>

<hr>

</body>

</html>

Notice that unlike the HTML code in Listing 8.12, in the Listing 8.14 you have to put in the full new URL that we want to redirect to. You have to add the http:// for the redirection to work.

Listing 8.15 URLREDIR.CPP-HttpExtensionProc

//----------------------------------------------------------------------------


DWORD CUrlredirExtension::

HttpExtensionProc( EXTENSION_CONTROL_BLOCK *pECB )

{

CHAR *lpszQuery = NULL;

CHAR szNewUrl[1024] = { 0 };

DWORD dwLen = 0;

if( !stricmp(pECB->lpszMethod, "get") )

// GET

lpszQuery = pECB->lpszQueryString;

else

{

// POST

return HSE_STATUS_ERROR;

}

//---------------------------------------------------

if( NULL != GetParamValue(lpszQuery, "Target") )


lstrcpy(szNewUrl, GetParamValue(lpszQuery, "Target") );

//---------------------------------------------------

dwLen = lstrlen( szNewUrl );

pECB->ServerSupportFunction( pECB->ConnID,

HSE_REQ_SEND_URL_REDIRECT_RESP, szNewUrl, &dwLen, (LPDWORD)NULL );

return HSE_STATUS_SUCCESS;

}

From Here...

This chapter introduces you to the capabilities you can use to write ISAPI extensions, including:

The following chapters give you more information on how to expand the capabilities of your extensions.


© 1996, QUE Corporation, an imprint of Macmillan Publishing USA, a Simon and Schuster Company.