<reponame>npocmaka/Windows-Server-2003
/*++

Copyright (c) 1995  Microsoft Corporation

Module Name:

    headers.cxx

Abstract:

    Contents:

        HTTP_REQUEST_HANDLE_OBJECT::CreateRequestBuffer
        HTTP_REQUEST_HANDLE_OBJECT::QueryRequestHeader
        HTTP_REQUEST_HANDLE_OBJECT::AddInternalResponseHeader
        HTTP_REQUEST_HANDLE_OBJECT::UpdateResponseHeaders
        HTTP_REQUEST_HANDLE_OBJECT::CreateResponseHeaders
        HTTP_REQUEST_HANDLE_OBJECT::QueryResponseVersion
        HTTP_REQUEST_HANDLE_OBJECT::QueryStatusCode
        HTTP_REQUEST_HANDLE_OBJECT::QueryStatusText
        HTTP_REQUEST_HANDLE_OBJECT::QueryRawResponseHeaders
        HTTP_REQUEST_HANDLE_OBJECT::RemoveAllRequestHeadersByName
        HTTP_REQUEST_HANDLE_OBJECT::CheckWellKnownHeaders
        MapHttpMethodType
        CreateEscapedUrlPath
        (CalculateHashNoCase)
Author:

    <NAME> (rfirth) 20-Dec-1995

Revision History:

    20-Dec-1995 rfirth
        Created

--*/

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

VOID
HTTP_REQUEST_HANDLE_OBJECT::ReplaceStatusHeader(
    IN LPCSTR lpszStatus
    )
/*
Description:
    Replace the status line in a header (eg. "200 OK") with the specified text.
Arguments:
    lpszStatus	- Status text (eg. "200 OK")
Return Value:
    None
*/
{
    LockHeaders();
    
    //INET_ASSERT (!_CacheWriteInProgress);
    
    LPSTR pszHeader = _ResponseHeaders.GetHeaderPointer(_ResponseBuffer, 0);
    
    INET_ASSERT(pszHeader);
    
    LPSTR pszStatus = StrChr (pszHeader, ' ');
    SKIPWS(pszStatus);
    
    INET_ASSERT (!memcmp(pszStatus, "206", 3));
    
    memcpy(pszStatus, lpszStatus, lstrlen(lpszStatus)+1);
    _ResponseHeaders.ShrinkHeader(_ResponseBuffer, 0,
        HTTP_QUERY_STATUS_TEXT, HTTP_QUERY_STATUS_TEXT,
        (DWORD) (pszStatus - pszHeader) + lstrlen(lpszStatus));
    
    UnlockHeaders();
}


LPSTR
HTTP_REQUEST_HANDLE_OBJECT::CreateRequestBuffer(
    OUT LPDWORD lpdwRequestLength,
    IN LPVOID lpOptional,
    IN DWORD dwOptionalLength,
    IN DWORD dwMaxPacketLength,
    OUT LPBOOL lpbCombinedData
    )

/*++

Routine Description:

    Creates a request buffer from the HTTP request and headers

Arguments:

    lpdwRequestLength   - pointer to returned buffer length

    lpOptional          - pointer to optional data

    dwOptionalLength    - length of optional data

    dwMaxPacketLength   - maximum length of buffer

    lpbCombinedData     - output TRUE if data successfully combined into one

Return Value:

    LPSTR
        Success - pointer to allocated buffer

        Failure - NULL

--*/

{
    DEBUG_ENTER((DBG_HTTP,
                 Pointer,
                 "HTTP_REQUEST_HANDLE_OBJECT::CreateRequestBuffer",
                 "%#x, %#x, %d, %B, %d, %#x",
                 lpdwRequestLength,
                 lpOptional,
                 dwOptionalLength,
                 dwMaxPacketLength,
                 lpbCombinedData
                 ));

    PERF_ENTER(CreateRequestBuffer);

    LPSTR requestBuffer = NULL;

    *lpbCombinedData = FALSE;

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

    DWORD headersLength;
    DWORD requestLength;
    DWORD optionalLength;
    HEADER_STRING * pRequest = _RequestHeaders.GetFirstHeader();
    HEADER_STRING & request = *pRequest;
/*
    WCHAR wszUrl[1024];
    LPWSTR pwszUrl = NULL;
    BYTE utf8Url[2048];
    LPBYTE pbUrl = NULL;
*/
    LPSTR pszObject = _RequestHeaders.ObjectName();
    DWORD dwObjectLength = _RequestHeaders.ObjectNameLength();

    if (pRequest == NULL) {
        goto Cleanup;
    }
    UNREFERENCED_PARAMETER(request);
    INET_ASSERT(request.HaveString());

    headersLength = _RequestHeaders.HeadersLength();
    requestLength = headersLength + (sizeof("\r\n") - 1);

/*------------------------------------------------------------------
    GlobalEnableUtf8Encoding = FALSE;
    if (GlobalEnableUtf8Encoding
        && StringContainsHighAnsi(pszObject, dwObjectLength)) {

        pwszUrl = wszUrl;

        DWORD arrayElements = ARRAY_ELEMENTS(wszUrl);

        if (dwObjectLength > ARRAY_ELEMENTS(wszUrl)) {
            arrayElements = dwObjectLength;
            pwszUrl = (LPWSTR)ALLOCATE_FIXED_MEMORY(arrayElements * sizeof(*pwszUrl));
            if (pwszUrl == NULL) {
                goto utf8_cleanup;
            }
        }



        PFNINETMULTIBYTETOUNICODE pfnMBToUnicode;
        pfnMBToUnicode = GetInetMultiByteToUnicode( );
        if (pfnMBToUnicode == NULL) {
            goto utf8_cleanup;
        }

        HRESULT hr;
        DWORD dwMode;
        INT nMBChars;
        INT nWChars;

        nMBChars = dwObjectLength;
        nWChars = arrayElements;
        dwMode = 0;

        hr = pfnMBToUnicode(&dwMode,
                                GetCodePage(),
                                pszObject,
                                &nMBChars,
                                pwszUrl,
                                &nWChars
                               );
        if (hr != S_OK || nWChars == 0) {
            goto utf8_cleanup;
        }

        DWORD nBytes;

        nBytes = CountUnicodeToUtf8(pwszUrl, (DWORD)nWChars, TRUE);
        pbUrl = utf8Url;
        if (nBytes > ARRAY_ELEMENTS(utf8Url)) {
            pbUrl = (LPBYTE)ALLOCATE_FIXED_MEMORY(nBytes);
            if (pbUrl == NULL) {
                goto utf8_cleanup;
            }
        }

        DWORD error;

        error = ConvertUnicodeToUtf8(pwszUrl,
                                     (DWORD)nWChars,
                                     pbUrl,
                                     nBytes,
                                     TRUE
                                     );

        INET_ASSERT(error == ERROR_SUCCESS);

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

        requestLength = requestLength - dwObjectLength + nBytes;
        headersLength = headersLength - dwObjectLength + nBytes;
        pszObject = (LPSTR)pbUrl;
        dwObjectLength = nBytes;
        goto after_utf8;

utf8_cleanup:

        if ((pwszUrl != wszUrl) && (pwszUrl != NULL)) {
            FREE_MEMORY(pwszUrl);
        }
        pwszUrl = NULL;
        if ((pbUrl != utf8Url) && (pbUrl != NULL)) {
            FREE_MEMORY(pbUrl);
        }
        pbUrl = NULL;
        pszObject = NULL;
        dwObjectLength = 0;
    }

after_utf8:
------------------------------------------------------------------*/

    optionalLength = dwOptionalLength;
    if (requestLength + optionalLength <= dwMaxPacketLength) {
        requestLength += optionalLength;
    } else {
        optionalLength = 0;
    }

    requestBuffer = (LPSTR)ResizeBuffer(NULL, requestLength, FALSE);
    if (requestBuffer != NULL) {
        if (optionalLength != 0) {
            *lpbCombinedData = TRUE;
        }
    } else if (optionalLength != 0) {
        requestLength = headersLength + (sizeof("\r\n") - 1);
        optionalLength = 0;
        requestBuffer = (LPSTR)ResizeBuffer(NULL, requestLength, FALSE);
    }
    if (requestBuffer != NULL) {

        LPSTR buffer = requestBuffer;

        //
        // copy the headers. Remember: header 0 is the request
        //

        if (ERROR_SUCCESS != _RequestHeaders.CopyHeaders(&buffer, pszObject, dwObjectLength))
        {
            ResizeBuffer(requestBuffer, 0, FALSE);
            requestBuffer = buffer = NULL;
            goto Cleanup;
        }

        //
        // terminate the request
        //

        *buffer++ = '\r';
        *buffer++ = '\n';

        if (optionalLength != 0) {
            if (dwOptionalLength != 0) {
                memcpy(buffer, lpOptional, dwOptionalLength);
                buffer += dwOptionalLength;
            }
        }

        INET_ASSERT((SIZE_T)(buffer-requestBuffer) == requestLength);

        *lpdwRequestLength = requestLength;

    }

Cleanup:

    _RequestHeaders.UnlockHeaders();

    DEBUG_PRINT(HTTP,
                INFO,
                ("request length = %d, combined = %B\n",
                *lpdwRequestLength,
                *lpbCombinedData
                ));

/*
    if ((pbUrl != NULL) && (pbUrl != utf8Url)) {
        FREE_MEMORY(pbUrl);
    }
    if ((pwszUrl != NULL) && (pwszUrl != wszUrl)) {
        FREE_MEMORY(pwszUrl);
    }
*/

quit:
    PERF_LEAVE(CreateRequestBuffer);

    DEBUG_LEAVE(requestBuffer);

    return requestBuffer;
}


