/*++ Copyright (c) 1996 Microsoft Corporation Module Name: prefetch.cxx Abstract: Functions and methods to support http random read access. Contents: AttemptReadFromFile SetStreamPointer SetLengthFromCache ReadLoop_Fsm WriteResponseBufferToCache WriteQueryBufferToCache Author: Rajeev Dujari (rajeevd) March 1996 Revision History: Ahsan Kabir (akabir) November 1997 --*/ #include #define READLOOP_BUFSIZE 2048 BOOL HTTP_REQUEST_HANDLE_OBJECT::AttemptReadFromFile( LPVOID lpBuf, DWORD cbToRead, DWORD* pcbRead ) { DEBUG_ENTER((DBG_CACHE, Bool, "INTERNET_CONNECT_HANDLE_OBJECT::AttemptReadFromFile", "%#x, %d, %#x", lpBuf, cbToRead, pcbRead )); BOOL fSuccess; DWORD dwBytesToCopy = 0; if (!cbToRead) { *pcbRead = 0; DEBUG_LEAVE(TRUE); return TRUE; } if (IsCacheReadInProgress()) { INET_ASSERT(_VirtualCacheFileSize == _RealCacheFileSize); // Entire read should be satisfied from cache. *pcbRead = cbToRead; if (ReadUrlCacheEntryStream (_hCacheStream, _dwCurrentStreamPosition, lpBuf, pcbRead, 0)) { AdvanceReadPosition (*pcbRead); DEBUG_LEAVE(TRUE); return TRUE; } else { *pcbRead = 0; DEBUG_LEAVE(FALSE); return FALSE; } } else if (IsCacheWriteInProgress()) { // See if the read is completely within the file. if (!IsEndOfFile() && _dwCurrentStreamPosition + cbToRead > _VirtualCacheFileSize) { DEBUG_PRINT(HTTP, ERROR, ("AttemptRead Failed streampos=%d cbToRead=%d, _VitrualCacheFileSize=%d\n", _dwCurrentStreamPosition, cbToRead, _VirtualCacheFileSize)); DEBUG_LEAVE(FALSE); return FALSE; } HANDLE hfRead; hfRead = GetDownloadFileReadHandle(); if (hfRead == INVALID_HANDLE_VALUE) { DEBUG_LEAVE(FALSE); return FALSE; } // Read the data from the file. SetFilePointer (hfRead, _dwCurrentStreamPosition, NULL, FILE_BEGIN); fSuccess = ReadFile (hfRead, lpBuf, cbToRead, pcbRead, NULL); if (!fSuccess) { DEBUG_LEAVE(FALSE); return FALSE; } AdvanceReadPosition (*pcbRead); DEBUG_LEAVE(TRUE); return TRUE; } else { DEBUG_LEAVE(FALSE); return FALSE; } } // Called from InternetSetFilePointer DWORD HTTP_REQUEST_HANDLE_OBJECT::SetStreamPointer( LONG lDistanceToMove, DWORD dwMoveMethod ) { DWORD dwErr = ERROR_SUCCESS; // Fail if data is not from cache or going to cache. if (!IsCacheReadInProgress() && !IsCacheWriteInProgress()) { dwErr = ERROR_INTERNET_INVALID_OPERATION; goto done; } // BUGBUG: we don't handle chunked transfer, new with http 1.1 if (IsChunkEncoding()) { dwErr = ERROR_INTERNET_INVALID_OPERATION; goto done; } switch (dwMoveMethod) { case FILE_BEGIN: _dwCurrentStreamPosition = (DWORD) lDistanceToMove; break; case FILE_CURRENT: if (lDistanceToMove < 0 && ((DWORD) -lDistanceToMove) > _dwCurrentStreamPosition) { dwErr = ERROR_NEGATIVE_SEEK; } else { _dwCurrentStreamPosition += lDistanceToMove; } break; case FILE_END: if (!IsContentLength()) dwErr = ERROR_INTERNET_INVALID_OPERATION; else if (lDistanceToMove < 0 && ((DWORD) -lDistanceToMove) > _ContentLength) dwErr = ERROR_NEGATIVE_SEEK; else _dwCurrentStreamPosition = _ContentLength + lDistanceToMove; break; default: dwErr = ERROR_INVALID_PARAMETER; break; } done: if (dwErr == ERROR_SUCCESS) { if (IsKeepAlive() && IsContentLength()) _BytesRemaining = _ContentLength - _dwCurrentStreamPosition; if (_VirtualCacheFileSize > _dwCurrentStreamPosition) SetAvailableDataLength (_VirtualCacheFileSize - _dwCurrentStreamPosition); else SetAvailableDataLength (0); return _dwCurrentStreamPosition; } else { SetLastError (dwErr); return (DWORD) -1L; } } DWORD CFsm_ReadLoop::RunSM( IN CFsm * Fsm ) { DEBUG_ENTER((DBG_HTTP, Dword, "CFsm_ReadLoop::RunSM", "%#x", Fsm )); DWORD error; HTTP_REQUEST_HANDLE_OBJECT * pRequest; CFsm_ReadLoop * stateMachine = (CFsm_ReadLoop *)Fsm; pRequest = (HTTP_REQUEST_HANDLE_OBJECT *)Fsm->GetContext(); switch (Fsm->GetState()) { case FSM_STATE_INIT: case FSM_STATE_CONTINUE: error = pRequest->ReadLoop_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::ReadLoop_Fsm( IN CFsm_ReadLoop * Fsm ) { DEBUG_ENTER((DBG_HTTP, Dword, "HTTP_REQUEST_HANDLE_OBJECT::ReadLoop_Fsm", "%#x", Fsm )); CFsm_ReadLoop & fsm = *Fsm; DWORD dwErr = fsm.GetError(); if (fsm.GetState() == FSM_STATE_CONTINUE) { goto receive_continue; } // Set the goal for reading from the socket. fsm.m_dwReadEnd = _dwCurrentStreamPosition + ((fsm.m_dwSocketFlags & SF_NO_WAIT)? 1 : fsm.m_cbReadIn); // If app is trying to read beyond eof, limit it. if (fsm.m_dwReadEnd > _ContentLength) { fsm.m_dwReadEnd = _ContentLength; fsm.m_cbReadIn = fsm.m_dwReadEnd - _dwCurrentStreamPosition; } // Flush any data in response buffer to download file. dwErr = WriteResponseBufferToCache(); if (dwErr != ERROR_SUCCESS) goto done; // Flush any data in query buffer to download file. dwErr = WriteQueryBufferToCache(); if (dwErr != ERROR_SUCCESS) goto done; // BUGBUG: what if HaveReadFileExData? // Allocate receive buffer. We could optimize this to use // the client buffer or query buffer when available to avoid // reading from the file data that was just written. fsm.m_cbBuf = READLOOP_BUFSIZE; if (!(fsm.m_pBuf = (PVOID) new BYTE [fsm.m_cbBuf])) { dwErr = ERROR_NOT_ENOUGH_MEMORY; goto done; } HANDLE hfRead; hfRead = GetDownloadFileReadHandle(); if (hfRead == INVALID_HANDLE_VALUE) { dwErr = GetLastError(); DEBUG_PRINT(CACHE, ERROR, ("Cache: createfile failed error=%ld\n", dwErr)); goto done; } while (1) { // Calculate amount of data available for next read. if (_VirtualCacheFileSize > _dwCurrentStreamPosition) SetAvailableDataLength (_VirtualCacheFileSize - _dwCurrentStreamPosition); else SetAvailableDataLength (0); // Check if pending request can be satisfied from file. if (_EndOfFile || _VirtualCacheFileSize >= fsm.m_dwReadEnd) { INET_ASSERT (AvailableDataLength()); if (fsm.m_pRead) { // The client wants us to fill the buffer. if (fsm.m_dwSocketFlags & SF_NO_WAIT) { // If the app is greedy, give it all we've got. if (fsm.m_cbReadIn > AvailableDataLength()) fsm.m_cbReadIn = AvailableDataLength(); } else { INET_ASSERT (fsm.m_cbReadIn <= AvailableDataLength()); } } // Read the data from the file. LONG lRet; lRet = SetFilePointer (hfRead, _dwCurrentStreamPosition, NULL, FILE_BEGIN); INET_ASSERT ((DWORD) lRet == _dwCurrentStreamPosition); if (lRet == -1) { dwErr = GetLastError(); goto done; } BOOL fRet; fRet = ReadFile (hfRead, fsm.m_pRead, fsm.m_cbReadIn, fsm.m_pcbReadOut, NULL); if (!fRet) { dwErr = GetLastError(); goto done; } AdvanceReadPosition (*fsm.m_pcbReadOut); INET_ASSERT (dwErr == ERROR_SUCCESS); goto done; } INET_ASSERT (!_EndOfFile); INET_ASSERT (_Socket); fsm.m_cbRead = fsm.m_cbBuf; if (IsKeepAlive() && fsm.m_cbRead > _BytesInSocket) fsm.m_cbRead = _BytesInSocket; fsm.m_cbRecv = 0; // Get some more data from the socket. dwErr = _Socket->Receive ( &fsm.m_pBuf, // IN OUT LPVOID* lplpBuffer, &fsm.m_cbBuf, // IN OUT LPDWORD lpdwBufferLength, &fsm.m_cbRead, // IN OUT LPDWORD lpdwBufferRemaining, &fsm.m_cbRecv, // IN OUT LPDWORD lpdwBytesReceived, 0, // IN DWORD dwExtraSpace, fsm.m_dwSocketFlags, // IN DWORD dwFlags, &_EndOfFile // OUT LPBOOL lpbEof ); if (dwErr == ERROR_IO_PENDING) { if (fsm.m_dwSocketFlags & SF_NO_WAIT) { // InternetReadFileEx can set IRF_NO_WAIT. If we must go async // in this case, morph the request into a QueryDataAvailable // and app will call again to get the data after notification. fsm.m_pRead = NULL; fsm.m_cbReadIn = 1; fsm.m_pcbReadOut = NULL; } goto done; } receive_continue: if (dwErr != ERROR_SUCCESS) { DEBUG_PRINT(HTTP, ERROR, ("error %d on socket %#x\n", dwErr, _Socket->GetSocket())); goto done; } // Append data to download file. dwErr = WriteCache ((PBYTE) fsm.m_pBuf, fsm.m_cbRecv); if (dwErr != ERROR_SUCCESS) goto done; // If content length is known, check for EOF. if (IsKeepAlive()) { _BytesInSocket -= fsm.m_cbRecv; if (!_BytesInSocket) _EndOfFile = TRUE; } if (_EndOfFile) { // Set handle state. SetState(HttpRequestStateReopen); if (!IsKeepAlive()) CloseConnection(FALSE); SetData(FALSE); } } // end while (1) done: if (dwErr != ERROR_IO_PENDING) { delete [] fsm.m_pBuf; fsm.SetDone(); if (dwErr != ERROR_SUCCESS) { DEBUG_PRINT(HTTP, ERROR, ("Readloop: Error = %d\n", dwErr)); SetState(HttpRequestStateError); LocalEndCacheWrite(FALSE); } } DEBUG_LEAVE(dwErr); return dwErr; } // Called from ReadLoop to write data in response buffer to download file. DWORD HTTP_REQUEST_HANDLE_OBJECT::WriteResponseBufferToCache (VOID) { DWORD cbData = _BytesReceived - _DataOffset; if (!cbData) return ERROR_SUCCESS; DWORD dwErr = WriteCache (((LPBYTE) _ResponseBuffer) + _DataOffset, cbData); _DataOffset += cbData; DEBUG_PRINT(HTTP, ERROR, ("WriteResponseBufferToCache: Error = %d", dwErr)); return dwErr; } // Called from ReadLoop to write data in query buffer to download file. DWORD HTTP_REQUEST_HANDLE_OBJECT::WriteQueryBufferToCache (VOID) { if (!_QueryBytesAvailable) return ERROR_SUCCESS; DWORD dwErr = WriteCache (((LPBYTE) _QueryBuffer) + _QueryOffset, _QueryBytesAvailable); _QueryOffset += _QueryBytesAvailable; _QueryBytesAvailable = 0; DEBUG_PRINT(HTTP, ERROR, ("WriteQueryBufferToCache: Error = %d", dwErr)); return dwErr; }