/*++

Copyright (c) 1994-1997 Microsoft Corporation

Module Name:

    read.cxx

Abstract:

    This file contains the implementation of the HttpReadData API.

    Contents:
        HttpReadData
        CFsm_HttpReadData::RunSM
        HTTP_REQUEST_HANDLE_OBJECT::HttpReadData_Fsm
        HTTP_REQUEST_HANDLE_OBJECT::ReadData
        CFsm_ReadData::RunSM
        HTTP_REQUEST_HANDLE_OBJECT::ReadData_Fsm
        HTTP_REQUEST_HANDLE_OBJECT::QueryDataAvailable
        CFsm_HttpQueryAvailable::RunSM
        HTTP_REQUEST_HANDLE_OBJECT::QueryAvailable_Fsm
        HTTP_REQUEST_HANDLE_OBJECT::DrainResponse
        CFsm_DrainResponse::RunSM
        HTTP_REQUEST_HANDLE_OBJECT::DrainResponse_Fsm

Author:

    Keith Moore (keithmo) 16-Nov-1994

Revision History:

    Modified to make HttpReadData remotable. madana (2/8/95)

--*/

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

//
// private prototypes
//

PRIVATE
VOID
FilterHeaders(
    IN LPSTR lpszHeaderInfo,
    OUT LPDWORD lpdwLen
    );

//
// functions
//


DWORD
HttpReadData(
    IN HINTERNET hRequest,
    OUT LPVOID lpBuffer,
    IN DWORD dwNumberOfBytesToRead,
    OUT LPDWORD lpdwNumberOfBytesRead,
    IN DWORD dwSocketFlags
    )

/*++

Routine Description:

    Reads a block of data from an outstanding HTTP request

    Assumes: 1. this function can only be called from InternetReadFile() which
        globally validates parameters for all Internet data read
        functions

         2. We will never get a request for 0 bytes at this level. This
        request will have been handled in InternetReadFile()

Arguments:

    hRequest                - mapped HTTP request handle

    lpBuffer                - pointer to the buffer to receive the data

    dwNumberOfBytesToRead   - number of bytes to read into lpBuffer

    lpdwNumberOfBytesRead   - number of bytes read into lpBuffer

    dwSocketFlags           - controlling socket operation

Return Value:

    TRUE - The data was read successfully. lpdwNumberOfBytesRead points to the
    number of BYTEs actually read. This value will be set to zero
    when the transfer has completed.

    FALSE - The operation failed. Error status is available by calling
    GetLastError().

--*/

{
    DEBUG_ENTER((DBG_HTTP,
                 Dword,
                 "HttpReadData",
                 "%#x, %#x, %d, %#x, %#x",
                 hRequest,
                 lpBuffer,
                 dwNumberOfBytesToRead,
                 lpdwNumberOfBytesRead,
                 dwSocketFlags
                 ));

    DWORD error;
    HTTP_REQUEST_HANDLE_OBJECT* pRequest =
        (HTTP_REQUEST_HANDLE_OBJECT*) hRequest;

    if (pRequest->IsReadRequest()) {

        //
        // Invoke ReadLoop fsm only if the read and write positions
        // are different, otherwise older code is more efficient.
        //

        error = DoFsm (new CFsm_ReadLoop (pRequest, dwSocketFlags,
            (PBYTE) lpBuffer, dwNumberOfBytesToRead, lpdwNumberOfBytesRead));

    } else {

        error = DoFsm(new CFsm_HttpReadData(lpBuffer,
                                            dwNumberOfBytesToRead,
                                            lpdwNumberOfBytesRead,
                                            dwSocketFlags,
                                            pRequest
                                           ));
    }

    DEBUG_LEAVE(error);

    return error;
}


DWORD
CFsm_HttpReadData::RunSM(
    IN CFsm * Fsm
    )

/*++

Routine Description:

    description-of-function.

Arguments:

    Fsm -

Return Value:

    DWORD

--*/

{
    DEBUG_ENTER((DBG_HTTP,
                 Dword,
                 "CFsm_HttpReadData::RunSM",
                 "%#x",
                 Fsm
                 ));

    DWORD error;
    HTTP_REQUEST_HANDLE_OBJECT * pRequest;
    CFsm_HttpReadData * stateMachine = (CFsm_HttpReadData *)Fsm;

    pRequest = (HTTP_REQUEST_HANDLE_OBJECT *)Fsm->GetContext();
    switch (Fsm->GetState()) {
    case FSM_STATE_INIT:
    case FSM_STATE_CONTINUE:
        error = pRequest->HttpReadData_Fsm(stateMachine);
        break;

    default:
        error = ERROR_INTERNET_INTERNAL_ERROR;
        Fsm->SetDone(ERROR_INTERNET_INTERNAL_ERROR);

        INET_ASSERT(FALSE);

        break;
    }

    DEBUG_LEAVE(error);

    return error;
}


DWORD
HTTP_REQUEST_HANDLE_OBJECT::HttpReadData_Fsm(
    IN CFsm_HttpReadData * Fsm
    )

/*++

Routine Description:

    description-of-function.

Arguments:

    Fsm -

Return Value:

    DWORD

--*/

{
    DEBUG_ENTER((DBG_HTTP,
                 Dword,
                 "HTTP_REQUEST_HANDLE_OBJECT::HttpReadData_Fsm",
                 "%#x",
                 Fsm
                 ));

    CFsm_HttpReadData & fsm = *Fsm;
    DWORD error = fsm.GetError();

    if (fsm.GetState() == FSM_STATE_INIT) {

        if (!IsValidHttpState(READ)) {
            error = ERROR_INTERNET_INCORRECT_HANDLE_STATE;
            goto quit;
        }
        error = ReadData(fsm.m_lpBuffer,
                         fsm.m_dwNumberOfBytesToRead,
                         fsm.m_lpdwNumberOfBytesRead,
                         FALSE, // BUGBUG RFirthRemove on chkin
                         fsm.m_dwSocketFlags
                         );
        if (error != ERROR_SUCCESS) {
            goto quit;
        }
    }
    if (IsCacheWriteInProgress()) {
        if (*fsm.m_lpdwNumberOfBytesRead == 0) {

            DEBUG_PRINT(CACHE,
                        INFO,
                        ("Cache write complete\r\n"
                        ));

            LocalEndCacheWrite((error == ERROR_SUCCESS)
                               && (GetBytesInSocket() == 0));
        } else if (!HaveReadFileExData()) {

            INET_ASSERT(!IsCacheReadInProgress());

            if (WriteCache((LPBYTE)fsm.m_lpBuffer,
                           *fsm.m_lpdwNumberOfBytesRead) != ERROR_SUCCESS) {

                DEBUG_PRINT(CACHE,
                            ERROR,
                            ("Error in Cache write\n"
                            ));

                LocalEndCacheWrite(FALSE);
            }
        }
    }

quit:

    if (error != ERROR_IO_PENDING) {
        fsm.SetDone();
    }

    PERF_LOG(PE_TRACE, 0x1002);

    DEBUG_LEAVE(error);

    return error;
}

