#include <wininetp.h>
#include <perfdiag.hxx>
#include "httpp.h"

#include <wininetp.h>

#define DATE_AND_TIME_STRING_BUFFER_LENGTH  128

PRIVATE
BOOL
FMatchList(
    LPSTR *lplpList,
    DWORD cListLen,
    HEADER_STRING *lpHeader,
    LPSTR    lpBase
    )
{
    DWORD i;
    for (i=0; i < cListLen; ++i) {
       if (!lpHeader->Strnicmp(lpBase, lplpList[i], strlen(lplpList[i]))) {
          return (TRUE);
       }
    }
    return(FALSE);
}


DWORD
FASTCALL
CalculateHashNoCase(
    IN LPCSTR lpszString,
    IN DWORD dwStringLength
    )

/*++

Routine Description:

    Calculate a case-insensitive hash number given a string. Assumes input is
    7-bit ASCII

Arguments:

    lpszString      - string to hash

    dwStringLength  - length of lpszString, or -1 if we need to calculate

Return Value:

    DWORD - a generated hash value

--*/

{
    DWORD dwHash = HEADER_HASH_SEED;

    while (dwStringLength != 0) {
        CHAR ch = *lpszString;

        if ((ch >= 'A') && (ch <= 'Z')) {
            ch = MAKE_LOWER(ch);
        }
        dwHash += (DWORD)(dwHash << 5) + ch; /*+ *pszName++;*/

        ++lpszString;
        --dwStringLength;
    }
    return dwHash;
}

//
// methods
//



DWORD
HTTP_HEADERS::AllocateHeaders(
    IN DWORD dwNumberOfHeaders
    )

/*++

Routine Description:

    Allocates or grows the array of header pointers (HEADER_STRING objects)

Arguments:

    dwNumberOfHeaders   - number of additional header slots to create

Return Value:

    DWORD
        Success - ERROR_SUCCESS

        Failure - ERROR_NOT_ENOUGH_MEMORY

--*/

{
    DEBUG_ENTER((DBG_HTTP,
                 Dword,
                 "AllocateHeaders",
                 "%d",
                 dwNumberOfHeaders
                 ));

    PERF_ENTER(AllocateHeaders);

    //
    // we really need to be able to realloc an array of HEADER_STRING objects
    // (see below)
    //

    DWORD error;
    DWORD slots = _TotalSlots;


    if ( (_TotalSlots + dwNumberOfHeaders) >  (INVALID_HEADER_INDEX-1))
    {
        INET_ASSERT(FALSE);
        _NextOpenSlot = 0;
        _TotalSlots = 0;
        _FreeSlots = 0;
        error = ERROR_NOT_ENOUGH_MEMORY;
        goto quit;
    }


    _lpHeaders = (HEADER_STRING *)ResizeBuffer((HLOCAL)_lpHeaders,
                                               (_TotalSlots + dwNumberOfHeaders)
                                                    * sizeof(HEADER_STRING),
                                               FALSE // not moveable
                                               );
    if (_lpHeaders != NULL) {
        _NextOpenSlot = _TotalSlots;
        _TotalSlots += dwNumberOfHeaders;
        _FreeSlots += dwNumberOfHeaders;

        //
        // this is slightly ugly, but it seems there's no easy C++ way to
        // do this - we need to be able to realloc() an array of objects
        // created by new(), but so far, it can't be done
        //

        for (; slots < _TotalSlots; ++slots) {
            _lpHeaders[slots].Clear();
        }
        error = ERROR_SUCCESS;
    } else {

        INET_ASSERT(FALSE);
        _NextOpenSlot = 0;
        _TotalSlots = 0;
        _FreeSlots = 0;
        error = ERROR_NOT_ENOUGH_MEMORY;
    }

quit:

    INET_ASSERT(_FreeSlots <= _TotalSlots);

    PERF_LEAVE(AllocateHeaders);

    DEBUG_LEAVE(error);

    return error;
}


VOID
HTTP_HEADERS::FreeHeaders(
    VOID
    )

/*++

Routine Description:

    Free the headers strings and the headers array

Arguments:

    None.

Return Value:

    None.

--*/

{
    DEBUG_ENTER((DBG_HTTP,
                 None,
                 "FreeHeaders",
                 NULL
                 ));

    if (!LockHeaders())
    {
        goto quit;
    }

    //
    // free up each individual entry (free string buffers)
    //

    for (DWORD i = 0; i < _TotalSlots; ++i) {
        _lpHeaders[i] = (LPSTR)NULL;
    }

    //
    // followed by the array itself
    //

    if (_lpHeaders) {
        _lpHeaders = (HEADER_STRING *)FREE_MEMORY((HLOCAL)_lpHeaders);

        INET_ASSERT(_lpHeaders == NULL);
    }

    _TotalSlots = 0;
    _FreeSlots = 0;
    _HeadersLength = 0;
    _lpszVerb = NULL;
    _dwVerbLength = 0;
    _lpszObjectName = NULL;
    _dwObjectNameLength = 0;
    _lpszVersion = NULL;
    _dwVersionLength = 0;

    UnlockHeaders();

quit:
    DEBUG_LEAVE(0);
}


DWORD
HTTP_HEADERS::CopyHeaders(
    IN OUT LPSTR * lpBuffer,
    IN LPSTR lpszObjectName,
    IN DWORD dwObjectNameLength
    )

/*++

Routine Description:

    Copy the headers to the caller's buffer. Each header is terminated by CR-LF.
    This method is called to convert the request headers list to a buffer that
    we can send to the server

    N.B. This function MUST be called with the headers already locked

Arguments:

    lpBuffer            - pointer to pointer to buffer where headers are
                          written. We update the pointer

    lpszObjectName      - optional object name

    dwObjectNameLength  - optional object name length

Return Value:

    DWORD
        Success - ERROR_SUCCESS

        Failure - ERROR_NOT_ENOUGH_MEMORY
                    Ran out of memory while trying to synchronize src data access

--*/

{
    DEBUG_ENTER((DBG_HTTP,
                 None,
                 "CopyHeaders",
                 "%#x, %#x [%q], %d",
                 lpBuffer,
                 lpszObjectName,
                 lpszObjectName,
                 dwObjectNameLength
                 ));

    DWORD dwError = ERROR_SUCCESS;
    if (!LockHeaders())
    {
        dwError = ERROR_NOT_ENOUGH_MEMORY;
        goto quit;
    }

    DWORD i = 0;

    if (lpszObjectName != NULL) {
        memcpy(*lpBuffer, _lpszVerb, _dwVerbLength);
        *lpBuffer += _dwVerbLength;
        *(*lpBuffer)++ = ' ';
        memcpy(*lpBuffer, lpszObjectName, dwObjectNameLength);
        *lpBuffer += dwObjectNameLength;
        *(*lpBuffer)++ = ' ';
        memcpy(*lpBuffer, _lpszVersion, _dwVersionLength);
        *lpBuffer += _dwVersionLength;
        *(*lpBuffer)++ = '\r';
        *(*lpBuffer)++ = '\n';
        i = 1;
    }
    for (; i < _TotalSlots; ++i) {
        if (_lpHeaders[i].HaveString()) {
            _lpHeaders[i].CopyTo(*lpBuffer);
            *lpBuffer += _lpHeaders[i].StringLength();
            *(*lpBuffer)++ = '\r';
            *(*lpBuffer)++ = '\n';
        }
    }

    UnlockHeaders();

quit:
    DEBUG_LEAVE(dwError);

    return dwError;
}


HEADER_STRING *
FASTCALL
HTTP_HEADERS::FindFreeSlot(
    DWORD* piSlot
    )

/*++

Routine Description:

    Finds the next free slot in the headers list, or adds some new slots

    N.B. This function MUST be called with the headers already locked

Arguments:

    piSlot: returns index of slot found

Return Value:

    HEADER_STRING *  - pointer to next free slot

--*/

{
    DEBUG_ENTER((DBG_HTTP,
                 Pointer,
                 "FindFreeSlot",
                 NULL
                 ));

    PERF_ENTER(FindFreeSlot);

    DWORD i;
    DWORD error;
    HEADER_STRING * header = NULL;

    //
    // if there are no free slots, allocate some more
    //

    if (_FreeSlots == 0) {
        i = _TotalSlots;
        error = AllocateHeaders(HEADERS_INCREMENT);
    } else {
        i = 0;
        error = ERROR_SUCCESS;
        if (!_lpHeaders[_NextOpenSlot].HaveString())
        {
            --_FreeSlots;
            header = &_lpHeaders[_NextOpenSlot];
            *piSlot = _NextOpenSlot;
            _NextOpenSlot = (_NextOpenSlot == (_TotalSlots-1)) ? (_TotalSlots-1) : _NextOpenSlot++;
            goto quit;
        }
    }
    if (error == ERROR_SUCCESS) {
        for (; i < _TotalSlots; ++i) {
            if (!_lpHeaders[i].HaveString()) {
                --_FreeSlots;
                header = &_lpHeaders[i];
                *piSlot = i;
                _NextOpenSlot = (i == (_TotalSlots-1)) ? (_TotalSlots-1) : (i+1);
                break;
            }
        }
        if (header == NULL) {

            //
            // we would have just allocated extra slots if we didn't have
            // any, so we shouldn't be here
            //

            INET_ASSERT(FALSE);

            error = ERROR_WINHTTP_INTERNAL_ERROR;
        }
    }

quit:
    _Error = error;

    PERF_LEAVE(FindFreeSlot);

    DEBUG_LEAVE(header);

    return header;
}


