Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
65 commits
Select commit Hold shift + click to select a range
2b51cd9
wip
cirospaciari May 6, 2025
1ee110a
better
cirospaciari May 6, 2025
2d5fde5
cleanuop
cirospaciari May 6, 2025
65544a1
opsie
cirospaciari May 6, 2025
2e105c7
more
cirospaciari May 6, 2025
0989896
keep same behavior
cirospaciari May 6, 2025
eab1d58
comments + trim space
cirospaciari May 6, 2025
4da3401
less spaces
cirospaciari May 6, 2025
38e7a3a
less spaces
cirospaciari May 6, 2025
1265e93
cleanner
cirospaciari May 6, 2025
3f11d04
more comments
cirospaciari May 6, 2025
d224ac8
cleanup getHeaders
cirospaciari May 6, 2025
de869ca
init ancientHttp
cirospaciari May 6, 2025
860e0ef
init ancientHttp
cirospaciari May 6, 2025
f54300b
more
cirospaciari May 7, 2025
3c53a70
validate chunk encoding
cirospaciari May 7, 2025
18a92cc
more
cirospaciari May 7, 2025
fce0113
opsie
cirospaciari May 7, 2025
0c7e305
more
cirospaciari May 8, 2025
f7322ca
more
cirospaciari May 8, 2025
748b586
more
cirospaciari May 8, 2025
712d078
we actually wanna to error in fragmented requests
cirospaciari May 8, 2025
f6d1f7f
nope
cirospaciari May 8, 2025
883de94
more
cirospaciari May 9, 2025
169ca85
more
cirospaciari May 9, 2025
616ec02
Merge branch 'main' into ciro/http-parser-refactor
cirospaciari May 9, 2025
f20fb28
trailer
cirospaciari May 9, 2025
da451fc
more
cirospaciari May 9, 2025
b16c9ff
more
cirospaciari May 9, 2025
cbbd400
more
cirospaciari May 9, 2025
6825539
Merge branch 'ciro/http-parser-refactor' into ciro/http-parser-errors
cirospaciari May 9, 2025
b68e640
fix smuggling
cirospaciari May 9, 2025
9b97702
Merge branch 'main' into ciro/http-parser-refactor
cirospaciari May 9, 2025
924f08e
Merge branch 'ciro/http-parser-refactor' into ciro/http-parser-errors
cirospaciari May 9, 2025
f568ad3
fix capture
cirospaciari May 9, 2025
96f9175
more
cirospaciari May 9, 2025
82ea308
not yet
cirospaciari May 9, 2025
a2ce153
Merge branch 'main' into ciro/http-parser-errors
cirospaciari May 9, 2025
742d3e3
`bun run prettier`
cirospaciari May 9, 2025
6f28f15
ok
cirospaciari May 9, 2025
f09d7bc
close
cirospaciari May 9, 2025
eeee03a
opsie
cirospaciari May 9, 2025
615facc
opsie
cirospaciari May 9, 2025
0d25a57
more
cirospaciari May 9, 2025
e5d7387
more
cirospaciari May 9, 2025
50a8e36
`bun run prettier`
cirospaciari May 9, 2025
ae691ec
asan fail
cirospaciari May 9, 2025
c3326d5
again
cirospaciari May 9, 2025
4621d79
one more
cirospaciari May 9, 2025
606954b
revert
cirospaciari May 9, 2025
6dd4dfd
one more hey
cirospaciari May 9, 2025
ac914fd
ok
cirospaciari May 9, 2025
93dbd3e
more tests
cirospaciari May 9, 2025
e1651c4
comment
cirospaciari May 9, 2025
33b2669
comments
cirospaciari May 9, 2025
f5b7a5c
throw
cirospaciari May 9, 2025
745d9ed
Update packages/bun-uws/src/ChunkedEncoding.h
cirospaciari May 9, 2025
b17d954
Update packages/bun-uws/src/ChunkedEncoding.h
cirospaciari May 9, 2025
8867984
review
cirospaciari May 9, 2025
a49b51f
remove expectations
cirospaciari May 9, 2025
7a378fc
improve
cirospaciari May 9, 2025
49c8bb0
cleanup
cirospaciari May 9, 2025
8a66af2
await
cirospaciari May 9, 2025
927534b
more test fixes
cirospaciari May 9, 2025
d9268fe
remove test
cirospaciari May 9, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions packages/bun-uws/src/App.h
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,7 @@ struct TemplatedApp {
}