//
// HTTP_REQUEST_HANDLE_OBJECT methods
//


DWORD
HTTP_REQUEST_HANDLE_OBJECT::ReadData(
    OUT LPVOID lpBuffer,
    IN DWORD dwNumberOfBytesToRead,
    OUT LPDWORD lpdwNumberOfBytesRead,
    IN BOOL fNoAsync, // BUGBUG RFirthRemove on DrainSocket checkin
    IN DWORD dwSocketFlags
    )

/*++

Routine Description:

    HTTP_REQUEST_HANDLE_OBJECT ReadData method

    Reads data into users buffer. Reads from header buffer if data exists
    there, or reads from the socket

Arguments:

    lpBuffer                - pointer to users buffer

    dwNumberOfBytesToRead   - size of buffer/number of bytes to read

    lpdwNumberOfBytesRead   - pointer to returned number of bytes read

    fNoAsync                - TRUE if we want to override defaults and have
                              no Async Read.

    dwSocketFlags           - controlling socket operation

Return Value:

    DWORD
        Success - ERROR_SUCCESS

        Failure - WSA error

--*/

{
    DEBUG_ENTER((DBG_HTTP,
                 Dword,
                 "HTTP_REQUEST_HANDLE_OBJECT::ReadData",
                 "%#x, %d, %#x, %B, %#x",
                 lpBuffer,
                 dwNumberOfBytesToRead,
                 lpdwNumberOfBytesRead,
                 fNoAsync,
                 dwSocketFlags
                 ));

    DWORD error = DoFsm(new CFsm_ReadData(lpBuffer,
                                          dwNumberOfBytesToRead,
                                          lpdwNumberOfBytesRead,
                                          fNoAsync,
                                          dwSocketFlags,
                                          this
                                          ));

    DEBUG_LEAVE(error);

    return error;
}


DWORD
CFsm_ReadData::RunSM(
    IN CFsm * Fsm
    )

/*++

Routine Description:

    description-of-function.

Arguments:

    Fsm -

Return Value:

    DWORD

--*/

{
    DEBUG_ENTER((DBG_HTTP,
                 Dword,
                 "CFsm_ReadData::RunSM",
                 "%#x",
                 Fsm
                 ));

    DWORD error;
    HTTP_REQUEST_HANDLE_OBJECT * pRequest;
    CFsm_ReadData * stateMachine = (CFsm_ReadData *)Fsm;

    pRequest = (HTTP_REQUEST_HANDLE_OBJECT *)Fsm->GetContext();
    switch (Fsm->GetState()) {
    case FSM_STATE_INIT:
    case FSM_STATE_CONTINUE:
        error = pRequest->ReadData_Fsm(stateMachine);
        break;

    default:
        error = ERROR_INTERNET_INTERNAL_ERROR;
        Fsm->SetDone(ERROR_INTERNET_INTERNAL_ERROR);

        INET_ASSERT(FALSE);

        break;
    }

    DEBUG_LEAVE(error);

    return error;
}


DWORD
HTTP_REQUEST_HANDLE_OBJECT::ReadData_Fsm(
    IN CFsm_ReadData * Fsm
    )

/*++

Routine Description:

    description-of-function.

Arguments:

    Fsm -

Return Value:

    DWORD

--*/