VOID
HTTP_HEADERS::ShrinkHeader(
    IN LPBYTE pbBase,
    IN DWORD  iSlot,
    IN DWORD  dwOldQueryIndex,
    IN DWORD  dwNewQueryIndex,
    IN DWORD  cbNewSize
    )

/*++

Routine Description:

    Low level function that does a surgical replace of one header with another.
    This code updates internal structures such as bKnownHeaders and the stored
    hash value for the new Header.

    N.B. This function MUST be called with the headers already locked

Arguments:


Return Value:

    None.

--*/

{
    HEADER_STRING* pHeader = _lpHeaders + iSlot;

    UNREFERENCED_PARAMETER(pbBase);

    INET_ASSERT(_bKnownHeaders[dwOldQueryIndex] == (BYTE) iSlot ||
                dwNewQueryIndex == dwOldQueryIndex );

    //
    // Swap in the new header.  Update Length, Hash, and its cached position
    //  in the known header array.
    //

    _bKnownHeaders[dwOldQueryIndex] = INVALID_HEADER_INDEX;
    _bKnownHeaders[dwNewQueryIndex] = (BYTE) iSlot;

    pHeader->SetLength (cbNewSize);
    pHeader->SetHash (GlobalKnownHeaders[dwNewQueryIndex].HashVal);
}

DWORD
inline
HTTP_HEADERS::SlowFind(
    IN LPSTR lpBase,
    IN LPCSTR lpszHeaderName,
    IN DWORD dwHeaderNameLength,
    IN DWORD dwIndex,
    IN DWORD dwHash,
    OUT DWORD *lpdwQueryIndex,
    OUT BYTE  **lplpbPrevIndex
    )

/*++

Routine Description:

    Finds the next occurance of lpszHeaderName in the header array, uses
    a cached table of well known headers to accerlate the search if the
    string is a known header.

    N.B. This function MUST be called with the headers already locked

Arguments:


Return Value:

    DWORD  - index to Slot in array, or INVALID_HEADER_SLOT if not found

--*/

{

    //
    // Now see if this is a known header passed in as a string,
    //   If it is, we save ourselves the loop, and just map it right in to a known header
    //

    DWORD dwKnownQueryIndex = GlobalHeaderHashs[(dwHash % MAX_HEADER_HASH_SIZE)];

    *lpdwQueryIndex = INVALID_HEADER_SLOT;

    if ( dwKnownQueryIndex != 0 )
    {
        dwKnownQueryIndex--;

        if ( ((int)dwHeaderNameLength >= GlobalKnownHeaders[dwKnownQueryIndex].Length) &&
             strnicmp(lpszHeaderName,
                      GlobalKnownHeaders[dwKnownQueryIndex].Text,
                      GlobalKnownHeaders[dwKnownQueryIndex].Length) == 0)
        {
            *lpdwQueryIndex = dwKnownQueryIndex;

            INET_ASSERT((int)(dwHeaderNameLength) == GlobalKnownHeaders[dwKnownQueryIndex].Length);

            if ( lplpbPrevIndex )
            {
                return FastNukeFind(
                        dwKnownQueryIndex,
                        dwIndex,
                        lplpbPrevIndex
                        );
            }
            else
            {
                return FastFind(
                        dwKnownQueryIndex,
                        dwIndex
                        );
            }
        }
    }

    //
    // Otherwise we painfully enumerate the whole array of headers
    //

    for (DWORD i = 0; i < _TotalSlots; ++i)
    {
        HEADER_STRING * pString;

        pString = &_lpHeaders[i];

        if (!pString->HaveString()) {
            continue;
        }

        if (pString->HashStrnicmp(lpBase,
                                  lpszHeaderName,
                                  dwHeaderNameLength,
                                  dwHash) == 0)
        {

            //
            // if we haven't reached the required index yet, continue
            //

            if (dwIndex != 0) {
                --dwIndex;
                continue;
            }

            return i; // found index/slot
        }
    }

    return INVALID_HEADER_SLOT; // not found
}


DWORD
inline
HTTP_HEADERS::FastFind(
    IN DWORD  dwQueryIndex,
    IN DWORD  dwIndex
    )
/*++

Routine Description:

    Finds the next occurance of a known header string in the lpHeaders array.
    Since this is a known string, an index is used to refer to it.
    A cached table of well known headers is used to accerlate the search.

    N.B. This function MUST be called with the headers already locked

Arguments:


Return Value:

    DWORD  - index to Slot in array, or INVALID_HEADER_SLOT if not found

--*/

{
    DWORD dwSlot;

    dwSlot = _bKnownHeaders[dwQueryIndex];

    while ( (dwIndex > 0) && (dwSlot < INVALID_HEADER_INDEX) )
    {
        HEADER_STRING * pString;

        pString = &_lpHeaders[dwSlot];
        dwSlot  = pString->GetNextKnownIndex();

        dwIndex--;
    }

    if ( dwSlot >= INVALID_HEADER_INDEX)
    {
        return INVALID_HEADER_SLOT;
    }

    return dwSlot; // found it.
}


DWORD
inline
HTTP_HEADERS::FastNukeFind(
    IN DWORD  dwQueryIndex,
    IN DWORD  dwIndex,
    OUT BYTE **lplpbPrevIndex
    )
/*++

Routine Description:

    Finds the next occurance of a known header string in the lpHeaders array.
    Since this is a known string, an index is used to refer to it.
    A cached table of well known headers is used to accerlate the search.
    Also provides a ptr to ptr to the slot which directs us to the one found.
    This is needed for deletion purposes.

    N.B. This function MUST be called with the headers already locked

Arguments:


Return Value:

    DWORD  - index to Slot in array, or INVALID_HEADER_SLOT if not found

--*/

{
    BYTE *lpbSlot;

    *lplpbPrevIndex = lpbSlot = &_bKnownHeaders[dwQueryIndex];
    dwIndex++;

    while ( (dwIndex > 0) && (*lpbSlot < INVALID_HEADER_INDEX) )
    {
        HEADER_STRING * pString;

        pString = &_lpHeaders[*lpbSlot];
        *lplpbPrevIndex = lpbSlot;
        lpbSlot  = pString->GetNextKnownIndexPtr();

        dwIndex--;
    }

    if ( **lplpbPrevIndex >= INVALID_HEADER_INDEX ||
         dwIndex > 0 )
    {
        return INVALID_HEADER_SLOT;
    }

    return ((DWORD) **lplpbPrevIndex); // found it.
}

VOID
HTTP_HEADERS::RemoveAllByIndex(
    IN DWORD dwQueryIndex
    )
/*++

Routine Description:

    Removes all Known Headers found in the header array.

    N.B. This function MUST be called with the headers already locked

Arguments:

    dwQueryIndex - index to known header string to remove from array.

Return Value:

    None.

--*/


{
    BYTE bSlot;
    BYTE bPrevSlot;

    bSlot = bPrevSlot  = _bKnownHeaders[dwQueryIndex];

    while (bSlot < INVALID_HEADER_INDEX)
    {
        HEADER_STRING * pString;

        bPrevSlot   = bSlot;
        pString     = &_lpHeaders[bSlot];
        bSlot       = (BYTE) pString->GetNextKnownIndex();

        RemoveHeader(bPrevSlot, dwQueryIndex, &_bKnownHeaders[dwQueryIndex]);
    }

    _bKnownHeaders[dwQueryIndex] = INVALID_HEADER_INDEX;

    return;
}


BOOL
inline
HTTP_HEADERS::HeaderMatch(
    IN DWORD dwHash,
    IN LPSTR lpszHeaderName,
    IN DWORD dwHeaderNameLength,
    OUT DWORD *lpdwQueryIndex
    )

/*++

Routine Description:

    Looks up a Known HTTP header string using its Hash value and
     string contained the name of the header.

Arguments:

    dwHash              - Hash value of header name string

    lpszHeaderName      - name of header we are matching

    dwHeaderNameLength  - length of header name string

    lpdwQueryIndex      - If found, this is the HTTP_QUERY_* based index to the header.

Return Value:

    BOOL
        Success - The string and hash matched againsted a known header

        Failure - There is no known header for that hash & string pair.

--*/