DWORD
HTTP_REQUEST_HANDLE_OBJECT::QueryRequestHeader(
    IN LPCSTR lpszHeaderName,
    IN DWORD dwHeaderNameLength,
    IN LPVOID lpBuffer,
    IN OUT LPDWORD lpdwBufferLength,
    IN DWORD dwModifiers,
    IN OUT LPDWORD lpdwIndex
    )

/*++

Routine Description:

    Searches for an arbitrary request header and if found, returns its value

Arguments:

    lpszHeaderName      - pointer to the name of the header to find

    dwHeaderNameLength  - length of the header

    lpBuffer            - pointer to buffer for results

    lpdwBufferLength    - IN: length of lpBuffer
                          OUT: length of the returned header value, or required
                               length of lpBuffer

    dwModifiers         - how to return the data: as number, as SYSTEMTIME
                          structure, etc.

    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
                    lpBuffer not large enough for results
                    
                  ERROR_HTTP_HEADER_NOT_FOUND
                    Couldn't find the requested header

--*/

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

    PERF_ENTER(QueryRequestHeader);

    DWORD error;

    error = _RequestHeaders.FindHeader(NULL,
                                       lpszHeaderName,
                                       dwHeaderNameLength,
                                       dwModifiers,
                                       lpBuffer,
                                       lpdwBufferLength,
                                       lpdwIndex
                                       );

    PERF_LEAVE(QueryRequestHeader);

    DEBUG_LEAVE(error);

    return error;
}

DWORD
HTTP_REQUEST_HANDLE_OBJECT::QueryRequestHeader(
    IN DWORD dwQueryIndex,
    IN LPVOID lpBuffer,
    IN OUT LPDWORD lpdwBufferLength,
    IN DWORD dwModifiers,
    IN OUT LPDWORD lpdwIndex
    )

/*++

Routine Description:

    Searches for an arbitrary request header and if found, returns its value

Arguments:

    lpszHeaderName      - pointer to the name of the header to find

    dwHeaderNameLength  - length of the header

    lpBuffer            - pointer to buffer for results

    lpdwBufferLength    - IN: length of lpBuffer
                          OUT: length of the returned header value, or required
                               length of lpBuffer

    dwModifiers         - how to return the data: as number, as SYSTEMTIME
                          structure, etc.

    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
                    lpBuffer not large enough for results
                    
                  ERROR_HTTP_HEADER_NOT_FOUND
                    Couldn't find the requested header

--*/

{
    DEBUG_ENTER((DBG_HTTP,
                 Dword,
                 "QueryRequestHeader",
                 "%u, %#x [%#x], %#x, %#x [%d]",
                 dwQueryIndex,
                 lpBuffer,
                 lpdwBufferLength,
                 *lpdwBufferLength,
                 dwModifiers,
                 lpdwIndex,
                 *lpdwIndex
                 ));

    PERF_ENTER(QueryRequestHeader);

    DWORD error;

    error = _RequestHeaders.FindHeader(NULL,
                                       dwQueryIndex,
                                       dwModifiers,
                                       lpBuffer,
                                       lpdwBufferLength,
                                       lpdwIndex
                                       );

    PERF_LEAVE(QueryRequestHeader);

    DEBUG_LEAVE(error);

    return error;
}



DWORD
HTTP_REQUEST_HANDLE_OBJECT::AddInternalResponseHeader(
    IN DWORD dwHeaderIndex,
    IN LPSTR lpszHeader,
    IN DWORD dwHeaderLength
    )

/*++

Routine Description:

    Adds a created response header to the response header array. Unlike normal
    response headers, this will be a pointer to an actual string, not an offset
    into the response buffer.

    Even if the address of the response buffer changes, created response headers
    will remain fixed

    N.B. The header MUST NOT have a CR-LF terminator
    N.B.-2 This function must be called under the header lock.

Arguments:

    dwHeaderIndex   - index into header value we are actually creating

    lpszHeader      - pointer to created (internal) header to add

    dwHeaderLength  - length of response header, or -1 if ASCIIZ

Return Value:

    DWORD
        Success - ERROR_SUCCESS

        Failure - ERROR_NOT_ENOUGH_MEMORY

--*/

{
    DEBUG_ENTER((DBG_HTTP,
                 Dword,
                 "AddInternalResponseHeader",
                 "%u [%q], %q, %d",
                 dwHeaderIndex,
                 GlobalKnownHeaders[dwHeaderIndex].Text,
                 lpszHeader,
                 dwHeaderLength
                 ));

    DWORD error;

    if (dwHeaderLength == (DWORD)-1) {
        dwHeaderLength = lstrlen(lpszHeader);
    }

    INET_ASSERT((lpszHeader[dwHeaderLength - 1] != '\r')
                && (lpszHeader[dwHeaderLength - 1] != '\n'));

    //
    // find the next slot for this header
    //

    HEADER_STRING * freeHeader;

    //
    // if we already have all the headers (the 'empty' header is the last one
    // in the array) then change the last header to be the one we are adding
    // and add a new empty header, else just add this one
    //

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

        INET_ASSERT(error != ERROR_SUCCESS);

    } else {

        HEADER_STRING * lastHeader;

        lastHeader = _ResponseHeaders.GetEmptyHeader();
        if (lastHeader != NULL) {

            //
            // make copy of last header - its an offset string
            //

            *freeHeader = *lastHeader;

            //
            // use what was last header as free header
            //

            freeHeader = lastHeader;
        }
        freeHeader->MakeCopy(lpszHeader, dwHeaderLength);
        freeHeader->SetNextKnownIndex(_ResponseHeaders.FastAdd(dwHeaderIndex, iSlot));
        error = ERROR_SUCCESS;
    }

    DEBUG_LEAVE(error);

    return error;
}


