/*++ 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 #include #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; }