{
    *lpdwQueryIndex = GlobalHeaderHashs[(dwHash % MAX_HEADER_HASH_SIZE)];

    if ( *lpdwQueryIndex != 0 )
    {
        (*lpdwQueryIndex)--;

        if ( ((int)dwHeaderNameLength == GlobalKnownHeaders[*lpdwQueryIndex].Length) &&
             strnicmp(lpszHeaderName,
                      GlobalKnownHeaders[*lpdwQueryIndex].Text,
                      GlobalKnownHeaders[*lpdwQueryIndex].Length) == 0)
        {
            return TRUE;
        }
    }

    return FALSE;
}


BYTE
inline
HTTP_HEADERS::FastAdd(
    IN DWORD  dwQueryIndex,
    IN DWORD  dwSlot
    )
/*++

Routine Description:

    Rapidly adds a known string to the header array, this function
     is used to matain coherency of the _bKnownHeaders which
     contained indexed offsets into the header array for known headers.

    Note that this function is used instead of latter listed below
     in order to maintain proper order in headers received.

    N.B. This function MUST be called with the headers already locked

Arguments:

    dwQueryIndex - index to known header string to remove from array.

    dwSlot - Slot in which this header is being added.

Return Value:

    None.

--*/


{
    BYTE *lpbSlot;

    lpbSlot = &_bKnownHeaders[dwQueryIndex];

    while ( (*lpbSlot < INVALID_HEADER_INDEX) )
    {
        HEADER_STRING * pString;

        pString  = &_lpHeaders[*lpbSlot];
        lpbSlot  = pString->GetNextKnownIndexPtr();
    }

    INET_ASSERT(*lpbSlot == INVALID_HEADER_INDEX);

    *lpbSlot = (BYTE) dwSlot;
    return INVALID_HEADER_INDEX;
}


//BYTE
//inline
//HTTP_HEADERS::FastAdd(
//    IN DWORD  dwQueryIndex,
//    IN DWORD  dwSlot
//    )
//{
//    BYTE bOldSlot;
//
//    bOldSlot = _bKnownHeaders[dwQueryIndex];
//    _bKnownHeaders[dwQueryIndex] = (BYTE) dwSlot;
//
//    return bOldSlot;
//}




DWORD
HTTP_HEADERS::AddHeader(
    IN LPSTR lpszHeaderName,
    IN DWORD dwHeaderNameLength,
    IN LPSTR lpszHeaderValue,
    IN DWORD dwHeaderValueLength,
    IN DWORD dwIndex,
    IN DWORD dwFlags
    )

/*++

Routine Description:

    Adds a single header to the array of headers, given the header name and
    value. Called via HttpOpenRequest()

Arguments:

    lpszHeaderName      - pointer to name of header to add, e.g. "Accept:"

    dwHeaderNameLength  - length of the header name

    lpszHeaderValue     - pointer to value of header to add, e.g. "text/html"

    dwHeaderValueLength - length of the header value

    dwIndex             - if coalescing headers, index of header to update

    dwFlags             - flags controlling function:

                            COALESCE_HEADER_WITH_COMMA
                            COALESCE_HEADER_WITH_SEMICOLON
                                - headers of the same name can be combined

                            CLEAN_HEADER
                                - header is supplied by user, so we must ensure
                                  it has correct format

Return Value:

    DWORD
        Success - ERROR_SUCCESS

        Failure - ERROR_NOT_ENOUGH_MEMORY
                    Ran out of memory allocating string

                  ERROR_INVALID_PARAMETER
                    The header value was bad

--*/

{
    DEBUG_ENTER((DBG_HTTP,
                 Dword,
                 "AddHeader",
                 "%.*q, %d, %.*q, %d, %d, %#x",
                 min(dwHeaderNameLength + 1, 80),
                 lpszHeaderName,
                 dwHeaderNameLength,
                 min(dwHeaderValueLength + 1, 80),
                 lpszHeaderValue,
                 dwHeaderValueLength,
                 dwIndex,
                 dwFlags
                 ));

    PERF_ENTER(AddHeader);

    DWORD error = ERROR_HTTP_HEADER_NOT_FOUND;

    if (!LockHeaders())
    {
        error = ERROR_NOT_ENOUGH_MEMORY;
        goto quit;
    }

    INET_ASSERT(lpszHeaderName != NULL);
    INET_ASSERT(*lpszHeaderName != '\0');
    INET_ASSERT(dwHeaderNameLength != 0);
    INET_ASSERT(lpszHeaderValue != NULL);
    INET_ASSERT(*lpszHeaderValue != '\0');
    INET_ASSERT(dwHeaderValueLength != 0);
    INET_ASSERT(_FreeSlots <= _TotalSlots);

    //
    // we may have been handed a header with a trailing colon. We don't care
    // for such nasty imagery
    //

    if (lpszHeaderName[dwHeaderNameLength - 1] == ':') {
        --dwHeaderNameLength;
    }

    DWORD dwQueryIndex;
    DWORD dwHash = CalculateHashNoCase(lpszHeaderName, dwHeaderNameLength);

    //
    // if we are coalescing headers then find a header with the same name
    //

    if ((dwFlags & COALESCE_HEADER_WITH_COMMA) ||
        (dwFlags & COALESCE_HEADER_WITH_SEMICOLON) )
    {
        DWORD dwSlot;

        dwSlot = SlowFind(
                    NULL,
                    lpszHeaderName,
                    dwHeaderNameLength,
                    dwIndex,
                    dwHash,
                    &dwQueryIndex,
                    NULL
                    );

        if (dwSlot != ((DWORD) -1))
        {

            HEADER_STRING * pString;

            pString = &_lpHeaders[dwSlot];

            //
            // found what we are looking for. Coalesce it
            //

            pString->ResizeString((sizeof("; ")-1) + dwHeaderValueLength); // save us from multiple reallocs

            pString->Strncat(
                             (dwFlags & COALESCE_HEADER_WITH_SEMICOLON) ?
                                 "; " :
                                 ", ",
                              2);

            pString->Strncat(lpszHeaderValue, dwHeaderValueLength);
            _HeadersLength += 2 + dwHeaderValueLength;
            error = ERROR_SUCCESS;

        }
    }
    else
    {

        //
        // Check to verify that the header we're adding is a known header,
        //   If its a known header we use dwQueryIndex to update the known header array
        //   otherwise, IF ITS NOT, we make sure to set dwQueryIndex to INVALID_...
        //

        if (! HeaderMatch(dwHash, lpszHeaderName, dwHeaderNameLength, &dwQueryIndex) )
        {
            dwQueryIndex = INVALID_HEADER_SLOT;
        }

        /*
        // Perhaps this more efficent ???
        dwQueryIndex = GlobalHeaderHashs[(dwHash % MAX_HEADER_HASH_SIZE)];

        if ( dwQueryIndex != 0 )
        {
            dwQueryIndex--;

            if ( ((int)dwHeaderNameLength < GlobalKnownHeaders[dwQueryIndex].Length) ||
                 strnicmp(lpszHeaderName,
                          GlobalKnownHeaders[dwQueryIndex].Text,
                          GlobalKnownHeaders[dwQueryIndex].Length) != 0)
            {
                dwQueryIndex = INVALID_HEADER_SLOT;
            }
        }
        else
        {
            dwQueryIndex = INVALID_HEADER_SLOT;
        }
        */
    }


    //
    // if we didn't find the header value or we are not coalescing then add the
    // header
    //

    if (error == ERROR_HTTP_HEADER_NOT_FOUND)
    {
        //
        // find the next slot for this header
        //

        HEADER_STRING * freeHeader;
        DWORD iSlot;

        freeHeader = FindFreeSlot(&iSlot);
        if (freeHeader == NULL) {
            error = GetError();

            INET_ASSERT(error != ERROR_SUCCESS);

            goto Cleanup;
        }


        freeHeader->CreateStringBuffer((LPVOID)lpszHeaderName,
                                       dwHeaderNameLength,
                                       dwHeaderNameLength
                                       + sizeof(": ") - 1
                                       + dwHeaderValueLength
                                       + 1 // for extra NULL terminator
                                       );
        if (freeHeader->IsError()) {
            error = ::GetLastError();

            INET_ASSERT(error != ERROR_SUCCESS);

            goto Cleanup;
        }
        freeHeader->Strncat((LPVOID)": ", sizeof(": ") - 1);
        freeHeader->Strncat((LPVOID)lpszHeaderValue, dwHeaderValueLength);
        _HeadersLength += dwHeaderNameLength
                        + (sizeof(": ") - 1)
                        + dwHeaderValueLength
                        + (sizeof("\r\n") - 1)
                        ;
        freeHeader->SetHash(dwHash);

        if ( dwQueryIndex != INVALID_HEADER_SLOT )
        {
            freeHeader->SetNextKnownIndex(FastAdd(dwQueryIndex, iSlot));
        }

        error = ERROR_SUCCESS;
    }

Cleanup:
    UnlockHeaders();

quit:
    PERF_LEAVE(AddHeader);

    DEBUG_LEAVE(error);

    return error;
}