DWORD
HTTP_REQUEST_HANDLE_OBJECT::UpdateResponseHeaders(
    IN OUT LPBOOL lpbEof
    )

/*++

Routine Description:

    Given the next chunk of the response, updates the response headers. The
    buffer pointer, buffer length and number of bytes received values are all
    maintained in this object (_ResponseBuffer, _ResponseBufferLength and
    _BytesReceived, resp.)

Arguments:

    lpbEof  - IN: TRUE if we have reached the end of the response
              OUT: TRUE if we have reached the end of the response or the end
                   of the headers

Return Value:

    DWORD
        Success - ERROR_SUCCESS

        Failure - ERROR_NOT_ENOUGH_MEMORY

--*/

{
    DEBUG_ENTER((DBG_HTTP,
                 Dword,
                 "HTTP_REQUEST_HANDLE_OBJECT::UpdateResponseHeaders",
                 "%#x [%.*q], %d, %d, %#x [%B]",
                 _ResponseBuffer + _ResponseScanned,
                 min(_ResponseBufferLength + 1, 80),
                 _ResponseBuffer + _ResponseScanned,
                 _ResponseBufferLength,
                 _BytesReceived,
                 lpbEof,
                 *lpbEof
                 ));

    PERF_ENTER(UpdateResponseHeaders);

    LPSTR lpszBuffer = (LPSTR)_ResponseBuffer + _ResponseScanned;
    DWORD dwBytesReceived = _BytesReceived - _ResponseScanned;
    DWORD error = ERROR_SUCCESS;
    BOOL  success = TRUE;
    HEADER_STRING * statusLine;

    //
    // lock down the response headers for the duration of this request. The only
    // way another thread is going to wait on this lock is if the reference on
    // the HTTP request object goes to zero, which *shouldn't* happen
    //

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

    //
    // if input EOF is set then the caller is telling us that the end of the
    // response has been reached at transport level (the server closed the
    // connectiion)
    //

    if (*lpbEof) {
        SetEof(TRUE);
    }

    //
    // if we don't yet know whether we have a HTTP/1.0 (or greater) or HTTP/0.9
    // response yet, then try to find out.
    //
    // Only responses greater than HTTP/0.9 start with the "HTTP/#.#" string
    //

    if (!IsDownLevel() && !IsUpLevel()) {

#define MAKE_VERSION_ENTRY(string)  string, sizeof(string) - 1

        static struct {
            LPSTR Version;
            DWORD Length;
        } KnownVersionsStrings[] = {
            MAKE_VERSION_ENTRY("HTTP/"),
            MAKE_VERSION_ENTRY("S-HTTP/"),
            MAKE_VERSION_ENTRY("SHTTP/"),
            MAKE_VERSION_ENTRY("Secure-HTTP/"),

            //
            // allow for servers generating slightly off-the-wall responses
            //

            MAKE_VERSION_ENTRY("HTTP /")
        };

#define NUM_HTTP_VERSIONS   ARRAY_ELEMENTS(KnownVersionsStrings)

        //
        // We know this is the start of a HTTP response, but there may be some
        // noise at the start from bad HTML authoring, or bad content-length on
        // the previous response on a keep-alive connection. We will try to sync
        // up to the HTTP header (we will only look for this - I have never seen
        // any of the others, and I doubt its worth the increased complexity and
        // processing time)
        //

        //
        //  Due to possible DoS attacks outlined in RAID item 510295 we are to
        //be more stringent as to what sort of 'noise' is allowed before the start
        //of an HTTP response.  We now allow the noise to consist of at most
        //8 characters of whitespace or '\0'.  If the content-length is slightly
        //off because of a sloppy server, this should skip whatever terminators
        //the server expected us to use.
        //

        const DWORD c_dwSmallestAcceptableStatusLength = ARRAY_ELEMENTS("HTTP/1.1 100\r\n") - 1;
        const DWORD c_dwMaxNoiseAllowed = 8;
        const DWORD c_dwMaxPreHTTPLength = ARRAY_ELEMENTS("Secure-HTTP/")-1;

        LPSTR lpszBuf;
        DWORD bytesLeft;
        
        lpszBuf = lpszBuffer;
        bytesLeft = dwBytesReceived;

        //
        //  Check that we've read enough bytes for a status line to be ready.
        //
        if ((dwBytesReceived < c_dwSmallestAcceptableStatusLength) && !IsEof())
        {
            goto done;
        }

        //
        //  Allow up to c_dwMaxNoiseAllowed bytes worth of noise
        //
        int noiseBytesLeft = min(bytesLeft, c_dwMaxNoiseAllowed);
        int noiseBytesScanned = 0;
        while ((noiseBytesLeft > 0) 
               && (isspace((unsigned char)*lpszBuf)
                   || *lpszBuffer == '\0'))
        {
            ++lpszBuf;
            --bytesLeft;
            --noiseBytesLeft;
            ++noiseBytesScanned;
        }

        //
        // scan for the known version strings
        //

        for (int i = 0; i < NUM_HTTP_VERSIONS; ++i) {

            LPSTR version = KnownVersionsStrings[i].Version;
            DWORD length = KnownVersionsStrings[i].Length;

            if ((bytesLeft >= length)

            //
            // try the most common case as a direct comparison. memcmp()
            // should expand to cmpsd && cmpsb on x86 (most common platform
            // and one on which we are most interested in improving perf)
            //

            && (((i == 0)
                && (memcmp(lpszBuf, "HTTP/", sizeof("HTTP/") - 1) == 0))
                //&& (lpszBuf[0] == 'H')
                //&& (lpszBuf[1] == 'T')
                //&& (lpszBuf[2] == 'T')
                //&& (lpszBuf[3] == 'P')
                //&& (lpszBuf[4] == '/'))

            //
            //  "Clients should be tolerant in parsing the Status-Line"
            //  quote from HTTP/1.1 spec, therefore we perform a
            //  case-insensitive string comparison here
            //

            || (_strnicmp(lpszBuf, version, length) == 0))) {

                //
                // it starts with one of the recognized protocol version strings.
                // We assume its not a down-level server, although it could be,
                // sending back a plain text document that has e.g. "HTTP/1.0..."
                // at its start
                //
                // According to the HTTP "spec", though, it is mentioned that 0.9
                // servers typically only return HTML, hence we shouldn't see
                // even a 0.9 response start with non-HTML data
                //

                SetUpLevel(TRUE);
                _ResponseScanned += noiseBytesScanned;

                //
                // we have start of this response
                //

                lpszBuffer = lpszBuf;
                break;
            }
        }

        if (!IsUpLevel())
        {
            //
            // if we didn't find the start of a valid HTTP response and we have
            // not filled the response buffer sufficiently then allow
            // re-entry to retry.
            //
            // if we didn't find the start of a valid HTTP response and we
            //have filled the buffer sufficiently to expect the response,
            //report the response as invalid.
            //

            if ((bytesLeft < c_dwMaxPreHTTPLength ) && !IsEof())
            {
                 goto done;
            }
            else
            {
                error = ERROR_WINHTTP_INVALID_SERVER_RESPONSE;
                goto Cleanup;
            }
        }
    }

    //
    //  WinHTTP only accepts IsUpLevel() type responses.
    //

    INET_ASSERT(IsUpLevel());

    //
    // Note: at this point we can't store pointers into the response buffer
    // because it might move during a subsequent reallocation. We have to
    // maintain offsets into the buffer and convert to pointers when we come to
    // read the data out of the buffer (when the response is complete, or at
    // least we've finished receiving headers)
    //

    //
    // if we haven't checked the response yet, then the first thing to
    // get is the status line
    //

    statusLine = GetStatusLine();

    if (statusLine == NULL) {
        error = ERROR_NOT_ENOUGH_MEMORY;
        goto Cleanup;
    }

    if (!statusLine->HaveString())
    {
        BOOL fNeedMoreBuffer;
        int majorVersion = 0;
        int minorVersion = 0;
        BOOL fSupportsHttp1_1;

        _StatusCode = 0;

        //
        // Parse the status line.  It has already been checked up to the first '/'
        //

        error = _ResponseHeaders.ParseStatusLine(
            (LPSTR)_ResponseBuffer,
            _BytesReceived,
            IsEof(),
            &_ResponseScanned,
            &fNeedMoreBuffer,
            &_StatusCode,
            (LPDWORD)&majorVersion,
            (LPDWORD)&minorVersion
            );

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

        if (fNeedMoreBuffer)
        {
            error = ERROR_SUCCESS;
            goto Cleanup;
        }


        DEBUG_PRINT(HTTP,
                    INFO,
                    ("Version = %d.%d\n",
                    majorVersion,
                    minorVersion
                    ));

        DEBUG_PRINT(HTTP,
                    INFO,
                    ("_StatusCode = %d\n",
                    _StatusCode
                    ));

        fSupportsHttp1_1 = FALSE;

        if (majorVersion != 1)
        {
            //  WinHttp shouldn't accept a response with a major version greater than 1. 
            //  The protocol can change drastically between major versions, we'd have 
            //no idea how to parse a major version of 2. Accepting 1.x responses is OK, 
            //the protocol won't change in a non-compatible way across minor versions. 
            //
            //  WinHttp should not accept a major version of 0 as it is usupported.
            //
            error = ERROR_HTTP_INVALID_SERVER_RESPONSE;
            goto Cleanup;
        }
        else if (majorVersion == 1
                 && minorVersion >= 1)
        {
            fSupportsHttp1_1 = TRUE;
        }

        SetResponseHttp1_1(fSupportsHttp1_1);

        //
        // record the server HTTP version in the server info object
        //

        CServerInfo * pServerInfo = GetServerInfo();

        if (pServerInfo != NULL)
        {
            if (fSupportsHttp1_1)
            {
                pServerInfo->SetHttp1_1();

                //
                // Set the max connections per HTTP 1.1 server.
                //
                pServerInfo->SetNewLimit(GetMaxConnectionsPerServer(WINHTTP_OPTION_MAX_CONNS_PER_SERVER));
            } else {
                pServerInfo->SetHttp1_0();

                //
                // Set the max connections per HTTP 1.0 server.
                //
                pServerInfo->SetNewLimit(GetMaxConnectionsPerServer(WINHTTP_OPTION_MAX_CONNS_PER_1_0_SERVER));
            }
        }
    }

    //
    // continue scanning headers here until we have tested all the current
    // buffer, or we have found the start of the data
    //

    BOOL fFoundEndOfHeaders;

    error = _ResponseHeaders.ParseHeaders(
                (LPSTR)_ResponseBuffer,
                _BytesReceived > _dwMaxResponseHeaderSize ? _dwMaxResponseHeaderSize : _BytesReceived,
                _BytesReceived > _dwMaxResponseHeaderSize ? FALSE : IsEof(),
                &_ResponseScanned,
                &success,
                &fFoundEndOfHeaders
                );

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

    if ( !fFoundEndOfHeaders && _BytesReceived > _dwMaxResponseHeaderSize)
    {
        error = ERROR_WINHTTP_HEADER_SIZE_OVERFLOW;
        goto Cleanup;
    }

    if ( fFoundEndOfHeaders )
    {
        //
        // we found the end of the headers
        //

        SetEof(TRUE);

        //
        // and the start of the data
        //

        SetData(TRUE);
        _DataOffset = _ResponseScanned;

        DEBUG_PRINT(HTTP,
                    INFO,
                    ("found end of headers. _DataOffset = %d\n",
                    _DataOffset
                    ));

    }

done:

    //
    // if we have reached the end of the headers then we communicate this fact
    // to the caller
    //

    if (IsData() || IsEof()) {
        error = CheckWellKnownHeaders();
        if (ERROR_SUCCESS != error)
        {
            goto Cleanup;
        }
        *lpbEof = TRUE;

        /*

        Set connection persistency based on these rules:

        persistent = (1.0Request && Con: K-A && 1.0Response && Con: K-A)
                     || (1.1Request && Con: K-A && 1.0Response && Con: K-A)
                     || (1.0Request && Con: K-A && 1.1Response && Con: K-A)
                     || (1.1Request && !Con: Close && 1.1Response && !Con: Close)

        therefore,

        persistent = 1.1Request && 1.1Response
                        ? (!Con: Close in request || response)
                        : Con: K-A in request && response

        */

        if (IsRequestHttp1_1() && IsResponseHttp1_1()) {

            BOOL bHaveConnCloseRequest;

            bHaveConnCloseRequest = FindConnCloseRequestHeader(
                                        IsRequestUsingProxy()
                                            ? HTTP_QUERY_PROXY_CONNECTION
                                            : HTTP_QUERY_CONNECTION
                                            );
            if (!(IsConnCloseResponse() || bHaveConnCloseRequest)) {

                DEBUG_PRINT(HTTP,
                            INFO,
                            ("HTTP/1.1 persistent connection\n"
                            ));

                SetKeepAlive(TRUE);
                SetPersistentConnection(IsRequestUsingProxy()
                                        && !IsTalkingToSecureServerViaProxy()
                                        );
            } else {

                DEBUG_PRINT(HTTP,
                            INFO,
                            ("HTTP/1.1 non-persistent connection: close on: request: %B; response: %B\n",
                            bHaveConnCloseRequest,
                            IsConnCloseResponse()
                            ));

                SetKeepAlive(FALSE);
                SetNoLongerKeepAlive();
                ClearPersistentConnection();
            }
        }
    }

Cleanup:

    //
    // we are finished updating the response headers (no other thread should be
    // waiting for this if the reference count and object state is correct)
    //

    _ResponseHeaders.UnlockHeaders();

quit:
    PERF_LEAVE(UpdateResponseHeaders);

    DEBUG_LEAVE(error);

    return error;
}