{
    DEBUG_ENTER((DBG_HTTP,
                 Dword,
                 "HTTP_REQUEST_HANDLE_OBJECT::ReadData_Fsm",
                 "%#x",
                 Fsm
                 ));

    PERF_LOG(PE_TRACE, 0x6001);

    CFsm_ReadData & fsm = *Fsm;
    DWORD error = ERROR_SUCCESS;

    if (fsm.GetState() == FSM_STATE_CONTINUE) {

        PERF_LOG(PE_TRACE, 0x6101);

        error = fsm.GetError();
        goto receive_continue;
    }

    fsm.m_dwBytesRead = 0;
    fsm.m_dwBufferLeft = fsm.m_dwNumberOfBytesToRead;
    fsm.m_nBytesCopied = 0;

    //
    // if there's no data then we're done
    //

    if (!IsData()) {

        DEBUG_PRINT(HTTP,
                    ERROR,
                    ("!IsData()\n"
                    ));

        SetState(HttpRequestStateReopen);

        INET_ASSERT(error == ERROR_SUCCESS);

        goto quit;
    }

    //
    // If using keep-alive, reduce output buffer so we don't over-read.
    //

    if (IsKeepAlive() && IsContentLength()) {
        if (_BytesRemaining == 0) {

            INET_ASSERT(error == ERROR_SUCCESS);

            PERF_LOG(PE_TRACE, 0x6102);

            goto done;
        }

        PERF_LOG(PE_TRACE, 0x6103);

        fsm.m_dwBufferLeft = min(fsm.m_dwBufferLeft, _BytesRemaining);
    }

    //
    // if there's data left in the response buffer then copy it
    //

    fsm.m_bEof = FALSE;

    if (IsBufferedData()) {

        DWORD amountToCopy = min(fsm.m_dwNumberOfBytesToRead, BufferDataAvailToRead());

        if (amountToCopy != 0) {

            PERF_LOG(PE_TRACE, 0x6104);

            DEBUG_PRINT(HTTP,
                        INFO,
                        ("Copying %d (%#x) bytes from header buffer @ %#x - %d left\n",
                        amountToCopy,
                        amountToCopy,
                        BufferedDataStart(),
                        BufferDataAvailToRead() - amountToCopy
                        ));

            memcpy(fsm.m_lpBuffer, BufferedDataStart(), amountToCopy);
            ReduceDataAvailToRead(amountToCopy);
            fsm.m_dwBytesRead += amountToCopy;
            fsm.m_dwBufferLeft -= amountToCopy;
            fsm.m_nBytesCopied += amountToCopy;

            //
            // we don't update lpBuffer here. Receive() takes the address of
            // the start of the buffer
            //

        }

        //
        // if we exhausted all the buffer space, then we're done
        //

        if (fsm.m_dwBufferLeft == 0) {

            PERF_LOG(PE_TRACE, 0x6105);

            goto done;
        }
    }

    //
    // find out if we're async. Even though the handle was created for async I/O
    // the request may be satisfied immediately
    //

    DWORD asyncFlags;

    if ( fsm.m_fNoAsync )   // BUGBUG RFirthRemove on Checkin of DrainSocket
        asyncFlags = 0;
    else
        asyncFlags = (IsAsyncHandle()
                        && (fsm.m_dwBufferLeft > AvailableDataLength()))
                   ? SF_NON_BLOCKING
                   : 0
                   ;

    //
    // if we have data already received in the query buffer, then return that
    //

    if (HaveQueryData()) {

        PERF_LOG(PE_TRACE, 0x6106);

        DWORD nCopied;

        nCopied = CopyQueriedData((LPVOID)((LPBYTE)fsm.m_lpBuffer + fsm.m_dwBytesRead),
                                  fsm.m_dwBufferLeft
                                  );

        DEBUG_PRINT(HTTP,
                    INFO,
                    ("Copied %d (%#x) bytes from query buffer @ %#x - %d left\n",
                    nCopied,
                    nCopied,
                    (LPBYTE)_QueryBuffer - _QueryOffset,
                    _QueryBytesAvailable
                    ));

        fsm.m_dwBytesRead += nCopied;
        fsm.m_dwBufferLeft -= nCopied;
        fsm.m_nBytesCopied += nCopied;
        if (fsm.m_dwBufferLeft == 0) {
            goto done;
        }
    }

    //
    // If the Chunk parser claims we're done, then we're done,
    //  stop ready and tell the reader
    //

    //if ( IsChunkEncoding() && IsChunkedEncodingFinished() )
    //{
    //    fsm.m_bEof = TRUE;
    //    goto done;
    //}


    if (HaveReadFileExData()) {
        PERF_LOG(PE_TRACE, 0x6107);
        *(LPBYTE)fsm.m_lpBuffer = GetReadFileExData();
        --fsm.m_dwNumberOfBytesToRead;
        --fsm.m_dwBufferLeft;
        ++fsm.m_dwBytesRead;

        DEBUG_PRINT(HTTP,
                    INFO,
                    ("Copied 1 byte (%#x) from ReadFileEx buffer %#x\n",
                    (BYTE)_ReadFileExData & 0xff,
                    &_ReadFileExData
                    ));

        if (fsm.m_dwBufferLeft == 0) {
            goto done;
        }
    }

    //
    // If the Chunk parser claims we're done, then we're done,
    //  stop ready and tell the reader
    //

    if ( IsChunkEncoding() && IsChunkedEncodingFinished() )
    {
        PERF_LOG(PE_TRACE, 0x6108);
        fsm.m_bEof = TRUE;
        goto done;
    }

    //
    // we're about to check the socket. Make sure its valid to do so
    //

    //INET_ASSERT(_Socket != NULL);

    if ((_Socket == NULL) || !_Socket->IsOpen()) {

        //
        // socket was closed - no more data
        //

        //
        // there is no more data to be received on this object
        //

        SetData(FALSE);

        //
        // this object can now be re-used
        //

        SetState(HttpRequestStateReopen);
        fsm.m_bEof = TRUE;
        PERF_LOG(PE_TRACE, 0x6109);
        goto quit;
    }

read_again:

    fsm.m_nBytes = fsm.m_dwBytesRead;

    //
    // if we had a content-length and we don't think there is any data left to
    // read then we're done
    //

    if (IsContentLength() && (_BytesInSocket == 0)) {
        fsm.m_bEof = TRUE;
        PERF_LOG(PE_TRACE, 0x6110);
        goto done;
    }

    //
    // receive data into user's buffer. Because we don't own the buffer, we
    // cannot resize it
    //

    LPVOID lpBuffer;
    DWORD dwBytesToRead;
    DWORD dwBufferLeft;
    DWORD dwBytesRead;

    lpBuffer = fsm.m_lpBuffer;
    dwBytesToRead = fsm.m_dwNumberOfBytesToRead;
    dwBufferLeft = fsm.m_dwBufferLeft;
    dwBytesRead = fsm.m_dwBytesRead;

    //INET_ASSERT(!(fsm.m_dwSocketFlags & SF_NO_WAIT)
    //            ? (fsm.m_dwBufferLeft <= _BytesRemaining)
    //            : TRUE);

    PERF_LOG(PE_TRACE, 0x6111);

    if (IsBadNSServer() && !IsConnCloseResponse()) {
        SetBadNSReceiveTimeout();
    }

    error = _Socket->Receive(&fsm.m_lpBuffer,
                             &fsm.m_dwNumberOfBytesToRead,
                             &fsm.m_dwBufferLeft,
                             &fsm.m_dwBytesRead,
                             0,
                             SF_INDICATE
                             | ((fsm.m_dwSocketFlags & SF_NO_WAIT)
                                ? SF_NO_WAIT
                                : (IsChunkEncoding() ? 0 : SF_RECEIVE_ALL)),
                             &fsm.m_bEof
                             );

    //
    // only if we performed an asynchronous no-wait receive and there was no
    // data available in the socket will we get WSAEWOULDBLOCK. Make another
    // receive request, this time without no-wait. It will complete
    // asynchronously and the app must make another no-wait request
    //

    if (error == WSAEWOULDBLOCK) {

        PERF_LOG(PE_TRACE, 0x6112);

        INET_ASSERT(fsm.m_dwSocketFlags & SF_NO_WAIT);
        INET_ASSERT(!fsm.m_bEof);

        //
        // BUGBUG - IsAsyncHandle() || IsAsyncRequest()
        //

        if ((fsm.m_dwBytesRead == 0) && IsAsyncHandle()) {

            DEBUG_PRINT(HTTP,
                        INFO,
                        ("Initiating wait-for-data (1-byte read)\n"
                        ));

            fsm.m_lpBuffer = (LPVOID)&_ReadFileExData;
            fsm.m_dwNumberOfBytesToRead = 1;
            fsm.m_dwBufferLeft = 1;
            fsm.m_dwSocketFlags &= ~SF_NO_WAIT;

            INET_ASSERT(!_HaveReadFileExData);

            SetReadFileExData();

            _ReadFileExData = 0;

            //INET_ASSERT(fsm.m_dwBufferLeft <= _BytesRemaining);

            PERF_LOG(PE_TRACE, 0x6113);

            if (IsBadNSServer() && !IsConnCloseResponse()) {
                SetBadNSReceiveTimeout();
            }

            error = _Socket->Receive(&fsm.m_lpBuffer,
                                     &fsm.m_dwNumberOfBytesToRead,
                                     &fsm.m_dwBufferLeft,
                                     &fsm.m_dwBytesRead,
                                     0,
                                     fsm.m_dwSocketFlags,
                                     &fsm.m_bEof
                                     );
            if (error == ERROR_SUCCESS) {

                PERF_LOG(PE_TRACE, 0x6114);

                BOOL fReadNothing = (fsm.m_dwBytesRead == 0 ? TRUE : FALSE);

                //
                // we have successfully read a single byte from the socket.
                //

                //INET_ASSERT(FALSE);

                fsm.m_lpBuffer = lpBuffer;
                fsm.m_dwNumberOfBytesToRead = dwBytesToRead;
                fsm.m_dwBufferLeft = dwBufferLeft;
                fsm.m_dwBytesRead = dwBytesRead;
                if (fReadNothing) {
                    // Don't copy if nothing was actually read.
                    ResetReadFileExData();
                }
                else {
                    *(LPBYTE)fsm.m_lpBuffer = GetReadFileExData();
                    --fsm.m_dwBufferLeft;
                    ++fsm.m_dwBytesRead;
                }

                //
                // BUGBUG - if socket unblocked already, should go round & read
                //          again, not just return 1 byte
                //

            }

            PERF_LOG(PE_TRACE, 0x6115);

        } else {

            PERF_LOG(PE_TRACE, 0x6116);

            DEBUG_PRINT(HTTP,
                        WARNING,
                        ("Not initiating wait-for-data: bytesRead = %d, asyncHandle = %B\n",
                        fsm.m_dwBytesRead,
                        IsAsyncHandle()
                        ));

            //
            // read data from buffers but nothing available from socket
            //

            error = ERROR_SUCCESS;
        }
    }

    if (error == ERROR_IO_PENDING) {
        PERF_LOG(PE_TRACE, 0x6117);
        goto quit_pending;
    }

receive_continue:

    PERF_LOG(PE_TRACE, 0x6118);

    //
    // if we timed-out while talking to 'bad' NS server (returns HTTP/1.1 but
    // content-length or chunked encoding info) then close the connection and
    // reset any RFX status. We return SUCCESS in this case
    //

    if ((error == ERROR_INTERNET_TIMEOUT) && IsBadNSServer()) {

        DEBUG_PRINT(HTTP,
                    INFO,
                    ("Bad NS server: Closing connection %#x/%d on timeout\n",
                    _Socket ? _Socket->GetSocket() : -1,
                    _Socket ? _Socket->GetSourcePort() : -1
                    ));

        CloseConnection(TRUE);
        ResetReadFileExData();
        SetData(FALSE);
        _dwCurrentStreamPosition += fsm.m_dwBytesRead;
        fsm.m_bEof = TRUE;
        error = ERROR_SUCCESS;
        goto quit;
    }
    if (error == ERROR_SUCCESS) {
        if (IsContentLength()) {

            INET_ASSERT(fsm.m_dwBytesRead >= fsm.m_nBytes);

            _BytesInSocket -= fsm.m_dwBytesRead - fsm.m_nBytes;

            INET_ASSERT((int)_BytesInSocket >= 0);

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

        if ( IsChunkEncoding() && !(HaveReadFileExData()))
        {

            PERF_LOG(PE_TRACE, 0x6119);

            LPSTR lpszNewBuffer;
            DWORD dwNewBufferLength;
            DWORD dwBytesJustRead = (fsm.m_dwBytesRead - fsm.m_nBytes);

            error = _ctChunkInfo.ParseChunkInput(
                (((LPSTR) fsm.m_lpBuffer) + fsm.m_nBytesCopied),
                (fsm.m_dwBytesRead - fsm.m_nBytesCopied),
                &lpszNewBuffer,
                &dwNewBufferLength
                );

            //
            // hack - wait for more data
            //

            if ((error == ERROR_SUCCESS)
            && (dwNewBufferLength == 0)
            && !IsChunkedEncodingFinished()
            && (fsm.m_nBytesCopied == 0)) {

                PERF_LOG(PE_TRACE, 0x6120);

                fsm.m_dwBufferLeft += fsm.m_dwBytesRead;
                fsm.m_dwBytesRead = 0;

                struct fd_set read_fds;
                struct fd_set write_fds;
                struct fd_set except_fds;

                FD_ZERO(&read_fds);
                FD_ZERO(&write_fds);
                FD_ZERO(&except_fds);

                FD_SET(_Socket->GetSocket(), &read_fds);

                struct timeval to;

                to.tv_sec = 30;
                to.tv_usec = 0;
                int n = _I_select(1, &read_fds, &write_fds, &except_fds, &to);
                goto read_again;
            }

            fsm.m_dwBufferLeft += (fsm.m_dwBytesRead - fsm.m_nBytesCopied);
            fsm.m_dwBytesRead  -= (fsm.m_dwBytesRead - fsm.m_nBytesCopied);

            fsm.m_dwBufferLeft -= dwNewBufferLength;
            fsm.m_dwBytesRead  += dwNewBufferLength;
            fsm.m_nBytesCopied += dwNewBufferLength;

            INET_ASSERT(error == ERROR_SUCCESS); // I want to see this happen.

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

            if ( IsChunkedEncodingFinished() )
            {
                fsm.m_bEof = TRUE;
            }
        }
    } else {

        PERF_LOG(PE_TRACE, 0x6121);

        DEBUG_PRINT(HTTP,
                    ERROR,
                    ("error %d on socket %#x\n",
                    error,
                    _Socket->GetSocket()
                    ));

        //
        // socket error
        //

        SetState(HttpRequestStateError);

        //
        // cause connection to be closed/released
        //

        fsm.m_bEof = TRUE;
    }

done:

    //
    // only update bytes remaining, EOF and the current stream position values
    // if we're returning data. If we just completed reading ReadFileEx data
    // then don't update. The 1 byte of ReadFileEx data will be read on the next
    // read proper
    //

    if (HaveReadFileExData()) {
        goto quit;
    }

    //
    // whether the data came from the response buffer or the socket, if we have
    // a content-length, update the amount of data left to retrieve
    //

    if (IsChunkEncoding()
        && IsChunkedEncodingFinished()
        && (_QueryBytesAvailable == 0)
        && (BufferDataAvailToRead() == 0)) {
        fsm.m_bEof = TRUE;
    } else if (IsKeepAlive() && IsContentLength()) {
        _BytesRemaining -= fsm.m_dwBytesRead;

        INET_ASSERT((int)_BytesRemaining >= 0);
        //
        // if we have read all the entity-body then we can release the keep-alive
        // connection, or close the socket
        //

        if (_BytesRemaining<=0 && ((int)_BytesRemaining>=-2)) {
            // We might overshoot by 1 or 2 because of server misinformation.
            fsm.m_bEof = TRUE;
        }
    }

    DEBUG_PRINT(HTTP,
                INFO,
                ("read %d bytes\n",
                fsm.m_dwBytesRead
                ));

    _dwCurrentStreamPosition += fsm.m_dwBytesRead;

    //
    // if we reached the end of the connection - either the end of the server
    // connection for real, or we received all indicated data on a keep-alive
    // connection - then close the connection
    //

    if (fsm.m_bEof) {

        PERF_LOG(PE_TRACE, 0x6122);

        //
        // if we don't need to keep hold of the connection, release it. In the
        // case of multi-part authentication (NTLM) over keep-alive connection
        // we need to keep the connection. With Kerberos, we don't need to keep
        // the connection.
        //

        if (GetAuthState() != AUTHSTATE_CHALLENGE) {

            DEBUG_PRINT(HTTP,
                        INFO,
                        ("end of data - freeing connection %#x (Auth State = %s)\n",
                        _Socket ? _Socket->GetSocket() : 0,
                        InternetMapAuthState(GetAuthState())
                        ));
            if (!((GetStatusCode() == 407) && IsKeepAlive()))
                CloseConnection(FALSE);

        } else {

            // AUTHSTATE_CHALLENGE - check if request is through proxy or is kerberos.

            // When IsRequestUsingProxy returns TRUE, there are three types of connections possible:
            // 1) http request forwarded by the proxy to the server
            // 2) connect request to proxy to establish https tunnel
            // 3) using https tunnel through proxy to the server

            // I believe the various methods return:
            //��������������������������������������������� http��� conn.�� tunnel
            // IsRequestUsingProxy                          1����� 1������� 1
            // IsViaProxy������������                       1����� 1������� 0
            // IsTunnel������������������������������������ 0���   1������� 0
            // IsTalkingToSecureServerViaProxy������������� 0����� 0������� 1

            INET_ASSERT(_pAuthCtx->GetSchemeType() != AUTHCTX::SCHEME_NEGOTIATE);

            if (GetAuthCtx()->GetSchemeType() == AUTHCTX::SCHEME_KERBEROS)
            {
                DEBUG_PRINT(HTTP,
                            INFO,
                            ("freeing connection - kerberos and auth state challenge\n"
                            ));
                CloseConnection(FALSE);
            }                
            else if (IsRequestUsingProxy()
                && !(IsTunnel() || IsTalkingToSecureServerViaProxy())
                && (_pAuthCtx->GetFlags() & PLUGIN_AUTH_FLAGS_KEEP_ALIVE_NOT_REQUIRED)
                && !_pAuthCtx->_fIsProxy)
            {
                // Ordinarily, if the auth state is AUTHSTATE_CHALLENGE we wish to keep
                // the current connection open (keep alive) so that the response will go
                // out on the same socket. NTLM, which requires keep-alive, does not
                // work when going through a proxy. DPA on the other hand can work through
                // a proxy. In the case that the proxy does not return keep-alive with the
                // challenge (Catapult appears to be the only proxy that does) we want to
                // close the socket to ensure that it is not subsequently used for the response.

                DEBUG_PRINT(HTTP,
                            INFO,
                            ("freeing connection - auth state challenge\n"
                            ));
                CloseConnection(FALSE);
            }
            else
            {
                //  Keep alive required - don't close socket.
                DEBUG_PRINT(HTTP,
                            INFO,
                            ("not freeing connection - auth state challenge\n"
                            ));
            }

        }

        //
        // there is no more data to be received on this object
        //

        SetData(FALSE);

        //
        // this object can now be re-used
        //

        SetState(HttpRequestStateReopen);
    }

quit:

    //
    // update the amount of data returned then we're outta here
    //

    *fsm.m_lpdwNumberOfBytesRead = fsm.m_dwBytesRead;

    if (error != ERROR_IO_PENDING) {
        fsm.SetDone();
    }

quit_pending:

    PERF_LOG(PE_TRACE, 0x6002);

    DEBUG_LEAVE(error);

    return error;
}


DWORD
HTTP_REQUEST_HANDLE_OBJECT::QueryDataAvailable(
    OUT LPDWORD lpdwNumberOfBytesAvailable
    )

/*++

Routine Description:

    Determines how much data is available to be read by the caller

    BUGBUG - need cache case

Arguments:

    lpdwNumberOfBytesAvailable  - returned number of bytes available

Return Value:

    DWORD
    Success - ERROR_SUCCESS

    Failure - ERROR_INTERNET_INCORRECT_HANDLE_STATE

--*/

{
    DEBUG_ENTER((DBG_HTTP,
                 Dword,
                 "HTTP_REQUEST_HANDLE_OBJECT::QueryDataAvailable",
                 "%#x",
                 lpdwNumberOfBytesAvailable
                 ));

    DWORD error = DoFsm(new CFsm_HttpQueryAvailable(lpdwNumberOfBytesAvailable,
                                                    this
                                                    ));

    DEBUG_LEAVE(error);

    return error;
}


DWORD
CFsm_HttpQueryAvailable::RunSM(
    IN CFsm * Fsm
    )

/*++

Routine Description:

    description-of-function.

Arguments:

    Fsm -

Return Value:

    DWORD

--*/

{
    DEBUG_ENTER((DBG_HTTP,
                 Dword,
                 "CFsm_HttpQueryAvailable::RunSM",
                 "%#x",
                 Fsm
                 ));

    DWORD error;
    HTTP_REQUEST_HANDLE_OBJECT * pRequest;
    CFsm_HttpQueryAvailable * stateMachine = (CFsm_HttpQueryAvailable *)Fsm;

    pRequest = (HTTP_REQUEST_HANDLE_OBJECT *)Fsm->GetContext();
    switch (Fsm->GetState()) {
    case FSM_STATE_INIT:
    case FSM_STATE_CONTINUE:
        error = pRequest->QueryAvailable_Fsm(stateMachine);
        break;

    default:
        error = ERROR_INTERNET_INTERNAL_ERROR;
        Fsm->SetDone(ERROR_INTERNET_INTERNAL_ERROR);

        INET_ASSERT(FALSE);

        break;
    }

    DEBUG_LEAVE(error);

    return error;
}


DWORD
HTTP_REQUEST_HANDLE_OBJECT::QueryAvailable_Fsm(
    IN CFsm_HttpQueryAvailable * Fsm
    )

/*++

Routine Description:

    description-of-function.

Arguments:

    Fsm -

Return Value:

    DWORD

--*/

{
    DEBUG_ENTER((DBG_HTTP,
                 Dword,
                 "QueryAvailable_Fsm",
                 "%#x",
                 Fsm
                 ));

    CFsm_HttpQueryAvailable & fsm = *Fsm;
    DWORD error = fsm.GetError();
    DWORD bytesAvailable = 0;

    if (fsm.GetState() == FSM_STATE_CONTINUE) {
        goto fsm_continue;
    }

    INET_ASSERT(fsm.GetState() == FSM_STATE_INIT);

    if (IsReadRequest()) {
        *fsm.m_lpdwNumberOfBytesAvailable = 0;
        if (_Socket != NULL) {

            //
            // Invoked ReadLoop fsm only if the read and write positions
            // are different, otherwise older code is more efficient.
            //
            error = DoFsm (new CFsm_ReadLoop (this, 0, NULL, 1, NULL));

            if (error == ERROR_SUCCESS) {
                *fsm.m_lpdwNumberOfBytesAvailable = AvailableDataLength();
            }
        } else {

            DEBUG_PRINT(HTTP,
                        INFO,
                        ("no socket\n"
                        ));

            fsm.m_bEof = TRUE;
        }
        goto done;
    }

    //
    // the handle must be readable
    //

    if (!IsValidHttpState(READ)) {
        error = ERROR_INTERNET_INCORRECT_HANDLE_STATE;
        goto quit;
    }

    fsm.m_bEof = FALSE;

    //
    // error must be ERROR_SUCCESS - we just read it out of FSM & didn't jump
    // anywhere
    //

    INET_ASSERT(error == ERROR_SUCCESS);

    //
    // first check if there is data to receive at all
    //

    if (IsData()) {

        //
        // if there's buffered data still available from receiving the headers,
        // then return that length, else query the information from the socket
        //

        if (IsBufferedData()) {
            bytesAvailable = BufferDataAvailToRead();

            DEBUG_PRINT(HTTP,
                        INFO,
                        ("%d bytes available in buffer\n",
                        bytesAvailable
                        ));

        } else if (_Socket != NULL) {

            //
            // the rest of the data must be read from the socket
            //

            BOOL checkSocket;

            if (IsKeepAlive() && IsContentLength()) {
                checkSocket = ((int)_BytesInSocket > 0) ? TRUE : FALSE;
            } else if (IsChunkEncoding()) {
                checkSocket = !IsChunkedEncodingFinished();
            } else {
                checkSocket = TRUE;
            }
            if (checkSocket) {
                if (_QueryBuffer != NULL) {
                    bytesAvailable = _QueryBytesAvailable;
                    checkSocket = (bytesAvailable == 0) ? TRUE : FALSE;
                } else {
                    error = _Socket->AllocateQueryBuffer(&_QueryBuffer,
                                                         &_QueryBufferLength
                                                         );
                    if (error != ERROR_SUCCESS) {
                        checkSocket = FALSE;
                    }
                }
            } else if (IsKeepAlive() && IsContentLength() && (_BytesRemaining == 0)) {
                fsm.m_bEof = TRUE;
            } else if (IsChunkEncoding() && IsChunkedEncodingFinished()) {
                fsm.m_bEof = TRUE;
            }
            if (checkSocket) {

                INET_ASSERT(_Socket->IsValid());
                INET_ASSERT(_QueryBytesAvailable == 0);

                //
                // reset the query buffer offset
                //

                _QueryOffset = 0;

                //
                // don't create another FSM just for the DataAvailable2 wrapper.
                // If it ever becomes more than a call to Receive() then create
                // an FSM
                //

read_again:

                fsm.m_lpBuffer = _QueryBuffer;
                fsm.m_dwBufferLength = (IsKeepAlive() && IsContentLength())
                                     ? min(_BytesRemaining, _QueryBufferLength)
                                     : _QueryBufferLength;
                fsm.m_dwBufferLeft = fsm.m_dwBufferLength;

                //INET_ASSERT(fsm.m_dwBufferLeft <= _BytesRemaining);

                if (IsBadNSServer() && !IsConnCloseResponse()) {
                    SetBadNSReceiveTimeout();
                }

                error = _Socket->Receive(&fsm.m_lpBuffer,
                                         &fsm.m_dwBufferLength,
                                         &fsm.m_dwBufferLeft, // don't care about this
                                         &_QueryBytesAvailable,
                                         0,
                                         0,
                                         &fsm.m_bEof
                                         );
                if (error == ERROR_IO_PENDING) {
                    goto done;
                }

fsm_continue:

                if ((error == ERROR_INTERNET_TIMEOUT) && IsBadNSServer()) {

                    DEBUG_PRINT(HTTP,
                                INFO,
                                ("Bad NS server: Closing connection %#x/%d on timeout\n",
                                _Socket ? _Socket->GetSocket() : -1,
                                _Socket ? _Socket->GetSourcePort() : -1
                                ));

                    CloseConnection(TRUE);
                    _QueryBytesAvailable = 0;
                    error = ERROR_SUCCESS;
                }
                if (error == ERROR_SUCCESS) {


                    //if ( IsChunkEncoding() )
                    if ( IsChunkEncoding() && (_QueryBytesAvailable != 0))
                    {
                        LPSTR lpszNewBuffer;
                        DWORD dwNewBufferLength = 0;

                        error = _ctChunkInfo.ParseChunkInput(
                            (LPSTR) _QueryBuffer,
                            _QueryBytesAvailable,
                            &lpszNewBuffer,
                            &dwNewBufferLength
                            );

                        _QueryBytesAvailable = dwNewBufferLength;

                        INET_ASSERT(error == ERROR_SUCCESS); // I want to see this.

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

                        //
                        // hack - wait for more data
                        //

                        if ((dwNewBufferLength == 0) && !IsChunkedEncodingFinished()) {

                            struct fd_set read_fds;
                            struct fd_set write_fds;
                            struct fd_set except_fds;

                            FD_ZERO(&read_fds);
                            FD_ZERO(&write_fds);
                            FD_ZERO(&except_fds);

                            FD_SET(_Socket->GetSocket(), &read_fds);

                            struct timeval to;

                            to.tv_sec = 30;
                            to.tv_usec = 0;
                            int n = _I_select(1, &read_fds, &write_fds, &except_fds, &to);
                            if (n > 0) {
                                goto read_again;
                            }
                        }
                    }


                    bytesAvailable = _QueryBytesAvailable;

                    //
                    // note the amount of data that is available immediately.
                    // This allows e.g. async InternetReadFile() to complete
                    // synchronously if the next request is for <= bytesAvailable
                    //

                    //SetAvailableDataLength(bytesAvailable);

                    DEBUG_PRINT(HTTP,
                                INFO,
                                ("%d bytes available in socket %#x\n",
                                bytesAvailable,
                                (_Socket ? _Socket->GetSocket() : 0)
                                ));

                    if ((bytesAvailable == 0)
                    && (IsChunkEncoding() ? IsChunkedEncodingFinished() : TRUE)) {
                        fsm.m_bEof = TRUE;
                    }
                    if (IsKeepAlive() && IsContentLength()) {
                        _BytesInSocket -= bytesAvailable;

                        INET_ASSERT((int)_BytesInSocket >= 0);

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

            //
            // all data read from socket & socket released
            //

            INET_ASSERT(error == ERROR_SUCCESS);
            INET_ASSERT(bytesAvailable == 0);

            DEBUG_PRINT(HTTP,
                        INFO,
                        ("no socket\n"
                        ));

            fsm.m_bEof = TRUE;
        }
    } else {

        INET_ASSERT(error == ERROR_SUCCESS);

        //
        // we may have already removed all the data from the socket
        //

        DEBUG_PRINT(HTTP,
                    INFO,
                    ("all data has been read\n"
                    ));

        fsm.m_bEof = TRUE;
    }

quit:

    if ((error == ERROR_SUCCESS) && (bytesAvailable == 0)) {
        if (IsCacheWriteInProgress()) {
            LocalEndCacheWrite(TRUE);
        }
    }

    *fsm.m_lpdwNumberOfBytesAvailable = bytesAvailable;

    //
    // if we have reached the end of the data then we can release the connection
    //

    /*
    if (fsm.m_bEof || (bytesAvailable >= _BytesRemaining)) {
        if (_Socket != NULL) {
            CloseConnection(FALSE);
        }
    }
    */
    if (fsm.m_bEof) {
        if (_Socket != NULL) {
            CloseConnection(FALSE);
        }
    }

done:

    if (error != ERROR_IO_PENDING) {
        fsm.SetDone();
    }

    DEBUG_LEAVE(error);

    return error;
}


DWORD
HTTP_REQUEST_HANDLE_OBJECT::DrainResponse(
    OUT LPBOOL lpbDrained
    )

/*++

Routine Description:

    Receives any remaining response data into the buffer we allocated for the
    headers. Used in redirection: if the server returns some HTML page (e.g.)
    with the redirection response, we give the app a chance to read it. This
    way, we allow the app to retrieve the data immediately during the status
    callback in which we indicate that the request has been redirected

Arguments:

    lpbDrained  - TRUE if we really drained the socket else FALSE

Return Value:

    DWORD
        Success - ERROR_SUCCESS

        Failure - WSA error mapped to ERROR_INTERNET_XXX

--*/

{
    DEBUG_ENTER((DBG_HTTP,
                 Dword,
                 "HTTP_REQUEST_HANDLE_OBJECT::DrainResponse",
                 "%#x",
                 lpbDrained
                 ));

    DWORD error = DoFsm(new CFsm_DrainResponse(lpbDrained, this));

    DEBUG_LEAVE(error);

    return error;
}


DWORD
CFsm_DrainResponse::RunSM(
    IN CFsm * Fsm
    )

/*++

Routine Description:

    description-of-function.

Arguments:

    Fsm -

Return Value:

    DWORD

--*/

{
    DEBUG_ENTER((DBG_HTTP,
                 Dword,
                 "CFsm_DrainResponse::RunSM",
                 "%#x",
                 Fsm
                 ));

    DWORD error;
    HTTP_REQUEST_HANDLE_OBJECT * pRequest;
    CFsm_DrainResponse * stateMachine = (CFsm_DrainResponse *)Fsm;

    pRequest = (HTTP_REQUEST_HANDLE_OBJECT *)Fsm->GetContext();
    switch (Fsm->GetState()) {
    case FSM_STATE_INIT:
    case FSM_STATE_CONTINUE:
        error = pRequest->DrainResponse_Fsm(stateMachine);
        break;

    default:
        error = ERROR_INTERNET_INTERNAL_ERROR;
        Fsm->SetDone(ERROR_INTERNET_INTERNAL_ERROR);

        INET_ASSERT(FALSE);

        break;
    }

    DEBUG_LEAVE(error);

    return error;
}


DWORD
HTTP_REQUEST_HANDLE_OBJECT::DrainResponse_Fsm(
    IN CFsm_DrainResponse * Fsm
    )

/*++

Routine Description:

    description-of-function.

Arguments:

    Fsm -

Return Value:

    DWORD

--*/

{
    DEBUG_ENTER((DBG_HTTP,
                 Dword,
                 "HTTP_REQUEST_HANDLE_OBJECT::DrainResponse_Fsm",
                 "%#x",
                 Fsm
                 ));

    CFsm_DrainResponse & fsm = *Fsm;
    DWORD error = fsm.GetError();
    BOOL drainIt = FALSE;

    if (error != ERROR_SUCCESS) {
        goto quit;
    }
    if (fsm.GetState() == FSM_STATE_CONTINUE) {
        drainIt = TRUE;
        goto fsm_continue;
    }

    PERF_LOG(PE_TRACE, 0x8001);

    drainIt = TRUE;

    //
    // if the socket is already closed, we can't drain it
    //

    if ((_Socket == NULL) || !_Socket->IsValid()) {
        drainIt = FALSE;
    } else if (IsWantKeepAlive()) {

        //
        // IIS 1.0 has a bug where it can return a failure indication to a
        // request that was made using a keep-alive connection. The response
        // doesn't contain a keep-alive header but the server has left open the
        // connection AND it has returned us fewer bytes than was claimed in
        // the content-length header. If we try to drain the response buffer at
        // this point, we will wait a long time waiting for the server to send
        // us the non-existent additional bytes. Therefore, if we are talking
        // to an IIS 1.0 server, we don't drain the response buffer
        //

        LPSTR lpszServerBuf;
        DWORD serverBufferLength;

        _ResponseHeaders.LockHeaders();

        error = FastQueryResponseHeader(HTTP_QUERY_SERVER,
                                        (LPVOID *)&lpszServerBuf,
                                        &serverBufferLength,
                                        0
                                        );
        if (error == ERROR_SUCCESS) {

#define IIS         "Microsoft-IIS/"
#define IIS_LEN     (sizeof(IIS) - 1)

#define PWS         "Microsoft-PWS/"
#define PWS_LEN     (sizeof(PWS) - 1)

#define PWS95       "Microsoft-PWS-95/"
#define PWS95_LEN   (sizeof(PWS95) - 1)

#define IIS10       "Microsoft-Internet-Information-Server/"
#define IIS10_LEN   (sizeof(IIS10) - 1)

            if ((serverBufferLength > IIS_LEN)
                && !strnicmp(lpszServerBuf, IIS, IIS_LEN)) {

                int major_num = 0;

                for (DWORD i = IIS_LEN; i < serverBufferLength; ++i) {

                    char ch = lpszServerBuf[i];

                    if (isdigit(ch)) {
                        major_num = major_num * 10 + (int)(ch - '0');
                    } else {
                        break;
                    }
                }
                if (major_num < 4) {
                    drainIt = FALSE;
                }
            } else if (IsBadNSServer()) {
                drainIt = FALSE;
            } else if (((serverBufferLength > IIS10_LEN)
                        && !strncmp(lpszServerBuf, IIS10, IIS10_LEN))
                       || ((serverBufferLength > PWS_LEN)
                           && !strncmp(lpszServerBuf, PWS, PWS_LEN))
                       || ((serverBufferLength > PWS95_LEN)
                           && !strncmp(lpszServerBuf, PWS95, PWS95_LEN))) {
                drainIt = FALSE;
            }
        }
        _ResponseHeaders.UnlockHeaders();
    }

    error = ERROR_SUCCESS;

    if (drainIt) {

        fsm.m_dwAsyncFlags = IsAsyncHandle() ? SF_WAIT : 0;
        fsm.m_dwAmountToRead = IsContentLength() ? _BytesInSocket : (DWORD)-1;
        //DWORD bufferLeft = _ResponseBufferLength - _BytesReceived;
        fsm.m_dwBufferLeft = min(fsm.m_dwAmountToRead, _ResponseBufferLength - _BytesReceived);

        if (IsChunkEncoding() && IsChunkedEncodingFinished()) {
            fsm.m_dwAmountToRead = 0;
            fsm.m_bEof = TRUE;

            INET_ASSERT(fsm.m_dwBytesReceived == 0);

        }

        //
        // either receive the amount specified in the "Content-Length" header, or
        // receive until we hit the end of the connection. We may have already
        // received the entire response
        //

        while ((fsm.m_dwAmountToRead != 0) && !fsm.m_bEof && (error == ERROR_SUCCESS)) {

            fsm.m_dwPreviousBytesReceived = _BytesReceived;

            //
            // receive the rest of the data. We are assuming here that it is a
            // couple of K at the most. Notice that we don't care to make status
            // callbacks to the app while we are doing this
            //

            //INET_ASSERT(fsm.m_dwBufferLeft <= _BytesRemaining);

            error = _Socket->Receive((LPVOID *)&_ResponseBuffer,
                                     &_ResponseBufferLength,
                                     &fsm.m_dwBufferLeft,
                                     &_BytesReceived,
                                     0,   // dwExtraSpace
                                     SF_EXPAND
                                     | SF_COMPRESS
                                     | fsm.m_dwAsyncFlags,
                                     &fsm.m_bEof
                                     );
            if (error == ERROR_IO_PENDING) {
                goto quit;
            }

fsm_continue:

            if (error == ERROR_SUCCESS) {

                DWORD nRead = _BytesReceived - fsm.m_dwPreviousBytesReceived;

                if (IsContentLength()) {
                    fsm.m_dwAmountToRead -= nRead;

                    INET_ASSERT((int)fsm.m_dwAmountToRead >= 0);

                    _BytesInSocket -= nRead;

                    INET_ASSERT((int)_BytesInSocket >= 0);

                    if (IsKeepAlive()) {
                        _BytesRemaining -= nRead;

                        INET_ASSERT((int)_BytesRemaining >= 0);

                        //
                        // if we have read all the entity-body then we can
                        // release the keep-alive connection, or close the
                        // socket
                        //

                        //
                        // BUGBUG - put back post-ie30a
                        //

                        //if (_BytesRemaining == 0) {
                        //    fsm.m_bEof = TRUE;
                        //}
                    }
                }

                if ( IsChunkEncoding() )
                {
                    LPSTR lpszNewBuffer;
                    DWORD dwNewBufferLength;

                    INET_ASSERT(!IsContentLength());

                    error = _ctChunkInfo.ParseChunkInput(
                        (LPSTR) (_ResponseBuffer + fsm.m_dwPreviousBytesReceived),
                        nRead,
                        &lpszNewBuffer,
                        &dwNewBufferLength
                        );

                    nRead = dwNewBufferLength;
                    _BytesReceived = nRead + fsm.m_dwPreviousBytesReceived;

                    INET_ASSERT(error == ERROR_SUCCESS); // I want to see this happen.
                    if ( error != ERROR_SUCCESS )
                    {
                        break;
                    }

                    if ( IsChunkedEncodingFinished() )
                    {
                        fsm.m_bEof = TRUE;
                        break;
                    }
                }

                fsm.m_dwBytesReceived += nRead;
                fsm.m_dwPreviousBytesReceived = _BytesReceived;
            }
        }
    }

    if (error == ERROR_SUCCESS) {

        //
        // update the amount of data immediately available to the caller
        //

        IncreaseAvailableDataLength(fsm.m_dwBytesReceived);

        //
        // and set the end-of-file indication in the top level handle object
        //

        SetEndOfFile();

        //
        // there is no more data to be received on this HTTP object
        //

        //SetData(FALSE);

        //
        // this object can now be re-used
        //

        SetState(HttpRequestStateReopen);
    }

    //
    // return indication that we drained the socket
    //

    DEBUG_PRINT(HTTP,
                INFO,
                ("returning *lpbDrained = %B\n",
                drainIt
                ));

quit:

    if (error != ERROR_IO_PENDING) {
        fsm.SetDone();
        *fsm.m_lpbDrained = drainIt;
    }

    PERF_LOG(PE_TRACE, 0x8002);

    DEBUG_LEAVE(error);

    return error;
}

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

    if ((_Socket != NULL)
        && !IsContentLength()
        && !IsChunkEncoding()) {

        CServerInfo * pServerInfo = GetServerInfo();

        if (pServerInfo) {

            DWORD timeout = max(5000, 5 * pServerInfo->GetRTT());

            _Socket->SetTimeout(RECEIVE_TIMEOUT, timeout);
            SetTimeout(INTERNET_OPTION_RECEIVE_TIMEOUT, timeout);
        }
    }

    DEBUG_LEAVE(0);
}