DWORD
HTTP_HEADERS::AddHeader(
    IN DWORD dwQueryIndex,
    IN LPSTR lpszHeaderValue,
    IN DWORD dwHeaderValueLength,
    IN DWORD dwIndex,
    IN DWORD dwFlags
    )

/*++

Routine Description:

    Adds a single header to the array of headers, given the header name and
    value. Called via HttpOpenRequest()

Arguments:

    dwQueryIndex        - a index into a array of known HTTP headers, see wininet.h HTTP_QUERY_* codes

    lpszHeaderValue     - pointer to value of header to add, e.g. "text/html"

    dwHeaderValueLength - length of the header value

    dwIndex             - if coalescing headers, index of header to update

    dwFlags             - flags controlling function:

                            COALESCE_HEADER_WITH_COMMA
                            COALESCE_HEADER_WITH_SEMICOLON
                                - headers of the same name can be combined

                            CLEAN_HEADER
                                - header is supplied by user, so we must ensure
                                  it has correct format

Return Value:

    DWORD
        Success - ERROR_SUCCESS

        Failure - ERROR_NOT_ENOUGH_MEMORY
                    Ran out of memory allocating string

                  ERROR_INVALID_PARAMETER
                    The header value was bad

--*/

{
    DEBUG_ENTER((DBG_HTTP,
                 Dword,
                 "AddHeader",
                 "%q, %u, %.*q, %d, %d, %#x",
                 GlobalKnownHeaders[dwQueryIndex].Text,
                 dwQueryIndex,
                 min(dwHeaderValueLength + 1, 80),
                 lpszHeaderValue,
                 dwHeaderValueLength,
                 dwIndex,
                 dwFlags
                 ));

    PERF_ENTER(AddHeader);

    INET_ASSERT(dwQueryIndex <= HTTP_QUERY_MAX);
    INET_ASSERT(lpszHeaderValue != NULL);
    INET_ASSERT(*lpszHeaderValue != '\0');
    INET_ASSERT(dwHeaderValueLength != 0);
    INET_ASSERT(_FreeSlots <= _TotalSlots);

    DWORD error = ERROR_HTTP_HEADER_NOT_FOUND;
    LPSTR lpszHeaderName;
    DWORD dwHeaderNameLength;
    DWORD dwHash;

    dwHash             = GlobalKnownHeaders[dwQueryIndex].HashVal;
    lpszHeaderName     = GlobalKnownHeaders[dwQueryIndex].Text;
    dwHeaderNameLength = GlobalKnownHeaders[dwQueryIndex].Length;

    //
    // if we are coalescing headers then find a header with the same name
    //

    if ((dwFlags & COALESCE_HEADER_WITH_COMMA) ||
        (dwFlags & COALESCE_HEADER_WITH_SEMICOLON) )
    {
        DWORD dwSlot;

        dwSlot = FastFind(
                    dwQueryIndex,
                    dwIndex
                    );

        if (dwSlot != INVALID_HEADER_SLOT)
        {

            HEADER_STRING * pString;

            pString = &_lpHeaders[dwSlot];

            //
            // found what we are looking for. Coalesce it
            //

            pString->ResizeString((sizeof("; ")-1) + dwHeaderValueLength); // save us from multiple reallocs

            pString->Strncat(
                             (dwFlags & COALESCE_HEADER_WITH_SEMICOLON) ?
                                 "; " :
                                 ", ",
                              2);

            pString->Strncat(lpszHeaderValue, dwHeaderValueLength);
            _HeadersLength += 2 + dwHeaderValueLength;
            error = ERROR_SUCCESS;

        }
    }


    //
    // if we didn't find the header value or we are not coalescing then add the
    // header
    //

    if (error == ERROR_HTTP_HEADER_NOT_FOUND)
    {
        //
        // find the next slot for this header
        //

        HEADER_STRING * freeHeader;
        DWORD iSlot;

        freeHeader = FindFreeSlot(&iSlot);
        if (freeHeader == NULL) {
            error = GetError();

            INET_ASSERT(error != ERROR_SUCCESS);

            goto quit;
        }


        freeHeader->CreateStringBuffer((LPVOID)lpszHeaderName,
                                       dwHeaderNameLength,
                                       dwHeaderNameLength
                                       + sizeof(": ") - 1
                                       + dwHeaderValueLength
                                       + 1 // for extra NULL terminator
                                       );
        if (freeHeader->IsError()) {
            error = ::GetLastError();

            INET_ASSERT(error != ERROR_SUCCESS);

            goto quit;
        }
        freeHeader->Strncat((LPVOID)": ", sizeof(": ") - 1);
        freeHeader->Strncat((LPVOID)lpszHeaderValue, dwHeaderValueLength);
        _HeadersLength += dwHeaderNameLength
                        + (sizeof(": ") - 1)
                        + dwHeaderValueLength
                        + (sizeof("\r\n") - 1)
                        ;
        freeHeader->SetHash(dwHash);
        freeHeader->SetNextKnownIndex(FastAdd(dwQueryIndex, iSlot));

        error = ERROR_SUCCESS;
    }

quit:

    PERF_LEAVE(AddHeader);

    DEBUG_LEAVE(error);

    return error;
}



DWORD
HTTP_HEADERS::ReplaceHeader(
    IN LPSTR lpszHeaderName,
    IN DWORD dwHeaderNameLength,
    IN LPSTR lpszHeaderValue,
    IN DWORD dwHeaderValueLength,
    IN DWORD dwIndex,
    IN DWORD dwFlags
    )

/*++

Routine Description:

    Replaces a HTTP (request) header. The header can be replaced with a NULL
    value, meaning that the header is removed

Arguments:

    lpszHeaderName      - pointer to the header name

    dwHeaderNameLength  - length of the header name

    lpszHeaderValue     - pointer to the header value

    dwHeaderValueLength - length of the header value

    dwIndex             - index of header to replace

    dwFlags             - flags controlling function. Allowed flags are:

                            COALESCE_HEADER_WITH_COMMA
                            COALESCE_HEADER_WITH_SEMICOLON
                                - headers of the same name can be combined

                            ADD_HEADER
                                - if the header-name is not found and there is
                                  a valid header-value, then the header is added

                            ADD_HEADER_IF_NEW
                                - if the header-name exists then we return an
                                  error, else we add the header-value

Return Value:

    DWORD
        Success - ERROR_SUCCESS

        Failure - ERROR_HTTP_HEADER_NOT_FOUND
                    The requested header wasn't found

                  ERROR_HTTP_HEADER_ALREADY_EXISTS
                    The header already exists, and was not added or replaced

                  ERROR_NOT_ENOUGH_MEMORY
                    Ran out of memory trying to acquire lock

--*/

{
    DEBUG_ENTER((DBG_HTTP,
                 Dword,
                 "ReplaceHeader",
                 "%.*q, %d, %.*q, %d, %d, %#x",
                 min(dwHeaderNameLength + 1, 80),
                 lpszHeaderName,
                 dwHeaderNameLength,
                 min(dwHeaderValueLength + 1, 80),
                 lpszHeaderValue,
                 dwHeaderValueLength,
                 dwIndex,
                 dwFlags
                 ));

    PERF_ENTER(ReplaceHeader);

    INET_ASSERT(lpszHeaderName != NULL);
    INET_ASSERT(dwHeaderNameLength != 0);
    INET_ASSERT(lpszHeaderName[dwHeaderNameLength - 1] != ':');

    DWORD error = ERROR_HTTP_HEADER_NOT_FOUND;
    DWORD dwHash = CalculateHashNoCase(lpszHeaderName, dwHeaderNameLength);
    DWORD dwSlot;
    DWORD dwQueryIndex;
    BYTE *pbPrevByte;

    if (!LockHeaders())
    {
        error = ERROR_NOT_ENOUGH_MEMORY;
        goto quit;
    }

    dwSlot = SlowFind(
                NULL,
                lpszHeaderName,
                dwHeaderNameLength,
                dwIndex,
                dwHash,
                &dwQueryIndex,
                &pbPrevByte
                );

    if ( dwSlot != ((DWORD) -1))
    {
        //
        // if ADD_HEADER_IF_NEW is set, then we already have the header
        //

        if (dwFlags & ADD_HEADER_IF_NEW) {
            error = ERROR_HTTP_HEADER_ALREADY_EXISTS;
            goto Cleanup;
        }

        //
        // for both replace and remove operations, we are going to remove
        // the current header
        //

        RemoveHeader(dwSlot, dwQueryIndex, pbPrevByte);

        //
        // if replacing then add the new header value
        //

        if (dwHeaderValueLength != 0)
        {
            if ( dwQueryIndex != ((DWORD) -1) )
            {
                error = AddHeader(dwQueryIndex,
                                  lpszHeaderValue,
                                  dwHeaderValueLength,
                                  0,
                                  dwFlags
                                  );
            }
            else
            {
                error = AddHeader(lpszHeaderName,
                                  dwHeaderNameLength,
                                  lpszHeaderValue,
                                  dwHeaderValueLength,
                                  0,
                                  dwFlags
                                  );
            }


        } else {
            error = ERROR_SUCCESS;
        }
    }

    //
    // if we didn't find the header but ADD_HEADER is set then we simply add it
    // but only if the value length is not zero
    //

    if ((error == ERROR_HTTP_HEADER_NOT_FOUND)
    && (dwHeaderValueLength != 0)
    && (dwFlags & (ADD_HEADER | ADD_HEADER_IF_NEW)))
    {
        if ( dwQueryIndex != ((DWORD) -1) )
        {
            error = AddHeader(dwQueryIndex,
                              lpszHeaderValue,
                              dwHeaderValueLength,
                              0,
                              dwFlags
                              );
        }
        else
        {
            error = AddHeader(lpszHeaderName,
                              dwHeaderNameLength,
                              lpszHeaderValue,
                              dwHeaderValueLength,
                              0,
                              dwFlags
                              );

        }
    }

Cleanup:

    UnlockHeaders();

quit:
    PERF_LEAVE(ReplaceHeader);

    DEBUG_LEAVE(error);

    return error;
}



