KEYS.CPP

/*++ 

Copyright (c) 1997 Microsoft Corporation

Module Name: keys.cpp

Abstract:

This module contains functions which deal with keys and values
on inbound form data. Please, see keys.h for details.

--*/
#define WIN32_LEAN_AND_MEAN // the bare essential Win32 API
#include <windows.h>
#include <httpext.h>

//
// If your application plans to receive huge amounts of data
// and you don't want megabytes of memory allocated, define
// the USE_TEMPORARY_FILES. It is recommended to leave the
// define line below commented out unless absolutely necessary.
//

//#define USE_TEMPORARY_FILES

#include "keys.h"

#ifndef USE_MEMORY

#ifndef USE_TEMPORARY_FILES
#define USE_MEMORY
#endif

#endif


//
// If you want to record errors, modify this macro definition to
// call your own logging function. This sample does not save
// error strings.
//
#define LOG(errorstring) // OutputDebugString( errorstring ); \
// OutputDebugString ( "\r\n" )

//
//
// Intended external interface:
//
// GetKeyList Determines if data was sent, and if it was, the
// data is extracted by GetPostKeys or GetUrlKeys,
// two private functions within this file. A
// pointer to a linked list is returned ( as a
// handle ).
//
// GetKeyInfo Returns a pointer to the key name,
// the length of the data, a flag indicating if
// the data has control characters in it, and an
// instance number for duplicate key names.
//
// GetKeyBuffer Returns a pointer to the buffer holding the key's
// data.
//
// FindKey Sequentially searches linked list for key name.
//
// FreeKeyList Deallocates memory used by the linked list of
// keys. Also deletes content resources.
//
// GetKeyOffset Returns the offset to either the content memory
// buffer or the content temporary file.
//
// GetContentFile Returns a pointer to the temporary file, only
// when USE_TEMPORARY_FILES is defined.
//
// CloseContentFile Closes the content file, normally left open
// until FreeKeyList is called, available only
// when USE_TEMPORARY_FILES is defined.
//
// OpenContentFile Reopens the content file for additional use
// by GetKeyBuffer, available only when
// USE_TEMPORARY_FILES is defined.
//
// GetDataBuffer Returns a pointer to the content, only if the
// USE_TEMPORARY_FILES constant is NOT defined.
//
// Helper functions called only in this source file:
//
// GetQueryByte Similar to GetPostedByte, this function
// extracts data from the query string.
//
// HexDigitToInt Returns the decimal value of a hex character.
//
// GetFirstByte Sets up POSDATA struct and calls GetNextByte.
// Caller specifies function used to retrieve
// data, either GetPostedByte or GetQueryByte.
//
// GetNextByte Uses GetInboundByte ( specified in GetFirstByte )
// to retrieve inbound data, and decodes it using
// the URL encoding rules.
//
// BKL_Alloc Allocates memory used in GetPostKeys.
//
// BKL_Dealloc Deallocates memory used in GetPostKeys.
//
// BKL_Abort Cleans up all resources for abnormal exits from
// GetPostKeys.
//
// IsKeySeparator Returns TRUE if character is one of "=\r\n&\0".
//
// BuildKeyList Given a data extraction function ( i.e.
// GetPostedByte or GetQueryByte ), this function
// converts all keys into a linked list of POSTKEY
// structures.
//
// GetPostKeys Takes inbound data off the wire by calling
// BuildKeyList with GetPostedByte as the extraction
// function.
//
// GetUrlKeys Extracts data from the query string by calling
// BuildKeyList with GetQueryByte as the extraction
// function.
//
// GetPropAddr Calculates the address of the list's properties,
// appended to the first key in a list.
//
// The typedef for the linked list is kept privately in this file,
// and our interface isolates other source files from the
// implementation details.
//

//
// Constants for this source file only
//

#define MAX_KEY_NAME_LENGTH 256 // maximum size of an inbound key name
#define CONTENT_BUF_LENGTH 8192 // amount of content buffered before
// WriteFile call
// ( used for temporary files only )

#define GNB_NOTHING_LEFT 0 // GetNextByte return values
#define GNB_DECODED_CHAR 1
#define GNB_NORMAL_CHAR 2


//
// POSDATA struct is used with GetInboundByte to keep
// track of the position within incoming data.
// GETINBOUNDBYTE is a function pointer type.
//

typedef struct _tagPOSDATA {
EXTENSION_CONTROL_BLOCK *pECB;
int nCurrentPos; // overall position

int nBufferLength; // length of buffer

int nBufferPos; // position within buffer

int nAllocLength; // size of buffer as allocated

LPBYTE pData;
int (*GetInboundByte)(struct _tagPOSDATA * p);
} POSDATA, *PPOSDATA;

