|
|
/*++
Copyright (c) 2001 Microsoft Corporation
Module Name:
chunkflt.cxx
Abstract:
Contains a filter for encoding and decoding chunked transfers.
Contents: FILTER_LIST::Insert FILTER_LIST::RemoveAll FILTER_LIST::Decode ChunkFilter::Reset ChunkFilter::Decode ChunkFilter::Encode ChunkFilter::RegisterContext ChunkFilter::UnregisterContext
Revision History:
Created 13-Feb-2001
--*/
#include <wininetp.h>
// Global lookup table to map 0x0 - 0x7f bytes for obtaining mapping to its
// token value. All values above 0x7f are considered to be data.
const BYTE g_bChunkTokenTable[] = { /* 0x00 */ CHUNK_TOKEN_DATA, CHUNK_TOKEN_DATA, CHUNK_TOKEN_DATA, CHUNK_TOKEN_DATA, CHUNK_TOKEN_DATA, CHUNK_TOKEN_DATA, CHUNK_TOKEN_DATA, CHUNK_TOKEN_DATA, CHUNK_TOKEN_DATA, CHUNK_TOKEN_DATA, CHUNK_TOKEN_LF, CHUNK_TOKEN_DATA, CHUNK_TOKEN_DATA, CHUNK_TOKEN_CR, CHUNK_TOKEN_DATA, CHUNK_TOKEN_DATA,
/* 0x10 */ CHUNK_TOKEN_DATA, CHUNK_TOKEN_DATA, CHUNK_TOKEN_DATA, CHUNK_TOKEN_DATA, CHUNK_TOKEN_DATA, CHUNK_TOKEN_DATA, CHUNK_TOKEN_DATA, CHUNK_TOKEN_DATA, CHUNK_TOKEN_DATA, CHUNK_TOKEN_DATA, CHUNK_TOKEN_DATA, CHUNK_TOKEN_DATA, CHUNK_TOKEN_DATA, CHUNK_TOKEN_DATA, CHUNK_TOKEN_DATA, CHUNK_TOKEN_DATA,
/* 0x20 */ CHUNK_TOKEN_DATA, CHUNK_TOKEN_DATA, CHUNK_TOKEN_DATA, CHUNK_TOKEN_DATA, CHUNK_TOKEN_DATA, CHUNK_TOKEN_DATA, CHUNK_TOKEN_DATA, CHUNK_TOKEN_DATA, CHUNK_TOKEN_DATA, CHUNK_TOKEN_DATA, CHUNK_TOKEN_DATA, CHUNK_TOKEN_DATA, CHUNK_TOKEN_DATA, CHUNK_TOKEN_DATA, CHUNK_TOKEN_DATA, CHUNK_TOKEN_DATA,
/* 0x30 */ CHUNK_TOKEN_DIGIT, CHUNK_TOKEN_DIGIT, CHUNK_TOKEN_DIGIT, CHUNK_TOKEN_DIGIT, CHUNK_TOKEN_DIGIT, CHUNK_TOKEN_DIGIT, CHUNK_TOKEN_DIGIT, CHUNK_TOKEN_DIGIT, CHUNK_TOKEN_DIGIT, CHUNK_TOKEN_DIGIT, CHUNK_TOKEN_COLON, CHUNK_TOKEN_DATA, CHUNK_TOKEN_DATA, CHUNK_TOKEN_DATA, CHUNK_TOKEN_DATA, CHUNK_TOKEN_DATA,
/* 0x40 */ CHUNK_TOKEN_DATA, CHUNK_TOKEN_DIGIT, CHUNK_TOKEN_DIGIT, CHUNK_TOKEN_DIGIT, CHUNK_TOKEN_DIGIT, CHUNK_TOKEN_DIGIT, CHUNK_TOKEN_DIGIT, CHUNK_TOKEN_DATA, CHUNK_TOKEN_DATA, CHUNK_TOKEN_DATA, CHUNK_TOKEN_DATA, CHUNK_TOKEN_DATA, CHUNK_TOKEN_DATA, CHUNK_TOKEN_DATA, CHUNK_TOKEN_DATA, CHUNK_TOKEN_DATA,
/* 0x50 */ CHUNK_TOKEN_DATA, CHUNK_TOKEN_DATA, CHUNK_TOKEN_DATA, CHUNK_TOKEN_DATA, CHUNK_TOKEN_DATA, CHUNK_TOKEN_DATA, CHUNK_TOKEN_DATA, CHUNK_TOKEN_DATA, CHUNK_TOKEN_DATA, CHUNK_TOKEN_DATA, CHUNK_TOKEN_DATA, CHUNK_TOKEN_DATA, CHUNK_TOKEN_DATA, CHUNK_TOKEN_DATA, CHUNK_TOKEN_DATA, CHUNK_TOKEN_DATA,
/* 0x60 */ CHUNK_TOKEN_DATA, CHUNK_TOKEN_DIGIT, CHUNK_TOKEN_DIGIT, CHUNK_TOKEN_DIGIT, CHUNK_TOKEN_DIGIT, CHUNK_TOKEN_DIGIT, CHUNK_TOKEN_DIGIT, CHUNK_TOKEN_DATA, CHUNK_TOKEN_DATA, CHUNK_TOKEN_DATA, CHUNK_TOKEN_DATA, CHUNK_TOKEN_DATA, CHUNK_TOKEN_DATA, CHUNK_TOKEN_DATA, CHUNK_TOKEN_DATA, CHUNK_TOKEN_DATA,
/* 0x70 */ CHUNK_TOKEN_DATA, CHUNK_TOKEN_DATA, CHUNK_TOKEN_DATA, CHUNK_TOKEN_DATA, CHUNK_TOKEN_DATA, CHUNK_TOKEN_DATA, CHUNK_TOKEN_DATA, CHUNK_TOKEN_DATA, CHUNK_TOKEN_DATA, CHUNK_TOKEN_DATA, CHUNK_TOKEN_DATA, CHUNK_TOKEN_DATA, CHUNK_TOKEN_DATA, CHUNK_TOKEN_DATA, CHUNK_TOKEN_DATA, CHUNK_TOKEN_DATA };
// Look-up table to map a token in a given state to the next state
const CHUNK_DECODE_STATE g_eMapChunkTokenToNextState[CHUNK_DECODE_STATE_LAST+1][CHUNK_TOKEN_LAST+1] = { /*
|---------DIGIT----------|-------------CR-------------|-------------LF------------|----------COLON----------|-----------DATA----------| */ // CHUNK_DECODE_STATE_START
CHUNK_DECODE_STATE_SIZE, CHUNK_DECODE_STATE_SIZE, CHUNK_DECODE_STATE_SIZE, CHUNK_DECODE_STATE_SIZE, CHUNK_DECODE_STATE_SIZE,
// CHUNK_DECODE_STATE_SIZE
CHUNK_DECODE_STATE_SIZE, CHUNK_DECODE_STATE_SIZE_CRLF,CHUNK_DECODE_STATE_ERROR, CHUNK_DECODE_STATE_EXT, CHUNK_DECODE_STATE_EXT,
// CHUNK_DECODE_STATE_SIZE_CRLF
CHUNK_DECODE_STATE_ERROR, CHUNK_DECODE_STATE_SIZE_CRLF,CHUNK_DECODE_STATE_DATA, CHUNK_DECODE_STATE_ERROR, CHUNK_DECODE_STATE_ERROR,
// CHUNK_DECODE_STATE_EXT
CHUNK_DECODE_STATE_EXT, CHUNK_DECODE_STATE_SIZE_CRLF,CHUNK_DECODE_STATE_ERROR, CHUNK_DECODE_STATE_EXT, CHUNK_DECODE_STATE_EXT,
// CHUNK_DECODE_STATE_DATA
CHUNK_DECODE_STATE_SIZE, CHUNK_DECODE_STATE_DATA_CRLF,CHUNK_DECODE_STATE_ERROR, CHUNK_DECODE_STATE_DATA, CHUNK_DECODE_STATE_ERROR,
// CHUNK_DECODE_STATE_DATA_CRLF
CHUNK_DECODE_STATE_ERROR, CHUNK_DECODE_STATE_DATA_CRLF,CHUNK_DECODE_STATE_SIZE, CHUNK_DECODE_STATE_ERROR, CHUNK_DECODE_STATE_ERROR,
// CHUNK_DECODE_STATE_FOOTER_NAME
CHUNK_DECODE_STATE_FOOTER_NAME, CHUNK_DECODE_STATE_FINAL_CRLF, CHUNK_DECODE_STATE_ERROR, CHUNK_DECODE_STATE_FOOTER_VALUE, CHUNK_DECODE_STATE_FOOTER_NAME, // CHUNK_DECODE_STATE_FOOTER_VALUE
CHUNK_DECODE_STATE_FOOTER_VALUE, CHUNK_DECODE_STATE_FINAL_CRLF, CHUNK_DECODE_STATE_ERROR, CHUNK_DECODE_STATE_ERROR, CHUNK_DECODE_STATE_FOOTER_VALUE, // CHUNK_DECODE_STATE_FINAL_CRLF
CHUNK_DECODE_STATE_ERROR, CHUNK_DECODE_STATE_FINAL_CRLF, CHUNK_DECODE_STATE_FINISHED, CHUNK_DECODE_STATE_ERROR, CHUNK_DECODE_STATE_ERROR,
// CHUNK_DECODE_STATE_ERROR
CHUNK_DECODE_STATE_ERROR, CHUNK_DECODE_STATE_ERROR, CHUNK_DECODE_STATE_ERROR, CHUNK_DECODE_STATE_ERROR, CHUNK_DECODE_STATE_ERROR,
// CHUNK_DECODE_STATE_FINISHED -- force client to reset before reuse
CHUNK_DECODE_STATE_ERROR, CHUNK_DECODE_STATE_ERROR, CHUNK_DECODE_STATE_ERROR, CHUNK_DECODE_STATE_ERROR, CHUNK_DECODE_STATE_ERROR };
// Helper macros
// Where to next?
#define MAP_CHUNK_TOKEN_TO_NEXT_STATE(eCurState, chToken) \
(g_eMapChunkTokenToNextState[(eCurState)][(chToken)]) // Given a byte, what does it represent w/regards to chunked responses
#define GET_CHUNK_TOKEN(ch) ((ch) & 0x80 ? CHUNK_TOKEN_DATA : g_bChunkTokenTable[ch])
// Should only be used with digit tokens.
// Expects byte in range 0x30-0x39 (digits), 0x41-0x46 (uppercase hex),
// or 0x61-0x66 (lowercase hex)
#define GET_VALUE_FROM_ASCII_HEX(ch) ((ch) - ((ch) & 0xf0) + (((ch) & 0x40) ? 9 : 0))
HRESULT ChunkFilter::Reset(DWORD_PTR dwContext) { DEBUG_ENTER((DBG_HTTP, Dword, "ChunkFilter::Reset", "%#x", dwContext )); if (dwContext) (reinterpret_cast<ChunkDecodeContext *>(dwContext))->Reset();
DEBUG_LEAVE(TRUE);
return S_OK; }
HRESULT ChunkFilter::Decode( DWORD_PTR dwContext, IN OUT LPBYTE pInBuffer, IN DWORD dwInBufSize, IN OUT LPBYTE *ppOutBuffer, IN OUT LPDWORD pdwOutBufSize, OUT LPDWORD pdwBytesRead, OUT LPDWORD pdwBytesWritten ) /*++
Routine Description:
Decode downloaded chunked data based on the inputted context and its current state
Arguments:
dwContext - registered encode/decode context for this filter
pInBuffer - input data buffer to be processed
dwInBufSize - byte count of pInBuffer
ppOutBuffer - allocated buffer containing encoded/decoded data if not done in place with pInBuffer.
pdwOutBufSize - size of allocated output buffer, or 0 if pInBuffer holds the processed data
pdwBytesRead - Number of input buffer bytes used
pdwBytesWritten - Number of output buffer bytes written Return Value:
HRESULT Success - S_OK
Failure - E_FAIL
--*/ { HRESULT hResult = S_OK; LPBYTE pCurrentLoc = pInBuffer; LPBYTE pStartOfChunk = pInBuffer; ChunkDecodeContext * pCtx = reinterpret_cast<ChunkDecodeContext *>(dwContext); CHUNK_DECODE_STATE ePreviousState; BYTE chToken;
DEBUG_ENTER((DBG_HTTP, Dword, "ChunkFilter::Decode", "%x, [%x, %.10q], %u, %x, %u, %x, %x", dwContext, pInBuffer, *pInBuffer, dwInBufSize, ppOutBuffer, pdwOutBufSize, pdwBytesRead, pdwBytesWritten ));
if (!dwContext) { hResult = E_INVALIDARG; goto quit; } else if (!pdwBytesRead || !pdwBytesWritten || !pInBuffer || (ppOutBuffer && !pdwOutBufSize)) { hResult = E_POINTER; goto quit; }
*pdwBytesRead = 0; *pdwBytesWritten = 0; while (*pdwBytesRead < dwInBufSize && pCtx->GetState() != CHUNK_DECODE_STATE_ERROR) { chToken = GET_CHUNK_TOKEN(*pCurrentLoc); ePreviousState = pCtx->GetState();
DEBUG_PRINT(HTTP, INFO, ("ChunkFilter::Decode: %q, %q, %u/%u\n", InternetMapChunkState(ePreviousState), InternetMapChunkToken((CHUNK_TOKEN_VALUE)chToken), *pdwBytesRead, dwInBufSize )); INET_ASSERT(pCurrentLoc < pInBuffer + dwInBufSize);
switch (pCtx->GetState()) { case CHUNK_DECODE_STATE_START: pCtx->Reset(); pCtx->SetState(CHUNK_DECODE_STATE_SIZE);
// fall through
case CHUNK_DECODE_STATE_SIZE: { if (chToken == CHUNK_TOKEN_DIGIT) { if (pCtx->m_dwParsedSize & 0xF0000000) { // Don't allow overflow if by some chance
// the server is trying to send a chunk over
// 4 gigs worth in size.
pCtx->SetState(CHUNK_DECODE_STATE_ERROR); break; } pCtx->m_dwParsedSize <<= 4; pCtx->m_dwParsedSize += GET_VALUE_FROM_ASCII_HEX(*pCurrentLoc); } else { pCtx->SetState(MAP_CHUNK_TOKEN_TO_NEXT_STATE( CHUNK_DECODE_STATE_SIZE, chToken)); } break; } case CHUNK_DECODE_STATE_SIZE_CRLF: // Handle the zero case which can take us to the footer or final CRLF
// If it's the final CRLF, then this should be the end of the data.
if (pCtx->m_dwParsedSize == 0 && chToken == CHUNK_TOKEN_LF) { pCtx->SetState(CHUNK_DECODE_STATE_FOOTER_NAME); } else { pCtx->SetState(MAP_CHUNK_TOKEN_TO_NEXT_STATE( CHUNK_DECODE_STATE_SIZE_CRLF, chToken)); } break; case CHUNK_DECODE_STATE_DATA: { INET_ASSERT(pCtx->m_dwParsedSize); // account for EOB
if (pCurrentLoc + pCtx->m_dwParsedSize < pInBuffer + dwInBufSize) { const DWORD dwParsedSize = pCtx->m_dwParsedSize;
// Move or skip the parsed size and crlf, if needed.
// The start of the chunk could be equal this time if
// spread across multiple decode calls.
if (pStartOfChunk != pCurrentLoc) { MoveMemory(pStartOfChunk, pCurrentLoc, dwParsedSize); }
// -1 so we can look at the first byte after the data
// in the next pass.
pCurrentLoc += dwParsedSize - 1; *pdwBytesRead += dwParsedSize - 1; *pdwBytesWritten += dwParsedSize; pStartOfChunk += dwParsedSize; pCtx->m_dwParsedSize = 0;
// Should be CRLF terminated
pCtx->SetState(CHUNK_DECODE_STATE_DATA_CRLF); } else { const DWORD dwSlice = dwInBufSize - (DWORD)(pCurrentLoc - pInBuffer);
// We're reaching the end of the buffer before
// the end of the chunk. Update the parsed
// size remaining, so it will be carried over
// to the next call.
if (pStartOfChunk != pCurrentLoc) { // Skip over preceding size info.
MoveMemory(pStartOfChunk, pCurrentLoc, dwSlice); }
// -1 so we can look at the first byte after the data
// in the next pass. Offset should never be bigger than DWORD since
// since that's the biggest chunk we can handle.
*pdwBytesWritten += dwSlice; pCtx->m_dwParsedSize -= dwSlice; *pdwBytesRead += dwSlice - 1; pCurrentLoc = pInBuffer + dwInBufSize - 1; } break; } // All remaining states simply parse over the value and
// change state, depending on the token.
default: { pCtx->SetState(MAP_CHUNK_TOKEN_TO_NEXT_STATE( ePreviousState, chToken)); break; } } (*pdwBytesRead)++; pCurrentLoc++; }
if (pCtx->GetState() == CHUNK_DECODE_STATE_ERROR) { DEBUG_PRINT(HTTP, INFO, ("ChunkFilter::Decode entered error state\n" )); hResult = E_FAIL; } quit: DEBUG_LEAVE(hResult == S_OK ? TRUE : FALSE); return hResult; }
HRESULT ChunkFilter::Encode( DWORD_PTR dwContext, IN OUT LPBYTE pInBuffer, IN DWORD dwInBufSize, IN OUT LPBYTE *ppOutBuffer, IN OUT LPDWORD pdwOutBufSize, OUT LPDWORD pdwBytesRead, OUT LPDWORD pdwBytesWritten ) /*++
Routine Description:
Chunk data for uploading based on the inputted context and current state
Arguments:
dwContext - registered encode/decode context for this filter
pInBuffer - input data buffer to be processed
dwInBufSize - byte count of pInBuffer
ppOutBuffer - allocated buffer containing encoded/decoded data if not done in place with pInBuffer.
pdwOutBufSize - size of allocated output buffer, or 0 if pInBuffer holds the processed data
pdwBytesRead - Number of input buffer bytes used
pdwBytesWritten - Number of output buffer bytes written Return Value:
HRESULT E_NOTIMPL - currently no chunked upload support
--*/ { // We don't support chunked uploads...yet
return E_NOTIMPL; }
HRESULT ChunkFilter::RegisterContext(OUT DWORD_PTR *pdwContext) { DEBUG_ENTER((DBG_HTTP, Dword, "ChunkFilter::RegisterContext", "%#x", pdwContext ));
HRESULT hr = S_OK;
if (!pdwContext || IsBadWritePtr(pdwContext, sizeof(DWORD_PTR))) { hr = E_POINTER; goto quit; }
*pdwContext = (DWORD_PTR) New ChunkDecodeContext;
if (!*pdwContext) { hr = E_OUTOFMEMORY; }
quit: DEBUG_LEAVE(hr == S_OK ? TRUE : FALSE);
return hr; }
HRESULT ChunkFilter::UnregisterContext(IN DWORD_PTR dwContext) { DEBUG_ENTER((DBG_HTTP, Dword, "ChunkFilter::UnregisterContext", "%#x", dwContext ));
HRESULT hr = S_OK; if (!dwContext) { hr = E_INVALIDARG; goto quit; } delete reinterpret_cast<ChunkDecodeContext *>(dwContext);
quit: DEBUG_LEAVE(hr == S_OK ? TRUE : FALSE); return hr; }
// Always inserts as the beginning of the list
BOOL FILTER_LIST::Insert(IN BaseFilter *pFilter, IN DWORD_PTR dwContext) { LPFILTER_LIST_ENTRY pNewEntry; pNewEntry = New FILTER_LIST_ENTRY; if (pNewEntry != NULL) { pNewEntry->pFilter = pFilter; pNewEntry->dwContext = dwContext; pNewEntry->pNext = _pFilterEntry; _pFilterEntry = pNewEntry; _uFilterCount++; return TRUE; } else { return FALSE; } }
VOID FILTER_LIST::ClearList() { LPFILTER_LIST_ENTRY pEntry = _pFilterEntry; while (pEntry) { pEntry->pFilter->UnregisterContext(pEntry->dwContext); pEntry = pEntry->pNext; delete _pFilterEntry; _pFilterEntry = pEntry; } _uFilterCount = 0; }
DWORD FILTER_LIST::Decode( IN OUT LPBYTE pInBuffer, IN DWORD dwInBufSize, IN OUT LPBYTE *ppOutBuffer, IN OUT LPDWORD pdwOutBufSize, OUT LPDWORD pdwBytesRead, OUT LPDWORD pdwBytesWritten ) { LPFILTER_LIST_ENTRY pEntry = _pFilterEntry; HRESULT hr = S_OK; DWORD dwBytesRead = 0; DWORD dwBytesWritten = 0; LPBYTE pLocalInBuffer = pInBuffer; DWORD dwLocalInBufSize = dwInBufSize;
*pdwBytesRead = 0; *pdwBytesWritten = 0; // Loop through filters which should be in the proper order
while (pEntry) { dwBytesRead = 0; dwBytesWritten = 0; // At a minimum, we're guaranteed the decode method parses
// the input buffer until one of the following is met:
//
// - Input buffer is fully parsed and processed
// - Output buffer is filled up
// - Decoder reaches a finished state
// - Error occurs while processing input data
//
// Currently, only 1, 3, and 4 are possible since chunked
// transfers are decoded in place. We also don't need
// to loop since chunked decoding is always fully done
// in the first pass.
do { pLocalInBuffer = pLocalInBuffer + dwBytesRead; dwLocalInBufSize = dwLocalInBufSize - dwBytesRead; dwBytesWritten = 0; dwBytesRead = 0; hr = pEntry->pFilter->Decode(pEntry->dwContext, pLocalInBuffer, dwLocalInBufSize, ppOutBuffer, pdwOutBufSize, &dwBytesRead, &dwBytesWritten );
*pdwBytesWritten += dwBytesWritten; *pdwBytesRead += dwBytesRead; if (hr == S_OK && dwBytesRead < dwLocalInBufSize) { // Given the current requirements we shouldn't be here
// if there's still input buffer data to process.
RIP(FALSE); hr = E_FAIL; goto quit; } } while (hr == S_OK && dwLocalInBufSize > 0 && dwBytesRead < dwLocalInBufSize); pEntry = pEntry->pNext; } INET_ASSERT(hr != S_OK || dwBytesRead == dwLocalInBufSize); quit: switch (hr) { case S_OK: return ERROR_SUCCESS;
case E_OUTOFMEMORY: return ERROR_NOT_ENOUGH_MEMORY;
default: return ERROR_WINHTTP_INTERNAL_ERROR; } }
|