mirror of
https://github.com/oven-sh/bun
synced 2026-02-10 02:48:50 +00:00
* Upgrade uWebSockets & usockets * Update HttpRouter.h * Defensively prevent sending to blocking sockets * Add test for receiving large amounts of data * Large data optimization * Update loop.c * Avoid extra system call before entering event loop * Update internal.h * 1 less pointer lookup * Fix error * Update socket-huge-fixture.js --------- Co-authored-by: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com>
227 lines
7.1 KiB
C++
227 lines
7.1 KiB
C++
#include <iostream>
|
|
#include <cassert>
|
|
#include <sstream>
|
|
#include <iomanip>
|
|
#include <vector>
|
|
#include <climits>
|
|
|
|
#include "../src/ChunkedEncoding.h"
|
|
|
|
void consumeChunkEncoding(int maxConsume, std::string_view &chunkEncoded, uint64_t &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<size_t>(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<std::string_view> 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;
|
|
|
|
uint64_t 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<std::string_view> 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();
|
|
|
|
/* Since we have 2 chunked bodies in our buffer, the parser must stop with state == 0 exactly 2 times */
|
|
unsigned int stoppedWithClearState = 0;
|
|
|
|
/* Begin with a clear state and the full data */
|
|
uint64_t state = 0;
|
|
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 */
|
|
std::string_view data = chunkEncoded.substr(0, std::min<size_t>(maxConsume, chunkEncoded.length()));
|
|
|
|
|
|
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, true)) {
|
|
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();
|
|
}
|
|
|
|
if (chunks[chunkOffset].substr(0, chunk.length()) == chunk /*starts_with(chunk)*/) {
|
|
chunks[chunkOffset].remove_prefix(chunk.length());
|
|
if (!chunks[chunkOffset].length()) {
|
|
chunkOffset++;
|
|
}
|
|
} else {
|
|
std::cerr << "Chunk does not match! Should be <" << chunks[chunkOffset] << ">" << std::endl;
|
|
std::abort();
|
|
}
|
|
}
|
|
|
|
/* The parser returtned, okay count the times it has state == 0, it should be 2 per the whole buffer always */
|
|
if (state == 0) {
|
|
printf("Parser stopped with no state set!\n");
|
|
stoppedWithClearState++;
|
|
}
|
|
|
|
/* Only remove that which was consumed */
|
|
chunkEncoded.remove_prefix(data_length_before_parsing - data.length());
|
|
}
|
|
|
|
if (stoppedWithClearState != 2) {
|
|
std::cerr << "Error: The parser stopped with no state " << stoppedWithClearState << " times!" << std::endl;
|
|
std::abort();
|
|
}
|
|
}
|
|
|
|
void testWithoutTrailer() {
|
|
/* A list of chunks */
|
|
std::vector<std::string_view> 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());
|
|
|
|
uint64_t 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);
|
|
}
|
|
|
|
std::cout << "ALL BRUTEFORCE DONE" << std::endl;
|
|
} |