typedef int (*GETINBOUNDBYTE)(PPOSDATA p);


#ifdef USE_MEMORY

//
// LISTPROP struct is used to maintain a set of
// list-wide properties. This implementation
// uses the properties list to hold a buffer
// pointer.
//

typedef struct _tagLISTPROP {
LPBYTE lpbyBuf;
} LISTPROP, *PLISTPROP;

#elif defined USE_TEMPORARY_FILES

//
// This LISTPROP struct holds temporary
// file information.
//

typedef struct _tagLISTPROP {
char szTempFileName[MAX_PATH];
HANDLE hFile;
} LISTPROP, *PLISTPROP;

#endif


// This private helper needs a prototype
PLISTPROP GetPropAddr( HKEYLIST hKey );


int
GetPostedByte(
PPOSDATA pPosData
)
/*++

Purpose:

GetPostedByte returns a waiting character that is not
decoded yet. We have this function to smooth out the
inbound data: the server gives us blocks of data, one at
a time, and there can be any number of blocks.

For the first call, pPosData->nAllocLength must be zero,
and pECB must be set.

Arguments:

pPostData - pointer to POSTDATA struct

Returns:

incoming byte value or
-1 to indicate an error

--*/
{
int nBytesToCopy;

// For readability only...
EXTENSION_CONTROL_BLOCK *pECB;

pECB = pPosData->pECB;

//
// Initialize position struct on first call.
//

if ( !pPosData->nAllocLength ) {
// Initialize the members
pPosData->nCurrentPos = 0;
pPosData->nBufferPos = 0;
pPosData->nBufferLength = 0;
pPosData->nAllocLength = 0x10000; // 65536 bytes

// Allocate the memory
pPosData->pData = (LPBYTE) HeapAlloc(
GetProcessHeap( ),
HEAP_ZERO_MEMORY,
pPosData->nAllocLength );
}
//
// Was memory allocated? Is it still allocated?
// If not, return right away.
//

if ( !pPosData->pData ) {
LOG( "GetPostedByte: Buffer not allocated." );
return -1;
}
//
// Check for end. Deallocate and return if we're done.
//

if ( (DWORD) pPosData->nCurrentPos == pECB->cbTotalBytes ) {

HeapFree( GetProcessHeap( ), 0, (LPVOID) pPosData->pData );
pPosData->pData = 0;

return -1;
}

//
// Check for buffer not loaded. Load if necessary.
//

if ( pPosData->nBufferPos == pPosData->nBufferLength ) {

//
// Fill the buffer with new inbound data.
// Request it via ReadClient if necessary.
//

if ( pECB->cbAvailable < 1 ) {

// Calculate how much we should go and get
nBytesToCopy = pECB->cbTotalBytes - pPosData->nCurrentPos;
if ( nBytesToCopy > pPosData->nAllocLength ) {
nBytesToCopy = pPosData->nAllocLength;
}

// Let's go get the data
if ( !pECB->ReadClient(
pECB->ConnID,
pPosData->pData,
(LPDWORD) & nBytesToCopy
)) {
HeapFree( GetProcessHeap( ), 0, (LPVOID) pPosData->pData );
pPosData->pData = 0;

LOG( "GetPostedByte: Error reading data via ReadClient" );
return -1;
}
}else{
// Take at most nAllocLength bytes of data
if ( pECB->cbAvailable > (DWORD) (pPosData->nAllocLength) ) {
nBytesToCopy = pPosData->nAllocLength;
}else{
nBytesToCopy = pECB->cbAvailable;
}

// Copy the inbound data to our buffer
memcpy(
pPosData->pData,
&pECB->lpbData[pPosData->nCurrentPos],
nBytesToCopy
);

// Account for removed data
pECB->cbAvailable -= nBytesToCopy;
}

// Our buffer is now full
pPosData->nBufferLength = nBytesToCopy;
pPosData->nBufferPos = 0;

// Make sure we have something
if ( !nBytesToCopy ) {
HeapFree( GetProcessHeap( ), 0, (LPVOID) pPosData->pData );
pPosData->pData = 0;
return -1;
}
}
//
// Inc current pos, buffer pos, and return a character
//

pPosData->nCurrentPos++;
pPosData->nBufferPos++;
return ( (int)pPosData->pData[pPosData->nBufferPos - 1] );
}