DWORD
HTTP_HEADERS::ReplaceHeader(
    IN DWORD dwQueryIndex,
    IN LPSTR lpszHeaderValue,
    IN DWORD dwHeaderValueLength,
    IN DWORD dwIndex,
    IN DWORD dwFlags
    )

/*++

Routine Description:

    Replaces a HTTP (request) header. The header can be replaced with a NULL
    value, meaning that the header is removed

Arguments:

    lpszHeaderValue     - pointer to the header value

    dwQueryIndex        - a index into a array of known HTTP headers, see wininet.h HTTP_QUERY_* codes

    dwHeaderValueLength - length of the header value

    dwIndex             - index of header to replace

    dwFlags             - flags controlling function. Allowed flags are:

                            COALESCE_HEADER_WITH_COMMA
                            COALESCE_HEADER_WITH_SEMICOLON
                                - headers of the same name can be combined

                            ADD_HEADER
                                - if the header-name is not found and there is
                                  a valid header-value, then the header is added

                            ADD_HEADER_IF_NEW
                                - if the header-name exists then we return an
                                  error, else we add the header-value

Return Value:

    DWORD
        Success - ERROR_SUCCESS

        Failure - ERROR_HTTP_HEADER_NOT_FOUND
                    The requested header wasn't found

                  ERROR_HTTP_HEADER_ALREADY_EXISTS
                    The header already exists, and was not added or replaced

                  ERROR_NOT_ENOUGH_MEMORY
                    Ran out of memory trying to acquire lock

--*/

{
    DEBUG_ENTER((DBG_HTTP,
                 Dword,
                 "ReplaceHeader",
                 "%q, %u, %.*q, %d, %d, %#x",
                 GlobalKnownHeaders[dwQueryIndex].Text,
                 dwQueryIndex,
                 min(dwHeaderValueLength + 1, 80),
                 lpszHeaderValue,
                 dwHeaderValueLength,
                 dwIndex,
                 dwFlags
                 ));

    PERF_ENTER(ReplaceHeader);

    DWORD error = ERROR_HTTP_HEADER_NOT_FOUND;
    DWORD dwSlot;
    BYTE *pbPrevByte;

    if (!LockHeaders())
    {
        error = ERROR_NOT_ENOUGH_MEMORY;
        goto quit;
    }

    dwSlot = FastNukeFind(
                dwQueryIndex,
                dwIndex,
                &pbPrevByte
                );

    if ( dwSlot != INVALID_HEADER_SLOT)
    {
        //
        // if ADD_HEADER_IF_NEW is set, then we already have the header
        //

        if (dwFlags & ADD_HEADER_IF_NEW) {
            error = ERROR_HTTP_HEADER_ALREADY_EXISTS;
            goto Cleanup;
        }

        //
        // for both replace and remove operations, we are going to remove
        // the current header
        //

        RemoveHeader(dwSlot, dwQueryIndex, pbPrevByte);

        //
        // if replacing then add the new header value
        //

        if (dwHeaderValueLength != 0)
        {
            error = AddHeader(dwQueryIndex,
                              lpszHeaderValue,
                              dwHeaderValueLength,
                              0,
                              dwFlags
                              );
        } else {
            error = ERROR_SUCCESS;
        }
    }

    //
    // if we didn't find the header but ADD_HEADER is set then we simply add it
    // but only if the value length is not zero
    //

    if ((error == ERROR_HTTP_HEADER_NOT_FOUND)
    && (dwHeaderValueLength != 0)
    && (dwFlags & (ADD_HEADER | ADD_HEADER_IF_NEW)))
    {
        error = AddHeader(dwQueryIndex,
                          lpszHeaderValue,
                          dwHeaderValueLength,
                          0,
                          dwFlags
                          );
    }

Cleanup:

    UnlockHeaders();

quit:
    PERF_LEAVE(ReplaceHeader);

    DEBUG_LEAVE(error);

    return error;
}


DWORD
HTTP_HEADERS::FindHeader(
    IN LPSTR lpBase,
    IN LPCSTR lpszHeaderName,
    IN DWORD dwHeaderNameLength,
    IN DWORD dwModifiers,
    OUT LPVOID lpBuffer,
    IN OUT LPDWORD lpdwBufferLength,
    IN OUT LPDWORD lpdwIndex
    )

/*++

Routine Description:

    Finds a request or response header

Arguments:

    lpBase              - base for offset HEADER_STRINGs

    lpszHeaderName      - pointer to header name

    dwHeaderNameLength  - length of header name

    dwModifiers         - flags which modify returned value

    lpBuffer            - pointer to buffer for results

    lpdwBufferLength    - IN: length of lpBuffer
                          OUT: length of results, or required length of lpBuffer

    lpdwIndex           - IN: 0-based index of header to find
                          OUT: next header index if success returned

Return Value:

    DWORD
        Success - ERROR_SUCCESS

        Failure - ERROR_INSUFFICIENT_BUFFER
                    *lpdwBufferLength contains the amount required

                  ERROR_HTTP_HEADER_NOT_FOUND
                    The specified header (or index of header) was not found

                  ERROR_NOT_ENOUGH_MEMORY
                    Ran out of memory trying to acquire lock

--*/

