Leaked source code of windows server 2003
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.
 
 
 
 
 
 

609 lines
20 KiB

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