int
GetQueryByte(
IN OUT PPOSDATA pPosData
)
/*++

Purpose:

Returns a waiting character that is not
decoded yet. We have this function to match GetPostedData.

For the first call, pPosData->nAllocLength must be zero,
and pECB must be set.


Arguments:

pPostData - points to POSDATA structura

Returns:

byte value or -1 to indicate an error

--*/
{
// For readability only...
EXTENSION_CONTROL_BLOCK *pECB;

pECB = pPosData->pECB;

//
// Initialize position struct on first call.
//

if ( !pPosData->nAllocLength ) {
// Initialize the useful members
pPosData->nBufferPos = 0;
pPosData->nBufferLength = lstrlen( (LPCSTR) pECB->lpszQueryString );
pPosData->nAllocLength = -1;

char szMsg[256];

wsprintf(
szMsg,
"pPosData->nBufferLength=%i",
pPosData->nBufferLength
);
LOG( szMsg );
}

//
// Check for end. Deallocate and return if we're done.
//

if ( pPosData->nBufferPos == pPosData->nBufferLength ) {
return -1;
}

//
// Inc buffer pos and return a character
//

pPosData->nBufferPos++;
return ( (int)pECB->lpszQueryString[pPosData->nBufferPos - 1] );
}


//
// Now that we have GetPostedByte, and GetQueryByte, we can
// build a more useful function that decodes URL-style
// encoded characters.
//
// Recall that there are two special cases for this encoding:
//
// 1. Each plus sign must be converted to a space
// 2. A percent sign denotes a hex value-encoded character
//
// Percents are used to specify characters that are otherwise
// illegal. This includes percents themselves, ampersands,
// control characters, and so on.
//
// GetNextByte returns the decoded byte, plus a flag indicating
// normal character, decoded character, or failure. See top of
// file for return value constants.
//


int
HexDigitToInt(
IN char c
)
/*++

Purpose:
HexDigitToInt simply converts a hex-based character to an int.

Arguments:
tc - character to convert

Returns:
binary value of the character (0-15)
-1 if the character is not hex digit

--*/
{
if ( c >= '0' && c <= '9' ) {
return ( c - '0' );
}

if ( tolower( c ) >= 'a' && tolower( c ) <= 'f' ) {
return ( tolower( c ) - 'a' + 10 );
}

return -1;
}


int
GetNextByte(
IN OUT PPOSDATA pPosData,
OUT char * pc
)
/*++

Purpose:
Decode single byte of the input data

Arguments:
pPostData - points to POSDATA struct
pc - points to variable to accept decoded byte

Returns:
GNB_NORMAL_CHAR, GNB_NOTHING_LEFT or GNB_DECODED_CHAR

--*/
{
int nChar;
int nDigit;

// Initialize character pointer
*pc = 0;

// Fetch the next inbound character
nChar = pPosData->GetInboundByte( pPosData );
if ( nChar == -1 ) {
return GNB_NOTHING_LEFT;
}

// Plus signs: convert to spaces
if ( nChar == '+' ) {
*pc = ' ';
return GNB_DECODED_CHAR;
}
// Percent signs: convert hex values
else if ( nChar == '%' ) {
nChar = pPosData->GetInboundByte( pPosData );
nDigit = HexDigitToInt( nChar );
if ( nDigit == -1 ) {
return GNB_NOTHING_LEFT;
}

*pc = ( char ) ( ( UINT ) nDigit << 4 );

nChar = pPosData->GetInboundByte( pPosData );
nDigit = HexDigitToInt( nChar );
if ( nDigit == -1 ) {
*pc = 0; // incomplete

return GNB_NOTHING_LEFT;
}
*pc |= ( char ) ( UINT ) nDigit;

return GNB_DECODED_CHAR;
}
// Must be normal character then
*pc = (char) nChar;

return GNB_NORMAL_CHAR;
}


int
GetFirstByte(
IN OUT PPOSDATA pPosData,
IN EXTENSION_CONTROL_BLOCK * pECB,
OUT char * pc,
IN GETINBOUNDBYTE GetInboundByte
)
/*++

Purpose:

GetFirstByte eliminates the guesswork from initialization.
We call GetFirstByte with an uninitialized POSDATA structure,
and we call GetNextByte from there on.

Arguments:
pPosData - points to POSDATA struct to initialize
pECB - points to the extenstion control block
pc - points to variable to accept decoded byte
GetInboundByte - points to function to get incoming bytes


Returns:
same as GetNextByte()

--*/
{
// Initialize struct
pPosData->nAllocLength = 0;
pPosData->pECB = pECB;
pPosData->GetInboundByte = GetInboundByte;

// Make the call as usual
return GetNextByte( pPosData, pc );
}


//
// Structure used in data processing - the elements of the
// key list.
//