{
    DEBUG_ENTER((DBG_HTTP,
                 Dword,
                 "HTTP_HEADERS::FindHeader",
                 "%#x [%.*q], %d, %#x, %#x [%#x], %#x, %#x [%d]",
                 lpszHeaderName,
                 min(dwHeaderNameLength + 1, 80),
                 lpszHeaderName,
                 dwHeaderNameLength,
                 lpBuffer,
                 lpdwBufferLength,
                 *lpdwBufferLength,
                 dwModifiers,
                 lpdwIndex,
                 *lpdwIndex
                 ));


    PERF_ENTER(FindHeader);



    INET_ASSERT(lpdwIndex != NULL);

    DWORD error = ERROR_HTTP_HEADER_NOT_FOUND;
    DWORD dwSlot;
    HEADER_STRING * pString;
    DWORD dwQueryIndex;
    DWORD dwHash = CalculateHashNoCase(lpszHeaderName, dwHeaderNameLength);

    if (!LockHeaders())
    {
        error = ERROR_NOT_ENOUGH_MEMORY;
        goto quit;
    }

    dwSlot = SlowFind(
                lpBase,
                lpszHeaderName,
                dwHeaderNameLength,
                *lpdwIndex,
                dwHash,
                &dwQueryIndex,
                NULL
                );

    if ( dwSlot != ((DWORD) -1) )
    {
        pString = &_lpHeaders[dwSlot];

        //
        // found the header - get to the value
        //

        DWORD stringLen;
        LPSTR value;

        stringLen = pString->StringLength();

        INET_ASSERT(stringLen > dwHeaderNameLength);

        //
        // get a pointer to the value string
        //

        value = pString->StringAddress(lpBase) + dwHeaderNameLength;
        stringLen -= dwHeaderNameLength;

        //
        // the input string could be a substring of a different header
        //

        //INET_ASSERT(*value != ':');

        //
        // find the first non-space character in the value.
        //
        // N.B.: Servers can return empty headers, so we may end up with a
        // zero length string
        //

        do {
            ++value;
            --stringLen;
        } while ((stringLen > 0) && (*value == ' '));

        //
        // get the data in the format requested by the app
        //

        LPVOID lpData = NULL;
        DWORD dwDataSize = 0;
        DWORD dwRequiredSize = 0;
        SYSTEMTIME systemTime;
        DWORD number;

        //
        // error is no longer ERROR_HTTP_HEADER_NOT_FOUND, but it might not
        // really be success either...
        //

        error = ERROR_SUCCESS;

        if (dwModifiers & HTTP_QUERY_FLAG_SYSTEMTIME) {

            char buf[DATE_AND_TIME_STRING_BUFFER_LENGTH];

            if (stringLen < sizeof(buf)) {

                //
                // value probably does not point at a zero-terminated string
                // which HttpDateToSystemTime() expects, so we make a copy
                // and terminate it
                //

                memcpy((LPVOID)buf, (LPVOID)value, stringLen);
                buf[stringLen] = '\0';
                if (HttpDateToSystemTime(buf, &systemTime)) {
                    lpData = (LPVOID)&systemTime;
                    dwRequiredSize = dwDataSize = sizeof(systemTime);
                } else {

                    //
                    // couldn't convert date/time. Presume header must be bogus
                    //

                    error = ERROR_HTTP_INVALID_QUERY_REQUEST;

                    DEBUG_PRINT(HTTP,
                                ERROR,
                                ("cannot convert %.40q to SYSTEMTIME\n",
                                value
                                ));

                }
            } else {

                //
                // we would break the date/time buffer!
                //

                error = ERROR_WINHTTP_INTERNAL_ERROR;
            }
        } else if (dwModifiers & HTTP_QUERY_FLAG_NUMBER) {
            if (isdigit(*value)) {
                number = 0;
                for (int i = 0;
                    (stringLen > 0) && isdigit(value[i]);
                    ++i, --stringLen) {

                    number = number * 10 + (DWORD)(value[i] - '0');
                }
                lpData = (LPVOID)&number;
                dwRequiredSize = dwDataSize = sizeof(number);
            } else {

                //
                // not a numeric field. Request must be bogus for this header
                //

                error = ERROR_HTTP_INVALID_QUERY_REQUEST;

                DEBUG_PRINT(HTTP,
                            ERROR,
                            ("cannot convert %.20q to NUMBER\n",
                            value
                            ));

            }
        } else {
            lpData = (LPVOID)value;
            dwDataSize = stringLen;
            dwRequiredSize = dwDataSize + 1;
        }

        //
        // if error == ERROR_SUCCESS then we can attempt to copy the data
        //

        if (error == ERROR_SUCCESS) {
            if (*lpdwBufferLength < dwRequiredSize) {
                *lpdwBufferLength = dwRequiredSize;
                error = ERROR_INSUFFICIENT_BUFFER;
            } else {
                memcpy(lpBuffer, lpData, dwDataSize);
                *lpdwBufferLength = dwDataSize;

                //
                // if dwRequiredSize > dwDataSize, then this is a variable-
                // length item (i.e. a STRING!) so we add a terminating '\0'
                //

                if (dwRequiredSize > dwDataSize) {

                    INET_ASSERT(dwRequiredSize - dwDataSize == 1);

                    ((LPSTR)lpBuffer)[dwDataSize] = '\0';
                }

                //
                // successfully retrieved the requested header - bump the
                // index
                //

                ++*lpdwIndex;
            }
        }
    }

    UnlockHeaders();

quit:
    PERF_LEAVE(FindHeader);

    DEBUG_LEAVE(error);

    return error;
}



DWORD
HTTP_HEADERS::FindHeader(
    IN LPSTR lpBase,
    IN DWORD dwQueryIndex,
    IN DWORD dwModifiers,
    OUT LPVOID lpBuffer,
    IN OUT LPDWORD lpdwBufferLength,
    IN OUT LPDWORD lpdwIndex
    )
/*++

Routine Description:

    Finds a request or response header, based on index to the header name we are searching for.

Arguments:

    lpBase              - base for offset HEADER_STRINGs

    dwQueryIndex        - a index into a array of known HTTP headers, see wininet.h HTTP_QUERY_* codes

    dwModifiers         - flags which modify returned value

    lpBuffer            - pointer to buffer for results

    lpdwBufferLength    - IN: length of lpBuffer
                          OUT: length of results, or required length of lpBuffer

    lpdwIndex           - IN: 0-based index of header to find
                          OUT: next header index if success returned

Return Value:

    DWORD
        Success - ERROR_SUCCESS

        Failure - ERROR_INSUFFICIENT_BUFFER
                    *lpdwBufferLength contains the amount required

                  ERROR_HTTP_HEADER_NOT_FOUND
                    The specified header (or index of header) was not found

--*/
{

    DWORD error;
    LPSTR lpData;
    DWORD dwDataSize = 0;
    DWORD dwRequiredSize = 0;
    SYSTEMTIME systemTime;
    DWORD number;

    error = FastFindHeader(
                lpBase,
                dwQueryIndex,
                (LPVOID *)&lpData,
                &dwDataSize,
                *lpdwIndex
                );

    if ( error != ERROR_SUCCESS )
    {
        goto quit;
    }

    //
    // get the data in the format requested by the app
    //

    if (dwModifiers & HTTP_QUERY_FLAG_SYSTEMTIME)
    {
        char buf[DATE_AND_TIME_STRING_BUFFER_LENGTH];

        if (dwDataSize < sizeof(buf))
        {

            //
            // value probably does not point at a zero-terminated string
            // which HttpDateToSystemTime() expects, so we make a copy
            // and terminate it
            //

            memcpy((LPVOID)buf, (LPVOID)lpData, dwDataSize);
            buf[dwDataSize] = '\0';
            if (HttpDateToSystemTime(buf, &systemTime)) {
                lpData = (LPSTR)&systemTime;
                dwRequiredSize = dwDataSize = sizeof(systemTime);
            } else {

                //
                // couldn't convert date/time. Presume header must be bogus
                //

                error = ERROR_HTTP_INVALID_QUERY_REQUEST;

                DEBUG_PRINT(HTTP,
                            ERROR,
                            ("cannot convert %.40q to SYSTEMTIME\n",
                            lpData
                            ));

            }
        }
        else
        {

            //
            // we would break the date/time buffer!
            //

            error = ERROR_WINHTTP_INTERNAL_ERROR;
        }
    }
    else if (dwModifiers & HTTP_QUERY_FLAG_NUMBER)
    {
        if (isdigit(*lpData)) {
            number = 0;
            for (int i = 0;
                (dwDataSize > 0) && isdigit(lpData[i]);
                ++i, --dwDataSize) {

                number = number * 10 + (DWORD)(lpData[i] - '0');
            }
            lpData = (LPSTR)&number;
            dwRequiredSize = dwDataSize = sizeof(number);
        } else {

            //
            // not a numeric field. Request must be bogus for this header
            //

            error = ERROR_HTTP_INVALID_QUERY_REQUEST;

            DEBUG_PRINT(HTTP,
                        ERROR,
                        ("cannot convert %.20q to NUMBER\n",
                        lpData
                        ));

        }
    }
    else
    {
        dwRequiredSize = dwDataSize + 1;
    }

    //
    // if error == ERROR_SUCCESS then we can attempt to copy the data
    //

    if (error == ERROR_SUCCESS)
    {
        if (*lpdwBufferLength < dwRequiredSize)
        {
            *lpdwBufferLength = dwRequiredSize;
            error = ERROR_INSUFFICIENT_BUFFER;
        }
        else
        {
            memcpy(lpBuffer, lpData, dwDataSize);
            *lpdwBufferLength = dwDataSize;

            //
            // if dwRequiredSize > dwDataSize, then this is a variable-
            // length item (i.e. a STRING!) so we add a terminating '\0'
            //

            if (dwRequiredSize > dwDataSize)
            {
                INET_ASSERT(dwRequiredSize - dwDataSize == 1);

                ((LPSTR)lpBuffer)[dwDataSize] = '\0';
            }

            //
            // successfully retrieved the requested header - bump the
            // index
            //

            ++*lpdwIndex;
        }
    }
quit:

    return error;
}



DWORD
HTTP_HEADERS::FastFindHeader(
    IN LPSTR lpBase,
    IN DWORD dwQueryIndex,
    OUT LPVOID *lplpBuffer,
    IN OUT LPDWORD lpdwBufferLength,
    IN DWORD dwIndex
    )

