diff --git a/cmake/sources/JavaScriptSources.txt b/cmake/sources/JavaScriptSources.txt index 536897afd9..1da707d389 100644 --- a/cmake/sources/JavaScriptSources.txt +++ b/cmake/sources/JavaScriptSources.txt @@ -93,6 +93,7 @@ src/js/internal/tls.ts src/js/internal/tty.ts src/js/internal/url.ts src/js/internal/util/colors.ts +src/js/internal/util/deprecate.ts src/js/internal/util/inspect.d.ts src/js/internal/util/inspect.js src/js/internal/util/mime.ts diff --git a/packages/bun-types/overrides.d.ts b/packages/bun-types/overrides.d.ts index f52de8acbf..9672966265 100644 --- a/packages/bun-types/overrides.d.ts +++ b/packages/bun-types/overrides.d.ts @@ -168,6 +168,96 @@ declare global { UV_ENODATA: number; UV_EUNATCH: number; }; + binding(m: "http_parser"): { + methods: [ + "DELETE", + "GET", + "HEAD", + "POST", + "PUT", + "CONNECT", + "OPTIONS", + "TRACE", + "COPY", + "LOCK", + "MKCOL", + "MOVE", + "PROPFIND", + "PROPPATCH", + "SEARCH", + "UNLOCK", + "BIND", + "REBIND", + "UNBIND", + "ACL", + "REPORT", + "MKACTIVITY", + "CHECKOUT", + "MERGE", + "M - SEARCH", + "NOTIFY", + "SUBSCRIBE", + "UNSUBSCRIBE", + "PATCH", + "PURGE", + "MKCALENDAR", + "LINK", + "UNLINK", + "SOURCE", + "QUERY", + ]; + allMethods: [ + "DELETE", + "GET", + "HEAD", + "POST", + "PUT", + "CONNECT", + "OPTIONS", + "TRACE", + "COPY", + "LOCK", + "MKCOL", + "MOVE", + "PROPFIND", + "PROPPATCH", + "SEARCH", + "UNLOCK", + "BIND", + "REBIND", + "UNBIND", + "ACL", + "REPORT", + "MKACTIVITY", + "CHECKOUT", + "MERGE", + "M - SEARCH", + "NOTIFY", + "SUBSCRIBE", + "UNSUBSCRIBE", + "PATCH", + "PURGE", + "MKCALENDAR", + "LINK", + "UNLINK", + "SOURCE", + "PRI", + "DESCRIBE", + "ANNOUNCE", + "SETUP", + "PLAY", + "PAUSE", + "TEARDOWN", + "GET_PARAMETER", + "SET_PARAMETER", + "REDIRECT", + "RECORD", + "FLUSH", + "QUERY", + ]; + HTTPParser; + ConnectionsList; + }; binding(m: string): object; } diff --git a/packages/bun-uws/src/HttpContext.h b/packages/bun-uws/src/HttpContext.h index 8db0026c77..20f5177d0e 100644 --- a/packages/bun-uws/src/HttpContext.h +++ b/packages/bun-uws/src/HttpContext.h @@ -441,7 +441,7 @@ private: ((AsyncSocket *) s)->uncork(); /* Check if we should gracefully close the socket after parsing HTTP */ - if (httpContextData->flags.shouldCloseAfterParsingHttp && !httpErrorStatusCode) { + if (httpContextData->flags.shouldCloseAfterParsingHttp) { /* Gracefully close the socket by shutting down and then closing */ if (!us_socket_is_closed(SSL, s) && !us_socket_is_shut_down(SSL, s)) { us_socket_shutdown(SSL, s); diff --git a/packages/bun-uws/src/HttpResponse.h b/packages/bun-uws/src/HttpResponse.h index 8a92248960..4aaa3ada52 100644 --- a/packages/bun-uws/src/HttpResponse.h +++ b/packages/bun-uws/src/HttpResponse.h @@ -120,6 +120,10 @@ public: } } + if (httpResponseData->state & HttpResponseData::HTTP_WROTE_TRANSFER_ENCODING_HEADER) { + allowContentLength = false; + } + /* if write was called and there was previously no Content-Length header set */ if (httpResponseData->state & HttpResponseData::HTTP_WRITE_CALLED && !(httpResponseData->state & HttpResponseData::HTTP_WROTE_CONTENT_LENGTH_HEADER) && !httpResponseData->fromAncientRequest) { diff --git a/packages/bun-uws/src/HttpResponseData.h b/packages/bun-uws/src/HttpResponseData.h index eda5a15b2c..057d33831a 100644 --- a/packages/bun-uws/src/HttpResponseData.h +++ b/packages/bun-uws/src/HttpResponseData.h @@ -80,6 +80,7 @@ struct HttpResponseData : AsyncSocketData, HttpParser { HTTP_CONNECTION_CLOSE = 16, // used HTTP_WROTE_CONTENT_LENGTH_HEADER = 32, // used HTTP_WROTE_DATE_HEADER = 64, // used + HTTP_WROTE_TRANSFER_ENCODING_HEADER = 128, // used }; /* Shared context pointer */ diff --git a/src/bun.js/bindings/ErrorCode.cpp b/src/bun.js/bindings/ErrorCode.cpp index 6b59451aea..1215005680 100644 --- a/src/bun.js/bindings/ErrorCode.cpp +++ b/src/bun.js/bindings/ErrorCode.cpp @@ -2512,6 +2512,10 @@ JSC_DEFINE_HOST_FUNCTION(Bun::jsFunctionMakeErrorWithCode, (JSC::JSGlobalObject return JSC::JSValue::encode(createError(globalObject, ErrorCode::ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING, "A dynamic import callback was not specified."_s)); case ErrorCode::ERR_HTTP_TRAILER_INVALID: return JSC::JSValue::encode(createError(globalObject, ErrorCode::ERR_HTTP_TRAILER_INVALID, "Trailers are invalid with this transfer encoding"_s)); + case ErrorCode::ERR_HTTP_SOCKET_ENCODING: + return JSC::JSValue::encode(createError(globalObject, ErrorCode::ERR_HTTP_SOCKET_ENCODING, "Changing the socket encoding is not allowed per RFC7230 Section 3."_s)); + case ErrorCode::ERR_HTTP_REQUEST_TIMEOUT: + return JSC::JSValue::encode(createError(globalObject, ErrorCode::ERR_HTTP_REQUEST_TIMEOUT, "Request timeout"_s)); default: { break; diff --git a/src/bun.js/bindings/ErrorCode.ts b/src/bun.js/bindings/ErrorCode.ts index fcdf9ef6c2..669f20410e 100644 --- a/src/bun.js/bindings/ErrorCode.ts +++ b/src/bun.js/bindings/ErrorCode.ts @@ -87,7 +87,9 @@ const errors: ErrorCodeMapping = [ ["ERR_HTTP_INVALID_HEADER_VALUE", TypeError], ["ERR_HTTP_INVALID_STATUS_CODE", RangeError], ["ERR_HTTP_TRAILER_INVALID", Error], + ["ERR_HTTP_REQUEST_TIMEOUT", Error], ["ERR_HTTP_SOCKET_ASSIGNED", Error], + ["ERR_HTTP_SOCKET_ENCODING", Error], ["ERR_HTTP2_ALTSVC_INVALID_ORIGIN", TypeError], ["ERR_HTTP2_ALTSVC_LENGTH", TypeError], ["ERR_HTTP2_ERROR", Error], diff --git a/src/bun.js/bindings/NodeHTTP.cpp b/src/bun.js/bindings/NodeHTTP.cpp index 3a16e75f42..0b337f0032 100644 --- a/src/bun.js/bindings/NodeHTTP.cpp +++ b/src/bun.js/bindings/NodeHTTP.cpp @@ -1038,6 +1038,11 @@ static void writeFetchHeadersToUWSResponse(WebCore::FetchHeaders& headers, uWS:: } } + // Prevent automatic Date header insertion when user provides one + if (header.key == WebCore::HTTPHeaderName::TransferEncoding) { + data->state |= uWS::HttpResponseData::HTTP_WROTE_TRANSFER_ENCODING_HEADER; + } + // Prevent automatic Date header insertion when user provides one if (header.key == WebCore::HTTPHeaderName::Date) { data->state |= uWS::HttpResponseData::HTTP_WROTE_DATE_HEADER; diff --git a/src/bun.js/bindings/node/http/llhttp/README.md b/src/bun.js/bindings/node/http/llhttp/README.md index 8bda027179..39bfcc6481 100644 --- a/src/bun.js/bindings/node/http/llhttp/README.md +++ b/src/bun.js/bindings/node/http/llhttp/README.md @@ -1,5 +1,9 @@ Sources are from [llhttp](https://github.com/nodejs/llhttp) 9.3.0 (36151b9a7d6320072e24e472a769a5e09f9e969d) +Keep this in sync with: +- `src/bun.js/bindings/ProcessBindingHTTPParser.cpp` +- `packages/bun-types/overrides.d.ts` + ``` npm ci && make ``` diff --git a/src/js/builtins.d.ts b/src/js/builtins.d.ts index a037d541ad..4d8c8c4659 100644 --- a/src/js/builtins.d.ts +++ b/src/js/builtins.d.ts @@ -804,6 +804,8 @@ declare function $ERR_VM_MODULE_NOT_MODULE(): Error; declare function $ERR_VM_MODULE_DIFFERENT_CONTEXT(): Error; declare function $ERR_VM_MODULE_LINK_FAILURE(message: string, cause: Error): Error; declare function $ERR_HTTP_TRAILER_INVALID(): Error; +declare function $ERR_HTTP_SOCKET_ENCODING(): Error; +declare function $ERR_HTTP_REQUEST_TIMEOUT(): Error; /** * Convert a function to a class-like object. diff --git a/src/js/internal/http/server/_http_outgoing.ts b/src/js/internal/http/server/_http_outgoing.ts index 093598bcf1..7e24a0e38a 100644 --- a/src/js/internal/http/server/_http_outgoing.ts +++ b/src/js/internal/http/server/_http_outgoing.ts @@ -1,6 +1,6 @@ const { Stream } = require("internal/stream"); const { isUint8Array, validateString } = require("internal/validators"); -const { deprecate } = require("node:util"); +const { deprecate } = require("internal/util/deprecate"); const ObjectDefineProperty = Object.defineProperty; const ObjectKeys = Object.keys; const { diff --git a/src/js/internal/util/deprecate.ts b/src/js/internal/util/deprecate.ts new file mode 100644 index 0000000000..14adc5ec5d --- /dev/null +++ b/src/js/internal/util/deprecate.ts @@ -0,0 +1,45 @@ +const { validateString } = require("internal/validators"); + +const codesWarned = new Set(); + +function getDeprecationWarningEmitter(code, msg, deprecated, shouldEmitWarning = () => true) { + let warned = false; + return function () { + if (!warned && shouldEmitWarning()) { + warned = true; + if (code !== undefined) { + if (!codesWarned.has(code)) { + process.emitWarning(msg, "DeprecationWarning", code, deprecated); + codesWarned.add(code); + } + } else { + process.emitWarning(msg, "DeprecationWarning", deprecated); + } + } + }; +} + +function deprecate(fn, msg, code) { + // Lazy-load to avoid a circular dependency. + if (code !== undefined) validateString(code, "code"); + + const emitDeprecationWarning = getDeprecationWarningEmitter(code, msg, deprecated); + + function deprecated(...args) { + if (!process.noDeprecation) { + emitDeprecationWarning(); + } + if (new.target) { + return Reflect.construct(fn, args, new.target); + } + return fn.$apply(this, args); + } + + // The wrapper will keep the same prototype as fn to maintain prototype chain + Object.setPrototypeOf(deprecated, fn); + return deprecated; +} + +export default { + deprecate, +}; diff --git a/src/js/node/_http_common.ts b/src/js/node/_http_common.ts index de66529399..98d2d84e98 100644 --- a/src/js/node/_http_common.ts +++ b/src/js/node/_http_common.ts @@ -1,9 +1,7 @@ const { checkIsHttpToken } = require("internal/validators"); const FreeList = require("internal/freelist"); const { methods, allMethods, HTTPParser } = process.binding("http_parser"); -const incoming = require("node:_http_incoming"); - -const { IncomingMessage, readStart, readStop } = incoming; +const { IncomingMessage, readStart, readStop } = require("node:_http_incoming"); const RegExpPrototypeExec = RegExp.prototype.exec; diff --git a/src/js/node/_http_incoming.ts b/src/js/node/_http_incoming.ts index d5a7801245..727e6d5ff4 100644 --- a/src/js/node/_http_incoming.ts +++ b/src/js/node/_http_incoming.ts @@ -1,3 +1,4 @@ +// Hardcoded module "node:_http_incoming" const { Readable, finished } = require("node:stream"); const ObjectDefineProperty = Object.defineProperty; diff --git a/src/js/node/_http_outgoing.ts b/src/js/node/_http_outgoing.ts index c848145ca5..3b6342e91d 100644 --- a/src/js/node/_http_outgoing.ts +++ b/src/js/node/_http_outgoing.ts @@ -1,7 +1,7 @@ +// Hardcoded module "node:_http_outgoing" const EE = require("node:events"); const Stream = require("node:stream"); const { kOutHeaders, utcDate, kNeedDrain, kEmptyObject } = require("internal/http"); -const { Buffer } = require("node:buffer"); const { _checkIsHttpToken: checkIsHttpToken, _checkInvalidHeaderChar: checkInvalidHeaderChar, diff --git a/src/js/node/_http_server.ts b/src/js/node/_http_server.ts index 387d7ebacf..2b34842db8 100644 --- a/src/js/node/_http_server.ts +++ b/src/js/node/_http_server.ts @@ -1190,9 +1190,7 @@ ServerResponse.prototype[kRejectNonStandardBodyWrites] = undefined; Object.defineProperty(ServerResponse.prototype, "headersSent", { get() { - return ( - this[headerStateSymbol] === NodeHTTPHeaderState.sent || this[headerStateSymbol] === NodeHTTPHeaderState.assigned - ); + return this[headerStateSymbol] === NodeHTTPHeaderState.sent; }, set(value) { this[headerStateSymbol] = value ? NodeHTTPHeaderState.sent : NodeHTTPHeaderState.none; @@ -1459,8 +1457,6 @@ ServerResponse.prototype.detachSocket = function (socket) { }; ServerResponse.prototype._implicitHeader = function () { - if (this.headersSent) return; - // @ts-ignore this.writeHead(this.statusCode); }; @@ -1513,7 +1509,7 @@ ServerResponse.prototype._send = function (data, encoding, callback, _byteLength ServerResponse.prototype.writeHead = function (statusCode, statusMessage, headers) { if (this.headersSent) { - throw $ERR_HTTP_HEADERS_SENT("writeHead"); + throw $ERR_HTTP_HEADERS_SENT("write"); } _writeHead(statusCode, statusMessage, headers, this); diff --git a/src/js/node/dgram.ts b/src/js/node/dgram.ts index dc83eea4b2..ae40dfb472 100644 --- a/src/js/node/dgram.ts +++ b/src/js/node/dgram.ts @@ -57,7 +57,7 @@ const { isIP } = require("node:net"); const EventEmitter = require("node:events"); -const { deprecate } = require("node:util"); +const { deprecate } = require("internal/util/deprecate"); const SymbolDispose = Symbol.dispose; const SymbolAsyncDispose = Symbol.asyncDispose; diff --git a/src/js/node/util.ts b/src/js/node/util.ts index 470f2ca6b6..0d4e6ca1d2 100644 --- a/src/js/node/util.ts +++ b/src/js/node/util.ts @@ -5,6 +5,7 @@ const utl = require("internal/util/inspect"); const { promisify } = require("internal/promisify"); const { validateString, validateOneOf } = require("internal/validators"); const { MIMEType, MIMEParams } = require("internal/util/mime"); +const { deprecate } = require("internal/util/deprecate"); const internalErrorName = $newZigFunction("node_util_binding.zig", "internalErrorName", 1); const parseEnv = $newZigFunction("node_util_binding.zig", "parseEnv", 1); @@ -31,46 +32,6 @@ const formatWithOptions = utl.formatWithOptions; const format = utl.format; const stripVTControlCharacters = utl.stripVTControlCharacters; -const codesWarned = new Set(); - -function getDeprecationWarningEmitter(code, msg, deprecated, shouldEmitWarning = () => true) { - let warned = false; - return function () { - if (!warned && shouldEmitWarning()) { - warned = true; - if (code !== undefined) { - if (!codesWarned.has(code)) { - process.emitWarning(msg, "DeprecationWarning", code, deprecated); - codesWarned.add(code); - } - } else { - process.emitWarning(msg, "DeprecationWarning", deprecated); - } - } - }; -} - -function deprecate(fn, msg, code) { - // Lazy-load to avoid a circular dependency. - if (code !== undefined) validateString(code, "code"); - - const emitDeprecationWarning = getDeprecationWarningEmitter(code, msg, deprecated); - - function deprecated(...args) { - if (!process.noDeprecation) { - emitDeprecationWarning(); - } - if (new.target) { - return Reflect.construct(fn, args, new.target); - } - return fn.$apply(this, args); - } - - // The wrapper will keep the same prototype as fn to maintain prototype chain - Object.setPrototypeOf(deprecated, fn); - return deprecated; -} - var debugs = {}; var debugEnvRegex = /^$/; if (process.env.NODE_DEBUG) {