|
|
/*++
Copyright (c) 1994 Microsoft Corporation
Module Name:
response.cxx
Abstract:
This file contains the HTTP Request Handle Object ReceiveResponse method
Contents: CFsm_ReceiveResponse::RunSM HTTP_REQUEST_HANDLE_OBJECT::ReceiveResponse_Fsm
Author:
Keith Moore (keithmo) 16-Nov-1994
Revision History:
29-Apr-97 rfirth Conversion to FSM
--*/
#include <wininetp.h>
#include <perfdiag.hxx>
#include "httpp.h"
//
// private manifests
//
#define DEFAULT_RESPONSE_BUFFER_LENGTH (1 K)
//
// HTTP Request Handle Object methods
//
DWORD CFsm_ReceiveResponse::RunSM( IN CFsm * Fsm )
/*++
Routine Description:
description-of-function.
Arguments:
Fsm -
Return Value:
DWORD
--*/
{ DEBUG_ENTER((DBG_HTTP, Dword, "CFsm_ReceiveResponse::RunSM", "%#x", Fsm ));
CFsm_ReceiveResponse * stateMachine = (CFsm_ReceiveResponse *)Fsm; HTTP_REQUEST_HANDLE_OBJECT * pRequest; DWORD error;
START_SENDREQ_PERF();
pRequest = (HTTP_REQUEST_HANDLE_OBJECT *)Fsm->GetContext();
switch (Fsm->GetState()) { case FSM_STATE_INIT: case FSM_STATE_CONTINUE: error = pRequest->ReceiveResponse_Fsm(stateMachine); break;
case FSM_STATE_ERROR: error = Fsm->GetError(); INET_ASSERT (error == ERROR_WINHTTP_OPERATION_CANCELLED); Fsm->SetDone(); break;
default: error = ERROR_WINHTTP_INTERNAL_ERROR; Fsm->SetDone(ERROR_WINHTTP_INTERNAL_ERROR);
INET_ASSERT(FALSE);
break; }
STOP_SENDREQ_PERF();
DEBUG_LEAVE(error);
return error; }
DWORD HTTP_REQUEST_HANDLE_OBJECT::ReceiveResponse_Fsm( IN CFsm_ReceiveResponse * Fsm )
/*++
Routine Description:
description-of-function.
Arguments:
Fsm -
Return Value:
DWORD
--*/
{ #if INET_DEBUG
//#define RLF_TEST_CODE
#ifdef RLF_TEST_CODE
//
// single 100 response
//
#define TEST_HEADER_0 "HTTP/1.1 100 Continue\r\n" \
"\r\n"
//
// single 100 header
//
#define TEST_HEADER_1 "HTTP/1.1 100 Continue\r\n" \
"Server: Richard's Test-Case Virtual Server/1.0\r\n" \ "Date: Mon, 01 Apr 2000 00:00:01 GMT\r\n" \ "\r\n"
//
// continue header with moderate amount of data
//
#define TEST_HEADER_2 "HTTP/1.1 100 Continue\r\n" \
"Server: Richard's Test-Case Virtual Server/1.0\r\n" \ "Date: Mon, 01 Apr 2000 00:00:01 GMT\r\n" \ "Content-Length: 128\r\n" \ "Content-Type: octet/shmoctet\r\n" \ "\r\n" \ "0123456789abcdef" \ "0123456789abcdef" \ "0123456789abcdef" \ "0123456789abcdef" \ "0123456789abcdef" \ "0123456789abcdef" \ "0123456789abcdef" \ "0123456789abcdef"
//
// continue header seen from apache server
//
#define TEST_HEADER_3 "HTTP/1.1 100 Continue\r\n" \
"\r\n" \ "\n\n\n\n\n"
//
// multiple continue headers, no data
//
#define TEST_HEADER_4 "HTTP/1.1 100 Continue\r\n" \
"Server: Richard's Test-Case Virtual Server/1.0\r\n" \ "Date: Mon, 01 Apr 2000 00:00:01 GMT\r\n" \ "\r\n" \ "HTTP/1.1 100 Continue\r\n" \ "Server: Richard's Test-Case Virtual Server/1.0\r\n" \ "Date: Mon, 01 Apr 2000 00:00:01 GMT\r\n" \ "\r\n" \ "HTTP/1.1 100 Continue\r\n" \ "Server: Richard's Test-Case Virtual Server/1.0\r\n" \ "Date: Mon, 01 Apr 2000 00:00:01 GMT\r\n" \ "\r\n" \ "HTTP/1.1 100 Continue\r\n" \ "Server: Richard's Test-Case Virtual Server/1.0\r\n" \ "Date: Mon, 01 Apr 2000 00:00:01 GMT\r\n" \ "\r\n"
//
// single 100 response, preceeded by preamble and containing a chunked response
//
#define TEST_HEADER_5 "!!!! this is a pre-amble, should be ignored even though it includes HTTP !!!!" \
" " \ "HTTP/1.1 100 Go ahead punk, make my day\r\n" \ "Server: Richard's Test-Case Virtual Server/1.0\r\n" \ "Date: Mon, 01 Apr 2000 00:00:01 GMT\r\n" \ "Transfer-Encoding: chunked\r\n" \ "\r\n" \ "0010 this is the first chunk (16 bytes)\r\n" \ "0123456789abcdef" \ "\r\n" \ " 10; this is the second chunk (16 bytes)\r\n" \ "0123456789abcdef" \ "\r\n" \ "00F3\r\n" \ "0123456789abcdef" \ "0123456789abcdef" \ "0123456789abcdef" \ "0123456789abcdef" \ "0123456789abcdef" \ "0123456789abcdef" \ "0123456789abcdef" \ "0123456789abcdef" \ "0123456789abcdef" \ "0123456789abcdef" \ "0123456789abcdef" \ "0123456789abcdef" \ "0123456789abcdef" \ "0123456789abcdef" \ "0123456789abcdef" \ "012" \ "\r\n" \ "0000; the final chunk\r\n" \ "\r\n" \ "Entity-Header: this is the chunk footer\r\n" \ "\r\n"
//
// enpty chunk encoded response with empty footer
//
#define TEST_HEADER_6 "HTTP/1.1 100 Continue\r\n" \
"Server: Richard's Test-Case Virtual Server/1.0\r\n" \ "Date: Mon, 01 Apr 2000 00:00:01 GMT\r\n" \ "Transfer-Encoding: chunked\r\n" \ "\r\n" \ "0\r\n" \ "\r\n" \ "\r\n"
const struct {LPSTR ptr; DWORD len;} test_cases[] = { TEST_HEADER_0, sizeof(TEST_HEADER_0) - 1, TEST_HEADER_1, sizeof(TEST_HEADER_1) - 1, TEST_HEADER_2, sizeof(TEST_HEADER_2) - 1, TEST_HEADER_3, sizeof(TEST_HEADER_3) - 1, TEST_HEADER_4, sizeof(TEST_HEADER_4) - 1, TEST_HEADER_5, sizeof(TEST_HEADER_5) - 1, TEST_HEADER_6, sizeof(TEST_HEADER_6) - 1 }; DWORD test_index = 99;
#endif // def RLF_TEST_CODE
#endif // INET_DEBUG
DEBUG_ENTER((DBG_HTTP, Dword, "HTTP_REQUEST_HANDLE_OBJECT::ReceiveResponse_Fsm", "%#x", Fsm ));
PERF_ENTER(ReceiveResponse_Fsm);
CFsm_ReceiveResponse & fsm = *Fsm; DWORD error = fsm.GetError(); FSM_STATE state = fsm.GetState();
if (error != ERROR_SUCCESS) {
if (error == ERROR_WINHTTP_CLIENT_AUTH_CERT_NEEDED) {
if ((_Socket != NULL) && _Socket->IsSecure()) { if(m_pSecurityInfo) { /* SCLE ref */ m_pSecurityInfo->Release(); } /* SCLE ref */ m_pSecurityInfo = ((ICSecureSocket *)_Socket)->GetSecurityEntry(); }
SetState(HttpRequestStateOpen); CloseConnection(TRUE); fsm.SetDone(); goto quit2; }
goto quit; } if (state != FSM_STATE_INIT) { state = fsm.GetFunctionState(); } do { switch (state) { case FSM_STATE_INIT: if (_ResponseBuffer == NULL) { _ResponseBufferLength = DEFAULT_RESPONSE_BUFFER_LENGTH; _ResponseBuffer = (LPBYTE)ALLOCATE_MEMORY(_ResponseBufferLength); if (_ResponseBuffer == NULL) { _ResponseBufferLength = 0; error = ERROR_NOT_ENOUGH_MEMORY; goto quit; } }
INET_ASSERT(_BytesReceived == 0);
fsm.m_dwReceiveResponseStartTicks = GetTickCountWrap();
fsm.m_dwResponseLeft = _ResponseBufferLength; state = FSM_STATE_2;
//
// fall through
//
#ifdef RLF_TEST_CODE
InternetGetDebugVariable("WininetTestIndex", &test_index); if (test_index < ARRAY_ELEMENTS(test_cases)) { _BytesReceived = test_cases[test_index].len; memcpy(_ResponseBuffer, test_cases[test_index].ptr, _BytesReceived); fsm.m_dwResponseLeft = _ResponseBufferLength - _BytesReceived; }
#endif // def RLF_TEST_CODE
case FSM_STATE_2:
//
// we will allow Receive() to expand the buffer (and therefore initially
// allocate it), and to compress the buffer if we receive the end of the
// connection. It is up to UpdateResponseHeaders() to figure out when
// enough data has been read to indicate end of the headers
//
fsm.SetFunctionState(FSM_STATE_3);
INET_ASSERT(_Socket != NULL);
if (_Socket != NULL) { error = _Socket->Receive((LPVOID *)&_ResponseBuffer, &_ResponseBufferLength, &fsm.m_dwResponseLeft, &_BytesReceived, 0, SF_EXPAND | SF_COMPRESS | SF_INDICATE, &fsm.m_bEofResponseHeaders ); if (error == ERROR_IO_PENDING) { goto quit; } } else { error = ERROR_WINHTTP_OPERATION_CANCELLED; }
//
// fall through
//
case FSM_STATE_3:
//
// Check that ReceiveResponse processing hasn't taken too long.
//
if( (GetTickCountWrap() - fsm.m_dwReceiveResponseStartTicks) >= _dwReceiveResponseTimeout ) { error = ERROR_WINHTTP_TIMEOUT; }
//
// if we are using a keep-alive connection that was previously timed-out
// by the server, we may not find out about it until now
//
// Note: it seems we can get a zero length response at this point also,
// which I take to mean that the server-side socket has been closed
//
INET_ASSERT(_BytesReceived <= _ResponseBufferLength);
if ((error != ERROR_SUCCESS) || ((_BytesReceived == 0) && IsKeepAlive())) {
//
// We need to reset the state if we got a
// certificate request.
//
if (error == ERROR_WINHTTP_CLIENT_AUTH_CERT_NEEDED) {
if ((_Socket != NULL) && _Socket->IsSecure()) { if(m_pSecurityInfo) { /* SCLE ref */ m_pSecurityInfo->Release(); } /* SCLE ref */ m_pSecurityInfo = ((ICSecureSocket *)_Socket)->GetSecurityEntry(); }
SetState(HttpRequestStateOpen); } CloseConnection(TRUE); goto quit; }
//
// if we received no data then the server has closed the connection
// already
//
if (_BytesReceived != 0) {
BOOL bHaveFinalResponse;
do { bHaveFinalResponse = TRUE; error = UpdateResponseHeaders(&fsm.m_bEofResponseHeaders); //if (!(rand() % 7)) {
// error = ERROR_HTTP_INVALID_SERVER_RESPONSE;
//}
if (error != ERROR_SUCCESS) { //dprintf("UpdateResponseHeaders() returns %d\n", error);
break; }
DWORD statusCode;
statusCode = GetStatusCode();
//
// receive next packet if we didn't get a status code yet
//
if (statusCode == 0) { break; }
//
// discard any 1xx responses and get the headers again
//
if (fsm.m_bEofResponseHeaders && (statusCode >= HTTP_STATUS_CONTINUE) && (statusCode < HTTP_STATUS_OK)) { bHaveFinalResponse = FALSE;
if (fsm.m_dwHttpStatusContinueCount == 0) { error = ERROR_WINHTTP_INVALID_SERVER_RESPONSE; goto quit; } fsm.m_dwHttpStatusContinueCount--; fsm.SetFunctionState(FSM_STATE_4);
//
// get any data that came with the header
//
fsm.m_bDrained = FALSE; if ((IsContentLength() && (_BytesInSocket != 0)) || (IsChunkEncoding() && !IsDecodingFinished())) { error = DrainResponse(&fsm.m_bDrained); if (error != ERROR_SUCCESS) { goto quit; } }
//
// fall through
//
case FSM_STATE_4:
bHaveFinalResponse = FALSE; //
// now that we have drained the socket, we can indicate
// the response to the app. This gives apps chance to
// perform progress reporting for each 100 response
// received, e.g.
//
DWORD dwStatusCode = GetStatusCode(); InternetIndicateStatus(WINHTTP_CALLBACK_STATUS_INTERMEDIATE_RESPONSE, &dwStatusCode, sizeof(dwStatusCode) );
//
// if there is no more data left in the buffer then we
// can receive the next response at the start of the
// buffer, else continue from where the previous one
// ended
//
if (fsm.m_bDrained || !IsBufferedData()) { fsm.m_dwResponseLeft = _ResponseBufferLength; _BytesReceived = 0; _DataOffset = 0; _ResponseScanned = 0; } else { _ResponseScanned = _DataOffset; if (IsContentLength()) { _ResponseScanned += _ContentLength; } } _ResponseHeaders.FreeHeaders(); _ResponseHeaders.Initialize(); ZapFlags(); _ContentLength = 0; _BytesRemaining = 0; _BytesInSocket = 0; fsm.m_bEofResponseHeaders = FALSE; if (_DataOffset == 0) {
//
// need to read next response - nothing left in
// buffer
//
break; } }
// If we have a server authentication context
// and the response is anything but 401, mark
// the socket as authenticated.
AUTHCTX *pAuthCtx; pAuthCtx = GetAuthCtx(); if (pAuthCtx && !pAuthCtx->_fIsProxy && (GetStatusCode() != HTTP_STATUS_DENIED)) {
#define MICROSOFT_IIS_SERVER_SZ "Microsoft-IIS/"
#define MICROSOFT_PWS_SERVER_SZ "Microsoft-PWS/"
#define MICROSOFT_IIS_SERVER_LEN (sizeof(MICROSOFT_IIS_SERVER_SZ) - 1)
#define MICROSOFT_PWS_SERVER_LEN (sizeof(MICROSOFT_PWS_SERVER_SZ) - 1)
LPSTR pszBuf; DWORD cbBuf; cbBuf = MAX_PATH; if (FastQueryResponseHeader(HTTP_QUERY_SERVER, (LPVOID*) &pszBuf, &cbBuf, 0) == ERROR_SUCCESS) { if (cbBuf >= MICROSOFT_IIS_SERVER_LEN && (!strncmp(pszBuf, MICROSOFT_IIS_SERVER_SZ, MICROSOFT_IIS_SERVER_LEN) || !strncmp(pszBuf, MICROSOFT_PWS_SERVER_SZ, MICROSOFT_PWS_SERVER_LEN)))
{ // Found an IIS header. Mark socket as authenticated if
// IIS 1, 2 or 3. Lengths of both strings are same.
CHAR *pVer = pszBuf + MICROSOFT_IIS_SERVER_LEN; if (*pVer == '1' || *pVer == '2' || *pVer == '3' ) { // IIS 1, 2 or 3 - mark dirty.
_Socket->SetAuthenticated(); } } } else { // Unknown server; may be IIS 1,2 or 3.
_Socket->SetAuthenticated(); } }
} while (!bHaveFinalResponse); } else { error = ERROR_HTTP_INVALID_SERVER_RESPONSE; }
//
// set state to perform next receive
//
state = FSM_STATE_2; } } while ((error == ERROR_SUCCESS) && !fsm.m_bEofResponseHeaders);
//
// we should update the RTT as soon as we get received data from
// the socket, but then we'd have to store the RTT in the socket
// object or access this one, etc. Just keep it here for now -
// its a reasonable approximation in the normal IE case: not too
// much time spent in callbacks etc.
//
UpdateRTT(); //dprintf("RTT for %s = %d\n", GetURL(), GetRTT());
//dprintf("OS = %s, PS = %s\n", ((GetOriginServer() != NULL) ? GetOriginServer()->GetHostName() : "none"),
// ((GetServerInfo() != NULL) ? GetServerInfo()->GetHostName() : "none"));
//
// we have received the headers and possibly some (or all) of the data. The
// app can now query the headers and receive the data
//
SetState(HttpRequestStateObjectData);
//
// record the amount of data immediately available to the app
//
if ( IsChunkEncoding() ) { DWORD dwChunkBytesRead = 0; DWORD dwChunkBytesWritten = 0;
error = _ResponseFilterList.Decode( (LPBYTE) BufferedDataStart(), BufferedDataLength(), NULL, NULL, &dwChunkBytesRead, &dwChunkBytesWritten );
_ResponseBufferDataReadyToRead = dwChunkBytesWritten;
if ( error != ERROR_SUCCESS ) { goto quit; } }
SetAvailableDataLength(BufferDataAvailToRead());
//
// IIS caches authentication credentials on keep-alive sockets.
//
if (_Socket) {
if (IsAuthorized()) { _Socket->SetAuthorized(); } }
quit:
if (error != ERROR_IO_PENDING) { fsm.SetDone();
//
// if we got the socket from the keep-alive pool, but found no keep-
// alive header then we no longer have a keep-alive connection
//
if (_bKeepAliveConnection && !IsKeepAlive()) { //dprintf("*** %s - NO LONGER K-A socket %#x\n", GetURL(), _Socket->GetSocket());
SetNoLongerKeepAlive(); }
//
// don't maintain the connection if there's no more data to read. UNLESS
// we are in the middle of establishing an authenticated connection
// (implies using keep-alive connection, e.g. NTLM)
// IsData() returns FALSE if there's no data at all, otherwise we
// check to see if we have read all the data already (i.e. with the
// response headers)
//
if ((error != ERROR_SUCCESS) || (
//
// data-less response (ignoring keep-alive & content-length)
//
(!IsData()
//
// all data body in header buffer
//
|| (IsKeepAlive() && IsContentLength() && (BufferedDataLength() == GetContentLength()) ) )
//
// but only if not in the middle of auth negotiation and if the
// connection hasn't been dropped by the server
//
&& ((GetAuthState() != AUTHSTATE_NEGOTIATE) || IsNoLongerKeepAlive()) ) ) {
//dprintf("socket %#x [%#x/%d] error=%d, IsData()=%B, K-A=%B, C-L=%d, BDL=%d, AS=%d\n",
// _Socket,
// _Socket ? _Socket->GetSocket() : 0,
// _Socket ? _Socket->GetSourcePort() : 0,
// error,
// IsData(),
// IsKeepAlive(),
// GetContentLength(),
// BufferedDataLength(),
// GetAuthState()
// );
//
// BUGBUG - if this is a new keep-alive connection?
//
DEBUG_PRINT(HTTP, INFO, ("closing: error = %d, IsData() = %B, K-A = %B, IsC-L = %B, BDL = %d, C-L = %d\n", error, IsData(), IsKeepAlive(), IsContentLength(), BufferedDataLength(), GetContentLength() ));
if(GetStatusCode() != HTTP_STATUS_REDIRECT || (HTTP_METHOD_TYPE_HEAD == GetMethodType())) CloseConnection((error != ERROR_SUCCESS) ? TRUE : FALSE); else DEBUG_PRINT(HTTP, INFO, ("Not closing socket, Status code = %d \n", GetStatusCode()));
//
// set the relevant state
//
if (error != ERROR_SUCCESS && (error != ERROR_WINHTTP_SECURE_FAILURE || GetStatusFlags() & ~(WINHTTP_CALLBACK_STATUS_FLAG_INVALID_CA | WINHTTP_CALLBACK_STATUS_FLAG_CERT_DATE_INVALID | WINHTTP_CALLBACK_STATUS_FLAG_CERT_CN_INVALID | WINHTTP_CALLBACK_STATUS_FLAG_SECURITY_CHANNEL_ERROR | WINHTTP_CALLBACK_STATUS_FLAG_CERT_REVOKED | WINHTTP_CALLBACK_STATUS_FLAG_CERT_REV_FAILED)) && error != ERROR_WINHTTP_CLIENT_AUTH_CERT_NEEDED ) { SetState(HttpRequestStateError); } }
PERF_LEAVE(ReceiveResponse_Fsm); }
quit2:
DEBUG_LEAVE(error);
return error; }
|