diff --git a/src/ChunkedEncoding.h b/src/ChunkedEncoding.h index 435ea7b..9070e5c 100644 --- a/src/ChunkedEncoding.h +++ b/src/ChunkedEncoding.h @@ -52,7 +52,6 @@ namespace uWS { } /* Now we stand on \n so consume it and enable size */ if (data.length()) { - //printf("size of chunk is %d\n", state); state += 2; // include the two last /r/n state |= STATE_HAS_SIZE; data.remove_prefix(1); @@ -71,13 +70,8 @@ namespace uWS { return state & STATE_HAS_SIZE; } - // bättre interface - std::optional getNextChunk(std::string_view data, unsigned int &state); - - /* Consumes as much as possible, emitting chunks using cb. Changes passed state for later resumption. Returns number of bytes consumed. */ - inline unsigned int consumeChunkedEncoding(std::string_view data, unsigned int &state, MoveOnlyFunction cb) { - - std::string_view originalData = data; + /* 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) { while (data.length()) { @@ -85,12 +79,10 @@ namespace uWS { if ((state & STATE_IS_CHUNKED) && hasChunkSize(state) && chunkSize(state)) { while(data.length() && chunkSize(state)) { - //printf("dropping 1 byte of trailer\n"); data.remove_prefix(1); decChunkSize(state, 1); if (chunkSize(state) == 0) { - //printf("dropped the whole trailer\n"); state = 0; } } @@ -104,7 +96,7 @@ namespace uWS { // set trailer state and increase size to 4 state = 4 | STATE_IS_CHUNKED | STATE_HAS_SIZE; - cb(std::string_view(nullptr, 0)); + return std::string_view(nullptr, 0); } continue; } @@ -113,29 +105,90 @@ namespace uWS { if (data.length() >= chunkSize(state)) { // emit all but 2 bytes then reset state to 0 and goto beginning // not fin + std::string_view emitSoon; + bool shouldEmit = false; if (chunkSize(state) > 2) { - cb(std::string_view(data.data(), chunkSize(state) - 2)); + emitSoon = std::string_view(data.data(), chunkSize(state) - 2); + shouldEmit = true; } data.remove_prefix(chunkSize(state)); state = 0; + if (shouldEmit) { + return emitSoon; + } continue; } else { /* We will consume all our input data */ + std::string_view emitSoon; if (chunkSize(state) > 2) { unsigned int maximalAppEmit = chunkSize(state) - 2; if (data.length() > maximalAppEmit) { - cb(data.substr(0, maximalAppEmit)); + emitSoon = data.substr(0, maximalAppEmit); } else { - cb(data); + //cb(data); + emitSoon = data; } } decChunkSize(state, data.length()); - return originalData.length(); + // new: decrease data by its size (bug) + data.remove_prefix(data.length()); // ny bug fix för getNextChunk + if (emitSoon.length()) { + return emitSoon; + } else { + return std::nullopt; + } } } - return originalData.length() - data.length(); + return std::nullopt; } + + /* This is really just a wrapper for convenience */ + struct ChunkIterator { + + std::string_view data; + std::optional chunk; + unsigned int *state; + + ChunkIterator(std::string_view data, unsigned int *state) : data(data), state(state) { + chunk = uWS::getNextChunk(this->data, *state); + if (!chunk && this->data.length()) { + std::abort(); + } + } + + ChunkIterator() { + + } + + ChunkIterator begin() { + return *this; + } + + ChunkIterator end() { + return ChunkIterator(); + } + + std::string_view operator*() { + if (!chunk.has_value()) { + std::abort(); + } + return chunk.value(); + } + + bool operator!=(const ChunkIterator &other) const { + return other.chunk.has_value() != chunk.has_value(); + } + + ChunkIterator &operator++() { + chunk = uWS::getNextChunk(data, *state); + if (!chunk && this->data.length()) { + std::abort(); + } + return *this; + } + + }; } #endif // UWS_CHUNKEDENCODING_H diff --git a/tests/ChunkedEncoding.cpp b/tests/ChunkedEncoding.cpp index 8bec0a1..6accbe8 100644 --- a/tests/ChunkedEncoding.cpp +++ b/tests/ChunkedEncoding.cpp @@ -21,32 +21,30 @@ void runTest(unsigned int maxConsume) { /* Encode them in chunked encoding */ std::stringstream ss; for (std::string_view chunk : chunks) { - - // if length is 0 then append trailer also - + /* Generic chunked encoding format */ ss << std::hex << chunk.length() << "\r\n" << chunk << "\r\n"; - // every null chunk is followed by an empty trailer + /* Every null chunk is followed by an empty trailer */ if (chunk.length() == 0) { ss << "\r\n"; } } - - /* Consume them, checking that we get what we expect */ std::string buffer = ss.str(); + + /* Begin with a clear state and the full data */ unsigned int state = 0; + unsigned int chunkOffset = 0; std::string_view chunkEncoded = buffer; + while (chunkEncoded.length()) { + /* Parse a small part of the given data */ + std::string_view data = chunkEncoded.substr(0, std::min(maxConsume, chunkEncoded.length())); + chunkEncoded.remove_prefix(data.length()); - unsigned int consumed = UINT_MAX; - int chunkOffset = 0; - while (consumed) { - /* Consume up to maxConsume */ - std::string_view indata = chunkEncoded.substr(0, std::min(maxConsume, chunkEncoded.length())); - - consumed = uWS::consumeChunkedEncoding(indata, state, [&](std::string_view chunk) { - /* Print for logging */ - std::cout << "<" << chunk << ">"; + /* Whatever chunk we emit, or part of chunk, it must match the expected one */ + for (auto chunk : uWS::ChunkIterator(data, &state)) { + std::cout << "<" << chunk << ">" << std::endl; + /* Run check here */ if (!chunk.length() && chunks[chunkOffset].length()) { std::cout << "We got emitted an empty chunk but expected a non-empty one" << std::endl; std::abort(); @@ -61,17 +59,12 @@ void runTest(unsigned int maxConsume) { std::cerr << "Chunk does not match! Should be <" << chunks[chunkOffset] << ">" << std::endl; std::abort(); } - }); - if (consumed != indata.length()) { - std::cerr << "Chunk parser did not consume exactly the bytes passed!" << std::endl; - std::abort(); } - chunkEncoded.remove_prefix(consumed); } } int main() { - for (int i = 0; i < 1000; i++) { + for (int i = 1; i < 1000; i++) { runTest(i); }