Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 1 addition & 2 deletions packages/bun-uws/src/App.h
Original file line number Diff line number Diff line change
Expand Up @@ -767,9 +767,8 @@ struct TemplatedApp {

/* lenientHttpFlags: bit 0 = lenient header values (llhttp LENIENT_HEADERS),
* bit 1 = lenient transfer-encoding (llhttp LENIENT_TRANSFER_ENCODING). */
TemplatedApp &&setFlags(bool requireHostHeader, bool useStrictMethodValidation, uint8_t lenientHttpFlags, bool httpAllowHalfOpen) {
TemplatedApp &&setFlags(bool requireHostHeader, uint8_t lenientHttpFlags, bool httpAllowHalfOpen) {
httpContext->getSocketContextData()->flags.requireHostHeader = requireHostHeader;
httpContext->getSocketContextData()->flags.useStrictMethodValidation = useStrictMethodValidation;
httpContext->getSocketContextData()->flags.useInsecureHTTPParser = (lenientHttpFlags & 1) != 0;
httpContext->getSocketContextData()->flags.useLenientTransferEncoding = (lenientHttpFlags & 2) != 0;
httpContext->getSocketContextData()->flags.httpAllowHalfOpen = httpAllowHalfOpen;
Expand Down
22 changes: 12 additions & 10 deletions packages/bun-uws/src/HttpContext.h
Original file line number Diff line number Diff line change
Expand Up @@ -383,7 +383,7 @@ struct HttpContext {
nodeHttpRequestTrailers = &nodeHttpResponseData->nodeHttpRequestTrailers;
}

auto result = httpResponseData->template consumePostPadded<IsNodeHttp>(httpContextData->maxHeaderSize, httpContextData->maxHeadersCount, httpResponseData->isConnectRequest, httpContextData->flags.requireHostHeader,httpContextData->flags.useStrictMethodValidation, httpContextData->flags.useInsecureHTTPParser, httpContextData->flags.useLenientTransferEncoding, nodeHttpRequestTrailers, &httpResponseData->chunkedExtensionsByteCount, data, (unsigned int) length, s, [httpContextData](void *s, HttpRequest *httpRequest) -> void * {
auto result = httpResponseData->template consumePostPadded<IsNodeHttp>(httpContextData->maxHeaderSize, httpContextData->maxHeadersCount, httpResponseData->isConnectRequest, httpContextData->flags.requireHostHeader, httpContextData->flags.useInsecureHTTPParser, httpContextData->flags.useLenientTransferEncoding, nodeHttpRequestTrailers, &httpResponseData->chunkedExtensionsByteCount, data, (unsigned int) length, s, [httpContextData](void *s, HttpRequest *httpRequest) -> void * {

HttpResponseData<SSL> *httpResponseData = (HttpResponseData<SSL> *) us_socket_ext((us_socket_t *) s);

Expand Down Expand Up @@ -527,13 +527,16 @@ struct HttpContext {
((AsyncSocket<SSL> *) s)->cork();
}

/* Route the method and URL */
selectedRouter->getUserData() = {(HttpResponse<SSL> *) s, httpRequest};
if (!selectedRouter->route(httpRequest->getCaseSensitiveMethod(), httpRequest->getUrlForRouting())) {
/* We have to force close this socket as we have no handler for it.
* close() first sends the responses to earlier requests of this read. */
((AsyncSocket<SSL> *) s)->close();
return nullptr;
/* Bun.serve routes only the methods of HTTP_METHOD_NAMES. Any other method gets 501 (RFC 9110 15.6.2), and a
* request that no route takes gets 404, as over HTTP/2 and HTTP/3. No handler reads the content of such a request,
* so the connection closes after the answer. node:http's parser is strict: it answers 400 to an unknown method. */
if (!IsNodeHttp && methodIdFromWire(httpRequest->getCaseSensitiveMethod()) == HTTP_METHOD_NONE) [[unlikely]] {
((HttpResponse<SSL> *) s)->writeStatus("501 Not Implemented")->end({}, true);
} else {
selectedRouter->getUserData() = {(HttpResponse<SSL> *) s, httpRequest};
if (!selectedRouter->route(httpRequest->getCaseSensitiveMethod(), httpRequest->getUrlForRouting())) {
Comment thread
robobun marked this conversation as resolved.
((HttpResponse<SSL> *) s)->writeStatus("404 Not Found")->end({}, true);
}
}

/* First of all we need to check if this socket was deleted due to upgrade */
Expand Down Expand Up @@ -1142,8 +1145,7 @@ struct HttpContext {
std::span<const std::string_view> methods;
std::string method_buffer;
std::string_view method_sv_buffer;
// When it's NOT node:http, allow the uWS default precedence ordering.
if (method == "*" && !httpContextData->flags.useStrictMethodValidation) {
if (method == "*") {
methods = HTTP_METHOD_NAMES;
} else {
method_buffer = std::string(method);
Expand Down
1 change: 0 additions & 1 deletion packages/bun-uws/src/HttpContextData.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ struct HttpFlags {
bool rejectUnauthorized: 1 = false;
bool usingCustomExpectHandler: 1 = false;
bool requireHostHeader: 1 = true;
bool useStrictMethodValidation: 1 = false;
/* node:http parser leniency. Two llhttp lenient bits: useInsecureHTTPParser = LENIENT_HEADERS
* ("relaxed"+"insecure"); useLenientTransferEncoding = LENIENT_TRANSFER_ENCODING ("insecure"
* only). TE+CL conflict, chunked-size/CRLF, version, header-token checks stay enforced. */
Expand Down
10 changes: 5 additions & 5 deletions packages/bun-uws/src/HttpParser.h
Original file line number Diff line number Diff line change
Expand Up @@ -1192,7 +1192,7 @@ struct HttpResponseData;

/* This is the only caller of getHeaders and is thus the deepest part of the parser. */
template <bool ConsumeMinimally, bool IsNodeHttp>
HttpParserResult fenceAndConsumePostPadded(uint64_t maxHeaderSize, uint32_t maxHeadersCount, bool& isConnectRequest, bool requireHostHeader, bool useStrictMethodValidation, bool useInsecureHTTPParser, bool useLenientTransferEncoding, std::string *nodeHttpRequestTrailers, uint64_t *chunkedExtensionsByteCount, char *data, unsigned int length, void *user, HttpRequest *req, MoveOnlyFunction<void *(void *, HttpRequest *)> &requestHandler, MoveOnlyFunction<void *(void *, std::string_view, bool)> &dataHandler) {
HttpParserResult fenceAndConsumePostPadded(uint64_t maxHeaderSize, uint32_t maxHeadersCount, bool& isConnectRequest, bool requireHostHeader, bool useInsecureHTTPParser, bool useLenientTransferEncoding, std::string *nodeHttpRequestTrailers, uint64_t *chunkedExtensionsByteCount, char *data, unsigned int length, void *user, HttpRequest *req, MoveOnlyFunction<void *(void *, HttpRequest *)> &requestHandler, MoveOnlyFunction<void *(void *, std::string_view, bool)> &dataHandler) {

/* How much data we CONSUMED (to throw away) */
unsigned int consumedTotal = 0;
Expand Down Expand Up @@ -1245,7 +1245,7 @@ struct HttpResponseData;
return HttpParserResult::success(consumedTotal + length, user);
}
bool isConnectRequestLine = false;
auto result = getHeaders(data, data + length, req->headers, req->ancientHttp, isConnectRequestLine, useStrictMethodValidation, useInsecureHTTPParser, maxHeaderSize, maxHeaderFields);
auto result = getHeaders(data, data + length, req->headers, req->ancientHttp, isConnectRequestLine, /* useStrictMethodValidation: */ IsNodeHttp, useInsecureHTTPParser, maxHeaderSize, maxHeaderFields);
if(result.isError()) {
return result;
}
Expand Down Expand Up @@ -1575,7 +1575,7 @@ struct HttpResponseData;
* head, or WHOLE_READ when the request declared a body: the body is not parsed
* and is not the caller's. The handler may have destroyed this parser. */
template <bool IsNodeHttp>
HttpParserResult consumePostPadded(uint64_t maxHeaderSize, uint32_t maxHeadersCount, bool& isConnectRequest, bool requireHostHeader, bool useStrictMethodValidation, bool useInsecureHTTPParser, bool useLenientTransferEncoding, std::string *nodeHttpRequestTrailers, uint64_t *chunkedExtensionsByteCount, char *data, unsigned int length, void *user, MoveOnlyFunction<void *(void *, HttpRequest *)> &&requestHandler, MoveOnlyFunction<void *(void *, std::string_view, bool)> &&dataHandler) {
HttpParserResult consumePostPadded(uint64_t maxHeaderSize, uint32_t maxHeadersCount, bool& isConnectRequest, bool requireHostHeader, bool useInsecureHTTPParser, bool useLenientTransferEncoding, std::string *nodeHttpRequestTrailers, uint64_t *chunkedExtensionsByteCount, char *data, unsigned int length, void *user, MoveOnlyFunction<void *(void *, HttpRequest *)> &&requestHandler, MoveOnlyFunction<void *(void *, std::string_view, bool)> &&dataHandler) {
char *const readStart = data;
/* The fallback buffer may not exceed the configured per-request header
* limit (per-server maxHeaderSize can raise it above the default). */
Expand Down Expand Up @@ -1669,7 +1669,7 @@ struct HttpResponseData;
}

// break here on break
HttpParserResult consumed = fenceAndConsumePostPadded<true, IsNodeHttp>(maxHeaderSize, maxHeadersCount, isConnectRequest, requireHostHeader, useStrictMethodValidation, useInsecureHTTPParser, useLenientTransferEncoding, nodeHttpRequestTrailers, chunkedExtensionsByteCount, reassembled.data(), (unsigned int) reassembled.length(), user, &req, requestHandler, dataHandler);
HttpParserResult consumed = fenceAndConsumePostPadded<true, IsNodeHttp>(maxHeaderSize, maxHeadersCount, isConnectRequest, requireHostHeader, useInsecureHTTPParser, useLenientTransferEncoding, nodeHttpRequestTrailers, chunkedExtensionsByteCount, reassembled.data(), (unsigned int) reassembled.length(), user, &req, requestHandler, dataHandler);
/* Return data will be different than user if we are upgraded to WebSocket or have an error.
* The parser can be gone by then: no member is touched before this return. */
if (consumed.returnedData != user) {
Expand Down Expand Up @@ -1771,7 +1771,7 @@ struct HttpResponseData;
}
}

HttpParserResult consumed = fenceAndConsumePostPadded<false, IsNodeHttp>(maxHeaderSize, maxHeadersCount, isConnectRequest, requireHostHeader, useStrictMethodValidation, useInsecureHTTPParser, useLenientTransferEncoding, nodeHttpRequestTrailers, chunkedExtensionsByteCount, data, length, user, &req, requestHandler, dataHandler);
HttpParserResult consumed = fenceAndConsumePostPadded<false, IsNodeHttp>(maxHeaderSize, maxHeadersCount, isConnectRequest, requireHostHeader, useInsecureHTTPParser, useLenientTransferEncoding, nodeHttpRequestTrailers, chunkedExtensionsByteCount, data, length, user, &req, requestHandler, dataHandler);
/* Return data will be different than user if we are upgraded to WebSocket or have an error */
if (consumed.returnedData != user) {
/* A body or a fallback head ahead of this request moved data forward. */
Expand Down
2 changes: 0 additions & 2 deletions src/js/internal/http.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ const { setServerCustomOptions, setServerAppFlags, setServerMaxHeadersCount, dra
setServerCustomOptions: (
server: any,
requireHostHeader: boolean,
useStrictMethodValidation: boolean,
lenientHttpFlags: number,
maxHeaderSize: number,
onClientError: (ssl: boolean, socket: any, errorCode: number, rawPacket: ArrayBuffer) => undefined,
Expand All @@ -16,7 +15,6 @@ const { setServerCustomOptions, setServerAppFlags, setServerMaxHeadersCount, dra
setServerAppFlags: (
server: any,
requireHostHeader: boolean,
useStrictMethodValidation: boolean,
lenientHttpFlags: number,
httpAllowHalfOpen: boolean,
) => void;
Expand Down
4 changes: 1 addition & 3 deletions src/js/node/_http_server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1176,14 +1176,12 @@ Server.prototype[kRealListen] = function (tls, port, host, socketPath, reusePort
};

// Pushes the per-server parser/handler options down to the native listener.
// Always sets strict method validation, for node.js compatibility.
function applyServerCustomOptions(server: Server) {
const handle = server[serverSymbol];
if (!handle) return;
setServerCustomOptions(
handle,
server.requireHostHeader,
true,
serverLenientFlags(server),
typeof server.maxHeaderSize !== "undefined" ? server.maxHeaderSize : getMaxHTTPHeaderSize(),
onServerClientError.bind(server, server[kListenerGeneration]),
Expand Down Expand Up @@ -1213,7 +1211,7 @@ function httpAllowHalfOpenSet(this: Server, value) {
const next = !!value;
if (previous === next) return;
const handle = this[serverSymbol];
if (handle) setServerAppFlags(handle, this.requireHostHeader, true, serverLenientFlags(this), next);
if (handle) setServerAppFlags(handle, this.requireHostHeader, serverLenientFlags(this), next);
}

// Node.js keeps httpAllowHalfOpen as an own enumerable property of the server.
Expand Down
28 changes: 13 additions & 15 deletions src/jsc/bindings/NodeHTTP.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ using namespace JSC;
using namespace WebCore;

BUN_DECLARE_HOST_FUNCTION(Bun__drainMicrotasksFromJS);
extern "C" EncodedJSValue Server__setAppFlags(JSC::JSGlobalObject*, EncodedJSValue, bool require_host_header, bool use_strict_method_validation, uint8_t lenient_http_flags, bool http_allow_half_open);
extern "C" EncodedJSValue Server__setAppFlags(JSC::JSGlobalObject*, EncodedJSValue, bool require_host_header, uint8_t lenient_http_flags, bool http_allow_half_open);
extern "C" EncodedJSValue Server__setOnClientError(JSC::JSGlobalObject*, EncodedJSValue, EncodedJSValue);
extern "C" EncodedJSValue Server__setOnConnection(JSC::JSGlobalObject*, EncodedJSValue, EncodedJSValue);
extern "C" EncodedJSValue Server__setMaxHTTPHeaderSize(JSC::JSGlobalObject*, EncodedJSValue, uint64_t);
Expand Down Expand Up @@ -868,23 +868,22 @@ JSC_DEFINE_HOST_FUNCTION(jsHTTPSetCustomOptions, (JSGlobalObject * globalObject,
{
auto& vm = JSC::getVM(globalObject);
auto scope = DECLARE_THROW_SCOPE(vm);
ASSERT(callFrame->argumentCount() == 8);
ASSERT(callFrame->argumentCount() == 7);
// This is an internal binding.
JSValue serverValue = callFrame->uncheckedArgument(0);
JSValue requireHostHeader = callFrame->uncheckedArgument(1);
JSValue useStrictMethodValidation = callFrame->uncheckedArgument(2);
JSValue lenientHttpFlags = callFrame->uncheckedArgument(3);
JSValue maxHeaderSize = callFrame->uncheckedArgument(4);
JSValue callback = callFrame->uncheckedArgument(5);
JSValue onConnectionCallback = callFrame->argument(6);
JSValue httpAllowHalfOpen = callFrame->argument(7);
JSValue lenientHttpFlags = callFrame->uncheckedArgument(2);
JSValue maxHeaderSize = callFrame->uncheckedArgument(3);
JSValue callback = callFrame->uncheckedArgument(4);
JSValue onConnectionCallback = callFrame->argument(5);
JSValue httpAllowHalfOpen = callFrame->argument(6);

double maxHeaderSizeNumber = maxHeaderSize.toNumber(globalObject);
RETURN_IF_EXCEPTION(scope, {});
int32_t lenientBits = lenientHttpFlags.toInt32(globalObject);
RETURN_IF_EXCEPTION(scope, {});

Server__setAppFlags(globalObject, JSValue::encode(serverValue), requireHostHeader.toBoolean(globalObject), useStrictMethodValidation.toBoolean(globalObject), static_cast<uint8_t>(lenientBits & 0x3), httpAllowHalfOpen.toBoolean(globalObject));
Server__setAppFlags(globalObject, JSValue::encode(serverValue), requireHostHeader.toBoolean(globalObject), static_cast<uint8_t>(lenientBits & 0x3), httpAllowHalfOpen.toBoolean(globalObject));
RETURN_IF_EXCEPTION(scope, {});

Server__setMaxHTTPHeaderSize(globalObject, JSValue::encode(serverValue), maxHTTPHeaderSizeFromNumber(maxHeaderSizeNumber));
Expand All @@ -908,18 +907,17 @@ JSC_DEFINE_HOST_FUNCTION(jsHTTPSetAppFlags, (JSGlobalObject * globalObject, Call
{
auto& vm = JSC::getVM(globalObject);
auto scope = DECLARE_THROW_SCOPE(vm);
ASSERT(callFrame->argumentCount() == 5);
ASSERT(callFrame->argumentCount() == 4);
// This is an internal binding.
JSValue serverValue = callFrame->uncheckedArgument(0);
JSValue requireHostHeader = callFrame->uncheckedArgument(1);
JSValue useStrictMethodValidation = callFrame->uncheckedArgument(2);
JSValue lenientHttpFlags = callFrame->uncheckedArgument(3);
JSValue httpAllowHalfOpen = callFrame->argument(4);
JSValue lenientHttpFlags = callFrame->uncheckedArgument(2);
JSValue httpAllowHalfOpen = callFrame->argument(3);

int32_t lenientBits = lenientHttpFlags.toInt32(globalObject);
RETURN_IF_EXCEPTION(scope, {});

Server__setAppFlags(globalObject, JSValue::encode(serverValue), requireHostHeader.toBoolean(globalObject), useStrictMethodValidation.toBoolean(globalObject), static_cast<uint8_t>(lenientBits & 0x3), httpAllowHalfOpen.toBoolean(globalObject));
Server__setAppFlags(globalObject, JSValue::encode(serverValue), requireHostHeader.toBoolean(globalObject), static_cast<uint8_t>(lenientBits & 0x3), httpAllowHalfOpen.toBoolean(globalObject));
RETURN_IF_EXCEPTION(scope, {});

return JSValue::encode(jsUndefined());
Expand Down Expand Up @@ -951,7 +949,7 @@ JSValue createNodeHTTPInternalBinding(Zig::GlobalObject* globalObject)
JSC::JSFunction::create(vm, globalObject, 2, "setServerCustomOptions"_s, jsHTTPSetCustomOptions, ImplementationVisibility::Public), 0);
obj->putDirect(
vm, JSC::PropertyName(JSC::Identifier::fromString(vm, "setServerAppFlags"_s)),
JSC::JSFunction::create(vm, globalObject, 5, "setServerAppFlags"_s, jsHTTPSetAppFlags, ImplementationVisibility::Public), 0);
JSC::JSFunction::create(vm, globalObject, 4, "setServerAppFlags"_s, jsHTTPSetAppFlags, ImplementationVisibility::Public), 0);
obj->putDirect(
vm, JSC::PropertyName(JSC::Identifier::fromString(vm, "setServerMaxHeadersCount"_s)),
JSC::JSFunction::create(vm, globalObject, 2, "setServerMaxHeadersCount"_s, jsHTTPSetMaxHeadersCount, ImplementationVisibility::Public), 0);
Expand Down
2 changes: 0 additions & 2 deletions src/runtime/server/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1671,15 +1671,13 @@ impl<const SSL: bool, const DEBUG: bool> NewServer<SSL, DEBUG> {
pub(crate) fn set_flags(
&mut self,
require_host_header: bool,
use_strict_method_validation: bool,
lenient_http_flags: u8,
http_allow_half_open: bool,
) {
if let Some(app) = self.app {
// S012: `NewApp<SSL>` is a ZST opaque — safe `*mut → &mut` deref.
bun_opaque::opaque_deref_mut(app).set_flags(
require_host_header,
use_strict_method_validation,
lenient_http_flags,
http_allow_half_open,
);
Expand Down
7 changes: 0 additions & 7 deletions src/runtime/server/server_body.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3682,7 +3682,6 @@ fn server_set_app_flags(
global: &JSGlobalObject,
server: JSValue,
require_host_header: bool,
use_strict_method_validation: bool,
lenient_http_flags: u8,
http_allow_half_open: bool,
) -> JsResult<JSValue> {
Expand All @@ -3696,31 +3695,27 @@ fn server_set_app_flags(
// SAFETY: `as_` returned a non-null `*mut` to a live JS-wrapped server.
unsafe { &mut *this }.set_flags(
require_host_header,
use_strict_method_validation,
lenient_http_flags,
http_allow_half_open,
);
} else if let Some(this) = server.as_::<HTTPSServer>() {
// SAFETY: `as_` returned a non-null `*mut` to a live JS-wrapped server.
unsafe { &mut *this }.set_flags(
require_host_header,
use_strict_method_validation,
lenient_http_flags,
http_allow_half_open,
);
} else if let Some(this) = server.as_::<DebugHTTPServer>() {
// SAFETY: `as_` returned a non-null `*mut` to a live JS-wrapped server.
unsafe { &mut *this }.set_flags(
require_host_header,
use_strict_method_validation,
lenient_http_flags,
http_allow_half_open,
);
} else if let Some(this) = server.as_::<DebugHTTPSServer>() {
// SAFETY: `as_` returned a non-null `*mut` to a live JS-wrapped server.
unsafe { &mut *this }.set_flags(
require_host_header,
use_strict_method_validation,
lenient_http_flags,
http_allow_half_open,
);
Expand Down Expand Up @@ -3803,7 +3798,6 @@ extern "C" fn server_set_app_flags_shim(
global: &JSGlobalObject,
server: JSValue,
require_host_header: bool,
use_strict_method_validation: bool,
lenient_http_flags: u8,
http_allow_half_open: bool,
) -> JSValue {
Expand All @@ -3813,7 +3807,6 @@ extern "C" fn server_set_app_flags_shim(
global,
server,
require_host_header,
use_strict_method_validation,
lenient_http_flags,
http_allow_half_open,
),
Expand Down
Loading
Loading