/*++ Copyright (c) 1997 Microsoft Corporation Module Name: chunk.cxx Abstract: Contains a generic chunked transfer implimentation Author: Arthur L Bierer (arthurbi) 03-May-1997 Revision History: 03-May-1997 arthurbi Created --*/ #include inline CHUNK_TOKEN CHUNK_TRANSFER::GetToken( IN OUT LPSTR *lplpInputBuffer, IN LPSTR lpEndOfInputBuffer, OUT LPDWORD lpdwValue, IN DWORD dwExpectedTokenSize, OUT LPDWORD lpdwBytesTokenized ) /*++ Routine Description: Lexes through a byte stream, seperating data into tokens. Data is special cased for efficency. Arguments: lplpInputBuffer - Pointer to Pointer of Buffer that should be lexed, on return contains an offset where the next character to lex is. lpEndofInputBuffer - Pointer to last character to passed in Buffer. lpdwValue - On return, MAY contain numerical conversion of a text number (digit) token dwExpectedTokenSize - Expected size of token, in all cases except for data should be 1 lpdwBytesTokenized - On return, contains size of token Return Value: CHUNK_TOKEN Success - The Correct token. Failure - CHUNK_TOKEN_INVALID --*/ { BOOL fFirstIteration = TRUE; CHUNK_TOKEN ctToken = CHUNK_TOKEN_INVALID; DEBUG_ENTER((DBG_HTTP, Dword, "CHUNK_TRANSFER::GetToken", "%x [%x, %.10q], %x, %x, %u, %x", lplpInputBuffer, *lplpInputBuffer, *lplpInputBuffer, lpEndOfInputBuffer, lpdwValue, dwExpectedTokenSize, lpdwBytesTokenized )); *lpdwBytesTokenized = 0; while ( *lplpInputBuffer < lpEndOfInputBuffer && *lpdwBytesTokenized < dwExpectedTokenSize ) { // // Set Default Token type // ctToken = CHUNK_TOKEN_DATA; // // Handle Other, "special" tokens, only if asked for by the parser. // if ( dwExpectedTokenSize == 1 ) { if ( **lplpInputBuffer == '\r' ) { ctToken = CHUNK_TOKEN_CR; goto quit; } if ( **lplpInputBuffer == '\n' ) { ctToken = CHUNK_TOKEN_LF; goto quit; } if ( **lplpInputBuffer == ':' ) { ctToken = CHUNK_TOKEN_COLON; goto quit; } if ( **lplpInputBuffer >= '0' && **lplpInputBuffer <= '9' ) { *lpdwValue = (DWORD) (**lplpInputBuffer - '0'); ctToken = CHUNK_TOKEN_DIGIT; goto quit; } if ( **lplpInputBuffer >= 'A' && **lplpInputBuffer <= 'F' ) { *lpdwValue = (DWORD) (**lplpInputBuffer - 'A') + 10; ctToken = CHUNK_TOKEN_DIGIT; goto quit; } if ( **lplpInputBuffer >= 'a' && **lplpInputBuffer <= 'f' ) { *lpdwValue = (DWORD) (**lplpInputBuffer - 'a') + 10; ctToken = CHUNK_TOKEN_DIGIT; goto quit; } } fFirstIteration = FALSE; (*lplpInputBuffer)++; (*lpdwBytesTokenized)++; } quit: if (ctToken != CHUNK_TOKEN_DATA && ctToken != CHUNK_TOKEN_INVALID) { if ( !fFirstIteration) { ctToken = CHUNK_TOKEN_DATA; } else { // // Advance past this token, since we've only // lexed one token // (*lplpInputBuffer)++; (*lpdwBytesTokenized)++; } } DEBUG_PRINT(HTTP, INFO, ("GetToken: %q, expected=%u, actual=%u\n", InternetMapChunkToken(ctToken), dwExpectedTokenSize, *lpdwBytesTokenized )); DEBUG_LEAVE((DWORD)ctToken); return ctToken; } DWORD CHUNK_TRANSFER::ParseChunkInput( LPSTR lpInputBuffer, DWORD dwInputBufferSize, LPSTR *lplpInputBufferNew, LPDWORD lpdwInputBufferNewSize ) /*++ Routine Description: Parses a buffer of an assumed chunked encoding byte stream. Seperates out data from header information. Arguments: lpInputBuffer - Pointer to buffer containing a stream of bytes to parse dwInputBufferSize - size of byte lpInputBuffer lplpInputBufferNew - Offset into passed in lpInputBuffer, (not used, yet) lpdwInputBufferNewSize - On Return, cotains the size of lpInputBuffer ( data is compressed ) Return Value: DWORD Success - ERROR_SUCCESS Failure - --*/ { CHUNK_TOKEN ctToken; DWORD dwValue; DWORD dwExpectedTokenSize = 1; DWORD dwActualTokenSize = 0; LPSTR lpszEndOfInputBuffer = (LPSTR) (lpInputBuffer + dwInputBufferSize); LPSTR lpszStartOfFirstDataBuffer = NULL; DWORD dwFirstDataBufferSize = 0; LPSTR lpszStartOfNextDataBuffer = NULL; DWORD error = ERROR_SUCCESS; DEBUG_ENTER((DBG_HTTP, Dword, "CHUNK_TRANSFER::ParseChunkInput", "%x [%.10q], %u, %x, %x", lpInputBuffer, lpInputBuffer, dwInputBufferSize, lplpInputBufferNew, lpdwInputBufferNewSize )); *lplpInputBufferNew = lpInputBuffer; while ( *lplpInputBufferNew < lpszEndOfInputBuffer) { // // Calculate the max size of the token can be, only // relevant for data, since all other tokens are assumed // to be size of 1. // if (_csState == CHUNK_STATE_DATA_PARSE) { dwExpectedTokenSize = (_dwChunkDataSize - _dwChunkDataRead); lpszStartOfNextDataBuffer = *lplpInputBufferNew; } else { dwExpectedTokenSize = 1; } DEBUG_PRINT(HTTP, INFO, ("ParseChunk: %q, %u/%u\n", InternetMapChunkState(_csState), _dwChunkDataRead, _dwChunkDataSize )); // // Lex through the byte stream looking for our next token. // ctToken = GetToken( lplpInputBufferNew, lpszEndOfInputBuffer, &dwValue, dwExpectedTokenSize, &dwActualTokenSize ); if ( ctToken == CHUNK_TOKEN_INVALID ) { // // Need more data to parse... // error = ERROR_SUCCESS; goto quit; } // // Based on our current state, evalulate the token, // and figure out what to do next. // switch ( _csState ) { case CHUNK_STATE_START: ResetSubStateInfo(); if ( ctToken != CHUNK_TOKEN_DIGIT ) { DEBUG_PRINT(HTTP, INFO, ("-->CHUNK err: Got %q, while looking for NOT %q\n", InternetMapChunkToken(ctToken), InternetMapChunkToken(CHUNK_TOKEN_DIGIT) )); error = ERROR_INTERNET_INTERNAL_ERROR; goto quit; } SetState(CHUNK_STATE_SIZE_PARSE); // otherwise fall through case CHUNK_STATE_SIZE_PARSE: switch ( ctToken ) { case CHUNK_TOKEN_DIGIT: _dwCalculatedChunkSize *= BASE_HEX; _dwCalculatedChunkSize += dwValue; break; case CHUNK_TOKEN_CR: _dwCr++; // fall through case CHUNK_TOKEN_DATA: case CHUNK_TOKEN_COLON: _dwChunkDataSize = _dwCalculatedChunkSize; _dwChunkDataRead = 0; DEBUG_PRINT(HTTP, INFO, ("ChunkParse-GotChunksize: size=%u, %x\n", _dwCalculatedChunkSize, _dwCalculatedChunkSize )); if (ctToken == CHUNK_TOKEN_CR) { SetState(CHUNK_STATE_SIZE_CRLF); } else { SetState(CHUNK_STATE_EXT_PARSE); } break; default: // ERROR error = ERROR_INTERNET_INTERNAL_ERROR; goto quit; } break; case CHUNK_STATE_EXT_PARSE: switch ( ctToken ) { case CHUNK_TOKEN_CR: _dwCr++; SetState(CHUNK_STATE_SIZE_CRLF); break; case CHUNK_TOKEN_DIGIT: case CHUNK_TOKEN_DATA: case CHUNK_TOKEN_COLON: break; default: // ERROR error = ERROR_INTERNET_INTERNAL_ERROR; goto quit; } break; case CHUNK_STATE_SIZE_CRLF: switch ( ctToken ) { case CHUNK_TOKEN_LF: _dwLf++; if ( IsCrLf() ) { ClearCrLf(); if ( _dwCalculatedChunkSize == 0 ) { SetState(CHUNK_STATE_ZERO_FOOTER); } else { SetState(CHUNK_STATE_DATA_PARSE); } } else { DEBUG_PRINT(HTTP, INFO, ("-->CHUNK err: Got %q, But CRLF not matched, CR=%u, LF=%u\n", InternetMapChunkToken(ctToken), _dwCr, _dwLf )); error = ERROR_INTERNET_INTERNAL_ERROR; goto quit; } break; default: DEBUG_PRINT(HTTP, INFO, ("-->CHUNK err: Got %q, while looking for %q\n", InternetMapChunkToken(ctToken), InternetMapChunkToken(CHUNK_TOKEN_LF) )); error = ERROR_INTERNET_INTERNAL_ERROR; goto quit; } break; case CHUNK_STATE_DATA_PARSE: switch ( ctToken ) { case CHUNK_TOKEN_CR: case CHUNK_TOKEN_LF: case CHUNK_TOKEN_DATA: case CHUNK_TOKEN_DIGIT: case CHUNK_TOKEN_COLON: // // If this is the first piece of data we receive // than save it off, so we know the start of the // buffer we are returning as data. // if ( lpszStartOfFirstDataBuffer == NULL ) { lpszStartOfFirstDataBuffer = lpInputBuffer; } // // If this is not the first block of data in the passed in buffer, // we must move the block of data OVER any Chunked-tranfer header // information. // if ( (lpszStartOfFirstDataBuffer+dwFirstDataBufferSize) != lpszStartOfNextDataBuffer ) { MoveMemory((LPVOID) (lpszStartOfFirstDataBuffer+dwFirstDataBufferSize), // Dest (LPVOID) lpszStartOfNextDataBuffer, // Source dwActualTokenSize // size ); } // // Update the size of data we've parsed out, and // check to see if we've completely received all data. // dwFirstDataBufferSize += dwActualTokenSize; _dwChunkDataRead += dwActualTokenSize; if ( _dwChunkDataRead == _dwChunkDataSize ) { SetState(CHUNK_STATE_DATA_CRLF); } INET_ASSERT(_dwChunkDataRead <= _dwChunkDataSize); break; default: DEBUG_PRINT(HTTP, INFO, ("-->CHUNK err: Got %q, while looking for CR,LF,DATA,DIGIT,COLON\n", InternetMapChunkToken(ctToken) )); error = ERROR_INTERNET_INTERNAL_ERROR; goto quit; } break; case CHUNK_STATE_DATA_CRLF: switch (ctToken) { case CHUNK_TOKEN_CR: _dwCr++; break; case CHUNK_TOKEN_LF: _dwLf++; if ( IsCrLf() ) { ClearCrLf(); SetState(CHUNK_STATE_START); } else { DEBUG_PRINT(HTTP, INFO, ("-->CHUNK err: Got %q, BUT CRLF not matched, CR=%u, LF=%u\n", InternetMapChunkToken(ctToken), _dwCr, _dwLf )); error = ERROR_INTERNET_INTERNAL_ERROR; goto quit; } break; default: DEBUG_PRINT(HTTP, INFO, ("-->CHUNK err: Got %q, while looking for CR or LF (CHUNK DATA SIZE INCORRECT??)\n", InternetMapChunkToken(ctToken) )); error = ERROR_INTERNET_INTERNAL_ERROR; goto quit; } break; case CHUNK_STATE_ZERO_FOOTER: switch (ctToken) { case CHUNK_TOKEN_CR: _dwCr++; SetState(CHUNK_STATE_ZERO_FOOTER_FINAL_CRLF); break; case CHUNK_TOKEN_DATA: case CHUNK_TOKEN_DIGIT: case CHUNK_TOKEN_COLON: SetState(CHUNK_STATE_ZERO_FOOTER_NAME); break; default: DEBUG_PRINT(HTTP, INFO, ("-->CHUNK err: Got %q, while looking for DATA or CR\n", InternetMapChunkToken(ctToken) )); error = ERROR_INTERNET_INTERNAL_ERROR; goto quit; } break; case CHUNK_STATE_ZERO_FOOTER_NAME: switch (ctToken) { case CHUNK_TOKEN_DATA: case CHUNK_TOKEN_DIGIT: break; case CHUNK_TOKEN_COLON: SetState(CHUNK_STATE_ZERO_FOOTER_VALUE); break; default: DEBUG_PRINT(HTTP, INFO, ("-->CHUNK err: Got %q, while looking for DATA, DIGIT, COLON\n", InternetMapChunkToken(ctToken) )); error = ERROR_INTERNET_INTERNAL_ERROR; goto quit; } break; case CHUNK_STATE_ZERO_FOOTER_VALUE: switch (ctToken) { case CHUNK_TOKEN_DATA: case CHUNK_TOKEN_DIGIT: break; case CHUNK_TOKEN_CR: _dwCr++; SetState(CHUNK_STATE_ZERO_FOOTER_CRLF); break; default: DEBUG_PRINT(HTTP, INFO, ("-->CHUNK err: Got %q, while looking for DATA, DIGIT, CR\n", InternetMapChunkToken(ctToken) )); error = ERROR_INTERNET_INTERNAL_ERROR; goto quit; } break; case CHUNK_STATE_ZERO_FOOTER_CRLF: case CHUNK_STATE_ZERO_FOOTER_FINAL_CRLF: switch ( ctToken ) { case CHUNK_TOKEN_LF: _dwLf++; if ( IsCrLf() ) { ClearCrLf(); if ( _csState == CHUNK_STATE_ZERO_FOOTER_CRLF) { SetState(CHUNK_STATE_ZERO_FOOTER); } else { INET_ASSERT( _csState == CHUNK_STATE_ZERO_FOOTER_FINAL_CRLF); //Done? SetState(CHUNK_STATE_FINISHED); DEBUG_PRINT(HTTP, INFO, ("EOF chunk\n" )); error = ERROR_SUCCESS; goto quit; } } else { DEBUG_PRINT(HTTP, INFO, ("-->CHUNK err: Got %q, But CRLF not matched, CR=%u, LF=%u\n", InternetMapChunkToken(ctToken), _dwCr, _dwLf )); error = ERROR_INTERNET_INTERNAL_ERROR; goto quit; } break; default: DEBUG_PRINT(HTTP, INFO, ("-->CHUNK err: Got %q, while looking for LF\n", InternetMapChunkToken(ctToken) )); error = ERROR_INTERNET_INTERNAL_ERROR; goto quit; } break; case CHUNK_STATE_FINISHED: INET_ASSERT(FALSE); error = ERROR_SUCCESS; goto quit; default: INET_ASSERT(FALSE); error = ERROR_INTERNET_INTERNAL_ERROR; goto quit; } } quit: if ( error == ERROR_SUCCESS) { if ( dwFirstDataBufferSize > 0 ) { INET_ASSERT(lpInputBuffer == lpszStartOfFirstDataBuffer); } *lplpInputBufferNew = lpInputBuffer; *lpdwInputBufferNewSize = dwFirstDataBufferSize; } DEBUG_LEAVE(error); return error; }