typedef struct _tagPOSTKEY {
int nInstance; // used when key name is the same as another,
// normally 0

DWORD dwOffset; // offset into content file
DWORD dwLength; // length of data
BOOL bHasCtrlChars; // a character value < 32 is in data
struct _tagPOSTKEY *pNext; // linked list
struct _tagPOSTKEY *pHead; // first in linked list
LPBYTE lpbyBuf; // pointer to the key's data in the list
// buffer

// key string appended to structure
// for the head key, list properties are appended
} POSTKEY, *PPOSTKEY;



//
// These three helper functions isolate the memory allocation,
// deallocation and abnormal exit code. They are used only to
// keep BuildKeyList readable.
//


BOOL
BKL_Alloc(
OUT LPSTR * plpszKey,
OUT LPBYTE * plpbyBuf
)
{
// Allocate a buffer for the key name
*plpszKey = (LPSTR) HeapAlloc( GetProcessHeap( ),
HEAP_ZERO_MEMORY,
MAX_KEY_NAME_LENGTH );

if ( !*plpszKey ) {
return FALSE;
}

#ifdef USE_MEMORY
// Init buffer to NULL
*plpbyBuf = NULL;

#elif defined USE_TEMPORARY_FILES

// Allocate a buffer for the content
*plpbyBuf = (LPBYTE) HeapAlloc( GetProcessHeap( ),
HEAP_ZERO_MEMORY,
CONTENT_BUF_LENGTH );

if ( !*plpbyBuf ) {
HeapFree( GetProcessHeap( ), 0, (LPVOID) * plpszKey );
return FALSE;
}
#endif

return TRUE;
}


void
BKL_Dealloc(
IN LPSTR * plpsz,
IN LPBYTE * plpby
)
{
if ( plpsz && *plpsz ) {
HeapFree( GetProcessHeap( ), 0, (LPVOID) * plpsz );
}
if ( plpby && *plpby ) {
HeapFree( GetProcessHeap( ), 0, (LPVOID) * plpby );
}
}

//
// This allows us to clean up... with temporary files we have to close
// and delete them. Otherwise, we have to free a lot of memory.
//

#ifdef USE_TEMPORARY_FILES
#define MACRO_AbortCleanup BKL_Abort( pHead, hDataFile, \
lpszKeyNameBuf, lpbyContentBuf );\
if ( hDataFile != INVALID_HANDLE_VALUE ) DeleteFile( szTempPath )

#elif defined USE_MEMORY

#define MACRO_AbortCleanup BKL_Abort( pHead, INVALID_HANDLE_VALUE, \
lpszKeyNameBuf,lpbyContentBuf )
#endif


void
BKL_Abort(
IN PPOSTKEY pHead,
IN HANDLE hFile,
IN LPSTR lpszKey,
IN LPBYTE lpbyBuf
)
{
if ( pHead ) {
FreeKeyList( (HKEYLIST) pHead );
}

if ( hFile != INVALID_HANDLE_VALUE ) {
CloseHandle( hFile );
}

BKL_Dealloc( &lpszKey, &lpbyBuf );
}


BOOL
IsKeySeparator(
char c
)
/*++

Purpose:
Identify key separators

Arguments:
c - character

Returns:
TRUE if character is a key separator,
FALSE otherwise

--*/
{
return ( c == '=' || c == '\r' || c == '\n' || c == '&' || !c );
}