DWORD
HTTP_REQUEST_HANDLE_OBJECT::CreateResponseHeaders(
    IN OUT LPSTR* ppszBuffer,
    IN DWORD      dwBufferLength
    )

/*++

Routine Description:

    Create the response headers given a buffer containing concatenated headers.
    Called when we are creating this object from the cache

Arguments:

    lpszBuffer      - pointer to buffer containing headers

    dwBufferLength  - length of lpszBuffer

Return Value:

    DWORD
        Success - ERROR_SUCCESS

        Failure - ERROR_NOT_ENOUGH_MEMORY
                    Couldn't create headers

--*/

{
    DEBUG_ENTER((DBG_HTTP,
                 Dword,
                 "HTTP_REQUEST_HANDLE_OBJECT::CreateResponseHeaders",
                 "%.32q, %d",
                 ppszBuffer,
                 dwBufferLength
                 ));

    //
    // there SHOULD NOT already be a response buffer if we're adding an
    // external buffer
    //

    INET_ASSERT(_ResponseBuffer == NULL);

    DWORD error;
    BOOL eof = FALSE;

    _ResponseBuffer = (LPBYTE) *ppszBuffer;
    _ResponseBufferLength = dwBufferLength;
    _BytesReceived = dwBufferLength;
    error = UpdateResponseHeaders(&eof);
    if (error != ERROR_SUCCESS) {

        //
        // if we failed, we will clean up our variables including clearing
        // out the response buffer address and length, but leave freeing
        // the buffer to the caller
        //

        _ResponseBuffer = NULL;
        _ResponseBufferLength = 0;
        ResetResponseVariables();

    } else {

        //
        // Success - the object owns the buffer so the caller should not free.
        //

        *ppszBuffer = NULL;
    }

    DEBUG_LEAVE(error);

    return error;
}