/*++

Routine Description:

    Finds a request or response header slightly quicker than its higher level
     cousin, FindHeader.   Unlike FindHeader this function simply returns
     a pointer and length, and does not copy header data.


    lpBase              - base address of strings

    dwQueryIndex        - a index into a array known HTTP headers, see wininet.h HTTP_QUERY_* codes

    lplpBuffer          - pointer to pointer of the actual header to be returned in.

    lpdwBufferLength    - OUT: if successful, length of output buffer, minus 1
                               for any trailing EOS, or if the buffer is not
                               large enough, the size required

    dwIndex             - a index of which header we're asking for, as there can be multiple headers
                          under the same name.

Arguments:

    lpBase              - base for offset HEADER_STRINGs

    lpszHeaderName      - pointer to header name

    dwHeaderNameLength  - length of header name

    dwModifiers         - flags which modify returned value

    lpBuffer            - pointer to buffer for results

    lpdwBufferLength    - IN: length of lpBuffer
                          OUT: length of results, or required length of lpBuffer

    lpdwIndex           - IN: 0-based index of header to find
                          OUT: next header index if success returned

Return Value:

    DWORD
        Success - ERROR_SUCCESS

        Failure - ERROR_INSUFFICIENT_BUFFER
                    *lpdwBufferLength contains the amount required

                  ERROR_HTTP_HEADER_NOT_FOUND
                    The specified header (or index of header) was not found

--*/

{
    DEBUG_ENTER((DBG_HTTP,
                 Dword,
                 "HTTP_HEADERS::FastFindHeader",
                 "%q, %#x, %#x [%#x], %u",
                 GlobalKnownHeaders[dwQueryIndex].Text,
                 lplpBuffer,
                 lpdwBufferLength,
                 *lpdwBufferLength,
                 dwIndex
                 ));

    PERF_ENTER(FindHeader);

    DWORD error = ERROR_HTTP_HEADER_NOT_FOUND;

    HEADER_STRING * curHeader;
    DWORD dwSlot;

    dwSlot = FastFind(dwQueryIndex, dwIndex);

    if ( dwSlot != INVALID_HEADER_SLOT)
    {
        //
        // found the header - get to the value
        //

        DWORD stringLen;
        LPSTR value;

        curHeader = GetSlot(dwSlot);

        //
        // get a pointer to the value string
        //

        value     = curHeader->StringAddress(lpBase) + (GlobalKnownHeaders[dwQueryIndex].Length+1);
        stringLen = curHeader->StringLength() - (GlobalKnownHeaders[dwQueryIndex].Length+1);

        //
        // find the first non-space character in the value.
        //
        // N.B.: Servers can return empty headers, so we may end up with a
        // zero length string
        //

        while ((stringLen > 0) && (*value == ' '))
        {
            ++value;
            --stringLen;
        }

        //
        // get the data in the format requested by the app
        //

        //
        // error is no longer ERROR_HTTP_HEADER_NOT_FOUND, but it might not
        // really be success either...
        //

        error = ERROR_SUCCESS;

        *lplpBuffer = (LPVOID)value;
        *lpdwBufferLength = stringLen;
    }

    PERF_LEAVE(FindHeader);

    DEBUG_LEAVE(error);

    return error;
}



DWORD
HTTP_HEADERS::QueryRawHeaders(
    IN LPSTR lpBase,
    IN BOOL bCrLfTerminated,
    IN LPVOID lpBuffer,
    IN OUT LPDWORD lpdwBufferLength
    )

/*++

Routine Description:

    Returns all the request or response headers in a single buffer. The headers
    can be returned as ASCIIZ strings, or CR-LF terminated strings

Arguments:

    lpBase              - base address of strings

    bCrLfTerminated     - TRUE if each string is terminated with CR-LF

    lpBuffer            - pointer to buffer to write headers

    lpdwBufferLength    - IN: length of lpBuffer
                          OUT: if successful, length of output buffer, minus 1
                               for any trailing EOS, or if the buffer is not
                               large enough, the size required

Return Value:

    DWORD
        Success - ERROR_SUCCESS

        Failure - ERROR_INSUFFICIENT_BUFFER

                  ERROR_NOT_ENOUGH_MEMORY
                    Ran out of memory trying to acquire lock
--*/

{
    PERF_ENTER(QueryRawHeaders);

    DWORD error = ERROR_SUCCESS;
    DWORD requiredLength = 0;
    LPSTR lpszBuffer = (LPSTR)lpBuffer;

    if (!LockHeaders())
    {
        error = ERROR_NOT_ENOUGH_MEMORY;
        goto quit;
    }

    for (DWORD i = 0; i < _TotalSlots; ++i) {
        if (_lpHeaders[i].HaveString()) {

            DWORD length;

            length = _lpHeaders[i].StringLength();

            requiredLength += length + (bCrLfTerminated ? 2 : 1);
            if (*lpdwBufferLength > requiredLength) {
                _lpHeaders[i].CopyTo(lpBase, lpszBuffer);
                lpszBuffer += length;
                if (bCrLfTerminated) {
                    *lpszBuffer++ = '\r';
                    *lpszBuffer++ = '\n';
                } else {
                    *lpszBuffer++ = '\0';
                }
            }
        }
    }

    if (bCrLfTerminated)
    {
        requiredLength += 2;
        if (*lpdwBufferLength > requiredLength)
        {
            *lpszBuffer++ = '\r';
            *lpszBuffer++ = '\n';
        }
    }

    UnlockHeaders();

    ++requiredLength;

    if (*lpdwBufferLength < requiredLength) {
        error = ERROR_INSUFFICIENT_BUFFER;
    } else {
        *lpszBuffer = '\0';
        --requiredLength;   // remove 1 for trailing '\0'
    }
    *lpdwBufferLength = requiredLength;

quit:
    PERF_LEAVE(QueryRawHeaders);

    return error;
}


DWORD
HTTP_HEADERS::QueryFilteredRawHeaders(
    IN LPSTR lpBase,
    IN LPSTR *lplpFilterList,
    IN DWORD cListElements,
    IN BOOL  fExclude,
    IN BOOL  fSkipVerb,
    IN BOOL bCrLfTerminated,
    IN LPVOID lpBuffer,
    IN OUT LPDWORD lpdwBufferLength
    )

/*++

Routine Description:

    Returns all the request or response headers in a single buffer. The headers
    can be returned as ASCIIZ strings, or CR-LF terminated strings

Arguments:

    lpBase              - base address of strings

    bCrLfTerminated     - TRUE if each string is terminated with CR-LF

    lpBuffer            - pointer to buffer to write headers

    lpdwBufferLength    - IN: length of lpBuffer
                          OUT: if successful, length of output buffer, minus 1
                               for any trailing EOS, or if the buffer is not
                               large enough, the size required

Return Value:

    DWORD
        Success - ERROR_SUCCESS

        Failure - ERROR_INSUFFICIENT_BUFFER

--*/

{
    DWORD error = ERROR_NOT_SUPPORTED;

    DWORD requiredLength = 0;
    LPSTR lpszBuffer = (LPSTR)lpBuffer;
    BOOL fCopy;

    DWORD i = fSkipVerb ? 1 : 0;
    for (; i < _TotalSlots; ++i) {
       if (_lpHeaders[i].HaveString()) {
          fCopy = TRUE;
          if (lplpFilterList
             && FMatchList(lplpFilterList, cListElements, _lpHeaders+i, lpBase)) {
             fCopy = fExclude?FALSE:TRUE;
          }
          if (fCopy) {
              DWORD length;

              length = _lpHeaders[i].StringLength();
              requiredLength += length + (bCrLfTerminated ? 2 : 1);
              if (*lpdwBufferLength > requiredLength) {
                    _lpHeaders[i].CopyTo(lpBase, lpszBuffer);
                   lpszBuffer += length;
                   if (bCrLfTerminated) {
                       *lpszBuffer++ = '\r';
                       *lpszBuffer++ = '\n';
                    } else {
                       *lpszBuffer++ = '\0';
                   }
                }
            }
        }
    }

    if (bCrLfTerminated)
    {
        requiredLength += 2;
        if (*lpdwBufferLength > requiredLength)
        {
            *lpszBuffer++ = '\r';
            *lpszBuffer++ = '\n';
        }
    }

    ++requiredLength;


    if (*lpdwBufferLength < requiredLength) {
        error = ERROR_INSUFFICIENT_BUFFER;
    } else {
        *lpszBuffer = '\0';
        --requiredLength;   // remove 1 for trailing '\0'
        error = ERROR_SUCCESS;
    }
    *lpdwBufferLength = requiredLength;
    return error;
}


DWORD
HTTP_HEADERS::AddRequest(
    IN LPSTR lpszVerb,
    IN LPSTR lpszObject,
    IN LPSTR lpszVersion
    )

/*++

Routine Description:

    Builds the request line from its constituent parts. The request line is the
    first (0th) header in the request headers

    Assumes:    1. This is the one-and-only call to this method
                2. lpszObject must already be escaped if necessary

Arguments:

    lpszVerb    - pointer to HTTP verb, e.g. "GET"

    lpszObject  - pointer to HTTP object name, e.g. "/users/albert/~emc2.htm".

    lpszVersion - pointer to HTTP version string, e.g. "HTTP/1.0"

Return Value:

    DWORD
        Success - ERROR_SUCCESS

        Failure - ERROR_NOT_ENOUGH_MEMORY

--*/