PPOSTKEY
BuildKeyList(
IN EXTENSION_CONTROL_BLOCK * pECB,
IN GETINBOUNDBYTE GetInboundByte
)
/*++

Purpose:

Now that we have a way to get a decoded byte from the stream,
we can parse POST data. POST data comes in as:

key=data&key=data&key=data\r\n

A linked list of keys is established, and the head node
of the list is returned. A NULL indicates no keys or
an error.


Arguments:
pECB - pointer to the extension control block
GetInboundByte - pointer to function to get input data

Returns:
Pointer to the head node or NULL

--*/
{
PPOSTKEY pHead = NULL; // head of linked list ( the return val )
PPOSTKEY pTail = NULL; // last member in linked list
PPOSTKEY pNewPostKey; // pointer for unlinked, newly allocated
// objects

PPOSTKEY pListWalk; // linked list walking pointer
PLISTPROP pProp; // pointer to list properties
LPSTR lpszKeyNameBuf; // pointer to buffer, used in obtaining key
// name

int nPos; // position within key name buffer
DWORD dwOffset; // offset from start of content buffer or
// file

DWORD dwLength; // length of key data
char c; // general-purpose character
int nReturn; // general-purpose return code
POSDATA pd; // POSDATA struct needed in GetInboundByte
int nContentPos; // position within content buffer
LPBYTE lpbyContentBuf; // pointer to buffer
BOOL bHasCtrlChars; // flag to detect ctrl chars

// Call helper to allocate a buffer
if ( !BKL_Alloc( &lpszKeyNameBuf, &lpbyContentBuf ) ) {
LOG( "BuildKeyList: Memory allocation failure" );
return NULL;
}
nContentPos = dwOffset = 0;


#ifdef USE_MEMORY
//
// Allocate enough memory for all the content.
// For the POST method, the cbTotalBytes gives us the number
// of bytes that are being sent by the browser. We can
// allocate that much but we'll really only use about 75% of it.
// For the GET method, we need to allocate the size of the
// query string plus 1.
//

lpbyContentBuf = (LPBYTE) HeapAlloc( GetProcessHeap( ),
HEAP_ZERO_MEMORY,
pECB->cbTotalBytes +
lstrlen( pECB->lpszQueryString ) + 1 );

if ( !lpbyContentBuf ) {

LOG( "BuildKeyList: Error allocating content memory" );
BKL_Dealloc( &lpszKeyNameBuf, &lpbyContentBuf );

return NULL;
}
#elif defined USE_TEMPORARY_FILES

//
// When USE_TEMPORARY_FILES is chosen, we create
// a temporary file to store all the inbound data.
// This is done to support huge amounts of inbound
// data, like file uploads.
//

char szTempDir[MAX_PATH]; // directory of temporary files
char szTempPath[MAX_PATH]; // path of content file
HANDLE hDataFile; // handle to content file
DWORD dwBytesWritten; // used with WriteFile

// Get a temp file name
GetTempPath( MAX_PATH, szTempDir );
if ( !GetTempFileName( szTempDir, "key", 0, szTempPath ) ) {

LOG( "BuildKeyList: Error creating temporary file" );
BKL_Dealloc( &lpszKeyNameBuf, &lpbyContentBuf );

return NULL;
}
// Create the content file
hDataFile = CreateFile( szTempPath,
GENERIC_READ | GENERIC_WRITE,
0, // No sharing mode
NULL, // Default security attribs
CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
NULL // No template file
);

// Return if an error occured
if ( hDataFile == INVALID_HANDLE_VALUE ) {
LOG( "BuildKeyList: Error opening temporary file" );
MACRO_AbortCleanup;
return NULL;
}
#endif


//
// 'for' statement detects the start of a valid key name.
//
// To do inside 'for' loop:
// Obtain key name
// Write data to buffer or content file
// Create POSTKEY object
// Update links
//

for ( nReturn = GetFirstByte( &pd, pECB, &c, GetInboundByte );
nReturn != GNB_NOTHING_LEFT;
nReturn = GetNextByte( &pd, &c ) ) {

// If \r or \n, ignore and continue
if ( c == '\r' || c == '\n' ) {
continue;
}

// Get a key name
nPos = 0;
while ( !IsKeySeparator( c ) ) {
if ( nPos < MAX_KEY_NAME_LENGTH ) {
lpszKeyNameBuf[nPos] = c;
nPos++;
}
nReturn = GetNextByte( &pd, &c );
if ( nReturn == GNB_NOTHING_LEFT ) { // abrupt end!
break;
}
}

// If no equals sign or name too long,
// we have a browser formatting error
if ( c != '=' || nPos == MAX_KEY_NAME_LENGTH ) {
LOG( "BuildKeyList: Browser formatting error" );

MACRO_AbortCleanup;
return NULL;
}

// Truncate the name string, reset data info variables
lpszKeyNameBuf[nPos] = 0;
nPos++;
dwLength = 0;
bHasCtrlChars = FALSE;

//
// Move the data to the content buffer or file.
//
for ( nReturn = GetNextByte( &pd, &c );
!IsKeySeparator( c ) || nReturn == GNB_DECODED_CHAR;
nReturn = GetNextByte( &pd, &c ) ) {

lpbyContentBuf[nContentPos] = c;

nContentPos += sizeof ( char );
dwLength++;

// Check for ctrl chars
if ( c < 0x20 ) {
bHasCtrlChars = TRUE;
}

#ifdef USE_TEMPORARY_FILES
// If we have enough data, write buffer to disk
if ( nContentPos == CONTENT_BUF_LENGTH ) {
if ( !WriteFile( hDataFile, lpbyContentBuf,
nContentPos, &dwBytesWritten, NULL ) ) {

LOG( "BuildKeyList: Error writing to content file" );
MACRO_AbortCleanup;
return NULL;
}
nContentPos = 0;
}
#endif

} // for( nReturn


#ifdef USE_MEMORY
//
// Put a terminating NULL at the end of the key data.
//

lpbyContentBuf[nContentPos] = 0;
nContentPos++;

#elif defined USE_TEMPORARY_FILES

// Drain buffer
if ( nContentPos ) {
if ( !WriteFile( hDataFile, lpbyContentBuf,
nContentPos, &dwBytesWritten, NULL ) ) {

LOG( "BuildKeyList: Error writing to content file" );
MACRO_AbortCleanup;
return NULL;
}
nContentPos = 0;
}
#endif


// Allocate a POSTKEY object, allocate extra for first key
if ( pHead ) {
pNewPostKey = (PPOSTKEY) HeapAlloc(
GetProcessHeap( ),
HEAP_ZERO_MEMORY,
sizeof (POSTKEY) + nPos
);
}else{
pNewPostKey = (PPOSTKEY) HeapAlloc(
GetProcessHeap( ),
HEAP_ZERO_MEMORY,
sizeof (POSTKEY) + nPos +
sizeof (LISTPROP) );

pProp = (PLISTPROP) ( (LPBYTE)pNewPostKey +
sizeof (POSTKEY) + nPos );
}

// Check for valid pointer
if ( !pNewPostKey ) {
LOG( "BuildKeyList: POSTKEY memory allocation failure" );
MACRO_AbortCleanup;
return NULL;
}

//
// Set pNewPostKey members
//

// Set nInstance
pNewPostKey->nInstance = 0;
pListWalk = pHead;
while ( pListWalk ) {
// Check for duplicate key names
if ( !lstrcmpi( (LPCSTR) ( &pListWalk[1] ), lpszKeyNameBuf )) {
pNewPostKey->nInstance++;
}
pListWalk = pListWalk->pNext;
}

// Set dwOffset, dwLength, bHasCtrlChars, lpbyBuf
pNewPostKey->dwOffset = dwOffset;
pNewPostKey->dwLength = dwLength;
pNewPostKey->bHasCtrlChars = bHasCtrlChars;

#ifdef USE_MEMORY
pNewPostKey->lpbyBuf = &lpbyContentBuf[dwOffset];
dwOffset += dwLength + 1;

#elif defined USE_TEMPORARY_FILES

pNewPostKey->lpbyBuf = NULL;
dwOffset += dwLength;
#endif


// Copy key name
lstrcpy( (LPSTR) ( &pNewPostKey[1] ), lpszKeyNameBuf );

// Link
if ( pTail ) {
pTail->pNext = pNewPostKey;
}else{

#ifdef USE_TEMPORARY_FILES
// Copy content file name to list properties
lstrcpy( pProp->szTempFileName, szTempPath );

// Set handle
pProp->hFile = hDataFile;

#elif defined USE_MEMORY

// Set content buffer pointer
pProp->lpbyBuf = lpbyContentBuf;
#endif

// Set head
pHead = pNewPostKey;
}

pNewPostKey->pNext = NULL;
pTail = pNewPostKey;

pNewPostKey->pHead = pHead; // may point to itself

} // for ( nReturn

#ifdef USE_TEMPORARY_FILES
//
// If content file is empty, close it and delete it
//

if ( !pHead ) {
LOG( "Empty content file is being deleted." );
CloseHandle( hDataFile );
DeleteFile( szTempPath );
}
// Free work buffer
BKL_Dealloc( &lpszKeyNameBuf, &lpbyContentBuf );

#elif defined USE_MEMORY

// Free work buffer
BKL_Dealloc( &lpszKeyNameBuf, pHead ? NULL : &lpbyContentBuf );
#endif


return pHead;
}