DWORD
HTTP_REQUEST_HANDLE_OBJECT::QueryResponseVersion(
    IN LPVOID lpBuffer,
    IN OUT LPDWORD lpdwBufferLength
    )

/*++

Routine Description:

    Returns the HTTP version string from the status line

Arguments:

    lpBuffer            - pointer to buffer to copy version string into

    lpdwBufferLength    - IN: size of lpBuffer
                          OUT: size of version string excluding terminating '\0'
                               if successful, else required buffer length

Return Value:

    DWORD
        Success - ERROR_SUCCESS

        Failure - ERROR_INSUFFICIENT_BUFFER

--*/

{
    PERF_ENTER(QueryResponseVersion);

    DWORD error;

    HEADER_STRING * statusLine = GetStatusLine();

    if ((statusLine == NULL) || statusLine->IsError()) {
        error = ERROR_WINHTTP_INTERNAL_ERROR;
        goto quit;
    }

    LPSTR string;
    DWORD length;

    //
    // get a pointer into the response buffer where the status line starts
    // and its length
    //

    string = statusLine->StringAddress((LPSTR)_ResponseBuffer);
    length = (DWORD)statusLine->StringLength();

    //
    // the version string is the first token on the line, delimited by spaces
    //

    DWORD index;

    for (index = 0; index < length; ++index) {

        //
        // we'll also check for CR and LF, although just space should be
        // sufficient
        //

        if ((string[index] == ' ')
        || (string[index] == '\r')
        || (string[index] == '\n')) {
            break;
        }
    }
    if (*lpdwBufferLength > index) {
        memcpy(lpBuffer, (LPVOID)string, index);
        ((LPSTR)lpBuffer)[index] = '\0';
        *lpdwBufferLength = index;
        error = ERROR_SUCCESS;
    } else {
        *lpdwBufferLength = index + 1;
        error = ERROR_INSUFFICIENT_BUFFER;
    }

quit:

    PERF_LEAVE(QueryResponseVersion);

    return error;
}


DWORD
HTTP_REQUEST_HANDLE_OBJECT::QueryStatusCode(
    IN LPVOID lpBuffer,
    IN OUT LPDWORD lpdwBufferLength,
    IN DWORD dwModifiers
    )

/*++

Routine Description:

    Returns the status code as a string or a number

Arguments:

    lpBuffer            - pointer to buffer where results written

    lpdwBufferLength    - IN: length of buffer
                          OUT: size of returned information, or required size'
                               of buffer

    dwModifiers         - flags which modify returned value

Return Value:

    DWORD
        Success - ERROR_SUCCESS

        Failure - ERROR_INSUFFICIENT_BUFFER

--*/

{
    PERF_ENTER(QueryStatusCode);

    DWORD error;
    DWORD requiredSize;

    if (dwModifiers & HTTP_QUERY_FLAG_NUMBER) {
        requiredSize = sizeof(_StatusCode);
        if (*lpdwBufferLength >= requiredSize) {
            *(LPDWORD)lpBuffer = _StatusCode;
            error = ERROR_SUCCESS;
        } else {
            error = ERROR_INSUFFICIENT_BUFFER;
        }
    } else {

        //
        // the number should always be only 3 characters long, but we'll be
        // flexible (just in case)
        //

        char numBuf[sizeof("4294967296")];

        requiredSize = wsprintf(numBuf, "%u", _StatusCode) + 1;

#ifdef DEBUG
        // Debug check to make sure everything is good because the above
        // used to be ultoa.
        char debugBuf[sizeof("4294967296")];
        ultoa(_StatusCode, debugBuf, 10);
        if (strcmp(debugBuf,numBuf))
        {
            INET_ASSERT(FALSE);
        }

        INET_ASSERT(requiredSize == lstrlen(numBuf) + 1);
#endif

        if (*lpdwBufferLength >= requiredSize) {
            memcpy(lpBuffer, (LPVOID)numBuf, requiredSize);
            *lpdwBufferLength = requiredSize - 1;
            error = ERROR_SUCCESS;
        } else {
            *lpdwBufferLength = requiredSize;
            error = ERROR_INSUFFICIENT_BUFFER;
        }
    }

    PERF_LEAVE(QueryStatusCode);

    return error;
}


DWORD
HTTP_REQUEST_HANDLE_OBJECT::QueryStatusText(
    IN LPVOID lpBuffer,
    IN OUT LPDWORD lpdwBufferLength
    )

/*++

Routine Description:

    Returns the status text - if any - returned by the server in the status line

Arguments:

    lpBuffer            - pointer to buffer where status text is written

    lpdwBufferLength    - IN: size of lpBuffer
                          OUT: length of the status text string minus 1 for the
                               '\0', or the required buffer length if we return
                               ERROR_INSUFFICIENT_BUFFER

Return Value:

    DWORD
        Success - ERROR_SUCCESS

        Failure - ERROR_INSUFFICIENT_BUFFER

--*/

{
    PERF_ENTER(QueryStatusText);

    DWORD error;

    HEADER_STRING * statusLine = GetStatusLine();

    if ((statusLine == NULL) || statusLine->IsError()) {
        error = ERROR_WINHTTP_INTERNAL_ERROR;
        goto quit;
    }

    LPSTR str;
    DWORD len;

    //
    // find the third token on the status line. The status line has the form
    //
    //  "HTTP/1.0 302 Try again\r\n"
    //
    //   ^        ^   ^
    //   |        |   |
    //   |        |   +- status text
    //   |        +- status code
    //   +- version
    //

    str = statusLine->StringAddress((LPSTR)_ResponseBuffer);
    len = statusLine->StringLength();

    DWORD i;

    i = 0;

    int j;

    for (j = 0; j < 2; ++j) {
        while ((i < len) && (str[i] != ' ')) {
            ++i;
        }
        while ((i < len) && (str[i] == ' ')) {
            ++i;
        }
    }
    len -= i;
    if (*lpdwBufferLength > len) {
        memcpy(lpBuffer, (LPVOID)&str[i], len);
        ((LPSTR)lpBuffer)[len] = '\0';
        *lpdwBufferLength = len;
        error = ERROR_SUCCESS;
    } else {
        *lpdwBufferLength = len + 1;
        error = ERROR_INSUFFICIENT_BUFFER;
    }

quit:

    PERF_LEAVE(QueryStatusText);

    return error;
}