{
    PERF_ENTER(AddRequest);

    //
    // there must not be a header when this method is called
    //

    INET_ASSERT(_HeadersLength == 0);

    DWORD error = ERROR_SUCCESS;
    int verbLen = lstrlen(lpszVerb);
    int objectLen = lstrlen(lpszObject);
    int versionLen = lstrlen(lpszVersion);
    int len = verbLen       // "GET"
            + 1             //     ' '
            + objectLen     //        "/users/albert/~emc2.htm"
            + 1             //                                 ' '
            + versionLen    //                                    "HTTP/1.0"
            + 1             //                                              '\0'
            ;

    //
    // we are about to start updating the headers for the current
    // HTTP_REQUEST_HANDLE_OBJECT. Serialize access
    //

    HEADER_STRING * pRequest = GetFirstHeader();
    HEADER_STRING & request = *pRequest;

    if (pRequest == NULL) {
        error = ERROR_NOT_ENOUGH_MEMORY;
        goto quit;
    }

    INET_ASSERT(!request.HaveString());

    _lpszVerb = NULL;
    _dwVerbLength = 0;
    _lpszObjectName = NULL;
    _dwObjectNameLength = 0;
    _lpszVersion = NULL;
    _dwVersionLength = 0;

    request.CreateStringBuffer((LPVOID)lpszVerb, verbLen, len);
    if (request.IsError()) {
        error = GetLastError();

        INET_ASSERT(error != ERROR_SUCCESS);

    } else {
        request += ' ';
        request.Strncat((LPVOID)lpszObject, objectLen);
        request += ' ';
        request.Strncat((LPVOID)lpszVersion, versionLen);

        _HeadersLength = len - 1 + (sizeof("\r\n") - 1);

        //
        // we have used the first free slot in the headers array
        //

        --_FreeSlots;

        //
        // update the component variables in case of a ModifyRequest()
        //

        _lpszVerb = request.StringAddress();
        _dwVerbLength = verbLen;
        _lpszObjectName = _lpszVerb + verbLen + 1;
        _dwObjectNameLength = objectLen;
        _lpszVersion = _lpszObjectName + objectLen + 1;
        _dwVersionLength = versionLen;
        SetRequestVersion();
        error = request.IsError() ? ::GetLastError() : ERROR_SUCCESS;
    }

quit:

    PERF_LEAVE(AddRequest);

    return error;
}


DWORD
HTTP_HEADERS::ModifyRequest(
    IN HTTP_METHOD_TYPE tMethod,
    IN LPSTR lpszObjectName,
    IN DWORD dwObjectNameLength,
    IN LPSTR lpszVersion OPTIONAL,
    IN DWORD dwVersionLength
    )

/*++

Routine Description:

    Updates the request line. Used in redirection

Arguments:

    tMethod             - type of new method

    lpszObjectName      - pointer to new object name

    dwObjectNameLength  - length of new object name

    lpszVersion         - optional pointer to version string

    dwVersionLength     - length of lpszVersion string if present

Return Value:

    DWORD
        Success - ERROR_SUCCESS

        Failure - ERROR_NOT_ENOUGH_MEMORY

--*/

{
    DEBUG_ENTER((DBG_HTTP,
                 Dword,
                 "ModifyRequest",
                 "%s, %q, %d, %q, %d",
                 MapHttpMethodType(tMethod),
                 lpszObjectName,
                 dwObjectNameLength,
                 lpszVersion,
                 dwVersionLength
                 ));

    PERF_ENTER(ModifyRequest);

    INET_ASSERT(lpszObjectName != NULL);
    INET_ASSERT(dwObjectNameLength != 0);

    //
    // there must already be a header when this method is called
    //

    INET_ASSERT(_HeadersLength != 0);

    //
    // we are about to start updating the headers for the current
    // HTTP_REQUEST_HANDLE_OBJECT. Serialize access
    //

    //
    // BUGBUG [arthurbi] using two HEADER_STRINGs here causes an extra
    //  ReAlloc when use the Copy operator between the two.
    //

    HEADER_STRING * pRequest = GetFirstHeader();
    HEADER_STRING & request = *pRequest;
    HEADER_STRING newRequest;
    LPCSTR lpcszVerb;
    DWORD verbLength;
    DWORD error = ERROR_SUCCESS;
    DWORD length;

    //
    // there must already be a request line
    //

    if (pRequest == NULL) {
        error = ERROR_NOT_ENOUGH_MEMORY;
        goto quit;
    }

    INET_ASSERT(request.HaveString());

    //
    // get the verb/method to use.
    //

    if (tMethod == HTTP_METHOD_TYPE_UNKNOWN) {

        //
        // the method is unknown, read the old one out of the string
        //  and save off, basically we're reusing the previous one.
        //

        lpcszVerb = request.StringAddress();

        for (DWORD i = 0; i < request.StringLength(); i++) {
            if (lpcszVerb[i] == ' ') {
                break;
            }
        }

        INET_ASSERT((i > 0) && (i < (DWORD)request.StringLength()));

        verbLength = (DWORD)i;
    } else {

        //
        // its one of the normal kind, just map it.
        //

        verbLength = MapHttpMethodType(tMethod, &lpcszVerb);
    }
    if (lpszVersion == NULL) {
        lpszVersion = _lpszVersion;
        dwVersionLength = _dwVersionLength;
    }

    _lpszVerb = NULL;
    _dwVerbLength = 0;
    _lpszObjectName = NULL;
    _dwObjectNameLength = 0;
    _lpszVersion = NULL;
    _dwVersionLength = 0;

    //
    // calculate the new length from the component lengths we originally set
    // in AddRequest(), and the new object name
    //

    length = verbLength + 1 + dwObjectNameLength + 1 + dwVersionLength + 1;

    //
    // create a new request line
    //

    newRequest.CreateStringBuffer((LPVOID)lpcszVerb, verbLength, length);
    if (newRequest.IsError()) {
        error = GetLastError();
    } else {
        newRequest += ' ';
        newRequest.Strncat((LPVOID)lpszObjectName, dwObjectNameLength);
        newRequest += ' ';
        newRequest.Strncat((LPVOID)lpszVersion, dwVersionLength);

        //
        // remove the current request line length from the header buffer
        // aggregate
        //

        _HeadersLength -= request.StringLength();

        //
        // make the current request line the new one
        //

        request = newRequest.StringAddress();

        //
        // and update the address and length variables (version length is the
        // only thing that stays the same)
        //

        if (!request.IsError()) {
            _lpszVerb = request.StringAddress();
            _dwVerbLength = verbLength;
            _lpszObjectName = _lpszVerb + verbLength + 1;
            _dwObjectNameLength = dwObjectNameLength;
            _lpszVersion = _lpszObjectName + dwObjectNameLength + 1;
            _dwVersionLength = dwVersionLength;
            SetRequestVersion();

        //
        // and the new request line length to the aggregate header length
        //

            _HeadersLength += request.StringLength();
        } else {
            error = GetLastError();
        }
    }

quit:

    PERF_LEAVE(ModifyRequest);

    DEBUG_LEAVE(error);

    return error;
}


VOID
HTTP_HEADERS::SetRequestVersion(
    VOID
    )

/*++

Routine Description:

    Set _RequestVersionMajor and _RequestVersionMinor based on the HTTP
    version string

Arguments:

    None.

Return Value:

    None.

--*/

{
    DEBUG_ENTER((DBG_HTTP,
                 None,
                 "HTTP_HEADERS::SetRequestVersion",
                 NULL
                 ));

    INET_ASSERT(_lpszVersion != NULL);

    _RequestVersionMajor = 0;
    _RequestVersionMinor = 0;
    if (strncmp(_lpszVersion, "HTTP/", sizeof("HTTP/") - 1) == 0) {

        LPSTR pNum = _lpszVersion + sizeof("HTTP/") - 1;

        ExtractInt(&pNum, 0, (LPINT)&_RequestVersionMajor);
        while (!isdigit(*pNum) && (*pNum != '\0')) {
            ++pNum;
        }
        ExtractInt(&pNum, 0, (LPINT)&_RequestVersionMinor);

        DEBUG_PRINT(HTTP,
                    INFO,
                    ("request version = %d.%d\n",
                    _RequestVersionMajor,
                    _RequestVersionMinor
                    ));

    } else {

        DEBUG_PRINT(HTTP,
                    WARNING,
                    ("\"HTTP/\" not found in %q\n",
                    _lpszVersion
                    ));

    }

    DEBUG_LEAVE(0);
}