//
// We are now pretty much done with anything complex. BuildKeyList
// will do all our parse work, so now we need a few wrappers to
// make a nice, clean external interface.
//
// GetPostKeys calls BuildKeyList with GetPostedByte.
//
// GetUrlKeys calls BuildKeyList with GetQueryByte.
//

PPOSTKEY
GetPostKeys(
IN EXTENSION_CONTROL_BLOCK * pECB
)
{
return BuildKeyList( pECB, GetPostedByte );
}

PPOSTKEY
GetUrlKeys(
IN EXTENSION_CONTROL_BLOCK * pECB
)
{
return BuildKeyList( pECB, GetQueryByte );
}


PLISTPROP
GetPropAddr(
IN HKEYLIST hKey
)
/*++

Purpose:

GetPropAddr returns the address of the end of
the first key. We stuff list properties there.
This implementation of keys.cpp keeps a pointer
to the content buffer. The second version ( used
in IS2WCGI ) appends a temporary file name
to the first key.

Arguments:
hKey - pointer to a key list

Returns:
The address of the end of the first key

--*/
{
LPCSTR lpszKeyName;
PPOSTKEY pHead;

// Safety
if ( !hKey ) {

return NULL; 
}

// ContentPath follows POSTKEY struct and key name
pHead = (PPOSTKEY) hKey;
pHead = pHead->pHead;

lpszKeyName = (LPCSTR) ( &pHead[1] );

return (PLISTPROP) ( lpszKeyName + lstrlen( lpszKeyName ) + 1 );
}