DWORD
HTTP_REQUEST_HANDLE_OBJECT::QueryRawResponseHeaders(
    IN BOOL bCrLfTerminated,
    OUT LPVOID lpBuffer,
    IN OUT LPDWORD lpdwBufferLength
    )

/*++

Routine Description:

    Gets the raw response headers

Arguments:

    bCrLfTerminated     - TRUE if we want RAW_HEADERS_CRLF else RAW_HEADERS

    lpBuffer            - pointer to buffer where headers returned

    lpdwBufferLength    - IN: length of lpBuffer
                          OUT: returned length of lpBuffer

Return Value:

    DWORD
        Success - ERROR_SUCCESS

        Failure -

--*/

{
    DEBUG_ENTER((DBG_HTTP,
                 Dword,
                 "QueryRawHeaders",
                 "%B, %#x, %#x [%d]",
                 bCrLfTerminated,
                 lpBuffer,
                 lpdwBufferLength,
                 *lpdwBufferLength
                 ));

    PERF_ENTER(QueryRawHeaders);

    DWORD error = _ResponseHeaders.QueryRawHeaders(
                    (LPSTR)_ResponseBuffer,
                    bCrLfTerminated,
                    lpBuffer,
                    lpdwBufferLength
                    );

    IF_DEBUG_CODE() {
        if (error == ERROR_INSUFFICIENT_BUFFER) {

            DEBUG_PRINT(HTTP,
                        INFO,
                        ("*lpdwBufferLength = %d\n",
                        *lpdwBufferLength
                        ));

        }
    }

    PERF_LEAVE(QueryRawHeaders);

    DEBUG_LEAVE(error);

    return error;

}


VOID
HTTP_REQUEST_HANDLE_OBJECT::RemoveAllRequestHeadersByName(
    IN DWORD dwQueryIndex
    )

/*++

Routine Description:

    Removes all headers of a particular type from the request object

Arguments:

    lpszHeaderName  - name of header to remove

Return Value:

    None.

--*/

{
    DEBUG_ENTER((DBG_HTTP,
                 None,
                 "RemoveAllRequestHeadersByName",
                 "%q, %u",
                 GlobalKnownHeaders[dwQueryIndex].Text,
                 dwQueryIndex
                 ));

    PERF_ENTER(RemoveAllRequestHeadersByName);

    _RequestHeaders.RemoveAllByIndex(dwQueryIndex);

    PERF_LEAVE(RemoveAllRequestHeadersByName);

    DEBUG_LEAVE(0);
}

//
// private methods
//


PRIVATE
DWORD
HTTP_REQUEST_HANDLE_OBJECT::CheckWellKnownHeaders(
    VOID
    )

/*++

Routine Description:

    Tests for a couple of well-known headers that are important to us as well as
    the app:

        "Connection: Keep-Alive"
        "Proxy-Connection: Keep-Alive"
        "Connection: Close"
        "Proxy-Connection: Close"
        "Transfer-Encoding: chunked"
        "Content-Length: ####"
        "Content-Range: bytes ####-####/####"

    The header DOES NOT contain CR-LF. That is, dwHeaderLength will not include
    any counts for line termination

    We need to know if the server honoured a request for a keep-alive connection
    so that we don't try to receive until we hit the end of the connection. The
    server will keep it open.

    We need to know the content length if we are talking over a persistent (keep
    alive) connection.

    If either header is found, we set the corresponding flag in the HTTP_HEADERS
    object, and in the case of "Content-Length:" we parse out the length.

Arguments:

    None.

Return Value:

    DWORD
        Success - ERROR_SUCCESS

        Failure - ERROR_NOT_ENOUGH_MEMORY

--*/

