diff options
author | haturau <135221985+haturatu@users.noreply.github.com> | 2024-11-20 01:20:47 +0900 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-11-20 01:20:47 +0900 |
commit | 85719a67e59c7aa45bead26e4942d7df8b1b42d4 (patch) | |
tree | face0aecaac53e93ce2f23b53c48859bcf1a36ec /ext/node/polyfills | |
parent | 67697bc2e4a62a9670699fd18ad0dd8efc5bd955 (diff) | |
parent | 186b52731c6bb326c4d32905c5e732d082e83465 (diff) |
Merge branch 'denoland:main' into main
Diffstat (limited to 'ext/node/polyfills')
39 files changed, 1979 insertions, 688 deletions
diff --git a/ext/node/polyfills/01_require.js b/ext/node/polyfills/01_require.js index 5b0980c31..083d4e49b 100644 --- a/ext/node/polyfills/01_require.js +++ b/ext/node/polyfills/01_require.js @@ -11,6 +11,7 @@ import { op_require_can_parse_as_esm, op_require_init_paths, op_require_is_deno_dir_package, + op_require_is_maybe_cjs, op_require_is_request_relative, op_require_node_module_paths, op_require_package_imports_resolve, @@ -19,7 +20,6 @@ import { op_require_path_is_absolute, op_require_path_resolve, op_require_proxy_path, - op_require_read_closest_package_json, op_require_read_file, op_require_read_package_scope, op_require_real_path, @@ -523,17 +523,13 @@ function resolveExports( return; } - if (!parentPath) { - return false; - } - return op_require_resolve_exports( usesLocalNodeModulesDir, modulesPath, request, name, expansion, - parentPath, + parentPath ?? "", ) ?? false; } @@ -1064,23 +1060,22 @@ Module.prototype._compile = function (content, filename, format) { return result; }; -Module._extensions[".js"] = function (module, filename) { - const content = op_require_read_file(filename); - - let format; - if (StringPrototypeEndsWith(filename, ".js")) { - const pkg = op_require_read_closest_package_json(filename); - if (pkg?.type === "module") { - format = "module"; - } else if (pkg?.type === "commonjs") { - format = "commonjs"; - } - } else if (StringPrototypeEndsWith(filename, ".cjs")) { - format = "commonjs"; - } - - module._compile(content, filename, format); -}; +Module._extensions[".js"] = + Module._extensions[".ts"] = + Module._extensions[".jsx"] = + Module._extensions[".tsx"] = + function (module, filename) { + const content = op_require_read_file(filename); + const format = op_require_is_maybe_cjs(filename) ? undefined : "module"; + module._compile(content, filename, format); + }; + +Module._extensions[".cjs"] = + Module._extensions[".cts"] = + function (module, filename) { + const content = op_require_read_file(filename); + module._compile(content, filename, "commonjs"); + }; function loadESMFromCJS(module, filename, code) { const namespace = op_import_sync( @@ -1091,7 +1086,10 @@ function loadESMFromCJS(module, filename, code) { module.exports = namespace; } -Module._extensions[".mjs"] = function (module, filename) { +Module._extensions[".mjs"] = Module._extensions[".mts"] = function ( + module, + filename, +) { loadESMFromCJS(module, filename); }; @@ -1212,6 +1210,24 @@ function isBuiltin(moduleName) { !StringPrototypeStartsWith(moduleName, "internal/"); } +function getBuiltinModule(id) { + if (!isBuiltin(id)) { + return undefined; + } + + if (StringPrototypeStartsWith(id, "node:")) { + // Slice 'node:' prefix + id = StringPrototypeSlice(id, 5); + } + + const mod = loadNativeModule(id, id); + if (mod) { + return mod.exports; + } + + return undefined; +} + Module.isBuiltin = isBuiltin; Module.createRequire = createRequire; @@ -1291,6 +1307,8 @@ export function findSourceMap(_path) { return undefined; } +Module.findSourceMap = findSourceMap; + /** * @param {string | URL} _specifier * @param {string | URL} _parentUrl @@ -1304,7 +1322,7 @@ export function register(_specifier, _parentUrl, _options) { return undefined; } -export { builtinModules, createRequire, isBuiltin, Module }; +export { builtinModules, createRequire, getBuiltinModule, isBuiltin, Module }; export const _cache = Module._cache; export const _extensions = Module._extensions; export const _findPath = Module._findPath; diff --git a/ext/node/polyfills/_fs/_fs_common.ts b/ext/node/polyfills/_fs/_fs_common.ts index ac0bf5a55..a29548bb3 100644 --- a/ext/node/polyfills/_fs/_fs_common.ts +++ b/ext/node/polyfills/_fs/_fs_common.ts @@ -20,6 +20,7 @@ import { notImplemented, TextEncodings, } from "ext:deno_node/_utils.ts"; +import { type Buffer } from "node:buffer"; export type CallbackWithError = (err: ErrnoException | null) => void; diff --git a/ext/node/polyfills/_fs/_fs_copy.ts b/ext/node/polyfills/_fs/_fs_copy.ts index 2f8ddf4fc..0434bff4d 100644 --- a/ext/node/polyfills/_fs/_fs_copy.ts +++ b/ext/node/polyfills/_fs/_fs_copy.ts @@ -53,8 +53,9 @@ export function copyFile( }, (e) => { if (e instanceof Deno.errors.NotFound) { Deno.copyFile(srcStr, destStr).then(() => cb(null), cb); + } else { + cb(e); } - cb(e); }); } else { Deno.copyFile(srcStr, destStr).then(() => cb(null), cb); @@ -83,8 +84,9 @@ export function copyFileSync( } catch (e) { if (e instanceof Deno.errors.NotFound) { Deno.copyFileSync(srcStr, destStr); + } else { + throw e; } - throw e; } } else { Deno.copyFileSync(srcStr, destStr); diff --git a/ext/node/polyfills/_fs/_fs_open.ts b/ext/node/polyfills/_fs/_fs_open.ts index 8bd989790..31ca4bb61 100644 --- a/ext/node/polyfills/_fs/_fs_open.ts +++ b/ext/node/polyfills/_fs/_fs_open.ts @@ -147,8 +147,8 @@ export function open( export function openPromise( path: string | Buffer | URL, - flags?: openFlags = "r", - mode? = 0o666, + flags: openFlags = "r", + mode = 0o666, ): Promise<FileHandle> { return new Promise((resolve, reject) => { open(path, flags, mode, (err, fd) => { diff --git a/ext/node/polyfills/_fs/_fs_readFile.ts b/ext/node/polyfills/_fs/_fs_readFile.ts index 0f05ee167..cf7e0305d 100644 --- a/ext/node/polyfills/_fs/_fs_readFile.ts +++ b/ext/node/polyfills/_fs/_fs_readFile.ts @@ -19,6 +19,7 @@ import { TextEncodings, } from "ext:deno_node/_utils.ts"; import { FsFile } from "ext:deno_fs/30_fs.js"; +import { denoErrorToNodeError } from "ext:deno_node/internal/errors.ts"; function maybeDecode(data: Uint8Array, encoding: TextEncodings): string; function maybeDecode( @@ -87,7 +88,7 @@ export function readFile( } const buffer = maybeDecode(data, encoding); (cb as BinaryCallback)(null, buffer); - }, (err) => cb && cb(err)); + }, (err) => cb && cb(denoErrorToNodeError(err))); } } @@ -117,7 +118,12 @@ export function readFileSync( opt?: FileOptionsArgument, ): string | Buffer { path = path instanceof URL ? pathFromURL(path) : path; - const data = Deno.readFileSync(path); + let data; + try { + data = Deno.readFileSync(path); + } catch (err) { + throw denoErrorToNodeError(err); + } const encoding = getEncoding(opt); if (encoding && encoding !== "binary") { const text = maybeDecode(data, encoding); diff --git a/ext/node/polyfills/_fs/_fs_readlink.ts b/ext/node/polyfills/_fs/_fs_readlink.ts index 5f2312798..08bea843f 100644 --- a/ext/node/polyfills/_fs/_fs_readlink.ts +++ b/ext/node/polyfills/_fs/_fs_readlink.ts @@ -4,13 +4,10 @@ // deno-lint-ignore-file prefer-primordials import { TextEncoder } from "ext:deno_web/08_text_encoding.js"; -import { - intoCallbackAPIWithIntercept, - MaybeEmpty, - notImplemented, -} from "ext:deno_node/_utils.ts"; +import { MaybeEmpty, notImplemented } from "ext:deno_node/_utils.ts"; import { pathFromURL } from "ext:deno_web/00_infra.js"; import { promisify } from "ext:deno_node/internal/util.mjs"; +import { denoErrorToNodeError } from "ext:deno_node/internal/errors.ts"; type ReadlinkCallback = ( err: MaybeEmpty<Error>, @@ -69,12 +66,17 @@ export function readlink( const encoding = getEncoding(optOrCallback); - intoCallbackAPIWithIntercept<string, Uint8Array | string>( - Deno.readLink, - (data: string): string | Uint8Array => maybeEncode(data, encoding), - cb, - path, - ); + Deno.readLink(path).then((data: string) => { + const res = maybeEncode(data, encoding); + if (cb) cb(null, res); + }, (err: Error) => { + if (cb) { + (cb as (e: Error) => void)(denoErrorToNodeError(err, { + syscall: "readlink", + path, + })); + } + }); } export const readlinkPromise = promisify(readlink) as ( @@ -88,5 +90,12 @@ export function readlinkSync( ): string | Uint8Array { path = path instanceof URL ? pathFromURL(path) : path; - return maybeEncode(Deno.readLinkSync(path), getEncoding(opt)); + try { + return maybeEncode(Deno.readLinkSync(path), getEncoding(opt)); + } catch (error) { + throw denoErrorToNodeError(error, { + syscall: "readlink", + path, + }); + } } diff --git a/ext/node/polyfills/_fs/_fs_readv.ts b/ext/node/polyfills/_fs/_fs_readv.ts index 384f5e319..2259f029a 100644 --- a/ext/node/polyfills/_fs/_fs_readv.ts +++ b/ext/node/polyfills/_fs/_fs_readv.ts @@ -15,6 +15,7 @@ import { maybeCallback } from "ext:deno_node/_fs/_fs_common.ts"; import { validateInteger } from "ext:deno_node/internal/validators.mjs"; import * as io from "ext:deno_io/12_io.js"; import { op_fs_seek_async, op_fs_seek_sync } from "ext:core/ops"; +import process from "node:process"; type Callback = ( err: ErrnoException | null, diff --git a/ext/node/polyfills/_fs/_fs_stat.ts b/ext/node/polyfills/_fs/_fs_stat.ts index c4ed82d57..507cb05ea 100644 --- a/ext/node/polyfills/_fs/_fs_stat.ts +++ b/ext/node/polyfills/_fs/_fs_stat.ts @@ -290,8 +290,8 @@ export function convertFileInfoToStats(origin: Deno.FileInfo): Stats { isFIFO: () => false, isCharacterDevice: () => false, isSocket: () => false, - ctime: origin.mtime, - ctimeMs: origin.mtime?.getTime() || null, + ctime: origin.ctime, + ctimeMs: origin.ctime?.getTime() || null, }); return stats; @@ -336,9 +336,9 @@ export function convertFileInfoToBigIntStats( isFIFO: () => false, isCharacterDevice: () => false, isSocket: () => false, - ctime: origin.mtime, - ctimeMs: origin.mtime ? BigInt(origin.mtime.getTime()) : null, - ctimeNs: origin.mtime ? BigInt(origin.mtime.getTime()) * 1000000n : null, + ctime: origin.ctime, + ctimeMs: origin.ctime ? BigInt(origin.ctime.getTime()) : null, + ctimeNs: origin.ctime ? BigInt(origin.ctime.getTime()) * 1000000n : null, }); return stats; } @@ -383,7 +383,10 @@ export function stat( Deno.stat(path).then( (stat) => callback(null, CFISBIS(stat, options.bigint)), - (err) => callback(denoErrorToNodeError(err, { syscall: "stat" })), + (err) => + callback( + denoErrorToNodeError(err, { syscall: "stat", path: getPathname(path) }), + ), ); } @@ -417,9 +420,16 @@ export function statSync( return; } if (err instanceof Error) { - throw denoErrorToNodeError(err, { syscall: "stat" }); + throw denoErrorToNodeError(err, { + syscall: "stat", + path: getPathname(path), + }); } else { throw err; } } } + +function getPathname(path: string | URL) { + return typeof path === "string" ? path : path.pathname; +} diff --git a/ext/node/polyfills/_next_tick.ts b/ext/node/polyfills/_next_tick.ts index 5ee27728d..af306a29c 100644 --- a/ext/node/polyfills/_next_tick.ts +++ b/ext/node/polyfills/_next_tick.ts @@ -62,6 +62,8 @@ export function processTicksAndRejections() { callback(...args); } } + } catch (e) { + reportError(e); } finally { // FIXME(bartlomieju): Deno currently doesn't support async hooks // if (destroyHooksExist()) @@ -87,8 +89,7 @@ export function runNextTicks() { // runMicrotasks(); // if (!hasTickScheduled() && !hasRejectionToWarn()) // return; - if (!core.hasTickScheduled()) { - core.runMicrotasks(); + if (queue.isEmpty() || !core.hasTickScheduled()) { return true; } diff --git a/ext/node/polyfills/_process/streams.mjs b/ext/node/polyfills/_process/streams.mjs index 7936e82aa..3573956c9 100644 --- a/ext/node/polyfills/_process/streams.mjs +++ b/ext/node/polyfills/_process/streams.mjs @@ -66,14 +66,19 @@ export function createWritableStdioStream(writer, name, warmup = false) { // We cannot call `writer?.isTerminal()` eagerly here let getIsTTY = () => writer?.isTerminal(); + const getColumns = () => + stream._columns || + (writer?.isTerminal() ? Deno.consoleSize?.().columns : undefined); ObjectDefineProperties(stream, { columns: { __proto__: null, enumerable: true, configurable: true, - get: () => - writer?.isTerminal() ? Deno.consoleSize?.().columns : undefined, + get: () => getColumns(), + set: (value) => { + stream._columns = value; + }, }, rows: { __proto__: null, diff --git a/ext/node/polyfills/_tls_wrap.ts b/ext/node/polyfills/_tls_wrap.ts index a614b45df..e36fc637e 100644 --- a/ext/node/polyfills/_tls_wrap.ts +++ b/ext/node/polyfills/_tls_wrap.ts @@ -68,6 +68,7 @@ export class TLSSocket extends net.Socket { secureConnecting: boolean; _SNICallback: any; servername: string | null; + alpnProtocol: string | boolean | null; alpnProtocols: string[] | null; authorized: boolean; authorizationError: any; @@ -114,6 +115,7 @@ export class TLSSocket extends net.Socket { this.secureConnecting = true; this._SNICallback = null; this.servername = null; + this.alpnProtocol = null; this.alpnProtocols = tlsOptions.ALPNProtocols; this.authorized = false; this.authorizationError = null; @@ -151,10 +153,21 @@ export class TLSSocket extends net.Socket { handle.afterConnect = async (req: any, status: number) => { try { const conn = await Deno.startTls(handle[kStreamBaseField], options); + try { + const hs = await conn.handshake(); + if (hs.alpnProtocol) { + tlssock.alpnProtocol = hs.alpnProtocol; + } else { + tlssock.alpnProtocol = false; + } + } catch { + // Don't interrupt "secure" event to let the first read/write + // operation emit the error. + } handle[kStreamBaseField] = conn; tlssock.emit("secure"); tlssock.removeListener("end", onConnectEnd); - } catch { + } catch (_) { // TODO(kt3k): Handle this } return afterConnect.call(handle, req, status); @@ -269,6 +282,7 @@ export class ServerImpl extends EventEmitter { // Creates TCP handle and socket directly from Deno.TlsConn. // This works as TLS socket. We don't use TLSSocket class for doing // this because Deno.startTls only supports client side tcp connection. + // TODO(@satyarohith): set TLSSocket.alpnProtocol when we use TLSSocket class. const handle = new TCP(TCPConstants.SOCKET, await listener.accept()); const socket = new net.Socket({ handle }); this.emit("secureConnection", socket); diff --git a/ext/node/polyfills/_utils.ts b/ext/node/polyfills/_utils.ts index b50c113e1..79d84e00f 100644 --- a/ext/node/polyfills/_utils.ts +++ b/ext/node/polyfills/_utils.ts @@ -17,6 +17,7 @@ const { import { TextDecoder, TextEncoder } from "ext:deno_web/08_text_encoding.js"; import { errorMap } from "ext:deno_node/internal_binding/uv.ts"; import { codes } from "ext:deno_node/internal/error_codes.ts"; +import { ERR_NOT_IMPLEMENTED } from "ext:deno_node/internal/errors.ts"; export type BinaryEncodings = "binary"; @@ -34,8 +35,7 @@ export type TextEncodings = export type Encodings = BinaryEncodings | TextEncodings; export function notImplemented(msg: string): never { - const message = msg ? `Not implemented: ${msg}` : "Not implemented"; - throw new Error(message); + throw new ERR_NOT_IMPLEMENTED(msg); } export function warnNotImplemented(msg?: string) { diff --git a/ext/node/polyfills/_zlib.mjs b/ext/node/polyfills/_zlib.mjs index 851bd602f..07fc440ef 100644 --- a/ext/node/polyfills/_zlib.mjs +++ b/ext/node/polyfills/_zlib.mjs @@ -14,6 +14,7 @@ import { nextTick } from "ext:deno_node/_next_tick.ts"; import { isAnyArrayBuffer, isArrayBufferView, + isUint8Array, } from "ext:deno_node/internal/util/types.ts"; var kRangeErrorMessage = "Cannot create final Buffer. It would be larger " + @@ -158,6 +159,12 @@ export const inflateRawSync = function (buffer, opts) { function sanitizeInput(input) { if (typeof input === "string") input = Buffer.from(input); + if (isArrayBufferView(input) && !isUint8Array(input)) { + input = Buffer.from(input.buffer, input.byteOffset, input.byteLength); + } else if (isAnyArrayBuffer(input)) { + input = Buffer.from(input); + } + if ( !Buffer.isBuffer(input) && (input.buffer && !input.buffer.constructor === ArrayBuffer) diff --git a/ext/node/polyfills/child_process.ts b/ext/node/polyfills/child_process.ts index c37dfc410..eda718ff3 100644 --- a/ext/node/polyfills/child_process.ts +++ b/ext/node/polyfills/child_process.ts @@ -132,6 +132,8 @@ export function fork( rm = 2; } execArgv.splice(index, rm); + } else if (flag.startsWith("--no-warnings")) { + execArgv[index] = "--quiet"; } else { index++; } diff --git a/ext/node/polyfills/http.ts b/ext/node/polyfills/http.ts index f3f6f86ed..9a920adee 100644 --- a/ext/node/polyfills/http.ts +++ b/ext/node/polyfills/http.ts @@ -34,6 +34,7 @@ import { finished, Readable as NodeReadable, Writable as NodeWritable, + WritableOptions as NodeWritableOptions, } from "node:stream"; import { kUniqueHeaders, @@ -66,12 +67,13 @@ import { headersEntries } from "ext:deno_fetch/20_headers.js"; import { timerId } from "ext:deno_web/03_abort_signal.js"; import { clearTimeout as webClearTimeout } from "ext:deno_web/02_timers.js"; import { resourceForReadableStream } from "ext:deno_web/06_streams.js"; -import { TcpConn } from "ext:deno_net/01_net.js"; +import { UpgradedConn } from "ext:deno_net/01_net.js"; import { STATUS_CODES } from "node:_http_server"; import { methods as METHODS } from "node:_http_common"; +import { deprecate } from "node:util"; const { internalRidSymbol } = core; -const { ArrayIsArray } = primordials; +const { ArrayIsArray, StringPrototypeToLowerCase } = primordials; type Chunk = string | Buffer | Uint8Array; @@ -516,7 +518,7 @@ class ClientRequest extends OutgoingMessage { ); assert(typeof res.remoteAddrIp !== "undefined"); assert(typeof res.remoteAddrIp !== "undefined"); - const conn = new TcpConn( + const conn = new UpgradedConn( upgradeRid, { transport: "tcp", @@ -1183,49 +1185,95 @@ function onError(self, error, cb) { } } -export class ServerResponse extends NodeWritable { - statusCode = 200; - statusMessage?: string = undefined; - #headers: Record<string, string | string[]> = { __proto__: null }; - #hasNonStringHeaders: boolean = false; - #readable: ReadableStream; - override writable = true; - // used by `npm:on-finished` - finished = false; - headersSent = false; - #resolve: (value: Response | PromiseLike<Response>) => void; +export type ServerResponse = { + statusCode: number; + statusMessage?: string; + + _headers: Record<string, string | string[]>; + _hasNonStringHeaders: boolean; + + _readable: ReadableStream; + finished: boolean; + headersSent: boolean; + _resolve: (value: Response | PromiseLike<Response>) => void; + // deno-lint-ignore no-explicit-any + _socketOverride: any | null; // deno-lint-ignore no-explicit-any - #socketOverride: any | null = null; + socket: any | null; - static #enqueue(controller: ReadableStreamDefaultController, chunk: Chunk) { - try { - if (typeof chunk === "string") { - controller.enqueue(ENCODER.encode(chunk)); - } else { - controller.enqueue(chunk); - } - } catch (_) { - // The stream might have been closed. Ignore the error. - } - } + setHeader(name: string, value: string | string[]): void; + appendHeader(name: string, value: string | string[]): void; + getHeader(name: string): string | string[]; + removeHeader(name: string): void; + getHeaderNames(): string[]; + getHeaders(): Record<string, string | number | string[]>; + hasHeader(name: string): boolean; - /** Returns true if the response body should be null with the given - * http status code */ - static #bodyShouldBeNull(status: number) { - return status === 101 || status === 204 || status === 205 || status === 304; - } + writeHead( + status: number, + statusMessage?: string, + headers?: + | Record<string, string | number | string[]> + | Array<[string, string]>, + ): void; + writeHead( + status: number, + headers?: + | Record<string, string | number | string[]> + | Array<[string, string]>, + ): void; - constructor( + _ensureHeaders(singleChunk?: Chunk): void; + + respond(final: boolean, singleChunk?: Chunk): void; + // deno-lint-ignore no-explicit-any + end(chunk?: any, encoding?: any, cb?: any): void; + + flushHeaders(): void; + _implicitHeader(): void; + + // Undocumented field used by `npm:light-my-request`. + _header: string; + + assignSocket(socket): void; + detachSocket(socket): void; +} & { -readonly [K in keyof NodeWritable]: NodeWritable[K] }; + +type ServerResponseStatic = { + new ( resolve: (value: Response | PromiseLike<Response>) => void, socket: FakeSocket, - ) { - let controller: ReadableByteStreamController; - const readable = new ReadableStream({ - start(c) { - controller = c as ReadableByteStreamController; - }, - }); - super({ + ): ServerResponse; + _enqueue(controller: ReadableStreamDefaultController, chunk: Chunk): void; + _bodyShouldBeNull(statusCode: number): boolean; +}; + +export const ServerResponse = function ( + this: ServerResponse, + resolve: (value: Response | PromiseLike<Response>) => void, + socket: FakeSocket, +) { + this.statusCode = 200; + this.statusMessage = undefined; + this._headers = { __proto__: null }; + this._hasNonStringHeaders = false; + this.writable = true; + + // used by `npm:on-finished` + this.finished = false; + this.headersSent = false; + this._socketOverride = null; + + let controller: ReadableByteStreamController; + const readable = new ReadableStream({ + start(c) { + controller = c as ReadableByteStreamController; + }, + }); + + NodeWritable.call( + this, + { autoDestroy: true, defaultEncoding: "utf-8", emitClose: true, @@ -1234,16 +1282,16 @@ export class ServerResponse extends NodeWritable { write: (chunk, encoding, cb) => { // Writes chunks are directly written to the socket if // one is assigned via assignSocket() - if (this.#socketOverride && this.#socketOverride.writable) { - this.#socketOverride.write(chunk, encoding); + if (this._socketOverride && this._socketOverride.writable) { + this._socketOverride.write(chunk, encoding); return cb(); } if (!this.headersSent) { - ServerResponse.#enqueue(controller, chunk); + ServerResponse._enqueue(controller, chunk); this.respond(false); return cb(); } - ServerResponse.#enqueue(controller, chunk); + ServerResponse._enqueue(controller, chunk); return cb(); }, final: (cb) => { @@ -1259,192 +1307,269 @@ export class ServerResponse extends NodeWritable { } return cb(null); }, - }); - this.#readable = readable; - this.#resolve = resolve; - this.socket = socket; + } satisfies NodeWritableOptions, + ); + + this._readable = readable; + this._resolve = resolve; + this.socket = socket; + + this._header = ""; +} as unknown as ServerResponseStatic; + +Object.setPrototypeOf(ServerResponse.prototype, NodeWritable.prototype); +Object.setPrototypeOf(ServerResponse, NodeWritable); + +ServerResponse._enqueue = function ( + this: ServerResponse, + controller: ReadableStreamDefaultController, + chunk: Chunk, +) { + try { + if (typeof chunk === "string") { + controller.enqueue(ENCODER.encode(chunk)); + } else { + controller.enqueue(chunk); + } + } catch (_) { + // The stream might have been closed. Ignore the error. } +}; - setHeader(name: string, value: string | string[]) { - if (Array.isArray(value)) { - this.#hasNonStringHeaders = true; - } - this.#headers[name] = value; - return this; +/** Returns true if the response body should be null with the given + * http status code */ +ServerResponse._bodyShouldBeNull = function ( + this: ServerResponse, + status: number, +) { + return status === 101 || status === 204 || status === 205 || status === 304; +}; + +ServerResponse.prototype.setHeader = function ( + this: ServerResponse, + name: string, + value: string | string[], +) { + if (Array.isArray(value)) { + this._hasNonStringHeaders = true; } + this._headers[StringPrototypeToLowerCase(name)] = value; + return this; +}; - appendHeader(name: string, value: string | string[]) { - if (this.#headers[name] === undefined) { - if (Array.isArray(value)) this.#hasNonStringHeaders = true; - this.#headers[name] = value; +ServerResponse.prototype.appendHeader = function ( + this: ServerResponse, + name: string, + value: string | string[], +) { + const key = StringPrototypeToLowerCase(name); + if (this._headers[key] === undefined) { + if (Array.isArray(value)) this._hasNonStringHeaders = true; + this._headers[key] = value; + } else { + this._hasNonStringHeaders = true; + if (!Array.isArray(this._headers[key])) { + this._headers[key] = [this._headers[key]]; + } + const header = this._headers[key]; + if (Array.isArray(value)) { + header.push(...value); } else { - this.#hasNonStringHeaders = true; - if (!Array.isArray(this.#headers[name])) { - this.#headers[name] = [this.#headers[name]]; - } - const header = this.#headers[name]; - if (Array.isArray(value)) { - header.push(...value); - } else { - header.push(value); - } + header.push(value); } - return this; } + return this; +}; - getHeader(name: string) { - return this.#headers[name]; - } - removeHeader(name: string) { - delete this.#headers[name]; - } - getHeaderNames() { - return Object.keys(this.#headers); - } - getHeaders(): Record<string, string | number | string[]> { - // @ts-ignore Ignore null __proto__ - return { __proto__: null, ...this.#headers }; - } - hasHeader(name: string) { - return Object.hasOwn(this.#headers, name); - } +ServerResponse.prototype.getHeader = function ( + this: ServerResponse, + name: string, +) { + return this._headers[StringPrototypeToLowerCase(name)]; +}; - writeHead( - status: number, - statusMessage?: string, - headers?: - | Record<string, string | number | string[]> - | Array<[string, string]>, - ): this; - writeHead( - status: number, - headers?: - | Record<string, string | number | string[]> - | Array<[string, string]>, - ): this; - writeHead( - status: number, - statusMessageOrHeaders?: - | string - | Record<string, string | number | string[]> - | Array<[string, string]>, - maybeHeaders?: - | Record<string, string | number | string[]> - | Array<[string, string]>, - ): this { - this.statusCode = status; - - let headers = null; - if (typeof statusMessageOrHeaders === "string") { - this.statusMessage = statusMessageOrHeaders; - if (maybeHeaders !== undefined) { - headers = maybeHeaders; - } - } else if (statusMessageOrHeaders !== undefined) { - headers = statusMessageOrHeaders; - } +ServerResponse.prototype.removeHeader = function ( + this: ServerResponse, + name: string, +) { + delete this._headers[StringPrototypeToLowerCase(name)]; +}; - if (headers !== null) { - if (ArrayIsArray(headers)) { - headers = headers as Array<[string, string]>; - for (let i = 0; i < headers.length; i++) { - this.appendHeader(headers[i][0], headers[i][1]); - } - } else { - headers = headers as Record<string, string>; - for (const k in headers) { - if (Object.hasOwn(headers, k)) { - this.setHeader(k, headers[k]); - } +ServerResponse.prototype.getHeaderNames = function (this: ServerResponse) { + return Object.keys(this._headers); +}; + +ServerResponse.prototype.getHeaders = function ( + this: ServerResponse, +): Record<string, string | number | string[]> { + return { __proto__: null, ...this._headers }; +}; + +ServerResponse.prototype.hasHeader = function ( + this: ServerResponse, + name: string, +) { + return Object.hasOwn(this._headers, name); +}; + +ServerResponse.prototype.writeHead = function ( + this: ServerResponse, + status: number, + statusMessageOrHeaders?: + | string + | Record<string, string | number | string[]> + | Array<[string, string]>, + maybeHeaders?: + | Record<string, string | number | string[]> + | Array<[string, string]>, +) { + this.statusCode = status; + + let headers = null; + if (typeof statusMessageOrHeaders === "string") { + this.statusMessage = statusMessageOrHeaders; + if (maybeHeaders !== undefined) { + headers = maybeHeaders; + } + } else if (statusMessageOrHeaders !== undefined) { + headers = statusMessageOrHeaders; + } + + if (headers !== null) { + if (ArrayIsArray(headers)) { + headers = headers as Array<[string, string]>; + for (let i = 0; i < headers.length; i++) { + this.appendHeader(headers[i][0], headers[i][1]); + } + } else { + headers = headers as Record<string, string>; + for (const k in headers) { + if (Object.hasOwn(headers, k)) { + this.setHeader(k, headers[k]); } } } + } - return this; + return this; +}; + +ServerResponse.prototype._ensureHeaders = function ( + this: ServerResponse, + singleChunk?: Chunk, +) { + if (this.statusCode === 200 && this.statusMessage === undefined) { + this.statusMessage = "OK"; } + if (typeof singleChunk === "string" && !this.hasHeader("content-type")) { + this.setHeader("content-type", "text/plain;charset=UTF-8"); + } +}; - #ensureHeaders(singleChunk?: Chunk) { - if (this.statusCode === 200 && this.statusMessage === undefined) { - this.statusMessage = "OK"; - } - if ( - typeof singleChunk === "string" && - !this.hasHeader("content-type") - ) { - this.setHeader("content-type", "text/plain;charset=UTF-8"); - } - } - - respond(final: boolean, singleChunk?: Chunk) { - this.headersSent = true; - this.#ensureHeaders(singleChunk); - let body = singleChunk ?? (final ? null : this.#readable); - if (ServerResponse.#bodyShouldBeNull(this.statusCode)) { - body = null; - } - let headers: Record<string, string> | [string, string][] = this - .#headers as Record<string, string>; - if (this.#hasNonStringHeaders) { - headers = []; - // Guard is not needed as this is a null prototype object. - // deno-lint-ignore guard-for-in - for (const key in this.#headers) { - const entry = this.#headers[key]; - if (Array.isArray(entry)) { - for (const value of entry) { - headers.push([key, value]); - } - } else { - headers.push([key, entry]); +ServerResponse.prototype.respond = function ( + this: ServerResponse, + final: boolean, + singleChunk?: Chunk, +) { + this.headersSent = true; + this._ensureHeaders(singleChunk); + let body = singleChunk ?? (final ? null : this._readable); + if (ServerResponse._bodyShouldBeNull(this.statusCode)) { + body = null; + } + let headers: Record<string, string> | [string, string][] = this + ._headers as Record<string, string>; + if (this._hasNonStringHeaders) { + headers = []; + // Guard is not needed as this is a null prototype object. + // deno-lint-ignore guard-for-in + for (const key in this._headers) { + const entry = this._headers[key]; + if (Array.isArray(entry)) { + for (const value of entry) { + headers.push([key, value]); } + } else { + headers.push([key, entry]); } } - this.#resolve( - new Response(body, { - headers, - status: this.statusCode, - statusText: this.statusMessage, - }), - ); } + this._resolve( + new Response(body, { + headers, + status: this.statusCode, + statusText: this.statusMessage, + }), + ); +}; +ServerResponse.prototype.end = function ( + this: ServerResponse, // deno-lint-ignore no-explicit-any - override end(chunk?: any, encoding?: any, cb?: any): this { - this.finished = true; - if (!chunk && "transfer-encoding" in this.#headers) { - // FIXME(bnoordhuis) Node sends a zero length chunked body instead, i.e., - // the trailing "0\r\n", but respondWith() just hangs when I try that. - this.#headers["content-length"] = "0"; - delete this.#headers["transfer-encoding"]; - } + chunk?: any, + // deno-lint-ignore no-explicit-any + encoding?: any, + // deno-lint-ignore no-explicit-any + cb?: any, +) { + this.finished = true; + if (!chunk && "transfer-encoding" in this._headers) { + // FIXME(bnoordhuis) Node sends a zero length chunked body instead, i.e., + // the trailing "0\r\n", but respondWith() just hangs when I try that. + this._headers["content-length"] = "0"; + delete this._headers["transfer-encoding"]; + } + + // @ts-expect-error The signature for cb is stricter than the one implemented here + NodeWritable.prototype.end.call(this, chunk, encoding, cb); +}; - // @ts-expect-error The signature for cb is stricter than the one implemented here - return super.end(chunk, encoding, cb); - } +ServerResponse.prototype.flushHeaders = function (this: ServerResponse) { + // no-op +}; - flushHeaders() { - // no-op - } +// Undocumented API used by `npm:compression`. +ServerResponse.prototype._implicitHeader = function (this: ServerResponse) { + this.writeHead(this.statusCode); +}; - // Undocumented API used by `npm:compression`. - _implicitHeader() { - this.writeHead(this.statusCode); +ServerResponse.prototype.assignSocket = function ( + this: ServerResponse, + socket, +) { + if (socket._httpMessage) { + throw new ERR_HTTP_SOCKET_ASSIGNED(); } + socket._httpMessage = this; + this._socketOverride = socket; +}; - assignSocket(socket) { - if (socket._httpMessage) { - throw new ERR_HTTP_SOCKET_ASSIGNED(); - } - socket._httpMessage = this; - this.#socketOverride = socket; - } +ServerResponse.prototype.detachSocket = function ( + this: ServerResponse, + socket, +) { + assert(socket._httpMessage === this); + socket._httpMessage = null; + this._socketOverride = null; +}; - detachSocket(socket) { - assert(socket._httpMessage === this); - socket._httpMessage = null; - this.#socketOverride = null; - } -} +Object.defineProperty(ServerResponse.prototype, "connection", { + get: deprecate( + function (this: ServerResponse) { + return this._socketOverride; + }, + "ServerResponse.prototype.connection is deprecated", + "DEP0066", + ), + set: deprecate( + // deno-lint-ignore no-explicit-any + function (this: ServerResponse, socket: any) { + this._socketOverride = socket; + }, + "ServerResponse.prototype.connection is deprecated", + "DEP0066", + ), +}); // TODO(@AaronO): optimize export class IncomingMessageForServer extends NodeReadable { @@ -1677,6 +1802,8 @@ export class ServerImpl extends EventEmitter { this.#server.ref(); } this.#unref = false; + + return this; } unref() { @@ -1684,6 +1811,8 @@ export class ServerImpl extends EventEmitter { this.#server.unref(); } this.#unref = true; + + return this; } close(cb?: (err?: Error) => void): this { diff --git a/ext/node/polyfills/http2.ts b/ext/node/polyfills/http2.ts index a9ced2bd9..dc2379aeb 100644 --- a/ext/node/polyfills/http2.ts +++ b/ext/node/polyfills/http2.ts @@ -882,6 +882,7 @@ export class ClientHttp2Stream extends Duplex { trailersReady: false, endAfterHeaders: false, shutdownWritableCalled: false, + serverEndedCall: false, }; this[kDenoResponse] = undefined; this[kDenoRid] = undefined; @@ -1109,7 +1110,9 @@ export class ClientHttp2Stream extends Duplex { } debugHttp2(">>> chunk", chunk, finished, this[kDenoResponse].bodyRid); - if (chunk === null) { + if (finished || chunk === null) { + this[kState].serverEndedCall = true; + const trailerList = await op_http2_client_get_response_trailers( this[kDenoResponse].bodyRid, ); @@ -1237,7 +1240,9 @@ export class ClientHttp2Stream extends Duplex { this[kSession] = undefined; session[kMaybeDestroy](); - callback(err); + if (callback) { + callback(err); + } } [kMaybeDestroy](code = constants.NGHTTP2_NO_ERROR) { @@ -1280,6 +1285,9 @@ function shutdownWritable(stream, callback, streamRid) { if (state.flags & STREAM_FLAGS_HAS_TRAILERS) { onStreamTrailers(stream); callback(); + } else if (state.serverEndedCall) { + debugHttp2(">>> stream finished"); + callback(); } else { op_http2_client_send_data(streamRid, new Uint8Array(), true) .then(() => { diff --git a/ext/node/polyfills/inspector.js b/ext/node/polyfills/inspector.js new file mode 100644 index 000000000..7eb15ce91 --- /dev/null +++ b/ext/node/polyfills/inspector.js @@ -0,0 +1,210 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent and Node contributors. All rights reserved. MIT license. + +import process from "node:process"; +import { EventEmitter } from "node:events"; +import { primordials } from "ext:core/mod.js"; +import { + op_get_extras_binding_object, + op_inspector_close, + op_inspector_connect, + op_inspector_disconnect, + op_inspector_dispatch, + op_inspector_emit_protocol_event, + op_inspector_enabled, + op_inspector_open, + op_inspector_url, + op_inspector_wait, +} from "ext:core/ops"; +import { + isUint32, + validateFunction, + validateInt32, + validateObject, + validateString, +} from "ext:deno_node/internal/validators.mjs"; +import { + ERR_INSPECTOR_ALREADY_ACTIVATED, + ERR_INSPECTOR_ALREADY_CONNECTED, + ERR_INSPECTOR_CLOSED, + ERR_INSPECTOR_COMMAND, + ERR_INSPECTOR_NOT_ACTIVE, + ERR_INSPECTOR_NOT_CONNECTED, + ERR_INSPECTOR_NOT_WORKER, +} from "ext:deno_node/internal/errors.ts"; + +const { + SymbolDispose, + JSONParse, + JSONStringify, + SafeMap, +} = primordials; + +class Session extends EventEmitter { + #connection = null; + #nextId = 1; + #messageCallbacks = new SafeMap(); + + connect() { + if (this.#connection) { + throw new ERR_INSPECTOR_ALREADY_CONNECTED("The inspector session"); + } + this.#connection = op_inspector_connect(false, (m) => this.#onMessage(m)); + } + + connectToMainThread() { + if (isMainThread) { + throw new ERR_INSPECTOR_NOT_WORKER(); + } + if (this.#connection) { + throw new ERR_INSPECTOR_ALREADY_CONNECTED("The inspector session"); + } + this.#connection = op_inspector_connect(true, (m) => this.#onMessage(m)); + } + + #onMessage(message) { + const parsed = JSONParse(message); + try { + if (parsed.id) { + const callback = this.#messageCallbacks.get(parsed.id); + this.#messageCallbacks.delete(parsed.id); + if (callback) { + if (parsed.error) { + return callback( + new ERR_INSPECTOR_COMMAND( + parsed.error.code, + parsed.error.message, + ), + ); + } + + callback(null, parsed.result); + } + } else { + this.emit(parsed.method, parsed); + this.emit("inspectorNotification", parsed); + } + } catch (error) { + process.emitWarning(error); + } + } + + post(method, params, callback) { + validateString(method, "method"); + if (!callback && typeof params === "function") { + callback = params; + params = null; + } + if (params) { + validateObject(params, "params"); + } + if (callback) { + validateFunction(callback, "callback"); + } + + if (!this.#connection) { + throw new ERR_INSPECTOR_NOT_CONNECTED(); + } + const id = this.#nextId++; + const message = { id, method }; + if (params) { + message.params = params; + } + if (callback) { + this.#messageCallbacks.set(id, callback); + } + op_inspector_dispatch(this.#connection, JSONStringify(message)); + } + + disconnect() { + if (!this.#connection) { + return; + } + op_inspector_disconnect(this.#connection); + this.#connection = null; + // deno-lint-ignore prefer-primordials + for (const callback of this.#messageCallbacks.values()) { + process.nextTick(callback, new ERR_INSPECTOR_CLOSED()); + } + this.#messageCallbacks.clear(); + this.#nextId = 1; + } +} + +function open(port, host, wait) { + if (op_inspector_enabled()) { + throw new ERR_INSPECTOR_ALREADY_ACTIVATED(); + } + // inspectorOpen() currently does not typecheck its arguments and adding + // such checks would be a potentially breaking change. However, the native + // open() function requires the port to fit into a 16-bit unsigned integer, + // causing an integer overflow otherwise, so we at least need to prevent that. + if (isUint32(port)) { + validateInt32(port, "port", 0, 65535); + } else { + // equiv of handling args[0]->IsUint32() + port = undefined; + } + if (typeof host !== "string") { + // equiv of handling args[1]->IsString() + host = undefined; + } + op_inspector_open(port, host); + if (wait) { + op_inspector_wait(); + } + + return { + __proto__: null, + [SymbolDispose]() { + _debugEnd(); + }, + }; +} + +function close() { + op_inspector_close(); +} + +function url() { + return op_inspector_url(); +} + +function waitForDebugger() { + if (!op_inspector_wait()) { + throw new ERR_INSPECTOR_NOT_ACTIVE(); + } +} + +function broadcastToFrontend(eventName, params) { + validateString(eventName, "eventName"); + if (params) { + validateObject(params, "params"); + } + op_inspector_emit_protocol_event(eventName, JSONStringify(params ?? {})); +} + +const Network = { + requestWillBeSent: (params) => + broadcastToFrontend("Network.requestWillBeSent", params), + responseReceived: (params) => + broadcastToFrontend("Network.responseReceived", params), + loadingFinished: (params) => + broadcastToFrontend("Network.loadingFinished", params), + loadingFailed: (params) => + broadcastToFrontend("Network.loadingFailed", params), +}; + +const console = op_get_extras_binding_object().console; + +export { close, console, Network, open, Session, url, waitForDebugger }; + +export default { + open, + close, + url, + waitForDebugger, + console, + Session, + Network, +}; diff --git a/ext/node/polyfills/inspector.ts b/ext/node/polyfills/inspector.ts deleted file mode 100644 index 9de86ab14..000000000 --- a/ext/node/polyfills/inspector.ts +++ /dev/null @@ -1,82 +0,0 @@ -// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -// Copyright Joyent and Node contributors. All rights reserved. MIT license. - -import { EventEmitter } from "node:events"; -import { notImplemented } from "ext:deno_node/_utils.ts"; -import { primordials } from "ext:core/mod.js"; - -const { - SafeMap, -} = primordials; - -class Session extends EventEmitter { - #connection = null; - #nextId = 1; - #messageCallbacks = new SafeMap(); - - /** Connects the session to the inspector back-end. */ - connect() { - notImplemented("inspector.Session.prototype.connect"); - } - - /** Connects the session to the main thread - * inspector back-end. */ - connectToMainThread() { - notImplemented("inspector.Session.prototype.connectToMainThread"); - } - - /** Posts a message to the inspector back-end. */ - post( - _method: string, - _params?: Record<string, unknown>, - _callback?: (...args: unknown[]) => void, - ) { - notImplemented("inspector.Session.prototype.post"); - } - - /** Immediately closes the session, all pending - * message callbacks will be called with an - * error. - */ - disconnect() { - notImplemented("inspector.Session.prototype.disconnect"); - } -} - -/** Activates inspector on host and port. - * See https://nodejs.org/api/inspector.html#inspectoropenport-host-wait */ -function open(_port?: number, _host?: string, _wait?: boolean) { - notImplemented("inspector.Session.prototype.open"); -} - -/** Deactivate the inspector. Blocks until there are no active connections. - * See https://nodejs.org/api/inspector.html#inspectorclose */ -function close() { - notImplemented("inspector.Session.prototype.close"); -} - -/** Return the URL of the active inspector, or undefined if there is none. - * See https://nodejs.org/api/inspector.html#inspectorurl */ -function url() { - // TODO(kt3k): returns undefined for now, which means the inspector is not activated. - return undefined; -} - -/** Blocks until a client (existing or connected later) has sent Runtime.runIfWaitingForDebugger command. - * See https://nodejs.org/api/inspector.html#inspectorwaitfordebugger */ -function waitForDebugger() { - notImplemented("inspector.wairForDebugger"); -} - -const console = globalThis.console; - -export { close, console, open, Session, url, waitForDebugger }; - -export default { - close, - console, - open, - Session, - url, - waitForDebugger, -}; diff --git a/ext/node/polyfills/inspector/promises.js b/ext/node/polyfills/inspector/promises.js new file mode 100644 index 000000000..3483e53f5 --- /dev/null +++ b/ext/node/polyfills/inspector/promises.js @@ -0,0 +1,20 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent and Node contributors. All rights reserved. MIT license. + +import inspector from "node:inspector"; +import { promisify } from "ext:deno_node/internal/util.mjs"; + +class Session extends inspector.Session { + constructor() { + super(); + } +} +Session.prototype.post = promisify(inspector.Session.prototype.post); + +export * from "node:inspector"; +export { Session }; + +export default { + ...inspector, + Session, +}; diff --git a/ext/node/polyfills/internal/buffer.mjs b/ext/node/polyfills/internal/buffer.mjs index 6687f7394..dd549221f 100644 --- a/ext/node/polyfills/internal/buffer.mjs +++ b/ext/node/polyfills/internal/buffer.mjs @@ -2,10 +2,59 @@ // Copyright Joyent and Node contributors. All rights reserved. MIT license. // Copyright Feross Aboukhadijeh, and other contributors. All rights reserved. MIT license. -// TODO(petamoriken): enable prefer-primordials for node polyfills -// deno-lint-ignore-file prefer-primordials - -import { core } from "ext:core/mod.js"; +import { core, primordials } from "ext:core/mod.js"; +const { + isAnyArrayBuffer, + isArrayBuffer, + isDataView, + isSharedArrayBuffer, + isTypedArray, +} = core; +const { + ArrayBufferPrototypeGetByteLength, + ArrayBufferPrototypeGetDetached, + ArrayIsArray, + ArrayPrototypeSlice, + BigInt, + DataViewPrototypeGetByteLength, + Float32Array, + Float64Array, + MathFloor, + MathMin, + Number, + NumberIsInteger, + NumberIsNaN, + NumberMAX_SAFE_INTEGER, + NumberMIN_SAFE_INTEGER, + NumberPrototypeToString, + ObjectCreate, + ObjectDefineProperty, + ObjectPrototypeIsPrototypeOf, + ObjectSetPrototypeOf, + RangeError, + SafeRegExp, + String, + StringFromCharCode, + StringPrototypeCharCodeAt, + StringPrototypeIncludes, + StringPrototypeReplace, + StringPrototypeToLowerCase, + StringPrototypeTrim, + SymbolFor, + SymbolToPrimitive, + TypeError, + TypeErrorPrototype, + TypedArrayPrototypeCopyWithin, + TypedArrayPrototypeFill, + TypedArrayPrototypeGetBuffer, + TypedArrayPrototypeGetByteLength, + TypedArrayPrototypeGetByteOffset, + TypedArrayPrototypeSet, + TypedArrayPrototypeSlice, + TypedArrayPrototypeSubarray, + Uint8Array, + Uint8ArrayPrototype, +} = primordials; import { op_is_ascii, op_is_utf8, op_transcode } from "ext:core/ops"; import { TextDecoder, TextEncoder } from "ext:deno_web/08_text_encoding.js"; @@ -24,11 +73,6 @@ import { hexToBytes, utf16leToBytes, } from "ext:deno_node/internal_binding/_utils.ts"; -import { - isAnyArrayBuffer, - isArrayBufferView, - isTypedArray, -} from "ext:deno_node/internal/util/types.ts"; import { normalizeEncoding } from "ext:deno_node/internal/util.mjs"; import { validateBuffer } from "ext:deno_node/internal/validators.mjs"; import { isUint8Array } from "ext:deno_node/internal/util/types.ts"; @@ -50,9 +94,13 @@ const utf8Encoder = new TextEncoder(); // Temporary buffers to convert numbers. const float32Array = new Float32Array(1); -const uInt8Float32Array = new Uint8Array(float32Array.buffer); +const uInt8Float32Array = new Uint8Array( + TypedArrayPrototypeGetBuffer(float32Array), +); const float64Array = new Float64Array(1); -const uInt8Float64Array = new Uint8Array(float64Array.buffer); +const uInt8Float64Array = new Uint8Array( + TypedArrayPrototypeGetBuffer(float64Array), +); // Check endianness. float32Array[0] = -1; // 0xBF800000 @@ -64,10 +112,7 @@ export const kMaxLength = 2147483647; export const kStringMaxLength = 536870888; const MAX_UINT32 = 2 ** 32; -const customInspectSymbol = - typeof Symbol === "function" && typeof Symbol["for"] === "function" - ? Symbol["for"]("nodejs.util.inspect.custom") - : null; +const customInspectSymbol = SymbolFor("nodejs.util.inspect.custom"); export const INSPECT_MAX_BYTES = 50; @@ -76,23 +121,25 @@ export const constants = { MAX_STRING_LENGTH: kStringMaxLength, }; -Object.defineProperty(Buffer.prototype, "parent", { +ObjectDefineProperty(Buffer.prototype, "parent", { + __proto__: null, enumerable: true, get: function () { - if (!Buffer.isBuffer(this)) { + if (!BufferIsBuffer(this)) { return void 0; } - return this.buffer; + return TypedArrayPrototypeGetBuffer(this); }, }); -Object.defineProperty(Buffer.prototype, "offset", { +ObjectDefineProperty(Buffer.prototype, "offset", { + __proto__: null, enumerable: true, get: function () { - if (!Buffer.isBuffer(this)) { + if (!BufferIsBuffer(this)) { return void 0; } - return this.byteOffset; + return TypedArrayPrototypeGetByteOffset(this); }, }); @@ -103,10 +150,21 @@ function createBuffer(length) { ); } const buf = new Uint8Array(length); - Object.setPrototypeOf(buf, Buffer.prototype); + ObjectSetPrototypeOf(buf, BufferPrototype); return buf; } +/** + * @param {ArrayBufferLike} O + * @returns {boolean} + */ +function isDetachedBuffer(O) { + if (isSharedArrayBuffer(O)) { + return false; + } + return ArrayBufferPrototypeGetDetached(O); +} + export function Buffer(arg, encodingOrOffset, length) { if (typeof arg === "number") { if (typeof encodingOrOffset === "string") { @@ -133,6 +191,7 @@ function _from(value, encodingOrOffset, length) { return fromArrayBuffer(value, encodingOrOffset, length); } + // deno-lint-ignore prefer-primordials const valueOf = value.valueOf && value.valueOf(); if ( valueOf != null && @@ -147,8 +206,8 @@ function _from(value, encodingOrOffset, length) { return b; } - if (typeof value[Symbol.toPrimitive] === "function") { - const primitive = value[Symbol.toPrimitive]("string"); + if (typeof value[SymbolToPrimitive] === "function") { + const primitive = value[SymbolToPrimitive]("string"); if (typeof primitive === "string") { return fromString(primitive, encodingOrOffset); } @@ -162,13 +221,19 @@ function _from(value, encodingOrOffset, length) { ); } -Buffer.from = function from(value, encodingOrOffset, length) { +const BufferFrom = Buffer.from = function from( + value, + encodingOrOffset, + length, +) { return _from(value, encodingOrOffset, length); }; -Object.setPrototypeOf(Buffer.prototype, Uint8Array.prototype); +const BufferPrototype = Buffer.prototype; + +ObjectSetPrototypeOf(Buffer.prototype, Uint8ArrayPrototype); -Object.setPrototypeOf(Buffer, Uint8Array); +ObjectSetPrototypeOf(Buffer, Uint8Array); function assertSize(size) { validateNumber(size, "size", 0, kMaxLength); @@ -186,6 +251,7 @@ function _alloc(size, fill, encoding) { encoding, ); } + // deno-lint-ignore prefer-primordials return buffer.fill(fill, encoding); } return buffer; @@ -212,13 +278,14 @@ function fromString(string, encoding) { if (typeof encoding !== "string" || encoding === "") { encoding = "utf8"; } - if (!Buffer.isEncoding(encoding)) { + if (!BufferIsEncoding(encoding)) { throw new codes.ERR_UNKNOWN_ENCODING(encoding); } const length = byteLength(string, encoding) | 0; let buf = createBuffer(length); const actual = buf.write(string, encoding); if (actual !== length) { + // deno-lint-ignore prefer-primordials buf = buf.slice(0, actual); } return buf; @@ -226,11 +293,12 @@ function fromString(string, encoding) { function fromArrayLike(obj) { const buf = new Uint8Array(obj); - Object.setPrototypeOf(buf, Buffer.prototype); + ObjectSetPrototypeOf(buf, BufferPrototype); return buf; } function fromObject(obj) { + // deno-lint-ignore prefer-primordials if (obj.length !== undefined || isAnyArrayBuffer(obj.buffer)) { if (typeof obj.length !== "number") { return createBuffer(0); @@ -239,7 +307,7 @@ function fromObject(obj) { return fromArrayLike(obj); } - if (obj.type === "Buffer" && Array.isArray(obj.data)) { + if (obj.type === "Buffer" && ArrayIsArray(obj.data)) { return fromArrayLike(obj.data); } } @@ -248,7 +316,7 @@ function checked(length) { if (length >= kMaxLength) { throw new RangeError( "Attempt to allocate Buffer larger than maximum size: 0x" + - kMaxLength.toString(16) + " bytes", + NumberPrototypeToString(kMaxLength, 16) + " bytes", ); } return length | 0; @@ -256,25 +324,33 @@ function checked(length) { export function SlowBuffer(length) { assertSize(length); - return Buffer.alloc(+length); + return _alloc(+length); } -Object.setPrototypeOf(SlowBuffer.prototype, Uint8Array.prototype); +ObjectSetPrototypeOf(SlowBuffer.prototype, Uint8ArrayPrototype); -Object.setPrototypeOf(SlowBuffer, Uint8Array); +ObjectSetPrototypeOf(SlowBuffer, Uint8Array); -Buffer.isBuffer = function isBuffer(b) { - return b != null && b._isBuffer === true && b !== Buffer.prototype; +const BufferIsBuffer = Buffer.isBuffer = function isBuffer(b) { + return b != null && b._isBuffer === true && b !== BufferPrototype; }; -Buffer.compare = function compare(a, b) { - if (isInstance(a, Uint8Array)) { - a = Buffer.from(a, a.offset, a.byteLength); +const BufferCompare = Buffer.compare = function compare(a, b) { + if (isUint8Array(a)) { + a = BufferFrom( + a, + TypedArrayPrototypeGetByteOffset(a), + TypedArrayPrototypeGetByteLength(a), + ); } - if (isInstance(b, Uint8Array)) { - b = Buffer.from(b, b.offset, b.byteLength); + if (isUint8Array(b)) { + b = BufferFrom( + b, + TypedArrayPrototypeGetByteOffset(b), + TypedArrayPrototypeGetByteLength(b), + ); } - if (!Buffer.isBuffer(a) || !Buffer.isBuffer(b)) { + if (!BufferIsBuffer(a) || !BufferIsBuffer(b)) { throw new TypeError( 'The "buf1", "buf2" arguments must be one of type Buffer or Uint8Array', ); @@ -284,7 +360,7 @@ Buffer.compare = function compare(a, b) { } let x = a.length; let y = b.length; - for (let i = 0, len = Math.min(x, y); i < len; ++i) { + for (let i = 0, len = MathMin(x, y); i < len; ++i) { if (a[i] !== b[i]) { x = a[i]; y = b[i]; @@ -300,18 +376,18 @@ Buffer.compare = function compare(a, b) { return 0; }; -Buffer.isEncoding = function isEncoding(encoding) { +const BufferIsEncoding = Buffer.isEncoding = function isEncoding(encoding) { return typeof encoding === "string" && encoding.length !== 0 && normalizeEncoding(encoding) !== undefined; }; Buffer.concat = function concat(list, length) { - if (!Array.isArray(list)) { + if (!ArrayIsArray(list)) { throw new codes.ERR_INVALID_ARG_TYPE("list", "Array", list); } if (list.length === 0) { - return Buffer.alloc(0); + return _alloc(0); } if (length === undefined) { @@ -325,7 +401,7 @@ Buffer.concat = function concat(list, length) { validateOffset(length, "length"); } - const buffer = Buffer.allocUnsafe(length); + const buffer = _allocUnsafe(length); let pos = 0; for (let i = 0; i < list.length; i++) { const buf = list[i]; @@ -346,7 +422,7 @@ Buffer.concat = function concat(list, length) { // Zero-fill the remaining bytes if the specified `length` was more than // the actual total length, i.e. if we have some remaining allocated bytes // there were not initialized. - buffer.fill(0, pos, length); + TypedArrayPrototypeFill(buffer, 0, pos, length); } return buffer; @@ -354,7 +430,18 @@ Buffer.concat = function concat(list, length) { function byteLength(string, encoding) { if (typeof string !== "string") { - if (isArrayBufferView(string) || isAnyArrayBuffer(string)) { + if (isTypedArray(string)) { + return TypedArrayPrototypeGetByteLength(string); + } + if (isDataView(string)) { + return DataViewPrototypeGetByteLength(string); + } + if (isArrayBuffer(string)) { + return ArrayBufferPrototypeGetByteLength(string); + } + if (isSharedArrayBuffer(string)) { + // TODO(petamoriken): add SharedArayBuffer to primordials + // deno-lint-ignore prefer-primordials return string.byteLength; } @@ -463,6 +550,7 @@ Buffer.prototype.toString = function toString(encoding, start, end) { throw new codes.ERR_UNKNOWN_ENCODING(encoding); } + // deno-lint-ignore prefer-primordials return ops.slice(this, start, end); }; @@ -479,22 +567,29 @@ Buffer.prototype.equals = function equals(b) { if (this === b) { return true; } - return Buffer.compare(this, b) === 0; + return BufferCompare(this, b) === 0; }; -Buffer.prototype.inspect = function inspect() { - let str = ""; - const max = INSPECT_MAX_BYTES; - str = this.toString("hex", 0, max).replace(/(.{2})/g, "$1 ").trim(); - if (this.length > max) { - str += " ... "; - } - return "<Buffer " + str + ">"; -}; +const SPACER_PATTERN = new SafeRegExp(/(.{2})/g); -if (customInspectSymbol) { - Buffer.prototype[customInspectSymbol] = Buffer.prototype.inspect; -} +Buffer.prototype[customInspectSymbol] = + Buffer.prototype.inspect = + function inspect() { + let str = ""; + const max = INSPECT_MAX_BYTES; + str = StringPrototypeTrim( + StringPrototypeReplace( + // deno-lint-ignore prefer-primordials + this.toString("hex", 0, max), + SPACER_PATTERN, + "$1 ", + ), + ); + if (this.length > max) { + str += " ... "; + } + return "<Buffer " + str + ">"; + }; Buffer.prototype.compare = function compare( target, @@ -503,10 +598,14 @@ Buffer.prototype.compare = function compare( thisStart, thisEnd, ) { - if (isInstance(target, Uint8Array)) { - target = Buffer.from(target, target.offset, target.byteLength); + if (isUint8Array(target)) { + target = BufferFrom( + target, + TypedArrayPrototypeGetByteOffset(target), + TypedArrayPrototypeGetByteLength(target), + ); } - if (!Buffer.isBuffer(target)) { + if (!BufferIsBuffer(target)) { throw new codes.ERR_INVALID_ARG_TYPE( "target", ["Buffer", "Uint8Array"], @@ -563,8 +662,9 @@ Buffer.prototype.compare = function compare( } let x = thisEnd - thisStart; let y = end - start; - const len = Math.min(x, y); - const thisCopy = this.slice(thisStart, thisEnd); + const len = MathMin(x, y); + const thisCopy = TypedArrayPrototypeSlice(this, thisStart, thisEnd); + // deno-lint-ignore prefer-primordials const targetCopy = target.slice(start, end); for (let i = 0; i < len; ++i) { if (thisCopy[i] !== targetCopy[i]) { @@ -594,7 +694,8 @@ function bidirectionalIndexOf(buffer, val, byteOffset, encoding, dir) { byteOffset = -0x80000000; } byteOffset = +byteOffset; - if (Number.isNaN(byteOffset)) { + if (NumberIsNaN(byteOffset)) { + // deno-lint-ignore prefer-primordials byteOffset = dir ? 0 : (buffer.length || buffer.byteLength); } dir = !!dir; @@ -614,6 +715,7 @@ function bidirectionalIndexOf(buffer, val, byteOffset, encoding, dir) { if (ops === undefined) { throw new codes.ERR_UNKNOWN_ENCODING(encoding); } + // deno-lint-ignore prefer-primordials return ops.indexOf(buffer, val, byteOffset, dir); } @@ -630,6 +732,7 @@ function bidirectionalIndexOf(buffer, val, byteOffset, encoding, dir) { } Buffer.prototype.includes = function includes(val, byteOffset, encoding) { + // deno-lint-ignore prefer-primordials return this.indexOf(val, byteOffset, encoding) !== -1; }; @@ -649,7 +752,7 @@ Buffer.prototype.asciiSlice = function asciiSlice(offset, length) { if (offset === 0 && length === this.length) { return bytesToAscii(this); } else { - return bytesToAscii(this.slice(offset, length)); + return bytesToAscii(TypedArrayPrototypeSlice(this, offset, length)); } }; @@ -664,7 +767,9 @@ Buffer.prototype.base64Slice = function base64Slice( if (offset === 0 && length === this.length) { return forgivingBase64Encode(this); } else { - return forgivingBase64Encode(this.slice(offset, length)); + return forgivingBase64Encode( + TypedArrayPrototypeSlice(this, offset, length), + ); } }; @@ -683,7 +788,9 @@ Buffer.prototype.base64urlSlice = function base64urlSlice( if (offset === 0 && length === this.length) { return forgivingBase64UrlEncode(this); } else { - return forgivingBase64UrlEncode(this.slice(offset, length)); + return forgivingBase64UrlEncode( + TypedArrayPrototypeSlice(this, offset, length), + ); } }; @@ -728,7 +835,7 @@ Buffer.prototype.ucs2Slice = function ucs2Slice(offset, length) { if (offset === 0 && length === this.length) { return bytesToUtf16le(this); } else { - return bytesToUtf16le(this.slice(offset, length)); + return bytesToUtf16le(TypedArrayPrototypeSlice(this, offset, length)); } }; @@ -747,9 +854,9 @@ Buffer.prototype.utf8Slice = function utf8Slice(string, offset, length) { Buffer.prototype.utf8Write = function utf8Write(string, offset, length) { offset = offset || 0; - const maxLength = Math.min(length || Infinity, this.length - offset); + const maxLength = MathMin(length || Infinity, this.length - offset); const buf = offset || maxLength < this.length - ? this.subarray(offset, maxLength + offset) + ? TypedArrayPrototypeSubarray(this, offset, maxLength + offset) : this; return utf8Encoder.encodeInto(string, buf).written; }; @@ -801,7 +908,7 @@ Buffer.prototype.write = function write(string, offset, length, encoding) { Buffer.prototype.toJSON = function toJSON() { return { type: "Buffer", - data: Array.prototype.slice.call(this._arr || this, 0), + data: ArrayPrototypeSlice(this._arr || this, 0), }; }; function fromArrayBuffer(obj, byteOffset, length) { @@ -810,11 +917,12 @@ function fromArrayBuffer(obj, byteOffset, length) { byteOffset = 0; } else { byteOffset = +byteOffset; - if (Number.isNaN(byteOffset)) { + if (NumberIsNaN(byteOffset)) { byteOffset = 0; } } + // deno-lint-ignore prefer-primordials const maxLength = obj.byteLength - byteOffset; if (maxLength < 0) { @@ -836,7 +944,7 @@ function fromArrayBuffer(obj, byteOffset, length) { } const buffer = new Uint8Array(obj, byteOffset, length); - Object.setPrototypeOf(buffer, Buffer.prototype); + ObjectSetPrototypeOf(buffer, BufferPrototype); return buffer; } @@ -844,6 +952,7 @@ function _base64Slice(buf, start, end) { if (start === 0 && end === buf.length) { return forgivingBase64Encode(buf); } else { + // deno-lint-ignore prefer-primordials return forgivingBase64Encode(buf.slice(start, end)); } } @@ -852,9 +961,10 @@ const decoder = new TextDecoder(); function _utf8Slice(buf, start, end) { try { + // deno-lint-ignore prefer-primordials return decoder.decode(buf.slice(start, end)); } catch (err) { - if (err instanceof TypeError) { + if (ObjectPrototypeIsPrototypeOf(TypeErrorPrototype, err)) { throw new NodeError("ERR_STRING_TOO_LONG", "String too long"); } throw err; @@ -863,9 +973,9 @@ function _utf8Slice(buf, start, end) { function _latin1Slice(buf, start, end) { let ret = ""; - end = Math.min(buf.length, end); + end = MathMin(buf.length, end); for (let i = start; i < end; ++i) { - ret += String.fromCharCode(buf[i]); + ret += StringFromCharCode(buf[i]); } return ret; } @@ -994,42 +1104,38 @@ Buffer.prototype.readUint32BE = Buffer.prototype.readUInt32BE = readUInt32BE; Buffer.prototype.readBigUint64LE = Buffer.prototype.readBigUInt64LE = - defineBigIntMethod( - function readBigUInt64LE(offset) { - offset = offset >>> 0; - validateNumber(offset, "offset"); - const first = this[offset]; - const last = this[offset + 7]; - if (first === void 0 || last === void 0) { - boundsError(offset, this.length - 8); - } - const lo = first + this[++offset] * 2 ** 8 + - this[++offset] * 2 ** 16 + - this[++offset] * 2 ** 24; - const hi = this[++offset] + this[++offset] * 2 ** 8 + - this[++offset] * 2 ** 16 + last * 2 ** 24; - return BigInt(lo) + (BigInt(hi) << BigInt(32)); - }, - ); + function readBigUInt64LE(offset) { + offset = offset >>> 0; + validateNumber(offset, "offset"); + const first = this[offset]; + const last = this[offset + 7]; + if (first === void 0 || last === void 0) { + boundsError(offset, this.length - 8); + } + const lo = first + this[++offset] * 2 ** 8 + + this[++offset] * 2 ** 16 + + this[++offset] * 2 ** 24; + const hi = this[++offset] + this[++offset] * 2 ** 8 + + this[++offset] * 2 ** 16 + last * 2 ** 24; + return BigInt(lo) + (BigInt(hi) << 32n); + }; Buffer.prototype.readBigUint64BE = Buffer.prototype.readBigUInt64BE = - defineBigIntMethod( - function readBigUInt64BE(offset) { - offset = offset >>> 0; - validateNumber(offset, "offset"); - const first = this[offset]; - const last = this[offset + 7]; - if (first === void 0 || last === void 0) { - boundsError(offset, this.length - 8); - } - const hi = first * 2 ** 24 + this[++offset] * 2 ** 16 + - this[++offset] * 2 ** 8 + this[++offset]; - const lo = this[++offset] * 2 ** 24 + this[++offset] * 2 ** 16 + - this[++offset] * 2 ** 8 + last; - return (BigInt(hi) << BigInt(32)) + BigInt(lo); - }, - ); + function readBigUInt64BE(offset) { + offset = offset >>> 0; + validateNumber(offset, "offset"); + const first = this[offset]; + const last = this[offset + 7]; + if (first === void 0 || last === void 0) { + boundsError(offset, this.length - 8); + } + const hi = first * 2 ** 24 + this[++offset] * 2 ** 16 + + this[++offset] * 2 ** 8 + this[++offset]; + const lo = this[++offset] * 2 ** 24 + this[++offset] * 2 ** 16 + + this[++offset] * 2 ** 8 + last; + return (BigInt(hi) << 32n) + BigInt(lo); + }; Buffer.prototype.readIntLE = function readIntLE( offset, @@ -1148,43 +1254,39 @@ Buffer.prototype.readInt32BE = function readInt32BE(offset = 0) { last; }; -Buffer.prototype.readBigInt64LE = defineBigIntMethod( - function readBigInt64LE(offset) { - offset = offset >>> 0; - validateNumber(offset, "offset"); - const first = this[offset]; - const last = this[offset + 7]; - if (first === void 0 || last === void 0) { - boundsError(offset, this.length - 8); - } - const val = this[offset + 4] + this[offset + 5] * 2 ** 8 + - this[offset + 6] * 2 ** 16 + (last << 24); - return (BigInt(val) << BigInt(32)) + - BigInt( - first + this[++offset] * 2 ** 8 + this[++offset] * 2 ** 16 + - this[++offset] * 2 ** 24, - ); - }, -); +Buffer.prototype.readBigInt64LE = function readBigInt64LE(offset) { + offset = offset >>> 0; + validateNumber(offset, "offset"); + const first = this[offset]; + const last = this[offset + 7]; + if (first === void 0 || last === void 0) { + boundsError(offset, this.length - 8); + } + const val = this[offset + 4] + this[offset + 5] * 2 ** 8 + + this[offset + 6] * 2 ** 16 + (last << 24); + return (BigInt(val) << 32n) + + BigInt( + first + this[++offset] * 2 ** 8 + this[++offset] * 2 ** 16 + + this[++offset] * 2 ** 24, + ); +}; -Buffer.prototype.readBigInt64BE = defineBigIntMethod( - function readBigInt64BE(offset) { - offset = offset >>> 0; - validateNumber(offset, "offset"); - const first = this[offset]; - const last = this[offset + 7]; - if (first === void 0 || last === void 0) { - boundsError(offset, this.length - 8); - } - const val = (first << 24) + this[++offset] * 2 ** 16 + - this[++offset] * 2 ** 8 + this[++offset]; - return (BigInt(val) << BigInt(32)) + - BigInt( - this[++offset] * 2 ** 24 + this[++offset] * 2 ** 16 + - this[++offset] * 2 ** 8 + last, - ); - }, -); +Buffer.prototype.readBigInt64BE = function readBigInt64BE(offset) { + offset = offset >>> 0; + validateNumber(offset, "offset"); + const first = this[offset]; + const last = this[offset + 7]; + if (first === void 0 || last === void 0) { + boundsError(offset, this.length - 8); + } + const val = (first << 24) + this[++offset] * 2 ** 16 + + this[++offset] * 2 ** 8 + this[++offset]; + return (BigInt(val) << 32n) + + BigInt( + this[++offset] * 2 ** 24 + this[++offset] * 2 ** 16 + + this[++offset] * 2 ** 8 + last, + ); +}; Buffer.prototype.readFloatLE = function readFloatLE(offset) { return bigEndian @@ -1293,7 +1395,7 @@ Buffer.prototype.writeUint32BE = function wrtBigUInt64LE(buf, value, offset, min, max) { checkIntBI(value, min, max, buf, offset, 7); - let lo = Number(value & BigInt(4294967295)); + let lo = Number(value & 4294967295n); buf[offset++] = lo; lo = lo >> 8; buf[offset++] = lo; @@ -1301,7 +1403,7 @@ function wrtBigUInt64LE(buf, value, offset, min, max) { buf[offset++] = lo; lo = lo >> 8; buf[offset++] = lo; - let hi = Number(value >> BigInt(32) & BigInt(4294967295)); + let hi = Number(value >> 32n & 4294967295n); buf[offset++] = hi; hi = hi >> 8; buf[offset++] = hi; @@ -1314,7 +1416,7 @@ function wrtBigUInt64LE(buf, value, offset, min, max) { function wrtBigUInt64BE(buf, value, offset, min, max) { checkIntBI(value, min, max, buf, offset, 7); - let lo = Number(value & BigInt(4294967295)); + let lo = Number(value & 4294967295n); buf[offset + 7] = lo; lo = lo >> 8; buf[offset + 6] = lo; @@ -1322,7 +1424,7 @@ function wrtBigUInt64BE(buf, value, offset, min, max) { buf[offset + 5] = lo; lo = lo >> 8; buf[offset + 4] = lo; - let hi = Number(value >> BigInt(32) & BigInt(4294967295)); + let hi = Number(value >> 32n & 4294967295n); buf[offset + 3] = hi; hi = hi >> 8; buf[offset + 2] = hi; @@ -1335,31 +1437,27 @@ function wrtBigUInt64BE(buf, value, offset, min, max) { Buffer.prototype.writeBigUint64LE = Buffer.prototype.writeBigUInt64LE = - defineBigIntMethod( - function writeBigUInt64LE(value, offset = 0) { - return wrtBigUInt64LE( - this, - value, - offset, - BigInt(0), - BigInt("0xffffffffffffffff"), - ); - }, - ); + function writeBigUInt64LE(value, offset = 0) { + return wrtBigUInt64LE( + this, + value, + offset, + 0n, + 0xffffffffffffffffn, + ); + }; Buffer.prototype.writeBigUint64BE = Buffer.prototype.writeBigUInt64BE = - defineBigIntMethod( - function writeBigUInt64BE(value, offset = 0) { - return wrtBigUInt64BE( - this, - value, - offset, - BigInt(0), - BigInt("0xffffffffffffffff"), - ); - }, - ); + function writeBigUInt64BE(value, offset = 0) { + return wrtBigUInt64BE( + this, + value, + offset, + 0n, + 0xffffffffffffffffn, + ); + }; Buffer.prototype.writeIntLE = function writeIntLE( value, @@ -1450,29 +1548,25 @@ Buffer.prototype.writeInt32BE = function writeInt32BE(value, offset = 0) { return writeU_Int32BE(this, value, offset, -0x80000000, 0x7fffffff); }; -Buffer.prototype.writeBigInt64LE = defineBigIntMethod( - function writeBigInt64LE(value, offset = 0) { - return wrtBigUInt64LE( - this, - value, - offset, - -BigInt("0x8000000000000000"), - BigInt("0x7fffffffffffffff"), - ); - }, -); +Buffer.prototype.writeBigInt64LE = function writeBigInt64LE(value, offset = 0) { + return wrtBigUInt64LE( + this, + value, + offset, + -0x8000000000000000n, + 0x7fffffffffffffffn, + ); +}; -Buffer.prototype.writeBigInt64BE = defineBigIntMethod( - function writeBigInt64BE(value, offset = 0) { - return wrtBigUInt64BE( - this, - value, - offset, - -BigInt("0x8000000000000000"), - BigInt("0x7fffffffffffffff"), - ); - }, -); +Buffer.prototype.writeBigInt64BE = function writeBigInt64BE(value, offset = 0) { + return wrtBigUInt64BE( + this, + value, + offset, + -0x8000000000000000n, + 0x7fffffffffffffffn, + ); +}; Buffer.prototype.writeFloatLE = function writeFloatLE( value, @@ -1600,14 +1694,12 @@ Buffer.prototype.copy = function copy( } const len = sourceEnd - sourceStart; - if ( - this === target && typeof Uint8Array.prototype.copyWithin === "function" - ) { - this.copyWithin(targetStart, sourceStart, sourceEnd); + if (this === target) { + TypedArrayPrototypeCopyWithin(this, targetStart, sourceStart, sourceEnd); } else { - Uint8Array.prototype.set.call( + TypedArrayPrototypeSet( target, - this.subarray(sourceStart, sourceEnd), + TypedArrayPrototypeSubarray(this, sourceStart, sourceEnd), targetStart, ); } @@ -1627,11 +1719,11 @@ Buffer.prototype.fill = function fill(val, start, end, encoding) { if (encoding !== void 0 && typeof encoding !== "string") { throw new TypeError("encoding must be a string"); } - if (typeof encoding === "string" && !Buffer.isEncoding(encoding)) { + if (typeof encoding === "string" && !BufferIsEncoding(encoding)) { throw new TypeError("Unknown encoding: " + encoding); } if (val.length === 1) { - const code = val.charCodeAt(0); + const code = StringPrototypeCharCodeAt(val, 0); if (encoding === "utf8" && code < 128 || encoding === "latin1") { val = code; } @@ -1658,7 +1750,7 @@ Buffer.prototype.fill = function fill(val, start, end, encoding) { this[i] = val; } } else { - const bytes = Buffer.isBuffer(val) ? val : Buffer.from(val, encoding); + const bytes = BufferIsBuffer(val) ? val : BufferFrom(val, encoding); const len = bytes.length; if (len === 0) { throw new codes.ERR_INVALID_ARG_VALUE( @@ -1685,7 +1777,7 @@ function checkIntBI(value, min, max, buf, offset, byteLength2) { const n = typeof min === "bigint" ? "n" : ""; let range; if (byteLength2 > 3) { - if (min === 0 || min === BigInt(0)) { + if (min === 0 || min === 0n) { range = `>= 0${n} and < 2${n} ** ${(byteLength2 + 1) * 8}${n}`; } else { range = `>= -(2${n} ** ${(byteLength2 + 1) * 8 - 1}${n}) and < 2 ** ${ @@ -1710,7 +1802,7 @@ function checkIntBI(value, min, max, buf, offset, byteLength2) { function blitBuffer(src, dst, offset, byteLength = Infinity) { const srcLength = src.length; // Establish the number of bytes to be written - const bytesToWrite = Math.min( + const bytesToWrite = MathMin( // If byte length is defined in the call, then it sets an upper bound, // otherwise it is Infinity and is never chosen. byteLength, @@ -1730,15 +1822,9 @@ function blitBuffer(src, dst, offset, byteLength = Infinity) { return bytesToWrite; } -function isInstance(obj, type) { - return obj instanceof type || - obj != null && obj.constructor != null && - obj.constructor.name != null && obj.constructor.name === type.name; -} - const hexSliceLookupTable = function () { const alphabet = "0123456789abcdef"; - const table = new Array(256); + const table = []; for (let i = 0; i < 16; ++i) { const i16 = i * 16; for (let j = 0; j < 16; ++j) { @@ -1748,14 +1834,6 @@ const hexSliceLookupTable = function () { return table; }(); -function defineBigIntMethod(fn) { - return typeof BigInt === "undefined" ? BufferBigIntNotDefined : fn; -} - -function BufferBigIntNotDefined() { - throw new Error("BigInt not supported"); -} - export function readUInt48LE(buf, offset = 0) { validateNumber(offset, "offset"); const first = buf[offset]; @@ -2079,10 +2157,10 @@ export function byteLengthUtf8(str) { function base64ByteLength(str, bytes) { // Handle padding - if (str.charCodeAt(bytes - 1) === 0x3D) { + if (StringPrototypeCharCodeAt(str, bytes - 1) === 0x3D) { bytes--; } - if (bytes > 1 && str.charCodeAt(bytes - 1) === 0x3D) { + if (bytes > 1 && StringPrototypeCharCodeAt(str, bytes - 1) === 0x3D) { bytes--; } @@ -2090,7 +2168,7 @@ function base64ByteLength(str, bytes) { return (bytes * 3) >>> 2; } -export const encodingsMap = Object.create(null); +export const encodingsMap = ObjectCreate(null); for (let i = 0; i < encodings.length; ++i) { encodingsMap[encodings[i]] = i; } @@ -2220,7 +2298,7 @@ export const encodingOps = { }; export function getEncodingOps(encoding) { - encoding = String(encoding).toLowerCase(); + encoding = StringPrototypeToLowerCase(String(encoding)); switch (encoding.length) { case 4: if (encoding === "utf8") return encodingOps.utf8; @@ -2260,6 +2338,14 @@ export function getEncodingOps(encoding) { } } +/** + * @param {Buffer} source + * @param {Buffer} target + * @param {number} targetStart + * @param {number} sourceStart + * @param {number} sourceEnd + * @returns {number} + */ export function _copyActual( source, target, @@ -2278,6 +2364,7 @@ export function _copyActual( } if (sourceStart !== 0 || sourceEnd < source.length) { + // deno-lint-ignore prefer-primordials source = new Uint8Array(source.buffer, source.byteOffset + sourceStart, nb); } @@ -2287,7 +2374,7 @@ export function _copyActual( } export function boundsError(value, length, type) { - if (Math.floor(value) !== value) { + if (MathFloor(value) !== value) { validateNumber(value, type); throw new codes.ERR_OUT_OF_RANGE(type || "offset", "an integer", value); } @@ -2310,7 +2397,7 @@ export function validateNumber(value, name, min = undefined, max) { if ( (min != null && value < min) || (max != null && value > max) || - ((min != null || max != null) && Number.isNaN(value)) + ((min != null || max != null) && NumberIsNaN(value)) ) { throw new codes.ERR_OUT_OF_RANGE( name, @@ -2344,11 +2431,11 @@ function checkInt(value, min, max, buf, offset, byteLength) { export function toInteger(n, defaultVal) { n = +n; if ( - !Number.isNaN(n) && - n >= Number.MIN_SAFE_INTEGER && - n <= Number.MAX_SAFE_INTEGER + !NumberIsNaN(n) && + n >= NumberMIN_SAFE_INTEGER && + n <= NumberMAX_SAFE_INTEGER ) { - return ((n % 1) === 0 ? n : Math.floor(n)); + return ((n % 1) === 0 ? n : MathFloor(n)); } return defaultVal; } @@ -2421,7 +2508,7 @@ export function writeU_Int48BE(buf, value, offset, min, max) { value = +value; checkInt(value, min, max, buf, offset, 5); - const newVal = Math.floor(value * 2 ** -32); + const newVal = MathFloor(value * 2 ** -32); buf[offset++] = newVal >>> 8; buf[offset++] = newVal; buf[offset + 3] = value; @@ -2439,7 +2526,7 @@ export function writeU_Int40BE(buf, value, offset, min, max) { value = +value; checkInt(value, min, max, buf, offset, 4); - buf[offset++] = Math.floor(value * 2 ** -32); + buf[offset++] = MathFloor(value * 2 ** -32); buf[offset + 3] = value; value = value >>> 8; buf[offset + 2] = value; @@ -2482,12 +2569,12 @@ export function validateOffset( value, name, min = 0, - max = Number.MAX_SAFE_INTEGER, + max = NumberMAX_SAFE_INTEGER, ) { if (typeof value !== "number") { throw new codes.ERR_INVALID_ARG_TYPE(name, "number", value); } - if (!Number.isInteger(value)) { + if (!NumberIsInteger(value)) { throw new codes.ERR_OUT_OF_RANGE(name, "an integer", value); } if (value < min || value > max) { @@ -2500,7 +2587,7 @@ export function writeU_Int48LE(buf, value, offset, min, max) { value = +value; checkInt(value, min, max, buf, offset, 5); - const newVal = Math.floor(value * 2 ** -32); + const newVal = MathFloor(value * 2 ** -32); buf[offset++] = value; value = value >>> 8; buf[offset++] = value; @@ -2526,7 +2613,7 @@ export function writeU_Int40LE(buf, value, offset, min, max) { buf[offset++] = value; value = value >>> 8; buf[offset++] = value; - buf[offset++] = Math.floor(newVal * 2 ** -32); + buf[offset++] = MathFloor(newVal * 2 ** -32); return offset; } @@ -2560,14 +2647,14 @@ export function writeU_Int24LE(buf, value, offset, min, max) { export function isUtf8(input) { if (isTypedArray(input)) { - if (input.buffer.detached) { + if (isDetachedBuffer(TypedArrayPrototypeGetBuffer(input))) { throw new ERR_INVALID_STATE("Cannot validate on a detached buffer"); } return op_is_utf8(input); } if (isAnyArrayBuffer(input)) { - if (input.detached) { + if (isDetachedBuffer(input)) { throw new ERR_INVALID_STATE("Cannot validate on a detached buffer"); } return op_is_utf8(new Uint8Array(input)); @@ -2582,14 +2669,14 @@ export function isUtf8(input) { export function isAscii(input) { if (isTypedArray(input)) { - if (input.buffer.detached) { + if (isDetachedBuffer(TypedArrayPrototypeGetBuffer(input))) { throw new ERR_INVALID_STATE("Cannot validate on a detached buffer"); } return op_is_ascii(input); } if (isAnyArrayBuffer(input)) { - if (input.detached) { + if (isDetachedBuffer(input)) { throw new ERR_INVALID_STATE("Cannot validate on a detached buffer"); } return op_is_ascii(new Uint8Array(input)); @@ -2636,7 +2723,7 @@ export function transcode(source, fromEnco, toEnco) { const result = op_transcode(new Uint8Array(source), fromEnco, toEnco); return Buffer.from(result, toEnco); } catch (err) { - if (err.message.includes("Unable to transcode Buffer")) { + if (StringPrototypeIncludes(err.message, "Unable to transcode Buffer")) { throw illegalArgumentError; } else { throw err; diff --git a/ext/node/polyfills/internal/child_process.ts b/ext/node/polyfills/internal/child_process.ts index 6f209b719..cfff1079f 100644 --- a/ext/node/polyfills/internal/child_process.ts +++ b/ext/node/polyfills/internal/child_process.ts @@ -1191,8 +1191,12 @@ function toDenoArgs(args: string[]): string[] { } if (flagInfo === undefined) { - // Not a known flag that expects a value. Just copy it to the output. - denoArgs.push(arg); + if (arg === "--no-warnings") { + denoArgs.push("--quiet"); + } else { + // Not a known flag that expects a value. Just copy it to the output. + denoArgs.push(arg); + } continue; } @@ -1335,7 +1339,7 @@ export function setupChannel(target: any, ipc: number) { } } - process.nextTick(handleMessage, msg); + nextTick(handleMessage, msg); } } catch (err) { if ( @@ -1396,7 +1400,7 @@ export function setupChannel(target: any, ipc: number) { if (!target.connected) { const err = new ERR_IPC_CHANNEL_CLOSED(); if (typeof callback === "function") { - process.nextTick(callback, err); + nextTick(callback, err); } else { nextTick(() => target.emit("error", err)); } @@ -1412,7 +1416,18 @@ export function setupChannel(target: any, ipc: number) { .then(() => { control.unrefCounted(); if (callback) { - process.nextTick(callback, null); + nextTick(callback, null); + } + }, (err: Error) => { + control.unrefCounted(); + if (err instanceof Deno.errors.Interrupted) { + // Channel closed on us mid-write. + } else { + if (typeof callback === "function") { + nextTick(callback, err); + } else { + nextTick(() => target.emit("error", err)); + } } }); return queueOk[0]; @@ -1429,7 +1444,7 @@ export function setupChannel(target: any, ipc: number) { target.connected = false; target[kCanDisconnect] = false; control[kControlDisconnect](); - process.nextTick(() => { + nextTick(() => { target.channel = null; core.close(ipc); target.emit("disconnect"); diff --git a/ext/node/polyfills/internal/crypto/_randomInt.ts b/ext/node/polyfills/internal/crypto/_randomInt.ts index 7f4d703ad..e08b3e963 100644 --- a/ext/node/polyfills/internal/crypto/_randomInt.ts +++ b/ext/node/polyfills/internal/crypto/_randomInt.ts @@ -1,9 +1,15 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -// TODO(petamoriken): enable prefer-primordials for node polyfills -// deno-lint-ignore-file prefer-primordials - import { op_node_random_int } from "ext:core/ops"; +import { primordials } from "ext:core/mod.js"; +const { + Error, + MathCeil, + MathFloor, + MathPow, + NumberIsSafeInteger, + RangeError, +} = primordials; export default function randomInt(max: number): number; export default function randomInt(min: number, max: number): number; @@ -23,7 +29,9 @@ export default function randomInt( cb?: (err: Error | null, n?: number) => void, ): number | void { if (typeof max === "number" && typeof min === "number") { - [max, min] = [min, max]; + const temp = max; + max = min; + min = temp; } if (min === undefined) min = 0; else if (typeof min === "function") { @@ -32,13 +40,13 @@ export default function randomInt( } if ( - !Number.isSafeInteger(min) || - typeof max === "number" && !Number.isSafeInteger(max) + !NumberIsSafeInteger(min) || + typeof max === "number" && !NumberIsSafeInteger(max) ) { throw new Error("max or min is not a Safe Number"); } - if (max - min > Math.pow(2, 48)) { + if (max - min > MathPow(2, 48)) { throw new RangeError("max - min should be less than 2^48!"); } @@ -46,8 +54,8 @@ export default function randomInt( throw new Error("Min is bigger than Max!"); } - min = Math.ceil(min); - max = Math.floor(max); + min = MathCeil(min); + max = MathFloor(max); const result = op_node_random_int(min, max); if (cb) { diff --git a/ext/node/polyfills/internal/crypto/keygen.ts b/ext/node/polyfills/internal/crypto/keygen.ts index a40c76c0d..b023ab106 100644 --- a/ext/node/polyfills/internal/crypto/keygen.ts +++ b/ext/node/polyfills/internal/crypto/keygen.ts @@ -29,6 +29,8 @@ import { } from "ext:deno_node/internal/validators.mjs"; import { Buffer } from "node:buffer"; import { KeyFormat, KeyType } from "ext:deno_node/internal/crypto/types.ts"; +import process from "node:process"; +import { promisify } from "node:util"; import { op_node_generate_dh_group_key, @@ -569,7 +571,15 @@ export function generateKeyPair( privateKey: any, ) => void, ) { - createJob(kAsync, type, options).then((pair) => { + _generateKeyPair(type, options) + .then( + (res) => callback(null, res.publicKey, res.privateKey), + (err) => callback(err, null, null), + ); +} + +function _generateKeyPair(type: string, options: unknown) { + return createJob(kAsync, type, options).then((pair) => { const privateKeyHandle = op_node_get_private_key_from_pair(pair); const publicKeyHandle = op_node_get_public_key_from_pair(pair); @@ -588,12 +598,15 @@ export function generateKeyPair( } } - callback(null, publicKey, privateKey); - }).catch((err) => { - callback(err, null, null); + return { publicKey, privateKey }; }); } +Object.defineProperty(generateKeyPair, promisify.custom, { + enumerable: false, + value: _generateKeyPair, +}); + export interface KeyPairKeyObjectResult { publicKey: KeyObject; privateKey: KeyObject; diff --git a/ext/node/polyfills/internal/crypto/random.ts b/ext/node/polyfills/internal/crypto/random.ts index 4219414dc..a41b86819 100644 --- a/ext/node/polyfills/internal/crypto/random.ts +++ b/ext/node/polyfills/internal/crypto/random.ts @@ -38,6 +38,7 @@ import { ERR_INVALID_ARG_TYPE, ERR_OUT_OF_RANGE, } from "ext:deno_node/internal/errors.ts"; +import { Buffer } from "node:buffer"; export { default as randomBytes } from "ext:deno_node/internal/crypto/_randomBytes.ts"; export { diff --git a/ext/node/polyfills/internal/errors.ts b/ext/node/polyfills/internal/errors.ts index 51bd7a025..61b53fa96 100644 --- a/ext/node/polyfills/internal/errors.ts +++ b/ext/node/polyfills/internal/errors.ts @@ -18,7 +18,7 @@ */ import { primordials } from "ext:core/mod.js"; -const { JSONStringify, SymbolFor } = primordials; +const { JSONStringify, SafeArrayIterator, SymbolFor } = primordials; import { format, inspect } from "ext:deno_node/internal/util/inspect.mjs"; import { codes } from "ext:deno_node/internal/error_codes.ts"; import { @@ -1874,6 +1874,11 @@ export class ERR_SOCKET_CLOSED extends NodeError { super("ERR_SOCKET_CLOSED", `Socket is closed`); } } +export class ERR_SOCKET_CONNECTION_TIMEOUT extends NodeError { + constructor() { + super("ERR_SOCKET_CONNECTION_TIMEOUT", `Socket connection timeout`); + } +} export class ERR_SOCKET_DGRAM_IS_CONNECTED extends NodeError { constructor() { super("ERR_SOCKET_DGRAM_IS_CONNECTED", `Already connected`); @@ -2385,6 +2390,15 @@ export class ERR_INVALID_RETURN_VALUE extends NodeTypeError { } } +export class ERR_NOT_IMPLEMENTED extends NodeError { + constructor(message?: string) { + super( + "ERR_NOT_IMPLEMENTED", + message ? `Not implemented: ${message}` : "Not implemented", + ); + } +} + export class ERR_INVALID_URL extends NodeTypeError { input: string; constructor(input: string) { @@ -2558,19 +2572,6 @@ export class ERR_FS_RMDIR_ENOTDIR extends NodeSystemError { } } -export class ERR_OS_NO_HOMEDIR extends NodeSystemError { - constructor() { - const code = isWindows ? "ENOENT" : "ENOTDIR"; - const ctx: NodeSystemErrorCtx = { - message: "not a directory", - syscall: "home", - code, - errno: isWindows ? osConstants.errno.ENOENT : osConstants.errno.ENOTDIR, - }; - super(code, ctx, "Path is not a directory"); - } -} - export class ERR_HTTP_SOCKET_ASSIGNED extends NodeError { constructor() { super( @@ -2646,11 +2647,30 @@ export function aggregateTwoErrors( } return innerError || outerError; } + +export class NodeAggregateError extends AggregateError { + code: string; + constructor(errors, message) { + super(new SafeArrayIterator(errors), message); + this.code = errors[0]?.code; + } + + get [kIsNodeError]() { + return true; + } + + // deno-lint-ignore adjacent-overload-signatures + get ["constructor"]() { + return AggregateError; + } +} + codes.ERR_IPC_CHANNEL_CLOSED = ERR_IPC_CHANNEL_CLOSED; codes.ERR_INVALID_ARG_TYPE = ERR_INVALID_ARG_TYPE; codes.ERR_INVALID_ARG_VALUE = ERR_INVALID_ARG_VALUE; codes.ERR_OUT_OF_RANGE = ERR_OUT_OF_RANGE; codes.ERR_SOCKET_BAD_PORT = ERR_SOCKET_BAD_PORT; +codes.ERR_SOCKET_CONNECTION_TIMEOUT = ERR_SOCKET_CONNECTION_TIMEOUT; codes.ERR_BUFFER_OUT_OF_BOUNDS = ERR_BUFFER_OUT_OF_BOUNDS; codes.ERR_UNKNOWN_ENCODING = ERR_UNKNOWN_ENCODING; codes.ERR_PARSE_ARGS_INVALID_OPTION_VALUE = ERR_PARSE_ARGS_INVALID_OPTION_VALUE; @@ -2851,6 +2871,7 @@ export default { ERR_INVALID_SYNC_FORK_INPUT, ERR_INVALID_THIS, ERR_INVALID_TUPLE, + ERR_NOT_IMPLEMENTED, ERR_INVALID_URI, ERR_INVALID_URL, ERR_INVALID_URL_SCHEME, diff --git a/ext/node/polyfills/internal/net.ts b/ext/node/polyfills/internal/net.ts index 144612626..a3dcb3ed2 100644 --- a/ext/node/polyfills/internal/net.ts +++ b/ext/node/polyfills/internal/net.ts @@ -95,4 +95,5 @@ export function makeSyncWrite(fd: number) { }; } +export const kReinitializeHandle = Symbol("kReinitializeHandle"); export const normalizedArgsSymbol = Symbol("normalizedArgs"); diff --git a/ext/node/polyfills/internal/util/inspect.mjs b/ext/node/polyfills/internal/util/inspect.mjs index 3a61c387c..ae797449b 100644 --- a/ext/node/polyfills/internal/util/inspect.mjs +++ b/ext/node/polyfills/internal/util/inspect.mjs @@ -565,6 +565,19 @@ export function stripVTControlCharacters(str) { export function styleText(format, text) { validateString(text, "text"); + + if (Array.isArray(format)) { + for (let i = 0; i < format.length; i++) { + const item = format[i]; + const formatCodes = inspect.colors[item]; + if (formatCodes == null) { + validateOneOf(item, "format", Object.keys(inspect.colors)); + } + text = `\u001b[${formatCodes[0]}m${text}\u001b[${formatCodes[1]}m`; + } + return text; + } + const formatCodes = inspect.colors[format]; if (formatCodes == null) { validateOneOf(format, "format", Object.keys(inspect.colors)); diff --git a/ext/node/polyfills/internal_binding/_timingSafeEqual.ts b/ext/node/polyfills/internal_binding/_timingSafeEqual.ts index ff141fdbf..559b7685b 100644 --- a/ext/node/polyfills/internal_binding/_timingSafeEqual.ts +++ b/ext/node/polyfills/internal_binding/_timingSafeEqual.ts @@ -5,10 +5,11 @@ import { Buffer } from "node:buffer"; -function assert(cond) { - if (!cond) { - throw new Error("assertion failed"); +function toDataView(ab: ArrayBufferLike | ArrayBufferView): DataView { + if (ArrayBuffer.isView(ab)) { + return new DataView(ab.buffer, ab.byteOffset, ab.byteLength); } + return new DataView(ab); } /** Compare to array buffers or data views in a way that timing based attacks @@ -21,13 +22,11 @@ function stdTimingSafeEqual( return false; } if (!(a instanceof DataView)) { - a = new DataView(ArrayBuffer.isView(a) ? a.buffer : a); + a = toDataView(a); } if (!(b instanceof DataView)) { - b = new DataView(ArrayBuffer.isView(b) ? b.buffer : b); + b = toDataView(b); } - assert(a instanceof DataView); - assert(b instanceof DataView); const length = a.byteLength; let out = 0; let i = -1; @@ -41,7 +40,11 @@ export const timingSafeEqual = ( a: Buffer | DataView | ArrayBuffer, b: Buffer | DataView | ArrayBuffer, ): boolean => { - if (a instanceof Buffer) a = new DataView(a.buffer); - if (a instanceof Buffer) b = new DataView(a.buffer); + if (a instanceof Buffer) { + a = new DataView(a.buffer, a.byteOffset, a.byteLength); + } + if (b instanceof Buffer) { + b = new DataView(b.buffer, b.byteOffset, b.byteLength); + } return stdTimingSafeEqual(a, b); }; diff --git a/ext/node/polyfills/internal_binding/http_parser.ts b/ext/node/polyfills/internal_binding/http_parser.ts new file mode 100644 index 000000000..bad10d985 --- /dev/null +++ b/ext/node/polyfills/internal_binding/http_parser.ts @@ -0,0 +1,160 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +import { primordials } from "ext:core/mod.js"; +import { AsyncWrap } from "ext:deno_node/internal_binding/async_wrap.ts"; + +const { + ObjectDefineProperty, + ObjectEntries, + ObjectSetPrototypeOf, + SafeArrayIterator, +} = primordials; + +export const 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", +]; + +export const 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", +]; + +export function HTTPParser() { +} + +ObjectSetPrototypeOf(HTTPParser.prototype, AsyncWrap.prototype); + +function defineProps(obj: object, props: Record<string, unknown>) { + for (const entry of new SafeArrayIterator(ObjectEntries(props))) { + ObjectDefineProperty(obj, entry[0], { + __proto__: null, + value: entry[1], + enumerable: true, + writable: true, + configurable: true, + }); + } +} + +defineProps(HTTPParser, { + REQUEST: 1, + RESPONSE: 2, + kOnMessageBegin: 0, + kOnHeaders: 1, + kOnHeadersComplete: 2, + kOnBody: 3, + kOnMessageComplete: 4, + kOnExecute: 5, + kOnTimeout: 6, + kLenientNone: 0, + kLenientHeaders: 1, + kLenientChunkedLength: 2, + kLenientKeepAlive: 4, + kLenientTransferEncoding: 8, + kLenientVersion: 16, + kLenientDataAfterClose: 32, + kLenientOptionalLFAfterCR: 64, + kLenientOptionalCRLFAfterChunk: 128, + kLenientOptionalCRBeforeLF: 256, + kLenientSpacesAfterChunkSize: 512, + kLenientAll: 1023, +}); diff --git a/ext/node/polyfills/internal_binding/mod.ts b/ext/node/polyfills/internal_binding/mod.ts index f2d7f55bc..ebbfc629f 100644 --- a/ext/node/polyfills/internal_binding/mod.ts +++ b/ext/node/polyfills/internal_binding/mod.ts @@ -17,6 +17,7 @@ import * as types from "ext:deno_node/internal_binding/types.ts"; import * as udpWrap from "ext:deno_node/internal_binding/udp_wrap.ts"; import * as util from "ext:deno_node/internal_binding/util.ts"; import * as uv from "ext:deno_node/internal_binding/uv.ts"; +import * as httpParser from "ext:deno_node/internal_binding/http_parser.ts"; const modules = { "async_wrap": asyncWrap, @@ -32,7 +33,7 @@ const modules = { "fs_dir": {}, "fs_event_wrap": {}, "heap_utils": {}, - "http_parser": {}, + "http_parser": httpParser, icu: {}, inspector: {}, "js_stream": {}, diff --git a/ext/node/polyfills/internal_binding/tcp_wrap.ts b/ext/node/polyfills/internal_binding/tcp_wrap.ts index 973a1d1c0..d9f1c5356 100644 --- a/ext/node/polyfills/internal_binding/tcp_wrap.ts +++ b/ext/node/polyfills/internal_binding/tcp_wrap.ts @@ -299,8 +299,10 @@ export class TCP extends ConnectionWrap { * @param noDelay * @return An error status code. */ - setNoDelay(_noDelay: boolean): number { - // TODO(bnoordhuis) https://github.com/denoland/deno/pull/13103 + setNoDelay(noDelay: boolean): number { + if (this[kStreamBaseField] && "setNoDelay" in this[kStreamBaseField]) { + this[kStreamBaseField].setNoDelay(noDelay); + } return 0; } diff --git a/ext/node/polyfills/internal_binding/uv.ts b/ext/node/polyfills/internal_binding/uv.ts index aa468a0a5..6cd70a7e8 100644 --- a/ext/node/polyfills/internal_binding/uv.ts +++ b/ext/node/polyfills/internal_binding/uv.ts @@ -530,10 +530,12 @@ export function mapSysErrnoToUvErrno(sysErrno: number): number { export const UV_EAI_MEMORY = codeMap.get("EAI_MEMORY")!; export const UV_EBADF = codeMap.get("EBADF")!; +export const UV_ECANCELED = codeMap.get("ECANCELED")!; export const UV_EEXIST = codeMap.get("EEXIST"); export const UV_EINVAL = codeMap.get("EINVAL")!; export const UV_ENOENT = codeMap.get("ENOENT"); export const UV_ENOTSOCK = codeMap.get("ENOTSOCK")!; +export const UV_ETIMEDOUT = codeMap.get("ETIMEDOUT")!; export const UV_UNKNOWN = codeMap.get("UNKNOWN")!; export function errname(errno: number): string { diff --git a/ext/node/polyfills/net.ts b/ext/node/polyfills/net.ts index 48e1d0de8..2b0112519 100644 --- a/ext/node/polyfills/net.ts +++ b/ext/node/polyfills/net.ts @@ -31,6 +31,7 @@ import { isIP, isIPv4, isIPv6, + kReinitializeHandle, normalizedArgsSymbol, } from "ext:deno_node/internal/net.ts"; import { Duplex } from "node:stream"; @@ -50,9 +51,11 @@ import { ERR_SERVER_ALREADY_LISTEN, ERR_SERVER_NOT_RUNNING, ERR_SOCKET_CLOSED, + ERR_SOCKET_CONNECTION_TIMEOUT, errnoException, exceptionWithHostPort, genericNodeError, + NodeAggregateError, uvExceptionWithHostPort, } from "ext:deno_node/internal/errors.ts"; import type { ErrnoException } from "ext:deno_node/internal/errors.ts"; @@ -80,6 +83,7 @@ import { Buffer } from "node:buffer"; import type { LookupOneOptions } from "ext:deno_node/internal/dns/utils.ts"; import { validateAbortSignal, + validateBoolean, validateFunction, validateInt32, validateNumber, @@ -100,13 +104,25 @@ import { ShutdownWrap } from "ext:deno_node/internal_binding/stream_wrap.ts"; import { assert } from "ext:deno_node/_util/asserts.ts"; import { isWindows } from "ext:deno_node/_util/os.ts"; import { ADDRCONFIG, lookup as dnsLookup } from "node:dns"; -import { codeMap } from "ext:deno_node/internal_binding/uv.ts"; +import { + codeMap, + UV_ECANCELED, + UV_ETIMEDOUT, +} from "ext:deno_node/internal_binding/uv.ts"; import { guessHandleType } from "ext:deno_node/internal_binding/util.ts"; import { debuglog } from "ext:deno_node/internal/util/debuglog.ts"; import type { DuplexOptions } from "ext:deno_node/_stream.d.ts"; import type { BufferEncoding } from "ext:deno_node/_global.d.ts"; import type { Abortable } from "ext:deno_node/_events.d.ts"; import { channel } from "node:diagnostics_channel"; +import { primordials } from "ext:core/mod.js"; + +const { + ArrayPrototypeIncludes, + ArrayPrototypePush, + FunctionPrototypeBind, + MathMax, +} = primordials; let debug = debuglog("net", (fn) => { debug = fn; @@ -120,6 +136,9 @@ const kBytesWritten = Symbol("kBytesWritten"); const DEFAULT_IPV4_ADDR = "0.0.0.0"; const DEFAULT_IPV6_ADDR = "::"; +let autoSelectFamilyDefault = true; +let autoSelectFamilyAttemptTimeoutDefault = 250; + type Handle = TCP | Pipe; interface HandleOptions { @@ -214,6 +233,8 @@ interface TcpSocketConnectOptions extends ConnectOptions { hints?: number; family?: number; lookup?: LookupFunction; + autoSelectFamily?: boolean | undefined; + autoSelectFamilyAttemptTimeout?: number | undefined; } interface IpcSocketConnectOptions extends ConnectOptions { @@ -316,12 +337,6 @@ export function _normalizeArgs(args: unknown[]): NormalizedArgs { return arr; } -function _isTCPConnectWrap( - req: TCPConnectWrap | PipeConnectWrap, -): req is TCPConnectWrap { - return "localAddress" in req && "localPort" in req; -} - function _afterConnect( status: number, // deno-lint-ignore no-explicit-any @@ -372,7 +387,7 @@ function _afterConnect( socket.connecting = false; let details; - if (_isTCPConnectWrap(req)) { + if (req.localAddress && req.localPort) { details = req.localAddress + ":" + req.localPort; } @@ -384,7 +399,7 @@ function _afterConnect( details, ); - if (_isTCPConnectWrap(req)) { + if (details) { ex.localAddress = req.localAddress; ex.localPort = req.localPort; } @@ -393,6 +408,107 @@ function _afterConnect( } } +function _createConnectionError(req, status) { + let details; + + if (req.localAddress && req.localPort) { + details = req.localAddress + ":" + req.localPort; + } + + const ex = exceptionWithHostPort( + status, + "connect", + req.address, + req.port, + details, + ); + if (details) { + ex.localAddress = req.localAddress; + ex.localPort = req.localPort; + } + + return ex; +} + +function _afterConnectMultiple( + context, + current, + status, + handle, + req, + readable, + writable, +) { + debug( + "connect/multiple: connection attempt to %s:%s completed with status %s", + req.address, + req.port, + status, + ); + + // Make sure another connection is not spawned + clearTimeout(context[kTimeout]); + + // One of the connection has completed and correctly dispatched but after timeout, ignore this one + if (status === 0 && current !== context.current - 1) { + debug( + "connect/multiple: ignoring successful but timedout connection to %s:%s", + req.address, + req.port, + ); + handle.close(); + return; + } + + const self = context.socket; + + // Some error occurred, add to the list of exceptions + if (status !== 0) { + const ex = _createConnectionError(req, status); + ArrayPrototypePush(context.errors, ex); + + self.emit( + "connectionAttemptFailed", + req.address, + req.port, + req.addressType, + ex, + ); + + // Try the next address, unless we were aborted + if (context.socket.connecting) { + _internalConnectMultiple(context, status === UV_ECANCELED); + } + + return; + } + + _afterConnect(status, self._handle, req, readable, writable); +} + +function _internalConnectMultipleTimeout(context, req, handle) { + debug( + "connect/multiple: connection to %s:%s timed out", + req.address, + req.port, + ); + context.socket.emit( + "connectionAttemptTimeout", + req.address, + req.port, + req.addressType, + ); + + req.oncomplete = undefined; + ArrayPrototypePush(context.errors, _createConnectionError(req, UV_ETIMEDOUT)); + handle.close(); + + // Try the next address, unless we were aborted + if (context.socket.connecting) { + _internalConnectMultiple(context); + } +} + function _checkBindError(err: number, port: number, handle: TCP) { // EADDRINUSE may not be reported until we call `listen()` or `connect()`. // To complicate matters, a failed `bind()` followed by `listen()` or `connect()` @@ -495,6 +611,131 @@ function _internalConnect( } } +function _internalConnectMultiple(context, canceled?: boolean) { + clearTimeout(context[kTimeout]); + const self = context.socket; + + // We were requested to abort. Stop all operations + if (self._aborted) { + return; + } + + // All connections have been tried without success, destroy with error + if (canceled || context.current === context.addresses.length) { + if (context.errors.length === 0) { + self.destroy(new ERR_SOCKET_CONNECTION_TIMEOUT()); + return; + } + + self.destroy(new NodeAggregateError(context.errors)); + return; + } + + assert(self.connecting); + + const current = context.current++; + + if (current > 0) { + self[kReinitializeHandle](new TCP(TCPConstants.SOCKET)); + } + + const { localPort, port, flags } = context; + const { address, family: addressType } = context.addresses[current]; + let localAddress; + let err; + + if (localPort) { + if (addressType === 4) { + localAddress = DEFAULT_IPV4_ADDR; + err = self._handle.bind(localAddress, localPort); + } else { // addressType === 6 + localAddress = DEFAULT_IPV6_ADDR; + err = self._handle.bind6(localAddress, localPort, flags); + } + + debug( + "connect/multiple: binding to localAddress: %s and localPort: %d (addressType: %d)", + localAddress, + localPort, + addressType, + ); + + err = _checkBindError(err, localPort, self._handle); + if (err) { + ArrayPrototypePush( + context.errors, + exceptionWithHostPort(err, "bind", localAddress, localPort), + ); + _internalConnectMultiple(context); + return; + } + } + + debug( + "connect/multiple: attempting to connect to %s:%d (addressType: %d)", + address, + port, + addressType, + ); + self.emit("connectionAttempt", address, port, addressType); + + const req = new TCPConnectWrap(); + req.oncomplete = FunctionPrototypeBind( + _afterConnectMultiple, + undefined, + context, + current, + ); + req.address = address; + req.port = port; + req.localAddress = localAddress; + req.localPort = localPort; + req.addressType = addressType; + + ArrayPrototypePush( + self.autoSelectFamilyAttemptedAddresses, + `${address}:${port}`, + ); + + if (addressType === 4) { + err = self._handle.connect(req, address, port); + } else { + err = self._handle.connect6(req, address, port); + } + + if (err) { + const sockname = self._getsockname(); + let details; + + if (sockname) { + details = sockname.address + ":" + sockname.port; + } + + const ex = exceptionWithHostPort(err, "connect", address, port, details); + ArrayPrototypePush(context.errors, ex); + + self.emit("connectionAttemptFailed", address, port, addressType, ex); + _internalConnectMultiple(context); + return; + } + + if (current < context.addresses.length - 1) { + debug( + "connect/multiple: setting the attempt timeout to %d ms", + context.timeout, + ); + + // If the attempt has not returned an error, start the connection timer + context[kTimeout] = setTimeout( + _internalConnectMultipleTimeout, + context.timeout, + context, + req, + self._handle, + ); + } +} + // Provide a better error message when we call end() as a result // of the other side sending a FIN. The standard "write after end" // is overly vague, and makes it seem like the user's code is to blame. @@ -597,7 +838,7 @@ function _lookupAndConnect( ) { const { localAddress, localPort } = options; const host = options.host || "localhost"; - let { port } = options; + let { port, autoSelectFamilyAttemptTimeout, autoSelectFamily } = options; if (localAddress && !isIP(localAddress)) { throw new ERR_INVALID_IP_ADDRESS(localAddress); @@ -621,6 +862,22 @@ function _lookupAndConnect( port |= 0; + if (autoSelectFamily != null) { + validateBoolean(autoSelectFamily, "options.autoSelectFamily"); + } else { + autoSelectFamily = autoSelectFamilyDefault; + } + + if (autoSelectFamilyAttemptTimeout !== undefined) { + validateInt32(autoSelectFamilyAttemptTimeout); + + if (autoSelectFamilyAttemptTimeout < 10) { + autoSelectFamilyAttemptTimeout = 10; + } + } else { + autoSelectFamilyAttemptTimeout = autoSelectFamilyAttemptTimeoutDefault; + } + // If host is an IP, skip performing a lookup const addressType = isIP(host); if (addressType) { @@ -649,6 +906,7 @@ function _lookupAndConnect( const dnsOpts = { family: options.family, hints: options.hints || 0, + all: false, }; if ( @@ -665,6 +923,31 @@ function _lookupAndConnect( self._host = host; const lookup = options.lookup || dnsLookup; + if ( + dnsOpts.family !== 4 && dnsOpts.family !== 6 && !localAddress && + autoSelectFamily + ) { + debug("connect: autodetecting"); + + dnsOpts.all = true; + defaultTriggerAsyncIdScope(self[asyncIdSymbol], function () { + _lookupAndConnectMultiple( + self, + asyncIdSymbol, + lookup, + host, + options, + dnsOpts, + port, + localAddress, + localPort, + autoSelectFamilyAttemptTimeout, + ); + }); + + return; + } + defaultTriggerAsyncIdScope(self[asyncIdSymbol], function () { lookup( host, @@ -719,6 +1002,143 @@ function _lookupAndConnect( }); } +function _lookupAndConnectMultiple( + self: Socket, + asyncIdSymbol: number, + // deno-lint-ignore no-explicit-any + lookup: any, + host: string, + options: TcpSocketConnectOptions, + dnsopts, + port: number, + localAddress: string, + localPort: number, + timeout: number | undefined, +) { + defaultTriggerAsyncIdScope(self[asyncIdSymbol], function emitLookup() { + lookup(host, dnsopts, function emitLookup(err, addresses) { + // It's possible we were destroyed while looking this up. + // XXX it would be great if we could cancel the promise returned by + // the look up. + if (!self.connecting) { + return; + } else if (err) { + self.emit("lookup", err, undefined, undefined, host); + + // net.createConnection() creates a net.Socket object and immediately + // calls net.Socket.connect() on it (that's us). There are no event + // listeners registered yet so defer the error event to the next tick. + nextTick(_connectErrorNT, self, err); + return; + } + + // Filter addresses by only keeping the one which are either IPv4 or IPV6. + // The first valid address determines which group has preference on the + // alternate family sorting which happens later. + const validAddresses = [[], []]; + const validIps = [[], []]; + let destinations; + for (let i = 0, l = addresses.length; i < l; i++) { + const address = addresses[i]; + const { address: ip, family: addressType } = address; + self.emit("lookup", err, ip, addressType, host); + // It's possible we were destroyed while looking this up. + if (!self.connecting) { + return; + } + if (isIP(ip) && (addressType === 4 || addressType === 6)) { + destinations ||= addressType === 6 ? { 6: 0, 4: 1 } : { 4: 0, 6: 1 }; + + const destination = destinations[addressType]; + + // Only try an address once + if (!ArrayPrototypeIncludes(validIps[destination], ip)) { + ArrayPrototypePush(validAddresses[destination], address); + ArrayPrototypePush(validIps[destination], ip); + } + } + } + + // When no AAAA or A records are available, fail on the first one + if (!validAddresses[0].length && !validAddresses[1].length) { + const { address: firstIp, family: firstAddressType } = addresses[0]; + + if (!isIP(firstIp)) { + err = new ERR_INVALID_IP_ADDRESS(firstIp); + nextTick(_connectErrorNT, self, err); + } else if (firstAddressType !== 4 && firstAddressType !== 6) { + err = new ERR_INVALID_ADDRESS_FAMILY( + firstAddressType, + options.host, + options.port, + ); + nextTick(_connectErrorNT, self, err); + } + + return; + } + + // Sort addresses alternating families + const toAttempt = []; + for ( + let i = 0, + l = MathMax(validAddresses[0].length, validAddresses[1].length); + i < l; + i++ + ) { + if (i in validAddresses[0]) { + ArrayPrototypePush(toAttempt, validAddresses[0][i]); + } + if (i in validAddresses[1]) { + ArrayPrototypePush(toAttempt, validAddresses[1][i]); + } + } + + if (toAttempt.length === 1) { + debug( + "connect/multiple: only one address found, switching back to single connection", + ); + const { address: ip, family: addressType } = toAttempt[0]; + + self._unrefTimer(); + defaultTriggerAsyncIdScope( + self[asyncIdSymbol], + _internalConnect, + self, + ip, + port, + addressType, + localAddress, + localPort, + ); + + return; + } + + self.autoSelectFamilyAttemptedAddresses = []; + debug("connect/multiple: will try the following addresses", toAttempt); + + const context = { + socket: self, + addresses: toAttempt, + current: 0, + port, + localPort, + timeout, + [kTimeout]: null, + errors: [], + }; + + self._unrefTimer(); + defaultTriggerAsyncIdScope( + self[asyncIdSymbol], + _internalConnectMultiple, + context, + ); + }); + }); +} + function _afterShutdown(this: ShutdownWrap<TCP>) { // deno-lint-ignore no-explicit-any const self: any = this.handle[ownerSymbol]; @@ -777,6 +1197,7 @@ export class Socket extends Duplex { _host: string | null = null; // deno-lint-ignore no-explicit-any _parent: any = null; + autoSelectFamilyAttemptedAddresses: AddressInfo[] | undefined = undefined; constructor(options: SocketOptions | number) { if (typeof options === "number") { @@ -1546,6 +1967,16 @@ export class Socket extends Duplex { set _handle(v: Handle | null) { this[kHandle] = v; } + + // deno-lint-ignore no-explicit-any + [kReinitializeHandle](handle: any) { + this._handle?.close(); + + this._handle = handle; + this._handle[ownerSymbol] = this; + + _initSocketHandle(this); + } } export const Stream = Socket; @@ -1593,6 +2024,33 @@ export function connect(...args: unknown[]) { export const createConnection = connect; +/** https://docs.deno.com/api/node/net/#namespace_getdefaultautoselectfamily */ +export function getDefaultAutoSelectFamily() { + return autoSelectFamilyDefault; +} + +/** https://docs.deno.com/api/node/net/#namespace_setdefaultautoselectfamily */ +export function setDefaultAutoSelectFamily(value: boolean) { + validateBoolean(value, "value"); + autoSelectFamilyDefault = value; +} + +/** https://docs.deno.com/api/node/net/#namespace_getdefaultautoselectfamilyattempttimeout */ +export function getDefaultAutoSelectFamilyAttemptTimeout() { + return autoSelectFamilyAttemptTimeoutDefault; +} + +/** https://docs.deno.com/api/node/net/#namespace_setdefaultautoselectfamilyattempttimeout */ +export function setDefaultAutoSelectFamilyAttemptTimeout(value: number) { + validateInt32(value, "value", 1); + + if (value < 10) { + value = 10; + } + + autoSelectFamilyAttemptTimeoutDefault = value; +} + export interface ListenOptions extends Abortable { fd?: number; port?: number | undefined; @@ -2478,15 +2936,19 @@ export { BlockList, isIP, isIPv4, isIPv6, SocketAddress }; export default { _createServerHandle, _normalizeArgs, - isIP, - isIPv4, - isIPv6, BlockList, - SocketAddress, connect, createConnection, createServer, + getDefaultAutoSelectFamily, + getDefaultAutoSelectFamilyAttemptTimeout, + isIP, + isIPv4, + isIPv6, Server, + setDefaultAutoSelectFamily, + setDefaultAutoSelectFamilyAttemptTimeout, Socket, + SocketAddress, Stream, }; diff --git a/ext/node/polyfills/os.ts b/ext/node/polyfills/os.ts index e47e8679e..edc89ed2c 100644 --- a/ext/node/polyfills/os.ts +++ b/ext/node/polyfills/os.ts @@ -28,16 +28,17 @@ import { op_homedir, op_node_os_get_priority, op_node_os_set_priority, - op_node_os_username, + op_node_os_user_info, } from "ext:core/ops"; import { validateIntegerRange } from "ext:deno_node/_utils.ts"; import process from "node:process"; import { isWindows } from "ext:deno_node/_util/os.ts"; -import { ERR_OS_NO_HOMEDIR } from "ext:deno_node/internal/errors.ts"; import { os } from "ext:deno_node/internal_binding/constants.ts"; import { osUptime } from "ext:runtime/30_os.js"; import { Buffer } from "ext:deno_node/internal/buffer.mjs"; +import { primordials } from "ext:core/mod.js"; +const { StringPrototypeEndsWith, StringPrototypeSlice } = primordials; export const constants = os; @@ -136,6 +137,8 @@ export function arch(): string { (uptime as any)[Symbol.toPrimitive] = (): number => uptime(); // deno-lint-ignore no-explicit-any (machine as any)[Symbol.toPrimitive] = (): string => machine(); +// deno-lint-ignore no-explicit-any +(tmpdir as any)[Symbol.toPrimitive] = (): string | null => tmpdir(); export function cpus(): CPUCoreInfo[] { return op_cpus(); @@ -268,26 +271,27 @@ export function setPriority(pid: number, priority?: number) { export function tmpdir(): string | null { /* This follows the node js implementation, but has a few differences: - * On windows, if none of the environment variables are defined, - we return null. - * On unix we use a plain Deno.env.get, instead of safeGetenv, + * We use a plain Deno.env.get, instead of safeGetenv, which special cases setuid binaries. - * Node removes a single trailing / or \, we remove all. */ if (isWindows) { - const temp = Deno.env.get("TEMP") || Deno.env.get("TMP"); - if (temp) { - return temp.replace(/(?<!:)[/\\]*$/, ""); - } - const base = Deno.env.get("SYSTEMROOT") || Deno.env.get("WINDIR"); - if (base) { - return base + "\\temp"; + let temp = Deno.env.get("TEMP") || Deno.env.get("TMP") || + (Deno.env.get("SystemRoot") || Deno.env.get("windir")) + "\\temp"; + if ( + temp.length > 1 && StringPrototypeEndsWith(temp, "\\") && + !StringPrototypeEndsWith(temp, ":\\") + ) { + temp = StringPrototypeSlice(temp, 0, -1); } - return null; + + return temp; } else { // !isWindows - const temp = Deno.env.get("TMPDIR") || Deno.env.get("TMP") || + let temp = Deno.env.get("TMPDIR") || Deno.env.get("TMP") || Deno.env.get("TEMP") || "/tmp"; - return temp.replace(/(?<!^)\/*$/, ""); + if (temp.length > 1 && StringPrototypeEndsWith(temp, "/")) { + temp = StringPrototypeSlice(temp, 0, -1); + } + return temp; } } @@ -320,7 +324,6 @@ export function uptime(): number { return osUptime(); } -/** Not yet implemented */ export function userInfo( options: UserInfoOptions = { encoding: "utf-8" }, ): UserInfo { @@ -331,20 +334,10 @@ export function userInfo( uid = -1; gid = -1; } - - // TODO(@crowlKats): figure out how to do this correctly: - // The value of homedir returned by os.userInfo() is provided by the operating system. - // This differs from the result of os.homedir(), which queries environment - // variables for the home directory before falling back to the operating system response. - let _homedir = homedir(); - if (!_homedir) { - throw new ERR_OS_NO_HOMEDIR(); - } - let shell = isWindows ? null : (Deno.env.get("SHELL") || null); - let username = op_node_os_username(); + let { username, homedir, shell } = op_node_os_user_info(uid); if (options?.encoding === "buffer") { - _homedir = _homedir ? Buffer.from(_homedir) : _homedir; + homedir = homedir ? Buffer.from(homedir) : homedir; shell = shell ? Buffer.from(shell) : shell; username = Buffer.from(username); } @@ -352,7 +345,7 @@ export function userInfo( return { uid, gid, - homedir: _homedir, + homedir, shell, username, }; diff --git a/ext/node/polyfills/perf_hooks.ts b/ext/node/polyfills/perf_hooks.ts index d92b925b5..ec76b3ce2 100644 --- a/ext/node/polyfills/perf_hooks.ts +++ b/ext/node/polyfills/perf_hooks.ts @@ -8,6 +8,7 @@ import { performance as shimPerformance, PerformanceEntry, } from "ext:deno_web/15_performance.js"; +import { EldHistogram } from "ext:core/ops"; class PerformanceObserver { static supportedEntryTypes: string[] = []; @@ -89,10 +90,11 @@ const performance: ) => shimPerformance.dispatchEvent(...args), }; -const monitorEventLoopDelay = () => - notImplemented( - "monitorEventLoopDelay from performance", - ); +function monitorEventLoopDelay(options = {}) { + const { resolution = 10 } = options; + + return new EldHistogram(resolution); +} export default { performance, diff --git a/ext/node/polyfills/process.ts b/ext/node/polyfills/process.ts index 3dc6ce61a..647376d5c 100644 --- a/ext/node/polyfills/process.ts +++ b/ext/node/polyfills/process.ts @@ -15,7 +15,7 @@ import { import { warnNotImplemented } from "ext:deno_node/_utils.ts"; import { EventEmitter } from "node:events"; -import Module from "node:module"; +import Module, { getBuiltinModule } from "node:module"; import { report } from "ext:deno_node/internal/process/report.ts"; import { validateString } from "ext:deno_node/internal/validators.mjs"; import { @@ -38,7 +38,15 @@ import { versions, } from "ext:deno_node/_process/process.ts"; import { _exiting } from "ext:deno_node/_process/exiting.ts"; -export { _nextTick as nextTick, chdir, cwd, env, version, versions }; +export { + _nextTick as nextTick, + chdir, + cwd, + env, + getBuiltinModule, + version, + versions, +}; import { createWritableStdioStream, initStdin, @@ -520,9 +528,7 @@ Process.prototype.on = function ( } else if ( event !== "SIGBREAK" && event !== "SIGINT" && Deno.build.os === "windows" ) { - // Ignores all signals except SIGBREAK and SIGINT on windows. - // deno-lint-ignore no-console - console.warn(`Ignoring signal "${event}" on Windows`); + // TODO(#26331): Ignores all signals except SIGBREAK and SIGINT on windows. } else { EventEmitter.prototype.on.call(this, event, listener); Deno.addSignalListener(event as Deno.Signal, listener); @@ -730,6 +736,8 @@ Process.prototype.getegid = getegid; /** This method is removed on Windows */ Process.prototype.geteuid = geteuid; +Process.prototype.getBuiltinModule = getBuiltinModule; + // TODO(kt3k): Implement this when we added -e option to node compat mode Process.prototype._eval = undefined; @@ -911,7 +919,7 @@ Object.defineProperty(argv, "1", { if (Deno.mainModule?.startsWith("file:")) { return pathFromURL(new URL(Deno.mainModule)); } else { - return join(Deno.cwd(), "$deno$node.js"); + return join(Deno.cwd(), "$deno$node.mjs"); } }, }); diff --git a/ext/node/polyfills/timers.ts b/ext/node/polyfills/timers.ts index 02f69466e..e826416ed 100644 --- a/ext/node/polyfills/timers.ts +++ b/ext/node/polyfills/timers.ts @@ -15,10 +15,16 @@ import { setUnrefTimeout, Timeout, } from "ext:deno_node/internal/timers.mjs"; -import { validateFunction } from "ext:deno_node/internal/validators.mjs"; +import { + validateAbortSignal, + validateBoolean, + validateFunction, + validateObject, +} from "ext:deno_node/internal/validators.mjs"; import { promisify } from "ext:deno_node/internal/util.mjs"; export { setUnrefTimeout } from "ext:deno_node/internal/timers.mjs"; import * as timers from "ext:deno_web/02_timers.js"; +import { AbortError } from "ext:deno_node/internal/errors.ts"; const clearTimeout_ = timers.clearTimeout; const clearInterval_ = timers.clearInterval; @@ -89,10 +95,88 @@ export function clearImmediate(immediate: Immediate) { clearTimeout_(immediate._immediateId); } +async function* setIntervalAsync( + after: number, + value: number, + options: { signal?: AbortSignal; ref?: boolean } = { __proto__: null }, +) { + validateObject(options, "options"); + + if (typeof options?.signal !== "undefined") { + validateAbortSignal(options.signal, "options.signal"); + } + + if (typeof options?.ref !== "undefined") { + validateBoolean(options.ref, "options.ref"); + } + + const { signal, ref = true } = options; + + if (signal?.aborted) { + throw new AbortError(undefined, { cause: signal?.reason }); + } + + let onCancel: (() => void) | undefined = undefined; + let interval: Timeout | undefined = undefined; + try { + let notYielded = 0; + let callback: ((value?: object) => void) | undefined = undefined; + let rejectCallback: ((message?: string) => void) | undefined = undefined; + interval = new Timeout( + () => { + notYielded++; + if (callback) { + callback(); + callback = undefined; + rejectCallback = undefined; + } + }, + after, + [], + true, + ref, + ); + if (signal) { + onCancel = () => { + clearInterval(interval); + if (rejectCallback) { + rejectCallback(signal.reason); + callback = undefined; + rejectCallback = undefined; + } + }; + signal.addEventListener("abort", onCancel, { once: true }); + } + while (!signal?.aborted) { + if (notYielded === 0) { + await new Promise((resolve: () => void, reject: () => void) => { + callback = resolve; + rejectCallback = reject; + }); + } + for (; notYielded > 0; notYielded--) { + yield value; + } + } + } catch (error) { + if (signal?.aborted) { + throw new AbortError(undefined, { cause: signal?.reason }); + } + throw error; + } finally { + if (interval) { + clearInterval(interval); + } + if (onCancel) { + signal?.removeEventListener("abort", onCancel); + } + } +} + export const promises = { setTimeout: promisify(setTimeout), setImmediate: promisify(setImmediate), - setInterval: promisify(setInterval), + setInterval: setIntervalAsync, }; promises.scheduler = { diff --git a/ext/node/polyfills/vm.js b/ext/node/polyfills/vm.js index 183ddad2f..b64c847c5 100644 --- a/ext/node/polyfills/vm.js +++ b/ext/node/polyfills/vm.js @@ -182,6 +182,7 @@ function getContextOptions(options) { let defaultContextNameIndex = 1; export function createContext( + // deno-lint-ignore prefer-primordials contextObject = {}, options = { __proto__: null }, ) { diff --git a/ext/node/polyfills/zlib.ts b/ext/node/polyfills/zlib.ts index 3fe5f8bbd..6e5d02b5b 100644 --- a/ext/node/polyfills/zlib.ts +++ b/ext/node/polyfills/zlib.ts @@ -40,6 +40,58 @@ import { createBrotliCompress, createBrotliDecompress, } from "ext:deno_node/_brotli.js"; +import { ERR_INVALID_ARG_TYPE } from "ext:deno_node/internal/errors.ts"; +import { validateUint32 } from "ext:deno_node/internal/validators.mjs"; +import { op_zlib_crc32 } from "ext:core/ops"; +import { core, primordials } from "ext:core/mod.js"; +import { TextEncoder } from "ext:deno_web/08_text_encoding.js"; +const { + Uint8Array, + TypedArrayPrototypeGetBuffer, + TypedArrayPrototypeGetByteLength, + TypedArrayPrototypeGetByteOffset, + DataViewPrototypeGetBuffer, + DataViewPrototypeGetByteLength, + DataViewPrototypeGetByteOffset, +} = primordials; +const { isTypedArray, isDataView } = core; + +const enc = new TextEncoder(); +const toU8 = (input) => { + if (typeof input === "string") { + return enc.encode(input); + } + + if (isTypedArray(input)) { + return new Uint8Array( + TypedArrayPrototypeGetBuffer(input), + TypedArrayPrototypeGetByteOffset(input), + TypedArrayPrototypeGetByteLength(input), + ); + } else if (isDataView(input)) { + return new Uint8Array( + DataViewPrototypeGetBuffer(input), + DataViewPrototypeGetByteOffset(input), + DataViewPrototypeGetByteLength(input), + ); + } + + return input; +}; + +export function crc32(data, value = 0) { + if (typeof data !== "string" && !isArrayBufferView(data)) { + throw new ERR_INVALID_ARG_TYPE("data", [ + "Buffer", + "TypedArray", + "DataView", + "string", + ], data); + } + validateUint32(value, "value"); + + return op_zlib_crc32(toU8(data), value); +} export class Options { constructor() { @@ -87,6 +139,7 @@ export default { BrotliOptions, codes, constants, + crc32, createBrotliCompress, createBrotliDecompress, createDeflate, |