From 7cdb1f7224d88c329e012c4210461e5c9613be17 Mon Sep 17 00:00:00 2001 From: Alex Hultman Date: Tue, 18 Oct 2022 16:55:22 +0200 Subject: [PATCH] Do not expect a trailer after chunked body --- src/ChunkedEncoding.h | 15 +++-- tests/ChunkedEncoding.cpp | 135 +++++++++++++++++++++++++++++++++++++- 2 files changed, 143 insertions(+), 7 deletions(-) diff --git a/src/ChunkedEncoding.h b/src/ChunkedEncoding.h index dde4af5..5b2e091 100644 --- a/src/ChunkedEncoding.h +++ b/src/ChunkedEncoding.h @@ -86,7 +86,7 @@ namespace uWS { } /* Returns next chunk (empty or not), or if all data was consumed, nullopt is returned. */ - std::optional getNextChunk(std::string_view &data, unsigned int &state) { + std::optional getNextChunk(std::string_view &data, unsigned int &state, bool trailer = false) { while (data.length()) { @@ -118,7 +118,11 @@ namespace uWS { //printf("Setting state to trailer-parsing and emitting empty chunk\n"); // set trailer state and increase size to 4 - state = 4 /*| STATE_IS_CHUNKED*/ | STATE_HAS_SIZE; + if (trailer) { + state = 4 /*| STATE_IS_CHUNKED*/ | STATE_HAS_SIZE; + } else { + state = 2 /*| STATE_IS_CHUNKED*/ | STATE_HAS_SIZE; + } return std::string_view(nullptr, 0); } @@ -174,9 +178,10 @@ namespace uWS { std::string_view *data; std::optional chunk; unsigned int *state; + bool trailer; - ChunkIterator(std::string_view *data, unsigned int *state) : data(data), state(state) { - chunk = uWS::getNextChunk(*data, *state); + ChunkIterator(std::string_view *data, unsigned int *state, bool trailer = false) : data(data), state(state), trailer(trailer) { + chunk = uWS::getNextChunk(*data, *state, trailer); } ChunkIterator() { @@ -203,7 +208,7 @@ namespace uWS { } ChunkIterator &operator++() { - chunk = uWS::getNextChunk(*data, *state); + chunk = uWS::getNextChunk(*data, *state, trailer); return *this; } diff --git a/tests/ChunkedEncoding.cpp b/tests/ChunkedEncoding.cpp index 4fdb4b4..0aa955b 100644 --- a/tests/ChunkedEncoding.cpp +++ b/tests/ChunkedEncoding.cpp @@ -7,6 +7,99 @@ #include "../src/ChunkedEncoding.h" +void consumeChunkEncoding(int maxConsume, std::string_view &chunkEncoded, unsigned int &state) { + //int maxConsume = 200; + + if (uWS::isParsingChunkedEncoding(state)) { + std::cout << "already in chunked parsing state!" << std::endl; + std::abort(); + } + + // this should not break the parser + state = uWS::STATE_IS_CHUNKED; + + while (chunkEncoded.length()) { + + /* Split up the chunkEncoded string into further chunks for parsing */ + std::string_view data = chunkEncoded.substr(0, std::min(maxConsume, chunkEncoded.length())); + unsigned int data_length_before_parsing = data.length(); + + for (auto chunk : uWS::ChunkIterator(&data, &state, true)) { + } + + /* Only remove that which was consumed */ + chunkEncoded.remove_prefix(data_length_before_parsing - data.length()); + + if (state == 0) { + + if (chunkEncoded.length() == 0 || chunkEncoded.length() == 74) { + break; + } else { + std::abort(); + } + + // should be fine + state = uWS::STATE_IS_CHUNKED; + + //std::cout << "remaining chunk:" << chunkEncoded.length() << std::endl; + //std::abort(); + //break; + } + + /* Here we must be in parsingchunked state */ + if (!uWS::isParsingChunkedEncoding(state)) { + std::cout << "not in parsing chunked strate!" << std::endl; + std::abort(); + } + + } +} + +void runBetterTest(unsigned int maxConsume) { + /* A list of chunks */ + std::vector chunks = { + "Hello there I am the first segment", + "Why hello there", + "", + "I am last?", + "And I am a little longer but it doesn't matter", + "" + }; + + /* Encode them in chunked encoding */ + std::stringstream ss; + for (std::string_view chunk : chunks) { + /* Generic chunked encoding format */ + ss << std::hex << chunk.length() << "\r\n" << chunk << "\r\n"; + + /* Every null chunk is followed by an empty trailer */ + if (chunk.length() == 0) { + ss << "\r\n"; + } + } + std::string buffer = ss.str(); + std::string_view chunkEncoded = buffer; + + unsigned int state = 0; + + if (uWS::isParsingChunkedEncoding(state)) { + std::abort(); + } + consumeChunkEncoding(maxConsume, chunkEncoded, state); + if (state != 0) { + std::abort(); + } + consumeChunkEncoding(maxConsume, chunkEncoded, state); + if (state != 0) { + std::abort(); + } + + // consumeChunkEncoding(chunkEncoded) - consumes in further chunkes and does isParsingChunked checks + // assume state == 0 and we still have bytes to parse (we should have consumed EXACTLY right bytes) + // consumeChunkEncoding(chunkEncoded) + // assume state == 0 and we have no bytes to consume +} + void runTest(unsigned int maxConsume) { /* A list of chunks */ std::vector chunks = { @@ -39,6 +132,11 @@ void runTest(unsigned int maxConsume) { unsigned int chunkOffset = 0; std::string_view chunkEncoded = buffer; + // consumeChunkEncoding(chunkEncoded) - consumes in further chunkes and does isParsingChunked checks + // assume state == 0 and we still have bytes to parse (we should have consumed EXACTLY right bytes) + // consumeChunkEncoding(chunkEncoded) + // assume state == 0 and we have no bytes to consume + // this while should be more like if original size or "is parsing chunked" (which tests the uWS::wantsChunkedParsing(state)) while (chunkEncoded.length()) { /* Parse a small part of the given data */ @@ -48,10 +146,9 @@ void runTest(unsigned int maxConsume) { unsigned int data_length_before_parsing = data.length(); - /* Whatever chunk we emit, or part of chunk, it must match the expected one */ //std::cout << "Calling parser now" << std::endl; - for (auto chunk : uWS::ChunkIterator(&data, &state)) { + for (auto chunk : uWS::ChunkIterator(&data, &state, true)) { std::cout << "<" << chunk << ">" << std::endl; /* Run check here */ @@ -87,7 +184,41 @@ void runTest(unsigned int maxConsume) { } } +void testWithoutTrailer() { + /* A list of chunks */ + std::vector chunks = { + "Hello there I am the first segment", + "" + }; + + /* Encode them in chunked encoding */ + std::stringstream ss; + for (std::string_view chunk : chunks) { + /* Generic chunked encoding format */ + ss << std::hex << chunk.length() << "\r\n" << chunk << "\r\n"; + } + std::string buffer = ss.str(); + std::string_view dataToConsume(buffer.data(), buffer.length()); + + unsigned int state = uWS::STATE_IS_CHUNKED; + + for (auto chunk : uWS::ChunkIterator(&dataToConsume, &state)) { + + } + + if (state) { + std::abort(); + } +} + int main() { + + testWithoutTrailer(); + + for (int i = 1; i < 1000; i++) { + runBetterTest(i); + } + for (int i = 1; i < 1000; i++) { runTest(i); }