{
    DEBUG_ENTER((DBG_HTTP,
                 None,
                 "HTTP_REQUEST_HANDLE_OBJECT::CheckWellKnownHeaders",
                 NULL
                 ));

    DWORD dwError = ERROR_SUCCESS;

    //
    // check for "Content-Length:"
    //

    if ( IsResponseHeaderPresent(HTTP_QUERY_CONTENT_LENGTH) )
    {
        HEADER_STRING * curHeader;
        DWORD dwHeaderLength;
        LPSTR lpszHeader;

        DWORD iSlotContentLength = _ResponseHeaders._bKnownHeaders[HTTP_QUERY_CONTENT_LENGTH];
        curHeader = _ResponseHeaders.GetSlot(iSlotContentLength);

        lpszHeader     = curHeader->StringAddress((LPSTR)_ResponseBuffer);
        dwHeaderLength = curHeader->StringLength();

        dwHeaderLength -= GlobalKnownHeaders[HTTP_QUERY_CONTENT_LENGTH].Length+1;
        lpszHeader     += GlobalKnownHeaders[HTTP_QUERY_CONTENT_LENGTH].Length+1;

        while (dwHeaderLength && (*lpszHeader == ' ')) {
            --dwHeaderLength;
            ++lpszHeader;
        }
        while (dwHeaderLength && isdigit(*lpszHeader)) {
            _ContentLength = _ContentLength * 10 + (*lpszHeader - '0');
            --dwHeaderLength;
            ++lpszHeader;
        }

        //
        // once we have _ContentLength, we don't modify it (unless
        // we fix it up when using a 206 partial response to resume
        // a partial download.)  The header value should be returned
        // by HttpQueryInfo().  Instead, we keep account of the
        // amount of keep-alive data left to copy in _BytesRemaining
        //

        _BytesRemaining = _ContentLength;

        //
        // although we said we may be one past the end of the header, in
        // reality, if we received a buffer with "Content-Length:" then we
        // expect it to be terminated by CR-LF (or CR-CR-LF or just LF,
        // depending on the wackiness quotient of the server)
        //

        // MSXML3 bug 56001: commenting-out this assert; it's informational
        //                   only and ignorable.
        // INET_ASSERT((*lpszHeader == '\r') || (*lpszHeader == '\n'));

        SetHaveContentLength(TRUE);

        DEBUG_PRINT(HTTP,
                    INFO,
                    ("_ContentLength = %d\n",
                    _ContentLength
                    ));

        _BytesInSocket = (_ContentLength != 0)
                ? (_ContentLength - (_BytesReceived - _DataOffset))
                : 0;

        //
        // we could have multiple responses in the same buffer. If
        // the amount received is greater than the content length
        // then we have all the data; there are no bytes left in
        // the socket for the current response
        //

        if ((int)_BytesInSocket < 0) {
            _BytesInSocket = 0;
        }

        DEBUG_PRINT(HTTP,
                    INFO,
                    ("bytes left in socket = %d\n",
                    _BytesInSocket
                    ));

    }


    if ( IsResponseHeaderPresent(HTTP_QUERY_CONNECTION) ||
         IsResponseHeaderPresent(HTTP_QUERY_PROXY_CONNECTION) )
    {
        //
        // check for "Connection: Keep-Alive" or "Proxy-Connection: Keep-Alive".
        // This test protects us against the unlikely
        // event of a server returning to us a keep-alive response header (because
        // that would cause problems for the proxy)
        //

        if (IsWantKeepAlive() && (!IsKeepAlive() || IsResponseHttp1_1()))
        {
            HEADER_STRING * curHeader;
            DWORD dwHeaderLength, headerNameLength;
            LPSTR lpszHeader;


            DWORD iSlot;

            if (IsRequestUsingProxy() &&
                IsResponseHeaderPresent(HTTP_QUERY_PROXY_CONNECTION))
            {
                iSlot = _ResponseHeaders._bKnownHeaders[HTTP_QUERY_PROXY_CONNECTION];
                headerNameLength = GlobalKnownHeaders[HTTP_QUERY_PROXY_CONNECTION].Length+1;
            }
            else if (IsResponseHeaderPresent(HTTP_QUERY_CONNECTION))
            {
                iSlot = _ResponseHeaders._bKnownHeaders[HTTP_QUERY_CONNECTION];
                headerNameLength = GlobalKnownHeaders[HTTP_QUERY_CONNECTION].Length+1;
            }
            else
            {
                iSlot = _ResponseHeaders._bKnownHeaders[HTTP_QUERY_PROXY_CONNECTION];
                headerNameLength = GlobalKnownHeaders[HTTP_QUERY_PROXY_CONNECTION].Length+1;
            }

            curHeader      = _ResponseHeaders.GetSlot(iSlot);
            lpszHeader     = curHeader->StringAddress((LPSTR)_ResponseBuffer);
            dwHeaderLength = curHeader->StringLength();

            dwHeaderLength -= headerNameLength;
            lpszHeader     += headerNameLength;

            while (dwHeaderLength && (*lpszHeader == ' ')) {
                ++lpszHeader;
                --dwHeaderLength;
            }

            //
            // both headers use "Keep-Alive" as header-value ONLY for HTTP 1.0 servers
            //

            if (((int)dwHeaderLength >= KEEP_ALIVE_LEN)
            && !strnicmp(lpszHeader, KEEP_ALIVE_SZ, KEEP_ALIVE_LEN)) {

                DEBUG_PRINT(HTTP,
                            INFO,
                            ("Connection: Keep-Alive\n"
                            ));

                //
                // BUGBUG - we are setting k-a when coming from cache!
                //

                SetKeepAlive(TRUE);
                SetPersistentConnection(headerNameLength == HTTP_PROXY_CONNECTION_LEN);
            }

            //
            // also check for "Close" as header-value ONLY for HTTP 1.1 servers
            //

            else if ((*lpszHeader == 'C' || *lpszHeader == 'c')
                     && ((int)dwHeaderLength >= CLOSE_LEN)
                     && IsResponseHttp1_1()
                     && !strnicmp(lpszHeader, CLOSE_SZ, CLOSE_LEN)) {

                DEBUG_PRINT(HTTP,
                            INFO,
                            ("Connection: Close (HTTP/1.1)\n"
                            ));

                SetConnCloseResponse(TRUE);
            }
        }
    }

    //
    // check for "Transfer-Encoding:"
    //

    if (IsResponseHeaderPresent(HTTP_QUERY_TRANSFER_ENCODING) &&
        IsResponseHttp1_1())
    {

        //
        // If Http 1.1, check for Chunked Transfer
        //

        HEADER_STRING * curHeader;
        DWORD dwHeaderLength;
        LPSTR lpszHeader;
        DWORD iSlot;

        iSlot = _ResponseHeaders._bKnownHeaders[HTTP_QUERY_TRANSFER_ENCODING];
        curHeader = _ResponseHeaders.GetSlot(iSlot);

        lpszHeader     = curHeader->StringAddress((LPSTR)_ResponseBuffer);
        dwHeaderLength = curHeader->StringLength();

        dwHeaderLength -= GlobalKnownHeaders[HTTP_QUERY_TRANSFER_ENCODING].Length+1;
        lpszHeader     += GlobalKnownHeaders[HTTP_QUERY_TRANSFER_ENCODING].Length+1;

        while (dwHeaderLength && (*lpszHeader == ' ')) {
            ++lpszHeader;
            --dwHeaderLength;
        }

        //
        // look for "chunked" entry that confirms that we're doing chunked transfer encoding
        //

        //
        //TODO  We really should be able to handle multiple Transfer-Encoding headers.
        //  (perhaps accepting an 'identity' encoding followed by a 'chunked' encoding
        //   and recognizing that it is a chunked response, or rejecting a 'chunked' encoding
        //   followed by an invalid encoding)
        //

        if (((int)dwHeaderLength >= CHUNKED_LEN)
        && !strnicmp(lpszHeader, CHUNKED_SZ, CHUNKED_LEN))
        {
            INTERNET_HANDLE_OBJECT* pRoot = GetRootHandle(this);
            DWORD_PTR dwChunkFilterCtx = 0;

            // Now that we know this is a chunked response, allocate
            // a decoder context for parsing the data later.  If anything
            // fails here, the request needs to fail.            
            if (S_OK != pRoot->_ChunkFilter.RegisterContextEx(&dwChunkFilterCtx, _dwMaxResponseHeaderSize))
            {
                dwError = ERROR_NOT_ENOUGH_MEMORY;
                goto quit;
            }
            else if (!_ResponseFilterList.Insert(&pRoot->_ChunkFilter, dwChunkFilterCtx))
            {
                pRoot->_ChunkFilter.UnregisterContext(dwChunkFilterCtx);
                dwError = ERROR_NOT_ENOUGH_MEMORY;
                goto quit;
            }

            SetHaveChunkEncoding(TRUE);

            DEBUG_PRINT(HTTP,
                        INFO,
                        ("server is sending Chunked Transfer Encoding\n"
                        ));

            //
            // if both "transfer-encoding: chunked" and "content-length:"
            // were received then the chunking takes precedence
            //
            DEBUG_PRINT(HTTP,
                        INFO,
                    ("server sent both Content-Length and Transfer-Encoding: chunked headers\n"
                    ));

            if (IsContentLength()) {
                SetHaveContentLength(FALSE);
            }

        }
        else if (dwHeaderLength >= (DWORD)IDENTITY_LEN
                 && !strnicmp(lpszHeader, IDENTITY_SZ, IDENTITY_LEN))
        {
            //ignore
        }
        else
        {
            //
            //  We can't be sure when we've reached the end of this request,
            //and so we'll disallow keep-alive to prevent the next response
            //from being corrupted.
            //
            SetNoLongerKeepAlive();
        }
    }

    SetBadNSServer(FALSE);

    if (IsResponseHttp1_1())
    {

        //
        // For IIS 4.0 Servers, and all other normal servers, if we make
        //  a HEAD request, we should ignore the Content-Length.
        //
        // IIS 3.0 servers send an illegal body, and this is a bug in the server.
        //  since they're not HTTP 1.1 we should be ok here.
        //

        if ( (GetMethodType() == HTTP_METHOD_TYPE_HEAD) &&
             (_ContentLength > 0) &&
             IsWantKeepAlive()
             )
        {

            //
            // set length to 0
            //

            _ContentLength = 0;
            _BytesInSocket = 0;
            _BytesRemaining = 0;

        }

        if ( IsRequestHttp1_1() )
        {


            //
            // check for NS servers that don't return correct HTTP/1.1 responses
            //

            LPSTR buffer;
            DWORD buflen;
            DWORD status = FastQueryResponseHeader(HTTP_QUERY_SERVER,
                                                   (LPVOID*)&buffer,
                                                   &buflen,
                                                   0
                                                   );

    #define NSEP    "Netscape-Enterprise/3"
    #define NSEPLEN (sizeof(NSEP) - 1)
    #define NSFT    "Netscape-FastTrack/3"
    #define NSFTLEN (sizeof(NSFT) - 1)
    #define NSCS    "Netscape-Commerce/3"
    #define NSCSLEN (sizeof(NSCS) - 1)

            if (status == ERROR_SUCCESS) {

                BOOL fIsBadServer = ((buflen > NSEPLEN) && !strnicmp(buffer, NSEP, NSEPLEN))
                                 || ((buflen > NSFTLEN) && !strnicmp(buffer, NSFT, NSFTLEN))
                                 || ((buflen > NSCSLEN) && !strnicmp(buffer, NSCS, NSCSLEN));

                if ( fIsBadServer )
                {
                    CServerInfo * pServerInfo = GetServerInfo();

                    SetBadNSServer(fIsBadServer);

                    if (pServerInfo != NULL)
                    {
                        //
                        // Note this Bad Server info in the server info obj,
                        //   as we they fail to do keep-alive with SSL properly
                        //

                        pServerInfo->SetBadNSServer();
                    }


                    DEBUG_PRINT(HTTP,
                                INFO,
                                ("IsBadNSServer() == %B\n",
                                IsBadNSServer()
                                ));
                }
            }
        }

        //
        // BUGBUG - content-type: multipart/byteranges means we
        //          also have data
        //

        DWORD statusCode = GetStatusCode();

        if (!IsBadNSServer()
            && !IsContentLength()
            && !IsChunkEncoding()
            && (((statusCode >= HTTP_STATUS_CONTINUE)               // 100
                && (statusCode < HTTP_STATUS_OK))                   // 200
                || (statusCode == HTTP_STATUS_NO_CONTENT)           // 204
                || (statusCode == HTTP_STATUS_MOVED)                // 301
                || (statusCode == HTTP_STATUS_REDIRECT)             // 302
                || (statusCode == HTTP_STATUS_REDIRECT_METHOD)      // 303
                || (statusCode == HTTP_STATUS_NOT_MODIFIED)         // 304
                || (statusCode == HTTP_STATUS_REDIRECT_KEEP_VERB))  // 307
            || (GetMethodType() == HTTP_METHOD_TYPE_HEAD)) {

            DEBUG_PRINT(HTTP,
                        INFO,
                        ("header-only HTTP/1.1 response\n"
                        ));

            SetData(FALSE);
        }
    }

quit:
    DEBUG_LEAVE(dwError);
    return dwError;
}


