Skip to content

Commit 0a0205b

Browse files
cirospaciari190n
andauthored
compat(node:http) more (#19527)
Co-authored-by: cirospaciari <[email protected]> Co-authored-by: 190n <[email protected]>
1 parent de88da6 commit 0a0205b

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+2136
-135
lines changed

packages/bun-uws/src/App.h

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,7 @@ struct TemplatedApp {
249249
}
250250

251251
static TemplatedApp<SSL>* create(SocketContextOptions options = {}) {
252+
252253
auto* httpContext = HttpContext<SSL>::create(Loop::get(), options);
253254
if (!httpContext) {
254255
return nullptr;
@@ -628,8 +629,14 @@ struct TemplatedApp {
628629
return std::move(*this);
629630
}
630631

631-
TemplatedApp &&setRequireHostHeader(bool value) {
632-
httpContext->getSocketContextData()->flags.requireHostHeader = value;
632+
TemplatedApp &&setFlags(bool requireHostHeader, bool useStrictMethodValidation) {
633+
httpContext->getSocketContextData()->flags.requireHostHeader = requireHostHeader;
634+
httpContext->getSocketContextData()->flags.useStrictMethodValidation = useStrictMethodValidation;
635+
return std::move(*this);
636+
}
637+
638+
TemplatedApp &&setMaxHTTPHeaderSize(uint64_t maxHeaderSize) {
639+
httpContext->getSocketContextData()->maxHeaderSize = maxHeaderSize;
633640
return std::move(*this);
634641
}
635642

packages/bun-uws/src/ChunkedEncoding.h

Lines changed: 90 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -29,53 +29,110 @@
2929

3030
namespace uWS {
3131

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

3839
inline unsigned int chunkSize(uint64_t state) {
3940
return state & STATE_SIZE_MASK;
4041
}
4142

43+
inline bool isParsingChunkedExtension(uint64_t state) {
44+
return (state & STATE_IS_CHUNKED_EXTENSION) != 0;
45+
}
46+
4247
/* Reads hex number until CR or out of data to consume. Updates state. Returns bytes consumed. */
4348
inline void consumeHexNumber(std::string_view &data, uint64_t &state) {
44-
/* Consume everything higher than 32 */
45-
while (data.length() && data[0] > 32) {
46-
47-
unsigned char digit = (unsigned char)data[0];
48-
if (digit >= 'a') {
49-
digit = (unsigned char) (digit - ('a' - ':'));
50-
} else if (digit >= 'A') {
51-
digit = (unsigned char) (digit - ('A' - ':'));
52-
}
5349

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

56-
if (number > 16 || (chunkSize(state) & STATE_SIZE_OVERFLOW)) {
57-
state = STATE_IS_ERROR;
58-
return;
59-
}
52+
if(!isParsingChunkedExtension(state)){
53+
/* Consume everything higher than 32 and not ; (extension)*/
54+
while (data.length() && data[0] > 32 && data[0] != ';') {
55+
56+
unsigned char digit = (unsigned char)data[0];
57+
if (digit >= 'a') {
58+
digit = (unsigned char) (digit - ('a' - ':'));
59+
} else if (digit >= 'A') {
60+
digit = (unsigned char) (digit - ('A' - ':'));
61+
}
6062

61-
// extract state bits
62-
uint64_t bits = /*state &*/ STATE_IS_CHUNKED;
63+
unsigned int number = ((unsigned int) digit - (unsigned int) '0');
6364

64-
state = (state & STATE_SIZE_MASK) * 16ull + number;
65+
if (number > 16 || (chunkSize(state) & STATE_SIZE_OVERFLOW)) {
66+
state = STATE_IS_ERROR;
67+
return;
68+
}
6569

66-
state |= bits;
67-
data.remove_prefix(1);
68-
}
69-
/* Consume everything not /n */
70-
while (data.length() && data[0] != '\n') {
71-
data.remove_prefix(1);
70+
// extract state bits
71+
uint64_t bits = /*state &*/ STATE_IS_CHUNKED;
72+
73+
state = (state & STATE_SIZE_MASK) * 16ull + number;
74+
75+
state |= bits;
76+
data.remove_prefix(1);
77+
}
7278
}
73-
/* Now we stand on \n so consume it and enable size */
74-
if (data.length()) {
75-
state += 2; // include the two last /r/n
76-
state |= STATE_HAS_SIZE | STATE_IS_CHUNKED;
77-
data.remove_prefix(1);
79+
80+
auto len = data.length();
81+
if(len) {
82+
// consume extension
83+
if(data[0] == ';' || isParsingChunkedExtension(state)) {
84+
// mark that we are parsing chunked extension
85+
state |= STATE_IS_CHUNKED_EXTENSION;
86+
/* we got chunk extension lets remove it*/
87+
while(data.length()) {
88+
if(data[0] == '\r') {
89+
// we are done parsing extension
90+
state &= ~STATE_IS_CHUNKED_EXTENSION;
91+
break;
92+
}
93+
/* RFC 9110: Token format (TLDR; anything bellow 32 is not allowed)
94+
* TODO: add support for quoted-strings values (RFC 9110: 3.2.6. Quoted-String)
95+
* Example of chunked encoding with extensions:
96+
*
97+
* 4;key=value\r\n
98+
* Wiki\r\n
99+
* 5;foo=bar;baz=quux\r\n
100+
* pedia\r\n
101+
* 0\r\n
102+
* \r\n
103+
*
104+
* The chunk size is in hex (4, 5, 0), followed by optional
105+
* semicolon-separated extensions. Extensions consist of a key
106+
* (token) and optional value. The value may be a token or a
107+
* quoted string. The chunk data follows the CRLF after the
108+
* extensions and must be exactly the size specified.
109+
*
110+
* RFC 7230 Section 4.1.1 defines chunk extensions as:
111+
* chunk-ext = *( ";" chunk-ext-name [ "=" chunk-ext-val ] )
112+
* chunk-ext-name = token
113+
* chunk-ext-val = token / quoted-string
114+
*/
115+
if(data[0] <= 32) {
116+
state = STATE_IS_ERROR;
117+
return;
118+
}
119+
120+
data.remove_prefix(1);
121+
}
122+
}
123+
if(data.length() >= 2) {
124+
/* Consume \r\n */
125+
if((data[0] != '\r' || data[1] != '\n')) {
126+
state = STATE_IS_ERROR;
127+
return;
128+
}
129+
state += 2; // include the two last /r/n
130+
state |= STATE_HAS_SIZE | STATE_IS_CHUNKED;
131+
132+
data.remove_prefix(2);
133+
}
78134
}
135+
// short read
79136
}
80137

81138
inline void decChunkSize(uint64_t &state, unsigned int by) {

packages/bun-uws/src/HttpContext.h

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ struct HttpContext {
7171
// if we are SSL we need to handle the handshake properly
7272
us_socket_context_on_handshake(SSL, getSocketContext(), [](us_socket_t *s, int success, struct us_bun_verify_error_t verify_error, void* custom_data) {
7373
// if we are closing or already closed, we don't need to do anything
74-
if (!us_socket_is_closed(SSL, s) && !us_socket_is_shut_down(SSL, s)) {
74+
if (!us_socket_is_closed(SSL, s)) {
7575
HttpContextData<SSL> *httpContextData = getSocketContextDataS(s);
7676
httpContextData->flags.isAuthorized = success;
7777
if(httpContextData->flags.rejectUnauthorized) {
@@ -123,11 +123,8 @@ struct HttpContext {
123123

124124
/* Call filter */
125125
HttpContextData<SSL> *httpContextData = getSocketContextDataS(s);
126-
if(httpContextData->flags.isParsingHttp) {
127-
if(httpContextData->onClientError) {
128-
httpContextData->onClientError(SSL, s,uWS::HTTP_PARSER_ERROR_INVALID_EOF, nullptr, 0);
129-
}
130-
}
126+
127+
131128
for (auto &f : httpContextData->filterHandlers) {
132129
f((HttpResponse<SSL> *) s, -1);
133130
}
@@ -149,6 +146,7 @@ struct HttpContext {
149146

150147
/* Handle HTTP data streams */
151148
us_socket_context_on_data(SSL, getSocketContext(), [](us_socket_t *s, char *data, int length) {
149+
152150
// ref the socket to make sure we process it entirely before it is closed
153151
us_socket_ref(s);
154152

@@ -172,7 +170,6 @@ struct HttpContext {
172170

173171
/* Mark that we are inside the parser now */
174172
httpContextData->flags.isParsingHttp = true;
175-
176173
// clients need to know the cursor after http parse, not servers!
177174
// how far did we read then? we need to know to continue with websocket parsing data? or?
178175

@@ -182,7 +179,8 @@ struct HttpContext {
182179
#endif
183180

184181
/* The return value is entirely up to us to interpret. The HttpParser cares only for whether the returned value is DIFFERENT from passed user */
185-
auto result = httpResponseData->consumePostPadded(httpContextData->flags.requireHostHeader,data, (unsigned int) length, s, proxyParser, [httpContextData](void *s, HttpRequest *httpRequest) -> void * {
182+
183+
auto result = httpResponseData->consumePostPadded(httpContextData->maxHeaderSize, httpContextData->flags.requireHostHeader,httpContextData->flags.useStrictMethodValidation, data, (unsigned int) length, s, proxyParser, [httpContextData](void *s, HttpRequest *httpRequest) -> void * {
186184
/* For every request we reset the timeout and hang until user makes action */
187185
/* Warning: if we are in shutdown state, resetting the timer is a security issue! */
188186
us_socket_timeout(SSL, (us_socket_t *) s, 0);
@@ -201,6 +199,7 @@ struct HttpContext {
201199

202200
/* Mark pending request and emit it */
203201
httpResponseData->state = HttpResponseData<SSL>::HTTP_RESPONSE_PENDING;
202+
204203

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

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

212-
213211
/* Select the router based on SNI (only possible for SSL) */
214212
auto *selectedRouter = &httpContextData->router;
215213
if constexpr (SSL) {
@@ -261,7 +259,7 @@ struct HttpContext {
261259
}, [httpResponseData](void *user, std::string_view data, bool fin) -> void * {
262260
/* We always get an empty chunk even if there is no data */
263261
if (httpResponseData->inStream) {
264-
262+
265263
/* Todo: can this handle timeout for non-post as well? */
266264
if (fin) {
267265
/* If we just got the last chunk (or empty chunk), disable timeout */
@@ -299,7 +297,7 @@ struct HttpContext {
299297
});
300298

301299
auto httpErrorStatusCode = result.httpErrorStatusCode();
302-
300+
303301
/* Mark that we are no longer parsing Http */
304302
httpContextData->flags.isParsingHttp = false;
305303
/* If we got fullptr that means the parser wants us to close the socket from error (same as calling the errorHandler) */

packages/bun-uws/src/HttpContextData.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ struct HttpFlags {
3333
bool usingCustomExpectHandler: 1 = false;
3434
bool requireHostHeader: 1 = true;
3535
bool isAuthorized: 1 = false;
36+
bool useStrictMethodValidation: 1 = false;
3637
};
3738

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

6566
HttpFlags flags;
67+
uint64_t maxHeaderSize = 0; // 0 means no limit
6668

6769
// TODO: SNI
6870
void clearRoutes() {

0 commit comments

Comments
 (0)