HKEYLIST
GetKeyList(
IN EXTENSION_CONTROL_BLOCK * pECB
)
/*++

Purpose:

Examines the method and calls GetPostKeys or GetUrlKeys,
whichever is relevant.


Arguments:
pECB - points to the extension control block

Returns:
GetPropAddr returns the address of the end of
the first key. We stuff list properties there.
This implementation of keys.cpp keeps a pointer
to the content buffer. The second version ( used
in IS2WCGI ) appends a temporary file name
to the first key.

--*/
{
if ( !lstrcmpi( pECB->lpszMethod, "POST" ) ) {
LOG( "Method=POST" );
return (HKEYLIST) GetPostKeys( pECB );
}else if ( !lstrcmpi( pECB->lpszMethod, "GET" ) ) {
LOG( "Method=GET" );
return (HKEYLIST) GetUrlKeys( pECB );
}
LOG( "Unknown method" );

return NULL;
}


HKEYLIST
GetKeyInfo(
IN HKEYLIST hKey,
OUT LPCSTR * plpszKeyName,
OUT LPDWORD pdwLength,
OUT BOOL * pbHasCtrlChars,
OUT LPINT pnInstance
)
//
// GetKeyInfo is a wrapper for the POSTKEY linked list.
// It returns the members of the supplied POSTKEY object.
//
{
PPOSTKEY pPostKey;

// Safety
if ( !hKey ) {
return NULL;
}

pPostKey = (PPOSTKEY) hKey;

// Set the data members
if ( plpszKeyName )
*plpszKeyName = ( LPCSTR ) ( &pPostKey[1] );
if ( pdwLength )
*pdwLength = pPostKey->dwLength;
if ( pbHasCtrlChars )
*pbHasCtrlChars = pPostKey->bHasCtrlChars;
if ( pnInstance )
*pnInstance = pPostKey->nInstance;

// Return a handle to the next object in the list
return ( ( HKEYLIST ) pPostKey->pNext );
}



#ifdef USE_MEMORY
LPBYTE
GetKeyBuffer(
IN HKEYLIST hKey
)
{
//
// We have two versions of this function because
// we may want to use file i/o when the extension
// deals with massive amounts of inbound data
// ( like multi-megabyte uploads ).
//

//
// This version uses a memory buffer.
//

PPOSTKEY pKey;

// Safety
if ( !hKey ) {
return NULL;
}

pKey = (PPOSTKEY) hKey;

return (LPBYTE) pKey->lpbyBuf;
}

#elif defined USE_TEMPORARY_FILES

LPBYTE
GetKeyBuffer(
IN HKEYLIST hKey
)
{
//
// This version uses slow temporary files.
//

PLISTPROP pProp;
PPOSTKEY pKey;
DWORD dwRead;

// Get pointer to list properties
pProp = GetPropAddr( hKey );

// Safety
if ( !pProp ) {
return NULL;
}

pKey = (PPOSTKEY) hKey;

// Check if memory was already loaded for this key
if ( pKey->lpbyBuf ) {
return pKey->lpbyBuf;
}

// If not, let's allocate memory and do a ReadFile
pKey->lpbyBuf = (LPBYTE) HeapAlloc( GetProcessHeap( ),
HEAP_ZERO_MEMORY,
pKey->dwLength + 1 );
if ( !pKey->lpbyBuf ) {
LOG( "GetKeyBuffer: HeapAlloc failed" );
return NULL;
}
// Do the ReadFile
SetFilePointer( pProp->hFile, pKey->dwOffset, NULL, FILE_BEGIN );
if ( !ReadFile( pProp->hFile, pKey->lpbyBuf,
pKey->dwLength, &dwRead, NULL ) ||
dwRead != pKey->dwLength ) {
HeapFree( GetProcessHeap( ), 0, (LPVOID) pKey->lpbyBuf );
pKey->lpbyBuf = NULL;

LOG( "GetKeyBuffer: ReadFile failed" );
return NULL;
}
return pKey->lpbyBuf;
}
#endif