//
// this array has the same order as the HTTP_METHOD_TYPE enum
//

#define MAKE_REQUEST_METHOD_TYPE(Type) \
    sizeof(# Type) - 1, # Type, HTTP_METHOD_TYPE_ ## Type

//
// darrenmi - need a new macro because *_M-POST isn't a valid enum member.
// we need a seperate enum type and string value.
//
// map HTTP_METHOD_TYPE_MPOST <=> "M-POST"
//

#define MAKE_REQUEST_METHOD_TYPE2(EnumType,Type) \
    sizeof(# Type) - 1, # Type, HTTP_METHOD_TYPE_ ## EnumType

static const struct _REQUEST_METHOD {
    int Length;
    LPSTR Name;
    HTTP_METHOD_TYPE MethodType;
} MethodNames[] = {
    MAKE_REQUEST_METHOD_TYPE(GET),
    MAKE_REQUEST_METHOD_TYPE(HEAD),
    MAKE_REQUEST_METHOD_TYPE(POST),
    MAKE_REQUEST_METHOD_TYPE(PUT),
    MAKE_REQUEST_METHOD_TYPE(PROPFIND),
    MAKE_REQUEST_METHOD_TYPE(PROPPATCH),
    MAKE_REQUEST_METHOD_TYPE(LOCK),
    MAKE_REQUEST_METHOD_TYPE(UNLOCK),
    MAKE_REQUEST_METHOD_TYPE(COPY),
    MAKE_REQUEST_METHOD_TYPE(MOVE),
    MAKE_REQUEST_METHOD_TYPE(MKCOL),
    MAKE_REQUEST_METHOD_TYPE(CONNECT),
    MAKE_REQUEST_METHOD_TYPE(DELETE),
    MAKE_REQUEST_METHOD_TYPE(LINK),
    MAKE_REQUEST_METHOD_TYPE(UNLINK),
    MAKE_REQUEST_METHOD_TYPE(BMOVE),
    MAKE_REQUEST_METHOD_TYPE(BCOPY),
    MAKE_REQUEST_METHOD_TYPE(BPROPFIND),
    MAKE_REQUEST_METHOD_TYPE(BPROPPATCH),
    MAKE_REQUEST_METHOD_TYPE(BDELETE),
    MAKE_REQUEST_METHOD_TYPE(SUBSCRIBE),
    MAKE_REQUEST_METHOD_TYPE(UNSUBSCRIBE),
    MAKE_REQUEST_METHOD_TYPE(NOTIFY),
    MAKE_REQUEST_METHOD_TYPE(POLL), 
    MAKE_REQUEST_METHOD_TYPE(CHECKIN),
    MAKE_REQUEST_METHOD_TYPE(CHECKOUT),
    MAKE_REQUEST_METHOD_TYPE(INVOKE),
    MAKE_REQUEST_METHOD_TYPE(SEARCH),
    MAKE_REQUEST_METHOD_TYPE(PIN),
    MAKE_REQUEST_METHOD_TYPE2(MPOST,M-POST)
};


HTTP_METHOD_TYPE
MapHttpRequestMethod(
    IN LPCSTR lpszVerb
    )

/*++

Routine Description:

    Maps request method string to type. Method names *are* case-sensitive

Arguments:

    lpszVerb    - method (verb) string

Return Value:

    HTTP_METHOD_TYPE

--*/

{
    int verbLen = strlen(lpszVerb);

    for (int i = 0; i < ARRAY_ELEMENTS(MethodNames); ++i) {
        if ((MethodNames[i].Length == verbLen)
        && (memcmp(lpszVerb, MethodNames[i].Name, verbLen) == 0)) {
            return MethodNames[i].MethodType;
        }
    }

    //
    // we now hande HTTP_METHOD_TYPE_UNKNOWN
    //

    return HTTP_METHOD_TYPE_UNKNOWN;
}


DWORD
MapHttpMethodType(
    IN HTTP_METHOD_TYPE tMethod,
    OUT LPCSTR * lplpcszName
    )

/*++

Routine Description:

    Map a method type to the corresponding name and length

Arguments:

    tMethod     - to map

    lplpcszName - pointer to pointer to returned name

Return Value:

    DWORD
        Success - length of method name

        Failure - (DWORD)-1

--*/

{
    DWORD length;

    if ((tMethod >= HTTP_METHOD_TYPE_FIRST) && (tMethod <= HTTP_METHOD_TYPE_LAST)) {
        *lplpcszName = MethodNames[tMethod].Name;
        length = MethodNames[tMethod].Length;
    } else {
        length = (DWORD)-1;
    }
    return length;
}

#if INET_DEBUG

LPSTR
MapHttpMethodType(
    IN HTTP_METHOD_TYPE tMethod
    )
{
    return (tMethod == HTTP_METHOD_TYPE_UNKNOWN)
        ? "Unknown"
        : MethodNames[tMethod].Name;
}

#endif


