Greatly improved chunk-encoded parser and test
This commit is contained in:
+69
-16
@@ -52,7 +52,6 @@ namespace uWS {
|
|||||||
}
|
}
|
||||||
/* Now we stand on \n so consume it and enable size */
|
/* Now we stand on \n so consume it and enable size */
|
||||||
if (data.length()) {
|
if (data.length()) {
|
||||||
//printf("size of chunk is %d\n", state);
|
|
||||||
state += 2; // include the two last /r/n
|
state += 2; // include the two last /r/n
|
||||||
state |= STATE_HAS_SIZE;
|
state |= STATE_HAS_SIZE;
|
||||||
data.remove_prefix(1);
|
data.remove_prefix(1);
|
||||||
@@ -71,13 +70,8 @@ namespace uWS {
|
|||||||
return state & STATE_HAS_SIZE;
|
return state & STATE_HAS_SIZE;
|
||||||
}
|
}
|
||||||
|
|
||||||
// bättre interface
|
/* Returns next chunk (empty or not), or if all data was consumed, nullopt is returned. */
|
||||||
std::optional<std::string_view> getNextChunk(std::string_view data, unsigned int &state);
|
std::optional<std::string_view> 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<void(std::string_view)> cb) {
|
|
||||||
|
|
||||||
std::string_view originalData = data;
|
|
||||||
|
|
||||||
while (data.length()) {
|
while (data.length()) {
|
||||||
|
|
||||||
@@ -85,12 +79,10 @@ namespace uWS {
|
|||||||
if ((state & STATE_IS_CHUNKED) && hasChunkSize(state) && chunkSize(state)) {
|
if ((state & STATE_IS_CHUNKED) && hasChunkSize(state) && chunkSize(state)) {
|
||||||
|
|
||||||
while(data.length() && chunkSize(state)) {
|
while(data.length() && chunkSize(state)) {
|
||||||
//printf("dropping 1 byte of trailer\n");
|
|
||||||
data.remove_prefix(1);
|
data.remove_prefix(1);
|
||||||
decChunkSize(state, 1);
|
decChunkSize(state, 1);
|
||||||
|
|
||||||
if (chunkSize(state) == 0) {
|
if (chunkSize(state) == 0) {
|
||||||
//printf("dropped the whole trailer\n");
|
|
||||||
state = 0;
|
state = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -104,7 +96,7 @@ namespace uWS {
|
|||||||
// set trailer state and increase size to 4
|
// set trailer state and increase size to 4
|
||||||
state = 4 | STATE_IS_CHUNKED | STATE_HAS_SIZE;
|
state = 4 | STATE_IS_CHUNKED | STATE_HAS_SIZE;
|
||||||
|
|
||||||
cb(std::string_view(nullptr, 0));
|
return std::string_view(nullptr, 0);
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -113,29 +105,90 @@ namespace uWS {
|
|||||||
if (data.length() >= chunkSize(state)) {
|
if (data.length() >= chunkSize(state)) {
|
||||||
// emit all but 2 bytes then reset state to 0 and goto beginning
|
// emit all but 2 bytes then reset state to 0 and goto beginning
|
||||||
// not fin
|
// not fin
|
||||||
|
std::string_view emitSoon;
|
||||||
|
bool shouldEmit = false;
|
||||||
if (chunkSize(state) > 2) {
|
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));
|
data.remove_prefix(chunkSize(state));
|
||||||
state = 0;
|
state = 0;
|
||||||
|
if (shouldEmit) {
|
||||||
|
return emitSoon;
|
||||||
|
}
|
||||||
continue;
|
continue;
|
||||||
} else {
|
} else {
|
||||||
/* We will consume all our input data */
|
/* We will consume all our input data */
|
||||||
|
std::string_view emitSoon;
|
||||||
if (chunkSize(state) > 2) {
|
if (chunkSize(state) > 2) {
|
||||||
unsigned int maximalAppEmit = chunkSize(state) - 2;
|
unsigned int maximalAppEmit = chunkSize(state) - 2;
|
||||||
if (data.length() > maximalAppEmit) {
|
if (data.length() > maximalAppEmit) {
|
||||||
cb(data.substr(0, maximalAppEmit));
|
emitSoon = data.substr(0, maximalAppEmit);
|
||||||
} else {
|
} else {
|
||||||
cb(data);
|
//cb(data);
|
||||||
|
emitSoon = data;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
decChunkSize(state, data.length());
|
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<std::string_view> 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
|
#endif // UWS_CHUNKEDENCODING_H
|
||||||
|
|||||||
+14
-21
@@ -21,32 +21,30 @@ void runTest(unsigned int maxConsume) {
|
|||||||
/* Encode them in chunked encoding */
|
/* Encode them in chunked encoding */
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
for (std::string_view chunk : chunks) {
|
for (std::string_view chunk : chunks) {
|
||||||
|
/* Generic chunked encoding format */
|
||||||
// if length is 0 then append trailer also
|
|
||||||
|
|
||||||
ss << std::hex << chunk.length() << "\r\n" << chunk << "\r\n";
|
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) {
|
if (chunk.length() == 0) {
|
||||||
ss << "\r\n";
|
ss << "\r\n";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Consume them, checking that we get what we expect */
|
|
||||||
std::string buffer = ss.str();
|
std::string buffer = ss.str();
|
||||||
|
|
||||||
|
/* Begin with a clear state and the full data */
|
||||||
unsigned int state = 0;
|
unsigned int state = 0;
|
||||||
|
unsigned int chunkOffset = 0;
|
||||||
std::string_view chunkEncoded = buffer;
|
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<size_t>(maxConsume, chunkEncoded.length()));
|
||||||
|
chunkEncoded.remove_prefix(data.length());
|
||||||
|
|
||||||
unsigned int consumed = UINT_MAX;
|
/* Whatever chunk we emit, or part of chunk, it must match the expected one */
|
||||||
int chunkOffset = 0;
|
for (auto chunk : uWS::ChunkIterator(data, &state)) {
|
||||||
while (consumed) {
|
std::cout << "<" << chunk << ">" << std::endl;
|
||||||
/* Consume up to maxConsume */
|
|
||||||
std::string_view indata = chunkEncoded.substr(0, std::min<size_t>(maxConsume, chunkEncoded.length()));
|
|
||||||
|
|
||||||
consumed = uWS::consumeChunkedEncoding(indata, state, [&](std::string_view chunk) {
|
|
||||||
/* Print for logging */
|
|
||||||
std::cout << "<" << chunk << ">";
|
|
||||||
|
|
||||||
|
/* Run check here */
|
||||||
if (!chunk.length() && chunks[chunkOffset].length()) {
|
if (!chunk.length() && chunks[chunkOffset].length()) {
|
||||||
std::cout << "We got emitted an empty chunk but expected a non-empty one" << std::endl;
|
std::cout << "We got emitted an empty chunk but expected a non-empty one" << std::endl;
|
||||||
std::abort();
|
std::abort();
|
||||||
@@ -61,17 +59,12 @@ void runTest(unsigned int maxConsume) {
|
|||||||
std::cerr << "Chunk does not match! Should be <" << chunks[chunkOffset] << ">" << std::endl;
|
std::cerr << "Chunk does not match! Should be <" << chunks[chunkOffset] << ">" << std::endl;
|
||||||
std::abort();
|
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() {
|
int main() {
|
||||||
for (int i = 0; i < 1000; i++) {
|
for (int i = 1; i < 1000; i++) {
|
||||||
runTest(i);
|
runTest(i);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user