HKEYLIST
FindKey(
IN HKEYLIST hKeyList,
IN LPCSTR lpszSearchName
)
/*++

Purpose:

FindKey sequentially searches the linked list for a given key.

Arguments:

hKeyList - points to key list
lpszSearchName - points to a key name to find

Returns:

The return handle points to the element within the linked list.
Use it in GetKeyInfo, but not FreeKeyList.

--*/
{
PPOSTKEY pFindKey;

pFindKey = (PPOSTKEY) hKeyList;
while ( pFindKey ) {
if ( !lstrcmpi( lpszSearchName, ( LPCSTR ) ( &pFindKey[1] ) ) ) {
return ( ( HKEYLIST ) pFindKey );
}

pFindKey = pFindKey->pNext;
}

return NULL;
}


void
FreeKeyList(
IN HKEYLIST hHeadKey
)
/*++

Purpose:

FreeKeyList deallocates all the objects in the key list.
The content file is also deleted.

Arguments:
hHeadKey - points to the list head

--*/
{
PPOSTKEY pObject;
PPOSTKEY pDel;
PLISTPROP pProp;

// Safety
if ( !hHeadKey ) {
return;
}

#ifdef USE_TEMPORARY_FILES
// Close the content file
CloseContentFile( hHeadKey );

// delete the content file
pProp = GetPropAddr( hHeadKey );
DeleteFile( pProp->szTempFileName );

#elif defined USE_MEMORY

// delete content
pProp = GetPropAddr( hHeadKey );
HeapFree( GetProcessHeap( ), 0, (LPVOID) pProp->lpbyBuf );
#endif

// delete all objects in the list
pObject = (PPOSTKEY) hHeadKey;
pObject = pObject->pHead;
while ( pObject ) {

#ifdef USE_TEMPORARY_FILES
//
// Free each buffer when using temporary files
//

if ( pObject->lpbyBuf )
HeapFree( GetProcessHeap( ), 0, (LPVOID) pObject->lpbyBuf );
#endif

pDel = pObject;
pObject = pObject->pNext;

HeapFree( GetProcessHeap( ), 0, (LPVOID) pDel );
}
}


DWORD
GetKeyOffset(
IN HKEYLIST hKey
)
/*++

Purpose:

GetKeyOffset returns the offset of a key into the internal
buffer or temporary file. This is provided for IS2WCGI
so it can return an offset within the content file.

Arguments:

hKey - points to a key

Returns:

Offset of a key or NULL if no key provided

--*/
{
// Safety
if ( !hKey )
return NULL;

return ( (PPOSTKEY) hKey )->dwOffset;
}


#ifdef USE_TEMPORARY_FILES

LPCSTR
GetContentFile(
IN HKEYLIST hKeyList
)
/*++

Purpose:

GetContentFile returns a pointer to the name of the
temporary file. This is provided for the IS2WCGI
sample.

Arguments:


Returns:

--*/
{
PLISTPROP pProp;

// safety
if ( !hKeyList )
return NULL;

pProp = GetPropAddr( hKeyList );

return ( LPCSTR ) pProp->szTempFileName;
}


void
CloseContentFile(
IN HKEYLIST hKey
)
/*++

Purpose:

CloseContentFile forces the content file to be closed. This
allows you to pass the file to something else that may open
it. Call OpenContentFile before calling any other key
function.

Arguments:

--*/
{
PLISTPROP pProp;

if ( !hKey )
return;

pProp = GetPropAddr( hKey );
if ( pProp->hFile != INVALID_HANDLE_VALUE ) {
CloseHandle( pProp->hFile );
pProp->hFile = INVALID_HANDLE_VALUE;
}
}


void
OpenContentFile(
IN HKEYLIST hKey
)
/*++

Purpose:

OpenContentFile forces the content file to be reopened.
GetKeyBuffer will fail if the content file was closed by
CloseContentFile, but not reopened.

Arguments:


Returns:

--*/
{
PLISTPROP pProp;

if ( !hKey )
return;

pProp = GetPropAddr( hKey );

if ( pProp->hFile != INVALID_HANDLE_VALUE )
return;

// Create the content file
pProp->hFile = CreateFile( pProp->szTempFileName,
GENERIC_READ | GENERIC_WRITE,
0, // No sharing mode
NULL, // Default security attribs
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL );
}

#elif defined USE_MEMORY


LPBYTE
GetDataBuffer(
IN HKEYLIST hKeyList
)
/*++

Purpose:

GetBufferPointer returns a pointer to the buffer used
for content storage.

Arguments:

hKeyList - points to a key list

Returns:

pointer to the content buffer or NULL

--*/
{
PLISTPROP pProp;

// safety
if ( !hKeyList )
return NULL;

pProp = GetPropAddr( hKeyList );

return pProp->lpbyBuf;
}
#endif