Compare commits

...

4 Commits

Author SHA1 Message Date
cirospaciari
431fcf0fbd add test 2023-11-09 19:59:50 -03:00
cirospaciari
970ff6fc59 wip 2023-11-09 19:38:17 -03:00
Ciro Spaciari
63e5200a63 Merge branch 'main' into ciro/fix-maxRequestBodySize 2023-11-09 19:30:45 -03:00
cirospaciari
0cf68e5eab wip 2023-10-15 18:51:09 -03:00
3 changed files with 71 additions and 46 deletions

View File

@@ -29,18 +29,18 @@
namespace uWS {
constexpr uint32_t STATE_HAS_SIZE = 0x80000000;
constexpr uint32_t STATE_IS_CHUNKED = 0x40000000;
constexpr uint32_t STATE_SIZE_MASK = 0x3FFFFFFF;
constexpr uint32_t STATE_IS_ERROR = 0xFFFFFFFF;
constexpr uint32_t STATE_SIZE_OVERFLOW = 0x0F000000;
constexpr uint64_t STATE_HAS_SIZE = 0x8000000000000000;
constexpr uint64_t STATE_IS_CHUNKED = 0x4000000000000000;
constexpr uint64_t STATE_SIZE_MASK = 0x3FFFFFFFFFFFFFFF;
constexpr uint64_t STATE_IS_ERROR = 0xFFFFFFFFFFFFFFFF;
constexpr uint64_t STATE_SIZE_OVERFLOW = 0x0F00000000000000;
inline unsigned int chunkSize(unsigned int state) {
inline uint64_t chunkSize(uint64_t state) {
return state & STATE_SIZE_MASK;
}
/* Reads hex number until CR or out of data to consume. Updates state. Returns bytes consumed. */
inline void consumeHexNumber(std::string_view &data, unsigned int &state) {
inline void consumeHexNumber(std::string_view &data, uint64_t &state) {
/* Consume everything higher than 32 */
while (data.length() && data.data()[0] > 32) {
@@ -51,7 +51,7 @@ namespace uWS {
digit = (unsigned char) (digit - ('A' - ':'));
}
unsigned int number = ((unsigned int) digit - (unsigned int) '0');
uint64_t number = ((uint64_t) digit - (uint64_t) '0');
if (number > 16 || (chunkSize(state) & STATE_SIZE_OVERFLOW)) {
state = STATE_IS_ERROR;
@@ -59,9 +59,9 @@ namespace uWS {
}
// extract state bits
unsigned int bits = /*state &*/ STATE_IS_CHUNKED;
uint64_t bits = /*state &*/ STATE_IS_CHUNKED;
state = (state & STATE_SIZE_MASK) * 16u + number;
state = (state & STATE_SIZE_MASK) * 16ull + number;
state |= bits;
data.remove_prefix(1);
@@ -78,30 +78,30 @@ namespace uWS {
}
}
inline void decChunkSize(unsigned int &state, unsigned int by) {
inline void decChunkSize(uint64_t &state, uint64_t by) {
//unsigned int bits = state & STATE_IS_CHUNKED;
//uint64_t bits = state & STATE_IS_CHUNKED;
state = (state & ~STATE_SIZE_MASK) | (chunkSize(state) - by);
//state |= bits;
}
inline bool hasChunkSize(unsigned int state) {
inline bool hasChunkSize(uint64_t state) {
return state & STATE_HAS_SIZE;
}
/* Are we in the middle of parsing chunked encoding? */
inline bool isParsingChunkedEncoding(unsigned int state) {
inline bool isParsingChunkedEncoding(uint64_t state) {
return state & ~STATE_SIZE_MASK;
}
inline bool isParsingInvalidChunkedEncoding(unsigned int state) {
inline bool isParsingInvalidChunkedEncoding(uint64_t state) {
return state == STATE_IS_ERROR;
}
/* Returns next chunk (empty or not), or if all data was consumed, nullopt is returned. */
static std::optional<std::string_view> getNextChunk(std::string_view &data, unsigned int &state, bool trailer = false) {
static std::optional<std::string_view> getNextChunk(std::string_view &data, uint64_t &state, bool trailer = false) {
while (data.length()) {
// if in "drop trailer mode", just drop up to what we have as size
@@ -166,7 +166,7 @@ namespace uWS {
/* We will consume all our input data */
std::string_view emitSoon;
if (chunkSize(state) > 2) {
unsigned int maximalAppEmit = chunkSize(state) - 2;
uint64_t maximalAppEmit = chunkSize(state) - 2;
if (data.length() > maximalAppEmit) {
emitSoon = data.substr(0, maximalAppEmit);
} else {
@@ -174,7 +174,7 @@ namespace uWS {
emitSoon = data;
}
}
decChunkSize(state, (unsigned int) data.length());
decChunkSize(state, (uint64_t) data.length());
state |= STATE_IS_CHUNKED;
// new: decrease data by its size (bug)
data.remove_prefix(data.length()); // ny bug fix för getNextChunk
@@ -194,10 +194,10 @@ namespace uWS {
std::string_view *data;
std::optional<std::string_view> chunk;
unsigned int *state;
uint64_t *state;
bool trailer;
ChunkIterator(std::string_view *data, unsigned int *state, bool trailer = false) : data(data), state(state), trailer(trailer) {
ChunkIterator(std::string_view *data, uint64_t *state, bool trailer = false) : data(data), state(state), trailer(trailer) {
chunk = uWS::getNextChunk(*data, *state, trailer);
}

View File

@@ -37,7 +37,7 @@ namespace uWS
{
/* We require at least this much post padding */
static const unsigned int MINIMUM_HTTP_POST_PADDING = 32;
static const uint64_t MINIMUM_HTTP_POST_PADDING = 32;
static void *FULLPTR = (void *)~(uintptr_t)0;
struct HttpRequest
@@ -199,29 +199,29 @@ namespace uWS
private:
std::string fallback;
/* This guy really has only 30 bits since we reserve two highest bits to chunked encoding parsing state */
unsigned int remainingStreamingBytes = 0;
/* This guy really has only 62 bits since we reserve two highest bits to chunked encoding parsing state */
std::uint64_t remainingStreamingBytes = 0;
const size_t MAX_FALLBACK_SIZE = 1024 * 4;
/* Returns UINT_MAX on error. Maximum 999999999 is allowed. */
static unsigned int toUnsignedInteger(std::string_view str)
/* Returns UINT64_MAX on error. Maximum 999999999999999999 is allowed. */
static std::uint64_t toUnsignedInteger(std::string_view str)
{
/* We assume at least 32-bit integer giving us safely 999999999 (9 number of 9s) */
if (str.length() > 9)
/* We assume 999999999999999999 (18 number of 9s) */
if (str.length() > 18)
{
return UINT_MAX;
return UINT64_MAX;
}
unsigned int unsignedIntegerValue = 0;
uint64_t unsignedIntegerValue = 0;
for (char c : str)
{
/* As long as the letter is 0-9 we cannot overflow. */
if (c < '0' || c > '9')
{
return UINT_MAX;
return UINT64_MAX;
}
unsignedIntegerValue = unsignedIntegerValue * 10u + ((unsigned int)c - (unsigned int)'0');
unsignedIntegerValue = unsignedIntegerValue * 10ull + ((uint64_t)c - (uint64_t)'0');
}
return unsignedIntegerValue;
}
@@ -474,7 +474,7 @@ namespace uWS
* or [consumed, nullptr] for "break; I am closed or upgraded to websocket"
* or [whatever, fullptr] for "break and close me, I am a parser error!" */
template <int CONSUME_MINIMALLY>
std::pair<unsigned int, void *> fenceAndConsumePostPadded(char *data, unsigned int length, void *user, void *reserved, HttpRequest *req, MoveOnlyFunction<void *(void *, HttpRequest *)> &requestHandler, MoveOnlyFunction<void *(void *, std::string_view, bool)> &dataHandler)
std::pair<uint64_t, void *> fenceAndConsumePostPadded(char *data, uint64_t length, void *user, void *reserved, HttpRequest *req, MoveOnlyFunction<void *(void *, HttpRequest *)> &requestHandler, MoveOnlyFunction<void *(void *, std::string_view, bool)> &dataHandler)
{
/* How much data we CONSUMED (to throw away) */
@@ -574,16 +574,16 @@ namespace uWS
{
return {0, FULLPTR};
}
unsigned int consumed = (length - (unsigned int)dataToConsume.length());
uint64_t consumed = (length - (uint64_t)dataToConsume.length());
data = (char *)dataToConsume.data();
length = (unsigned int)dataToConsume.length();
length = (uint64_t)dataToConsume.length();
consumedTotal += consumed;
}
}
else if (contentLengthString.length())
{
remainingStreamingBytes = toUnsignedInteger(contentLengthString);
if (remainingStreamingBytes == UINT_MAX)
if (remainingStreamingBytes == UINT16_MAX)
{
/* Parser error */
return {0, FULLPTR};
@@ -591,7 +591,7 @@ namespace uWS
if (!CONSUME_MINIMALLY)
{
unsigned int emittable = std::min<unsigned int>(remainingStreamingBytes, length);
uint64_t emittable = std::min<uint64_t>(remainingStreamingBytes, length);
dataHandler(user, std::string_view(data, emittable), emittable == remainingStreamingBytes);
remainingStreamingBytes -= emittable;
@@ -616,7 +616,7 @@ namespace uWS
}
public:
void *consumePostPadded(char *data, unsigned int length, void *user, void *reserved, MoveOnlyFunction<void *(void *, HttpRequest *)> &&requestHandler, MoveOnlyFunction<void *(void *, std::string_view, bool)> &&dataHandler, MoveOnlyFunction<void *(void *)> &&errorHandler)
void *consumePostPadded(char *data, uint64_t length, void *user, void *reserved, MoveOnlyFunction<void *(void *, HttpRequest *)> &&requestHandler, MoveOnlyFunction<void *(void *, std::string_view, bool)> &&dataHandler, MoveOnlyFunction<void *(void *)> &&errorHandler)
{
/* This resets BloomFilter by construction, but later we also reset it again.
* Optimize this to skip resetting twice (req could be made global) */
@@ -638,7 +638,7 @@ namespace uWS
return FULLPTR;
}
data = (char *)dataToConsume.data();
length = (unsigned int)dataToConsume.length();
length = (uint64_t)dataToConsume.length();
}
else
{
@@ -679,16 +679,16 @@ namespace uWS
}
else if (fallback.length())
{
unsigned int had = (unsigned int)fallback.length();
uint64_t had = (uint64_t)fallback.length();
size_t maxCopyDistance = std::min<size_t>(MAX_FALLBACK_SIZE - fallback.length(), (size_t)length);
/* We don't want fallback to be short string optimized, since we want to move it */
fallback.reserve(fallback.length() + maxCopyDistance + std::max<unsigned int>(MINIMUM_HTTP_POST_PADDING, sizeof(std::string)));
fallback.reserve(fallback.length() + maxCopyDistance + std::max<uint64_t>(MINIMUM_HTTP_POST_PADDING, sizeof(std::string)));
fallback.append(data, maxCopyDistance);
// break here on break
std::pair<unsigned int, void *> consumed = fenceAndConsumePostPadded<true>(fallback.data(), (unsigned int)fallback.length(), user, reserved, &req, requestHandler, dataHandler);
std::pair<uint64_t, void *> consumed = fenceAndConsumePostPadded<true>(fallback.data(), (uint64_t)fallback.length(), user, reserved, &req, requestHandler, dataHandler);
if (consumed.second != user)
{
return consumed.second;
@@ -719,14 +719,14 @@ namespace uWS
return FULLPTR;
}
data = (char *)dataToConsume.data();
length = (unsigned int)dataToConsume.length();
length = (uint64_t)dataToConsume.length();
}
else
{
// this is exactly the same as above!
if (remainingStreamingBytes >= (unsigned int)length)
if (remainingStreamingBytes >= (uint64_t)length)
{
void *returnedUser = dataHandler(user, std::string_view(data, length), remainingStreamingBytes == (unsigned int)length);
void *returnedUser = dataHandler(user, std::string_view(data, length), remainingStreamingBytes == (uint64_t)length);
remainingStreamingBytes -= length;
return returnedUser;
}
@@ -759,7 +759,7 @@ namespace uWS
}
}
std::pair<unsigned int, void *> consumed = fenceAndConsumePostPadded<false>(data, length, user, reserved, &req, requestHandler, dataHandler);
std::pair<uint64_t, void *> consumed = fenceAndConsumePostPadded<false>(data, length, user, reserved, &req, requestHandler, dataHandler);
if (consumed.second != user)
{
return consumed.second;

View File

@@ -3,7 +3,7 @@ import { bunExe, bunEnv } from "harness";
import path from "path";
describe("Server", () => {
test("normlizes incoming request URLs", async () => {
test("normalizes incoming request URLs", async () => {
const server = Bun.serve({
fetch(request) {
return new Response(request.url, {
@@ -408,4 +408,29 @@ describe("Server", () => {
server.stop(true);
}
});
test("should accept more than 999 999 999 bytes in maxRequestSize", async () => {
let server;
const MAX_REQUEST_BODY_SIZE = 1000000000;
try {
server = Bun.serve({
async fetch(request, server) {
return new Response("Hi", { status: 200 });
},
port: 0,
maxRequestBodySize: MAX_REQUEST_BODY_SIZE,
});
const url = `http://${server.hostname}:${server.port}`;
for (const length of [MAX_REQUEST_BODY_SIZE - 1, MAX_REQUEST_BODY_SIZE, MAX_REQUEST_BODY_SIZE + 1]) {
const response = await fetch(url, {
method: "POST",
body: "A".repeat(length),
});
expect(response.status).toBe(length > MAX_REQUEST_BODY_SIZE ? 413 : 200);
}
} finally {
server?.stop(true);
}
});
});