You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1988 lines
52 KiB
1988 lines
52 KiB
/*++
|
|
|
|
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);
|
|
}
|