static TemplatedApp<SSL>* create(SocketContextOptions options = {}) {

auto* httpContext = HttpContext<SSL>::create(Loop::get(), options);
if (!httpContext) {
return nullptr;
Expand Down Expand Up @@ -628,8 +629,14 @@ struct TemplatedApp {
return std::move(*this);
}

TemplatedApp &&setRequireHostHeader(bool value) {
httpContext->getSocketContextData()->flags.requireHostHeader = value;
TemplatedApp &&setFlags(bool requireHostHeader, bool useStrictMethodValidation) {
httpContext->getSocketContextData()->flags.requireHostHeader = requireHostHeader;
httpContext->getSocketContextData()->flags.useStrictMethodValidation = useStrictMethodValidation;
return std::move(*this);
}

TemplatedApp &&setMaxHTTPHeaderSize(uint64_t maxHeaderSize) {
httpContext->getSocketContextData()->maxHeaderSize = maxHeaderSize;
return std::move(*this);
}

Expand Down
123 changes: 90 additions & 33 deletions packages/bun-uws/src/ChunkedEncoding.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,53 +29,110 @@

namespace uWS {

constexpr uint64_t STATE_HAS_SIZE = 1ull << (sizeof(uint64_t) * 8 - 1);//0x80000000;
constexpr uint64_t STATE_IS_CHUNKED = 1ull << (sizeof(uint64_t) * 8 - 2);//0x40000000;
constexpr uint64_t STATE_SIZE_MASK = ~(3ull << (sizeof(uint64_t) * 8 - 2));//0x3FFFFFFF;
constexpr uint64_t STATE_IS_ERROR = ~0ull;//0xFFFFFFFF;
constexpr uint64_t STATE_SIZE_OVERFLOW = 0x0Full << (sizeof(uint64_t) * 8 - 8);//0x0F000000;
constexpr uint64_t STATE_HAS_SIZE = 1ull << (sizeof(uint64_t) * 8 - 1);//0x8000000000000000;
constexpr uint64_t STATE_IS_CHUNKED = 1ull << (sizeof(uint64_t) * 8 - 2);//0x4000000000000000;
constexpr uint64_t STATE_IS_CHUNKED_EXTENSION = 1ull << (sizeof(uint64_t) * 8 - 3);//0x2000000000000000;
constexpr uint64_t STATE_SIZE_MASK = ~(STATE_HAS_SIZE | STATE_IS_CHUNKED | STATE_IS_CHUNKED_EXTENSION);//0x1FFFFFFFFFFFFFFF;
constexpr uint64_t STATE_IS_ERROR = ~0ull;//0xFFFFFFFFFFFFFFFF;
constexpr uint64_t STATE_SIZE_OVERFLOW = 0x0Full << (sizeof(uint64_t) * 8 - 8);//0x0F00000000000000;

inline unsigned int chunkSize(uint64_t state) {
return state & STATE_SIZE_MASK;
}

inline bool isParsingChunkedExtension(uint64_t state) {
return (state & STATE_IS_CHUNKED_EXTENSION) != 0;
}

/* Reads hex number until CR or out of data to consume. Updates state. Returns bytes consumed. */
inline void consumeHexNumber(std::string_view &data, uint64_t &state) {
/* Consume everything higher than 32 */
while (data.length() && data[0] > 32) {

unsigned char digit = (unsigned char)data[0];
if (digit >= 'a') {
digit = (unsigned char) (digit - ('a' - ':'));
} else if (digit >= 'A') {
digit = (unsigned char) (digit - ('A' - ':'));
}

unsigned int number = ((unsigned int) digit - (unsigned int) '0');
/* RFC 9110: 5.5 Field Values (TLDR; anything above 31 is allowed \r, \n ; depending on context)*/

if (number > 16 || (chunkSize(state) & STATE_SIZE_OVERFLOW)) {
state = STATE_IS_ERROR;
return;
}
if(!isParsingChunkedExtension(state)){
/* Consume everything higher than 32 and not ; (extension)*/
while (data.length() && data[0] > 32 && data[0] != ';') {

unsigned char digit = (unsigned char)data[0];
if (digit >= 'a') {
digit = (unsigned char) (digit - ('a' - ':'));
} else if (digit >= 'A') {
digit = (unsigned char) (digit - ('A' - ':'));
}

// extract state bits
uint64_t bits = /*state &*/ STATE_IS_CHUNKED;
unsigned int number = ((unsigned int) digit - (unsigned int) '0');

state = (state & STATE_SIZE_MASK) * 16ull + number;
if (number > 16 || (chunkSize(state) & STATE_SIZE_OVERFLOW)) {
state = STATE_IS_ERROR;
return;
}

state |= bits;
data.remove_prefix(1);
}
/* Consume everything not /n */
while (data.length() && data[0] != '\n') {
data.remove_prefix(1);
// extract state bits
uint64_t bits = /*state &*/ STATE_IS_CHUNKED;

state = (state & STATE_SIZE_MASK) * 16ull + number;

state |= bits;
data.remove_prefix(1);
}
}
/* Now we stand on \n so consume it and enable size */
if (data.length()) {
state += 2; // include the two last /r/n
state |= STATE_HAS_SIZE | STATE_IS_CHUNKED;
data.remove_prefix(1);

auto len = data.length();
if(len) {
// consume extension
if(data[0] == ';' || isParsingChunkedExtension(state)) {
// mark that we are parsing chunked extension
state |= STATE_IS_CHUNKED_EXTENSION;
/* we got chunk extension lets remove it*/
while(data.length()) {
if(data[0] == '\r') {
// we are done parsing extension
state &= ~STATE_IS_CHUNKED_EXTENSION;
break;
}
/* RFC 9110: Token format (TLDR; anything bellow 32 is not allowed)
* TODO: add support for quoted-strings values (RFC 9110: 3.2.6. Quoted-String)
* Example of chunked encoding with extensions:
*
* 4;key=value\r\n
* Wiki\r\n
* 5;foo=bar;baz=quux\r\n
* pedia\r\n
* 0\r\n
* \r\n
*
* The chunk size is in hex (4, 5, 0), followed by optional
* semicolon-separated extensions. Extensions consist of a key
* (token) and optional value. The value may be a token or a
* quoted string. The chunk data follows the CRLF after the
* extensions and must be exactly the size specified.
*
* RFC 7230 Section 4.1.1 defines chunk extensions as:
* chunk-ext = *( ";" chunk-ext-name [ "=" chunk-ext-val ] )
* chunk-ext-name = token
* chunk-ext-val = token / quoted-string
*/
if(data[0] <= 32) {
state = STATE_IS_ERROR;
return;
}

data.remove_prefix(1);
}
}
if(data.length() >= 2) {
/* Consume \r\n */
if((data[0] != '\r' || data[1] != '\n')) {
state = STATE_IS_ERROR;
return;
}
state += 2; // include the two last /r/n
state |= STATE_HAS_SIZE | STATE_IS_CHUNKED;

data.remove_prefix(2);
}
}
// short read
}

inline void decChunkSize(uint64_t &state, unsigned int by) {
Expand Down
20 changes: 9 additions & 11 deletions packages/bun-uws/src/HttpContext.h
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ struct HttpContext {
// if we are SSL we need to handle the handshake properly
us_socket_context_on_handshake(SSL, getSocketContext(), [](us_socket_t *s, int success, struct us_bun_verify_error_t verify_error, void* custom_data) {
// if we are closing or already closed, we don't need to do anything
if (!us_socket_is_closed(SSL, s) && !us_socket_is_shut_down(SSL, s)) {
if (!us_socket_is_closed(SSL, s)) {
HttpContextData<SSL> *httpContextData = getSocketContextDataS(s);
httpContextData->flags.isAuthorized = success;
if(httpContextData->flags.rejectUnauthorized) {
Expand Down Expand Up @@ -123,11 +123,8 @@ struct HttpContext {

/* Call filter */
HttpContextData<SSL> *httpContextData = getSocketContextDataS(s);
if(httpContextData->flags.isParsingHttp) {
if(httpContextData->onClientError) {
httpContextData->onClientError(SSL, s,uWS::HTTP_PARSER_ERROR_INVALID_EOF, nullptr, 0);
}
}


for (auto &f : httpContextData->filterHandlers) {
f((HttpResponse<SSL> *) s, -1);
}
Expand All @@ -149,6 +146,7 @@ struct HttpContext {

/* Handle HTTP data streams */
us_socket_context_on_data(SSL, getSocketContext(), [](us_socket_t *s, char *data, int length) {

// ref the socket to make sure we process it entirely before it is closed
us_socket_ref(s);

Expand All @@ -172,7 +170,6 @@ struct HttpContext {

/* Mark that we are inside the parser now */
httpContextData->flags.isParsingHttp = true;

// clients need to know the cursor after http parse, not servers!
// how far did we read then? we need to know to continue with websocket parsing data? or?

Expand All @@ -182,7 +179,8 @@ struct HttpContext {
#endif

/* The return value is entirely up to us to interpret. The HttpParser cares only for whether the returned value is DIFFERENT from passed user */
auto result = httpResponseData->consumePostPadded(httpContextData->flags.requireHostHeader,data, (unsigned int) length, s, proxyParser, [httpContextData](void *s, HttpRequest *httpRequest) -> void * {

auto result = httpResponseData->consumePostPadded(httpContextData->maxHeaderSize, httpContextData->flags.requireHostHeader,httpContextData->flags.useStrictMethodValidation, data, (unsigned int) length, s, proxyParser, [httpContextData](void *s, HttpRequest *httpRequest) -> void * {
/* For every request we reset the timeout and hang until user makes action */
/* Warning: if we are in shutdown state, resetting the timer is a security issue! */
us_socket_timeout(SSL, (us_socket_t *) s, 0);
Expand All @@ -201,6 +199,7 @@ struct HttpContext {

/* Mark pending request and emit it */
httpResponseData->state = HttpResponseData<SSL>::HTTP_RESPONSE_PENDING;


/* Mark this response as connectionClose if ancient or connection: close */
if (httpRequest->isAncient() || httpRequest->getHeader("connection").length() == 5) {
Expand All @@ -209,7 +208,6 @@ struct HttpContext {

httpResponseData->fromAncientRequest = httpRequest->isAncient();


/* Select the router based on SNI (only possible for SSL) */
auto *selectedRouter = &httpContextData->router;
if constexpr (SSL) {
Expand Down Expand Up @@ -261,7 +259,7 @@ struct HttpContext {
}, [httpResponseData](void *user, std::string_view data, bool fin) -> void * {
/* We always get an empty chunk even if there is no data */
if (httpResponseData->inStream) {

/* Todo: can this handle timeout for non-post as well? */
if (fin) {
/* If we just got the last chunk (or empty chunk), disable timeout */
Expand Down Expand Up @@ -299,7 +297,7 @@ struct HttpContext {
});

auto httpErrorStatusCode = result.httpErrorStatusCode();

/* Mark that we are no longer parsing Http */
httpContextData->flags.isParsingHttp = false;
/* If we got fullptr that means the parser wants us to close the socket from error (same as calling the errorHandler) */
Expand Down
2 changes: 2 additions & 0 deletions packages/bun-uws/src/HttpContextData.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ struct HttpFlags {
bool usingCustomExpectHandler: 1 = false;
bool requireHostHeader: 1 = true;
bool isAuthorized: 1 = false;
bool useStrictMethodValidation: 1 = false;
};

template <bool SSL>
Expand Down Expand Up @@ -63,6 +64,7 @@ struct alignas(16) HttpContextData {
OnClientErrorCallback onClientError = nullptr;

HttpFlags flags;
uint64_t maxHeaderSize = 0; // 0 means no limit

// TODO: SNI
void clearRoutes() {
Expand Down
Loading